Skip to content

Commit

Permalink
highload-wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
rise1507 committed Mar 28, 2024
1 parent 424edad commit ac411e4
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 1 deletion.
235 changes: 235 additions & 0 deletions src/contract/highloadWallet/HighloadWalletContractV3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
const {Cell} = require("../../boc");
const {Contract} = require("../index.js");
const {Address, BN, nacl} = require("../../utils");

// https://github.com/ton-blockchain/highload-wallet-contract-v3, tag 'v3.00'
const CODE_HEX = "b5ee9c7241021001000228000114ff00f4a413f4bcf2c80b01020120020d02014803040078d020d74bc00101c060b0915be101d0d3030171b0915be0fa4030f828c705b39130e0d31f018210ae42e5a4ba9d8040d721d74cf82a01ed55fb04e030020120050a02027306070011adce76a2686b85ffc00201200809001aabb6ed44d0810122d721d70b3f0018aa3bed44d08307d721d70b1f0201200b0c001bb9a6eed44d0810162d721d70b15800e5b8bf2eda2edfb21ab09028409b0ed44d0810120d721f404f404d33fd315d1058e1bf82325a15210b99f326df82305aa0015a112b992306dde923033e2923033e25230800df40f6fa19ed021d721d70a00955f037fdb31e09130e259800df40f6fa19cd001d721d70a00937fdb31e0915be270801f6f2d48308d718d121f900ed44d0d3ffd31ff404f404d33fd315d1f82321a15220b98e12336df82324aa00a112b9926d32de58f82301de541675f910f2a106d0d31fd4d307d30cd309d33fd315d15168baf2a2515abaf2a6f8232aa15250bcf2a304f823bbf2a35304800df40f6fa199d024d721d70a00f2649130e20e01fe5309800df40f6fa18e13d05004d718d20001f264c858cf16cf8301cf168e1030c824cf40cf8384095005a1a514cf40e2f800c94039800df41704c8cbff13cb1ff40012f40012cb3f12cb15c9ed54f80f21d0d30001f265d3020171b0925f03e0fa4001d70b01c000f2a5fa4031fa0031f401fa0031fa00318060d721d300010f0020f265d2000193d431d19130e272b1fb00b585bf03";
const HIGHLOAD_WALLET_SUBWALLET_ID = 0x10ad;

const checkTimeout = (seconds) => {
if (!seconds) throw new Error('invalid timeout');
if (seconds < 60 * 10) throw new Error('minimum timeout 10 minute');
if (seconds > 60 * 60 * 24 * 30) throw new Error('maximum timeout 30 days');
}

