Skip to content

Commit 9fb3b1c

Browse files
committed
feat(sdk-coin-flrp): add support for multiple UTXOs and no change output in ExportInPTxBuilder tests
1 parent 1c5cda6 commit 9fb3b1c

File tree

3 files changed

+175
-4
lines changed

3 files changed

+175
-4
lines changed

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,30 @@ interface CheckSignature {
3939
(signature: string, addressHex: string): boolean;
4040
}
4141

42+
/**
43+
* Checks if an empty signature has an embedded address (non-zero bytes after position 90)
44+
* @param signature Hex string of the signature
45+
*/
46+
function hasEmbeddedAddress(signature: string): boolean {
47+
if (!isEmptySignature(signature)) return false;
48+
const cleanSig = utils.removeHexPrefix(signature);
49+
if (cleanSig.length < 130) return false;
50+
const embeddedPart = cleanSig.substring(90, 130);
51+
// Check if it's not all zeros
52+
return embeddedPart !== '0'.repeat(40);
53+
}
54+
4255
/**
4356
* Generates a function to check if a signature slot matches a given address.
44-
* If all signatures are empty with embedded addresses, it matches by address.
57+
* If signatures have embedded addresses, it matches by address.
4558
* Otherwise, it just finds empty slots.
4659
* @param signatures Array of signature hex strings
4760
*/
4861
function generateSelectorSignature(signatures: string[]): CheckSignature {
49-
if (signatures.length > 1 && signatures.every((sig) => isEmptySignature(sig))) {
62+
// Check if any empty signature has an embedded address
63+
const hasEmbeddedAddresses = signatures.some((sig) => isEmptySignature(sig) && hasEmbeddedAddress(sig));
64+
65+
if (hasEmbeddedAddresses) {
5066
// Look for address embedded in the empty signature (after position 90)
5167
return function (sig: string, address: string): boolean {
5268
try {
@@ -61,7 +77,7 @@ function generateSelectorSignature(signatures: string[]): CheckSignature {
6177
}
6278
};
6379
} else {
64-
// Look for any empty slot
80+
// Look for any empty slot (no embedded addresses)
6581
return function (sig: string, address: string): boolean {
6682
return isEmptySignature(sig);
6783
};

modules/sdk-coin-flrp/test/resources/transactionData/exportInP.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Test data for export with single UTXO
12
export const EXPORT_IN_P = {
23
txhash: '2Zsejg6FXjRB5t362rBncYbNohKLEjzZYcB9NceaxSmBX323HF',
34
unsignedHex:
@@ -43,3 +44,107 @@ export const EXPORT_IN_P = {
4344
INVALID_CHAIN_ID: 'wrong chain id',
4445
VALID_C_CHAIN_ID: 'yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp',
4546
};
47+
48+
// Test data for export with 2 UTXOs
49+
// Total input: 2 FLR (1 FLR + 1 FLR)
50+
// Export amount: 1.5 FLR
51+
// Fee: 279432 nFLR
52+
// Change: ~0.5 FLR (499,720,568 nFLR)
53+
export const EXPORT_IN_P_TWO_UTXOS = {
54+
txhash: '2FEYQ3uEwREx44U96QAWmeyEsBUw4MTXxJNyFB3wScpiSouVu1',
55+
unsignedHex:
56+
'0x0000000000120000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000007000000001dc92178000000000000000000000001000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f910000000285492a9f3b2ba883350d66428a51e131ec5de24ec49ef4834961102e69fed15f0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000019c48f440c6b801f4953ea908423170275eb761186be1e009cb3a6360cd18e1b60000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000059682f00000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91',
57+
halfSigntxHex:
58+
'0x0000000000120000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000007000000001dc92178000000000000000000000001000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f910000000285492a9f3b2ba883350d66428a51e131ec5de24ec49ef4834961102e69fed15f0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000019c48f440c6b801f4953ea908423170275eb761186be1e009cb3a6360cd18e1b60000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000059682f00000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91000000020000000900000002377f4333c83df3f3d15d7d564ae23cce559ee7ab25a507382b7a48825654ae677da05a065bb5c2bbc32009d716b340b71cf1447b149496443af36178f721c226010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500100000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003329be7d01cd3ebaae6654d7327dd9f17a2e15810000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001',
59+
fullSigntxHex:
60+
'0x0000000000120000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000007000000001dc92178000000000000000000000001000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f910000000285492a9f3b2ba883350d66428a51e131ec5de24ec49ef4834961102e69fed15f0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000019c48f440c6b801f4953ea908423170275eb761186be1e009cb3a6360cd18e1b60000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000059682f00000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91000000020000000900000002377f4333c83df3f3d15d7d564ae23cce559ee7ab25a507382b7a48825654ae677da05a065bb5c2bbc32009d716b340b71cf1447b149496443af36178f721c22601cc969c605fac579e909346a02e0f6316d347612281b52d1d8ab023e699cb77005222e850e2a963fc2a9eb278d06845b586657399746bc0d9f2d08ef7f25b4e6c0100000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003329be7d01cd3ebaae6654d7327dd9f17a2e15810000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001',
61+
outputs: [
62+
{
63+
outputID: 0,
64+
amount: '1000000000', // 1 FLR in nFLR
65+
txid: '21hcD64N9QzdayPjhKLsBQBa8FyXcsJGNStBZ3vCRdCCEsLru2',
66+
outputidx: '0',
67+
addresses: [
68+
'0x3329be7d01cd3ebaae6654d7327dd9f17a2e1581',
69+
'0x7e918a5e8083ae4c9f2f0ed77055c24bf3665001',
70+
'0xc7324437c96c7c8a6a152da2385c1db5c3ab1f91',
71+
],
72+
threshold: 2,
73+
},
74+
{
75+
outputID: 0,
76+
amount: '1000000000', // 1 FLR in nFLR
77+
txid: '2Bq6DhNRDNEo8vcFRWGnBkqT5YHUGVnKzGXCNHwZVK8yJRxhAV',
78+
outputidx: '0',
79+
addresses: [
80+
'0x3329be7d01cd3ebaae6654d7327dd9f17a2e1581',
81+
'0x7e918a5e8083ae4c9f2f0ed77055c24bf3665001',
82+
'0xc7324437c96c7c8a6a152da2385c1db5c3ab1f91',
83+
],
84+
threshold: 2,
85+
},
86+
],
87+
amount: '1500000000', // 1.5 FLR in nFLR
88+
pAddresses: [
89+
'P-costwo1xv5mulgpe5lt4tnx2ntnylwe79azu9vpja6lut',
90+
'P-costwo106gc5h5qswhye8e0pmthq4wzf0ekv5qppsrvpu',
91+
'P-costwo1cueygd7fd37g56s49k3rshqakhp6k8u3adzt6m',
92+
],
93+
privateKeys: [
94+
'26a38e543bcb6cfa52d2b78d4c31330d38f5e84dcdb0be1df72722d33e4c1940',
95+
'ef576892dd582d93914a3dba3b77cc4e32e470c32f4127817345473aae719d14',
96+
'a408583e8ba09bc619c2cdd8f89f09839fddf6f3929def25251f1aa266ff7d24',
97+
],
98+
sourceChainId: 'vE8M98mEQH6wk56sStD1ML8HApTgSqfJZLk9gQ3Fsd4i6m3Bi',
99+
threshold: 2,
100+
fee: '279432',
101+
locktime: 0,
102+
// Expected change: 2,000,000,000 - 1,500,000,000 - 279,432 = 499,720,568 nFLR (~0.5 FLR)
103+
expectedChange: '499720568',
104+
};
105+
106+
// Test data for export with NO change output
107+
// UTXO exactly covers amount + fee
108+
// UTXO: 1,000,000,000 nFLR (1 FLR)
109+
// Export amount: 999,720,568 nFLR (~0.9997 FLR)
110+
// Fee: 279,432 nFLR
111+
// Change: 0
112+
export const EXPORT_IN_P_NO_CHANGE = {
113+
txhash: 'eg5at8mZ6EeAGj1FR5sgSRwprJLxq8Xe2yBSs53P1VwEmsRuT',
114+
unsignedHex:
115+
'0x000000000012000000720000000000000000000000000000000000000000000000000000000000000000000000000000000185492a9f3b2ba883350d66428a51e131ec5de24ec49ef4834961102e69fed15f0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000007000000003b968678000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91',
116+
halfSigntxHex:
117+
'0x000000000012000000720000000000000000000000000000000000000000000000000000000000000000000000000000000185492a9f3b2ba883350d66428a51e131ec5de24ec49ef4834961102e69fed15f0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000007000000003b968678000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f910000000100000009000000027e132939cbdc2a26208d15d1b67b97ed5a406db2b12f84783472f5dc9ff4bc5605c3503a9cb7216f20a50dc2d680f6e6d644c5d9aa8015236ba08a35e7c4092f010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001',
118+
fullSigntxHex:
119+
'0x000000000012000000720000000000000000000000000000000000000000000000000000000000000000000000000000000185492a9f3b2ba883350d66428a51e131ec5de24ec49ef4834961102e69fed15f0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000003b9aca000000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000007000000003b968678000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f910000000100000009000000027e132939cbdc2a26208d15d1b67b97ed5a406db2b12f84783472f5dc9ff4bc5605c3503a9cb7216f20a50dc2d680f6e6d644c5d9aa8015236ba08a35e7c4092f01d3e9c2d213962cfffe69e8d40012fc147d2d445cbfd081b3d0d40252726363ec3ec6e263bc675936a62dfa17335c480281587e34461cd8f9c3a0b80e73b688ac00',
120+
outputs: [
121+
{
122+
outputID: 0,
123+
amount: '1000000000', // 1 FLR in nFLR
124+
txid: '21hcD64N9QzdayPjhKLsBQBa8FyXcsJGNStBZ3vCRdCCEsLru2',
125+
outputidx: '0',
126+
addresses: [
127+
'0x3329be7d01cd3ebaae6654d7327dd9f17a2e1581',
128+
'0x7e918a5e8083ae4c9f2f0ed77055c24bf3665001',
129+
'0xc7324437c96c7c8a6a152da2385c1db5c3ab1f91',
130+
],
131+
threshold: 2,
132+
},
133+
],
134+
// amount + fee = 999,720,568 + 279,432 = 1,000,000,000 (exact UTXO amount)
135+
amount: '999720568',
136+
pAddresses: [
137+
'P-costwo1xv5mulgpe5lt4tnx2ntnylwe79azu9vpja6lut',
138+
'P-costwo106gc5h5qswhye8e0pmthq4wzf0ekv5qppsrvpu',
139+
'P-costwo1cueygd7fd37g56s49k3rshqakhp6k8u3adzt6m',
140+
],
141+
privateKeys: [
142+
'26a38e543bcb6cfa52d2b78d4c31330d38f5e84dcdb0be1df72722d33e4c1940',
143+
'ef576892dd582d93914a3dba3b77cc4e32e470c32f4127817345473aae719d14',
144+
'a408583e8ba09bc619c2cdd8f89f09839fddf6f3929def25251f1aa266ff7d24',
145+
],
146+
sourceChainId: 'vE8M98mEQH6wk56sStD1ML8HApTgSqfJZLk9gQ3Fsd4i6m3Bi',
147+
threshold: 2,
148+
fee: '279432',
149+
locktime: 0,
150+
};

modules/sdk-coin-flrp/test/unit/lib/exportInPTxBuilder.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import assert from 'assert';
22
import 'should';
3-
import { EXPORT_IN_P as testData } from '../../resources/transactionData/exportInP';
3+
import {
4+
EXPORT_IN_P as testData,
5+
EXPORT_IN_P_TWO_UTXOS as twoUtxoTestData,
6+
EXPORT_IN_P_NO_CHANGE as noChangeTestData,
7+
} from '../../resources/transactionData/exportInP';
48
import { TransactionBuilderFactory, DecodedUtxoObj } from '../../../src/lib';
59
import { coins } from '@bitgo/statics';
610
import signFlowTest from './signFlowTestSuit';
@@ -87,6 +91,52 @@ describe('Flrp Export In P Tx Builder', () => {
8791
txHash: testData.txhash,
8892
});
8993

94+
signFlowTest({
95+
transactionType: 'Export P2C with 2 UTXOs',
96+
newTxFactory: () => new TransactionBuilderFactory(coins.get('tflrp')),
97+
newTxBuilder: () =>
98+
new TransactionBuilderFactory(coins.get('tflrp'))
99+
.getExportInPBuilder()
100+
.threshold(twoUtxoTestData.threshold)
101+
.locktime(twoUtxoTestData.locktime)
102+
.fromPubKey(twoUtxoTestData.pAddresses)
103+
.amount(twoUtxoTestData.amount)
104+
.externalChainId(twoUtxoTestData.sourceChainId)
105+
.fee(twoUtxoTestData.fee)
106+
.utxos(twoUtxoTestData.outputs),
107+
unsignedTxHex: twoUtxoTestData.unsignedHex,
108+
halfSignedTxHex: twoUtxoTestData.halfSigntxHex,
109+
fullSignedTxHex: twoUtxoTestData.fullSigntxHex,
110+
privateKey: {
111+
prv1: twoUtxoTestData.privateKeys[0],
112+
prv2: twoUtxoTestData.privateKeys[1],
113+
},
114+
txHash: twoUtxoTestData.txhash,
115+
});
116+
117+
signFlowTest({
118+
transactionType: 'Export P2C with no change output',
119+
newTxFactory: () => new TransactionBuilderFactory(coins.get('tflrp')),
120+
newTxBuilder: () =>
121+
new TransactionBuilderFactory(coins.get('tflrp'))
122+
.getExportInPBuilder()
123+
.threshold(noChangeTestData.threshold)
124+
.locktime(noChangeTestData.locktime)
125+
.fromPubKey(noChangeTestData.pAddresses)
126+
.amount(noChangeTestData.amount)
127+
.externalChainId(noChangeTestData.sourceChainId)
128+
.fee(noChangeTestData.fee)
129+
.utxos(noChangeTestData.outputs),
130+
unsignedTxHex: noChangeTestData.unsignedHex,
131+
halfSignedTxHex: noChangeTestData.halfSigntxHex,
132+
fullSignedTxHex: noChangeTestData.fullSigntxHex,
133+
privateKey: {
134+
prv1: noChangeTestData.privateKeys[0],
135+
prv2: noChangeTestData.privateKeys[1],
136+
},
137+
txHash: noChangeTestData.txhash,
138+
});
139+
90140
it('Should full sign a export tx from unsigned raw tx', () => {
91141
const txBuilder = new TransactionBuilderFactory(coins.get('tflrp')).from(testData.unsignedHex);
92142
txBuilder.sign({ key: testData.privateKeys[0] });

0 commit comments

Comments
 (0)