@dev.sail.money/sailor 0.0.2 → 1.0.0-38

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 (199) hide show
  1. package/AGENTS.md +43 -15
  2. package/README.md +217 -126
  3. package/examples/permissions/BoundedApproveAndCallBatch.sol +179 -0
  4. package/examples/permissions/BoundedBet_Limitless_Base.sol +8 -7
  5. package/examples/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +13 -13
  6. package/examples/permissions/BoundedPerp_GMXv2_Arbitrum.sol +50 -39
  7. package/examples/permissions/BoundedStake_Venice_Base.sol +85 -0
  8. package/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +82 -0
  9. package/examples/permissions/BoundedSwap_UniswapV3_Base.sol +15 -12
  10. package/examples/permissions/BoundedSwap_UniswapV4_Unichain.sol +14 -8
  11. package/examples/permissions/BoundedTransfer_ERC20_Ethereum.sol +6 -6
  12. package/examples/permissions/BoundedVault_ERC4626_Base.sol +97 -0
  13. package/examples/permissions/README.md +29 -2
  14. package/examples/permissions/SailCalldata.sol +118 -0
  15. package/examples/permissions/interfaces/IBatchPermission.sol +38 -0
  16. package/package.json +17 -12
  17. package/packages/cli/dist/index.cjs +4164 -2508
  18. package/packages/cli/dist/server.cjs +897 -1566
  19. package/packages/sdk/dist/chains.d.ts +12 -0
  20. package/packages/sdk/dist/chains.d.ts.map +1 -0
  21. package/packages/sdk/dist/chains.js +94 -0
  22. package/packages/sdk/dist/chains.js.map +1 -0
  23. package/packages/sdk/dist/deployments.d.ts +14 -7
  24. package/packages/sdk/dist/deployments.d.ts.map +1 -1
  25. package/packages/sdk/dist/deployments.js +132 -141
  26. package/packages/sdk/dist/deployments.js.map +1 -1
  27. package/packages/sdk/dist/index.d.ts +3 -2
  28. package/packages/sdk/dist/index.d.ts.map +1 -1
  29. package/packages/sdk/dist/index.js +3 -2
  30. package/packages/sdk/dist/index.js.map +1 -1
  31. package/packages/sdk/dist/intelligence.d.ts +1 -1
  32. package/packages/sdk/dist/intelligence.js +1 -1
  33. package/packages/sdk/dist/lifi.d.ts +17 -0
  34. package/packages/sdk/dist/lifi.d.ts.map +1 -1
  35. package/packages/sdk/dist/lifi.js +24 -0
  36. package/packages/sdk/dist/lifi.js.map +1 -1
  37. package/packages/sdk/dist/safe.d.ts +83 -0
  38. package/packages/sdk/dist/safe.d.ts.map +1 -1
  39. package/packages/sdk/dist/safe.js +92 -1
  40. package/packages/sdk/dist/safe.js.map +1 -1
  41. package/packages/sdk/dist/templates/ammLiquidity.d.ts +24 -11
  42. package/packages/sdk/dist/templates/ammLiquidity.d.ts.map +1 -1
  43. package/packages/sdk/dist/templates/ammLiquidity.js +39 -31
  44. package/packages/sdk/dist/templates/ammLiquidity.js.map +1 -1
  45. package/packages/sdk/dist/templates/approveAndCallBatch.d.ts +24 -10
  46. package/packages/sdk/dist/templates/approveAndCallBatch.d.ts.map +1 -1
  47. package/packages/sdk/dist/templates/approveAndCallBatch.js +36 -23
  48. package/packages/sdk/dist/templates/approveAndCallBatch.js.map +1 -1
  49. package/packages/sdk/dist/templates/boundedBorrow.d.ts +19 -9
  50. package/packages/sdk/dist/templates/boundedBorrow.d.ts.map +1 -1
  51. package/packages/sdk/dist/templates/boundedBorrow.js +28 -19
  52. package/packages/sdk/dist/templates/boundedBorrow.js.map +1 -1
  53. package/packages/sdk/dist/templates/boundedSwap.d.ts +19 -9
  54. package/packages/sdk/dist/templates/boundedSwap.d.ts.map +1 -1
  55. package/packages/sdk/dist/templates/boundedSwap.js +30 -20
  56. package/packages/sdk/dist/templates/boundedSwap.js.map +1 -1
  57. package/packages/sdk/dist/templates/defiBundle.d.ts +35 -9
  58. package/packages/sdk/dist/templates/defiBundle.d.ts.map +1 -1
  59. package/packages/sdk/dist/templates/defiBundle.js +84 -22
  60. package/packages/sdk/dist/templates/defiBundle.js.map +1 -1
  61. package/packages/sdk/dist/templates/pendle.d.ts +23 -8
  62. package/packages/sdk/dist/templates/pendle.d.ts.map +1 -1
  63. package/packages/sdk/dist/templates/pendle.js +34 -14
  64. package/packages/sdk/dist/templates/pendle.js.map +1 -1
  65. package/packages/sdk/dist/templates/transferTarget.d.ts +11 -3
  66. package/packages/sdk/dist/templates/transferTarget.d.ts.map +1 -1
  67. package/packages/sdk/dist/templates/transferTarget.js +14 -7
  68. package/packages/sdk/dist/templates/transferTarget.js.map +1 -1
  69. package/packages/sdk/dist/types.d.ts +19 -1
  70. package/packages/sdk/dist/types.d.ts.map +1 -1
  71. package/packages/sdk/package.json +28 -0
  72. package/packages/ui/dist/assets/{add-DaJhwIBV.js → add-Dl1etsL9.js} +1 -1
  73. package/packages/ui/dist/assets/{all-wallets-BUxsqWXi.js → all-wallets-C0eHLOGG.js} +1 -1
  74. package/packages/ui/dist/assets/{app-store-DkltwTqE.js → app-store-B-VMDEZ3.js} +1 -1
  75. package/packages/ui/dist/assets/{apple-owVOeaIT.js → apple-DkDXzKns.js} +1 -1
  76. package/packages/ui/dist/assets/{arrow-bottom-D2mmNJve.js → arrow-bottom-DtPzuS76.js} +1 -1
  77. package/packages/ui/dist/assets/{arrow-bottom-circle-CbNYijx-.js → arrow-bottom-circle-D7odSAO8.js} +1 -1
  78. package/packages/ui/dist/assets/{arrow-left-DJB61s4C.js → arrow-left-zJV9tpx0.js} +1 -1
  79. package/packages/ui/dist/assets/{arrow-right-BBrsQ9R4.js → arrow-right-BOREfe7o.js} +1 -1
  80. package/packages/ui/dist/assets/{arrow-top-Cil6bOc8.js → arrow-top-CipQc3Af.js} +1 -1
  81. package/packages/ui/dist/assets/{bank-CbwEmRo3.js → bank-C5s7eoV5.js} +1 -1
  82. package/packages/ui/dist/assets/{basic-CLNfjw3m.js → basic-D2es4Vq8.js} +1 -1
  83. package/packages/ui/dist/assets/{browser-B5TtF4Pb.js → browser-DITQWDC9.js} +1 -1
  84. package/packages/ui/dist/assets/{card-CO7BVB-C.js → card-C3DDkaYK.js} +1 -1
  85. package/packages/ui/dist/assets/{ccip-2W7K3_J3.js → ccip-UBXL3JiN.js} +1 -1
  86. package/packages/ui/dist/assets/{checkmark-bold-D9xGHzPE.js → checkmark-bold-D8yW0_K_.js} +1 -1
  87. package/packages/ui/dist/assets/{checkmark-BEtSHq9m.js → checkmark-ngef3MAl.js} +1 -1
  88. package/packages/ui/dist/assets/{chevron-bottom-BDztht6i.js → chevron-bottom-C56BipDR.js} +1 -1
  89. package/packages/ui/dist/assets/{chevron-left-EV4GFNbc.js → chevron-left-BmIPtPl_.js} +1 -1
  90. package/packages/ui/dist/assets/{chevron-right-B4_bB9oR.js → chevron-right-BnySHQ8h.js} +1 -1
  91. package/packages/ui/dist/assets/{chevron-top-D54xPNzF.js → chevron-top-BDGZnNW3.js} +1 -1
  92. package/packages/ui/dist/assets/{chrome-store-DYUpAJJq.js → chrome-store-BYIqJZVF.js} +1 -1
  93. package/packages/ui/dist/assets/{clock-Ca1T1Soz.js → clock-Bl4mUHAM.js} +1 -1
  94. package/packages/ui/dist/assets/{close-BZqWjurK.js → close-B9rhEX6U.js} +1 -1
  95. package/packages/ui/dist/assets/{coinPlaceholder-e6fl2XDo.js → coinPlaceholder-1cO0FQsl.js} +1 -1
  96. package/packages/ui/dist/assets/{compass-DCLC7zIh.js → compass-7i-VuXu2.js} +1 -1
  97. package/packages/ui/dist/assets/{copy-Th2AaD-O.js → copy-OqqXix2J.js} +1 -1
  98. package/packages/ui/dist/assets/{core-Ckx_cyuH.js → core-tX9kIIDJ.js} +3 -3
  99. package/packages/ui/dist/assets/cursor-BoyeQ9fN.js +3 -0
  100. package/packages/ui/dist/assets/{cursor-transparent-BKHeABKB.js → cursor-transparent-5aoRH67u.js} +1 -1
  101. package/packages/ui/dist/assets/{desktop-CBjY8t6F.js → desktop-BaPXK9R6.js} +1 -1
  102. package/packages/ui/dist/assets/{disconnect-DbSs2cli.js → disconnect-LlK5K1CF.js} +1 -1
  103. package/packages/ui/dist/assets/{discord-ZlLOAUkM.js → discord-BdcQNWY_.js} +1 -1
  104. package/packages/ui/dist/assets/{etherscan-CKUrqWYN.js → etherscan-Bb-WxpO1.js} +1 -1
  105. package/packages/ui/dist/assets/{events-CiKP71cK.js → events-DjdZr6no.js} +1 -1
  106. package/packages/ui/dist/assets/{exclamation-triangle-DA1QzFiO.js → exclamation-triangle-COx4VtPV.js} +1 -1
  107. package/packages/ui/dist/assets/{extension-BVJkmvpJ.js → extension-DF63DTWO.js} +1 -1
  108. package/packages/ui/dist/assets/{external-link-D_bsR7B2.js → external-link-DyghCkQu.js} +1 -1
  109. package/packages/ui/dist/assets/{facebook-CmFmhojx.js → facebook-Dcg4bZMR.js} +1 -1
  110. package/packages/ui/dist/assets/{fallback-Ofl6uSnB.js → fallback-DJIr_fH3.js} +1 -1
  111. package/packages/ui/dist/assets/{farcaster-Co-M3Ss8.js → farcaster-BkmV5HjO.js} +1 -1
  112. package/packages/ui/dist/assets/{filters-B1WwNaFU.js → filters-DLHj1T_P.js} +1 -1
  113. package/packages/ui/dist/assets/{github-CP4fP6gn.js → github-BFDCgKrF.js} +1 -1
  114. package/packages/ui/dist/assets/{google-CsOIXJ6V.js → google-C08SpmIy.js} +1 -1
  115. package/packages/ui/dist/assets/{help-circle-DiMkomdF.js → help-circle-DU1IFmWp.js} +1 -1
  116. package/packages/ui/dist/assets/{id-lmscL5LX.js → id-DtDRGf3L.js} +1 -1
  117. package/packages/ui/dist/assets/{image-B-ubJrY5.js → image-CgCXJEjT.js} +1 -1
  118. package/packages/ui/dist/assets/{index-Dbh5V1Z0.js → index-8chM4S5Y.js} +1 -1
  119. package/packages/ui/dist/assets/{index-BaukYv-x.js → index-B5sCtNuq.js} +1 -1
  120. package/packages/ui/dist/assets/{index-CF0KMmke.js → index-BBfBEazf.js} +3 -3
  121. package/packages/ui/dist/assets/{index-CZR1Qjhs.js → index-BhXPwltt.js} +1 -1
  122. package/packages/ui/dist/assets/index-Cm05Py20.css +1 -0
  123. package/packages/ui/dist/assets/{index-DVgfCzCo.js → index-D37bD6Yt.js} +1 -1
  124. package/packages/ui/dist/assets/index-DZfBh-cg.js +1775 -0
  125. package/packages/ui/dist/assets/{index.es-C78cE5SI.js → index.es-BEcNQEn-.js} +4 -4
  126. package/packages/ui/dist/assets/{info-Cqg57EVo.js → info-ClsdYA4P.js} +1 -1
  127. package/packages/ui/dist/assets/{info-circle-DkeSWNKV.js → info-circle-DJmn4Bsv.js} +1 -1
  128. package/packages/ui/dist/assets/{lightbulb-DNlO4qKh.js → lightbulb-CXSftjXS.js} +1 -1
  129. package/packages/ui/dist/assets/{mail-kVQ8Jb9Y.js → mail-cdYKOl9P.js} +1 -1
  130. package/packages/ui/dist/assets/{metamask-sdk-CBalSvz7.js → metamask-sdk-Co3aIEln.js} +1 -1
  131. package/packages/ui/dist/assets/{mobile-BEteuhF7.js → mobile-DEHYlk8L.js} +1 -1
  132. package/packages/ui/dist/assets/{more-DBWmXQli.js → more-Cq_fo8pI.js} +1 -1
  133. package/packages/ui/dist/assets/{network-placeholder-Dg1uUHiL.js → network-placeholder-_dLCK4xB.js} +1 -1
  134. package/packages/ui/dist/assets/{nftPlaceholder-i3AHSiD9.js → nftPlaceholder-Dz4HKEr4.js} +1 -1
  135. package/packages/ui/dist/assets/{off-BtMm0fi2.js → off-B1k1lhkr.js} +1 -1
  136. package/packages/ui/dist/assets/{parseSignature-Cb5FlWWg.js → parseSignature-Cr0ptV2X.js} +1 -1
  137. package/packages/ui/dist/assets/{play-store-iKKkXa6a.js → play-store-lYqe4eeL.js} +1 -1
  138. package/packages/ui/dist/assets/{plus-CA5NaRtb.js → plus-QMgh1krr.js} +1 -1
  139. package/packages/ui/dist/assets/{qr-code-D2kiqR7h.js → qr-code-ClVHbZWN.js} +1 -1
  140. package/packages/ui/dist/assets/{recycle-horizontal-Dcme7R03.js → recycle-horizontal-CxGYnWid.js} +1 -1
  141. package/packages/ui/dist/assets/{refresh-Dega3sDp.js → refresh-CPysMza_.js} +1 -1
  142. package/packages/ui/dist/assets/{reown-logo-xNkksyWJ.js → reown-logo-OoL_zJd0.js} +1 -1
  143. package/packages/ui/dist/assets/{search-HYl7NO8x.js → search-2GaRbf1I.js} +1 -1
  144. package/packages/ui/dist/assets/{secp256k1-Cxd6_SiH.js → secp256k1-BrB8qSSy.js} +1 -1
  145. package/packages/ui/dist/assets/{send-CJU8CUAo.js → send-CC2UuIfD.js} +1 -1
  146. package/packages/ui/dist/assets/{swapHorizontal-IMUKiUre.js → swapHorizontal-BRqYwsqT.js} +1 -1
  147. package/packages/ui/dist/assets/{swapHorizontalBold-CNYnNJ9-.js → swapHorizontalBold-Bj0GSRq9.js} +1 -1
  148. package/packages/ui/dist/assets/{swapHorizontalMedium-B9VxEYsT.js → swapHorizontalMedium-oLOjpU2A.js} +1 -1
  149. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-Dz33l_Jh.js → swapHorizontalRoundedBold-TJ652QXb.js} +1 -1
  150. package/packages/ui/dist/assets/{swapVertical-CHUmjVJ0.js → swapVertical-e0NLyV3x.js} +1 -1
  151. package/packages/ui/dist/assets/{telegram-kl9S2mbU.js → telegram-WhJHVeoU.js} +1 -1
  152. package/packages/ui/dist/assets/{three-dots-U5lhA1Am.js → three-dots-BlBAOyW-.js} +1 -1
  153. package/packages/ui/dist/assets/{twitch-KTEUWXEp.js → twitch-BH7vWmPc.js} +1 -1
  154. package/packages/ui/dist/assets/{twitterIcon-BHiq8mRg.js → twitterIcon-As0Nkanp.js} +1 -1
  155. package/packages/ui/dist/assets/{verify-CfN-BXNd.js → verify-BEJ0QuLl.js} +1 -1
  156. package/packages/ui/dist/assets/{verify-filled-DwZccetj.js → verify-filled-B8Ww2N7z.js} +1 -1
  157. package/packages/ui/dist/assets/{w3m-modal-CS-PFqPE.js → w3m-modal-5rOSZgOR.js} +1 -1
  158. package/packages/ui/dist/assets/{wallet-DVlGkhOY.js → wallet-CAfC3aml.js} +1 -1
  159. package/packages/ui/dist/assets/{wallet-placeholder-CvR_iEWX.js → wallet-placeholder-4RZI464Z.js} +1 -1
  160. package/packages/ui/dist/assets/{walletconnect-8pZBDvVI.js → walletconnect-BiltKqAe.js} +1 -1
  161. package/packages/ui/dist/assets/{warning-circle-ylLEE0Yp.js → warning-circle-CI4jqpHo.js} +1 -1
  162. package/packages/ui/dist/assets/{x-C_TBsTMj.js → x-FBttjBWO.js} +1 -1
  163. package/packages/ui/dist/index.html +2 -2
  164. package/scripts/check-init.mjs +2 -3
  165. package/templates/custom-mandate/README.md +31 -0
  166. package/templates/custom-mandate/mandates/BoundedCallPermission.sol +8 -2
  167. package/templates/custom-mandate/mandates/SailCalldata.sol +118 -0
  168. package/templates/default/.env.example +20 -0
  169. package/templates/{dca-rebalancer → default}/.github/workflows/agent-tick.yml +8 -7
  170. package/templates/{dca-rebalancer → default}/.sail/config.json +2 -2
  171. package/templates/default/AGENTS.md +171 -0
  172. package/templates/default/README.md +16 -0
  173. package/templates/default/examples/dca/README.md +16 -0
  174. package/templates/default/examples/dca/agent.ts +174 -0
  175. package/templates/{dca-rebalancer/src → default/examples/dca}/mandate.ts +9 -31
  176. package/templates/{dca-rebalancer → default}/package.json +2 -2
  177. package/templates/default/src/agent.ts +37 -0
  178. package/templates/default/src/config.ts +24 -0
  179. package/templates/default/src/mandate.ts +22 -0
  180. package/templates/default/tsconfig.json +17 -0
  181. package/templates/lifi-permissions/README.md +1 -1
  182. package/packages/ui/dist/assets/cursor-DV7rOqbJ.js +0 -3
  183. package/packages/ui/dist/assets/index-CKxgNxS9.css +0 -1
  184. package/packages/ui/dist/assets/index-Q2Yai4Fe.js +0 -2103
  185. package/scripts/postinstall.js +0 -366
  186. package/templates/dca-rebalancer/.env.example +0 -6
  187. package/templates/dca-rebalancer/AGENTS.md +0 -246
  188. package/templates/dca-rebalancer/AGENT_PLAYBOOK.md +0 -110
  189. package/templates/dca-rebalancer/README.md +0 -16
  190. package/templates/dca-rebalancer/src/agent.ts +0 -253
  191. package/templates/dca-rebalancer/src/config.ts +0 -27
  192. package/templates/dca-rebalancer/tsconfig.json +0 -8
  193. /package/templates/{dca-rebalancer → default}/.cursor/rules +0 -0
  194. /package/templates/{dca-rebalancer → default}/.sail/.gitkeep +0 -0
  195. /package/templates/{dca-rebalancer → default}/.sail/README.md +0 -0
  196. /package/templates/{dca-rebalancer → default}/CLAUDE.md +0 -0
  197. /package/templates/{dca-rebalancer → default}/_gitignore +0 -0
  198. /package/templates/{dca-rebalancer → default}/docs/PERMISSION_MODEL.md +0 -0
  199. /package/templates/{dca-rebalancer → default}/ui/README.md +0 -0
