|
| 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_WITHDRAW_OPCODE } from '../../src/lib/constants'; |
| 7 | + |
| 8 | +describe('Ton Whales Withdrawal Builder', () => { |
| 9 | + const factory = new TransactionBuilderFactory(coins.get('tton')); |
| 10 | + |
| 11 | + // Define the scenarios we want to test |
| 12 | + const scenarios = [ |
| 13 | + { |
| 14 | + name: 'Partial Withdrawal (10 TON)', |
| 15 | + fixture: testData.signedTonWhalesWithdrawalTransaction, |
| 16 | + }, |
| 17 | + { |
| 18 | + name: 'Full Withdrawal (Amount 0)', |
| 19 | + fixture: testData.signedTonWhalesFullWithdrawalTransaction, |
| 20 | + }, |
| 21 | + ]; |
| 22 | + |
| 23 | + scenarios.forEach((scenario) => { |
| 24 | + describe(scenario.name, () => { |
| 25 | + const fixture = scenario.fixture; |
| 26 | + |
| 27 | + it('should parse a raw transaction and extract correct parameters', async function () { |
| 28 | + const txBuilder = factory.from(fixture.tx); |
| 29 | + const builtTx = await txBuilder.build(); |
| 30 | + const jsonTx = builtTx.toJson(); |
| 31 | + |
| 32 | + // Verify Business Logic Fields |
| 33 | + should.equal(builtTx.type, TransactionType.TonWhalesWithdrawal); |
| 34 | + |
| 35 | + // NOTE: In withdrawals, recipient.amount is the FEE, withdrawAmount is the STAKE |
| 36 | + should.equal(jsonTx.amount, fixture.recipient.amount); |
| 37 | + should.equal(jsonTx.withdrawAmount, fixture.withdrawAmount); |
| 38 | + |
| 39 | + should.equal(jsonTx.destination, fixture.recipient.address); |
| 40 | + should.equal(jsonTx.sender, fixture.sender); |
| 41 | + |
| 42 | + // Verify Network Constraints |
| 43 | + should.equal(jsonTx.seqno, fixture.seqno); |
| 44 | + should.equal(jsonTx.expirationTime, fixture.expireTime); |
| 45 | + should.equal(jsonTx.bounceable, fixture.bounceable); |
| 46 | + |
| 47 | + // Verify Payload Structure |
| 48 | + // Logic: DecimalOpCode + HexQueryId + DecimalAmount |
| 49 | + const msg = builtTx['message'] || ''; |
| 50 | + should.equal(msg.startsWith(TON_WHALES_WITHDRAW_OPCODE), true); |
| 51 | + |
| 52 | + // Ensure the payload ENDS with the decimal amount (either "1000..." or "0") |
| 53 | + should.equal(msg.endsWith(fixture.withdrawAmount), true); |
| 54 | + }); |
| 55 | + |
| 56 | + it('should parse and rebuild the transaction resulting in the same hex', async function () { |
| 57 | + const txBuilder = factory.from(fixture.tx); |
| 58 | + const builtTx = await txBuilder.build(); |
| 59 | + |
| 60 | + // Verify the parser extracted the signature |
| 61 | + const signature = builtTx.signature[0]; |
| 62 | + should.exist(signature); |
| 63 | + signature.should.not.be.empty(); |
| 64 | + |
| 65 | + // Rebuild from the parsed object |
| 66 | + const builder2 = factory.from(builtTx.toBroadcastFormat()); |
| 67 | + const builtTx2 = await builder2.build(); |
| 68 | + |
| 69 | + // The output of the second build should match the original raw transaction |
| 70 | + should.equal(builtTx2.toBroadcastFormat(), fixture.tx); |
| 71 | + should.equal(builtTx2.type, TransactionType.TonWhalesWithdrawal); |
| 72 | + }); |
| 73 | + |
| 74 | + it('should build a transaction from scratch that byte-for-byte matches the raw fixture', async function () { |
| 75 | + // Get the specific Withdrawal Builder |
| 76 | + const builder = factory.getTonWhalesWithdrawalBuilder(); |
| 77 | + |
| 78 | + // Set Header Info from Fixture |
| 79 | + builder.sender(fixture.sender); |
| 80 | + builder.publicKey(fixture.publicKey); |
| 81 | + builder.sequenceNumber(fixture.seqno); |
| 82 | + builder.expireTime(fixture.expireTime); |
| 83 | + builder.bounceable(fixture.bounceable); |
| 84 | + |
| 85 | + // Set Destination and ATTACHED VALUE (The Fee) |
| 86 | + builder.send({ |
| 87 | + address: fixture.recipient.address, |
| 88 | + amount: fixture.recipient.amount, |
| 89 | + }); |
| 90 | + |
| 91 | + // Set Payload Data (The Unstake Amount) |
| 92 | + // Note: This works for both partial (amount > 0) and full (amount = "0") |
| 93 | + builder.setWithdrawalMessage(fixture.withdrawAmount, fixture.queryId); |
| 94 | + |
| 95 | + // Attach Signature from Fixture (Mocking the HSM signing process) |
| 96 | + if (fixture.signature) { |
| 97 | + builder.addSignature({ pub: fixture.publicKey }, Buffer.from(fixture.signature, 'hex')); |
| 98 | + } |
| 99 | + |
| 100 | + // Build Signed Transaction |
| 101 | + const signedBuiltTx = await builder.build(); |
| 102 | + |
| 103 | + // Byte-for-byte equality with the Sandbox output |
| 104 | + should.equal(signedBuiltTx.toBroadcastFormat(), fixture.tx); |
| 105 | + should.equal(signedBuiltTx.type, TransactionType.TonWhalesWithdrawal); |
| 106 | + }); |
| 107 | + }); |
| 108 | + }); |
| 109 | +}); |
0 commit comments