技术分析:Solana如何实现账户交易?

Solana Hub

    
    
    solana官方的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的数量进行了转移,
    转账成功!