はじめに

Facebook の仮想通貨(暗号通貨, 暗号資産)である Libra を、Node.js 環境で触ることのできるクライアント libra-core の使い方をまとめた。

インストール方法は Mac に Facebook の仮想通貨 Libra の Node.js クライアント libra-core をインストールする を参照。

TL;DR

  • libra-core v1.0.5 を色々と試した
  • 送金に関する Issue/PR #33 があるので注意
  • バージョンアップで大きく変わるかもしれないので、この記事は参考程度にとどめておいた方が良いと思う
この記事が参考になった方
ここここからチャージや購入してくれると嬉しいです(ブログ主へのプレゼントではなく、ご自身へのチャージ)
欲しいもの / Wish list

目次

  1. はじめに
  2. TL;DR
  3. 環境・条件
  4. 詳細
    1. ライブラリロード
    2. ウォレット 作成
    3. ウォレット 復元
    4. アカウント(アドレス) 作成
    5. アカウント(アドレス) 復元
    6. 鋳造(mint)
    7. 残高の確認
    8. 送金
      1. 2回目以降の送金ができない場合
    9. シーケンス番号の確認
    10. トランザクションの確認
  5. まとめ
  6. 参考文献

環境・条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.14.5
BuildVersion: 18F132

$ nodenv --version
nodenv 1.3.0

$ node --version
v12.7.0

$ npm --version
6.10.0

$ npm v libra-core
libra-core@1.0.5 | MIT | deps: 9 | versions: 5

詳細

Node.js のコンソール上で検証を行った。

ライブラリロード

Node.js は(2019/08/10 現時点の v12.7.0 では) import/export を正式サポートしていないため、require で読み込む必要がある。

参考記事: node.js モジュール(ES Module)のimport/exportにハマる。 - かもメモ

1
2
3
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const LibraClient = libra_core.LibraClient;

なお、TypeScript を使うのであれば 公式: Usage 通りに import を使えば良い。(libra-core は TypeScript で書かれている。)

1
import { LibraWallet, LibraClient } from 'libra-core';

ウォレット 作成

公式: Creating an Account

new LibraWallet() で新規ウォレットを作成できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const wallet = new LibraWallet();
console.log(wallet);
// => LibraWallet {
// lastChild: 0,
// accounts: {},
// config: {
// mnemonic: 'pitch peasant (略) drive fly'
// },
// keyFactory: KeyFactory {
// seed: Seed {
// data: <Buffer 75 11 (略) 8f a2>
// },
// hkdf: Hkdf { hashAlgorithm: 'sha3-256' },
// masterPrk: Uint8Array [
// 53, 69, (略), 180, 104
// ]
// }
// }

ウォレット 復元

既存のウォレットを復元したい場合には、mnemonic phrases を渡せば OK。

mnemonic は config.mnemonic で取得できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const wallet = new LibraWallet();
const mnemonic = wallet.config.mnemonic;
const wallet2 = new LibraWallet({mnemonic: mnemonic})
console.log(wallet2)
// => LibraWallet {
// lastChild: 0,
// accounts: {},
// config: {
// mnemonic: 'pitch peasant (略) drive fly'
// },
// keyFactory: KeyFactory {
// seed: Seed {
// data: <Buffer 75 11 (略) 8f a2>
// },
// hkdf: Hkdf { hashAlgorithm: 'sha3-256' },
// masterPrk: Uint8Array [
// 53, 69, (略), 180, 104
// ]
// }
// }

アカウント(アドレス) 作成

LibraWallet.newAccount() でアカウントを作成できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const wallet = new LibraWallet();
const account = wallet.newAccount();
console.log(account);
// => Account {
// keyPair: KeyPair {
// eddsaPair: KeyPair {
// eddsa: [EDDSA],
// _secret: <Buffer 60 92 (略) e5 98>,
// _pubBytes: [Array],
// _hash: [Array],
// _privBytes: [Array],
// _priv: [BN],
// _pub: [Point]
// }
// },
// address: AccountAddress {
// addressBytes: Uint8Array [
// 139, 69, (略), 59, 213
// ]
// }
// }

