@dev.sail.money/sailor 1.3.0-98 → 1.4.0-242

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 (231) hide show
  1. package/AGENTS.md +7 -7
  2. package/LICENSE +1 -1
  3. package/README.md +64 -472
  4. package/package.json +11 -6
  5. package/packages/cli/README.md +1 -1
  6. package/packages/cli/dist/index.cjs +1469 -463
  7. package/packages/cli/dist/server.cjs +19796 -21737
  8. package/packages/sdk/README.md +1 -1
  9. package/packages/sdk/dist/abis/ConfigurablePermission.d.ts +130 -0
  10. package/packages/sdk/dist/abis/ConfigurablePermission.d.ts.map +1 -0
  11. package/packages/sdk/dist/abis/ConfigurablePermission.js +79 -0
  12. package/packages/sdk/dist/abis/ConfigurablePermission.js.map +1 -0
  13. package/packages/sdk/dist/abis/SailKernel.d.ts +42 -0
  14. package/packages/sdk/dist/abis/SailKernel.d.ts.map +1 -1
  15. package/packages/sdk/dist/abis/SailKernel.js +34 -0
  16. package/packages/sdk/dist/abis/SailKernel.js.map +1 -1
  17. package/packages/sdk/dist/abis/index.d.ts +1 -0
  18. package/packages/sdk/dist/abis/index.d.ts.map +1 -1
  19. package/packages/sdk/dist/abis/index.js +1 -0
  20. package/packages/sdk/dist/abis/index.js.map +1 -1
  21. package/packages/sdk/dist/chains.d.ts +0 -7
  22. package/packages/sdk/dist/chains.d.ts.map +1 -1
  23. package/packages/sdk/dist/chains.js +81 -32
  24. package/packages/sdk/dist/chains.js.map +1 -1
  25. package/packages/sdk/dist/client.d.ts +1 -1
  26. package/packages/sdk/dist/client.d.ts.map +1 -1
  27. package/packages/sdk/dist/client.js +137 -16
  28. package/packages/sdk/dist/client.js.map +1 -1
  29. package/packages/sdk/dist/deployments.d.ts +6 -4
  30. package/packages/sdk/dist/deployments.d.ts.map +1 -1
  31. package/packages/sdk/dist/deployments.js +120 -118
  32. package/packages/sdk/dist/deployments.js.map +1 -1
  33. package/packages/sdk/dist/eip712.d.ts +84 -0
  34. package/packages/sdk/dist/eip712.d.ts.map +1 -1
  35. package/packages/sdk/dist/eip712.js +148 -0
  36. package/packages/sdk/dist/eip712.js.map +1 -1
  37. package/packages/sdk/dist/fees.d.ts +2 -1
  38. package/packages/sdk/dist/fees.d.ts.map +1 -1
  39. package/packages/sdk/dist/fees.js +2 -1
  40. package/packages/sdk/dist/fees.js.map +1 -1
  41. package/packages/sdk/dist/index.d.ts +3 -2
  42. package/packages/sdk/dist/index.d.ts.map +1 -1
  43. package/packages/sdk/dist/index.js +3 -2
  44. package/packages/sdk/dist/index.js.map +1 -1
  45. package/packages/sdk/dist/intelligence.d.ts +1 -1
  46. package/packages/sdk/dist/intelligence.js +1 -1
  47. package/packages/sdk/dist/safe.d.ts +49 -0
  48. package/packages/sdk/dist/safe.d.ts.map +1 -1
  49. package/packages/sdk/dist/safe.js +74 -0
  50. package/packages/sdk/dist/safe.js.map +1 -1
  51. package/packages/sdk/package.json +6 -2
  52. package/packages/ui/dist/assets/{add-BX5reKtK.js → add-CBbu2pe_.js} +2 -2
  53. package/packages/ui/dist/assets/{all-wallets-IZ3PlVF-.js → all-wallets-C1oy4NUu.js} +2 -2
  54. package/packages/ui/dist/assets/{app-store-ARgRdMdF.js → app-store-DKqyf0Zv.js} +1 -1
  55. package/packages/ui/dist/assets/{apple-VCaqsI9w.js → apple-DyoIbCFy.js} +2 -2
  56. package/packages/ui/dist/assets/{arrow-bottom-circle-h5vFiZ2I.js → arrow-bottom-circle-CMS-OqZQ.js} +2 -2
  57. package/packages/ui/dist/assets/arrow-bottom-pUWeEqYu.js +8 -0
  58. package/packages/ui/dist/assets/arrow-left-B81HMJNL.js +8 -0
  59. package/packages/ui/dist/assets/arrow-right-Z5czoKlC.js +8 -0
  60. package/packages/ui/dist/assets/arrow-top-C6ym1oll.js +8 -0
  61. package/packages/ui/dist/assets/{bank-6c28yOM9.js → bank-B41nlslf.js} +2 -2
  62. package/packages/ui/dist/assets/{basic-CJnbTfSd.js → basic-CWqOb7CV.js} +177 -177
  63. package/packages/ui/dist/assets/{browser-CDKlxM3D.js → browser-DDKdqoZ9.js} +2 -2
  64. package/packages/ui/dist/assets/{card-DQdED02q.js → card-BgOmTajq.js} +2 -2
  65. package/packages/ui/dist/assets/ccip-CtJUKWRd.js +1 -0
  66. package/packages/ui/dist/assets/{checkmark-JoRNVoFV.js → checkmark-CGqQipH4.js} +2 -2
  67. package/packages/ui/dist/assets/{checkmark-bold-B6bGHVIs.js → checkmark-bold-Ci5xf90j.js} +2 -2
  68. package/packages/ui/dist/assets/chevron-bottom-D7Xert17.js +8 -0
  69. package/packages/ui/dist/assets/chevron-left-BGbUebR2.js +8 -0
  70. package/packages/ui/dist/assets/chevron-right-xVsESnHU.js +8 -0
  71. package/packages/ui/dist/assets/chevron-top-DEA9cR6v.js +8 -0
  72. package/packages/ui/dist/assets/{chrome-store-D-fiJ9qJ.js → chrome-store-BsyI-4PI.js} +2 -2
  73. package/packages/ui/dist/assets/{clock-xCPkzwjj.js → clock-Dct9oQ3M.js} +2 -2
  74. package/packages/ui/dist/assets/{close-BkqmPpWG.js → close-BvSCnI2y.js} +2 -2
  75. package/packages/ui/dist/assets/{coinPlaceholder-PIvN9oq_.js → coinPlaceholder-p7RiWkon.js} +2 -2
  76. package/packages/ui/dist/assets/{compass-CW5kWcxW.js → compass-BUeeZqX4.js} +2 -2
  77. package/packages/ui/dist/assets/{copy-DUZlAQJJ.js → copy-C5jXbxLq.js} +2 -2
  78. package/packages/ui/dist/assets/core-CiAm42Mq.js +907 -0
  79. package/packages/ui/dist/assets/cursor-C0T7JWFO.js +3 -0
  80. package/packages/ui/dist/assets/{cursor-transparent-BUAMB0G1.js → cursor-transparent-BZp3-JmA.js} +2 -2
  81. package/packages/ui/dist/assets/{desktop-BxdlwV8P.js → desktop-BIui_Lzb.js} +2 -2
  82. package/packages/ui/dist/assets/{disconnect-GelNh3mn.js → disconnect-B3Bxdchp.js} +2 -2
  83. package/packages/ui/dist/assets/{discord-N0jnxkzC.js → discord-VWI0yDyx.js} +2 -2
  84. package/packages/ui/dist/assets/{etherscan-B41u7wF4.js → etherscan-BpBvUT9i.js} +2 -2
  85. package/packages/ui/dist/assets/events-DQ172AOg.js +1 -0
  86. package/packages/ui/dist/assets/{exclamation-triangle-BWJC56FC.js → exclamation-triangle-o1TJHIUw.js} +2 -2
  87. package/packages/ui/dist/assets/{extension-G6I6oogw.js → extension-4RaLvbh5.js} +2 -2
  88. package/packages/ui/dist/assets/external-link-BHIwjDgj.js +8 -0
  89. package/packages/ui/dist/assets/{facebook-47twy32b.js → facebook-Bes5g-wN.js} +2 -2
  90. package/packages/ui/dist/assets/{farcaster-DYqnqCqs.js → farcaster-CiJ-352m.js} +2 -2
  91. package/packages/ui/dist/assets/{filters-dF6xeJnE.js → filters-DMOteumb.js} +2 -2
  92. package/packages/ui/dist/assets/{github-DbW4Ihz2.js → github-D2nDec9X.js} +2 -2
  93. package/packages/ui/dist/assets/{google-TKm7G8LF.js → google-lZZlQZPQ.js} +2 -2
  94. package/packages/ui/dist/assets/{help-circle-CY3qTmsG.js → help-circle-D4cSSziR.js} +1 -1
  95. package/packages/ui/dist/assets/{id-JpHW4q14.js → id-BmhXZq5P.js} +2 -2
  96. package/packages/ui/dist/assets/{image-Dip2fG98.js → image-Dz4FPeET.js} +2 -2
  97. package/packages/ui/dist/assets/index-B07bCKJ8.css +1 -0
  98. package/packages/ui/dist/assets/index-BV8s6ez1.js +1 -0
  99. package/packages/ui/dist/assets/index-BeUp3nOq.js +1 -0
  100. package/packages/ui/dist/assets/index-CvRylmZJ.js +16 -0
  101. package/packages/ui/dist/assets/{index-q0mGElxZ.js → index-DeoQO45V.js} +3 -3
  102. package/packages/ui/dist/assets/index-Et5S3ZaW.js +47 -0
  103. package/packages/ui/dist/assets/index-LiKeH8Cd.js +1780 -0
  104. package/packages/ui/dist/assets/index.es-dfpEui-9.js +26 -0
  105. package/packages/ui/dist/assets/{info-B1enFsqh.js → info-77RSvIMg.js} +2 -2
  106. package/packages/ui/dist/assets/{info-circle-D4j3FISs.js → info-circle-CWsII-NN.js} +2 -2
  107. package/packages/ui/dist/assets/{lightbulb-DZbKr-vS.js → lightbulb-BZXzm7XE.js} +2 -2
  108. package/packages/ui/dist/assets/{mail-D-TO_uVQ.js → mail-DMG1YqB0.js} +2 -2
  109. package/packages/ui/dist/assets/metamask-sdk-K1x_iqcc.js +542 -0
  110. package/packages/ui/dist/assets/{mobile-Bkmwzav2.js → mobile-DuJfOBSk.js} +2 -2
  111. package/packages/ui/dist/assets/{more-D1_I_jiB.js → more-Cs9ySK2F.js} +2 -2
  112. package/packages/ui/dist/assets/{network-placeholder-DUaFjoXh.js → network-placeholder-CMDPmB1E.js} +2 -2
  113. package/packages/ui/dist/assets/{nftPlaceholder-Cc0iUryh.js → nftPlaceholder-Cj0f9y2u.js} +2 -2
  114. package/packages/ui/dist/assets/{off-BvX71sse.js → off-9zSSsm-e.js} +2 -2
  115. package/packages/ui/dist/assets/{play-store-XzLbtbTd.js → play-store-CPwIb8Gj.js} +2 -2
  116. package/packages/ui/dist/assets/{plus-B77_RmYR.js → plus-BOHQri8o.js} +2 -2
  117. package/packages/ui/dist/assets/{qr-code-BNeIhB1d.js → qr-code-DqhbK276.js} +1 -1
  118. package/packages/ui/dist/assets/{recycle-horizontal-B9sX-yty.js → recycle-horizontal-BpGM56Qz.js} +2 -2
  119. package/packages/ui/dist/assets/{refresh-BWRygnjm.js → refresh-BPzZlhTP.js} +2 -2
  120. package/packages/ui/dist/assets/{reown-logo-DCAEsGFm.js → reown-logo-OCkI0xzo.js} +2 -2
  121. package/packages/ui/dist/assets/{search-ZXIqrawS.js → search-B1JEJvwq.js} +2 -2
  122. package/packages/ui/dist/assets/{secp256k1-Doz05h4g.js → secp256k1-BqUeV9Cb.js} +1 -1
  123. package/packages/ui/dist/assets/{send-BQcO0jNC.js → send-JNsDlNoq.js} +2 -2
  124. package/packages/ui/dist/assets/{swapHorizontal-4yZEzz2K.js → swapHorizontal-C3Z1FImK.js} +2 -2
  125. package/packages/ui/dist/assets/{swapHorizontalBold-DxBKu3sl.js → swapHorizontalBold-Cmdy2iAn.js} +2 -2
  126. package/packages/ui/dist/assets/{swapHorizontalMedium-DAQYJ2JK.js → swapHorizontalMedium-DFX57Nn9.js} +2 -2
  127. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-CUlZzFJ6.js → swapHorizontalRoundedBold-baB389c8.js} +2 -2
  128. package/packages/ui/dist/assets/{swapVertical-CjWHAaDM.js → swapVertical-DmAYGfK1.js} +2 -2
  129. package/packages/ui/dist/assets/{telegram-JBeabiJP.js → telegram-BHbYE-nI.js} +2 -2
  130. package/packages/ui/dist/assets/{three-dots-D3R8OjSx.js → three-dots-BwRWl6qJ.js} +2 -2
  131. package/packages/ui/dist/assets/{twitch-1f9z7xVO.js → twitch-Drx0q6B6.js} +2 -2
  132. package/packages/ui/dist/assets/{twitterIcon-BW7n2xnp.js → twitterIcon-AuJ2KQ6p.js} +2 -2
  133. package/packages/ui/dist/assets/{verify-Cs5owI38.js → verify-Dkrd3q7o.js} +2 -2
  134. package/packages/ui/dist/assets/{verify-filled-Ci5sx5qS.js → verify-filled-B9e3-nz-.js} +2 -2
  135. package/packages/ui/dist/assets/{w3m-modal-DnWtss8u.js → w3m-modal-DtkEjQA7.js} +1 -1
  136. package/packages/ui/dist/assets/{wallet-MaGNqaW-.js → wallet-CqYzSiBd.js} +2 -2
  137. package/packages/ui/dist/assets/{wallet-placeholder-kTOy12_L.js → wallet-placeholder-B05kTN_K.js} +2 -2
  138. package/packages/ui/dist/assets/{walletconnect-CyuP92jg.js → walletconnect-BwoPXLaS.js} +3 -3
  139. package/packages/ui/dist/assets/{warning-circle-BImUCdpG.js → warning-circle-DK9Ai8rT.js} +2 -2
  140. package/packages/ui/dist/assets/{x-II45JHpV.js → x-BNdh5DVW.js} +2 -2
  141. package/packages/ui/dist/index.html +2 -2
  142. package/templates/default/.agents/skills/sail-mandates/SKILL.md +1 -1
  143. package/templates/default/.agents/skills/sail-mandates/references/approvals.md +3 -3
  144. package/templates/default/.agents/skills/sail-mandates/references/examples-index.md +3 -3
  145. package/templates/default/.agents/skills/sail-onboarding/SKILL.md +34 -33
  146. package/templates/default/.agents/skills/sail-servers/SKILL.md +1 -0
  147. package/templates/default/.agents/skills/sail-swap-quote/SKILL.md +76 -0
  148. package/templates/default/.agents/skills/sail-template-approve-batch/SKILL.md +98 -0
  149. package/templates/default/.agents/skills/sail-template-borrow/SKILL.md +68 -0
  150. package/templates/default/.agents/skills/sail-template-deposit/SKILL.md +61 -0
  151. package/templates/default/.agents/skills/sail-template-swap/SKILL.md +207 -0
  152. package/templates/default/.agents/skills/sail-template-swap-no-oracle/SKILL.md +100 -0
  153. package/templates/default/.agents/skills/sail-template-transfer/SKILL.md +52 -0
  154. package/templates/default/.agents/skills/sail-template-withdraw/SKILL.md +50 -0
  155. package/templates/default/.agents/skills/sail-templates/SKILL.md +142 -0
  156. package/templates/default/.agents/skills/sail-templates/catalog.mjs +200 -0
  157. package/templates/default/.agents/skills/sail-templates/deployed.json +133 -0
  158. package/templates/default/.agents/skills/sail-templates/oracles/IOracle.sol +13 -0
  159. package/templates/default/.agents/skills/sail-templates/oracles/UniV3TwapOracle.sol +114 -0
  160. package/templates/default/.agents/skills/sail-templates/oracles/libs/FullMath.sol +62 -0
  161. package/templates/default/.agents/skills/sail-templates/oracles/libs/TickMath.sol +44 -0
  162. package/templates/default/.agents/skills/sail-templates/references/config-schemas.md +139 -0
  163. package/templates/default/.agents/skills/sail-templates/references/reuse-flow.md +139 -0
  164. package/templates/default/.agents/skills/sail-token-resolve/SKILL.md +174 -0
  165. package/templates/default/.env.example +9 -0
  166. package/templates/default/AGENTS.md +107 -13
  167. package/templates/default/docs/PERMISSION_MODEL.md +2 -2
  168. package/{examples → templates/default/examples}/custom-mandate/.sail/contracts/interfaces/IPermission.sol +4 -0
  169. package/{examples → templates/default/examples}/custom-mandate/README.md +4 -1
  170. package/{examples → templates/default/examples}/permissions/interfaces/IBatchPermission.sol +2 -0
  171. package/{examples → templates/default/examples}/permissions/interfaces/IPermission.sol +4 -0
  172. package/templates/default/scripts/quote-swap.mjs +211 -0
  173. package/templates/default/scripts/resolve-token.mjs +992 -0
  174. package/templates/default/scripts/shared-template-addr.mjs +110 -0
  175. package/templates/default/test/BoundedCallPermission.t.sol +2 -1
  176. package/docs/PERMISSION_MODEL.md +0 -93
  177. package/examples/README.md +0 -24
  178. package/examples/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +0 -84
  179. package/examples/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +0 -97
  180. package/examples/lifi-permissions/README.md +0 -53
  181. package/packages/ui/dist/assets/arrow-bottom-Cwptw8fW.js +0 -8
  182. package/packages/ui/dist/assets/arrow-left-bM1TRcV9.js +0 -8
  183. package/packages/ui/dist/assets/arrow-right-BYd_N4Jr.js +0 -8
  184. package/packages/ui/dist/assets/arrow-top-CLUnwVE2.js +0 -8
  185. package/packages/ui/dist/assets/ccip-BL7RIPfY.js +0 -1
  186. package/packages/ui/dist/assets/chevron-bottom-BMCelIEu.js +0 -8
  187. package/packages/ui/dist/assets/chevron-left-ryPLhHCc.js +0 -8
  188. package/packages/ui/dist/assets/chevron-right-CdDNRv7f.js +0 -8
  189. package/packages/ui/dist/assets/chevron-top-BxWIjjGk.js +0 -8
  190. package/packages/ui/dist/assets/core-von2c1sc.js +0 -907
  191. package/packages/ui/dist/assets/cursor-BzRVY7BD.js +0 -3
  192. package/packages/ui/dist/assets/events-C9icG2YB.js +0 -1
  193. package/packages/ui/dist/assets/external-link-BPaGQf4a.js +0 -8
  194. package/packages/ui/dist/assets/fallback-BTfJeGjh.js +0 -1
  195. package/packages/ui/dist/assets/index-BGJ_8fMk.js +0 -1
  196. package/packages/ui/dist/assets/index-Bq3PAopp.js +0 -16
  197. package/packages/ui/dist/assets/index-C4_cA5XW.js +0 -1
  198. package/packages/ui/dist/assets/index-CaDN0gL4.js +0 -1
  199. package/packages/ui/dist/assets/index-DLElIcBr.js +0 -1789
  200. package/packages/ui/dist/assets/index-lhNfD4jA.css +0 -1
  201. package/packages/ui/dist/assets/index.es-BaCKS_tW.js +0 -26
  202. package/packages/ui/dist/assets/metamask-sdk-DljW3QD1.js +0 -542
  203. package/packages/ui/dist/assets/native-CJ5et6AR.js +0 -1
  204. package/packages/ui/dist/assets/parseSignature-BAS-wKoj.js +0 -1
  205. package/scripts/check-docs.mjs +0 -309
  206. package/scripts/check-init.mjs +0 -123
  207. package/scripts/check-update.mjs +0 -177
  208. package/scripts/clean.mjs +0 -17
  209. package/scripts/pack.mjs +0 -21
  210. package/templates/default/.agents/skills/sail-overview/SKILL.md +0 -100
  211. package/templates/default/.agents/skills/sail-sailor/SKILL.md +0 -190
  212. package/templates/default/.agents/skills/sail-setup/SKILL.md +0 -38
  213. package/templates/default/.gitlab-ci.yml +0 -58
  214. /package/{examples → templates/default/examples}/custom-mandate/foundry.toml +0 -0
  215. /package/{examples → templates/default/examples}/custom-mandate/mandates/BoundedCallPermission.sol +0 -0
  216. /package/{examples → templates/default/examples}/custom-mandate/mandates/README.md +0 -0
  217. /package/{examples → templates/default/examples}/custom-mandate/mandates/SailCalldata.sol +0 -0
  218. /package/{examples → templates/default/examples}/permissions/BoundedApproveAndCallBatch.sol +0 -0
  219. /package/{examples → templates/default/examples}/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +0 -0
  220. /package/{examples → templates/default/examples}/permissions/BoundedLimitless_Base.sol +0 -0
  221. /package/{examples → templates/default/examples}/permissions/BoundedPerp_GMXv2_Arbitrum.sol +0 -0
  222. /package/{examples → templates/default/examples}/permissions/BoundedStake_Venice_Base.sol +0 -0
  223. /package/{examples → templates/default/examples}/permissions/BoundedSupply_AaveV3_Arbitrum.sol +0 -0
  224. /package/{examples → templates/default/examples}/permissions/BoundedSwapNative_UniswapV3_Base.sol +0 -0
  225. /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV3_Base.sol +0 -0
  226. /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV4_Unichain.sol +0 -0
  227. /package/{examples → templates/default/examples}/permissions/BoundedTransfer_ERC20_Ethereum.sol +0 -0
  228. /package/{examples → templates/default/examples}/permissions/BoundedVault_ERC4626_Base.sol +0 -0
  229. /package/{examples → templates/default/examples}/permissions/README.md +0 -0
  230. /package/{examples → templates/default/examples}/permissions/SailCalldata.sol +0 -0
  231. /package/{examples → templates/default/examples}/permissions/foundry.toml +0 -0
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#040b16" />
7
7
  <title>Sail - Unlocking Personalized Money</title>
