fx-ton-lib
Version:
TonWeb - JavaScript API for TON blockchain
350 lines (297 loc) • 11.7 kB
JavaScript
const { Contract } = require("../index.js");
const { Cell } = require("../../boc");
const { nacl, stringToBytes, Address, BN } = require("../../utils");
/**
* Abstract standard wallet class
*/
class WalletContract extends Contract {
/**
* @param provider {HttpProvider}
* @param options? {{code: Cell, publicKey?: Uint8Array, address?: Address | string, wc?: number}}
*/
constructor(provider, options) {
if (!options.publicKey && !options.address) throw new Error('WalletContract required publicKey or address in options')
super(provider, options);
this.methods = {
/**
* @param params {{secretKey: Uint8Array, toAddress: Address | string, amount: BN | number, seqno: number, payload: string | Uint8Array | Cell, sendMode: number, stateInit?: Cell, expireAt?: number}}
*/
transfer: (params) => Contract.createMethod(provider, this.createTransferMessage(params.secretKey, params.toAddress, params.amount, params.seqno, params.payload, params.sendMode, !Boolean(params.secretKey), params.stateInit, params.expireAt)),
seqno: () => {
return {
/**
* @return {Promise<number>}
*/
call: async () => {
const address = await this.getAddress();
let n = null;
try {
n = (await provider.call2(address.toString(), 'seqno')).toNumber();
} catch (e) {
}
return n;
}
}
}
}
/**
* @param secretKey {Uint8Array}
*/
this.deploy = (secretKey) => Contract.createMethod(provider, this.createInitExternalMessage(secretKey));
this.deployWithoutSK = (InitExternalMessage) => Contract.createMethod(provider, InitExternalMessage);
}
getName() {
throw new Error('override me');
}
/**
* @override
* @protected
* @return {Cell} cell contains wallet data
*/
createDataCell() {
// 4 byte seqno, 32 byte publicKey
const cell = new Cell();
cell.bits.writeUint(0, 32); // seqno
cell.bits.writeBytes(this.options.publicKey);
return cell;
}
/**
* @protected
* @param seqno? {number}
* @return {Cell}
*/
createSigningMessage(seqno) {
seqno = seqno || 0;
const cell = new Cell();
cell.bits.writeUint(seqno, 32);
return cell;
}
/**
* External message for initialization
* @param secretKey {Uint8Array} nacl.KeyPair.secretKey
* @return {{address: Address, message: Cell, body: Cell, sateInit: Cell, code: Cell, data: Cell}}
*/
async createInitExternalMessage(secretKey) {
if (!this.options.publicKey) {
const keyPair = nacl.sign.keyPair.fromSecretKey(secretKey)
this.options.publicKey = keyPair.publicKey;
}
const { stateInit, address, code, data } = await this.createStateInit();
const signingMessage = this.createSigningMessage();
const signature = nacl.sign.detached(await signingMessage.hash(), secretKey);
const body = new Cell();
body.bits.writeBytes(signature);
body.writeCell(signingMessage);
const header = Contract.createExternalMessageHeader(address);
const externalMessage = Contract.createCommonMsgInfo(header, stateInit, body);
return {
address: address,
message: externalMessage,
body,
signingMessage,
stateInit,
code,
data,
};
}
/**
* @protected
* @param signingMessage {Cell}
* @param secretKey {Uint8Array} nacl.KeyPair.secretKey
* @param seqno {number}
* @param dummySignature? {boolean}
* @return {Promise<{address: Address, signature: Uint8Array, message: Cell, cell: Cell, body: Cell, resultMessage: Cell}>}
*/
async createExternalMessage(
signingMessage,
secretKey,
seqno,
dummySignature = false
) {
const signature = dummySignature ? new Uint8Array(64) : nacl.sign.detached(await signingMessage.hash(), secretKey);
const body = new Cell();
body.bits.writeBytes(signature);
body.writeCell(signingMessage);
let stateInit = null, code = null, data = null;
if (seqno === 0) {
if (!this.options.publicKey) {
const keyPair = nacl.sign.keyPair.fromSecretKey(secretKey)
this.options.publicKey = keyPair.publicKey;
}
const deploy = await this.createStateInit();
stateInit = deploy.stateInit;
code = deploy.code;
data = deploy.data;
}
const selfAddress = await this.getAddress();
const header = Contract.createExternalMessageHeader(selfAddress);
const resultMessage = Contract.createCommonMsgInfo(header, stateInit, body);
return {
address: selfAddress,
message: resultMessage, // old wallet_send_generate_external_message
body: body,
signature: signature,
signingMessage: signingMessage,
stateInit,
code,
data,
};
}
/**
* @param secretKey {Uint8Array} nacl.KeyPair.secretKey
* @param address {Address | string}
* @param amount {BN | number} in nanograms
* @param seqno {number}
* @param payload? {string | Uint8Array | Cell}
* @param sendMode? {number}
* @param dummySignature? {boolean}
* @param stateInit? {Cell}
* @param expireAt? {number}
* @return {Promise<{address: Address, signature: Uint8Array, message: Cell, cell: Cell, body: Cell, resultMessage: Cell}>}
*/
async createTransferMessage(
secretKey,
address,
amount,
seqno,
payload = "",
sendMode = 3,
dummySignature = false,
stateInit = null,
expireAt = undefined
) {
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);
const signingMessage = this.createSigningMessage(seqno, expireAt);
signingMessage.bits.writeUint8(sendMode);
signingMessage.refs.push(order);
return this.createExternalMessage(signingMessage, secretKey, seqno, dummySignature);
}
/**
* External message for initialization
*
*
*/
async createInitExternalMessageWithoutSecretKey() {
const { stateInit, address, code, data } = await this.createStateInit();
const signingMessage = this.createSigningMessage();
return await signingMessage.hash();
}
async signInitExternalMessage(signature) {
const { stateInit, address, code, data } = await this.createStateInit();
const signingMessage = this.createSigningMessage();
const body = new Cell();
body.bits.writeBytes(signature);
body.writeCell(signingMessage);
const header = Contract.createExternalMessageHeader(address);
const externalMessage = Contract.createCommonMsgInfo(header, stateInit, body);
return {
address: address,
message: externalMessage,
body,
signingMessage,
stateInit,
code,
data,
};
}
async createTransferMessageWithoutSecretKey(
publicKey,
address,
amount,
seqno,
payload = "",
sendMode = 3,
dummySignature = false,
stateInit = null,
expireAt = undefined
) {
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)
}
}
if (seqno === 0) {
if (!this.options.publicKey) {
this.options.publicKey = publicKey;
}
const deploy = await this.createStateInit();
stateInit = deploy.stateInit;
}
const orderHeader = Contract.createInternalMessageHeader(new Address(address), new BN(amount));
const order = Contract.createCommonMsgInfo(orderHeader, stateInit, payloadCell);
const signingMessage = this.createSigningMessage(seqno, expireAt);
signingMessage.bits.writeUint8(sendMode);
signingMessage.refs.push(order);
return (await signingMessage.hash());
//return this.createExternalMessage(signingMessage, secretKey, seqno, dummySignature);
}
async signTransferMessage(publicKey, signature, address, amount, seqno, payload,sendMode,expireAt) {
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)
}
}
let stateInit = null, code = null, data = null;
if (seqno === 0) { // 说明不需要提前部署钱包
if (!this.options.publicKey) {
this.options.publicKey = publicKey;
}
const deploy = await this.createStateInit();
stateInit = deploy.stateInit;
code = deploy.code;
data = deploy.data;
}
const orderHeader = Contract.createInternalMessageHeader(new Address(address), new BN(amount));
const order = Contract.createCommonMsgInfo(orderHeader, stateInit, payloadCell);
const signingMessage = this.createSigningMessage(seqno, expireAt);
signingMessage.bits.writeUint8(sendMode);
signingMessage.refs.push(order);
const body = new Cell();
body.bits.writeBytes(signature);
body.writeCell(signingMessage);
const selfAddress = await this.getAddress();
const header = Contract.createExternalMessageHeader(selfAddress);
const resultMessage = Contract.createCommonMsgInfo(header, stateInit, body);
return {
address: selfAddress,
message: resultMessage, // old wallet_send_generate_external_message
body: body,
signature: signature,
signingMessage: signingMessage,
stateInit,
code,
data,
};
}
}
module.exports = { WalletContract };