アカウント作成すると(当然だが) wallet.accounts に内容が追加されている。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console.log(wallet)
// => LibraWallet {
// lastChild: 1,
// accounts: {
// '8b45xxxx93bd5': Account { keyPair: [KeyPair], address: [AccountAddress] }
// },
// config: {
// mnemonic: 'pitch peasant (略) drive fly'
// },
// keyFactory: KeyFactory {
// seed: Seed {
// data: <Buffer 75 11 (略) 8f a2>
// },
// hkdf: Hkdf { hashAlgorithm: 'sha3-256' },
// masterPrk: Uint8Array [
// 53, 69, (略), 180, 104
// ]
// }
// }

アカウント(アドレス) 復元

LibraAccount.fromSecretKey() でアカウントの復元。引数として、秘密鍵を Hex String で渡す必要がある。

秘密鍵は LibraAccount.keyPair.getSecretKey() で取得できるので、Buffer.from().toString("hex") と組み合わせることで Hex String に変換できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const LibraAccount = libra_core.Account;

const wallet = new LibraWallet();
const account = wallet.newAccount();
const secretKey = account.keyPair.getSecretKey()
console.log(secretKey)
// => Uint8Array [
// 96, 146, (略), 229, 152
// ]

const secretKeyHexStr = Buffer.from(secretKey).toString("hex")
console.log(secretKeyHexStr)
// => 6092xxxx8e598

const account2 = LibraAccount.fromSecretKey(secretKeyHexStr)
console.log(account2)
// => Account {
// keyPair: KeyPair {
// eddsaPair: KeyPair {
// eddsa: [EDDSA],
// _secret: <Buffer 60 92 (略) e5 98>,
// _pubBytes: undefined
// }
// }
// }

console.log(account.getAddress().toString())
// => '8b45xxxx3bd5'
console.log(account2.getAddress().toString())
// => '8b45xxxx3bd5'

LibraWallet.addAccount() で、復元したアカウントをウォレットに追加できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
wallet2.addAccount(account2)
console.log(wallet2)
LibraWallet {
lastChild: 0,
accounts: {
'8b45xxxx3bd5': Account { keyPair: [KeyPair], address: [AccountAddress] }
},
config: {
mnemonic: 'pitch peasant (略) drive fly'
},
keyFactory: KeyFactory {
seed: Seed {
data: <Buffer 75 11 (略) 8f a2>
},
hkdf: Hkdf { hashAlgorithm: 'sha3-256' },
masterPrk: Uint8Array [
53, 69, (略), 180, 104
]
}
}

鋳造(mint)

公式: Minting Amount

LibraClient.mintWithFaucetService() で Testnet で遊ぶための Libra Coin を鋳造できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const LibraAccount = libra_core.Account;
const LibraClient = libra_core.LibraClient;
const LibraNetwork = libra_core.LibraNetwork;

async function mint(address, amount=20e6) {
const client = new LibraClient({ network: LibraNetwork.Testnet });

// mint libracoins to users accounts
await client.mintWithFaucetService(address, amount);
}

// 既存アカウントを使いたい場合は mnemonic や fromSecretKey() を使う
// const wallet = new LibraWallet({mnemonic: "your mnemonics"});
// const account = LibraAccount.fromSecretKey(secretKeyHexStr);
const wallet = new LibraWallet();
const account = wallet.newAccount();

mint(account.getAddress(), 20e6);
console.log(`https://www.libravista.com/address/${account.getAddress().toString()}`)
// => https://www.libravista.com/address/8b45xxxx93bd5

LibraVista で残高などを簡単に確認できるので、鋳造できているかを確認してみると良い。

libra_1

残高の確認

公式: Checking an address balance

LibraClient.getAccountState() で、アカウントの状態(accountState)を取得できる。accountState.balance に残高情報が入っている。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const LibraAccount = libra_core.Account;
const LibraClient = libra_core.LibraClient;
const LibraNetwork = libra_core.LibraNetwork;

async function checkBalance(accountAddress) {
const client = new LibraClient({ network: LibraNetwork.Testnet });
const accountState = await client.getAccountState(accountAddress);
console.log(accountState.balance.toString());
}

