From 8565d9f77b791a36991efad96d0cd22f51848573 Mon Sep 17 00:00:00 2001 From: skod-crypto Date: Thu, 12 Feb 2026 17:02:13 -0500 Subject: [PATCH] feat(sdk-coin-ton): vesting withdrawal builder --- modules/sdk-coin-ton/src/lib/index.ts | 1 + .../lib/tonWhalesVestingWithdrawBuilder.ts | 35 +++++ .../src/lib/transactionBuilderFactory.ts | 8 +- modules/sdk-coin-ton/test/resources/ton.ts | 12 ++ .../unit/tonWhalesVestingWithdrawBuilder.ts | 120 ++++++++++++++++++ 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 modules/sdk-coin-ton/src/lib/tonWhalesVestingWithdrawBuilder.ts create mode 100644 modules/sdk-coin-ton/test/unit/tonWhalesVestingWithdrawBuilder.ts diff --git a/modules/sdk-coin-ton/src/lib/index.ts b/modules/sdk-coin-ton/src/lib/index.ts index 0c2d0b67c1..5cd31f2a7d 100644 --- a/modules/sdk-coin-ton/src/lib/index.ts +++ b/modules/sdk-coin-ton/src/lib/index.ts @@ -9,4 +9,5 @@ export { TransactionBuilder } from './transactionBuilder'; export { TransferBuilder } from './transferBuilder'; export { TransactionBuilderFactory } from './transactionBuilderFactory'; export { TonWhalesVestingDepositBuilder } from './tonWhalesVestingDepositBuilder'; +export { TonWhalesVestingWithdrawBuilder } from './tonWhalesVestingWithdrawBuilder'; export { Interface, Utils }; diff --git a/modules/sdk-coin-ton/src/lib/tonWhalesVestingWithdrawBuilder.ts b/modules/sdk-coin-ton/src/lib/tonWhalesVestingWithdrawBuilder.ts new file mode 100644 index 0000000000..d56939b748 --- /dev/null +++ b/modules/sdk-coin-ton/src/lib/tonWhalesVestingWithdrawBuilder.ts @@ -0,0 +1,35 @@ +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import { Recipient, TransactionType } from '@bitgo/sdk-core'; +import { TransactionBuilder } from './transactionBuilder'; +import { Transaction } from './transaction'; + +export class TonWhalesVestingWithdrawBuilder extends TransactionBuilder { + constructor(_coinConfig: Readonly) { + super(_coinConfig); + this._transaction = new Transaction(_coinConfig); + } + protected get transactionType(): TransactionType { + return TransactionType.TonWhalesVestingWithdrawal; + } + setWithdrawMessage(): TonWhalesVestingWithdrawBuilder { + this.transaction.message = 'Withdraw'; + return this; + } + + setForwardAmount(amount: string): TonWhalesVestingWithdrawBuilder { + if (!this.transaction.recipient) { + this.transaction.recipient = { address: '', amount: amount }; + } else { + this.transaction.recipient.amount = amount; + } + return this; + } + + send(recipient: Recipient): TonWhalesVestingWithdrawBuilder { + this.transaction.recipient = recipient; + return this; + } + setMessage(msg: string): TonWhalesVestingWithdrawBuilder { + throw new Error('Method not implemented. Use setWithdrawMessage() instead.'); + } +} diff --git a/modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts index 67c2c90b0e..0890f12ec3 100644 --- a/modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts +++ b/modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts @@ -9,6 +9,7 @@ import { TokenTransaction } from './tokenTransaction'; import { TonWhalesDepositBuilder } from './tonWhalesDepositBuilder'; import { TonWhalesWithdrawalBuilder } from './tonWhalesWithdrawalBuilder'; import { TonWhalesVestingDepositBuilder } from './tonWhalesVestingDepositBuilder'; +import { TonWhalesVestingWithdrawBuilder } from './tonWhalesVestingWithdrawBuilder'; export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { constructor(_coinConfig: Readonly) { @@ -50,7 +51,8 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { builder = this.getTonWhalesVestingDepositBuilder(); break; case TransactionType.TonWhalesVestingWithdrawal: - throw new InvalidTransactionError('TonWhalesVestingWithdrawal builder not implemented yet'); + builder = this.getTonWhalesVestingWithdrawBuilder(); + break; default: throw new InvalidTransactionError('unsupported transaction'); } @@ -96,4 +98,8 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { getTonWhalesVestingDepositBuilder(): TonWhalesVestingDepositBuilder { return new TonWhalesVestingDepositBuilder(this._coinConfig); } + + getTonWhalesVestingWithdrawBuilder(): TonWhalesVestingWithdrawBuilder { + return new TonWhalesVestingWithdrawBuilder(this._coinConfig); + } } diff --git a/modules/sdk-coin-ton/test/resources/ton.ts b/modules/sdk-coin-ton/test/resources/ton.ts index 1349d89c84..13b34aad4b 100644 --- a/modules/sdk-coin-ton/test/resources/ton.ts +++ b/modules/sdk-coin-ton/test/resources/ton.ts @@ -218,3 +218,15 @@ export const tonWhalesVestingDepositFixture = { expireTime: 1234567890, bounceable: true, }; + +export const tonWhalesVestingWithdrawFixture = { + recipient: { + address: 'EQDr9Sq482A6ikIUh5mUUjJaBUUJBrye13CJiDB-R31_lwIq', + amount: '200000000', + }, + sender: 'EQBkD52LACNxGgaoAxm5Nhs0SN6gg8hNaceNYifev88Y7qoZ', + publicKey: '9d6d3714aeb1f007f6e6aa728f79fdd005ea2c7ad459b2f54d73f9e672426230', + seqno: 0, + expireTime: 1234567890, + bounceable: true, +}; diff --git a/modules/sdk-coin-ton/test/unit/tonWhalesVestingWithdrawBuilder.ts b/modules/sdk-coin-ton/test/unit/tonWhalesVestingWithdrawBuilder.ts new file mode 100644 index 0000000000..7c5ea193a4 --- /dev/null +++ b/modules/sdk-coin-ton/test/unit/tonWhalesVestingWithdrawBuilder.ts @@ -0,0 +1,120 @@ +import should from 'should'; +import { TransactionType } from '@bitgo/sdk-core'; +import { TransactionBuilderFactory } from '../../src'; +import { coins } from '@bitgo/statics'; +import * as testData from '../resources/ton'; + +describe('Ton Whales Vesting Withdraw Builder', () => { + const factory = new TransactionBuilderFactory(coins.get('tton')); + const fixture = testData.tonWhalesVestingWithdrawFixture; + + it('should build an unsigned vesting withdraw transaction', async function () { + const txBuilder = factory.getTonWhalesVestingWithdrawBuilder(); + + txBuilder.sender(fixture.sender); + txBuilder.publicKey(fixture.publicKey); + txBuilder.sequenceNumber(fixture.seqno); + txBuilder.expireTime(fixture.expireTime); + txBuilder.bounceable(fixture.bounceable); + + txBuilder.send({ + address: fixture.recipient.address, + amount: fixture.recipient.amount, + }); + txBuilder.setForwardAmount(fixture.recipient.amount); + txBuilder.setWithdrawMessage(); + + const tx = await txBuilder.build(); + + should.equal(tx.type, TransactionType.TonWhalesVestingWithdrawal); + should.equal(tx.toJson().bounceable, fixture.bounceable); + should.equal(tx.toJson().destination, fixture.recipient.address); + should.equal(tx.toJson().amount, fixture.recipient.amount); + + tx.inputs.length.should.equal(1); + tx.inputs[0].should.deepEqual({ + address: fixture.sender, + value: fixture.recipient.amount, + coin: 'tton', + }); + + tx.outputs.length.should.equal(1); + tx.outputs[0].should.deepEqual({ + address: fixture.recipient.address, + value: fixture.recipient.amount, + coin: 'tton', + }); + }); + + it('should build and parse a vesting withdraw transaction', async function () { + const txBuilder = factory.getTonWhalesVestingWithdrawBuilder(); + + txBuilder.sender(fixture.sender); + txBuilder.publicKey(fixture.publicKey); + txBuilder.sequenceNumber(fixture.seqno); + txBuilder.expireTime(fixture.expireTime); + txBuilder.bounceable(fixture.bounceable); + + txBuilder.send({ + address: fixture.recipient.address, + amount: fixture.recipient.amount, + }); + txBuilder.setForwardAmount(fixture.recipient.amount); + txBuilder.setWithdrawMessage(); + + const tx = await txBuilder.build(); + const rawTx = tx.toBroadcastFormat(); + + const txBuilder2 = factory.from(rawTx); + const tx2 = await txBuilder2.build(); + + should.equal(tx2.type, TransactionType.TonWhalesVestingWithdrawal); + should.equal(tx2.toBroadcastFormat(), rawTx); + }); + + it('should set the correct message for vesting withdraw', async function () { + const txBuilder = factory.getTonWhalesVestingWithdrawBuilder(); + + txBuilder.sender(fixture.sender); + txBuilder.publicKey(fixture.publicKey); + txBuilder.sequenceNumber(fixture.seqno); + txBuilder.expireTime(fixture.expireTime); + txBuilder.bounceable(fixture.bounceable); + + txBuilder.send({ + address: fixture.recipient.address, + amount: fixture.recipient.amount, + }); + txBuilder.setForwardAmount(fixture.recipient.amount); + txBuilder.setWithdrawMessage(); + + const tx = await txBuilder.build(); + const message = tx['message']; + should.equal(message, 'Withdraw'); + }); + + it('should support vesting contract specific flags', async function () { + const txBuilder = factory.getTonWhalesVestingWithdrawBuilder(); + + txBuilder.sender(fixture.sender); + txBuilder.publicKey(fixture.publicKey); + txBuilder.sequenceNumber(fixture.seqno); + txBuilder.expireTime(fixture.expireTime); + txBuilder.bounceable(true); + txBuilder.isV3ContractMessage(true); + txBuilder.subWalletId(268); + + txBuilder.send({ + address: fixture.recipient.address, + amount: fixture.recipient.amount, + }); + txBuilder.setForwardAmount(fixture.recipient.amount); + txBuilder.setWithdrawMessage(); + + const tx = await txBuilder.build(); + + should.equal(tx.type, TransactionType.TonWhalesVestingWithdrawal); + should.equal(tx.toJson().bounceable, true); + should.equal(tx.toJson().sub_wallet_id, 268); + }); +});