@dev.sail.money/sailor 0.1.0-local → 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 (153) hide show
  1. package/AGENTS.md +139 -140
  2. package/LICENSE +21 -21
  3. package/README.md +428 -430
  4. package/docs/PERMISSION_MODEL.md +93 -93
  5. package/examples/permissions/BoundedApproveAndCallBatch.sol +179 -179
  6. package/examples/permissions/BoundedBet_Limitless_Base.sol +97 -97
  7. package/examples/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +94 -94
  8. package/examples/permissions/BoundedPerp_GMXv2_Arbitrum.sol +154 -154
  9. package/examples/permissions/BoundedStake_Venice_Base.sol +85 -85
  10. package/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +82 -82
  11. package/examples/permissions/BoundedSwap_UniswapV3_Base.sol +116 -116
  12. package/examples/permissions/BoundedSwap_UniswapV4_Unichain.sol +150 -150
  13. package/examples/permissions/BoundedTransfer_ERC20_Ethereum.sol +73 -73
  14. package/examples/permissions/BoundedVault_ERC4626_Base.sol +97 -97
  15. package/examples/permissions/README.md +79 -79
  16. package/examples/permissions/SailCalldata.sol +118 -118
  17. package/examples/permissions/foundry.toml +10 -10
  18. package/examples/permissions/interfaces/IBatchPermission.sol +38 -38
  19. package/examples/permissions/interfaces/IPermission.sol +18 -18
  20. package/package.json +44 -45
  21. package/packages/cli/README.md +34 -34
  22. package/packages/cli/dist/index.cjs +734 -705
  23. package/packages/cli/dist/server.cjs +627 -538
  24. package/packages/sdk/README.md +65 -65
  25. package/packages/sdk/dist/intelligence.d.ts +1 -1
  26. package/packages/sdk/dist/intelligence.js +1 -1
  27. package/packages/sdk/package.json +80 -80
  28. package/packages/ui/dist/assets/{add-BxpXfVWe.js → add-Dl1etsL9.js} +1 -1
  29. package/packages/ui/dist/assets/{all-wallets-BKTn_sWK.js → all-wallets-C0eHLOGG.js} +1 -1
  30. package/packages/ui/dist/assets/{app-store-CfuKbwxR.js → app-store-B-VMDEZ3.js} +1 -1
  31. package/packages/ui/dist/assets/{apple-BKSBbNYg.js → apple-DkDXzKns.js} +1 -1
  32. package/packages/ui/dist/assets/{arrow-bottom-D4bG6gZi.js → arrow-bottom-DtPzuS76.js} +1 -1
  33. package/packages/ui/dist/assets/{arrow-bottom-circle-BNTs1p0T.js → arrow-bottom-circle-D7odSAO8.js} +1 -1
  34. package/packages/ui/dist/assets/{arrow-left-2uee3vYv.js → arrow-left-zJV9tpx0.js} +1 -1
  35. package/packages/ui/dist/assets/{arrow-right-BktjMV6h.js → arrow-right-BOREfe7o.js} +1 -1
  36. package/packages/ui/dist/assets/{arrow-top-Izu28fX4.js → arrow-top-CipQc3Af.js} +1 -1
  37. package/packages/ui/dist/assets/{bank-USBaAyFM.js → bank-C5s7eoV5.js} +1 -1
  38. package/packages/ui/dist/assets/{basic-C_9KjTEH.js → basic-D2es4Vq8.js} +1 -1
  39. package/packages/ui/dist/assets/{browser-DAEMAKV7.js → browser-DITQWDC9.js} +1 -1
  40. package/packages/ui/dist/assets/{card-DT8yDkKN.js → card-C3DDkaYK.js} +1 -1
  41. package/packages/ui/dist/assets/{ccip-CkqfGSxX.js → ccip-UBXL3JiN.js} +1 -1
  42. package/packages/ui/dist/assets/{checkmark-bold-D2gjOQo2.js → checkmark-bold-D8yW0_K_.js} +1 -1
  43. package/packages/ui/dist/assets/{checkmark-CsgdEXFj.js → checkmark-ngef3MAl.js} +1 -1
  44. package/packages/ui/dist/assets/{chevron-bottom-tprFynYV.js → chevron-bottom-C56BipDR.js} +1 -1
  45. package/packages/ui/dist/assets/{chevron-left-D2Zj1gNB.js → chevron-left-BmIPtPl_.js} +1 -1
  46. package/packages/ui/dist/assets/{chevron-right-D1rRuAVe.js → chevron-right-BnySHQ8h.js} +1 -1
  47. package/packages/ui/dist/assets/{chevron-top-24dL1mbL.js → chevron-top-BDGZnNW3.js} +1 -1
  48. package/packages/ui/dist/assets/{chrome-store-Vy-5niYX.js → chrome-store-BYIqJZVF.js} +1 -1
  49. package/packages/ui/dist/assets/{clock-qBjLnVdJ.js → clock-Bl4mUHAM.js} +1 -1
  50. package/packages/ui/dist/assets/{close-DARDwgcu.js → close-B9rhEX6U.js} +1 -1
  51. package/packages/ui/dist/assets/{coinPlaceholder-BvpIbPlD.js → coinPlaceholder-1cO0FQsl.js} +1 -1
  52. package/packages/ui/dist/assets/{compass-BMTO0ayt.js → compass-7i-VuXu2.js} +1 -1
  53. package/packages/ui/dist/assets/{copy-PaXeRHza.js → copy-OqqXix2J.js} +1 -1
  54. package/packages/ui/dist/assets/{core-BFnStQd-.js → core-tX9kIIDJ.js} +3 -3
  55. package/packages/ui/dist/assets/cursor-BoyeQ9fN.js +3 -0
  56. package/packages/ui/dist/assets/{cursor-transparent-BEMdi-8q.js → cursor-transparent-5aoRH67u.js} +1 -1
  57. package/packages/ui/dist/assets/{desktop-CfuLLThw.js → desktop-BaPXK9R6.js} +1 -1
  58. package/packages/ui/dist/assets/{disconnect-DhwgJMiR.js → disconnect-LlK5K1CF.js} +1 -1
  59. package/packages/ui/dist/assets/{discord-po8qoN1s.js → discord-BdcQNWY_.js} +1 -1
  60. package/packages/ui/dist/assets/{etherscan-BEsz0_yx.js → etherscan-Bb-WxpO1.js} +1 -1
  61. package/packages/ui/dist/assets/{events-Bz33Unzu.js → events-DjdZr6no.js} +1 -1
  62. package/packages/ui/dist/assets/{exclamation-triangle-7CjTAGOQ.js → exclamation-triangle-COx4VtPV.js} +1 -1
  63. package/packages/ui/dist/assets/{extension-CmxjEWEt.js → extension-DF63DTWO.js} +1 -1
  64. package/packages/ui/dist/assets/{external-link-CmQ--bNS.js → external-link-DyghCkQu.js} +1 -1
  65. package/packages/ui/dist/assets/{facebook-CIBn9b65.js → facebook-Dcg4bZMR.js} +1 -1
  66. package/packages/ui/dist/assets/{fallback-DATyrQlb.js → fallback-DJIr_fH3.js} +1 -1
  67. package/packages/ui/dist/assets/{farcaster-OJ3Jasxg.js → farcaster-BkmV5HjO.js} +1 -1
  68. package/packages/ui/dist/assets/{filters-D4x09zeL.js → filters-DLHj1T_P.js} +1 -1
  69. package/packages/ui/dist/assets/{github-ZlIuMArp.js → github-BFDCgKrF.js} +1 -1
  70. package/packages/ui/dist/assets/{google-Gwg85sfv.js → google-C08SpmIy.js} +1 -1
  71. package/packages/ui/dist/assets/{help-circle-D1uOWYcX.js → help-circle-DU1IFmWp.js} +1 -1
  72. package/packages/ui/dist/assets/{id-C0-5UdYk.js → id-DtDRGf3L.js} +1 -1
  73. package/packages/ui/dist/assets/{image-D_DUsv8-.js → image-CgCXJEjT.js} +1 -1
  74. package/packages/ui/dist/assets/{index-izd7vu_r.js → index-8chM4S5Y.js} +1 -1
  75. package/packages/ui/dist/assets/{index-CrYzBWfD.js → index-B5sCtNuq.js} +1 -1
  76. package/packages/ui/dist/assets/{index-DdbJhIdl.js → index-BBfBEazf.js} +3 -3
  77. package/packages/ui/dist/assets/{index-BCzex_R6.js → index-BhXPwltt.js} +1 -1
  78. package/packages/ui/dist/assets/index-Cm05Py20.css +1 -0
  79. package/packages/ui/dist/assets/{index-DiojfeVM.js → index-D37bD6Yt.js} +1 -1
  80. package/packages/ui/dist/assets/index-DZfBh-cg.js +1775 -0
  81. package/packages/ui/dist/assets/{index.es-DdkHhQAj.js → index.es-BEcNQEn-.js} +4 -4
  82. package/packages/ui/dist/assets/{info-CiRd_kEG.js → info-ClsdYA4P.js} +1 -1
  83. package/packages/ui/dist/assets/{info-circle-ypxjqarK.js → info-circle-DJmn4Bsv.js} +1 -1
  84. package/packages/ui/dist/assets/{lightbulb-B-pxLxd8.js → lightbulb-CXSftjXS.js} +1 -1
  85. package/packages/ui/dist/assets/{mail-BYmicuVZ.js → mail-cdYKOl9P.js} +1 -1
  86. package/packages/ui/dist/assets/{metamask-sdk-Ccl6DG7Q.js → metamask-sdk-Co3aIEln.js} +1 -1
  87. package/packages/ui/dist/assets/{mobile-CtP5PqVT.js → mobile-DEHYlk8L.js} +1 -1
  88. package/packages/ui/dist/assets/{more-6C2733we.js → more-Cq_fo8pI.js} +1 -1
  89. package/packages/ui/dist/assets/{network-placeholder-CdhxMzqd.js → network-placeholder-_dLCK4xB.js} +1 -1
  90. package/packages/ui/dist/assets/{nftPlaceholder-DVmTWEAY.js → nftPlaceholder-Dz4HKEr4.js} +1 -1
  91. package/packages/ui/dist/assets/{off-DNYLughs.js → off-B1k1lhkr.js} +1 -1
  92. package/packages/ui/dist/assets/{parseSignature-Dq2B5Bu3.js → parseSignature-Cr0ptV2X.js} +1 -1
  93. package/packages/ui/dist/assets/{play-store-D7Qut5ta.js → play-store-lYqe4eeL.js} +1 -1
  94. package/packages/ui/dist/assets/{plus-kqMyjt3q.js → plus-QMgh1krr.js} +1 -1
  95. package/packages/ui/dist/assets/{qr-code-DiUCWRbz.js → qr-code-ClVHbZWN.js} +1 -1
  96. package/packages/ui/dist/assets/{recycle-horizontal-Boe3XiS-.js → recycle-horizontal-CxGYnWid.js} +1 -1
  97. package/packages/ui/dist/assets/{refresh-CrBgBQYO.js → refresh-CPysMza_.js} +1 -1
  98. package/packages/ui/dist/assets/{reown-logo-CFZCCHSx.js → reown-logo-OoL_zJd0.js} +1 -1
  99. package/packages/ui/dist/assets/{search-ChTDrghU.js → search-2GaRbf1I.js} +1 -1
  100. package/packages/ui/dist/assets/{secp256k1-DAV5Q_FR.js → secp256k1-BrB8qSSy.js} +1 -1
  101. package/packages/ui/dist/assets/{send-DLFbBFe1.js → send-CC2UuIfD.js} +1 -1
  102. package/packages/ui/dist/assets/{swapHorizontal-BEs3emfG.js → swapHorizontal-BRqYwsqT.js} +1 -1
  103. package/packages/ui/dist/assets/{swapHorizontalBold-CC-Hfa7W.js → swapHorizontalBold-Bj0GSRq9.js} +1 -1
  104. package/packages/ui/dist/assets/{swapHorizontalMedium-BmR0H8DC.js → swapHorizontalMedium-oLOjpU2A.js} +1 -1
  105. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-BdP5NGIH.js → swapHorizontalRoundedBold-TJ652QXb.js} +1 -1
  106. package/packages/ui/dist/assets/{swapVertical-CPrGEJPY.js → swapVertical-e0NLyV3x.js} +1 -1
  107. package/packages/ui/dist/assets/{telegram-CxNoZ80Q.js → telegram-WhJHVeoU.js} +1 -1
  108. package/packages/ui/dist/assets/{three-dots-BRa6SBpL.js → three-dots-BlBAOyW-.js} +1 -1
  109. package/packages/ui/dist/assets/{twitch-BC338bG5.js → twitch-BH7vWmPc.js} +1 -1
  110. package/packages/ui/dist/assets/{twitterIcon-BGZmt2i9.js → twitterIcon-As0Nkanp.js} +1 -1
  111. package/packages/ui/dist/assets/{verify-CEstW0zw.js → verify-BEJ0QuLl.js} +1 -1
  112. package/packages/ui/dist/assets/{verify-filled-OkZb0weU.js → verify-filled-B8Ww2N7z.js} +1 -1
  113. package/packages/ui/dist/assets/{w3m-modal-pS09ECwE.js → w3m-modal-5rOSZgOR.js} +1 -1
  114. package/packages/ui/dist/assets/{wallet-BXVKCgC9.js → wallet-CAfC3aml.js} +1 -1
  115. package/packages/ui/dist/assets/{wallet-placeholder-C_kNhB1c.js → wallet-placeholder-4RZI464Z.js} +1 -1
  116. package/packages/ui/dist/assets/{walletconnect-CRKIuUHH.js → walletconnect-BiltKqAe.js} +1 -1
  117. package/packages/ui/dist/assets/{warning-circle-DB2NnwlJ.js → warning-circle-CI4jqpHo.js} +1 -1
  118. package/packages/ui/dist/assets/{x-DT4RmwL5.js → x-FBttjBWO.js} +1 -1
  119. package/packages/ui/dist/index.html +14 -14
  120. package/scripts/check-docs.mjs +262 -262
  121. package/scripts/check-init.mjs +108 -108
  122. package/templates/custom-mandate/.sail/contracts/interfaces/IPermission.sol +18 -18
  123. package/templates/custom-mandate/README.md +116 -116
  124. package/templates/custom-mandate/foundry.toml +8 -8
  125. package/templates/custom-mandate/mandates/BoundedCallPermission.sol +41 -41
  126. package/templates/custom-mandate/mandates/README.md +16 -16
  127. package/templates/custom-mandate/mandates/SailCalldata.sol +118 -118
  128. package/templates/default/.cursor/rules +25 -25
  129. package/templates/default/.env.example +20 -20
  130. package/templates/default/.github/workflows/agent-tick.yml +33 -33
  131. package/templates/default/.sail/README.md +13 -13
  132. package/templates/default/.sail/config.json +10 -10
  133. package/templates/default/AGENTS.md +171 -171
  134. package/templates/default/CLAUDE.md +2 -2
  135. package/templates/default/README.md +16 -16
  136. package/templates/default/_gitignore +13 -13
  137. package/templates/default/docs/PERMISSION_MODEL.md +93 -93
  138. package/templates/default/examples/dca/README.md +16 -16
  139. package/templates/default/examples/dca/agent.ts +174 -174
  140. package/templates/default/examples/dca/mandate.ts +45 -45
  141. package/templates/default/package.json +17 -17
  142. package/templates/default/src/agent.ts +37 -37
  143. package/templates/default/src/config.ts +24 -24
  144. package/templates/default/src/mandate.ts +22 -22
  145. package/templates/default/tsconfig.json +17 -17
  146. package/templates/default/ui/README.md +3 -3
  147. package/templates/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +84 -84
  148. package/templates/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +97 -97
  149. package/templates/lifi-permissions/README.md +53 -53
  150. package/packages/ui/dist/assets/cursor-BDvw-B17.js +0 -3
  151. package/packages/ui/dist/assets/index-BUhrHLpY.js +0 -1775
  152. package/packages/ui/dist/assets/index-Cq02kQmy.css +0 -1
  153. package/scripts/postinstall.js +0 -81
