2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

nem / symbolAdvent Calendar 2024

Day 23

Windows と TypeScript ではじめる Symbol 通信 13【アカウント情報】

Last updated at Posted at 2024-12-22

ノード情報の取得

ノードへリクエスト

アカウント情報を取得するパケットタイプは、0x443です。

Catapult.ts
/** パケットタイプ */
private PacketType = {
  PULL_BLOCK: 0x0_04,
  CHAIN_STATISTICS: 0x0_05,
  PULL_BLOCKS: 0x0_08,
  PUSH_TRANSACTIONS: 0x0_09,
  PUSH_PARTIAL_TRANSACTIONS: 0x1_00,
  NODE_DISCOVERY_PULL_PING: 0x1_11,
  TIME_SYNC_NETWORK_TIME: 0x1_20,
  FINALIZATION_STATISTICS: 0x1_32,
  UNLOCKED_ACCOUNTS: 0x3_04,
  ACCOUNT_INFOS: 0x4_43,
  METADATA_INFOS: 0x4_44,
}

async getAccountInfos(addresses: Uint8Array[]) {
  const accountInfos: AccountInfo[] = []
  try {
    const payload = Buffer.concat(addresses)
    const socketData = await this.request(this.PacketType.ACCOUNT_INFOS, payload)
    if (socketData) {
      let offset = 0
      do {
        const accountInfo = AccountInfo.deserialize(socketData.slice(offset))
        accountInfos.push(accountInfo)
        offset += accountInfo.accountInfoSize
      } while (offset < socketData.length)
    }
  } catch (e) {
    if (e instanceof Error) console.error(e.message)
    else console.error(e)
  } finally {
    this.close()
  }
  return accountInfos
}

レスポンスデータの解析

シリアライズされたデータが返ってくるので、デシリアライズします。

src/models/AccountInfo.ts
import { Address, models } from 'symbol-sdk/symbol'
import { PacketBuffer } from '../PacketBuffer.js'

// AccountType          Unlinked: 0x00, Main: 0x01, Remote: 0x02, Remote_Unlinked: 0x03
// AccountStateFormat   Regular(通常のアカウント): 0, HighValue(収穫可能なアカウント): 1
// AccountKeyTypeFlags  Unset: 0x00, Linked: 0x01, Node: 0x02, VRF: 0x04

export class AccountInfo {
  constructor(
    public accountInfoSize: number,
    public accountDataSize: number,
    public accountInfoKey: string,
    public version: number,
    public address: Address,
    public addressHeight: models.Height,
    public publicKey: models.PublicKey,
    public publicKeyHeight: models.Height,
    public accountType: number,
    public accountStateFormat: number,
    public accountKeyTypeFlags: number,
    public votingPublickeysCount: number,
    public linkedPublicKey: models.PublicKey | undefined,
    public nodePublicKey: models.PublicKey | undefined,
    public vrfPublicKey: models.PublicKey | undefined,
    public votingKeys: VotingKey[],
    public importanceScore: bigint | undefined,
    public importanceHeight: models.Height | undefined,
    public activityBuckets: ActivityBucket[],
    public balancesCount: number,
    public balances: models.Mosaic[]
  ) {}

