@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,4 +1,4 @@
1
- import{F as l}from"./core-Ckx_cyuH.js";import"./index-Q2Yai4Fe.js";import"./events-CiKP71cK.js";import"./index.es-C78cE5SI.js";import"./fallback-Ofl6uSnB.js";const o=l`<svg fill="none" viewBox="0 0 96 67">
1
+ import{F as l}from"./core-tX9kIIDJ.js";import"./index-DZfBh-cg.js";import"./events-DjdZr6no.js";import"./index.es-BEcNQEn-.js";import"./fallback-DJIr_fH3.js";const o=l`<svg fill="none" viewBox="0 0 96 67">
2
2
  <path
3
3
  fill="currentColor"
4
4
  d="M25.32 18.8a32.56 32.56 0 0 1 45.36 0l1.5 1.47c.63.62.63 1.61 0 2.22l-5.15 5.05c-.31.3-.82.3-1.14 0l-2.07-2.03a22.71 22.71 0 0 0-31.64 0l-2.22 2.18c-.31.3-.82.3-1.14 0l-5.15-5.05a1.55 1.55 0 0 1 0-2.22l1.65-1.62Zm56.02 10.44 4.59 4.5c.63.6.63 1.6 0 2.21l-20.7 20.26c-.62.61-1.63.61-2.26 0L48.28 41.83a.4.4 0 0 0-.56 0L33.03 56.21c-.63.61-1.64.61-2.27 0L10.07 35.95a1.55 1.55 0 0 1 0-2.22l4.59-4.5a1.63 1.63 0 0 1 2.27 0L31.6 43.63a.4.4 0 0 0 .57 0l14.69-14.38a1.63 1.63 0 0 1 2.26 0l14.69 14.38a.4.4 0 0 0 .57 0l14.68-14.38a1.63 1.63 0 0 1 2.27 0Z"
@@ -1,4 +1,4 @@
1
- import{F as r}from"./core-Ckx_cyuH.js";import"./index-Q2Yai4Fe.js";import"./events-CiKP71cK.js";import"./index.es-C78cE5SI.js";import"./fallback-Ofl6uSnB.js";const a=r`<svg fill="none" viewBox="0 0 20 20">
1
+ import{F as r}from"./core-tX9kIIDJ.js";import"./index-DZfBh-cg.js";import"./events-DjdZr6no.js";import"./index.es-BEcNQEn-.js";import"./fallback-DJIr_fH3.js";const a=r`<svg fill="none" viewBox="0 0 20 20">
2
2
  <path
3
3
  fill="currentColor"
4
4
  d="M11 6.67a1 1 0 1 0-2 0v2.66a1 1 0 0 0 2 0V6.67ZM10 14.5a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"
@@ -1,4 +1,4 @@
1
- import{F as l}from"./core-Ckx_cyuH.js";import"./index-Q2Yai4Fe.js";import"./events-CiKP71cK.js";import"./index.es-C78cE5SI.js";import"./fallback-Ofl6uSnB.js";const a=l`<svg fill="none" viewBox="0 0 41 40">
1
+ import{F as l}from"./core-tX9kIIDJ.js";import"./index-DZfBh-cg.js";import"./events-DjdZr6no.js";import"./index.es-BEcNQEn-.js";import"./fallback-DJIr_fH3.js";const a=l`<svg fill="none" viewBox="0 0 41 40">
2
2
  <g clip-path="url(#a)">
3
3
  <path fill="#000" d="M.8 0h40v40H.8z" />
4
4
  <path
@@ -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-Q2Yai4Fe.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-CKxgNxS9.css">
8
+ <script type="module" crossorigin src="/assets/index-DZfBh-cg.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Cm05Py20.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -53,9 +53,8 @@ try {
53
53
  fail(`\`sailor init ${PROJECT}\` exited non-zero.\n ${out || err.message}`);
54
54
  }
55
55
 
