前準備
変更しない値を一括で記述しておきます。アカウントはコードで作成したものでもウォレットで作成した物、どちらでもかまいません。
import { Network } from 'symbol-sdk/symbol';
export class SymConst {
/** RestGatewayURL */
static readonly REST_GATEWAY_URL = 'https://sym-test-01.opening-line.jp:3001';
/** ネットワーク識別 */
static readonly NETWORK_IDENTIFIER = Network.TESTNET.name;
/** カレントモザイクID */
static readonly CURRENCY_MOSAIC_ID = BigInt('0x72C0212E67A08BCE');
/** アリス秘密鍵 */
static readonly ALICE_PRIVATE_KEY =
'F10B0***********************************************************';
/** ボブ秘密鍵 */
static readonly BOB_PRIVATE_KEY =
'6EBF9***********************************************************';
/** ボブ公開鍵 */
static readonly BOB_PUBLIC_KEY =
'94EC711522B4B32A1B6A6ED61D86D1E3EE11AFB9B912A17F8983EED3808819FD';
/** キャロル公開鍵 */
static readonly CAROL_PUBLIC_KEY =
'249B8ADE64EFF216D43BB655EF41DC1D7B8DDF96BD655749FFAF78BE3ACE7D77';
}
トランザクション
簡単なトランスファートランザクションの発行です。
アカウント生成
アリスからボブへの転送を考えているので、アリスは秘密鍵からアカウント生成します。ボブは公開鍵からのアドレスを生成します。
// faced生成
const facade = new SymbolFacade(SymConst.NETWORK_IDENTIFIER);
// 送信者:アリス
// 秘密鍵からアリスのキーペア生成
const aliceKeyPair = new KeyPair(new PrivateKey(SymConst.ALICE_PRIVATE_KEY));
// 受信者:ボブ
// 文字列公開鍵からPublicKey生成
const bobPublicKey = new PublicKey(
Uint8Array.from(Buffer.from(SymConst.BOB_PUBLIC_KEY, 'hex'))
);
// ボブのアドレスを取得
const bobAddress = new Address(
facade.network.publicKeyToAddress(bobPublicKey)
);
トランザクションの生成
デッドラインは、承認され無かった場合の待ち時間で、未承認のまま期限が過ぎると取り消されます。最大6時間となっています。
転送モザイクは、配列になっていて複数モザイクを同時に転送できます。一度に転送できる上限があるのかは分かりませんが、1アカウントにつき1,000モザイクまでしか持てないので、1,000が上限になってくるかと思います。
メッセージは、平文(メッセージタイプ:0x00
)で転送します。メッセージタイプの1バイトを含む1024バイトまで格納できます。
// デッドライン設定
const deadlineTimestamp = facade.now().addHours(2).timestamp;
// 転送モザイク設定
const sendMosaics = [
{ mosaicId: SymConst.CURRENCY_MOSAIC_ID, amount: 1_000000n },
];
// 平文メッセージ
const messageData = new Uint8Array([
0x00,
...new TextEncoder().encode('Hello, Symbol!!'),
]);
v3.2.0ではNetworkTimestamp
にバグがあり、タイムスタンプが正の整数で返ってきません。少し修正が必要です。
if (referenceDatetime < this.epoch)
throw RangeError('timestamp cannot be before epoch');
- const subtractDates = (lhs, rhs) => (lhs - rhs);
- return (subtractDates(referenceDatetime, this.epoch) / this.timeUnits) | 0;
+ const differenceMilliseconds = referenceDatetime.getTime() - this.epoch.getTime();
+ return Math.trunc(differenceMilliseconds / this.timeUnits);
}
}
上記の値をセットして転送トランザクションを生成します。最後にトランザクション発行の手数料を設定しています。トランザクションサイズに100を掛ければ、大抵のノードが承認します。細かい手数料は/network/fees/transaction
を参照してください。
// 転送トランザクション生成
const transferTx = facade.transactionFactory.create({
type: 'transfer_transaction_v1',
signerPublicKey: aliceKeyPair.publicKey,
recipientAddress: bobAddress,
deadline: deadlineTimestamp,
mosaics: sendMosaics,
message: messageData,
});
transferTx.fee = new models.Amount(BigInt(transferTx.size * 100)); // 手数料
署名
アリスのキーペアで署名を生成して、トランザクションと合わせます。トランザクションハッシュもこのタイミングで取得出来ます。今回、特に用途はないです。
// アリス署名
const transferTxSignature = facade.signTransaction(aliceKeyPair, transferTx);
const transferTxPayloadJson = SymbolTransactionFactory.attachSignature(
transferTx,
transferTxSignature
);
const transferTxHash = facade.hashTransaction(transferTx);
アナウンス
RESTにアナウンスします。一応レスポンスはありますが、「ノードに送信できたよ!」っていうレスポンスなので、ノード側でリジェクトされている可能性もあります。別途確認のコードを追加した方が良いです(今回は省略します)。アナウンス先のノードを接続先にしたウォレットを立ち上げておくと、エラーの場合、通知が出るのでテストする際は便利です。
// RESTにアナウンス
const transactionsResponse = await fetch(
new URL('/transactions', SymConst.REST_GATEWAY_URL),
{
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: transferTxPayloadJson,
}
);
// アナウンス結果を表示
const transactionsResponseJson = await transactionsResponse.json();
console.log(`restResponse : ${transactionsResponseJson.message}`);
コード例
import {
Address,
KeyPair,
NetworkTimestamp,
SymbolFacade,
SymbolTransactionFactory,
models,
} from 'symbol-sdk/symbol';
import { SymConst } from './SymConst.js';
import { PrivateKey, PublicKey } from 'symbol-sdk';
try {
// faced生成
const facade = new SymbolFacade(SymConst.NETWORK_IDENTIFIER);
// 送信者:アリス
// 秘密鍵からアリスのキーペア生成
const aliceKeyPair = new KeyPair(new PrivateKey(SymConst.ALICE_PRIVATE_KEY));
// 受信者:ボブ
// 文字列公開鍵からPublicKey生成
const bobPublicKey = new PublicKey(
Uint8Array.from(Buffer.from(SymConst.BOB_PUBLIC_KEY, 'hex'))
);
// ボブのアドレスを取得
const bobAddress = new Address(
facade.network.publicKeyToAddress(bobPublicKey)
);
// デッドライン設定
const deadlineTimestamp = facade.now().addHours(2).timestamp;
// 転送モザイク設定
const sendMosaics = [
{ mosaicId: SymConst.CURRENCY_MOSAIC_ID, amount: 1_000000n },
];
// 平文メッセージ
const messageData = new Uint8Array([
0x00,
...new TextEncoder().encode('Hello, Symbol!!'),
]);
// 転送トランザクション生成
const transferTx = facade.transactionFactory.create({
type: 'transfer_transaction_v1',
signerPublicKey: aliceKeyPair.publicKey,
recipientAddress: bobAddress,
deadline: deadlineTimestamp,
mosaics: sendMosaics,
message: messageData,
});
transferTx.fee = new models.Amount(BigInt(transferTx.size * 100)); // 手数料
// 未署名トランザクションを表示
console.log(
`unsignedTx : ${Buffer.from(transferTx.serialize())
.toString('hex')
.toUpperCase()}`
);
// アリス署名
const transferTxSignature = facade.signTransaction(aliceKeyPair, transferTx);
const transferTxPayloadJson = SymbolTransactionFactory.attachSignature(
transferTx,
transferTxSignature
);
const transferTxHash = facade.hashTransaction(transferTx);
// 署名トランザクションを表示
console.log(
`signedTx : ${Buffer.from(transferTx.serialize())
.toString('hex')
.toUpperCase()}`
);
// トランザクションハッシュを表示
console.log(`txHash : ${transferTxHash}`);
// RESTにアナウンス
const transactionsResponse = await fetch(
new URL('/transactions', SymConst.REST_GATEWAY_URL),
{
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: transferTxPayloadJson,
}
);
// アナウンス結果を表示
const transactionsResponseJson = await transactionsResponse.json();
console.log(`restResponse : ${transactionsResponseJson.message}`);
} catch (e) {
console.error(e);
}
メッセージを暗号化
メッセージを暗号化することも出来ます。暗号化用のクラスが用意されていて、アリスの秘密鍵とボブの公開鍵で暗号化。自動的にメッセージタイプ0x01
が付与されます。ただ、v2の二重16進数化バグがあるので、バグが修正されたv3で暗号化した文章は現在のウォレットで復号できません。
const message = 'Hello Symbol!!';
const aliceMsgEncoder = new MessageEncoder(aliceKeyPair);
const messageData = aliceMsgEncoder.encode(
bobPublicKey,
new TextEncoder().encode(message)
);
メッセージにバイナリデータを格納
決まりはないのですが、メッセージタイプ:0xFF
をバイナリデータとして扱います。メッセージタイプは特に決まりが無く、各アプリでどう扱うかに任せられます。
const messageData = new Uint8Array([
0xff,
...new TextEncoder().encode("Hello, Symbol!!"),
]);
以下は、Zero埋めした1023バイトをメッセージに入れたトランザクションです。