fix(tools): stop telling the user USDC moved when it was account credits - #163
Merged
Conversation
Six paid tool files had zero key-mode awareness and printed the wrong payment instrument in 38 places. PredictionMarket closed every result with `_$0.005 paid via x402._` on a call that signed nothing. BuyPhoneNumber reported `$5 USDC charged` against a wallet that never moved. Worst of the set, the ImageGen / VideoGen / phone approval prompts asked the user to approve a spend in a currency the session does not use — the human says yes to the wrong sentence, and the model reads the same wrong sentence when it reasons about what it can still afford. New src/payments/billing-copy.ts owns the wording. The split matters: Runtime output — receipts, approval prompts, cancellations — calls the helpers, which read the mode at call time. invalidateKey() demotes a session mid-run after a 401, so a helper that captured the mode at import would keep claiming account credits while Franklin signed from the wallet again. There is a test for exactly that. Static spec.description text is built once at module load, before any mode is settled, and cannot re-render. Branching there would freeze whichever mode happened to be resolved first, so those strings are reworded to be true in both: "Costs $0.001 per call", not "Costs $0.001 USDC from the wallet". Same for "wallet-owned phone number" -> "BlockRun-provisioned". Key mode deliberately states no dollar amount on a receipt. The local figure is a catalog estimate; the account ledger is authoritative, so the receipt points at Activity rather than inventing precision it does not have. A regression fence scans every paid tool for an unconditional payment-rail string and names the offending file:line, so the next tool cannot reintroduce the class. Mutation-tested: reverting one call site, ignoring the mode in the helper, and memoising the mode at import each fail a named test. 730 local tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rm7cRGtC7wCofhYuHRo21h
VickyXAI
added a commit
that referenced
this pull request
Sep 5, 2026
main went red on `the mode is read at call time, not captured at import`. The code is right; the test was wrong. #163 landed before #158, when invalidateKey() demoted a session to wallet mode after a 401, so the test used it to prove the billing copy re-reads the mode. #158 changed that on purpose: a rejected key now only refreshes the credential, because an account failure is never permission to spend from a wallet. The property still holds — useWalletMode() (`--wallet`) is the lever that actually flips the mode. The test now uses it, and additionally asserts that invalidateKey() does NOT move billing to the wallet, so it cannot drift back. Both directions are mutation-tested: freezing the mode at import fails it, and restoring invalidateKey()'s old demotion fails it too. Claude-Session: https://claude.ai/code/session_01Rm7cRGtC7wCofhYuHRo21h Co-authored-by: 1bcMax <195689928+1bcMax@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Follow-up to #162 (merged). Verified against
main, not against the closed #156.The problem
Six paid tool files have zero key-mode awareness and state the wrong payment instrument in 38 places.
isKeyModerefs beforephone.tsprediction.tsvideogen.tsimagegen.tsvoice.tsmusicgen.tsmodal.ts,blockrun.tsThe worst of the set are the approval prompts in ImageGen / VideoGen / phone. The human is asked to approve a spend in a currency the session does not use, and the model reads the same wrong sentence when it reasons about what it can still afford.
Before / after
The split that matters
Runtime output (receipts, prompts, cancellations) calls the new
src/payments/billing-copy.tshelpers, which read the mode at call time.invalidateKey()demotes a session mid-run after a 401 — a helper that captured the mode at import would keep claiming account credits while Franklin signed from the wallet again. There is a test for exactly that.Static
spec.descriptionis built once at module load, before any mode is settled, and cannot re-render. Branching there would freeze whichever mode resolved first, so those strings are reworded to be true in both:Costs $0.001 per call, notCosts $0.001 USDC from the wallet. Likewisewallet-owned phone number→BlockRun-provisioned.Key mode deliberately states no dollar amount on a receipt. The local figure is a catalog estimate; the account ledger is authoritative, so it points at Activity rather than inventing precision.
Verification
A regression fence scans every paid tool for an unconditional payment-rail string and names the offending
file:line, so the next tool cannot reintroduce the class.Mutation-tested — each fails a named test:
phone.tscall sitechargedNoteignore the mode730 local tests pass.
Credit
phone.tshad 5 of these fixed in #156 before it was closed as superseded; that approach is carried forward here and extended to the other six files.🤖 Generated with Claude Code
https://claude.ai/code/session_01Rm7cRGtC7wCofhYuHRo21h