Off-chain gateway for an x402-style credit layer: for each request Maple evaluates a borrower (Solana address) and a target paid URL, runs credit → policy → repayment (stub engines today), optionally GETs the seller with Maple’s wallet after policy allows, and can record a receipt on Solana when enabled.
| Layer | Role |
|---|---|
| HTTP | Bun.serve — GET/POST /maple with headers (see below). |
| x402 | @x402/fetch + @x402/svm; Maple can pay sellers using MAPLE_SVM_X402_PRIVATE_KEY. Each /maple call gets a Maple-generated payment id for the payment-identifier extension when the seller advertises it. |
| On-chain | Anchor program maple records Receipt PDAs (buyer + resource hash + nonce per purchase). |
- Runtime: Bun — use
buninstead ofnpmfor installs and scripts. - Config: copy
.env.example→.env(Bun loads.envautomatically;.env*is gitignored).
- Bun
- Solana CLI (
solana,solana-keygen) - Anchor (
anchor, Rust,cargo-build-sbf) — only needed to build or deploy the on-chain program
bun install
bun run build # anchor build + bun test
bun run build:anchor # only the Solana program + IDLArtifacts:
- Program binary:
target/deploy/maple.so - IDL:
target/idl/maple.json— a copy used by TypeScript lives atsrc/idl/maple.json(regenerate withanchor buildand copy if you change the program interface).
Program ID: FVJ5CDuy9NTpzUwj9XdBdEFfM7aHFsMv3FPRC9GsrP8E (see programs/maple/src/lib.rs / Anchor.toml). If you generate a new program keypair, run anchor keys sync and redeploy.
Deployments use the Solana CLI default wallet (solana config get → keypair path) and must have enough SOL on the target cluster for rent and fees.
solana config set --url devnet
solana balance # fund if needed: solana airdrop 2cd /path/to/maple
anchor buildanchor deploy --provider.cluster devnetUse mainnet-beta or localnet instead of devnet when appropriate; Anchor.toml lists the same program id for localnet and devnet.
The first deploy creates an IDL account. Later deploys often fail at the “create IDL” step with an error like account already in use — the program binary still upgrades, but you must upgrade the IDL separately:
anchor idl upgrade -f target/idl/maple.json \
FVJ5CDuy9NTpzUwj9XdBdEFfM7aHFsMv3FPRC9GsrP8E \
--provider.cluster devnetsolana program show FVJ5CDuy9NTpzUwj9XdBdEFfM7aHFsMv3FPRC9GsrP8E --url devnetChecklist for the gateway when testing receipts against devnet: set MAPLE_RPC_URL to the same cluster (e.g. https://api.devnet.solana.com) and ensure MAPLE_AUTHORITY_KEYPAIR is the wallet that pays rent for new receipt accounts (see .env.example).
| Variable | Purpose |
|---|---|
PORT |
Gateway port (default 3040, or next free in a small range). |
MAPLE_DEFAULT_ESTIMATED_CENTS |
Default spend estimate for policy when the header is omitted. |
MAPLE_MAX_TX_CENTS |
Policy cap per request (stub policy engine). |
MAPLE_SOLANA_CLUSTER |
devnet / mainnet-beta / testnet — logged at startup; used with MAPLE_RPC_URL for a mismatch warning (default devnet). |
MAPLE_POLICY_ALLOW_ALL |
Set true / 1 / yes to always allow policy (skips estimate vs MAPLE_MAX_TX_CENTS); use for local/dev only. |
MAPLE_CHAIN_ENABLED |
Set true / 1 to submit RecordReceipt transactions. |
MAPLE_RPC_URL |
Solana HTTP RPC (must match where the program is deployed). |
MAPLE_SVM_RPC_URL |
Optional RPC for @x402/svm client; defaults to MAPLE_RPC_URL when set. |
MAPLE_AUTHORITY_KEYPAIR |
JSON array or base58 secret; signs and pays for on-chain receipts. |
MAPLE_SVM_X402_PRIVATE_KEY |
Base58 64-byte secret; Maple payer for x402 to seller URLs. Fund USDC on the same cluster as the seller. |
MAPLE_X402_FETCH |
Set 0 / false to skip HTTP to the seller (pipeline only). |
See .env.example for a template.
bun run devListen address: http://127.0.0.1:<PORT> (default 3040). Use bun run start without hot reload.
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
JSON list of routes |
GET |
/health |
Liveness ({ "ok": true }) |
GET or POST |
/maple |
Run the pipeline; requires headers below |
Defined in src/constants.ts:
x-maple-buyer-address— Solana public key (base58) of the borrower (credit / policy identity). Not the key that pays the seller.x-maple-request-url— Full URL of the paid resource (e.g. sellerGET /demo).x-maple-estimated-cents— Optional; spend estimate for policy (defaults from env or100).
Missing required headers → 400 with error: "missing_headers".
JSON includes pipeline fields (trace, credit, policy, repayment, chain) plus:
paymentId— Maple-generated id (prefixmaple_) for x402 payment-identifier when the seller supports it.sellerFetch— Present when policy allows and x402 fetch runs: HTTP result orskippedwith a reason.
When policy allows and MAPLE_SVM_X402_PRIVATE_KEY is set (and MAPLE_X402_FETCH is not disabled), Maple performs a paid GET to x-maple-request-url using @x402/fetch. Omit the key or set MAPLE_X402_FETCH=false for credit/policy only without calling the seller.
With MAPLE_CHAIN_ENABLED=true and a valid MAPLE_AUTHORITY_KEYPAIR, Maple submits a RecordReceipt instruction after the pipeline. Receipt accounts use a per-request nonce so repeat purchases to the same URL do not collide.
- Seller:
cd examples/seller-express-svm, configure.env,bun run dev. - Maple: configure
.env(payer + optional chain),bun run devfrom repo root. - Request:
curl -s http://127.0.0.1:3040/maple \
-H 'content-type: application/json' \
-H 'x-maple-buyer-address: So11111111111111111111111111111111111111112' \
-H 'x-maple-request-url: http://127.0.0.1:4021/demo' \
-H 'x-maple-estimated-cents: 1'cd examples/seller-express-svm
cp env.example .env
# Set SVM_PAY_TO to your devnet receiving pubkey
bun run devPoint x-maple-request-url at your seller base URL and path. More background: docs/SELLER.md, docs/BUYERS.md.
| Example | What it does |
|---|---|
examples/buyer-x402-direct |
x402 only: @x402/fetch + SVM_PRIVATE_KEY — pays the seller directly (no Maple gateway). |
examples/buyer-maple |
Maple gateway: @maple/buyer — POST /maple with x-maple-* headers; Maple runs credit/policy and may paid-fetch the seller with MAPLE_SVM_X402_PRIVATE_KEY. |
AGENTS.md— product goal and conventionsdocs/SELLER.md,docs/BUYERS.md— x402 notes- x402 payment-identifier extension — idempotency semantics for paid retries