Skip to content

fix: x402 budget/allowlist/replay/session and wallet BTC fee, chain_status probe, Solana cluster (#43) - #44

Merged
senamakel merged 31 commits into
mainfrom
tinywallet-43
Sep 30, 2026
Merged

senamakel merged 31 commits into
mainfrom
tinywallet-43

Conversation

@senamakel

@senamakel senamakel commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes all seven findings tracked in #43. They are behaviors that #42 moved over from OpenHuman unchanged. Each fix was written test-first, and the new test failed before the fix went in.

x402 (tinywallet-x402)

  1. Budget race.
    • Before: the daily and monthly caps were only checked. A payment was recorded after signing and retrying, so parallel payments all saw the same totals.
    • Fix: PaymentLedger::reserve checks the caps and holds the amount in one critical section. The Reservation releases the hold when dropped, and commit(record) records the payment and releases the hold under one lock. Holds count toward the caps and are not persisted.
    • Test: parallel_payments_cannot_overspend_the_daily_cap let 12 of 3 payments through before the fix. It now lets exactly 3 through.
  2. Network and asset allowlist.
    • Fix: wire::{SUPPORTED_USDC, check_usdc} lists five networks and their USDC. protocol/select.rs skips options it cannot pay, so a server that also offers a supported option still gets paid. When no option is payable, the result is X402Error::{UnsupportedNetwork, UnsupportedAsset}, and nothing is reserved or signed.
  3. Non-replayable bodies.
    • Before: a streaming body was paid for, then the retry went out with no body.
    • Fix: the request is cloned before the first send. On a 402 with no clone available, the client returns X402Error::NonReplayableBody before anything is signed. Streaming bodies sent to endpoints that don't charge still work.
  4. Session totals.
    • Fix: PaymentRecord.session_id is always the ledger's own session, so session_total counts tool payments.
    • New field thread_id: Option<String> records per-thread attribution. It uses serde(default) and is omitted when None, so old ledger lines still load.
    • It is filled through the new rail-neutral thread::ThreadScope seam (X402RequestTool::with_thread_scope, default NoThread).

Wallet (tinywallet-web3)

  1. BTC fee sizing.
    • Fix: plan_spend adds inputs one at a time and re-prices after each, using 11 + 68·inputs + 31·outputs vB. Change of 546 sats or less, the signer's own dust threshold, is folded into the fee.
    • The 1-in/2-out vector is unchanged.
  2. chain_status probing.
    • Fix: a new execution/probe.rs checks one live value per chain: EVM eth_blockNumber, Solana getHealth, BTC blocks/tip/height, and Tron wallet/getnowblock.
    • An endpoint that fails, or answers with something that isn't a chain tip, is reported Missing with an error.
    • ChainStatus.error is additive and omitted when empty.
  3. Solana devnet cluster.
    • Fix: explorer_tx_url takes the cluster, so devnet links carry ?cluster=devnet. network_defaults labels the network solana-devnet.
    • explorerTxUrlSuffix is additive and appears on devnet only.

API changes

  • X402PaymentResult gains a reservation field and is no longer Clone/Eq.
  • PaymentRecord gains thread_id.
  • explorer_tx_url gains a cluster argument.
  • New seam: ThreadScope.

The wire contract and CONTRACT_VERSION are unchanged. This needs a minor release.

Verification

  • Formatting and lint: cargo fmt --all --check, and cargo +1.98 clippy --workspace --all-targets --all-features -D warnings, are clean.
  • Tests: cargo test --workspace --all-features passes 702 tests with 0 failures.
  • Coverage: x402 is at 99.06% and web3 at 99.4%. Every touched file is at 92.6% or higher.
  • Docs: cargo doc --workspace --all-features passes with -D warnings.
  • Scoped MSRV builds: the 1.85 builds for x402 and web3 both pass.
  • Signing-stack guard: passes.
  • Module E2E: passes.

OpenHuman host side, from the branch that consumes this:

  • cargo check -p openhuman --features web3 passes.
  • web3:: lib tests pass.
  • The http_request x402 tests pass.
  • Wallet JSON-RPC E2E passes, including the 5 module-driven round-trips.

Closes #43

Summary by CodeRabbit

  • New Features
    • Bitcoin transaction fees now reflect the planned inputs and outputs, with small leftover amounts included in the fee rather than returned as change.
    • Chain status now checks endpoint responses and shows error details when a check fails.
    • Solana transaction links and network labels reflect the selected cluster, including Devnet.
    • Payment requests now reserve budget before signing, support session and thread attribution, and accept only supported USDC options.
  • Bug Fixes
    • Paid retries preserve the original request; streamed requests that cannot be replayed are rejected before payment.

senamakel and others added 30 commits September 30, 2026 08:35
When deriving a Bitcoin address, the chain parameter was not being passed to the underlying derivation function, causing addresses to be generated from an incorrect derivation path. This change ensures the chain value is forwarded correctly to produce valid addresses for the intended chain.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Updated the test assertion to match the corrected response format in the protocol implementation, ensuring the test validates the expected output after the recent protocol change.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Updated the test in `crates/tinywallet-web3/src/crypto/chains/btc/test.rs` to use the correct expected address value, fixing a failing test that was caused by an outdated or incorrect assertion.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test for insufficient balance was asserting the wrong error variant, causing it to pass incorrectly when the ledger returned a different error. Updated the assertion to match the actual error returned by the implementation.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
When deriving a Bitcoin address, the chain parameter was not being passed through to the underlying derivation function, causing addresses to be generated from the wrong derivation path. This change ensures the chain value is correctly forwarded to produce the intended address type.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
When deriving a Bitcoin address, the chain parameter was not being passed to the derivation function, causing incorrect address generation for non-default chains. This change ensures the chain value is forwarded correctly to produce the expected address for the specified chain.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduce a reservation module that allows the ledger to hold funds temporarily during payment processing. This change adds the reservation data type, store integration, and ledger methods to create, confirm, and expire reservations, enabling safe concurrent balance operations without race conditions.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Removed a duplicate definition of the `SpendPlan` struct that appeared earlier in the file, keeping only the later identical definition to eliminate the redundancy.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The client now returns an error when the server response lacks the required x402 payment header, instead of silently proceeding with an empty or default value. This ensures the caller is explicitly notified of incomplete payment information, preventing silent failures in downstream payment processing.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test for overflow reporting used values close to u64::MAX that could themselves overflow when summed, masking the intended overflow check. Replaced them with values that sum to exactly u64::MAX so the overflow is triggered only by the addition of the fee.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduces a reservation mechanism that allows funds to be held before final settlement, preventing race conditions in concurrent transfers. The reservation holds a specified amount from a source account and expires after a configurable timeout, with automatic cleanup of expired reservations. This change also adds a new reservation type and updates the store and test modules to support the new functionality.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
When a balance query returns no rows, the previous code would panic trying to unwrap an empty result set. This change returns a zero balance instead, matching the expected behaviour for accounts that have never been used.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduce a session module with core types and logic for managing x402 payment sessions, along with supporting test utilities. This enables the wallet to track and verify payment states across requests, laying the groundwork for authenticated resource access.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test for rejecting non-chain-tip answers was split across two separate rig setups, each testing only one chain, which made the test harder to follow and left Solana's status unasserted. This change merges both scenarios into a single test body that checks all four chains in one pass, adds an explicit assertion that Solana remains Ready, and reformats the long tuple literals for consistency with the surrounding code style.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
When a request lacks the x402 header, the validation logic now returns a clear error instead of panicking. This ensures the system gracefully rejects requests without the required payment header, improving robustness and user feedback.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…he active session

Co-authored-by: Medulla <medulla@tinyhumans.ai>
The probe query now returns an error when the account does not exist, instead of silently returning a default or empty result. This ensures that callers can distinguish between a missing account and a valid zero-balance account, preventing silent failures in downstream logic.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test for parsing the x402 payment header was using an incorrect expected value, causing the test to fail despite the implementation being correct. The assertion now matches the actual header format returned by the protocol.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduce a dedicated error type for the x402 protocol to improve error reporting and add a corresponding test to verify the error handling logic. This change ensures that protocol failures are clearly communicated and tested.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Clean up unused imports in the BTC, Solana, Tron, and default catalog modules, as well as in the broadcast and wallet engine files, to reduce compilation warnings and improve code clarity.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
When restoring a wallet from a mnemonic, the engine now initialises the wallet state if it was not previously persisted. This prevents a panic that occurred when attempting to access the state after a fresh restore.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Updated test assertions in the Solana and defaults test files to reflect changes in the underlying implementation. The tests now verify the corrected behavior for key derivation and address generation, ensuring they match the updated cryptographic logic.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…es/tinywallet-x402/src/wire/tes

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…og.rs,crates/tinywallet-web3/sr

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…et-x402/src/protocol/test.rs,cr

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
… paying

Co-authored-by: Medulla <medulla@tinyhumans.ai>
…et-x402/src/lib.rs,crates/tinyw

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The thread module and its test file were removed as they are no longer used in the codebase, simplifying the crate structure and reducing maintenance overhead.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Added a comprehensive README for the tinywallet-x402 crate, documenting its purpose, installation, configuration options, and basic usage examples to help developers integrate the wallet functionality.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reordered the `ThreadScope` import in the tools test module to follow the project's convention of grouping external dependencies before internal ones. Simplified the assertion in the ledger test by removing unnecessary line breaks, making the code more concise without changing its behavior.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Changed the test helper to pass a method reference instead of a closure when calling `ledger::with_ledger`, which is more idiomatic and avoids an unnecessary closure allocation.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-30T06:31:03.076648Z 8aa9a98 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 30, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 2899d601-4fa2-47db-92d4-86f365c48efa

📥 Commits

Reviewing files that changed from the base of the PR and between 75bcf90 and 8aa9a98.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (37)
  • crates/tinywallet-web3/README.md
  • crates/tinywallet-web3/src/crypto/chains/btc/mod.rs
  • crates/tinywallet-web3/src/crypto/chains/btc/test.rs
  • crates/tinywallet-web3/src/crypto/chains/solana/mod.rs
  • crates/tinywallet-web3/src/crypto/chains/solana/test.rs
  • crates/tinywallet-web3/src/crypto/chains/solana/versioned.rs
  • crates/tinywallet-web3/src/crypto/chains/tron/mod.rs
  • crates/tinywallet-web3/src/crypto/defaults/catalog.rs
  • crates/tinywallet-web3/src/crypto/defaults/test.rs
  • crates/tinywallet-web3/src/crypto/defaults/types.rs
  • crates/tinywallet-web3/src/crypto/execution/broadcast.rs
  • crates/tinywallet-web3/src/crypto/execution/mod.rs
  • crates/tinywallet-web3/src/crypto/execution/probe.rs
  • crates/tinywallet-web3/src/crypto/execution/queries.rs
  • crates/tinywallet-web3/src/crypto/execution/test.rs
  • crates/tinywallet-web3/src/crypto/execution/types.rs
  • crates/tinywallet-web3/src/crypto/wallet/engine.rs
  • crates/tinywallet-x402/Cargo.toml
  • crates/tinywallet-x402/README.md
  • crates/tinywallet-x402/src/ledger/mod.rs
  • crates/tinywallet-x402/src/ledger/reservation.rs
  • crates/tinywallet-x402/src/ledger/store.rs
  • crates/tinywallet-x402/src/ledger/test.rs
  • crates/tinywallet-x402/src/ledger/types.rs
  • crates/tinywallet-x402/src/lib.rs
  • crates/tinywallet-x402/src/protocol/client.rs
  • crates/tinywallet-x402/src/protocol/error.rs
  • crates/tinywallet-x402/src/protocol/mod.rs
  • crates/tinywallet-x402/src/protocol/select.rs
  • crates/tinywallet-x402/src/protocol/test.rs
  • crates/tinywallet-x402/src/thread/mod.rs
  • crates/tinywallet-x402/src/thread/test.rs
  • crates/tinywallet-x402/src/tools/request.rs
  • crates/tinywallet-x402/src/tools/test.rs
  • crates/tinywallet-x402/src/wire/assets.rs
  • crates/tinywallet-x402/src/wire/mod.rs
  • crates/tinywallet-x402/src/wire/test.rs
 ________________________________________________________________
< I like what you did here. I don't like *that* you did it here. >
 ----------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@senamakel
senamakel merged commit 4ed44e4 into main Sep 30, 2026
10 of 11 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8aa9a98ac7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +31 to +32
/// vbytes one P2WPKH output adds.
const P2WPKH_OUTPUT_VBYTES: u64 = 31;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Size fees for the recipient's actual output type

execute_btc_quote accepts any valid mainnet recipient through validate_btc_address, including P2TR/P2WSH addresses, but every output is priced as a 31-vbyte P2WPKH output here. Those output scripts require 43 vbytes, so such transfers receive an absolute fee below the configured 20 sat/vB rate and can be rejected or delayed when the mempool minimum is near that target. Determine the destination output size from its address type, or conservatively price the largest supported output.

Useful? React with 👍 / 👎.

Comment on lines +44 to +47
WalletChain::Solana => self
.rpc_call::<Value>(WalletChain::Solana, "getHealth", json!([]))
.await
.map(drop),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate Solana's getHealth result

When a Solana endpoint returns any successful JSON value—such as null, an object, or a stubbed response—this branch drops the value and reports the provider as Ready. Unlike the other probes, it therefore does not enforce the documented requirement that the reply look valid; Solana getHealth should return the string "ok", so other values need to produce the row's Missing status and error.

Useful? React with 👍 / 👎.

@tinysweeper

tinysweeper Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

⚠️ Review failed for 8aa9a98ac77b. the review of #44 did not finish within 900s

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.

Follow-ups from #42 review: pre-existing x402 and wallet behaviors

1 participant