8
- <script type="module" crossorigin src="/assets/index-DLElIcBr.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-lhNfD4jA.css">
8
+ <script type="module" crossorigin src="/assets/index-LiKeH8Cd.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-B07bCKJ8.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -116,4 +116,4 @@ The fee is read via `readPermissionRegistrationFee()` in `packages/sdk/src/fees.
116
116
 
117
117
  ## Clone templates (deploy-clone)
118
118
 
119
- `sailor mandate deploy-clone --template boundedApprove --sma <SMA> --tokens <csv> --spenders <csv> --max <wei> --json` deploys + registers an EIP-1167 clone of a published implementation in one transaction (owner signs `RegisterPermission` for the predicted clone address — BLOCKS; agent submits `deployAndAttach`). The only template key is `boundedApprove`. Implementations come from the SDK deployment registry (`standaloneTemplates`) — currently **empty on all six chains** pending redeployment against the new kernel, so deploy-clone errors with a clear message and you should write and deploy a bounded-approve permission with `sailor mandate deploy` instead. Check availability with `sailor mandate templates --json`.
119
+ `sailor mandate deploy-clone --template boundedApprove --sma <SMA> --tokens <csv> --spenders <csv> --max <wei> --json` deploys + registers an EIP-1167 clone of a published implementation in one transaction (owner signs `RegisterPermission` for the predicted clone address — BLOCKS; agent submits `deployAndAttach`). The only template key is `boundedApprove`. Implementations come from the SDK deployment registry (`standaloneTemplates`) — that map is now populated with the shared swap/borrow/deposit/withdraw/transfer/approve-and-call-batch templates on all eleven chains, but no `boundedApprove` clone implementation is deployed yet, so deploy-clone errors with a clear message and you should write and deploy a bounded-approve permission with `sailor mandate deploy` instead. Check availability with `sailor mandate templates --json`.
@@ -25,12 +25,12 @@ This needs no batch support and works on every kernel.
25
25
 
