Warning
Work in progress. Interfaces, the reason vocabulary and the payload shape are all still moving, and releases may break each other.
Warning
Unaudited and pre-release. Do not point it at real funds.
Point it at testnets only. Nothing here has been reviewed for value at risk.
Charge for an HTTP endpoint in ugnot, and let any x402 client pay it.
x402 is the payment protocol behind HTTP 402: a server answers 402 with the
terms it accepts, a client pays, and the request succeeds. This adds gno.land as one of those terms.
The resource is any endpoint — no realm and no contract call are involved.
The protocol, route matching, header encoding and the retry come from the ecosystem's own middleware. This supplies the chain-specific parts:
- Seller mechanism — list gno in a route's
accepts[]and the middleware prices it, offers it and reads the payment. gnofacilitator— verifies payments and broadcasts them. It holds no keys.- Client mechanism — one
register()call lets a stock x402 client pay a gno chain.
routes := x402http.RoutesConfig{
"GET /weather": {
Accepts: x402http.PaymentOptions{{
Scheme: "exact",
PayTo: "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq",
Price: x402.AssetAmount{Asset: "ugnot", Amount: "250000"},
Network: "gno:dev",
}},
Description: "Weather data",
},
}
handler := nethttpmw.X402Payment(nethttpmw.Config{
Routes: routes,
Facilitator: x402http.NewHTTPFacilitatorClient(&x402http.FacilitatorConfig{URL: facilitatorURL}),
Schemes: []nethttpmw.SchemeConfig{{Network: "gno:dev", Server: gnoexact.NewExactGnoScheme()}},
})(mux)An unpaid request gets 402, with the terms in the PAYMENT-REQUIRED header as base64 JSON — v2
carries them there, not in the body:
{ "x402Version": 2,
"error": "Payment required",
"resource": { "url": "http://localhost:8080/weather", "description": "Weather data",
"mimeType": "application/json" },
"accepts": [{
"scheme": "exact", "network": "gno:dev", "asset": "ugnot", "amount": "250000",
"payTo": "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq",
"maxTimeoutSeconds": 300,
"extra": { "areFeesSponsored": false }
}] }Runnable, against a facilitator and a real chain → examples/weather.
npm i @gnoverse/x402-gnoimport { ExactGnoScheme } from "@gnoverse/x402-gno/exact/client";
const client = new x402Client().register("gno:*", new ExactGnoScheme(wallet));
const paid = wrapFetchWithPayment(fetch, client);
const res = await paid("https://api.example.com/weather");Recognising the 402, selecting an entry, encoding PAYMENT-SIGNATURE and retrying are
@x402/fetch's own code, unmodified. The payment is signed against the chain id the offer names
rather than the one the wallet's node reports — see client/README.md for what
that does and does not buy you.
An x402 mechanism has three roles — server (the seller), facilitator, client (the buyer)
— and the ecosystem names them the same way in every language: @x402/evm publishes
./exact/server, ./exact/facilitator and ./exact/client, and its Go module has the matching
directories. The top level here is that grid. Which language a role is written in is an
implementation detail of the role, so no directory names one.
| Role | Path | What |
|---|---|---|
| server | server/exact/ |
upstream's SchemeNetworkServer, implemented for gno |
| facilitator | facilitator/ |
verification, settlement, and the /verify /settle /supported service |
| client | client/src/exact/client.ts |
upstream's SchemeNetworkClient, TypeScript |
Plus cmd/gnofacilitator/ (the binary), examples/weather/ (a priced endpoint you can run and
curl) and e2e/ (one real payment through a real node — its own Go module).
Both manifests — go.mod and package.json — live at the root, so go test ./..., npm test and
every make target run from here.
- The payer pays the network fee.
std.Feenames no payer and the chain charges the first signer, so the payer of the resource is also the payer of the gas. Offers state this asextra.areFeesSponsored: false. - The facilitator holds no key. It verifies offline and broadcasts a transaction the payer already signed, so it cannot move funds anywhere the payer did not sign for.
- A failed resource is not charged for. The flow is
authorization: verify, serve, then settle. The middleware buffers the response and skips settlement on any status ≥400. - A failed settlement withholds the response. The buffered body is discarded.
- Prices name their denomination. gno has no default asset, so
"$0.001"errors rather than resolving to a token.
Accepted risk: between /verify and /settle a payer can consume the signed-over account
sequence with another transaction. gno has no pull-settlement primitive, so this is cooperative — the
same exposure XRPL documents for its own exact mechanism.
make test # Library tests (no chain, no npm)
make js-test # Client mechanism, including the sign-doc byte-equality check
make test-e2e # One real payment against an in-memory node
make lint # go vet (both modules) + gofmt -l
make build # bin/gnofacilitator
make help # Every targetmake test-e2e starts a node via gno.land's txtar harness, stands up the facilitator and a priced
endpoint, pays with the JS client, and asserts the seller's balance changed.
Built against github.com/x402-foundation/x402/go/v2 for
the protocol and gnolang/gno for the chain.
Implemented: the exact scheme, the authorization flow, v2 headers, gno:<chain-id> as a CAIP-2
network, prices in native units.
Not implemented: dollar-string pricing, a browser paywall, payload extensions, and a CAIP-2
namespace registration — the last is not required by the schema (near:testnet ships without one).
Apache-2.0. See LICENSE.