Skip to content

0.7.0 (5/7): payer balance, wallet --network, two serve modes - #8

Merged
dzakwannajmi merged 6 commits into
mainfrom
0.7.0/5-payer-balance-wallet-serve
Oct 8, 2026
Merged

dzakwannajmi merged 6 commits into
mainfrom
0.7.0/5-payer-balance-wallet-serve

Conversation

@dzakwannajmi

Copy link
Copy Markdown
Collaborator

Part 5 of 7 of 0.7.0, which is on npm since 2026-10-08. The PRs follow the release branch's commit order and are merged with merge commits, so every commit keeps its hash.

What changes

  • X402-06 reads the payer's balance before signing: a payer below the price gets no verdict (ERROR (setup)) and nothing is sent.
  • Each payment adapter generates its own payer keys; wasit wallet --network for the Base Sepolia and Solana devnet payers.
  • x402 v1 challenge messages no longer read as Stellar-only.
  • wasit serve modes v1-challenge and malformed-header.

Commits

  • 21f7bdc feat(core): no verdict when the payer cannot cover the price
  • adc7781 docs: the payer balance read before X402-06, and Circle's faucet for Solana devnet
  • 3b45048 feat(core): each payment adapter generates its own payer keys
  • 4c3ca55 feat(cli): wasit wallet --network for the Base Sepolia and Solana devnet payers
  • 3d9c11c fix(core): x402 v1 challenge messages no longer read as Stellar-only
  • d82555a feat(cli): wasit serve v1-challenge and malformed-header modes

Verification

Unit tests and the clean install run in CI. The payer-balance and wallet runs are in docs/evidence/2026-10-05-payer-balance-check-run.md and 2026-10-05-wallet-network-and-serve-modes-run.md (folded into the per-date file in PR 7/7).

🤖 Generated with Claude Code

A payer that cannot fund the payment got its refusal reported as the
target's: on Solana devnet X402-06 failed with "got 402
(invalid_exact_svm_transaction_simulation_failed)", a reason real
defects give too, so it cannot be told apart afterwards. On Stellar the
SDK client's own simulation stopped the build first, reported as a
harness error with only the token's code (Error(Contract, #13)).

X402-06 now reads the payer's balance of the advertised asset once the
option to pay is known and before anything is signed. When the chain
reports less than the price, X402-06 is ERROR (setup), naming the payer
key to fund, nothing is signed or sent, and the negative checks are
skipped. When the balance cannot be read, the run pays as before, so a
failed read never becomes a verdict. Read before signing, so a target
that settles and then refuses still shows as one.

Each adapter reads it its own way: EVM through ERC-20 balanceOf; Solana
as the sum of the payer's token accounts for the mint, never less than
what the client can spend; Stellar by simulating the token's balance,
and, since a Stellar Asset Contract reports a missing trustline as an
error, by looking the trustline up when the token is that asset's SAC
(recognised by its own contract id, the issuer excepted).

Live, with fresh unfunded payers: ERROR (setup) on Stellar, Base Sepolia
and Solana devnet; with the funded payers every x402 fixture passes
10/10. 227 core tests; 7 mutants killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Solana devnet

CHECKS.md's X402-06 row and "The payment checks cannot pay" say that the
payer's balance is read before signing, that a balance below the price
gives ERROR (setup) with nothing sent, and that an unreadable one pays
as before. The CHANGELOG records it as a change.

The evidence: fresh unfunded payers on Stellar, Base Sepolia and Solana
devnet get ERROR (setup) naming the key to fund, where Solana used to
fail X402-06 and Stellar to stop on the token's bare error code; funded
payers pass 10/10 on every fixture. It also records one Permit2 run that
drew 402 without a reason and moved no funds, not reproduced in the
five runs after it.

The Solana devnet payer's USDC comes from faucet.circle.com, as the
configuration guide, CHECKS.md and the Solana evidence now say.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
generatePayerKey() returns a new random key in the form the adapter's
payerKeyEnv takes: a Stellar secret, an EVM private key, or the base58 of
a Solana keypair's 64 bytes, the form wallets export. So a key handed out
by `wasit wallet` is never one the payment checks would refuse at
preflight; a test reads every generated key back through its adapter, and
checks a Solana key's address against the one kit derives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…net payers

`wasit wallet create --role x402` made Stellar keys only, so a builder
paying on Base Sepolia or Solana devnet had to find another way to make
the key the payment checks read. `--network eip155:84532` and
`--network solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` now generate it,
printing the EVM_PRIVATE_KEY or SVM_PRIVATE_KEY line and the address to
fund at faucet.circle.com; that is the only step, since the payer needs
no ETH or SOL. `status --network` shows the configured payer's USDC
balance of the SDK's default asset and exits 2 when it could not be
read; `fund --network` prints the faucet step. `--fund` stays Stellar
only (Friendbot).

`--network` takes the testnets `wasit serve` poses on, nothing else, and
the MPP roles stay on Stellar. The serve network profiles gain a name, a
faucet label and the native token, which the messages use. The
dashboard's environment panel lists EVM_PRIVATE_KEY and SVM_PRIVATE_KEY.
Stellar behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
X402-02's detail for a challenge in the v1 body form gave Stellar's
`exact` scheme as the reason a header is required, and the payment
checks' skip reason said Wasit pays through the exact scheme "on
Stellar". Both predate the other chains: a v1 challenge from a Base
Sepolia paywall was told about Stellar. They now say x402 v2 signals
payment in PAYMENT-REQUIRED and that the payment checks pay through v2
only, keeping the Stellar fact as Stellar's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two modes about the challenge itself, for testing how an agent reads
one. `v1-challenge` sends only the x402 v1 form: the
PaymentRequirementsResponse in the 402 body, with v1's field names
(maxAmountRequired, resource, description, mimeType) and the network's v1
name (base-sepolia, solana-devnet), and no PAYMENT-REQUIRED header, as a
paywall on the old SDK does. It refuses Stellar, which v1 names no
network for. `malformed-header` sends a PAYMENT-REQUIRED header that is
base64 of the challenge's JSON cut short. Both answer a payment with 402
and log which header it came in and what that says about the agent.

Wasit's read checks see exactly that: the v1 challenge is complete as v1
(X402-04 "All required v1 fields present") and fails X402-02 and X402-05;
the malformed header fails X402-03. With the official x402 SDK client as
the agent, it paid the v1 challenge as v1 in X-PAYMENT on Base Sepolia
and Solana devnet, and refused the malformed one without paying. The
evidence file also records `wasit wallet --network` as run.

The tests that every challenge is a well-formed v2 one now cover the
four modes it is true of. 75 CLI tests; 6 mutants killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
wasit Ready Ready Preview Oct 8, 2026 6:49am UTC

@dzakwannajmi
dzakwannajmi merged commit ab4432f into main Oct 8, 2026
5 checks passed
@dzakwannajmi
dzakwannajmi deleted the 0.7.0/5-payer-balance-wallet-serve branch October 8, 2026 06:50

This branch was successfully deployed

1 active deployment
Preview — d82555a9 Deployed Oct 8, 2026 by vercel[bot]
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