56
- // A successful fresh init prints the welcome + next steps, ending with the
57
- // `Say: "start"` call-to-action. (Older builds printed "Done!".)
58
- if (!/Say: "start"/.test(stdout)) {
56
+ // A successful fresh init prints the AGENTS.md onboarding banner.
57
+ if (!/AGENTS\.md/i.test(stdout)) {
59
58
  fail(`init did not report success.\n stdout: ${stdout.trim()}`);
60
59
  }
61
60
 
@@ -78,8 +78,39 @@ Both attach paths open the browser signing station so the owner authorizes the r
78
78
  > contract address you provide. A bug can block all agent activity or authorize transactions you did
79
79
  > not intend. Review carefully before attaching.**
80
80
 
81
+ ## Extracting calldata parameters safely
82
+
83
+ When you need to bound a specific call argument (amount cap, recipient check, slippage floor),
84
+ use `SailCalldata` instead of manual `abi.decode`. The two common bugs it prevents:
85
+
86
+ 1. **Forgetting the length check** — decoding before checking `txData.length` can revert or
87
+ silently return wrong values. `SailCalldata.hasParams(txData, N)` is the one-line guard.
88
+ 2. **Wrong slot index** — off-by-one decodes the wrong parameter. Named helpers make the
89
+ intent explicit: `asAddress(txData, 0)`, `asUint256(txData, 1)`, `asAddress(txData, 2)`.
90
+
91
+ ```solidity
92
+ import {SailCalldata} from "./SailCalldata.sol";
93
+
94
+ function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
95
+ if (ctx.target != POOL) return false;
96
+ if (ctx.selector != SEL_SUPPLY) return false;
97
+ // supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)
98
+ if (!SailCalldata.hasParams(txData, 4)) return false;
99
+ address asset = SailCalldata.asAddress(txData, 0);
100
+ uint256 amount = SailCalldata.asUint256(txData, 1);
101
+ address onBehalfOf = SailCalldata.asAddress(txData, 2);
102
+ // ...
103
+ }
104
+ ```
105
+
106
+ Available helpers: `asAddress`, `asUint256`, `asInt256`, `asBytes32`, `asBool`,
107
+ `asUint128`, `asUint64`, `asUint32`, `asUint24`, `asUint16`, `asBytes4`.
108
+ Only covers static (fixed-size) types. For `bytes`, `string`, or dynamic arrays,
109
+ use `abi.decode(txData[4:], ...)` after the `hasParams` guard.
110
+
81
111
  ## Structure
82
112
 
83
113
  - `foundry.toml` — Foundry config with `@sail/` remapping to `.sail/contracts/`
84
114
  - `.sail/contracts/interfaces/IPermission.sol` — interface copy (matches SailProtocol)
85
115
  - `mandates/BoundedCallPermission.sol` — general primitive: allowlisted targets, optional selector filter, max ETH value
116
+ - `mandates/SailCalldata.sol` — safe calldata parameter extraction helpers
@@ -2,12 +2,18 @@
2
2
  pragma solidity 0.8.26;
3
3
 
4
4
  import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
5
+ // SailCalldata: safe helpers for extracting calldata parameters inside evaluate().
6
+ // Use SailCalldata.hasParams(txData, N) + SailCalldata.asAddress/asUint256/... instead of
7
+ // manual abi.decode when you need to bound specific call arguments (amounts, recipients, etc.).
8
+ // See SailCalldata.sol for the full API and examples/permissions/ for protocol examples.
9
+ import {SailCalldata} from "./SailCalldata.sol";
5
10
 
6
11
  /// @title BoundedCallPermission
7
12
  /// @notice General-purpose IPermission primitive. Bounds the universal properties of any call:
8
13
  /// allowed targets, allowed selectors, and max ETH value. Protocol-agnostic.
9
- /// For calldata-parameter bounds (amount caps, recipient checks, slippage), write a
10
- /// protocol-specific permission see examples/permissions/ for the pattern per protocol.
14
+ /// For calldata-parameter bounds (amount caps, recipient checks, slippage), use
15
+ /// SailCalldata (imported above) and write a protocol-specific permission
16
+ /// see examples/permissions/ for the pattern per protocol.
11
17
  /// @dev Deploy one instance per SMA with constructor-configured parameters.
12
18
  contract BoundedCallPermission is IPermission {
13
19
  bytes32 private constant DISCRIMINATOR = keccak256("BoundedCallPermission");
@@ -0,0 +1,118 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // SailCalldata — safe static-parameter extraction for IPermission.evaluate()
6
+ //
7
+ // PROBLEM
8
+ // evaluate(bytes calldata txData, Context calldata ctx) receives raw ABI-
9
+ // encoded calldata. Extracting parameters by hand is the most dangerous part
10
+ // of permission writing:
11
+ // • Forgetting the length check before decoding → silent wrong value or
12
+ // revert instead of clean `return false`.
13
+ // • Wrong slot index → decoding the wrong parameter (type-checks, still wrong).
14
+ // • Truncation bugs when casting uint256 slots to address (padding bits).
15
+ //
16
+ // SOLUTION
17
+ // This library centralises the three operations into named helpers:
18
+ // 1. hasParams() — explicit length guard (call once, at the top of evaluate)
19
+ // 2. asAddress() — extract + mask to 20 bytes
20
+ // 3. asUint256(), asInt256(), asBytes32(), asBool(), asUint128() — typed slots
21
+ //
22
+ // All helpers are view/pure and add zero gas overhead beyond the slice itself.
23
+ //
24
+ // USAGE (replace abi.decode pattern)
25
+ //
26
+ // // Before:
27
+ // if (txData.length < 4 + 3 * 32) return false;
28
+ // (address asset, uint256 amount, address onBehalfOf) =
29
+ // abi.decode(txData[4:], (address, uint256, address));
30
+ //
31
+ // // After:
32
+ // if (!SailCalldata.hasParams(txData, 3)) return false;
33
+ // address asset = SailCalldata.asAddress(txData, 0);
34
+ // uint256 amount = SailCalldata.asUint256(txData, 1);
35
+ // address onBehalfOf = SailCalldata.asAddress(txData, 2);
36
+ //
37
+ // LIMITATIONS
38
+ // Only covers static (fixed-size) ABI types. Dynamic types (bytes, string,
39
+ // arrays) use pointer indirection — decode them with abi.decode(txData[4:], ...)
40
+ // after the hasParams() guard, or write a dedicated extractor.
41
+ //
42
+ // SLOT INDEXING
43
+ // Slots are 0-indexed from the first constructor parameter (after the 4-byte
44
+ // selector). Slot 0 = bytes [4..35], slot 1 = bytes [36..67], etc.
45
+ // ─────────────────────────────────────────────────────────────────────────────
46
+
47
+ library SailCalldata {
48
+ // ── Guards ────────────────────────────────────────────────────────────────
49
+
50
+ /// @notice Returns true when txData is long enough to hold `params` static
51
+ /// 32-byte ABI slots after the 4-byte selector.
52
+ /// @dev Call this once at the top of evaluate() before any slot access.
53
+ /// Returns false (not revert) so evaluate() can return false cleanly.
54
+ function hasParams(bytes calldata txData, uint256 params) internal pure returns (bool) {
55
+ return txData.length >= 4 + params * 32;
56
+ }
57
+
58
+ // ── Static-type extractors ────────────────────────────────────────────────
59
+ // All assume hasParams() has already been checked for the relevant slot.
60
+ // Accessing an out-of-bounds slot will cause the calldata slice to revert —
61
+ // always guard with hasParams() first.
62
+
63
+ /// @notice Extract an address from ABI slot `i` (0-indexed after selector).
64
+ /// Masks to 20 bytes, discarding the ABI zero-padding in the upper 12.
65
+ function asAddress(bytes calldata txData, uint256 i) internal pure returns (address) {
66
+ return address(uint160(uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]))));
67
+ }
68
+
69
+ /// @notice Extract a uint256 from ABI slot `i`.
70
+ function asUint256(bytes calldata txData, uint256 i) internal pure returns (uint256) {
71
+ return uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]));
72
+ }
73
+
74
+ /// @notice Extract an int256 from ABI slot `i`.
75
+ function asInt256(bytes calldata txData, uint256 i) internal pure returns (int256) {
76
+ return int256(uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32])));
77
+ }
78
+
79
+ /// @notice Extract a bytes32 from ABI slot `i`.
80
+ function asBytes32(bytes calldata txData, uint256 i) internal pure returns (bytes32) {
81
+ return bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]);
82
+ }
83
+
84
+ /// @notice Extract a bool from ABI slot `i` (true when slot value is non-zero).
85
+ function asBool(bytes calldata txData, uint256 i) internal pure returns (bool) {
86
+ return asUint256(txData, i) != 0;
87
+ }
88
+
89
+ /// @notice Extract a uint128 from ABI slot `i` (lower 128 bits of the slot).
90
+ function asUint128(bytes calldata txData, uint256 i) internal pure returns (uint128) {
91
+ return uint128(asUint256(txData, i));
92
+ }
93
+
94
+ /// @notice Extract a uint64 from ABI slot `i`.
95
+ function asUint64(bytes calldata txData, uint256 i) internal pure returns (uint64) {
96
+ return uint64(asUint256(txData, i));
97
+ }
98
+
99
+ /// @notice Extract a uint32 from ABI slot `i`.
100
+ function asUint32(bytes calldata txData, uint256 i) internal pure returns (uint32) {
101
+ return uint32(asUint256(txData, i));
102
+ }
103
+
104
+ /// @notice Extract a uint24 from ABI slot `i` (e.g. Uniswap fee tier).
105
+ function asUint24(bytes calldata txData, uint256 i) internal pure returns (uint24) {
106
+ return uint24(asUint256(txData, i));
107
+ }
108
+
109
+ /// @notice Extract a uint16 from ABI slot `i` (e.g. Aave referral code).
110
+ function asUint16(bytes calldata txData, uint256 i) internal pure returns (uint16) {
111
+ return uint16(asUint256(txData, i));
112
+ }
113
+
114
+ /// @notice Extract bytes4 (a function selector) from ABI slot `i`.
115
+ function asBytes4(bytes calldata txData, uint256 i) internal pure returns (bytes4) {
116
+ return bytes4(asBytes32(txData, i));
117
+ }
118
+ }
@@ -0,0 +1,20 @@
1
+ # Sailor agent environment
2
+ #
3
+ # RPC configuration — two patterns, pick one:
4
+ #
5
+ # Option A: single active chain (simplest)
6
+ RPC_URL=https://your-rpc-endpoint
7
+ CHAIN_ID=8453
8
+ #
9
+ # Option B: per-chain endpoints (multi-chain projects, or if you prefer explicit names)
10
+ # Set CHAIN_ID to the chain sailor run uses; omit RPC_URL if all chains have a specific var.
11
+ # CHAIN_ID=8453
12
+ # BASE_RPC_URL=https://your-base-endpoint
13
+ # ARBITRUM_RPC_URL=https://your-arbitrum-endpoint
14
+ # UNICHAIN_RPC_URL=https://your-unichain-endpoint
15
+ # ETH_MAINNET_RPC_URL=https://your-mainnet-endpoint
16
+ # BASE_SEPOLIA_RPC_URL=https://your-base-sepolia-endpoint
17
+ # SEPOLIA_RPC_URL=https://your-sepolia-endpoint
18
+
19
+ # Optional: non-interactive passphrase (CI, GitHub Actions, launchd, systemd)
20
+ # SAIL_PASSPHRASE=change-me-to-a-strong-passphrase
@@ -12,21 +12,22 @@ jobs:
12
12
  steps:
