@@ -11,7 +11,6 @@ import {
1111 TransferableInput ,
1212 Address ,
1313 utils as FlareUtils ,
14- avmSerial ,
1514} from '@flarenetwork/flarejs' ;
1615import utils from './utils' ;
1716import { DecodedUtxoObj , FlareTransactionType , SECP256K1_Transfer_Output , Tx } from './iface' ;
@@ -63,16 +62,6 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
6362
6463 // Calculate fee based on input/output difference
6564 const fee = totalInputAmount - totalOutputAmount ;
66- // Calculate cost units using the same method as buildFlareTransaction
67- const feeSize = this . calculateImportCost ( baseTx ) ;
68- // Use integer division to ensure feeRate can be converted back to BigInt
69- const feeRate = Math . floor ( Number ( fee ) / feeSize ) ;
70-
71- this . transaction . _fee = {
72- fee : fee . toString ( ) ,
73- feeRate : feeRate ,
74- size : feeSize ,
75- } ;
7665
7766 // Use credentials passed from TransactionBuilderFactory (properly extracted using codec)
7867 const credentials = parsedCredentials || [ ] ;
@@ -88,6 +77,30 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
8877 const inputThreshold = firstInput . sigIndicies ( ) . length || this . transaction . _threshold ;
8978 this . transaction . _threshold = inputThreshold ;
9079
80+ // Create a temporary UnsignedTx for accurate fee size calculation
81+ // This includes the full structure (ImportTx, AddressMaps, Credentials)
82+ const tempAddressMap = new FlareUtils . AddressMap ( ) ;
83+ for ( let i = 0 ; i < inputThreshold ; i ++ ) {
84+ if ( this . transaction . _fromAddresses && this . transaction . _fromAddresses [ i ] ) {
85+ tempAddressMap . set ( new Address ( this . transaction . _fromAddresses [ i ] ) , i ) ;
86+ }
87+ }
88+ const tempAddressMaps = new FlareUtils . AddressMaps ( [ tempAddressMap ] ) ;
89+ const tempCredentials =
90+ credentials . length > 0 ? credentials : [ new Credential ( Array ( inputThreshold ) . fill ( utils . createNewSig ( '' ) ) ) ] ;
91+ const tempUnsignedTx = new UnsignedTx ( baseTx , [ ] , tempAddressMaps , tempCredentials ) ;
92+
93+ // Calculate cost units using the full UnsignedTx structure
94+ const feeSize = this . calculateImportCost ( tempUnsignedTx ) ;
95+ // Use integer division to ensure feeRate can be converted back to BigInt
96+ const feeRate = Math . floor ( Number ( fee ) / feeSize ) ;
97+
98+ this . transaction . _fee = {
99+ fee : fee . toString ( ) ,
100+ feeRate : feeRate ,
101+ size : feeSize ,
102+ } ;
103+
91104 // Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
92105 // This matches the approach used in credentials: addressesIndex determines signature order
93106 // AddressMaps should map addresses to signature slots in the same order as credentials
@@ -166,7 +179,8 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
166179 const { inputs, amount, credentials } = this . createInputs ( ) ;
167180
168181 // Calculate import cost units (matching AVAXP's costImportTx approach)
169- // Create a temporary transaction with full amount to calculate fee size
182+ // Create a temporary UnsignedTx with full amount to calculate fee size
183+ // This includes the full structure (ImportTx, AddressMaps, Credentials) for accurate size calculation
170184 const tempOutput = new evmSerial . Output (
171185 new Address ( this . transaction . _to [ 0 ] ) ,
172186 new BigIntPr ( amount ) ,
@@ -180,8 +194,18 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
180194 [ tempOutput ]
181195 ) ;
182196
183- // Calculate feeSize once using full amount (matching AVAXP approach)
184- const feeSize = this . calculateImportCost ( tempImportTx ) ;
197+ // Create AddressMaps for fee calculation (same as final transaction)
198+ const firstUtxo = this . transaction . _utxos [ 0 ] ;
199+ const tempAddressMap = firstUtxo
200+ ? this . createAddressMapForUtxo ( firstUtxo , this . transaction . _threshold )
201+ : new FlareUtils . AddressMap ( ) ;
202+ const tempAddressMaps = new FlareUtils . AddressMaps ( [ tempAddressMap ] ) ;
203+
204+ // Create temporary UnsignedTx with full structure for accurate fee calculation
205+ const tempUnsignedTx = new UnsignedTx ( tempImportTx , [ ] , tempAddressMaps , credentials ) ;
206+
207+ // Calculate feeSize once using full UnsignedTx (matching AVAXP approach)
208+ const feeSize = this . calculateImportCost ( tempUnsignedTx ) ;
185209 const feeRate = BigInt ( this . transaction . _fee . feeRate ) ;
186210 const fee = feeRate * BigInt ( feeSize ) ;
187211
@@ -211,21 +235,11 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
211235 [ output ]
212236 ) ;
213237
214- // Create AddressMaps based on signature slot order (matching credential order), not sorted addresses
215- // This matches the approach used in credentials: addressesIndex determines signature order
216- // AddressMaps should map addresses to signature slots in the same order as credentials
217- // For C-chain imports, we typically have one input, so use the first UTXO
218- // Use centralized method for AddressMap creation
219- const firstUtxo = this . transaction . _utxos [ 0 ] ;
220- const addressMap = firstUtxo
221- ? this . createAddressMapForUtxo ( firstUtxo , this . transaction . _threshold )
222- : new FlareUtils . AddressMap ( ) ;
223- const addressMaps = new FlareUtils . AddressMaps ( [ addressMap ] ) ;
224-
238+ // Reuse the AddressMaps already calculated for fee calculation
225239 const unsignedTx = new UnsignedTx (
226240 importTx ,
227241 [ ] , // Empty UTXOs array, will be filled during processing
228- addressMaps ,
242+ tempAddressMaps ,
229243 credentials
230244 ) ;
231245
@@ -298,39 +312,21 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder {
298312 }
299313
300314 /**
301- * Calculate the import cost for C-chain import transactions
302- * Matches AVAXP's costImportTx formula:
303- * - Base byte cost: transactionSize * txBytesGas (1 gas per byte)
304- * - Per-input cost: numInputs * costPerSignature (1000 per signature) * threshold
305- * - Fixed fee: 10000
306- *
307- * This returns cost "units" to be multiplied by feeRate, matching AVAXP's approach:
308- * AVAXP: fee = feeRate.muln(costImportTx(tx))
309- * FLRP: fee = feeRate * calculateImportCost(tx)
310- *
311- * @param tx The ImportTx to calculate the cost for
315+ * @param unsignedTx The UnsignedTx to calculate the cost for (includes ImportTx, AddressMaps, and Credentials)
312316 * @returns The total cost units
313317 */
314- private calculateImportCost ( tx : evmSerial . ImportTx ) : number {
315- const codec = avmSerial . getAVMManager ( ) . getDefaultCodec ( ) ;
316- const txBytes = tx . toBytes ( codec ) ;
317-
318- // Base byte cost: 1 gas per byte (matching AVAX txBytesGas)
318+ private calculateImportCost ( unsignedTx : UnsignedTx ) : number {
319+ const signedTxBytes = unsignedTx . getSignedTx ( ) . toBytes ( ) ;
319320 const txBytesGas = 1 ;
320- let bytesCost = txBytes . length * txBytesGas ;
321-
322- // Per-input cost: costPerSignature (1000) per signature
321+ let bytesCost = signedTxBytes . length * txBytesGas ;
323322 const costPerSignature = 1000 ;
324- const numInputs = tx . importedInputs . length ;
325- const numSignatures = this . transaction . _threshold ; // Each input requires threshold signatures
326- const inputCost = numInputs * costPerSignature * numSignatures ;
327- bytesCost += inputCost ;
328-
329- // Fixed fee component
323+ const importTx = unsignedTx . getTx ( ) as evmSerial . ImportTx ;
324+ importTx . importedInputs . forEach ( ( input : TransferableInput ) => {
325+ const inCost = costPerSignature * input . sigIndicies ( ) . length ;
326+ bytesCost += inCost ;
327+ } ) ;
330328 const fixedFee = 10000 ;
331- const totalCost = bytesCost + fixedFee ;
332-
333- return totalCost ;
329+ return bytesCost + fixedFee ;
334330 }
335331
336332 /**
0 commit comments