@dev.sail.money/sailor 1.0.0-42 → 1.1.0-44
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.
- package/AGENTS.md +5 -3
- package/README.md +29 -20
- package/docs/PERMISSION_MODEL.md +1 -1
- package/examples/README.md +24 -0
- package/package.json +1 -1
- package/packages/cli/README.md +0 -1
- package/packages/cli/dist/index.cjs +146 -185
- package/packages/cli/dist/server.cjs +2 -1
- package/packages/sdk/README.md +2 -2
- package/packages/sdk/dist/intelligence.d.ts +1 -1
- package/packages/sdk/dist/intelligence.js +1 -1
- package/packages/ui/dist/assets/{add-i2P8A2gs.js → add-BHh1DUhq.js} +1 -1
- package/packages/ui/dist/assets/{all-wallets-BVgI7zL8.js → all-wallets-BksVbtLO.js} +1 -1
- package/packages/ui/dist/assets/{app-store-BewBVZmc.js → app-store-DAXz4Z64.js} +1 -1
- package/packages/ui/dist/assets/{apple-C7MUnEQz.js → apple-D1qi9OLG.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-DfToMb64.js → arrow-bottom-BiqLduSV.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-circle-DJb-rBBb.js → arrow-bottom-circle-DjOKHyE8.js} +1 -1
- package/packages/ui/dist/assets/{arrow-left-DsWmN2OG.js → arrow-left-Cv3GiY8T.js} +1 -1
- package/packages/ui/dist/assets/{arrow-right-DJUX6dvg.js → arrow-right-cU9gH-83.js} +1 -1
- package/packages/ui/dist/assets/{arrow-top-bK9-SH4h.js → arrow-top-CwdOsDUA.js} +1 -1
- package/packages/ui/dist/assets/{bank-LlHF24Ta.js → bank-BvkUuR5o.js} +1 -1
- package/packages/ui/dist/assets/{basic-BgERj65k.js → basic-ULr_cCSL.js} +1 -1
- package/packages/ui/dist/assets/{browser-B9v0kcdd.js → browser-ofrFja4K.js} +1 -1
- package/packages/ui/dist/assets/{card-QhDXGe3I.js → card-CD4ERbnK.js} +1 -1
- package/packages/ui/dist/assets/{ccip-CP4Hg6QU.js → ccip-C7eZiz3a.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-ScbLFePj.js → checkmark-6onG3n_x.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-bold-SxqQsHPF.js → checkmark-bold-NcCoTwYz.js} +1 -1
- package/packages/ui/dist/assets/{chevron-bottom-HOzCXggY.js → chevron-bottom-BbtzkXrF.js} +1 -1
- package/packages/ui/dist/assets/{chevron-left-DmtXEk14.js → chevron-left-BupFb2YQ.js} +1 -1
- package/packages/ui/dist/assets/{chevron-right-DJCWUGen.js → chevron-right-B8ORkqXW.js} +1 -1
- package/packages/ui/dist/assets/{chevron-top-Bj9XyjOS.js → chevron-top-3bPA7cI0.js} +1 -1
- package/packages/ui/dist/assets/{chrome-store-C0Rq-0OK.js → chrome-store-TyiIOIxo.js} +1 -1
- package/packages/ui/dist/assets/{clock-DJiHF6By.js → clock-XdWZm05K.js} +1 -1
- package/packages/ui/dist/assets/{close-B58KpBOm.js → close-O2N8lyoj.js} +1 -1
- package/packages/ui/dist/assets/{coinPlaceholder-QrD1nktf.js → coinPlaceholder-DmXeJurd.js} +1 -1
- package/packages/ui/dist/assets/{compass-DkdmCS4M.js → compass-BNxtPTdy.js} +1 -1
- package/packages/ui/dist/assets/{copy-BKkOeIGO.js → copy-CcKL-ay_.js} +1 -1
- package/packages/ui/dist/assets/{core-BJ5Wn_0H.js → core-CA8vptSL.js} +3 -3
- package/packages/ui/dist/assets/cursor-DOh7MR7A.js +3 -0
- package/packages/ui/dist/assets/{cursor-transparent-CwUDRud2.js → cursor-transparent-OF-vM6pK.js} +1 -1
- package/packages/ui/dist/assets/{desktop-CKk_cTjE.js → desktop-BfhpeGBs.js} +1 -1
- package/packages/ui/dist/assets/{disconnect-BR4tm4ry.js → disconnect-C82unXY9.js} +1 -1
- package/packages/ui/dist/assets/{discord-CimCnw-R.js → discord-CXKsMGcp.js} +1 -1
- package/packages/ui/dist/assets/{etherscan-DKSSzuOS.js → etherscan-CNJ1W4Ky.js} +1 -1
- package/packages/ui/dist/assets/{events-HAbmebGY.js → events-Bqdt3zhf.js} +1 -1
- package/packages/ui/dist/assets/{exclamation-triangle-CnI_W0DF.js → exclamation-triangle-CopyPOzr.js} +1 -1
- package/packages/ui/dist/assets/{extension-BSpH53zE.js → extension-DXl-l9r3.js} +1 -1
- package/packages/ui/dist/assets/{external-link-D2QNdCHI.js → external-link-DbaKKqZS.js} +1 -1
- package/packages/ui/dist/assets/{facebook-CgSGAlNU.js → facebook-uQnRLN_c.js} +1 -1
- package/packages/ui/dist/assets/{fallback-CUotDuSc.js → fallback-qvxUxnWI.js} +1 -1
- package/packages/ui/dist/assets/{farcaster-SPALE2oQ.js → farcaster-CjxFnq3Q.js} +1 -1
- package/packages/ui/dist/assets/{filters-DjDEAYKk.js → filters-ONZDIBOt.js} +1 -1
- package/packages/ui/dist/assets/{github-CP9cMjmv.js → github-CV4aFvqa.js} +1 -1
- package/packages/ui/dist/assets/{google-DHxowSYF.js → google-BzKMlcn7.js} +1 -1
- package/packages/ui/dist/assets/{help-circle-Y1PsVJnm.js → help-circle-JrQdZVju.js} +1 -1
- package/packages/ui/dist/assets/{id-Du_4Z0EW.js → id-DkP1sjPL.js} +1 -1
- package/packages/ui/dist/assets/{image-tsFuvzhF.js → image-DnevZUmm.js} +1 -1
- package/packages/ui/dist/assets/index-BFj8874c.js +1789 -0
- package/packages/ui/dist/assets/{index-CNBJUjiM.js → index-CZVBCY_B.js} +1 -1
- package/packages/ui/dist/assets/{index-tPkIpVzb.js → index-Cf1OFgoJ.js} +3 -3
- package/packages/ui/dist/assets/{index-0OXGjc-u.js → index-DBk2Tmio.js} +1 -1
- package/packages/ui/dist/assets/index-ZMTNJl7U.css +1 -0
- package/packages/ui/dist/assets/{index-7RnuTfdS.js → index-aTtdADh3.js} +1 -1
- package/packages/ui/dist/assets/{index-DExn8x4G.js → index-dsFClx06.js} +1 -1
- package/packages/ui/dist/assets/{index.es-DIo7ubqt.js → index.es-BeGshwm4.js} +4 -4
- package/packages/ui/dist/assets/{info-DwysfTrK.js → info-CL9fTXa-.js} +1 -1
- package/packages/ui/dist/assets/{info-circle-BgqfZd9m.js → info-circle-1g_YMvEJ.js} +1 -1
- package/packages/ui/dist/assets/{lightbulb-Cy1dZk-v.js → lightbulb-BCz1ZXej.js} +1 -1
- package/packages/ui/dist/assets/{mail-q8uF2fJv.js → mail-C34-jLtr.js} +1 -1
- package/packages/ui/dist/assets/{metamask-sdk-CC2GhoTH.js → metamask-sdk-CSM1cHvw.js} +1 -1
- package/packages/ui/dist/assets/{mobile-BM3dHloL.js → mobile-C6LNNWIU.js} +1 -1
- package/packages/ui/dist/assets/{more-DtxChT3n.js → more-Mq--l-Tg.js} +1 -1
- package/packages/ui/dist/assets/{network-placeholder-CkS7N2-s.js → network-placeholder-D8dAk4sm.js} +1 -1
- package/packages/ui/dist/assets/{nftPlaceholder-BP2BELBx.js → nftPlaceholder-DelmkBov.js} +1 -1
- package/packages/ui/dist/assets/{off-CGlNgOvT.js → off-7H_l5MJ-.js} +1 -1
- package/packages/ui/dist/assets/{parseSignature-B-9InYad.js → parseSignature-DYe1tJDq.js} +1 -1
- package/packages/ui/dist/assets/{play-store-j4WI9tEY.js → play-store-Czh3YKh7.js} +1 -1
- package/packages/ui/dist/assets/{plus-DoBLB2r6.js → plus-ComRxU33.js} +1 -1
- package/packages/ui/dist/assets/{qr-code-CxMPa6dI.js → qr-code-CuqCcyVv.js} +1 -1
- package/packages/ui/dist/assets/{recycle-horizontal-tKM6wEFt.js → recycle-horizontal-DFKQpQAP.js} +1 -1
- package/packages/ui/dist/assets/{refresh-D3gJv-wD.js → refresh-CTJiyWAT.js} +1 -1
- package/packages/ui/dist/assets/{reown-logo-CB8aduTF.js → reown-logo-C6YIY-fD.js} +1 -1
- package/packages/ui/dist/assets/{search-CEFrSvpQ.js → search-BiwxRFHX.js} +1 -1
- package/packages/ui/dist/assets/{secp256k1-BiXu1g8S.js → secp256k1-CPGf_4B1.js} +1 -1
- package/packages/ui/dist/assets/{send-DpKQgtRb.js → send-D03N9lRH.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontal-a9Ybf18t.js → swapHorizontal-B5x5kCS-.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalBold-DP452gU2.js → swapHorizontalBold-DCMf8a5a.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalMedium-eG99M64A.js → swapHorizontalMedium-BYlOGVkE.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalRoundedBold-BAc_to7T.js → swapHorizontalRoundedBold-CZwHe_mN.js} +1 -1
- package/packages/ui/dist/assets/{swapVertical-BU1HNAb2.js → swapVertical-ytho09QV.js} +1 -1
- package/packages/ui/dist/assets/{telegram-DFyhBidh.js → telegram-FqqxzOzC.js} +1 -1
- package/packages/ui/dist/assets/{three-dots-BdnfIeUB.js → three-dots-zaLgHURe.js} +1 -1
- package/packages/ui/dist/assets/{twitch-DdurBvf3.js → twitch-CxKccKXq.js} +1 -1
- package/packages/ui/dist/assets/{twitterIcon-JS1Kh388.js → twitterIcon-CxT39J_l.js} +1 -1
- package/packages/ui/dist/assets/{verify-C-ZvR7T4.js → verify-DxwXI19y.js} +1 -1
- package/packages/ui/dist/assets/{verify-filled-Dzgdgepq.js → verify-filled-BTHJmMkV.js} +1 -1
- package/packages/ui/dist/assets/{w3m-modal-B_CKEhcT.js → w3m-modal-BGEooFHU.js} +1 -1
- package/packages/ui/dist/assets/{wallet-C4JYrLNC.js → wallet-N29oXhAV.js} +1 -1
- package/packages/ui/dist/assets/{wallet-placeholder-DURyjfcE.js → wallet-placeholder-DBI8jWka.js} +1 -1
- package/packages/ui/dist/assets/{walletconnect-C5I2F5B-.js → walletconnect-BT1fqf0b.js} +1 -1
- package/packages/ui/dist/assets/{warning-circle-Blhki0Aq.js → warning-circle-f1tnN6R2.js} +1 -1
- package/packages/ui/dist/assets/{x-NdGVznpk.js → x-NK3JDZtO.js} +1 -1
- package/packages/ui/dist/index.html +2 -2
- package/scripts/check-docs.mjs +51 -4
- package/scripts/check-init.mjs +12 -0
- package/templates/default/.agents/skills/sail-ci/SKILL.md +66 -0
- package/templates/default/.agents/skills/sail-extend/SKILL.md +74 -0
- package/templates/default/.agents/skills/sail-mandates/SKILL.md +93 -0
- package/templates/default/.agents/skills/sail-mandates/references/approvals.md +42 -0
- package/templates/default/.agents/skills/sail-mandates/references/calls-schema.md +42 -0
- package/templates/default/.agents/skills/sail-mandates/references/constructor-args.md +45 -0
- package/templates/default/.agents/skills/sail-mandates/references/examples-index.md +31 -0
- package/templates/default/.agents/skills/sail-mandates/references/simulate-calls.md +58 -0
- package/templates/default/.agents/skills/sail-onboarding/SKILL.md +73 -0
- package/templates/default/.agents/skills/sail-project-info/SKILL.md +30 -0
- package/templates/default/.agents/skills/sail-servers/SKILL.md +43 -0
- package/templates/default/.agents/skills/sail-transactions/SKILL.md +63 -0
- package/templates/default/AGENTS.md +37 -126
- package/templates/default/test/BoundedCallPermission.t.sol +73 -0
- package/packages/ui/dist/assets/cursor-Dk5BeeUC.js +0 -3
- package/packages/ui/dist/assets/index-BODuSfdj.css +0 -1
- package/packages/ui/dist/assets/index-Bj9jEvxf.js +0 -1775
- /package/{templates → examples}/custom-mandate/.sail/contracts/interfaces/IPermission.sol +0 -0
- /package/{templates → examples}/custom-mandate/README.md +0 -0
- /package/{templates → examples}/custom-mandate/foundry.toml +0 -0
- /package/{templates → examples}/custom-mandate/mandates/BoundedCallPermission.sol +0 -0
- /package/{templates → examples}/custom-mandate/mandates/README.md +0 -0
- /package/{templates → examples}/custom-mandate/mandates/SailCalldata.sol +0 -0
- /package/{templates → examples}/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +0 -0
- /package/{templates → examples}/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +0 -0
- /package/{templates → examples}/lifi-permissions/README.md +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sail-onboarding
|
|
3
|
+
description: Walks the agent through setting up a new Sailor project or resuming a partially set-up one — SMA deployment, agent wallet creation, address prediction, and multi-chain deployment. Use when the project has no SMA yet, when .sail/account.json is missing or incomplete, when the user says "start" or "continue", or when deploying the SMA to an additional chain.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sail onboarding
|
|
7
|
+
|
|
8
|
+
## Running the CLI
|
|
9
|
+
|
|
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
|
+
|
|
12
|
+
Stage machine keyed off `.sail/`. Read the state, enter at the right stage, never re-run completed stages.
|
|
13
|
+
|
|
14
|
+
## Determine where the user is
|
|
15
|
+
|
|
16
|
+
| `.sail/` state | Stage |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `config.json` has `chainId: null` | Stage 0 — chain not chosen; ask which chain, write it to `config.json` |
|
|
19
|
+
| No `account.json` | Stage 1 — SMA not deployed |
|
|
20
|
+
| `account.json` exists, `state/mandates.json` empty or absent | SMA live, no permissions — hand off to **sail-mandates** |
|
|
21
|
+
| `account.json` + tracked mandates | Fully onboarded — hand off to **sail-transactions** / running |
|
|
22
|
+
|
|
23
|
+
Supported chains: Ethereum (1), Base (8453), Arbitrum (42161), Unichain (130), Base Sepolia (84532), Eth Sepolia (11155111). `sailor chains --json` lists them with kernel addresses.
|
|
24
|
+
|
|
25
|
+
## Stage 1 — Deploy the SMA and create the agent wallet
|
|
26
|
+
|
|
27
|
+
In the browser. Run `sailor ui start`, open the printed URL, connect the owner wallet, choose the network, deploy the SMA, then create the agent wallet — a separate signing key the agent uses to submit transactions. **Both wallets need gas, and the split is not what it looks like:** the owner *signs* (SMA deployment, mandate authorization) but the **agent wallet submits and pays for every on-chain transaction** — including `mandate deploy` and `mandate attach` during setup, not just dispatches once running. Fund the agent wallet before Stage 3 or attach fails with `gas required exceeds allowance`. The owner wallet needs gas only for transactions it submits directly in the browser (the SMA deployment). The owner key never leaves the browser.
|
|
28
|
+
|
|
29
|
+
Headless alternative (the agent drives, the owner only signs in the browser):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
sailor keys generate # create the agent wallet (interactive: role + passphrase)
|
|
33
|
+
sailor station start --json & # signing daemon — BLOCKS; run in background
|
|
34
|
+
sailor owner connect --json # BLOCKS up to 300s waiting for a wallet to connect in the browser
|
|
35
|
+
sailor scan --json # discover the owner's Safes and state
|
|
36
|
+
sailor onboard --new-sma --json # deploy SMA — BLOCKS waiting for the owner's browser signature
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`onboard --new-sma` pushes a `create-sma` signing request to the browser, waits for the owner to approve (default timeout 10 minutes), then persists the SMA to `.sail/account.json`. Tell the user: "approve the request in the signing station in your browser."
|
|
40
|
+
|
|
41
|
+
## Deterministic address (salt)
|
|
42
|
+
|
|
43
|
+
Every SMA deployment uses a CREATE2 salt (default `0`). The kernel binds the salt to the owner, the agent (manager) wallet, and the fee policy — so **create the agent wallet first, then predict**:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
sailor account predict --owner <owner> --manager <agent-wallet> --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The salt is saved to `.sail/account.json` (`saltNonce`) automatically on deploy. All supported chains share the same protocol addresses via CREATE2, so the same owner, manager, and salt produce the same SMA address on every chain. `predict` reads no keys and spends no gas. Use `--salt <n>` for a non-default salt, `--chain <id>` for one chain only.
|
|
50
|
+
|
|
51
|
+
If `predict` errors with "depends on the agent (manager) wallet", the agent wallet does not exist yet — generate it first.
|
|
52
|
+
|
|
53
|
+
## Multi-chain deployment
|
|
54
|
+
|
|
55
|
+
Once the SMA is live on one chain, deploy it at the same address on another supported chain:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
sailor account predict --json # confirm the address matches first
|
|
59
|
+
sailor account deploy-chain --chain 42161 --json # BLOCKS waiting for the owner's browser signature
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The owner approves in the browser (switch the wallet to the target chain before signing); no new salt or agent wallet is needed. The command is idempotent — if the SMA already has code at the predicted address on the target chain it records the chain and exits cleanly. Deployed chains accumulate in `account.json` `deployedChains`.
|
|
63
|
+
|
|
64
|
+
If `deploy-chain` refuses with an address mismatch, the SMA was deployed against the old per-chain contracts (pre-CREATE2) and cannot be reproduced cross-chain — the fix it prints is to deploy a fresh SMA with `sailor onboard --new-sma`.
|
|
65
|
+
|
|
66
|
+
## Gas requirements
|
|
67
|
+
|
|
68
|
+
- Owner wallet: SMA deployment, mandate signing (EIP-712), any additional-chain deployment.
|
|
69
|
+
- Agent wallet: `mandate deploy`, `mandate attach`, `mandate revoke`, and every dispatch submission once the agent runs, plus permission registration fees on fee-charging chains.
|
|
70
|
+
|
|
71
|
+
`sailor doctor` — read-only preflight: kernel dispatch model, permission health, RPC reachability, gas balances in both wallets. Do not proceed to Stage 3 with a failing doctor.
|
|
72
|
+
|
|
73
|
+
During setup, always ask before anything that costs gas.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sail-project-info
|
|
3
|
+
description: Read-only commands and state files that answer questions about the project, account, mandate, chains, keys, or environment. Use when the user asks "what's the state of…", "is X set up", "which chains…", "what permissions are registered", or before any operation that needs current state.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sail project info
|
|
7
|
+
|
|
8
|
+
Every command here is read-only and supports `--json` for machine reading. Prefer `--json`; parse, don't scrape. None of them block on the browser.
|
|
9
|
+
|
|
10
|
+
| Command | Reports | Backed by |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| `sailor status` | One-screen setup progress: keys present, account, signed mandate, session, agent run state | Local: `.sail/account.json`, `mandate.json`, `session.json`, `keys/`, agent pid file |
|
|
13
|
+
| `sailor doctor --json` | Preflight: kernel dispatch model, permission health (per-permission evaluate probes), RPC reachability, gas balances of owner + agent wallets | On-chain reads via the configured RPC; `--account <address>` overrides the SMA |
|
|
14
|
+
| `sailor capabilities --json` | Feasibility map: resolved chain, kernel model, available mandate templates, strategy primitives | `deployments.ts` registry + on-chain typehash detection |
|
|
15
|
+
| `sailor chains --json` | Supported chains and their SailKernel addresses | Static registry; add `--verify` to `eth_getCode` each kernel (one RPC call per configured chain) |
|
|
16
|
+
| `sailor scan --json` | Owner's Safes, which are Sail SMAs, their managers/permissions/sessions, local keys — persisted to `.sail/state/context.json` | Safe Transaction Service + kernel reads; `--owner <address>` overrides the saved owner |
|
|
17
|
+
| `sailor owner show --json` | The saved project owner address | `.sail/state/owner.json` |
|
|
18
|
+
| `sailor keys show` | Address of each stored key (decrypts to derive the address; prompts for passphrase unless `SAIL_PASSPHRASE` is set) | `.sail/keys/*.json` |
|
|
19
|
+
| `sailor mandate list` | Permission contracts deployed from this project, with attachment history | `.sail/state/mandates.json` |
|
|
20
|
+
| `sailor mandate templates --json` | How to author a permission + any community-deployed standalone template addresses on this chain (unaudited, informational) | `deployments.ts` `standaloneTemplates` |
|
|
21
|
+
|
|
22
|
+
## Which source answers what
|
|
23
|
+
|
|
24
|
+
- "Is the SMA deployed?" — `account.json` exists and has `safe`; `doctor` confirms on-chain.
|
|
25
|
+
- "What can the agent do right now?" — on-chain `getPermissions()` is the truth; `mandate sign` reconciles against it. `state/mandates.json` is an append-only historical record — a permission revoked on-chain still appears there.
|
|
26
|
+
- "Is the RPC working / is there gas?" — `sailor doctor --json`.
|
|
27
|
+
- "Same address on another chain?" — `account.json` `deployedChains`, verified by `sailor account predict`.
|
|
28
|
+
- "Is anything running?" — `.sail/runtime/ui.json` (dashboard), `.sail/runtime/server.json` (signing station), agent pid via `sailor status`.
|
|
29
|
+
|
|
30
|
+
Run `sailor doctor` before any operation that spends gas — it is the cheapest way to catch a dead RPC, an unfunded wallet, or a kernel mismatch first.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sail-servers
|
|
3
|
+
description: Start, stop, and health-check the two local servers — the Sailor dashboard and the signing station. Use when launching the UI, when a signing request needs a browser, when a port or pid question comes up, or when a command appears stuck waiting for a signature.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sail servers
|
|
7
|
+
|
|
8
|
+
Two distinct local servers. Both are per-project, both write state under `.sail/runtime/`, and both start idempotently (starting twice reports "already running" and exits 0).
|
|
9
|
+
|
|
10
|
+
## Dashboard — `sailor ui`
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
sailor ui start # spawns a detached Express server, prints the URL, returns immediately
|
|
14
|
+
sailor ui status # ● running http://localhost:<port> (pid N)
|
|
15
|
+
sailor ui stop # SIGTERM via the pid file
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- Port: deterministic per project — `3333 + (hash(projectPath) % 667)`, i.e. somewhere in 3333–3999, bumped to the next free port if taken. **Do not assume 3333** — always use the URL the command prints or read `.sail/runtime/ui.json` (`{ pid, port, startedAt }`).
|
|
19
|
+
- The server serves the pre-built React app (`SERVE_DIST=1`) and a local `/api` that reads `.sail/` state (`SAIL_DIR` env).
|
|
20
|
+
- `ui start` does not block — no `&` needed.
|
|
21
|
+
|
|
22
|
+
## Signing station — `sailor station`
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
sailor station start --json # BLOCKS — run in the background
|
|
26
|
+
sailor station status --json # running / stopped, url, pid
|
|
27
|
+
sailor station stop --json # SIGTERM, verified against the recorded URL first
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- Port: defaults to **3141**, bumped to the next free port if taken. The actual URL is in `.sail/runtime/server.json` (`{ url, wsUrl, port, pid, startedAt, requestSecret }`).
|
|
31
|
+
- Health/discovery endpoint: `GET http://localhost:<port>/config` returns `{ url, wsUrl, port, pid, pendingCount }` — `pendingCount` is the number of signing requests waiting for the owner. All other endpoints require a per-startup secret; do not poll them.
|
|
32
|
+
- `station start` **blocks** (the listening socket keeps the process alive) — run it in the background. It is idempotent: if a reachable daemon exists it reports already-running and exits 0.
|
|
33
|
+
- The URL to give the user is the **dashboard** station route printed by the command: `http://localhost:<ui-port>/#/station`.
|
|
34
|
+
|
|
35
|
+
## How they relate
|
|
36
|
+
|
|
37
|
+
Signing-flow commands (`mandate deploy/attach/deploy-clone/revoke`, `onboard`, `account deploy-chain`, `account rotate-signer`, `owner connect`) push requests to a running station daemon if one exists, otherwise they spin up an ephemeral in-process signing server for the duration of the command. Starting a persistent station first means the owner connects their wallet once and approves a whole sequence of requests in the same browser tab — do this before any multi-step signing flow.
|
|
38
|
+
|
|
39
|
+
## Troubleshooting
|
|
40
|
+
|
|
41
|
+
- Command stuck "waiting"? It is blocked on a browser signature — check `GET /config` `pendingCount`, and tell the user to open the station URL and approve. Signing requests time out after 10 minutes.
|
|
42
|
+
- Stale pid file (process died): `ui status` / `station stop` clean it up automatically.
|
|
43
|
+
- `sailor ui start` errors about a missing server bundle or UI dist: the package build is incomplete — re-run the sailor build.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sail-transactions
|
|
3
|
+
description: How dispatches and EVM transactions work in a Sailor project — the selective dispatch model, signing, batching, permission resolution, running the agent, and which CLI commands block on a browser signature. Use when building or debugging dispatches, writing agent tick code, running the agent, or reasoning about why a transaction was denied or reverted.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sail transactions
|
|
7
|
+
|
|
8
|
+
## The dispatch model
|
|
9
|
+
|
|
10
|
+
The deployed kernels run the **selective** model: every dispatch names one registered permission, and the kernel calls that permission's `evaluate()` (or `evaluateBatch()` for batches) on-chain before executing. A permission returning false, reverting, or exceeding its gas cap is a denial — fail-closed.
|
|
11
|
+
|
|
12
|
+
Never hardcode the model. `detectKernelCapabilities` (SDK) reads the on-chain `DISPATCH_TYPEHASH()` and is the only reliable source; the static label in `deployments.ts` is an offline fallback.
|
|
13
|
+
|
|
14
|
+
## Signing
|
|
15
|
+
|
|
16
|
+
Never hand-roll the EIP-712 dispatch struct. Use `buildDispatchSignature` from the SDK — it reads the on-chain typehash and builds the correct typed data for the detected model.
|
|
17
|
+
|
|
18
|
+
## Writing agent code
|
|
19
|
+
|
|
20
|
+
The runner (`sailor run`) calls `agent.tick(ctx)` from `src/agent.ts` and submits each returned `Dispatch`:
|
|
21
|
+
|
|
22
|
+
- A dispatch is `{ calls: [{ target, value, data }, …] }`. One call = single dispatch; multiple calls = batch dispatch (single on-chain tx, all-or-nothing).
|
|
23
|
+
- You do not name permissions: the runner probes each registered permission in registration order (off-chain `evaluate()` / `previewBatch` via `eth_call`) and routes to the first that accepts. Set `dispatch.permission` only as an explicit override to skip the probe.
|
|
24
|
+
- **Approve + action — two non-mixable models** (details: `../sail-mandates/references/approvals.md`):
|
|
25
|
+
- **Per-call (default):** two separate single-call dispatches, each gated by its own `IPermission`. Emit the approve only when on-chain allowance is insufficient (the `examples/dca/` pattern).
|
|
26
|
+
- **Atomic batch (advanced):** one `Dispatch` with `calls: [approveCall, actionCall]` authorized by a single `IBatchPermission`. A normal `IPermission` cannot authorize a batch (`PermissionNotBatchAware`). Do not mix the models.
|
|
27
|
+
- `ctx` provides `read.balance/allowance/decimals`, `publicClient` (arbitrary reads), `log()`, and a persistent `data` slot.
|
|
28
|
+
- Return `[]` to skip a tick — no gas spent.
|
|
29
|
+
|
|
30
|
+
## Running
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
sailor run --once # single tick — confirm it works before automating
|
|
34
|
+
sailor run # continuous; interval SAILOR_INTERVAL seconds (default 60)
|
|
35
|
+
sailor run --chain <id> # override CHAIN_ID for this run
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`run` needs `.sail/account.json`, `.sail/mandate.json`, a manager key, `CHAIN_ID` and an RPC URL (`.sail/.env.local`). Set `SAIL_PASSPHRASE` in `.env.local` to unlock the agent wallet non-interactively. Neither form blocks on a browser — the signed mandate is the authorization.
|
|
39
|
+
|
|
40
|
+
## Where results land
|
|
41
|
+
|
|
42
|
+
Everything appends to `.sail/activity.jsonl` (one JSON per line): `tick_start`/`tick_end`, `dispatch_approved`, `dispatch_executed` (with `txHash`), `dispatch_reverted` (with `txHash`, `gasUsed`), `dispatch_denied` (with `reason`, e.g. `no_permission_match` plus the rejected selector), `error`, and owner-side `owner_signed`/`owner_rejected` events. On stderr, reverts surface as `reverted: <txHash> (gas used: N)`; denials as `skipped: no registered permission authorizes call to <target> (selector 0x…)`. A failed dispatch never stops the loop.
|
|
43
|
+
|
|
44
|
+
## Commands that BLOCK on a browser signature
|
|
45
|
+
|
|
46
|
+
These open a signing channel, push a request, and wait (default timeout 10 minutes) for the owner to approve in the browser. When one is pending, tell the user: "approve the request in the signing station in your browser" and give them the station URL the command printed.
|
|
47
|
+
|
|
48
|
+
| Command | What the owner signs |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `sailor onboard --new-sma` | `create-sma` transaction (owner pays gas) |
|
|
51
|
+
| `sailor account deploy-chain --chain <id>` | `create-sma` transaction on the target chain |
|
|
52
|
+
| `sailor account rotate-signer` | Delegate rotation + mandate re-approvals |
|
|
53
|
+
| `sailor mandate deploy` | `deploy-mandate` contract-creation transaction (owner pays gas) |
|
|
54
|
+
| `sailor mandate attach` | `RegisterPermission` EIP-712 (off-chain signature; agent submits and pays gas) |
|
|
55
|
+
| `sailor mandate deploy-clone` | `RegisterPermission` EIP-712 for the predicted clone address |
|
|
56
|
+
| `sailor mandate revoke` | `RevokePermissions` EIP-712 (agent submits and pays gas) |
|
|
57
|
+
| `sailor owner connect` | Nothing — blocks up to 300s waiting for a wallet to connect |
|
|
58
|
+
|
|
59
|
+
All take `--json` for machine-readable output; in `--json` mode the blocking commands emit a `waiting_for_signature` status with the URL before blocking.
|
|
60
|
+
|
|
61
|
+
## Session control
|
|
62
|
+
|
|
63
|
+
`sailor session pause` instantly revokes dispatch rights without touching custody; `sailor session resume` restores them.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
This guide is for agents operating a scaffolded Sailor project. (Contributors to the Sailor codebase: see AGENTS.md at the monorepo root.)
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
Sail Protocol is infrastructure for onchain Separately Managed Accounts run by AI agents. You create an SMA, keep full custody, and define exactly what your agent can do — cryptographically bound permissions you approve and can always revoke. The agent executes within those bounds on every transaction. It cannot exceed them.
|
|
2
6
|
|
|
3
7
|
I'm Sailor, the operator that sets this up. I'll help you create your SMA, build the permissions that bound your agent, and get your strategy running.
|
|
@@ -32,140 +36,47 @@ During **setup**, always ask before anything that costs gas. Once the **mandate
|
|
|
32
36
|
|
|
33
37
|
When the user says start (or any first message), present the welcome above in full — definition, stage list, handoff line — before doing anything else. Do not launch the UI yet. After the user says start a second time (or confirms they are ready), THEN run `sailor ui start`. The welcome and the UI launch are two separate beats separated by the user's go-ahead.
|
|
34
38
|
|
|
35
|
-
Determine the user's progress by reading `.sail/` — do not ask; read it.
|
|
36
|
-
|
|
37
39
|
If the user's first message is an npm install command, run it, then present the welcome immediately after it completes — do not wait for another message.
|
|
38
40
|
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
In the browser. Run `sailor ui start`, open the printed URL, connect your owner wallet, choose your network, and deploy your SMA. Then create your agent wallet — a separate signing key I use to submit transactions on your behalf. You need gas in both: the owner wallet to deploy and sign the mandate; the agent wallet to submit transactions once the agent is running. The owner key never leaves the browser.
|
|
42
|
-
|
|
43
|
-
**Deterministic address (salt):** every SMA deployment uses a CREATE2 salt. The CLI defaults to salt `0`, giving you a predictable address you can verify before spending gas: `sailor account predict --owner <your-wallet> --manager <agent-wallet>`. The kernel binds the salt to your owner wallet, agent (manager) wallet, and fee policy — create your agent wallet first, then predict. The salt is saved in `.sail/account.json` automatically. All supported chains (Ethereum, Base, Arbitrum, Unichain, plus testnets) share the same protocol addresses via CREATE2, so the same owner, manager, and salt produce the **same SMA address on every chain**.
|
|
44
|
-
|
|
45
|
-
**Multi-chain deployment:** once your SMA is live on one chain, deploy it at the same address on any other supported chain with `sailor account deploy-chain --chain <id>` (e.g. `--chain 42161` for Arbitrum). The owner approves the deployment in the browser; no new salt or agent wallet needed. Run `sailor account predict` first to confirm the address matches.
|
|
46
|
-
|
|
47
|
-
## Stage 2 — Define your strategy
|
|
48
|
-
|
|
49
|
-
Tell me what you want your agent to do. I'll ask the right questions, establish the on-chain bounds with you (tokens, amounts, slippage, venues), and set up your RPC endpoint once you've chosen your chain. Blank slate — you define the strategy.
|
|
50
|
-
|
|
51
|
-
For a worked end-to-end example (DCA / Uniswap V3 / Base), consult `examples/dca/` — reference only; not your strategy.
|
|
52
|
-
|
|
53
|
-
## Stage 3 — Build, test, and sign your mandate
|
|
54
|
-
|
|
55
|
-
I'll write the permission contracts that bound your agent, prove in plain English what each one permits and blocks against sample calls, deploy them, and walk you through signing to authorize. Author, verify, sign — one step.
|
|
56
|
-
|
|
57
|
-
Permission contracts live in `mandates/`. The user authors, reviews, and owns them. For examples by protocol and chain, see `examples/permissions/`.
|
|
58
|
-
|
|
59
|
-
**Prerequisite — Foundry:** `forge build` requires the Foundry toolchain. If `forge` is not found, install it:
|
|
60
|
-
```bash
|
|
61
|
-
curl -L https://foundry.paradigm.xyz | bash # then restart shell
|
|
62
|
-
foundryup
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
**Approve coverage — mandatory:** ERC-20 `approve()` calls are NOT covered by supply or deposit permission contracts. If your strategy approves a token before supplying, you MUST deploy a separate bounded-approve permission that covers that specific `(token, spender, maxAmount)` combination, and authorize it alongside the supply permission. An agent that calls `approve()` without a matching permission will be rejected by the kernel.
|
|
66
|
-
|
|
67
|
-
**Batching:** if a strategy tick needs to approve before supplying, build both calls into a single dispatch array — `[approveCall, supplyCall]` — not two separate ticks. Splitting them wastes a tick and the approval sits exposed until the next run.
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
forge build
|
|
71
|
-
sailor mandate deploy --contract <Name> --sma <SMA> # deploy only — do NOT --attach yet
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
**Constructor args:** quoting rules differ by shell.
|
|
41
|
+
## Project state — read `.sail/`, never ask
|
|
75
42
|
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
sailor mandate deploy --contract <Name> --args '["0xToken","1000000"]' --sma <SMA>
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
PowerShell — use escaped inner quotes inside single quotes:
|
|
82
|
-
```powershell
|
|
83
|
-
sailor mandate deploy --contract <Name> --args '[\"0xToken\",\"1000000\"]' --sma <SMA>
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Any shell — `--args-file` avoids quoting entirely:
|
|
87
|
-
```json
|
|
88
|
-
["0xToken", "1000000"]
|
|
89
|
-
```
|
|
90
|
-
```bash
|
|
91
|
-
sailor mandate deploy --contract <Name> --args-file args.json --sma <SMA>
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Before AUTHORIZING (attaching) the permission, BACK the plain-English claims with an actual on-chain probe. `evaluate()` lives on the deployed contract, so deploy first, then — before the irreversible authorization — generate sample calls from the user's stated strategy (ones the permission MUST accept and ones it MUST reject) and run them through `sailor mandate simulate`. This is an off-chain `eth_call` (no gas, no signing) that reports what the permission's `evaluate()` returns for each call, and flags any target with no contract code (a wrong or wrong-chain address):
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# one call inline, or a batch via JSON
|
|
98
|
-
sailor mandate simulate --address <PermissionOrName> --sma <SMA> \
|
|
99
|
-
--target <addr> --calldata <hex> --expect pass
|
|
100
|
-
sailor mandate simulate --address <PermissionOrName> --sma <SMA> --calls calls.json
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
`calls.json` is an array of `{ target, calldata, value?, expect: "pass"|"fail", label }`. A mismatch between `expect` and the actual result exits non-zero — do not authorize until every sample matches. Simulate proves what the permission DOES; it does not guarantee it is correct.
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
sailor mandate attach --address <PermissionOrName> --sma <SMA> # authorize, once simulate is clean
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Registration requires the owner to sign in the browser. If the wrong wallet is connected, the CLI rejects it.
|
|
110
|
-
|
|
111
|
-
## Stage 4 — Run
|
|
112
|
-
|
|
113
|
-
Your agent starts executing within its mandate — locally on a schedule or via GitHub Actions. No per-transaction confirmation. The mandate is the authorization.
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
sailor run # local, continuous
|
|
117
|
-
sailor run --once # single tick — confirm it works before automating
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
For GitHub Actions:
|
|
121
|
-
|
|
122
|
-
1. Run `sailor keys export-ci` — copies your encrypted agent wallet to `ci-keystore.json` in the project root and adds it to `.gitignore` as an allowed file. The keystore is geth v3 encrypted; the raw private key is never exposed.
|
|
123
|
-
2. Commit the required files. CI needs these non-secret files to be in the repo:
|
|
124
|
-
```bash
|
|
125
|
-
npm install # generate package-lock.json if it doesn't exist
|
|
126
|
-
git add ci-keystore.json package-lock.json .sail/account.json .sail/config.json .sail/mandate.json
|
|
127
|
-
git commit -m "chore: add CI keystore and sail state" && git push
|
|
128
|
-
```
|
|
129
|
-
`package-lock.json` is required by `npm ci` (used in the workflow). `.sail/account.json`, `.sail/config.json`, and `.sail/mandate.json` contain only public addresses and flags — no secrets. The `.gitignore` already has `!` exceptions for all of these.
|
|
130
|
-
3. Add two secrets in GitHub (Settings → Secrets → Actions):
|
|
131
|
-
- `SAIL_PASSPHRASE` — the passphrase that encrypts your agent wallet
|
|
132
|
-
- `RPC_URL` — your RPC endpoint
|
|
133
|
-
4. Install the `gh` CLI — required to manage the workflow from the terminal (trigger runs, check logs, add secrets without opening the browser):
|
|
134
|
-
- macOS: `brew install gh`
|
|
135
|
-
- Windows: `winget install --id GitHub.cli` or `scoop install gh`
|
|
136
|
-
- Linux: see https://github.com/cli/cli/blob/trunk/docs/install_linux.md
|
|
137
|
-
Then authenticate with the `workflow` scope:
|
|
138
|
-
```bash
|
|
139
|
-
gh auth login --scopes workflow
|
|
140
|
-
```
|
|
141
|
-
The `workflow` scope is required — without it, `gh` cannot trigger or inspect Actions runs. Verify with:
|
|
142
|
-
```bash
|
|
143
|
-
gh auth status # confirm workflow scope is listed
|
|
144
|
-
gh workflow run agent-tick.yml # manual trigger
|
|
145
|
-
gh run list --workflow agent-tick.yml # check run history
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
The scaffolded workflow at `.github/workflows/agent-tick.yml` picks up `ci-keystore.json`, unlocks it with `SAIL_PASSPHRASE`, and runs on the configured schedule. No private key ever appears in the workflow or in secrets.
|
|
149
|
-
|
|
150
|
-
## Stage 5 — Extend
|
|
151
|
-
|
|
152
|
-
I can set up notifications (Telegram, email, or other) for runs and transactions, and build you a custom dashboard tailored to your strategy — a price chart and portfolio view for a trading agent, health-factor and yield for a lending agent.
|
|
153
|
-
|
|
154
|
-
These are things the coding assistant builds on request — not Sailor features. Raise them once the agent is live; build on request.
|
|
155
|
-
|
|
156
|
-
## Signing (for custom runners)
|
|
157
|
-
|
|
158
|
-
Use `buildDispatchSignature` from `@sail.money/sdk` — it reads the on-chain `DISPATCH_TYPEHASH` and builds the correct typed data. Never hand-roll the EIP-712 struct or hardcode the dispatch model.
|
|
43
|
+
Determine the user's progress by reading `.sail/` — do not ask; read it.
|
|
159
44
|
|
|
160
|
-
|
|
45
|
+
| File | What it tells you |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `config.json` | Project manifest: name, `chainId` (null until the user picks a chain), contract addresses |
|
|
48
|
+
| `account.json` | Active SMA: safe, owner, manager (agent wallet), chainId, saltNonce, deployedChains |
|
|
49
|
+
| `mandate.json` | The signed mandate the runner executes against (absent = not signed yet) |
|
|
50
|
+
| `keys/` | Encrypted geth-v3 keystores (agent wallet, mandate signer) — never read or print contents |
|
|
51
|
+
| `state/mandates.json` | Append-only record of every permission deployed/attached from this project |
|
|
52
|
+
| `runtime/` | Live process state: `ui.json` (dashboard pid/port), `server.json` (signing station url/pid) |
|
|
53
|
+
| `activity.jsonl` | Unified activity log — agent dispatches and owner signing decisions, one JSON per line |
|
|
54
|
+
| `.env.local` | RPC_URL / CHAIN_ID / per-chain RPC vars / SAIL_PASSPHRASE — never commit or print |
|
|
55
|
+
|
|
56
|
+
## Skills
|
|
57
|
+
|
|
58
|
+
Detailed procedures live in skills. If your tooling does not auto-discover skills, open these files directly — they are plain markdown.
|
|
59
|
+
|
|
60
|
+
| Skill | Load when | Path |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| sail-onboarding | New project setup, or resuming a partially set-up project | `.agents/skills/sail-onboarding/SKILL.md` |
|
|
63
|
+
| sail-project-info | Any question about project, account, mandate, chain, or environment state | `.agents/skills/sail-project-info/SKILL.md` |
|
|
64
|
+
| sail-servers | Starting, stopping, or health-checking the dashboard or signing station | `.agents/skills/sail-servers/SKILL.md` |
|
|
65
|
+
| sail-transactions | Building dispatches or any EVM transaction for the agent | `.agents/skills/sail-transactions/SKILL.md` |
|
|
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` |
|
|
68
|
+
| sail-extend | Notifications or a custom dashboard, once the agent is live | `.agents/skills/sail-extend/SKILL.md` |
|
|
69
|
+
|
|
70
|
+
## Invariants — apply to every turn
|
|
161
71
|
|
|
162
72
|
- Do not present the welcome and immediately launch the UI — wait for the second "start"
|
|
163
|
-
- Do not describe, mention, or present any code in `src/` or `examples/` as the user's strategy — treat
|
|
73
|
+
- Do not describe, mention, or present any code in `src/` or `examples/` as the user's strategy — treat strategy definition as a blank slate; ask what they want
|
|
164
74
|
- Do not ask a running agent to confirm individual dispatches within its mandate
|
|
165
75
|
- Do not put an owner key in the terminal — owner signing is browser-only
|
|
166
|
-
- Do not hand-roll dispatch EIP-712 signatures — use `buildDispatchSignature`
|
|
167
|
-
- Do not hardcode the dispatch model — detect it on-chain
|
|
76
|
+
- Do not hand-roll dispatch EIP-712 signatures — use `buildDispatchSignature` from the SDK
|
|
77
|
+
- Do not hardcode the dispatch model — detect it on-chain with `detectKernelCapabilities`
|
|
168
78
|
- Do not present example permissions as audited or as a supported menu
|
|
169
79
|
- Do not commit `SAIL_PASSPHRASE` or private keys
|
|
170
|
-
-
|
|
80
|
+
- ERC-20 `approve()` calls are NOT covered by supply, swap, or deposit permissions — every approve the strategy makes needs explicit coverage. Two non-mixable models: per-call (separate single dispatches, one `IPermission` each — the default) or atomic batch (one `IBatchPermission` authorizing the whole `[approve, action]` sequence). A normal `IPermission` cannot authorize a batch. Details: `.agents/skills/sail-mandates/references/approvals.md`
|
|
81
|
+
- Never authorize (attach) a permission before `forge test` and `sailor mandate simulate` both pass against samples derived from the user's strategy
|
|
171
82
|
- Do not pass `--args` inline JSON from PowerShell — use `--args-file` instead
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {Context} from "@sail/interfaces/IPermission.sol";
|
|
5
|
+
import {BoundedCallPermission} from "../mandates/BoundedCallPermission.sol";
|
|
6
|
+
|
|
7
|
+
/// Foundry tests for the scaffolded example permission. Runs with `forge test`
|
|
8
|
+
/// and needs no external libraries: a failed `require` reverts, and a reverting
|
|
9
|
+
/// test function is a test failure. (Add forge-std with `forge install
|
|
10
|
+
/// foundry-rs/forge-std` if you want richer assertions and cheatcodes.)
|
|
11
|
+
///
|
|
12
|
+
/// When you author a permission in mandates/, copy this file and derive the
|
|
13
|
+
/// cases from the user's strategy: every call the agent must be able to make
|
|
14
|
+
/// (evaluate returns true) and every bound it must not cross (returns false).
|
|
15
|
+
/// `forge test` must pass BEFORE `sailor mandate deploy`, and simulate must
|
|
16
|
+
/// pass before `sailor mandate attach`.
|
|
17
|
+
contract BoundedCallPermissionTest {
|
|
18
|
+
address internal constant ROUTER = 0x1111111111111111111111111111111111111111;
|
|
19
|
+
address internal constant UNKNOWN = 0x2222222222222222222222222222222222222222;
|
|
20
|
+
bytes4 internal constant ALLOWED_SELECTOR = bytes4(keccak256("exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))"));
|
|
21
|
+
bytes4 internal constant OTHER_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
|
|
22
|
+
|
|
23
|
+
BoundedCallPermission internal permission;
|
|
24
|
+
|
|
25
|
+
function setUp() public {
|
|
26
|
+
address[] memory targets = new address[](1);
|
|
27
|
+
targets[0] = ROUTER;
|
|
28
|
+
bytes4[] memory selectors = new bytes4[](1);
|
|
29
|
+
selectors[0] = ALLOWED_SELECTOR;
|
|
30
|
+
permission = new BoundedCallPermission(targets, selectors, 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function _ctx(address target, bytes4 selector, uint256 value) internal view returns (Context memory) {
|
|
34
|
+
return Context({
|
|
35
|
+
account: address(0xACC0), // the Safe
|
|
36
|
+
manager: address(0xA9E7), // the agent wallet
|
|
37
|
+
submitter: address(0xA9E7),
|
|
38
|
+
target: target,
|
|
39
|
+
selector: selector,
|
|
40
|
+
value: value,
|
|
41
|
+
blockTimestamp: block.timestamp,
|
|
42
|
+
blockNumber: block.number
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function test_AllowsBoundedCall() public view {
|
|
47
|
+
require(
|
|
48
|
+
permission.evaluate("", _ctx(ROUTER, ALLOWED_SELECTOR, 0)),
|
|
49
|
+
"must allow allowlisted target + selector with no ETH"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function test_RejectsUnknownTarget() public view {
|
|
54
|
+
require(
|
|
55
|
+
!permission.evaluate("", _ctx(UNKNOWN, ALLOWED_SELECTOR, 0)),
|
|
56
|
+
"must reject a target outside the allowlist"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function test_RejectsUnknownSelector() public view {
|
|
61
|
+
require(
|
|
62
|
+
!permission.evaluate("", _ctx(ROUTER, OTHER_SELECTOR, 0)),
|
|
63
|
+
"must reject a selector outside the allowlist"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function test_RejectsEthValueAboveMax() public view {
|
|
68
|
+
require(
|
|
69
|
+
!permission.evaluate("", _ctx(ROUTER, ALLOWED_SELECTOR, 1)),
|
|
70
|
+
"must reject ETH value above MAX_VALUE"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{F as o}from"./core-BJ5Wn_0H.js";import"./index-Bj9jEvxf.js";import"./events-HAbmebGY.js";import"./index.es-DIo7ubqt.js";import"./fallback-CUotDuSc.js";const l=o` <svg fill="none" viewBox="0 0 13 4">
|
|
2
|
-
<path fill="currentColor" d="M.5 0h12L8.9 3.13a3.76 3.76 0 0 1-4.8 0L.5 0Z" />
|
|
3
|
-
</svg>`;export{l as cursorSvg};
|