diff --git a/modules/sdk-coin-flrp/src/lib/ExportInPTxBuilder.ts b/modules/sdk-coin-flrp/src/lib/ExportInPTxBuilder.ts index eb64859da6..ce30b4c73c 100644 --- a/modules/sdk-coin-flrp/src/lib/ExportInPTxBuilder.ts +++ b/modules/sdk-coin-flrp/src/lib/ExportInPTxBuilder.ts @@ -76,16 +76,29 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder { this.computeAddressesIndexFromParsed(); + // Use parsed credentials if available, otherwise create new ones based on sigIndices + // The sigIndices from the parsed transaction (stored in addressesIndex) determine + // the correct credential ordering for on-chain verification const txCredentials = credentials.length > 0 ? credentials : this.transaction._utxos.map((utxo) => { const utxoThreshold = utxo.threshold || this.transaction._threshold; + const sigIndices = utxo.addressesIndex ?? []; + // Use sigIndices-based method if we have valid sigIndices from parsed transaction + if (sigIndices.length >= utxoThreshold && sigIndices.every((idx) => idx >= 0)) { + return this.createCredentialForUtxoWithSigIndices(utxo, utxoThreshold, sigIndices); + } return this.createCredentialForUtxo(utxo, utxoThreshold); }); + // Create addressMaps using sigIndices from parsed transaction for consistency const addressMaps = this.transaction._utxos.map((utxo) => { const utxoThreshold = utxo.threshold || this.transaction._threshold; + const sigIndices = utxo.addressesIndex ?? []; + if (sigIndices.length >= utxoThreshold && sigIndices.every((idx) => idx >= 0)) { + return this.createAddressMapForUtxoWithSigIndices(utxo, utxoThreshold, sigIndices); + } return this.createAddressMapForUtxo(utxo, utxoThreshold); }); @@ -178,19 +191,27 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder { throw new BuildTransactionError(`Could not find matching UTXO for input ${inputTxid}:${inputOutputIdx}`); } + const transferInput = input.input as TransferInput; + const actualSigIndices = transferInput.sigIndicies(); + return { ...originalUtxo, addressesIndex: originalUtxo.addressesIndex, addresses: originalUtxo.addresses, threshold: originalUtxo.threshold || this.transaction._threshold, + actualSigIndices, }; }); this.transaction._utxos = utxosWithIndex; - const txCredentials = utxosWithIndex.map((utxo) => this.createCredentialForUtxo(utxo, utxo.threshold)); + const txCredentials = utxosWithIndex.map((utxo) => + this.createCredentialForUtxoWithSigIndices(utxo, utxo.threshold, utxo.actualSigIndices) + ); - const addressMaps = utxosWithIndex.map((utxo) => this.createAddressMapForUtxo(utxo, utxo.threshold)); + const addressMaps = utxosWithIndex.map((utxo) => + this.createAddressMapForUtxoWithSigIndices(utxo, utxo.threshold, utxo.actualSigIndices) + ); const fixedUnsignedTx = new UnsignedTx(innerTx, [], new FlareUtils.AddressMaps(addressMaps), txCredentials); diff --git a/modules/sdk-coin-flrp/src/lib/ImportInCTxBuilder.ts b/modules/sdk-coin-flrp/src/lib/ImportInCTxBuilder.ts index 4ea3696edc..a259431dd6 100644 --- a/modules/sdk-coin-flrp/src/lib/ImportInCTxBuilder.ts +++ b/modules/sdk-coin-flrp/src/lib/ImportInCTxBuilder.ts @@ -76,20 +76,31 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder { this.computeAddressesIndexFromParsed(); + // Create addressMaps using sigIndices from parsed transaction for consistency const addressMaps = this.transaction._utxos.map((utxo) => { const utxoThreshold = utxo.threshold || this.transaction._threshold; + const sigIndices = utxo.addressesIndex ?? []; + if (sigIndices.length >= utxoThreshold && sigIndices.every((idx) => idx >= 0)) { + return this.createAddressMapForUtxoWithSigIndices(utxo, utxoThreshold, sigIndices); + } return this.createAddressMapForUtxo(utxo, utxoThreshold); }); const flareAddressMaps = new FlareUtils.AddressMaps(addressMaps); + // Use parsed credentials if available, otherwise create new ones based on sigIndices let txCredentials: Credential[]; if (credentials.length > 0) { txCredentials = credentials; } else { - txCredentials = this.transaction._utxos.map((utxo) => - this.createCredentialForUtxo(utxo, utxo.threshold || this.transaction._threshold) - ); + txCredentials = this.transaction._utxos.map((utxo) => { + const utxoThreshold = utxo.threshold || this.transaction._threshold; + const sigIndices = utxo.addressesIndex ?? []; + if (sigIndices.length >= utxoThreshold && sigIndices.every((idx) => idx >= 0)) { + return this.createCredentialForUtxoWithSigIndices(utxo, utxoThreshold, sigIndices); + } + return this.createCredentialForUtxo(utxo, utxoThreshold); + }); } const unsignedTx = new UnsignedTx(baseTx, [], flareAddressMaps, txCredentials); @@ -178,19 +189,27 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder { throw new BuildTransactionError(`Could not find matching UTXO for input ${inputTxid}:${inputOutputIdx}`); } + const transferInput = input.input as TransferInput; + const actualSigIndices = transferInput.sigIndicies(); + return { ...originalUtxo, addressesIndex: originalUtxo.addressesIndex, addresses: originalUtxo.addresses, threshold: originalUtxo.threshold || this.transaction._threshold, + actualSigIndices, }; }); this.transaction._utxos = utxosWithIndex; - const txCredentials = utxosWithIndex.map((utxo) => this.createCredentialForUtxo(utxo, utxo.threshold)); + const txCredentials = utxosWithIndex.map((utxo) => + this.createCredentialForUtxoWithSigIndices(utxo, utxo.threshold, utxo.actualSigIndices) + ); - const addressMaps = utxosWithIndex.map((utxo) => this.createAddressMapForUtxo(utxo, utxo.threshold)); + const addressMaps = utxosWithIndex.map((utxo) => + this.createAddressMapForUtxoWithSigIndices(utxo, utxo.threshold, utxo.actualSigIndices) + ); const fixedUnsignedTx = new UnsignedTx(innerTx, [], new FlareUtils.AddressMaps(addressMaps), txCredentials); diff --git a/modules/sdk-coin-flrp/src/lib/ImportInPTxBuilder.ts b/modules/sdk-coin-flrp/src/lib/ImportInPTxBuilder.ts index a925cb5da9..c2bd1e0d60 100644 --- a/modules/sdk-coin-flrp/src/lib/ImportInPTxBuilder.ts +++ b/modules/sdk-coin-flrp/src/lib/ImportInPTxBuilder.ts @@ -95,16 +95,29 @@ export class ImportInPTxBuilder extends AtomicTransactionBuilder { this.computeAddressesIndexFromParsed(); + // Use parsed credentials if available, otherwise create new ones based on sigIndices + // The sigIndices from the parsed transaction (stored in addressesIndex) determine + // the correct credential ordering for on-chain verification const txCredentials = credentials.length > 0 ? credentials : this.transaction._utxos.map((utxo) => { const utxoThreshold = utxo.threshold || this.transaction._threshold; + const sigIndices = utxo.addressesIndex ?? []; + // Use sigIndices-based method if we have valid sigIndices from parsed transaction + if (sigIndices.length >= utxoThreshold && sigIndices.every((idx) => idx >= 0)) { + return this.createCredentialForUtxoWithSigIndices(utxo, utxoThreshold, sigIndices); + } return this.createCredentialForUtxo(utxo, utxoThreshold); }); + // Create addressMaps using sigIndices from parsed transaction for consistency const addressMaps = this.transaction._utxos.map((utxo) => { const utxoThreshold = utxo.threshold || this.transaction._threshold; + const sigIndices = utxo.addressesIndex ?? []; + if (sigIndices.length >= utxoThreshold && sigIndices.every((idx) => idx >= 0)) { + return this.createAddressMapForUtxoWithSigIndices(utxo, utxoThreshold, sigIndices); + } return this.createAddressMapForUtxo(utxo, utxoThreshold); }); @@ -209,19 +222,27 @@ export class ImportInPTxBuilder extends AtomicTransactionBuilder { throw new BuildTransactionError(`Could not find matching UTXO for input ${inputTxid}:${inputOutputIdx}`); } + const transferInput = input.input as TransferInput; + const actualSigIndices = transferInput.sigIndicies(); + return { ...originalUtxo, addressesIndex: originalUtxo.addressesIndex, addresses: originalUtxo.addresses, threshold: originalUtxo.threshold || this.transaction._threshold, + actualSigIndices, }; }); this.transaction._utxos = utxosWithIndex; - const txCredentials = utxosWithIndex.map((utxo) => this.createCredentialForUtxo(utxo, utxo.threshold)); + const txCredentials = utxosWithIndex.map((utxo) => + this.createCredentialForUtxoWithSigIndices(utxo, utxo.threshold, utxo.actualSigIndices) + ); - const addressMaps = utxosWithIndex.map((utxo) => this.createAddressMapForUtxo(utxo, utxo.threshold)); + const addressMaps = utxosWithIndex.map((utxo) => + this.createAddressMapForUtxoWithSigIndices(utxo, utxo.threshold, utxo.actualSigIndices) + ); const fixedUnsignedTx = new UnsignedTx(innerTx, [], new FlareUtils.AddressMaps(addressMaps), txCredentials); diff --git a/modules/sdk-coin-flrp/src/lib/atomicTransactionBuilder.ts b/modules/sdk-coin-flrp/src/lib/atomicTransactionBuilder.ts index 37ba420c50..210ab63554 100644 --- a/modules/sdk-coin-flrp/src/lib/atomicTransactionBuilder.ts +++ b/modules/sdk-coin-flrp/src/lib/atomicTransactionBuilder.ts @@ -288,4 +288,128 @@ export abstract class AtomicTransactionBuilder extends TransactionBuilder { return addressMap; } + + /** + * Create credential using the ACTUAL sigIndices from FlareJS. + * + * This method determines which sender addresses correspond to which sigIndex positions, + * then creates the credential with signatures in the correct order matching the sigIndices. + * + * sigIndices tell us which positions in the UTXO's owner addresses need to sign. + * We need to figure out which sender addresses are at those positions and create + * signature slots in the same order as sigIndices. + * + * @param utxo - The UTXO to create credential for + * @param threshold - Number of signatures required + * @param actualSigIndices - The actual sigIndices from FlareJS's built input + * @returns Credential with signatures ordered to match sigIndices + * @protected + */ + protected createCredentialForUtxoWithSigIndices( + utxo: DecodedUtxoObj, + threshold: number, + actualSigIndices: number[] + ): Credential { + const sender = this.transaction._fromAddresses; + const addressesIndex = utxo.addressesIndex ?? []; + + // either user (0) or recovery (2) + const firstIndex = this.recoverSigner ? 2 : 0; + + if (threshold === 1) { + if (sender && sender.length > firstIndex && addressesIndex[firstIndex] !== undefined) { + return new Credential([utils.createEmptySigWithAddress(Buffer.from(sender[firstIndex]).toString('hex'))]); + } + return new Credential([utils.createNewSig('')]); + } + + // For threshold >= 2, use the actual sigIndices order from FlareJS + // sigIndices[i] = position in UTXO's owner addresses that needs to sign + // addressesIndex[senderIdx] = position in UTXO's owner addresses for that sender + // + // We need to find which sender corresponds to each sigIndex and create signatures + // in the sigIndices order. + if (actualSigIndices.length >= 2 && addressesIndex.length >= 2 && sender && sender.length >= threshold) { + const emptySignatures: ReturnType[] = []; + + for (const sigIdx of actualSigIndices) { + // Find which sender address is at this UTXO position + // addressesIndex[senderIdx] tells us which UTXO position each sender is at + const senderIdx = addressesIndex.findIndex((utxoPos) => utxoPos === sigIdx); + + if (senderIdx === firstIndex) { + // This sigIndex slot is for user/recovery - embed their address + emptySignatures.push(utils.createEmptySigWithAddress(Buffer.from(sender[firstIndex]).toString('hex'))); + } else { + // BitGo (HSM) or unknown sender - empty signature + emptySignatures.push(utils.createNewSig('')); + } + } + + return new Credential(emptySignatures); + } + + // Fallback: create threshold empty signatures + const emptySignatures: ReturnType[] = []; + for (let i = 0; i < threshold; i++) { + emptySignatures.push(utils.createNewSig('')); + } + return new Credential(emptySignatures); + } + + /** + * Create AddressMap using the ACTUAL sigIndices from FlareJS. + * + * Maps sender addresses to signature slots based on the actual sigIndices order. + * + * @param utxo - The UTXO to create AddressMap for + * @param threshold - Number of signatures required + * @param actualSigIndices - The actual sigIndices from FlareJS's built input + * @returns AddressMap that maps addresses to signature slots + * @protected + */ + protected createAddressMapForUtxoWithSigIndices( + utxo: DecodedUtxoObj, + threshold: number, + actualSigIndices: number[] + ): FlareUtils.AddressMap { + const addressMap = new FlareUtils.AddressMap(); + const sender = this.transaction._fromAddresses; + const addressesIndex = utxo.addressesIndex ?? []; + + const firstIndex = this.recoverSigner ? 2 : 0; + const bitgoIndex = 1; + + if (threshold === 1) { + if (sender && sender.length > firstIndex) { + addressMap.set(new Address(sender[firstIndex]), 0); + } else if (sender && sender.length > 0) { + addressMap.set(new Address(sender[0]), 0); + } + return addressMap; + } + + // For threshold >= 2, map addresses based on actual sigIndices order + if (actualSigIndices.length >= 2 && addressesIndex.length >= 2 && sender && sender.length >= threshold) { + actualSigIndices.forEach((sigIdx, slotIdx) => { + // Find which sender is at this UTXO position + const senderIdx = addressesIndex.findIndex((utxoPos) => utxoPos === sigIdx); + + if (senderIdx === bitgoIndex || senderIdx === firstIndex) { + addressMap.set(new Address(sender[senderIdx]), slotIdx); + } + }); + + return addressMap; + } + + // Fallback + if (sender && sender.length >= threshold) { + sender.slice(0, threshold).forEach((addr, i) => { + addressMap.set(new Address(addr), i); + }); + } + + return addressMap; + } } diff --git a/modules/sdk-coin-flrp/src/lib/transaction.ts b/modules/sdk-coin-flrp/src/lib/transaction.ts index 1596f97b07..10ba77e2b5 100644 --- a/modules/sdk-coin-flrp/src/lib/transaction.ts +++ b/modules/sdk-coin-flrp/src/lib/transaction.ts @@ -182,7 +182,16 @@ export class Transaction extends BaseTransaction { const signature = await secp256k1.sign(unsignedBytes, prv); let signatureSet = false; - if (hasMatchingAddress) { + // Check if any credential has embedded addresses - if so, we MUST use address-based matching + const hasEmbeddedAddressesInCredentials = unsignedTx.credentials.some((credential) => { + const signatures = credential.getSignatures(); + return signatures.some((sig) => isEmptySignature(sig) && hasEmbeddedAddress(sig)); + }); + + // Use address-based slot matching if: + // 1. addressMap contains matching address, OR + // 2. credentials have embedded addresses (prioritize this to ensure correct slot placement) + if (hasMatchingAddress || hasEmbeddedAddressesInCredentials) { // Use address-based slot matching (like AVAX-P) let checkSign: CheckSignature | undefined = undefined; @@ -209,8 +218,9 @@ export class Transaction extends BaseTransaction { } // Fallback: If address-based matching didn't work (e.g., ImportInC loaded from unsigned tx - // where P-chain addresses aren't in addressMaps), sign ALL empty slots across ALL credentials. - // This handles multisig where each UTXO needs a credential signed by the same key. + // where P-chain addresses aren't in addressMaps AND no embedded addresses), sign ALL empty + // slots across ALL credentials. This handles multisig where each UTXO needs a credential + // signed by the same key. if (!signatureSet) { for (const credential of unsignedTx.credentials) { const signatures = credential.getSignatures(); diff --git a/modules/sdk-coin-flrp/src/lib/utils.ts b/modules/sdk-coin-flrp/src/lib/utils.ts index b4db2dba82..3c507066dc 100644 --- a/modules/sdk-coin-flrp/src/lib/utils.ts +++ b/modules/sdk-coin-flrp/src/lib/utils.ts @@ -377,8 +377,14 @@ export class Utils implements BaseUtils { /** * Sort addresses lexicographically by their byte representation. * This matches how addresses are stored on-chain in Avalanche/Flare P-chain UTXOs. + * + * IMPORTANT: This sorting MUST be consistent with FlareJS's internal address sorting. + * FlareJS uses the same lexicographic comparison: `hexA.localeCompare(hexB)`. + * The sigIndices in transaction inputs depend on this sorted order, so any deviation + * would cause signature order mismatches and on-chain verification failures. + * * @param addresses - Array of bech32 address strings (e.g., "P-costwo1...") - * @returns Array of addresses sorted by hex value + * @returns Array of addresses sorted by hex value (ascending lexicographic order) */ public sortAddressesByHex(addresses: string[]): string[] { return [...addresses].sort((a, b) => { diff --git a/modules/sdk-coin-flrp/test/resources/transactionData/exportInP.ts b/modules/sdk-coin-flrp/test/resources/transactionData/exportInP.ts index 9e40fccdf9..2db5795cb9 100644 --- a/modules/sdk-coin-flrp/test/resources/transactionData/exportInP.ts +++ b/modules/sdk-coin-flrp/test/resources/transactionData/exportInP.ts @@ -4,9 +4,9 @@ export const EXPORT_IN_P = { txhash: '2R4iE6sX6BtAeTNrVdzifczs7qRGQw3yiaFTXaw9j9A4R6D5FW', // Unsigned tx from script (with empty signatures + credential structure) unsignedHex: - '0x0000000000120000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000000daba24000000000000000000000001000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f9100000002b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000000015d35f30000000100000000e65421a9e3307ed1f644e6e44855f94e01c35ea2bce2cdd9005a77e48decbd5c0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002e7b2b80000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000003473bc0000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f910000000200000009000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500100000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf36650010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000360f0650', + '0x0000000000120000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000000daba24000000000000000000000001000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f9100000002b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000000015d35f30000000100000000e65421a9e3307ed1f644e6e44855f94e01c35ea2bce2cdd9005a77e48decbd5c0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002e7b2b80000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000003473bc0000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f910000000200000009000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000090000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf36650012845ffc4', halfSigntxHex: - '0x0000000000120000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000000daba24000000000000000000000001000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f9100000002b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000000015d35f30000000100000000e65421a9e3307ed1f644e6e44855f94e01c35ea2bce2cdd9005a77e48decbd5c0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002e7b2b80000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000003473bc0000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f9100000002000000090000000184918d7e399e192bf14262c9f9622feb13f412e987f6c411b90215175e4ef4d61c629593188d703e53579a88cef6973414b7c2bef10c582ea883b1767be2622a00000000090000000284918d7e399e192bf14262c9f9622feb13f412e987f6c411b90215175e4ef4d61c629593188d703e53579a88cef6973414b7c2bef10c582ea883b1767be2622a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e39df12d', + '0x0000000000120000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000000daba24000000000000000000000001000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f9100000002b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000000015d35f30000000100000000e65421a9e3307ed1f644e6e44855f94e01c35ea2bce2cdd9005a77e48decbd5c0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002e7b2b80000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000003473bc0000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f9100000002000000090000000184918d7e399e192bf14262c9f9622feb13f412e987f6c411b90215175e4ef4d61c629593188d703e53579a88cef6973414b7c2bef10c582ea883b1767be2622a00000000090000000284918d7e399e192bf14262c9f9622feb13f412e987f6c411b90215175e4ef4d61c629593188d703e53579a88cef6973414b7c2bef10c582ea883b1767be2622a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001f945a1ff', fullSigntxHex: '0x0000000000120000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000000daba24000000000000000000000001000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f9100000002b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000000015d35f30000000100000000e65421a9e3307ed1f644e6e44855f94e01c35ea2bce2cdd9005a77e48decbd5c0000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002e7b2b80000000200000000000000010000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000003473bc0000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f9100000002000000090000000184918d7e399e192bf14262c9f9622feb13f412e987f6c411b90215175e4ef4d61c629593188d703e53579a88cef6973414b7c2bef10c582ea883b1767be2622a00000000090000000284918d7e399e192bf14262c9f9622feb13f412e987f6c411b90215175e4ef4d61c629593188d703e53579a88cef6973414b7c2bef10c582ea883b1767be2622a0067902f8f061a628182a3ec1d768b100a3d13fcc01966a993c3300bf3cb5d3dc32870442f83c67741d22f5e7a1168f5ef7f22f26d7b62a183528fcbfd0fdede9300f1e3d1ac', amount: '55000000', // 0.055 (0.05 FLR + 0.005 FLR fee) diff --git a/modules/sdk-coin-flrp/test/resources/transactionData/importInC.ts b/modules/sdk-coin-flrp/test/resources/transactionData/importInC.ts index 1521ebd402..f8e64f35c9 100644 --- a/modules/sdk-coin-flrp/test/resources/transactionData/importInC.ts +++ b/modules/sdk-coin-flrp/test/resources/transactionData/importInC.ts @@ -1,9 +1,9 @@ export const IMPORT_IN_C = { txhash: '2WoCobCwRcNGN1V57HJnRjwSus7Ejx4UavtcByJQiHZdDx2CgX', unsignedHex: - '0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000000000000000000000000000000000000000000000000000000000000000000058781ab65cabfe88b9ee1d13d61182e07a4264b3774e351de3f0ebeb8314e92700000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ace9fd62aadb8b8825eb285edd311be25e8de543959616ead19f360fabc370560000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b9f3b7bb347a74a52688f98ab8d9c9a095420d63179d7c8030b2deb2dbe8d5500000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ba561801af08fa19dc3cd6767a328ed6d203ba82f27b8013220e7a26f1e3d1ac0000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc00000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b500000000105f74b258734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000856084f', + '0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000000000000000000000000000000000000000000000000000000000000000000058781ab65cabfe88b9ee1d13d61182e07a4264b3774e351de3f0ebeb8314e92700000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ace9fd62aadb8b8825eb285edd311be25e8de543959616ead19f360fabc370560000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b9f3b7bb347a74a52688f98ab8d9c9a095420d63179d7c8030b2deb2dbe8d5500000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ba561801af08fa19dc3cd6767a328ed6d203ba82f27b8013220e7a26f1e3d1ac0000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc00000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b500000000105f74b258734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000090000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000090000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000090000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000090000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001000000090000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500198b8ee44', halfSigntxHex: - '0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000000000000000000000000000000000000000000000000000000000000000000058781ab65cabfe88b9ee1d13d61182e07a4264b3774e351de3f0ebeb8314e92700000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ace9fd62aadb8b8825eb285edd311be25e8de543959616ead19f360fabc370560000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b9f3b7bb347a74a52688f98ab8d9c9a095420d63179d7c8030b2deb2dbe8d5500000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ba561801af08fa19dc3cd6767a328ed6d203ba82f27b8013220e7a26f1e3d1ac0000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc00000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b500000000105f74b258734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a9d1c497', + '0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000000000000000000000000000000000000000000000000000000000000000000058781ab65cabfe88b9ee1d13d61182e07a4264b3774e351de3f0ebeb8314e92700000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ace9fd62aadb8b8825eb285edd311be25e8de543959616ead19f360fabc370560000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b9f3b7bb347a74a52688f98ab8d9c9a095420d63179d7c8030b2deb2dbe8d5500000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ba561801af08fa19dc3cd6767a328ed6d203ba82f27b8013220e7a26f1e3d1ac0000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc00000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b500000000105f74b258734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500100000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500100000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500100000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500100000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001ab8f2763', fullSigntxHex: '0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da55524790000000000000000000000000000000000000000000000000000000000000000000000058781ab65cabfe88b9ee1d13d61182e07a4264b3774e351de3f0ebeb8314e92700000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ace9fd62aadb8b8825eb285edd311be25e8de543959616ead19f360fabc370560000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b489f3c616329c3d56a29c24702c348522e87e1ad0e23f02fbb405be599fbae30000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001b9f3b7bb347a74a52688f98ab8d9c9a095420d63179d7c8030b2deb2dbe8d5500000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc0000000020000000000000001ba561801af08fa19dc3cd6767a328ed6d203ba82f27b8013220e7a26f1e3d1ac0000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000003473bc00000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b500000000105f74b258734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000500000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae016fbefab7d2cdd70fdb6a83733ebbb971c97d10798b06cbb4b568a8e913c628f54f0fc1ca30ab91e5e13ff0909fc9ebe94d56787ad43fd251f83b7fedb718b01d0100000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae016fbefab7d2cdd70fdb6a83733ebbb971c97d10798b06cbb4b568a8e913c628f54f0fc1ca30ab91e5e13ff0909fc9ebe94d56787ad43fd251f83b7fedb718b01d0100000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae016fbefab7d2cdd70fdb6a83733ebbb971c97d10798b06cbb4b568a8e913c628f54f0fc1ca30ab91e5e13ff0909fc9ebe94d56787ad43fd251f83b7fedb718b01d0100000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae016fbefab7d2cdd70fdb6a83733ebbb971c97d10798b06cbb4b568a8e913c628f54f0fc1ca30ab91e5e13ff0909fc9ebe94d56787ad43fd251f83b7fedb718b01d0100000009000000027ddd847561ba582f4e35b7ca01ccdf8961f13199b9d885c1ac79d9d852a3ec7163f82662b0544744ab3564bfd675bdd2355255dce6cf808ea5ca6e462cea99ae016fbefab7d2cdd70fdb6a83733ebbb971c97d10798b06cbb4b568a8e913c628f54f0fc1ca30ab91e5e13ff0909fc9ebe94d56787ad43fd251f83b7fedb718b01d0110e51104', amount: '50000000', diff --git a/modules/sdk-coin-flrp/test/resources/transactionData/importInP.ts b/modules/sdk-coin-flrp/test/resources/transactionData/importInP.ts index d2c94c2415..57200dbb6a 100644 --- a/modules/sdk-coin-flrp/test/resources/transactionData/importInP.ts +++ b/modules/sdk-coin-flrp/test/resources/transactionData/importInP.ts @@ -1,11 +1,11 @@ export const IMPORT_IN_P = { txhash: '8c2JV9dBWaUXqQBgmWg6PWnRXcLvFgM5xgBaKVqC9L15csCtg', unsignedHex: - '0x0000000000110000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000002e7b2b8000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91000000000000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000014bb60d547249da44959e410165c296bbe449f50d84b42f50950de1f3ed4214900000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002faf0800000000200000000000000010000000100000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf366500100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b6c30ed', + '0x0000000000110000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000002e7b2b8000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91000000000000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000014bb60d547249da44959e410165c296bbe449f50d84b42f50950de1f3ed4214900000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002faf08000000002000000000000000100000001000000090000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf3665001e2cd8f61', signedHex: '0x0000000000110000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000002e7b2b8000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91000000000000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000014bb60d547249da44959e410165c296bbe449f50d84b42f50950de1f3ed4214900000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002faf080000000020000000000000001000000010000000900000002b55278276e7d712d6896247ddc9298600a4b4e87088842edb444149e71665fef6318b69d35baeb684e197e12ebc2b9881af36d5d5b8af08cf2aaefdcf385384600c59a868ee3007a2a3d8ab44408a2f4f19a848e4bfdffe1e25d725651d53a77de433490f92fa0337042abcea24daa978f0d0725c6c2ada11e6b45a56433f82a0b0065c41625', halfSigntxHex: - '0x0000000000110000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000002e7b2b8000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91000000000000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000014bb60d547249da44959e410165c296bbe449f50d84b42f50950de1f3ed4214900000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002faf080000000020000000000000001000000010000000900000002b55278276e7d712d6896247ddc9298600a4b4e87088842edb444149e71665fef6318b69d35baeb684e197e12ebc2b9881af36d5d5b8af08cf2aaefdcf385384600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083c15074', + '0x0000000000110000007200000000000000000000000000000000000000000000000000000000000000000000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000002e7b2b8000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f91000000000000000078db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000014bb60d547249da44959e410165c296bbe449f50d84b42f50950de1f3ed4214900000000058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000050000000002faf080000000020000000000000001000000010000000900000002b55278276e7d712d6896247ddc9298600a4b4e87088842edb444149e71665fef6318b69d35baeb684e197e12ebc2b9881af36d5d5b8af08cf2aaefdcf3853846000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e918a5e8083ae4c9f2f0ed77055c24bf36650019c805cfd', cAddressPrivateKey: 'ef576892dd582d93914a3dba3b77cc4e32e470c32f4127817345473aae719d14', cAddressPublicKey: '033ca1801f51484063f3bce093413ca06f7d91c44c3883f642eb103eda5e0eaed3', diff --git a/modules/sdk-coin-flrp/test/unit/lib/signatureIndex.ts b/modules/sdk-coin-flrp/test/unit/lib/signatureIndex.ts index 23772e10af..417b757555 100644 --- a/modules/sdk-coin-flrp/test/unit/lib/signatureIndex.ts +++ b/modules/sdk-coin-flrp/test/unit/lib/signatureIndex.ts @@ -993,4 +993,385 @@ describe('Signature Index Handling - AVAX P Alignment', () => { }); }); }); + + /** + * Tests specifically for the sigIndices fix. + * + * Background: FLRP import to P transactions were failing with: + * - "could not parse transaction" (HSM error) + * - "wrong signature" (on-chain error) + * + * Root cause: Mismatch between SDK's assumed credential order and FlareJS's + * actual sigIndices order. When UTXO addresses are sorted differently than + * sender addresses, the credentials must follow FlareJS's sigIndices order + * (ascending by UTXO position), not the SDK's sender address order. + * + * The fix reads actual sigIndices from FlareJS-built transaction inputs + * and creates credentials in that exact order. + * + * Real-world failure examples: + * 1. Wallet 697738a870cd159b6ab80f7071e3146a - HSM error "could not parse transaction" + * - UTXO addresses: [r6gzm4acz..., x3u4xeal7..., l4f9rrc7w...] + * - Sender addresses: [l4f9rrc7w..., x3u4xeal7..., r6gzm4acz...] + * + * 2. Wallet 69773884a565c4dbaf680e360b920c9e - On-chain "wrong signature" + * - UTXO addresses: [t37m2e8fa..., urz6r3jy2..., 7r4k0nqne...] + * - Sender addresses: [urz6r3jy2..., 7r4k0nqne..., t37m2e8fa...] + */ + describe('SigIndices Fix Verification', () => { + describe('Credential ordering matches FlareJS sigIndices', () => { + it('should create credentials in correct order when UTXO addresses differ from sender order', async () => { + // Test data where UTXO addresses are in different order than pAddresses + // UTXO addresses: [xv5mulgpe..., 06gc5h5qs..., cueygd7fd...] + // pAddresses: [06gc5h5qs..., cueygd7fd..., xv5mulgpe...] + // + // FlareJS will compute sigIndices based on which addresses are signing + // and their positions in the UTXO's address list (sorted by address bytes) + + const txBuilder = newFactory() + .getImportInPBuilder() + .threshold(importPTestData.threshold) + .locktime(importPTestData.locktime) + .fromPubKey(importPTestData.corethAddresses) + .to(importPTestData.pAddresses) + .externalChainId(importPTestData.sourceChainId) + .feeState(importPTestData.feeState) + .context(importPTestData.context) + .decodedUtxos(importPTestData.utxos); + + const tx = await txBuilder.build(); + const hex = tx.toBroadcastFormat(); + + // The fix ensures the unsigned tx has credentials ordered correctly + // Before fix: credentials were in sender address order + // After fix: credentials are in sigIndices order (ascending UTXO position) + hex.should.equal(importPTestData.unsignedHex); + + // Verify we can sign and the signatures go in the correct slots + const builder1 = newFactory().from(hex); + builder1.sign({ key: importPTestData.privateKeys[2] }); // user key + const halfSignedTx = await builder1.build(); + + halfSignedTx.toBroadcastFormat().should.equal(importPTestData.halfSigntxHex); + + const builder2 = newFactory().from(halfSignedTx.toBroadcastFormat()); + builder2.sign({ key: importPTestData.privateKeys[0] }); // bitgo key + const fullSignedTx = await builder2.build(); + + fullSignedTx.toBroadcastFormat().should.equal(importPTestData.signedHex); + fullSignedTx.toJson().signatures.length.should.equal(2); + }); + + it('should produce valid signatures when signing keys are provided in any order', async () => { + // This tests that the fix properly handles the address mapping + // regardless of which key signs first + const txBuilder1 = newFactory() + .getImportInPBuilder() + .threshold(importPTestData.threshold) + .locktime(importPTestData.locktime) + .fromPubKey(importPTestData.corethAddresses) + .to(importPTestData.pAddresses) + .externalChainId(importPTestData.sourceChainId) + .feeState(importPTestData.feeState) + .context(importPTestData.context) + .decodedUtxos(importPTestData.utxos); + + // Sign user first, then bitgo + txBuilder1.sign({ key: importPTestData.privateKeys[2] }); + txBuilder1.sign({ key: importPTestData.privateKeys[0] }); + const tx1 = await txBuilder1.build(); + + const txBuilder2 = newFactory() + .getImportInPBuilder() + .threshold(importPTestData.threshold) + .locktime(importPTestData.locktime) + .fromPubKey(importPTestData.corethAddresses) + .to(importPTestData.pAddresses) + .externalChainId(importPTestData.sourceChainId) + .feeState(importPTestData.feeState) + .context(importPTestData.context) + .decodedUtxos(importPTestData.utxos); + + // Sign bitgo first, then user + txBuilder2.sign({ key: importPTestData.privateKeys[0] }); + txBuilder2.sign({ key: importPTestData.privateKeys[2] }); + const tx2 = await txBuilder2.build(); + + // Both should produce the same valid transaction + tx1.toBroadcastFormat().should.equal(tx2.toBroadcastFormat()); + tx1.toJson().signatures.length.should.equal(2); + tx2.toJson().signatures.length.should.equal(2); + }); + + it('should handle Export P transaction with correct sigIndices order', async () => { + const txBuilder = newFactory() + .getExportInPBuilder() + .threshold(exportPTestData.threshold) + .locktime(exportPTestData.locktime) + .fromPubKey(exportPTestData.pAddresses) + .externalChainId(exportPTestData.sourceChainId) + .feeState(exportPTestData.feeState) + .context(exportPTestData.context) + .decodedUtxos(exportPTestData.utxos) + .amount(exportPTestData.amount); + + const tx = await txBuilder.build(); + const hex = tx.toBroadcastFormat(); + + hex.should.equal(exportPTestData.unsignedHex); + + // Verify signing flow + const builder1 = newFactory().from(hex); + builder1.sign({ key: exportPTestData.privateKeys[2] }); + const halfSignedTx = await builder1.build(); + + halfSignedTx.toBroadcastFormat().should.equal(exportPTestData.halfSigntxHex); + }); + + it('should handle Import C transaction with correct sigIndices order', async () => { + const txBuilder = newFactory() + .getImportInCBuilder() + .threshold(importCTestData.threshold) + .fromPubKey(importCTestData.pAddresses) + .decodedUtxos(importCTestData.utxos) + .to(importCTestData.to) + .fee(importCTestData.fee) + .context(importCTestData.context); + + const tx = await txBuilder.build(); + const hex = tx.toBroadcastFormat(); + + hex.should.equal(importCTestData.unsignedHex); + + // Verify signing flow + const builder1 = newFactory().from(hex); + builder1.sign({ key: importCTestData.privateKeys[2] }); + const halfSignedTx = await builder1.build(); + + halfSignedTx.toBroadcastFormat().should.equal(importCTestData.halfSigntxHex); + }); + }); + + describe('Two-phase signing flow (mimics HSM flow)', () => { + it('should support build -> serialize -> parse -> sign -> serialize -> parse -> sign flow for Import P', async () => { + // Phase 1: Build unsigned transaction + const builder1 = newFactory() + .getImportInPBuilder() + .threshold(importPTestData.threshold) + .locktime(importPTestData.locktime) + .fromPubKey(importPTestData.corethAddresses) + .to(importPTestData.pAddresses) + .externalChainId(importPTestData.sourceChainId) + .feeState(importPTestData.feeState) + .context(importPTestData.context) + .decodedUtxos(importPTestData.utxos); + + const unsignedTx = await builder1.build(); + const unsignedHex = unsignedTx.toBroadcastFormat(); + + // Phase 2: Parse unsigned and add first signature (user/backup) + const builder2 = newFactory().from(unsignedHex); + builder2.sign({ key: importPTestData.privateKeys[2] }); + const halfSignedTx = await builder2.build(); + const halfSignedHex = halfSignedTx.toBroadcastFormat(); + + halfSignedTx.toJson().signatures.length.should.equal(1); + + // Phase 3: Parse half-signed and add second signature (bitgo) + const builder3 = newFactory().from(halfSignedHex); + builder3.sign({ key: importPTestData.privateKeys[0] }); + const fullSignedTx = await builder3.build(); + + fullSignedTx.toJson().signatures.length.should.equal(2); + fullSignedTx.toBroadcastFormat().should.equal(importPTestData.signedHex); + }); + }); + + describe('Failed transaction scenarios (real-world cases)', () => { + /** + * This test simulates the exact failure pattern from wallet 697738a870cd159b6ab80f7071e3146a + * which failed at HSM with "could not parse transaction". + * + * The failure occurred because: + * - UTXO addresses were in order: [addr0, addr1, addr2] + * - Sender addresses were in order: [addr2, addr1, addr0] (reversed) + * - FlareJS computed sigIndices based on UTXO positions + * - SDK was creating credentials based on sender positions (wrong) + * + * After the fix, credentials are created in the order specified by FlareJS sigIndices. + */ + it('should handle address ordering where UTXO order differs significantly from sender order', async () => { + // Simulate the failed case pattern: + // UTXO addresses: [pAddresses[2], pAddresses[1], pAddresses[0]] + // Sender addresses: [pAddresses[0], pAddresses[1], pAddresses[2]] + const reorderedUtxos = [ + { + ...importPTestData.utxos[0], + addresses: [ + importPTestData.pAddresses[2], // backup at UTXO position 0 + importPTestData.pAddresses[1], // bitgo at UTXO position 1 + importPTestData.pAddresses[0], // user at UTXO position 2 + ], + }, + ]; + + const txBuilder = newFactory() + .getImportInPBuilder() + .threshold(importPTestData.threshold) + .locktime(importPTestData.locktime) + .fromPubKey(importPTestData.corethAddresses) + .to(importPTestData.pAddresses) + .externalChainId(importPTestData.sourceChainId) + .feeState(importPTestData.feeState) + .context(importPTestData.context) + .decodedUtxos(reorderedUtxos); + + const tx = await txBuilder.build(); + const hex = tx.toBroadcastFormat(); + + // Verify transaction can be built without errors + hex.should.be.a.String(); + hex.length.should.be.greaterThan(0); + + // Verify we can parse and sign the transaction + const builder1 = newFactory().from(hex); + builder1.sign({ key: importPTestData.privateKeys[2] }); // user key + const halfSignedTx = await builder1.build(); + + halfSignedTx.toJson().signatures.length.should.equal(1); + + const builder2 = newFactory().from(halfSignedTx.toBroadcastFormat()); + builder2.sign({ key: importPTestData.privateKeys[0] }); // bitgo key + const fullSignedTx = await builder2.build(); + + fullSignedTx.toJson().signatures.length.should.equal(2); + + // Both signatures should be present and valid + const signatures = fullSignedTx.toJson().signatures; + signatures.forEach((sig: string) => { + sig.should.be.a.String(); + sig.length.should.be.greaterThan(0); + }); + }); + + /** + * This test simulates scenarios with multiple UTXOs where each has different + * address orderings, similar to the failed transaction with 4 UTXOs. + */ + it('should handle multiple UTXOs with varying address orderings', async () => { + // Create multiple UTXOs with different address orderings + // Use same txid but different outputidx to create valid unique UTXOs + const multipleUtxos = [ + { + ...importPTestData.utxos[0], + outputidx: '0', + addresses: [ + importPTestData.pAddresses[2], // backup first + importPTestData.pAddresses[0], // user second + importPTestData.pAddresses[1], // bitgo third + ], + }, + { + ...importPTestData.utxos[0], + outputidx: '1', + addresses: [ + importPTestData.pAddresses[1], // bitgo first + importPTestData.pAddresses[2], // backup second + importPTestData.pAddresses[0], // user third + ], + }, + { + ...importPTestData.utxos[0], + outputidx: '2', + addresses: [ + importPTestData.pAddresses[0], // user first + importPTestData.pAddresses[1], // bitgo second + importPTestData.pAddresses[2], // backup third + ], + }, + ]; + + const txBuilder = newFactory() + .getImportInPBuilder() + .threshold(importPTestData.threshold) + .locktime(importPTestData.locktime) + .fromPubKey(importPTestData.corethAddresses) + .to(importPTestData.pAddresses) + .externalChainId(importPTestData.sourceChainId) + .feeState(importPTestData.feeState) + .context(importPTestData.context) + .decodedUtxos(multipleUtxos); + + const tx = await txBuilder.build(); + const hex = tx.toBroadcastFormat(); + + // Verify transaction builds successfully + hex.should.be.a.String(); + tx.toJson().inputs.length.should.equal(3); + + // Verify full signing flow works + const builder1 = newFactory().from(hex); + builder1.sign({ key: importPTestData.privateKeys[2] }); + const halfSignedTx = await builder1.build(); + + const builder2 = newFactory().from(halfSignedTx.toBroadcastFormat()); + builder2.sign({ key: importPTestData.privateKeys[0] }); + const fullSignedTx = await builder2.build(); + + // Each input should have its own credential + fullSignedTx.toJson().signatures.length.should.equal(2); + }); + + /** + * This test verifies that signing order doesn't matter even with + * complex address orderings - the fix ensures signatures go to + * the correct slots based on FlareJS sigIndices. + */ + it('should produce identical results regardless of signing order with reordered addresses', async () => { + const reorderedUtxos = [ + { + ...importPTestData.utxos[0], + addresses: [importPTestData.pAddresses[2], importPTestData.pAddresses[1], importPTestData.pAddresses[0]], + }, + ]; + + // Sign user first, then bitgo + const builder1 = newFactory() + .getImportInPBuilder() + .threshold(importPTestData.threshold) + .locktime(importPTestData.locktime) + .fromPubKey(importPTestData.corethAddresses) + .to(importPTestData.pAddresses) + .externalChainId(importPTestData.sourceChainId) + .feeState(importPTestData.feeState) + .context(importPTestData.context) + .decodedUtxos(reorderedUtxos); + + builder1.sign({ key: importPTestData.privateKeys[2] }); + builder1.sign({ key: importPTestData.privateKeys[0] }); + const tx1 = await builder1.build(); + + // Sign bitgo first, then user + const builder2 = newFactory() + .getImportInPBuilder() + .threshold(importPTestData.threshold) + .locktime(importPTestData.locktime) + .fromPubKey(importPTestData.corethAddresses) + .to(importPTestData.pAddresses) + .externalChainId(importPTestData.sourceChainId) + .feeState(importPTestData.feeState) + .context(importPTestData.context) + .decodedUtxos(reorderedUtxos); + + builder2.sign({ key: importPTestData.privateKeys[0] }); + builder2.sign({ key: importPTestData.privateKeys[2] }); + const tx2 = await builder2.build(); + + // Both should produce identical fully signed transactions + tx1.toBroadcastFormat().should.equal(tx2.toBroadcastFormat()); + tx1.toJson().signatures.length.should.equal(2); + tx2.toJson().signatures.length.should.equal(2); + }); + }); + }); });