@ton/ton
Version:
[](https://www.npmjs.com/package/ton)
455 lines (454 loc) • 15.3 kB
JavaScript
"use strict";
/**
* Copyright (c) Whales Corp.
* All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TonClient = void 0;
const HttpApi_1 = require("./api/HttpApi");
const core_1 = require("@ton/core");
class TonClient {
constructor(parameters) {
this.parameters = {
endpoint: parameters.endpoint
};
this.api = new HttpApi_1.HttpApi(this.parameters.endpoint, {
timeout: parameters.timeout,
apiKey: parameters.apiKey,
adapter: parameters.httpAdapter
});
}
/**
* Get Address Balance
* @param address address for balance check
* @returns balance
*/
async getBalance(address) {
return (await this.getContractState(address)).balance;
}
/**
* Invoke get method
* @param address contract address
* @param name name of method
* @param params optional parameters
* @returns stack and gas_used field
*/
async runMethod(address, name, stack = []) {
let res = await this.api.callGetMethod(address, name, stack);
if (res.exit_code !== 0) {
throw Error('Unable to execute get method. Got exit_code: ' + res.exit_code);
}
return { gas_used: res.gas_used, stack: parseStack(res.stack) };
}
/**
* Invoke get method
* @param address contract address
* @param name name of method
* @param params optional parameters
* @returns stack and gas_used field
* @deprecated use runMethod instead
*/
async callGetMethod(address, name, stack = []) {
return this.runMethod(address, name, stack);
}
/**
* Invoke get method that returns error code instead of throwing error
* @param address contract address
* @param name name of method
* @param params optional parameters
* @returns stack and gas_used field
*/
async runMethodWithError(address, name, params = []) {
let res = await this.api.callGetMethod(address, name, params);
return { gas_used: res.gas_used, stack: parseStack(res.stack), exit_code: res.exit_code };
}
/**
* Invoke get method that returns error code instead of throwing error
* @param address contract address
* @param name name of method
* @param params optional parameters
* @returns stack and gas_used field
* @deprecated use runMethodWithError instead
*/
async callGetMethodWithError(address, name, stack = []) {
return this.runMethodWithError(address, name, stack);
}
/**
* Get transactions
* @param address address
*/
async getTransactions(address, opts) {
// Fetch transactions
let tx = await this.api.getTransactions(address, opts);
let res = [];
for (let r of tx) {
res.push((0, core_1.loadTransaction)(core_1.Cell.fromBoc(Buffer.from(r.data, 'base64'))[0].beginParse()));
}
return res;
}
/**
* Get transaction by it's id
* @param address address
* @param lt logical time
* @param hash transaction hash
* @returns transaction or null if not exist
*/
async getTransaction(address, lt, hash) {
let res = await this.api.getTransaction(address, lt, hash);
if (res) {
return (0, core_1.loadTransaction)(core_1.Cell.fromBoc(Buffer.from(res.data, 'base64'))[0].beginParse());
}
else {
return null;
}
}
/**
* Locate outcoming transaction of destination address by incoming message
* @param source message source address
* @param destination message destination address
* @param created_lt message's created lt
* @returns transaction
*/
async tryLocateResultTx(source, destination, created_lt) {
let res = await this.api.tryLocateResultTx(source, destination, created_lt);
return (0, core_1.loadTransaction)(core_1.Cell.fromBase64(res.data).beginParse());
}
/**
* Locate incoming transaction of source address by outcoming message
* @param source message source address
* @param destination message destination address
* @param created_lt message's created lt
* @returns transaction
*/
async tryLocateSourceTx(source, destination, created_lt) {
let res = await this.api.tryLocateSourceTx(source, destination, created_lt);
return (0, core_1.loadTransaction)(core_1.Cell.fromBase64(res.data).beginParse());
}
/**
* Fetch latest masterchain info
* @returns masterchain info
*/
async getMasterchainInfo() {
let r = await this.api.getMasterchainInfo();
return {
workchain: r.init.workchain,
shard: r.last.shard,
initSeqno: r.init.seqno,
latestSeqno: r.last.seqno
};
}
/**
* Fetch latest workchain shards
* @param seqno masterchain seqno
*/
async getWorkchainShards(seqno) {
let r = await this.api.getShards(seqno);
return r.map((m) => ({
workchain: m.workchain,
shard: m.shard,
seqno: m.seqno
}));
}
/**
* Fetch transactions inf shards
* @param workchain
* @param seqno
* @param shard
*/
async getShardTransactions(workchain, seqno, shard) {
let tx = await this.api.getBlockTransactions(workchain, seqno, shard);
if (tx.incomplete) {
throw Error('Unsupported');
}
return tx.transactions.map((v) => ({
account: core_1.Address.parseRaw(v.account),
lt: v.lt,
hash: v.hash
}));
}
/**
* Send message to a network
* @param src source message
*/
async sendMessage(src) {
const boc = (0, core_1.beginCell)()
.store((0, core_1.storeMessage)(src))
.endCell()
.toBoc();
await this.api.sendBoc(boc);
}
/**
* Send file to a network
* @param src source file
*/
async sendFile(src) {
await this.api.sendBoc(src);
}
/**
* Estimate fees for external message
* @param address target address
* @returns
*/
async estimateExternalMessageFee(address, args) {
return await this.api.estimateFee(address, { body: args.body, initCode: args.initCode, initData: args.initData, ignoreSignature: args.ignoreSignature });
}
/**
* Send external message to contract
* @param contract contract to send message
* @param src message body
*/
async sendExternalMessage(contract, src) {
if (await this.isContractDeployed(contract.address) || !contract.init) {
const message = (0, core_1.external)({
to: contract.address,
body: src
});
await this.sendMessage(message);
}
else {
const message = (0, core_1.external)({
to: contract.address,
init: contract.init,
body: src
});
await this.sendMessage(message);
}
}
/**
* Check if contract is deployed
* @param address addres to check
* @returns true if contract is in active state
*/
async isContractDeployed(address) {
return (await this.getContractState(address)).state === 'active';
}
/**
* Resolves contract state
* @param address contract address
*/
async getContractState(address) {
let info = await this.api.getAddressInformation(address);
let balance = BigInt(info.balance);
let state = info.state;
return {
balance,
extra_currencies: info.extra_currencies,
state,
code: info.code !== '' ? Buffer.from(info.code, 'base64') : null,
data: info.data !== '' ? Buffer.from(info.data, 'base64') : null,
lastTransaction: info.last_transaction_id.lt !== '0' ? {
lt: info.last_transaction_id.lt,
hash: info.last_transaction_id.hash,
} : null,
blockId: {
workchain: info.block_id.workchain,
shard: info.block_id.shard,
seqno: info.block_id.seqno
},
timestampt: info.sync_utime
};
}
/**
* Open contract
* @param src source contract
* @returns contract
*/
open(src) {
return (0, core_1.openContract)(src, (args) => createProvider(this, args.address, args.init));
}
/**
* Create a provider
* @param address address
* @param init optional init
* @returns provider
*/
provider(address, init) {
return createProvider(this, address, init ?? null);
}
}
exports.TonClient = TonClient;
function parseStackEntry(x) {
const typeName = x['@type'];
switch (typeName) {
case 'tvm.list':
case 'tvm.tuple':
return x.elements.map(parseStackEntry);
case 'tvm.cell':
return core_1.Cell.fromBoc(Buffer.from(x.bytes, 'base64'))[0];
case 'tvm.slice':
return core_1.Cell.fromBoc(Buffer.from(x.bytes, 'base64'))[0];
case 'tvm.stackEntryCell':
return parseStackEntry(x.cell);
case 'tvm.stackEntrySlice':
return parseStackEntry(x.slice);
case 'tvm.stackEntryTuple':
return parseStackEntry(x.tuple);
case 'tvm.stackEntryList':
return parseStackEntry(x.list);
case 'tvm.stackEntryNumber':
return parseStackEntry(x.number);
case 'tvm.numberDecimal':
return BigInt(x.number);
default:
throw Error('Unsupported item type: ' + typeName);
}
}
function parseStackItem(s) {
if (s[0] === 'num') {
let val = s[1];
if (val.startsWith('-')) {
return { type: 'int', value: -BigInt(val.slice(1)) };
}
else {
return { type: 'int', value: BigInt(val) };
}
}
else if (s[0] === 'null') {
return { type: 'null' };
}
else if (s[0] === 'cell') {
return { type: 'cell', cell: core_1.Cell.fromBoc(Buffer.from(s[1].bytes, 'base64'))[0] };
}
else if (s[0] === 'slice') {
return { type: 'slice', cell: core_1.Cell.fromBoc(Buffer.from(s[1].bytes, 'base64'))[0] };
}
else if (s[0] === 'builder') {
return { type: 'builder', cell: core_1.Cell.fromBoc(Buffer.from(s[1].bytes, 'base64'))[0] };
}
else if (s[0] === 'tuple' || s[0] === 'list') {
if (s[1].elements.length === 0) {
return { type: 'null' };
}
return { type: 'tuple', items: s[1].elements.map(parseStackEntry) };
}
else {
throw Error('Unsupported stack item type: ' + s[0]);
}
}
function parseStack(src) {
let stack = [];
for (let s of src) {
stack.push(parseStackItem(s));
}
return new core_1.TupleReader(stack);
}
function createProvider(client, address, init) {
return {
async getState() {
let state = await client.getContractState(address);
let balance = state.balance;
let last = state.lastTransaction ? { lt: BigInt(state.lastTransaction.lt), hash: Buffer.from(state.lastTransaction.hash, 'base64') } : null;
let ecMap = null;
let storage;
if (state.state === 'active') {
storage = {
type: 'active',
code: state.code ? state.code : null,
data: state.data ? state.data : null,
};
}
else if (state.state === 'uninitialized') {
storage = {
type: 'uninit',
};
}
else if (state.state === 'frozen') {
storage = {
type: 'frozen',
stateHash: Buffer.alloc(0),
};
}
else {
throw Error('Unsupported state');
}
if (state.extra_currencies && state.extra_currencies.length > 0) {
ecMap = {};
for (let ec of state.extra_currencies) {
ecMap[ec.id] = BigInt(ec.amount);
}
}
return {
balance,
extracurrency: ecMap,
last,
state: storage,
};
},
async get(name, args) {
if (typeof name !== 'string') {
throw new Error('Method name must be a string for TonClient provider');
}
let method = await client.runMethod(address, name, args);
return { stack: method.stack };
},
async external(message) {
//
// Resolve init
//
let neededInit = null;
if (init && !await client.isContractDeployed(address)) {
neededInit = init;
}
//
// Send package
//
const ext = (0, core_1.external)({
to: address,
init: neededInit,
body: message
});
let boc = (0, core_1.beginCell)()
.store((0, core_1.storeMessage)(ext))
.endCell()
.toBoc();
await client.sendFile(boc);
},
async internal(via, message) {
// Resolve init
let neededInit = null;
if (init && (!await client.isContractDeployed(address))) {
neededInit = init;
}
// Resolve bounce
let bounce = true;
if (message.bounce !== null && message.bounce !== undefined) {
bounce = message.bounce;
}
// Resolve value
let value;
if (typeof message.value === 'string') {
value = (0, core_1.toNano)(message.value);
}
else {
value = message.value;
}
// Resolve body
let body = null;
if (typeof message.body === 'string') {
body = (0, core_1.comment)(message.body);
}
else if (message.body) {
body = message.body;
}
// Send internal message
await via.send({
to: address,
value,
bounce,
sendMode: message.sendMode,
extracurrency: message.extracurrency,
init: neededInit,
body
});
},
open(contract) {
return (0, core_1.openContract)(contract, (args) => createProvider(client, args.address, args.init ?? null));
},
getTransactions(address, lt, hash, limit) {
return client.getTransactions(address, { limit: limit ?? 100, lt: lt.toString(), hash: hash.toString('base64'), inclusive: true });
}
};
}