Skip to content

Commit

Permalink
feat: add TON support
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerHFB authored and a0ngo committed Dec 1, 2024
1 parent 7f3e56f commit 13ea16e
Showing 1 changed file with 55 additions and 22 deletions.
77 changes: 55 additions & 22 deletions apps/recovery-relay/lib/wallets/TON/index.ts
Original file line number Diff line number Diff line change
@@ -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<number> {
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<string> {
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
Expand All @@ -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)) {
Expand All @@ -53,20 +83,25 @@ export class Ton extends BaseTon implements ConnectedWallet {
throw e;
}
}

public async prepare(): Promise<AccountData> {
// 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<string, any>();
extraParams.set('seqno', seqno);

const preperedData = {
balance,
memo: this.memo,
feeRate,
extraParams,
insufficientBalance: balance < 0.005,
Expand All @@ -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
Expand All @@ -100,7 +134,6 @@ export class Ton extends BaseTon implements ConnectedWallet {
.storeRef(dataCell)
.storeBit(0) // No library
.endCell();

return stateInit;
}
}

0 comments on commit 13ea16e

Please sign in to comment.