Skip to content

Commit f08d5eb

Browse files
authored
feat(ton): add support for building wallet contract v3 compatible transactions
2 parents 7331471 + b5653fb commit f08d5eb

File tree

4 files changed

+88
-5
lines changed

4 files changed

+88
-5
lines changed

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class Transaction extends BaseTransaction {
1818
expireTime: number;
1919
sender: string;
2020
publicKey: string;
21+
isV3ContractMessage: boolean;
2122
protected unsignedMessage: string;
2223
protected finalMessage: string;
2324

@@ -26,6 +27,7 @@ export class Transaction extends BaseTransaction {
2627
this.bounceable = false;
2728
this.fromAddressBounceable = true;
2829
this.toAddressBounceable = true;
30+
this.isV3ContractMessage = false;
2931
}
3032

3133
canSign(key: BaseKey): boolean {
@@ -93,7 +95,9 @@ export class Transaction extends BaseTransaction {
9395
// expireAt should be set as per the provided arg value, regardless of the seqno
9496
message.bits.writeUint(expireAt, 32);
9597
message.bits.writeUint(seqno, 32);
96-
message.bits.writeUint(0, 8); // op
98+
if (!this.isV3ContractMessage) {
99+
message.bits.writeUint(0, 8); // op
100+
}
97101
return message;
98102
}
99103

@@ -140,7 +144,7 @@ export class Transaction extends BaseTransaction {
140144
body.writeCell(signingMessage);
141145

142146
let stateInit;
143-
if (seqno === 0) {
147+
if (seqno === 0 && !this.isV3ContractMessage) {
144148
const WalletClass = TonWeb.Wallets.all['v4R2'];
145149
const wallet = new WalletClass(new TonWeb.HttpProvider(), {
146150
publicKey: TonWeb.utils.hexToBytes(this.publicKey),
@@ -266,10 +270,14 @@ export class Transaction extends BaseTransaction {
266270
const seqno = slice.loadUint(32).toNumber();
267271

268272
const op = slice.loadUint(8).toNumber();
269-
if (op !== 0) throw new Error('invalid op');
273+
if (op !== 3) {
274+
if (op !== 0) throw new Error('invalid op');
270275

271-
const sendMode = slice.loadUint(8).toNumber();
272-
if (sendMode !== 3) throw new Error('invalid sendMode');
276+
const sendMode = slice.loadUint(8).toNumber();
277+
if (sendMode !== 3) throw new Error('invalid sendMode');
278+
} else {
279+
this.isV3ContractMessage = true;
280+
}
273281

274282
let order = slice.loadRef();
275283

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,9 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
179179
this.transaction.publicKey = key;
180180
return this;
181181
}
182+
183+
isV3ContractMessage(bool: boolean): TransactionBuilder {
184+
this.transaction.isV3ContractMessage = bool;
185+
return this;
186+
}
182187
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const privateKeys = {
1010
prvKey4: 'ba4c313bcf830b825adaa3ae08cfde86e79e15a84e6fdc3b1fe35a6bb82d9f22',
1111
prvKey5: 'fdb9ea1bb7f0120ce6eb7b047ac6744c4298f277756330e18dbd5419590a1ef2',
1212
prvKey6: 'f66d30357e6a4180e9ab41865f0f347cbaffe60f9d499fe6c2f27046327652f6',
13+
prvKey7: '339f9b7c8b039e1f71cc28c5fa30db29913624e2d53d4323a2664d3cfb5b07a4',
1314
};
1415

1516
export const addresses = {
@@ -24,6 +25,13 @@ export const addresses = {
2425
],
2526
};
2627

28+
export const vestingContract = {
29+
address: 'UQD0eii2CN6p_hznXcqBFlHHbcBQShW2Vj1EaIK2MnxVrWC6',
30+
addressBounceable: 'EQD0eii2CN6p_hznXcqBFlHHbcBQShW2Vj1EaIK2MnxVrT1_',
31+
publicKey: '339f9b7c8b039e1f71cc28c5fa30db29913624e2d53d4323a2664d3cfb5b07a4',
32+
signature: '4K2iXQ5G5lU/1TKhfQn4xaAgL7sK91mFQb6OixkRwpZFJCYSbhmDJqLJfWRVRjfbL+cX4HjC7siqsGMDUgh4DQ==',
33+
};
34+
2735
export const sender = {
2836
address: 'UQAbJug-k-tufWMjEC1RKSM0iiJTDUcYkC7zWANHrkT55Afg',
2937
publicKey: 'c0c3b9dc09932121ee351b2448c50a3ae2571b12951245c85f3bd95d5e7a06f8',
@@ -71,6 +79,21 @@ export const signedSendTransaction = {
7179
},
7280
};
7381

82+
export const v3CompatibleSignedSendTransaction = {
83+
txBounceable:
84+
'te6cckEBAgEAqAAB34gB6PRRbBG9U/w5zruVAiyjjtuAoJQrbKx6iNEFbGT4q1oHBW0S6HI3Mqn+qZUL6E/GLQEBfdhXuswqDfR0WMiOFLIpITCTcMwZNRZL6yKqMb7Zfzi/A8YXdkVVgxgakEPAaU1NGLtH0CDAAAAAGBwBAGZiAGcJlmF0UvErDsi5Rs21SP70rP1K36wtjBImqtbV96EuHMS0AAAAAAAAAAAAAAAAAAAiW72E',
85+
txIdBounceable: '4i1GCyN5IkQQ-vESvNl4Wp1ejp7LfazRlNWzUbtGwSA=',
86+
bounceableSignable: 'lOEOTzPXnPotTTHi7xgivFNUHH+xUgq/nKpaP/bK+Xo=',
87+
recipient: {
88+
address: 'EQDOEyzC6KXiVh2Rco2bapH96Vn6lb9YWxgkTVWtq-9CXL0m',
89+
amount: '10000000',
90+
},
91+
recipientNonBounceable: {
92+
address: 'UQDOEyzC6KXiVh2Rco2bapH96Vn6lb9YWxgkTVWtq-9CXODj',
93+
amount: '10000000',
94+
},
95+
};
96+
7497
export const signedTokenSendTransaction = {
7598
tx: 'te6cckECGgEABB0AAuGIAVSGb+UGjjP3lvt+zFA8wouI3McEd6CKbO2TwcZ3OfLKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmpoxdJlgLSAAAAAAADgEXAgE0AhYBFP8A9KQT9LzyyAsDAgEgBBECAUgFCALm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQYHAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAkQAgEgCg8CAVgLDAA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA0OABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xITFBUAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjF9NTAQHUHhbX00VGZ3d2r8hbJxuz7PaxmuCOJ6kgckppQAFmQgABT9LR3Iqffskp0J9gWYO8Azlnb33BCMj8FqIUIGxGOZpiWgAAAAAAAAAAAAAAAAABGAGuD4p+pQAAAAAAAAAAQ7msoAgA/BGdBi/R01erquxJOvPgGKclBawUs3MAi0/IdctKQz8AKpDN/KDRxn7y32/ZigeYUXEbmOCO9BFNnbJ4OM7nPllGHoSBGQAkAAAAAGpldHRvbiB0ZXN0aW5nwHtw7A==',
7699
tx2: 'te6cckECGgEABBQAAuGIAVSGb+UGjjP3lvt+zFA8wouI3McEd6CKbO2TwcZ3OfLKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmpoxdJlgLSAAAAAAADgEXAgE0AhYBFP8A9KQT9LzyyAsDAgEgBBECAUgFCALm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQYHAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAkQAgEgCg8CAVgLDAA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA0OABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xITFBUAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjF9NTAQHUHhbX00VGZ3d2r8hbJxuz7PaxmuCOJ6kgckppQAFoYgABT9LR3Iqffskp0J9gWYO8Azlnb33BCMj8FqIUIGxGOaAX14QAAAAAAAAAAAAAAAAAARgBrg+KfqUAAAAAAAAAAEO5rKAIAPwRnQYv0dNXq6rsSTrz4BinJQWsFLNzAItPyHXLSkM/ACqQzfyg0cZ+8t9v2YoHmFFxG5jgjvQRTZ2yeDjO5z5ZRzEtARkAEAAAAAB0ZXN0MfeoHg==',

modules/sdk-coin-ton/test/unit/transferBuilder.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,51 @@ describe('Ton Transfer Builder', () => {
212212
const txJson2 = tx2.toJson();
213213
txJson2.destinationAlias.should.equal(otherFormat);
214214
});
215+
216+
it('should build a transfer tx compatible with v3 wallet contract', async function () {
217+
const txBuilder = factory.getTransferBuilder();
218+
txBuilder.sender(testData.vestingContract.address);
219+
txBuilder.sequenceNumber(3);
220+
txBuilder.expireTime(1761215512);
221+
txBuilder.send(testData.v3CompatibleSignedSendTransaction.recipient);
222+
txBuilder.isV3ContractMessage(true);
223+
txBuilder.bounceable(true);
224+
txBuilder.addSignature(
225+
{ pub: testData.vestingContract.publicKey },
226+
Buffer.from(testData.vestingContract.signature, 'base64')
227+
);
228+
const tx = await txBuilder.build();
229+
should.equal(tx.type, TransactionType.Send);
230+
tx.inputs.length.should.equal(1);
231+
tx.inputs[0].should.deepEqual({
232+
address: testData.vestingContract.address,
233+
value: testData.v3CompatibleSignedSendTransaction.recipient.amount,
234+
coin: 'tton',
235+
});
236+
tx.outputs.length.should.equal(1);
237+
tx.outputs[0].should.deepEqual({
238+
address: testData.v3CompatibleSignedSendTransaction.recipient.address,
239+
value: testData.v3CompatibleSignedSendTransaction.recipient.amount,
240+
coin: 'tton',
241+
});
242+
});
243+
244+
it('should build a v3 compatible send transaction using raw transaction hex', async function () {
245+
const txBuilder = factory.from(testData.v3CompatibleSignedSendTransaction.txBounceable);
246+
const builtTx = await txBuilder.build();
247+
const jsonTx = builtTx.toJson();
248+
should.equal(builtTx.type, TransactionType.Send);
249+
should.equal(
250+
builtTx.signablePayload.toString('base64'),
251+
testData.v3CompatibleSignedSendTransaction.bounceableSignable
252+
);
253+
should.equal(builtTx.id, testData.v3CompatibleSignedSendTransaction.txIdBounceable);
254+
jsonTx.sender.should.equal(testData.vestingContract.addressBounceable);
255+
jsonTx.destination.should.equal(testData.v3CompatibleSignedSendTransaction.recipient.address);
256+
jsonTx.destinationAlias.should.equal(testData.v3CompatibleSignedSendTransaction.recipientNonBounceable.address);
257+
jsonTx.amount.should.equal(testData.v3CompatibleSignedSendTransaction.recipient.amount);
258+
jsonTx.seqno.should.equal(3);
259+
jsonTx.expirationTime.should.equal(1761215512);
260+
jsonTx.bounceable.should.equal(true);
261+
});
215262
});

0 commit comments

Comments
 (0)