Feature: `icp cycles buy` — buy cycles with a card for the current identity via an on-chain gateway
Maintainer thường phản hồi trong vòng 1 ngày
Một pull request liên quan đã được merge.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 35/100
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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
- Ngôn ngữ chính
- Rust
- Star
- 115
- Fork
- 14
- Merge trung bình
- 19 giờ 57 phút
- Pull request đã merge (30 ngày)
- 33
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của dfinity/icp-cli
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 1 ngày
-
sync plugin OOMs ("memory allocation of N bytes failed") when the target canister already has assetsĐang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
dfinity/icp-cli#703 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 74/100
Maintainer thường phản hồi trong vòng 1 ngày
-
question
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của dfinity/icp-cli
Issue tương tự
-
Bump anybuf version to ^1.0.0Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
wormhole-foundation/wormhole#5062 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
RUSTSEC-2026-0348: Brotli certificate decompression can allocate up to 1 GiB before authenticationĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 60/100
-
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 62/100
-
core: Amount::from_str panics on values above 21M BTC instead of returning an errorCó thể đã có người làm @bhuvan-somisetty đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
crate: spate-s3
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày