Skip to content

Instantly share code, notes, and snippets.

@YasunoriMATSUOKA
Last active November 8, 2022 08:39
Show Gist options
  • Save YasunoriMATSUOKA/432acf3eeca83897f55a549d2d99e34a to your computer and use it in GitHub Desktop.
Save YasunoriMATSUOKA/432acf3eeca83897f55a549d2d99e34a to your computer and use it in GitHub Desktop.
Send tx with CDN of @nemtus/symbol-sdk-typescript and @nemtus/symbol-sdk-openapi-generator-typescript-axios
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/@nemtus/symbol-sdk-openapi-generator-typescript-axios@latest/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@nemtus/symbol-sdk-typescript@latest/index.min.js"></script>
</head>
<body>
<script>
const restApiClient = window.symbolSdkOpenAPIGeneratorTypeScriptAxios;
const symbolSdk = window.symbolSdk.default;
const NODE_DOMAIN = "001-sai-dual.symboltest.net";
(async () => {
// epochAdjustment, networkCurrencyMosaicIdの取得のためNetworkRoutesApi.getNetworkPropertiesを呼び出す
const configurationParameters = {
basePath: `https://${NODE_DOMAIN}:3001`,
};
const configuration = new restApiClient.Configuration(
configurationParameters
);
const networkRoutesApi = new restApiClient.NetworkRoutesApi(
configuration
);
const networkPropertiesDTO = (
await networkRoutesApi.getNetworkProperties()
).data;
// epochAdjustmentのレスポンス値は文字列でsが末尾に含まれるため除去してnumberに変換する
const epochAdjustmentOriginal =
networkPropertiesDTO.network.epochAdjustment;
if (!epochAdjustmentOriginal) {
throw Error("epochAdjustment is not found");
}
const epochAdjustment = parseInt(
epochAdjustmentOriginal.replace(/s/g, "")
);
// networkCurrencyMosaicIdのレスポンス値はhex文字列で途中に'が含まれるため除去してBigIntに変換する
const networkCurrencyMosaicIdOriginal =
networkPropertiesDTO.chain.currencyMosaicId;
if (!networkCurrencyMosaicIdOriginal) {
throw Error("networkCurrencyMosaicId is not found");
}
const networkCurrencyMosaicId = BigInt(
networkCurrencyMosaicIdOriginal.replace(/'/g, "")
);
// facadeの中に指定するtestnet等のネットワーク名を取得するためNetworkRoutesApi.getNetworkTypeを呼び出す
const networkTypeDTO = (await networkRoutesApi.getNetworkType()).data;
if (!networkTypeDTO) {
throw Error("networkType is not found");
}
const networkName = networkTypeDTO.name;
// ネットワーク名を指定してSDKを初期化
const facade = new symbolSdk.facade.SymbolFacade(networkName);
// トランザクションを送信するアカウント関連データを作成
const privateKey = new symbolSdk.PrivateKey(
"PUT_YOUR_PRIVATE_KEY_HERE"
);
const keyPair = new symbolSdk.symbol.KeyPair(privateKey);
const signerPublicKeyString = keyPair.publicKey.toString();
const signerAddressString = facade.network
.publicKeyToAddress(keyPair.publicKey)
.toString();
// deadlineの計算(2時間で設定しているが変更可能、ただし遠すぎるとエラーになる。5~6時間くらいにテストネットでは閾値がある?)
const now = Date.now();
const deadline = BigInt(
now - epochAdjustment * 1000 + 2 * 60 * 60 * 1000
);
// 送信先アドレス ... Faucetのアドレスをここでは指定
const recipientAddressString =
"TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY";
// トランザクションのデータ生成
const transaction = facade.transactionFactory.create({
type: "transfer_transaction",
signerPublicKey: signerPublicKeyString,
deadline,
recipientAddress: recipientAddressString,
mosaics: [{ mosaicId: networkCurrencyMosaicId, amount: 1000000n }],
});
// 手数料設定 ... 送信先ノードの設定によるが100なら基本的に足りないことはないと思う
const feeMultiplier = 100;
transaction.fee.value = BigInt(transaction.size * feeMultiplier);
// 署名
const signature = facade.signTransaction(keyPair, transaction);
transaction.signature = new symbolSdk.symbol.Signature(signature.bytes);
// 各ネットワーク固有のgenerationHashSeedを設定
transaction.network.generationHashSeed = facade.network;
// トランザクションのハッシュを計算 ... トランザクションの承認状態を後でWebSocketで確認する時などに必要
const hash = facade.hashTransaction(transaction);
console.log(hash.toString());
console.log(
`https://testnet.symbol.fyi/transactions/${hash.toString()}`
);
// トランザクション送信時にはこのデータを使う必要あり
const transactionPayload =
facade.transactionFactory.constructor.attachSignature(
transaction,
signature
);
// 1 confirmation以外の場合の設定
const confirmationHeight = 6; // 6confで確認と見なす場合
let transactionHeight = 0;
let blockHeight = 0;
let finalizedBlockHeight = 0;
// WebSocketでトランザクション送信時の各種イベントに応じた処理を事前定義しておく必要がある
const ws = new WebSocket(`wss://${NODE_DOMAIN}:3001/ws`);
ws.onopen = () => {
console.log("connection open");
};
ws.onclose = () => {
console.log("connection closed");
};
ws.onmessage = (msg) => {
const res = JSON.parse(msg.data);
if ("uid" in res) {
console.log(`uid : ${res.uid}`);
// ターゲットアドレスのトランザクションが未承認状態になったのを監視
const unconfirmedBody = `{"uid": "${res.uid}", "subscribe": "unconfirmedAdded/${recipientAddressString}"}`;
console.log(unconfirmedBody);
ws.send(unconfirmedBody);
// ターゲットアドレスのトランザクションが承認されるの監視
const confirmedBody = `{"uid": "${res.uid}", "subscribe": "confirmedAdded/${recipientAddressString}"}`;
console.log(confirmedBody);
ws.send(confirmedBody);
// ターゲットアドレスのトランザクションがエラーになったのを監視
const statusBody = `{"uid": "${res.uid}", "subscribe": "status/${recipientAddressString}"}`;
console.log(statusBody);
ws.send(statusBody);
// 新しいブロックを監視
const blockBody = `{"uid": "${res.uid}", "subscribe": "block"}`;
console.log(blockBody);
ws.send(blockBody);
// ファイナライズされたブロックを監視
const finalizedBlockBody = `{"uid": "${res.uid}", "subscribe": "finalizedBlock"}`;
console.log(finalizedBlockBody);
ws.send(finalizedBlockBody);
}
// トランザクションが未承認になったときに発火
if (
res.topic === `unconfirmedAdded/${recipientAddressString}` &&
res.data.meta.hash === hash.toString()
) {
console.log("transaction unconfirmed");
}
// トランザクションが承認されたときに発火
if (
res.topic === `confirmedAdded/${recipientAddressString}` &&
res.data.meta.hash === hash.toString()
) {
console.log("transaction confirmed");
transactionHeight = parseInt(res.data.meta.height);
}
// ブロック生成時に発火
if (res.topic === `block`) {
console.log("block");
blockHeight = parseInt(res.data.block.height);
}
// ブロックのファイナライズ時に発火
if (res.topic === `finalizedBlock`) {
console.log("finalizedBlock");
console.log(res);
finalizedBlockHeight = parseInt(res.data.height);
}
// トランザクションがエラーになったときに発火
if (
res.topic === `status/${recipientAddressString}` &&
res.data.hash === hash.toString()
) {
console.log(res.data.code);
ws.close();
} else {
console.log(res);
}
// confirmationHeightブロック後に監視終了
if (
transactionHeight !== 0 &&
transactionHeight + confirmationHeight - 1 <= blockHeight
) {
console.log(
`${confirmationHeight} blocks confirmed. transactionHeight is ${transactionHeight} blockHeight is ${blockHeight}.`
);
ws.close();
} else {
console.log(
`wait for ${confirmationHeight} blocks. transactionHeight is ${transactionHeight} blockHeight is ${blockHeight}.`
);
}
// finalizedBlockHeightが対象ブロックを追い越した後に監視終了
if (
transactionHeight !== 0 &&
transactionHeight <= finalizedBlockHeight
) {
console.log(
`${finalizedBlockHeight} block finalized. transactionHeight is ${transactionHeight} blockHeight is ${blockHeight}.`
);
ws.close();
} else {
console.log(
`wait for finalized block. transactionHeight is ${transactionHeight} blockHeight is ${blockHeight}.`
);
}
};
// トランザクションのアナウンス実行
try {
const transactionRoutesApi = new restApiClient.TransactionRoutesApi(
configuration
);
console.log(transactionPayload);
const response = await transactionRoutesApi.announceTransaction({
transactionPayload,
});
console.log(response.data);
} catch (err) {
console.error(err);
}
})();
</script>
</body>
</html>
@YasunoriMATSUOKA
Copy link
Author

最後ブロック数経過した後、対象のトランザクションハッシュがブロックチェーンに刻まれているかREST APIたたいて確認すべきではありそう。

@YasunoriMATSUOKA
Copy link
Author

フォーク元の変更に追従した結果、Revision 2のように若干記述方法が変わったことに注意。メインネットでのアップグレード及びテストネットのリセット後に対応済の場合はRevision 2の書き方をする必要がある。もし古いテストネットを使う場合は古いバージョンのSDKを使ってRevision 1の書き方をする必要があると思う。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment