Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 27 additions & 85 deletions modules/sdk-coin-flrp/src/lib/ExportInPTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,10 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
this.transaction._rawSignedBytes = rawBytes;
}

// Create proper UnsignedTx wrapper with credentials
const sortedAddresses = [...this.transaction._fromAddresses].sort((a, b) => Buffer.compare(a, b));

// When credentials were extracted, use them directly to preserve existing signatures
// Otherwise, create empty credentials with dynamic ordering based on addressesIndex
// Match avaxp behavior: order depends on UTXO address positions
// Use centralized method for credential creation
const txCredentials =
credentials.length > 0
? credentials
Expand All @@ -126,39 +124,11 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
// Get UTXO for this input to determine addressesIndex
const utxo = this.transaction._utxos[inputIdx];

// either user (0) or recovery (2)
const firstIndex = this.recoverSigner ? 2 : 0;
const bitgoIndex = 1;

// If UTXO has addresses, compute dynamic ordering
if (utxo && utxo.addresses && utxo.addresses.length > 0) {
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
const addressesIndex = this.transaction._fromAddresses.map((a) =>
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
);

// Dynamic ordering based on addressesIndex
let sigSlots: ReturnType<typeof utils.createNewSig>[];
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
// Bitgo comes first: [zeros, userAddress]
sigSlots = [
utils.createNewSig(''),
utils.createEmptySigWithAddress(
Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')
),
];
} else {
// User comes first: [userAddress, zeros]
sigSlots = [
utils.createEmptySigWithAddress(
Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')
),
utils.createNewSig(''),
];
}
return new Credential(sigSlots);
// Use centralized method, but handle case where inputThreshold might differ
if (inputThreshold === this.transaction._threshold) {
return this.createCredentialForUtxo(utxo, this.transaction._threshold);
} else {
// Fallback: use all zeros if no UTXO addresses available
// Fallback: use all zeros if threshold differs (shouldn't happen normally)
const sigSlots: ReturnType<typeof utils.createNewSig>[] = [];
for (let i = 0; i < inputThreshold; i++) {
sigSlots.push(utils.createNewSig(''));
Expand All @@ -167,15 +137,13 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
}
});

// Create address maps for signing - one per input/credential
// Each address map contains all addresses mapped to their indices
const addressMaps = txCredentials.map(() => {
const addressMap = new FlareUtils.AddressMap();
sortedAddresses.forEach((addr, i) => {
addressMap.set(new Address(addr), i);
});
return addressMap;
});
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
// This matches the approach used in credentials: addressesIndex determines signature order
// AddressMaps should map addresses to signature slots in the same order as credentials
// Use centralized method for AddressMap creation
const addressMaps = txCredentials.map((credential, credIdx) =>
this.createAddressMapForUtxo(this.transaction._utxos[credIdx], this.transaction._threshold)
);

// Always create a new UnsignedTx with properly structured credentials
const unsignedTx = new UnsignedTx(exportTx, [], new FlareUtils.AddressMaps(addressMaps), txCredentials);
Expand Down Expand Up @@ -227,15 +195,13 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
this.exportedOutputs() // exportedOutputs
);

// Create address maps for signing - one per input/credential
const sortedAddresses = [...this.transaction._fromAddresses].sort((a, b) => Buffer.compare(a, b));
const addressMaps = credentials.map(() => {
const addressMap = new FlareUtils.AddressMap();
sortedAddresses.forEach((addr, i) => {
addressMap.set(new Address(addr), i);
});
return addressMap;
});
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
// This matches the approach used in credentials: addressesIndex determines signature order
// AddressMaps should map addresses to signature slots in the same order as credentials
// Use centralized method for AddressMap creation
const addressMaps = credentials.map((credential, credIdx) =>
this.createAddressMapForUtxo(this.transaction._utxos[credIdx], this.transaction._threshold)
);

