Update 2024/5/31

sdk v3.2.2 以降のtips

はじめに

v3.2.2でいくつかのクラスや関数が追加されました。これらは分かりやすく、使いやすくすることを目的としていますが、従来の記述方法でも問題ありませんのでご安心ください。
本記事は主にv3.2.2に追加されたクラス、関数について解説しており、すでに Symbol に対しての知識がある方を対象にしています。
Symbol の学習は以下をおすすめします。

インポート

import {
  SymbolFacade,
  descriptors,
  models,
  generateMosaicId,
  generateNamespaceId,
  metadataGenerateKey,
} from 'symbol-sdk/symbol'
import { PrivateKey, PublicKey, utils } from 'symbol-sdk'

const facade = new SymbolFacade('testnet')

アカウント

新規作成

const aliceAccount = facade.createAccount(PrivateKey.random())

インポート

const bobAccount = facade.createAccount(
  new PrivateKey('5DB8324E7EB83E7665D500B014283260EF312139034E86DFB7EE736503EA****')
)

パブリックアカウント

const bobPublicAccount = facade.createPublicAccount(
  new PublicKey('4C4BD7F8E1E1AC61DB817089F9416A7EDC18339F06CDC851495B271533FAD13B')
)

アドレス出力

const aliceAddress = aliceAccount.address.toString()
const bobAddress = bobPublicAccount.address.toString()

アカウントによる暗号化

const encoded = aliceAccount
  .messageEncoder()
  .encode(bobPublicAccount.publicKey, new TextEncoder().encode('hello, symbol!'))

復号

const decoded = bobAccount.messageEncoder().tryDecode(alicePublicAccount.publicKey, encoded)
  • encodedUint8Arrayです。受け渡しの際は、hexString に変換してください。また復号の際はUint8Arrayに変換してください。
const encodedHex = utils.uint8ToHex(encoded)
const encodedBytes = utils.hexToUint8(encodedHex)

トランザクション

v3.2.1 以前のコード

const transaction = facade.transactionFactory.create({
  type: 'transfer_transaction_v1',
  signerPublicKey: aliceAccount.keyPair.publicKey,
  recipientAddress: bobPublicAccount.address,
  mosaics: [
    {
      mosaicId: 0x72c0212e67a08bcen,
      amount: 1_000000n,
    },
  ],
  message: `\0Hello, Symbol!!`,
  fee: 1_000000n,
  new NetworkTimestamp(facade.network.fromDatetime(new Date())).addHours(2).timestamp,
})
TypeScriptにおいてcreate()の引数の型はobjectであるため、key に何を指定すればいいか分からない、value の型が分からないなどの問題がありました。
v3.2.2 以降のコードについて、TransferTransactionを元に説明します。

TransferTransaction

const transferTransactionDescripter = new descriptors.TransferTransactionV1Descriptor(
  bobPublicAccount.address, // or new Address('TA5LGYEWS6L2WYBQ75J2DGK7IOZHYVWFWRLOFWI'),
  [
    new descriptors.UnresolvedMosaicDescriptor(
      new models.UnresolvedMosaicId(0x72c0212e67a08bcen),
      new models.Amount(1_000000n)
    ),
  ],
  `\0Hello, Symbol!!`
)
const transferTransaction = facade.createTransactionFromTypedDescriptor(
  transferTransactionDescripter,
  aliceAccount.publicKey,
  100,
  3600
)

const signature = aliceAccount.signTransaction(transaction)
const payload = facade.transactionFactory.static.attachSignature(transaction, signature)
まず、全てのトランザクションはdescriptorsを使用してdescriptorを作成します。この時、引数の型は明確です。 続いてdescriptorを元にcreateTransactionFromTypedDescriptor()によってトランザクションを作成します。

createTransactionFromTypedDescriptor()の引数について

  1. descriptor
  2. 署名者の公開鍵
  3. 手数料乗数 トランザクションサイズに対して設定した乗数を元に手数料を決定します *1
  4. デッドライン、現在時刻からの秒数 *2
  5. optional: 連署者の数 *3
