The gateway is live in simulation mode on mainnet: https://cyclepay.raymondk.co,
frontend shy4u-4qaaa-aaaay-aadhq-cai, backend saz2a-riaaa-aaaay-aadha-cai.
The canister publishes a state hash: one SHA-256 over every asset it serves, each
one's bytes in every encoding, its content_type and its response headers, plus the
redirect rules in match order. Compute the same number from your own build and compare.
You need icp, Node, and a Rust toolchain: the hash is defined by the certified-assets
project's own preparation code, so computing it means building that project's verifier
once from source.
git clone --recurse-submodules https://github.com/marc0olo/cyclepay && cd cyclepay
git checkout vX.Y.Z # the release the deployment published
npm --prefix src/frontend ci && npm --prefix src/frontend run build
scripts/check-frontend-hash.py -e icThat script reads the pinned certified-assets release out of icp.yaml, builds the
matching verifier, refuses to compare unless the canister reports running that release,
and diffs the two numbers. To run the steps yourself instead:
cargo install --git https://github.com/dfinity/certified-assets \
--tag v0.3.3 --locked state-hash-cli # the release icp.yaml pins
state-hash src/frontend/dist
icp canister call shy4u-4qaaa-aaaay-aadhq-cai state_hash '()' -n ic -o hex | tail -c 65⚠️ Check out the tag, notmain.mainmoves on after a release, and one changed HTML comment is enough to change the hash.⚠️ Build the verifier with--locked, never download one. Without--locked,cargo installre-resolves dependencies, and a newerbrotlipatch emits different bytes for the same input.⚠️ Do not check the frontend's module hash instead. The@dfinity/static-siterecipe installs a pre-built certified-assets wasm, so that hash describes the recipe, not the page.
Every release publishes the module hashes its container build produced. Rebuild the tag yourself and compare your build, the release notes, and the canister:
git checkout vX.Y.Z
scripts/reproducible-build.sh vX.Y.Z # writes release/MODULE-HASHES.txt
icp canister status saz2a-riaaa-aaaay-aadha-cai -n ic -p
gh release view vX.Y.Z # the published hashesAll three must agree, including the # build arch: line: the same commit produces
different bytes on different platforms. RELEASE.md is the procedure
that keeps them in step.
Status: the running module is v0.1.0-beta.1's published build. It was installed
from the container artifact whose hashes that release publishes, and the install gated on
the canister reporting them. No hash is restated here; the commands read the live ones.
No identity, no permissions, nothing installed but icp.
The price you are shown is produced by the code that locks it. quote_previews is a
public query running the same pricing path as order creation, and returns both rate
inputs so the arithmetic is yours to redo:
icp canister call backend quote_previews '(vec { 1_000 : nat })' -e icPricing crosses no trust boundary. Both rates come from canisters, the Exchange Rate
Canister for USD/ICP and the CMC for XDR/ICP, read on a timer. There are exactly three
HTTPS outcalls in the system and all three go to Stripe (create, expire and retrieve a
Checkout Session). See docs/ARCHITECTURE.md.
The operational state is public, so solvency is checkable against the cycles ledger without this canister's cooperation:
icp canister call backend reserve_status '()' -e ic # floor, promised, available
icp canister call backend pricing_status '()' -e ic # both rates, the divisor
icp canister call backend lifecycle_config '()' -e ic # the gate's bounds
icp canister call backend cycles_status '()' -e ic
icp canister call backend recovery_status '()' -e ic
icp canister call backend health '()' -e icThe running canister names its own commit. The recipe embeds the committed
src/backend/dist/backend.did as the canister's candid:service metadata, so you can
find which tree it was built from without any published hash:
icp canister metadata backend candid:service -e ic > /tmp/deployed.did
git show <ref>:src/backend/dist/backend.did | diff - /tmp/deployed.didAn exact match (bar one trailing newline) means the deployed interface is that ref's. It narrows the commit rather than proving the bytes, since commits that do not touch the interface share it.
The reserve floor is enforced by an omission in a type. src/backend/Delivery.mo
declares the cycles-ledger interface the canister may call, and icrc2_approve and the
ledger's withdraw are absent, so they cannot be called. scripts/test-all.sh fails on
a declaration that widens it.
Frontend responses are certified per response: each carries IC-Certificate over
the asset tree and the gateway rejects a response whose certificate does not verify.
Every suite is in the repo and one command runs them all: scripts/test-all.sh,
with docs/TEST-COVERAGE.md stating what is not covered and why.
receipt(orderId) is owner-scoped (caller == order.owner). It returns both rate inputs
and the delivery block index, so a buyer can recompute their own price and confirm the
transfer on the cycles ledger independently.
- The published hash is reproducible on one architecture. The build is pinned to
--platform linux/amd64and the release records it. Rebuilding elsewhere gives different bytes and proves nothing. - The hashes are published by us. Anyone can produce the same bytes, but nobody else counter-signs them.
- Any single controller can upgrade and drain. IC controllers are OR-semantics; the hardening path is a multisig canister as sole controller.
- The webhook secret is plaintext canister state, protected by the confidential
subnet rather than by cryptography. HMAC is symmetric, so a canister that can verify
can also forge. Checkpoint and state-sync confidentiality are confirmed on the target
subnet; attestation coverage of every replica is not.
RUNBOOK.md's confidential-subnet checklist carries it. - A purchase requires an allow-listed principal while test payments are accepted, so a visitor cannot exercise the buying path end to end on the live gateway.