// Create unsigned transaction
const unsignedTx = new UnsignedTx(
Expand Down Expand Up @@ -316,39 +282,15 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {

// Create credential with empty signatures for slot identification
// Match avaxp behavior: dynamic ordering based on addressesIndex from UTXO
const hasAddresses =
this.transaction._fromAddresses && this.transaction._fromAddresses.length >= this.transaction._threshold;

if (!hasAddresses) {
// If addresses not available, use all zeros
const emptySignatures = sigIndices.map(() => utils.createNewSig(''));
credentials.push(new Credential(emptySignatures));
// Use centralized method for credential creation
// Note: Use utxoThreshold if it differs from transaction threshold (should be rare)
const thresholdToUse =
utxoThreshold === this.transaction._threshold ? this.transaction._threshold : utxoThreshold;
if (thresholdToUse === this.transaction._threshold) {
credentials.push(this.createCredentialForUtxo(utxo, thresholdToUse));
} else {
// Compute addressesIndex: position of each _fromAddresses in UTXO's address list
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
const addressesIndex = this.transaction._fromAddresses.map((a) =>
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
);

// either user (0) or recovery (2)
const firstIndex = this.recoverSigner ? 2 : 0;
const bitgoIndex = 1;

// Dynamic ordering based on addressesIndex
let emptySignatures: ReturnType<typeof utils.createNewSig>[];
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
// Bitgo comes first in signature order: [zeros, userAddress]
emptySignatures = [
utils.createNewSig(''),
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
];
} else {
// User comes first in signature order: [userAddress, zeros]
emptySignatures = [
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
utils.createNewSig(''),
];
}
// Fallback: use all zeros if threshold differs (shouldn't happen normally)
const emptySignatures = sigIndices.map(() => utils.createNewSig(''));
credentials.push(new Credential(emptySignatures));
}
}
Expand Down
85 changes: 42 additions & 43 deletions modules/sdk-coin-flrp/src/lib/ImportInCTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,37 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
const inputThreshold = firstInput.sigIndicies().length || this.transaction._threshold;
this.transaction._threshold = inputThreshold;

// Create proper UnsignedTx wrapper with credentials
const toAddress = new Address(output.address.toBytes());
const addressMap = new FlareUtils.AddressMap([[toAddress, 0]]);
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
// This matches the approach used in credentials: addressesIndex determines signature order
// AddressMaps should map addresses to signature slots in the same order as credentials
// If _fromAddresses is available, create AddressMap based on UTXO order (matching credential order)
// Otherwise, fall back to mapping just the output address
const firstUtxo = this.transaction._utxos[0];
let addressMap: FlareUtils.AddressMap;
if (
firstUtxo &&
firstUtxo.addresses &&
firstUtxo.addresses.length > 0 &&
this.transaction._fromAddresses &&
this.transaction._fromAddresses.length >= this.transaction._threshold
) {
// Use centralized method for AddressMap creation
addressMap = this.createAddressMapForUtxo(firstUtxo, this.transaction._threshold);
} else {
// Fallback: map output address to slot 0 (for C-chain imports, output is the destination)
// Or map addresses sequentially if _fromAddresses is available but UTXO addresses are not
addressMap = new FlareUtils.AddressMap();
if (this.transaction._fromAddresses && this.transaction._fromAddresses.length >= this.transaction._threshold) {
this.transaction._fromAddresses.slice(0, this.transaction._threshold).forEach((addr, i) => {
addressMap.set(new Address(addr), i);
});
} else {
// Last resort: map output address
const toAddress = new Address(output.address.toBytes());
addressMap.set(toAddress, 0);
}
}

const addressMaps = new FlareUtils.AddressMaps([addressMap]);

// When credentials were extracted, use them directly to preserve existing signatures
Expand Down Expand Up @@ -159,11 +187,15 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
[output]
);

// Create unsigned transaction with all potential signers in address map
const addressMap = new FlareUtils.AddressMap();
this.transaction._fromAddresses.forEach((addr, i) => {
addressMap.set(new Address(addr), i);
});
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
// This matches the approach used in credentials: addressesIndex determines signature order
// AddressMaps should map addresses to signature slots in the same order as credentials
// For C-chain imports, we typically have one input, so use the first UTXO
// Use centralized method for AddressMap creation
const firstUtxo = this.transaction._utxos[0];
const addressMap = firstUtxo
? this.createAddressMapForUtxo(firstUtxo, this.transaction._threshold)
: new FlareUtils.AddressMap();
const addressMaps = new FlareUtils.AddressMaps([addressMap]);

const unsignedTx = new UnsignedTx(
Expand Down Expand Up @@ -230,41 +262,8 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {

// Create credential with empty signatures for slot identification
// Match avaxp behavior: dynamic ordering based on addressesIndex from UTXO
const hasAddresses =
this.transaction._fromAddresses && this.transaction._fromAddresses.length >= this.transaction._threshold;

if (!hasAddresses) {
// If addresses not available, use all zeros
const emptySignatures = sigIndices.map(() => utils.createNewSig(''));
credentials.push(new Credential(emptySignatures));
} else {
// Compute addressesIndex: position of each _fromAddresses in UTXO's address list
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
const addressesIndex = this.transaction._fromAddresses.map((a) =>
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
);

// either user (0) or recovery (2)
const firstIndex = this.recoverSigner ? 2 : 0;
const bitgoIndex = 1;

// Dynamic ordering based on addressesIndex
let emptySignatures: ReturnType<typeof utils.createNewSig>[];
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
// Bitgo comes first in signature order: [zeros, userAddress]
emptySignatures = [
utils.createNewSig(''),
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
];
} else {
// User comes first in signature order: [userAddress, zeros]
emptySignatures = [
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
utils.createNewSig(''),
];
}
credentials.push(new Credential(emptySignatures));
}
// Use centralized method for credential creation
credentials.push(this.createCredentialForUtxo(utxo, this.transaction._threshold));
});

