Skip to content

Commit 4dca6a7

Browse files
committed
fix(sdk-coin-flrp): centralize credential and AddressMap creation for UTXO-based transactions
TICKET: WIN-8279
1 parent 27529df commit 4dca6a7

File tree

4 files changed

+146
-356
lines changed

4 files changed

+146
-356
lines changed

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

Lines changed: 21 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
113113
// When credentials were extracted, use them directly to preserve existing signatures
114114
// Otherwise, create empty credentials with dynamic ordering based on addressesIndex
115115
// Match avaxp behavior: order depends on UTXO address positions
116+
// Use centralized method for credential creation
116117
const txCredentials =
117118
credentials.length > 0
118119
? credentials
@@ -123,39 +124,11 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
123124
// Get UTXO for this input to determine addressesIndex
124125
const utxo = this.transaction._utxos[inputIdx];
125126

126-
// either user (0) or recovery (2)
127-
const firstIndex = this.recoverSigner ? 2 : 0;
128-
const bitgoIndex = 1;
129-
130-
// If UTXO has addresses, compute dynamic ordering
131-
if (utxo && utxo.addresses && utxo.addresses.length > 0) {
132-
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
133-
const addressesIndex = this.transaction._fromAddresses.map((a) =>
134-
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
135-
);
136-
137-
// Dynamic ordering based on addressesIndex
138-
let sigSlots: ReturnType<typeof utils.createNewSig>[];
139-
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
140-
// Bitgo comes first: [zeros, userAddress]
141-
sigSlots = [
142-
utils.createNewSig(''),
143-
utils.createEmptySigWithAddress(
144-
Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')
145-
),
146-
];
147-
} else {
148-
// User comes first: [userAddress, zeros]
149-
sigSlots = [
150-
utils.createEmptySigWithAddress(
151-
Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')
152-
),
153-
utils.createNewSig(''),
154-
];
155-
}
156-
return new Credential(sigSlots);
127+
// Use centralized method, but handle case where inputThreshold might differ
128+
if (inputThreshold === this.transaction._threshold) {
129+
return this.createCredentialForUtxo(utxo, this.transaction._threshold);
157130
} else {
158-
// Fallback: use all zeros if no UTXO addresses available
131+
// Fallback: use all zeros if threshold differs (shouldn't happen normally)
159132
const sigSlots: ReturnType<typeof utils.createNewSig>[] = [];
160133
for (let i = 0; i < inputThreshold; i++) {
161134
sigSlots.push(utils.createNewSig(''));
@@ -167,39 +140,10 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
167140
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
168141
// This matches the approach used in credentials: addressesIndex determines signature order
169142
// AddressMaps should map addresses to signature slots in the same order as credentials
170-
const addressMaps = txCredentials.map((credential, credIdx) => {
171-
const addressMap = new FlareUtils.AddressMap();
172-
const utxo = this.transaction._utxos[credIdx];
173-
174-
// If UTXO has addresses, compute addressesIndex to determine signature order
175-
if (utxo && utxo.addresses && utxo.addresses.length > 0) {
176-
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
177-
const addressesIndex = this.transaction._fromAddresses.map((a) =>
178-
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
179-
);
180-
181-
const firstIndex = this.recoverSigner ? 2 : 0;
182-
const bitgoIndex = 1;
183-
184-
// Determine signature slot order based on addressesIndex (same logic as credentials)
185-
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
186-
// Bitgo comes first: slot 0 = bitgo, slot 1 = firstIndex
187-
addressMap.set(new Address(this.transaction._fromAddresses[bitgoIndex]), 0);
188-
addressMap.set(new Address(this.transaction._fromAddresses[firstIndex]), 1);
189-
} else {
190-
// User/recovery comes first: slot 0 = firstIndex, slot 1 = bitgo
191-
addressMap.set(new Address(this.transaction._fromAddresses[firstIndex]), 0);
192-
addressMap.set(new Address(this.transaction._fromAddresses[bitgoIndex]), 1);
193-
}
194-
} else {
195-
// Fallback: map addresses sequentially if no UTXO addresses available
196-
this.transaction._fromAddresses.slice(0, this.transaction._threshold).forEach((addr, i) => {
197-
addressMap.set(new Address(addr), i);
198-
});
199-
}
200-
201-
return addressMap;
202-
});
143+
// Use centralized method for AddressMap creation
144+
const addressMaps = txCredentials.map((credential, credIdx) =>
145+
this.createAddressMapForUtxo(this.transaction._utxos[credIdx], this.transaction._threshold)
146+
);
203147

204148
// Always create a new UnsignedTx with properly structured credentials
205149
const unsignedTx = new UnsignedTx(exportTx, [], new FlareUtils.AddressMaps(addressMaps), txCredentials);
@@ -254,39 +198,10 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
254198
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
255199
// This matches the approach used in credentials: addressesIndex determines signature order
256200
// AddressMaps should map addresses to signature slots in the same order as credentials
257-
const addressMaps = credentials.map((credential, credIdx) => {
258-
const addressMap = new FlareUtils.AddressMap();
259-
const utxo = this.transaction._utxos[credIdx];
260-
261-
// If UTXO has addresses, compute addressesIndex to determine signature order
262-
if (utxo && utxo.addresses && utxo.addresses.length > 0) {
263-
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
264-
const addressesIndex = this.transaction._fromAddresses.map((a) =>
265-
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
266-
);
267-
268-
const firstIndex = this.recoverSigner ? 2 : 0;
269-
const bitgoIndex = 1;
270-
271-
// Determine signature slot order based on addressesIndex (same logic as credentials)
272-
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
273-
// Bitgo comes first: slot 0 = bitgo, slot 1 = firstIndex
274-
addressMap.set(new Address(this.transaction._fromAddresses[bitgoIndex]), 0);
275-
addressMap.set(new Address(this.transaction._fromAddresses[firstIndex]), 1);
276-
} else {
277-
// User/recovery comes first: slot 0 = firstIndex, slot 1 = bitgo
278-
addressMap.set(new Address(this.transaction._fromAddresses[firstIndex]), 0);
279-
addressMap.set(new Address(this.transaction._fromAddresses[bitgoIndex]), 1);
280-
}
281-
} else {
282-
// Fallback: map addresses sequentially if no UTXO addresses available
283-
this.transaction._fromAddresses.slice(0, this.transaction._threshold).forEach((addr, i) => {
284-
addressMap.set(new Address(addr), i);
285-
});
286-
}
287-
288-
return addressMap;
289-
});
201+
// Use centralized method for AddressMap creation
202+
const addressMaps = credentials.map((credential, credIdx) =>
203+
this.createAddressMapForUtxo(this.transaction._utxos[credIdx], this.transaction._threshold)
204+
);
290205

