diff --git a/modules/sdk-coin-ton/src/lib/index.ts b/modules/sdk-coin-ton/src/lib/index.ts index 5e85dbb20d..0c2d0b67c1 100644 --- a/modules/sdk-coin-ton/src/lib/index.ts +++ b/modules/sdk-coin-ton/src/lib/index.ts @@ -8,4 +8,5 @@ export { TokenTransferBuilder } from './tokenTransferBuilder'; export { TransactionBuilder } from './transactionBuilder'; export { TransferBuilder } from './transferBuilder'; export { TransactionBuilderFactory } from './transactionBuilderFactory'; +export { TonWhalesVestingDepositBuilder } from './tonWhalesVestingDepositBuilder'; export { Interface, Utils }; diff --git a/modules/sdk-coin-ton/src/lib/tonWhalesVestingDepositBuilder.ts b/modules/sdk-coin-ton/src/lib/tonWhalesVestingDepositBuilder.ts new file mode 100644 index 0000000000..3a18a93ebc --- /dev/null +++ b/modules/sdk-coin-ton/src/lib/tonWhalesVestingDepositBuilder.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 TonWhalesVestingDepositBuilder extends TransactionBuilder { + constructor(_coinConfig: Readonly) { + super(_coinConfig); + this._transaction = new Transaction(_coinConfig); + } + protected get transactionType(): TransactionType { + return TransactionType.TonWhalesVestingDeposit; + } + setDepositMessage(): TonWhalesVestingDepositBuilder { + this.transaction.message = 'Deposit'; + return this; + } + + setDepositAmount(amount: string): TonWhalesVestingDepositBuilder { + if (!this.transaction.recipient) { + this.transaction.recipient = { address: '', amount: amount }; + } else { + this.transaction.recipient.amount = amount; + } + return this; + } + + send(recipient: Recipient): TonWhalesVestingDepositBuilder { + this.transaction.recipient = recipient; + return this; + } + setMessage(msg: string): TonWhalesVestingDepositBuilder { + throw new Error('Method not implemented. Use setDepositMessage() instead.'); + } +} diff --git a/modules/sdk-coin-ton/src/lib/transaction.ts b/modules/sdk-coin-ton/src/lib/transaction.ts index 19a6ca0f97..2c251a2adb 100644 --- a/modules/sdk-coin-ton/src/lib/transaction.ts +++ b/modules/sdk-coin-ton/src/lib/transaction.ts @@ -355,6 +355,11 @@ export class Transaction extends BaseTransaction { if (opcode === 0) { const payloadBytes = order.loadBits(order.getFreeBits()); payload = new TextDecoder().decode(payloadBytes); + if (payload === 'Deposit') { + this.transactionType = TransactionType.TonWhalesVestingDeposit; + } else if (payload === 'Withdraw') { + this.transactionType = TransactionType.TonWhalesVestingWithdrawal; + } } else if (opcode === 4096) { const queryId = order.loadUint(64).toNumber(); withdrawAmount = order.loadCoins().toNumber().toString(); diff --git a/modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts index 9867e6f3ff..67c2c90b0e 100644 --- a/modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts +++ b/modules/sdk-coin-ton/src/lib/transactionBuilderFactory.ts @@ -8,6 +8,7 @@ import { TokenTransferBuilder } from './tokenTransferBuilder'; import { TokenTransaction } from './tokenTransaction'; import { TonWhalesDepositBuilder } from './tonWhalesDepositBuilder'; import { TonWhalesWithdrawalBuilder } from './tonWhalesWithdrawalBuilder'; +import { TonWhalesVestingDepositBuilder } from './tonWhalesVestingDepositBuilder'; export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { constructor(_coinConfig: Readonly) { @@ -45,6 +46,11 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { case TransactionType.TonWhalesWithdrawal: builder = this.getTonWhalesWithdrawalBuilder(); break; + case TransactionType.TonWhalesVestingDeposit: + builder = this.getTonWhalesVestingDepositBuilder(); + break; + case TransactionType.TonWhalesVestingWithdrawal: + throw new InvalidTransactionError('TonWhalesVestingWithdrawal builder not implemented yet'); default: throw new InvalidTransactionError('unsupported transaction'); } @@ -86,4 +92,8 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { getTonWhalesWithdrawalBuilder(): TonWhalesWithdrawalBuilder { return new TonWhalesWithdrawalBuilder(this._coinConfig); } + + getTonWhalesVestingDepositBuilder(): TonWhalesVestingDepositBuilder { + return new TonWhalesVestingDepositBuilder(this._coinConfig); + } } diff --git a/modules/sdk-coin-ton/test/resources/ton.ts b/modules/sdk-coin-ton/test/resources/ton.ts index 2396cefa47..1349d89c84 100644 --- a/modules/sdk-coin-ton/test/resources/ton.ts +++ b/modules/sdk-coin-ton/test/resources/ton.ts @@ -206,3 +206,15 @@ export const getMemberStackFixture = { rawAddress: '0:640f9d8b0023711a06a80319b9361b3448dea083c84d69c78d6227debfcf18ee', boc: 'te6cckEBAQEAJAAAQ4AMgfOxYARuI0DVAGM3JsNmiRvUEHkJrTjxrET71/njHdDqtWeh', }; + +export const tonWhalesVestingDepositFixture = { + recipient: { + address: 'EQDr9Sq482A6ikIUh5mUUjJaBUUJBrye13CJiDB-R31_lwIq', + amount: '10000000000', + }, + sender: 'EQBkD52LACNxGgaoAxm5Nhs0SN6gg8hNaceNYifev88Y7qoZ', + publicKey: '9d6d3714aeb1f007f6e6aa728f79fdd005ea2c7ad459b2f54d73f9e672426230', + seqno: 0, + expireTime: 1234567890, + bounceable: true, +}; diff --git a/modules/sdk-coin-ton/test/unit/tonWhalesVestingDepositBuilder.ts b/modules/sdk-coin-ton/test/unit/tonWhalesVestingDepositBuilder.ts new file mode 100644 index 0000000000..311fd394d3 --- /dev/null +++ b/modules/sdk-coin-ton/test/unit/tonWhalesVestingDepositBuilder.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 Deposit Builder', () => { + const factory = new TransactionBuilderFactory(coins.get('tton')); + const fixture = testData.tonWhalesVestingDepositFixture; + + it('should build an unsigned vesting deposit transaction', async function () { + const txBuilder = factory.getTonWhalesVestingDepositBuilder(); + + 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.setDepositAmount(fixture.recipient.amount); + txBuilder.setDepositMessage(); + + const tx = await txBuilder.build(); + + should.equal(tx.type, TransactionType.TonWhalesVestingDeposit); + 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 deposit transaction', async function () { + const txBuilder = factory.getTonWhalesVestingDepositBuilder(); + + 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.setDepositAmount(fixture.recipient.amount); + txBuilder.setDepositMessage(); + + const tx = await txBuilder.build(); + const rawTx = tx.toBroadcastFormat(); + + const txBuilder2 = factory.from(rawTx); + const tx2 = await txBuilder2.build(); + + should.equal(tx2.type, TransactionType.TonWhalesVestingDeposit); + should.equal(tx2.toBroadcastFormat(), rawTx); + }); + + it('should set the correct message for vesting deposit', async function () { + const txBuilder = factory.getTonWhalesVestingDepositBuilder(); + + 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.setDepositAmount(fixture.recipient.amount); + txBuilder.setDepositMessage(); + + const tx = await txBuilder.build(); + const message = tx['message']; + should.equal(message, 'Deposit'); + }); + + it('should support vesting contract specific flags', async function () { + const txBuilder = factory.getTonWhalesVestingDepositBuilder(); + + 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.setDepositAmount(fixture.recipient.amount); + txBuilder.setDepositMessage(); + + const tx = await txBuilder.build(); + + should.equal(tx.type, TransactionType.TonWhalesVestingDeposit); + should.equal(tx.toJson().bounceable, true); + should.equal(tx.toJson().sub_wallet_id, 268); + }); +}); diff --git a/modules/sdk-core/src/account-lib/baseCoin/enum.ts b/modules/sdk-core/src/account-lib/baseCoin/enum.ts index 657d88167e..1c984d1a4a 100644 --- a/modules/sdk-core/src/account-lib/baseCoin/enum.ts +++ b/modules/sdk-core/src/account-lib/baseCoin/enum.ts @@ -123,6 +123,8 @@ export enum TransactionType { // ton whales TonWhalesDeposit, TonWhalesWithdrawal, + TonWhalesVestingDeposit, + TonWhalesVestingWithdrawal, } /**