  static deserialize(payload: Uint8Array) {
    const packetBuf = new PacketBuffer(Buffer.from(payload))
    const accountInfoSize = packetBuf.readUInt32LE()
    const accountDataSize = packetBuf.readUInt32LE()
    const accountInfoKey = packetBuf.readHexString(24).toUpperCase()
    const version = packetBuf.readUInt16LE()
    const address = Address.fromDecodedAddressHexString(packetBuf.readHexString(24))
    const addressHeight = new models.Height(packetBuf.readBigUInt64LE())
    const publicKey = new models.PublicKey(packetBuf.readHexString(32))
    const publicKeyHeight = new models.Height(packetBuf.readBigUInt64LE())
    const accountType = packetBuf.readUInt8()
    const accountStateFormat = packetBuf.readUInt8()
    const accountKeyTypeFlags = packetBuf.readUInt8()
    const votingKeysCount = packetBuf.readUInt8()
    let linkedPublicKey
    if (accountKeyTypeFlags & 0x01) {
      linkedPublicKey = new models.PublicKey(packetBuf.readHexString(32))
    }
    let nodePublicKey
    if (accountKeyTypeFlags & 0x02) {
      nodePublicKey = new models.PublicKey(packetBuf.readHexString(32))
    }
    let vrfPublicKey
    if (accountKeyTypeFlags & 0x04) {
      vrfPublicKey = new models.PublicKey(packetBuf.readHexString(32))
    }
    const votingKeys: VotingKey[] = []
    for (let i = 0; i < votingKeysCount; i++) {
      const votingPublicKey = new models.PublicKey(packetBuf.readHexString(32))
      const startEpoch = packetBuf.readUInt32LE()
      const endEpoch = packetBuf.readUInt32LE()
      const votingKey = new VotingKey(votingPublicKey, startEpoch, endEpoch)
      votingKeys.push(votingKey)
    }
    let importanceScore
    let importanceHeight
    const heightActivityBuckets: ActivityBucket[] = []
    if (accountStateFormat === 1) {
      importanceScore = packetBuf.readBigUInt64LE()
      importanceHeight = new models.Height(packetBuf.readBigUInt64LE())
      for (let i = 0; i < 5; i++) {
        const startHeight = new models.Height(packetBuf.readBigUInt64LE())
        const totalFeesPaid = new models.Amount(packetBuf.readBigUInt64LE())
        const beneficiaryCount = packetBuf.readUInt32LE()
        const rawScore = packetBuf.readBigUInt64LE()
        const activityBucket = new ActivityBucket(
          startHeight,
          totalFeesPaid,
          beneficiaryCount,
          rawScore
        )
        heightActivityBuckets.push(activityBucket)
      }
    }
    const balancesCount = packetBuf.readUInt16LE()
    const balances: models.Mosaic[] = []
    for (let i = 0; i < balancesCount; i++) {
      const mosaic = new models.Mosaic()
      mosaic.mosaicId = new models.MosaicId(packetBuf.readBigUInt64LE())
      mosaic.amount = new models.Amount(packetBuf.readBigUInt64LE())
      balances.push(mosaic)
    }

    return new AccountInfo(
      accountInfoSize,
      accountDataSize,
      accountInfoKey,
      version,
      address,
      addressHeight,
      publicKey,
      publicKeyHeight,
      accountType,
      accountStateFormat,
      accountKeyTypeFlags,
      votingKeysCount,
      linkedPublicKey,
      nodePublicKey,
      vrfPublicKey,
      votingKeys,
      importanceScore,
      importanceHeight,
      heightActivityBuckets,
      balancesCount,
      balances
    )
  }

  toJson() {
    const linkedPublicKey = this.linkedPublicKey
      ? { publicKey: this.linkedPublicKey?.toString() }
      : undefined
    const nodePublicKey = this.nodePublicKey
      ? { publicKey: this.nodePublicKey?.toString() }
      : undefined
    const vrfPublicKey = this.vrfPublicKey
      ? { publicKey: this.vrfPublicKey?.toString() }
      : undefined
    const votingKeys =
      this.votingKeys.length !== 0 ? this.votingKeys.map((v) => v.toJson()) : undefined
    return {
      version: this.version,
      address: this.address.toString(),
      addressHeight: this.addressHeight.value.toString(),
      publicKey: this.publicKey.toString(),
      publicKeyHeight: this.publicKeyHeight.value.toString(),
      accountType: this.accountType,
      supplementalPublicKeys: {
        linked: linkedPublicKey,
        node: nodePublicKey,
        vrf: vrfPublicKey,
        voting: votingKeys,
      },
      activityBuckets: this.activityBuckets.map((a) => a.toJson()),
      mosaics: this.balances.map((b) => b.toJson()),
      importance: this.importanceScore?.toString() ?? '0',
      importanceHeight: this.importanceHeight?.value.toString() ?? '0',
    }
  }
}

export class SupplementalPublicKeys {
  constructor(
    public linked: SupplementalPublicKey | undefined,
    public node: SupplementalPublicKey | undefined,
    public vrf: SupplementalPublicKey | undefined,
    public voting: VotingKey[]
  ) {}

  toJson() {
    return {
      linked: this.linked?.toString(),
      node: this.node?.toString(),
      vrf: this.vrf?.toString(),
      voting: this.voting.map((v) => v.toJson()),
    }
  }
}

export class SupplementalPublicKey {
  constructor(public publicKey: models.PublicKey) {}
  toJson() {
    return {
      publicKey: this.publicKey.toString(),
    }
  }
}