*1 サイズに対応して変動します。固定で手数料を設定したい場合は以下で対応可能です。
transferTransaction.fee = new models.Amount(0xffffffffn)
*2 new Date()にて取得した現在時刻を元にデッドラインを設定しています。多少のズレが生じる可能性があるためネットワークから取得するなど厳格に設定したい場合などは以下で対応可能です。
transferTransaction.deadline = new models.Timestamp(0xffffffffn)
*3 アグリゲートトランザクションの項にて後述します

TransferTransaction Message について

Stringで設定できますが、生データの場合はUint8Arrayで渡してください。
なお、デスクトップウォレットやエクスプローラーなどの慣習で冒頭 1byte に 0 を追加することで平文メッセージと判断します。 同じく対応したい場合は\0を追加する以下が簡単です。
const message = `\0ここにテキスト`

Namespaceエイリアスから送信する場合

const address = Address.fromNamespaceId(
  new models.NamespaceId(generateNamespaceId('namespace_name')),
  facade.network.identifier
)

AggregateTranasction

alice と bob によるモザイクの交換を例にした簡単なサンプル

Transaction構築

const transferTransactionDescripterFromAlice = new descriptors.TransferTransactionV1Descriptor(
  bobPublicAccount.address,
  [
    new descriptors.UnresolvedMosaicDescriptor(
      new models.UnresolvedMosaicId(0x72c0212e67a08bcen),
      new models.Amount(1_000000n)
    ),
  ],
  `\0from alice`
)

const transferTransactionDescripterFromBob = new descriptors.TransferTransactionV1Descriptor(
  aliceAccount.address,
  [
    new descriptors.UnresolvedMosaicDescriptor(
      new models.UnresolvedMosaicId(0x72c0212e67a08bcen),
      new models.Amount(1_000000n)
    ),
  ],
  `\0from bob`
)

const embeddedTransactions = [
  facade.createEmbeddedTransactionFromTypedDescriptor(transferTransactionDescripterFromAlice, aliceAccount.publicKey),
  facade.createEmbeddedTransactionFromTypedDescriptor(transferTransactionDescripterFromBob, bobPublicAccount.publicKey),
]

const aggregateTransactionDescriptor = new descriptors.AggregateCompleteTransactionV2Descriptor(
  facade.static.hashEmbeddedTransactions(embeddedTransactions),
  embeddedTransactions
)

const aggregateTransaction = facade.createTransactionFromTypedDescriptor(
  aggregateTransactionDescriptor,
  aliceAccount.publicKey,
  100,
  3600,
  1 // ここで連署者の数を設定する
)

const signature = aliceAccount.signTransaction(aggregateTransaction)
const payload = facade.transactionFactory.static.attachSignature(aggregateTransaction, signature)

連署

ボブがペイロードを受取、連署し、そのままアナウンス
const aggregateTransactionDeserealized = facade.transactionFactory.static.deserialize(
  aggregateTransaction.serialize()
) as models.AggregateCompleteTransactionV2

const bobCosignature = bobAccount.cosignTransaction(aggregateTransactionDeserealized)
aggregateTransactionDeserealized.cosignatures.push(bobCosignature)

const payload = JSON.stringify({
  payload: utils.uint8ToHex(aggregateTransactionDeserealized.serialize()),
})
アグリゲートトランザクションの場合は、インナートランザクションのdescriptorをそれぞれ作成し、AggregateCompleteTransactionV2Descriptor()の引数とします。
連署者の人数分、トランザクションのサイズが変動するためcreateTransactionFromTypedDescriptor()の第五引数で連署者の人数を設定します。

MosaicDefinitionTransaction, MosaicSupplyChangeTransaction