291206
// Create unsigned transaction
292207
const unsignedTx = new UnsignedTx(
@@ -367,39 +282,15 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
367282

368283
// Create credential with empty signatures for slot identification
369284
// Match avaxp behavior: dynamic ordering based on addressesIndex from UTXO
370-
const hasAddresses =
371-
this.transaction._fromAddresses && this.transaction._fromAddresses.length >= this.transaction._threshold;
372-
373-
if (!hasAddresses) {
374-
// If addresses not available, use all zeros
375-
const emptySignatures = sigIndices.map(() => utils.createNewSig(''));
376-
credentials.push(new Credential(emptySignatures));
285+
// Use centralized method for credential creation
286+
// Note: Use utxoThreshold if it differs from transaction threshold (should be rare)
287+
const thresholdToUse =
288+
utxoThreshold === this.transaction._threshold ? this.transaction._threshold : utxoThreshold;
289+
if (thresholdToUse === this.transaction._threshold) {
290+
credentials.push(this.createCredentialForUtxo(utxo, thresholdToUse));
377291
} else {
378-
// Compute addressesIndex: position of each _fromAddresses in UTXO's address list
379-
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
380-
const addressesIndex = this.transaction._fromAddresses.map((a) =>
381-
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
382-
);
383-
384-
// either user (0) or recovery (2)
385-
const firstIndex = this.recoverSigner ? 2 : 0;
386-
const bitgoIndex = 1;
387-
388-
// Dynamic ordering based on addressesIndex
389-
let emptySignatures: ReturnType<typeof utils.createNewSig>[];
390-
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
391-
// Bitgo comes first in signature order: [zeros, userAddress]
392-
emptySignatures = [
393-
utils.createNewSig(''),
394-
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
395-
];
396-
} else {
397-
// User comes first in signature order: [userAddress, zeros]
398-
emptySignatures = [
399-
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
400-
utils.createNewSig(''),
401-
];
402-
}
292+
// Fallback: use all zeros if threshold differs (shouldn't happen normally)
293+
const emptySignatures = sigIndices.map(() => utils.createNewSig(''));
403294
credentials.push(new Credential(emptySignatures));
404295
}
405296
}

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

