Skip to content

Commit b0b5dce

Browse files
committed
feat(sdk-coin-ton): add ton whales deposit tx building
TICKET: SC-4503
1 parent 3dbbcfe commit b0b5dce

File tree

7 files changed

+186
-1
lines changed

7 files changed

+186
-1
lines changed

modules/sdk-coin-ton/src/lib/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export const JETTON_TRANSFER_OPCODE = 0x0f8a7ea5;
44
export const WITHDRAW_OPCODE = '00001000';
55
export const VESTING_CONTRACT_CODE_B64 =
66
'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';
7+
export const TON_WHALES_DEPOSIT_OPCODE = '2077040623';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { BaseCoin as CoinConfig } from '@bitgo/statics';
2+
import { Recipient, TransactionType } from '@bitgo/sdk-core';
3+
import { TransactionBuilder } from './transactionBuilder';
4+
import { Transaction } from './transaction';
5+
import { TON_WHALES_DEPOSIT_OPCODE } from './constants';
6+
7+
export class TonWhalesDepositBuilder extends TransactionBuilder {
8+
constructor(_coinConfig: Readonly<CoinConfig>) {
9+
super(_coinConfig);
10+
this._transaction = new Transaction(_coinConfig);
11+
}
12+
13+
protected get transactionType(): TransactionType {
14+
return TransactionType.TonWhalesDeposit;
15+
}
16+
17+
setDepositMessage(queryId?: string): TonWhalesDepositBuilder {
18+
// Deposit payload is just OpCode + QueryId.
19+
// The Amount is in the transaction value (recipient.amount)
20+
// The Gas Limit is hardcoded in Transaction.ts build()
21+
const qId = queryId || '0000000000000000';
22+
this.transaction.message = TON_WHALES_DEPOSIT_OPCODE + qId;
23+
return this;
24+
}
25+
26+
setDepositAmount(amount: string): TonWhalesDepositBuilder {
27+
if (!this.transaction.recipient) {
28+
this.transaction.recipient = { address: '', amount: amount };
29+
} else {
30+
this.transaction.recipient.amount = amount;
31+
}
32+
return this;
33+
}
34+
35+
send(recipient: Recipient): TonWhalesDepositBuilder {
36+
this.transaction.recipient = recipient;
37+
return this;
38+
}
39+
40+
setMessage(msg: string): TonWhalesDepositBuilder {
41+
throw new Error('Method not implemented.');
42+
}
43+
}

modules/sdk-coin-ton/src/lib/transaction.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { Cell } from 'tonweb/dist/types/boc/cell';
55
import { BaseKey, BaseTransaction, Entry, Recipient, TransactionRecipient, TransactionType } from '@bitgo/sdk-core';
66
import { BaseCoin as CoinConfig } from '@bitgo/statics';
77
import { TransactionExplanation, TxData } from './iface';
8-
import { WITHDRAW_OPCODE, WALLET_ID, JETTON_TRANSFER_OPCODE, VESTING_CONTRACT_WALLET_ID } from './constants';
8+
import {
9+
WITHDRAW_OPCODE,
10+
WALLET_ID,
11+
JETTON_TRANSFER_OPCODE,
12+
VESTING_CONTRACT_WALLET_ID,
13+
TON_WHALES_DEPOSIT_OPCODE,
14+
} from './constants';
915

