@dev.sail.money/sailor 0.0.2 → 0.1.0-local

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 (211) hide show
  1. package/AGENTS.md +140 -111
  2. package/LICENSE +21 -21
  3. package/README.md +430 -337
  4. package/docs/PERMISSION_MODEL.md +93 -93
  5. package/examples/permissions/BoundedApproveAndCallBatch.sol +179 -0
  6. package/examples/permissions/BoundedBet_Limitless_Base.sol +97 -96
  7. package/examples/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +94 -94
  8. package/examples/permissions/BoundedPerp_GMXv2_Arbitrum.sol +154 -143
  9. package/examples/permissions/BoundedStake_Venice_Base.sol +85 -0
  10. package/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +82 -0
  11. package/examples/permissions/BoundedSwap_UniswapV3_Base.sol +116 -113
  12. package/examples/permissions/BoundedSwap_UniswapV4_Unichain.sol +150 -144
  13. package/examples/permissions/BoundedTransfer_ERC20_Ethereum.sol +73 -73
  14. package/examples/permissions/BoundedVault_ERC4626_Base.sol +97 -0
  15. package/examples/permissions/README.md +79 -52
  16. package/examples/permissions/SailCalldata.sol +118 -0
  17. package/examples/permissions/foundry.toml +10 -10
  18. package/examples/permissions/interfaces/IBatchPermission.sol +38 -0
  19. package/examples/permissions/interfaces/IPermission.sol +18 -18
  20. package/package.json +45 -39
  21. package/packages/cli/README.md +34 -34
  22. package/packages/cli/dist/index.cjs +4571 -2944
  23. package/packages/cli/dist/server.cjs +1252 -2010
  24. package/packages/sdk/README.md +65 -65
  25. package/packages/sdk/dist/chains.d.ts +12 -0
  26. package/packages/sdk/dist/chains.d.ts.map +1 -0
  27. package/packages/sdk/dist/chains.js +94 -0
  28. package/packages/sdk/dist/chains.js.map +1 -0
  29. package/packages/sdk/dist/deployments.d.ts +14 -7
  30. package/packages/sdk/dist/deployments.d.ts.map +1 -1
  31. package/packages/sdk/dist/deployments.js +132 -141
  32. package/packages/sdk/dist/deployments.js.map +1 -1
  33. package/packages/sdk/dist/index.d.ts +3 -2
  34. package/packages/sdk/dist/index.d.ts.map +1 -1
  35. package/packages/sdk/dist/index.js +3 -2
  36. package/packages/sdk/dist/index.js.map +1 -1
  37. package/packages/sdk/dist/intelligence.d.ts +1 -1
  38. package/packages/sdk/dist/intelligence.js +1 -1
  39. package/packages/sdk/dist/lifi.d.ts +17 -0
  40. package/packages/sdk/dist/lifi.d.ts.map +1 -1
  41. package/packages/sdk/dist/lifi.js +24 -0
  42. package/packages/sdk/dist/lifi.js.map +1 -1
  43. package/packages/sdk/dist/safe.d.ts +83 -0
  44. package/packages/sdk/dist/safe.d.ts.map +1 -1
  45. package/packages/sdk/dist/safe.js +92 -1
  46. package/packages/sdk/dist/safe.js.map +1 -1
  47. package/packages/sdk/dist/templates/ammLiquidity.d.ts +24 -11
  48. package/packages/sdk/dist/templates/ammLiquidity.d.ts.map +1 -1
  49. package/packages/sdk/dist/templates/ammLiquidity.js +39 -31
  50. package/packages/sdk/dist/templates/ammLiquidity.js.map +1 -1
  51. package/packages/sdk/dist/templates/approveAndCallBatch.d.ts +24 -10
  52. package/packages/sdk/dist/templates/approveAndCallBatch.d.ts.map +1 -1
  53. package/packages/sdk/dist/templates/approveAndCallBatch.js +36 -23
  54. package/packages/sdk/dist/templates/approveAndCallBatch.js.map +1 -1
  55. package/packages/sdk/dist/templates/boundedBorrow.d.ts +19 -9
  56. package/packages/sdk/dist/templates/boundedBorrow.d.ts.map +1 -1
  57. package/packages/sdk/dist/templates/boundedBorrow.js +28 -19
  58. package/packages/sdk/dist/templates/boundedBorrow.js.map +1 -1
  59. package/packages/sdk/dist/templates/boundedSwap.d.ts +19 -9
  60. package/packages/sdk/dist/templates/boundedSwap.d.ts.map +1 -1
  61. package/packages/sdk/dist/templates/boundedSwap.js +30 -20
  62. package/packages/sdk/dist/templates/boundedSwap.js.map +1 -1
  63. package/packages/sdk/dist/templates/defiBundle.d.ts +35 -9
  64. package/packages/sdk/dist/templates/defiBundle.d.ts.map +1 -1
  65. package/packages/sdk/dist/templates/defiBundle.js +84 -22
  66. package/packages/sdk/dist/templates/defiBundle.js.map +1 -1
  67. package/packages/sdk/dist/templates/pendle.d.ts +23 -8
  68. package/packages/sdk/dist/templates/pendle.d.ts.map +1 -1
  69. package/packages/sdk/dist/templates/pendle.js +34 -14
  70. package/packages/sdk/dist/templates/pendle.js.map +1 -1
  71. package/packages/sdk/dist/templates/transferTarget.d.ts +11 -3
  72. package/packages/sdk/dist/templates/transferTarget.d.ts.map +1 -1
  73. package/packages/sdk/dist/templates/transferTarget.js +14 -7
  74. package/packages/sdk/dist/templates/transferTarget.js.map +1 -1
  75. package/packages/sdk/dist/types.d.ts +19 -1
  76. package/packages/sdk/dist/types.d.ts.map +1 -1
  77. package/packages/sdk/package.json +80 -52
  78. package/packages/ui/dist/assets/{add-DaJhwIBV.js → add-BxpXfVWe.js} +1 -1
  79. package/packages/ui/dist/assets/{all-wallets-BUxsqWXi.js → all-wallets-BKTn_sWK.js} +1 -1
  80. package/packages/ui/dist/assets/{app-store-DkltwTqE.js → app-store-CfuKbwxR.js} +1 -1
  81. package/packages/ui/dist/assets/{apple-owVOeaIT.js → apple-BKSBbNYg.js} +1 -1
  82. package/packages/ui/dist/assets/{arrow-bottom-D2mmNJve.js → arrow-bottom-D4bG6gZi.js} +1 -1
  83. package/packages/ui/dist/assets/{arrow-bottom-circle-CbNYijx-.js → arrow-bottom-circle-BNTs1p0T.js} +1 -1
  84. package/packages/ui/dist/assets/{arrow-left-DJB61s4C.js → arrow-left-2uee3vYv.js} +1 -1
  85. package/packages/ui/dist/assets/{arrow-right-BBrsQ9R4.js → arrow-right-BktjMV6h.js} +1 -1
  86. package/packages/ui/dist/assets/{arrow-top-Cil6bOc8.js → arrow-top-Izu28fX4.js} +1 -1
  87. package/packages/ui/dist/assets/{bank-CbwEmRo3.js → bank-USBaAyFM.js} +1 -1
  88. package/packages/ui/dist/assets/{basic-CLNfjw3m.js → basic-C_9KjTEH.js} +1 -1
  89. package/packages/ui/dist/assets/{browser-B5TtF4Pb.js → browser-DAEMAKV7.js} +1 -1
  90. package/packages/ui/dist/assets/{card-CO7BVB-C.js → card-DT8yDkKN.js} +1 -1
  91. package/packages/ui/dist/assets/{ccip-2W7K3_J3.js → ccip-CkqfGSxX.js} +1 -1
  92. package/packages/ui/dist/assets/{checkmark-BEtSHq9m.js → checkmark-CsgdEXFj.js} +1 -1
  93. package/packages/ui/dist/assets/{checkmark-bold-D9xGHzPE.js → checkmark-bold-D2gjOQo2.js} +1 -1
  94. package/packages/ui/dist/assets/{chevron-bottom-BDztht6i.js → chevron-bottom-tprFynYV.js} +1 -1
  95. package/packages/ui/dist/assets/{chevron-left-EV4GFNbc.js → chevron-left-D2Zj1gNB.js} +1 -1
  96. package/packages/ui/dist/assets/{chevron-right-B4_bB9oR.js → chevron-right-D1rRuAVe.js} +1 -1
  97. package/packages/ui/dist/assets/{chevron-top-D54xPNzF.js → chevron-top-24dL1mbL.js} +1 -1
  98. package/packages/ui/dist/assets/{chrome-store-DYUpAJJq.js → chrome-store-Vy-5niYX.js} +1 -1
  99. package/packages/ui/dist/assets/{clock-Ca1T1Soz.js → clock-qBjLnVdJ.js} +1 -1
  100. package/packages/ui/dist/assets/{close-BZqWjurK.js → close-DARDwgcu.js} +1 -1
  101. package/packages/ui/dist/assets/{coinPlaceholder-e6fl2XDo.js → coinPlaceholder-BvpIbPlD.js} +1 -1
  102. package/packages/ui/dist/assets/{compass-DCLC7zIh.js → compass-BMTO0ayt.js} +1 -1
  103. package/packages/ui/dist/assets/{copy-Th2AaD-O.js → copy-PaXeRHza.js} +1 -1
  104. package/packages/ui/dist/assets/{core-Ckx_cyuH.js → core-BFnStQd-.js} +3 -3
  105. package/packages/ui/dist/assets/cursor-BDvw-B17.js +3 -0
  106. package/packages/ui/dist/assets/{cursor-transparent-BKHeABKB.js → cursor-transparent-BEMdi-8q.js} +1 -1
  107. package/packages/ui/dist/assets/{desktop-CBjY8t6F.js → desktop-CfuLLThw.js} +1 -1
  108. package/packages/ui/dist/assets/{disconnect-DbSs2cli.js → disconnect-DhwgJMiR.js} +1 -1
  109. package/packages/ui/dist/assets/{discord-ZlLOAUkM.js → discord-po8qoN1s.js} +1 -1
  110. package/packages/ui/dist/assets/{etherscan-CKUrqWYN.js → etherscan-BEsz0_yx.js} +1 -1
  111. package/packages/ui/dist/assets/{events-CiKP71cK.js → events-Bz33Unzu.js} +1 -1
  112. package/packages/ui/dist/assets/{exclamation-triangle-DA1QzFiO.js → exclamation-triangle-7CjTAGOQ.js} +1 -1
  113. package/packages/ui/dist/assets/{extension-BVJkmvpJ.js → extension-CmxjEWEt.js} +1 -1
  114. package/packages/ui/dist/assets/{external-link-D_bsR7B2.js → external-link-CmQ--bNS.js} +1 -1
  115. package/packages/ui/dist/assets/{facebook-CmFmhojx.js → facebook-CIBn9b65.js} +1 -1
  116. package/packages/ui/dist/assets/{fallback-Ofl6uSnB.js → fallback-DATyrQlb.js} +1 -1
  117. package/packages/ui/dist/assets/{farcaster-Co-M3Ss8.js → farcaster-OJ3Jasxg.js} +1 -1
  118. package/packages/ui/dist/assets/{filters-B1WwNaFU.js → filters-D4x09zeL.js} +1 -1
  119. package/packages/ui/dist/assets/{github-CP4fP6gn.js → github-ZlIuMArp.js} +1 -1
  120. package/packages/ui/dist/assets/{google-CsOIXJ6V.js → google-Gwg85sfv.js} +1 -1
  121. package/packages/ui/dist/assets/{help-circle-DiMkomdF.js → help-circle-D1uOWYcX.js} +1 -1
  122. package/packages/ui/dist/assets/{id-lmscL5LX.js → id-C0-5UdYk.js} +1 -1
  123. package/packages/ui/dist/assets/{image-B-ubJrY5.js → image-D_DUsv8-.js} +1 -1
  124. package/packages/ui/dist/assets/{index-CZR1Qjhs.js → index-BCzex_R6.js} +1 -1
  125. package/packages/ui/dist/assets/index-BUhrHLpY.js +1775 -0
  126. package/packages/ui/dist/assets/index-Cq02kQmy.css +1 -0
  127. package/packages/ui/dist/assets/{index-BaukYv-x.js → index-CrYzBWfD.js} +1 -1
  128. package/packages/ui/dist/assets/{index-CF0KMmke.js → index-DdbJhIdl.js} +3 -3
  129. package/packages/ui/dist/assets/{index-DVgfCzCo.js → index-DiojfeVM.js} +1 -1
  130. package/packages/ui/dist/assets/{index-Dbh5V1Z0.js → index-izd7vu_r.js} +1 -1
  131. package/packages/ui/dist/assets/{index.es-C78cE5SI.js → index.es-DdkHhQAj.js} +4 -4
  132. package/packages/ui/dist/assets/{info-Cqg57EVo.js → info-CiRd_kEG.js} +1 -1
  133. package/packages/ui/dist/assets/{info-circle-DkeSWNKV.js → info-circle-ypxjqarK.js} +1 -1
  134. package/packages/ui/dist/assets/{lightbulb-DNlO4qKh.js → lightbulb-B-pxLxd8.js} +1 -1
  135. package/packages/ui/dist/assets/{mail-kVQ8Jb9Y.js → mail-BYmicuVZ.js} +1 -1
  136. package/packages/ui/dist/assets/{metamask-sdk-CBalSvz7.js → metamask-sdk-Ccl6DG7Q.js} +1 -1
  137. package/packages/ui/dist/assets/{mobile-BEteuhF7.js → mobile-CtP5PqVT.js} +1 -1
  138. package/packages/ui/dist/assets/{more-DBWmXQli.js → more-6C2733we.js} +1 -1
  139. package/packages/ui/dist/assets/{network-placeholder-Dg1uUHiL.js → network-placeholder-CdhxMzqd.js} +1 -1
  140. package/packages/ui/dist/assets/{nftPlaceholder-i3AHSiD9.js → nftPlaceholder-DVmTWEAY.js} +1 -1
  141. package/packages/ui/dist/assets/{off-BtMm0fi2.js → off-DNYLughs.js} +1 -1
  142. package/packages/ui/dist/assets/{parseSignature-Cb5FlWWg.js → parseSignature-Dq2B5Bu3.js} +1 -1
  143. package/packages/ui/dist/assets/{play-store-iKKkXa6a.js → play-store-D7Qut5ta.js} +1 -1
  144. package/packages/ui/dist/assets/{plus-CA5NaRtb.js → plus-kqMyjt3q.js} +1 -1
  145. package/packages/ui/dist/assets/{qr-code-D2kiqR7h.js → qr-code-DiUCWRbz.js} +1 -1
  146. package/packages/ui/dist/assets/{recycle-horizontal-Dcme7R03.js → recycle-horizontal-Boe3XiS-.js} +1 -1
  147. package/packages/ui/dist/assets/{refresh-Dega3sDp.js → refresh-CrBgBQYO.js} +1 -1
  148. package/packages/ui/dist/assets/{reown-logo-xNkksyWJ.js → reown-logo-CFZCCHSx.js} +1 -1
  149. package/packages/ui/dist/assets/{search-HYl7NO8x.js → search-ChTDrghU.js} +1 -1
  150. package/packages/ui/dist/assets/{secp256k1-Cxd6_SiH.js → secp256k1-DAV5Q_FR.js} +1 -1
  151. package/packages/ui/dist/assets/{send-CJU8CUAo.js → send-DLFbBFe1.js} +1 -1
  152. package/packages/ui/dist/assets/{swapHorizontal-IMUKiUre.js → swapHorizontal-BEs3emfG.js} +1 -1
  153. package/packages/ui/dist/assets/{swapHorizontalBold-CNYnNJ9-.js → swapHorizontalBold-CC-Hfa7W.js} +1 -1
  154. package/packages/ui/dist/assets/{swapHorizontalMedium-B9VxEYsT.js → swapHorizontalMedium-BmR0H8DC.js} +1 -1
  155. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-Dz33l_Jh.js → swapHorizontalRoundedBold-BdP5NGIH.js} +1 -1
  156. package/packages/ui/dist/assets/{swapVertical-CHUmjVJ0.js → swapVertical-CPrGEJPY.js} +1 -1
  157. package/packages/ui/dist/assets/{telegram-kl9S2mbU.js → telegram-CxNoZ80Q.js} +1 -1
  158. package/packages/ui/dist/assets/{three-dots-U5lhA1Am.js → three-dots-BRa6SBpL.js} +1 -1
  159. package/packages/ui/dist/assets/{twitch-KTEUWXEp.js → twitch-BC338bG5.js} +1 -1
  160. package/packages/ui/dist/assets/{twitterIcon-BHiq8mRg.js → twitterIcon-BGZmt2i9.js} +1 -1
  161. package/packages/ui/dist/assets/{verify-CfN-BXNd.js → verify-CEstW0zw.js} +1 -1
  162. package/packages/ui/dist/assets/{verify-filled-DwZccetj.js → verify-filled-OkZb0weU.js} +1 -1
  163. package/packages/ui/dist/assets/{w3m-modal-CS-PFqPE.js → w3m-modal-pS09ECwE.js} +1 -1
  164. package/packages/ui/dist/assets/{wallet-DVlGkhOY.js → wallet-BXVKCgC9.js} +1 -1
  165. package/packages/ui/dist/assets/{wallet-placeholder-CvR_iEWX.js → wallet-placeholder-C_kNhB1c.js} +1 -1
  166. package/packages/ui/dist/assets/{walletconnect-8pZBDvVI.js → walletconnect-CRKIuUHH.js} +1 -1
  167. package/packages/ui/dist/assets/{warning-circle-ylLEE0Yp.js → warning-circle-DB2NnwlJ.js} +1 -1
  168. package/packages/ui/dist/assets/{x-C_TBsTMj.js → x-DT4RmwL5.js} +1 -1
  169. package/packages/ui/dist/index.html +14 -14
  170. package/scripts/check-docs.mjs +262 -262
  171. package/scripts/check-init.mjs +108 -109
  172. package/scripts/postinstall.js +81 -366
  173. package/templates/custom-mandate/.sail/contracts/interfaces/IPermission.sol +18 -18
  174. package/templates/custom-mandate/README.md +116 -85
  175. package/templates/custom-mandate/foundry.toml +8 -8
  176. package/templates/custom-mandate/mandates/BoundedCallPermission.sol +41 -35
  177. package/templates/custom-mandate/mandates/README.md +16 -16
  178. package/templates/custom-mandate/mandates/SailCalldata.sol +118 -0
  179. package/templates/{dca-rebalancer → default}/.cursor/rules +25 -25
  180. package/templates/default/.env.example +20 -0
  181. package/templates/{dca-rebalancer → default}/.github/workflows/agent-tick.yml +33 -32
  182. package/templates/{dca-rebalancer → default}/.sail/README.md +13 -13
  183. package/templates/{dca-rebalancer → default}/.sail/config.json +10 -10
  184. package/templates/default/AGENTS.md +171 -0
  185. package/templates/{dca-rebalancer → default}/CLAUDE.md +2 -2
  186. package/templates/default/README.md +16 -0
  187. package/templates/{dca-rebalancer → default}/_gitignore +13 -13
  188. package/templates/{dca-rebalancer → default}/docs/PERMISSION_MODEL.md +93 -93
  189. package/templates/default/examples/dca/README.md +16 -0
  190. package/templates/default/examples/dca/agent.ts +174 -0
  191. package/templates/{dca-rebalancer/src → default/examples/dca}/mandate.ts +45 -67
  192. package/templates/{dca-rebalancer → default}/package.json +17 -17
  193. package/templates/default/src/agent.ts +37 -0
  194. package/templates/default/src/config.ts +24 -0
  195. package/templates/default/src/mandate.ts +22 -0
  196. package/templates/default/tsconfig.json +17 -0
  197. package/templates/{dca-rebalancer → default}/ui/README.md +3 -3
  198. package/templates/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +84 -84
  199. package/templates/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +97 -97
  200. package/templates/lifi-permissions/README.md +53 -53
  201. package/packages/ui/dist/assets/cursor-DV7rOqbJ.js +0 -3
  202. package/packages/ui/dist/assets/index-CKxgNxS9.css +0 -1
  203. package/packages/ui/dist/assets/index-Q2Yai4Fe.js +0 -2103
  204. package/templates/dca-rebalancer/.env.example +0 -6
  205. package/templates/dca-rebalancer/AGENTS.md +0 -246
  206. package/templates/dca-rebalancer/AGENT_PLAYBOOK.md +0 -110
  207. package/templates/dca-rebalancer/README.md +0 -16
  208. package/templates/dca-rebalancer/src/agent.ts +0 -253
  209. package/templates/dca-rebalancer/src/config.ts +0 -27
  210. package/templates/dca-rebalancer/tsconfig.json +0 -8
  211. /package/templates/{dca-rebalancer → default}/.sail/.gitkeep +0 -0
