如何从Web应用签名并调用Solana链上Anchor智能合约
完整实现步骤
1. 前置准备
- 安装前端所需依赖:
npm install @project-serum/anchor
- 找到你Anchor工程部署后生成的IDL文件,路径是
target/idl/basic_2.json,把这个文件复制到前端项目的src目录下。同时记录你部署合约时生成的程序ID(Program ID),确认IDL里的address字段和你实际部署到对应网络(devnet/localnet)的程序ID一致。 - 提前明确:调用
increment方法前必须先调用create方法创建counter账户,保存好counter账户的公钥,increment需要传入这个已存在的链上账户,无法凭空调用。如果还没创建,可以直接在前端实现create逻辑,创建时需要新生成一个Keypair作为counter账户的签名者。
2. 前端代码修改
首先修改文件头部的导入部分,新增Anchor相关引入和IDL导入:
import { Program, AnchorProvider, web3 } from '@project-serum/anchor'; // 导入你复制过来的IDL文件,路径按实际存放位置调整 import idl from './basic_2.json'; // 替换成你自己部署得到的合约Program ID const PROGRAM_ID = new web3.PublicKey("你的合约Program ID");
在组件里connection定义的位置后面,新增初始化Anchor程序实例的逻辑:
// 放在connection定义代码之后 const anchorProvider = useMemo(() => { if (!selectedWallet?.publicKey) return null; // 把现有wallet实例包装成Anchor认可的provider格式 const walletAdaptor = { publicKey: selectedWallet.publicKey, signTransaction: selectedWallet.signTransaction.bind(selectedWallet), signAllTransactions: selectedWallet.signAllTransactions?.bind(selectedWallet), }; return new AnchorProvider(connection, walletAdaptor, { commitment: 'confirmed' }); }, [connection, selectedWallet]); const program = useMemo(() => { if (!anchorProvider) return null; return new Program(idl as any, PROGRAM_ID, anchorProvider); }, [anchorProvider]);
替换你原来的sendTransaction方法,补充创建counter和调用increment的逻辑:
// 创建counter账户,首次使用调用一次即可 async function createCounter() { try { if (!program || !selectedWallet?.publicKey) { throw new Error('请先连接钱包'); } addLog('生成新的counter账户密钥对'); const counterKeypair = web3.Keypair.generate(); addLog('发起create交易'); const txHash = await program.rpc.create(selectedWallet.publicKey, { accounts: { counter: counterKeypair.publicKey, user: selectedWallet.publicKey, systemProgram: web3.SystemProgram.programId, }, signers: [counterKeypair] }); addLog(`创建成功,counter地址:${counterKeypair.publicKey.toBase58()},交易哈希:${txHash}`); // 必须把counter公钥存下来,后续increment要用到 localStorage.setItem('counterAddress', counterKeypair.publicKey.toBase58()); // 查询链上初始状态 const counterAccount = await program.account.counter.fetch(counterKeypair.publicKey); addLog(`初始count值:${counterAccount.count.toNumber()}`); } catch (e) { console.warn(e); addLog(`错误: ${(e as Error).message}`); } } // 调用increment方法 async function incrementCounter() { try { if (!program || !selectedWallet?.publicKey) { throw new Error('请先连接钱包'); } // 读取之前存储的counter地址 const counterAddressStr = localStorage.getItem('counterAddress'); if (!counterAddressStr) { throw new Error('请先创建counter账户'); } const counterPubkey = new web3.PublicKey(counterAddressStr); addLog('发起increment交易'); const txHash = await program.rpc.increment({ accounts: { counter: counterPubkey, authority: selectedWallet.publicKey } }); addLog(`交易已提交,哈希:${txHash},等待链上确认`); await connection.confirmTransaction(txHash, 'confirmed'); // 查询更新后的链上数据 const counterAccount = await program.account.counter.fetch(counterPubkey); addLog(`increment执行成功,当前count值:${counterAccount.count.toNumber()}`); } catch (e) { console.warn(e); addLog(`错误: ${(e as Error).message}`); } }
最后修改页面的按钮区域,替换原来的测试转账按钮,加上对应操作入口:
<button onClick={createCounter}>创建Counter账户</button> <button onClick={incrementCounter}>调用Increment</button>
常见问题说明
- 本地测试时需要把network从devnet改成本地节点地址
http://localhost:8899,同时确保本地solana测试节点在运行、合约已经部署到对应节点。 - create方法生成的counterKeypair仅在创建交易时需要签名,创建完成后只需要存储公钥即可,不需要保存私钥。
- 如果调用时报账户不存在错误,先确认传入的counter地址是create方法生成的对应地址,且前端连接的网络和合约部署的网络一致。
内容的提问来源于stack exchange,提问作者DeeHawk
相关产品推荐
相关产品推荐

