Skip to content
Closed
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
2 changes: 1 addition & 1 deletion modules/sdk-coin-flrp/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Utils from './utils';
export * from './iface';
export { KeyPair } from './keyPair';
export { Utils };
export { Utils, calcImportCGas, calcImportCFee, ATOMIC_TX_BASE_COST, COST_PER_SIGNATURE, COST_PER_BYTE } from './utils';
export { TransactionBuilderFactory } from './transactionBuilderFactory';
export { TransactionBuilder } from './transactionBuilder';
export { Transaction } from './transaction';
Expand Down
48 changes: 48 additions & 0 deletions modules/sdk-coin-flrp/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,5 +596,53 @@ export class Utils implements BaseUtils {
}
}

/**
* Coreth (C-chain EVM) atomic import gas cost constants.
* Source: coreth plugin/evm/atomic/params.go and avalanchego params.
*/
export const ATOMIC_TX_BASE_COST = 10_000n; // AtomicTxBaseCost fixed overhead
export const COST_PER_SIGNATURE = 1_000n; // gas per signature slot in the tx
export const COST_PER_BYTE = 1n; // gas per byte of serialized tx

/**
* Compute the gas units required for a C-chain atomic import transaction.
*
* Formula (from coreth import_tx.go):
* gas = txByteLen × CTxBytesGas + numSigs × CCostPerSignature + AtomicTxBaseCost
*
* @param txByteLen - serialized byte length of the transaction
* @param numSigs - number of signature slots in the transaction
* @returns gas units as bigint
*/
export function calcImportCGas(txByteLen: number, numSigs: number): bigint {
return BigInt(txByteLen) * COST_PER_BYTE + BigInt(numSigs) * COST_PER_SIGNATURE + ATOMIC_TX_BASE_COST;
}

/**
* Compute the fee in nFLR for a C-chain atomic import transaction.
*
* Applies a multiplier to the base gas estimate to pad against base-fee
* volatility between the time the transaction is built and when it is
* broadcast. A value of 2n (2×) is the recommended default; callers may
* pass a smaller multiplier (minimum 1n) if they prefer a tighter bound.
*
* @param txByteLen - serialized byte length of the transaction
* @param numSigs - number of signature slots
* @param baseFeeNanoFlr - current C-chain base fee in nFLR (e.g. 500_000_000_000n for 500 gwei)
* @param multiplier - fee multiplier for volatility padding (default 2n)
* @returns fee in nFLR as bigint
*/
export function calcImportCFee(
txByteLen: number,
numSigs: number,
baseFeeNanoFlr: bigint,
multiplier = 2n
): bigint {
if (multiplier < 1n) {
throw new Error('multiplier must be >= 1');
}
return calcImportCGas(txByteLen, numSigs) * baseFeeNanoFlr * multiplier;
}

const utils = new Utils();
export default utils;
62 changes: 62 additions & 0 deletions modules/sdk-coin-flrp/test/unit/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,3 +952,65 @@ describe('Utils', function () {
});
});
});

import { calcImportCGas, calcImportCFee, ATOMIC_TX_BASE_COST, COST_PER_SIGNATURE, COST_PER_BYTE } from '../../../src/lib/utils';

