Skip to content

Commit ae7cd92

Browse files
committed
feat(sdk-coin-flrp): enhance transaction handling for C-chain and P-chain imports/exports
TICKET: WIN-8145
1 parent 108b8e4 commit ae7cd92

File tree

3 files changed

+680
-10
lines changed

3 files changed

+680
-10
lines changed

modules/sdk-coin-flrp/src/lib/transaction.ts

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import {
1212
utils as FlareUtils,
1313
Credential,
1414
pvmSerial,
15+
evmSerial,
1516
UnsignedTx,
1617
secp256k1,
1718
EVMUnsignedTx,
1819
Address,
1920
} from '@flarenetwork/flarejs';
2021
import { Buffer } from 'buffer';
2122
import { createHash } from 'crypto';
22-
import { DecodedUtxoObj, TransactionExplanation, Tx, TxData } from './iface';
23+
import { DecodedUtxoObj, TransactionExplanation, Tx, TxData, ADDRESS_SEPARATOR, FlareTransactionType } from './iface';
2324
import { KeyPair } from './keyPair';
2425
import utils from './utils';
2526

@@ -288,28 +289,132 @@ export class Transaction extends BaseTransaction {
288289
return { fee: '0', ...this._fee };
289290
}
290291

292+
/**
293+
* Check if this transaction is for C-chain (EVM transactions)
294+
*/
295+
get isTransactionForCChain(): boolean {
296+
const tx = (this._flareTransaction as UnsignedTx).getTx();
297+
const txType = (tx as { _type?: string })._type;
298+
return txType === FlareTransactionType.EvmExportTx || txType === FlareTransactionType.EvmImportTx;
299+
}
300+
291301
get outputs(): Entry[] {
302+
const tx = (this._flareTransaction as UnsignedTx).getTx();
303+
292304
switch (this.type) {
305+
case TransactionType.Import:
306+
if (this.isTransactionForCChain) {
307+
// C-chain Import: output is to a C-chain address
308+
const importTx = tx as evmSerial.ImportTx;
309+
return importTx.Outs.map((out) => ({
310+
address: '0x' + Buffer.from(out.address.toBytes()).toString('hex'),
311+
value: out.amount.value().toString(),
312+
}));
313+
} else {
314+
// P-chain Import: outputs are the baseTx.outputs (destination on P-chain)
315+
const pvmImportTx = tx as pvmSerial.ImportTx;
316+
return pvmImportTx.baseTx.outputs.map(utils.mapOutputToEntry(this._network));
317+
}
318+
319+
case TransactionType.Export:
320+
if (this.isTransactionForCChain) {
321+
// C-chain Export: exported outputs go to P-chain
322+
const exportTx = tx as evmSerial.ExportTx;
323+
return exportTx.exportedOutputs.map(utils.mapOutputToEntry(this._network));
324+
} else {
325+
// P-chain Export: exported outputs go to C-chain
326+
const pvmExportTx = tx as pvmSerial.ExportTx;
327+
return pvmExportTx.outs.map(utils.mapOutputToEntry(this._network));
328+
}
329+
293330
case TransactionType.AddPermissionlessValidator:
294331
return [
295332
{
296-
address: (
297-
(this._flareTransaction as UnsignedTx).getTx() as pvmSerial.AddPermissionlessValidatorTx
298-
).subnetValidator.validator.nodeId.toString(),
299-
value: (
300-
(this._flareTransaction as UnsignedTx).getTx() as pvmSerial.AddPermissionlessValidatorTx
301-
).subnetValidator.validator.weight.toJSON(),
333+
address: (tx as pvmSerial.AddPermissionlessValidatorTx).subnetValidator.validator.nodeId.toString(),
334+
value: (tx as pvmSerial.AddPermissionlessValidatorTx).subnetValidator.validator.weight.toJSON(),
302335
},
303336
];
337+
304338
default:
305339
return [];
306340
}
307341
}
308342

