Feature: `icp cycles buy` — buy cycles with a card for the current identity via an on-chain gateway
I maintainer di solito rispondono entro 1 giorno
@raymondk ci sta già lavorando.
Dal 7/10/2026.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 35/100
Direzione di ricerca
Read the existing crates/icp-canister-interfaces cycles-ledger interface and commands/cycles/mint.rs and transfer.rs first; the proposed entry point is commands/cycles/buy.rs. The issue specifies gateway methods, UX, polling, and error handling, but leaves decisions such as the default gateway and command naming to maintainers. Done means implementing the agreed command and its tests for order creation, checkout output, waiting or resume, and gateway errors.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
Add icp cycles buy: purchase cycles with a credit card for the CLI's current identity, through an on-chain cycles gateway, with no ICP, no wallet and no browser sign-in. The command creates the order as the local identity, hands the hosted Stripe Checkout URL to the user, and waits until the cycles land on that identity's cycles-ledger account. The natural next command is icp deploy -e ic.
Today a developer with a card and no ICP has to: sign in to a web gateway with Internet Identity, pay, then enable CLI access in id.ai, run icp identity link web <name> --app <origin>, make it the default identity, and verify the principal matches, before icp deploy can spend what they bought. Every one of those steps exists only because the browser identity was the buyer. If the CLI identity is the buyer, all of them disappear.
This was validated end to end on 2026-10-07 against the CyclePay gateway (https://github.com/raymondk/cyclepay) on a local network with a real Stripe sandbox: a plain Ed25519 PEM identity → create_order → hosted Checkout → checkout.session.completed webhook → icrc1_transfer → 7.24 T cycles (minus the 100 M ledger fee) on the PEM principal's cycles-ledger account, driven by a shell wrapper over icp canister call. No gateway change was needed for the purchase itself.
Proposed UX
icp cycles buy --usd <amount> [-n ic | -e <env>] [--identity <name>]
[--gateway <principal>] [--yes] [--no-open] [--no-wait] [--json] [-q]
icp cycles buy --resume <order-id> [...] # continue waiting on an order
Interactive run:
$ icp cycles buy --usd 10 -n ic
Buyer rv4ez-fgjbj-...-vae (identity: default)
Quote $10.00 → 7.238 T cycles (card processing $0.59, rate locked at order creation)
Proceed? [y/N] y
Order 26dd22b2cdf36f27d316ca5d0d19b39e (payable for 35 min)
Pay here: https://checkout.stripe.com/c/pay/cs_test_...
Waiting for payment ⠋ created
paid
delivered
Delivered 7.238 T cycles to rv4ez-fgjbj-...-vae (ledger block 1234)
Balance 7.238 T cycles
Next icp deploy -e ic
Behaviour:
- Always print the URL, and try
open::thatonly on a TTY unless--no-open. Agents, SSH sessions and containers have no browser; the user commonly pays on another device. The payment happens entirely on Stripe's hosted page; the CLI never sees card data. --yesskips the confirmation;--jsonemits one object after creation (order_id,checkout_url,usd_cents,locked_cycles,expires_at) and one at the end (status,cycles_delivered,block_index);-qprints only the URL, then only the final status.- Ctrl-C leaves the order payable until its Stripe deadline and prints
icp cycles buy --resume <id>and the cancel hint.--no-waitreturns right after printing the URL. - Poll
get_orderevery ~3 s. Terminal states:delivered(exit 0),expired/cancelled/abandoned(exit 1),needsReview(exit 1 with "your card was charged; the gateway operator will resolve delivery"). - Pin the quoted quantity: pass
minCycles = quote × 0.95so a rate move against the buyer between quote and creation is refused withquoteChanged { quoted; minimum }instead of silently delivering less; on that error, re-quote and re-confirm. A move in the buyer's favour passes through. - Destination is always the caller's own default-subaccount cycles-ledger account. The gateway refuses anything else (
destinationNotOwned), so there is no--toflag. To fund a canister afterwards:icp canister top-up.
Gateway interface
The gateway is a Motoko canister; mainnet backend saz2a-riaaa-aaaay-aadha-cai (the frontend shy4u-4qaaa-aaaay-aadhq-cai serves https://cyclepay.raymondk.co). Five methods are needed; the full .did is src/backend/dist/backend.did in the gateway repo.
type Amount = variant { custom : nat; tier : text }; // USD cents, or a preset id
type Account = record { owner : principal; subaccount : opt blob };
type Destination = variant { cyclesLedgerAccount : Account };
type OrderStatus = variant { created; paid; delivered; expired; cancelled; needsReview; abandoned };
type Order = record {
id : text; status : OrderStatus; lockedCycles : nat;
stripeSessionUrl : opt text; expiresAtNs : opt int;
paidUsdCents : opt nat; expiredBy : opt variant { sessionExpired; sessionFailed };
/* …pricing, destination, owner, problems… */
};
type QuotePreview = record { usdCents : nat; feeCents : nat; netCents : opt nat; cycles : opt nat };
type QuotePreviews = record { quotes : vec QuotePreview; rates : opt Rates };
type Reason = variant { // admission refusals
amountAboveMax : record { usdCents : nat; maxUsdCents : nat };
amountBelowMin : record { usdCents : nat; minUsdCents : nat };
tooManyOpenOrders : record { open : nat; max : nat };
canisterCyclesLow : record { balance : nat; minimum : nat };
reserveShort : record { available : nat; needed : nat };
unboundedGiveaway; buyerNotAllowed;
};
type CreateOrderError = variant {
anonymous; unknownTier : text; tierBelowFees : text;
simulationScaleTooSmall : record { scaledCycles : nat; ledgerFee : nat };
reserveUnavailable; rateUnavailable;
quoteChanged : record { quoted : nat; minimum : nat };
idGeneration; notAdmitted : Reason; destinationNotOwned;
sessionUnavailable : text; cancelledDuringCreation;
};
service : {
quote_previews : (amounts : vec nat) -> (QuotePreviews) query; // public
can_purchase : (usdCents : nat) -> (variant { ok; err : Reason }) query; // advisory preflight
create_order : (Amount, Destination, minCycles : opt nat)
-> (variant { ok : record { order : Order }; err : CreateOrderError });
get_order : (id : text) -> (opt Order) query; // owner-scoped
cancel_order : (id : text) -> (variant { ok : Order; err : CancelOrderError });
}
(Record field names inside Reason abbreviated above; take them from the .did.) quote_previews is computed by the same function that prices the order, so the CLI should display its figure rather than compute one.
Suggested home: a typed interface module in crates/icp-canister-interfaces (same shape as cycles_ledger.rs), and a commands/cycles/buy.rs beside mint.rs / transfer.rs. open is already a workspace dependency (used by identity link web), as are indicatif / render/spinner.rs for the wait.
Error mapping
| gateway answer | CLI message / action |
|---|---|
notAdmitted(amountBelowMin / amountAboveMax) |
state both figures; exit 1 |
notAdmitted(tooManyOpenOrders) |
the identity already has a payable order; print its id, offer --resume <id> or cancel_order; do not create another |
notAdmitted(reserveShort) |
the gateway cannot cover this amount right now; a smaller amount may succeed |
notAdmitted(canisterCyclesLow / unboundedGiveaway), reserveUnavailable, rateUnavailable, sessionUnavailable |
gateway-side, not the buyer's fault; retry later |
notAdmitted(buyerNotAllowed) |
this gateway is in simulation mode and only sells to allow-listed principals |
quoteChanged { quoted; minimum } |
re-quote, show the new figure, re-confirm |
cancelledDuringCreation |
an order was cancelled from elsewhere mid-creation; start over |
anonymous, destinationNotOwned, unknownTier, tierBelowFees, simulationScaleTooSmall, idGeneration |
not reachable from this command when it builds the arguments itself; report verbatim |
Open questions for maintainers
- Default gateway.
--gateway <principal>with a default: hardcoded, or anicp settingskey? A gateway is a third party taking card payments, so whichever default ships is an endorsement decision rather than a technical one. - Name.
cycles buyreads naturally besidecycles mint;cycles purchaseis the alternative. --cycles <amount>as an alternative to--usd, resolved to the smallest USD amount whose quote clears it. Useful for "I need 5 T to deploy"; can be a follow-up.- Tolerance. 5 % matches the gateway's own web frontend; should it be a flag?
- Gateway readiness. The mainnet CyclePay deployment currently runs in simulation mode (Stripe sandbox, allow-listed buyers, a scaling divisor), tracked in the gateway repo. The CLI side is independent of that.
Related
- Gateway: return route for buyers who did not pay from the browser (the Stripe success page assumed the viewer owns the order): https://github.com/raymondk/cyclepay/pull/6
- Agent skill for this flow in
dfinity/icskills(draft, documents this command plus theicp canister callfallback): https://github.com/dfinity/icskills/pull/416
- Lingua principale
- Rust
- Stelle
- 115
- Fork
- 14
- Merge medio
- 21h 31m
- PR unite (30g)
- 28
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di dfinity/icp-cli
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
I maintainer di solito rispondono entro 1 giorno
-
sync plugin OOMs ("memory allocation of N bytes failed") when the target canister already has assetsAperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
dfinity/icp-cli#703 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 74/100
I maintainer di solito rispondono entro 1 giorno
-
question
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di dfinity/icp-cli
Issue simili
-
Custom hosts written as trailing-dot FQDNs (`host.local.`) aren't detected as local backendsForse già presa @roboomp l’ha presa oggi. Apertabug prio:p2 providers triaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
can1357/oh-my-pi#15798 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
requires-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
apache/datafusion-comet#7001 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
-
Add a Storybook section to CONTRIBUTING.mdForse già presa @ChanakyaG2004 l’ha presa oggi. Apertadocumentation good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
FlowwStar/FlowStar#995 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno