5. SDK 编程实战
本章使用 JavaScript/TypeScript 和最新版本的 @solana/spl-token 包进行代币编程。
5.1 项目初始化
创建新项目并安装依赖:
bashmkdir solana-token-demo && cd solana-token-demo npm init -y npm install @solana/web3.js @solana/spl-token
创建基础配置文件:
tsx// config.ts import { Connection, Keypair, clusterApiUrl } from '@solana/web3.js'; import fs from 'fs'; // 连接到 Devnet export const connection = new Connection(clusterApiUrl('devnet'), 'confirmed'); // 从文件加载钱包(与 CLI 使用相同的密钥) export function loadWallet(): Keypair { const secretKey = JSON.parse( fs.readFileSync(`${process.env.HOME}/.config/solana/id.json`, 'utf-8') ); return Keypair.fromSecretKey(new Uint8Array(secretKey)); } // 辅助函数:格式化代币数量 export function formatTokenAmount(amount: bigint, decimals: number): string { const divisor = BigInt(10 ** decimals); const integerPart = amount / divisor; const fractionalPart = amount % divisor; if (fractionalPart === 0n) { return integerPart.toString(); } const fractionalStr = fractionalPart.toString().padStart(decimals, '0'); return `${integerPart}.${fractionalStr.replace(/0+$/, '')}`; }
5.2 创建代币
tsx// create-token.ts import { createMint } from '@solana/spl-token'; import { connection, loadWallet } from './config'; async function createToken() { const payer = loadWallet(); console.log('创建新代币...'); console.log('钱包地址:', payer.publicKey.toBase58()); // 创建 Mint 账户 const mint = await createMint( connection, // 连接 payer, // 支付交易费用和租金的账户 payer.publicKey, // Mint Authority(铸币权限) payer.publicKey, // Freeze Authority(冻结权限),可设为 null 6 // 小数位数 ); console.log('代币创建成功!'); console.log('Mint 地址:', mint.toBase58()); return mint; } createToken() .then(() => process.exit(0)) .catch((err) => { console.error('错误:', err); process.exit(1); });
5.3 获取或创建 ATA
tsx// create-ata.ts import { getOrCreateAssociatedTokenAccount, getAssociatedTokenAddress } from '@solana/spl-token'; import { PublicKey } from '@solana/web3.js'; import { connection, loadWallet } from './config'; async function getOrCreateATA(mintAddress: string) { const payer = loadWallet(); const mint = new PublicKey(mintAddress); // 方法一:只计算 ATA 地址(不创建) const ataAddress = await getAssociatedTokenAddress( mint, payer.publicKey ); console.log('计算得出的 ATA 地址:', ataAddress.toBase58()); // 方法二:获取或创建 ATA(如果不存在则创建) const tokenAccount = await getOrCreateAssociatedTokenAccount( connection, payer, // 支付方 mint, // 代币 Mint payer.publicKey // ATA 所有者 ); console.log('Token Account 地址:', tokenAccount.address.toBase58()); console.log('当前余额:', tokenAccount.amount.toString()); return tokenAccount; } // 使用方式:npx ts-node create-ata.ts <MINT_ADDRESS> const mintAddress = process.argv[2]; if (!mintAddress) { console.error('请提供 Mint 地址作为参数'); process.exit(1); } getOrCreateATA(mintAddress);
5.4 铸造代币
tsx// mint-tokens.ts import { mintTo, getMint, getAccount } from '@solana/spl-token'; import { PublicKey } from '@solana/web3.js'; import { connection, loadWallet, formatTokenAmount } from './config'; async function mintTokens( mintAddress: string, destinationAddress: string, amount: number ) { const payer = loadWallet(); const mint = new PublicKey(mintAddress); const destination = new PublicKey(destinationAddress); // 获取 Mint 信息以确定精度 const mintInfo = await getMint(connection, mint); // 将用户输入的数量转换为底层数值 const rawAmount = BigInt(amount * (10 ** mintInfo.decimals)); console.log('铸造代币...'); console.log('数量:', amount); console.log('底层数值:', rawAmount.toString()); // 执行铸造 const signature = await mintTo( connection, payer, // 支付方 mint, // Mint 地址 destination, // 接收账户(Token Account,不是钱包地址) payer, // Mint Authority rawAmount // 数量(底层数值) ); console.log('铸造成功!'); console.log('交易签名:', signature); // 查询更新后的信息 const updatedMint = await getMint(connection, mint); const destinationAccount = await getAccount(connection, destination); console.log('当前总供应量:', formatTokenAmount(updatedMint.supply, mintInfo.decimals)); console.log('接收账户余额:', formatTokenAmount(destinationAccount.amount, mintInfo.decimals)); } // 使用方式:npx ts-node mint-tokens.ts <MINT> <TOKEN_ACCOUNT> <AMOUNT> mintTokens(process.argv[2], process.argv[3], Number(process.argv[4]));
5.5 转账
tsx// transfer.ts import { transfer, getOrCreateAssociatedTokenAccount, getMint } from '@solana/spl-token'; import { PublicKey } from '@solana/web3.js'; import { connection, loadWallet } from './config'; async function transferTokens( mintAddress: string, recipientWallet: string, amount: number ) { const payer = loadWallet(); const mint = new PublicKey(mintAddress); const recipient = new PublicKey(recipientWallet); // 获取精度 const mintInfo = await getMint(connection, mint); const rawAmount = BigInt(amount * (10 ** mintInfo.decimals)); // 获取发送方的 Token Account const sourceAccount = await getOrCreateAssociatedTokenAccount( connection, payer, mint, payer.publicKey ); // 获取或创建接收方的 Token Account // 注意:这里创建账户的费用由 payer 支付 const destinationAccount = await getOrCreateAssociatedTokenAccount( connection, payer, mint, recipient // 接收方的钱包地址(不是 Token Account) ); console.log('发送方 Token Account:', sourceAccount.address.toBase58()); console.log('接收方 Token Account:', destinationAccount.address.toBase58()); console.log('转账数量:', amount); // 执行转账 const signature = await transfer( connection, payer, // 支付方 & 签名者 sourceAccount.address, // 发送方 Token Account destinationAccount.address, // 接收方 Token Account payer, // 发送方所有者(签名) rawAmount ); console.log('转账成功!'); console.log('交易签名:', signature); } // 使用方式:npx ts-node transfer.ts <MINT> <RECIPIENT_WALLET> <AMOUNT> transferTokens(process.argv[2], process.argv[3], Number(process.argv[4]));
5.6 授权与委托
授权机制允许你让第三方(如 DeFi 协议)在限额内使用你的代币:
tsx// approve-delegate.ts import { approve, revoke, getAccount, getOrCreateAssociatedTokenAccount, getMint } from '@solana/spl-token'; import { PublicKey } from '@solana/web3.js'; import { connection, loadWallet } from './config'; async function approveDelegate( mintAddress: string, delegateAddress: string, amount: number ) { const payer = loadWallet(); const mint = new PublicKey(mintAddress); const delegate = new PublicKey(delegateAddress); const mintInfo = await getMint(connection, mint); const rawAmount = BigInt(amount * (10 ** mintInfo.decimals)); // 获取 Token Account const tokenAccount = await getOrCreateAssociatedTokenAccount( connection, payer, mint, payer.publicKey ); // 授权给委托人 const signature = await approve( connection, payer, tokenAccount.address, // 被授权的 Token Account delegate, // 委托人地址 payer, // Token Account 所有者 rawAmount // 授权额度 ); console.log('授权成功!'); console.log('委托人:', delegateAddress); console.log('授权额度:', amount); console.log('交易签名:', signature); // 验证授权状态 const accountInfo = await getAccount(connection, tokenAccount.address); console.log('当前委托人:', accountInfo.delegate?.toBase58() || '无'); console.log('委托额度:', accountInfo.delegatedAmount.toString()); } async function revokeDelegate(mintAddress: string) { const payer = loadWallet(); const mint = new PublicKey(mintAddress); const tokenAccount = await getOrCreateAssociatedTokenAccount( connection, payer, mint, payer.publicKey ); // 撤销授权 const signature = await revoke( connection, payer, tokenAccount.address, payer ); console.log('已撤销授权'); console.log('交易签名:', signature); }
5.7 批量操作与事务
将多个操作组合在一个原子事务中:
tsx// batch-operations.ts import { createMint, getOrCreateAssociatedTokenAccount, createMintToInstruction, createTransferInstruction, getMint } from '@solana/spl-token'; import { Transaction, sendAndConfirmTransaction, Keypair, PublicKey } from '@solana/web3.js'; import { connection, loadWallet } from './config'; async function batchMintAndTransfer() { const payer = loadWallet(); // 第一步:创建代币 console.log('1. 创建新代币...'); const mint = await createMint( connection, payer, payer.publicKey, null, // 不设置冻结权限 6 ); console.log('Mint 地址:', mint.toBase58()); // 第二步:创建发送方和接收方的 Token Account console.log('2. 创建 Token Accounts...'); const senderATA = await getOrCreateAssociatedTokenAccount( connection, payer, mint, payer.publicKey ); // 创建一个临时接收方 const recipient = Keypair.generate(); const recipientATA = await getOrCreateAssociatedTokenAccount( connection, payer, mint, recipient.publicKey ); // 第三步:构建批量交易(铸造 + 转账) console.log('3. 构建批量交易...'); const transaction = new Transaction(); // 添加铸造指令:铸造 1000 个代币 transaction.add( createMintToInstruction( mint, senderATA.address, payer.publicKey, // Mint Authority 1000_000_000n // 1000 * 10^6 ) ); // 添加转账指令:转 200 个给接收方 transaction.add( createTransferInstruction( senderATA.address, recipientATA.address, payer.publicKey, 200_000_000n // 200 * 10^6 ) ); // 发送交易 console.log('4. 发送交易...'); const signature = await sendAndConfirmTransaction( connection, transaction, [payer] ); console.log('交易成功!'); console.log('签名:', signature); console.log('发送方余额: 800'); console.log('接收方余额: 200'); } batchMintAndTransfer();