Repository navigation
activity: record the fee each wallet transaction paid (part of #463) - #632
Merged
2 commits merged intoOct 12, 2026
Merged
2 commits merged into
2 commits merged into
Conversation
The activity row's expansion has no fee because the log never stored one: reconciliation reads getTransactionStatus, which carries finality only (GitHub 463). The receipt carries the true fee, so reconciliation now reads it once, for a row this wallet submitted, after the chain reports an outcome that has a receipt (accepted or reverted), and stores it as the optional feePaid field. - Only origin "wallet" rows qualify. A receipt's fee is paid by the transaction's sender; on a discovered incoming transfer it would be someone else's fee. - A recorded fee is final: it is never read again or replaced. - A missing receipt, an unusable fee (no unit, out-of-field amount) or a failed read stores nothing and is retried on a later pass while the transaction is still observed. The reviewed estimate is never stored as a fee paid. - The field is optional and validated, so stored rows from before it load unchanged. Activity is plain storage, not the vault, so no vault schema bump is needed. Callers cannot set it when recording a row. The cost is one receipt read per wallet transaction, once, not one per poll. The value at submission is not recorded here: the background holds no price for most submissions, so it needs a decision about where that price comes from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found that a reverted or L1-final row is never observed again, so if the one fee read in the pass that reached that outcome failed, the fee was lost for good. Slow passes (the 15-minute alarm and a user refresh) now also read the fee for wallet rows that have a receipt-bearing outcome, no fee, and are inside the 7-day observation window, at most eight per pass. A backfill row writes only when its fee is found; fast passes never backfill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7eb9028
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #463 (the fee-paid half; value at submission needs a decision, see below). Stacked on #598. The value at submission is left open (see Open decision).
GitHub #463: the fee paid
Problem
The activity row's expansion has no fee because the log never stored one. Reconciliation reads
starknet_getTransactionStatus, which reports finality only. The fee is in the receipt'sactual_fee.The issue says either field needs a vault schema bump. That isn't so: activity lives in plain
storage.localunderbramble:activity, not in the vault, and its rows already gain optional fields without a migration (as #598 did withdirection).Fix
packages/platform/starknet/src/rpc/transaction-fee.ts):getTransactionFeesreads receipts with the same bounded fan-out and failover as status reads.readReceiptFeetrusts only the RPC 0.7+ shape{ amount, unit }, with a canonical in-field amount and a unit ofFRIorWEI. A bare felt has no unit and is dropped, not guessed.feePaidonActivityEntry, validated byisTransactionFee. Stored rows from before it still load.record()callers cannot set it; only reconciliation writes it.tx-reconcile.ts) reads the fee once, for a row this wallet submitted (origin: "wallet"), after the chain reports an outcome that has a receipt (accepted or reverted).getTransactionFeesis an optionalRuntimeRpcmember, wired in the worker's default RPC.Cost: one receipt read per wallet transaction on success. A receipt that stays unreadable is retried at most once per slow pass for 7 days.
Tests
packages/platform/starknet/tests/rpc/transaction-fee.test.ts(new): the accepted shapes, eleven rejected shapes (bare felt, unknown unit, negative, out-of-field, non-string…), the stored-fee validator, a hash with no fee left out, and failover on a transport error.packages/features/transactions/tests/activity/activity-fee-paid.test.ts(new): rows without the field still load, valid fees load, malformed ones fail the row.tests/background/transactions/tx-reconcile-fee.test.ts(new), through the real reconciler and in-memory log:Open decision: the value at submission
Not recorded here. The background holds no price for most submissions: prices live in the side panel, per display currency. Options:
{ amount, currency }. New transactions only; the background must treat it as a display figure, never an amount.Recommended: option 1, in its own PR together with #480's display.
Residuals
withRpcFallbackreturns the first node's answer, so a receipt lagging on that node waits for a later pass.Mutation checks
Every revert failed at least one test:
feePaidChecks run locally (Linux, Bun 1.3.3)
bun run checkbun run check:coveragebun run check:auditbun run buildReview
DeepSeek (
deepseek-v4-pro-0813, about $0.014) found that a reverted or L1-final row is never observed again, so one failed read lost its fee. Fixed by the backfill in the second commit, with tests. Its point about unknown keys on the receipt's fee object doesn't apply:readReceiptFeebuilds a fresh{ amount, unit }. Its other points are informational and listed as residuals.🤖 Generated with Claude Code