Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/sdk-coin-ton/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const JETTON_TRANSFER_OPCODE = 0x0f8a7ea5;
export const WITHDRAW_OPCODE = '00001000';
export const VESTING_CONTRACT_CODE_B64 =
'te6cckECHAEAA/sAART/APSkE/S88sgLAQIBIAISAgFIAwUDrNBsIiDXScFgkVvgAdDTAwFxsJFb4PpAMNs8AdMf0z/4S1JAxwUjghCnczrNurCOpGwS2zyCEPdzOs0BcIAYyMsFUATPFiP6AhPLassfyz/JgED7AOMOExQEAc74SlJAxwUDghByWKabuhOwjtGOLAH6QH/IygAC+kQByMoHy//J0PhEECOBAQj0QfhkINdKwgAglQHUMNAB3rMS5oIQ8limmzJwgBjIywVQBM8WI/oCE8tqyx/LP8mAQPsA2zySXwPiGwIBIAYPAgEgBwoCAW4ICQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwAIBYgsMAUutNG2eNvwiRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQBMCAWoNDgAPol+1E0NcLH4BL6LHbPPpEAcjKB8v/ydD4RIEBCPQKb6ExhMCASAQEQEpukYts8+EX4RvhH+Ej4SfhK+Ev4RIEwINuYRts82zyBMVA7jygwjXGCDTH9Mf0x8C+CO78mTtRNDTH9Mf0/8wWrryoVAzuvKiAvkBQDP5EPKj+ADbPCDXSsABjpntRO1F7UeRW+1n7WXtZI6C2zztQe3xAfL/kTDi+EGk+GHbPBMUGwB+7UTQ0x8B+GHTHwH4YtP/Afhj9AQB+GTUAdDTPwH4ZdMfAfhm0x8B+GfTHwH4aPoAAfhp+kAB+Gr6QAH4a9HRAlzTB9TR+CPbPCDCAI6bIsAD8uBkIdDTA/pAMfpA+EpSIMcFs5JfBOMNkTDiAfsAFRYAYPhF+EagUhC8kjBw4PhF+EigUhC5kzD4SeD4SfhJ+EUTofhHqQT4RvhHqQQQI6mEoQP6IfpEAcjKB8v/ydD4RIEBCPQKb6Exj18zAXKwwALy4GUB+gAxcdch+gAx+gAx0z8x0x8x0wABwADy4GbTAAGT1DDQ3iFx2zyOKjHTHzAgghBOc3RLuiGCEEdldCS6sSGCEFZ0Q3C6sQGCEFZvdGW6sfLgZ+MOcJJfA+IgwgAYFxoC6gFw2zyObSDXScIAjmPTHyHAACKDC7qxIoEQAbqxIoIQR9VDkbqxIoIQWV8HvLqxIoIQafswbLqxIoIQVm90ZbqxIoIQVnRDcLqx8uBnAcAAIddJwgCwjhXTBzAgwGQhwHexIcBEsQHAV7Hy4GiRMOKRMOLjDRgZAEQB+kQBw/+SW3DgAfgzIG6SW3Dg0CDXSYMHuZJbcODXC/+6ABrTHzCCEFZvdGW68uBnAA6TcvsCkTDiAGb4SPhH+Eb4RcjLP8sfyx/LH/hJ+gL4Ss8W+EvPFsn4RPhD+EL4QcjLH8sfy//0AMzJ7VSo1+S9';
export const TON_WHALES_DEPOSIT_OPCODE = '2077040623';
43 changes: 43 additions & 0 deletions modules/sdk-coin-ton/src/lib/tonWhalesDepositBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { Recipient, TransactionType } from '@bitgo/sdk-core';
import { TransactionBuilder } from './transactionBuilder';
import { Transaction } from './transaction';
import { TON_WHALES_DEPOSIT_OPCODE } from './constants';

export class TonWhalesDepositBuilder extends TransactionBuilder {
constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._transaction = new Transaction(_coinConfig);
}

protected get transactionType(): TransactionType {
return TransactionType.TonWhalesDeposit;
}

setDepositMessage(queryId?: string): TonWhalesDepositBuilder {
// Deposit payload is just OpCode + QueryId.
// The Amount is in the transaction value (recipient.amount)
// The Gas Limit is hardcoded in Transaction.ts build()
const qId = queryId || '0000000000000000';
this.transaction.message = TON_WHALES_DEPOSIT_OPCODE + qId;
return this;
}

setDepositAmount(amount: string): TonWhalesDepositBuilder {
if (!this.transaction.recipient) {
this.transaction.recipient = { address: '', amount: amount };
} else {
this.transaction.recipient.amount = amount;
}
return this;
}

send(recipient: Recipient): TonWhalesDepositBuilder {
this.transaction.recipient = recipient;
return this;
}