// 既存アカウントを使いたい場合は mnemonic や fromSecretKey() を使う
// const wallet = new LibraWallet({mnemonic: "your mnemonics"});
// const account = LibraAccount.fromSecretKey(secretKeyHexStr);
const wallet = new LibraWallet();
const account = wallet.newAccount();

checkBalance(account.getAddress());
// => 20000000

送金

公式: Transfering Libra Coins

LibraClient.transferCoins() で送金。送金元アカウント、送金先アドレス、送金額を引数として渡す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const LibraAccount = libra_core.Account;
const LibraClient = libra_core.LibraClient;
const LibraNetwork = libra_core.LibraNetwork;


async function send(srcAccount, destAddress, amount) {
const client = new LibraClient({ network: LibraNetwork.Testnet });
const response = await client.transferCoins(srcAccount, destAddress, amount);

// wait for transaction confirmation
await response.awaitConfirmation(client);
}

// 既存アカウントを使いたい場合は mnemonic や fromSecretKey() を使う
// const wallet = new LibraWallet({mnemonic: "your mnemonics"});
// const account = LibraAccount.fromSecretKey(secretKeyHexStr);
const wallet = new LibraWallet();
const account = wallet.newAccount();
const account2 = wallet.newAccount();

send(account, account2.getAddress().toString(), 1e5);
console.log(`https://www.libravista.com/address/${account.getAddress().toString()}`)
// => https://www.libravista.com/address/8b45xxxx93bd5

同じように LibraVista で確認する。

2回目以降の送金ができない場合

本問題は v1.0.6 で解消済み

libra-core v1.0.5 にはバグがあるため、2回目以降の送金を行うには Pull Request #33 の内容を取り込む必要がある。

Pull Request の内容を参考に以下の2ファイルを修正することで、2回目以降の送金ができるようになる。

node_modules/libra-core/build/common/CursorBuffer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
diff --git a/node_modules/libra-core/build/common/CursorBuffer.js b/node_modules/libra-core/build/common/CursorBuffer.js
index 21590ac..9587bb6 100644
--- a/node_modules/libra-core/build/common/CursorBuffer.js
+++ b/node_modules/libra-core/build/common/CursorBuffer.js
@@ -52,5 +52,18 @@ class CursorBuffer {
this.bytePositon += x;
return value;
}
+
+ /**
+ * Read bool as 1 byte
+ *
+ */
+ readBool() {
+ const value = this.dataView.getUint8(this.bytePositon);
+ this.bytePositon += 1;
+ if(value !== 0 && value !== 1) {
+ throw new Error(`bool must be 0 or 1, found ${value}`);
+ }
+ return value !== 0;
+ }
}
exports.CursorBuffer = CursorBuffer;

