Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Either way you get a real paid API response. No API keys. No native ETH. No brid
| `selat skill run <name> [--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 "<intent>" --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 <quote-id> [--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. |
Expand Down
10 changes: 6 additions & 4 deletions lib/circle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
13 changes: 7 additions & 6 deletions test/circle-version-floor.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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");
});
2 changes: 1 addition & 1 deletion test/init-and-history.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions test/init-circle-autoinstall.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});
Loading