diff --git a/modules/sdk-coin-flrp/src/lib/transaction.ts b/modules/sdk-coin-flrp/src/lib/transaction.ts index c57920a681..5f8eaa5945 100644 --- a/modules/sdk-coin-flrp/src/lib/transaction.ts +++ b/modules/sdk-coin-flrp/src/lib/transaction.ts @@ -254,9 +254,96 @@ export class Transaction extends BaseTransaction { signatures: this.signature, outputs: this.outputs, changeOutputs: this.changeOutputs, + sourceChain: this.sourceChain, + destinationChain: this.destinationChain, }; } + /** + * Get the source chain id or undefined if it's not a cross chain transfer. + */ + get sourceChain(): string | undefined { + const tx = (this._flareTransaction as UnsignedTx).getTx(); + + switch (this.type) { + case TransactionType.Import: + if (this.isTransactionForCChain) { + // C-chain Import: source is the chain we're importing FROM (P-chain) + const importTx = tx as evmSerial.ImportTx; + return this.blockchainIDtoAlias(Buffer.from(importTx.sourceChain.toBytes())); + } else { + // P-chain Import: source is the chain we're importing FROM (C-chain) + const pvmImportTx = tx as pvmSerial.ImportTx; + return this.blockchainIDtoAlias(Buffer.from(pvmImportTx.sourceChain.toBytes())); + } + + case TransactionType.Export: + if (this.isTransactionForCChain) { + // C-chain Export: source is C-chain (the blockchain ID) + const exportTx = tx as evmSerial.ExportTx; + return this.blockchainIDtoAlias(Buffer.from(exportTx.blockchainId.toBytes())); + } else { + // P-chain Export: source is P-chain (the blockchain ID from baseTx) + const pvmExportTx = tx as pvmSerial.ExportTx; + return this.blockchainIDtoAlias(Buffer.from(pvmExportTx.baseTx.BlockchainId.toBytes())); + } + + default: + return undefined; + } + } + + /** + * Get the destination chain id or undefined if it's not a cross chain transfer. + */ + get destinationChain(): string | undefined { + const tx = (this._flareTransaction as UnsignedTx).getTx(); + + switch (this.type) { + case TransactionType.Import: + if (this.isTransactionForCChain) { + // C-chain Import: destination is C-chain (the blockchain ID) + const importTx = tx as evmSerial.ImportTx; + return this.blockchainIDtoAlias(Buffer.from(importTx.blockchainId.toBytes())); + } else { + // P-chain Import: destination is P-chain (the blockchain ID from baseTx) + const pvmImportTx = tx as pvmSerial.ImportTx; + return this.blockchainIDtoAlias(Buffer.from(pvmImportTx.baseTx.BlockchainId.toBytes())); + } + + case TransactionType.Export: + if (this.isTransactionForCChain) { + // C-chain Export: destination is P-chain (the destination chain) + const exportTx = tx as evmSerial.ExportTx; + return this.blockchainIDtoAlias(Buffer.from(exportTx.destinationChain.toBytes())); + } else { + // P-chain Export: destination is C-chain (the destination chain) + const pvmExportTx = tx as pvmSerial.ExportTx; + return this.blockchainIDtoAlias(Buffer.from(pvmExportTx.destination.toBytes())); + } + + default: + return undefined; + } + } + + /** + * Convert a blockchainId buffer to string and return P or C alias if it matches any of those chains. + * @param {Buffer} blockchainIDBuffer + * @return {string} blockchainID or alias if exists. + * @private + */ + private blockchainIDtoAlias(blockchainIDBuffer: Buffer): string { + const blockchainId = utils.cb58Encode(blockchainIDBuffer); + if (blockchainId === this._network.cChainBlockchainID) { + return 'C'; + } + if (blockchainId === this._network.blockchainID) { + return 'P'; + } + return blockchainId; + } + setTransaction(tx: Tx): void { this._flareTransaction = tx as UnsignedTx; } diff --git a/modules/sdk-coin-flrp/test/unit/lib/transactionBuilderFactory.ts b/modules/sdk-coin-flrp/test/unit/lib/transactionBuilderFactory.ts new file mode 100644 index 0000000000..a4a8f56c97 --- /dev/null +++ b/modules/sdk-coin-flrp/test/unit/lib/transactionBuilderFactory.ts @@ -0,0 +1,67 @@ +import 'should'; +import { coins } from '@bitgo/statics'; +import { TransactionBuilderFactory, TxData } from '../../../src/lib'; +import { EXPORT_IN_P } from '../../resources/transactionData/exportInP'; +import { IMPORT_IN_P } from '../../resources/transactionData/importInP'; +import { IMPORT_IN_C } from '../../resources/transactionData/importInC'; +import { EXPORT_IN_C } from '../../resources/transactionData/exportInC'; + +describe('Flrp Transaction Builder Factory', () => { + const factory = new TransactionBuilderFactory(coins.get('tflrp')); + + describe('Cross chain transfer has source and destination chains', () => { + // P-chain Export to C-chain: source is P, destination is C + const p2cExportTxs = [EXPORT_IN_P.unsignedHex, EXPORT_IN_P.halfSigntxHex, EXPORT_IN_P.fullSigntxHex]; + + // C-chain Import from P-chain: source is P, destination is C + const p2cImportTxs = [IMPORT_IN_C.unsignedHex, IMPORT_IN_C.halfSigntxHex, IMPORT_IN_C.fullSigntxHex]; + + // P-chain Import from C-chain: source is C, destination is P + const c2pImportTxs = [IMPORT_IN_P.unsignedHex, IMPORT_IN_P.halfSigntxHex, IMPORT_IN_P.fullSigntxHex]; + + // C-chain Export to P-chain: source is C, destination is P + const c2pExportTxs = [EXPORT_IN_C.unsignedHex, EXPORT_IN_C.signedHex]; + + async function toJson(txHex: string): Promise { + const txBuilder = factory.from(txHex); + const tx = await txBuilder.build(); + return tx.toJson(); + } + + describe('P to C chain transfers', () => { + it('Should have sourceChain P and destinationChain C for Export from P-chain', async () => { + for (const rawTx of p2cExportTxs) { + const txJson = await toJson(rawTx); + txJson.sourceChain!.should.equal('P'); + txJson.destinationChain!.should.equal('C'); + } + }); + + it('Should have sourceChain P and destinationChain C for Import to C-chain', async () => { + for (const rawTx of p2cImportTxs) { + const txJson = await toJson(rawTx); + txJson.sourceChain!.should.equal('P'); + txJson.destinationChain!.should.equal('C'); + } + }); + }); + + describe('C to P chain transfers', () => { + it('Should have sourceChain C and destinationChain P for Import to P-chain', async () => { + for (const rawTx of c2pImportTxs) { + const txJson = await toJson(rawTx); + txJson.sourceChain!.should.equal('C'); + txJson.destinationChain!.should.equal('P'); + } + }); + + it('Should have sourceChain C and destinationChain P for Export from C-chain', async () => { + for (const rawTx of c2pExportTxs) { + const txJson = await toJson(rawTx); + txJson.sourceChain!.should.equal('C'); + txJson.destinationChain!.should.equal('P'); + } + }); + }); + }); +});