Lines changed: 11 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -90,39 +90,23 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
9090
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
9191
// This matches the approach used in credentials: addressesIndex determines signature order
9292
// AddressMaps should map addresses to signature slots in the same order as credentials
93-
const addressMap = new FlareUtils.AddressMap();
94-
9593
// If _fromAddresses is available, create AddressMap based on UTXO order (matching credential order)
9694
// Otherwise, fall back to mapping just the output address
9795
const firstUtxo = this.transaction._utxos[0];
96+
let addressMap: FlareUtils.AddressMap;
9897
if (
9998
firstUtxo &&
10099
firstUtxo.addresses &&
101100
firstUtxo.addresses.length > 0 &&
102101
this.transaction._fromAddresses &&
103102
this.transaction._fromAddresses.length >= this.transaction._threshold
104103
) {
105-
const utxoAddresses = firstUtxo.addresses.map((a) => utils.parseAddress(a));
106-
const addressesIndex = this.transaction._fromAddresses.map((a) =>
107-
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
108-
);
109-
110-
const firstIndex = this.recoverSigner ? 2 : 0;
111-
const bitgoIndex = 1;
112-
113-
// Determine signature slot order based on addressesIndex (same logic as credentials)
114-
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
115-
// Bitgo comes first: slot 0 = bitgo, slot 1 = firstIndex
116-
addressMap.set(new Address(this.transaction._fromAddresses[bitgoIndex]), 0);
117-
addressMap.set(new Address(this.transaction._fromAddresses[firstIndex]), 1);
118-
} else {
119-
// User/recovery comes first: slot 0 = firstIndex, slot 1 = bitgo
120-
addressMap.set(new Address(this.transaction._fromAddresses[firstIndex]), 0);
121-
addressMap.set(new Address(this.transaction._fromAddresses[bitgoIndex]), 1);
122-
}
104+
// Use centralized method for AddressMap creation
105+
addressMap = this.createAddressMapForUtxo(firstUtxo, this.transaction._threshold);
123106
} else {
124107
// Fallback: map output address to slot 0 (for C-chain imports, output is the destination)
125108
// Or map addresses sequentially if _fromAddresses is available but UTXO addresses are not
109+
addressMap = new FlareUtils.AddressMap();
126110
if (this.transaction._fromAddresses && this.transaction._fromAddresses.length >= this.transaction._threshold) {
127111
this.transaction._fromAddresses.slice(0, this.transaction._threshold).forEach((addr, i) => {
128112
addressMap.set(new Address(addr), i);
@@ -206,36 +190,12 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
206190
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
207191
// This matches the approach used in credentials: addressesIndex determines signature order
208192
// AddressMaps should map addresses to signature slots in the same order as credentials
209-
const addressMap = new FlareUtils.AddressMap();
210-
211-
// For C-chain imports, we typically have one input, so use the first UTXO to determine address order
193+
// For C-chain imports, we typically have one input, so use the first UTXO
194+
// Use centralized method for AddressMap creation
212195
const firstUtxo = this.transaction._utxos[0];
213-
if (firstUtxo && firstUtxo.addresses && firstUtxo.addresses.length > 0) {
214-
const utxoAddresses = firstUtxo.addresses.map((a) => utils.parseAddress(a));
215-
const addressesIndex = this.transaction._fromAddresses.map((a) =>
216-
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
217-
);
218-
219-
const firstIndex = this.recoverSigner ? 2 : 0;
220-
const bitgoIndex = 1;
221-
222-
// Determine signature slot order based on addressesIndex (same logic as credentials)
223-
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
224-
// Bitgo comes first: slot 0 = bitgo, slot 1 = firstIndex
225-
addressMap.set(new Address(this.transaction._fromAddresses[bitgoIndex]), 0);
226-
addressMap.set(new Address(this.transaction._fromAddresses[firstIndex]), 1);
227-
} else {
228-
// User/recovery comes first: slot 0 = firstIndex, slot 1 = bitgo
229-
addressMap.set(new Address(this.transaction._fromAddresses[firstIndex]), 0);
230-
addressMap.set(new Address(this.transaction._fromAddresses[bitgoIndex]), 1);
231-
}
232-
} else {
233-
// Fallback: map addresses sequentially if no UTXO addresses available
234-
this.transaction._fromAddresses.slice(0, this.transaction._threshold).forEach((addr, i) => {
235-
addressMap.set(new Address(addr), i);
236-
});
237-
}
238-
196+
const addressMap = firstUtxo
197+
? this.createAddressMapForUtxo(firstUtxo, this.transaction._threshold)
198+
: new FlareUtils.AddressMap();
239199
const addressMaps = new FlareUtils.AddressMaps([addressMap]);
240200

241201
const unsignedTx = new UnsignedTx(
@@ -302,41 +262,8 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
302262

303263
// Create credential with empty signatures for slot identification
304264
// Match avaxp behavior: dynamic ordering based on addressesIndex from UTXO
305-
const hasAddresses =
306-
this.transaction._fromAddresses && this.transaction._fromAddresses.length >= this.transaction._threshold;
307-
308-
if (!hasAddresses) {
309-
// If addresses not available, use all zeros
310-
const emptySignatures = sigIndices.map(() => utils.createNewSig(''));
311-
credentials.push(new Credential(emptySignatures));
312-
} else {
313-
// Compute addressesIndex: position of each _fromAddresses in UTXO's address list
314-
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
315-
const addressesIndex = this.transaction._fromAddresses.map((a) =>
316-
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
317-
);
318-
319-
// either user (0) or recovery (2)
320-
const firstIndex = this.recoverSigner ? 2 : 0;
321-
const bitgoIndex = 1;
322-
323-
// Dynamic ordering based on addressesIndex
324-
let emptySignatures: ReturnType<typeof utils.createNewSig>[];
325-
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
326-
// Bitgo comes first in signature order: [zeros, userAddress]
327-
emptySignatures = [
328-
utils.createNewSig(''),
329-
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
330-
];
331-
} else {
332-
// User comes first in signature order: [userAddress, zeros]
333-
emptySignatures = [
334-
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
335-
utils.createNewSig(''),
336-
];
337-
}
338-
credentials.push(new Credential(emptySignatures));
339-
}
265+
// Use centralized method for credential creation
266+
credentials.push(this.createCredentialForUtxo(utxo, this.transaction._threshold));
340267
});
341268

342269
return {

0 commit comments

Comments
 (0)