Hermes plugin: x402 exact payments on nano:mainnet with a pre-signing guard
Find a file
2026-10-29 21:41:43 +03:00
hermes_nano_x402 hermes-nano-x402 0.1.3: x402 exact payments on nano:mainnet with a pre-signing guard 2026-10-29 21:41:43 +03:00
hermes_nano_x402.egg-info hermes-nano-x402 0.1.3: x402 exact payments on nano:mainnet with a pre-signing guard 2026-10-29 21:41:43 +03:00
tests hermes-nano-x402 0.1.3: x402 exact payments on nano:mainnet with a pre-signing guard 2026-10-29 21:41:43 +03:00
LICENSE hermes-nano-x402 0.1.3: x402 exact payments on nano:mainnet with a pre-signing guard 2026-10-29 21:41:43 +03:00
PKG-INFO hermes-nano-x402 0.1.3: x402 exact payments on nano:mainnet with a pre-signing guard 2026-10-29 21:41:43 +03:00
pyproject.toml hermes-nano-x402 0.1.3: x402 exact payments on nano:mainnet with a pre-signing guard 2026-10-29 21:41:43 +03:00
README.md hermes-nano-x402 0.1.3: x402 exact payments on nano:mainnet with a pre-signing guard 2026-10-29 21:41:43 +03:00
setup.cfg hermes-nano-x402 0.1.3: x402 exact payments on nano:mainnet with a pre-signing guard 2026-10-29 21:41:43 +03:00

hermes-nano-x402

A Hermes Agent plugin that buys x402 pay-per-call resources with Nano (XNO) on nano:mainnet (scheme: exact) from inside the agent loop — with a payment guard that runs before anything is signed.

Hermes is the framework binding; the signing primitives are pure Python (no compiled crypto dependency), so pip install works on any machine the agent runs on.

  • Framework: Hermes Agent (hermes_agent.plugins entry point + a drop-in plugin directory)
  • Registry: PyPI — pip install hermes-nano-x402 (install scripts off, no build step)
  • License: MIT
  • Verified seller used in the example: pursekeeper /v1/fetch at Ӿ0.001, catalogue /.well-known/x402

Install

pip install hermes-nano-x402            # from the public registry, scripts off by default
hermes plugins enable hermes-nano-x402  # plugins are opt-in in Hermes

Or straight from the repository:

git clone https://git.kaki87.net/x402assef/hermes-nano-x402
pip install ./hermes-nano-x402
hermes plugins enable hermes-nano-x402

The plugin registers one tool, nano_x402_fetch, into the nano_x402 toolset.

Configure (environment only — the package never holds a key)

export NANO_SEED=<64 hex>                       # the signer; never logged, never printed
export NANO_X402_ALLOW_PAYTO=nano_1xug1q5t7nxoj3ywwzokiea9jz8fq8qfgzp8pbyfr3co3e5xgj755uofu8ue
export NANO_X402_ALLOW_ORIGINS=https://pursekeeper.dev
export NANO_X402_MAX_PAY=0.01                   # per-call cap, XNO
export NANO_X402_MAX_TOTAL=0.05                 # cumulative cap for the process/run, XNO
# optional: NANO_INDEX=0, NANO_RPC=https://<node>, NANO_ACCOUNT_API=https://pursekeeper.dev
export NANO_PUBLISH=1                          # broadcast the signed block; unset = dry run (sign only)
export NANO_X402_PUBLISH_RPC=https://rpc.nano.to   # node used for `process`

NANO_X402_ALLOW_PAYTO is the operator allowlist. Empty allowlist = every quote is refused.

The one-call example (agent loop, not a script beside it)

Start Hermes and ask it, in the normal loop:

Use nano_x402_fetch on https://pursekeeper.dev/v1/echo?msg=hello and show me what came back.

Hermes calls the tool the way it calls any other:

{"tool": "nano_x402_fetch",
 "arguments": {"url": "https://pursekeeper.dev/v1/echo?msg=hello"}}

and the tool returns

HTTP 200 | paid 0.001 XNO | block <BLOCK HASH>

{"echo":"hello", ...}

A paid resource worth trying next: https://pursekeeper.dev/v1/fetch?url=https://example.com (the catalogue is https://pursekeeper.dev/.well-known/x402).

Under the hood, in the agent process: unsigned GET → 402 with PAYMENT-REQUIRED → :class:Quotevalidation →PaymentGuard.authorize→ one signed send block →PAYMENT-SIGNATURE: base64({x402Version, resource, accepted, payload:{block}}) → the block is published to a Nano node (process, do_work: true) behind a **publish gate**, then the signed request is re-presented and the seller verifies and serves. NANO_PUBLISHunset means the block is signed and *not* broadcast — the dry-run mode used in the tests and inexamples/live_probe.py`.

The guard (rule 3, each line has a test)

Refusal Trigger
wrong_network the offer is not exact on nano:mainnet
payto_not_allowed payTo is not in NANO_X402_ALLOW_PAYTO
origin_not_allowed the quote's origin is not in NANO_X402_ALLOW_ORIGINS
amount_above_cap quote > NANO_X402_MAX_PAY
cumulative_cap signed total + quote > NANO_X402_MAX_TOTAL
duplicate_request this request already has a block — it is re-presented, never signed twice
quote_expired older than min(maxTimeoutSeconds, NANO_X402_MAX_QUOTE_AGE) — re-fetched, never reused
redirect_refused a redirect to another origin (nothing sent there, nothing signed)
work_required extra.workOptional is false and no work provider is configured
insufficient_balance the account cannot cover the quoted amount
malformed_quote bad base64, not v2, non-integer amount, zero amount, bad maxTimeoutSeconds, bad payTo

publish_failed joins the guard when a settle step is configured: a destination outside the publish allowlist, an amount over the publish cap, a link that is not the quoted account, an unsigned block, or a node rejection means the signed request is never sent.

Also enforced: one send block per call; no paid retry after a non-2xx; signed requests never follow redirects; extra.workOptional sellers are tried with work: "0" first (they may compute it), and if the node answers "Block work is less than threshold" the work is computed offline in hermes_nano_x402/work.py and the same signed block is published again — work is not covered by the signature, so no second block is ever signed. NANO_LOCAL_WORK=0 disables that fallback, and NANO_WORK_TIMEOUT (seconds, default 1800) bounds it.

Verify it yourself

pip install "hermes-nano-x402[dev]"
python -m pytest -q                 # 67 tests, all offline against a mocked 402 and a mocked node
python examples/live_probe.py       # reads a real 402, runs the guard, signs — broadcasts nothing

tests/test_nano_crypto.py pins the block hash and signature to the reference implementation (nanocurrency, npm) for a fixed seed, so the signing path cannot silently drift.

What the package contains

hermes_nano_x402/
  __init__.py      plugin entry point: register(ctx) -> tool nano_x402_fetch
  client.py        402 -> guard -> one signed block -> re-present
  guard.py         caps, allowlists, dedupe, redirect policy
  quote.py         x402 v2 PAYMENT-REQUIRED parsing/validation
  signer.py        EnvSigner: seed from the environment, never printed
  publish.py       publish the signed block (process RPC) behind its own gate, dry run by default
  nano_crypto.py   ed25519-blake2b, base32 addresses, state-block hashes (pure Python)
  plugin.yaml      Hermes manifest
examples/live_probe.py
tools/gen_claim_account.py   offline keypair -> nano_ address for a bounty claim
tests/             67 offline tests

No seed, private key or generated key material is stored, logged or printed anywhere in the package (tests/test_no_secrets.py scans for it).

License

MIT — see LICENSE.