node_modules/libra-core/build/wallet/Accounts.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
diff --git a/node_modules/libra-core/build/wallet/Accounts.js b/node_modules/libra-core/build/wallet/Accounts.js
index 51e74ea..258a036 100644
--- a/node_modules/libra-core/build/wallet/Accounts.js
+++ b/node_modules/libra-core/build/wallet/Accounts.js
@@ -19,24 +19,26 @@ class AccountState {
* Returns an empty AccountState
*/
static default(address) {
- return new AccountState(new Uint8Array(Buffer.from(address, 'hex')), new bignumber_js_1.default(0), new bignumber_js_1.default(0), new bignumber_js_1.default(0), new bignumber_js_1.default(0));
+ return new AccountState(new Uint8Array(Buffer.from(address, 'hex')), new bignumber_js_1.default(0), new bignumber_js_1.default(0), new bignumber_js_1.default(0), new bignumber_js_1.default(0), true);
}
static fromBytes(bytes) {
const cursor = new CursorBuffer_1.CursorBuffer(bytes);
const authenticationKeyLen = cursor.read32();
const authenticationKey = cursor.readXBytes(authenticationKeyLen);
const balance = cursor.read64();
+ const delegatedWithdrawalCapability = cursor.readBool();
const receivedEventsCount = cursor.read64();
const sentEventsCount = cursor.read64();
const sequenceNumber = cursor.read64();
- return new AccountState(authenticationKey, balance, receivedEventsCount, sentEventsCount, sequenceNumber);
+ return new AccountState(authenticationKey, balance, receivedEventsCount, sentEventsCount, sequenceNumber, delegatedWithdrawalCapability);
}
- constructor(authenticationKey, balance, receivedEventsCount, sentEventsCount, sequenceNumber) {
+ constructor(authenticationKey, balance, receivedEventsCount, sentEventsCount, sequenceNumber, delegatedWithdrawalCapability) {
this.balance = balance;
this.sequenceNumber = sequenceNumber;
this.authenticationKey = authenticationKey;
this.sentEventsCount = sentEventsCount;
this.receivedEventsCount = receivedEventsCount;
+ this.delegatedWithdrawalCapability = delegatedWithdrawalCapability;
}
}
exports.AccountState = AccountState;

シーケンス番号の確認

残高取得 でも使用した LibraClient.getAccountState() の戻り値 accountState 内に現在のシーケンス番号(次に使用されるシーケンス番号)が入っており、accountState.sequenceNumber で確認できる。
※Libra は送金を行うごとにシーケンス番号が 1ずつ増えていく。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const LibraAccount = libra_core.Account;
const LibraClient = libra_core.LibraClient;
const LibraNetwork = libra_core.LibraNetwork;

async function checkSequenceNumber(address) {
const client = new LibraClient({ network: LibraNetwork.Testnet });
const accountState = await client.getAccountState(address);
console.log(accountState.sequenceNumber.toString());
}

// 既存アカウントを使いたい場合は mnemonic や fromSecretKey() を使う
// const wallet = new LibraWallet({mnemonic: "your mnemonics"});
// const account = LibraAccount.fromSecretKey(secretKeyHexStr);
const wallet = new LibraWallet();
const account = wallet.newAccount();

checkSequenceNumber(account.getAddress());
// => 1

トランザクションの確認

公式: Query Transaction with Sequence Number

LibraClient.getAccountTransaction() でトランザクションの確認。対象のアドレスと、シーケンス番号を引数として渡す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const libra_core = require("libra-core");
const LibraWallet = libra_core.LibraWallet;
const LibraAccount = libra_core.Account;
const LibraClient = libra_core.LibraClient;
const LibraNetwork = libra_core.LibraNetwork;

async function checkTransaction(address, seq) {
const client = new LibraClient({ network: LibraNetwork.Testnet });
const transaction = await client.getAccountTransaction(address, seq);
console.log(transaction);
}

// 既存アカウントを使いたい場合は mnemonic や fromSecretKey() を使う
// const wallet = new LibraWallet({mnemonic: "your mnemonics"});
// const account = LibraAccount.fromSecretKey(secretKeyHexStr);
const wallet = new LibraWallet();
const account = wallet.newAccount();

checkTransaction(account.getAddress(), 0);
// => LibraSignedTransactionWithProof {
// signedTransaction: LibraSignedTransaction {
// transaction: LibraTransaction {
// program: [Object],
// gasContraint: [Object],
// expirationTime: [BigNumber],
// sendersAddress: [AccountAddress],
// sequenceNumber: [BigNumber]
// },
// publicKey: Uint8Array [
// 174, 252, (略), 39, 25
// ],
// signature: Uint8Array [
// 24, 147, (略), 184, 11
// ]
// },
// proof: {
// wrappers_: { '1': [Object], '2': [Object] },
// messageId_: undefined,
// arrayIndexOffset_: -1,
// array: [ [Array], [Array] ],
// pivot_: 1.79xxxx57e+308,
// convertedPrimitiveFields_: {}
// },
// events: [
// LibraTransactionEvent {
// data: [Uint8Array],
// sequenceNumber: [BigNumber],
// address: [AccountAddress],
// path: [Uint8Array]
// },
// LibraTransactionEvent {
// data: [Uint8Array],
// sequenceNumber: [BigNumber],
// address: [AccountAddress],
// path: [Uint8Array]
// }
// ]
// }

まとめ

  • libra-core v1.0.5 を色々と試した
  • 送金に関する Issue/PR #33 があるので注意
  • バージョンアップで大きく変わるかもしれないので、この記事は参考程度にとどめておいた方が良いと思う

参考文献

関連記事

この記事が参考になった方
ここここからチャージや購入してくれると嬉しいです(ブログ主へのプレゼントではなく、ご自身へのチャージ)
欲しいもの / Wish list