Demo Gateway Operations
1.0.1 patch deployment
The public website and demo Worker run 1.0.1. Actual Worker modules, current configuration, secret bindings and the existing Durable Object namespace were verified. The pre-existing funded head was preserved. A user-operated public browser payment on a separate network advanced it from 1.4 to 1.6 Testnet KAS; the user confirmed the identical retry, and the agent independently verified selected-chain acceptance and the successor UTXO. The automated local browser payment/retry also passed. See the deployment proof.
The original test caller correctly hit the one-grant rolling 24-hour quota. Bounded logging confirmed that category; the approved Worker was restored and its module hashes rechecked. No quota or payment state was reset.
This patch retains escrow-v5/head-v2 and demo-gateway-state-v2. Routine patch
deployment preserves active payment state. The historical clean-cutover
procedure below is not needed for this patch.
1.0.0 demo cutover
This section records the original 1.0.0 website and demo Worker cutover. The funded browser payment and same-transaction retry passed through the actual public origins; see the deployment proof. The earlier local qualification and RC2 deployment records remain separate historical evidence. The steps below repeat the fresh-state cutover; do not reset active payment state on routine redeploys.
-
Run the authenticated transport relay on a Node host behind an HTTPS reverse proxy. The public demo uses the Cloudflare Container host in the existing demo account; this packages the same bounded relay and needs no separate server. It has no wallet, grants or payment database. For another Node host, create a private 0600 token file with at least 32 random bytes and a private JSON config:
{ "endpoints": ["wss://vector-10.kaspa.green/kaspa/testnet-10/wrpc/json"], "tokenFile": "/private/demo-pnn-token", "host": "127.0.0.1", "port": 8789 }npm ci node scripts/demo-pnn-relay.mjs /private/demo-pnn-relay.jsonOnly explicitly listed endpoints and seven required RPC methods are allowed. Requests/responses are capped at 4,259,840 bytes, sockets enforce the same reassembled-message cap, calls time out, and concurrency is limited to 32. HTTP redirects are rejected. Keep this transport token out of the browser.
-
Configure the candidate Worker with the same PNN endpoints,
KASPA_X402_PNN_RPC_ORIGIN=https://your-private-relay.example, and secretKASPA_X402_PNN_RPC_TOKENcontaining the relay token. Keep the explicit Testnet trust policy and configure the existing admission/admin secrets.KASPA_X402_RELEASE_VERSIONis1.0.0. Enable the gateway and hash-chain demo only when the relay and fresh state are ready. A failed/missing relay must not fall back to an unbounded native WebSocket. -
Use a new private data directory with the existing
demo:hash-chain initoperator command to create a funded head-v2 genesis. Retain the owner key, signed setup and registration privately. Register itscloudflare-head.jsonusing the admin endpoint. The candidate'sdemo-gateway-state-v2object is separate from RC2'sdemo-gateway-v1.0.0-rc.2; do not copy old heads/channels. Preserve RC2 recovery keys and refund outstanding old channels separately. -
Before authorizing deployment, run the actual candidate locally against funded Testnet. The config contains
sdkModule(the Node Kaspa WASM module),rpcUrl(a synced Testnet-10 Borsh endpoint), andwalletFile(an isolated Testnet private-key file). Start with a clean committed candidate:npm run build npm run site:build node verification/proof-demo-release-e2e.mjs --out /private/controlled.json node scripts/proof-browser-demo-testnet.mjs --live \ --config /private/browser-proof.json --out /private/fresh-browser-runThe proof creates fresh head/payer state and exercises the built page through local HTTPS origins, the actual Worker/SQLite DO and the bounded relay. The browser claims the grant, signs/broadcasts on Testnet and receives the paid report. Retry must return the same transaction; the head must advance once. Keep the entire output directory private: it includes recovery keys/grants. Publish only the sanitized report. This is single-source Testnet evidence, not a deployed-host or independent-mainnet claim.
-
After separate deployment authorization, coordinate the Worker and website cutover and run the same paid smoke on the public origins. Update
site/release.json's observed deployment fields only after verifying them. Do not treat relabelling the RC2 gateway, or disabling its payments, as a completed 1.0.0 demo release.
Recorded RC2 operations
Development source uses escrow-v5 and head-v2 with signer-chosen sighash types; reference signing still defaults to ALL. The published/deployed RC2 service described below uses escrow-v4 and head-v1. Follow the signature policy and template transition for a development integration or operator cutover; the existing hosted RC2 service is not evidence of deployment of the new templates.
Status: operations runbook for the deployed RC2 kaspa:testnet-10 gateway.
This runbook describes the public demo service at:
https://demo.kaspa-x402.org
RC2 was published and deployed on 2026-10-01. The hosted gateway uses PNN/WSS evidence for native exact, batch, and hash-chain payments. Release validation covered all 18 funded flows, hosted native/batch payments, three browser hash-chain payments across rotation, and retries after redeployment. The RC2 release reference and gateway deployment record link to the published evidence and identify the release source.
The gateway is an integration target, not a wallet, custodian, faucet, facilitator, mainnet service, or availability commitment.
Operator Controls
The Worker configuration lives in packages/demo-gateway/wrangler.jsonc.
Important non-secret variables:
| Variable | Purpose |
|---|---|
KASPA_X402_GATEWAY_ENABLED |
Set to false to stop protected exact and batch endpoints with HTTP 503. /health, /canary, /metrics, and /supported remain visible. |
KASPA_X402_PAY_TO |
Testnet address receiving exact payments. |
KASPA_X402_SERVER_PUBLIC_KEY |
Testnet server public key advertised in batch escrow terms. |
KASPA_X402_EXACT_AMOUNT |
Exact-payment price in sompi. Must be at least 10000000. |
KASPA_X402_EXACT_PROFILE |
Exact profile: standard-native (default) or optional additive. |
KASPA_X402_BATCH_AMOUNT |
Fixed per-request v1 RC2 batch charge in sompi. |
KASPA_X402_MIN_DEPOSIT_SOMPI |
Batch escrow deposit floor. Must be at least 10000000. |
KASPA_X402_CLAIM_RESERVE_SOMPI |
Advertised v1 RC2 minimum successor reserve R. Must be at least 10000000; the advertised deposit floor must cover the request ceiling plus this reserve. |
KASPA_X402_REFUND_TIMEOUT_DAA_DELTA |
Maximum DAA horizon for the persisted absolute batch timeout. The Worker rolls the timeout only at the minimum-lead boundary. |
KASPA_X402_MINIMUM_REFUND_LEAD_DAA |
Minimum remaining DAA lead required before accepting a batch payment. |
KASPA_X402_GLOBAL_CONCURRENCY |
Deployment-wide cap for in-flight protected requests. Enforced by renewable leases in the gateway Durable Object; default 64, maximum 256. |
KASPA_X402_PER_CALLER_CONCURRENCY |
Per-ingress active lease cap in the same Durable Object; default 8, maximum 256. |
KASPA_X402_ADMISSION_HMAC_KEY |
Secret with at least 32 bytes, required when enabled. HMACs the Cloudflare ingress IP into an opaque admission key. Set as a Worker secret; do not commit it. |
KASPA_X402_SITE_BASE_URL |
Standards site base URL used by canary checks. |
KASPA_X402_RELEASE_VERSION |
Current release version checked against the standards site's /release.json. |
KASPA_X402_GATEWAY_BASE_URL |
Gateway base URL used by canary checks. |
KASPA_X402_HOSTED_EXACT_SETTLEMENT_ENABLED |
Set to true only when the hosted exact verifier, PNN broadcast path, and finality observation are deployed. Additive also requires a durable available head. |
KASPA_X402_CHAIN_BROADCAST_MODE |
pnn for all hosted chain reads, transaction submission, and selected-chain recovery. REST mode is rejected. |
KASPA_X402_PNN_ENDPOINTS |
Comma-separated TN10 WSS submission endpoints. In single-source mode these are one trusted verification authority, regardless of URL count. |
KASPA_X402_CHAIN_TRUST_MODE |
Required when the gateway or hash-chain route is enabled: trusted-single-source or independent-corroboration. A single node remains supported. |
KASPA_X402_PNN_VERIFICATION_AUTHORITIES |
In corroboration mode, JSON array of { "trustDomain": "operator-id", "endpoints": ["wss://..."] } groups. Each group is one administrative authority. |
KASPA_X402_CHAIN_REQUIRED_AUTHORITIES |
In corroboration mode, number of distinct verification authorities required to agree; minimum 2. Conflicts always reject. |
KASPA_X402_HASH_CHAIN_POOL_SIZE |
Maximum registered nonretired hash-chain heads in the shared Durable Object; default 1, maximum 16. |
KASPA_X402_HASH_CHAIN_ENABLED |
Enable hash-chain exact in the existing gateway Durable Object after registering a funded Testnet head. Default: false. |
The Worker must not receive a mainnet, operator wallet, head owner, or faucet key. Hash-chain registration supplies only its limited one-time grant keys. Merchant claim broadcasting is disabled in the hosted gateway package.
trusted-single-source makes the configured PNN group a full payment-authority
dependency. Multiple failover URLs do not corroborate each other. Independent
corroboration is recommended for higher-assurance Testnet-10 deployments; it
does not satisfy the project's mainnet launch gates. A second node is not required for
Testnet-10 operation. The optional corroborated
mode reads origins, UTXOs, acceptance, checkpoint, finality, and selected-chain
lineage from distinct configured trust domains; submission can use separate
endpoints. A conflicting view fails closed. /health exposes the mode and
required authority count without exposing endpoint URLs.
Nearby selected tips can corroborate when their authorization facts agree;
snapshot DAA scores must be within 100, and refund timing uses the highest
verified DAA.
Rusty Kaspa 2.1.0 at pinned commit
01b532e8b553523216471682649693af92f0fd16 filters V2
minConfirmationCount by sink blue-score distance, with a strict
distance > minConfirmationCount test. It does not measure selected-parent
positions. The adapter therefore requests the unfiltered ordered V2 path,
counts positions through its pinned checkpoint (including the accepting
block), and records that measured depth.
Secret variables:
| Variable | Purpose |
|---|---|
KASPA_X402_ADMIN_TOKEN |
Bearer token for additive exact-head registration, reconciliation, and stats endpoints. Set with wrangler secret put; do not commit it. |
v1 RC2 State
The deployed RC2 Worker resolves GATEWAY_STATE with the logical object name
demo-gateway-v1.0.0-rc.2. It uses a clean RC state model and must not import
pre-RC channel or replay state.
Development state-v2 clean cutover
This development branch changes the logical Durable Object name to
demo-gateway-state-v2. Deploying this branch creates a fresh object for all
gateway channel, admission, replay, and hash-chain state. The new Worker never
addresses demo-gateway-v1.0.0-rc.2, so it cannot read its old channel records
or admission leases. There is no state migration or legacy parser. The old
object remains stored but is not used by the new Worker.
Before an approved deployment, finish or refund any funded channels that still
depend on the old object. Then run the build and disabled deployment commands
below. Confirm /health on the new Worker, wait for a scheduled PNN canary to
record a recent DAA checkpoint, and only then run the public-enable deployment
command. A missing or stale checkpoint makes unsigned /exact and /batch
return 503 quote_unavailable. Register new additive and hash-chain heads on
the fresh object before enabling those optional profiles. Do not reuse the old
head or channel records.
Deploy
Do not deploy the v1 RC2 Worker until the v1 RC2 static site is live.
The Worker canary reads KASPA_X402_RELEASE_VERSION, so Worker-first deployment
would fail its current-release check.
From the repository root:
npm --workspace @kaspa-x402/demo-gateway run build
npm --workspace @kaspa-x402/demo-gateway exec -- wrangler deploy --config wrangler.jsonc
The checked-in candidate configuration is fail-closed with
KASPA_X402_GATEWAY_ENABLED=false. After the disabled deployment and funded
operator canaries pass, the separate public-enable deployment is:
npm --workspace @kaspa-x402/demo-gateway exec -- wrangler deploy --config wrangler.jsonc --var KASPA_X402_GATEWAY_ENABLED:true
After deployment, verify:
curl -fsS https://demo.kaspa-x402.org/health
curl -fsS https://demo.kaspa-x402.org/canary
npm run check:demo-gateway
check:demo-gateway starts an isolated local Worker, proves the missing-cache
503 boundary, seeds a deterministic PNN checkpoint in its new Durable Object,
then verifies exact and batch 402 offers, a corrective 402 for a foreign
payment scheme, HEAD, health, and supported kinds. A deployed paid check still
requires an isolated funded testnet wallet.
Batch Collection And Refunds
A successful voucher response records an authorized cumulative charge, not an
on-chain merchant payout. Batch receipt transaction fields identify commitments;
claims and refunds have separate on-chain transaction ids. The demo Worker's
scheduled canary does not collect vouchers or hold a merchant signing key.
A merchant integration must persist vouchers and current channel lineage, run
previewClaim, and configure a claimBuilder for executeClaim to build and
sign the collection transaction. The
claim must exceed its estimated fee and satisfy transaction/output policies.
With the demo's 500-sompi price and 10,000-sompi configured claim fee, two calls
(1,000 sompi) are not economical to claim. That configured fee is not a guarantee
of the actual network fee; estimate against the actual transaction.
Schedule collection with enough time for confirmation and recovery before the absolute refund DAA. The server's minimum refund lead limits new payments; it does not schedule collection. After timeout, the buyer must submit and confirm a refund. The merchant claim branch has no matching expiry, so claim and refund can compete until a spend is accepted. An accepted refund returns the remaining escrow less its fee, including charges the merchant has not collected.
Batch v3 charges a fixed price per accepted call, not a later measured token count. Payment does not prove useful delivery; handlers with non-repeatable side effects need their own durable idempotency/outbox handling. Top-ups require both buyer and provider authorization.
Exact Profiles And Additive Heads
Hosted exact is enabled only when
KASPA_X402_HOSTED_EXACT_SETTLEMENT_ENABLED=true. The default
standard-native profile requires no merchant inventory. Optional additive
also requires at least one durable merchant-owned KIP-10 head. The Worker does
not hold the merchant wallet key and does not create heads from public
requests.
- Create one or more independent merchant-owned KIP-10 additive heads from an isolated TN10 wallet.
- Record each head id/version, current outpoint and amount, serialized script public key, redeem script, additive threshold, status, and timestamps.
- Register the head records with the Worker:
KASPA_X402_DEMO_ADMIN_TOKEN=<token> \
npm run demo:exact-heads -- register --file heads.json
- Confirm head availability:
KASPA_X402_DEMO_ADMIN_TOKEN=<token> npm run demo:exact-heads -- stats
curl -fsS https://demo.kaspa-x402.org/supported
standard-native advertises exact without a head. additive advertises exact
only while a matching head is available. Issuing an unpaid 402 does not reserve,
retire, or consume a head. Settlement atomically claims the advertised current
outpoint, verifies a same-script successor whose delta equals the exact price,
and advances that same durable lineage. Stale competing clients receive a fresh
402 against the current head.
Before each additive offer, the Worker confirms only the selected durable current outpoint against the accepted address UTXO set. It makes at most two bounded candidate attempts, independent of total head inventory. A missing or conflicting selected head becomes unavailable. Full-pool checks are operator work, not anonymous request work. If a known external transaction advanced a head, provide the complete ordered accepted lineage to recover it:
KASPA_X402_DEMO_ADMIN_TOKEN=<token> \
npm run demo:exact-heads -- reconcile \
--head-id <head-id> \
--transactions <first-txid>,<next-txid>
The gateway accepts only a chain in which every transaction spends the prior outpoint, preserves the same script and output index, satisfies the KIP-10 threshold, and ends at the current unspent head. Never use an arbitrary output to the same P2SH address as lineage evidence.
Rollback
Use the Cloudflare Workers deployment list for the project named
kaspa-x402-demo-gateway.
Rollback procedure:
- Identify the last deployment that served valid
/healthand/canary. - Roll back to that Worker version in Cloudflare.
- Confirm
https://demo.kaspa-x402.org/healthreturnsok: true. - Run an unpaid batch request and confirm HTTP
402; confirm/exactreturns503 exact_unavailableunless a working hosted exact settlement path is deliberately enabled and tested. - Record the version, reason, and verification result in the operator notes.
Emergency Disable
Set:
KASPA_X402_GATEWAY_ENABLED=false
Deploy the Worker. Protected endpoints must return:
{ "ok": false, "error": "gateway_disabled" }
Health and canary endpoints stay readable. /health proves only that the
Worker loaded its configuration; use a fresh /canary result and funded proof
to assess chain readiness.
Re-enable by restoring:
KASPA_X402_GATEWAY_ENABLED=true
Chain Evidence Or PNN Outage
The Worker uses the configured Testnet-10 PNN/WSS nodes for all funding,
UTXO, DAA, transaction, and selected-chain evidence. It does not depend on the
public REST transaction index. The reference batch policy still requires 30
confirmations proven by selected-chain traversal; blue-score difference alone
is insufficient. /health shows configuration; /canary probes the node.
- If node evidence is unavailable, disable the gateway.
- Switch only to reviewed
kaspa:testnet-10PNN endpoints; never to mainnet. - Unknown, removed-chain, or incomplete evidence fails closed. Never discard a stored pre-broadcast checkpoint to manufacture a successful retry.
- Re-enable after a fresh
/canary, batch deposit and replay rejection, and exact payment checks pass.
The gateway persists PNN funding snapshots before exact submission and stores accepted transaction receipts for recovery. Offers also preserve a bounded ring of 128 node checkpoints, with one per 300 DAA scores, so a new batch deposit can be located from a checkpoint before its creation. Without a stored checkpoint, historical transactions use a bounded selected-parent traversal (at most 1,024 blocks, subject to the node timeout). A missing or pruned origin is unavailable evidence, not permission to weaken admission. Existing channels retain their verified genesis evidence and lineage cursor. Each batch lineage read stops at a fixed selected-chain checkpoint, including the recent chain data so its cursor cannot skip an unconfirmed transition. Covenant transitions still need the configured 30-block depth; a recent transition keeps the lane pending until that evidence is available. PNN receipts have separate limits of 4,096 records, 64 MiB total, and 64 KiB per record. Capacity exhaustion fails closed and requires operator maintenance; uncertain or active evidence is not automatically discarded.
On restart, resume every batch observer from its stored checkpoint. Process removed blocks before additions. If history is pruned, branching, or otherwise incomplete, keep the lane unavailable; do not reset its checkpoint, scan from a new arbitrary block, or trust peer channel metadata. A removed refund restores refund-only state and requires authoritative reconciliation before retry.
Scheduled Canary
The Worker runs a non-spending scheduled canary every 15 minutes.
The canary checks:
kaspa:testnet-10PNN health and virtual DAA evidence;- the public
payment-requiredschema URL; - the current release metadata with a cache-busted request;
- the public docs index and expected page marker;
- exact support advertisement only when hosted exact settlement is enabled and,
for
additive, a head is available, without claiming that head; - unpaid batch offer shape and deposit floor;
- unsupported foreign payment scheme rejection.
The scheduled canary skips paid exact and replay checks because the Worker does not hold spending keys. Those checks must be run manually from an isolated funded testnet wallet so canary failures cannot spend unbounded funds.
Read the enabled state and latest canary report:
curl -fsS https://demo.kaspa-x402.org/canary
An operator should check /canary after each gateway or site deployment and at
least once per day while the demo is advertised. If ok is false, disable
broad public guidance until the failed check is understood and fixed.
Manual Paid Canary
Use an isolated testnet key. Do not import a key that controls mainnet funds.
Minimum manual paid checks:
- If exact is not deployed with a working KIP-10 settlement path, request
GET /exactand confirm HTTP503 exact_unavailable. - If exact is deployed, request
GET /exactand confirm HTTP402. Forstandard-native, build an ordinary exact native transfer. Foradditive, spend the advertised head and increase its same-script successor by exactly the advertised amount, with no separate merchant payment output. Retry withPAYMENT-SIGNATURE, and confirm the Worker broadcasts the artifact through PNN, observes accepted finality, and returns HTTP200. - Retry the identical paid exact request and confirm idempotent HTTP
200. - Present the same exact transaction to a different resource and confirm conflict rejection.
- Open a batch channel with a deposit-voucher payment and confirm HTTP
200. - Reuse the channel with a voucher-only payment and confirm HTTP
200. - Replay an earlier stale batch voucher after the later voucher and confirm
corrective HTTP
402.
Record transaction ids, output indexes, Worker version, response status, and
PAYMENT-RESPONSE summaries in the operator notes.
The committed hosted exact proof pays /exact with a signed exact transaction
artifact, confirms the Worker-broadcast transaction is accepted, retries the
same payment for idempotency, and rejects the same transaction on a different
resource. In default standard-native mode it needs no admin state. In
additive mode it funds and registers a reusable head first:
KASPA_X402_RPC_URL=<tn10-rpc-url> \
KASPA_X402_FUNDING_WALLET=wallet-key:/path/to/testnet-key \
KASPA_X402_KASPA_WASM_MODULE=/path/to/kaspa.js \
KASPA_X402_EXPECTED_GATEWAY_ORIGIN=https://demo.kaspa-x402.org \
KASPA_X402_EXPECTED_EXACT_PROFILE=standard-native \
KASPA_X402_EXPECTED_EXACT_AMOUNT=20000000 \
KASPA_X402_EXPECTED_EXACT_PAY_TO=<expected-merchant-address> \
KASPA_X402_LIVE_CONFIRM=I_UNDERSTAND_THIS_USES_TESTNET_FUNDS \
npm run proof:hosted-exact
Set KASPA_X402_EXACT_PROFILE=additive and
KASPA_X402_EXPECTED_EXACT_PROFILE=additive, then provide
KASPA_X402_DEMO_ADMIN_TOKEN=<token> to exercise the optional head profile.
The additive proof derives and pins the locally created head address when an
explicit expected payTo is absent. The proof refuses to sign if the gateway
origin, resource, profile, amount, recipient, or network differs from the
operator pins.
Also confirm that unsupported legacy exact-transfer evidence is rejected and
does not return protected content.
Hash-chain Demo
The RC2 hash-chain demo runs in the existing GatewayState Durable
Object. Grant tables use its SQLite storage; payment responses use the existing
gateway ledger. Pages, the Worker, its binding, and the standard exact and batch
routes remain the same. One funded head and manual resets are sufficient for
this Testnet demo. No Container or separate server is required.
The hash-chain route uses the configured Testnet-10 PNNs for fresh UTXOs and selected-chain V2 transaction evidence. It snapshots the payer's accepted funding outputs before delivering a signing grant because spent outputs can disappear from the public REST index. Paid retries recheck selected-chain acceptance. The public REST API is not the hash-chain verification source.
Cloudflare Hosting
-
Build and check the Worker and browser client:
npm run build npm run site:build npm run check:hash-chain-worker npm run check:browser-demo -
Use a private local operator config. Set
dataDirto a new private directory,sdkModuleto the pinned Node WASM SDK,rpcUrlto a synced Testnet-10 node, andwalletFileto the funded Testnet operator wallet. Also setpublicBaseUrltohttps://demo.kaspa-x402.organdadminTokenFileto the private file containing the existing gateway admin token. Run:node scripts/hash-chain-demo.mjs init --config <private-config> --liveThis funds a 1 KAS head and creates 32 fresh one-use grants. It saves
cloudflare-head.jsonwith mode0600alongside the local owner key and grant database. Keep these files outside the published site. -
Deploy the Worker with
KASPA_X402_GATEWAY_ENABLED=true, preserving the live gateway variables and existing admin secret. LeaveKASPA_X402_HASH_CHAIN_ORIGINempty. Publish the fresh head:node scripts/hash-chain-demo.mjs publish --config <private-config>Publication uses the authenticated HTTPS admin route and returns only public head metadata. The operator wallet and owner keys stay local. Use this head only in the Cloudflare demo; the local Node service must not issue its grants.
-
Set
KASPA_X402_HASH_CHAIN_ENABLED=true, deploy the Worker, and deploy the static site to the existing Pages project. Check/hash-chain/statusand/supported, then make a funded browser payment and retry that same payment. Check the existing exact and batch demos too.
An expired disclosed grant automatically enters needsRotation. The same
claimant is limited to one disclosure per 24 hours and its qualifying funding
outpoint remains in a durable tombstone across restart and owner rotation.
Assigned grants from a pre-admission store remain readable for authenticated
retry and selected-payment recovery. Because their original qualifying funding
outpoint is unknown, that payer key cannot qualify another grant; the payer
must use a fresh key and newly funded output for a later grant.
To recover capacity, the operator signs and broadcasts the owner rotation from
its own wallet, waits for selected-chain acceptance, then sends the rotation
transaction ID, an earlier selected-chain checkpoint, and the matching fresh
private grant chain to authenticated HTTPS POST /admin/hash-chain/recover.
The Worker verifies that the selected transaction spends the quarantined head
and installs the new guard only after acceptance. It never holds or uses the
owner private key. Register independent funded heads up to the configured pool
size before service; another ready head can continue issuing while one needs
rotation. Ordinary exact and batch routes remain available during recovery.
Caller Admission
The Worker derives an opaque caller identity from
Cloudflare's ingress IP and
sets X-KASPA-X402-DEMO-CALLER on its private Durable Object call. The service
returns that identity with the quote so the browser binds payment to the same
caller. The four-live-quote limit applies per public IP; users sharing an IP
share that limit. Use the same public IP for quotes, payments, and retries.
Resource requests without trusted caller metadata are unavailable. Local
Worker checks must supply CF-Connecting-IP; direct Node service checks also need
the proxy bearer token and a 64-character lowercase hex caller header.
Optional Node Hosting
For a separate Node host, the Worker can proxy the same resource and grant routes under the public origin. Use this option instead of Cloudflare hosting. This optional Node adapter still uses a REST index for selected-chain reads; it is not the PNN-only configuration deployed by the RC2 public gateway.
Build the workspaces first (npm run build). Use Node 22.13 or newer and the
pinned Rusty-Kaspa 2.0.0 Node WASM SDK, including its websocket dependency.
Create a private config file, outside the published site, with:
{
"dataDir": "./.kaspa-x402-live/hash-chain-demo",
"sdkModule": "./.kaspa-x402-live/runtime/sdk-v2.0.0/kaspa.js",
"rpcUrl": "wss://your-testnet-10-node/kaspa/testnet-10/wrpc/borsh",
"apiBase": "https://api-tn10.kaspa.org",
"publicBaseUrl": "https://demo.kaspa-x402.org",
"walletFile": "./.kaspa-x402-live/operator-wallet.json",
"proxyTokenFile": "./.kaspa-x402-live/hash-chain-proxy-token",
"port": 8788
}
The operator wallet file contains a funded Testnet private key as plain hex or
JSON with a private_key field. Wallet and token files must have mode 0600.
Generate a random proxy token of at least 32 characters and save it privately.
The service listens on loopback by default; expose it through an HTTPS reverse
proxy or tunnel. Keep its data directory on persistent disk.
-
Create the demo head with explicit Testnet broadcast permission:
node scripts/hash-chain-demo.mjs init --config <private-config> --liveThis funds a 1 KAS merchant head and installs 32 one-use grants. It waits for 30 genesis confirmations. The private directory holds the encrypted issuer database, its encryption key, and the owner key.
-
Start the Node process:
node scripts/hash-chain-demo.mjs serve --config <private-config>The same private directory holds
payments.sqlite, which saves exact settlement attempts, handler results, and paid responses. Restarting the process preserves identical paid retries, including after quote expiry. Keep both databases together on persistent disk and run one service process. -
Configure the Worker with
KASPA_X402_HASH_CHAIN_ORIGINset to the service's HTTPS origin. SetKASPA_X402_HASH_CHAIN_PROXY_TOKENas a Wrangler secret containing the same token. Deploy this separately from the static website. An empty origin leaves the existing demo working and hash-chain unavailable. -
Check
/hash-chain/status,/supported, and an unpaid/hash-chain/reportquote. Then run one funded payment in the browser. The default price is 0.2 KAS. The target fee is 0.01 KAS; small change may raise it to at most 0.1 KAS, as shown before payment and in the result.
For an abandoned grant or an exhausted chain, stop the Node process, wait for any assigned grant to expire, and run:
node scripts/hash-chain-demo.mjs status --config <private-config>
node scripts/hash-chain-demo.mjs rotate --config <private-config> --live
node scripts/hash-chain-demo.mjs serve --config <private-config>
Rotation uses the owner key and a separate funding input for its fee, preserves
the merchant value and covenant ID, and installs a fresh set of grants. It
does not reuse an abandoned key. If setup or rotation was interrupted after
submission, recover --config <private-config> checks the saved transaction
and records its accepted result without broadcasting again. Resolve a pending
setup before another init/rotation. Keep the pending setup file private.
The service uses selected-chain PNN evidence and fresh PNN UTXO reads for the current head. If the configured Testnet nodes fail, it stays unavailable/pending. Local simulation checks do not establish live payment acceptance. Enable the public flow only after the deployed paid browser check.
Durable State Policy
The hosted gateway uses one SQLite-backed Durable Object. It stores exact replay records, payment identifiers, batch channels, settlement commitments, locks, rate counters, metrics, and the latest canary report.
Each v1 RC2 batch row also owns its immutable covenant launch manifest, append-only accepted/removed lineage journal, durable selected-chain checkpoint, and atomically derived current head. Those fields must be committed together.
Policy for the public release candidate:
- durable state is operational evidence, not a user account database;
- no private keys or wallet seeds are stored;
- no unauthenticated backup, export, or admin read route is exposed;
- additive exact-head admin routes require
KASPA_X402_ADMIN_TOKEN; - state may be reset during Testnet incidents after the gateway is disabled and the reset is disclosed in operator notes;
- production operators should design their own backup and state-partitioning policy before using this code outside the hosted demo.
For v1 RC2, demo-gateway-v1.0.0-rc.2 is the authoritative Testnet state.
Re-register only independently verified, still-unspent additive heads after a
deliberate reset, and require batch clients to create new v1 RC lanes.
Rotate Addresses And Keys
Use this when a testnet address is too noisy, a test key is suspected exposed, or a clean public demo history is needed.
- Disable the gateway.
- Generate a new
kaspatest:pay-to address using an isolated testnet wallet. - Generate a new server public key for batch terms. Keep any private signing material outside the Worker.
- Update
KASPA_X402_PAY_TOandKASPA_X402_SERVER_PUBLIC_KEY. - Keep the current release's logical Durable Object name unless an incident explicitly requires another disclosed fresh-state cutover.
- Deploy and verify
/health,/canary, unpaid offers, paid exact only when hosted exact settlement is enabled, batch deposit-voucher, voucher-only reuse, and replay rejection. - Re-enable the gateway.
Incident Note Template
Date:
Operator:
Worker version:
Incident:
User-visible impact:
Gateway enabled state:
Chain evidence:
Canary result:
Actions taken:
Paid evidence affected:
Follow-up:
Keep incident notes factual and testnet-scoped.
Source: /docs/demo-operations.md