これらトランザクションはアグリゲートトランザクションとすることが多いと思うので、アグリゲートにてコードを記述します。(それぞれシングルトランザクションでも可能です)
const mosaicNonce = 0x1111
const mosaicId = generateMosaicId(aliceAccount.address, mosaicNonce)
const mosaicDefinitionDescriptor = new descriptors.MosaicDefinitionTransactionV1Descriptor(
  new models.MosaicId(mosaicId),
  new models.BlockDuration(0n),
  new models.MosaicNonce(mosaicNonce),
  new models.MosaicFlags(models.MosaicFlags.TRANSFERABLE.value + models.MosaicFlags.SUPPLY_MUTABLE.value),
  0
)
const mosaicSupplyChangeDescriptor = new descriptors.MosaicSupplyChangeTransactionV1Descriptor(
  new models.UnresolvedMosaicId(mosaicId),
  new models.Amount(1000n),
  models.MosaicSupplyChangeAction.INCREASE
)

const mosaicEmbeddedTransactions = [
  facade.createEmbeddedTransactionFromTypedDescriptor(mosaicDefinitionDescriptor, aliceAccount.publicKey),
  facade.createEmbeddedTransactionFromTypedDescriptor(mosaicSupplyChangeDescriptor, aliceAccount.publicKey),
]

const mosaicAggregateDescriptor = new descriptors.AggregateCompleteTransactionV2Descriptor(
  facade.static.hashEmbeddedTransactions(mosaicEmbeddedTransactions),
  mosaicEmbeddedTransactions
)

const mosaicAggregateTransaction = facade.createTransactionFromTypedDescriptor(
  mosaicAggregateDescriptor,
  aliceAccount.publicKey,
  100,
  3600
)

const signature = aliceAccount.signTransaction(mosaicAggregateTransaction)
const payload = facade.transactionFactory.static.attachSignature(mosaicAggregateTransaction, signature)

AccountMetadataTransaction

MetadataTransaction は署名者が一人であっても SourceAddress と TargetAddress が別のケースがあるためアグリゲートにする必要があります。
const metadata = new TextEncoder().encode('metadata')
const accountMetadataTransactionDescriptor = new descriptors.AccountMetadataTransactionV1Descriptor(
  aliceAccount.address,
  metadataGenerateKey('test'),
  metadata.byteLength,
  metadata
)
const accountMetadataEmbeddedTransactions = [
  facade.createEmbeddedTransactionFromTypedDescriptor(accountMetadataTransactionDescriptor, aliceAccount.publicKey),
]
const accountMetadataAggregateDescriptor = new descriptors.AggregateCompleteTransactionV2Descriptor(
  facade.static.hashEmbeddedTransactions(accountMetadataEmbeddedTransactions),
  accountMetadataEmbeddedTransactions
)
const accountMetadataAggregateTransaction = facade.createTransactionFromTypedDescriptor(
  accountMetadataAggregateDescriptor,
  aliceAccount.publicKey,
  100,
  3600
)

const signature = aliceAccount.signTransaction(accountMetadataAggregateTransaction)
const payload = facade.transactionFactory.static.attachSignature(accountMetadataAggregateTransaction, signature)
key の引数は BigInt ですが、String を BigInt にするためのmetadataGenerateKey()という関数が新たに作成されました。メタデータは String 型でも受付けますが、Uint8Array でのサイズを指定する必要があるため、上記のようにUint8Arrayに変換して渡すことをおすすめします。

NamespaceRegistrationTransaction, AddressAliasTransaction