setMessage(msg: string): TonWhalesDepositBuilder {
throw new Error('Method not implemented.');
}
}
20 changes: 19 additions & 1 deletion modules/sdk-coin-ton/src/lib/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { Cell } from 'tonweb/dist/types/boc/cell';
import { BaseKey, BaseTransaction, Entry, Recipient, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { TransactionExplanation, TxData } from './iface';
import { WITHDRAW_OPCODE, WALLET_ID, JETTON_TRANSFER_OPCODE, VESTING_CONTRACT_WALLET_ID } from './constants';
import {
WITHDRAW_OPCODE,
WALLET_ID,
JETTON_TRANSFER_OPCODE,
VESTING_CONTRACT_WALLET_ID,
TON_WHALES_DEPOSIT_OPCODE,
} from './constants';

export class Transaction extends BaseTransaction {
public recipient: Recipient;
Expand Down Expand Up @@ -127,6 +133,11 @@ export class Transaction extends BaseTransaction {
payloadCell.bits.writeUint(parseInt(WITHDRAW_OPCODE, 16), 32);
payloadCell.bits.writeUint(parseInt(queryId, 16), 64);
payloadCell.bits.writeCoins(new BN(withdrawAmount));
} else if (payload.length >= 26 && payload.substring(0, 10) === TON_WHALES_DEPOSIT_OPCODE) {
const queryId = payload.substring(10, 26);
payloadCell.bits.writeUint(parseInt(TON_WHALES_DEPOSIT_OPCODE, 10), 32);
payloadCell.bits.writeUint(parseInt(queryId, 16), 64);
payloadCell.bits.writeCoins(TonWeb.utils.toNano('1'));
} else {
payloadCell.bits.writeUint(0, 32);
payloadCell.bits.writeString(payload);
Expand Down Expand Up @@ -369,6 +380,13 @@ export class Transaction extends BaseTransaction {
forwardTonAmount: forwardTonAmount.toString(),
message: message,
};
} else if (opcode === parseInt(TON_WHALES_DEPOSIT_OPCODE, 10)) {
this.transactionType = TransactionType.TonWhalesDeposit;
const queryId = order.loadUint(64).toNumber();
// This is the gas limit, which must be read to advance the cursor
// We do not need to store it
order.loadCoins();
payload = TON_WHALES_DEPOSIT_OPCODE + queryId.toString(16).padStart(16, '0');
} else {
payload = '';
}
Expand Down
8 changes: 8 additions & 0 deletions modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SingleNominatorWithdrawBuilder } from './singleNominatorWithdrawBuilder
import { Transaction } from './transaction';
import { TokenTransferBuilder } from './tokenTransferBuilder';
import { TokenTransaction } from './tokenTransaction';
import { TonWhalesDepositBuilder } from './tonWhalesDepositBuilder';

export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
constructor(_coinConfig: Readonly<CoinConfig>) {
Expand Down Expand Up @@ -37,6 +38,9 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
case TransactionType.SendToken:
builder = this.getTokenTransferBuilder();
break;
case TransactionType.TonWhalesDeposit:
builder = this.getTonWhalesDepositBuilder();
break;
default:
throw new InvalidTransactionError('unsupported transaction');
}
Expand Down Expand Up @@ -70,4 +74,8 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
getWalletInitializationBuilder(): void {
throw new Error('Method not implemented.');
}

getTonWhalesDepositBuilder(): TonWhalesDepositBuilder {
return new TonWhalesDepositBuilder(this._coinConfig);
}
}
18 changes: 18 additions & 0 deletions modules/sdk-coin-ton/test/resources/ton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,21 @@ export const signedSingleNominatorWithdrawTransaction = {
amount: '123400000',
},
};