309343
get changeOutputs(): Entry[] {
310-
return (
311-
(this._flareTransaction as UnsignedTx).getTx() as pvmSerial.AddPermissionlessValidatorTx
312-
).baseTx.outputs.map(utils.mapOutputToEntry(this._network));
344+
const tx = (this._flareTransaction as UnsignedTx).getTx();
345+
346+
// C-chain transactions and Import transactions don't have change outputs
347+
if (this.isTransactionForCChain || this.type === TransactionType.Import) {
348+
return [];
349+
}
350+
351+
switch (this.type) {
352+
case TransactionType.Export:
353+
// P-chain Export: change outputs are in baseTx.outputs
354+
return (tx as pvmSerial.ExportTx).baseTx.outputs.map(utils.mapOutputToEntry(this._network));
355+
356+
case TransactionType.AddPermissionlessValidator:
357+
return (tx as pvmSerial.AddPermissionlessValidatorTx).baseTx.outputs.map(utils.mapOutputToEntry(this._network));
358+
359+
default:
360+
return [];
361+
}
362+
}
363+
364+
get inputs(): Entry[] {
365+
const tx = (this._flareTransaction as UnsignedTx).getTx();
366+
367+
switch (this.type) {
368+
case TransactionType.Import:
369+
if (this.isTransactionForCChain) {
370+
// C-chain Import: inputs come from P-chain (importedInputs)
371+
const importTx = tx as evmSerial.ImportTx;
372+
return importTx.importedInputs.map((input) => ({
373+
id: utils.cb58Encode(Buffer.from(input.utxoID.txID.toBytes())) + ':' + input.utxoID.outputIdx.value(),
374+
address: this.fromAddresses.sort().join(ADDRESS_SEPARATOR),
375+
value: input.amount().toString(),
376+
}));
377+
} else {
378+
// P-chain Import: inputs are ins (imported from C-chain)
379+
const pvmImportTx = tx as pvmSerial.ImportTx;
380+
return pvmImportTx.ins.map((input) => ({
381+
id: utils.cb58Encode(Buffer.from(input.utxoID.txID.toBytes())) + ':' + input.utxoID.outputIdx.value(),
382+
address: this.fromAddresses.sort().join(ADDRESS_SEPARATOR),
383+
value: input.amount().toString(),
384+
}));
385+
}
386+
387+
case TransactionType.Export:
388+
if (this.isTransactionForCChain) {
389+
// C-chain Export: inputs are from C-chain (EVM inputs)
390+
const exportTx = tx as evmSerial.ExportTx;
391+
return exportTx.ins.map((evmInput) => ({
392+
address: '0x' + Buffer.from(evmInput.address.toBytes()).toString('hex'),
393+
value: evmInput.amount.value().toString(),
394+
nonce: Number(evmInput.nonce.value()),
395+
}));
396+
} else {
397+
// P-chain Export: inputs are from baseTx.inputs
398+
const pvmExportTx = tx as pvmSerial.ExportTx;
399+
return pvmExportTx.baseTx.inputs.map((input) => ({
400+
id: utils.cb58Encode(Buffer.from(input.utxoID.txID.toBytes())) + ':' + input.utxoID.outputIdx.value(),
401+
address: this.fromAddresses.sort().join(ADDRESS_SEPARATOR),
402+
value: input.amount().toString(),
403+
}));
404+
}
405+
406+
case TransactionType.AddPermissionlessValidator:
407+
default:
408+
const baseTx = tx as pvmSerial.AddPermissionlessValidatorTx;
409+
if (baseTx.baseTx?.inputs) {
410+
return baseTx.baseTx.inputs.map((input) => ({
411+
id: utils.cb58Encode(Buffer.from(input.utxoID.txID.toBytes())) + ':' + input.utxoID.outputIdx.value(),
412+
address: this.fromAddresses.sort().join(ADDRESS_SEPARATOR),
413+
value: input.amount().toString(),
414+
}));
415+
}
416+
return [];
417+
}
313418
}
314419

315420
explainTransaction(): TransactionExplanation {

modules/sdk-coin-flrp/test/resources/account.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export const ACCOUNT_2 = {
3434
'xprv9s21ZrQH143K2KjD8ytSfLWDfe2585pBJNdadLgwsEKoGLbGdHCKSK5yDnfcmToazd3oPLDXprtXnCvsn9T6MDJz1qwMPaq22oTrzqvyeDQ',
3535
xPublicKey:
3636
'xprv9s21ZrQH143K2KjD8ytSfLWDfe2585pBJNdadLgwsEKoGLbGdHCKSK5yDnfcmToazd3oPLDXprtXnCvsn9T6MDJz1qwMPaq22oTrzqvyeDQ',
37+
addressMainnet: 'P-flare1xvngrrmqzldj50kpvqx7mhkx3htvh5x87nkl9j',
38+
addressTestnet: 'P-costwo1xvngrrmqzldj50kpvqx7mhkx3htvh5x8mhgsqy',
3739
};
3840

3941
export const ACCOUNT_3 = {

0 commit comments

Comments
 (0)