describe('calcImportCGas / calcImportCFee', function () {
describe('calcImportCGas', function () {
it('should compute gas for the ticket example (311 bytes, 1 sig)', function () {
// Expected: 311 * 1 + 1 * 1000 + 10000 = 11311
assert.strictEqual(calcImportCGas(311, 1), 11311n);
});

it('should include AtomicTxBaseCost even for zero bytes and zero sigs', function () {
assert.strictEqual(calcImportCGas(0, 0), ATOMIC_TX_BASE_COST);
});

it('should scale with byte count', function () {
// 100 bytes, 2 sigs: 100 + 2000 + 10000 = 12100
assert.strictEqual(calcImportCGas(100, 2), 12100n);
});

it('should use exported constants', function () {
assert.strictEqual(ATOMIC_TX_BASE_COST, 10_000n);
assert.strictEqual(COST_PER_SIGNATURE, 1_000n);
assert.strictEqual(COST_PER_BYTE, 1n);
});
});

describe('calcImportCFee', function () {
it('should reproduce the ticket failing-fee with 1× multiplier', function () {
// 5700 gas (old estimate) * 500 gwei = 2,850,000 nFLR (insufficient)
// Correct: 11311 gas * 500 gwei * 1x = 5,655,500 nFLR
const baseFee = 500_000_000_000n; // 500 gwei in nFLR (1 gwei = 1e9 nFLR? no — actually nFLR = wei for FLR)
// On Flare: 1 gwei = 1e9 nwei but nFLR is 1e-9 FLR. baseFee in nFLR = baseFee * 1 (gwei = nFLR units).
// The ticket says 500 gwei base fee => 500*1e9 nFLR/gas if we treat gwei as wei.
// But the ticket says 5700 gas * 500 gwei = 2,850,000 nFLR, so 1 gwei = 500 nFLR/5700 * 5700 →
// actually: 5700 * 500 = 2,850,000 nFLR means baseFee is 500 nFLR/gas = 500 gwei where 1 gwei = 1 nFLR.
// Wait: nFLR = nano-FLR. 1 FLR = 1e9 nFLR. 1 gwei = 1e9 wei but for FLR we use nFLR as the smallest unit.
// So 500 gwei (= 500 * 1e9 nwei) but FLR's base denom is nFLR. The ticket says 5700 * 500 = 2.85M nFLR.
// So the baseFee passed to the builder is 500 (nFLR per gas unit).
const baseFeePerGas = 500n; // 500 nFLR/gas
const fee = calcImportCFee(311, 1, baseFeePerGas, 1n);
assert.strictEqual(fee, 11311n * 500n);
});

it('should double the fee with default 2× multiplier', function () {
const baseFeePerGas = 500n;
const feeX1 = calcImportCFee(311, 1, baseFeePerGas, 1n);
const feeX2 = calcImportCFee(311, 1, baseFeePerGas);
assert.strictEqual(feeX2, feeX1 * 2n);
});

it('should throw for multiplier < 1', function () {
assert.throws(() => calcImportCFee(311, 1, 500n, 0n), /multiplier must be >= 1/);
});

it('the 2× fee should comfortably cover the ticket scenario (500 gwei base, 311B, 1 sig)', function () {
const baseFeePerGas = 500n;
const paddedFee = calcImportCFee(311, 1, baseFeePerGas, 2n);
const requiredFee = 11311n * 500n; // minimum required by the node
assert.ok(paddedFee >= requiredFee, `padded fee ${paddedFee} should be >= required ${requiredFee}`);
});
});
});
4 changes: 4 additions & 0 deletions modules/statics/src/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export interface FlareNetwork extends BaseNetwork {
addSubnetValidatorFee?: string;
addSubnetDelegatorFee?: string;
xChainBlockchainID?: string;
/** Fee multiplier applied to C-chain atomic import gas estimates to buffer base-fee volatility (default 2). */
importFeeMultiplier?: number;
}

import { CoinFamily } from './base';
Expand Down Expand Up @@ -2367,6 +2369,7 @@ export class FlareP extends Mainnet implements FlareNetwork {
txFee = '200000'; // FLR P-chain import requires higher fee than base txFee
baseTxFee = '1000000';
maxImportFee = '10000000'; // defaults
importFeeMultiplier = 2; // 2× buffer for C-chain base-fee volatility
createAssetTxFee = '1000000';
createSubnetTx = '100000000'; // defaults
transformSubnetTxFee = '100000000';
Expand Down Expand Up @@ -2403,6 +2406,7 @@ export class FlarePTestnet extends Testnet implements FlareNetwork {
txFee = '200000'; // FLR P-chain import requires higher fee than base txFee
baseTxFee = '1000000';
maxImportFee = '10000000'; // defaults
importFeeMultiplier = 2; // 2× buffer for C-chain base-fee volatility
createAssetTxFee = '1000000';
createSubnetTx = '100000000'; // defaults
transformSubnetTxFee = '100000000';
Expand Down
Loading