-
Notifications
You must be signed in to change notification settings - Fork 274
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ton): add single nominator withdraw txn
- Loading branch information
1 parent
a10dac8
commit 587134c
Showing
3 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { TransactionBuilder } from './transactionBuilder'; | ||
import { BaseCoin as CoinConfig } from '@bitgo/statics'; | ||
import { Recipient, TransactionType } from '@bitgo/sdk-core'; | ||
import TonWeb from 'tonweb'; | ||
|
||
export class WithdrawBuilder extends TransactionBuilder { | ||
constructor(_coinConfig: Readonly<CoinConfig>) { | ||
super(_coinConfig); | ||
} | ||
|
||
protected get transactionType(): TransactionType { | ||
return TransactionType.Send; | ||
} | ||
|
||
send(recipient: Recipient): WithdrawBuilder { | ||
this.transaction.recipient = recipient; | ||
return this; | ||
} | ||
|
||
private createMessage(): string { | ||
const opcode = '00001000'; | ||
const query_id = '0000000000000000'; | ||
const amount = TonWeb.utils.toNano(this.transaction.recipient.amount); | ||
return opcode.concat(query_id).concat(amount.toString('hex')); | ||
} | ||
|
||
setMessage(): WithdrawBuilder { | ||
this.transaction.message = this.createMessage(); | ||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import should from 'should'; | ||
import { TransactionType } from '@bitgo/sdk-core'; | ||
import { TransactionBuilderFactory } from '../../src/lib/transactionBuilderFactory'; | ||
import { coins } from '@bitgo/statics'; | ||
import * as testData from '../resources/ton'; | ||
import { KeyPair } from '../../src/lib/keyPair'; | ||
import * as utils from '../../src/lib/utils'; | ||
import TonWeb from 'tonweb'; | ||
|
||
describe('Ton Withdraw Builder', () => { | ||
const factory = new TransactionBuilderFactory(coins.get('tton')); | ||
it('should build a unsigned withdraw tx', async function () { | ||
const txId = 'CfCL3fPQVRn90snJI_Al4zPIeLzGHsNN0W9NoQttXQo='.replace(/\//g, '_').replace(/\+/g, '-'); | ||
const txBuilder = factory.getWithdrawBuilder(); | ||
txBuilder.sender(testData.sender.address); | ||
txBuilder.sequenceNumber(0); | ||
txBuilder.publicKey(testData.sender.publicKey); | ||
txBuilder.expireTime(1234567890); | ||
txBuilder.send(testData.recipients[0]); | ||
txBuilder.setMessage(); | ||
const tx = await txBuilder.build(); | ||
should.equal(tx.type, TransactionType.Send); | ||
should.equal(tx.toJson().bounceable, false); | ||
tx.inputs.length.should.equal(1); | ||
tx.inputs[0].should.deepEqual({ | ||
address: testData.sender.address, | ||
value: testData.recipients[0].amount, | ||
coin: 'tton', | ||
}); | ||
tx.outputs.length.should.equal(1); | ||
tx.outputs[0].should.deepEqual({ | ||
address: testData.recipients[0].address, | ||
value: testData.recipients[0].amount, | ||
coin: 'tton', | ||
}); | ||
tx.id.should.equal(txId); | ||
const rawTx = tx.toBroadcastFormat(); | ||
console.log(rawTx); | ||
rawTx.should.equal( | ||
'te6cckECGAEAA9oAAuGIADZN0H0n1tz6xkYgWqJSRmkURKYajjEgXeawBo9cifPIGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmpoxdJlgLSAAAAAAADgEXAgE0AhYBFP8A9KQT9LzyyAsDAgEgBBECAUgFCALm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/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+wAACvQAye1UAFEAAAAAKamjF8DDudwJkyEh7jUbJEjFCjriVxsSlRJFyF872V1eegb4QAC+QgAaRefBOjTi/hwqDjv+7I6nGj9WEAe3ls/rFuBEQvggr6A613oAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAxYjY2N2E1NmQ0ODgwMDC/jRLK' | ||
); | ||
}); | ||
|
||
it('should build a unsigned withdraw tx with bounceable flag', async function () { | ||
const txId = '1X_pl438wIz2kSAX_ek7aYCIgqn9RslTLKRIczXfoUU='.replace(/\//g, '_').replace(/\+/g, '-'); | ||
const txBuilder = factory.getWithdrawBuilder(); | ||
txBuilder.sender(testData.sender.address); | ||
txBuilder.sequenceNumber(0); | ||
txBuilder.publicKey(testData.sender.publicKey); | ||
txBuilder.expireTime(1234567890); | ||
txBuilder.send(testData.recipients[0]); | ||
txBuilder.setMessage(); | ||
txBuilder.bounceable(true); | ||
const tx = await txBuilder.build(); | ||
should.equal(tx.type, TransactionType.Send); | ||
should.equal(tx.toJson().bounceable, true); | ||
tx.inputs.length.should.equal(1); | ||
tx.inputs[0].should.deepEqual({ | ||
address: testData.sender.address, | ||
value: testData.recipients[0].amount, | ||
coin: 'tton', | ||
}); | ||
tx.outputs.length.should.equal(1); | ||
tx.outputs[0].should.deepEqual({ | ||
address: testData.recipients[0].address, | ||
value: testData.recipients[0].amount, | ||
coin: 'tton', | ||
}); | ||
tx.id.should.equal(txId); | ||
const rawTx = tx.toBroadcastFormat(); | ||
console.log(rawTx); | ||
rawTx.should.equal( | ||
'te6cckECGAEAA9oAAuGIADZN0H0n1tz6xkYgWqJSRmkURKYajjEgXeawBo9cifPIGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmpoxdJlgLSAAAAAAADgEXAgE0AhYBFP8A9KQT9LzyyAsDAgEgBBECAUgFCALm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/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+wAACvQAye1UAFEAAAAAKamjF8DDudwJkyEh7jUbJEjFCjriVxsSlRJFyF872V1eegb4QAC+YgAaRefBOjTi/hwqDjv+7I6nGj9WEAe3ls/rFuBEQvggr6A613oAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAxYjY2N2E1NmQ0ODgwMDCumWKy' | ||
); | ||
}); | ||
|
||
it('should build a send from rawTx', async function () { | ||
const txBuilder = factory.from(testData.signedTransaction.tx); | ||
const builtTx = await txBuilder.build(); | ||
const jsonTx = builtTx.toJson(); | ||
should.equal(builtTx.type, TransactionType.Send); | ||
should.equal(builtTx.signablePayload.toString('base64'), testData.signedTransaction.signable); | ||
should.equal(builtTx.id, testData.signedTransaction.txId); | ||
const builder2 = factory.from(builtTx.toBroadcastFormat()); | ||
const builtTx2 = await builder2.build(); | ||
should.equal(builtTx2.type, TransactionType.Send); | ||
should.equal(builtTx.toBroadcastFormat(), testData.signedTransaction.tx); | ||
builtTx.inputs.length.should.equal(1); | ||
builtTx.outputs.length.should.equal(1); | ||
jsonTx.sender.should.equal('EQCSBjR3fUOL98WTw2F_IT4BrcqjZJWVLWUSz5WQDpaL9Jpl'); | ||
jsonTx.destination.should.equal('EQA0i8-CdGnF_DhUHHf92R1ONH6sIA9vLZ_WLcCIhfBBXwtG'); | ||
jsonTx.amount.should.equal('10000000'); | ||
jsonTx.seqno.should.equal(6); | ||
jsonTx.expirationTime.should.equal(1695997582); | ||
|
||
const builtTx3 = await txBuilder.bounceable(false).fromAddressBounceable(false).toAddressBounceable(false).build(); | ||
txBuilder.from(testData.signedTransaction.tx); | ||
const jsonTx3 = builtTx3.toJson(); | ||
should.equal(jsonTx3.bounceable, false); | ||
should.equal(builtTx3.signablePayload.toString('base64'), testData.signedTransaction.bounceableSignable); | ||
should.equal(builtTx3.id, testData.signedTransaction.txIdBounceable); | ||
should.equal(builtTx3.toBroadcastFormat(), testData.signedTransaction.tx); | ||
jsonTx3.sender.should.equal('UQCSBjR3fUOL98WTw2F_IT4BrcqjZJWVLWUSz5WQDpaL9Meg'); | ||
jsonTx3.destination.should.equal('UQA0i8-CdGnF_DhUHHf92R1ONH6sIA9vLZ_WLcCIhfBBX1aD'); | ||
jsonTx3.amount.should.equal('10000000'); | ||
jsonTx3.seqno.should.equal(6); | ||
jsonTx3.expirationTime.should.equal(1695997582); | ||
}); | ||
|
||
it('should parse a raw transaction and set flags', async function () { | ||
const factory = new TransactionBuilderFactory(coins.get('tton')); | ||
const txBuilder = factory.from(testData.signedTransaction.tx); | ||
const txBuilderBounceable = factory.from(testData.signedTransaction.txBounceable); | ||
|
||
const tx = await txBuilder.build(); | ||
const txBounceable = await txBuilderBounceable.build(); | ||
tx.toJson().bounceable.should.equal(false); | ||
txBounceable.toJson().bounceable.should.equal(true); | ||
}); | ||
|
||
xit('should build a signed withdraw tx and submit onchain', async function () { | ||
const tonweb = new TonWeb(new TonWeb.HttpProvider('https://testnet.toncenter.com/api/v2/jsonRPC')); | ||
const keyPair = new KeyPair({ prv: testData.privateKeys.prvKey1 }); | ||
const publicKey = keyPair.getKeys().pub; | ||
const address = await utils.default.getAddressFromPublicKey(publicKey); | ||
const txBuilder = factory.getWithdrawBuilder(); | ||
txBuilder.sender(address); | ||
|
||
const WalletClass = tonweb.wallet.all['v4R2']; | ||
const wallet = new WalletClass(tonweb.provider, { | ||
publicKey: tonweb.utils.hexToBytes(publicKey), | ||
wc: 0, | ||
}); | ||
const seqno = await wallet.methods.seqno().call(); | ||
txBuilder.sequenceNumber(seqno as number); | ||
txBuilder.publicKey(publicKey); | ||
const expireAt = Math.floor(Date.now() / 1e3) + 60 * 60 * 24 * 7; // 7 days | ||
txBuilder.expireTime(expireAt); | ||
txBuilder.send({ | ||
address: 'EQD6Tm5rybJHqMflQkjpCRB7Lm5QpGqFoYwiLqMjPtaqPSdZ', | ||
amount: '10000000', | ||
}); | ||
txBuilder.setMessage(); | ||
const tx = await txBuilder.build(); | ||
should.equal(tx.type, TransactionType.Send); | ||
const signable = tx.signablePayload; | ||
const signature = keyPair.signMessageinUint8Array(signable); | ||
const signedTx = await txBuilder.build(); | ||
const builder2 = factory.from(signedTx.toBroadcastFormat()); | ||
builder2.addSignature(keyPair.getKeys(), Buffer.from(signature)); | ||
const tx2 = await builder2.build(); | ||
const signature2 = keyPair.signMessageinUint8Array(tx2.signablePayload); | ||
should.equal(Buffer.from(signature).toString('hex'), Buffer.from(signature2).toString('hex')); | ||
await new Promise((resolve) => setTimeout(resolve, 2000)); | ||
const result = await tonweb.provider.sendBoc(tx2.toBroadcastFormat()); | ||
console.log(JSON.stringify(result)); | ||
}); | ||
|
||
it('should build a signed withdraw tx using add signature', async function () { | ||
const keyPair = new KeyPair({ prv: testData.privateKeys.prvKey1 }); | ||
const publicKey = keyPair.getKeys().pub; | ||
const address = await utils.default.getAddressFromPublicKey(publicKey); | ||
const txBuilder = factory.getWithdrawBuilder(); | ||
txBuilder.sender(address); | ||
txBuilder.sequenceNumber(0); | ||
txBuilder.publicKey(publicKey); | ||
const expireAt = Math.floor(Date.now() / 1e3) + 60 * 60 * 24 * 7; | ||
txBuilder.expireTime(expireAt); | ||
txBuilder.send(testData.recipients[0]); | ||
txBuilder.setMessage(); | ||
const tx = await txBuilder.build(); | ||
should.equal(tx.type, TransactionType.Send); | ||
const signable = tx.signablePayload; | ||
const signature = keyPair.signMessageinUint8Array(signable); | ||
txBuilder.addSignature(keyPair.getKeys(), Buffer.from(signature)); | ||
const signedTx = await txBuilder.build(); | ||
const builder2 = factory.from(signedTx.toBroadcastFormat()); | ||
const tx2 = await builder2.build(); | ||
const signature2 = keyPair.signMessageinUint8Array(tx2.signablePayload); | ||
should.equal(Buffer.from(signature).toString('hex'), Buffer.from(signature2).toString('hex')); | ||
should.equal(tx.toBroadcastFormat(), tx2.toBroadcastFormat()); | ||
}); | ||
|
||
it('should build withdraw tx for non-bounceable address', async function () { | ||
const txBuilder = factory.getWithdrawBuilder(); | ||
txBuilder.sender(testData.sender.address); | ||
txBuilder.sequenceNumber(0); | ||
txBuilder.publicKey(testData.sender.publicKey); | ||
txBuilder.expireTime(1234567890); | ||
const address = 'EQAWzEKcdnykvXfUNouqdS62tvrp32bCxuKS6eQrS6ISgcLo'; | ||
const otherFormat = 'UQAWzEKcdnykvXfUNouqdS62tvrp32bCxuKS6eQrS6ISgZ8t'; | ||
const amount = '10000000'; | ||
txBuilder.send({ address, amount }); | ||
txBuilder.setMessage(); | ||
const tx = await txBuilder.build(); | ||
should.equal(tx.type, TransactionType.Send); | ||
tx.inputs.length.should.equal(1); | ||
tx.inputs[0].should.deepEqual({ | ||
address: testData.sender.address, | ||
value: amount, | ||
coin: 'tton', | ||
}); | ||
tx.outputs.length.should.equal(1); | ||
tx.outputs[0].should.deepEqual({ | ||
address, | ||
value: amount, | ||
coin: 'tton', | ||
}); | ||
const txJson = tx.toJson(); | ||
txJson.destination.should.equal(address); | ||
const builder2 = factory.from(tx.toBroadcastFormat()); | ||
const tx2 = await builder2.build(); | ||
const txJson2 = tx2.toJson(); | ||
txJson2.destinationAlias.should.equal(otherFormat); | ||
}); | ||
}); |