From dd4c74a953c5a3309a79adf2da85b8933769c685 Mon Sep 17 00:00:00 2001 From: "arunchockalingam504@bitgo.com" Date: Fri, 3 Jul 2026 09:06:44 +0000 Subject: [PATCH] fix(sdk-coin-flrp): subtract minImportToCFee from ExportInP outputAmount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For P→C exports (ExportInP), the exported UTXO amount includes the C-chain import fee that is added by the platform during transaction building (amount = userAmount + importToCFee). Without adjusting the outputAmount, the confirmation UI reads the gross UTXO amount as the displayed send amount. When the platform additionally applies its own import-fee subtraction from the same gross value, the resulting spendAmount can appear as 0 or be misinterpreted, causing the export confirmation UI to display "- TFLR" instead of the correct amount. Fix: in Flrp.explainTransaction, when the transaction is a P-chain export (ExportInP, !isTransactionForCChain), subtract minImportToCFee from outputAmount and each output's amount. This yields the minimum expected net C-chain receipt, aligning the confirmation display with the amount the user will actually receive after ImportInC, consistent with how ExportInC subtracts minImportToPFee (CECHO-1450). Add minImportToCFee to the FlareNetwork interface and set it to 2850000 nFLR (250 nFLR/gas × ~11400 gas, the minimum EVM import fee). Add test coverage verifying ExportInP outputAmount is adjusted. Ticket: CECHO-1518 Session-Id: 6f2ea6bd-01d6-4a82-a82f-0af0974e5165 Task-Id: 65e37cf1-5c7c-4605-8126-070b8d699a02 --- modules/sdk-coin-flrp/src/flrp.ts | 18 ++++++++++++++++++ modules/sdk-coin-flrp/test/unit/flrp.ts | 24 ++++++++++++++++++++++++ modules/statics/src/networks.ts | 7 +++++-- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/modules/sdk-coin-flrp/src/flrp.ts b/modules/sdk-coin-flrp/src/flrp.ts index 870249eb5e..e0dcb8d426 100644 --- a/modules/sdk-coin-flrp/src/flrp.ts +++ b/modules/sdk-coin-flrp/src/flrp.ts @@ -451,6 +451,24 @@ export class Flrp extends BaseCoin { }; } + // For a P→C export (ExportInP), the exported UTXO amount includes the C-chain + // import fee that will be deducted when ImportInC is confirmed. Subtract the + // minimum import-to-C fee so the displayed amount reflects the expected net + // C-chain receipt, consistent with how ExportInC subtracts minImportToPFee. + if (!tx.isTransactionForCChain && explanation.type === TransactionType.Export) { + const minImportToCFee = BigInt((this._staticsCoin.network as FlareNetwork).minImportToCFee); + const adjustedOutputs = explanation.outputs.map((o) => ({ + ...o, + amount: (BigInt(o.amount) - minImportToCFee).toString(), + })); + const adjustedOutputAmount = (BigInt(explanation.outputAmount) - minImportToCFee).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 132084c09a..fc18c593ba 100644 --- a/modules/sdk-coin-flrp/test/unit/flrp.ts +++ b/modules/sdk-coin-flrp/test/unit/flrp.ts @@ -313,6 +313,30 @@ describe('Flrp test cases', function () { signedExplain.outputs[0].amount.should.equal(expectedAdjustedAmount); }); + it('should subtract minImportToCFee from ExportInP outputAmount to show expected net C-chain receipt', async () => { + // The ExportInP UTXO amount is the gross amount going to C-chain, which includes + // the C-chain import fee that will be deducted when ImportInC is confirmed. We + // subtract minImportToCFee to show the minimum net amount the user will receive + // on C-chain, preventing the confirmation UI from showing a higher-than-actual + // amount and causing the display to appear as "- TFLR" instead of the correct amount. + const minImportToCFee = BigInt('2850000'); // from FlarePTestnet.minImportToCFee + const grossOutputAmount = BigInt(EXPORT_IN_P.amount); // 55000000 (includes import fee) + const expectedAdjustedAmount = (grossOutputAmount - minImportToCFee).toString(); + + // Should work for both half-signed and fully-signed ExportInP hex + const halfSignedExplain = await basecoin.explainTransaction({ + halfSigned: { txHex: EXPORT_IN_P.halfSigntxHex }, + }); + halfSignedExplain.outputAmount.should.equal(expectedAdjustedAmount); + halfSignedExplain.outputs.should.be.an.Array(); + halfSignedExplain.outputs.length.should.equal(1); + halfSignedExplain.outputs[0].amount.should.equal(expectedAdjustedAmount); + + const signedExplain = await basecoin.explainTransaction({ txHex: EXPORT_IN_P.fullSigntxHex }); + 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'); }); diff --git a/modules/statics/src/networks.ts b/modules/statics/src/networks.ts index 5f92117deb..1e7da18297 100644 --- a/modules/statics/src/networks.ts +++ b/modules/statics/src/networks.ts @@ -18,6 +18,7 @@ export interface FlareNetwork extends BaseNetwork { vm?: string; txFee: string; minImportToPFee: string; + minImportToCFee: string; maxImportFee: string; createSubnetTx?: string; createChainTx?: string; @@ -2318,7 +2319,8 @@ export class FlareP extends Mainnet implements FlareNetwork { hrp = 'flare'; alias = 'P'; vm = 'platformvm'; - minImportToPFee = '1261000'; // 0.1261 FLR + minImportToPFee = '1261000'; // minimum PVM import-to-P fee in nFLR + minImportToCFee = '2850000'; // minimum EVM import-to-C fee in nFLR (250 nFLR/gas * ~11400 gas) txFee = '200000'; // FLR P-chain import requires higher fee than base txFee baseTxFee = '1000000'; maxImportFee = '10000000'; // defaults @@ -2354,7 +2356,8 @@ export class FlarePTestnet extends Testnet implements FlareNetwork { alias = 'P'; assetId = 'fxMAKpBQQpFedrUhWMsDYfCUJxdUw4mneTczKBzNg3rc2JUub'; vm = 'platformvm'; - minImportToPFee = '1261000'; // 0.1261 FLR + minImportToPFee = '1261000'; // minimum PVM import-to-P fee in nFLR + minImportToCFee = '2850000'; // minimum EVM import-to-C fee in nFLR (250 nFLR/gas * ~11400 gas) txFee = '200000'; // FLR P-chain import requires higher fee than base txFee baseTxFee = '1000000'; maxImportFee = '10000000'; // defaults