class HighloadWalletContractV3 extends Contract {

/**
* @param provider {HttpProvider}
* @param options
* @param options.[publicKey] {Uint8Array}
* @param options.[timeout] {number}
* @param options.[walletId] {number}
* @param options.[address] {Address | string}
*/
constructor(provider, options) {
if (options.wc) throw new Error('only basechain (wc = 0) supported');
options.wc = 0;
if (!options.address) {
if (!options.publicKey) throw new Error('no publicKey');
if (!(options.publicKey instanceof Uint8Array)) throw new Error('publicKey not Uint8Array');
checkTimeout(options.timeout);
}
options.code = Cell.oneFromBoc(CODE_HEX);
super(provider, options);
if (!this.options.walletId) this.options.walletId = HIGHLOAD_WALLET_SUBWALLET_ID;

this.methods = {
/**
* @param params {{secretKey: Uint8Array, queryId: HighloadQueryId, createdAt: number, toAddress: Address | string, amount: BN, payload?: string | Uint8Array | Cell, sendMode?: number, needDeploy?: boolean }}
*/
transfer: (params) => Contract.createMethod(provider, this.createTransferMessage(params.secretKey, params.toAddress, params.amount, params.queryId, params.payload, params.sendMode, params.createdAt, params.needDeploy)),
}

this.methods.getPublicKey = this.getPublicKey.bind(this);
this.methods.getWalletId = this.getWalletId.bind(this);
this.methods.getLastCleanTime = this.getLastCleanTime.bind(this);
this.methods.getTimeout = this.getTimeout.bind(this);
this.methods.isProcessed = this.isProcessed.bind(this);
}

getName() {
return 'highload-3';
}

/**
* @override
* @return {Cell} cell contains wallet data
*/
createDataCell() {
if (this.options.walletId !== 0 && !this.options.walletId) throw new Error('no walletId');
if (!this.options.publicKey) throw new Error('no publicKey');
if (!(this.options.publicKey instanceof Uint8Array)) throw new Error('publicKey not Uint8Array');
checkTimeout(this.options.timeout);

const cell = new Cell();
cell.bits.writeBytes(this.options.publicKey);
cell.bits.writeUint(this.options.walletId, 32);
cell.bits.writeUint(0, 1); // empty old_queries
cell.bits.writeUint(0, 1); // empty queries
cell.bits.writeUint(0, 64); // last_clean_time
cell.bits.writeUint(this.options.timeout, 22);
return cell;
}

/**
* @private
* @param queryId {HighloadQueryId}
* @param createdAt {number}
* @param sendMode {number}
* @param messageToSend {Cell}
* @return {Cell}
*/
createSigningMessage(queryId, createdAt, sendMode, messageToSend) {
if (isNaN(sendMode) || sendMode === undefined || sendMode === null) throw new Error('invalid sendMode');
if (isNaN(createdAt) || createdAt === undefined || createdAt === null) throw new Error('invalid createdAt');
checkTimeout(this.options.timeout);

const cell = new Cell();
cell.bits.writeUint(this.options.walletId, 32);
cell.refs.push(messageToSend);
cell.bits.writeUint(sendMode, 8);
cell.bits.writeUint(Number(queryId.getShift()), 13);
cell.bits.writeUint(Number(queryId.getBitNumber()), 10);
cell.bits.writeUint(createdAt, 64);
cell.bits.writeUint(this.options.timeout, 22);
return cell;
}

/**
* @param secretKey {Uint8Array} nacl.KeyPair.secretKey
* @param address {Address | string}
* @param amount {BN | number} in nanotons
* @param queryId {HighloadQueryId}
* @param [payload] {string | Uint8Array | Cell}
* @param [sendMode] {number}
* @param createAt {number}
* @param [needDeploy] {boolean}
* @return {Promise<{address: Address, signature: Uint8Array, message: Cell, cell: Cell, body: Cell, signingMessage: Cell}>}
*/
async createTransferMessage(
secretKey,
address,
amount,
queryId,
payload = "",
sendMode = 3,
createAt,
needDeploy = false
) {
if (queryId === null || queryId === undefined) {
throw new Error('queryId must be number >= 0')
}
if (createAt === null || createAt === undefined || createAt < 0) {
throw new Error('createAt must be number >= 0')
}
if (sendMode === null || sendMode === undefined) {
sendMode = 3;
}
const messageToSend = Contract.createOutMsg(address, amount, payload);
const signingMessage = this.createSigningMessage(queryId, createAt, sendMode, messageToSend);

return this.createExternalMessage(signingMessage, secretKey, needDeploy);
}

/**
* @protected
* @param signingMessage {Cell}
* @param secretKey {Uint8Array} nacl.KeyPair.secretKey
* @param needDeploy {boolean}
* @return {Promise<{address: Address, signature: Uint8Array, message: Cell, cell: Cell, body: Cell, signingMessage: Cell}>}
*/
async createExternalMessage(
signingMessage,
secretKey,
needDeploy
) {
const signature = nacl.sign.detached(await signingMessage.hash(), secretKey);

const body = new Cell();
body.bits.writeBytes(signature);
body.refs.push(signingMessage);

let stateInit = null, code = null, data = null;

if (needDeploy) {
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,
};
}

/**
* @return {Promise<number>}
*/
async getWalletId() {
const myAddress = await this.getAddress();
const id = await this.provider.call2(myAddress.toString(), 'get_subwallet_id');
return id.toNumber();
}

/**
* @return {Promise<BN>}
*/
async getPublicKey() {
const myAddress = await this.getAddress();
return this.provider.call2(myAddress.toString(), 'get_public_key');
}

/**
* @return {Promise<number>}
*/
async getLastCleanTime() {
const myAddress = await this.getAddress();
const id = await this.provider.call2(myAddress.toString(), 'get_last_clean_time');
return id.toNumber();
}

/**
* @return {Promise<number>}
*/
async getTimeout() {
const myAddress = await this.getAddress();
const id = await this.provider.call2(myAddress.toString(), 'get_timeout');
return id.toNumber();
}

/**
* @param queryId {HighloadQueryId}
* @param needClean {boolean}
* @return {Promise<boolean>}
*/
async isProcessed(queryId, needClean) {
const myAddress = await this.getAddress();
const result = await this.provider.call2(myAddress.toString(), 'processed?', [['num', queryId.getQueryId().toString()], ['num', needClean ? '-1' : '0']]);
return !result.isZero();
}

}

