文章目录
要做的事
Solana 支持简单发币,这种情况无法自定义代币名称、图片,也没有拓展字段。
使用合约 TOKEN_2022_PROGRAM 可以直接实现这个功能。
1、水龙头(devnet 环境)
免费领取 solana
https://faucet.solana.com/
- Mainnet - https://api.mainnet-beta.solana.com
- Devnet - https://api.devnet.solana.com
- Testnet - https://api.testnet.solana.com
2、Solana Playground
https://beta.solpg.io/
2.1、选择环境
选择 devnet
3、基于 Solana Playground (pg模式)发行自定义参数的代币
3.1、账户体系
需要注意的是,Solana 的账户体系在发币过程需要三个账号。
你的登录账号(用于付费)-代币账号-关联账号(用于承接发行的额度)
https://solana.com/docs/core/tokens
下面代码选择的是:
3.2、代码
直接粘贴到 https://beta.solpg.io/660cea45cffcf4b13384d012 覆盖即可运行
import * as web3 from "@solana/web3.js";
import * as token from "@solana/spl-token";
import {
Connection,
Keypair,
SystemProgram,
Transaction,
clusterApiUrl,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
ExtensionType,
TOKEN_2022_PROGRAM_ID,
createInitializeMintInstruction,
getMintLen,
createInitializeMetadataPointerInstruction,
getMint,
getMetadataPointerState,
getTokenMetadata,
TYPE_SIZE,
LENGTH_SIZE,
createMintToInstruction,
} from "@solana/spl-token";
import {
createInitializeInstruction,
createUpdateFieldInstruction,
createRemoveKeyInstruction,
pack,
TokenMetadata,
} from "@solana/spl-token-metadata";
// Playground wallet
const payer = pg.wallet.keypair;
// Connection to devnet cluster
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
// Transaction to send
let transaction: Transaction;
// Transaction signature returned from sent transaction
let transactionSignature: string;
// Generate new keypair for Mint Account
const mintKeypair = Keypair.generate();
// Address for Mint Account
const mint = mintKeypair.publicKey;
// Decimals for Mint Account
const decimals = 2;
// Authority that can mint new tokens
const mintAuthority = pg.wallet.publicKey;
// Authority that can update token metadata
const updateAuthority = pg.wallet.publicKey;
// Metadata to store in Mint Account
const metaData: TokenMetadata = {
updateAuthority: updateAuthority,
mint: mint,
name: "TestDemoJJJ",
symbol: "TestDemo JJJ",
//uri: "https://profile-avatar.csdnimg.cn/b521ddb316d54ee0811a45941c53b042_bestcxx.jpg!1",
uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/metadata.json",
additionalMetadata: [
["namea", "aaa"],
["ccc", "ddd"],
],
};
// Size of MetadataExtension 2 bytes for type, 2 bytes for length
const metadataExtension = TYPE_SIZE + LENGTH_SIZE;
// Size of metadata
const metadataLen = pack(metaData).length;
// Size of Mint Account with extension
const mintLen = getMintLen([ExtensionType.MetadataPointer]);
// Minimum lamports required for Mint Account
const lamports = await connection.getMinimumBalanceForRentExemption(
mintLen + metadataExtension + metadataLen
);
// Instruction to invoke System Program to create new account
const createAccountInstruction = SystemProgram.createAccount({
fromPubkey: payer.publicKey, // Account that will transfer lamports to created account
newAccountPubkey: mint, // Address of the account to create
space: mintLen, // Amount of bytes to allocate to the created account
lamports, // Amount of lamports transferred to created account
programId: TOKEN_2022_PROGRAM_ID, // Program assigned as owner of created account
});
// Instruction to initialize the MetadataPointer Extension
const initializeMetadataPointerInstruction =
createInitializeMetadataPointerInstruction(
mint, // Mint Account address
updateAuthority, // Authority that can set the metadata address
mint, // Account address that holds the metadata
TOKEN_2022_PROGRAM_ID
);
// Instruction to initialize Mint Account data
const initializeMintInstruction = createInitializeMintInstruction(
mint, // Mint Account Address
decimals, // Decimals of Mint
mintAuthority, // Designated Mint Authority
null, // Optional Freeze Authority
TOKEN_2022_PROGRAM_ID // Token Extension Program ID
);
// Instruction to initialize Metadata Account data
const initializeMetadataInstruction = createInitializeInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Token Extension Program as Metadata Program
metadata: mint, // Account address that holds the metadata
updateAuthority: updateAuthority, // Authority that can update the metadata
mint: mint, // Mint Account address
mintAuthority: mintAuthority, // Designated Mint Authority
name: metaData.name,
symbol: metaData.symbol,
uri: metaData.uri,
});
// Instruction to update metadata, adding custom field
const updateFieldInstruction = createUpdateFieldInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Token Extension Program as Metadata Program
metadata: mint, // Account address that holds the metadata
updateAuthority: updateAuthority, // Authority that can update the metadata
field: metaData.additionalMetadata[0][0], // key
value: metaData.additionalMetadata[0][1], // value
});
const updateFieldInstruction1 = createUpdateFieldInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Token Extension Program as Metadata Program
metadata: mint, // Account address that holds the metadata
updateAuthority: updateAuthority, // Authority that can update the metadata
field: metaData.additionalMetadata[1][0], // key
value: metaData.additionalMetadata[1][1], // value
});
// Add instructions to new transaction
transaction = new Transaction().add(
createAccountInstruction,
initializeMetadataPointerInstruction,
initializeMintInstruction,
initializeMetadataInstruction,
updateFieldInstruction,
updateFieldInstruction1
);
// Send transaction
transactionSignature = await sendAndConfirmTransaction(
connection,
transaction,
[payer, mintKeypair] // Signers
);
console.log(
"\nCreate Mint Account:",
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
);
// Retrieve mint information
const mintInfo = await getMint(
connection,
mint,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
// Retrieve and log the metadata pointer state
const metadataPointer = getMetadataPointerState(mintInfo);
console.log("\nMetadata Pointer:", JSON.stringify(metadataPointer, null, 2));
// Retrieve and log the metadata state
const metadata = await getTokenMetadata(
connection,
mint // Mint Account address
);
console.log("\nMetadata:", JSON.stringify(metadata, null, 2));
// // Instruction to remove a key from the metadata
// const removeKeyInstruction = createRemoveKeyInstruction({
// programId: TOKEN_2022_PROGRAM_ID, // Token Extension Program as Metadata Program
// metadata: mint, // Address of the metadata
// updateAuthority: updateAuthority, // Authority that can update the metadata
// key: metaData.additionalMetadata[0][0], // Key to remove from the metadata
// idempotent: true, // If the idempotent flag is set to true, then the instruction will not error if the key does not exist
// });
// Add instruction to new transaction
//transaction = new Transaction().add(removeKeyInstruction);
// Send transaction
// transactionSignature = await sendAndConfirmTransaction(
// connection,
// transaction,
// [payer]
// );
// console.log(
// "\nRemove Additional Metadata Field:",
// `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
// );
//Retrieve and log the metadata state
const updatedMetadata = await getTokenMetadata(
connection,
mint // Mint Account address
);
console.log("\nUpdated Metadata:", JSON.stringify(updatedMetadata, null, 2));
console.log(
"\nMint Account:",
`https://solana.fm/address/${mint}?cluster=devnet-solana`
);
//todo-begin
try {
// const transaction = await buildCreateAssociatedTokenAccountTransaction(
// payer.publicKey,
// mint
// );
const associatedTokenAddress = await token.getAssociatedTokenAddress(
mint,
payer.publicKey,
false,
TOKEN_2022_PROGRAM_ID // 使用SPL Token 2022的程序ID
);
console.log(
"build associatedTokenAddress:",
associatedTokenAddress.toBase58()
);
const transaction = new web3.Transaction().add(
token.createAssociatedTokenAccountInstruction(
payer.publicKey,
associatedTokenAddress,
payer.publicKey,
mint,
TOKEN_2022_PROGRAM_ID // 使用SPL Token 2022的程序ID
)
);
// 指定交易的最近区块哈希
transaction.recentBlockhash = (
await connection.getLatestBlockhash()
).blockhash;
// 添加签署者
transaction.feePayer = payer.publicKey;
// 签名交易
transaction.sign(payer);
// 发送并确认交易
const transactionSignature = await connection.sendRawTransaction(
transaction.serialize()
);
await connection.confirmTransaction(transactionSignature);
console.log(
"buildCreateAssociatedTokenAccountTransaction Transaction successful with signature:",
transactionSignature
);
console.log(
"\nbuildCreateAssociatedTokenAccountTransaction:",
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
);
//todo-end
//todo-begin
const instruction = createMintToInstruction(
mint,
associatedTokenAddress, // 改为使用关联代币账户地址
mintAuthority, // mint authority 要确保这已被正确设置为 `mint` 的授权者
1000000000,
[],
TOKEN_2022_PROGRAM_ID
);
var transaction2 = new Transaction().add(instruction);
// 指定交易的最近区块哈希
transaction2.recentBlockhash = (
await connection.getLatestBlockhash()
).blockhash;
// 添加签署者
transaction2.feePayer = payer.publicKey;
// 签名交易
transaction2.sign(payer);
// 发送并确认交易
var transactionSignature2 = await connection.sendRawTransaction(
transaction2.serialize()
);
await connection.confirmTransaction(transactionSignature2);
console.log(
"mintTo Transaction successful with signature:",
transactionSignature2
);
console.log(
"\nmintTo:",
`https://solana.fm/tx/${transactionSignature2}?cluster=devnet-solana`
);
//todo-end
} catch (error) {
console.error("Error creating associated token account:", error);
}
// function
async function buildCreateAssociatedTokenAccountTransaction(
payer: web3.PublicKey,
mint: web3.PublicKey
): Promise<web3.Transaction> {
const associatedTokenAddress = await token.getAssociatedTokenAddress(
mint,
payer,
false,
TOKEN_2022_PROGRAM_ID // 使用SPL Token 2022的程序ID
);
const transaction = new web3.Transaction().add(
token.createAssociatedTokenAccountInstruction(
payer,
associatedTokenAddress,
payer,
mint,
TOKEN_2022_PROGRAM_ID // 使用SPL Token 2022的程序ID
)
);
return transaction;
}
4、浏览器& Phantom钱包 展示代币图片
4.1、 浏览器和钱包展示的图片来自特定 uri 返回的 json 中的特定字段。
-
Phantom 钱包(谷歌浏览器安装插件):
-
浏览器
https://explorer.solana.com/address/Fg6t4x38NuxN**********zdhs?cluster=devnet
4.2、需要怎么做?
需要提供一个可以公开访问的 url,返回固定信息的 json
比如 :https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/metadata.json
{
"name": "OPOS",
"symbol": "OPOS",
"description": "Only Possible On Solana",
"image": "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/image.png",
"attributes": [
{
"trait_type": "Item",
"value": "Developer Portal"
}
]
}
应用方法是,把这个 url 传入到创建代币的入参uri 中
// Metadata to store in Mint Account
const metaData: TokenMetadata = {
updateAuthority: updateAuthority,
mint: mint,
name: "TestDemoJJJ",
symbol: "TestDemo JJJ",
//uri: "https://profile-avatar.csdnimg.cn/b521ddb316d54ee0811a45941c53b042_bestcxx.jpg!1",
uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/metadata.json",
additionalMetadata: [
["namea", "aaa"],
["ccc", "ddd"],
],
};
5、更多 api 接口
https://solana.com/zh/docs/rpc/http/gettokenaccountsbyowner#code-sample
https://solana.com/zh/docs/rpc/http/getsupply