13
13
  - uses: actions/checkout@v4
14
14
 
15
- - uses: pnpm/action-setup@v4
16
- with:
17
- version: 9
18
-
19
15
  - uses: actions/setup-node@v4
20
16
  with:
21
17
  node-version: 20
22
- cache: "pnpm"
18
+ cache: "npm"
23
19
 
24
20
  - name: Install dependencies
25
- run: pnpm install --frozen-lockfile
21
+ run: npm ci
22
+
23
+ - name: Copy keystore to expected path
24
+ run: |
25
+ mkdir -p .sail/keys
26
+ cp ci-keystore.json .sail/keys/manager.json
26
27
 
27
28
  - name: Run agent tick
28
29
  env:
29
30
  RPC_URL: ${{ secrets.RPC_URL }}
30
- MANAGER_KEY: ${{ secrets.MANAGER_KEY }}
31
+ SAIL_PASSPHRASE: ${{ secrets.SAIL_PASSPHRASE }}
31
32
  CHAIN_ID: ${{ vars.CHAIN_ID || '8453' }}
32
33
  run: npx sailor run --once
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
- "name": "dca-rebalancer",
4
- "chainId": 8453,
3
+ "name": "sail-agent",
4
+ "chainId": null,
5
5
  "stateDir": ".sail/state",