return {
Expand Down
100 changes: 18 additions & 82 deletions modules/sdk-coin-flrp/src/lib/ImportInPTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,55 +92,19 @@ export class ImportInPTxBuilder extends AtomicTransactionBuilder {
}

// Create proper UnsignedTx wrapper with credentials
const sortedAddresses = [...this.transaction._fromAddresses].sort((a, b) => Buffer.compare(a, b));
const addressMaps = sortedAddresses.map((a, i) => new FlareUtils.AddressMap([[new Address(a), i]]));

// When credentials were extracted, use them directly to preserve existing signatures
// Match avaxp behavior: dynamic ordering based on addressesIndex from UTXO
// Use centralized methods for credential and AddressMap creation
const txCredentials =
credentials.length > 0
? credentials
: this.transaction._utxos.map((utxo) => {
// either user (0) or recovery (2)
const firstIndex = this.recoverSigner ? 2 : 0;
const bitgoIndex = 1;

// If UTXO has addresses, compute dynamic ordering
if (utxo && utxo.addresses && utxo.addresses.length > 0) {
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
const addressesIndex = this.transaction._fromAddresses.map((a) =>
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
);

// Dynamic ordering based on addressesIndex
let sigSlots: ReturnType<typeof utils.createNewSig>[];
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
// Bitgo comes first: [zeros, userAddress]
sigSlots = [
utils.createNewSig(''),
utils.createEmptySigWithAddress(
Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')
),
];
} else {
// User comes first: [userAddress, zeros]
sigSlots = [
utils.createEmptySigWithAddress(
Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')
),
utils.createNewSig(''),
];
}
return new Credential(sigSlots);
} else {
// Fallback: use all zeros if no UTXO addresses available
const sigSlots: ReturnType<typeof utils.createNewSig>[] = [];
for (let i = 0; i < this.transaction._threshold; i++) {
sigSlots.push(utils.createNewSig(''));
}
return new Credential(sigSlots);
}
});
: this.transaction._utxos.map((utxo) => this.createCredentialForUtxo(utxo, this.transaction._threshold));

// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
// This matches the approach used in credentials: addressesIndex determines signature order
// AddressMaps should map addresses to signature slots in the same order as credentials
const addressMaps = this.transaction._utxos.map((utxo) =>
this.createAddressMapForUtxo(utxo, this.transaction._threshold)
);

const unsignedTx = new UnsignedTx(importTx, [], new FlareUtils.AddressMaps(addressMaps), txCredentials);

Expand Down Expand Up @@ -200,8 +164,13 @@ export class ImportInPTxBuilder extends AtomicTransactionBuilder {
inputs // importedInputs (ins)
);

// Create address maps for signing
const addressMaps = this.transaction._fromAddresses.map((a, i) => new FlareUtils.AddressMap([[new Address(a), i]]));
// Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
// This matches the approach used in credentials: addressesIndex determines signature order
// AddressMaps should map addresses to signature slots in the same order as credentials
// Use centralized method for AddressMap creation
const addressMaps = credentials.map((credential, credIdx) =>
this.createAddressMapForUtxo(this.transaction._utxos[credIdx], this.transaction._threshold)
);

// Create unsigned transaction
const unsignedTx = new UnsignedTx(
Expand Down Expand Up @@ -264,41 +233,8 @@ export class ImportInPTxBuilder extends AtomicTransactionBuilder {

// Create credential with empty signatures for slot identification
// Match avaxp behavior: dynamic ordering based on addressesIndex from UTXO
const hasAddresses =
this.transaction._fromAddresses && this.transaction._fromAddresses.length >= this.transaction._threshold;

if (!hasAddresses) {
// If addresses not available, use all zeros
const emptySignatures = sigIndices.map(() => utils.createNewSig(''));
credentials.push(new Credential(emptySignatures));
} else {
// Compute addressesIndex: position of each _fromAddresses in UTXO's address list
const utxoAddresses = utxo.addresses.map((a) => utils.parseAddress(a));
const addressesIndex = this.transaction._fromAddresses.map((a) =>
utxoAddresses.findIndex((u) => Buffer.compare(Buffer.from(u), Buffer.from(a)) === 0)
);

// either user (0) or recovery (2)
const firstIndex = this.recoverSigner ? 2 : 0;
const bitgoIndex = 1;

// Dynamic ordering based on addressesIndex
let emptySignatures: ReturnType<typeof utils.createNewSig>[];
if (addressesIndex[bitgoIndex] < addressesIndex[firstIndex]) {
// Bitgo comes first in signature order: [zeros, userAddress]
emptySignatures = [
utils.createNewSig(''),
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
];
} else {
// User comes first in signature order: [userAddress, zeros]
emptySignatures = [
utils.createEmptySigWithAddress(Buffer.from(this.transaction._fromAddresses[firstIndex]).toString('hex')),
utils.createNewSig(''),
];
}
credentials.push(new Credential(emptySignatures));
}
// Use centralized method for credential creation
credentials.push(this.createCredentialForUtxo(utxo, this.transaction._threshold));
});

return {
Expand Down
Loading