UNPKG

@ton3/liteclient

Version:
544 lines (482 loc) 16.8 kB
import { Account, AccountStatus, AccountTransactionId, BlockId, Transaction, TransactionId, } from '@tonkite/core'; import { Address, BOC, Coins, Hashmap, Slice, Utils, Bit } from 'ton3-core'; import { LiteApi, WaitOptions } from '../liteapi'; import { AccountId, RunMethodResult } from '../../dataTypes/liteServer'; import { Stack, StackValue } from '../../utils/stack' import { loadAccount, loadCurrencyCollection, loadShardAccount, loadShardHashes, ShardAccount, } from '@tonkite/types'; import { TransactionCursor } from './cursor'; type Modify<T, R> = Omit<T, keyof R> & R; type GetMethodResponse<T> = Modify<RunMethodResult, { result: T | null }> export class BlockchainClient { constructor(private readonly api: LiteApi) {} async getMasterchainInfo(wait?: WaitOptions): Promise<BlockId> { const masterchainInfo = await this.api.getMasterchainInfo(wait); return { workchain: masterchainInfo.last.workchain, shard: masterchainInfo.last.shard, seqno: masterchainInfo.last.seqno, rootHash: masterchainInfo.last.root_hash, fileHash: masterchainInfo.last.file_hash, }; } async getAllShardsInfo(blockId: BlockId, wait?: WaitOptions): Promise<BlockId[]> { const allShards = await this.api.getAllShardsInfo({ workchain: blockId.workchain, shard: blockId.shard, seqno: blockId.seqno, root_hash: blockId.rootHash, file_hash: blockId.fileHash, }, wait); const [ cell ] = BOC.from(allShards.data) const shardHashes = loadShardHashes(cell.slice()); return shardHashes .map((workchain) => workchain.shards.map( (shard): BlockId => ({ workchain: workchain.workchain, shard: shard.id, seqno: shard.description.seq_no, rootHash: shard.description.root_hash, fileHash: shard.description.file_hash, }), ), ) .flat(); } async lookupBlock(blockId: Pick<BlockId, 'workchain' | 'shard' | 'seqno'>, wait?: WaitOptions): Promise<BlockId> { const result = await this.api.lookupBlock({ id: { workchain: blockId.workchain, shard: blockId.shard, seqno: blockId.seqno, }, }, wait); return { workchain: result.id.workchain, shard: result.id.shard, seqno: result.id.seqno, rootHash: result.id.root_hash, fileHash: result.id.file_hash, }; } async getAccountState( data: { account: Address, blockId: BlockId }, wait?: WaitOptions ): Promise<Account> { const accountStateRaw = await this.api.getAccountState({ blockId: { workchain: data.blockId.workchain, shard: data.blockId.shard, seqno: data.blockId.seqno, root_hash: data.blockId.rootHash, file_hash: data.blockId.fileHash, }, account: { workchain: data.account.workchain, id: data.account.hash, } }, wait); if (!accountStateRaw.state.length) { return new Account( { workchain: accountStateRaw.id.workchain, shard: accountStateRaw.id.shard, seqno: accountStateRaw.id.seqno, rootHash: accountStateRaw.id.root_hash, fileHash: accountStateRaw.id.file_hash, }, data.account, Coins.fromNano(0), { lt: 0n, hash: new Uint8Array(32), }, AccountStatus.UNINITIALIZED, null, null, null, { lastPaid: null, duePayment: null, usage: { bits: 0, cells: 0, publicCells: 0, }, }, ); } const [ _, proof ] = BOC.from(accountStateRaw.proof); /** * shard_ident$00 shard_pfx_bits:(#<= 60) * workchain_id:int32 shard_prefix:uint64 = ShardIdent; */ const loadShardIdent = (slice: Slice) => { if (slice.loadUint(2) !== 0b00) { throw new Error('Incorrect ShardIdent'); } const shardPrefixBits = slice.loadBigUint(6); // NOTE: log2(60 + 1) = 5.93 const workchainId = slice.loadInt(32); const shardPrefix = slice.loadBigUint(64); return { shardPrefixBits, workchainId, shardPrefix, }; }; const loadDepthBalanceInfo = (slice: Slice) => { const splitDepth = slice.loadUint(5); // log2(30 + 1) = 4.95 const balance = loadCurrencyCollection(slice); return { splitDepth, balance, }; }; /** * depth_balance$_ split_depth:(#<= 30) balance:CurrencyCollection = DepthBalanceInfo; * _ (HashmapAugE 256 ShardAccount DepthBalanceInfo) = ShardAccounts; */ const loadShardAccounts = (slice: Slice): [Uint8Array, { shardAccount: ShardAccount }][] => { return [ ...Hashmap.parse<Uint8Array, { shardAccount: ShardAccount }>( 256, Slice.parse(slice.refs[0]), { deserializers: { key: Utils.Helpers.bitsToBytes, value: (cell) => { const cs = Slice.parse(cell); const depthBalanceInfo = loadDepthBalanceInfo(cs); const shardAccount = loadShardAccount(cs); return { shardAccount, depthBalanceInfo, }; }, }, }, ), ]; }; /** * shard_state#9023afe2 global_id:int32 * shard_id:ShardIdent * seq_no:uint32 vert_seq_no:# * gen_utime:uint32 gen_lt:uint64 * min_ref_mc_seqno:uint32 * out_msg_queue_info:^OutMsgQueueInfo * before_split:(## 1) * accounts:^ShardAccounts * ^[ overload_history:uint64 underload_history:uint64 * total_balance:CurrencyCollection * total_validator_fees:CurrencyCollection * libraries:(HashmapE 256 LibDescr) * master_ref:(Maybe BlkMasterInfo) ] * custom:(Maybe ^McStateExtra) * = ShardStateUnsplit; */ const loadShardStateUnsplit = (slice: Slice) => { if (slice.loadUint(32) !== 0x9023afe2) { throw new Error('Incorrect ShardStateUnsplit'); } const global_id = slice.loadInt(32); const shard_id = loadShardIdent(slice); const seq_no = slice.loadUint(32); const vert_seq_no = slice.loadUint(32); const gen_utime = slice.loadUint(32); const gen_lt = slice.loadBigUint(64); const min_ref_mc_seqno = slice.loadUint(32); const out_msg_queue_info = slice.loadRef(); // ^OutMsgQueueInfo const before_split = slice.loadBit(); const shardAccounts = loadShardAccounts(Slice.parse(slice.loadRef())); const next = Slice.parse(slice.loadRef()); const overload_history = next.loadBigUint(64); const underload_history = next.loadBigUint(64); // const total_balance = loadCurrencyCollection(next); // const total_validator_fees = loadCurrencyCollection(next); return { global_id, shard_id, seq_no, vert_seq_no, gen_utime, gen_lt, min_ref_mc_seqno, out_msg_queue_info, before_split, shardAccounts, overload_history, underload_history, // total_balance, // total_validator_fees, }; }; const [ cell ] = BOC.from(accountStateRaw.state) const shardState = loadShardStateUnsplit(proof.refs[0].slice()); const accountState = loadAccount(cell.slice()); const shardAccount = shardState.shardAccounts .filter(([accountHash]) => { return Buffer.from(accountHash).equals(Buffer.from(data.account.hash, 0)); }) .map(([_, { shardAccount }]) => shardAccount)[0]; return new Account( { workchain: accountStateRaw.id.workchain, shard: accountStateRaw.id.shard, seqno: accountStateRaw.id.seqno, rootHash: accountStateRaw.id.root_hash, fileHash: accountStateRaw.id.file_hash, }, data.account, accountState.storage.balance.coins, { lt: shardAccount.lastTransactionId.lt, hash: shardAccount.lastTransactionId.hash, }, accountState.storage.state.type as AccountStatus, accountState.storage.state.type === 'frozen' ? accountState.storage.state.stateHash : null, accountState.storage.state.type === 'active' ? accountState.storage.state.code : null, accountState.storage.state.type === 'active' ? accountState.storage.state.data : null, { lastPaid: new Date(accountState.storageStat.lastPaid * 1000), duePayment: accountState.storageStat.duePayment, usage: { bits: accountState.storageStat.used.bits, cells: accountState.storageStat.used.cells, publicCells: accountState.storageStat.used.publicCells, }, }, ); } private shardIdent (slice: Slice) { slice.skipBits(2) const shard_pfx_bits = slice.loadBits(Math.ceil(Math.log2(60 + 1))) const workchain_id = slice.loadInt(32) const shard_prefix = slice.loadBigUint(64) return { shard_pfx_bits, workchain_id, shard_prefix } } private globalVersion (slice: Slice) { slice.skipBits(8) const version = slice.loadUint(32) const capabilities = slice.loadBigUint(64) return { version, capabilities } } private extBlkRef (slice: Slice) { const end_lt = slice.loadBigUint(64) const seq_no = slice.loadUint(32) const root_hash = slice.loadBytes(256) const file_hash = slice.loadBytes(256) return { end_lt, seq_no, root_hash, file_hash } } private blkMasterInfo (slice: Slice) { const master = this.extBlkRef(slice) return { master } } private blkPrevInfo (slice: Slice, arg: Bit) { if (arg === 0) { return this.extBlkRef(slice) } return [ this.extBlkRef(slice.loadRef().slice()), this.extBlkRef(slice.loadRef().slice()) ] } private blkInfo (slice: Slice) { slice.skipBits(32) const version = slice.loadUint(32) const not_master = slice.loadBit() const after_merge = slice.loadBit() const before_split = slice.loadBit() const after_split = slice.loadBit() const want_split = Boolean(slice.loadBit()) const want_merge = Boolean(slice.loadBit()) const key_block = Boolean(slice.loadBit()) const vert_seqno_incr = slice.loadBit() const flags = slice.loadUint(8) const seq_no = slice.loadUint(32) const vert_seq_no = slice.loadUint(32) const shard = this.shardIdent(slice) const gen_utime = slice.loadUint(32) const start_lt = slice.loadBigUint(64) const end_lt = slice.loadBigUint(64) const gen_validator_list_hash_short = slice.loadUint(32) const gen_catchain_seqno = slice.loadUint(32) const min_ref_mc_seqno = slice.loadUint(32) const prev_key_block_seqno = slice.loadUint(32) const gen_software = (flags & (1 << 0)) ? this.globalVersion(slice) : null const master_ref = Boolean(not_master) ? this.blkMasterInfo(slice.loadRef().slice()) : null const prev_ref = this.blkPrevInfo(slice.loadRef().slice(), after_merge) const prev_vert_ref = Boolean(vert_seqno_incr) ? this.blkPrevInfo(slice.loadRef().slice(), 0) : null return { version, not_master, after_merge, before_split, after_split, want_split, want_merge, key_block, vert_seqno_incr, flags, seq_no, vert_seq_no, shard, gen_utime, start_lt, end_lt, gen_validator_list_hash_short, gen_catchain_seqno, min_ref_mc_seqno, prev_key_block_seqno, gen_software, master_ref, prev_ref, prev_vert_ref } } public async getBlock (blockId: BlockId, wait?: WaitOptions) { const { data } = await this.api.getBlock({ workchain: blockId.workchain, shard: blockId.shard, seqno: blockId.seqno, root_hash: blockId.rootHash, file_hash: blockId.fileHash }, wait) const slice = BOC.from(data).root[0].slice() slice.skipBits(32) const global_id = slice.loadInt(32) const info = this.blkInfo(slice.loadRef().slice()) const value_flow = slice.loadRef() const state_update = slice.loadRef() const extra = slice.loadRef() return { global_id, info, value_flow, state_update, extra } } async getBlockTransactions( data: { blockId: BlockId, after?: Pick<TransactionId, 'account' | 'lt'> | null, }, wait?: WaitOptions ): Promise<TransactionId[]> { const result = await this.api.listBlockTransactions({ blockId: { workchain: data.blockId.workchain, shard: data.blockId.shard, seqno: data.blockId.seqno, root_hash: data.blockId.rootHash, file_hash: data.blockId.fileHash, }, count: 40, after: data.after ? { account: data.after.account.hash, lt: data.after.lt } : null }, wait); return result.ids.map((id) => ({ account: new Address(`${result.id.workchain}:${Utils.Helpers.bytesToHex(id.account!)}`), lt: id.lt!, hash: id.hash!, })); } async getOneTransaction( data: { blockId: BlockId, account: AccountId, lt: bigint }, wait?: WaitOptions ) { return await this.api.getOneTransaction({ blockId: { workchain: data.blockId.workchain, shard: data.blockId.shard, seqno: data.blockId.seqno, root_hash: data.blockId.rootHash, file_hash: data.blockId.fileHash }, accountId: data.account, lt: data.lt }, wait) } getTransactions( data: { account: Address, after: AccountTransactionId, take?: number } ): AsyncIterable<Transaction> { const { account, after, take = Infinity } = data return new TransactionCursor(this.api, account, after, take); } public async invokeGetMethod<T extends StackValue[] = StackValue[]> ( data: { account: Account, method: string | number, stack: StackValue[] }, wait?: WaitOptions ): Promise<GetMethodResponse<T>> { const { account, method, stack = [] } = data const methodName = typeof method !== 'number' ? Utils.Helpers.stringToBytes(method) : null const methodId = BigInt(typeof method !== 'number' ? (Utils.Checksum.crc16(methodName) & 0xffff) | 0x10000 : method) const blockId = { workchain: account.blockId.workchain, shard: account.blockId.shard, seqno: account.blockId.seqno, root_hash: account.blockId.rootHash, file_hash: account.blockId.fileHash } const accountId = { workchain: account.address.workchain, id: account.address.hash } const params = new BOC([ Stack.serialize(stack) ]).toBytes() const response = await this.api.runSmcMethod({ blockId, accountId, methodId, params }, wait) const result: GetMethodResponse<T> = { ...response, result: response.result ? Stack.deserialize<T>(BOC.from(response.result).root[0]) : null } return result } public async getTime(wait?: WaitOptions) { return await this.api.getTime(wait) } }