How it works.
A three-rail gateway from Stripe (card), USDC on Base (x402), and ck-USDC on ICP into the Internet Computer's Cycles Minting Canister. Cycles land on the canister you name, or on a Cycles Ledger account (yours, or on a card order one you name) minus 0.0002T in Cycles Ledger fees. Every order's cycles are paid from CyclePay's backend canister ICP balance as soon as the payment clears, and CyclePay refills that balance from what the rails collect.
Architecture overview.
The service converts fiat and stablecoin payments into ICP cycles through three components working together: a payment processor (one of three rails), a backend server, and an on-chain canister. All three rails share the same fulfillment path; only the ingress differs.
Payment processing
Stripe (card), x402 facilitator (USDC on Base), or the ck-USDC ICRC-2 ledger (ck-USDC on ICP). Each rail emits a confirmation: Stripe webhook, on-chain Transfer event, or ICRC-2 transfer_from block index.
Backend server
Hono backend on Railway (backend-production-9bca7.up.railway.app). Verifies payment per rail, enforces idempotency, calls the canister to fulfill, and runs the auto-refill worker (30-min cadence) and treasury sweepers.
CyclePay canister
Transfers ICP to the CMC via icrc1_transfer. For a canister it calls notify_top_up and the cycles land on the target canister. For a Cycles Ledger account it calls notify_mint_cycles into a CyclePay holding subaccount for that order, then transfers the cycles on the Cycles Ledger to the beneficiary. Same path regardless of which rail paid.
Three payment rails, one canister flow.
Pay how you like. The three rails take payment differently (card, EVM signature, ICRC-2 approval), but they all converge on the same canister method that mints cycles via the CMC. Pick the rail that matches your wallet; the resulting cycles are identical.
Stripe (card)
POST /v1/checkout returns a Stripe-hosted checkout URL. The user pays in a
browser; Stripe fires a webhook; the backend calls the canister. Best for human flows and
saved cards driving auto-refill. Treasury is rebalanced manually through Coinbase.
USDC on Base (x402)
POST /v1/x402/topup. First call returns 402 Payment Required with
payment requirements in the PAYMENT-REQUIRED header; sign an EIP-3009
transferWithAuthorization, repost with it in PAYMENT-SIGNATURE (x402 v2;
legacy X-PAYMENT also works). Hot wallet receives at 0x206389Da055eC021d003326bA2F23DA39bad2869.
Manual treasury rebalance until DFINITY ships Base bridge support.
ck-USDC on ICP
POST /v1/ckusdc/topup. The user signs an ICRC-2 approve for
amount + fee; the backend pulls via icrc2_transfer_from. Cycles
land synchronously in the response. The ck-USDC lands in CyclePay's hot wallet, and a
server worker swaps it to ICP on ICPSwap, so no bridge is needed.
See the API reference for endpoint shapes and request/response schemas, and agent security for what a delegation lets its holder do. Refilling the backend canister's ICP: ck-USDC is swapped to ICP and sent there automatically; card and Base USDC proceeds are converted to ICP and moved there by hand.
CyclePay from your terminal.
Sign in to icp-cli with the same Internet Identity you use on this site, then
check balances and spend cycles from the terminal. Buying cycles still finishes on this
website for now.
Requirements
icp-cli0.3.0 or later (current release: 1.6.0)- A browser on the same machine, for the sign-in
- An Internet Identity (id.ai)
Step 1 · Install icp-cli
npm install -g @icp-sdk/icp-cli
icp --version Step 2 · Turn on CLI access
Sign in at id.ai, open your identity settings and turn on CLI access. It is off by default and is stored per browser, so turn it on in the browser you will sign in with. Without it, the sign-in page shows “CLI access not enabled”.
Step 3 · Sign in from the terminal
icp identity link web cyclepay --app 4w3yu-fyaaa-aaaac-beeoa-cai.icp0.io
Press Enter when prompted. Your browser opens id.ai; sign in with your passkey and the
delegation comes back to icp-cli over a localhost callback.
--app gives the terminal the same principal this website shows you, so your
history, ledger balance and auto-refill rules are the same account on both.
Step 4 · Check it is the same principal
icp canister call --query --identity cyclepay -n ic ivcos-eqaaa-aaaab-qablq-cai whoami '()'
# prints your principal: it should match the one shown on this site
Pass --identity cyclepay on each command instead of making it your default
identity, so your deploys keep using the identity you chose for them. When the delegation
expires, run icp identity reauth cyclepay and sign in again.
What you can do from the terminal today
Check your Cycles Ledger balance
( um5iw-rqaaa-aaaaq-qaaba-cai ):
icp cycles balance --identity cyclepay -n ic Top up a canister from your ledger balance (no CyclePay call needed):
icp canister top-up <canister-id> --amount 500b --identity cyclepay -n ic Transfer cycles to another principal:
icp cycles transfer 1t <receiver-principal> --identity cyclepay -n ic What still happens on the website
- Buying cycles. No released
icp-clicommand buys from CyclePay yet, so finish purchases on this site. Card payment opens Stripe in the browser either way. A purchase can go to a canister or to a Cycles Ledger account. A terminal tool can open/?dest=ledger&to=<principal>&usd=<1..200>&from=icp-clito preselect its identity's Cycles Ledger account and the amount (or/?dest=canister&to=<canister id>for a canister); the user pays by card, without a sign-in while guest checkout is on and the amount is $5 to $100, otherwise after signing in. - ck-USDC approvals. The ICRC-2
approveis signed in the browser on this site.
Buy cycles from any agent.
CyclePay is designed to be agent-actionable. The endpoints are stable and the auth is a
single Bearer header. Drop the /llms.txt URL or the
snippet below into your agent's context and it can request top-ups on demand.
Auth today
The Bearer token is an Internet Identity delegation from a browser sign-in on this site
(see the API reference). No released icp-cli command buys
from CyclePay yet, so finish purchases on this site; the
CLI Quick Start covers what the terminal can already do. Treat the token like a password: keep it out of logs, chats and agent
transcripts.
Three ways an agent buys cycles
1. Stripe (card). The agent posts a request, receives a hosted Stripe URL, and shows it to the user to complete payment. After Stripe confirms, cycles are delivered via the CMC.
# $TOKEN is a browser sign-in delegation (see /api).
# Note: API base is the Railway backend, NOT the asset canister.
curl -X POST https://backend-production-9bca7.up.railway.app/v1/topup \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"destination_type":"canister","canister_id":"<your-canister-id>","amount_usd":10}'
# Response:
# { "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_...", ... }
# Open checkout_url in a browser to finish payment. 2. USDC on Base via x402. The endpoint speaks x402 v2, so standard x402 v2
clients work with it; to pay more than $1, raise the client's per-payment cap
(spendControls.maxAmountPerPayment, $1 by default). The first POST returns
402 Payment Required with the payment requirements in the
PAYMENT-REQUIRED header (and the body). The caller (or its wallet) signs an EIP-3009
transferWithAuthorization, sends it base64-encoded in the
PAYMENT-SIGNATURE header (legacy X-PAYMENT also works), and POSTs the
same body again. The receipt comes back in PAYMENT-RESPONSE. The facilitator settles the USDC
on-chain to CyclePay's Base wallet (0x206389Da055eC021d003326bA2F23DA39bad2869),
and CyclePay's ICP pays for the cycles right away. Auth is optional for a canister: pass a
delegation token if you want the order tied to your II principal and listed in your history.
A ledger destination requires it and credits that principal's Cycles Ledger account.
# Step 1: discover requirements (no payment header)
curl -X POST https://backend-production-9bca7.up.railway.app/v1/x402/topup \
-H "Content-Type: application/json" \
-d '{"destination_type":"canister","canister_id":"<your-canister-id>","amount_usd_cents":1000}'
# 402, PAYMENT-REQUIRED header = base64 of the body:
# { "x402Version": 2, "resource": { "url": "...", ... }, "accepts": [{ "scheme":"exact", "network":"eip155:8453", "asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount":"10000000", "payTo":"0x...", "maxTimeoutSeconds":300, "extra":{ "name":"USD Coin", "version":"2" } }], ... }
# Step 2: sign EIP-3009 transferWithAuthorization for that asset/payTo/amount,
# send it as PAYMENT-SIGNATURE, repost the same body. The route verifies,
# settles via the facilitator, RPC-confirms the on-chain transfer, then
# fulfills cycles; the receipt is in the PAYMENT-RESPONSE header.
curl -X POST https://backend-production-9bca7.up.railway.app/v1/x402/topup \
-H "PAYMENT-SIGNATURE: <base64 x402 v2 PaymentPayload>" \
-H "Content-Type: application/json" \
-d '{"destination_type":"canister","canister_id":"<your-canister-id>","amount_usd_cents":1000}' 3. ck-USDC on ICP.
Signed in with Internet Identity, the customer signs an ICRC-2 approve
from that same II principal (Plug, OISY, NNS dapp and Stoic are not supported today)
granting CyclePay's hot wallet (nsp5q-aelxk-xax2u-3sqxb-xeod6-fduhf-zmoir-qxpp3-xusb4-nez2q-5ae) permission to pull
amount + 0.01 ck-USDC. The approve itself costs another 0.01. POST to /v1/ckusdc/topup with the II Bearer token; the server
pulls funds via icrc2_transfer_from and fulfills cycles synchronously
in the response, with no checkout URL. The fulfillment path is shared with Stripe
and x402. Best-fit rail for ICP-native users: the ck-USDC lands in CyclePay's hot wallet
and a server worker swaps it to ICP on ICPSwap, so this rail needs no bridge.
# Pre-flight: see the user's ck-USDC balance (handy for "you have $X" UX)
curl -X GET https://backend-production-9bca7.up.railway.app/v1/ckusdc/balance \
-H "Authorization: Bearer $TOKEN"
# Step 1: as the same II principal, sign an ICRC-2 approve on the ck-USDC
# ledger (xevnm-gaaaa-aaaar-qafnq-cai) with spender
# nsp5q-aelxk-xax2u-3sqxb-xeod6-fduhf-zmoir-qxpp3-xusb4-nez2q-5ae
# for amount + 10_000 (0.01 ck-USDC). The Buy page on this site does this
# from the user's II identity automatically.
# Step 2: pull and fulfill in one call
curl -X POST https://backend-production-9bca7.up.railway.app/v1/ckusdc/topup \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"destination_type":"canister","canister_id":"<your-canister-id>","amount_usdc_atomic":"10000000","request_id":"<fresh UUID per purchase>"}'
# 10_000_000 = 10.00 USDC (6 decimals), a multiple of 10_000 ($0.01).
# $1 to $200 per order. request_id: a fresh UUID per purchase, the same
# one on every retry (required for safe retries).
# Response: { success, order_id, cycles, cycles_raw, block_index, destination_type, canister_id } Drop this into your agent's context
Add a single line pointing at /llms.txt to any
Claude Code CLAUDE.md, Cursor .cursor/rules, Windsurf rules
file, or system prompt:
# To buy ICP cycles, fetch and follow:
# https://www.cyclepay.org/llms.txt (or https://4w3yu-fyaaa-aaaac-beeoa-cai.icp0.io/llms.txt)
# Index of the CyclePay API, auth, CLI flow, and copy-paste curl examples. For a longer copy-pasteable agent config block, see the CLAUDE.md snippet on the API page. For the full endpoint list, see the API reference.
How it works end-to-end.
Three steps take you from payment to cycles deposited, regardless of which rail you choose. The entire flow typically completes in under 15 seconds.
// @icp-sdk/auth/client
const auth = new AuthClient();
await auth.signIn({
maxTimeToLive:
BigInt(24 * 60 * 60 * 1e9) // 24h
}); # Card via Stripe: returns checkout_url
POST /v1/checkout
Authorization: Bearer $TOKEN
{ "canister_id": "...", "amount_usd": 25 }
# USDC on Base: wallet signs EIP-3009
POST /v1/x402/topup
PAYMENT-SIGNATURE: <base64 EIP-3009 sig>
{ "canister_id": "...",
"amount_usd_cents": 2500 }
# ck-USDC on ICP: wallet signs ICRC-2
POST /v1/ckusdc/topup
Authorization: Bearer $TOKEN
{ "canister_id": "...",
"amount_usdc_atomic": "25000000",
"request_id": "<fresh UUID>" } notify_top_up and the cycles land on the target canister. For a
Cycles Ledger order it calls notify_mint_cycles into a CyclePay holding subaccount
for the order, then transfers the cycles to the beneficiary's Cycles Ledger account (two 100M
fees, 0.0002T). Either way the CMC burns the ICP and mints cycles at its current ICP/XDR rate.
// On-chain flow:
// 1. icrc1_transfer: ICP → CMC subaccount
// 2a. Canister mode:
// notify_top_up → cycles on target
// 2b. Ledger mode:
// notify_mint_cycles → holding account
// icrc1_transfer → beneficiary's
// Cycles Ledger account (−0.0002T)
//
// 1T cycles = 1 XDR (set by the protocol)
// Rate: CMC's current ICP/XDR rate Why Internet Identity over CLI key files
Internet Identity replaces long-lived local keys with hardware-backed passkeys and biometric auth.
Your root of trust lives in your device's secure enclave, not in a key that icp-cli
keeps on this machine.
# Private key kept by icp-cli on this machine
# (OS keyring by default, or a PEM under
# identity/keys/ with plaintext storage)
# Issues:
# - Can be exported, copied or leaked
# - No built-in expiry or scoping
# - Each agent/machine = another key
# - If the machine is wiped, the key is gone # Passkey in device secure enclave
# (TPM, Secure Enclave, or FIDO2 key)
# Advantages:
# - Hardware-backed, can't be copied
# - Biometric or PIN to authenticate
# - II issues time-limited delegations
# - Anchor recoverable via recovery devices
# - No PEM files on disk to manage Security model.
Multiple layers of protection ensure every payment results in exactly one cycle deposit, on every rail.
Journaling
Every fulfillment writes a JournalEntry to canister stable storage before
the ICP transfer. States: Pending → IcpTransferred →
CyclesDelivered. Every 60 seconds a timer retries the CMC step for entries whose ICP
already moved; entries survive canister upgrades via ic-stable-structures.
Idempotency
Per-rail keys, all converging on the canister's
PROCESSED_INTENTS map. Stripe: pi_xxx. x402:
x402:<SHA-256 of network|asset|from|nonce> (the EIP-3009 authorization's fields, lowercased). ck-USDC: ckusdc:<block_index>.
Auto-refill uses a deterministic hour-bucketed key per (subscription, hour). Replays at any
level short-circuit safely.
Access control
fulfill_order restricted to authorized callers (server principal) and
controllers. II Bearer tokens are verified server-side hop-by-hop against each prior
signer's DER public key; a bare principal sent as the token is refused.
From payment to cycles.
The complete path a payment takes to land on a canister or a Cycles Ledger account. The Stripe rail is shown below; the x402 and ck-USDC rails replace steps 1 and 2 with an EVM signature or an ICRC-2 approve respectively, then merge into the same canister flow at step 3.
┌──────────────────────────────────────────────────────┐
│ 1. USER pays. Pick a rail: │
│ ├─ Stripe Checkout ($25 card payment) │
│ ├─ x402: sign EIP-3009 transferWithAuthorization │
│ │ for USDC on Base; settle via facilitator │
│ └─ ck-USDC: sign ICRC-2 approve(amount + fee); │
│ server pulls via icrc2_transfer_from │
│ │
│ 2. RAIL confirms │
│ ├─ Stripe webhook → signature + idempotency │
│ ├─ x402: facilitator + RPC verify on-chain xfer │
│ └─ ck-USDC: ICRC-2 returns block index │
│ │
│ 3. BACKEND calls CyclePay canister (fulfill_order) │
│ └─ Passes destination + USD amount │
│ (net of the rail fee; destination = │
│ canister ID or ledger beneficiary) │
│ + per-rail payment_intent_id (idempotent) │
│ │
│ 4. CANISTER calls ICP Ledger (icrc1_transfer) │
│ └─ Sends ICP to CMC subaccount │
│ └─ ICP amount derived on-chain from the CMC │
│ rate and the canister's XDR/USD rate │
│ │
│ 5. CANISTER calls CMC, by destination: │
│ ├─ [canister] notify_top_up │
│ │ └─ CMC burns ICP, mints cycles │
│ │ └─ Cycles → target canister │
│ │ │
│ └─ [ledger] notify_mint_cycles │
│ └─ CMC mints into a CyclePay holding │
│ subaccount for the order │
│ └─ icrc1_transfer on the Cycles Ledger │
│ → beneficiary, minus 2 × 100M fees │
│ │
│ CMC: rkp4c-7iaaa-aaaaa-aaaca-cai │
│ 1T cycles = 1 XDR (rate varies daily) │
│ │
│ 6. CYCLES delivered │
│ └─ Journal entry written on-chain │
│ └─ Stripe: order completes asynchronously after │
│ webhook; x402 + ck-USDC: cycles return │
│ synchronously in the API response │
└──────────────────────────────────────────────────────┘ Reference canisters.
Key system canisters on the Internet Computer that the cycles service interacts with.
What a delegation lets its holder do.
An Internet Identity sign-in hands out a time-limited delegation for your CyclePay principal. Neither the terminal sign-in nor the web sign-in limits it to particular canisters, so treat both as you would a password for that principal until they expire.
Terminal and web sign-ins
# Session key: OS keyring (default)
# Delegation: icp-cli identity directory
# Lifetime: 8 hours (id.ai default)
# Canisters: all
#
# Together they let icp-cli sign any
# call as your CyclePay principal,
# including icrc1_transfer on the
# Cycles Ledger, which spends the
# balance you bought here.
#
# Anything that can run icp as your
# OS user can do the same until the
# delegation expires. # The Bearer token for the CyclePay API
# is the delegation chain itself.
# Anyone who copies it can call the API
# as you until it expires:
#
# place orders (one-off card orders
# still need checkout in a browser)
# create auto-refill rules, which
# charge your saved card off-session
# with no checkout
# spend any ck-USDC allowance you
# granted CyclePay that is unused
#
# It cannot sign IC calls on its own:
# that needs the session key, which
# stays in the browser. Giving an agent less
If an agent only needs to reach one canister, sign it a narrower delegation from your linked identity instead of sharing the identity itself:
icp identity delegation sign --identity cyclepay \
--key-pem agent-public-key.pem \
--duration 24h \
--canisters <your-budget-canister> - Scope by canister. Point
--canistersat a wrapper canister you control, never at the Cycles Ledger (um5iw-rqaaa-aaaaq-qaaba-cai): a delegation for the ledger can transfer the whole balance. - Expose only the methods you need. A budget canister exposes only
top_up_canisterto agents, with a per-call cap, a daily cap and an optional allowlist. Register the agent's principal withadd_agent, and never delegate from the identity that owns the budget canister: the owner can withdraw its whole balance. - Short lifetime. Pick the shortest
--durationthat covers the job, and never sign 30-day delegations for agent use. It also stops working when the id.ai delegation behind it expires. - Reviewed agents only. The agent's full source plus its prompt boundary determine what it can be coaxed into doing, so treat both as trusted code.
Zero margin, transparent fees.
Zero markup on top of the protocol-defined cycle rate. CyclePay charges $0; you pay only the third-party processing fee for the rail you choose.
# Pricing model
# ──────────────────────────────────────
Cycle rate: 1T cycles = 1 XDR (set by the protocol; only an
NNS vote can change it)
XDR rate: USD per XDR, refreshed by the CyclePay canister
from the Exchange Rate Canister (CXDR/USD) every
6 hours (public query get_xdr_rate_status on 2bwus).
Quotes before you pay come from /v1/rate and can
differ slightly from what is minted.
Service fee: $0.00 (zero margin, every rail)
Per-rail fee (third-party, pass-through):
Stripe (card): 2.9% + $0.30 per transaction
(auto-refill charges the exact shortfall
plus this fee)
USDC on Base (x402): none to you (the facilitator pays the gas)
ck-USDC on ICP: 0.01 ck-USDC per ledger operation
(approve + transfer = 0.02, paid on top)
Cycles Ledger delivery: ledger orders deliver 0.0002T less
(two 100M Cycles Ledger fees)
# Example: $25 card purchase via Stripe
# ──────────────────────────────────────
Gross: $25.00
Stripe fee: -$1.03 (2.9% of $25 = $0.725, rounded half up to $0.73, + $0.30)
Net converted: $23.97 (the checkout quote and the mint use the same figure)
Cycles minted: net_usd / canister XDR/USD rate = T cycles
# Example: $25 ck-USDC purchase
# ──────────────────────────────────────
You pay: 25.02 ck-USDC (25.00 + two 0.01 ledger fees)
Net converted: 25.00 ck-USDC
Cycles minted: net_usdc / canister XDR/USD rate = T cycles
# The CMC mints at its current ICP/XDR rate, which it
# refreshes from the Exchange Rate Canister (XRC)
# every few minutes.