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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/AGENTS.md +10 -7
  2. package/LICENSE +1 -1
  3. package/README.md +64 -472
  4. package/package.json +12 -8
  5. package/packages/cli/README.md +1 -1
  6. package/packages/cli/dist/index.cjs +1633 -535
  7. package/packages/cli/dist/server.cjs +19791 -21738
  8. package/packages/sdk/README.md +1 -1
  9. package/packages/sdk/dist/abis/ConfigurablePermission.d.ts +130 -0
  10. package/packages/sdk/dist/abis/ConfigurablePermission.d.ts.map +1 -0
  11. package/packages/sdk/dist/abis/ConfigurablePermission.js +79 -0
  12. package/packages/sdk/dist/abis/ConfigurablePermission.js.map +1 -0
  13. package/packages/sdk/dist/abis/SailGovernance.d.ts +6 -5
  14. package/packages/sdk/dist/abis/SailGovernance.d.ts.map +1 -1
  15. package/packages/sdk/dist/abis/SailGovernance.js +6 -5
  16. package/packages/sdk/dist/abis/SailGovernance.js.map +1 -1
  17. package/packages/sdk/dist/abis/SailKernel.d.ts +42 -0
  18. package/packages/sdk/dist/abis/SailKernel.d.ts.map +1 -1
  19. package/packages/sdk/dist/abis/SailKernel.js +34 -0
  20. package/packages/sdk/dist/abis/SailKernel.js.map +1 -1
  21. package/packages/sdk/dist/abis/index.d.ts +1 -0
  22. package/packages/sdk/dist/abis/index.d.ts.map +1 -1
  23. package/packages/sdk/dist/abis/index.js +1 -0
  24. package/packages/sdk/dist/abis/index.js.map +1 -1
  25. package/packages/sdk/dist/chains.d.ts +0 -7
  26. package/packages/sdk/dist/chains.d.ts.map +1 -1
  27. package/packages/sdk/dist/chains.js +81 -32
  28. package/packages/sdk/dist/chains.js.map +1 -1
  29. package/packages/sdk/dist/client.d.ts +1 -1
  30. package/packages/sdk/dist/client.d.ts.map +1 -1
  31. package/packages/sdk/dist/client.js +137 -16
  32. package/packages/sdk/dist/client.js.map +1 -1
  33. package/packages/sdk/dist/deployments.d.ts +6 -4
  34. package/packages/sdk/dist/deployments.d.ts.map +1 -1
  35. package/packages/sdk/dist/deployments.js +120 -118
  36. package/packages/sdk/dist/deployments.js.map +1 -1
  37. package/packages/sdk/dist/eip712.d.ts +84 -0
  38. package/packages/sdk/dist/eip712.d.ts.map +1 -1
  39. package/packages/sdk/dist/eip712.js +148 -0
  40. package/packages/sdk/dist/eip712.js.map +1 -1
  41. package/packages/sdk/dist/fees.d.ts +59 -10
  42. package/packages/sdk/dist/fees.d.ts.map +1 -1
  43. package/packages/sdk/dist/fees.js +74 -41
  44. package/packages/sdk/dist/fees.js.map +1 -1
  45. package/packages/sdk/dist/index.d.ts +5 -3
  46. package/packages/sdk/dist/index.d.ts.map +1 -1
  47. package/packages/sdk/dist/index.js +4 -3
  48. package/packages/sdk/dist/index.js.map +1 -1
  49. package/packages/sdk/dist/intelligence.d.ts +7 -2
  50. package/packages/sdk/dist/intelligence.d.ts.map +1 -1
  51. package/packages/sdk/dist/intelligence.js +2 -2
  52. package/packages/sdk/dist/intelligence.js.map +1 -1
  53. package/packages/sdk/dist/safe.d.ts +49 -0
  54. package/packages/sdk/dist/safe.d.ts.map +1 -1
  55. package/packages/sdk/dist/safe.js +74 -0
  56. package/packages/sdk/dist/safe.js.map +1 -1
  57. package/packages/sdk/dist/signing.d.ts +14 -0
  58. package/packages/sdk/dist/signing.d.ts.map +1 -1
  59. package/packages/sdk/package.json +6 -2
  60. package/packages/ui/dist/assets/{add-CLs0-B0p.js → add-CBbu2pe_.js} +2 -2
  61. package/packages/ui/dist/assets/{all-wallets-CDOk28sj.js → all-wallets-C1oy4NUu.js} +2 -2
  62. package/packages/ui/dist/assets/{app-store-DVzpi1qN.js → app-store-DKqyf0Zv.js} +1 -1
  63. package/packages/ui/dist/assets/{apple-C9U9iH1l.js → apple-DyoIbCFy.js} +2 -2
  64. package/packages/ui/dist/assets/{arrow-bottom-circle-jxial9Si.js → arrow-bottom-circle-CMS-OqZQ.js} +2 -2
  65. package/packages/ui/dist/assets/arrow-bottom-pUWeEqYu.js +8 -0
  66. package/packages/ui/dist/assets/arrow-left-B81HMJNL.js +8 -0
  67. package/packages/ui/dist/assets/arrow-right-Z5czoKlC.js +8 -0
  68. package/packages/ui/dist/assets/arrow-top-C6ym1oll.js +8 -0
  69. package/packages/ui/dist/assets/{bank-B98it1qN.js → bank-B41nlslf.js} +2 -2
  70. package/packages/ui/dist/assets/{basic-DsgeyPNu.js → basic-CWqOb7CV.js} +177 -177
  71. package/packages/ui/dist/assets/{browser-j4vCeHwE.js → browser-DDKdqoZ9.js} +2 -2
  72. package/packages/ui/dist/assets/{card-CzxbMvEd.js → card-BgOmTajq.js} +2 -2
  73. package/packages/ui/dist/assets/ccip-CtJUKWRd.js +1 -0
  74. package/packages/ui/dist/assets/{checkmark-D5sWBbk3.js → checkmark-CGqQipH4.js} +2 -2
  75. package/packages/ui/dist/assets/{checkmark-bold-BW6w3KCC.js → checkmark-bold-Ci5xf90j.js} +2 -2
  76. package/packages/ui/dist/assets/chevron-bottom-D7Xert17.js +8 -0
  77. package/packages/ui/dist/assets/chevron-left-BGbUebR2.js +8 -0
  78. package/packages/ui/dist/assets/chevron-right-xVsESnHU.js +8 -0
  79. package/packages/ui/dist/assets/chevron-top-DEA9cR6v.js +8 -0
  80. package/packages/ui/dist/assets/{chrome-store-B1OMtzC-.js → chrome-store-BsyI-4PI.js} +2 -2
  81. package/packages/ui/dist/assets/{clock-e0HueehS.js → clock-Dct9oQ3M.js} +2 -2
  82. package/packages/ui/dist/assets/{close-Cy1ytr26.js → close-BvSCnI2y.js} +2 -2
  83. package/packages/ui/dist/assets/{coinPlaceholder-DqJu3ZPV.js → coinPlaceholder-p7RiWkon.js} +2 -2
  84. package/packages/ui/dist/assets/{compass-XDz_N2Hg.js → compass-BUeeZqX4.js} +2 -2
  85. package/packages/ui/dist/assets/{copy-DmDo3Nob.js → copy-C5jXbxLq.js} +2 -2
  86. package/packages/ui/dist/assets/core-CiAm42Mq.js +907 -0
  87. package/packages/ui/dist/assets/cursor-C0T7JWFO.js +3 -0
  88. package/packages/ui/dist/assets/{cursor-transparent-DF2aA10D.js → cursor-transparent-BZp3-JmA.js} +2 -2
  89. package/packages/ui/dist/assets/{desktop-CVydOWLn.js → desktop-BIui_Lzb.js} +2 -2
  90. package/packages/ui/dist/assets/{disconnect-BuN2Whnz.js → disconnect-B3Bxdchp.js} +2 -2
  91. package/packages/ui/dist/assets/{discord-DMNPvTB2.js → discord-VWI0yDyx.js} +2 -2
  92. package/packages/ui/dist/assets/{etherscan-CacW-93f.js → etherscan-BpBvUT9i.js} +2 -2
  93. package/packages/ui/dist/assets/events-DQ172AOg.js +1 -0
  94. package/packages/ui/dist/assets/{exclamation-triangle-B26hIvqO.js → exclamation-triangle-o1TJHIUw.js} +2 -2
  95. package/packages/ui/dist/assets/{extension-B7rI0gv7.js → extension-4RaLvbh5.js} +2 -2
  96. package/packages/ui/dist/assets/external-link-BHIwjDgj.js +8 -0
  97. package/packages/ui/dist/assets/{facebook-BDqbE6kl.js → facebook-Bes5g-wN.js} +2 -2
  98. package/packages/ui/dist/assets/{farcaster-zQ3oQzZ8.js → farcaster-CiJ-352m.js} +2 -2
  99. package/packages/ui/dist/assets/{filters-DCglZYsM.js → filters-DMOteumb.js} +2 -2
  100. package/packages/ui/dist/assets/{github-BH-BYjss.js → github-D2nDec9X.js} +2 -2
  101. package/packages/ui/dist/assets/{google-DYFWxZbC.js → google-lZZlQZPQ.js} +2 -2
  102. package/packages/ui/dist/assets/{help-circle-BXH_DtZ_.js → help-circle-D4cSSziR.js} +1 -1
  103. package/packages/ui/dist/assets/{id-Dr2yTeX1.js → id-BmhXZq5P.js} +2 -2
  104. package/packages/ui/dist/assets/{image-DeJey1Eg.js → image-Dz4FPeET.js} +2 -2
  105. package/packages/ui/dist/assets/index-B07bCKJ8.css +1 -0
  106. package/packages/ui/dist/assets/index-BV8s6ez1.js +1 -0
  107. package/packages/ui/dist/assets/index-BeUp3nOq.js +1 -0
  108. package/packages/ui/dist/assets/index-CvRylmZJ.js +16 -0
  109. package/packages/ui/dist/assets/{index-DVjAiPHI.js → index-DeoQO45V.js} +3 -3
  110. package/packages/ui/dist/assets/index-Et5S3ZaW.js +47 -0
  111. package/packages/ui/dist/assets/index-LiKeH8Cd.js +1780 -0
  112. package/packages/ui/dist/assets/index.es-dfpEui-9.js +26 -0
  113. package/packages/ui/dist/assets/{info-B91pD5Rw.js → info-77RSvIMg.js} +2 -2
  114. package/packages/ui/dist/assets/{info-circle-CySl3_on.js → info-circle-CWsII-NN.js} +2 -2
  115. package/packages/ui/dist/assets/{lightbulb-wwG8LQLo.js → lightbulb-BZXzm7XE.js} +2 -2
  116. package/packages/ui/dist/assets/{mail-D617YZ8y.js → mail-DMG1YqB0.js} +2 -2
  117. package/packages/ui/dist/assets/metamask-sdk-K1x_iqcc.js +542 -0
  118. package/packages/ui/dist/assets/{mobile-CD9KlA0m.js → mobile-DuJfOBSk.js} +2 -2
  119. package/packages/ui/dist/assets/{more-BQ_rpPXo.js → more-Cs9ySK2F.js} +2 -2
  120. package/packages/ui/dist/assets/{network-placeholder-BA7nl9e6.js → network-placeholder-CMDPmB1E.js} +2 -2
  121. package/packages/ui/dist/assets/{nftPlaceholder-D1EaaeDn.js → nftPlaceholder-Cj0f9y2u.js} +2 -2
  122. package/packages/ui/dist/assets/{off-DnGG35_w.js → off-9zSSsm-e.js} +2 -2
  123. package/packages/ui/dist/assets/{play-store-C5u2FvYB.js → play-store-CPwIb8Gj.js} +2 -2
  124. package/packages/ui/dist/assets/{plus-CM4xJkvI.js → plus-BOHQri8o.js} +2 -2
  125. package/packages/ui/dist/assets/{qr-code-kM3FUIdR.js → qr-code-DqhbK276.js} +1 -1
  126. package/packages/ui/dist/assets/{recycle-horizontal-DWrRWIBM.js → recycle-horizontal-BpGM56Qz.js} +2 -2
  127. package/packages/ui/dist/assets/{refresh-CztbjK2d.js → refresh-BPzZlhTP.js} +2 -2
  128. package/packages/ui/dist/assets/{reown-logo-DUHEIodF.js → reown-logo-OCkI0xzo.js} +2 -2
  129. package/packages/ui/dist/assets/{search-BJtb4mn1.js → search-B1JEJvwq.js} +2 -2
  130. package/packages/ui/dist/assets/{secp256k1-BjrTZpQx.js → secp256k1-BqUeV9Cb.js} +1 -1
  131. package/packages/ui/dist/assets/{send-B3_kavG1.js → send-JNsDlNoq.js} +2 -2
  132. package/packages/ui/dist/assets/{swapHorizontal-BrF9POiv.js → swapHorizontal-C3Z1FImK.js} +2 -2
  133. package/packages/ui/dist/assets/{swapHorizontalBold-Dgf8OjXj.js → swapHorizontalBold-Cmdy2iAn.js} +2 -2
  134. package/packages/ui/dist/assets/{swapHorizontalMedium-C6wX0JZj.js → swapHorizontalMedium-DFX57Nn9.js} +2 -2
  135. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-DTAVw6oF.js → swapHorizontalRoundedBold-baB389c8.js} +2 -2
  136. package/packages/ui/dist/assets/{swapVertical-KGlTFNoi.js → swapVertical-DmAYGfK1.js} +2 -2
  137. package/packages/ui/dist/assets/{telegram-DnLrK-4y.js → telegram-BHbYE-nI.js} +2 -2
  138. package/packages/ui/dist/assets/{three-dots-C6_oMJfw.js → three-dots-BwRWl6qJ.js} +2 -2
  139. package/packages/ui/dist/assets/{twitch-DvPqPD4f.js → twitch-Drx0q6B6.js} +2 -2
  140. package/packages/ui/dist/assets/{twitterIcon-BZdk82Am.js → twitterIcon-AuJ2KQ6p.js} +2 -2
  141. package/packages/ui/dist/assets/{verify-CbESD5G0.js → verify-Dkrd3q7o.js} +2 -2
  142. package/packages/ui/dist/assets/{verify-filled-BRjTrLxw.js → verify-filled-B9e3-nz-.js} +2 -2
  143. package/packages/ui/dist/assets/{w3m-modal-D9b2TGHc.js → w3m-modal-DtkEjQA7.js} +1 -1
  144. package/packages/ui/dist/assets/{wallet-B87E5UId.js → wallet-CqYzSiBd.js} +2 -2
  145. package/packages/ui/dist/assets/{wallet-placeholder-DmssSbYf.js → wallet-placeholder-B05kTN_K.js} +2 -2
  146. package/packages/ui/dist/assets/{walletconnect-CZkLKWMZ.js → walletconnect-BwoPXLaS.js} +3 -3
  147. package/packages/ui/dist/assets/{warning-circle-BPTpHB1P.js → warning-circle-DK9Ai8rT.js} +2 -2
  148. package/packages/ui/dist/assets/{x-D_dEtEDU.js → x-BNdh5DVW.js} +2 -2
  149. package/packages/ui/dist/index.html +2 -2
  150. package/templates/default/.agents/skills/sail-mandates/SKILL.md +13 -1
  151. package/templates/default/.agents/skills/sail-mandates/references/approvals.md +3 -3
  152. package/templates/default/.agents/skills/sail-mandates/references/examples-index.md +3 -3
  153. package/templates/default/.agents/skills/sail-onboarding/SKILL.md +2 -2
  154. package/templates/default/.agents/skills/sail-project-info/SKILL.md +2 -0
  155. package/templates/default/.agents/skills/sail-servers/SKILL.md +1 -0
  156. package/templates/default/.agents/skills/sail-swap-quote/SKILL.md +76 -0
  157. package/templates/default/.agents/skills/sail-template-approve-batch/SKILL.md +98 -0
  158. package/templates/default/.agents/skills/sail-template-borrow/SKILL.md +68 -0
  159. package/templates/default/.agents/skills/sail-template-deposit/SKILL.md +61 -0
  160. package/templates/default/.agents/skills/sail-template-swap/SKILL.md +207 -0
  161. package/templates/default/.agents/skills/sail-template-swap-no-oracle/SKILL.md +100 -0
  162. package/templates/default/.agents/skills/sail-template-transfer/SKILL.md +52 -0
  163. package/templates/default/.agents/skills/sail-template-withdraw/SKILL.md +50 -0
  164. package/templates/default/.agents/skills/sail-templates/SKILL.md +142 -0
  165. package/templates/default/.agents/skills/sail-templates/catalog.mjs +200 -0
  166. package/templates/default/.agents/skills/sail-templates/deployed.json +133 -0
  167. package/templates/default/.agents/skills/sail-templates/oracles/IOracle.sol +13 -0
  168. package/templates/default/.agents/skills/sail-templates/oracles/UniV3TwapOracle.sol +114 -0
  169. package/templates/default/.agents/skills/sail-templates/oracles/libs/FullMath.sol +62 -0
  170. package/templates/default/.agents/skills/sail-templates/oracles/libs/TickMath.sol +44 -0
  171. package/templates/default/.agents/skills/sail-templates/references/config-schemas.md +139 -0
  172. package/templates/default/.agents/skills/sail-templates/references/reuse-flow.md +139 -0
  173. package/templates/default/.agents/skills/sail-token-resolve/SKILL.md +174 -0
  174. package/templates/default/.env.example +9 -0
  175. package/templates/default/AGENTS.md +21 -2
  176. package/templates/default/docs/PERMISSION_MODEL.md +2 -2
  177. package/{examples → templates/default/examples}/custom-mandate/.sail/contracts/interfaces/IPermission.sol +4 -0
  178. package/{examples → templates/default/examples}/custom-mandate/README.md +4 -1
  179. package/{examples → templates/default/examples}/permissions/interfaces/IBatchPermission.sol +2 -0
  180. package/{examples → templates/default/examples}/permissions/interfaces/IPermission.sol +4 -0
  181. package/templates/default/scripts/quote-swap.mjs +211 -0
  182. package/templates/default/scripts/resolve-token.mjs +992 -0
  183. package/templates/default/scripts/shared-template-addr.mjs +110 -0
  184. package/templates/default/test/BoundedCallPermission.t.sol +2 -1
  185. package/docs/PERMISSION_MODEL.md +0 -93
  186. package/examples/README.md +0 -24
  187. package/examples/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +0 -84
  188. package/examples/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +0 -97
  189. package/examples/lifi-permissions/README.md +0 -53
  190. package/packages/cli/dist/mcp.cjs +0 -15534
  191. package/packages/ui/dist/assets/arrow-bottom-DjQ4jGMe.js +0 -8
  192. package/packages/ui/dist/assets/arrow-left-BjAyI8D1.js +0 -8
  193. package/packages/ui/dist/assets/arrow-right-BlFO4Tcx.js +0 -8
  194. package/packages/ui/dist/assets/arrow-top-DJ3oh3DH.js +0 -8
  195. package/packages/ui/dist/assets/ccip-U9yiu_EK.js +0 -1
  196. package/packages/ui/dist/assets/chevron-bottom-BbHhmTQq.js +0 -8
  197. package/packages/ui/dist/assets/chevron-left-D8BDH4Wo.js +0 -8
  198. package/packages/ui/dist/assets/chevron-right-BQuQk5zc.js +0 -8
  199. package/packages/ui/dist/assets/chevron-top-1oeSXkx4.js +0 -8
  200. package/packages/ui/dist/assets/core-DbkMGSYW.js +0 -907
  201. package/packages/ui/dist/assets/cursor-DRFqL8nZ.js +0 -3
  202. package/packages/ui/dist/assets/events-CZkVDFih.js +0 -1
  203. package/packages/ui/dist/assets/external-link-CX8skCbH.js +0 -8
  204. package/packages/ui/dist/assets/fallback-DWQYKf3l.js +0 -1
  205. package/packages/ui/dist/assets/index-BP-UuQ8g.js +0 -1
  206. package/packages/ui/dist/assets/index-BksbyFjf.js +0 -1
  207. package/packages/ui/dist/assets/index-C55-Ne92.js +0 -16
  208. package/packages/ui/dist/assets/index-D6_Zei-R.js +0 -1790
  209. package/packages/ui/dist/assets/index-DZ2C2rHq.css +0 -1
  210. package/packages/ui/dist/assets/index-uSdXE5b_.js +0 -1
  211. package/packages/ui/dist/assets/index.es-dZlNPpfR.js +0 -26
  212. package/packages/ui/dist/assets/metamask-sdk-RbLbxUgF.js +0 -542
  213. package/packages/ui/dist/assets/native-CJ5et6AR.js +0 -1
  214. package/packages/ui/dist/assets/parseSignature-E29x8poM.js +0 -1
  215. package/scripts/check-docs.mjs +0 -309
  216. package/scripts/check-init.mjs +0 -123
  217. package/scripts/check-update.mjs +0 -177
  218. package/scripts/clean.mjs +0 -17
  219. package/scripts/pack.mjs +0 -21
  220. /package/{examples → templates/default/examples}/custom-mandate/foundry.toml +0 -0
  221. /package/{examples → templates/default/examples}/custom-mandate/mandates/BoundedCallPermission.sol +0 -0
  222. /package/{examples → templates/default/examples}/custom-mandate/mandates/README.md +0 -0
  223. /package/{examples → templates/default/examples}/custom-mandate/mandates/SailCalldata.sol +0 -0
  224. /package/{examples → templates/default/examples}/permissions/BoundedApproveAndCallBatch.sol +0 -0
  225. /package/{examples → templates/default/examples}/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +0 -0
  226. /package/{examples → templates/default/examples}/permissions/BoundedLimitless_Base.sol +0 -0
  227. /package/{examples → templates/default/examples}/permissions/BoundedPerp_GMXv2_Arbitrum.sol +0 -0
  228. /package/{examples → templates/default/examples}/permissions/BoundedStake_Venice_Base.sol +0 -0
  229. /package/{examples → templates/default/examples}/permissions/BoundedSupply_AaveV3_Arbitrum.sol +0 -0
  230. /package/{examples → templates/default/examples}/permissions/BoundedSwapNative_UniswapV3_Base.sol +0 -0
  231. /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV3_Base.sol +0 -0
  232. /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV4_Unichain.sol +0 -0
  233. /package/{examples → templates/default/examples}/permissions/BoundedTransfer_ERC20_Ethereum.sol +0 -0
  234. /package/{examples → templates/default/examples}/permissions/BoundedVault_ERC4626_Base.sol +0 -0
  235. /package/{examples → templates/default/examples}/permissions/README.md +0 -0
  236. /package/{examples → templates/default/examples}/permissions/SailCalldata.sol +0 -0
  237. /package/{examples → templates/default/examples}/permissions/foundry.toml +0 -0
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: sail-template-swap-no-oracle
3
+ description: Gate an SMA's DEX swaps for tokens that have NO oracle by REUSING the shared SwapPermissionNoOracle singleton (Protocol/contracts/templates/SwapPermissionNoOracle.sol) — register + configure, no per-SMA deploy. Use for a bounded swap mandate on Uniswap V3/V3-02/V2 (and forks) where no manipulation-resistant price feed exists: router + token-in/out allowlists, a per-tx cap, recipient pinned to the SMA, and a per-pair live-pool "hallucination band". NOT manipulation-resistant — if the token HAS an oracle use sail-template-swap instead; for the LI.FI aggregator author a bespoke permission via sail-mandates. Deployed on all 11 Sailor-bundled chains (recorded in sail-templates/deployed.json).
4
+ compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires SwapPermissionNoOracle deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
5
+ metadata:
6
+ workspace: sailor-harness
7
+ classification: generic
8
+ status: draft
9
+ origin: Protocol/contracts/templates/SwapPermissionNoOracle.sol
10
+ ---
11
+
12
+ # sail-template-swap-no-oracle — bounded DEX swap for oracle-less tokens
13
+
14
+ Reuse the shared **`SwapPermissionNoOracle`** singleton instead of authoring/deploying a swap
15
+ contract. Register its address on the SMA and `configure()` your routers, token allowlists, cap,
16
+ and a **reference pool per tradeable pair**. Family overview + flow:
17
+ [`sail-templates`](../sail-templates/SKILL.md). For the oracle-gated tier see
18
+ [`sail-template-swap`](../sail-template-swap/SKILL.md).
19
+
20
+ > ⚠️ **Pick the right tier.** This template provides **NO manipulation-resistant slippage
21
+ > protection**. Its price band reads a *single pool's live spot price*, which any party can move
22
+ > within the same transaction (flash-loan / sandwich / MEV). It only catches an **honest mistake**
23
+ > — a confused manager/agent quoting a wildly wrong number (a "hallucination guard"). If the token
24
+ > has an independent, freshness-checked price feed, use [`sail-template-swap`](../sail-template-swap/SKILL.md)
25
+ > instead. Only reach for this when no such oracle exists.
26
+
27
+ ## What it enforces (per account, from source)
28
+
29
+ Supported selectors (any other ⇒ `false`) — identical decode region to `SwapPermission`:
30
+
31
+ | Selector | Function | Venue |
32
+ |---|---|---|
33
+ | `0x414bf389` | `exactInputSingle((tokenIn,tokenOut,fee,recipient,deadline,amountIn,amountOutMinimum,sqrtPriceLimitX96))` | Uniswap V3 SwapRouter |
34
+ | `0x04e45aaf` | `exactInputSingle((tokenIn,tokenOut,fee,recipient,amountIn,amountOutMinimum,sqrtPriceLimitX96))` | Uniswap V3 SwapRouter02 |
35
+ | `0x38ed1739` | `swapExactTokensForTokens(amountIn,amountOutMin,path[],to,deadline)` | Uniswap V2 Router |
36
+
37
+ Structural invariants: `target ∈ routers`; `tokenIn`/`path[0] ∈ tokensIn`; `tokenOut`/`path[last]
38
+ ∈ tokensOut`; `recipient`/`to == SMA` (funds can't leave the account); `amountIn ≤ maxAmountPerTx`.
39
+
40
+ **Price band (the only difference from `SwapPermission`):** for the swap's `(tokenIn, tokenOut)`
41
+ pair, read the live spot price of the operator-named reference pool and require
42
+ `amountOutMin ≥ expectedOut × (10_000 − toleranceBps)/10_000` **and** `amountOutMin > 0`. Fails
43
+ closed (denies) if the pool is missing, unreadable, illiquid, doesn't price the pair, or the floor
44
+ truncates to zero.
45
+
46
+ > **V2 intermediate hops are NOT checked** — only `path[0]`/`path[last]`. Restrict to V3 or ensure
47
+ > any plausible intermediate token is acceptable. Does NOT cover Universal Router, Uniswap V4, or
48
+ > aggregators (opaque calldata).
49
+
50
+ ## Config blob (authoritative — `config-schemas.md`)
51
+
52
+ ```
53
+ abi.encode(address[] routers, address[] tokensIn, address[] tokensOut,
54
+ uint256 maxAmountPerTx, ReferencePool[] referencePools)
55
+
56
+ struct ReferencePool { address tokenIn; address tokenOut; address pool;
57
+ PoolKind kind /* 0=V2, 1=V3 */; uint256 toleranceBps; }
58
+ ```
59
+ | Field | Notes |
60
+ |---|---|
61
+ | `routers` | DEX routers the agent may call |
62
+ | `tokensIn` / `tokensOut` | sell-side / buy-side allowlists |
63
+ | `maxAmountPerTx` | per-swap cap, base units (e.g. `25_000_000` = 25 USDC) |
64
+ | `referencePools` | one per directional `(tokenIn, tokenOut)` pair — see coverage rule below |
65
+ | `ReferencePool.pool` | a V2 pair or V3 pool that actually prices the pair (validated at configure) |
66
+ | `ReferencePool.kind` | `0` = V2 (`getReserves`), `1` = V3 (`slot0`+`liquidity`) |
67
+ | `ReferencePool.toleranceBps` | band width, **≤ 5_000 (50%)** or configure reverts. e.g. `1_000` = 10% |
68
+
69
+ **Strict coverage rule:** `configure()` reverts `MissingReferencePool` unless every non-self
70
+ directional combination of `tokensIn × tokensOut` has a `ReferencePool`. The pool's
71
+ `token0()`/`token1()` must match the pair (orientation is precomputed at configure time), else it
72
+ reverts `PoolTokenMismatch`. `pool == 0` reverts `ZeroPool`.
73
+
74
+ Size the cap with `uniswap-v3-quote`. Verify the SDK encoder's param tuple matches the source blob
75
+ above before using it (see [`sail-templates`](../sail-templates/SKILL.md) notes).
76
+
77
+ ## Steps
78
+
79
+ Register → configure → simulate → reconfigure mechanics (and the encoding gotcha) live in
80
+ [`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
81
+ `sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
82
+ permission live.
83
+
84
+ Template-specific bits:
85
+
86
+ - **Singleton:** `SwapPermissionNoOracle` — `node SKILLS/sail-templates/catalog.mjs --chain <id>`.
87
+ - **Spec to confirm:** sell/buy tokens, per-swap cap, router/fee tier, recipient = SMA, and for each
88
+ pair the reference pool + tolerance. **Explicitly confirm the no-oracle trade-off** with the user:
89
+ this is a hallucination guard, not slippage/manipulation protection.
90
+ - **Blob:** `abi.encode(routers[], tokensIn[], tokensOut[], maxAmountPerTx, ReferencePool[])` —
91
+ **flat params, no wrapper**; `ReferencePool{tokenIn, tokenOut, pool, kind, toleranceBps}`.
92
+ - **Simulate (mandatory — unaudited example):** an allowed swap passes; wrong recipient, over-cap,
93
+ disallowed token, and an `amountOutMin` below the pool-implied floor are rejected.
94
+
95
+ ## Notes
96
+ - The band is **not** manipulation-resistant — re-state this whenever authorizing. Prefer
97
+ [`sail-template-swap`](../sail-template-swap/SKILL.md) wherever an oracle exists.
98
+ - `toleranceBps` is capped at 50% in source; a wider band is meaningless and rejected at configure.
99
+ - Unaudited example — step 4 is mandatory.
100
+ - Aggregator routing (opaque calldata) → author a bespoke permission via [`sail-mandates`](../sail-mandates/SKILL.md).
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: sail-template-transfer
3
+ description: Gate an SMA's ERC-20 transfers by REUSING the shared TransferPermission singleton (Protocol/contracts/templates/TransferPermission.sol) — register + configure, no per-SMA deploy. Use to let an agent move approved tokens, within a per-tx cap, only to a recipient allowlist (partner protocols, CEX deposit addrs, co-manager wallets). For returning funds to a single fixed Safe, prefer sail-template-withdraw. NOTE: `sailor mandate attach` only registers — you must also configure per-account (see steps).
4
+ compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires TransferPermission deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
5
+ metadata:
6
+ workspace: sailor-harness
7
+ classification: generic
8
+ status: draft
9
+ origin: Protocol/contracts/templates/TransferPermission.sol
10
+ ---
11
+
12
+ # sail-template-transfer — bounded transfer to an allowlist via the shared singleton
13
+
14
+ Reuse the shared **`TransferPermission`** singleton. Family overview + flow:
15
+ [`sail-templates`](../sail-templates/SKILL.md).
16
+
17
+ ## What it enforces (per account, from source)
18
+
19
+ Selectors: `0xa9059cbb` `transfer`, `0x23b872dd` `transferFrom`. Invariants: `value == 0`;
20
+ `allowedRecipients`/`allowedTokens` must be non-empty with no zero addresses
21
+ (`EmptyAllowlist`/`ZeroAddress` revert at configure otherwise); `target (token) ∈
22
+ allowedTokens`; `to ∈ allowedRecipients`; `amount ≤ maxAmountPerTx`; **`transferFrom` requires
23
+ `from == SMA`** (cannot pull from third parties that approved the Safe). Max 50 entries per
24
+ allowlist.
25
+
26
+ ## Config blob (authoritative — `config-schemas.md`)
27
+
28
+ ```
29
+ abi.encode(address[] allowedRecipients, address[] allowedTokens, uint256 maxAmountPerTx)
30
+ ```
31
+ | Field | Notes |
32
+ |---|---|
33
+ | `allowedRecipients` | addresses the agent may send to (≤ 50) |
34
+ | `allowedTokens` | ERC-20 tokens the agent may move (≤ 50) |
35
+ | `maxAmountPerTx` | per-transfer cap, base units |
36
+
37
+ > Recipient/token allowlists are mutable via `reconfigure` (permissionSigner). In production
38
+ > use a multisig/timelock as the signer — it can widen the allowlist. Vet recipient contracts.
39
+
40
+ ## Steps
41
+
42
+ Register → configure → simulate → reconfigure mechanics (and the encoding gotcha) live in
43
+ [`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
44
+ `sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
45
+ permission live. Template-specific bits:
46
+
47
+ - **Singleton:** `TransferPermission` — `node SKILLS/sail-templates/catalog.mjs --chain <id>`.
48
+ - **Spec to confirm:** recipients, tokens, cap.
49
+ - **Blob:** `abi.encode(allowedRecipients[], allowedTokens[], maxAmountPerTx)` — **flat params, no
50
+ wrapper**.
51
+ - **Simulate (mandatory — unaudited example):** allowed recipient/token within cap passes; off-list
52
+ recipient, wrong token, over-cap, or `transferFrom` with `from != SMA` is rejected.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: sail-template-withdraw
3
+ description: Gate an SMA's ERC-20 withdrawals by REUSING the shared WithdrawPermission singleton (Protocol/contracts/templates/WithdrawPermission.sol) — register + configure, no per-SMA deploy. Use when the agent may move approved tokens, within a per-tx cap, only to ONE fixed recipient (typically the owner's Safe) for safe-to-safe consolidation. For a mutable multi-recipient allowlist, use sail-template-transfer instead. NOTE: `sailor mandate attach` only registers — you must also configure per-account (see steps).
4
+ compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires WithdrawPermission deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
5
+ metadata:
6
+ workspace: sailor-harness
7
+ classification: generic
8
+ status: draft
9
+ origin: Protocol/contracts/templates/WithdrawPermission.sol
10
+ ---
11
+
12
+ # sail-template-withdraw — bounded withdraw to a fixed recipient via the shared singleton
13
+
14
+ Reuse the shared **`WithdrawPermission`** singleton. Family overview + flow:
15
+ [`sail-templates`](../sail-templates/SKILL.md).
16
+
17
+ ## What it enforces (per account, from source)
18
+
19
+ Selectors: `0xa9059cbb` `transfer`, `0x23b872dd` `transferFrom`. Invariants: `value == 0`;
20
+ `tokens` must be non-empty and `allowedRecipient` non-zero, with no zero-address tokens
21
+ (`EmptyAllowlist`/`ZeroAddress` revert at configure otherwise); `target (token) ∈ tokens`;
22
+ `to == allowedRecipient` (a single address from config); `amount ≤ maxAmountPerTx`;
23
+ **`transferFrom` requires `from == SMA`**.
24
+
25
+ > Single recipient per config. To change it, `reconfigure` with a new blob (no redeploy). This
26
+ > is the consolidation primitive — funds can only flow to the one approved address.
27
+
28
+ ## Config blob (authoritative — `config-schemas.md`)
29
+
30
+ ```
31
+ abi.encode(address[] tokens, address allowedRecipient, uint256 maxAmountPerTx)
32
+ ```
33
+ | Field | Notes |
34
+ |---|---|
35
+ | `tokens` | ERC-20 tokens the agent may move |
36
+ | `allowedRecipient` | the single destination (e.g. owner's Safe) |
37
+ | `maxAmountPerTx` | per-withdraw cap, base units |
38
+
39
+ ## Steps
40
+
41
+ Register → configure → simulate → reconfigure mechanics (and the encoding gotcha) live in
42
+ [`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
43
+ `sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
44
+ permission live. Template-specific bits:
45
+
46
+ - **Singleton:** `WithdrawPermission` — `node SKILLS/sail-templates/catalog.mjs --chain <id>`.
47
+ - **Spec to confirm:** tokens, the single recipient, cap.
48
+ - **Blob:** `abi.encode(tokens[], allowedRecipient, maxAmountPerTx)` — **flat params, no wrapper**.
49
+ - **Simulate (mandatory — unaudited example):** withdraw to the recipient within cap passes; any
50
+ other `to`, wrong token, over-cap, or `transferFrom` with `from != SMA` is rejected.
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: sail-templates
3
+ description: Registry + reuse guide for Sail's shared permission templates (Protocol/contracts/templates). Load this when you need to know which permission primitives are available as reusable templates and want to gate an SMA's mandate by REUSING a configurable singleton — deploy once, then register + configure per SMA (no per-SMA deploy). Covers swap (oracle-gated and no-oracle), borrow, transfer, deposit, withdraw, and approve-and-call-batch. All seven are deployed today, at the SAME address on every supported chain (CREATE2); addresses live in deployed.json.
4
+ compatibility: Node 18+; a Sailor project (`@sail/sdk`, `sailor` CLI); read access to the workspace `Protocol/contracts/templates/` (or set SAIL_PROTOCOL_DIR).
5
+ metadata:
6
+ workspace: sailor-harness
7
+ classification: generic
8
+ status: draft
9
+ origin: Protocol/contracts/templates source contracts
10
+ ---
11
+
12
+ # Sail shared permission templates — registry & reuse
13
+
14
+ `Protocol/contracts/templates/` holds seven **shared permission templates** — reusable
15
+ `IPermission` patterns covering the most common DeFi primitives. Each extends
16
+ `ConfigurablePermission`, which means it is a **configurable singleton**:
17
+
18
+ > Deploy the contract **once per chain**. Every SMA then reuses that single address via
19
+ > **register + configure** — its own routers / tokens / caps live in the singleton's
20
+ > `mapping(address => …)`. **No per-SMA deploy, no per-SMA audit, minimal gas.**
21
+
22
+ > **Deployment status (2026-07-01, by `0xB01dCE…815B6`):** **all seven** templates are live on
23
+ > every chain Sailor bundles — Ethereum, Base, Arbitrum, Optimism, Unichain, BSC, World Chain,
24
+ > HyperEVM, MegaETH, Base Sepolia, and Eth Sepolia (11 chains) — all bound to the current
25
+ > CREATE2 kernel `0x38b508756c976e876EFF05a29E731A4d348BA6ED`. This is a CREATE2 deploy with a
26
+ > global salt, so the kernel and every template address are **identical on every chain**. Live
27
+ > addresses are in [`deployed.json`](deployed.json) (keyed by chainId → contract name, though
28
+ > the value is the same for all 11 keys). This supersedes the prior 2026-06-09 deploy (kernel
29
+ > `0x02ABC18B65A328de2e749F56ba79ACF2718a6659`) — those addresses are dead.
30
+
31
+ > ⚠️ **Audit scope.** These are marked *"UNAUDITED EXAMPLE — NOT PART OF THE TRUSTED CORE"* in
32
+ > source. The kernel runs them safely (staticcall + gas cap + fail-closed) but does not verify
33
+ > their internal logic. Always `sailor mandate simulate` against your SMA before authorizing.
34
+
35
+ > ⚠️ **CRITICAL — "register" ≠ "configure" in the shipped CLI.** A shared template is useless
36
+ > until it is both **registered** on the kernel AND **configured** per-account. `sailor mandate
37
+ > attach` does **only the register** half (it submits `RegisterPermission` and nothing more). A
38
+ > registered-but-unconfigured singleton has `isConfigured == false` and the kernel **denies every
39
+ > call**. Run `sailor mandate configure` as the separate second step (see the reuse flow below).
40
+
41
+ ## The seven templates
42
+
43
+ | Primitive | Contract | Deployed? | Skill |
44
+ |---|---|---|---|
45
+ | DEX swap (oracle band) | `SwapPermission` | ✅ 11 chains | [`sail-template-swap`](../sail-template-swap/SKILL.md) |
46
+ | DEX swap (no oracle) | `SwapPermissionNoOracle` | ✅ 11 chains | [`sail-template-swap-no-oracle`](../sail-template-swap-no-oracle/SKILL.md) |
47
+ | Lending borrow | `BorrowPermission` | ✅ 11 chains | [`sail-template-borrow`](../sail-template-borrow/SKILL.md) |
48
+ | Transfer (allowlist) | `TransferPermission` | ✅ 11 chains | [`sail-template-transfer`](../sail-template-transfer/SKILL.md) |
49
+ | Vault/lending deposit | `DepositPermission` | ✅ 11 chains | [`sail-template-deposit`](../sail-template-deposit/SKILL.md) |
50
+ | Withdraw to fixed addr | `WithdrawPermission` | ✅ 11 chains | [`sail-template-withdraw`](../sail-template-withdraw/SKILL.md) |
51
+ | Approve+call batch | `ApproveAndCallBatchPermission` | ✅ 11 chains | [`sail-template-approve-batch`](../sail-template-approve-batch/SKILL.md) |
52
+
53
+ Authoritative config tuples + enforced invariants (from source):
54
+ [references/config-schemas.md](references/config-schemas.md).
55
+
56
+ ### One source of truth per concern
57
+
58
+ This hub is canonical for everything shared; each spoke skill carries only what's unique to its
59
+ primitive (selectors, invariants, config blob, probe cases) and defers the rest here. When
60
+ something changes, edit it in ONE place:
61
+
62
+ | Concern | Lives in | Spokes do |
63
+ |---|---|---|
64
+ | Which templates exist | source contracts, auto-detected by [`catalog.mjs`](catalog.mjs) | — |
65
+ | Deployed addresses (per chain) | [`deployed.json`](deployed.json) | link here |
66
+ | Register → configure → simulate flow | [references/reuse-flow.md](references/reuse-flow.md) | link here, don't restate |
67
+ | Config tuples + invariants | [references/config-schemas.md](references/config-schemas.md) | quote only their own tuple |
68
+
69
+ So a change to the CLI flow is a single edit to
70
+ `reuse-flow.md`, not seven spoke edits. The deliberately-fuller [`sail-template-swap`](../sail-template-swap/SKILL.md)
71
+ is the worked-example exemplar; the other spokes stay thin.
72
+
73
+ ## Step 1 — see what exists / deployment status
74
+
75
+ The list is auto-detected from source; deployment status comes from `deployed.json`:
76
+
77
+ ```bash
78
+ node SKILLS/sail-templates/catalog.mjs # all templates
79
+ node SKILLS/sail-templates/catalog.mjs --chain 8453 # status on one chain
80
+ node SKILLS/sail-templates/catalog.mjs --json # machine-readable
81
+ ```
82
+
83
+ (The catalog warns if a new source contract appears with no curated metadata — a signal to
84
+ add it here and write a skill.)
85
+
86
+ ## Step 2 — the reuse flow (per template)
87
+
88
+ > The on-chain **intended** design is one signed call (`MandateFactory.attach`) that registers
89
+ > the address on the kernel and writes the per-account config together. The **shipped** CLI
90
+ > does not implement that combined call yet, so today these are two separate steps:
91
+ > `sailor mandate attach` (register) followed by `sailor mandate configure` (configure). The
92
+ > flow below describes what actually works now.
93
+
94
+ 1. **Deploy once** (one-time, Protocol team / `DeploySharedTemplates`, CREATE2 — same address on
95
+ every chain) → record the address in `deployed.json`. (Already done for all seven.)
96
+ 2. **Build the config blob** for your SMA (per-primitive skill gives the exact params).
97
+ 3. **Register** the singleton address on the SMA's kernel (this does NOT configure it):
98
+ ```bash
99
+ sailor mandate attach --address <DEPLOYED_ADDRESS> --sma <SMA> --label "<primitive>"
100
+ ```
101
+ A comma-separated `--address` list registers several in one signature (`attachBatch`).
102
+ 4. **Configure** the per-account bounds — this is the step that makes the permission actually
103
+ allow calls:
104
+ ```bash
105
+ sailor mandate configure --address <DEPLOYED_ADDRESS> --sma <SMA> \
106
+ --template <TemplateName> --args-file ./config.json
107
+ ```
108
+ This drives `configureDirect(account, <config blob>)` as an owner transaction (the owner is
109
+ the `permissionSigner`), pre-flighted with an `eth_call` before any signing or gas. See
110
+ [references/reuse-flow.md](references/reuse-flow.md) for the exact encoding gotcha and the
111
+ signing-station path.
112
+ 5. **Simulate** off-chain (no gas) — prove allowed calls pass and bad ones fail:
113
+ ```bash
114
+ sailor mandate simulate --address <DEPLOYED_ADDRESS> --sma <SMA> --calls ./probe.json
115
+ ```
116
+ 6. **Reconfigure** later (new caps / allowlists) via `configure`/`configureDirect` on the same
117
+ singleton — same address, no re-register. (On-chain `MandateFactory.reconfigure` is the
118
+ intended batch path for this.)
119
+
120
+ Full mechanics (EIP-712 sigs, `configure`/`configureDirect`, `attachBatch`, `replace`,
121
+ `detach`, and the register-vs-configure split): [references/reuse-flow.md](references/reuse-flow.md).
122
+
123
+ ## When NOT to use these
124
+
125
+ No shared template covers: **perps** (GMX, Gains, Synthetix), **prediction markets** (Azuro,
126
+ Limitless), or the **LI.FI aggregator**. For those, author a bespoke `IPermission` via
127
+ `sailor mandate deploy` — see [`sail-mandates`](../sail-mandates/SKILL.md) for the
128
+ authoring flow and `examples/permissions/` for worked per-protocol patterns.
129
+
130
+ ## Notes
131
+
132
+ - The kernel and every template address are identical on every chain (CREATE2 global salt) —
133
+ but that pairing is generation-specific: a template address from one deploy generation (e.g.
134
+ the superseded 2026-06-09 deploy) is never valid against a different generation's kernel.
135
+ Always resolve both from the same `deployed.json` entry / SDK deployment.
136
+ - Caps/amounts are in **base units**. Size them with `sail-pyth-prices` / `uniswap-v3-quote`.
137
+ - The config encoder must match each contract's `_applyConfig` decode exactly — use the SDK
138
+ builder under `@sail/sdk/templates` **only after** verifying its param tuple equals the
139
+ source blob in `config-schemas.md` (the SDK builders track a previously-deployed set and may
140
+ differ from these source contracts).
141
+ - **CLI gap:** `sailor mandate attach` (register) and `sailor mandate configure` (configure) are
142
+ still separate commands — no combined one-step call ships yet. Always run both.
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+ // sail-templates / catalog.mjs
3
+ // Tracks the shared permission templates that live in
4
+ // Protocol/contracts/templates/*.sol
5
+ // These are configurable singletons (extend ConfigurablePermission): deploy once
6
+ // per chain, then every SMA reuses that address via REGISTER + CONFIGURE (attach only
7
+ // registers on the kernel; configure writes the per-account bounds — see SKILL.md).
8
+ // All seven are deployed (2026-07-01, CREATE2 global salt) — same address on every chain.
9
+ // Deployment status is read from deployed.json next to this file.
10
+ //
11
+ // The template LIST is auto-detected from source (so it tracks additions/removals),
12
+ // the rich per-template detail is curated below, and deployment status is read from
13
+ // deployed.json.
14
+ //
15
+ // Usage:
16
+ // node catalog.mjs # human view, all detected templates
17
+ // node catalog.mjs --json # machine-readable
18
+ // node catalog.mjs --chain 8453 # deployment status for one chain
19
+ // node catalog.mjs --protocol /path/to/Protocol # override Protocol dir
20
+
21
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
22
+ import { dirname, join, resolve } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+
25
+ const HERE = dirname(fileURLToPath(import.meta.url));
26
+
27
+ const CHAIN_NAMES = {
28
+ 1: "Ethereum", 8453: "Base", 42161: "Arbitrum", 10: "Optimism", 130: "Unichain",
29
+ 56: "BSC", 480: "World Chain", 999: "HyperEVM", 4326: "MegaETH",
30
+ 84532: "Base Sepolia", 11155111: "Sepolia",
31
+ };
32
+
33
+ // Curated detail, keyed by the Solidity contract name. Config blobs are the
34
+ // authoritative abi.encode tuples taken from each contract's `_applyConfig`.
35
+ const META = {
36
+ SwapPermission: {
37
+ primitive: "DEX swaps (Uniswap V3 / V3-02 / V2)",
38
+ skill: "sail-template-swap",
39
+ config: "(address[] routers, address[] tokensIn, address[] tokensOut, uint256 maxAmountPerTx, uint256 maxSlippageBps, address priceOracle, uint256 maxPriceAgeSec)",
40
+ },
41
+ SwapPermissionNoOracle: {
42
+ primitive: "DEX swaps for tokens with NO oracle — live-pool hallucination band (NOT manipulation-resistant)",
43
+ skill: "sail-template-swap-no-oracle",
44
+ config: "(address[] routers, address[] tokensIn, address[] tokensOut, uint256 maxAmountPerTx, ReferencePool[] referencePools) — ReferencePool{address tokenIn, address tokenOut, address pool, PoolKind kind (0=V2,1=V3), uint256 toleranceBps}",
45
+ },
46
+ BorrowPermission: {
47
+ primitive: "Lending borrows (Aave / Morpho / Compound) with LTV check",
48
+ skill: "sail-template-borrow",
49
+ config: "(address[] protocols, address[] assets, uint256 maxAmountPerTx, uint256 maxLtvBps, address collateralOracle, address borrowOracle, uint256 maxPriceAgeSec)",
50
+ },
51
+ TransferPermission: {
52
+ primitive: "ERC-20 transfers to a recipient allowlist (from == account)",
53
+ skill: "sail-template-transfer",
54
+ config: "(address[] allowedRecipients, address[] allowedTokens, uint256 maxAmountPerTx)",
55
+ },
56
+ DepositPermission: {
57
+ primitive: "Vault / lending deposits (ERC-4626 + Aave v2/v3)",
58
+ skill: "sail-template-deposit",
59
+ config: "(address[] targets, address[] tokens, uint256 maxAmountPerTx)",
60
+ },
61
+ WithdrawPermission: {
62
+ primitive: "ERC-20 withdrawals to a single fixed recipient",
63
+ skill: "sail-template-withdraw",
64
+ config: "(address[] tokens, address allowedRecipient, uint256 maxAmountPerTx)",
65
+ },
66
+ ApproveAndCallBatchPermission: {
67
+ primitive: "Atomic approve / consuming-call / reset-to-zero batch",
68
+ skill: "sail-template-approve-batch",
69
+ config: "Config{ address[] tokens, address[] spenders, ConsumingPair[] consumingPairs /* (address target, bytes4 selector) */, uint256[] maxApprovalAmounts, bool requireAmountMatch, bool allowUnconstrainedRecipient /* default false = recipient pinned to account; true = opt out */ }",
70
+ },
71
+ };
72
+
73
+ function argValue(flag) {
74
+ const i = process.argv.indexOf(flag);
75
+ return i >= 0 ? process.argv[i + 1] : null;
76
+ }
77
+ const hasFlag = (flag) => process.argv.includes(flag);
78
+
79
+ function findProtocolDir() {
80
+ const fromArg = argValue("--protocol");
81
+ if (fromArg) return resolve(fromArg);
82
+ if (process.env.SAIL_PROTOCOL_DIR) return resolve(process.env.SAIL_PROTOCOL_DIR);
83
+ for (const start of [process.cwd(), HERE]) {
84
+ let dir = start;
85
+ for (let i = 0; i < 8; i++) {
86
+ if (existsSync(join(dir, "Protocol", "contracts", "templates"))) return join(dir, "Protocol");
87
+ const parent = dirname(dir);
88
+ if (parent === dir) break;
89
+ dir = parent;
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+
95
+ // Detect concrete templates from source: `contract X is ... ConfigurablePermission ...`
96
+ // (skips `abstract contract`, e.g. the ConfigurablePermission base itself).
97
+ function detectTemplates(protocolDir) {
98
+ const dir = join(protocolDir, "contracts", "templates");
99
+ const out = [];
100
+ for (const file of readdirSync(dir).filter((f) => f.endsWith(".sol"))) {
101
+ const src = readFileSync(join(dir, file), "utf8");
102
+ const m = src.match(/^\s*contract\s+(\w+)\s+is\s+([^{]+)\{/m);
103
+ if (!m) continue;
104
+ if (!/ConfigurablePermission/.test(m[2])) continue;
105
+ out.push({ name: m[1], file: `contracts/templates/${file}` });
106
+ }
107
+ return out.sort((a, b) => a.name.localeCompare(b.name));
108
+ }
109
+
110
+ function loadDeployed() {
111
+ const path = join(HERE, "deployed.json");
112
+ if (!existsSync(path)) return { chains: {} };
113
+ try {
114
+ return JSON.parse(readFileSync(path, "utf8"));
115
+ } catch {
116
+ return { chains: {} };
117
+ }
118
+ }
119
+
120
+ function statusFor(deployed, name) {
121
+ const rows = [];
122
+ for (const [chainId, map] of Object.entries(deployed.chains ?? {})) {
123
+ const addr = map?.[name];
124
+ rows.push({ chainId: Number(chainId), name: CHAIN_NAMES[chainId] ?? `chain ${chainId}`, address: addr ?? null });
125
+ }
126
+ return rows.sort((a, b) => a.chainId - b.chainId);
127
+ }
128
+
129
+ function main() {
130
+ const protocolDir = findProtocolDir();
131
+ if (!protocolDir) {
132
+ console.error("Could not locate Protocol/contracts/templates. Pass --protocol <path> or set SAIL_PROTOCOL_DIR.");
133
+ process.exit(1);
134
+ }
135
+ const templates = detectTemplates(protocolDir);
136
+ const deployed = loadDeployed();
137
+ const onlyChain = argValue("--chain");
138
+
139
+ const catalog = templates.map((t) => ({
140
+ ...t,
141
+ ...(META[t.name] ?? { primitive: "(uncurated — see source)", skill: null, config: "(see source)" }),
142
+ deployments: statusFor(deployed, t.name).filter((r) => !onlyChain || r.chainId === Number(onlyChain)),
143
+ }));
144
+
145
+ // IOracle adapters usable as `priceOracle` in SwapPermission config (deployed.json `oracles`).
146
+ const oracles = [];
147
+ for (const [chainId, byLabel] of Object.entries(deployed.oracles ?? {})) {
148
+ if (onlyChain && Number(chainId) !== Number(onlyChain)) continue;
149
+ for (const [label, o] of Object.entries(byLabel ?? {})) {
150
+ oracles.push({ chainId: Number(chainId), name: CHAIN_NAMES[chainId] ?? `chain ${chainId}`, label, ...o });
151
+ }
152
+ }
153
+
154
+ if (hasFlag("--json")) {
155
+ console.log(JSON.stringify({ protocolDir, templates: catalog, oracles }, null, 2));
156
+ return;
157
+ }
158
+
159
+ console.log("Sail shared permission templates — source: Protocol/contracts/templates\n");
160
+ console.log("Model: configurable singletons. Deploy ONCE per chain, then every SMA reuses the");
161
+ console.log("address via register (`sailor mandate attach`) + configure (no per-SMA deploy).");
162
+ console.log("NOTE: `sailor mandate attach` registers ONLY — you must also configure per-account");
163
+ console.log("(configureDirect today); see SKILL.md / references/reuse-flow.md.\n");
164
+ const anyUncurated = catalog.some((t) => !META[t.name]);
165
+ for (const t of catalog) {
166
+ const flag = META[t.name] ? "" : " ⚠️ NEW/uncurated — add to META + write a skill";
167
+ console.log(`━━ ${t.name}${flag} ━━`);
168
+ console.log(` primitive: ${t.primitive}`);
169
+ console.log(` source: ${t.file}`);
170
+ if (t.skill) console.log(` skill: ${t.skill}`);
171
+ console.log(` config: ${t.config}`);
172
+ const live = t.deployments.filter((d) => d.address);
173
+ if (live.length) {
174
+ console.log(" deployed:");
175
+ for (const d of t.deployments) {
176
+ console.log(` ${d.name} (${d.chainId}): ${d.address ?? "— not yet deployed"}`);
177
+ }
178
+ } else {
179
+ console.log(" deployed: not yet on any tracked chain (record in deployed.json once deployed)");
180
+ }
181
+ console.log("");
182
+ }
183
+ if (oracles.length) {
184
+ console.log("━━ IOracle adapters (usable as SwapPermission `priceOracle`) ━━");
185
+ for (const o of oracles) {
186
+ const pairs = (o.pairs ?? []).map((p) => p.label ?? `${p.base}/${p.quote}`).join(", ");
187
+ console.log(` ${o.label} (${o.name} ${o.chainId}): ${o.address}${o.default ? " [default]" : ""}`);
188
+ console.log(` kind: ${o.kind ?? "?"}${o.twapWindowSec ? `, twap=${o.twapWindowSec}s` : ""}, decimals: ${o.priceDecimals ?? "?"}, pairs: ${pairs || "?"}`);
189
+ if (o.note) console.log(` ↳ ${o.note}`);
190
+ }
191
+ console.log("");
192
+ }
193
+ if (anyUncurated) {
194
+ console.log("⚠️ A source template has no curated metadata above — the source folder changed.");
195
+ }
196
+ console.log("⚠️ Shared templates are UNAUDITED EXAMPLES — not part of the trusted core.");
197
+ console.log(" Always `sailor mandate simulate` against your SMA before authorizing on-chain.");
198
+ }
199
+
200
+ main();