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
104 changes: 50 additions & 54 deletions modules/sdk-coin-flrp/src/lib/ImportInCTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
TransferableInput,
Address,
utils as FlareUtils,
avmSerial,
} from '@flarenetwork/flarejs';
import utils from './utils';
import { DecodedUtxoObj, FlareTransactionType, SECP256K1_Transfer_Output, Tx } from './iface';
Expand Down Expand Up @@ -63,16 +62,6 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {

// Calculate fee based on input/output difference
const fee = totalInputAmount - totalOutputAmount;
// Calculate cost units using the same method as buildFlareTransaction
const feeSize = this.calculateImportCost(baseTx);
// Use integer division to ensure feeRate can be converted back to BigInt
const feeRate = Math.floor(Number(fee) / feeSize);

this.transaction._fee = {
fee: fee.toString(),
feeRate: feeRate,
size: feeSize,
};

// Use credentials passed from TransactionBuilderFactory (properly extracted using codec)
const credentials = parsedCredentials || [];
Expand All @@ -88,6 +77,30 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
const inputThreshold = firstInput.sigIndicies().length || this.transaction._threshold;
this.transaction._threshold = inputThreshold;

// Create a temporary UnsignedTx for accurate fee size calculation
// This includes the full structure (ImportTx, AddressMaps, Credentials)
const tempAddressMap = new FlareUtils.AddressMap();
for (let i = 0; i < inputThreshold; i++) {
if (this.transaction._fromAddresses && this.transaction._fromAddresses[i]) {
tempAddressMap.set(new Address(this.transaction._fromAddresses[i]), i);
}
}
const tempAddressMaps = new FlareUtils.AddressMaps([tempAddressMap]);
const tempCredentials =
credentials.length > 0 ? credentials : [new Credential(Array(inputThreshold).fill(utils.createNewSig('')))];
const tempUnsignedTx = new UnsignedTx(baseTx, [], tempAddressMaps, tempCredentials);

// Calculate cost units using the full UnsignedTx structure
const feeSize = this.calculateImportCost(tempUnsignedTx);
// Use integer division to ensure feeRate can be converted back to BigInt
const feeRate = Math.floor(Number(fee) / feeSize);

this.transaction._fee = {
fee: fee.toString(),
feeRate: feeRate,
size: feeSize,
};

// 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
Expand Down Expand Up @@ -166,7 +179,8 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
const { inputs, amount, credentials } = this.createInputs();

// Calculate import cost units (matching AVAXP's costImportTx approach)
// Create a temporary transaction with full amount to calculate fee size
// Create a temporary UnsignedTx with full amount to calculate fee size
// This includes the full structure (ImportTx, AddressMaps, Credentials) for accurate size calculation
const tempOutput = new evmSerial.Output(
new Address(this.transaction._to[0]),
new BigIntPr(amount),
Expand All @@ -180,8 +194,18 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
[tempOutput]
);

// Calculate feeSize once using full amount (matching AVAXP approach)
const feeSize = this.calculateImportCost(tempImportTx);
// Create AddressMaps for fee calculation (same as final transaction)
const firstUtxo = this.transaction._utxos[0];
const tempAddressMap = firstUtxo
? this.createAddressMapForUtxo(firstUtxo, this.transaction._threshold)
: new FlareUtils.AddressMap();
const tempAddressMaps = new FlareUtils.AddressMaps([tempAddressMap]);

// Create temporary UnsignedTx with full structure for accurate fee calculation
const tempUnsignedTx = new UnsignedTx(tempImportTx, [], tempAddressMaps, credentials);

// Calculate feeSize once using full UnsignedTx (matching AVAXP approach)
const feeSize = this.calculateImportCost(tempUnsignedTx);
const feeRate = BigInt(this.transaction._fee.feeRate);
const fee = feeRate * BigInt(feeSize);

Expand Down Expand Up @@ -211,21 +235,11 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
[output]
);

// 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]);

// Reuse the AddressMaps already calculated for fee calculation
const unsignedTx = new UnsignedTx(
importTx,
[], // Empty UTXOs array, will be filled during processing
addressMaps,
tempAddressMaps,
credentials
);

Expand Down Expand Up @@ -298,39 +312,21 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
}

