diff --git a/apps/recovery-relay/lib/wallets/TON/index.ts b/apps/recovery-relay/lib/wallets/TON/index.ts index 1c8336dc..4372429e 100644 --- a/apps/recovery-relay/lib/wallets/TON/index.ts +++ b/apps/recovery-relay/lib/wallets/TON/index.ts @@ -1,33 +1,58 @@ import { Ton as BaseTon } from '@fireblocks/wallet-derivation'; -import { ConnectedWallet } from '../ConnectedWallet'; import { TonClient, WalletContractV4 } from '@ton/ton'; import { beginCell, Cell, fromNano } from '@ton/core'; import { AccountData } from '../types'; import { defaultTonWalletV4R2code } from './tonParams'; import axios from 'axios'; +import { LateInitConnectedWallet } from '../LateInitConnectedWallet'; + +export class Ton extends BaseTon implements LateInitConnectedWallet { + public memo: string | undefined; + + public updateDataEndpoint(memo?: string): void { + this.memo = memo; + } + + public getLateInitLabel(): string { + throw new Error('Method not implemented.'); + } -export class Ton extends BaseTon implements ConnectedWallet { public rpcURL: string | undefined; + public setRPCUrl(url: string): void { this.rpcURL = url; } - private client = new TonClient({ - endpoint: this.isTestnet ? 'https://testnet.toncenter.com/api/v2/jsonRPC' : 'https://toncenter.com/api/v2/jsonRPC', - }); + + private client: TonClient | undefined; + + private init() { + this.client = new TonClient({ + endpoint: this.rpcURL!, + }); + } + private tonWallet = WalletContractV4.create({ publicKey: Buffer.from(this.publicKey.replace('0x', ''), 'hex'), workchain: 0 }); public async getBalance(): Promise { - await new Promise((resolve) => setTimeout(resolve, 2000)); - const contract = this.client.open(this.tonWallet); - return Number(fromNano(await contract.getBalance())); + if (this.client) { + await new Promise((resolve) => setTimeout(resolve, 2000)); + const contract = this.client.open(this.tonWallet); + return Number(fromNano(await contract.getBalance())); + } else { + this.relayLogger.error('TON: Client failed to initialize'); + throw new Error('TON: Client failed to initialize'); + } } + public async broadcastTx(tx: string): Promise { try { + // init the TonClient + this.init(); + + // parse the tx back to Ton Cell const body = Cell.fromBoc(Buffer.from(tx, 'base64'))[0]; const pubKey = Buffer.from(this.publicKey.replace('0x', ''), 'hex'); - const externalMessage = beginCell().storeUint(0b10, 2).storeUint(0, 2).storeAddress(this.tonWallet.address).storeCoins(0); - const seqno = await this.getSeqno(); if (seqno === 0) { // for the fist transaction we initialize a state init struct which consists of init struct and code @@ -40,11 +65,16 @@ export class Ton extends BaseTon implements ConnectedWallet { } const finalExternalMessage = externalMessage.storeBit(1).storeRef(body).endCell(); - await new Promise((resolve) => setTimeout(resolve, 2000)); - await this.client.sendFile(finalExternalMessage.toBoc()); - const txHash = finalExternalMessage.hash().toString('hex'); - this.relayLogger.debug(`TON: Tx broadcasted: ${txHash}`); - return txHash; + if (this.client) { + // broadcast Tx and calc TxHash + await new Promise((resolve) => setTimeout(resolve, 2000)); + await this.client.sendFile(finalExternalMessage.toBoc()); + const txHash = finalExternalMessage.hash().toString('hex'); + this.relayLogger.debug(`TON: Tx broadcasted: ${txHash}`); + return txHash; + } else { + throw new Error('TON: Client failed to initialize'); + } } catch (e) { this.relayLogger.error(`TON: Error broadcasting tx: ${e}`); if (axios.isAxiosError(e)) { @@ -53,13 +83,17 @@ export class Ton extends BaseTon implements ConnectedWallet { throw e; } } + public async prepare(): Promise { - // get the balance - const balance = await this.getBalance(); // returned in nanoTon + // init the TonClient + this.init(); + // get the balance, returned in nanoTon + const balance = await this.getBalance(); // fee for regular tx is hardcoded to 0.02 TON const feeRate = 0.02; - await new Promise((resolve) => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 2000)); + // get seqno of the wallet, set it as exrtaParams const seqno = await this.getSeqno(); const extraParams = new Map(); @@ -67,6 +101,7 @@ export class Ton extends BaseTon implements ConnectedWallet { const preperedData = { balance, + memo: this.memo, feeRate, extraParams, insufficientBalance: balance < 0.005, @@ -76,21 +111,20 @@ export class Ton extends BaseTon implements ConnectedWallet { } private async getSeqno() { await new Promise((resolve) => setTimeout(resolve, 2000)); - return await this.client.open(this.tonWallet).getSeqno(); + return await this.client!.open(this.tonWallet).getSeqno(); } private createStateInit(pubKey: Buffer) { // the initial data cell our contract will hold. Wallet V4 has an extra value for plugins in the end const dataCell = beginCell() .storeUint(0, 32) // Seqno 0 for the first tx - .storeUint(698983191, 32) // Subwallet ID + .storeUint(698983191, 32) // Subwallet ID -> https://docs.ton.org/v3/guidelines/smart-contracts/howto/wallet#subwallet-ids .storeBuffer(pubKey) // Public Key .storeBit(0) // only for Wallet V4 .endCell(); // we take a boiler place already made WalletV4R2 code const codeCell = Cell.fromBoc(Buffer.from(defaultTonWalletV4R2code, 'base64'))[0]; - const stateInit = beginCell() .storeBit(0) // No split_depth .storeBit(0) // No special @@ -100,7 +134,6 @@ export class Ton extends BaseTon implements ConnectedWallet { .storeRef(dataCell) .storeBit(0) // No library .endCell(); - return stateInit; } }