26
26
  Approve + action run as one `dispatchBatch` — atomic, single transaction. A batch dispatch consults **exactly one batch-aware `IBatchPermission`**, never a pair of `IPermission`s:
27
27
 
28
- - The permission implements `@sail/interfaces/IBatchPermission.sol` — `evaluateBatch(Call[] calls, BatchContext ctx)` validates the WHOLE sequence (ordering, the approve's spender/amount, the action, and mandatory allowance cleanup) and `isBatchPermission()` returns true. `examples/permissions/BoundedApproveAndCallBatch.sol` is the model.
28
+ - The permission implements `@sail/interfaces/IBatchPermission.sol` — `evaluateBatch(Call[] calls, BatchContext ctx)` validates the WHOLE sequence (ordering, the approve's spender/amount, the action, and mandatory allowance cleanup) and `isBatchPermission()` returns true. **For the canonical `approve → swap/deposit/supply → reset` shape, a shared `ApproveAndCallBatchPermission` singleton is deployed** on Base, Arbitrum, Unichain, Sepolia, and Base Sepolia (resolve it with `node scripts/shared-template-addr.mjs ApproveAndCallBatchPermission`) — reuse it via `attach` + `configure` rather than authoring one. `examples/permissions/BoundedApproveAndCallBatch.sol` remains the model for a non-standard shape you must author yourself.
29
29
  - A normal `IPermission` placed in a batch is rejected by the kernel with `PermissionNotBatchAware`. You cannot assemble a batch from two narrow per-call permissions — that was the trap.
30
30
  - Batch is a **selective-kernel** feature (`dispatchBatch` / `previewBatch`); conjunctive kernels have neither. Confirm the model with `sailor doctor`; details in `docs/PERMISSION_MODEL.md`.
31
- - At runtime, return one `Dispatch` whose `calls` array is `[approveCall, actionCall]`; the runner detects `calls.length > 1` and routes through `dispatch.batch`.
31
+ - At runtime, return one `Dispatch` whose `calls` array is the batch. The shared `ApproveAndCallBatchPermission` requires **exactly** `[approve(spender, amount), action, approve(spender, 0)]` the trailing reset to zero is mandatory, and the pre-batch allowance on that `(token, spender)` pair must already be zero (so never combine it with a standing approve). The runner detects `calls.length > 1` and routes through `dispatch.batch`.
32
32
 
33
- Use Model B only when atomicity genuinely matters (e.g. the approve must not be observable between calls). Otherwise prefer Model A less contract to author and test.
33
+ **For autonomous recurring actions (a DCA or rebalancer the agent runs on its own), Model B is the default not an advanced option.** Model A's separate approve dispatch needs the owner back in the loop every time the bounded allowance runs out, which a recurring agent cannot do; on the first tick after the allowance is consumed it stalls (or, in a simulation, is tempted to cheat the allowance in). Reach for Model A when the owner is genuinely in the loop per action anyway. For bounded swaps specifically, [`sail-template-swap`'s "Approve coverage"](../../sail-template-swap/SKILL.md) works through the choice with concrete addresses.
34
34
 
35
35
  ## Verifying each model
36
36
 
@@ -15,7 +15,7 @@ Permissions only bound on-chain actions: venues with off-chain order matching (P
15
15
  | `BoundedStake_Venice_Base.sol` | Venice (VVV) staking · Base | Recipient binding on stake; claim() structurally routes rewards to caller (the SMA) | `0xadc9772e` stake(address,uint256), `0x4e71d92d` claim() |
16
16
  | `BoundedTransfer_ERC20_Ethereum.sol` | ERC-20 · any EVM | Token allowlist + recipient allowlist + per-transfer cap; caps are per-token base units (USDC 6 decimals, WETH 18) — one permission per token if amounts differ | `0xa9059cbb` transfer(address,uint256) |
17
17
  | `BoundedPerp_GMXv2_Arbitrum.sol` | GMX v2 ExchangeRouter · Arbitrum | Market allowlist + collateral cap + size cap (USD is 1e30-scaled) + long/short flags on a struct-typed call | `0x212234c3` createOrder(CreateOrderParams) — MUST re-verify, see below |
18
- | `BoundedBet_Limitless_Base.sol` | Limitless CTF exchange · Base | Condition allowlist + stake cap + outcome allowlist on a prediction market | buy(bytes32,uint256,uint256) — **ABI UNVERIFIED**, see below |
18
+ | `BoundedLimitless_Base.sol` | Limitless CTF exchange · Base | Condition allowlist + stake cap + outcome allowlist on a prediction market | buy(bytes32,uint256,uint256) — **ABI UNVERIFIED**, see below |
19
19
  | `BoundedApproveAndCallBatch.sol` | Sail batch dispatch · any selective kernel | `IBatchPermission`: atomic approve → consume → reset-to-0 in exactly 3 calls; single-call evaluate() deliberately returns false | `0x095ea7b3` approve (calls 0 and 2) |
20
20
 
21
21
  ## Lessons the examples encode
@@ -29,6 +29,6 @@ Permissions only bound on-chain actions: venues with off-chain order matching (P
29
29
 
30
30
  ## Interfaces (vendored in `examples/permissions/interfaces/`)
31
31
 
32
- - `IPermission.evaluate(bytes txData, Context ctx) → bool`; Context: `account, manager, submitter, target, selector, value, blockTimestamp, blockNumber`.
33
- - `IBatchPermission.evaluateBatch(Call[] calls, BatchContext ctx) → bool` + `isBatchPermission() → true`; Call: `(target, value, data)`; BatchContext: `account, manager, submitter, permission, batchHash, blockTimestamp, blockNumber`.
32
+ - `IPermission.evaluate(bytes txData, Context ctx) → bool`; Context: `account, manager, submitter, target, selector, value, blockTimestamp, blockNumber, configEpoch`.
33
+ - `IBatchPermission.evaluateBatch(Call[] calls, BatchContext ctx) → bool` + `isBatchPermission() → true`; Call: `(target, value, data)`; BatchContext: `account, manager, submitter, permission, batchHash, blockTimestamp, blockNumber, configEpoch`.
34
34
  - `SailCalldata.sol`: bounded slot-indexed calldata readers (`hasParams`, `asAddress`, `asUint256`, …) — use these instead of `abi.decode` to avoid wrong-offset reads.
@@ -5,46 +5,47 @@ description: Walks the agent through setting up a new Sailor project or resuming
5
5
 
6
6
  # Sail onboarding
7
7
 
8
- ## First contact
8
+ ## Running the CLI
9
9
 
10
- On the user's first message (or after `sailor init` completes), present the full onboarding overview below do NOT launch the UI yet. The goal is for the user to understand every stage before committing.
10
+ **Determine the installation mode first**read `.sail/config.json installMode` before running any command:
11
11
 
12
- ---
12
+ - `"local"` (or field absent) — `sailor` is on the PATH. Run commands directly: `sailor <command>`
13
+ - `"docker"` — sailor runs in a container. Read `containerName` from the same config. Prefix every command:
14
+ `docker exec <containerName> sailor <command>`
15
+ Project files are on your **local filesystem** (mounted at `/workspace` inside the container) — read and write them normally from local paths. Do NOT use `docker exec` to read files; the volume mount makes them directly accessible.
13
16
 
14
- > **Sail Protocol** is infrastructure for onchain Separately Managed Accounts (SMAs) run by AI agents. You keep full custody; the agent acts only within cryptographically-bound permissions you approve and can always revoke. It cannot exceed them.
15
- >
16
- > Here is the complete path. Read it before we begin — each stage requires something from you.
17
- >
18
- > ---
19
- >
20
- > **Stage 1 — Deploy your SMA and create your agent wallet**
21
- > We open a browser interface where you connect your owner wallet (MetaMask or any WalletConnect wallet), choose your chain, and deploy your SMA — a smart contract account you own. We also create a separate agent wallet: a signing key the agent uses to submit transactions on your behalf. Both wallets need gas. The owner wallet signs the deployment; the agent wallet pays for everything after that.
22
- >
23
- > **Stage 2 — Define your strategy**
24
- > You tell me what you want the agent to do: which protocol, which tokens, what conditions, what limits. I help you separate the constraints that must be enforced on-chain (amount caps, venue allowlists, slippage floors) from the ones that live in agent logic (cadence, schedule, timing). Nothing is deployed yet — this is design.
25
- >
26
- > **Stage 3 — Build, test, and sign your mandate**
27
- > I write the Solidity permission contracts that encode the strategy bounds from Stage 2. You review them. We run Foundry tests against your actual strategy parameters (must-pass and must-fail cases). We deploy the contracts on-chain and simulate them before you sign anything. You authorize the permission set in the browser — one signature. After this, the agent can act autonomously within those bounds.
28
- >
29
- > **Stage 4 — Run your agent**
30
- > We run the agent locally first (`sailor run --once`) to confirm a full tick works. Then we automate it — GitHub Actions, a self-hosted runner, a local daemon, or Docker — matched to your latency needs and infrastructure comfort.
31
- >
32
- > **Stage 5 — Extend**
33
- > Once the agent is live, I offer to add run and transaction notifications (Telegram, email) and a strategy-specific dashboard. This is optional, but I will offer it — you opt out explicitly if you don't want it.
34
- >
35
- > ---
36
- >
37
- > Ready to begin? Say **start** and I'll open the setup interface in your browser for Stage 1.
17
+ ### Starting the Docker container
38
18
 
39
- ---
19
+ If the container is not running, start it from the project root:
20
+
21
+ ```bash
22
+ docker run -d --name agent -P -v "${PWD}:/workspace" sailmoney/sailor
23
+ ```
24
+
25
+ - `-d` — detached, runs in the background
26
+ - `--name agent` — names the container; use a different name with `-e SAILOR_CONTAINER_NAME=<name>` if needed
27
+ - `-P` — publishes all exposed ports to random available host ports (UI: 3334, station: 3141)
28
+ - `-v "${PWD}:/workspace"` — mounts the current project directory into the container
40
29
 
41
- After the user says "start" (or "ready" or any affirmative), THEN run `sailor ui start` the explanation and the UI launch are two separate beats. Do not skip the explanation. If the user's first message is an npm install command, run it, then present the overview immediately after.
30
+ After starting, resolve the host port before opening the UI in the browser:
42
31
 
43
- ## Running sailor commands
32
+ ```bash
33
+ docker port agent 3334
34
+ # → 0.0.0.0:49201 (use 49201, not 3334, in the browser URL)
35
+ ```
36
+
37
+ All `sailor` commands run via `docker exec`:
38
+
39
+ ```bash
40
+ docker exec agent sailor --version
41
+ docker exec agent sailor init
42
+ docker exec agent sailor ui start
43
+ docker exec agent sailor station start --json &
44
+ ```
44
45
 
45
- Before running any command, check how sailor is installed see **sail-sailor** for the full guide (local vs Docker, `installMode` detection, `docker exec` prefix, `--json` flag, `SAIL_PASSPHRASE`).
46
+ The published package is **`@sail.money/sailor`** always use the scoped name with the registry. The bare name `sailor` is a different, unrelated npm package; never `npx sailor@<version>` or `npm i sailor`. Install it (`npm i -g @sail.money/sailor`, or as a project dep), after which the `sailor` bin works bare (`sailor <command>`) and `npx sailor <command>` resolves the installed bin. Every `sailor …` command in these skills assumes it is installed. Confirm the toolchain up front and pin a recent version — `npx @sail.money/sailor@latest --version` because an old cached `npx` build can be missing newer commands (e.g. `mandate simulate`); if a documented command reports "unknown command", you are on a stale version, not hitting a missing feature.
46
47
 
47
- Short version: read `.sail/config.json installMode`. If `"docker"`, prefix every command with `docker exec <containerName>`. If `"local"` or absent, run `sailor <command>` directly.
48
+ After upgrading the CLI, run `sailor update` from the project root to pull in updated skills, `AGENTS.md`, `Dockerfile`, and other tooling files. User files (`src/`, `mandates/`, `.sail/`, `package.json`) are never touched.
48
49
 
49
50
  Stage machine keyed off `.sail/`. Read the state, enter at the right stage, never re-run completed stages.
50
51
 
@@ -57,7 +58,7 @@ Stage machine keyed off `.sail/`. Read the state, enter at the right stage, neve
57
58
  | `account.json` exists, `state/mandates.json` empty or absent | SMA live, no permissions — hand off to **sail-mandates** |
58
59
  | `account.json` + tracked mandates | SMA live with permissions — hand off to **sail-transactions** to run. Once the agent is live, the flow is not done: **offer Extend (stage 5 — notifications + a custom dashboard) by default**, unless the operator opts out, then hand off to **sail-extend**. The end state is "stage 5 offered," not "running" |
59
60
 
60
- Supported chains: Ethereum (1), Base (8453), Arbitrum (42161), Unichain (130), Base Sepolia (84532), Eth Sepolia (11155111). `sailor chains --json` lists them with kernel addresses.
61
+ Supported chains: Ethereum (1), Base (8453), Arbitrum (42161), Optimism (10), Unichain (130), BSC (56), World Chain (480), HyperEVM (999), MegaETH (4326), Base Sepolia (84532), Eth Sepolia (11155111). `sailor chains --json` lists them with kernel addresses.
61
62
 
62
63
  ## Stage 1 — Deploy the SMA and create the agent wallet
63
64
 
@@ -20,6 +20,7 @@ sailor ui start --expose tailscale # also serve it on the tailnet over HTTPS (
20
20
  - The server serves the pre-built React app (`SERVE_DIST=1`) and a local `/api` that reads `.sail/` state (`SAIL_DIR` env).
21
21
  - `ui start` does not block — no `&` needed.
22
22
  - `--expose tailscale` (optional): proxies the dashboard onto the operator's tailnet over HTTPS via `tailscale serve` (tailnet-private, never `funnel`). Requires `tailscale` installed + logged in, and Serve + HTTPS enabled for the tailnet (else the command prints the enable link). `ui stop` tears the proxy down. To allow extra browser origins, set `SAILOR_CORS_ORIGINS` (comma-separated; the local origin is always allowed).
23
+ - Binds `127.0.0.1` by default (local only). To expose without `--expose tailscale` — e.g. behind a reverse proxy on a domain — set `SAILOR_HOST=0.0.0.0`. The `/api` key-management endpoints are **unauthenticated**, so only do this behind your own auth (reverse-proxy basic-auth or a private tailnet), and throttle them with `SAILOR_RATE_LIMIT_PER_MIN` (default 100; or `rateLimitPerMin` in `.sail/config.json`).
23
24
 
24
25
  ## Signing station — `sailor station`
25
26
 
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: sail-swap-quote
3
+ description: Fetch a live Uniswap V3 quote via QuoterV2 and compute a slippage-adjusted amountOutMinimum for a swap. Use after sail-token-resolve to show the user the current price and to produce the amountOutMinimum floor the agent must embed in every swap dispatch. Runs the bundled `scripts/quote-swap.mjs` (no dependencies, no gas).
4
+ ---
5
+
6
+ # sail-swap-quote — live price + amountOutMinimum floor
7
+
8
+ After [`sail-token-resolve`](../sail-token-resolve/SKILL.md) confirms a token is
9
+ swap-ready, get the actual number: how much tokenOut the user's amountIn buys
10
+ right now, and the `amountOutMinimum` floor that protects against slippage.
11
+
12
+ ## When to load
13
+
14
+ - After `sail-token-resolve` returned `swapReady: true`.
15
+ - Before presenting a mandate to the user (show them the current price).
16
+ - To validate `amountOutMinimum` before the agent dispatches a real swap.
17
+ - Whenever the user asks "what's the price of X?" or "how much WETH for 25 USDC?".
18
+
19
+ ## Run it
20
+
21
+ ```bash
22
+ # From the project root. Inputs come from sail-token-resolve's output.
23
+ node scripts/quote-swap.mjs \
24
+ --token-in 0x078D782b760474a361dDA0AF3839290b0EF57AD6 --decimals-in 6 \
25
+ --token-out 0x4200000000000000000000000000000000000006 --decimals-out 18 \
26
+ --amount 25000000 --fee 3000 --slippage-bps 100
27
+ ```
28
+
29
+ - `--amount` is in **base units** of tokenIn (25 USDC = `25000000`, not `25`).
30
+ - `--fee` is the tier from `sail-token-resolve` (deepest pool).
31
+ - `--slippage-bps` defaults to `100` (1%). Tighten (e.g. `50`) for stable pairs,
32
+ loosen for volatile ones. `0` removes the floor — testing only.
33
+ - `--chain` / `--rpc` override the project's active chain/RPC.
34
+
35
+ Output: one JSON object on stdout; human notes on stderr.
36
+
37
+ ## What it returns
38
+
39
+ ```json
40
+ {
41
+ "amountIn": "25000000", "amountOut": "15805313193992907",
42
+ "amountOutMinimum": "15647260062052977", // amountOut × (10000-slip)/10000
43
+ "slippageBps": 100, "fee": 3000,
44
+ "human": { "amountIn": "25.0", "amountOut": "0.015805313193992907",
45
+ "amountOutMinimum": "0.015647260062052977",
46
+ "price": "0.015805313193992907 out per 25.0 in" }
47
+ }
48
+ ```
49
+
50
+ ## How the floor is computed
51
+
52
+ ```
53
+ amountOutMinimum = amountOut × (10000 − slippageBps) / 10000
54
+ ```
55
+
56
+ At 1% slippage, `amountOut × 9900 / 10000`. The agent must embed a value **at
57
+ least this high** in its swap calldata's `amountOutMinimum` field — the
58
+ `SwapPermission` mandate rejects any swap whose `amountOutMinimum` is below the
59
+ pool-implied floor (fail-closed). Re-quote close to dispatch time; a stale quote
60
+ is the most common reason a legitimate swap gets rejected.
61
+
62
+ ## What to do with it
63
+
64
+ 1. **Show the user** the `human.price` line before they approve the mandate.
65
+ 2. **Record `amountOutMinimum`** as the floor the agent's dispatch logic must
66
+ respect. Hand the full quote to [`sail-template-swap`](../sail-template-swap/SKILL.md)
67
+ — the mandate's `maxSlippageBps` bound is the on-chain enforcement of this.
68
+ 3. **Re-quote at dispatch time** — pool state moves; a quote taken minutes ago may
69
+ no longer match.
70
+
71
+ ## If it fails
72
+
73
+ | Error | Meaning |
74
+ |---|---|
75
+ | `QuoterV2 reverted` / `amountOut == 0` | No pool at this fee tier (despite resolve-token). Try the next tier from resolve-token's `probedTiers`, or re-run resolve-token — liquidity may have shifted. |
76
+ | `No RPC for chain` | Run from the project root, or pass `--rpc`. |
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: sail-template-approve-batch
3
+ description: Gate an SMA's "approve → call → reset" interactions by REUSING the shared ApproveAndCallBatchPermission singleton (Protocol/contracts/templates/ApproveAndCallBatchPermission.sol) — register + configure, no per-SMA deploy. Use when an agent must approve an ERC-20 to a protocol, make one consuming call, and reset the allowance to zero — all in one atomic batch — with token/spender/target/selector allowlists and a per-token approval cap. NOTE: `sailor mandate attach` only registers — you must also configure per-account (see steps).
4
+ compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires ApproveAndCallBatchPermission deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
5
+ metadata:
6
+ workspace: sailor-harness
7
+ classification: generic
8
+ status: draft
9
+ origin: Protocol/contracts/templates/ApproveAndCallBatchPermission.sol
10
+ ---
11
+
12
+ # sail-template-approve-batch — atomic approve/call/reset via the shared singleton
13
+
14
+ Reuse the shared **`ApproveAndCallBatchPermission`** singleton — the safest way to bracket a
15
+ single protocol interaction that needs an allowance. Family overview + flow:
16
+ [`sail-templates`](../sail-templates/SKILL.md).
17
+
18
+ ## What it enforces (per account, from source)
19
+
20
+ Authorises exactly this 3-call batch:
21
+ ```
22
+ [0] approve(spender, amount) on an allowlisted ERC-20
23
+ [1] consuming call on an allowlisted (target, selector)
24
+ [2] approve(spender, 0) reset to zero (same atomic batch)
25
+ ```
26
+ Invariants: `tokens`, `spenders`, and `consumingPairs` must each be non-empty with no zero
27
+ addresses/selectors (`EmptyAllowlist` reverts at configure otherwise); `token ∈ tokens` and
28
+ `amount ≤ maxApprovalAmount[token]`; `spender ∈ spenders`; `(target, selector) ∈ consumingPairs`;
29
+ the pre-batch allowance on `(token, spender)` must be zero; the consuming call must target the
30
+ approved `spender` and pull the approved `token`; the reset-to-zero is required; if
31
+ `requireAmountMatch`, the consuming call's leading `uint256` arg must equal the approve amount.
32
+ Malformed calldata reverts (kernel treats revert as deny).
33
+
34
+ > **Output recipient — `allowUnconstrainedRecipient` (opt-out, default-safe).** By DEFAULT
35
+ > (`allowUnconstrainedRecipient = false`, i.e. omitted) the consuming call's output recipient is
36
+ > decoded and **must equal the SMA** — the safe posture — and any selector outside the decodable
37
+ > set below is denied (fail-closed). Set `allowUnconstrainedRecipient = true` ONLY to deliberately
38
+ > leave the recipient unconstrained (e.g. to authorize a selector outside the decodable set); then
39
+ > only allowlist `(target, selector)` pairs whose semantics you trust to deliver output to the SMA.
40
+ > ⚠️ This is an **opt-out** flag, not an opt-in — do not set it `true` thinking that "requires"
41
+ > or "pins" the recipient; that gets you the opposite (unconstrained) behavior on-chain.
42
+ >
43
+ > **Decodable set** (recipient AND consumed-asset both resolvable at a fixed calldata offset —
44
+ > any other selector is denied regardless of `allowUnconstrainedRecipient`, since the consumed
45
+ > asset can never be bound to the approved token): `swapExactTokensForTokens` (V2), V3
46
+ > `exactInputSingle` (both the SwapRouter and SwapRouter02 layouts), Aave V2 `deposit` / Aave V3
47
+ > `supply`, ERC-4626 `deposit` / `mint`.
48
+
49
+ ## Config blob (authoritative — `config-schemas.md`)
50
+
51
+ ```
52
+ abi.encode(Config{
53
+ address[] tokens; // approvable ERC-20s
54
+ address[] spenders; // allowed allowance recipients
55
+ ConsumingPair[] consumingPairs; // {address target; bytes4 selector} — pair bound together
56
+ uint256[] maxApprovalAmounts; // index-parallel with tokens; per-token cap
57
+ bool requireAmountMatch; // call[1] leading uint256 must equal approve amount
58
+ bool allowUnconstrainedRecipient; // OPT-OUT (default false = call[1] output recipient decoded, must == account)
59
+ })
60
+ struct ConsumingPair { address target; bytes4 selector; }
61
+ ```
62
+ `maxApprovalAmounts.length` must equal `tokens.length` (configure reverts otherwise).
63
+ `consumingPairs` is a **struct array** `(address target, bytes4 selector)[]` — NOT two flat
64
+ `address[]`/`bytes4[]` arrays. A selector is authorised only on the target it is paired with.
65
+
66
+ **ABI tuple (verified round-trip):** `(address[],address[],(address,bytes4)[],uint256[],bool,bool)`.
67
+ Encode with cast — note `selector` is a `bytes4` (left-aligned), and both trailing bools are required:
68
+ ```bash
69
+ cast abi-encode 'cfg((address[],address[],(address,bytes4)[],uint256[],bool,bool))' \
70
+ '([<token>],[<spender>],[(<target>,<selector>)],[<maxApproval>],<requireAmountMatch>,<allowUnconstrainedRecipient>)'
71
+ ```
72
+ `allowUnconstrainedRecipient` is an **opt-out** — leave it `false` unless you deliberately want
73
+ the consuming call's output recipient unconstrained (see the field note above).
74
+ The leading word is the `0x20` struct offset — that is correct, not flat-param corruption. A
75
+ generic (un-named) revert inside `_applyConfig` means the blob did not decode to this exact tuple
76
+ (usually: flat params with no `0x20` offset, the two flat arrays instead of `ConsumingPair[]`, or a
77
+ missing trailing bool). The named errors (`TokensAndAmountsLengthMismatch`, `EmptyAllowlist`,
78
+ `AllowlistTooLong`) fire only after a successful decode.
79
+
80
+ ## Steps
81
+
82
+ Register → configure → simulate → reconfigure mechanics live in
83
+ [`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
84
+ `sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
85
+ permission live. Template-specific bits:
86
+
87
+ - **Singleton:** `ApproveAndCallBatchPermission` — `node SKILLS/sail-templates/catalog.mjs --chain
88
+ <id>`.
89
+ - **Spec to confirm:** tokens+caps, spenders, `(target, selector)` consuming pairs,
90
+ `requireAmountMatch`, `allowUnconstrainedRecipient`. Get each selector with `cast sig` and verify
91
+ it delivers output to the SMA (leave `allowUnconstrainedRecipient = false`, the default, to
92
+ enforce that on-chain — set `true` only to deliberately opt out).
93
+ - **Blob — ⚠️ NOT flat params.** `abi.encode(Config{ … })` — a **single wrapped struct**, so the
94
+ blob starts with a `0x20` offset word. `consumingPairs` is `(address,bytes4)[]` (a struct array),
95
+ and both trailing bools are required — see the verified `cast abi-encode` form above. Unlike
96
+ transfer/withdraw/deposit/borrow/swap, wrapping is required here; a flat blob reverts at configure.
97
+ - **Simulate (mandatory — unaudited example):** the full allowed approve/call/reset batch passes;
98
+ missing reset, off-list spender/target/selector, or over-cap approve is rejected.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: sail-template-borrow
3
+ description: Gate an SMA's lending borrows by REUSING the shared BorrowPermission singleton (Protocol/contracts/templates/BorrowPermission.sol) — register + configure, no per-SMA deploy. Use for a bounded borrow mandate against Aave / Morpho / Compound with a protocol + asset allowlist, per-tx cap, and an on-chain LTV check via collateral + borrow oracles. NOTE: `sailor mandate attach` only registers — you must also configure per-account (see steps).
4
+ compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires BorrowPermission deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
5
+ metadata:
6
+ workspace: sailor-harness
7
+ classification: generic
8
+ status: draft
9
+ origin: Protocol/contracts/templates/BorrowPermission.sol
10
+ ---
11
+
12
+ # sail-template-borrow — bounded lending borrow via the shared singleton
13
+
14
+ Reuse the shared **`BorrowPermission`** singleton. Family overview + flow:
15
+ [`sail-templates`](../sail-templates/SKILL.md).
16
+
17
+ ## What it enforces (per account, from source)
18
+
19
+ Selectors (any other ⇒ `false`): Aave `borrow(address,uint256,uint256,uint16,address)`
20
+ (**variable-rate only** — a stable-rate borrow, `rateMode != 2`, is rejected), Morpho
21
+ **Optimizer/Morpho-Aave** `borrow(address,uint256,address,address)` (**NOT Morpho Blue** — its
22
+ ABI differs and simply won't match this selector, i.e. fails closed; target a Blue-specific
23
+ permission instead), Compound `borrow(uint256)` (the call target is the **cToken**; the template
24
+ resolves `cToken.underlying()` and keys the allowlist/cap/LTV on the underlying — a target with
25
+ no `underlying()`, e.g. cETH, is denied). Invariants: `target ∈ protocols`; `asset ∈ assets`
26
+ (Aave/Morpho decode the asset; Compound resolves it via `underlying()`); `amount ≤
27
+ maxAmountPerTx`; `onBehalfOf`/`receiver == SMA`; **`_ltvCheck` passes** — the projected debt
28
+ value (via `borrowOracle`) against collateral value (via `collateralOracle`) must stay within
29
+ `maxLtvBps`, when oracles are configured (see ORACLE MODES below).
30
+
31
+ > **Oracle modes:** configure **zero** oracles for amount-cap-only borrowing (no LTV ceiling is
32
+ > applied at all, despite `maxLtvBps` being stored) or **both** — exactly one oracle reverts
33
+ > `OracleConfigInconsistent` at configure (a single feed can't price a ratio). When both are set,
34
+ > the check still depends on their honesty/freshness — see staleness via `maxPriceAgeSec`.
35
+
36
+ ## Config blob (authoritative — `config-schemas.md`)
37
+
38
+ ```
39
+ abi.encode(address[] protocols, address[] assets, uint256 maxAmountPerTx,
40
+ uint256 maxLtvBps, address collateralOracle, address borrowOracle,
41
+ uint256 maxPriceAgeSec)
42
+ ```
43
+ | Field | Notes |
44
+ |---|---|
45
+ | `protocols` | lending pools the agent may borrow from |
46
+ | `assets` | borrowable ERC-20 allowlist |
47
+ | `maxAmountPerTx` | per-borrow cap, base units |
48
+ | `maxLtvBps` | max loan-to-value, bps (e.g. `5000` = 50%) |
49
+ | `collateralOracle` / `borrowOracle` | price sources for the LTV check |
50
+ | `maxPriceAgeSec` | oracle freshness bound; `> 0` when oracles are set |
51
+
52
+ > `maxLtvBps` is a **per-transaction** projection, not a tracked lifetime LTV. For
53
+ > portfolio-level exposure, rely on the protocol's health factor or pair with a
54
+ > position-monitoring permission.
55
+
56
+ ## Steps
57
+
58
+ Register → configure → simulate → reconfigure mechanics (and the encoding gotcha) live in
59
+ [`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
60
+ `sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
61
+ permission live. Template-specific bits:
62
+
63
+ - **Singleton:** `BorrowPermission` — `node SKILLS/sail-templates/catalog.mjs --chain <id>`.
64
+ - **Spec to confirm:** protocols, assets, cap, max LTV, oracles.
65
+ - **Blob:** `abi.encode(protocols[], assets[], maxAmountPerTx, maxLtvBps, collateralOracle,
66
+ borrowOracle, maxPriceAgeSec)` — **flat params, no wrapper**.
67
+ - **Simulate (mandatory — unaudited example):** allowed borrow within LTV passes; over-cap,
68
+ over-LTV, or wrong recipient is rejected.
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: sail-template-deposit
3
+ description: Gate an SMA's deposits into vaults / lending pools by REUSING the shared DepositPermission singleton (Protocol/contracts/templates/DepositPermission.sol) — register + configure, no per-SMA deploy. Use for a bounded deposit mandate into ERC-4626 vaults (deposit/mint) or Aave v2/v3 (deposit/supply) with a target + token allowlist and a per-tx cap; the resulting position always accrues to the SMA. NOTE: `sailor mandate attach` only registers — you must also configure per-account (see steps).
4
+ compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires DepositPermission deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
5
+ metadata:
6
+ workspace: sailor-harness
7
+ classification: generic
8
+ status: draft
9
+ origin: Protocol/contracts/templates/DepositPermission.sol
10
+ ---
11
+
12
+ # sail-template-deposit — bounded vault/lending deposit via the shared singleton
13
+
14
+ Reuse the shared **`DepositPermission`** singleton. Family overview + flow:
15
+ [`sail-templates`](../sail-templates/SKILL.md). Discover yield targets first with
16
+ `sail-defillama-yields`.
17
+
18
+ ## What it enforces (per account, from source)
19
+
20
+ Selectors (any other ⇒ `false`):
21
+
22
+ | Function | Venue |
23
+ |---|---|
24
+ | `deposit(uint256 assets, address receiver)` | ERC-4626 / simple vault |
25
+ | `mint(uint256 shares, address receiver)` | ERC-4626 |
26
+ | `deposit(address asset, uint256 amount, address onBehalfOf, uint16)` | Aave v2 |
27
+ | `supply(address asset, uint256 amount, address onBehalfOf, uint16)` | Aave v3 |
28
+
29
+ Invariants: `value == 0`; `targets`/`tokens` must be non-empty with no zero addresses
30
+ (`EmptyAllowlist`/`ZeroAddress` revert at configure otherwise); `target ∈ targets`; token
31
+ allowlist enforced — Aave checks the `asset` arg, ERC-4626 requires the `target` (vault) itself
32
+ ∈ `tokens`; `amount`/`shares ≤ maxAmountPerTx`; `receiver`/`onBehalfOf == SMA`.
33
+
34
+ > `mint`'s cap is in **shares**, not underlying. At a high share price the effective asset cap
35
+ > is `maxAmountPerTx × sharePrice` — size accordingly. For ERC-4626 `deposit`/`mint` the token
36
+ > allowlist is the last line of defence (no asset in calldata) — only allowlist vaults whose
37
+ > accepted token you trust.
38
+
39
+ ## Config blob (authoritative — `config-schemas.md`)
40
+
41
+ ```
42
+ abi.encode(address[] targets, address[] tokens, uint256 maxAmountPerTx)
43
+ ```
44
+ | Field | Notes |
45
+ |---|---|
46
+ | `targets` | vault / lending-pool addresses |
47
+ | `tokens` | ERC-20 allowlist (the asset for Aave; the vault itself for ERC-4626) |
48
+ | `maxAmountPerTx` | per-deposit cap (assets, or shares for `mint`), base units |
49
+
50
+ ## Steps
51
+
52
+ Register → configure → simulate → reconfigure mechanics (and the encoding gotcha) live in
53
+ [`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
54
+ `sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
55
+ permission live. Template-specific bits:
56
+
57
+ - **Singleton:** `DepositPermission` — `node SKILLS/sail-templates/catalog.mjs --chain <id>`.
58
+ - **Spec to confirm:** targets, tokens, cap (note the share-price caveat for `mint`).
59
+ - **Blob:** `abi.encode(targets[], tokens[], maxAmountPerTx)` — **flat params, no wrapper**.
60
+ - **Simulate (mandatory — unaudited example):** allowed deposit within cap passes; off-list
61
+ target/token, over-cap, or `receiver != SMA` is rejected.