@ton3/liteclient
Version:
TON Blockchain LiteClient
296 lines (253 loc) • 8.15 kB
text/typescript
import EventEmitter from 'events'
import { ADNLClient } from 'adnl'
import Debug from 'debug'
import { StreamReader, StreamWriter, uintToBytes } from '../../tl/stream'
import * as dataTypes from '../../dataTypes'
import {
AccountId,
AccountState,
AllShardsInfo,
BlockData,
BlockHeader,
BlockTransactions,
MasterchainInfo,
RunMethodResult,
SendMsgStatus,
TransactionId3,
TransactionList,
TransactionInfo,
Version,
} from '../../dataTypes/liteServer';
import * as functions from '../../functions';
import { BlockIdExt } from '../../dataTypes/tonNode';
import { LookupBlockInput } from '../../functions/liteServer';
import { Utils } from 'ton3-core';
export interface WaitOptions {
seqno: number
timeout?: number
}
const debug = Debug('tonkite:lite-api:client');
let counter = 0;
const nextQueryId = () => {
const buffer = Utils.Helpers.hexToBytes('0000000095d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d10')
buffer.set(uintToBytes(counter++, 4).reverse(), 0)
return buffer
}
const createADNLQuery = (queryId: Uint8Array, query: Uint8Array) => {
const queryPadding = 4 - (((query.length + 1) % 4) || 4);
const buffer = new Uint8Array(4 + 32 + 1 + query.length + queryPadding);
// crc32('adnl.message.query query_id:int256 query:bytes = adnl.Message')
// Write 4 bytes number in LE order
buffer.set(uintToBytes(0xb48bf97a, 4).reverse(), 0)
// query_id:int256
buffer.set(queryId, 4)
// query:bytes
buffer.set([ query.length ], 4 + 32);
buffer.set(query, 4 + 32 + 1);
return buffer;
};
class LiteApiError extends Error {
constructor(message: string, readonly code: number) {
super(message);
}
}
export class LiteApi {
#events = new EventEmitter();
constructor(private readonly adnlClient: ADNLClient) {
adnlClient.on('data', (data: Buffer) => {
const dataReader = new StreamReader(data);
const adnlAnswer = dataTypes.adnl.answerMessage.read(dataReader);
const [tag, answer] = this.parseLiteServerAnswer(adnlAnswer.answer);
this.#events.emit(Utils.Helpers.bytesToHex(adnlAnswer.queryId), {
tag,
answer,
});
});
}
/**
* Parses an answer with a parser (chosen according to a tag)
* @param dataReader
* @private
*/
private parseLiteServerAnswer(dataReader: StreamReader) {
const tag = dataReader.readInt32LE();
const codecs = Object.values(dataTypes.liteServer) as any[];
const codec = codecs.find((codec) => codec.tag === tag);
if (!codec) {
throw new Error(`Data type 0x${tag.toString(16).padStart(8, '0')} is not supported.`);
}
return [tag, codec.read(dataReader)];
}
protected query<T>(dataWriterFn: (dataWriter: StreamWriter) => void, wait?: WaitOptions): Promise<T> {
const dataWriter = new StreamWriter();
const queryWriter = new StreamWriter();
if (wait) {
const { seqno, timeout = 10000 } = wait
dataTypes.liteServer.waitMasterchainSeqno.write(dataWriter, seqno, timeout);
}
dataWriterFn(dataWriter);
dataTypes.liteServer.query.write(queryWriter, dataWriter.buffer);
const queryId = nextQueryId();
this.adnlClient.write(createADNLQuery(queryId, queryWriter.buffer));
return new Promise<T>((resolve, reject) => {
this.#events.once(Utils.Helpers.bytesToHex(queryId), ({ tag, answer }) => {
if (tag === dataTypes.liteServer.error.tag) {
return reject(new LiteApiError(answer.message, answer.code));
}
resolve(answer);
});
});
}
getMasterchainInfo(wait?: WaitOptions) {
debug('query getMasterchainInfo()');
return this.query<MasterchainInfo>((dataWriter) => {
functions.liteServer.getMasterchainInfo(dataWriter);
}, wait);
}
getTime(wait?: WaitOptions) {
debug('query getTime()');
return this.query<string>((dataWriter) => {
functions.liteServer.getTime(dataWriter);
}, wait);
}
getVersion(wait?: WaitOptions) {
debug('query getVersion()');
return this.query<Version>((dataWriter) => {
functions.liteServer.getVersion(dataWriter);
}, wait);
}
getBlock(blockId: BlockIdExt, wait?: WaitOptions) {
debug('query getBlock()');
return this.query<BlockData>((dataWriter) => {
functions.liteServer.getBlock(dataWriter, blockId);
}, wait);
}
getAllShardsInfo(blockId: BlockIdExt, wait?: WaitOptions) {
debug('query getAllShardsInfo()');
return this.query<AllShardsInfo>((dataWriter) => {
functions.liteServer.getAllShardsInfo(dataWriter, blockId);
}, wait);
}
getState(blockId: BlockIdExt, wait?: WaitOptions) {
debug('query getState()');
return this.query<unknown>((dataWriter) => {
functions.liteServer.getState(dataWriter, blockId);
}, wait);
}
getBlockHeader(
data: {
blockId: BlockIdExt,
mode: number
},
wait?: WaitOptions
) {
debug('query getBlockHeader()');
return this.query<BlockHeader>((dataWriter) => {
functions.liteServer.getBlockHeader(dataWriter, data.blockId, data.mode);
}, wait);
}
lookupBlock(input: LookupBlockInput, wait?: WaitOptions) {
debug('query lookupBlock()');
return this.query<BlockHeader>((dataWriter) => {
functions.liteServer.lookupBlock(dataWriter, input);
}, wait);
}
getAccountState(
data: {
blockId: BlockIdExt,
account: AccountId
},
wait?: WaitOptions
) {
debug(
'query getAccountState(%d, %d, %s)',
data.blockId.workchain,
data.blockId.seqno,
`${data.account.workchain}:${Utils.Helpers.bytesToHex(data.account.id)}`,
);
return this.query<AccountState>((dataWriter) => {
functions.liteServer.getAccountState(dataWriter, data.blockId, data.account);
}, wait);
}
listBlockTransactions(
data: {
blockId: BlockIdExt,
count: number,
after?: TransactionId3 | null,
options?: {
reverseOrder?: boolean;
wantProof?: boolean;
},
},
wait?: WaitOptions
) {
return this.query<BlockTransactions>((dataWriter) => {
debug('query listBlockTransactions()');
functions.liteServer.listBlockTransactions(
dataWriter,
data.blockId,
data.count || 20,
data.after || null,
data?.options?.reverseOrder || false,
data?.options?.wantProof || false,
);
}, wait);
}
getTransactions(
data: {
account: AccountId,
lt: bigint,
hash: Uint8Array,
count?: number
},
wait?: WaitOptions
) {
debug(
'query getTransactions(%s, %s, %s)',
`${data.account.workchain}:${Utils.Helpers.bytesToHex(data.account.id)}`,
data.lt.toString(10),
Utils.Helpers.bytesToHex(data.hash),
);
return this.query<TransactionList>((dataWriter) => {
functions.liteServer.getTransactions(dataWriter, data.count || 20, data.account, data.lt, data.hash);
}, wait);
}
getOneTransaction(
data: {
blockId: BlockIdExt,
accountId: AccountId,
lt: bigint
},
wait?: WaitOptions
) {
debug(
'query getOneTransaction(%s, %s, %s)',
`${data.blockId.workchain},${data.blockId.shard.toString(16)},${data.blockId.seqno}`,
`${data.accountId.workchain}:${Utils.Helpers.bytesToHex(data.accountId.id)}`,
data.lt.toString(10),
);
return this.query<TransactionInfo>((dataWriter) => {
functions.liteServer.getOneTransaction(dataWriter, data.blockId, data.accountId, data.lt);
}, wait);
}
runSmcMethod(
data: {
blockId: BlockIdExt,
accountId: AccountId,
methodId: bigint
params: Uint8Array
},
wait?: WaitOptions
) {
debug('query runSmcMethod()');
return this.query<RunMethodResult>((dataWriter) => {
functions.liteServer.runSmcMethod(dataWriter, data.blockId, data.accountId, data.methodId, data.params);
}, wait);
}
sendMessage(body: Uint8Array, wait?: WaitOptions) {
debug('query sendMessage()');
return this.query<SendMsgStatus>((dataWriter) => {
functions.liteServer.sendMessage(dataWriter, body);
}, wait);
}
}