export const signedTonWhalesDepositTransaction = {
recipient: {
//https://testnet.tonscan.org/address/kQDr9Sq482A6ikIUh5mUUjJaBUUJBrye13CJiDB-R31_l7mg
address: 'EQDr9Sq482A6ikIUh5mUUjJaBUUJBrye13CJiDB-R31_lwIq',
amount: '10000000000', // 10 TON
},
// This is the raw TX from sandboxing a deposit to Ton Whales
tx: 'te6cckEBAgEAvAAB4YgAyB87FgBG4jQNUAYzcmw2aJG9QQeQmtOPGsRPvX+eMdwFf6OLyGMsPoPXNPLUqMoUZTIrdu2maNNUK52q+Wa0BJhNq9e/qHXYsF9xU5TYbOsZt1EBGJf1GpkumdgXj0/4CU1NGLtKFdHwAAAC4AAcAQCLYgB1+pVcebAdRSEKQ8zKKRktAqKEg15Pa7hExBg/I76/y6gSoF8gAAAAAAAAAAAAAAAAAAB7zR/vAAAAAGlCugJDuaygCErRw2Y=',
seqno: 92,
queryId: '000000006942ba02',
expireTime: 1765980734,
sender: 'EQBkD52LACNxGgaoAxm5Nhs0SN6gg8hNaceNYifev88Y7qoZ',
publicKey: '9d6d3714aeb1f007f6e6aa728f79fdd005ea2c7ad459b2f54d73f9e672426230',
signature:
'aff471790c6587d07ae69e5a9519428ca6456eddb4cd1a6a8573b55f2cd6809309b57af7f50ebb160bee2a729b0d9d6336ea202312fea35325d33b02f1e9ff01',
bounceable: true,
};
93 changes: 93 additions & 0 deletions modules/sdk-coin-ton/test/unit/tonWhalesDepositBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import should from 'should';
import { TransactionType } from '@bitgo/sdk-core';
import { TransactionBuilderFactory } from '../../src'; // Adjust path as needed
import { coins } from '@bitgo/statics';
import * as testData from '../resources/ton';
import { TON_WHALES_DEPOSIT_OPCODE } from '../../src/lib/constants';

describe('Ton Whales Deposit Builder', () => {
const factory = new TransactionBuilderFactory(coins.get('tton'));
const fixture = testData.signedTonWhalesDepositTransaction;

it('should parse a raw transaction and extract correct parameters', async function () {
const txBuilder = factory.from(fixture.tx);
const builtTx = await txBuilder.build();
const jsonTx = builtTx.toJson();

// Verify Business Logic Fields
should.equal(builtTx.type, TransactionType.TonWhalesDeposit);
should.equal(jsonTx.amount, fixture.recipient.amount);
should.equal(jsonTx.destination, fixture.recipient.address);
should.equal(jsonTx.sender, fixture.sender);

// Verify Network Constraints
should.equal(jsonTx.seqno, fixture.seqno);
should.equal(jsonTx.expirationTime, fixture.expireTime);
should.equal(jsonTx.bounceable, fixture.bounceable);

// Verify Payload Structure (OpCode Check)
const msg = builtTx['message'] || '';
should.equal(msg.startsWith(TON_WHALES_DEPOSIT_OPCODE), true);
});

it('should parse and rebuild the transaction resulting in the same hex', async function () {
const txBuilder = factory.from(fixture.tx);
const builtTx = await txBuilder.build();

// Verify the parser extracted the signature
const signature = builtTx.signature[0];
should.exist(signature);
signature.should.not.be.empty();

// Rebuild from the parsed object
const builder2 = factory.from(builtTx.toBroadcastFormat());
const builtTx2 = await builder2.build();

// The output of the second build should match the original raw transaction
should.equal(builtTx2.toBroadcastFormat(), fixture.tx);
should.equal(builtTx2.type, TransactionType.TonWhalesDeposit);
});

it('should build a transaction from scratch that byte-for-byte matches the raw fixture', async function () {
const builder = factory.getTonWhalesDepositBuilder();

// Set Header Info from Fixture
builder.sender(fixture.sender);
builder.publicKey(fixture.publicKey);
builder.sequenceNumber(fixture.seqno);
builder.expireTime(fixture.expireTime);
builder.bounceable(fixture.bounceable);

// Set Staking Info from Fixture
builder.send({
address: fixture.recipient.address,
amount: fixture.recipient.amount,
});
builder.setDepositAmount(fixture.recipient.amount);

// Set the specific QueryID from Fixture so binary hash matches
builder.setDepositMessage(fixture.queryId);

// Attach Signature from Fixture (Mocking the HSM signing process)
if (fixture.signature) {
builder.addSignature({ pub: fixture.publicKey }, Buffer.from(fixture.signature, 'hex'));
}

// Build Signed Transaction
const signedBuiltTx = await builder.build();

// Final Assertion: Byte-for-byte equality with the Sandbox output
should.equal(signedBuiltTx.toBroadcastFormat(), fixture.tx);
should.equal(signedBuiltTx.type, TransactionType.TonWhalesDeposit);
});

it('should parse the bounceable flag correctly', async function () {
const txBuilder = factory.from(fixture.tx);
const tx = await txBuilder.build();

// The fixture is set to true, so the parser must reflect that
const isBounceable = tx.toJson().bounceable;
should.equal(isBounceable, fixture.bounceable);
should.equal(typeof isBounceable, 'boolean');
});
});
4 changes: 4 additions & 0 deletions modules/sdk-core/src/account-lib/baseCoin/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ export enum TransactionType {

// flrp
ImportToC,

// ton whales
TonWhalesDeposit,
TonWhalesWithdraw,
}

/**
Expand Down