HSP is a verifiable settlement layer for stablecoin payments.
Instead of trusting a payment processor’s “paid” flag, every payment carries cryptographic
evidence that anyone can re-check. A payer signs a Mandate; an adapter settles it
and emits a signed Receipt; a verifier checks the pair (plus any compliance
Attestations) against one rule:
You do not need to run any infrastructure. The organizer hosts this shared sandbox; you point the SDK at it and pay. Values marked <…> below (Coordinator URL, API key, faucet, repo) are shared once the sandbox is live.
Payment rails move money; almost none give the relying party a cryptographic answer to “did the thing I authorized actually happen, under the conditions I demanded?” HSP is that answer layer. The flow is always the same three moves:
The payer signs a mandate — an EIP-712 message: pay this much of this token to this recipient on this chain, meeting these requirements.
The payer’s own wallet broadcasts the on-chain transfer. HSP is zero-custody: no service ever holds your funds.
A verifier independently checks the receipt against the mandate and a pinned trust policy. ACCEPT means ship — and never requires trusting the Coordinator.
Three wire objects carry the evidence — and that is the entire protocol surface:
| Object | Signed by | Says |
|---|---|---|
| Mandate | the payer | “I intend to pay X to Y, and I require capabilities Z” |
| Receipt | an adapter | “I observed the settlement that satisfies this mandate” |
| Attestation | an issuer | “I vouch the subject is KYC’d / not sanctioned / …” |
Compliance is not a bolt-on: KYC and sanctions are ordinary capabilities in those sets, toggled per payment. The protocol is a pre-1.0 draft — these concepts are stable; wire details may still change.
Every scenario below is implemented, tested against real chains, and inspectable in the Explorer — the decision trace shows exactly which capability was required and what satisfied it. The matrix is two settlement paths × two policies:
| # | Scenario | What it demonstrates | Requires |
|---|---|---|---|
| 1 | Public · evm-transfer | a plain ERC-20 stablecoin transfer, verified — the hello-world | ∅ |
| 2 | Public · x402 | paying via real Coinbase x402 v2 (HTTP-native, client-pull) | ∅ |
| 3 | Compliant · evm-transfer | a transfer that also carries KYC + sanctions attestations | attests:kyc + attests:sanctions |
| 4 | Compliant · x402 | KYC / sanctions enforced over the x402 path | attests:kyc + attests:sanctions |
Build agentic commerce, paid APIs (x402 paywalls), compliant settlement flows — or your own settlement adapter (§07).
Coming soon — private payments. A later release will add and open up privacy-preserving settlement: hide the recipient (stealth addresses) or the amount (a shielded pool), each with optional view-key disclosure to a regulator. They slot in as adapters — the capability model already reserves room for them, so nothing else on this page changes when they ship.
You consume HSP through a small layered stack — each layer is independently usable, and each lower layer is what the one above is built on. All of it runs from TypeScript source; no build step.
pay() + independent verify() — most developers start hereThe Coordinator is the only service you talk to over the network. It registers mandates, observes
your on-chain settlement, runs the verifier, stores the (mandate, receipt, attestations) triple,
and serves status, a web Explorer, and this portal. It is custody-free — it
signs observations with an adapter key, never moves money.
The SDK is distributed as a repository for the hackathon (not yet on npm). The organizer shares the repo link together with the sandbox details. Then:
git clone <REPO_URL> && cd hsp # everything runs in Docker (no node on your host) — see the repo README, # or use your own Node 20+ toolchain: npm install
Open the faucet page (/faucet/) and paste your address — you receive gas + test USDC, rate-limited per address & IP. Or by API:
curl -X POST -H 'content-type: application/json' \ -d '{"address":"0xYOUR_ADDRESS"}' <FAUCET_URL>/faucet
import { HSPClient } from '@hsp/sdk'; import { resolveChain } from '@hsp/core/chains/index'; const chain = resolveChain('hashkey-testnet'); // pinned testnet USDC const hsp = new HSPClient({ coordinatorUrl: process.env.HSP_COORDINATOR_URL, // <COORDINATOR_URL> apiKey: process.env.HSP_API_KEY, // <API_KEY> signer: { kind: 'privateKey', privateKey: process.env.HSP_PRIVATE_KEY }, chain, }); // USDC has 6 decimals → 1 USDC = 1_000_000 base units const handle = await hsp.pay({ to: '0xRecipient', amount: 1_000_000n }); await handle.awaitSettled(); // → "SETTLED"
That single pay() call builds the mandate, signs it, registers it, broadcasts the ERC-20
transfer from your own wallet, asks the Coordinator to observe it, and returns a handle.
Open /explorer and paste the paymentId — see the
mandate, the receipt, and the subset chain that produced ACCEPT.
Typed requirements, written verb:object:version. proves:… — the settlement
structurally proves something. attests:… — an issuer vouches for something.
A pure function of (mandate, receipt, attestations, policy). ACCEPT iff
requiredCapabilities ⊆ satisfiedCapabilities. You can run it yourself — you never have
to trust the Coordinator’s word.
Three backends, one wire signature. The signing account is also the settling account
(wallet-settling: Transfer.from must equal the mandate signer).
Equals the mandateHash. Use it to query status, deep-link the Explorer, and de-duplicate:
one on-chain transfer settles at most one payment.
The SDK fetches matching attestations from the issuer and registers them alongside your mandate; the verifier credits them.
const hsp = new HSPClient({ /* …as above… */ issuerUrl: process.env.HSP_ISSUER_URL }); await hsp.pay({ to: '0xRecipient', amount: 1_000_000n, profile: { compliance: ['kyc', 'sanctions'] }, // signs the caps in });
You sign an HSP mandate and an EIP-3009 authorization; the facilitator settles on-chain (you pay no gas) and bridges it to a verifiable HSP receipt.
await hsp.payX402({ merchant: '0xMerchant', facilitatorUrl: process.env.HSP_FACILITATOR_URL, amount: 1_000_000n, // profile: { compliance: ['kyc','sanctions'] } // optional });
Pin the adapter’s observation address once (out-of-band, from /chains)
and verify locally — the proof is yours to check.
import { HSPVerifier } from '@hsp/sdk'; const verifier = new HSPVerifier({ chain, adapterAddress: PINNED }); const d = await verifier.verify(mandate, receipt, attestations); if (d.ok && d.outcomeClass === 'ACCEPT') ship();
Want to settle a different way — a new rail, a new proof? @hsp/devkit gives you a
template plus a conformance runner that checks your adapter against the protocol’s generic obligations
using the real verifier. The verifier never changes; only your proof schema does.
# 1. start from a template that already passes conformance cp packages/devkit/template/my-adapter.ts my-team/adapter.ts cp packages/devkit/template/run-conformance.ts my-team/run-conformance.ts # 2. make it settle YOUR way, then self-test: npx tsx my-team/run-conformance.ts # → forged signatures, replays, deadline, observation reuse… all exercised
Then submit (adapterId, instanceKey, signing address, reorgPolicy) to the organizer to be
registered in the sandbox Coordinator’s trust set — payers can settle through you. No protocol changes,
no permission from the spec.
Two pieces let an AI agent pay safely — the agent never sees a private key beyond a small demo
wallet you scope, and hsp_pay is bounded by hard server-side guardrails.
An MCP server exposing hsp_quote / hsp_pay / hsp_status /
hsp_verify. hsp_pay requires explicit confirm: true and is bounded by
a SpendGuard: per-tx cap, daily cap, optional recipient allowlist.
// .mcp.json env "HSP_COORDINATOR_URL": "<COORDINATOR_URL>", "HSP_API_KEY": "<API_KEY>", "HSP_CHAIN": "hashkey-testnet", "HSP_AGENT_PRIVATE_KEY": "0xDEMO_SMALL_AMOUNT_ONLY", "HSP_MAX_AMOUNT_BASE_UNITS": "10000000", "HSP_DAILY_CAP_BASE_UNITS": "50000000"
A skill that teaches the agent the safe flow — always quote → get user approval → pay —
and routes intents to the right tool. Drop it into your agent’s skills directory and point the MCP
server at <COORDINATOR_URL> with your scoped demo key.
| Method | Path | Purpose |
|---|---|---|
| POST | /payments | register a signed mandate (+ attestations) |
| POST | /payments/:id/observe | ask the Coordinator to observe your settlement tx |
| GET | /payments/:id | payment status + the stored triple |
| GET | /payments/:id/explain | label-resolved decision trace (what the Explorer shows) |
| GET | /requirements?chain= | the deployment’s requirement advertisement |
| GET | /chains | chain registry + the adapter address to pin |
| GET | /stats | public aggregate dashboard (JSON) |
| GET | /docs, /explorer | this portal + the decision-trace UI |
Write endpoints need Authorization: Bearer <API_KEY>. paymentId = mandateHash
(idempotent); a 202 from observe means the tx is still confirming — the SDK retries for you.
Every verifier decision carries an outcomeClass — the HTTP-status-code of settlement.
Branch on the class; log the errorCode.
| Class | Meaning | What to do |
|---|---|---|
| ACCEPT | settled and verified under your policy | ship |
| RETRYABLE | transient — not observable yet, stale state | retry |
| POLICY | a policy / requirement isn’t met | fix the mandate / attestations |
| PERMANENT | structurally invalid — contradicts the mandate | give up / debug |
| Frequent code | It means |
|---|---|
| HSP-MAND-EXPIRED | settled after the mandate deadline (an on-time settlement stays verifiable later) |
| HSP-RCPT-SIG | receipt not signed by a trusted adapter — check your pinned address |
| HSP-RCPT-PROOF | amount / recipient / token mismatch — exact-amount only; no fee-on-transfer or multi-log txs on the public path |
| HSP-RCPT-OBS-REUSED | this on-chain transfer already settled another mandate (one settlement, one payment) |
| HSP-MAND-REQ-INSUFFICIENT | mandate doesn’t require everything the deployment’s policy floor demands |
HSP is zero-custody: your wallet settles; the Coordinator only signs observations with an
adapter key — it cannot move funds. A relying party (a merchant, an auditor, a platform) pins the
adapter’s address once (from GET /chains, out-of-band) and runs the
verifier itself. If the Coordinator lied, your independent verification fails — that is the whole design.
Record the adapter address once and hardcode it. Never re-fetch it from the party you are trying not to trust.
Example and MCP agent keys are for small testnet amounts. Anything that matters belongs in a real wallet — use the eip1193 path; the key stays in the wallet.
Set HSP_MAX_AMOUNT_BASE_UNITS and HSP_DAILY_CAP_BASE_UNITS for any agent-held key; the faucet rate-limits per address & IP; team API keys are per-team — don’t share.
The hackathon sandbox runs the full stack on HashKey Chain testnet so you run nothing yourself. One HTTPS host fronts every service; point your SDK at these base URLs:
| You set | Value | What it is |
|---|---|---|
| HSP_COORDINATOR_URL | <COORDINATOR_URL> | the hub — register, observe, verify, Explorer |
| HSP_ISSUER_URL | <COORDINATOR_URL>/issuer | mock compliance issuer — testing only (kyc + sanctions) |
| HSP_FACILITATOR_URL | <COORDINATOR_URL>/facilitator | x402 v2 facilitator (for scenarios 2 & 4) |
| HSP_API_KEY | get one → /register | your team’s write key (Bearer) — self-service |
| HSP_CHAIN | hashkey-testnet | chain name + the pinned stablecoin |
Faucet (testnet gas + USDC): /faucet/ — rate-limited per address & IP.
The adapter address to pin for independent verification is published at /chains.
No external accounts to register. Grab your own API key at /register (self-service), and generate your own testnet wallet (a private key) funded from the faucet. No Circle, Coinbase, KYC-provider, RPC, or npm sign-up — the mock issuer and the x402 facilitator are part of this sandbox. The Explorer is public: looking up a payment by its id needs no key (an API key is only for browsing the full payment list).
| Name | Chain ID | Stablecoin | Note |
|---|---|---|---|
| hashkey-testnet | 133 | USDC · 6 dec | faucet-friendly — start here · RPC testnet.hsk.xyz |
| anvil-dev | 31337 | per-run MockERC20 | local development |
| hashkey | 177 | USDC.e · 6 dec | mainnet — real money |
| ethereum | 1 | USDC · 6 dec | mainnet — real money |
Registry defaults shown; when this page is served by a live Coordinator the table reflects its actual configuration.