ライブラリの Solana Web3 JavaScript API があるため、実装自体はかんたんにできる。
Agenda
補足
- devnetやtestnetを使えば、Solanaノードサーバーをローカル起動(% solana-test-validator)する必要なし
- JSのため、Nodeを使ってターミナルから「% node [JSファイル]」で実行する。Nodeが動けば場所はどこでもよい
- 今回、結果のログは「console.log()」でターミナル上に出力
- 今回はNodeを使うが、JSが動けばCodePenやChrome検証ツールなどでも実行可能。その場合は、scriptタグでSolanaのweb3.jsを読み込む形となる
- 今回のサンプル実装はSolana公式サイトのほぼコピペ
準備
- Nodeをインストールする
- Install をもとに、Yarnまたはnpmで「@solana/web3.js」をインストールする
solanaWeb3.jsのサンプル出力
Usage をサンプル実装。
% vi solanaWeb3.js
(viで以下を記述する)
const solanaWeb3 = require('@solana/web3.js');
console.log(solanaWeb3);
Nodeを起動すると、ターミナルにSolanaブロックチェーン情報が表示される。
% node solanaWeb3.js
{
Account: [class Account],
Authorized: [class Authorized],
BLOCKHASH_CACHE_TIMEOUT_MS: 30000,
BPF_LOADER_DEPRECATED_PROGRAM_ID: PublicKey {
_bn: <BN: *****************************>
},
BPF_LOADER_PROGRAM_ID: PublicKey {
_bn: <BN: *****************************>>
},
BpfLoader: [class BpfLoader],
Connection: [class Connection],
Ed25519Program: [class Ed25519Program] {
programId: PublicKey {
_bn: <BN: *****************************>>
}
},
Enum: [class Enum extends Struct],
EpochSchedule: [class EpochSchedule],
FeeCalculatorLayout: NearUInt64 { span: 8, property: 'lamportsPerSignature' },
Keypair: [class Keypair],
LAMPORTS_PER_SOL: 1000000000,
Loader: [class Loader] { chunkSize: 932 },
Lockup: [class Lockup] {
default: Lockup { unixTimestamp: 0, epoch: 0, custodian: [PublicKey] }
},
MAX_SEED_LENGTH: 32,
・・・省略
Keypair作成の場合
Connecting to a Wallet のKeypair作成であれば、サンプルどおりに記述するだけで動く。
% vi keypair.js
(viで以下を記述する)
const {Keypair} = require("@solana/web3.js");
let keypair = Keypair.generate();
console.log(keypair);
Nodeを起動すると、Keypairがランダム作成される。
% node keypair.js
Keypair {
_keypair: {
publicKey: Uint8Array(32) [
169, 183, 61, 209, 30, 177, 24, 56,
61, 228, 138, 84, 130, 220, 57, 157,
87, 120, 159, 120, 143, 241, 166, 19,
139, 230, 72, 108, 110, 86, 229, 102
],
secretKey: Uint8Array(64) [
35, 78, 180, 190, 104, 77, 3, 174, 245, 37, 190,
180, 63, 108, 221, 244, 68, 4, 99, 15, 138, 130,
75, 176, 228, 85, 115, 213, 29, 200, 170, 151, 169,
183, 61, 209, 30, 177, 24, 56, 61, 228, 138, 84,
130, 220, 57, 157, 87, 120, 159, 120, 143, 241, 166,
19, 139, 230, 72, 108, 110, 86, 229, 102
]
}
}
トランザクションの実装
送金する場合の実装。
Interacting with Custom Programs のほぼコピペ(最後に載っている完成形のソース)で、以下だけ追記。
- awaitエラー対策としてfunction化
- console.log出力(特にトランザクション結果のSinatureが取得できないと結果がわからないため)
- testnetで実際にトランザクションを走らせるため、結果までに時間がかかるため、数分は待つ必要あり
% vi transactions.js
(viで以下を記述する)
const {struct,u32, ns64} = require("@solana/buffer-layout");
const {Buffer} = require('buffer');
const web3 = require("@solana/web3.js");
const cluster = 'testnet';
async function main() {
let keypair = web3.Keypair.generate();
let payer = web3.Keypair.generate();
let connection = new web3.Connection(web3.clusterApiUrl(cluster));
let airdropSignature = await connection.requestAirdrop(
payer.publicKey,
web3.LAMPORTS_PER_SOL,
);
await connection.confirmTransaction(airdropSignature);
let allocateTransaction = new web3.Transaction({
feePayer: payer.publicKey
});
let keys = [{pubkey: keypair.publicKey, isSigner: true, isWritable: true}];
let params = { space: 100 };
let allocateStruct = {
index: 8,
layout: struct([
u32('instruction'),
ns64('space'),
])
};
let data = Buffer.alloc(allocateStruct.layout.span);
let layoutFields = Object.assign({instruction: allocateStruct.index}, params);
allocateStruct.layout.encode(layoutFields, data);
allocateTransaction.add(new web3.TransactionInstruction({
keys,
programId: web3.SystemProgram.programId,
data,
}));
let signature = await web3.sendAndConfirmTransaction(connection, allocateTransaction, [payer, keypair]);
console.log('Cluster -> ', cluster);
console.log('From -> ', keypair.publicKey.toString());
console.log('To -> ', payer.publicKey.toString());
console.log('Signature -> ', signature);
}
main();
Nodeでトランザクションを実行する。
% node transactions.js
Cluster -> testnet
From -> FLjBZzd7qEmodu4WrTyiTXCaLsGUjjPRHRCMYAgZkp2c
To -> FNxQPD9aFpUVFEWkqfF6rwdNWQZhLXUehuvVGGc83DKX
Signature -> 5MhJWsX8Ru2CKe1tUH8KpPgDXUVRonYKT9aNYAUegw7fp152AKJKxjE878PB2C99crXPsPVG5368kfmyf6BMgzrF
トランザクション結果はtestnetで確認できる。ターミナルに出力されたSignatureで検索する。
トランザクションが失敗する場合(調査中)
現象
まだ調査中だが、以下のようにトランザクションが失敗するケースがある。
% node transactions.js
/Users/user/node_modules/@solana/web3.js/lib/index.cjs.js:5631
throw new Error(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`);
^
Error: Transaction was not confirmed in 60.01 seconds. It is unknown if it succeeded or failed. Check signature 3y4Y2wBJMaamfTPbxUY5pJ9YU9deDeCsSrBEXUR2Ncgkt3eQR5oAnMoQ3AZRmt679SUiqx9Rz7cn8VbD32FXUGpo using the Solana Explorer or CLI tools.
at Connection.confirmTransaction (/Users/user/node_modules/@solana/web3.js/lib/index.cjs.js:5631:13)
at async main (/Users/user/Desktop/blockchain/nodeServer/transactions.js:16:3)
成功/失敗は Solana Explorer で、ターミナルに出力されたSignatureを検索すると、確認できる。
対策1(時間を置く)
もしかしたら、立て続けに連続実行するとエラーになる? 数分時間を置いてから実行すると成功率が高い気がする。
対策2(Clusterをlocalhostにする)
testnetやdevnetではなく、localhostに接続することもできる。newするときにConnectionでURLを指定するだけ。
抜粋すると以下のようなイメージ。
const web3 = require('@solana/web3.js');
let connection = new web3.Connection('http://localhost:8899');
実装方法
1.solana-test-validatorでノードを起動する
2.solana logsでログをリッスンモードにする(ログを見たい場合)
3.ソースを以下のように修正
(変更前)
let connection = new web3.Connection(web3.clusterApiUrl(cluster));
(変更後)
let connection = new web3.Connection('http://localhost:8899');
4.JSを実行する
% node transactions.js
localなので処理速度も早いため、連続実行したい場合はlocalhost指定がよい。
% solana logs
Transaction executed in slot 8515:
Signature: 4utcoP4eN1nFqGsX4Ktn3TVgiqEtSPsdRArb6UcDtBcBBZog51osBy6Esn9vsndWU17V7X3TQwX1zyQGDYvhpUx7
Status: Ok
Log Messages:
Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
Transaction executed in slot 8548:
Signature: 4js3J88tqxEcoTn9z9kG3JFeNfBWNbRyTR1f3KrpA52TRyxBBKpyr31Scxqw6wDu92FyyvBT2Urwa6jAWFueLDGr
Status: Ok
Log Messages:
Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
awaitがある箇所の実装(エラー対策)
awaitがあるとSolana公式サイトをコピペ実装してもとエラーになる。
% node transactions.js
/Users/user/Desktop/blockchain/nodeServer/tx2.js:10
let airdropSignature = await connection.requestAirdrop(
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
at Object.compileFunction (node:vm:352:18)
at wrapSafe (node:internal/modules/cjs/loader:1031:15)
at Module._compile (node:internal/modules/cjs/loader:1065:27)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)
at node:internal/main/run_main_module:17:47
awaitは関数内で使用する必要があるため、以下のようにすることで実行可能になる。
async function main() {
(・・・サンプルコードを記述)
}
main();
コード
GitHubにサンプルコードを格納しておきました。
256hax/solana-rust-js-vanilla-example - GitHub
参考
- Solana Web3 JavaScript API
- Solana Web3 API Reference
- Figment learn-web3-dapp/pages/api/solana/
サンプル実装がファイル単位になっているのでわかりやすい。 - Solana 101 Learn to build on Solana with Figment Learn
解説はFigmentのSolana101がおすすめ。ローカルでサーバーを起動して実際に動かしながら勉強ができる。