@openproduct/web-sdk
Version:
OpenMask Web - TypeScript Browser SDK for TON blockchain
266 lines (236 loc) • 7.03 kB
text/typescript
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<ExternalMessage> {
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<ExternalMessage>}
*/
async createExternalMessage(
signingMessage: Cell,
secretKey: Uint8Array,
seqno: number,
dummySignature = false
): Promise<ExternalMessage> {
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<ExternalMessage>}
*/
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
);
}
}