Skip to content

feat(agent-world): x402 payment bridge (section-agnostic)#11

Merged
graycyrus merged 2 commits into
feature/tiny.placefrom
feat/agent-world-x402
Jun 16, 2026
Merged

feat(agent-world): x402 payment bridge (section-agnostic)#11
graycyrus merged 2 commits into
feature/tiny.placefrom
feat/agent-world-x402

Conversation

@graycyrus

Copy link
Copy Markdown
Owner

What

Adds src/openhuman/tinyplace/payment.rs — the reusable x402 fulfillment bridge shared across register / buy / bid / offer. It turns a 402 Payment Required PaymentChallenge into a signed X402PaymentMap by paying on-chain through the OpenHuman wallet, then signing the authorization via the tiny.place SDK.

This is the foundation PR; the section write-handlers (register, marketplace) consume this bridge in follow-up PRs.

How it works

  • validate_challenge — require asset/amount/to/network; route SOL→native, USDC→SPL; reject unsupported assets and expired challenges (lenient on unparseable expiry — backend is source of truth).
  • to_transfer_params — map the challenge to the wallet's PrepareTransferParams.
  • build_payment_map — delegate canonical-message + Ed25519 signing + flattening to the SDK's build_x402_payment_map (no hand-rolled crypto). The on-chain tx is carried as onChainTx/tx/transaction references, never the signature field (which is the off-chain authorization signature).
  • fulfill_paymentvalidate → prepare_transfer → execute_prepared(confirmed:true) → build_payment_map → { payment_map, on_chain_tx, quote_id }.

Generic via PaymentContext { purpose, nonce_prefix, extra_metadata }.

Scope (deliberately minimal)

  • 2 files: new payment.rs, +1 line mod payment; in mod.rs.
  • No RPC controller / schema / all.rs / UI / i18n — #![allow(dead_code)] until the register handler lands.
  • No real funds move in this PR. fulfill_payment is unreachable (no controller); the user-confirmation gate lives in the future consumer's confirmed RPC param.
  • Network/cluster/mint selection deferred to the live-test follow-up — the bridge passes challenge.network through verbatim and never picks a cluster.

Tests

13 offline unit tests (mock PaymentChallenge + deterministic LocalSigner seed; no network, no funds), including:

  • signature_verifies_against_pubkey — reconstructs the canonical message from the flattened map and verifies the Ed25519 authorization signature against the signer's public key (exactly what the backend does).
  • on_chain_tx_in_references_not_signature — proves the on-chain tx lands in references and signature is a distinct 64-byte Ed25519 sig.

Verification

  • GGML_NATIVE=OFF cargo check --locked --lib
  • cargo test --lib -- tinyplace::payment13 passed / 0 failed
  • cargo fmt --check

Adds src/openhuman/tinyplace/payment.rs — the reusable x402 fulfillment
bridge shared across register / buy / bid / offer. Turns a 402
PaymentChallenge into a signed X402PaymentMap by paying on-chain through
the OpenHuman wallet, then signing the authorization via the tiny.place SDK.

- validate_challenge: require asset/amount/to/network; route SOL->native,
  USDC->SPL; reject unsupported assets and expired challenges (lenient on
  unparseable expiry).
- to_transfer_params: map the challenge to the wallet's PrepareTransferParams.
- build_payment_map: delegate canonical-message + Ed25519 signing + flatten
  to the SDK's build_x402_payment_map (no hand-rolled crypto). The on-chain
  tx is carried as onChainTx/tx/transaction references, NEVER the signature
  field (which is the off-chain authorization signature).
- fulfill_payment: validate -> prepare_transfer -> execute_prepared(confirmed)
  -> build_payment_map -> { payment_map, on_chain_tx, quote_id }.

Generic via PaymentContext { purpose, nonce_prefix, extra_metadata }. No RPC
controller / UI in this PR (#![allow(dead_code)] until the register handler);
network/cluster/mint selection deferred to the live-test follow-up.

13 offline unit tests (mock challenge + deterministic LocalSigner seed; no
network, no funds), including signature-verifies-against-pubkey and
on-chain-tx-in-references-not-signature.
truncate() sliced on byte offsets (&s[..6] / &s[s.len()-4..]), which would
panic on a multi-byte UTF-8 boundary. Switch to char-based take/skip so the
log helper is safe on any input. Add a regression test.
@graycyrus graycyrus changed the base branch from main to feature/tiny.place June 16, 2026 17:22
@graycyrus graycyrus merged commit ec44049 into feature/tiny.place Jun 16, 2026
20 of 21 checks passed
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