Skip to content

feat(ledger): book ccxt venue fees against their native asset (draft, #4277) - #4278

Closed
TaprootFreak wants to merge 1 commit into
developfrom
fix/exchange-tx-native-fee
Closed

feat(ledger): book ccxt venue fees against their native asset (draft, #4277)#4278
TaprootFreak wants to merge 1 commit into
developfrom
fix/exchange-tx-native-fee

Conversation

@TaprootFreak

Copy link
Copy Markdown
Collaborator

Draft for evaluation, not a merge candidate. Addresses #4277. The honest recommendation is hold / won't-fix — the benefit is immaterial and the semantic blast radius is high. Opening it so the change and its trade-offs are concrete.

What #4277 is, and the deeper finding

The ccxt Trade branch (exchange-tx.consumer.ts tradeSpec) books the venue fee as a CHF-only EXPENSE/spread-{exchange} leg valued at the persisted, 2-dp-rounded feeAmountChf. A Binance micro-fill's BNB commission is worth < 0.005 CHFfeeAmountChf = 0 → no leg (the #4277 symptom, and the benign cause of the pre-#4268 checkNativeBalance noise).

Investigating the fix surfaced a bigger point: the quote-spread plug (residualChf = -Σ amountChf) makes total spread-{exchange} = quoteChf − baseChf independent of feeAmountChf, so the venue fee is algebraically neutralized on every ccxt fill (not just sub-cent ones) — it is neither recognized as a cost nor tracked as a native outflow. The fee-currency (BNB) journal balance therefore carries a growing phantom that §7 reconciliation only observes and alarms (§7 never writes), so nothing is silently corrupted today.

The change

Mirror payout-order.consumer.appendDistinctFeeLegs: book the fee against its native ASSET/{exchange}/{feeCurrency} account (mark-valued), so the native outflow survives even when its CHF rounds to 0. New private appendCcxtFeeLeg replaces the two-line CHF push. Conservative by design: the native leg is booked only for a fee in a distinct asset (≠ base/quote — those are already in cost, native booking there would double-count) that has a bootstrapped account and a historical mark; otherwise it falls back to today's exact CHF spread leg, so no trade that books today starts deferring. Sign-aware off the signed feeAmount (maker rebate → Dr / INCOME). Scrypt's market-spread branch is untouched. No new account names, no CoA/bootstrap change, no migration; forward-only.

Blast radius (why hold)

  • Because the plug neutralizes the fee for material fills too, this doesn't merely recover sub-cent legs — it recognizes the venue fee as real spread-{exchange} expense on all ccxt trades (Binance/MEXC/Kraken) and starts native fee-asset tracking. Cumulative P&L/native impact is the total ccxt fees over the ledger period (still small vs total balance, but larger than the sub-cent-only ~tens-of-CHF/20-months in Ledger: sub-cent fee-less exchange micro-fills omit the EXPENSE fee leg (feeAmountChf rounds to 0) #4277).
  • It shifts the §7 Binance/BNB diff and equity-parity baselines toward correct (§7 is a pure observer → no double-correction), and the content-change scan will reverse+rebook the recently-synced trade window on first deploy (append-only, bounded burst).
  • Requires Binance/BNB (and every ccxt fee currency) to be a bootstrapped ASSET account for the fix to bite; otherwise it safely no-ops to the CHF fallback.

Bottom line: correct and buildable, but the cost/benefit doesn't justify a merge on its own. Nothing is lost today that §7 doesn't already surface.

Does the pattern generalize? (the survey)

A repo-wide audit of the ledger for the same anti-pattern (a crypto-native amount booked via a pre-rounded *AmountChf field, losing sub-cent precision) found three confirmed sites; fiat/CHF-denominated 2-dp values are correct and were not flagged:

  1. exchange-tx.consumer.ts (this PR) — ccxt venue fee. Immaterial.
  2. trading-order.consumer.ts (book, network-fee leg) — DfxDex arbitrage on-chain gas (txFeeAmount, native) booked only as CHF txFeeAmountChf; no native gas-coin leg. Materiality plausibly non-trivial but unquantified (L1 gas). Higher fix risk (TradingOrder has no fee-currency column → the gas token must be derived from the swap chain). → own issue, not this PR.
  3. payout-order.consumer.ts (appendDistinctFeeLegs early-return gate if (feeChf === 0) return) — short-circuits before the correct native-leg machinery, so a distinct non-payout fee asset whose CHF sum rounds to 0 loses its native leg. Immaterial, narrowest edge, lowest fix risk (un-gate the existing native legs, gate only the CHF EXPENSE). → small follow-up issue.

So: the approach is not exchange-tx-specific — the same "book fees natively, not via a pre-rounded CHF scalar" principle applies to trading-order and (narrowly) payout-order. Each is an independent mechanism on a different entity with different materiality and risk, so they belong in separate issues rather than one bundled PR.

Verification (Node 20)

prettier / eslint / type-check / build clean · DI graph resolves · full accounting suite 780/780 (incl. 3 new: native distinct-asset fee; sub-cent fee preserved natively; CHF fallback when the fee account is missing). The existing ccxt fee test (feeAmountChf only) now exercises the fallback path unchanged, as a regression guard.

Staying draft intentionally. If the team decides native fee tracking is a principle worth the churn, I'll take it through the full review-to-ready gate and file the two sibling issues; merge stays with the maintainer.

…4277)

Draft for evaluation — the honest recommendation is hold/won't-fix given an
immaterial benefit against a high semantic blast radius (see the PR description).

The ccxt Trade branch books the venue fee as a CHF-only spread leg valued at the
pre-2dp-rounded feeAmountChf, so a sub-cent fee (a Binance micro-fill's BNB
commission) rounds to 0 and no leg is booked. And because the quote-spread plug
neutralizes it, the fee has zero net P&L effect on every ccxt fill and its native
outflow is never tracked. Mirror payout-order appendDistinctFeeLegs: book the fee
against its native ASSET/{exchange}/{feeCurrency} account (mark-valued) so the
native outflow survives even when its CHF value rounds to 0. Conservative: only for
a distinct fee asset (not base/quote) with a bootstrapped account and a historical
mark, else fall back to today's CHF spread leg — no trade that books today defers.
@TaprootFreak

Copy link
Copy Markdown
Collaborator Author

Folded into #4279, which packages all three native-fee sites (exchange-tx + trading-order + payout-order) from the #4277 audit into one PR. Closing this exchange-tx-only draft.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant