import BN from "bn.js"; import nacl from "tweetnacl"; import { Cell } from "../../boc/cell"; import { CellMessage } from "../../message/cellMessage"; import { CommonMessageInfo } from "../../message/commonMessageInfo"; import { InternalMessage } from "../../message/internalMessage"; import TonHttpProvider from "../../providers/httpProvider"; import Address from "../../utils/address"; import { Contract, ExternalMessage, Options } from "../contract"; export interface TransferParams { secretKey: Uint8Array; toAddress: Address | string; amount: BN | number; seqno: number; payload: string | Uint8Array | Cell; sendMode: number; stateInit?: Cell; } /** * Abstract standard wallet class */ export class WalletContract extends Contract { deploy: (secretKey: Uint8Array) => void; constructor(provider: TonHttpProvider, options: Options) { if (!options.publicKey && !options.address) throw new Error( "WalletContract required publicKey or address in options" ); super(provider, options); /** * @param secretKey {Uint8Array} */ this.deploy = (secretKey: Uint8Array) => Contract.createMethod( provider, this.createInitExternalMessage(secretKey) ); } public transfer = (params: TransferParams) => Contract.createMethod( this.provider, this.createTransferMessage( params.secretKey, params.toAddress, params.amount, params.seqno, params.payload, params.sendMode, !Boolean(params.secretKey), params.stateInit ) ); public seqno = () => { return { call: async () => { const address = await this.getAddress(); let n = null; try { n = this.provider.getSeqno(address.toString()); } catch (e) {} return n; }, }; }; getName() { throw new Error("override me"); } /** * @override * @protected * @return {Cell} cell contains wallet data */ protected 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} */ protected createSigningMessage(seqno?: number) { 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: Uint8Array ): Promise { 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} */ async createExternalMessage( signingMessage: Cell, secretKey: Uint8Array, seqno: number, dummySignature = false ): Promise { 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: Cell | null = null, code: Cell | Uint8Array | null = null, data: Cell | null = 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, }; } createPayloadCell = (payload: string | Uint8Array | Cell = ""): Cell => { let payloadCell = new Cell(); if (payload) { if (typeof payload !== "string" && "refs" in payload) { // 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); } } return payloadCell; }; /** * @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} * @return {Promise} */ async createTransferMessage( secretKey: Uint8Array, address: Address | string, amount: BN | number, seqno: number, payload: string | Uint8Array | Cell = "", sendMode: number = 3, dummySignature = false, stateInit: Cell | null = null ) { const payloadCell = this.createPayloadCell(payload); const to = new Address(address); const order = new InternalMessage({ to, value: amount, bounce: to.isBounceable, body: new CommonMessageInfo({ stateInit: stateInit ? new CellMessage(stateInit) : undefined, body: new CellMessage(payloadCell), }), }); const orderCell = new Cell(); order.writeTo(orderCell); const signingMessage = this.createSigningMessage(seqno); signingMessage.bits.writeUint8(sendMode); signingMessage.refs.push(orderCell); return this.createExternalMessage( signingMessage, secretKey, seqno, dummySignature ); } }