1016
export class Transaction extends BaseTransaction {
1117
public recipient: Recipient;
@@ -127,6 +133,11 @@ export class Transaction extends BaseTransaction {
127133
payloadCell.bits.writeUint(parseInt(WITHDRAW_OPCODE, 16), 32);
128134
payloadCell.bits.writeUint(parseInt(queryId, 16), 64);
129135
payloadCell.bits.writeCoins(new BN(withdrawAmount));
136+
} else if (payload.length >= 26 && payload.substring(0, 10) === TON_WHALES_DEPOSIT_OPCODE) {
137+
const queryId = payload.substring(10, 26);
138+
payloadCell.bits.writeUint(parseInt(TON_WHALES_DEPOSIT_OPCODE, 10), 32);
139+
payloadCell.bits.writeUint(parseInt(queryId, 16), 64);
140+
payloadCell.bits.writeCoins(TonWeb.utils.toNano('1'));
130141
} else {
131142
payloadCell.bits.writeUint(0, 32);
132143
payloadCell.bits.writeString(payload);
@@ -369,6 +380,13 @@ export class Transaction extends BaseTransaction {
369380
forwardTonAmount: forwardTonAmount.toString(),
370381
message: message,
371382
};
383+
} else if (opcode === parseInt(TON_WHALES_DEPOSIT_OPCODE, 10)) {
384+
this.transactionType = TransactionType.TonWhalesDeposit;
385+
const queryId = order.loadUint(64).toNumber();
386+
// This is the gas limit, which must be read to advance the cursor
387+
// We do not need to store it
388+
order.loadCoins();
389+
payload = TON_WHALES_DEPOSIT_OPCODE + queryId.toString(16).padStart(16, '0');
372390
} else {
373391
payload = '';
374392
}

modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SingleNominatorWithdrawBuilder } from './singleNominatorWithdrawBuilder
66
import { Transaction } from './transaction';
77
import { TokenTransferBuilder } from './tokenTransferBuilder';
88
import { TokenTransaction } from './tokenTransaction';
9+
import { TonWhalesDepositBuilder } from './tonWhalesDepositBuilder';
910

1011
export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
1112
constructor(_coinConfig: Readonly<CoinConfig>) {
@@ -37,6 +38,9 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
3738
case TransactionType.SendToken:
3839
builder = this.getTokenTransferBuilder();
3940
break;
41+
case TransactionType.TonWhalesDeposit:
42+
builder = this.getTonWhalesDepositBuilder();
43+
break;
4044
default:
4145
throw new InvalidTransactionError('unsupported transaction');
4246
}
@@ -70,4 +74,8 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
7074
getWalletInitializationBuilder(): void {
7175
throw new Error('Method not implemented.');
7276
}
77+
78+
getTonWhalesDepositBuilder(): TonWhalesDepositBuilder {
79+
return new TonWhalesDepositBuilder(this._coinConfig);
80+
}
7381
}