Namespace を登録し、アドレスのエイリアスとして設定します。これもアグリゲートで行います。 今回は ROOT ネームスペースをエイリアスとします。
const namespaceId = new models.NamespaceId(generateNamespaceId('alice_namespace'))
const namespaceRegistrationTransactionDescriptor = new descriptors.NamespaceRegistrationTransactionV1Descriptor(
  namespaceId,
  models.NamespaceRegistrationType.ROOT,
  new models.BlockDuration(0x000f0000n),
  undefined,
  'alice_namespace'
)
const namespaceAddressAliasTransactionDescriptor = new descriptors.AddressAliasTransactionV1Descriptor(
  namespaceId,
  aliceAccount.address,
  models.AliasAction.LINK
)
const namespaceEmbeddedTransactions = [
  facade.createEmbeddedTransactionFromTypedDescriptor(
    namespaceRegistrationTransactionDescriptor,
    aliceAccount.publicKey
  ),
  facade.createEmbeddedTransactionFromTypedDescriptor(
    namespaceAddressAliasTransactionDescriptor,
    aliceAccount.publicKey
  ),
]
const namespaceAggregateDescriptor = new descriptors.AggregateCompleteTransactionV2Descriptor(
  facade.static.hashEmbeddedTransactions(namespaceEmbeddedTransactions),
  namespaceEmbeddedTransactions
)
const namespaceAggregateTransaction = facade.createTransactionFromTypedDescriptor(
  namespaceAggregateDescriptor,
  aliceAccount.publicKey,
  100,
  3600
)
const signature = aliceAccount.signTransaction(namespaceAggregateTransaction)
const payload = facade.transactionFactory.static.attachSignature(namespaceAggregateTransaction, signature)
一旦、トランザクションについてはここまでとします。今後、需要があるなど必要があれば追記していきます。

アドレス from REST API

Address クラスへの変換

REST からアドレスを取得すると、hex(16 進数文字列)になります。Address クラスへの変換。
ex) 987E8740D1B46E53274F79F447F8EABE628EAB33D1134837
const address = Address.fromDecodedAddressHexString('987E8740D1B46E53274F79F447F8EABE628EAB33D1134837')
また、ネームスペースがアドレスにエイリアスされている場合は以下でネームスペース ID を取得できます。 されていない場合はundefind
const namespaceAddress = Address.fromDecodedAddressHexString('99D63C374D96D7ACAE000000000000000000000000000000')
const namespaceId = namespaceAddress.toNamespaceId()

ネームスペース ID からエイリアスされているアドレスの取得

fetch(`http://NODE:3000/namespaces/${namespaceId?.toString().substring(2)}`)
  .then((response) => response.json())
  .then((data) => {
    console.log('Success:', data)
  })
  .catch((error) => {
    console.error('Error:', error)
  })

ネームスペース ID からエイリアス名の取得

const requestParameters = {
  namespaceIds: [namespaceId?.toString().substring(2)],
}
const headerParameters = {
  'Content-Type': 'application/json',
}
const body = JSON.stringify(requestParameters)
fetch('http://NODE:3000/namespaces/names', {
  method: 'POST',
  headers: headerParameters,
  body: body,
})
  .then((response) => response.json())
  .then((data) => {
    console.log('Success:', data)
  })
  .catch((error) => {
    console.error('Error:', error)
  })

ニーモニック

ニーモニックを元にルートアカウント、子アカウントの sdk での作成方法。BIP44 に則っています。
const bip32 = new Bip32()
const mnemonic = bip32.random()
console.log(mnemonic)

親アカウント

const bip32Node = bip32.fromMnemonic(mnemonic, 'password')
console.log(bip32Node.privateKey)
なお、デスクトップウォレットで作成したアカウントを再現したい場合はパスフレーズを空にしてください。

子アカウント

Bip44 に則ってnumber[]を渡します。三番目がアカウントなので複数取得する場合は以下のようにします。
for (let i = 0; i < 10; i++) {
  const child = bip32Node.derivePath([44, 4343, i, 0, 0]).privateKey
  console.log(`child${i}: ${child}`)
}

トランザクションのデシリアライズ

ペイロードを元にトランザクションをデシリアライズ可能です。 以前は models のみでしたが同じ関数が facade からも使えます 引数の形は Uint8Array なのでtransactionserialize()して使う、もしくはペイロードを hex で受け取った場合は、utils.hexToUint8()Uint8Arrayに変換してください。
models.TransactionFactory.deserialize(transaction.serialize())
facade.transactionFactory.static.deserialize(transaction.serialize())

const uint8Payload = utils.hexToUint8(payload)
models 経由では引数の形が any なので厳格に使用したい場合は facade 経由が良いかもしれません。

News
Community
Docs
Contact