tonweb
Version:
TonWeb - JavaScript API for TON blockchain
283 lines (263 loc) • 9.98 kB
JavaScript
const {Cell} = require("../boc");
const {Address, bytesToBase64, bytesToHex, BN} = require("../utils");
class Contract {
/**
* @param provider {HttpProvider}
* @param options {{code?: Cell, address?: Address | string, wc?: number}}
*/
constructor(provider, options) {
this.provider = provider;
this.options = options;
this.address = options.address ? new Address(options.address) : null;
if (!options.wc) options.wc = this.address ? this.address.wc : 0;
this.methods = {};
}
/**
* @return {Promise<Address>}
*/
async getAddress() {
if (!this.address) {
this.address = (await this.createStateInit()).address;
}
return this.address;
}
/**
* @private
* @return {Cell} cell contains contact code
*/
createCodeCell() {
if (!this.options.code) throw new Error('Contract: options.code is not defined')
return this.options.code;
}
/**
* Method to override
* @protected
* @return {Cell} cell contains contract data
*/
createDataCell() {
return new Cell();
}
/**
* @protected
* @return {Promise<{stateInit: Cell, address: Address, code: Cell, data: Cell}>}
*/
async createStateInit() {
const codeCell = this.createCodeCell();
const dataCell = this.createDataCell();
const stateInit = Contract.createStateInit(codeCell, dataCell);
const stateInitHash = await stateInit.hash();
const address = new Address(this.options.wc + ":" + bytesToHex(stateInitHash));
return {
stateInit: stateInit,
address: address,
code: codeCell,
data: dataCell,
}
}
// _ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
// code:(Maybe ^Cell) data:(Maybe ^Cell)
// library:(Maybe ^Cell) = StateInit;
/**
* @param code {Cell}
* @param data {Cell}
* @param library {null}
* @param splitDepth {null}
* @param ticktock {null}
* @return {Cell}
*/
static createStateInit(code,
data,
library = null,
splitDepth = null,
ticktock = null) {
if (library)
throw "Library in state init is not implemented";
if (splitDepth)
throw "Split depth in state init is not implemented";
if (ticktock)
throw "Ticktock in state init is not implemented";
const stateInit = new Cell();
stateInit.bits.writeBitArray([Boolean(splitDepth), Boolean(ticktock), Boolean(code), Boolean(data), Boolean(library)]);
if (code)
stateInit.refs.push(code);
if (data)
stateInit.refs.push(data);
if (library)
stateInit.refs.push(library);
return stateInit;
}
// extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32))
// = ExtraCurrencyCollection;
// currencies$_ grams:Grams other:ExtraCurrencyCollection
// = CurrencyCollection;
//int_msg_info$0 ihr_disabled:Bool bounce:Bool
//src:MsgAddressInt dest:MsgAddressInt
//value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
//created_lt:uint64 created_at:uint32 = CommonMsgInfo;
/**
* @param dest {Address | string}
* @param gramValue {number | BN}
* @param ihrDisabled {boolean}
* @param bounce {null | boolean}
* @param bounced {boolean}
* @param src {Address | string}
* @param currencyCollection {null}
* @param ihrFees {number | BN}
* @param fwdFees {number | BN}
* @param createdLt {number | BN}
* @param createdAt {number | BN}
* @return {Cell}
*/
static createInternalMessageHeader(dest,
gramValue = 0,
ihrDisabled = true,
bounce = null,
bounced = false,
src = null,
currencyCollection = null,
ihrFees = 0,
fwdFees = 0,
createdLt = 0,
createdAt = 0) {
const message = new Cell();
message.bits.writeBit(false);
message.bits.writeBit(ihrDisabled);
if (!(bounce === null)) {
message.bits.writeBit(bounce);
} else {
message.bits.writeBit((new Address(dest)).isBounceable);
}
message.bits.writeBit(bounced);
message.bits.writeAddress(src ? new Address(src) : null);
message.bits.writeAddress(new Address(dest));
message.bits.writeGrams(gramValue);
if (currencyCollection) {
throw "Currency collections are not implemented yet";
}
message.bits.writeBit(Boolean(currencyCollection));
message.bits.writeGrams(ihrFees);
message.bits.writeGrams(fwdFees);
message.bits.writeUint(createdLt, 64);
message.bits.writeUint(createdAt, 32);
return message;
}
//ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
//import_fee:Grams = CommonMsgInfo;
/**
* @param dest {Address | string}
* @param src {Address | string}
* @param importFee {number | BN}
* @return {Cell}
*/
static createExternalMessageHeader(dest,
src = null,
importFee = 0) {
const message = new Cell();
message.bits.writeUint(2, 2);
message.bits.writeAddress(src ? new Address(src) : null);
message.bits.writeAddress(new Address(dest));
message.bits.writeGrams(importFee);
return message;
}
/**
* @param address {Address | string}
* @param amount {BN} in nanotons
* @param payload {string | Uint8Array | Cell}
* @param stateInit? {Cell}
* @return {Cell}
*/
static createOutMsg(address, amount, payload, stateInit = null) {
let payloadCell = new Cell();
if (payload) {
if (payload.refs) { // is Cell
payloadCell = payload;
} else if (typeof payload === 'string') {
if (payload.length > 0) {
payloadCell.bits.writeUint(0, 32);
payloadCell.bits.writeString(payload);
}
} else {
payloadCell.bits.writeBytes(payload)
}
}
const orderHeader = Contract.createInternalMessageHeader(new Address(address), new BN(amount));
const order = Contract.createCommonMsgInfo(orderHeader, stateInit, payloadCell);
return order;
}
//tblkch.pdf, page 57
/**
* Create CommonMsgInfo contains header, stateInit, body
* @param header {Cell}
* @param stateInit? {Cell}
* @param body? {Cell}
* @return {Cell}
*/
static createCommonMsgInfo(header, stateInit = null, body = null) {
const commonMsgInfo = new Cell();
commonMsgInfo.writeCell(header);
if (stateInit) {
commonMsgInfo.bits.writeBit(true);
//-1: need at least one bit for body
// TODO we also should check for free refs here
// TODO: temporary always push in ref because WalletQueryParser can parse only ref
if (false && (commonMsgInfo.bits.getFreeBits() - 1 >= stateInit.bits.getUsedBits())) {
commonMsgInfo.bits.writeBit(false);
commonMsgInfo.writeCell(stateInit);
} else {
commonMsgInfo.bits.writeBit(true);
commonMsgInfo.refs.push(stateInit);
}
} else {
commonMsgInfo.bits.writeBit(false);
}
// TODO we also should check for free refs here
if (body) {
if ((commonMsgInfo.bits.getFreeBits() >= body.bits.getUsedBits()) && (commonMsgInfo.refs.length + body.refs.length <= 4)) {
commonMsgInfo.bits.writeBit(false);
commonMsgInfo.writeCell(body);
} else {
commonMsgInfo.bits.writeBit(true);
commonMsgInfo.refs.push(body);
}
} else {
commonMsgInfo.bits.writeBit(false);
}
return commonMsgInfo;
}
static createMethod(provider, queryPromise) {
return {
/**
* @return {Promise<Cell>}
*/
getBody: async () => {
return (await queryPromise).body;
},
/**
* @return {Promise<Cell>}
*/
getQuery: async () => {
return (await queryPromise).message;
},
send: async () => {
const query = await queryPromise;
const boc = bytesToBase64(await query.message.toBoc(false));
return provider.sendBoc(boc);
},
estimateFee: async () => {
const query = await queryPromise;
const serialized = query.code ? // deploy
{
address: query.address.toString(true, true, false),
body: bytesToBase64(await query.body.toBoc(false)),
init_code: bytesToBase64(await query.code.toBoc(false)),
init_data: bytesToBase64(await query.data.toBoc(false)),
} : {
address: query.address.toString(true, true, true),
body: bytesToBase64(await query.body.toBoc(false)),
};
return provider.getEstimateFee(serialized);
}
}
}
}
module.exports = {Contract};