@@ -1,73 +1,73 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
3
-
4
- // ─────────────────────────────────────────────────────────────────────────────
5
- // Protocol : ERC-20
6
- // Version : Standard ERC-20 (version-agnostic)
7
- // Chain : Ethereum mainnet (works on any EVM — the most general example)
8
- //
9
- // ENFORCED ON-CHAIN (via kernel evaluate() on every dispatch):
10
- // transfer(address to, uint256 amount)
11
- // • target must be in ALLOWED_TOKENS
12
- // • recipient (to) must be in ALLOWED_RECIPIENTS
13
- // • amount ≤ MAX_AMOUNT_PER_TRANSFER
14
- //
15
- // NOT ENFORCED (agent code — can change without a new contract):
16
- // • transfer frequency / timing
17
- // • choice of token within ALLOWED_TOKENS
18
- // • choice of recipient within ALLOWED_RECIPIENTS
19
- //
20
- // VERIFY BEFORE USE:
21
- // • Selector 0xa9059cbb = transfer(address,uint256) — universally standard.
22
- // • ALLOWED_TOKENS prevents the agent from transferring tokens not in the set.
23
- // If a protocol uses non-standard transfer methods (e.g. transferFrom or
24
- // proprietary hooks), add separate selector entries.
25
- // • MAX_AMOUNT_PER_TRANSFER is denominated in the token's base units.
26
- // Different tokens have different decimals (USDC = 6, WETH = 18).
27
- // Set one permission per token if amounts differ across tokens.
28
- // ─────────────────────────────────────────────────────────────────────────────
29
-
30
- import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
31
-
32
- contract BoundedTransfer_ERC20_Ethereum is IPermission {
33
- bytes32 private constant DISCRIMINATOR = keccak256("BoundedTransfer_ERC20_Ethereum");
34
-
35
- mapping(address => bool) public isAllowedToken;
36
- mapping(address => bool) public isAllowedRecipient;
37
- uint256 public immutable MAX_AMOUNT_PER_TRANSFER;
38
-
39
- // transfer(address,uint256)
40
- bytes4 private constant SEL_TRANSFER = 0xa9059cbb;
41
-
42
- /// @param allowedTokens ERC-20 contracts the agent may transfer from
43
- /// @param allowedRecipients Addresses the agent may send to
44
- /// @param maxAmountPerTransfer Per-call amount cap (in token base units)
45
- constructor(
46
- address[] memory allowedTokens,
47
- address[] memory allowedRecipients,
48
- uint256 maxAmountPerTransfer
49
- ) {
50
- MAX_AMOUNT_PER_TRANSFER = maxAmountPerTransfer;
51
- for (uint256 i = 0; i < allowedTokens.length; i++) {
52
- isAllowedToken[allowedTokens[i]] = true;
53
- }
54
- for (uint256 i = 0; i < allowedRecipients.length; i++) {
55
- isAllowedRecipient[allowedRecipients[i]] = true;
56
- }
57
- }
58
-
59
- function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
60
- if (!isAllowedToken[ctx.target]) return false;
61
- if (ctx.selector != SEL_TRANSFER) return false;
62
- if (txData.length < 4 + 2 * 32) return false;
63
-
64
- (address to, uint256 amount) = abi.decode(txData[4:], (address, uint256));
65
-
66
- if (!isAllowedRecipient[to]) return false;
67
- if (amount > MAX_AMOUNT_PER_TRANSFER) return false;
68
-
69
- return true;
70
- }
71
-
72
- function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
73
- }
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // Protocol : ERC-20
6
+ // Version : Standard ERC-20 (version-agnostic)
7
+ // Chain : Ethereum mainnet (works on any EVM — the most general example)
8
+ //
9
+ // ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
10
+ // transfer(address to,uint256 amount) selector 0xa9059cbb
11
+ // • target must be in ALLOWED_TOKENS
12
+ // • recipient (to) must be in ALLOWED_RECIPIENTS
13
+ // • amount ≤ MAX_AMOUNT_PER_TRANSFER
14
+ //
15
+ // AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
16
+ // • transfer frequency / timing
17
+ // • choice of token within ALLOWED_TOKENS
18
+ // • choice of recipient within ALLOWED_RECIPIENTS
19
+ //
20
+ // VERIFY BEFORE USE:
21
+ // • Selector 0xa9059cbb = transfer(address,uint256) — universally standard.
22
+ // • ALLOWED_TOKENS prevents the agent from transferring tokens not in the set.
23
+ // If a protocol uses non-standard transfer methods (e.g. transferFrom or
24
+ // proprietary hooks), add separate selector entries.
25
+ // • MAX_AMOUNT_PER_TRANSFER is denominated in the token's base units.
26
+ // Different tokens have different decimals (USDC = 6, WETH = 18).
27
+ // Set one permission per token if amounts differ across tokens.
28
+ // ─────────────────────────────────────────────────────────────────────────────
29
+
30
+ import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
31
+
32
+ contract BoundedTransfer_ERC20_Ethereum is IPermission {
33
+ bytes32 private constant DISCRIMINATOR = keccak256("BoundedTransfer_ERC20_Ethereum");
34
+
35
+ mapping(address => bool) public isAllowedToken;
36
+ mapping(address => bool) public isAllowedRecipient;
37
+ uint256 public immutable MAX_AMOUNT_PER_TRANSFER;
38
+
39
+ // transfer(address,uint256)
40
+ bytes4 private constant SEL_TRANSFER = 0xa9059cbb;
41
+
42
+ /// @param allowedTokens ERC-20 contracts the agent may transfer from
43
+ /// @param allowedRecipients Addresses the agent may send to
44
+ /// @param maxAmountPerTransfer Per-call amount cap (in token base units)
45
+ constructor(
46
+ address[] memory allowedTokens,
47
+ address[] memory allowedRecipients,
48
+ uint256 maxAmountPerTransfer
49
+ ) {
50
+ MAX_AMOUNT_PER_TRANSFER = maxAmountPerTransfer;
51
+ for (uint256 i = 0; i < allowedTokens.length; i++) {
52
+ isAllowedToken[allowedTokens[i]] = true;
53
+ }
54
+ for (uint256 i = 0; i < allowedRecipients.length; i++) {
55
+ isAllowedRecipient[allowedRecipients[i]] = true;
56
+ }
57
+ }
58
+
59
+ function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
60
+ if (!isAllowedToken[ctx.target]) return false;
61
+ if (ctx.selector != SEL_TRANSFER) return false;
62
+ if (txData.length < 4 + 2 * 32) return false;
63
+
64
+ (address to, uint256 amount) = abi.decode(txData[4:], (address, uint256));
65
+
66
+ if (!isAllowedRecipient[to]) return false;
67
+ if (amount > MAX_AMOUNT_PER_TRANSFER) return false;
68
+
69
+ return true;
70
+ }
71
+
72
+ function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
73
+ }
@@ -0,0 +1,97 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // Protocol : ERC-4626 (tokenized vault standard)
6
+ // Version : Standard EIP-4626 interface (version-agnostic)
7
+ // Chain : Base mainnet (8453) — reference vault below; works on any EVM
8
+ // Target : Reference vault — Moonwell Flagship USDC (MetaMorpho, ERC-4626)
9
+ // 0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca (verified ERC-4626 on Base: asset()=USDC)
10
+ // Written against the STANDARD interface, so it generalizes to any 4626 vault.
11
+ //
12
+ // ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
13
+ // deposit(uint256 assets,address receiver) selector 0x6e553f65
14
+ // • target (the vault) must be in ALLOWED_VAULTS
15
+ // • assets ≤ MAX_DEPOSIT_ASSETS
16
+ // • receiver must equal ctx.account (the SMA — shares cannot be minted to another address)
17
+ // withdraw(uint256 assets,address receiver,address owner) selector 0xb460af94
18
+ // redeem(uint256 shares,address receiver,address owner) selector 0xba087652
19
+ // • target (the vault) must be in ALLOWED_VAULTS
20
+ // • receiver must equal ctx.account (assets cannot be sent elsewhere)
21
+ // • owner must equal ctx.account (cannot burn shares the SMA merely has allowance over)
22
+ //
23
+ // AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
24
+ // • Which vault within ALLOWED_VAULTS to use, and deposit/withdraw timing/cadence
25
+ // • Withdraw/redeem amount (only the receiver/owner are bound, not the size — the SMA can
26
+ // always withdraw its own position in full; add a cap here if your strategy needs one)
27
+ // • Underlying ASSET: enforced TRANSITIVELY via ALLOWED_VAULTS, not from calldata. A 4626
28
+ // vault's asset() is fixed at deploy; the deposit/withdraw calldata carries NO asset field,
29
+ // so the asset cannot be read from it. Allowlist only vaults whose asset() you have verified.
30
+ //
31
+ // VERIFY BEFORE USE:
32
+ // • Selectors are the EIP-4626 standard (verified via `cast sig`): deposit 0x6e553f65,
33
+ // withdraw 0xb460af94, redeem 0xba087652. (mint(uint256,address)=0x94bf804d is NOT gated
34
+ // here — add it if your agent mints shares by exact share count.)
35
+ // • Confirm each allowlisted vault actually implements ERC-4626 and its asset() is what you
36
+ // expect (e.g. on the reference vault, asset() == USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913).
37
+ // • A deposit also needs a prior ERC-20 approve of the vault — gate that with a separate
38
+ // approve permission (see BoundedApproveAndCallBatch.sol for the atomic approve→call→reset pattern).
39
+ // • MAX_DEPOSIT_ASSETS is in the asset's base units (USDC = 6 decimals).
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+
42
+ import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
43
+
44
+ contract BoundedVault_ERC4626_Base is IPermission {
45
+ bytes32 private constant DISCRIMINATOR = keccak256("BoundedVault_ERC4626_Base");
46
+
47
+ mapping(address => bool) public isAllowedVault;
48
+ uint256 public immutable MAX_DEPOSIT_ASSETS;
49
+
50
+ // EIP-4626 standard selectors
51
+ bytes4 private constant SEL_DEPOSIT = 0x6e553f65; // deposit(uint256,address)
52
+ bytes4 private constant SEL_WITHDRAW = 0xb460af94; // withdraw(uint256,address,address)
53
+ bytes4 private constant SEL_REDEEM = 0xba087652; // redeem(uint256,address,address)
54
+
55
+ /// @param allowedVaults ERC-4626 vaults the agent may deposit into / withdraw from
56
+ /// @param maxDepositAssets Per-deposit cap in the asset's base units (must be > 0)
57
+ constructor(address[] memory allowedVaults, uint256 maxDepositAssets) {
58
+ require(allowedVaults.length > 0, "empty vault allowlist");
59
+ require(maxDepositAssets > 0, "zero deposit cap");
60
+ MAX_DEPOSIT_ASSETS = maxDepositAssets;
61
+ for (uint256 i = 0; i < allowedVaults.length; i++) {
62
+ require(allowedVaults[i] != address(0), "zero vault address");
63
+ isAllowedVault[allowedVaults[i]] = true;
64
+ }
65
+ }
66
+
67
+ function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
68
+ if (!isAllowedVault[ctx.target]) return false;
69
+ if (txData.length < 4) return false;
70
+
71
+ // deposit(uint256 assets, address receiver)
72
+ if (ctx.selector == SEL_DEPOSIT) {
73
+ if (txData.length < 4 + 2 * 32) return false;
74
+ (uint256 assets, address receiver) = abi.decode(txData[4:], (uint256, address));
75
+ if (assets > MAX_DEPOSIT_ASSETS) return false;
76
+ if (receiver != ctx.account) return false; // shares must accrue to the SMA
77
+ return true;
78
+ }
79
+
80
+ // withdraw(uint256 assets, address receiver, address owner)
81
+ // redeem(uint256 shares, address receiver, address owner)
82
+ // Identical parameter layout: (uint256, address, address).
83
+ // Amount (assets/shares) is NOT bounded — the SMA can always exit its full position.
84
+ // Add a maxWithdrawAssets constructor param and check here if your strategy needs a cap.
85
+ if (ctx.selector == SEL_WITHDRAW || ctx.selector == SEL_REDEEM) {
86
+ if (txData.length < 4 + 3 * 32) return false;
87
+ (, address receiver, address owner) = abi.decode(txData[4:], (uint256, address, address));
88
+ if (receiver != ctx.account) return false; // assets must return to the SMA
89
+ if (owner != ctx.account) return false; // only the SMA's own shares may be burned
90
+ return true;
91
+ }
92
+
93
+ return false;
94
+ }
95
+
96
+ function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
97
+ }
@@ -1,52 +1,79 @@
1
- # Permission Examples
2
-
3
- These are example permission contracts for common DeFi protocols, maintained by Sailor.
4
-
5
- **They are not part of Sail Protocol. They are not audited by Sail. They are not a supported
6
- or exhaustive set.** The protocol accepts any contract implementing IPermission — these examples
7
- teach the bounding pattern for specific protocols so you (and your AI agent) can adapt them or
8
- write your own.
9
-
10
- ## Permissions are only as strong as the protocol is on-chain
11
-
12
- A permission is evaluated by the kernel on every dispatch — but it can only see what happens
13
- on-chain. For venues with off-chain order matching (e.g. Polymarket, Hyperliquid), a permission
14
- can constrain deposits, withdrawals, and sub-accounts, but NOT the orders your agent signs
15
- off-chain. Prefer fully on-chain venues — Uniswap, Aave, GMX, Synthetix, Limitless — where every
16
- action passes through the kernel and your bounds actually hold.
17
-
18
- ## Permissions are protocol- and version-specific
19
-
20
- Calldata differs by protocol and by version. A Uniswap V3 swap is a different decode than a
21
- Uniswap V4 swap. Use the example closest to YOUR exact protocol+version+chain, verify the decode
22
- against the protocol's real ABI, and confirm what it enforces before deploying.
23
-
24
- You own what you deploy.
25
-
26
- ---
27
-
28
- ## Examples
29
-
30
- | File | Protocol | Version | Chain | Status |
31
- |---|---|---|---|---|
32
- | `BoundedSwap_UniswapV3_Base.sol` | Uniswap Swap | V3 SwapRouter02 | Base | Full decode |
33
- | `BoundedSwap_UniswapV4_Unichain.sol` | Uniswap Swap | V4 Universal Router | Unichain | Partial decode — see header |
34
- | `BoundedBorrow_AaveV3_Arbitrum.sol` | Aave Borrow | V3 Pool | Arbitrum | Full decode — verify selector |
35
- | `BoundedTransfer_ERC20_Ethereum.sol` | ERC-20 Transfer | — | Ethereum (any EVM) | Full decode |
36
- | `BoundedPerp_GMXv2_Arbitrum.sol` | GMX Perpetuals | V2 ExchangeRouter | Arbitrum | Partial decode — see header |
37
- | `BoundedBet_Limitless_Base.sol` | Limitless Prediction | CTF Exchange | Base | UNVERIFIED ABI — see header |
38
-
39
- ## Using these examples
40
-
41
- Each file is self-contained and explains in its header exactly what is and is not enforced.
42
- Read the header before deploying.
43
-
44
- To compile:
45
- ```bash
46
- forge build
47
- ```
48
-
49
- To deploy within a Sailor project (copy the .sol file to `mandates/` first):
50
- ```bash
51
- sailor mandate deploy --contract <Name> --args '[...]' --attach --sma <SMA>
52
- ```
1
+ # Permission Examples
2
+
3
+ These are example permission contracts for common DeFi protocols, maintained by Sailor.
4
+
5
+ **They are not part of Sail Protocol. They are not audited by Sail. They are not a supported
6
+ or exhaustive set.** The protocol accepts any contract implementing IPermission — these examples
7
+ teach the bounding pattern for specific protocols so you (and your AI agent) can adapt them or
8
+ write your own.
9
+
10
+ ## Permissions are only as strong as the protocol is on-chain
11
+
12
+ A permission is evaluated by the kernel on every dispatch — but it can only see what happens
13
+ on-chain. For venues with off-chain order matching (e.g. Polymarket, Hyperliquid), a permission
14
+ can constrain deposits, withdrawals, and sub-accounts, but NOT the orders your agent signs
15
+ off-chain. Prefer fully on-chain venues — Uniswap, Aave, GMX, Synthetix, Limitless — where every
16
+ action passes through the kernel and your bounds actually hold.
17
+
18
+ ## Permissions are protocol- and version-specific
19
+
20
+ Calldata differs by protocol and by version. A Uniswap V3 swap is a different decode than a
21
+ Uniswap V4 swap. Use the example closest to YOUR exact protocol+version+chain, verify the decode
22
+ against the protocol's real ABI, and confirm what it enforces before deploying.
23
+
24
+ You own what you deploy.
25
+
26
+ ---
27
+
28
+ ## Examples
29
+
30
+ Each header has two blocks: **ENFORCES ON-CHAIN** (bounds the kernel checks in `evaluate()` on
31
+ every dispatch — these actually hold) and **AGENT-ENFORCED / NOT BOUNDED HERE** (left to your
32
+ off-chain agent code these do *not* hold on-chain). Read both before deploying.
33
+
34
+ | File | Protocol | Version | Chain | Status |
35
+ |---|---|---|---|---|
36
+ | `BoundedSwap_UniswapV3_Base.sol` | Uniswap Swap | V3 SwapRouter02 | Base | Full decode |
37
+ | `BoundedSwap_UniswapV4_Unichain.sol` | Uniswap Swap | V4 Universal Router | Unichain | Partial decode — see header |
38
+ | `BoundedBorrow_AaveV3_Arbitrum.sol` | Aave Borrow | V3 Pool | Arbitrum | Full decode |
39
+ | `BoundedSupply_AaveV3_Arbitrum.sol` | Aave Supply | V3 Pool | Arbitrum | Full decode |
40
+ | `BoundedVault_ERC4626_Base.sol` | ERC-4626 Vault deposit/withdraw | EIP-4626 standard | Base (any EVM) | Full decode |
41
+ | `BoundedStake_Venice_Base.sol` | Venice (VVV) staking | sVVV staking | Base | Full decode |
42
+ | `BoundedTransfer_ERC20_Ethereum.sol` | ERC-20 Transfer | — | Ethereum (any EVM) | Full decode |
43
+ | `BoundedPerp_GMXv2_Arbitrum.sol` | GMX Perpetuals | V2 ExchangeRouter | Arbitrum | Reference pattern — verify selector/struct/router against live GMX ABI (see header) |
44
+ | `BoundedBet_Limitless_Base.sol` | Limitless Prediction | CTF Exchange | Base | UNVERIFIED ABI — see header |
45
+ | `BoundedApproveAndCallBatch.sol` | Atomic approve→call→reset | Sail `IBatchPermission` | Any selective kernel | Full decode — batch-only (see note below) |
46
+
47
+ The `interfaces/` directory holds `IPermission.sol` (single-call permissions) and
48
+ `IBatchPermission.sol` (batch permissions — see the batch example).
49
+
50
+ ## Using these examples
51
+
52
+ Each file is self-contained and explains in its header exactly what is and is not enforced.
53
+ Read the header before deploying.
54
+
55
+ To compile:
56
+ ```bash
57
+ forge build
58
+ ```
59
+
60
+ To deploy within a Sailor project (copy the .sol file to `mandates/` first):
61
+ ```bash
62
+ sailor mandate deploy --contract <Name> --args '[...]' --attach --sma <SMA>
63
+ ```
64
+
65
+ ## Verify before you authorize
66
+
67
+ Prove a permission accepts the calls you want and rejects the ones you don't — before paying
68
+ registration gas — with `sailor mandate simulate` (off-chain `eth_call`, no gas, signs nothing):
69
+ ```bash
70
+ sailor mandate simulate --address <permission> --calls calls.json
71
+ ```
72
+ Every example here was checked this way (valid calls PASS, out-of-bounds calls FAIL).
73
+
74
+ **Batch permissions are different.** `BoundedApproveAndCallBatch.sol` implements `IBatchPermission`
75
+ and is gated by the kernel's `dispatchBatch`, not single `dispatch` — its single-call `evaluate()`
76
+ deliberately returns `false`. `sailor mandate simulate` only probes single `evaluate()`, so it
77
+ **cannot** verify a batch permission. Until simulate gains a batch mode, verify a batch permission
78
+ by calling its `evaluateBatch(calls, ctx)` view directly (e.g. `cast call`) with the call sequences
79
+ you expect to pass and fail — same fail-closed `eth_call` semantics, the correct entrypoint.
@@ -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
+ }
@@ -1,10 +1,10 @@
1
- [profile.default]
2
- src = "."
3
- out = "out"
4
- libs = ["lib"]
5
- remappings = ["@sail/interfaces/=interfaces/"]
6
- solc = "0.8.26"
7
- optimizer = true
8
- optimizer_runs = 200
9
- # Examples compile with: forge build (from this directory)
10
- # Deploy within a Sailor project using: sailor mandate deploy --contract <Name> --args '[...]'
1
+ [profile.default]
2
+ src = "."
3
+ out = "out"
4
+ libs = ["lib"]
5
+ remappings = ["@sail/interfaces/=interfaces/"]
6
+ solc = "0.8.26"
7
+ optimizer = true
8
+ optimizer_runs = 200
9
+ # Examples compile with: forge build (from this directory)
10
+ # Deploy within a Sailor project using: sailor mandate deploy --contract <Name> --args '[...]'
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // Mirrors SailProtocol contracts/interfaces/IBatchPermission.sol. A batch-aware
5
+ // permission gates `dispatchBatch` — an ordered array of subcalls executed as one
6
+ // atomic transaction. Exactly ONE batch permission is consulted per batch dispatch
7
+ // (selective model), and it owns validation of every subcall AND the cross-call
8
+ // invariants (ordering, matching amounts, mandatory cleanup) that no per-call
9
+ // IPermission can express.
10
+
11
+ /// @notice A single subcall in a batch dispatch.
12
+ /// @dev The kernel executes each Call as safe.execTransactionFromModule(target, value, data, 0)
13
+ /// — operation is always CALL, never DELEGATECALL.
14
+ struct Call {
15
+ address target; // MUST NOT equal the kernel (enforced by the kernel)
16
+ uint256 value; // native ETH forwarded (wei)
17
+ bytes data; // calldata for the subcall
18
+ }
19
+
20
+ /// @notice Execution context passed to evaluateBatch on each batch dispatch (read-only snapshot).
21
+ struct BatchContext {
22
+ address account; // the Safe (SMA) whose assets are moved
23
+ address manager; // the delegated signer who authorised the batch
24
+ address submitter; // msg.sender of the dispatch (may differ from manager via a relayer)
25
+ address permission; // this batch permission contract
26
+ bytes32 batchHash; // keccak256(abi.encode(calls)) — stable id for the exact sequence
27
+ uint256 blockTimestamp; // block.timestamp at dispatch
28
+ uint256 blockNumber; // block.number at dispatch
29
+ }
30
+
31
+ interface IBatchPermission {
32
+ /// @notice Validate an entire batch of subcalls. Called via staticcall under a gas cap;
33
+ /// a revert / OOG / malformed return is treated as `false` (fail-closed).
34
+ function evaluateBatch(Call[] calldata calls, BatchContext calldata ctx) external view returns (bool);
35
+
36
+ /// @notice Marker the kernel uses (try/catch) to detect batch-aware permissions. MUST return true.
37
+ function isBatchPermission() external pure returns (bool);
38
+ }
@@ -1,18 +1,18 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
3
-
4
- struct Context {
5
- address account;
6
- address manager;
7
- address submitter;
8
- address target;
9
- bytes4 selector;
10
- uint256 value;
11
- uint256 blockTimestamp;
12
- uint256 blockNumber;
13
- }
14
-
15
- interface IPermission {
16
- function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool);
17
- function discriminator() external view returns (bytes32);
18
- }
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ struct Context {
5
+ address account;
6
+ address manager;
7
+ address submitter;
8
+ address target;
9
+ bytes4 selector;
10
+ uint256 value;
11
+ uint256 blockTimestamp;
12
+ uint256 blockNumber;
13
+ }
14
+
15
+ interface IPermission {
16
+ function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool);
17
+ function discriminator() external view returns (bytes32);
18
+ }