-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmintNft.ts
245 lines (213 loc) · 7.57 KB
/
mintNft.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";
import {
Connection,
Keypair,
PublicKey,
SystemProgram,
Transaction,
clusterApiUrl,
} from "@solana/web3.js";
import {
MINT_SIZE,
TOKEN_PROGRAM_ID,
createAssociatedTokenAccountInstruction,
createInitializeMint2Instruction,
createMintToInstruction,
createSetAuthorityInstruction,
getAssociatedTokenAddressSync,
getMinimumBalanceForRentExemptMint,
} from "@solana/spl-token";
import {
PROGRAM_ID as METADATA_PROGRAM_ID,
createCreateMetadataAccountV3Instruction,
} from "@metaplex-foundation/mpl-token-metadata";
import { getRandomUri } from "@/utils/utils";
type GetResponse = {
label: string;
icon: string;
};
type PostRequest = {
account: string;
};
type PostResponse = {
transaction: string;
message: string;
};
type PostError = {
error: string;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<GetResponse | PostResponse | PostError>
) {
if (req.method === "GET") {
return get(res);
} else if (req.method === "POST") {
return await post(req, res);
} else {
return res.status(405).json({ error: "Method not allowed" });
}
}
function get(res: NextApiResponse<GetResponse>) {
// GET Response
res.status(200).json({
label: "NFT Minter",
icon: "https://raw.githubusercontent.com/ZYJLiu/opos-asset/main/assets/OPOS_Social_Square.png",
});
}
async function post(
req: NextApiRequest,
res: NextApiResponse<PostResponse | PostError>
) {
// Address of the user's wallet
const { account } = req.body as PostRequest;
// Address generated by the frontend
// The "reference" address is used by the frontend to find the transaction once sent
const { reference } = req.query;
if (!account || !reference) {
res.status(400).json({
error: "Required data missing. Account or reference not provided.",
});
return;
}
try {
// Helper function to build the serialized transaction
const transaction = await buildTransaction(
new PublicKey(account),
new PublicKey(reference)
);
// POST Response
res.status(200).json({
transaction,
message: "Confirm to Mint NFT",
});
} catch (error) {
console.error(error);
res.status(500).json({ error: "Error creating transaction" });
return;
}
}
// Helper function to build the transaction
async function buildTransaction(account: PublicKey, reference: PublicKey) {
// Connect to devnet cluster
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
// Generate keypair to use as address of token account
const mintKeypair = Keypair.generate();
// Calculate minimum lamports for space required by mint account
const lamportsForMintAccount = await getMinimumBalanceForRentExemptMint(
connection
);
// 1) Instruction to invoke System Program to create new account with space for new mint account
const createMintAccountInstruction = SystemProgram.createAccount({
fromPubkey: account, // payer
newAccountPubkey: mintKeypair.publicKey, // mint account address
space: MINT_SIZE, // space (in bytes) required by mint account
lamports: lamportsForMintAccount, // lamports to transfer to mint account
programId: TOKEN_PROGRAM_ID, // new program owner
});
// 2) Instruction to invoke Token Program to initialize mint account
const initializeMintInstruction = createInitializeMint2Instruction(
mintKeypair.publicKey, // mint address
0, // decimals of mint (0 for NFTs)
account, // mint authority
null // freeze authority
);
// Derive associated token account address from mint address and token account owner
// This address is a PDA (Program Derived Address) and is generated deterministically
const associatedTokenAccountAddress = getAssociatedTokenAddressSync(
mintKeypair.publicKey, // mint address
account // token account owner
);
// 3) Instruction to invoke Associated Token Program to create associated token account
// The Associated Token Program invokes the Token Program to create the token account with a PDA as the address of the token account
const createTokenAccountInstruction = createAssociatedTokenAccountInstruction(
account, // payer
associatedTokenAccountAddress, // associated token account address
account, // owner
mintKeypair.publicKey // mint address
);
// 4) Instruction to invoke Token Program to mint 1 token to associated token account
const mintTokenInstruction = createMintToInstruction(
mintKeypair.publicKey, // mint address
associatedTokenAccountAddress, // destination
account, // mint authority
1 // amount
);
// Derive the Metadata account address
const [metadataAccountAddress] = PublicKey.findProgramAddressSync(
[
Buffer.from("metadata"), // hard-coded string "metadata"
METADATA_PROGRAM_ID.toBuffer(), // metadata program address
mintKeypair.publicKey.toBuffer(), // mint address
],
METADATA_PROGRAM_ID // metadata program address
);
// 5) Instruction invoke Metaplex Token Metadata Program to create the Metadata account
const createMetadataInstruction = createCreateMetadataAccountV3Instruction(
{
metadata: metadataAccountAddress, // metadata account address
mint: mintKeypair.publicKey, // mint address
mintAuthority: account, // authority to mint tokens
payer: account, // payer
updateAuthority: account, // authority to update metadata account
},
{
createMetadataAccountArgsV3: {
data: {
creators: null, // creators of the NFT (optional)
name: "OPOS", // on-chain name
symbol: "OPOS", // on-chain symbol
uri: getRandomUri(), // off-chain metadata
sellerFeeBasisPoints: 0, // royalty fee
collection: null, // collection the NFT belongs to (optional)
uses: null, // uses (optional)
},
collectionDetails: null, // collection details (optional)
isMutable: false, // whether the metadata can be changed
},
}
);
// 6) Instruction to invoke Token Program to set mint authority to null
const setAuthorityInstruction = createSetAuthorityInstruction(
mintKeypair.publicKey, // mint address
account, // current authority (mint authority)
0, // authority type (mint authority)
null // new authority
);
// Add the reference to an instruction
// Used by frontend to find the transaction once sent
createMintAccountInstruction.keys.push({
pubkey: reference,
isSigner: false,
isWritable: false,
});
// Get latest blockhash
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();
// create new Transaction and add instruction
const transaction = new Transaction({
feePayer: account, // transaction fee payer
blockhash: blockhash, // recent blockhash
lastValidBlockHeight: lastValidBlockHeight, // last valid block height (when transaction expires)
}).add(
// 1) Create mint account
createMintAccountInstruction,
// 2) Initialize mint account
initializeMintInstruction,
// 3) Create associated token account
createTokenAccountInstruction,
// 4) Mint 1 token to associated token account
mintTokenInstruction,
// 5) Create metadata account
createMetadataInstruction,
// 6) Set mint authority to null
setAuthorityInstruction
);
// Sign the transaction with the mint keypair
transaction.sign(mintKeypair);
// Serialize the transaction
return transaction
.serialize({ requireAllSignatures: false })
.toString("base64");
}