@dev.sail.money/sailor 1.2.0-74 → 1.2.0-76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,8 +6,9 @@ Permissions only bound on-chain actions: venues with off-chain order matching (P
6
6
 
7
7
  | File | Protocol / chain | Teaches | Key verified selectors |
8
8
  |---|---|---|---|
9
- | `BoundedSwap_UniswapV3_Base.sol` | Uniswap V3 SwapRouter02 · Base | Selector allowlist + amount cap + tokenOut allowlist + min-out slippage floor (MIN_BPS) | `0x04e45aaf` exactInputSingle (SwapRouter02), `0x095ea7b3` approve |
10
- | `BoundedSwap_UniswapV4_Unichain.sol` | Uniswap V4 Universal Router · Unichain | Decoding command/action bytes: single V4_SWAP command, single SWAP_EXACT_IN_SINGLE action, currency + amount + slippage bounds | `0x3593564c` execute(bytes,bytes[],uint256), `0x24856bc3` execute(bytes,bytes[]) |
9
+ | `BoundedSwap_UniswapV3_Base.sol` | Uniswap V3 SwapRouter02 · Base | Selector allowlist + input-spend cap + tokenOut allowlist; teaches why slippage CANNOT be bounded on-chain (amountOutMinimum is a different token than amountIn — pass an off-chain-quoted value) | `0x04e45aaf` exactInputSingle (SwapRouter02), `0x095ea7b3` approve |
10
+ | `BoundedSwap_UniswapV4_Unichain.sol` | Uniswap V4 Universal Router · Unichain | Decoding command/action bytes: single V4_SWAP command, single SWAP_EXACT_IN_SINGLE action, currency + input-spend bounds; same slippage caveat as the V3 example | `0x3593564c` execute(bytes,bytes[],uint256), `0x24856bc3` execute(bytes,bytes[]) |
11
+ | `BoundedSwapNative_UniswapV3_Base.sol` | Uniswap V3 SwapRouter02 (native ETH) · Base | Native-asset spend: bound `ctx.value` (the real spend) + assert `amountIn == ctx.value`; tokenIn = WETH (router wraps the sent ETH). The canonical "bound Context.value" example | `0x04e45aaf` exactInputSingle (SwapRouter02) |
11
12
  | `BoundedBorrow_AaveV3_Arbitrum.sol` | Aave V3 Pool · Arbitrum | Asset allowlist + borrow cap + `onBehalfOf == ctx.account` binding + rate-mode allowlist (use [2]; stable deprecated in V3.1) | `0xa415bcad` borrow(address,uint256,uint256,uint16,address) |
12
13
  | `BoundedSupply_AaveV3_Arbitrum.sol` | Aave V3 Pool · Arbitrum | SailCalldata-based decoding; supply cap + `onBehalfOf == ctx.account`; supply does NOT gate withdrawal, and the prior approve needs its own permission | `0x617ba037` supply(address,uint256,address,uint16) |
13
14
  | `BoundedVault_ERC4626_Base.sol` | ERC-4626 standard · any EVM | Vault allowlist; deposit capped + receiver bound; withdraw/redeem receiver+owner bound but amount-unbounded (SMA can always fully exit) | `0x6e553f65` deposit, `0xb460af94` withdraw, `0xba087652` redeem |
@@ -19,6 +20,8 @@ Permissions only bound on-chain actions: venues with off-chain order matching (P
19
20
 
20
21
  ## Lessons the examples encode
21
22
 
23
+ - **Value-carrying calls — bound `Context.value`, always.** For any call that can carry native asset, the value actually leaving the account is `ctx.value` (`msg.value`), NOT the calldata amount. A permission that bounds only a calldata `amount`/`amountIn` leaves the real spend uncapped. Rule: **every value-carrying call must explicitly bound `Context.value`** — and where the calldata declares its own amount, also assert `amount == ctx.value` so the two can't drift. `BoundedSwapNative_UniswapV3_Base.sol` is the worked native-ETH example; the ERC-20 `BoundedSwap_UniswapV3_Base.sol` carries no value, so it bounds `amountIn` only. When in doubt, add `if (ctx.value > MAX) return false;`.
24
+ - **Slippage can't be bounded on-chain without a price oracle.** `amountOutMinimum` (output token) and `amountIn`/`ctx.value` (input token) are different denominations — a ratio between them is meaningless for any cross-price pair (USDC→WETH), giving false confidence while protecting nothing. The swap examples deliberately do NOT constrain `amountOutMinimum`; the agent computes it off-chain from a live quote and the router enforces it by reverting. Bound the input spend on-chain; bound the output off-chain.
22
25
  - **Venice — the wrong-selector bug.** The live contract's function is `stake(address,uint256)` = `0xadc9772e`. The intuitive single-arg `stake(uint256)` = `0xa694fc3a` does not exist on the target. Gating the wrong selector fails closed: every legitimate stake silently rejected. Always confirm the selector against the deployed contract.
23
26
  - **GMX — versioned ABIs drift.** GMX has multiple ExchangeRouter deployments and the `CreateOrderParams` struct has gained fields over time. Before deploying: pick the exact router the agent calls, read its verified ABI, recompute with `cast sig "createOrder(<exact tuple>)"`, and update the selector and struct if they differ. The committed `0x212234c3` is verified against gmx-io/gmx-synthetics at time of writing — re-verify anyway.
24
27
  - **Limitless — unverified ABI as a worked warning.** The exchange address and `buy` signature are inferred from CTF patterns, not verified against the deployed contract. The contract's own header lists the verification steps; do them before any deploy.
@@ -9,6 +9,8 @@ description: Walks the agent through setting up a new Sailor project or resuming
9
9
 
10
10
  The project does not install `sailor` as a dependency, so invoke it with **`npx sailor <command>`** unless it is installed globally (`npm i -g @sail.money/sailor`, then `sailor` works bare). Every `sailor …` command in these skills assumes one of those. Confirm the toolchain up front and pin a recent version — `npx sailor@latest --version` — because an old cached `npx` build can be missing newer commands (e.g. `mandate simulate`); if a documented command reports "unknown command", you are on a stale version, not hitting a missing feature.
11
11
 
12
+ After upgrading the CLI, run `sailor update` from the project root to pull in updated skills, `AGENTS.md`, `Dockerfile`, and other tooling files. User files (`src/`, `mandates/`, `.sail/`, `package.json`) are never touched.
13
+
12
14
  Stage machine keyed off `.sail/`. Read the state, enter at the right stage, never re-run completed stages.
13
15
 
14
16
  ## Determine where the user is
@@ -64,7 +64,7 @@ Detailed procedures live in skills. If your tooling does not auto-discover skill
64
64
  | sail-servers | Starting, stopping, or health-checking the dashboard or signing station | `.agents/skills/sail-servers/SKILL.md` |
65
65
  | sail-transactions | Building dispatches or any EVM transaction for the agent | `.agents/skills/sail-transactions/SKILL.md` |
66
66
  | sail-mandates | Designing, authoring, testing, deploying, or authorizing permission contracts | `.agents/skills/sail-mandates/SKILL.md` |
67
- | sail-ci | Automating the agent on a schedule via GitHub Actions | `.agents/skills/sail-ci/SKILL.md` |
67
+ | sail-automation | Automating the agent GitHub Actions, self-hosted runner, Docker, or local daemon | `.agents/skills/sail-automation/SKILL.md` |
68
68
  | sail-extend | Notifications or a custom dashboard, once the agent is live | `.agents/skills/sail-extend/SKILL.md` |
69
69
 
70
70
  ## Invariants — apply to every turn
@@ -0,0 +1,18 @@
1
+ FROM node:20-slim
2
+ WORKDIR /app
3
+
4
+ COPY package*.json ./
5
+ RUN npm install
6
+
7
+ COPY . .
8
+
9
+ # Seconds between runs. Tune to your strategy: 60 = per-minute, 300 = 5 min, 86400 = daily.
10
+ ENV AGENT_INTERVAL=300
11
+
12
+ CMD ["sh", "-c", "\
13
+ mkdir -p .sail/keys && \
14
+ cp ci-keystore.json .sail/keys/manager.json && \
15
+ while true; do \
16
+ npx sailor run --once; \
17
+ sleep ${AGENT_INTERVAL}; \
18
+ done"]
@@ -0,0 +1,15 @@
1
+ # Secrets and local state — never bake into the image.
2
+ .sail/keys/
3
+ .sail/runtime/
4
+ .sail/state/
5
+ .sail/.env.local
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+
10
+ # Build outputs
11
+ node_modules/
12
+ out/
13
+ cache/
14
+ broadcast/
15
+ dist/
@@ -1,63 +0,0 @@
1
- ---
2
- name: sail-ci
3
- description: Run the agent unattended — cloud (GitHub Actions cron + workflow_dispatch), a local OS service (sailor service install), or on-demand via the trigger seam (sailor trigger github) — with cadence guidance and the committed-keystore trust model. Use after sailor run --once works.
4
- ---
5
-
6
- # Sail CI — automating the agent
7
-
8
- Confirm `sailor run --once` works first. Three hosts run the same loop; pick by latency, privacy, and ops:
9
-
10
- - **Cloud** — GitHub Actions cron + `workflow_dispatch`. Zero infra; cron drifts (see Cadence).
11
- - **Local daemon** — `sailor service install`. Private, no committed keys, lower latency, no GitHub — you run the host.
12
- - **Event-driven** — an external system fires a run via the trigger seam (a keeper/watcher on a price move or deposit). The direction, not yet built; today the seam is `sailor trigger github`.
13
-
14
- ## Cadence
15
-
16
- Match the interval to volatility: **LP / perp → minutes; DCA / rebalance → daily; treasury → hourly–daily.** Actions cron is a *heartbeat/backstop* that drifts and skips under load — not low-latency; for that, use an external trigger or the local daemon.
17
-
18
- ## Cloud: GitHub Actions
19
-
20
- `.github/workflows/agent-tick.yml` runs `npx sailor run --once` on cron (default hourly `0 * * * *`, a generic placeholder — tune `cron` to your strategy per Cadence above; `workflow_dispatch` enables manual/external runs), via `npm ci`. `CHAIN_ID` comes from the repo variable (default `8453`).
21
-
22
- 1. **Export** — `sailor keys export-ci` writes the geth-v3 encrypted `ci-keystore.json` (raw key never exposed) and allowlists it in `.gitignore`.
23
- 2. **Commit** the non-secret files (`npm install` first for the lockfile):
24
-
25
- ```bash
26
- git add ci-keystore.json package-lock.json .sail/account.json .sail/config.json .sail/mandate.json
27
- git commit -m "chore: add CI keystore and sail state" && git push
28
- ```
29
-
30
- 3. **Secrets** (Settings → Secrets and variables → Actions): `SAIL_PASSPHRASE`, `RPC_URL`. If not Base, set the repo **variable** `CHAIN_ID`.
31
- 4. **Drive with `gh`** (needs the `workflow` scope — `gh auth login --scopes workflow`):
32
-
33
- ```bash
34
- gh secret set SAIL_PASSPHRASE && gh secret set RPC_URL
35
- gh workflow run agent-tick.yml # manual run
36
- gh run list --workflow agent-tick.yml # history
37
- gh run view --log # latest logs
38
- ```
39
-
40
- ## On-demand / external trigger
41
-
42
- ```bash
43
- sailor trigger github # fire workflow_dispatch — the same job cron runs
44
- # --reason <text> --ref <branch> --workflow <file> --repo <owner/repo> --json
45
- ```
46
-
47
- Wakes the agent between cron ticks — the seam keepers, watchers, or your backend call.
48
-
49
- ## Local daemon
50
-
51
- ```bash
52
- sailor service install --interval 300 # launchd/systemd/Task Scheduler; restarts on crash
53
- sailor service status | stop | uninstall
54
- sailor service logs -f # .sail/agent.log
55
- ```
56
-
57
- `--project`/`--chain` scope it; `--force` overrides a TCC path or unresolved passphrase.
58
-
59
- ## Keys & trust
60
-
61
- Cloud commits only the **encrypted** keystore; `SAIL_PASSPHRASE` is a secret, never committed (the same value the dashboard stores locally at `0600`). Whoever triggers or submits, the on-chain **mandate is the backstop**, bounding the manager regardless of host — choose cloud vs local with that in mind.
62
-
63
- A failing run's logs show the same stderr as the local runner (`reverted: <txHash>`, `skipped: no registered permission…`) — debug with the sail-transactions skill.