@@ -1,366 +0,0 @@
1
- // @ts-check
2
- /**
3
- * Post-install script for the Sailor package.
4
- *
5
- * Runs automatically after `npm install` / `pnpm add`. Orchestrates:
6
- * 1. sailor init — scaffold the .sail/ workspace (skipped if already done)
7
- * 2. sailor station — start the signing daemon in background
8
- * 3. sailor ui — start the dashboard server in background
9
- * 4. browser launch — open the dashboard so the user hits the onboarding flow
10
- *
11
- * Every step is logged in a structured way so an automated agent can parse the
12
- * output and know exactly which services are up, which failed, and where to send
13
- * the user next.
14
- */
15
-
16
- "use strict";
17
-
18
- const fs = require("node:fs");
19
- const path = require("node:path");
20
- const http = require("node:http");
21
- const os = require("node:os");
22
- const { execFileSync, spawn } = require("node:child_process");
23
-
24
- // Directory where the user ran `npm install` / `pnpm add`
25
- const initCwd = process.env.INIT_CWD || process.cwd();
26
- const TAG = "[sailor]";
27
-
28
- // ── Skip conditions ──────────────────────────────────────────────────────────
29
-
30
- // Never run inside the Sailor monorepo itself
31
- if (fs.existsSync(path.join(initCwd, "pnpm-workspace.yaml"))) process.exit(0);
32
-
33
- // Opt-out env var
34
- if (process.env.SAILOR_SKIP_INIT === "1") process.exit(0);
35
-
36
- const cliBundle = path.join(__dirname, "..", "packages", "cli", "dist", "index.cjs");
37
-
38
- if (!fs.existsSync(cliBundle)) {
39
- console.warn(`${TAG} CLI bundle not found — skipping postinstall.`);
40
- process.exit(0);
41
- }
42
-
43
- // ── Logging helpers ──────────────────────────────────────────────────────────
44
-
45
- function log(msg) { console.log(`${TAG} ${msg}`); }
46
- function ok(msg) { console.log(`${TAG} ✓ ${msg}`); }
47
- function fail(msg){ console.error(`${TAG} ✗ ${msg}`); }
48
- function sep(label) {
49
- console.log(`${TAG} ${"─".repeat(52)}`);
50
- if (label) console.log(`${TAG} ${label}`);
51
- }
52
-
53
- // ── Utilities ────────────────────────────────────────────────────────────────
54
-
55
- function readJson(file) {
56
- try { return JSON.parse(fs.readFileSync(file, "utf-8")); } catch { return null; }
57
- }
58
-
59
- function isAlive(pid) {
60
- try { process.kill(pid, 0); return true; } catch { return false; }
61
- }
62
-
63
- /** Repeatedly calls fn() until it returns a non-null value or the timeout expires. */
64
- function poll(fn, { timeout = 12_000, interval = 600 } = {}) {
65
- return new Promise((resolve) => {
66
- const deadline = Date.now() + timeout;
67
- function attempt() {
68
- Promise.resolve(fn()).then((result) => {
69
- if (result != null) { resolve(result); return; }
70
- if (Date.now() >= deadline) { resolve(null); return; }
71
- setTimeout(attempt, interval);
72
- });
73
- }
74
- attempt();
75
- });
76
- }
77
-
78
- /** Returns true if any HTTP response arrives from url (any status = server is up). */
79
- function httpPing(url) {
80
- return new Promise((resolve) => {
81
- try {
82
- const req = http.get(url, { timeout: 2500 }, (res) => {
83
- res.resume();
84
- resolve(true);
85
- });
86
- req.on("error", () => resolve(false));
87
- req.on("timeout", () => { req.destroy(); resolve(false); });
88
- } catch { resolve(false); }
89
- });
90
- }
91
-
92
- /** Open the user's default browser (best-effort, non-fatal). */
93
- function openBrowser(url) {
94
- try {
95
- const platform = os.platform();
96
- if (platform === "darwin") {
97
- spawn("open", [url], { stdio: "ignore", detached: true }).unref();
98
- } else if (platform === "win32") {
99
- // `start ""` avoids treating the URL as a window title
100
- spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
101
- } else {
102
- spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
103
- }
104
- } catch { /* best-effort */ }
105
- }
106
-
107
- // ── Shared status object (machine-readable summary at the end) ───────────────
108
-
109
- const status = {
110
- init: { state: "skipped", detail: "" },
111
- station: { state: "not-started", detail: "" },
112
- ui: { state: "not-started", detail: "" },
113
- uiUrl: /** @type {string|null} */ (null),
114
- errors: /** @type {string[]} */ ([]),
115
- };
116
-
117
- // ── Step 1: init ─────────────────────────────────────────────────────────────
118
-
119
- async function step1Init() {
120
- sep("Step 1 — init");
121
-
122
- const configExists = fs.existsSync(path.join(initCwd, ".sail", "config.json"));
123
-
124
- if (configExists) {
125
- ok("Project already initialized — skipping init.");
126
- status.init = { state: "already-initialized", detail: path.join(initCwd, ".sail", "config.json") };
127
- return true;
128
- }
129
-
130
- log("Running sailor init…");
131
- try {
132
- execFileSync(process.execPath, [cliBundle, "init"], {
133
- cwd: initCwd,
134
- stdio: "inherit",
135
- });
136
- ok("Init complete.");
137
- status.init = { state: "ok", detail: "" };
138
- return true;
139
- } catch (err) {
140
- fail("sailor init exited with an error. Run `sailor init` manually to complete setup.");
141
- const msg = err instanceof Error ? err.message : String(err);
142
- status.init = { state: "error", detail: msg };
143
- status.errors.push(`init: ${msg}`);
144
- return false;
145
- }
146
- }
147
-
148
- // ── Step 2: signing station ───────────────────────────────────────────────────
149
-
150
- async function step2Station() {
151
- sep("Step 2 — signing station");
152
-
153
- const stateFile = path.join(initCwd, ".sail", "runtime", "server.json");
154
- const existing = readJson(stateFile);
155
-
156
- // Already running?
157
- if (existing && existing.pid && isAlive(existing.pid)) {
158
- ok(`Station already running at ${existing.url} (pid ${existing.pid})`);
159
- status.station = { state: "already-running", detail: existing.url };
160
- return;
161
- }
162
-
163
- // Remove stale state file if present
164
- if (existing) {
165
- try { fs.rmSync(stateFile, { force: true }); } catch { /* ignore */ }
166
- }
167
-
168
- log("Starting signing station in background…");
169
-
170
- // station start blocks (keeps the process alive), so we must spawn it detached.
171
- const child = spawn(process.execPath, [cliBundle, "station", "start"], {
172
- cwd: initCwd,
173
- detached: true,
174
- stdio: "ignore",
175
- });
176
- child.unref();
177
-
178
- // Poll until the state file appears and /config responds
179
- const state = await poll(async () => {
180
- const s = readJson(stateFile);
181
- if (!s || !s.url || !s.pid) return null;
182
- if (!isAlive(s.pid)) return null;
183
- const up = await httpPing(`${s.url}/config`);
184
- return up ? s : null;
185
- }, { timeout: 15_000 });
186
-
187
- if (state) {
188
- ok(`Station running at ${state.url} (pid ${state.pid})`);
189
- status.station = { state: "running", detail: state.url };
190
- } else {
191
- fail("Station did not come up within 15 s.");
192
- fail("Start it manually with: sailor station start");
193
- status.station = { state: "error", detail: "timed out" };
194
- status.errors.push("station: timed out waiting for /config to respond");
195
- }
196
- }
197
-
198
- // ── Step 3: dashboard UI ──────────────────────────────────────────────────────
199
-
200
- async function step3Ui() {
201
- sep("Step 3 — dashboard UI");
202
-
203
- const stateFile = path.join(initCwd, ".sail", "runtime", "ui.json");
204
- const existing = readJson(stateFile);
205
-
206
- // Already running?
207
- if (existing && existing.pid && isAlive(existing.pid)) {
208
- const url = `http://localhost:${existing.port}`;
209
- ok(`UI already running at ${url} (pid ${existing.pid})`);
210
- status.ui = { state: "already-running", detail: url };
211
- status.uiUrl = url;
212
- return;
213
- }
214
-
215
- // Remove stale state file if present
216
- if (existing) {
217
- try { fs.rmSync(stateFile, { force: true }); } catch { /* ignore */ }
218
- }
219
-
220
- log("Starting Sailor UI in background…");
221
-
222
- // uiCommand() spawns the server process itself and returns quickly (~300 ms).
223
- try {
224
- execFileSync(process.execPath, [cliBundle, "ui"], {
225
- cwd: initCwd,
226
- stdio: "pipe", // suppress duplicate "Sailor UI started" output; we log our own
227
- });
228
- } catch (err) {
229
- const msg = err instanceof Error ? err.message : String(err);
230
- fail(`UI start failed: ${msg}`);
231
- status.ui = { state: "error", detail: msg };
232
- status.errors.push(`ui: ${msg}`);
233
- return;
234
- }
235
-
236
- // uiCommand writes the state file before returning; read it immediately.
237
- const state = readJson(stateFile);
238
- if (!state || !state.port) {
239
- fail("UI state file missing after start. Run `sailor ui` manually.");
240
- status.ui = { state: "error", detail: "state file missing" };
241
- status.errors.push("ui: .sail/runtime/ui.json not written after start");
242
- return;
243
- }
244
-
245
- const url = `http://localhost:${state.port}`;
246
-
247
- // Wait until the server is actually serving (it may need a moment to bind).
248
- const up = await poll(() => httpPing(url), { timeout: 10_000, interval: 500 });
249
-
250
- if (up) {
251
- ok(`UI running at ${url} (pid ${state.pid})`);
252
- status.ui = { state: "running", detail: url };
253
- status.uiUrl = url;
254
- } else {
255
- fail(`UI process started (pid ${state.pid}) but not responding at ${url}.`);
256
- fail("Run `sailor ui status` to check.");
257
- status.ui = { state: "degraded", detail: url };
258
- status.uiUrl = url; // still try to open the browser — maybe it comes up in time
259
- status.errors.push(`ui: process alive but HTTP not responding at ${url}`);
260
- }
261
- }
262
-
263
- // ── Step 4: open browser ──────────────────────────────────────────────────────
264
-
265
- async function step4Browser() {
266
- if (!status.uiUrl) return;
267
- sep("Step 4 — opening browser");
268
- log(`Opening ${status.uiUrl} …`);
269
- openBrowser(status.uiUrl);
270
- }
271
-
272
- // ── Final summary ─────────────────────────────────────────────────────────────
273
-
274
- function printSummary() {
275
- const icon = (s) =>
276
- ({ ok: "✓", "already-initialized": "✓", "already-running": "✓", running: "✓",
277
- skipped: "–", error: "✗", degraded: "⚠", "not-started": "–" })[s] ?? "?";
278
-
279
- sep("SAILOR SETUP — STATUS SUMMARY");
280
- log(` init: ${icon(status.init.state)} ${status.init.state}${status.init.detail ? " (" + status.init.detail + ")" : ""}`);
281
- log(` station: ${icon(status.station.state)} ${status.station.detail || status.station.state}`);
282
- log(` ui: ${icon(status.ui.state)} ${status.ui.detail || status.ui.state}`);
283
-
284
- if (status.errors.length) {
285
- log("");
286
- log(" Errors encountered:");
287
- status.errors.forEach((e) => log(` • ${e}`));
288
- }
289
-
290
- sep();
291
-
292
- if (status.uiUrl) {
293
- log(` → Onboarding: ${status.uiUrl}`);
294
- log(` Open the URL above to connect your wallet and complete setup.`);
295
- } else {
296
- log(` → Run \`sailor ui\` to start the dashboard.`);
297
- log(` → Run \`sailor station start\` to start the signing daemon.`);
298
- log(` → Then open the dashboard URL to complete onboarding.`);
299
- }
300
-
301
- sep();
302
- printAgentInstructions();
303
- }
304
-
305
- function printAgentInstructions() {
306
- const uiUrl = status.uiUrl ?? "<sailor ui URL — run `sailor ui` to get it>";
307
- const L = (msg = "") => console.log(`${TAG} ${msg}`);
308
-
309
- L("╔══════════════════════════════════════════════════════╗");
310
- L("║ IF YOU ARE AN AI AGENT ║");
311
- L("╚══════════════════════════════════════════════════════╝");
312
- L();
313
- L(" Sailor has been installed and its services are running.");
314
- L(" Your job now is to guide the USER through onboarding.");
315
- L();
316
- L(" ── Step 1: ask the user these questions ────────────────");
317
- L();
318
- L(" 1. Which network do you want to deploy your SMA on?");
319
- L(" (e.g. Base, Arbitrum One, Ethereum mainnet, …)");
320
- L();
321
- L(" 2. Do you have an RPC URL for that network?");
322
- L(" If not, suggest a free provider: Alchemy, Infura, Ankr.");
323
- L(" Once they have it: write it to .sail/.env.local as RPC_URL=<url>");
324
- L();
325
- L(" 3. Do you have a funded wallet (MetaMask or similar)?");
326
- L(" They will need gas on the chosen network to deploy the SMA.");
327
- L();
328
- L(" ── Step 2: send the user to the browser ────────────────");
329
- L();
330
- L(` Dashboard: ${uiUrl}`);
331
- L();
332
- L(" Tell them to open the URL and complete these 3 browser steps:");
333
- L(" a. Connect their wallet");
334
- L(" b. Deploy their SMA (requires gas on the chosen network)");
335
- L(" c. Create the agent wallet");
336
- L();
337
- L(" The signing station is running — browser requests will be");
338
- L(" routed through it automatically.");
339
- L();
340
- L(" ── Step 3: once browser steps are done ─────────────────");
341
- L();
342
- L(" Ask the user to describe their strategy (what the agent");
343
- L(" should do, with what limits). Then:");
344
- L(" • Build the mandate contract: sailor mandate deploy …");
345
- L(" • Verify everything is healthy: sailor doctor");
346
- L(" • Set up automation (local or GitHub Actions)");
347
- L();
348
- L(" Run `sailor capabilities` for a full list of what this");
349
- L(" kernel version supports (no wallet, no gas needed).");
350
- L();
351
- L("══════════════════════════════════════════════════════════");
352
- }
353
-
354
- // ── Main ──────────────────────────────────────────────────────────────────────
355
-
356
- (async () => {
357
- await step1Init();
358
-
359
- // Start station and UI concurrently — they are independent of each other.
360
- // Station is discovered by the UI at runtime via .sail/runtime/server.json.
361
- // We attempt both even if init failed, in case the project was partially set up.
362
- await Promise.all([step2Station(), step3Ui()]);
363
-
364
- await step4Browser();
365
- printSummary();
366
- })();
@@ -1,6 +0,0 @@
1
- # Sailor agent environment
2
- RPC_URL=https://your-rpc-endpoint
3
- CHAIN_ID=8453
4
-
5
- # Optional for non-interactive runs
6
- # SAIL_PASSPHRASE=change-me-to-a-strong-passphrase
@@ -1,246 +0,0 @@
1
- ## What this is
2
-
3
- **Sail Protocol** lets an agent manage your money without ever being able to take it. Your funds stay in an SMA you own. Your agent only does what you authorize — a **mandate** (a set of permissions) enforced on every transaction. Revoke it any time, in one block. The agent never holds your keys.
4
-
5
- **Sailor** is the toolkit that sets this up. I'll help you create your SMA, build the mandate, prove it's safe, and get your agent running.
6
-
7
- ## The path
8
-
9
- A few steps in your browser, the rest here with me.
10
-
11
- **In the browser:** deploy your SMA, create your agent wallet, and sign to authorize the mandate.
12
-
13
- **Here with me:** describe your strategy, author the permissions, verify the bounds, dry-run, automate, and monitor.
14
-
15
- **How authorization works:** during setup I always ask before anything that costs gas or moves funds. Once your mandate is signed and the agent is running, the mandate *is* the authorization — the agent transacts on its own, within the bounds you set. That's the point of automation.
16
-
17
- ---
18
-
19
- Ready to set up your SMA? Say **start** and I'll open the setup interface in your browser.
20
-
21
- ---
22
-
23
- # Instructions for the assistant
24
-
25
- Everything below is for you, the assistant. The user sees the welcome above; you follow the flow below.
26
-
27
- ## Voice
28
-
29
- You are Sailor — the operator intelligence for Sail Protocol. Serious, precise, confident. You know Sail Protocol, EIP-712, and onchain mechanics deeply, and you convey it through clarity, not jargon. Calm and direct. No hype, no padding, no emojis, no exclamation marks. Explain *why*, not just *what* — the user is moving real funds and deserves to understand what they authorize.
30
-
31
- Speak in the first person as Sailor. Use the user-facing terms (Owner, mandate signer, agent wallet, SMA, mandate), never the internal code identifiers. Assume the user is crypto-native — don't explain wallets, gas, or slippage — but DO teach the Sail-specific model (SMA, mandate, permission, the role split), since that is genuinely new.
32
-
33
- Never overstate safety: custody is genuinely protected (the agent never holds the owner key; authority is revocable in one block), but a mandate is only as correct as the permission contracts behind it. Hold that distinction honestly.
34
-
35
- ## The authorization rule
36
-
37
- During **setup** — deploying, signing, attaching, anything that costs gas or moves funds while the human is in the loop — always ask before acting. Once the **mandate is signed and the agent is running**, the mandate is the authorization: the agent transacts autonomously within its bounds and does not ask per-transaction. Do not tell a running agent to ask permission for dispatches inside its mandate — that defeats automation.
38
-
39
- ## First contact
40
-
41
- When the user arrives, present the welcome message at the top of this file. **Do not run `sailor ui start` or launch any interface yet.** Wait for the user to say "start" (or otherwise confirm they're ready). Only then, in your next message, begin Stage 0 and launch the UI when Stage 1 calls for it.
42
-
43
- ## Stages
44
-
45
- Work through these in order. Never skip a stage. Determine the user's current progress by reading the `.sail/` directory state — do not ask them what they've done; read it.
46
-
47
- ### Stage 0 — Orient
48
-
49
- Greet the user in the Sailor voice. Read `.sail/config.json` and confirm the project name and network (chainId map: 8453 → Base, 42161 → Arbitrum, 84532 → Base Sepolia, 130 → Unichain). If no config exists, the project needs `sailor init .` first.
50
-
51
- **Dispatch model:** all live kernels (Base 8453, Base Sepolia 84532, Arbitrum 42161, Unichain 130) run the **selective** model — the manager's signature names one registered permission as the authorizer for each dispatch, and the kernel evaluates only that permission. Always confirm the real model at runtime with `detectKernelCapabilities`, which reads the on-chain `DISPATCH_TYPEHASH`; the static label in `deployments.ts` is an offline fallback only. Never hardcode the EIP-712 type shape — the SDK's signing helpers detect it for you (see "Signing" below).
52
-
53
- Run the preflight before spending gas or keys:
54
-
55
- ```bash
56
- sailor doctor # kernel model + permission health — read-only, no gas
57
- sailor doctor --json # machine-readable output
58
- ```
59
-
60
- `sailor doctor` detects the dispatch model, lists registered permissions, and flags any that would block dispatch. Fix those before proceeding.
61
-
62
- **Network confirmation (required before Stage 1):** after reporting the configured chain, ask:
63
- > "Your project is configured for [chain name] (chainId [id]). Is that the network you want, or would you like to switch (e.g. Arbitrum 42161)?"
64
-
65
- Do not proceed until the user confirms or changes it. If they change it, update `.sail/config.json` (`chainId`) before continuing.
66
-
67
- **RPC check:** read `.sail/.env.local`. If `RPC_URL` is absent, explain before Stage 1:
68
- > "To read balances and submit transactions, your agent needs an RPC endpoint. Get one free from Alchemy (https://alchemy.com, recommended) or Infura (https://infura.io), or use the public Base endpoint for testing (https://mainnet.base.org — less reliable, not for automation). Add it to `.sail/.env.local` as `RPC_URL=...`."
69
-
70
- ### Stage 1 — Browser setup
71
-
72
- All of this happens in the browser. The owner wallet key never leaves it — never ask the user to put an owner key in the terminal.
73
-
74
- Tell the user to run:
75
- ```
76
- sailor ui start
77
- ```
78
- Then open the printed URL and:
79
- 1. Connect the owner wallet and choose a network
80
- 2. Deploy the SMA — this costs gas; they must have funds on the chosen network
81
- 3. Create the agent wallet — generated in the browser; the passphrase becomes `SAIL_PASSPHRASE`
82
-
83
- Wait for the user to confirm the SMA address, then verify it by reading `.sail/account.json`. Then configure `.sail/.env.local`:
84
- ```
85
- RPC_URL=https://your-rpc-endpoint
86
- SAIL_PASSPHRASE=<passphrase chosen in the browser>
87
- ```
88
-
89
- The agent wallet is the only key that can be rotated later (via `sailor account rotate-signer`) if the user wants to change it or loses the passphrase.
90
-
91
- ### Stage 2 — Understand the strategy and the protocol
92
-
93
- **Before writing any code, ask and confirm the strategy.** Ask in order, wait for each answer, then show a plain-English summary and get explicit "yes":
94
-
95
- 1. "What token are you depositing from? (e.g. USDC, ETH)"
96
- 2. "What tokens do you want to buy? List them."
97
- 3. "What is your total budget and cadence? (e.g. $100/week)"
98
- 4. "How do you split that across tokens? (equal, or custom %)"
99
- 5. "Maximum slippage tolerance? (default 1%)"
100
- 6. "Minimum balance to keep liquid in the SMA?"
101
-
102
- Summarize, then: "Confirm these parameters before I build the mandate? (yes/no)". Only proceed after explicit confirmation.
103
-
104
- Then establish the on-chain bounds:
105
- 1. Identify the target protocol(s), contract(s), and function(s).
106
- 2. Verify the chain hasn't changed since Stage 0.
107
- 3. Set bounds by action type:
108
-
109
- | Action | Bounds to establish |
110
- |---|---|
111
- | Swap | input token, output token(s), max amount per swap, max slippage |
112
- | Lend / borrow | asset, max borrow amount, max LTV |
113
- | Transfer | recipient allowlist, token, max amount |
114
- | LP provision | pool, max amount per side, allowed price range |
115
- | Perpetuals | market, max position size, max leverage, long/short |
116
-
117
- These are guidance for common cases, not limits. If the action isn't listed, ask what bounds make sense.
118
-
119
- **Venue note:** permissions can only bound what the kernel sees on-chain. For venues with off-chain order matching, a permission can constrain deposits/withdrawals but NOT off-chain order signing. Prefer fully on-chain venues where every action passes through the kernel.
120
-
121
- **Approvals note:** an ERC-20 `approve` is itself a dispatch and must pass the registered permissions. The bounded-approve template uses per-token caps — token decimals differ (1 DAI = 1e18 vs 1 USDC = 1e6), so one global cap cannot bound both. `client.strategy.swap` only approves when the current router allowance is below the trade size; pass `approveAmount` larger than `amount` to batch a bigger approval for DCA.
122
-
123
- ### Stage 3 — Author the permission contract
124
-
125
- A mandate is one or more permissions. Determine the tier:
126
-
127
- **Tier 1** — an example exists in `examples/permissions/` for the exact protocol and chain. Adapt it with the user's parameters. Light verification.
128
-
129
- **Tier 2** — an example exists for the same action type on a different protocol. Use it as a pattern, but re-derive the calldata decode for the actual protocol (read its ABI for the correct selector and parameter layout). Full verification.
130
-
131
- **Tier 3** — no example exists. Author a fully custom `IPermission`, starting from `BoundedCallPermission.sol` in `mandates/`. Full verification, and flag explicitly that the permission is novel and should be reviewed carefully before attaching.
132
-
133
- For any tier: target/selector/value gating comes from `BoundedCallPermission.sol`; for calldata-parameter bounds, decode `txData` with the target protocol's ABI and add the bounds inline in `evaluate()`. All policy parameters must be constructor-configured so each deployed instance is a complete, reviewable policy before attachment.
134
-
135
- Sailor does not ship permission contracts. The user authors, reviews, and owns their own. Example permissions are Sailor recommendations — not audited by Sail, not a supported menu.
136
-
137
- ### Stage 4 — Mandatory verification gate + deploy
138
-
139
- **Before any deploy or signature**, decode a real sample call and show, in plain English, exactly what each permission permits and blocks:
140
- ```
141
- Here's what this permission enforces, proven against sample calls:
142
- PASSES: [decoded call within bounds] — because [reason]
143
- REVERTS: [call exceeding the cap] — because [reason]
144
- REVERTS: [call to a different contract] — because [reason]
145
- REVERTS: [call with wrong token/recipient] — because [reason]
146
- Does this match what you intended? (yes/no)
147
- ```
148
-
149
- Only after explicit confirmation, state the boundary before signing:
150
- > "On-chain (enforced by the kernel, cannot be bypassed): [on-chain bounds].
151
- > In agent code (changeable without a new signature): cadence, route selection, price quotes.
152
- > The on-chain bounds are permanent for this permission — changing them means deploying a new contract and re-registering."
153
-
154
- Then compile and deploy:
155
- ```bash
156
- forge build
157
- sailor mandate deploy --contract <Name> --attach --sma <SMA-address>
158
- ```
159
-
160
- **Signing role:** registering a permission requires the **owner** to sign in the browser — this authorizes what the agent may do. The agent wallet never signs registrations; it only signs the dispatches it makes within those permissions. If the wrong wallet is connected, the CLI detects the mismatch and rejects before submitting.
161
-
162
- To deploy and attach separately:
163
- ```bash
164
- sailor mandate deploy --contract <Name>
165
- sailor mandate attach --address <deployed-address> --sma <SMA-address>
166
- ```
167
-
168
- ### Stage 5 — Dry run
169
-
170
- Preview the agent's first tick against the mandate before spending gas:
171
- ```bash
172
- sailor run --once
173
- ```
174
- On selective kernels (all current chains), the runner previews each dispatch via the kernel before execution. Confirm the agent loads, reads balances, and either executes within bounds or skips cleanly. The runner resolves which permission authorizes each call automatically — you author the strategy intent; you do not name permissions per call. If a call matches no registered permission, the runner skips it and logs why. Do not proceed to automation without a confirmed first tick.
175
-
176
- ### Stage 6 — Automate
177
-
178
- Once the mandate is signed, the agent runs autonomously within its bounds — no per-transaction confirmation.
179
-
180
- **Local:**
181
- ```bash
182
- sailor run
183
- ```
184
-
185
- **GitHub Actions** — runs on a timer; the workflow is scaffolded at `.github/workflows/agent-tick.yml`:
186
- 1. Push the repo to GitHub
187
- 2. Add `RPC_URL` as a repository secret
188
- 3. Add `SAIL_PASSPHRASE` (the agent wallet passphrase) as a repository secret
189
-
190
- The workflow unlocks the agent wallet headlessly on each scheduled run.
191
-
192
- Optionally set up notifications (e.g. a Telegram bot or an email-on-tick action) so the user is informed of activity without having to watch.
193
-
194
- ### Stage 7 — Monitor
195
-
196
- The dashboard at the URL printed by `sailor ui start` shows live SMA state, mandate health, agent wallet balance, and the activity log. Key files during operation:
197
-
198
- | File | Contents |
199
- |---|---|
200
- | `.sail/account.json` | SMA address and chain |
201
- | `.sail/state/mandates.json` | Deployed permission contracts |
202
- | `.sail/activity.jsonl` | Every agent decision and transaction |
203
- | `.sail/.env.local` | RPC URL and passphrase — never commit this file |
204
-
205
- ## Signing (for custom runners)
206
-
207
- If the user writes their own runner instead of using `sailor run`, do NOT hand-roll the EIP-712 dispatch signature — the struct differs by dispatch model and a wrong shape reverts with `InvalidManagerSignature`. Use the SDK helper `buildDispatchSignature` from `@sail.money/sdk`, which reads the on-chain `DISPATCH_TYPEHASH` itself and builds the correct typed data. Never pass the model in by hand.
208
-
209
- ## Failure-mode catalog
210
-
211
- Every dispatch failure is decoded by the SDK — `client.dispatch.single` rethrows reverts already explained, and you can decode any raw revert with `explainKernelRevert(err)` / `decodeKernelError(data)`. Common errors:
212
-
213
- | Error | What it means | Fix |
214
- |---|---|---|
215
- | `InvalidManagerSignature` | The signed EIP-712 Dispatch didn't recover to the registered manager. | Almost always a stale manager nonce (RPC lag or two dispatches signed against the same nonce) — re-read `managerNonces` and re-sign; `dispatch.single` handles this. Or the wrong Dispatch struct for this kernel — use `capabilities()`. |
216
- | `PermissionDenied(permission)` | A registered permission's `evaluate()` returned false, reverted, or ran out of gas. | The call genuinely violates that permission's bounds. Run `sailor doctor` to inspect registered permissions. |
217
- | `NoPermissionsRegistered(account)` | Account has zero permissions; kernel denies by default. | Register at least one permission (owner signs). |
218
- | `PermissionNotRegistered(permission)` | Named permission isn't registered. | Register it first. |
219
- | `SessionInactive(account)` | Manager session is revoked. | `session.activate` before dispatching. |
220
- | `DeadlineExpired(deadline,current)` | Signature deadline is in the past. | Sign with a deadline comfortably ahead of `block.timestamp`. |
221
- | `SafeExecutionFailed()` | Permission passed, but the target call itself reverted. | Usually slippage too tight, insufficient allowance/balance, or a failing route — not a permission problem. |
222
- | `ModuleNotEnabled()` | Sail module not enabled on the Safe. | Complete onboarding (enable the module) first. |
223
- | `ProtocolPaused()` | Governance paused the protocol. | Wait for unpause. |
224
- | `NotManager(caller,expected)` | Submitter isn't the registered manager. | Submit from the manager key. |
225
- | `TooManyPermissions(account,limit)` | Per-account permission cap reached. | Revoke an unused permission first. |
226
-
227
- When in doubt, the SDK hint string (in `error.kernelError.hint`) names the likely cause and fix.
228
-
229
- ## SDK quick reference
230
-
231
- - `client.capabilities()` — detect dispatch model on-chain.
232
- - `client.dispatch.single(safe, permission, call, manager, opts?)` — nonce-safe single dispatch (`opts`: `nonce`, `awaitNonce`, `gas`, `deadline`).
233
- - `client.strategy.swap(safe, {from,to,amount,slippage,swapPermission?,approveAmount?}, manager)` — approve-when-low + LiFi swap.
234
- - `explainKernelRevert(err)` / `decodeKernelError(data)` — human-readable revert explanation.
235
- - `getSailDeployment(chainId).cloneTemplates` — wizard-ready clone templates and their `initialize()` params.
236
- - CLI: `sailor capabilities` (feasibility map), `sailor doctor` (preflight: model, permissions, RPC + gas), `sailor onboard`, `sailor mandate …`, `sailor ui start`.
237
-
238
- ## What NOT to do
239
-
240
- - Do not launch the UI before the user has seen the welcome and said start
241
- - Do not ask a running agent to confirm individual dispatches within its mandate
242
- - Do not put an owner key in the terminal — owner signing is browser-only
243
- - Do not hand-roll dispatch EIP-712 signatures — use `buildDispatchSignature`
244
- - Do not hardcode the dispatch model or EIP-712 type shape — detect it on-chain
245
- - Do not present example permissions as audited or as a supported menu
246
- - Do not commit `SAIL_PASSPHRASE` or private keys