@@ -1,150 +1,150 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
3
-
4
- // ─────────────────────────────────────────────────────────────────────────────
5
- // Protocol : Uniswap V4
6
- // Version : Universal Router + PoolManager (V4 singleton)
7
- // NOTE: V4 is NOT the same as V3. Calldata encoding is completely
8
- // different — do NOT adapt a V3 permission for V4.
9
- // Chain : Unichain mainnet
10
- // Target : Universal Router 0xef740bf23acae26f6492b10de645d6b98dc8eaf3
11
- // (verify on Uniscan before use)
12
- //
13
- // ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
14
- // execute(bytes,bytes[],uint256) selector 0x3593564c / execute(bytes,bytes[]) selector 0x24856bc3
15
- // • target must be UNIVERSAL_ROUTER
16
- // • first command byte (masking the allow-failure MSB) must be V4_SWAP (0x10)
17
- // • exactly one command (single-swap path — disallow multi-hop command strings)
18
- // • V4 action inside must be SWAP_EXACT_IN_SINGLE (0x00)
19
- // • tokenIn (from poolKey, derived by zeroForOne) must be FIXED_CURRENCY_IN
20
- // • tokenOut must be in ALLOWED_CURRENCIES_OUT
21
- // • amountIn ≤ MAX_AMOUNT_IN
22
- // • amountOutMinimum ≥ amountIn × MIN_BPS / 10 000 (slippage floor — see caveat below)
23
- //
24
- // AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
25
- // • real (cross-denomination) slippage — see MIN_BPS caveat in evaluate()
26
- // • swap frequency / cadence
27
- //
28
- // DOCUMENTED LIMITATIONS (on-chain, but intentionally not constrained):
29
- // • hookData is not inspected (hooks can alter swap behavior on-chain; if the
30
- // pool uses a hook that significantly changes execution, this permission cannot
31
- // constrain it. Deploy against pools with address(0) hooks or audited hooks only.)
32
- // • fee tier and tickSpacing within the PoolKey are not constrained here
33
- // (add pool-key checks if you want to restrict to a specific pool)
34
- // • The ALL_CURRENCY_PAIR constant (FIXED_CURRENCY_IN, allowedCurrenciesOut) does
35
- // not constrain which pool is used when multiple pools share the same currency pair
36
- //
37
- // VERIFY BEFORE USE:
38
- // • Confirm Universal Router address on Unichain (shown above; verify on Uniscan).
39
- // • V4_SWAP command byte = 0x10, SWAP_EXACT_IN_SINGLE action = 0x00 — verify
40
- // against deployed UniversalRouter and V4Router on Unichain if contract is updated.
41
- // • PoolKey struct layout (currency0, currency1, fee, tickSpacing, hooks) must
42
- // match the deployed PoolManager on Unichain. If layout changes, update struct.
43
- // • hookData is not bounded. Only use with unhookyed pools or audited, bounded hooks.
44
- // • Calldata revert = false (kernel treats revert as denial) — malformed inputs
45
- // are safely rejected, but verify with actual calldata samples before deployment.
46
- // ─────────────────────────────────────────────────────────────────────────────
47
-
48
- import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
49
-
50
- contract BoundedSwap_UniswapV4_Unichain is IPermission {
51
- bytes32 private constant DISCRIMINATOR = keccak256("BoundedSwap_UniswapV4_Unichain");
52
-
53
- address public immutable UNIVERSAL_ROUTER;
54
- address public immutable FIXED_CURRENCY_IN;
55
- mapping(address => bool) public isAllowedCurrencyOut;
56
- uint256 public immutable MAX_AMOUNT_IN;
57
- uint256 public immutable MIN_BPS;
58
-
59
- // execute(bytes,bytes[],uint256) — with deadline
60
- bytes4 private constant SEL_EXECUTE_DEADLINE = 0x3593564c;
61
- // execute(bytes,bytes[]) — without deadline
62
- bytes4 private constant SEL_EXECUTE = 0x24856bc3;
63
- // Universal Router command byte for V4_SWAP
64
- uint8 private constant CMD_V4_SWAP = 0x10;
65
- // Bit mask to strip the "allow failure" MSB from a command byte
66
- uint8 private constant CMD_MASK = 0x3f;
67
- // V4Router action: SWAP_EXACT_IN_SINGLE
68
- uint8 private constant ACT_SWAP_EXACT_IN_SINGLE = 0x00;
69
-
70
- // PoolKey layout must match the deployed V4 PoolManager on Unichain
71
- struct PoolKey {
72
- address currency0; // Currency — address type in V4
73
- address currency1;
74
- uint24 fee;
75
- int24 tickSpacing;
76
- address hooks; // IHooks — address(0) for unhookyed pools
77
- }
78
-
79
- struct ExactInputSingleParams {
80
- PoolKey poolKey;
81
- bool zeroForOne;
82
- uint128 amountIn;
83
- uint128 amountOutMinimum;
84
- bytes hookData; // not inspected — see limitations header
85
- }
86
-
87
- constructor(
88
- address universalRouter,
89
- address fixedCurrencyIn,
90
- address[] memory allowedCurrenciesOut,
91
- uint256 maxAmountIn,
92
- uint256 minBps
93
- ) {
94
- require(minBps <= 10_000, "minBps > 10000");
95
- UNIVERSAL_ROUTER = universalRouter;
96
- FIXED_CURRENCY_IN = fixedCurrencyIn;
97
- MAX_AMOUNT_IN = maxAmountIn;
98
- MIN_BPS = minBps;
99
- for (uint256 i = 0; i < allowedCurrenciesOut.length; i++) {
100
- isAllowedCurrencyOut[allowedCurrenciesOut[i]] = true;
101
- }
102
- }
103
-
104
- function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
105
- if (ctx.target != UNIVERSAL_ROUTER) return false;
106
- if (ctx.selector != SEL_EXECUTE_DEADLINE && ctx.selector != SEL_EXECUTE) return false;
107
- if (txData.length < 4) return false;
108
-
109
- // Decode the execute call. Both overloads start with (bytes commands, bytes[] inputs).
110
- // abi.decode ignores trailing fields, so decoding as (bytes, bytes[]) works for both.
111
- (bytes memory commands, bytes[] memory inputs) = abi.decode(txData[4:], (bytes, bytes[]));
112
-
113
- // Enforce: exactly one command, and it must be V4_SWAP
114
- if (commands.length != 1) return false;
115
- if ((uint8(commands[0]) & CMD_MASK) != CMD_V4_SWAP) return false;
116
- if (inputs.length != 1) return false;
117
-
118
- // Decode the V4 router call (actions + per-action params)
119
- (bytes memory v4Actions, bytes[] memory v4Params) =
120
- abi.decode(inputs[0], (bytes, bytes[]));
121
-
122
- // Enforce: exactly one V4 action, and it must be SWAP_EXACT_IN_SINGLE
123
- if (v4Actions.length != 1) return false;
124
- if (uint8(v4Actions[0]) != ACT_SWAP_EXACT_IN_SINGLE) return false;
125
- if (v4Params.length != 1) return false;
126
-
127
- // Decode ExactInputSingleParams from the action param.
128
- // hookData is a dynamic bytes field — revert here means false (fail closed).
129
- ExactInputSingleParams memory p = abi.decode(v4Params[0], (ExactInputSingleParams));
130
-
131
- // Derive tokenIn and tokenOut from the PoolKey and zeroForOne flag.
132
- // In V4, currency0 < currency1 (sorted by address). zeroForOne = true means
133
- // trading currency0 for currency1.
134
- address tokenIn = p.zeroForOne ? p.poolKey.currency0 : p.poolKey.currency1;
135
- address tokenOut = p.zeroForOne ? p.poolKey.currency1 : p.poolKey.currency0;
136
-
137
- if (tokenIn != FIXED_CURRENCY_IN) return false;
138
- if (!isAllowedCurrencyOut[tokenOut]) return false;
139
- if (p.amountIn > MAX_AMOUNT_IN) return false;
140
- // Slippage floor: amountOutMinimum ≥ amountIn × MIN_BPS / 10 000.
141
- // WARNING: compares tokenOut against tokenIn base units. For same-price/same-decimal
142
- // pairs this maps to a slippage %. For cross-price pairs it is trivially satisfied —
143
- // real slippage is enforced by the agent off-chain, not by this contract.
144
- if (p.amountOutMinimum < (uint256(p.amountIn) * MIN_BPS) / 10_000) return false;
145
-
146
- return true;
147
- }
148
-
149
- function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
150
- }
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+ // Protocol : Uniswap V4
6
+ // Version : Universal Router + PoolManager (V4 singleton)
7
+ // NOTE: V4 is NOT the same as V3. Calldata encoding is completely
8
+ // different — do NOT adapt a V3 permission for V4.
9
+ // Chain : Unichain mainnet
10
+ // Target : Universal Router 0xef740bf23acae26f6492b10de645d6b98dc8eaf3
11
+ // (verify on Uniscan before use)
12
+ //
13
+ // ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
14
+ // execute(bytes,bytes[],uint256) selector 0x3593564c / execute(bytes,bytes[]) selector 0x24856bc3
15
+ // • target must be UNIVERSAL_ROUTER
16
+ // • first command byte (masking the allow-failure MSB) must be V4_SWAP (0x10)
17
+ // • exactly one command (single-swap path — disallow multi-hop command strings)
18
+ // • V4 action inside must be SWAP_EXACT_IN_SINGLE (0x00)
19
+ // • tokenIn (from poolKey, derived by zeroForOne) must be FIXED_CURRENCY_IN
20
+ // • tokenOut must be in ALLOWED_CURRENCIES_OUT
21
+ // • amountIn ≤ MAX_AMOUNT_IN
22
+ // • amountOutMinimum ≥ amountIn × MIN_BPS / 10 000 (slippage floor — see caveat below)
23
+ //
24
+ // AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
25
+ // • real (cross-denomination) slippage — see MIN_BPS caveat in evaluate()
26
+ // • swap frequency / cadence
27
+ //
28
+ // DOCUMENTED LIMITATIONS (on-chain, but intentionally not constrained):
29
+ // • hookData is not inspected (hooks can alter swap behavior on-chain; if the
30
+ // pool uses a hook that significantly changes execution, this permission cannot
31
+ // constrain it. Deploy against pools with address(0) hooks or audited hooks only.)
32
+ // • fee tier and tickSpacing within the PoolKey are not constrained here
33
+ // (add pool-key checks if you want to restrict to a specific pool)
34
+ // • The ALL_CURRENCY_PAIR constant (FIXED_CURRENCY_IN, allowedCurrenciesOut) does
35
+ // not constrain which pool is used when multiple pools share the same currency pair
36
+ //
37
+ // VERIFY BEFORE USE:
38
+ // • Confirm Universal Router address on Unichain (shown above; verify on Uniscan).
39
+ // • V4_SWAP command byte = 0x10, SWAP_EXACT_IN_SINGLE action = 0x00 — verify
40
+ // against deployed UniversalRouter and V4Router on Unichain if contract is updated.
41
+ // • PoolKey struct layout (currency0, currency1, fee, tickSpacing, hooks) must
42
+ // match the deployed PoolManager on Unichain. If layout changes, update struct.
43
+ // • hookData is not bounded. Only use with unhookyed pools or audited, bounded hooks.
44
+ // • Calldata revert = false (kernel treats revert as denial) — malformed inputs
45
+ // are safely rejected, but verify with actual calldata samples before deployment.
46
+ // ─────────────────────────────────────────────────────────────────────────────
47
+
48
+ import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
49
+
50
+ contract BoundedSwap_UniswapV4_Unichain is IPermission {
51
+ bytes32 private constant DISCRIMINATOR = keccak256("BoundedSwap_UniswapV4_Unichain");
52
+
53
+ address public immutable UNIVERSAL_ROUTER;
54
+ address public immutable FIXED_CURRENCY_IN;
55
+ mapping(address => bool) public isAllowedCurrencyOut;
56
+ uint256 public immutable MAX_AMOUNT_IN;
57
+ uint256 public immutable MIN_BPS;
58
+
59
+ // execute(bytes,bytes[],uint256) — with deadline
60
+ bytes4 private constant SEL_EXECUTE_DEADLINE = 0x3593564c;
61
+ // execute(bytes,bytes[]) — without deadline
62
+ bytes4 private constant SEL_EXECUTE = 0x24856bc3;
63
+ // Universal Router command byte for V4_SWAP
64
+ uint8 private constant CMD_V4_SWAP = 0x10;
65
+ // Bit mask to strip the "allow failure" MSB from a command byte
66
+ uint8 private constant CMD_MASK = 0x3f;
67
+ // V4Router action: SWAP_EXACT_IN_SINGLE
68
+ uint8 private constant ACT_SWAP_EXACT_IN_SINGLE = 0x00;
69
+
70
+ // PoolKey layout must match the deployed V4 PoolManager on Unichain
71
+ struct PoolKey {
72
+ address currency0; // Currency — address type in V4
73
+ address currency1;
74
+ uint24 fee;
75
+ int24 tickSpacing;
76
+ address hooks; // IHooks — address(0) for unhookyed pools
77
+ }
78
+
79
+ struct ExactInputSingleParams {
80
+ PoolKey poolKey;
81
+ bool zeroForOne;
82
+ uint128 amountIn;
83
+ uint128 amountOutMinimum;
84
+ bytes hookData; // not inspected — see limitations header
85
+ }
86
+
87
+ constructor(
88
+ address universalRouter,
89
+ address fixedCurrencyIn,
90
+ address[] memory allowedCurrenciesOut,
91
+ uint256 maxAmountIn,
92
+ uint256 minBps
93
+ ) {
94
+ require(minBps <= 10_000, "minBps > 10000");
95
+ UNIVERSAL_ROUTER = universalRouter;
96
+ FIXED_CURRENCY_IN = fixedCurrencyIn;
97
+ MAX_AMOUNT_IN = maxAmountIn;
98
+ MIN_BPS = minBps;
99
+ for (uint256 i = 0; i < allowedCurrenciesOut.length; i++) {
100
+ isAllowedCurrencyOut[allowedCurrenciesOut[i]] = true;
101
+ }
102
+ }
103
+
104
+ function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
105
+ if (ctx.target != UNIVERSAL_ROUTER) return false;
106
+ if (ctx.selector != SEL_EXECUTE_DEADLINE && ctx.selector != SEL_EXECUTE) return false;
107
+ if (txData.length < 4) return false;
108
+
109
+ // Decode the execute call. Both overloads start with (bytes commands, bytes[] inputs).
110
+ // abi.decode ignores trailing fields, so decoding as (bytes, bytes[]) works for both.
111
+ (bytes memory commands, bytes[] memory inputs) = abi.decode(txData[4:], (bytes, bytes[]));
112
+
113
+ // Enforce: exactly one command, and it must be V4_SWAP
114
+ if (commands.length != 1) return false;
115
+ if ((uint8(commands[0]) & CMD_MASK) != CMD_V4_SWAP) return false;
116
+ if (inputs.length != 1) return false;
117
+
118
+ // Decode the V4 router call (actions + per-action params)
119
+ (bytes memory v4Actions, bytes[] memory v4Params) =
120
+ abi.decode(inputs[0], (bytes, bytes[]));
121
+
122
+ // Enforce: exactly one V4 action, and it must be SWAP_EXACT_IN_SINGLE
123
+ if (v4Actions.length != 1) return false;
124
+ if (uint8(v4Actions[0]) != ACT_SWAP_EXACT_IN_SINGLE) return false;
125
+ if (v4Params.length != 1) return false;
126
+
127
+ // Decode ExactInputSingleParams from the action param.
128
+ // hookData is a dynamic bytes field — revert here means false (fail closed).
129
+ ExactInputSingleParams memory p = abi.decode(v4Params[0], (ExactInputSingleParams));
130
+
131
+ // Derive tokenIn and tokenOut from the PoolKey and zeroForOne flag.
132
+ // In V4, currency0 < currency1 (sorted by address). zeroForOne = true means
133
+ // trading currency0 for currency1.
134
+ address tokenIn = p.zeroForOne ? p.poolKey.currency0 : p.poolKey.currency1;
135
+ address tokenOut = p.zeroForOne ? p.poolKey.currency1 : p.poolKey.currency0;
136
+
137
+ if (tokenIn != FIXED_CURRENCY_IN) return false;
138
+ if (!isAllowedCurrencyOut[tokenOut]) return false;
139
+ if (p.amountIn > MAX_AMOUNT_IN) return false;
140
+ // Slippage floor: amountOutMinimum ≥ amountIn × MIN_BPS / 10 000.
141
+ // WARNING: compares tokenOut against tokenIn base units. For same-price/same-decimal
142
+ // pairs this maps to a slippage %. For cross-price pairs it is trivially satisfied —
143
+ // real slippage is enforced by the agent off-chain, not by this contract.
144
+ if (p.amountOutMinimum < (uint256(p.amountIn) * MIN_BPS) / 10_000) return false;
145
+
146
+ return true;
147
+ }
148
+
149
+ function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
150
+ }
@@ -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
- // 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
- }
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
+ }