@@ -12,6 +12,7 @@ import {
1212 Address ,
1313 TransferOutput ,
1414 OutputOwners ,
15+ Signature ,
1516 utils as FlareUtils ,
1617} from '@flarenetwork/flarejs' ;
1718import utils from './utils' ;
@@ -75,7 +76,7 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
7576 return TransactionType . Export ;
7677 }
7778
78- initBuilder ( tx : Tx ) : this {
79+ initBuilder ( tx : Tx , rawBytes ?: Buffer ) : this {
7980 const baseTx = tx as evmSerial . ExportTx ;
8081 if ( ! this . verifyTxType ( baseTx . _type ) ) {
8182 throw new NotSupported ( 'Transaction cannot be parsed or has an unsupported transaction type' ) ;
@@ -106,17 +107,149 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
106107 const outputAmount = transferOutput . amount ( ) ;
107108 const fee = inputAmount - outputAmount ;
108109 this . _amount = outputAmount ;
109- this . transaction . _fee . feeRate = Number ( fee ) - Number ( this . fixedFee ) ;
110+ // Store the actual fee directly (don't subtract fixedFee since buildFlareTransaction doesn't add it back)
111+ this . transaction . _fee . feeRate = Number ( fee ) ;
110112 this . transaction . _fee . fee = fee . toString ( ) ;
111113 this . transaction . _fee . size = 1 ;
112114 this . transaction . _fromAddresses = [ Buffer . from ( input . address . toBytes ( ) ) ] ;
113115 this . transaction . _locktime = transferOutput . getLocktime ( ) ;
114116
115117 this . _nonce = input . nonce . value ( ) ;
116- this . transaction . setTransaction ( tx ) ;
118+
119+ // Check if this is a signed transaction by looking for credentials in raw bytes
120+ const isSignedTransaction = rawBytes ? this . hasCredentialsInRawBytes ( rawBytes , baseTx ) : false ;
121+
122+ // If it's a signed transaction, store the original raw bytes to preserve exact format
123+ if ( isSignedTransaction && rawBytes ) {
124+ this . transaction . _rawSignedBytes = rawBytes ;
125+ }
126+
127+ // Extract credentials from raw bytes if present (for signed transactions)
128+ const credentials = rawBytes ? this . extractCredentialsFromRawBytes ( rawBytes , baseTx ) : [ ] ;
129+
130+ // Create proper UnsignedTx wrapper with credentials
131+ const fromAddress = new Address ( this . transaction . _fromAddresses [ 0 ] ) ;
132+ const addressMap = new FlareUtils . AddressMap ( [
133+ [ fromAddress , 0 ] ,
134+ [ fromAddress , 1 ] ,
135+ ] ) ;
136+ const addressMaps = new FlareUtils . AddressMaps ( [ addressMap ] ) ;
137+
138+ const unsignedTx = new UnsignedTx (
139+ baseTx ,
140+ [ ] ,
141+ addressMaps ,
142+ credentials . length > 0 ? credentials : [ new Credential ( [ utils . createNewSig ( '' ) ] ) ]
143+ ) ;
144+
145+ this . transaction . setTransaction ( unsignedTx ) ;
117146 return this ;
118147 }
119148
149+ /**
150+ * Check if raw bytes contain credentials (i.e., it's a signed transaction)
151+ */
152+ private hasCredentialsInRawBytes ( rawBytes : Buffer , tx : evmSerial . ExportTx ) : boolean {
153+ try {
154+ const codec = FlareUtils . getManagerForVM ( 'EVM' ) . getDefaultCodec ( ) ;
155+ const txBytes = tx . toBytes ( codec ) ;
156+ const txSize = txBytes . length ;
157+
158+ // If raw bytes are longer than tx bytes, there are credentials
159+ if ( rawBytes . length > txSize ) {
160+ const credentialBytes = rawBytes . slice ( txSize ) ;
161+ // Check if there's at least a num_credentials field with value > 0
162+ if ( credentialBytes . length >= 4 ) {
163+ const numCredentials = credentialBytes . readUInt32BE ( 0 ) ;
164+ return numCredentials > 0 ;
165+ }
166+ }
167+ return false ;
168+ } catch ( e ) {
169+ return false ;
170+ }
171+ }
172+
173+ /**
174+ * Extract credentials from raw transaction bytes.
175+ * Signed transactions have credentials appended after the transaction body.
176+ *
177+ * @param rawBytes - The full raw transaction bytes
178+ * @param tx - The parsed transaction (to calculate its size)
179+ * @returns Array of credentials, or empty array if none found
180+ */
181+ private extractCredentialsFromRawBytes ( rawBytes : Buffer , tx : evmSerial . ExportTx ) : Credential [ ] {
182+ try {
183+ // Get the size of the transaction without credentials using the default codec
184+ const codec = FlareUtils . getManagerForVM ( 'EVM' ) . getDefaultCodec ( ) ;
185+ const txBytes = tx . toBytes ( codec ) ;
186+ const txSize = txBytes . length ;
187+
188+ // If raw bytes are longer than tx bytes, there are credentials
189+ if ( rawBytes . length <= txSize ) {
190+ return [ ] ;
191+ }
192+
193+ // Extract credential bytes (everything after the transaction)
194+ const credentialBytes = rawBytes . slice ( txSize ) ;
195+
196+ // Parse credentials
197+ // Format: [num_credentials: 4 bytes] [credentials...]
198+ if ( credentialBytes . length < 4 ) {
199+ return [ ] ;
200+ }
201+
202+ const numCredentials = credentialBytes . readUInt32BE ( 0 ) ;
203+ if ( numCredentials === 0 ) {
204+ return [ ] ;
205+ }
206+
207+ const credentials : Credential [ ] = [ ] ;
208+ let offset = 4 ;
209+
210+ for ( let i = 0 ; i < numCredentials ; i ++ ) {
211+ if ( offset + 8 > credentialBytes . length ) {
212+ break ;
213+ }
214+
215+ // Read type ID (4 bytes) - Type ID 9 = secp256k1 credential
216+ const typeId = credentialBytes . readUInt32BE ( offset ) ;
217+ offset += 4 ;
218+
219+ // Validate credential type (9 = secp256k1)
220+ if ( typeId !== 9 ) {
221+ continue ; // Skip unsupported credential types
222+ }
223+
224+ // Read number of signatures (4 bytes)
225+ const numSigs = credentialBytes . readUInt32BE ( offset ) ;
226+ offset += 4 ;
227+
228+ // Parse all signatures for this credential
229+ const signatures : Signature [ ] = [ ] ;
230+ for ( let j = 0 ; j < numSigs ; j ++ ) {
231+ if ( offset + 65 > credentialBytes . length ) {
232+ break ;
233+ }
234+ // Each signature is 65 bytes (64 bytes signature + 1 byte recovery)
235+ const sigBytes = Buffer . from ( credentialBytes . slice ( offset , offset + 65 ) ) ;
236+ signatures . push ( new Signature ( sigBytes ) ) ;
237+ offset += 65 ;
238+ }
239+
240+ // Create credential with the parsed signatures
241+ if ( signatures . length > 0 ) {
242+ credentials . push ( new Credential ( signatures ) ) ;
243+ }
244+ }
245+
246+ return credentials ;
247+ } catch ( e ) {
248+ // If parsing fails, return empty credentials
249+ return [ ] ;
250+ }
251+ }
252+
120253 static verifyTxType ( txnType : string ) : boolean {
121254 return txnType === FlareTransactionType . EvmExportTx ;
122255 }
@@ -147,8 +280,9 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
147280 throw new Error ( 'nonce is required' ) ;
148281 }
149282
150- const txFee = BigInt ( this . fixedFee ) ;
151- const fee = BigInt ( this . transaction . _fee . feeRate ) + txFee ;
283+ // For EVM exports, feeRate represents the total fee (baseFee * gasUnits)
284+ // Don't add fixedFee as it's already accounted for in the EVM gas model
285+ const fee = BigInt ( this . transaction . _fee . feeRate ) ;
152286 this . transaction . _fee . fee = fee . toString ( ) ;
153287 this . transaction . _fee . size = 1 ;
154288
@@ -158,6 +292,15 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
158292 const amount = new BigIntPr ( this . _amount + fee ) ;
159293 const nonce = new BigIntPr ( this . _nonce ) ;
160294 const input = new evmSerial . Input ( fromAddress , amount , assetId , nonce ) ;
295+ // Map all destination P-chain addresses for multisig support
296+ // Sort addresses alphabetically by hex representation (required by Avalanche/Flare protocol)
297+ const sortedToAddresses = [ ...this . transaction . _to ] . sort ( ( a , b ) => {
298+ const aHex = Buffer . from ( a ) . toString ( 'hex' ) ;
299+ const bHex = Buffer . from ( b ) . toString ( 'hex' ) ;
300+ return aHex . localeCompare ( bHex ) ;
301+ } ) ;
302+ const toAddresses = sortedToAddresses . map ( ( addr ) => new Address ( addr ) ) ;
303+
161304 const exportTx = new evmSerial . ExportTx (
162305 new Int ( this . transaction . _networkID ) ,
163306 utils . flareIdString ( this . transaction . _blockchainID ) ,
@@ -168,9 +311,11 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
168311 assetId ,
169312 new TransferOutput (
170313 new BigIntPr ( this . _amount ) ,
171- new OutputOwners ( new BigIntPr ( this . transaction . _locktime ) , new Int ( this . transaction . _threshold ) , [
172- new Address ( this . transaction . _to [ 0 ] ) ,
173- ] )
314+ new OutputOwners (
315+ new BigIntPr ( this . transaction . _locktime ) ,
316+ new Int ( this . transaction . _threshold ) ,
317+ toAddresses
318+ )
174319 )
175320 ) ,
176321 ]
0 commit comments