@dev.sail.money/sailor 0.0.2-31 → 0.0.2-34

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.
Files changed (99) hide show
  1. package/AGENTS.md +11 -12
  2. package/README.md +100 -118
  3. package/package.json +1 -1
  4. package/packages/cli/dist/index.cjs +44 -20
  5. package/packages/cli/dist/server.cjs +20 -9
  6. package/packages/sdk/dist/intelligence.d.ts +1 -1
  7. package/packages/sdk/dist/intelligence.js +1 -1
  8. package/packages/ui/dist/assets/{add-B0J2XPqD.js → add-BqSzokiL.js} +1 -1
  9. package/packages/ui/dist/assets/{all-wallets-DAWTUGbI.js → all-wallets-DRghRzBH.js} +1 -1
  10. package/packages/ui/dist/assets/{app-store-B-bz9zO1.js → app-store-CHQohbxT.js} +1 -1
  11. package/packages/ui/dist/assets/{apple-CW_aatUl.js → apple-DyXS52Cz.js} +1 -1
  12. package/packages/ui/dist/assets/{arrow-bottom-D9xphoWP.js → arrow-bottom-CvZE3jWY.js} +1 -1
  13. package/packages/ui/dist/assets/{arrow-bottom-circle-D-N3HlXh.js → arrow-bottom-circle-BP_2WEkR.js} +1 -1
  14. package/packages/ui/dist/assets/{arrow-left-DofAd9ta.js → arrow-left--QDrrrK2.js} +1 -1
  15. package/packages/ui/dist/assets/{arrow-right-CLBZVLVF.js → arrow-right-AdmPdxdO.js} +1 -1
  16. package/packages/ui/dist/assets/{arrow-top-B47Y4sI6.js → arrow-top-Bwg6vVDn.js} +1 -1
  17. package/packages/ui/dist/assets/{bank-CVHPZeNC.js → bank-Cz2q0uEn.js} +1 -1
  18. package/packages/ui/dist/assets/{basic-CijTV8XG.js → basic-yFwOroQX.js} +1 -1
  19. package/packages/ui/dist/assets/{browser-D12J6hPl.js → browser-oL-NMPhY.js} +1 -1
  20. package/packages/ui/dist/assets/{card-suad8wBG.js → card-CF8mKo0W.js} +1 -1
  21. package/packages/ui/dist/assets/{ccip-Bev57e2Y.js → ccip-C4BRVTyT.js} +1 -1
  22. package/packages/ui/dist/assets/{checkmark-DSzbM9ge.js → checkmark-CGcssumz.js} +1 -1
  23. package/packages/ui/dist/assets/{checkmark-bold-Ctlpy8fR.js → checkmark-bold-CEx1x56A.js} +1 -1
  24. package/packages/ui/dist/assets/{chevron-bottom-DD4PYpsh.js → chevron-bottom-B17SVO6Z.js} +1 -1
  25. package/packages/ui/dist/assets/{chevron-left-HJzgI5fr.js → chevron-left-Bmlagpn4.js} +1 -1
  26. package/packages/ui/dist/assets/{chevron-right-BAJMtoWG.js → chevron-right-Ce0oTagP.js} +1 -1
  27. package/packages/ui/dist/assets/{chevron-top-CSTGBRNq.js → chevron-top-Ou3mYfjQ.js} +1 -1
  28. package/packages/ui/dist/assets/{chrome-store-CSgmzP0o.js → chrome-store-DWdtrqUk.js} +1 -1
  29. package/packages/ui/dist/assets/{clock-BGKXrbjA.js → clock-gBTuGt5c.js} +1 -1
  30. package/packages/ui/dist/assets/{close-B-9LI-cc.js → close-c4Ordvo2.js} +1 -1
  31. package/packages/ui/dist/assets/{coinPlaceholder-C9zB6O8f.js → coinPlaceholder-DKx_73vx.js} +1 -1
  32. package/packages/ui/dist/assets/{compass-DNbNVsgN.js → compass-Blxk0h-Z.js} +1 -1
  33. package/packages/ui/dist/assets/{copy-r_J027hY.js → copy-Cn01fjX1.js} +1 -1
  34. package/packages/ui/dist/assets/{core-CuWvvvu4.js → core-KcoX-IO2.js} +3 -3
  35. package/packages/ui/dist/assets/cursor-D7a2ynBo.js +3 -0
  36. package/packages/ui/dist/assets/{cursor-transparent-By6KxbOE.js → cursor-transparent-DiQTwo8L.js} +1 -1
  37. package/packages/ui/dist/assets/{desktop-DRMmsjrd.js → desktop-c0OGFp4k.js} +1 -1
  38. package/packages/ui/dist/assets/{disconnect-C69Z8KUW.js → disconnect-D6nYEXxt.js} +1 -1
  39. package/packages/ui/dist/assets/{discord-p3AKvqDk.js → discord-CU6UsGj5.js} +1 -1
  40. package/packages/ui/dist/assets/{etherscan-C2zTiWaN.js → etherscan-H4xFZDt1.js} +1 -1
  41. package/packages/ui/dist/assets/{events-DKTfpIHs.js → events-Bh5TaEvc.js} +1 -1
  42. package/packages/ui/dist/assets/{exclamation-triangle-D4IJznwI.js → exclamation-triangle-k3JV87dF.js} +1 -1
  43. package/packages/ui/dist/assets/{extension-C0y2g1tg.js → extension-Db7in92k.js} +1 -1
  44. package/packages/ui/dist/assets/{external-link-fkbBBTcW.js → external-link-M8ucb8_c.js} +1 -1
  45. package/packages/ui/dist/assets/{facebook-nsIgKROR.js → facebook-DqyDwZLk.js} +1 -1
  46. package/packages/ui/dist/assets/{fallback-DHv3hSPW.js → fallback-CpXrChod.js} +1 -1
  47. package/packages/ui/dist/assets/{farcaster-CzBHn8fo.js → farcaster-BhXVEUn7.js} +1 -1
  48. package/packages/ui/dist/assets/{filters-gW1TGI8D.js → filters-BsmF8Lsr.js} +1 -1
  49. package/packages/ui/dist/assets/{github-D9UuzE25.js → github-CTlGk78L.js} +1 -1
  50. package/packages/ui/dist/assets/{google-DxUfChw6.js → google-IbDv5sDf.js} +1 -1
  51. package/packages/ui/dist/assets/{help-circle-2dNDsXrX.js → help-circle-CxsLWQrd.js} +1 -1
  52. package/packages/ui/dist/assets/{id-rNBDU8mz.js → id-CiF75iBE.js} +1 -1
  53. package/packages/ui/dist/assets/{image-C9Peu4QW.js → image-B5bE_jY7.js} +1 -1
  54. package/packages/ui/dist/assets/{index-B1wosqUU.js → index-C4oDoM-t.js} +1 -1
  55. package/packages/ui/dist/assets/{index-JwrWbcaz.js → index-CGF5V3MX.js} +1 -1
  56. package/packages/ui/dist/assets/{index-_F9WbMAT.js → index-CNQ6HiTi.js} +3 -3
  57. package/packages/ui/dist/assets/{index-BzT0MJhc.js → index-DE_m8aQ1.js} +78 -78
  58. package/packages/ui/dist/assets/{index-4lrTXbkY.js → index-DKKjEVeH.js} +1 -1
  59. package/packages/ui/dist/assets/{index-B1aFIpJ0.js → index-DcrJPNJt.js} +1 -1
  60. package/packages/ui/dist/assets/{index.es-wlYgJouQ.js → index.es-B6xObjNP.js} +4 -4
  61. package/packages/ui/dist/assets/{info-cGbqKpFv.js → info-DPW3VbOc.js} +1 -1
  62. package/packages/ui/dist/assets/{info-circle-B8Xfr9A0.js → info-circle-lcMYVscf.js} +1 -1
  63. package/packages/ui/dist/assets/{lightbulb-CM2m-PnZ.js → lightbulb-DZeX-zL3.js} +1 -1
  64. package/packages/ui/dist/assets/{mail-_qO7Zcxu.js → mail-BXAoBPf-.js} +1 -1
  65. package/packages/ui/dist/assets/{metamask-sdk-Dy961bnw.js → metamask-sdk-EP1I3cQl.js} +1 -1
  66. package/packages/ui/dist/assets/{mobile-C6TDJh2K.js → mobile-Bacg80DQ.js} +1 -1
  67. package/packages/ui/dist/assets/{more-3pPTR0Gx.js → more-HXg_jFBb.js} +1 -1
  68. package/packages/ui/dist/assets/{network-placeholder-BtFT2yZA.js → network-placeholder-BeKgcBFu.js} +1 -1
  69. package/packages/ui/dist/assets/{nftPlaceholder-BfBZEH1N.js → nftPlaceholder-CbZ8lpZw.js} +1 -1
  70. package/packages/ui/dist/assets/{off-Bg5cnmyC.js → off-DbC3cKAC.js} +1 -1
  71. package/packages/ui/dist/assets/{parseSignature-CSIsnC1G.js → parseSignature-Diak-ncT.js} +1 -1
  72. package/packages/ui/dist/assets/{play-store-Dg32m5PL.js → play-store-CGxL9l1V.js} +1 -1
  73. package/packages/ui/dist/assets/{plus-Ce97GbOa.js → plus-Bxn8ifny.js} +1 -1
  74. package/packages/ui/dist/assets/{qr-code-D3KdZWUh.js → qr-code-86kM91qz.js} +1 -1
  75. package/packages/ui/dist/assets/{recycle-horizontal-DOKfyzVh.js → recycle-horizontal-Cn7PmrIT.js} +1 -1
  76. package/packages/ui/dist/assets/{refresh-DSjW7q17.js → refresh-Dsn4oWtE.js} +1 -1
  77. package/packages/ui/dist/assets/{reown-logo-B0n-8waR.js → reown-logo-BfLGU5kY.js} +1 -1
  78. package/packages/ui/dist/assets/{search-CL2iyGid.js → search-BkrouRbM.js} +1 -1
  79. package/packages/ui/dist/assets/{secp256k1-DdqDRGog.js → secp256k1-DnPEOQhM.js} +1 -1
  80. package/packages/ui/dist/assets/{send-C_Rm4fzj.js → send-Bj15h9aG.js} +1 -1
  81. package/packages/ui/dist/assets/{swapHorizontal-0d_94RdY.js → swapHorizontal-DHL_fMHx.js} +1 -1
  82. package/packages/ui/dist/assets/{swapHorizontalBold-BukSRa8V.js → swapHorizontalBold-CZFfrlj9.js} +1 -1
  83. package/packages/ui/dist/assets/{swapHorizontalMedium-DvroDkEf.js → swapHorizontalMedium-BbRRKxwu.js} +1 -1
  84. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-BAehcn9y.js → swapHorizontalRoundedBold-BCd9TFoe.js} +1 -1
  85. package/packages/ui/dist/assets/{swapVertical-kblIte_7.js → swapVertical-DUlxeXRd.js} +1 -1
  86. package/packages/ui/dist/assets/{telegram-DHLO89MI.js → telegram-Z7oqwOYs.js} +1 -1
  87. package/packages/ui/dist/assets/{three-dots-ctb5FHLw.js → three-dots-N_HHY4eF.js} +1 -1
  88. package/packages/ui/dist/assets/{twitch-CK_fCqNu.js → twitch-LHuomK7F.js} +1 -1
  89. package/packages/ui/dist/assets/{twitterIcon-BCngN3WD.js → twitterIcon-aqwTGFUp.js} +1 -1
  90. package/packages/ui/dist/assets/{verify-Dy-B59vy.js → verify-Dk65Ky3F.js} +1 -1
  91. package/packages/ui/dist/assets/{verify-filled-DHDHx8Lk.js → verify-filled-CYnRVU40.js} +1 -1
  92. package/packages/ui/dist/assets/{w3m-modal-DRNXP3Ww.js → w3m-modal-CIO-YdMF.js} +1 -1
  93. package/packages/ui/dist/assets/{wallet-DriPOF7d.js → wallet-NSBhLacz.js} +1 -1
  94. package/packages/ui/dist/assets/{wallet-placeholder-B4ukOjpR.js → wallet-placeholder-DZOTp3gD.js} +1 -1
  95. package/packages/ui/dist/assets/{walletconnect-Cjl1Ki75.js → walletconnect-T36vfDCB.js} +1 -1
  96. package/packages/ui/dist/assets/{warning-circle-C7eCTFhJ.js → warning-circle-D3dtMC5l.js} +1 -1
  97. package/packages/ui/dist/assets/{x-B8jYZY9t.js → x-wOYd1xe3.js} +1 -1
  98. package/packages/ui/dist/index.html +1 -1
  99. package/packages/ui/dist/assets/cursor-0ZcCqvYy.js +0 -3
