diff --git a/README.md b/README.md index 1f826ab..6e54845 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Either way you get a real paid API response. No API keys. No native ETH. No brid | `selat skill run [--param value ...]` | Run an installed agent skill, passing its params as `--flags`. `--max-amount` is hard-capped at **$1.10** for everyone (`isTTY` / `--allow-high-max-amount` do not raise it). Requires an armed session budget (`selat budget start` writing `session.json`; env cannot arm). | | `selat skill compare "" --live-probe [--limit N] [--json] [--pay]` | Vet catalog candidates for an intent **side-by-side, without accounts or keys**: shortlists the top N (default 5) via the same federated discovery as `selat search`, free-probes each candidate's live 402 at its **catalog serviceUrl** (never settles; `--live-probe` is required — it acknowledges the probe may invoke each target's method/body), and prints one aligned table — live price, rail (direct x402 / routed MPP), probe latency, reachability, and the selat-skills registry reliability badge — sorted reachable-first, then price. `--json` for machine consumption. `--pay` adds one **capped settled test call** per candidate (asks for confirmation first; `--yes` to authorize non-interactively; `--max-amount` overrides the per-call cap, which otherwise defaults to live price + 25 % clamped to $1.10 — an explicit flag is also hard-capped at $1.10 for everyone; `isTTY` / `--allow-high-max-amount` do not raise it; requires a file-armed session budget) and saves each response body as an output sample. Apify prepaid-token candidates are probed but skipped by `--pay` — test those with `selat run`. | | `selat skill new/validate/verify/register/submit` | Author and contribute a skill: scaffold → static SOP check → live-402 verify (writes the receipt that gates submission) → index entry → PR to [selat-skills](https://github.com/SELAT-AI/selat-skills). `submit` opens the PR **from your fork by default** unless you have write access to the skills repo; `--fork` / `--no-fork` force either flow. | -| `selat fund [--chain ... --amount ... --method direct\|eco]` | Top up Gateway balance. Dry-runs first; requires explicit confirm. Refused while `selat freeze` is on. **Deposits have a 0.5 USDC minimum** (the Circle CLI's Gateway floor — `fund` refuses smaller amounts up front rather than mid-flow). `selat` requires **Circle CLI ≥1.1.1** (`selat init` upgrades an older install; `selat doctor` reports the version). The eco path additionally refuses pre-1.0.0 installs (their eco pipeline is the retired one that stranded deposits). **Both methods are gasless** — the deposit runs through your agent wallet (a smart-contract account) with sponsored gas, so you never need to hold native ETH. The difference is **destination**: **`--method direct`** keeps the balance on the chain you deposited from; **`--method eco`** sources from Base (Circle CLI ≥1.0.0's eco coverage) but settles the resulting Gateway balance on **Polygon** regardless of source chain. After an Eco deposit, pay and check balance with `--chain polygon` (not the source chain), or the call fails with `insufficient_balance`. Fundable chains (base, optimism, arbitrum, polygon, ethereum, avalanche, unichain, arc — direct only) are the intersection of [Gateway-supported](https://developers.circle.com/gateway/references/supported-blockchains) and [Agent-Wallet-supported](https://developers.circle.com/agent-stack/agent-wallets/supported-blockchains) chains — Gateway-only chains (e.g. HyperEVM, Sei, Sonic, World Chain) are excluded because the agent wallet doesn't exist there, and Agent-Wallet-only chains (e.g. Monad) have no Gateway deposit path. | +| `selat fund [--chain ... --amount ... --method direct\|eco]` | Top up Gateway balance. Dry-runs first; requires explicit confirm. Refused while `selat freeze` is on. **Deposits have a 0.5 USDC minimum** (the Circle CLI's Gateway floor — `fund` refuses smaller amounts up front rather than mid-flow). `selat` requires **Circle CLI ≥1.1.4** (`selat init` upgrades an older install; `selat doctor` reports the version). The eco path additionally refuses pre-1.0.0 installs (their eco pipeline is the retired one that stranded deposits). **Both methods are gasless** — the deposit runs through your agent wallet (a smart-contract account) with sponsored gas, so you never need to hold native ETH. The difference is **destination**: **`--method direct`** keeps the balance on the chain you deposited from; **`--method eco`** sources from Base (Circle CLI ≥1.0.0's eco coverage) but settles the resulting Gateway balance on **Polygon** regardless of source chain. After an Eco deposit, pay and check balance with `--chain polygon` (not the source chain), or the call fails with `insufficient_balance`. Fundable chains (base, optimism, arbitrum, polygon, ethereum, avalanche, unichain, arc — direct only) are the intersection of [Gateway-supported](https://developers.circle.com/gateway/references/supported-blockchains) and [Agent-Wallet-supported](https://developers.circle.com/agent-stack/agent-wallets/supported-blockchains) chains — Gateway-only chains (e.g. HyperEVM, Sei, Sonic, World Chain) are excluded because the agent wallet doesn't exist there, and Agent-Wallet-only chains (e.g. Monad) have no Gateway deposit path. | | `selat fund --onramp [--address 0x..]` | **Fiat funding (buy USDC with a card).** Mints a **Circle Onramp** session and prints/opens a browser widget URL — you pick the amount in the widget, and nothing moves until you complete the purchase there (the CLI never sees your card). Sessions are **chain-scoped** to the chains `selat fund` can deposit from, so purchased USDC never lands somewhere it would strand. `--address` targets any of your agent wallets (default: the configured one; a warning prints when they differ). This is SELAT's built-in onramp — **not** Transak (`circle wallet fund --method fiat`), which is a different, unscoped flow. Once the purchase lands on-chain, re-run `selat fund` to deposit into Gateway. | | `selat spend [--json\|--wallet 0x..]` | Unified spend report (read-only): settled spend from the `selat-pay` ledger (per-call payments + Apify token buys, with a charged-but-failed/disputable total) plus Apify token utilization (consumed vs remaining, flagging prepaid-balance waste). | | `selat refund claim\|query [--chain …]` | File or query a refund claim for a prior paid `selatx…` quote via `selat-pay`. Auth (SIWx), not a payment — does not consume session budget or `--max-amount`. Remaining flags (`--chain`, `--router-url`, signer flags) pass through unchanged. | diff --git a/lib/circle.mjs b/lib/circle.mjs index 7c0643f..e65d5e8 100644 --- a/lib/circle.mjs +++ b/lib/circle.mjs @@ -53,12 +53,14 @@ export async function hasCircle() { } /** - * Oldest Circle CLI this stack works with. 1.1.1 is the first release that + * Oldest Circle CLI this stack works with. 1.1.1 was the first release that * lists Arc mainnet (ARC) and accepts it as a `gateway deposit` source; the - * selat-discovery skill's Arc deposit path assumes it. Mirrored by the skill's - * `@circle-fin/cli` peer floor. + * floor moved to 1.1.4 because Circle's own wallet-operations backend now + * rejects 1.1.2/1.1.3 (`circle gateway balance` / `circle wallet balance` + * error with "no longer supported ... Required: 1.1.4"). Mirrored by the + * skill's `@circle-fin/cli` peer floor. */ -export const MIN_CIRCLE_CLI_VERSION = "1.1.1"; +export const MIN_CIRCLE_CLI_VERSION = "1.1.4"; /** Installed Circle CLI version ("1.1.1"), or null when unreadable / absent. */ export async function circleVersion() { diff --git a/test/circle-version-floor.test.mjs b/test/circle-version-floor.test.mjs index 2d0dbdd..99ac136 100644 --- a/test/circle-version-floor.test.mjs +++ b/test/circle-version-floor.test.mjs @@ -3,12 +3,13 @@ import assert from "node:assert/strict"; import { MIN_CIRCLE_CLI_VERSION, versionAtLeast, circleInstallPlan } from "../lib/circle.mjs"; -// The stack needs Circle CLI >= 1.1.1 (first release that lists Arc mainnet). -// ensureCircle() decides install / upgrade / ok from this pure plan so an -// existing older install is upgraded instead of silently kept. +// The stack needs Circle CLI >= 1.1.4 (Circle's wallet-operations backend +// rejects 1.1.2/1.1.3). ensureCircle() decides install / upgrade / ok from +// this pure plan so an existing older install is upgraded instead of +// silently kept. -test("floor is 1.1.1", () => { - assert.equal(MIN_CIRCLE_CLI_VERSION, "1.1.1"); +test("floor is 1.1.4", () => { + assert.equal(MIN_CIRCLE_CLI_VERSION, "1.1.4"); }); test("versionAtLeast compares dotted numerics, rejects garbage", () => { @@ -26,6 +27,6 @@ test("circleInstallPlan: absent → install, old → upgrade, current → ok", ( assert.equal(circleInstallPlan({ installed: true, version: "1.0.0" }), "upgrade"); // Unreadable version on an installed binary is treated as old (reinstall). assert.equal(circleInstallPlan({ installed: true, version: null }), "upgrade"); - assert.equal(circleInstallPlan({ installed: true, version: "1.1.1" }), "ok"); + assert.equal(circleInstallPlan({ installed: true, version: "1.1.4" }), "ok"); assert.equal(circleInstallPlan({ installed: true, version: "1.3.0" }), "ok"); }); diff --git a/test/init-and-history.test.mjs b/test/init-and-history.test.mjs index 7da98ba..4a18fdc 100644 --- a/test/init-and-history.test.mjs +++ b/test/init-and-history.test.mjs @@ -122,7 +122,7 @@ test("init reuses configured wallet when Circle wallet listing fails", async () `#!/usr/bin/env bash set -euo pipefail if [[ "\${1:-}" == "--version" ]]; then - printf '1.1.1\\n' + printf '1.1.4\\n' exit 0 fi if [[ "\${1:-}" == "wallet" && "\${2:-}" == "status" ]]; then diff --git a/test/init-circle-autoinstall.test.mjs b/test/init-circle-autoinstall.test.mjs index 44f18eb..118780f 100644 --- a/test/init-circle-autoinstall.test.mjs +++ b/test/init-circle-autoinstall.test.mjs @@ -51,13 +51,13 @@ test("missing Circle CLI: init attempts the install without asking", async () => test("old Circle CLI: init attempts the upgrade without asking", async () => { const r = await runInit(await fixture({ circleVersion: "1.0.0" })); - assert.match(r.out, /1\.0\.0 is below the 1\.1\.1 floor — upgrading @circle-fin\/cli@latest/); + assert.match(r.out, /1\.0\.0 is below the 1\.1\.4 floor — upgrading @circle-fin\/cli@latest/); assert.match(r.out, /installs are disabled/); assert.doesNotMatch(r.out, /FAKE-NPM/); }); test("current Circle CLI: init leaves it alone", async () => { - const r = await runInit(await fixture({ circleVersion: "1.1.1" })); - assert.match(r.out, /Circle CLI 1\.1\.1 on PATH/); + const r = await runInit(await fixture({ circleVersion: "1.1.4" })); + assert.match(r.out, /Circle CLI 1\.1\.4 on PATH/); assert.doesNotMatch(r.out, /installing|upgrading|installs are disabled|FAKE-NPM/); });