/**
* Calculate the import cost for C-chain import transactions
* Matches AVAXP's costImportTx formula:
* - Base byte cost: transactionSize * txBytesGas (1 gas per byte)
* - Per-input cost: numInputs * costPerSignature (1000 per signature) * threshold
* - Fixed fee: 10000
*
* This returns cost "units" to be multiplied by feeRate, matching AVAXP's approach:
* AVAXP: fee = feeRate.muln(costImportTx(tx))
* FLRP: fee = feeRate * calculateImportCost(tx)
*
* @param tx The ImportTx to calculate the cost for
* @param unsignedTx The UnsignedTx to calculate the cost for (includes ImportTx, AddressMaps, and Credentials)
* @returns The total cost units
*/
private calculateImportCost(tx: evmSerial.ImportTx): number {
const codec = avmSerial.getAVMManager().getDefaultCodec();
const txBytes = tx.toBytes(codec);

// Base byte cost: 1 gas per byte (matching AVAX txBytesGas)
private calculateImportCost(unsignedTx: UnsignedTx): number {
const signedTxBytes = unsignedTx.getSignedTx().toBytes();
const txBytesGas = 1;
let bytesCost = txBytes.length * txBytesGas;

// Per-input cost: costPerSignature (1000) per signature
let bytesCost = signedTxBytes.length * txBytesGas;
const costPerSignature = 1000;
const numInputs = tx.importedInputs.length;
const numSignatures = this.transaction._threshold; // Each input requires threshold signatures
const inputCost = numInputs * costPerSignature * numSignatures;
bytesCost += inputCost;

// Fixed fee component
const importTx = unsignedTx.getTx() as evmSerial.ImportTx;
importTx.importedInputs.forEach((input: TransferableInput) => {
const inCost = costPerSignature * input.sigIndicies().length;
bytesCost += inCost;
});
const fixedFee = 10000;
const totalCost = bytesCost + fixedFee;

return totalCost;
return bytesCost + fixedFee;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export const IMPORT_IN_C = {
txhash: '2Q5RkxF2eRK3KCzDaijoScyunahbEvt6ai6YZipmShQTPryfky',
txhash: '5wgxtB8tSyS2MNroAQgs8fA9sDUWCwrGJG88b77xUm1po685Q',
unsignedHex:
'0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000000000000000000000000000000000000000000000000000000000000000000001fcea1c0e2cb7e3d77c993eb74ee05d98c24325ded1918e8a0595c96a789e2f790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000001dcd65000000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b5000000001d8114dc58734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000100000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003329be7d01cd3ebaae6654d7327dd9f17a2e15810000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000576b6e21',
'0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000000000000000000000000000000000000000000000000000000000000000000001fcea1c0e2cb7e3d77c993eb74ee05d98c24325ded1918e8a0595c96a789e2f790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000001dcd65000000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b5000000001d6587f058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd0000000100000009000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003329be7d01cd3ebaae6654d7327dd9f17a2e158100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ea9607a',
halfSignedSignature:
'0xd365ef7ce45aebc4e81bc03f600867f515cebb25c4a0e8e1f06d9fe0a00d41fd2efac6c6df392e5f92e271c57486e39425537da7cafbb085cd1bd21aff06955d00',
halfSigntxHex:
'0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000000000000000000000000000000000000000000000000000000000000000000001fcea1c0e2cb7e3d77c993eb74ee05d98c24325ded1918e8a0595c96a789e2f790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000001dcd65000000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b5000000001d8114dc58734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000010000000900000002decd468a395c16b7bc799d387196848aec99602b00fe8cdc2d9ed55aaf373db13aa33444c9e43a8707a75ece2dc7081c628422b6b137f7c11f428b99c48b1db901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000077fae449',
'0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000000000000000000000000000000000000000000000000000000000000000000001fcea1c0e2cb7e3d77c993eb74ee05d98c24325ded1918e8a0595c96a789e2f790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000001dcd65000000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b5000000001d6587f058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000010000000900000002e0c6d38e404ebeb08417a49c6c227e8c6ec5fd0c6a49d74eafa280cb8c6e19fe4d230834a0a3af5c3a49f3aefa9fc5f3d1be234aa8625f878634c9c079e58086010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000196ceb32',
fullSigntxHex:
'0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000000000000000000000000000000000000000000000000000000000000000000001fcea1c0e2cb7e3d77c993eb74ee05d98c24325ded1918e8a0595c96a789e2f790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000001dcd65000000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b5000000001d8114dc58734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000010000000900000002decd468a395c16b7bc799d387196848aec99602b00fe8cdc2d9ed55aaf373db13aa33444c9e43a8707a75ece2dc7081c628422b6b137f7c11f428b99c48b1db901d833ae918ca0bc59a4495e98837ffca0870666aaea0fbb8fd9b510e21e24f81071c2a622cd8979138e65ae413a0b1b573e2615dba04778a44f2b6c72566dd13401d6bc2f0a',
'0x0000000000000000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da5552479000000000000000000000000000000000000000000000000000000000000000000000001fcea1c0e2cb7e3d77c993eb74ee05d98c24325ded1918e8a0595c96a789e2f790000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000005000000001dcd65000000000200000000000000010000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b5000000001d6587f058734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000010000000900000002e0c6d38e404ebeb08417a49c6c227e8c6ec5fd0c6a49d74eafa280cb8c6e19fe4d230834a0a3af5c3a49f3aefa9fc5f3d1be234aa8625f878634c9c079e5808601ecb60f57234c5e852add6fbbf90f2c8613b4b1a520aefe6f7e78b5e21155d1f131107cf9177764c8dda5936e1dd8d5846ea9b7c016e3811565ab8b48776a15a100bb68cc63',
fullSignedSignature:
'0x70d2ca9711622142610ddd347e482cbe5dc45aeafe66876bb82bfd57581300045b8457d804cc1b8f2efc10401367e5919b1912ee26d2d48c06cf82dc3f146acd00',

Expand Down Expand Up @@ -39,7 +39,7 @@ export const IMPORT_IN_C = {
to: '0x17Dbd11B9dD1c9bE337353db7C14f9fb3662E5B5',
sourceChainId: 'vE8M98mEQH6wk56sStD1ML8HApTgSqfJZLk9gQ3Fsd4i6m3Bi',
threshold: 2,
fee: '409', // feeRate multiplier: 5,000,000 (desired fee) ÷ ~12,228 (cost units) ≈ 409
fee: '550',
locktime: 0,
INVALID_CHAIN_ID: 'wrong chain id',
VALID_C_CHAIN_ID: 'yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp',
Expand Down
Loading