HighloadWalletContractV3.WALLET_ID_BASE = HIGHLOAD_WALLET_SUBWALLET_ID;
HighloadWalletContractV3.codeHex = CODE_HEX;

module.exports = {HighloadWalletContractV3};
11 changes: 11 additions & 0 deletions src/contract/highloadWallet/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const {HighloadWalletContractV3} = require("./HighloadWalletContractV3");
const {HighloadQueryId} = require("./HighloadQueryId");

module.exports.default = {
HighloadQueryId,
HighloadWalletContractV3,
all: {
'highload-3': HighloadWalletContractV3,
},
list: [HighloadWalletContractV3]
};
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const HttpProvider = require("./providers").default;
const {Contract} = require("./contract");
const Wallets = require("./contract/wallet").default;
const LockupWallets = require("./contract/lockup").default;
const HighloadWallets = require("./contract/highloadWallet").default;
const NFT = require("./contract/token/nft").default;
const JETTON = require("./contract/token/ft").default;
const {BlockSubscription, InMemoryBlockStorage} = require("./providers/blockSubscription");
Expand Down Expand Up @@ -96,6 +97,7 @@ TonWeb.token = {
ft: JETTON,
jetton: JETTON,
}
TonWeb.HighloadWallets = HighloadWallets;
TonWeb.dns = Dns;
TonWeb.dns.DnsCollection = DnsCollection;
TonWeb.dns.DnsItem = DnsItem;
Expand Down
3 changes: 2 additions & 1 deletion src/test-highload-query-id.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const {HighloadQueryId} = require("./contract/highloadWallet/HighloadQueryId");
const TonWeb = require("./index");
const HighloadQueryId = TonWeb.HighloadWallets.HighloadQueryId;

if (HighloadQueryId.fromSeqno(0n).toSeqno() !== 0n) throw new Error();

Expand Down
53 changes: 53 additions & 0 deletions src/test-highload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const TonWeb = require("./index");
const {HighloadWalletContractV3, HighloadQueryId} = TonWeb.HighloadWallets;
const {Address, toNano} = TonWeb.utils;
const init = async () => {

const tonweb = new TonWeb(new TonWeb.HttpProvider('https://testnet.toncenter.com/api/v2/jsonRPC', {apiKey: ''}));

// Create v4 wallet

const seed = TonWeb.utils.hexToBytes('607cdaf518cd38050b536005bea2667d008d5dda1027f9549479f4a42ac315c4');

const keyPair = TonWeb.utils.nacl.sign.keyPair.fromSeed(seed);
console.log('wallet public key =', TonWeb.utils.bytesToHex(keyPair.publicKey));

const highloadWallet = new HighloadWalletContractV3(tonweb.provider, {
publicKey: keyPair.publicKey,
timeout: 60 * 60, // 1 hour
});

const highloadAddress = await highloadWallet.getAddress();

console.log('Highload-wallet address is ' + highloadAddress.toString(true, true, true));

const queryId = new HighloadQueryId();
queryId.increase();

const createAt = Math.floor(Date.now() / 1000) - 60;
console.log(createAt);

const transfer = highloadWallet.methods.transfer({
secretKey: keyPair.secretKey,
queryId: queryId,
createdAt: createAt,
toAddress: new Address('UQCdqXGvONLwOr3zCNX5FjapflorB6ZsOdcdfLrjsDLt3AF4'),
amount: toNano('0.01'), // 0.01 TON
payload: 'Hello',
sendMode: 3,
needDeploy: queryId.getQueryId() === 0n
});

// console.log(await transfer.send());

console.log('isProcessed', await highloadWallet.isProcessed(queryId, false));
console.log('isProcessed 0', await highloadWallet.isProcessed(HighloadQueryId.fromQueryId(0n), false));
console.log('isProcessed 10', await highloadWallet.isProcessed(HighloadQueryId.fromQueryId(10n), false));
console.log('getWalletId', await highloadWallet.getWalletId());
console.log('getLastCleanTime', await highloadWallet.getLastCleanTime());
console.log('getTimeout', await highloadWallet.getTimeout());
console.log('getPublicKey', await highloadWallet.getPublicKey());

}

init();

0 comments on commit ac411e4

Please sign in to comment.