@@ -2,68 +2,127 @@ import { BaseCoin as CoinConfig } from '@bitgo/statics';
22import { BuildTransactionError , NotSupported , TransactionType } from '@bitgo/sdk-core' ;
33import { AtomicTransactionBuilder } from './atomicTransactionBuilder' ;
44import {
5- evmSerial ,
5+ pvmSerial ,
6+ avaxSerial ,
67 UnsignedTx ,
78 Int ,
89 Id ,
910 TransferableInput ,
11+ TransferableOutput ,
12+ TransferOutput ,
13+ TransferInput ,
14+ OutputOwners ,
1015 utils as FlareUtils ,
1116 Address ,
1217 BigIntPr ,
18+ Credential ,
19+ Bytes ,
1320} from '@flarenetwork/flarejs' ;
1421import utils from './utils' ;
1522import { DecodedUtxoObj , FlareTransactionType , SECP256K1_Transfer_Output , Tx } from './iface' ;
1623
1724export class ImportInPTxBuilder extends AtomicTransactionBuilder {
1825 constructor ( _coinConfig : Readonly < CoinConfig > ) {
1926 super ( _coinConfig ) ;
20- // external chain id is P
21- this . _externalChainId = utils . cb58Decode ( this . transaction . _network . blockchainID ) ;
22- // chain id is C
23- this . transaction . _blockchainID = Buffer . from (
24- utils . cb58Decode ( this . transaction . _network . cChainBlockchainID )
25- ) . toString ( 'hex' ) ;
27+ // For Import INTO P-chain:
28+ // - external chain (source) is C-chain
29+ // - blockchain ID (destination) is P-chain
30+ this . _externalChainId = utils . cb58Decode ( this . transaction . _network . cChainBlockchainID ) ;
31+ // P-chain blockchain ID (from network config - typically all zeros for primary network)
32+ this . transaction . _blockchainID = Buffer . from ( utils . cb58Decode ( this . transaction . _network . blockchainID ) ) . toString (
33+ 'hex'
34+ ) ;
2635 }
2736
2837 protected get transactionType ( ) : TransactionType {
2938 return TransactionType . Import ;
3039 }
3140
32- initBuilder ( tx : Tx ) : this {
33- const baseTx = tx as evmSerial . ImportTx ;
34- if ( ! this . verifyTxType ( baseTx . _type ) ) {
41+ initBuilder ( tx : Tx , rawBytes ?: Buffer ) : this {
42+ const importTx = tx as pvmSerial . ImportTx ;
43+
44+ if ( ! this . verifyTxType ( importTx . _type ) ) {
3545 throw new NotSupported ( 'Transaction cannot be parsed or has an unsupported transaction type' ) ;
3646 }
3747
3848 // The regular change output is the tx output in Import tx.
39- // createInputOutput results in a single item array.
4049 // It's expected to have only one output with the addresses of the sender.
41- const outputs = baseTx . Outs ;
50+ const outputs = importTx . baseTx . outputs ;
4251 if ( outputs . length !== 1 ) {
4352 throw new BuildTransactionError ( 'Transaction can have one external output' ) ;
4453 }
4554
4655 const output = outputs [ 0 ] ;
4756 const assetId = output . assetId . toBytes ( ) ;
48- if ( Buffer . compare ( assetId , Buffer . from ( this . transaction . _assetId ) ) !== 0 ) {
57+ if ( Buffer . compare ( assetId , Buffer . from ( this . transaction . _assetId , 'hex' ) ) !== 0 ) {
4958 throw new Error ( 'The Asset ID of the output does not match the transaction' ) ;
5059 }
5160
52- // Set locktime to 0 since it's not used in EVM outputs
53- this . transaction . _locktime = BigInt ( 0 ) ;
61+ const transferOutput = output . output as TransferOutput ;
62+ const outputOwners = transferOutput . outputOwners ;
63+
64+ // Set locktime from output
65+ this . transaction . _locktime = outputOwners . locktime . value ( ) ;
5466
55- // Set threshold to 1 since EVM outputs only have one address
56- this . transaction . _threshold = 1 ;
67+ // Set threshold from output
68+ this . transaction . _threshold = outputOwners . threshold . value ( ) ;
5769
58- // Convert output address to buffer and set as fromAddress
59- this . transaction . _fromAddresses = [ Buffer . from ( output . address . toBytes ( ) ) ] ;
70+ // Convert output addresses to buffers and set as fromAddresses
71+ this . transaction . _fromAddresses = outputOwners . addrs . map ( ( addr ) => Buffer . from ( addr . toBytes ( ) ) ) ;
6072
6173 // Set external chain ID from the source chain
62- this . _externalChainId = Buffer . from ( baseTx . sourceChain . toString ( ) ) ;
74+ this . _externalChainId = Buffer . from ( importTx . sourceChain . toBytes ( ) ) ;
6375
6476 // Recover UTXOs from imported inputs
65- this . transaction . _utxos = this . recoverUtxos ( baseTx . importedInputs ) ;
77+ this . transaction . _utxos = this . recoverUtxos ( importTx . ins ) ;
78+
79+ // Calculate and set fee from input/output difference
80+ const totalInputAmount = importTx . ins . reduce ( ( sum , input ) => sum + input . amount ( ) , BigInt ( 0 ) ) ;
81+ const outputAmount = transferOutput . amount ( ) ;
82+ const fee = totalInputAmount - outputAmount ;
83+ this . transaction . _fee . fee = fee . toString ( ) ;
84+
85+ // Check if raw bytes contain credentials
86+ // For PVM transactions, credentials start after the unsigned tx bytes
87+ let hasCredentials = false ;
88+ let credentials : Credential [ ] = [ ] ;
89+
90+ if ( rawBytes ) {
91+ // Try standard extraction first
92+ const result = utils . extractCredentialsFromRawBytes ( rawBytes , importTx , 'PVM' ) ;
93+ hasCredentials = result . hasCredentials ;
94+ credentials = result . credentials ;
95+
96+ // If extraction failed but raw bytes are longer, try parsing credentials at known offset
97+ // For ImportTx, the unsigned tx is typically 302 bytes
98+ if ( ( ! hasCredentials || credentials . length === 0 ) && rawBytes . length > 350 ) {
99+ hasCredentials = true ;
100+ // Try to extract credentials at the standard position (302 bytes)
101+ const credResult = utils . parseCredentialsAtOffset ( rawBytes , 302 ) ;
102+ if ( credResult . length > 0 ) {
103+ credentials = credResult ;
104+ }
105+ }
106+ }
107+
108+ // If there are credentials in raw bytes, store the original bytes to preserve exact format
109+ if ( rawBytes && hasCredentials ) {
110+ this . transaction . _rawSignedBytes = rawBytes ;
111+ }
66112
113+ // Create proper UnsignedTx wrapper with credentials
114+ const sortedAddresses = [ ...this . transaction . _fromAddresses ] . sort ( ( a , b ) => Buffer . compare ( a , b ) ) ;
115+ const addressMaps = sortedAddresses . map ( ( a , i ) => new FlareUtils . AddressMap ( [ [ new Address ( a ) , i ] ] ) ) ;
116+
117+ // Create credentials if none exist
118+ const txCredentials =
119+ credentials . length > 0
120+ ? credentials
121+ : [ new Credential ( sortedAddresses . slice ( 0 , this . transaction . _threshold ) . map ( ( ) => utils . createNewSig ( '' ) ) ) ] ;
122+
123+ const unsignedTx = new UnsignedTx ( importTx , [ ] , new FlareUtils . AddressMaps ( addressMaps ) , txCredentials ) ;
124+
125+ this . transaction . setTransaction ( unsignedTx ) ;
67126 return this ;
68127 }
69128
@@ -76,46 +135,123 @@ export class ImportInPTxBuilder extends AtomicTransactionBuilder {
76135 }
77136
78137 /**
79- * Build the import transaction
138+ * Build the import transaction for P-chain
80139 * @protected
81140 */
82141 protected buildFlareTransaction ( ) : void {
83142 // if tx has credentials, tx shouldn't change
84143 if ( this . transaction . hasCredentials ) return ;
85144
86- const { inputs, credentials } = this . createInputOutput ( BigInt ( this . transaction . fee . fee ) ) ;
145+ const { inputs, credentials, totalAmount } = this . createImportInputs ( ) ;
87146
88- // Convert TransferableInput to evmSerial.Output
89- const evmOutputs = inputs . map ( ( input ) => {
90- return new evmSerial . Output (
91- new Address ( this . transaction . _fromAddresses [ 0 ] ) ,
92- new BigIntPr ( input . input . amount ( ) ) ,
93- new Id ( input . assetId . toBytes ( ) )
94- ) ;
95- } ) ;
147+ // Calculate fee from transaction fee settings
148+ const fee = BigInt ( this . transaction . fee . fee ) ;
149+ const outputAmount = totalAmount - fee ;
150+
151+ // Create the output for P-chain (TransferableOutput with TransferOutput)
152+ const assetIdBytes = new Uint8Array ( Buffer . from ( this . transaction . _assetId , 'hex' ) ) ;
153+
154+ // Create OutputOwners with the P-chain addresses (sorted by byte value as per AVAX protocol)
155+ const sortedAddresses = [ ...this . transaction . _fromAddresses ] . sort ( ( a , b ) => Buffer . compare ( a , b ) ) ;
156+ const outputOwners = new OutputOwners (
157+ new BigIntPr ( this . transaction . _locktime ) ,
158+ new Int ( this . transaction . _threshold ) ,
159+ sortedAddresses . map ( ( addr ) => new Address ( addr ) )
160+ ) ;
161+
162+ const transferOutput = new TransferOutput ( new BigIntPr ( outputAmount ) , outputOwners ) ;
163+ const output = new TransferableOutput ( new Id ( assetIdBytes ) , transferOutput ) ;
96164
97- // Create the import transaction
98- const importTx = new evmSerial . ImportTx (
165+ // Create the BaseTx for the P-chain import transaction
166+ const baseTx = new avaxSerial . BaseTx (
99167 new Int ( this . transaction . _networkID ) ,
100- Id . fromString ( this . transaction . _blockchainID . toString ( ) ) ,
101- Id . fromString ( this . _externalChainId . toString ( ) ) ,
102- inputs ,
103- evmOutputs
168+ new Id ( Buffer . from ( this . transaction . _blockchainID , 'hex' ) ) ,
169+ [ output ] , // outputs
170+ [ ] , // inputs (empty for import - inputs come from importedInputs)
171+ new Bytes ( new Uint8Array ( 0 ) ) // empty memo
172+ ) ;
173+
174+ // Create the P-chain import transaction using pvmSerial.ImportTx
175+ const importTx = new pvmSerial . ImportTx (
176+ baseTx ,
177+ new Id ( this . _externalChainId ) , // sourceChain (C-chain)
178+ inputs // importedInputs (ins)
104179 ) ;
105180
106- const addressMaps = this . transaction . _fromAddresses . map ( ( a ) => new FlareUtils . AddressMap ( [ [ new Address ( a ) , 0 ] ] ) ) ;
181+ // Create address maps for signing
182+ const addressMaps = this . transaction . _fromAddresses . map ( ( a , i ) => new FlareUtils . AddressMap ( [ [ new Address ( a ) , i ] ] ) ) ;
107183
108184 // Create unsigned transaction
109185 const unsignedTx = new UnsignedTx (
110186 importTx ,
111- [ ] , // Empty UTXOs array, will be filled during processing
187+ [ ] , // Empty UTXOs array
112188 new FlareUtils . AddressMaps ( addressMaps ) ,
113189 credentials
114190 ) ;
115191
116192 this . transaction . setTransaction ( unsignedTx ) ;
117193 }
118194
195+ /**
196+ * Create inputs from UTXOs for P-chain import
197+ * @returns inputs, credentials, and total amount
198+ */
199+ protected createImportInputs ( ) : {
200+ inputs : TransferableInput [ ] ;
201+ credentials : Credential [ ] ;
202+ totalAmount : bigint ;
203+ } {
204+ const sender = this . transaction . _fromAddresses . slice ( ) ;
205+ if ( this . recoverSigner ) {
206+ // switch first and last signer
207+ const tmp = sender . pop ( ) ;
208+ sender . push ( sender [ 0 ] ) ;
209+ if ( tmp ) {
210+ sender [ 0 ] = tmp ;
211+ }
212+ }
213+
214+ let totalAmount = BigInt ( 0 ) ;
215+ const inputs : TransferableInput [ ] = [ ] ;
216+ const credentials : Credential [ ] = [ ] ;
217+
218+ this . transaction . _utxos . forEach ( ( utxo : DecodedUtxoObj ) => {
219+ const amount = BigInt ( utxo . amount ) ;
220+ totalAmount += amount ;
221+
222+ // Create signature indices for threshold
223+ const sigIndices : number [ ] = [ ] ;
224+ for ( let i = 0 ; i < this . transaction . _threshold ; i ++ ) {
225+ sigIndices . push ( i ) ;
226+ }
227+
228+ // Use fromNative to create TransferableInput
229+ // fromNative expects cb58-encoded strings for txId and assetId
230+ const txIdCb58 = utxo . txid ; // Already cb58 encoded
231+ const assetIdCb58 = utils . cb58Encode ( Buffer . from ( this . transaction . _assetId , 'hex' ) ) ;
232+
233+ const transferableInput = TransferableInput . fromNative (
234+ txIdCb58 ,
235+ Number ( utxo . outputidx ) ,
236+ assetIdCb58 ,
237+ amount ,
238+ sigIndices
239+ ) ;
240+
241+ inputs . push ( transferableInput ) ;
242+
243+ // Create credential with empty signatures for threshold signers
244+ const emptySignatures = sigIndices . map ( ( ) => utils . createNewSig ( '' ) ) ;
245+ credentials . push ( new Credential ( emptySignatures ) ) ;
246+ } ) ;
247+
248+ return {
249+ inputs,
250+ credentials,
251+ totalAmount,
252+ } ;
253+ }
254+
119255 /**
120256 * Recover UTXOs from imported inputs
121257 * @param importedInputs Array of transferable inputs
@@ -124,12 +260,12 @@ export class ImportInPTxBuilder extends AtomicTransactionBuilder {
124260 private recoverUtxos ( importedInputs : TransferableInput [ ] ) : DecodedUtxoObj [ ] {
125261 return importedInputs . map ( ( input ) => {
126262 const utxoId = input . utxoID ;
127- const transferInput = input . input ;
263+ const transferInput = input . input as TransferInput ;
128264 const utxo : DecodedUtxoObj = {
129265 outputID : SECP256K1_Transfer_Output ,
130- amount : transferInput . amount . toString ( ) ,
131- txid : utils . cb58Encode ( Buffer . from ( utxoId . ID . toString ( ) ) ) ,
132- outputidx : utxoId . outputIdx . toBytes ( ) . toString ( ) ,
266+ amount : transferInput . amount ( ) . toString ( ) ,
267+ txid : utils . cb58Encode ( Buffer . from ( utxoId . txID . toBytes ( ) ) ) ,
268+ outputidx : utxoId . outputIdx . value ( ) . toString ( ) ,
133269 threshold : this . transaction . _threshold ,
134270 addresses : this . transaction . _fromAddresses . map ( ( addr ) =>
135271 utils . addressToString ( this . transaction . _network . hrp , this . transaction . _network . alias , Buffer . from ( addr ) )
0 commit comments