技术分析:Solana如何实现账户交易?
Solana Hubsolana官方的solana-program-library中给出了很多实用的实例,本次我们将介绍其中一个调用交易实例的例子,链接的examples中可以找到对应的rust program例程,我们用自定义的ts写的client去调用即可。?
生成钱包地址并领取空投:?
在发起交易前需要创建两个账户,并在其中添加相应的代币:
- 创建两个新钱包,并输出json文件
$?solana-keygen?new?--outfile?xxx.json?
- 空投x个SOL到钱包的公钥地址中
$?solana?airdrop?x?pubkey
- cd到transfer-lamports的目录下
$?cd?.../transfer-lamports
- 编译rust项目,获得json和so文件
$?cargo?build-bpf
ts程序中实现调用和交易的关键步骤: 1.建立一个JSON RPC连接
- 首先获得solana的配置信息并做解析,这一步的信息也可用CLI的solana config get得出查看
async?function?getConfig():?Promise<any>?{
??//?Path?to?Solana?CLI?config?file
??const?CONFIG_FILE_PATH?=?path.resolve(
????os.homedir(),
????'.config',
????'solana',
????'cli',
????'config.yml',
??);
??const?configYml?=?await?fs.readFile(CONFIG_FILE_PATH,?{encoding:?'utf8'});
??return?yaml.parse(configYml);
}
- 接着取其中的rpc_url建立连接
const?config?=?await?getConfig();
rpcUrl?=?config.json_rpc_url
connection?=?new?Connection(rpcUrl,?'confirmed');
2.设置payer交易费用支付者
- 首先计算建立账号所需的费用
const?{feeCalculator}?=?await?connection.getRecentBlockhash();
//?Calculate?the?cost?to?fund?the?greeter?account
fees?+=?await?connection.getMinimumBalanceForRentExemption(SIZE);
//?Calculate?the?cost?of?sending?transactions
fees?+=?feeCalculator.lamportsPerSignature?*?100;?//?wag
- 接着从配置文件中读取密钥对作为支付者
const?config?=?await?getConfig();
payerAccount?=?readAccountFromFile(config.keypair_path);
- 如果账户中的lamports余额低于最小要求时,则需要请求空投,一般这会发生在命令行配置的密钥对中
const?lamports?=?await?connection.getBalance(payerAccount.publicKey);
??if?(lamports?<?fees)?{
????const?sig?=?await?connection.requestAirdrop(
??????payerAccount.publicKey,
??????fees?-?lamports,
????);
????await?connection.confirmTransaction(sig);
??}
3.检查BPF项目是否部署
在第一步我们编译rust生成bpf文件后,并没有进行部署,这一步就是为了检查项目是否部署在链上,通过检查programId是否有关联账号来判断。
- 首先读取项目密钥对的路径文件,并获得公钥即programId,这个文件在编译后生成,一般在target/deploy目录下的json文件中
const?PROGRAM_KEYPAIR_PATH?=?('.../src/transfer-lamports/target/deploy/spl_example_transfer_lamports-keypair.json');
const?programAccount?=?await?readAccountFromFile(PROGRAM_KEYPAIR_PATH);
programId?=?programAccount.publicKey;
- 接着读取programId的账户信息,通过查看账户信息判断是否部署,如果账户是空的并且存在so文件路径,则需要进行program部署,如果不存在so文件,则需要先进行编译生成文件再进行部署。如果账户存在,但是programInfo.executable是false,则代表program已经部署但无法执行,需要进行检查修改
const?programInfo?=?await?connection.getAccountInfo(programId);
??if?(programInfo?===?null)?{
????if?(fs.existsSync(PROGRAM_SO_PATH))?{
??????throw?new?Error(
????????'Program?needs?to?be?deployed?with?`solana?program?deploy?dist/program/helloworld.so`',
??????);
????}?else?{
??????throw?new?Error('Program?needs?to?be?built?and?deployed');
????}
??}
??else?if?(!programInfo.executable)?{
????throw?new?Error(`Program?is?not?executable`);
??}
4.交易指令发起
在完成前面的步骤后,则可进行ts的交易指令发起编写
- 首先设置两个新钱包的json文件路径,并获取付款账号的公钥,这里为TransferOne
const?TRANSFER_ONE?=?('.../transfer_one.json');
const?TRANSFER_TWO?=?('.../transfer_two.json');
TransferOne?=?await?readAccountFromFile(TRANSFER_ONE);
- 新建一笔交易,添加指令,指令为系统指令,修改TransferOne账号拥有者为新的programId,原拥有者是系统。
const?transaction?=??new?Transaction().add(
SystemProgram.assign({accountPubkey:?TransferOne.publicKey,?programId})?,);
- 发送交易并确认,发送账号要包含费用支付账号,转账者账号。
await?sendAndConfirmTransaction(
????????connection,
????????transaction,
????????[payerAccount,TransferOne],??
????);
- 新建一个交易指令,把转账者和接受者的AccountMeta,programId,空的data添加进去。
const?instruction?=?new?TransactionInstruction({
??????keys:?[{pubkey:?TransferOne.publicKey,?isSigner:?false,?isWritable:?true},
????????{pubkey:?TransferTwo.publicKey,?isSigner:?false,?isWritable:?true}],
??????programId,
??????data:?Buffer.alloc(0),?
????});
- 发送交易并确认
await?sendAndConfirmTransaction(
??????connection,
??????new?Transaction().add(instruction),
??????[payerAccount],
????);
以上便是ts中关键的一些实现步骤,不包括全部代码,接下来便是CLi的命令执行和日志查看?
程序部署执行操作:
- 设置环境为本地
solana?config?set?--url?localhost?
- 打开一个新终端,启动测试验证节点
solana-test-validator?
- 打开一个新终端,启动solana日志监听
solana?logs?
- 打开一个新终端,在已经编译生成bpf文件的基础上,部署程序到链上
solana?program?deploy?.../src/transfer-lamports/target/deploy/spl_example_transfer_lamports.so?
- 安装npm环境
npm?install?
- 启动程序
npm?run?start?
- 在rust中添加交易前后打印账号信息的代码,留意lamports的余额变化
Program?log:?source_info?AccountInfo?{?key:?9yYpfeaZj5BR7t7NNwtVGriTVdQRrh3onNjDRJ3Q3hxj?owner:?7t8m9KKZb3bDE4ez3CKR7cMF7NVHXbo7cU6zFVGWpjKb?is_signer:?false?is_writable:?true?executable:?false?rent_epoch:?0?lamports:?9999999970?data.len:?0??}
Program?log:?destination_info?AccountInfo?{?key:?8LNqub4QPXyNZKaHJCzgYaRDDwaYBp91abDhGoFzjP4X?owner:?11111111111111111111111111111111?is_signer:?false?is_writable:?true?executable:?false?rent_epoch:?0?lamports:?1000000030?data.len:?0??}
Program?log:?source_info?AccountInfo?{?key:?9yYpfeaZj5BR7t7NNwtVGriTVdQRrh3onNjDRJ3Q3hxj?owner:?7t8m9KKZb3bDE4ez3CKR7cMF7NVHXbo7cU6zFVGWpjKb?is_signer:?false?is_writable:?true?executable:?false?rent_epoch:?0?lamports:?9999999965?data.len:?0??}
Program?log:?destination_info?AccountInfo?{?key:?8LNqub4QPXyNZKaHJCzgYaRDDwaYBp91abDhGoFzjP4X?owner:?11111111111111111111111111111111?is_signer:?false?is_writable:?true?executable:?false?rent_epoch:?0?lamports:?1000000035?data.len:?0??}
可以看到lamports的数量进行了转移,
转账成功!