package/AGENTS.md CHANGED
@@ -28,16 +28,15 @@ Use the user-facing terms in all CLI output, prompts, and errors. The code ident
28
28
 
29
29
  ## Dispatch model
30
30
 
31
- Active kernels vary by chain verified on-chain via `DISPATCH_TYPEHASH()`:
31
+ All six chains share the same kernel at the same CREATE2 address, verified on-chain via `DISPATCH_TYPEHASH()`:
32
32
 
33
- | Chain | Kernel | Model | DISPATCH_TYPEHASH |
34
- |---|---|---|---|
35
- | Base 8453 | `0x6319d3dfDDe3804ba93D65752b00c52bFb05a1ab` | **selective** | `0xbe50c539...` |
36
- | Base Sepolia 84532 | `0xf1D0F4C9893612627409948BAa9d82a01a373799` | **selective** | `0xbe50c539...` |
37
- | Arbitrum 42161 | `0x2716B12832DED0EF5688519c5Fe069EFc0374E02` | **selective** | `0xbe50c539...` |
38
- | Unichain 130 | `0xD985029960a9B7C2E7E38e102C448b8b8539B156` | **selective** | `0xbe50c539...` |
33
+ | Kernel (all 6 chains) | Model | DISPATCH_TYPEHASH |
34
+ |---|---|---|
35
+ | `0x02ABC18B65A328de2e749F56ba79ACF2718a6659` | **selective** | `0xbe50c539...` |
36
+
37
+ Supported chains: Ethereum (1), Base (8453), Arbitrum (42161), Unichain (130), Base Sepolia (84532), Eth Sepolia (11155111).
39
38
 