export class VotingKey {
  constructor(
    public publickey: models.PublicKey,
    public startEpoch: number,
    public endEpoch: number
  ) {}

  toJson() {
    return {
      publickey: this.publickey.toString(),
      startEpoch: this.startEpoch.toString(),
      endEpoch: this.endEpoch.toString(),
    }
  }
}

export class ActivityBucket {
  constructor(
    public startHeight: models.Height,
    public totalFeesPaid: models.Amount,
    public beneficiaryCount: number,
    public rawScore: bigint
  ) {}

  toJson() {
    return {
      startHeight: this.startHeight.value.toString(),
      totalFeesPaid: this.totalFeesPaid.value.toString(),
      beneficiaryCount: this.beneficiaryCount,
      rawScore: this.rawScore.toString(),
    }
  }
}

メインの作成

実行するコードを作成します。

src/mainAccountInfo.ts
import { Address } from 'symbol-sdk/symbol'
import { Catapult } from './Catapult.js'
import { utils } from 'symbol-sdk'

const addresses = [
  new Address('TADGQ7GDVIIOPD7TICWISNLARICML2TSFGEJWIA').bytes,
  new Address('TBZN46UIU5BFLJI46VB4JTHHCE5EN2RFLR7NX3A').bytes,
  new Address('TBJHAFYICU7LOTDROE7QVR6FN5O6KZYOAYLVSVA').bytes,
  new Address('TAX3CCCOQTC2JC5FA23DTUVIQKOMUO2NWFXAUXA').bytes,
  new Address('TATVSNP4N3IUP7VS57R7KSWZ7CKKX5U3YDPSOMQ').bytes,
]

const catapult = new Catapult(
  'cert/ca.crt.pem',
  'cert/node.crt.pem',
  'cert/node.key.pem',
  '127.0.0.1'
)
const accountInfos = await catapult.getAccountInfos(addresses)
for (const accountInfo of accountInfos) {
  console.log(JSON.stringify(accountInfo.toJson(), null, 2))
}

実行

yarn tsx .\src\mainAccountInfo.ts

実行すると以下の様に結果が出力されます。

socket connected.
{
  "version": 1,
  "address": "TADGQ7GDVIIOPD7TICWISNLARICML2TSFGEJWIA",
  "addressHeight": "144956",
  "publicKey": "03A9EF5A45C8D4D059D3E9C95DC5956091E3E855DC812F363DB41D987BF927A6",
  "publicKeyHeight": "144961",
  "accountType": 1,
  "supplementalPublicKeys": {
    "linked": {
      "publicKey": "B697066EC0419DF7354727822BB83581BC17E99D82914623700272BCA9C9B355"
    },
    "vrf": {
      "publicKey": "97D8B26A629FB6AF1CAA06441AEBDA8D8504F3E867292C01F9581A5C80FE5702"
    },
    "voting": [
      {
        "publickey": "EC167F9C009F64F183DEE8FECFA737DDFFB3FD6BC01C82E681ED8886D6D0D656",
        "startEpoch": "1568",
        "endEpoch": "2287"
      }
    ]
  },
  "activityBuckets": [
    {
      "startHeight": "1965240",
      "totalFeesPaid": "0",
      "beneficiaryCount": 0,
      "rawScore": "14178634374253"
    },
    {
      "startHeight": "1965060",
      "totalFeesPaid": "0",
      "beneficiaryCount": 0,
      "rawScore": "14177684407803"
    },
    {
      "startHeight": "1964880",
      "totalFeesPaid": "0",
      "beneficiaryCount": 0,
      "rawScore": "14177007248007"
    },
    {
      "startHeight": "1964700",
      "totalFeesPaid": "0",
      "beneficiaryCount": 0,
      "rawScore": "14176147652219"
    },
    {
      "startHeight": "1964520",
      "totalFeesPaid": "0",
      "beneficiaryCount": 0,
      "rawScore": "14174655376668"
    }
  ],
  "mosaics": [
    {
      "mosaicId": "8268645399043017678",
      "amount": "15515353361466"
    }
  ],
  "importance": "14177684407803",
  "importanceHeight": "1965240"
}
{
  "version": 1,
  "address": "TBZN46UIU5BFLJI46VB4JTHHCE5EN2RFLR7NX3A",
...
socket close: 1091
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?