@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,67 +1,45 @@
1
- import type { Address } from "@sail/sdk";
2
-
3
- // ── Token addresses (Base mainnet) ────────────────────────────────────────────
4
-
5
- /**
6
- * Tokens in the DCA basket.
7
- * ALLOWED_TOKENS[0] = USDC (input — what the agent spends)
8
- * ALLOWED_TOKENS[1] = WETH (output — what the agent accumulates)
9
- */
10
- export const ALLOWED_TOKENS: Address[] = [
11
- "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base (6 decimals)
12
- "0x4200000000000000000000000000000000000006", // WETH on Base (18 decimals)
13
- ];
14
-
15
- // ── Swap parameters ───────────────────────────────────────────────────────────
16
-
17
- /**
18
- * Amount of USDC to spend per swap (in USDC base units, 6 decimals).
19
- * Default: 5 USDC. Adjust before deploying to production.
20
- */
21
- export const SWAP_AMOUNT_USDC = 5_000_000n; // 5 USDC
22
-
23
- /**
24
- * Minimum USDC balance the SMA must hold before a swap is attempted.
25
- * Must be ≥ SWAP_AMOUNT_USDC. Adds a safety margin so a single swap
26
- * doesn't drain the account to zero.
27
- */
28
- export const MIN_USDC_TO_SWAP = 6_000_000n; // 6 USDC
29
-
30
- /**
31
- * Slippage tolerance in basis points (100 = 1%).
32
- * amountOutMinimum = quote × (1 SLIPPAGE_BPS / 10 000).
33
- * If no quote is available, the agent skips the swap entirely (fail closed).
34
- *
35
- * Tighten for large trades or illiquid pairs; loosen if valid swaps are
36
- * frequently denied by price impact. Default 1% is conservative for
37
- * USDC/WETH on Base.
38
- */
39
- export const SLIPPAGE_BPS = 100; // 1%
40
-
41
- /**
42
- * Uniswap V3 pool fee tier for the USDC/WETH pool on Base.
43
- * 500 = 0.05% — the most liquid USDC/WETH pool on Base mainnet.
44
- */
45
- export const SWAP_FEE_TIER = 500;
46
-
47
- // ── Rebalancing ───────────────────────────────────────────────────────────────
48
-
49
- /** Rebalance when allocation drift exceeds this fraction (0.05 = 5%). */
50
- export const REBALANCE_THRESHOLD = 0.05;
51
-
52
- // ── Contract addresses (Base mainnet) ─────────────────────────────────────────
53
-
54
- /** Uniswap SwapRouter02 on Base. Target for exactInputSingle swaps. */
55
- export const SWAP_ROUTER: Address = "0x2626664c2603336E57B271c5C0b26F421741e481";
56
-
57
- /**
58
- * Uniswap V3 QuoterV2 on Base.
59
- * Called off-chain (via eth_call) to obtain the expected output amount
60
- * before computing amountOutMinimum. If this call fails, the agent skips
61
- * the swap — it never submits with amountOutMinimum = 0.
62
- *
63
- * Note: amountOutMinimum is set by the agent from the QuoterV2 result.
64
- * For defense-in-depth, add an on-chain minimum check in your permission
65
- * contract so the kernel also validates the minimum out requirement.
66
- */
67
- export const QUOTER_V2: Address = "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a";
1
+ // Reference example not the user's strategy. Consult for patterns; author the user's own in src/.
2
+ // Shows: token addresses, swap parameters, and contract addresses for a USDC→WETH DCA on Base mainnet.
3
+
4
+ import type { Address } from "@sail.money/sailor/sdk";
5
+
6
+ // ── Token addresses (Base mainnet) ────────────────────────────────────────────
7
+
8
+ /**
9
+ * Tokens in the DCA basket.
10
+ * ALLOWED_TOKENS[0] = USDC (input — what the agent spends)
11
+ * ALLOWED_TOKENS[1] = WETH (output — what the agent accumulates)
12
+ */
13
+ export const ALLOWED_TOKENS: Address[] = [
14
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base (6 decimals)
15
+ "0x4200000000000000000000000000000000000006", // WETH on Base (18 decimals)
16
+ ];
17
+
18
+ // ── Swap parameters ───────────────────────────────────────────────────────────
19
+
20
+ /** Amount of USDC to spend per swap (in USDC base units, 6 decimals). Default: 5 USDC. */
21
+ export const SWAP_AMOUNT_USDC = 5_000_000n; // 5 USDC
22
+
23
+ /** Minimum USDC balance the SMA must hold before a swap is attempted. */
24
+ export const MIN_USDC_TO_SWAP = 6_000_000n; // 6 USDC
25
+
26
+ /** Slippage tolerance in basis points (100 = 1%). */
27
+ export const SLIPPAGE_BPS = 100; // 1%
28
+
29
+ /** Uniswap V3 pool fee tier for the USDC/WETH pool on Base (500 = 0.05%). */
30
+ export const SWAP_FEE_TIER = 500;
31
+
32
+ /** Rebalance when allocation drift exceeds this fraction (0.05 = 5%). */
33
+ export const REBALANCE_THRESHOLD = 0.05;
34
+
35
+ // ── Contract addresses (Base mainnet) ─────────────────────────────────────────
36
+
37
+ /** Uniswap SwapRouter02 on Base. Target for exactInputSingle swaps. */
38
+ export const SWAP_ROUTER: Address = "0x2626664c2603336E57B271c5C0b26F421741e481";
39
+
40
+ /**
41
+ * Uniswap V3 QuoterV2 on Base.
42
+ * Called off-chain (via eth_call) to obtain the expected output amount
43
+ * before computing amountOutMinimum.
44
+ */
45
+ export const QUOTER_V2: Address = "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a";
@@ -1,17 +1,17 @@
1
- {
2
- "name": "dca-rebalancer",
3
- "version": "0.1.0",
4
- "description": "Dollar-cost-average rebalancer — starter Sail Protocol agent",
5
- "type": "module",
6
- "scripts": {
7
- "typecheck": "tsc -p tsconfig.json --noEmit"
8
- },
9
- "dependencies": {
10
- "@sail/sdk": "workspace:*",
11
- "viem": "^2.21.0"
12
- },
13
- "devDependencies": {
14
- "@types/node": "^22.0.0",
15
- "typescript": "^5.5.0"
16
- }
17
- }
1
+ {
2
+ "name": "sail-agent",
3
+ "version": "0.1.0",
4
+ "description": "Sail Protocol agent starter",
5
+ "type": "module",
6
+ "scripts": {
7
+ "typecheck": "tsc -p tsconfig.json --noEmit"
8
+ },
9
+ "dependencies": {
10
+ "@sail/sdk": "workspace:*",
11
+ "viem": "^2.21.0"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "^22.0.0",
15
+ "typescript": "^5.5.0"
16
+ }
17
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Your agent — implement your strategy here.
3
+ *
4
+ * The runner calls tick() on every interval. Return an array of Dispatch intents;
5
+ * the runner submits each one through the kernel against a matching registered permission.
6
+ * Return [] to skip this tick (no gas spent).
7
+ *
8
+ * The runner resolves which permission authorizes each call automatically — you
9
+ * express what you want to do; you do not name permissions per call.
10
+ *
11
+ * For a worked end-to-end example (DCA / Uniswap V3 / Base):
12
+ * see examples/dca/agent.ts and examples/dca/mandate.ts
13
+ */
14
+
15
+ import type { Agent, AgentContext, Dispatch } from "@sail.money/sailor/sdk";
16
+
17
+ export const agent: Agent = {
18
+ name: "my-agent",
19
+ description: "Describe your strategy here.",
20
+
21
+ async tick(ctx: AgentContext): Promise<Dispatch[]> {
22
+ ctx.log(`tick — block ${ctx.blockNumber}, sma ${ctx.safe}`);
23
+
24
+ // TODO: implement your strategy.
25
+ // Read on-chain state, decide what to do, return intent dispatches.
26
+ // ctx.read.balance(tokenAddress) — read token balance of the SMA
27
+ // ctx.data._publicClient — viem PublicClient for arbitrary on-chain reads
28
+ // ctx.log(msg) — append a message to the activity log
29
+ //
30
+ // Example (from examples/dca/agent.ts):
31
+ // const balance = await ctx.read.balance(USDC_ADDRESS);
32
+ // if (balance < MIN_AMOUNT) return [];
33
+ // return [{ txHash: "0x", calls: [{ target: ROUTER, value: 0n, data: swapCalldata }], success: false, gasUsed: 0n }];
34
+
35
+ return [];
36
+ },
37
+ };
@@ -0,0 +1,24 @@
1
+ /** Reads RPC_URL and CHAIN_ID from environment (set via .sail/.env.local or GitHub Secrets). */
2
+ export function getEnvConfig(): { rpcUrl: string; chainId: number } {
3
+ const rpcUrl = process.env["RPC_URL"];
4
+ if (!rpcUrl) {
5
+ throw new Error(
6
+ "RPC_URL is not set.\n" +
7
+ "Add RPC_URL to .sail/.env.local or set it as an environment variable.",
8
+ );
9
+ }
10
+
11
+ if (!process.env["CHAIN_ID"]) {
12
+ throw new Error(
13
+ "CHAIN_ID is not set.\n" +
14
+ "Open this folder in your AI coding assistant and say 'start' — Stage 1 will ask\n" +
15
+ "which chain you want and set CHAIN_ID in .sail/.env.local.",
16
+ );
17
+ }
18
+ const chainId = Number(process.env["CHAIN_ID"]);
19
+ if (Number.isNaN(chainId)) {
20
+ throw new Error(`Invalid CHAIN_ID: ${process.env["CHAIN_ID"]}`);
21
+ }
22
+
23
+ return { rpcUrl, chainId };
24
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Strategy parameters and contract addresses for your agent.
3
+ *
4
+ * Put your token addresses, protocol contracts, amounts, and other
5
+ * constants here. Import them into agent.ts.
6
+ *
7
+ * For a worked example (DCA / USDC→WETH / Uniswap V3 / Base):
8
+ * see examples/dca/mandate.ts
9
+ *
10
+ * For protocol-specific permission examples (Uniswap, Aave, GMX, …):
11
+ * see examples/permissions/
12
+ */
13
+
14
+ // TODO: replace with your strategy's parameters.
15
+ // Example structure:
16
+ //
17
+ // import type { Address } from "@sail/sdk";
18
+ //
19
+ // export const INPUT_TOKEN: Address = "0x..."; // token you're spending
20
+ // export const OUTPUT_TOKEN: Address = "0x..."; // token you're acquiring
21
+ // export const MAX_AMOUNT_PER_TICK = 0n; // in input token base units
22
+ // export const PROTOCOL_CONTRACT: Address = "0x..."; // target contract
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "target": "ES2022",
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "resolveJsonModule": true,
10
+ "noEmit": true,
11
+ "baseUrl": ".",
12
+ "paths": {
13
+ "@sail.money/sailor/sdk": ["../../packages/sdk/src/index.ts"]
14
+ }
15
+ },
16
+ "include": ["src"]
17
+ }
@@ -1,3 +1,3 @@
1
- # UI
2
-
3
- Vite + React UI lands in the next prompt.
1
+ # UI
2
+
3
+ Vite + React UI lands in the next prompt.
@@ -1,84 +1,84 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
3
-
4
- import {IPermission, Context} from "../../.sail/contracts/interfaces/IPermission.sol";
5
- import {CloneInitializable} from "../../.sail/contracts/templates/base/CloneInitializable.sol";
6
-
7
- /// @title LifiBoundedApprovePermissionCloneable
8
- /// @notice EIP-1167 clone-template approval permission with PER-TOKEN caps. The
9
- /// logic contract is deployed once and registered in the SDK's
10
- /// standaloneTemplates; each account clones it via
11
- /// PermissionFactory.deployAndAttach and configures via initialize().
12
- ///
13
- /// The manager may ONLY approve the LiFi Diamond, and only on tokens that
14
- /// have a configured cap, up to that token's cap. Passes through any
15
- /// non-approve call (conjunctive model).
16
- ///
17
- /// Caps are PER TOKEN (not one global cap) because token value and decimals
18
- /// differ — e.g. 1 DAI = 1e18 base units vs 1 USDC = 1e6. A single cap can't
19
- /// bound both sensibly.
20
- contract LifiBoundedApprovePermissionCloneable is IPermission, CloneInitializable {
21
- bytes4 private constant APPROVE = 0x095ea7b3;
22
- address public constant LIFI_DIAMOND = 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE;
23
-
24
- // Per-token approve cap, in the token's base units. 0 = token not allowed.
25
- // A mapping occupies its own slot pointer and does not pack with the
26
- // CloneInitializable `_initialized` bool at slot 0.
27
- mapping(address token => uint256 cap) public maxApprovePerToken;
28
- address public permissionSigner;
29
-
30
- error NotPermissionSigner();
31
- error ZeroAddress();
32
- error LengthMismatch();
33
-
34
- modifier onlyPermissionSigner() {
35
- if (msg.sender != permissionSigner) revert NotPermissionSigner();
36
- _;
37
- }
38
-
39
- /// @dev Lock the logic contract; only clones (fresh storage) can be initialized.
40
- constructor() {
41
- _disableInitializers();
42
- }
43
-
44
- /// @notice One-time per-clone configuration.
45
- /// @param tokens Tokens the manager may approve to the LiFi Diamond.
46
- /// @param caps Per-token cap (token base units); index-aligned with `tokens`.
47
- /// @param _permissionSigner Owner wallet; sole authority for post-init updates.
48
- function initialize(
49
- address[] memory tokens,
50
- uint256[] memory caps,
51
- address _permissionSigner
52
- ) external initializer {
53
- if (_permissionSigner == address(0)) revert ZeroAddress();
54
- if (tokens.length != caps.length) revert LengthMismatch();
55
- permissionSigner = _permissionSigner;
56
- for (uint256 i; i < tokens.length; i++) {
57
- maxApprovePerToken[tokens[i]] = caps[i];
58
- }
59
- }
60
-
61
- /// @notice Set (cap > 0) or clear (cap == 0) a token's per-tx approve cap.
62
- function setTokenCap(address token, uint256 cap) external onlyPermissionSigner {
63
- maxApprovePerToken[token] = cap;
64
- }
65
-
66
- function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
67
- // Pass through calls outside this permission's domain (conjunctive model).
68
- if (ctx.selector != APPROVE) return true;
69
- if (ctx.target == address(0)) return false; // token is ctx.target
70
- if (txData.length < 68) return false;
71
-
72
- (address spender, uint256 amount) = abi.decode(txData[4:], (address, uint256));
73
- if (spender != LIFI_DIAMOND) return false;
74
-
75
- uint256 cap = maxApprovePerToken[ctx.target];
76
- if (cap == 0) return false; // token not allowed
77
- if (amount > cap) return false; // over the per-token cap
78
- return true;
79
- }
80
-
81
- function discriminator() external pure returns (bytes32) {
82
- return keccak256("LifiBoundedApprovePermissionCloneable.v1");
83
- }
84
- }
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {IPermission, Context} from "../../.sail/contracts/interfaces/IPermission.sol";
5
+ import {CloneInitializable} from "../../.sail/contracts/templates/base/CloneInitializable.sol";
6
+
7
+ /// @title LifiBoundedApprovePermissionCloneable
8
+ /// @notice EIP-1167 clone-template approval permission with PER-TOKEN caps. The
9
+ /// logic contract is deployed once and registered in the SDK's
10
+ /// standaloneTemplates; each account clones it via
11
+ /// PermissionFactory.deployAndAttach and configures via initialize().
12
+ ///
13
+ /// The manager may ONLY approve the LiFi Diamond, and only on tokens that
14
+ /// have a configured cap, up to that token's cap. Passes through any
15
+ /// non-approve call (conjunctive model).
16
+ ///
17
+ /// Caps are PER TOKEN (not one global cap) because token value and decimals
18
+ /// differ — e.g. 1 DAI = 1e18 base units vs 1 USDC = 1e6. A single cap can't
19
+ /// bound both sensibly.
20
+ contract LifiBoundedApprovePermissionCloneable is IPermission, CloneInitializable {
21
+ bytes4 private constant APPROVE = 0x095ea7b3;
22
+ address public constant LIFI_DIAMOND = 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE;
23
+
24
+ // Per-token approve cap, in the token's base units. 0 = token not allowed.
25
+ // A mapping occupies its own slot pointer and does not pack with the
26
+ // CloneInitializable `_initialized` bool at slot 0.
27
+ mapping(address token => uint256 cap) public maxApprovePerToken;
28
+ address public permissionSigner;
29
+
30
+ error NotPermissionSigner();
31
+ error ZeroAddress();
32
+ error LengthMismatch();
33
+
34
+ modifier onlyPermissionSigner() {
35
+ if (msg.sender != permissionSigner) revert NotPermissionSigner();
36
+ _;
37
+ }
38
+
39
+ /// @dev Lock the logic contract; only clones (fresh storage) can be initialized.
40
+ constructor() {
41
+ _disableInitializers();
42
+ }
43
+
44
+ /// @notice One-time per-clone configuration.
45
+ /// @param tokens Tokens the manager may approve to the LiFi Diamond.
46
+ /// @param caps Per-token cap (token base units); index-aligned with `tokens`.
47
+ /// @param _permissionSigner Owner wallet; sole authority for post-init updates.
48
+ function initialize(
49
+ address[] memory tokens,
50
+ uint256[] memory caps,
51
+ address _permissionSigner
52
+ ) external initializer {
53
+ if (_permissionSigner == address(0)) revert ZeroAddress();
54
+ if (tokens.length != caps.length) revert LengthMismatch();
55
+ permissionSigner = _permissionSigner;
56
+ for (uint256 i; i < tokens.length; i++) {
57
+ maxApprovePerToken[tokens[i]] = caps[i];
58
+ }
59
+ }
60
+
61
+ /// @notice Set (cap > 0) or clear (cap == 0) a token's per-tx approve cap.
62
+ function setTokenCap(address token, uint256 cap) external onlyPermissionSigner {
63
+ maxApprovePerToken[token] = cap;
64
+ }
65
+
66
+ function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
67
+ // Pass through calls outside this permission's domain (conjunctive model).
68
+ if (ctx.selector != APPROVE) return true;
69
+ if (ctx.target == address(0)) return false; // token is ctx.target
70
+ if (txData.length < 68) return false;
71
+
72
+ (address spender, uint256 amount) = abi.decode(txData[4:], (address, uint256));
73
+ if (spender != LIFI_DIAMOND) return false;
74
+
75
+ uint256 cap = maxApprovePerToken[ctx.target];
76
+ if (cap == 0) return false; // token not allowed
77
+ if (amount > cap) return false; // over the per-token cap
78
+ return true;
79
+ }
80
+
81
+ function discriminator() external pure returns (bytes32) {
82
+ return keccak256("LifiBoundedApprovePermissionCloneable.v1");
83
+ }
84
+ }
@@ -1,97 +1,97 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
3
-
4
- import {IPermission, Context} from "../../.sail/contracts/interfaces/IPermission.sol";
5
- import {CloneInitializable} from "../../.sail/contracts/templates/base/CloneInitializable.sol";
6
-
7
- /// @title LifiDiamondSwapPermissionCloneable
8
- /// @notice EIP-1167 clone-template version of LifiDiamondSwapPermission. The logic
9
- /// contract is deployed once and registered in the SDK's standaloneTemplates;
10
- /// each account gets its own clone via PermissionFactory.deployAndAttach,
11
- /// configured through initialize() (NOT the constructor).
12
- ///
13
- /// Restricts manager-initiated swaps to the official LiFi Diamond on Base:
14
- /// - target must be the LiFi Diamond,
15
- /// - selector must be allowlisted,
16
- /// - receiver embedded in the calldata must equal ctx.account (the SMA),
17
- /// - the minAmount field must not exceed the configured cap.
18
- /// Passes through any call whose target is not the diamond (conjunctive model).
19
- ///
20
- /// Calldata layout (validated against live Base quotes):
21
- /// selector(4) + word0(32) + word1(32) + word2(32) + receiver(32) + minAmount(32)
22
- /// → receiver at offset 100, minAmount at offset 132.
23
- contract LifiDiamondSwapPermissionCloneable is IPermission, CloneInitializable {
24
- // Official LiFi Diamond on Base Mainnet.
25
- address public constant LIFI_DIAMOND = 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE;
26
-
27
- // Storage starts after CloneInitializable's `_initialized` bool (slot 0). A
28
- // mapping occupies its own slot pointer and does not pack with the bool.
29
- mapping(bytes4 selector => bool) public isAllowedSelector;
30
- uint256 public maxMinAmountPerTx;
31
- address public permissionSigner;
32
-
33
- error NotPermissionSigner();
34
- error ZeroAddress();
35
-
36
- modifier onlyPermissionSigner() {
37
- if (msg.sender != permissionSigner) revert NotPermissionSigner();
38
- _;
39
- }
40
-
41
- /// @dev Lock the logic contract; only clones (fresh storage) can be initialized.
42
- constructor() {
43
- _disableInitializers();
44
- }
45
-
46
- /// @notice One-time per-clone configuration.
47
- /// @param allowedSelectors LiFi Diamond selectors to allowlist on init.
48
- /// @param _maxMinAmountPerTx Cap on the minAmount field (type(uint256).max = uncapped).
49
- /// @param _permissionSigner Owner wallet; sole authority for post-init updates.
50
- function initialize(
51
- bytes4[] memory allowedSelectors,
52
- uint256 _maxMinAmountPerTx,
53
- address _permissionSigner
54
- ) external initializer {
55
- if (_permissionSigner == address(0)) revert ZeroAddress();
56
- maxMinAmountPerTx = _maxMinAmountPerTx;
57
- permissionSigner = _permissionSigner;
58
- for (uint256 i; i < allowedSelectors.length; i++) {
59
- isAllowedSelector[allowedSelectors[i]] = true;
60
- }
61
- }
62
-
63
- function setMaxMinAmountPerTx(uint256 newMax) external onlyPermissionSigner {
64
- maxMinAmountPerTx = newMax;
65
- }
66
-
67
- /// @notice Add a LiFi selector. Only after verifying its calldata places
68
- /// `receiver` at offset 100 and `minAmount` at offset 132.
69
- function addSelector(bytes4 selector) external onlyPermissionSigner {
70
- isAllowedSelector[selector] = true;
71
- }
72
-
73
- function removeSelector(bytes4 selector) external onlyPermissionSigner {
74
- isAllowedSelector[selector] = false;
75
- }
76
-
77
- uint256 private constant RECEIVER_OFFSET = 100;
78
- uint256 private constant MIN_DATA_LEN = 164;
79
-
80
- function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
81
- // Pass through calls outside this permission's domain (conjunctive model).
82
- if (ctx.target != LIFI_DIAMOND) return true;
83
- if (!isAllowedSelector[ctx.selector]) return false;
84
- if (txData.length < MIN_DATA_LEN) return false;
85
-
86
- address receiver = abi.decode(txData[RECEIVER_OFFSET:RECEIVER_OFFSET + 32], (address));
87
- uint256 minAmount = abi.decode(txData[RECEIVER_OFFSET + 32:RECEIVER_OFFSET + 64], (uint256));
88
-
89
- if (receiver != ctx.account) return false;
90
- if (minAmount > maxMinAmountPerTx) return false;
91
- return true;
92
- }
93
-
94
- function discriminator() external pure returns (bytes32) {
95
- return keccak256("LifiDiamondSwapPermissionCloneable.v1");
96
- }
97
- }
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {IPermission, Context} from "../../.sail/contracts/interfaces/IPermission.sol";
5
+ import {CloneInitializable} from "../../.sail/contracts/templates/base/CloneInitializable.sol";
6
+
7
+ /// @title LifiDiamondSwapPermissionCloneable
8
+ /// @notice EIP-1167 clone-template version of LifiDiamondSwapPermission. The logic
9
+ /// contract is deployed once and registered in the SDK's standaloneTemplates;
10
+ /// each account gets its own clone via PermissionFactory.deployAndAttach,
11
+ /// configured through initialize() (NOT the constructor).
12
+ ///
13
+ /// Restricts manager-initiated swaps to the official LiFi Diamond on Base:
14
+ /// - target must be the LiFi Diamond,
15
+ /// - selector must be allowlisted,
16
+ /// - receiver embedded in the calldata must equal ctx.account (the SMA),
17
+ /// - the minAmount field must not exceed the configured cap.
18
+ /// Passes through any call whose target is not the diamond (conjunctive model).
19
+ ///
20
+ /// Calldata layout (validated against live Base quotes):
21
+ /// selector(4) + word0(32) + word1(32) + word2(32) + receiver(32) + minAmount(32)
22
+ /// → receiver at offset 100, minAmount at offset 132.
23
+ contract LifiDiamondSwapPermissionCloneable is IPermission, CloneInitializable {
24
+ // Official LiFi Diamond on Base Mainnet.
25
+ address public constant LIFI_DIAMOND = 0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE;
26
+
27
+ // Storage starts after CloneInitializable's `_initialized` bool (slot 0). A
28
+ // mapping occupies its own slot pointer and does not pack with the bool.
29
+ mapping(bytes4 selector => bool) public isAllowedSelector;
30
+ uint256 public maxMinAmountPerTx;
31
+ address public permissionSigner;
32
+
33
+ error NotPermissionSigner();
34
+ error ZeroAddress();
35
+
36
+ modifier onlyPermissionSigner() {
37
+ if (msg.sender != permissionSigner) revert NotPermissionSigner();
38
+ _;
39
+ }
40
+
41
+ /// @dev Lock the logic contract; only clones (fresh storage) can be initialized.
42
+ constructor() {
43
+ _disableInitializers();
44
+ }
45
+
46
+ /// @notice One-time per-clone configuration.
47
+ /// @param allowedSelectors LiFi Diamond selectors to allowlist on init.
48
+ /// @param _maxMinAmountPerTx Cap on the minAmount field (type(uint256).max = uncapped).
49
+ /// @param _permissionSigner Owner wallet; sole authority for post-init updates.
50
+ function initialize(
51
+ bytes4[] memory allowedSelectors,
52
+ uint256 _maxMinAmountPerTx,
53
+ address _permissionSigner
54
+ ) external initializer {
55
+ if (_permissionSigner == address(0)) revert ZeroAddress();
56
+ maxMinAmountPerTx = _maxMinAmountPerTx;
57
+ permissionSigner = _permissionSigner;
58
+ for (uint256 i; i < allowedSelectors.length; i++) {
59
+ isAllowedSelector[allowedSelectors[i]] = true;
60
+ }
61
+ }
62
+
63
+ function setMaxMinAmountPerTx(uint256 newMax) external onlyPermissionSigner {
64
+ maxMinAmountPerTx = newMax;
65
+ }
66
+
67
+ /// @notice Add a LiFi selector. Only after verifying its calldata places
68
+ /// `receiver` at offset 100 and `minAmount` at offset 132.
69
+ function addSelector(bytes4 selector) external onlyPermissionSigner {
70
+ isAllowedSelector[selector] = true;
71
+ }
72
+
73
+ function removeSelector(bytes4 selector) external onlyPermissionSigner {
74
+ isAllowedSelector[selector] = false;
75
+ }
76
+
77
+ uint256 private constant RECEIVER_OFFSET = 100;
78
+ uint256 private constant MIN_DATA_LEN = 164;
79
+
80
+ function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
81
+ // Pass through calls outside this permission's domain (conjunctive model).
82
+ if (ctx.target != LIFI_DIAMOND) return true;
83
+ if (!isAllowedSelector[ctx.selector]) return false;
84
+ if (txData.length < MIN_DATA_LEN) return false;
85
+
86
+ address receiver = abi.decode(txData[RECEIVER_OFFSET:RECEIVER_OFFSET + 32], (address));
87
+ uint256 minAmount = abi.decode(txData[RECEIVER_OFFSET + 32:RECEIVER_OFFSET + 64], (uint256));
88
+
89
+ if (receiver != ctx.account) return false;
90
+ if (minAmount > maxMinAmountPerTx) return false;
91
+ return true;
92
+ }
93
+
94
+ function discriminator() external pure returns (bytes32) {
95
+ return keccak256("LifiDiamondSwapPermissionCloneable.v1");
96
+ }
97
+ }