6
6
  "contracts": {
7
7
  "kernel": "",
@@ -0,0 +1,171 @@
1
+ Sail Protocol is infrastructure for onchain Separately Managed Accounts run by AI agents. You create an SMA, keep full custody, and define exactly what your agent can do — cryptographically bound permissions you approve and can always revoke. The agent executes within those bounds on every transaction. It cannot exceed them.
2
+
3
+ I'm Sailor, the operator that sets this up. I'll help you create your SMA, build the permissions that bound your agent, and get your strategy running.
4
+
5
+ Here's where we're headed:
6
+
7
+ 1. Deploy your SMA and create your agent wallet
8
+ 2. Define your strategy
9
+ 3. Build, test, and sign your mandate
10
+ 4. Run your agent — locally or on a schedule
11
+ 5. Extend with notifications and a custom dashboard
12
+
13
+ Ready? Say **start** and I'll open the setup interface in your browser.
14
+
15
+ ---
16
+
17
+ # Instructions for the assistant
18
+
19
+ Everything below is for you, the assistant. The user sees the welcome above; you follow the flow below.
20
+
21
+ ## Voice
22
+
23
+ You are Sailor. Serious, precise, confident. No hype, no emojis, no exclamation marks. Explain *why*, not just *what* — the user is moving real funds. Use user-facing terms (SMA, mandate, permissions, agent wallet, owner). Assume crypto-native; teach the Sail-specific model.
24
+
25
+ Never overstate safety: custody is protected, but a mandate is only as correct as its permission contracts.
26
+
27
+ ## Authorization rule
28
+
29
+ During **setup**, always ask before anything that costs gas. Once the **mandate is signed and the agent is running**, the mandate is the authorization — the agent transacts autonomously. Do not ask per-dispatch.
30
+
31
+ ## First contact
32
+
33
+ When the user says start (or any first message), present the welcome above in full — definition, stage list, handoff line — before doing anything else. Do not launch the UI yet. After the user says start a second time (or confirms they are ready), THEN run `sailor ui start`. The welcome and the UI launch are two separate beats separated by the user's go-ahead.
34
+
35
+ Determine the user's progress by reading `.sail/` — do not ask; read it.
36
+
37
+ If the user's first message is an npm install command, run it, then present the welcome immediately after it completes — do not wait for another message.
38
+
39
+ ## Stage 1 — Deploy your SMA and create your agent wallet
40
+
41
+ In the browser. Run `sailor ui start`, open the printed URL, connect your owner wallet, choose your network, and deploy your SMA. Then create your agent wallet — a separate signing key I use to submit transactions on your behalf. You need gas in both: the owner wallet to deploy and sign the mandate; the agent wallet to submit transactions once the agent is running. The owner key never leaves the browser.
42
+
43
+ **Deterministic address (salt):** every SMA deployment uses a CREATE2 salt. The CLI defaults to salt `0`, giving you a predictable address you can verify before spending gas: `sailor account predict --owner <your-wallet> --manager <agent-wallet>`. The kernel binds the salt to your owner wallet, agent (manager) wallet, and fee policy — create your agent wallet first, then predict. The salt is saved in `.sail/account.json` automatically. All supported chains (Ethereum, Base, Arbitrum, Unichain, plus testnets) share the same protocol addresses via CREATE2, so the same owner, manager, and salt produce the **same SMA address on every chain**.
44
+
45
+ **Multi-chain deployment:** once your SMA is live on one chain, deploy it at the same address on any other supported chain with `sailor account deploy-chain --chain <id>` (e.g. `--chain 42161` for Arbitrum). The owner approves the deployment in the browser; no new salt or agent wallet needed. Run `sailor account predict` first to confirm the address matches.
46
+
47
+ ## Stage 2 — Define your strategy
48
+
49
+ Tell me what you want your agent to do. I'll ask the right questions, establish the on-chain bounds with you (tokens, amounts, slippage, venues), and set up your RPC endpoint once you've chosen your chain. Blank slate — you define the strategy.
50
+
51
+ For a worked end-to-end example (DCA / Uniswap V3 / Base), consult `examples/dca/` — reference only; not your strategy.
52
+
53
+ ## Stage 3 — Build, test, and sign your mandate
54
+
55
+ I'll write the permission contracts that bound your agent, prove in plain English what each one permits and blocks against sample calls, deploy them, and walk you through signing to authorize. Author, verify, sign — one step.
56
+
57
+ Permission contracts live in `mandates/`. The user authors, reviews, and owns them. For examples by protocol and chain, see `examples/permissions/`.
58
+
59
+ **Prerequisite — Foundry:** `forge build` requires the Foundry toolchain. If `forge` is not found, install it:
60
+ ```bash
61
+ curl -L https://foundry.paradigm.xyz | bash # then restart shell
62
+ foundryup
63
+ ```
64
+
65
+ **Approve coverage — mandatory:** ERC-20 `approve()` calls are NOT covered by supply or deposit permission contracts. If your strategy approves a token before supplying, you MUST deploy a separate bounded-approve permission that covers that specific `(token, spender, maxAmount)` combination, and authorize it alongside the supply permission. An agent that calls `approve()` without a matching permission will be rejected by the kernel.
66
+
67
+ **Batching:** if a strategy tick needs to approve before supplying, build both calls into a single dispatch array — `[approveCall, supplyCall]` — not two separate ticks. Splitting them wastes a tick and the approval sits exposed until the next run.
68
+
69
+ ```bash
70
+ forge build
71
+ sailor mandate deploy --contract <Name> --sma <SMA> # deploy only — do NOT --attach yet
72
+ ```
73
+
74
+ **Constructor args:** quoting rules differ by shell.
75
+
76
+ Bash / Git Bash:
77
+ ```bash
78
+ sailor mandate deploy --contract <Name> --args '["0xToken","1000000"]' --sma <SMA>
79
+ ```
80
+
81
+ PowerShell — use escaped inner quotes inside single quotes:
82
+ ```powershell
83
+ sailor mandate deploy --contract <Name> --args '[\"0xToken\",\"1000000\"]' --sma <SMA>
84
+ ```
85
+
86
+ Any shell — `--args-file` avoids quoting entirely:
87
+ ```json
88
+ ["0xToken", "1000000"]
89
+ ```
90
+ ```bash
91
+ sailor mandate deploy --contract <Name> --args-file args.json --sma <SMA>
92
+ ```
93
+
94
+ Before AUTHORIZING (attaching) the permission, BACK the plain-English claims with an actual on-chain probe. `evaluate()` lives on the deployed contract, so deploy first, then — before the irreversible authorization — generate sample calls from the user's stated strategy (ones the permission MUST accept and ones it MUST reject) and run them through `sailor mandate simulate`. This is an off-chain `eth_call` (no gas, no signing) that reports what the permission's `evaluate()` returns for each call, and flags any target with no contract code (a wrong or wrong-chain address):
95
+
96
+ ```bash
97
+ # one call inline, or a batch via JSON
98
+ sailor mandate simulate --address <PermissionOrName> --sma <SMA> \
99
+ --target <addr> --calldata <hex> --expect pass
100
+ sailor mandate simulate --address <PermissionOrName> --sma <SMA> --calls calls.json
101
+ ```
102
+
103
+ `calls.json` is an array of `{ target, calldata, value?, expect: "pass"|"fail", label }`. A mismatch between `expect` and the actual result exits non-zero — do not authorize until every sample matches. Simulate proves what the permission DOES; it does not guarantee it is correct.
104
+
105
+ ```bash
106
+ sailor mandate attach --address <PermissionOrName> --sma <SMA> # authorize, once simulate is clean
107
+ ```
108
+
109
+ Registration requires the owner to sign in the browser. If the wrong wallet is connected, the CLI rejects it.
110
+
111
+ ## Stage 4 — Run
112
+
113
+ Your agent starts executing within its mandate — locally on a schedule or via GitHub Actions. No per-transaction confirmation. The mandate is the authorization.
114
+
115
+ ```bash
116
+ sailor run # local, continuous
117
+ sailor run --once # single tick — confirm it works before automating
118
+ ```
119
+
120
+ For GitHub Actions:
121
+
122
+ 1. Run `sailor keys export-ci` — copies your encrypted agent wallet to `ci-keystore.json` in the project root and adds it to `.gitignore` as an allowed file. The keystore is geth v3 encrypted; the raw private key is never exposed.
123
+ 2. Commit the required files. CI needs these non-secret files to be in the repo:
124
+ ```bash
125
+ npm install # generate package-lock.json if it doesn't exist
126
+ git add ci-keystore.json package-lock.json .sail/account.json .sail/config.json .sail/mandate.json
127
+ git commit -m "chore: add CI keystore and sail state" && git push
128
+ ```
129
+ `package-lock.json` is required by `npm ci` (used in the workflow). `.sail/account.json`, `.sail/config.json`, and `.sail/mandate.json` contain only public addresses and flags — no secrets. The `.gitignore` already has `!` exceptions for all of these.
130
+ 3. Add two secrets in GitHub (Settings → Secrets → Actions):
131
+ - `SAIL_PASSPHRASE` — the passphrase that encrypts your agent wallet
132
+ - `RPC_URL` — your RPC endpoint
133
+ 4. Install the `gh` CLI — required to manage the workflow from the terminal (trigger runs, check logs, add secrets without opening the browser):
134
+ - macOS: `brew install gh`
135
+ - Windows: `winget install --id GitHub.cli` or `scoop install gh`
136
+ - Linux: see https://github.com/cli/cli/blob/trunk/docs/install_linux.md
137
+ Then authenticate with the `workflow` scope:
138
+ ```bash
139
+ gh auth login --scopes workflow
140
+ ```
141
+ The `workflow` scope is required — without it, `gh` cannot trigger or inspect Actions runs. Verify with:
142
+ ```bash
143
+ gh auth status # confirm workflow scope is listed
144
+ gh workflow run agent-tick.yml # manual trigger
145
+ gh run list --workflow agent-tick.yml # check run history
146
+ ```
147
+
148
+ The scaffolded workflow at `.github/workflows/agent-tick.yml` picks up `ci-keystore.json`, unlocks it with `SAIL_PASSPHRASE`, and runs on the configured schedule. No private key ever appears in the workflow or in secrets.
149
+
150
+ ## Stage 5 — Extend
151
+
152
+ I can set up notifications (Telegram, email, or other) for runs and transactions, and build you a custom dashboard tailored to your strategy — a price chart and portfolio view for a trading agent, health-factor and yield for a lending agent.
153
+
154
+ These are things the coding assistant builds on request — not Sailor features. Raise them once the agent is live; build on request.
155
+
156
+ ## Signing (for custom runners)
157
+
158
+ Use `buildDispatchSignature` from `@sail.money/sdk` — it reads the on-chain `DISPATCH_TYPEHASH` and builds the correct typed data. Never hand-roll the EIP-712 struct or hardcode the dispatch model.
159
+
160
+ ## What NOT to do
161
+
162
+ - Do not present the welcome and immediately launch the UI — wait for the second "start"
163
+ - Do not describe, mention, or present any code in `src/` or `examples/` as the user's strategy — treat Stage 2 as a blank slate; ask what they want
164
+ - Do not ask a running agent to confirm individual dispatches within its mandate
165
+ - Do not put an owner key in the terminal — owner signing is browser-only
166
+ - Do not hand-roll dispatch EIP-712 signatures — use `buildDispatchSignature`
167
+ - Do not hardcode the dispatch model — detect it on-chain
168
+ - Do not present example permissions as audited or as a supported menu
169
+ - Do not commit `SAIL_PASSPHRASE` or private keys
170
+ - Do not write a supply or deposit permission without also deploying a bounded-approve permission for each token the agent will approve — approve calls have no mandate coverage otherwise
171
+ - Do not pass `--args` inline JSON from PowerShell — use `--args-file` instead
@@ -0,0 +1,16 @@
1
+ # Sail Protocol Agent
2
+
3
+ A blank Sail Protocol agent project. Open this folder in your AI coding assistant and say:
4
+
5
+ > start
6
+
7
+ Your assistant will walk you through every step — chain selection, SMA deployment, strategy design, mandate authoring, and automation.
8
+
9
+ ## Project layout
10
+
11
+ - `src/agent.ts` — your agent's tick loop (implement your strategy here)
12
+ - `src/mandate.ts` — your strategy parameters and contract addresses
13
+ - `mandates/` — Foundry workspace for your IPermission contracts
14
+ - `examples/dca/` — worked reference: DCA (USDC→WETH) on Base via Uniswap V3
15
+ - `examples/permissions/` — protocol-specific permission examples
16
+ - `.sail/` — local project state (keys, account, activity log)
@@ -0,0 +1,16 @@
1
+ # DCA reference example
2
+
3
+ **Reference only — not your strategy.** This shows a complete worked implementation of a dollar-cost-averaging agent (USDC → WETH via Uniswap V3 on Base). Consult it for patterns when authoring your own strategy in `src/`.
4
+
5
+ ## What it shows
6
+
7
+ | File | Purpose |
8
+ |---|---|
9
+ | `mandate.ts` | Token addresses, swap parameters, and contract addresses for Base mainnet |
10
+ | `agent.ts` | Full tick loop: check balance → approve if needed → quote → swap with slippage protection |
11
+
12
+ ## How to use it
13
+
14
+ In Stage 2 (strategy), when you describe what you want, your assistant will adapt these patterns for your protocol, chain, and parameters — not copy them as-is.
15
+
16
+ The on-chain permission contract that authorizes these dispatches must be authored separately (Stage 3). The agent code here is only the intent-building side; the mandate enforces the on-chain bounds.