modules/sdk-coin-ton/test/resources/ton.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,21 @@ export const signedSingleNominatorWithdrawTransaction = {
146146
amount: '123400000',
147147
},
148148
};
149+
150+
export const signedTonWhalesDepositTransaction = {
151+
recipient: {
152+
//https://testnet.tonscan.org/address/kQDr9Sq482A6ikIUh5mUUjJaBUUJBrye13CJiDB-R31_l7mg
153+
address: 'EQDr9Sq482A6ikIUh5mUUjJaBUUJBrye13CJiDB-R31_lwIq',
154+
amount: '10000000000', // 10 TON
155+
},
156+
// This is the raw TX from sandboxing a deposit to Ton Whales
157+
tx: 'te6cckEBAgEAvAAB4YgAyB87FgBG4jQNUAYzcmw2aJG9QQeQmtOPGsRPvX+eMdwFf6OLyGMsPoPXNPLUqMoUZTIrdu2maNNUK52q+Wa0BJhNq9e/qHXYsF9xU5TYbOsZt1EBGJf1GpkumdgXj0/4CU1NGLtKFdHwAAAC4AAcAQCLYgB1+pVcebAdRSEKQ8zKKRktAqKEg15Pa7hExBg/I76/y6gSoF8gAAAAAAAAAAAAAAAAAAB7zR/vAAAAAGlCugJDuaygCErRw2Y=',
158+
seqno: 92,
159+
queryId: '000000006942ba02',
160+
expireTime: 1765980734,
161+
sender: 'EQBkD52LACNxGgaoAxm5Nhs0SN6gg8hNaceNYifev88Y7qoZ',
162+
publicKey: '9d6d3714aeb1f007f6e6aa728f79fdd005ea2c7ad459b2f54d73f9e672426230',
163+
signature:
164+
'aff471790c6587d07ae69e5a9519428ca6456eddb4cd1a6a8573b55f2cd6809309b57af7f50ebb160bee2a729b0d9d6336ea202312fea35325d33b02f1e9ff01',
165+
bounceable: true,
166+
};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import should from 'should';
2+
import { TransactionType } from '@bitgo/sdk-core';
3+
import { TransactionBuilderFactory } from '../../src'; // Adjust path as needed
4+
import { coins } from '@bitgo/statics';
5+
import * as testData from '../resources/ton';
6+
import { TON_WHALES_DEPOSIT_OPCODE } from '../../src/lib/constants';
7+
8+
describe('Ton Whales Deposit Builder', () => {
9+
const factory = new TransactionBuilderFactory(coins.get('tton'));
10+
const fixture = testData.signedTonWhalesDepositTransaction;
11+
12+
it('should parse a raw transaction and extract correct parameters', async function () {
13+
const txBuilder = factory.from(fixture.tx);
14+
const builtTx = await txBuilder.build();
15+
const jsonTx = builtTx.toJson();
16+
17+
// Verify Business Logic Fields
18+
should.equal(builtTx.type, TransactionType.TonWhalesDeposit);
19+
should.equal(jsonTx.amount, fixture.recipient.amount);
20+
should.equal(jsonTx.destination, fixture.recipient.address);
21+
should.equal(jsonTx.sender, fixture.sender);
22+
23+
// Verify Network Constraints
24+
should.equal(jsonTx.seqno, fixture.seqno);
25+
should.equal(jsonTx.expirationTime, fixture.expireTime);
26+
should.equal(jsonTx.bounceable, fixture.bounceable);
27+
28+
// Verify Payload Structure (OpCode Check)
29+
const msg = builtTx['message'] || '';
30+
should.equal(msg.startsWith(TON_WHALES_DEPOSIT_OPCODE), true);
31+
});
32+
33+
it('should parse and rebuild the transaction resulting in the same hex', async function () {
34+
const txBuilder = factory.from(fixture.tx);
35+
const builtTx = await txBuilder.build();
36+
37+
// Verify the parser extracted the signature
38+
const signature = builtTx.signature[0];
39+
should.exist(signature);
40+
signature.should.not.be.empty();
41+
42+
// Rebuild from the parsed object
43+
const builder2 = factory.from(builtTx.toBroadcastFormat());
44+
const builtTx2 = await builder2.build();
45+
46+
// The output of the second build should match the original raw transaction
47+
should.equal(builtTx2.toBroadcastFormat(), fixture.tx);
48+
should.equal(builtTx2.type, TransactionType.TonWhalesDeposit);
49+
});
50+
51+
it('should build a transaction from scratch that byte-for-byte matches the raw fixture', async function () {
52+
const builder = factory.getTonWhalesDepositBuilder();
53+
54+
// Set Header Info from Fixture
55+
builder.sender(fixture.sender);
56+
builder.publicKey(fixture.publicKey);
57+
builder.sequenceNumber(fixture.seqno);
58+
builder.expireTime(fixture.expireTime);
59+
builder.bounceable(fixture.bounceable);
60+
61+
// Set Staking Info from Fixture
62+
builder.send({
63+
address: fixture.recipient.address,
64+
amount: fixture.recipient.amount,
65+
});
66+
builder.setDepositAmount(fixture.recipient.amount);
67+
68+
// Set the specific QueryID from Fixture so binary hash matches
69+
builder.setDepositMessage(fixture.queryId);
70+
71+
// Attach Signature from Fixture (Mocking the HSM signing process)
72+
if (fixture.signature) {
73+
builder.addSignature({ pub: fixture.publicKey }, Buffer.from(fixture.signature, 'hex'));
74+
}
75+
76+
// Build Signed Transaction
77+
const signedBuiltTx = await builder.build();
78+
79+
// Final Assertion: Byte-for-byte equality with the Sandbox output
80+
should.equal(signedBuiltTx.toBroadcastFormat(), fixture.tx);
81+
should.equal(signedBuiltTx.type, TransactionType.TonWhalesDeposit);
82+
});
83+
84+
it('should parse the bounceable flag correctly', async function () {
85+
const txBuilder = factory.from(fixture.tx);
86+
const tx = await txBuilder.build();
87+
88+
// The fixture is set to true, so the parser must reflect that
89+
const isBounceable = tx.toJson().bounceable;
90+
should.equal(isBounceable, fixture.bounceable);
91+
should.equal(typeof isBounceable, 'boolean');
92+
});
93+
});

modules/sdk-core/src/account-lib/baseCoin/enum.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export enum TransactionType {
119119

120120
// flrp
121121
ImportToC,
122+
123+
// ton whales
124+
TonWhalesDeposit,
125+
TonWhalesWithdraw,
122126
}
123127

124128
/**

0 commit comments

Comments
 (0)