40
- All four kernels are live and bootstrapped (genesis allowlist set, `createAccount` verified working, zero fees). Unichain (130) additionally has the full permission-template suite deployed and source-verified (7 shared + 12 standalone) it is the only chain with templates so far; the other three have core only. `packages/sdk/src/deployments.ts` is the canonical source of truth for kernel addresses, templates, and metadata.
39
+ All six kernels are live and bootstrapped (genesis allowlist set, `createAccount` verified working, zero fees). No templates are deployed against the current kernel on any chain yet`knownTemplates` and `standaloneTemplates` are empty for all six entries. `packages/sdk/src/deployments.ts` is the canonical source of truth for kernel addresses, templates, and metadata.
41
40
 
42
41
  **Always use `detectKernelCapabilities` for the real model** — it reads the on-chain typehash and
43
42
  overrides the static label in `deployments.ts`. The static label is a fallback for offline use only.
@@ -56,11 +55,11 @@ Pass the detected value — never hardcode the type shape.
56
55
 
57
56
  ## Active addresses
58
57
 
59
- All four chain records in `packages/sdk/src/deployments.ts` are live — no commented-out or pending
58
+ All six chain records in `packages/sdk/src/deployments.ts` are live — no commented-out or pending
60
59
  addresses remain. This file is the source of truth this guide mirrors.
61
60
 
62
61
  - `packages/sdk/src/deployments.ts` — `SailDeployment` records; canonical source of truth
63
- - `packages/chains/src/index.ts` — `ChainConfig` per chainId; kept in sync with deployments
62
+ - `packages/sdk/src/chains.ts` — `ChainConfig` per chainId; canonical per-chain registry
64
63
 
65
64
  ## Key files
66
65
 
@@ -78,10 +77,10 @@ addresses remain. This file is the source of truth this guide mirrors.
78
77
 
79
78
  ```bash
80
79
  pnpm install
81
- pnpm build # builds all packages; dependency order: sdk → chains → cli → ui
80
+ pnpm build # builds all packages; dependency order: sdk → cli → ui
82
81
  ```
83
82
 
84
- Build order matters — `cli` imports from `sdk` and `chains`.
83
+ Build order matters — `cli` imports from `sdk`.
85
84
 
86
85
  ## Test
87
86
 
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Sailor
2
2
 
3
- > A toolkit for building and operating Sail Protocol SMAs with AI agents.
3
+ > The operator toolkit for Sail Protocol — SDK, CLI, and local dashboard for building and running mandated agents.
4
4
 
