diff --git a/modules/sdk-coin-flrp/src/flrp.ts b/modules/sdk-coin-flrp/src/flrp.ts index 951f4a03f3..2848b31390 100644 --- a/modules/sdk-coin-flrp/src/flrp.ts +++ b/modules/sdk-coin-flrp/src/flrp.ts @@ -428,8 +428,30 @@ export class Flrp extends BaseCoin { } try { const txBuilder = this.getBuilder().from(txHex); - const tx = await txBuilder.build(); - return tx.explainTransaction(); + const tx = (await txBuilder.build()) as FlrpLib.Transaction; + const explanation = tx.explainTransaction(); + + // For a C→P export (ExportInC viewed from the FLRP P-chain perspective), the + // exportedOutputs contain the P-chain UTXO amount, which is the gross amount + // BEFORE the import fee is deducted. The platform uses this explanation to show + // the "pending import amount" before the ImportInP is confirmed. To avoid + // displaying a higher-than-actual amount, subtract the minimum import-to-P fee + // so the displayed amount reflects the expected net P-chain receipt. + if (tx.isTransactionForCChain && explanation.type === TransactionType.Export) { + const minImportToPFee = BigInt((this._staticsCoin.network as FlareNetwork).minImportToPFee); + const adjustedOutputs = explanation.outputs.map((o) => ({ + ...o, + amount: (BigInt(o.amount) - minImportToPFee).toString(), + })); + const adjustedOutputAmount = (BigInt(explanation.outputAmount) - minImportToPFee).toString(); + return { + ...explanation, + outputs: adjustedOutputs, + outputAmount: adjustedOutputAmount, + }; + } + + return explanation; } catch (e) { throw new Error(`Invalid transaction: ${e.message}`); } diff --git a/modules/sdk-coin-flrp/test/unit/flrp.ts b/modules/sdk-coin-flrp/test/unit/flrp.ts index 74cb887464..f44c526720 100644 --- a/modules/sdk-coin-flrp/test/unit/flrp.ts +++ b/modules/sdk-coin-flrp/test/unit/flrp.ts @@ -295,6 +295,24 @@ describe('Flrp test cases', function () { txExplain.changeOutputs.should.be.empty(); }); + it('should subtract minImportToPFee from ExportInC outputAmount to show expected net P-chain receipt', async () => { + // The ExportInC UTXO amount is the gross amount going to P-chain. The actual P-chain + // credit will be UTXO - importFee. We subtract minImportToPFee to show the minimum + // net amount the user will receive on P-chain, preventing the pending display from + // showing an inflated amount compared to the confirmed P-chain balance. + const minImportToPFee = BigInt('1261000'); // from FlarePTestnet.minImportToPFee + const expectedAdjustedAmount = (BigInt(EXPORT_IN_C.amount) - minImportToPFee).toString(); + + // Should work for both unsigned (pending) and signed ExportInC hex + const unsignedExplain = await basecoin.explainTransaction({ txHex: EXPORT_IN_C.unsignedHex }); + unsignedExplain.outputAmount.should.equal(expectedAdjustedAmount); + unsignedExplain.outputs[0].amount.should.equal(expectedAdjustedAmount); + + const signedExplain = await basecoin.explainTransaction({ txHex: EXPORT_IN_C.signedHex }); + signedExplain.outputAmount.should.equal(expectedAdjustedAmount); + signedExplain.outputs[0].amount.should.equal(expectedAdjustedAmount); + }); + it('should fail when transaction hex is not provided', async () => { await basecoin.explainTransaction({}).should.be.rejectedWith('missing transaction hex'); });