Wallet provider abstraction, selection, and lifecycle commands (ENG-1974, ENG-1975)#12
Wallet provider abstraction, selection, and lifecycle commands (ENG-1974, ENG-1975)#12AkasshP wants to merge 8 commits into
Conversation
Also fix x402 challenge parsing to accept `amount` field (v2 compat).
- tests/providers.test.ts: stub providers (cdp/para/privy) reject with "not yet implemented" and expose no exportPrivateKey; keystore provider auto-creates on first use, round-trips export, honors explicit password, rejects wrong password; requireAccount/getOwnAddress dispatch to the selected provider and --private-key overrides it - tests/x402-protocol.test.ts: lock in the `amount` fallback (x402 v2), precedence of maxAmountRequired, and rejection when both are absent Covers the test gap flagged in the PR #10 review. 50/50 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed
One question on the v2 fallback while you're in there: v1's 🤖 Generated with Claude Code |
Exercises the branch added in fc1173d: an invalid `amount` with no `maxAmountRequired` reports accepts[i].amount in the error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… change The Radius RPC change (2026-06) made eth_getBalance return token_balance × per_unit_exchange_rate + raw_native, so the SBC token balance is already included in the native balance. The balance command was summing eth_getBalance + SBC balanceOf on top, double-counting SBC (e.g. reporting $0.20 for a wallet holding only 0.1 SBC). eth_getBalance is now treated as the total; the raw-native (RUSD) line is derived as the remainder via splitAggregateBalance (clamped at zero), assuming the 1 SBC = 1 native unit peg. JSON output gains totalWei and rusdWei now reports the derived native remainder. Note: per the same RPC change, the returned balance may exceed what the network will provision for a single transaction — the CLI does not gate sends on balance (viem's eth_estimateGas path validates instead). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Heads-up: pushed two more commits while testing this branch against testnet (with Eriks).
Suite is 56/56. PR description updated to match. 🤖 Generated with Claude Code |
Two interop fixes verified end-to-end against a live Radius x402 server (rad-read.replit.app — paid 0.001 SBC on mainnet, content delivered): 1. v2 payment header envelope. The CLI emitted the v1 shape (flat scheme/network, numeric validity bounds) regardless of challenge version. Per coinbase/x402 specs/schemes/exact/scheme_exact_evm.md, v2 echoes the chosen accepts entry verbatim as `accepted`, carries the challenge's `resource`, and stringifies validAfter/validBefore. parseChallenge now retains the raw accepts entry and resource for the echo. v1 output is unchanged. 2. EIP-2612 permit settlement (Radius flavor). SBC does not implement EIP-3009 transferWithAuthorization, so signTransferAuthorization payloads can never settle against it. Radius servers advertise `extra.settlementMethod: "permit-transferFrom"` plus `extra.settlementSpender` in the challenge; the CLI now detects that, reads the owner's permit nonce on-chain, signs an EIP-2612 Permit for the settlement spender (deadline = now + maxTimeoutSeconds), and sends the documented flat envelope with a `kind: "permit-eip2612"` payload (v/r/s split signature). Servers without the extra keep the EIP-3009 path. Tests: v2 envelope round-trip + v1 stability, permit signature recovery to owner, permit header shape. 60/60 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per docs.radiustech.xyz/skills/x402.md and the reference client in radiustechsystems/skills (plugins/radius/skills/x402): - Permit2 dual-signature settlement when the challenge advertises extra.assetTransferMethod: "permit2": an EIP-2612 Permit for the canonical Permit2 contract (sequential nonce from token.nonces, goes in extensions.eip2612GasSponsoring for gas-free allowance setup) plus a Permit2 PermitWitnessTransferFrom for the x402 proxy (random 128-bit nonce, witness binds payTo), sharing one deadline. Typed data taken verbatim from references/permit2-typed-data.template.json. - Challenge detection: official v2 servers carry the challenge in a base64 PAYMENT-REQUIRED response header; body JSON remains the fallback for older servers. - Payment is sent in both PAYMENT-SIGNATURE (official v2) and X-Payment (legacy) headers; receipts read from PAYMENT-RESPONSE before x-payment-response. - v2 envelope keeps flat scheme/network alongside the accepted echo, matching the skill's payload example. Settlement dispatch is now: permit2 (assetTransferMethod) → permit-eip2612 (settlementMethod, verified live against rad-read) → EIP-3009 (standard x402 default). Tests: permit2 signature recovery under the skill typed data, payload structure field-for-field vs the skill example, nonce randomness, canonical addresses. 64/64 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Two more commits pushed after exercising the full x402 flow against a live server (rad-read.replit.app, with Eriks):
Suite is 64/64. The permit2 path is spec-verified (payload matches the skill's reference client Known follow-ups deliberately left out of this PR: the pay-confirmation prompt doesn't name the network (mainnet vs testnet), and X-PAYMENT will go out over plain http without warning. Both small, both deserve their own PR. 🤖 Generated with Claude Code |
Summary
Lands both ENG-1974 and ENG-1975 (supersedes #10, which this branch was originally stacked on).
Provider abstraction (ENG-1974)
src/lib/providers/types.tswith theWalletProviderinterface:getAccount,getAddress, optionallogin/logout/exportPrivateKey,statussrc/lib/account.tsintosrc/lib/providers/keystore.ts;requireAccount/getOwnAddressare thin shims that delegate to the active provider (--private-keystill overrides any provider)wallet exportroutes throughprovider.exportPrivateKey— providers without it get a clear error (remote key material is not exportable)PrivateKeyAccount→LocalAccountineip3009.tsso remote providers (viemtoAccount(...)) plug into the x402 pathamountalongsidemaxAmountRequired(v2 compat); parse errors report whichever field was actually the sourceacceptedecho +resource, string validity bounds, flat scheme/network kept per the Radius skill); v1 output unchangedextra.assetTransferMethod: "permit2", official Radius flavor) → EIP-2612 permit (extra.settlementMethod: "permit-transferFrom", verified live on mainnet) → EIP-3009 (standard x402); SBC has no EIP-3009 so the prior path could never settle against itPAYMENT-REQUIREDheader (body fallback), payment sent inPAYMENT-SIGNATURE+X-Payment, receipts fromPAYMENT-RESPONSEfirstwallet balanceno longer double-counts SBC:eth_getBalancenow returns the aggregate (token_balance × rate + raw_native, June 2026 RPC change), so it is treated as the total and the raw-native RUSD line is derived as the remainder (src/lib/balance.ts)Provider selection + lifecycle (ENG-1975)
--wallet <keystore|cdp|para|privy>global flag; resolution order: CLI flag →RADIUS_WALLETenv →~/.radius/config.json→ defaultkeystorewallet login,wallet logout,wallet statuscommands dispatching to the active providerloginpoints towallet new/wallet import,logoutis a no-op,statusreports the cached addressgetAccount/getAddresshard-error with "not yet implemented" instead of falling back to the local keystoreTests (64 total, was 32)
tests/providers.test.ts(new): stub provider rejection, keystore auto-create/reuse/export round-trip, explicit-password create + wrong-password rejection, shim dispatch,--private-keyoverride,0x-prefix normalizationtests/x402-protocol.test.ts:amountfallback,maxAmountRequiredprecedence, neither-present rejection, amount-sourced error labelManual test plan
radius-cli wallet address— returns address without password promptradius-cli wallet sign "hello"— signs through providerradius-cli --network testnet wallet send <addr> 0.001 RUSD— real tx on testnetradius-cli wallet x402 get <url>— full x402 flowradius-cli wallet login|logout|status(+--json) — keystore lifecycleradius-cli --wallet cdp wallet address|sign— "not yet implemented" error, no keystore fallbackradius-cli --wallet privy wallet export— "remote key material is not exportable"radius-cli --wallet metamask wallet status— invalid provider errorRADIUS_WALLET=para radius-cli wallet status— env override worksradius-cli --private-key <key> wallet address— override bypasses providernpm test— 64/64 pass