5
- Sailor is the operator layer for [Sail Protocol](../SailProtocol): the tooling an agent builder uses to create a Separately Managed Account, bound it with permissions, and run a strategy against it. It wraps the on-chain primitives — SailKernel dispatch, MandateFactory registration, EIP-712 mandate signing behind a TypeScript SDK, a CLI, and a local dashboard. An agent is an async function that receives context and returns intended transactions; Sailor previews each through the kernel, executes the approved ones, and records what happened. It does not deploy the protocol or author new permission templates — that lives in Sail Protocol. It sits one level up: turning a deployed SailKernel into something an operator can actually drive.
5
+ Sailor is the off-chain operator layer for [Sail Protocol](https://github.com/sail-money/SailProtocol): the tooling an operator uses to create a Separately Managed Account, register a mandate, and run a strategy agent against it. It wraps SailKernel dispatch, MandateFactory registration, and EIP-712 mandate signing behind a TypeScript SDK, a CLI, and a local dashboard. It does not deploy the protocol or author permission templates — those live in Sail Protocol. It targets already-deployed SailKernel instances and gives operators the tooling to drive them.
6
6
 
7
7
  ---
8
8
 
@@ -10,38 +10,60 @@ Sailor is the operator layer for [Sail Protocol](../SailProtocol): the tooling a
10
10
 
11
11
  | Package | Name | Role |
12
12
  |---|---|---|
13
- | `packages/sdk` | `@sail/sdk` (internal) | TypeScript library: SailorClient, EIP-712 helpers, ABIs, chain registry |
13
+ | `packages/sdk` | `@sail.money/sdk` / `@sail.money/sailor/sdk` | TypeScript library: SailorClient, EIP-712 helpers, ABIs, deployment registry, chain registry |
14
14
  | `packages/cli` | `@sail.money/sailor` | CLI for account setup, mandate signing, and agent execution |
15
- | `packages/ui` | `sailor-ui` | Local dashboard running on localhost:3333 |
15
+ | `packages/ui` | `sailor-ui` | Local dashboard running at localhost:3333 |
16
16
  | `templates/default` | — | Default agent starter (neutral; what `sailor init` scaffolds) |
17
17
  | `templates/custom-mandate` | — | Solidity reference: IPermission scaffold (not a project template) |
18
18
  | `templates/lifi-permissions` | — | Solidity reference: LiFi clone permission contracts (not a project template) |
19
19
 
20
20
  ---
21
21
 
22
- ## How it works
23
-
24
- The path from nothing to a running agent is five stages, guided by your AI coding assistant through the scaffolded `AGENTS.md`:
25
-
26
- 1. **Deploy your SMA and create your agent wallet** — done in the browser. Your owner wallet never leaves it.
27
- 2. **Define your strategy** — describe what you want your agent to do. The assistant asks the right questions to establish on-chain bounds (tokens, amounts, venues), then helps design the permission contracts.
28
- 3. **Build, test, and sign your mandate** — the assistant authors the permission contracts, proves in plain English what each one permits and blocks, deploys them, and walks you through signing to authorize.
29
- 4. **Run** `sailor run` executes your agent locally on a schedule, or via the GitHub Actions workflow the scaffold provides.
30
- 5. **Extend** *(optional)* the assistant can wire notifications (Telegram, email) and build a custom dashboard tailored to your strategy.
22
+ ## Protocol model
23
+
24
+ ```mermaid
25
+ flowchart TD
26
+ Owner["**Owner**<br/>holds the Safe · signs the mandate"]
27
+ Manager["**Manager**<br/>agent · signs dispatches"]
28
+ SMA["**SMA**<br/>Safe · holds assets · executes"]
29
+ Mandate["**Mandate**<br/>set of permission contracts"]
30
+ Kernel["**Sail Kernel**<br/>evaluates permission · trusted core<br/>dispatches to Safe on success"]
31
+
32
+ Owner -- "01 deploys & owns" --> SMA
33
+ Owner -- "02 signs mandate (EIP-712)" --> Mandate
34
+ Owner -- "03 appoints · instant revocation" --> Manager
35
+ Manager -- "04 signs dispatch (EIP-712)" --> Kernel
36
+ Mandate -- "05 defines bounds" --> Kernel
37
+ Kernel -- "06 ✓ executes · ✗ outside mandate: reverts" --> SMA
38
+ ```
31
39
 
32
- Run `npx sailor init my-agent`, open the scaffolded folder in Claude Code, Cursor, Codex, or any AI coding assistant, and say **"start"**. The `AGENTS.md` in the project drives the assistant through all five stages.
40
+ Sailor is the operator tooling that drives the Manager/dispatch and mandate-registration flows (steps 02–05).
33
41
 
34
42
  ---
35
43
 
36
44
  ## Roles
37
45
 
38
- Sailor operates the three roles Sail Protocol separates:
46
+ Sail Protocol separates three authority roles. Sailor operates all of them:
39
47
 
40
48
  | Role | Authority | Held by |
41
49
  |---|---|---|
42
- | **Owner** | Holds the Safe. Custody anchor. | The LP (Safe owner) — same wallet as MetaMask |
43
- | **Permission Signer** | Signs mandate registration and revocation via EIP-712. | Same as Owner, or a separate key |
44
- | **Manager** | Executes dispatches within permitted bounds. Signs each dispatch. | The agent key — encrypted in `.sail/keys/manager.json` |
50
+ | **Owner** | Holds the Safe. Custody anchor. Always self-custodial. | The LP (Safe owner) |
51
+ | **Permission Signer** | Authorizes the mandate. Signs registration and revocation via EIP-712. | Same as Owner, or a separate signing key |
52
+ | **Manager** | Executes dispatches within mandate bounds. Signs each dispatch. | The agent wallet — encrypted in `.sail/keys/manager.json` |
53
+
54
+ ---
55
+
56
+ ## How it works
57
+
58
+ The path from nothing to a running agent follows the protocol lifecycle:
59
+
60
+ 1. **Deploy your SMA** — `sailor onboard --new-sma` creates the SMA on-chain. `sailor account predict` computes the deterministic address in advance. The same owner, permission signer, manager, and salt produce the same SMA address on every supported chain.
61
+ 2. **Author your permissions** — describe what the agent may do. Permission contracts encode the bounds: tokens, amounts, venues, call targets. Author them in the scaffolded Foundry workspace.
62
+ 3. **Simulate, deploy, and sign your mandate** — `sailor mandate simulate` probes a permission off-chain before authorizing it. `sailor mandate deploy --attach` deploys and registers it on-chain. `sailor mandate sign` builds and signs the registration payload against live on-chain state.
63
+ 4. **Run** — `sailor run` executes the agent locally on a schedule, or via the GitHub Actions workflow the scaffold provides.
64
+ 5. **Operate** — `sailor doctor` checks kernel health and gas balances; `sailor chains` lists supported chains and deployment addresses; `sailor session pause` instantly revokes dispatch rights without touching Safe custody.
65
+
66
+ Run `npx sailor init my-agent`, open the scaffolded folder in Claude Code, Cursor, or any AI coding assistant, and say **"start"**. The `AGENTS.md` in the project guides the assistant through all five stages.
45
67
 
46
68
  ---
47
69
 
@@ -71,9 +93,9 @@ sailor init my-agent
71
93
  Prerequisites:
72
94
 
73
95
  - Node.js 18+
74
- - A wallet (MetaMask or Rabby)
96
+ - A wallet (MetaMask, Rabby, Phantom, and more)
75
97
  - An RPC URL (e.g. Alchemy free tier)
76
- - A supported chain: **Base, Base Sepolia, Arbitrum, or Unichain** — verified deployments are bundled in `@sail.money/sailor`.
98
+ - A supported chain: **Ethereum, Base, Arbitrum, Unichain, Base Sepolia, or Eth Sepolia** — verified deployments are bundled in `@sail.money/sailor`.
77
99
 
78
100
  ### Recommended — assistant-driven
79
101
 
@@ -84,18 +106,31 @@ npm install
84
106
 
85
107
  Open this folder in Claude Code, Cursor, Codex, or any AI coding assistant and say **"start"**. The scaffolded `AGENTS.md` guides the assistant through all five stages — SMA deployment, strategy definition, mandate authoring, running, and automation. No manual steps required.
86
108
 
87
- ### Direct CLI reference (advanced)
88
-
89
- For users who prefer the terminal:
109
+ ### Direct CLI reference
90
110
 
91
111
  ```bash
92
- sailor capabilities # what you can build on this chain — read-only, no gas
93
- sailor doctor # kernel model + RPC reachability + gas balances
94
- sailor ui start # open http://localhost:3333 to deploy SMA + create agent wallet
95
- sailor run --once # single tick confirm it works before automating
96
- sailor run # start the agent (continuous)
97
- sailor keys export-ci # copy the encrypted agent wallet to ci-keystore.json for CI commits
98
- sailor mandate sign # sign a mandate reconciles against live on-chain permissions first
112
+ # Discovery
113
+ sailor chains # list supported chains and kernel addresses
114
+ sailor capabilities # what you can build on this chain read-only, no gas
115
+ sailor doctor # kernel model + RPC reachability + gas balances
116
+
117
+ # SMA setup
118
+ sailor account predict # compute deterministic SMA address before deploying
119
+ sailor onboard --new-sma # deploy SMA and optionally attach a mandate
120
+
121
+ # Mandate lifecycle
122
+ sailor mandate simulate # probe a permission off-chain (no gas) before registering
123
+ sailor mandate sign # sign the mandate — reconciles against live on-chain state
124
+ sailor mandate deploy # deploy a Foundry-compiled permission contract
125
+ sailor mandate attach # register an already-deployed permission on an SMA
126
+
127
+ # Agent operation
128
+ sailor run --once # single tick — confirm it works before automating
129
+ sailor run # start the agent (continuous)
130
+ sailor keys export-ci # copy encrypted agent wallet to ci-keystore.json for CI
131
+
132
+ # Dashboard
133
+ sailor ui start # open http://localhost:3333
99
134
  ```
100
135
 
101
136
  `sailor run` writes reverted transactions to stderr as `reverted: <txHash> (gas used: N)`; successful dispatches are appended to `.sail/activity.jsonl`.
@@ -186,19 +221,19 @@ sailor ui stop
186
221
 
187
222
  ## Agent-driven onboarding & custom mandates
188
223
 
189
- For chains with a bundled Sail deployment (Base, Base Sepolia, Arbitrum, Unichain), an agent can drive the whole
190
- setup through a browser **signing station**. The station is a local HTTP +
191
- WebSocket daemon that bridges the CLI and the owner's wallet: the agent never
192
- holds the owner key — it pushes signing requests, the owner approves them in the
193
- browser, and the agent submits the transactions it's allowed to.
224
+ On any of the six supported chains, an agent can drive the whole setup through
225
+ a browser **signing station**. The station is a local HTTP + WebSocket daemon
226
+ that bridges the CLI and the owner's wallet: the agent never holds the owner
227
+ key — it pushes signing requests, the owner approves them in the browser, and
228
+ the agent submits the transactions it's allowed to.
194
229
 
195
230
  ```bash
196
- sailor keys generate # manager (agent) key
197
- sailor station start & # signing daemon (serves the UI)
231
+ sailor keys generate # create the manager (agent) key
232
+ sailor station start & # signing daemon (serves the UI)
198
233
  # owner opens the printed URL once and connects their wallet
199
- sailor owner connect # detect & persist the owner
200
- sailor scan # discover the owner's Safes + state
201
- sailor onboard --new-sma # create an SMA + (optionally) attach a mandate
234
+ sailor owner connect # detect & persist the owner
235
+ sailor scan # discover the owner's Safes + state
236
+ sailor onboard --new-sma # create an SMA + (optionally) attach a mandate
202
237
  ```
203
238
 
204
239
  Agents author their own permission contracts and deploy them from the scaffolded
@@ -258,14 +293,16 @@ The SDK is available as a subpath export for use in agent code:
258
293
  import type { Agent, AgentContext, Dispatch } from '@sail.money/sailor/sdk'
259
294
  ```
260
295
 
296
+ The SDK is also published separately as `@sail.money/sdk` for projects that consume it independently of the CLI.
297
+
261
298
  ### npm (`publish-npm.yml`)
262
299
 
263
300
  Published to the public npm registry under the `@sail.money` scope.
264
301
 
265
302
  | Trigger | Package | Version | dist-tag |
266
303
  |---|---|---|---|
267
- | Tag push (`v*`) | `@sail.money/sailor` | `1.0.0` | `latest` |
268
- | Manual dispatch | `@dev.sail.money/sailor` | `1.0.0-42` | `dev` |
304
+ | Tag push (`v*`) | `@sail.money/sailor` | `0.1.0` | `latest` |
305
+ | Manual dispatch | `@dev.sail.money/sailor` | `0.1.0-42` | `dev` |
269
306
 
270
307
  ```bash
271
308
  npm install @sail.money/sailor # latest stable (tag push)
@@ -313,36 +350,6 @@ Either way, `@sail.money/sailor/sdk` imports work unchanged.
313
350
 
314
351
  ---
315
352
 
316
- ## Architecture
317
-
318
- ```
319
- ┌────────────────────┐ ┌────────────────────┐
320
- │ Permission Signer │ │ Manager/Agent │
321
- │ MetaMask / local │ │ .sail/keys/manager │
322
- └─────────┬──────────┘ └─────────┬──────────┘
323
- │ │
324
- │ EIP-712 mandate │ dispatch
325
- ▼ ▼
326
- ┌─────────────────────────────────────────────────────────────────────┐
327
- │ SailKernel │
328
- │ (Sail Protocol) │
329
- └─────────┬───────────────────────┬───────────────────────┬───────────┘
330
- │ │ │
331
- │ registration │ execution │ evaluation
332
- ▼ ▼ ▼
333
- ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
334
- │ MandateFactory │ │ Safe │ │ Permissions │
335
- │ (register perms) │ │ (custody) │ │ (named, per-call) │
336
- └────────────────────┘ └────────────────────┘ └────────────────────┘
337
-
338
- sailor CLI / @sail.money/sailor/sdk drive both signing paths above.
339
- .sail/ (account · mandate · activity) ──→ sailor-ui (localhost:3333)
340
- ```
341
-
342
- The CLI and SDK sit between the operator and SailKernel: they build the EIP-712 payloads, submit dispatches, and read kernel state via viem. The permission signer authorizes the mandate — registration runs through MandateFactory — while the manager key signs each dispatch the kernel evaluates against a named permission before executing it through the Safe. All local state — the deployed account, the signed mandate, and the agent's activity log — lives under `.sail/` on disk, which the dashboard reads through a small local server. Sailor never holds the Owner key and runs no hosted backend; the wallet talks to the chain directly.
343
-
344
- ---
345
-
346
353
  ## Security model
347
354
 
348
355
  - The agent signs dispatches; the kernel evaluates the named permission on every call. A permission returning false or exceeding its gas cap is treated as denial — fail-closed.
@@ -355,67 +362,42 @@ The CLI and SDK sit between the operator and SailKernel: they build the EIP-712
355
362
 
356
363
  ## State of the project
357
364
 
358
- Sailor is functional and published as [`@sail.money/sailor`](https://www.npmjs.com/package/@sail.money/sailor) on npm (v0.0.1). The SDK, CLI, keystore, mandate flows, agent runner, and dashboard are implemented and have been exercised end to end against Base Sepolia.
365
+ Sailor is functional and published as [`@sail.money/sailor`](https://www.npmjs.com/package/@sail.money/sailor) on npm (v0.1.0). The SDK, CLI, keystore, mandate flows, agent runner, and dashboard are implemented and have been exercised end to end.
359
366
 
360
- The Sail Protocol trusted core is deployed on Base, Base Sepolia, Arbitrum, and Unichain as staging deployments for testing ahead of a formal launch. All four run the selective dispatch model, with verified deployments bundled in `@sail.money/sailor`. These deployments are under an ongoing external audit by [Octane Security](https://octane.security) and are not final — do not use them with funds you are not prepared to lose. Permission templates are not yet deployed against the Base, Arbitrum, and Base Sepolia kernels; **Unichain** ships the full template suite (7 shared + 12 standalone, source-verified) and its template registries are populated. The remaining template registries will be filled in as templates are deployed on the other chains and at mainnet launch.
367
+ The Sail Protocol trusted core is deployed on six chains — Ethereum, Base, Arbitrum, Unichain, Base Sepolia, and Eth Sepolia — via CREATE2, with every core contract at the same address on every chain. All six run the selective dispatch model with zero fees and are bootstrapped with a genesis allowlist so `createAccount` is usable immediately. These deployments are under an ongoing external audit by [Octane Security](https://octane.security) and are not final — do not use them with funds you are not prepared to lose.
368
+
369
+ Permission templates have not yet been deployed against the current kernel on any chain; `knownTemplates` and `standaloneTemplates` are empty for all six chains in `packages/sdk/src/deployments.ts` and will be populated as templates are deployed and verified against the new kernel.
361
370
 
362
371
  ---
363
372
 
364
373
  ## Deployments
365
374
 
366
- The Sail Protocol trusted core is live on the following chains as **staging deployments** ahead of a formal launch. All addresses are bundled in `@sail.money/sailor` and run the selective dispatch model with zero fees. Permission templates are not yet deployed against the Base, Arbitrum, and Base Sepolia kernels; **Unichain** ships the full template suite (7 shared + 12 standalone, source-verified on uniscan.xyz) and has its onboarding allowlists seeded at genesis.
375
+ All core contracts are deployed at the same address on every supported chain via CREATE2 (commit `1199b33`, 2026-06-09). An SMA created with the same owner, permission signer, manager, fee policy, and salt has the same address on every supported chain.
367
376
 
368
- ### Base (8453)
377
+ ### Core addresses (identical on all 6 chains)
369
378
 
370
379
  | Contract | Address |
371
380
  |---|---|
372
- | SailKernel | `0x6319d3dfDDe3804ba93D65752b00c52bFb05a1ab` |
373
- | SailGovernance | `0x7E897D919872b1587577617ffFC42113679d0C50` |
374
- | Timelock | `0x8eC3Ca951E193C6E3713A70022454d7A1f083281` |
375
- | PermissionFactory | `0x7724EACd97C8601d5AC244Aadbf76ad87353Ff31` |
376
- | StandardFeePolicy | `0x65850a8D5050aeAade68289ff96c4F119a24B82e` |
377
- | SafeModuleEnabler | `0xC84EdE78f93291A1fab19F51c4c7e938AB302Edf` |
381
+ | SailKernel | `0x02ABC18B65A328de2e749F56ba79ACF2718a6659` |
382
+ | SailGovernance | `0x7A478118715791728BDE3bc7A4D7ECfdEB89C6EC` |
383
+ | TimelockController | `0xE48Ba8DB6d748adafD13155c3590f62e58a77f56` |
384
+ | MandateFactory | `0x14EDd6c2a56EfC0d71E215ab13094B9AF90543d2` |
385
+ | StandardFeePolicy | `0xe7B5901b839cFFDEd9D4108A22712C8BfdA1D80D` |
386
+ | SafeModuleEnabler | `0x7897Cb53a4be4a2eaAf46D60573C4Fd83b33fE1F` |
378
387
  | Treasury | `0xB01dCE443d052e44b7D13726c0EC9fFB7f5815B6` |
379
388
 
380
- ### Arbitrum (42161)
389
+ These addresses are bundled in `@sail.money/sailor` and exposed via `getSailDeployment(chainId)` in the SDK. The Protocol repository is the canonical source of truth for deployment details — see [deployments/addresses.md](https://github.com/sail-money/Protocol/blob/main/deployments/addresses.md).
381
390
 
382
- | Contract | Address |
383
- |---|---|
384
- | SailKernel | `0x2716B12832DED0EF5688519c5Fe069EFc0374E02` |
385
- | SailGovernance | `0xd6AbB7A1036ADc7958Abffec9Da03450c5a2Ec8e` |
386
- | Timelock | `0x114CB7110C780f7E3a6093AfE0B52463a569857C` |
387
- | PermissionFactory | `0x23681A8A4C9819D8EaB37E46B858da6F3c85E683` |
388
- | StandardFeePolicy | `0xAdfB986D48480bC67a7cF3751d30599161632e0D` |
389
- | SafeModuleEnabler | `0xabe2a6D03F592BC602cA1dBDCD885ba2493274f9` |
390
- | Treasury | `0xB01dCE443d052e44b7D13726c0EC9fFB7f5815B6` |
391
+ ### Supported chains
391
392
 
392
- ### Base Sepolia (84532)
393
-
394
- | Contract | Address |
393
+ | Chain | Chain ID |
395
394
  |---|---|
396
- | SailKernel | `0xf1D0F4C9893612627409948BAa9d82a01a373799` |
397
- | SailGovernance | `0xEaD44bC6999E7b00b9b2E11c1660248DC2a30993` |
398
- | Timelock | `0x97B863e392C9859336788D5Ec454527d33C95B74` |
399
- | PermissionFactory | `0xdfF6a2272F667cDf78Af4681b9c88A219998db95` |
400
- | StandardFeePolicy | `0x05570F7973b46Eb9Ed4518422891EFC26BD58b97` |
401
- | SafeModuleEnabler | `0xB2C2B52d94412e3472C9fb2B52186eA12a935869` |
402
- | Treasury | `0xB01dCE443d052e44b7D13726c0EC9fFB7f5815B6` |
403
-
404
- ### Unichain (130)
405
-
406
- First chain to ship the full permission-template suite (7 shared + 12 standalone, source-verified on [uniscan.xyz](https://uniscan.xyz)). Genesis allowlist bootstrap — onboarding usable without the 48h timelock.
407
-
408
- | Contract | Address |
409
- |---|---|
410
- | SailKernel | `0xD985029960a9B7C2E7E38e102C448b8b8539B156` |
411
- | SailGovernance | `0xAb5C90ECfF2763f6f20f8E553E3b8778dD9C349A` |
412
- | Timelock | `0xd44FbBB37f01e235E0EE5386948F216d36D0CEf2` |
413
- | PermissionFactory | `0x8edDb62Aa49CeB837abf2653be2d93Ad9Fe6777D` |
414
- | StandardFeePolicy | `0x7bBA8BE3c01c972757aA4a230A00D58aB600A1F1` |
415
- | SafeModuleEnabler | `0xFE9227A9F2baf704060c604466df354a5A137b9B` |
416
- | Treasury | `0xB01dCE443d052e44b7D13726c0EC9fFB7f5815B6` |
417
-
418
- The 19 template addresses are in `packages/sdk/src/deployments.ts` (`knownTemplates` + `standaloneTemplates` for chain 130).
395
+ | Ethereum | 1 |
396
+ | Base | 8453 |
397
+ | Arbitrum | 42161 |
398
+ | Unichain | 130 |
399
+ | Base Sepolia | 84532 |
400
+ | Eth Sepolia | 11155111 |
419
401
 
420
402
  ---
421
403
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.sail.money/sailor",
3
- "version": "0.0.2-31",
3
+ "version": "0.0.2-34",
4
4
  "description": "Operator toolkit for Sail Protocol",
5
5
  "bin": {
6
6
  "sailor": "packages/cli/dist/index.cjs"
@@ -38484,6 +38484,8 @@ function getRpcUrl(chainId) {
38484
38484
  const perChainVar = chains[chainId]?.rpcEnvVar;
38485
38485
  const fromProjectChain = perChainVar ? env[perChainVar] : void 0;
38486
38486
  if (fromProjectChain?.trim()) return fromProjectChain.trim();
38487
+ const fromProjectChainId = env[`RPC_URL_${chainId}`];
38488
+ if (fromProjectChainId?.trim()) return fromProjectChainId.trim();
38487
38489
  if (env.RPC_URL?.trim()) return env.RPC_URL.trim();
38488
38490
  const fromEnvChain = perChainVar ? process.env[perChainVar] : void 0;
38489
38491
  if (fromEnvChain?.trim()) return fromEnvChain.trim();
@@ -39294,6 +39296,15 @@ var SigningClient = class {
39294
39296
  throw new Error("Timed out waiting for wallet connection in the signing UI");
39295
39297
  }
39296
39298
  };
39299
+ function toLoopbackIPv4(url) {
39300
+ try {
39301
+ const u = new URL(url);
39302
+ if (u.hostname === "localhost") u.hostname = "127.0.0.1";
39303
+ return u.toString().replace(/\/$/, "");
39304
+ } catch {
39305
+ return url;
39306
+ }
39307
+ }
39297
39308
  function readRuntimeServerState(projectRoot) {
39298
39309
  const file = (0, import_node_path5.join)(projectRoot, RUNTIME_SERVER_FILE);
39299
39310
  if (!(0, import_node_fs6.existsSync)(file)) return null;
@@ -39303,10 +39314,13 @@ function readRuntimeServerState(projectRoot) {
39303
39314
  return null;
39304
39315
  }
39305
39316
  }
39317
+ function signingPageUrl(_channel, dashboardPort) {
39318
+ return `http://localhost:${dashboardPort}/#/station`;
39319
+ }
39306
39320
  async function discoverDaemon(projectRoot = process.cwd()) {
39307
39321
  const state = readRuntimeServerState(projectRoot);
39308
39322
  if (!state?.url) return null;
39309
- const client = new SigningClient(state.url, state.requestSecret ?? "");
39323
+ const client = new SigningClient(toLoopbackIPv4(state.url), state.requestSecret ?? "");
39310
39324
  return await client.ping() ? client : null;
39311
39325
  }
39312
39326
  async function createSigningChannel(projectRoot = process.cwd()) {
@@ -39650,7 +39664,7 @@ Deploying SMA on ${getChainById(targetChainId).name} (chain ${targetChainId})\u2
39650
39664
  const channel = await createSigningChannel(process.cwd());
39651
39665
  try {
39652
39666
  await channel.start();
39653
- const stationUrl = `http://localhost:${projectPort(process.cwd())}/#/station`;
39667
+ const stationUrl = signingPageUrl(channel, projectPort(process.cwd()));
39654
39668
  if (json) {
39655
39669
  console.log(
39656
39670
  JSON.stringify(
@@ -39801,7 +39815,9 @@ var ProjectContext = class {
39801
39815
  throw new Error('No Sailor project found here. Run "sailor init" first.');
39802
39816
  }
39803
39817
  this.config = cfg;
39804
- this.chainId = cfg.chainId ?? 8453;
39818
+ const envLocal = parseEnvFile(sailPath(".env.local"));
39819
+ const envChainId = process.env.CHAIN_ID ?? envLocal.CHAIN_ID;
39820
+ this.chainId = envChainId ? Number(envChainId) : cfg.chainId ?? 8453;
39805
39821
  this.deployment = getSailDeployment(this.chainId);
39806
39822
  const overrides = cfg.contracts ?? {};
39807
39823
  this.contracts = {
@@ -41076,7 +41092,7 @@ Project: ${project.name}`));
41076
41092
  () => console.log(
41077
41093
  `
41078
41094
  \u2192 Open the Sailor dashboard to approve signing requests:
41079
- http://localhost:${projectPort(process.cwd())}/#/station
41095
+ ${signingPageUrl(channel, projectPort(process.cwd()))}
41080
41096
  `
41081
41097
  )
41082
41098
  );
@@ -41633,7 +41649,7 @@ async function runDeploy(project, channel, options) {
41633
41649
  console.log(
41634
41650
  `
41635
41651
  \u2192 Open the Sailor dashboard to approve signing requests:
41636
- http://localhost:${projectPort(process.cwd())}/#/station
41652
+ ${signingPageUrl(channel, projectPort(process.cwd()))}
41637
41653
  `
41638
41654
  );
41639
41655
  console.log(`Pushing deploy request for "${contractName}"\u2026`);
@@ -41865,7 +41881,7 @@ ${spec.label} clone (${options.template})`);
41865
41881
  for (const d of spec.describe(initParams)) console.log(` ${d.label}: ${d.value}`);
41866
41882
  console.log(`
41867
41883
  \u2192 Open the Sailor dashboard to approve signing requests:
41868
- http://localhost:${projectPort(process.cwd())}/#/station
41884
+ ${signingPageUrl(channel, projectPort(process.cwd()))}
41869
41885
  `);
41870
41886
  });
41871
41887
  const nonce = await publicClient.readContract({
@@ -42029,7 +42045,7 @@ async function runAttach(project, channel, options) {
42029
42045
  console.log(
42030
42046
  `
42031
42047
  \u2192 Open the Sailor dashboard to approve signing requests:
42032
- http://localhost:${projectPort(process.cwd())}/#/station
42048
+ ${signingPageUrl(channel, projectPort(process.cwd()))}
42033
42049
  `
42034
42050
  );
42035
42051
  }
@@ -42112,7 +42128,7 @@ Revoking ${targets.length} permission(s) from ${sma}:`);
42112
42128
  console.log(
42113
42129
  `
42114
42130
  \u2192 Open the Sailor dashboard to approve signing requests:
42115
- http://localhost:${projectPort(process.cwd())}/#/station
42131
+ ${signingPageUrl(channel, projectPort(process.cwd()))}
42116
42132
  `
42117
42133
  );
42118
42134
  });
@@ -42642,7 +42658,9 @@ ${unregistered.length} permission(s) are not yet registered on this SMA. Initiat
42642
42658
  // Only include permissions that are currently active on-chain.
42643
42659
  permissions: activePermissions.map((p) => ({ template: p.label, params: {} }))
42644
42660
  };
42645
- writeJsonFile(sailPath("mandate.json"), storedMandate);
42661
+ const existingRaw = readJsonFile(sailPath("mandate.json"));
42662
+ const existing = existingRaw ? Array.isArray(existingRaw) ? existingRaw : [existingRaw] : [];
42663
+ writeJsonFile(sailPath("mandate.json"), [...existing, storedMandate]);
42646
42664
  console.log(`
42647
42665
  \u2713 Saved to .sail/mandate.json \u2014 agent is ready to run.`);
42648
42666
  }
@@ -43010,7 +43028,7 @@ SMA: ${smaAddress}`);
43010
43028
  console.log(
43011
43029
  `
43012
43030
  \u2192 Open the Sailor dashboard to approve signing requests:
43013
- http://localhost:${projectPort(process.cwd())}/#/station
43031
+ ${signingPageUrl(channel, projectPort(process.cwd()))}
43014
43032
  `
43015
43033
  );
43016
43034
  });
@@ -43386,7 +43404,7 @@ async function ownerConnect(options) {
43386
43404
  await channel.start();
43387
43405
  if (!options.json) {
43388
43406
  console.log("\u2192 Open the Sailor dashboard and connect your wallet:");
43389
- console.log(` http://localhost:${projectPort(projectRoot)}/#/station`);
43407
+ console.log(` ${signingPageUrl(channel, projectPort(projectRoot))}`);
43390
43408
  if (channel.remote) console.log(" (using the running signing station)");
43391
43409
  console.log("\nWaiting for a wallet connection\u2026");
43392
43410
  }
@@ -43536,7 +43554,8 @@ async function runCommand(opts) {
43536
43554
  if (!account2) {
43537
43555
  throw new Error('No account found at .sail/account.json.\nRun "sailor account create" first.');
43538
43556
  }
43539
- const mandate2 = readJsonFile(sailPath("mandate.json"));
43557
+ const mandateRaw = readJsonFile(sailPath("mandate.json"));
43558
+ const mandate2 = Array.isArray(mandateRaw) ? mandateRaw[0] : mandateRaw;
43540
43559
  if (!mandate2) {
43541
43560
  throw new Error('No mandate found at .sail/mandate.json.\nRun "sailor mandate sign" first.');
43542
43561
  }
@@ -43544,18 +43563,23 @@ async function runCommand(opts) {
43544
43563
  for (const [k, v] of Object.entries(env)) {
43545
43564
  if (v && !process.env[k]) process.env[k] = v;
43546
43565
  }
43547
- const rpcUrl = env.RPC_URL ?? process.env.RPC_URL;
43548
43566
  const configChainId = readJsonFile(sailPath("config.json"))?.chainId;
43549
- const chainIdRaw = env.CHAIN_ID ?? process.env.CHAIN_ID ?? (configChainId != null ? String(configChainId) : void 0);
43550
- if (!rpcUrl || !chainIdRaw) {
43567
+ const chainIdRaw = opts.chain != null ? String(opts.chain) : process.env.CHAIN_ID ?? env.CHAIN_ID ?? (configChainId != null ? String(configChainId) : void 0);
43568
+ if (!chainIdRaw) {
43551
43569
  throw new Error(
43552
- "RPC_URL must be set in .sail/.env.local.\n RPC_URL=https://your-rpc-endpoint\n CHAIN_ID=8453 (or omit if set in .sail/config.json)"
43570
+ "CHAIN_ID must be set in .sail/.env.local or .sail/config.json.\n CHAIN_ID=8453 (Base) or CHAIN_ID=42161 (Arbitrum)"
43553
43571
  );
43554
43572
  }
43555
43573
  const chainId = Number(chainIdRaw);
43556
43574
  if (Number.isNaN(chainId)) {
43557
43575
  throw new Error(`Invalid CHAIN_ID: "${chainIdRaw}".`);
43558
43576
  }
43577
+ const rpcUrl = getRpcUrl(chainId);
43578
+ if (!rpcUrl) {
43579
+ throw new Error(
43580
+ "RPC_URL must be set in .sail/.env.local.\n RPC_URL=https://your-rpc-endpoint (or BASE_RPC_URL / RPC_URL_8453 for per-chain config)"
43581
+ );
43582
+ }
43559
43583
  try {
43560
43584
  const parsed = new URL(rpcUrl);
43561
43585
  if (!["http:", "https:"].includes(parsed.protocol)) {
@@ -43625,7 +43649,7 @@ Configure the chain in @sail/chains or set KERNEL_ADDRESS in .sail/.env.local.`
43625
43649
  } catch {
43626
43650
  }
43627
43651
  const intervalSec = (() => {
43628
- const raw = env.SAILOR_INTERVAL ?? process.env.SAILOR_INTERVAL;
43652
+ const raw = process.env.SAILOR_INTERVAL ?? env.SAILOR_INTERVAL;
43629
43653
  const n = raw === void 0 ? DEFAULT_INTERVAL_SEC : Number(raw);
43630
43654
  return Number.isNaN(n) || n <= 0 ? DEFAULT_INTERVAL_SEC : n;
43631
43655
  })();
@@ -43634,7 +43658,7 @@ Configure the chain in @sail/chains or set KERNEL_ADDRESS in .sail/.env.local.`
43634
43658
  appendActivity({ ts: nowIso(), actor: "agent", type: "log", msg });
43635
43659
  };
43636
43660
  const agentData = {
43637
- ...loadAgentData(env.SAILOR_DATA ?? process.env.SAILOR_DATA),
43661
+ ...loadAgentData(process.env.SAILOR_DATA ?? env.SAILOR_DATA),
43638
43662
  _publicClient: publicClient
43639
43663
  };
43640
43664
  const readBalance = async (token) => {
@@ -44349,9 +44373,9 @@ owner.command("connect").description("Open the signing station, wait for your wa
44349
44373
  owner.command("show").description("Show the saved project owner").option("--json", "Emit machine-readable JSON").action(actionWith(ownerShow));
44350
44374
  program2.command("scan").description("Discover the owner's SMAs, their permissions, and local keys; save to context.json").option("--owner <address>", "Owner address to scan (defaults to the saved project owner)").option("--json", "Emit machine-readable JSON").action(actionWith(scan));
44351
44375
  program2.command("status").description("Show current account, permission, and session status").action(action(status));
44352
- program2.command("run").description("Run the agent execution loop (use --once for a single tick)").option("--once", "Run a single tick then exit").action(async (opts) => {
44376
+ program2.command("run").description("Run the agent execution loop (use --once for a single tick)").option("--once", "Run a single tick then exit").option("--chain <chainId>", "Chain ID to run on (overrides CHAIN_ID env and .env.local)").action(async (opts) => {
44353
44377
  try {
44354
- await runCommand({ once: opts.once });
44378
+ await runCommand({ once: opts.once, chain: opts.chain ? Number(opts.chain) : void 0 });
44355
44379
  } catch (err) {
44356
44380
  console.error(`Error: ${err.message}`);
44357
44381
  closePrompts();