@dev.sail.money/sailor 1.3.0-98 → 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 (231) hide show
  1. package/AGENTS.md +7 -7
  2. package/LICENSE +1 -1
  3. package/README.md +64 -472
  4. package/package.json +11 -6
  5. package/packages/cli/README.md +1 -1
  6. package/packages/cli/dist/index.cjs +1469 -463
  7. package/packages/cli/dist/server.cjs +19796 -21737
  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/SailKernel.d.ts +42 -0
  14. package/packages/sdk/dist/abis/SailKernel.d.ts.map +1 -1
  15. package/packages/sdk/dist/abis/SailKernel.js +34 -0
  16. package/packages/sdk/dist/abis/SailKernel.js.map +1 -1
  17. package/packages/sdk/dist/abis/index.d.ts +1 -0
  18. package/packages/sdk/dist/abis/index.d.ts.map +1 -1
  19. package/packages/sdk/dist/abis/index.js +1 -0
  20. package/packages/sdk/dist/abis/index.js.map +1 -1
  21. package/packages/sdk/dist/chains.d.ts +0 -7
  22. package/packages/sdk/dist/chains.d.ts.map +1 -1
  23. package/packages/sdk/dist/chains.js +81 -32
  24. package/packages/sdk/dist/chains.js.map +1 -1
  25. package/packages/sdk/dist/client.d.ts +1 -1
  26. package/packages/sdk/dist/client.d.ts.map +1 -1
  27. package/packages/sdk/dist/client.js +137 -16
  28. package/packages/sdk/dist/client.js.map +1 -1
  29. package/packages/sdk/dist/deployments.d.ts +6 -4
  30. package/packages/sdk/dist/deployments.d.ts.map +1 -1
  31. package/packages/sdk/dist/deployments.js +120 -118
  32. package/packages/sdk/dist/deployments.js.map +1 -1
  33. package/packages/sdk/dist/eip712.d.ts +84 -0
  34. package/packages/sdk/dist/eip712.d.ts.map +1 -1
  35. package/packages/sdk/dist/eip712.js +148 -0
  36. package/packages/sdk/dist/eip712.js.map +1 -1
  37. package/packages/sdk/dist/fees.d.ts +2 -1
  38. package/packages/sdk/dist/fees.d.ts.map +1 -1
  39. package/packages/sdk/dist/fees.js +2 -1
  40. package/packages/sdk/dist/fees.js.map +1 -1
  41. package/packages/sdk/dist/index.d.ts +3 -2
  42. package/packages/sdk/dist/index.d.ts.map +1 -1
  43. package/packages/sdk/dist/index.js +3 -2
  44. package/packages/sdk/dist/index.js.map +1 -1
  45. package/packages/sdk/dist/intelligence.d.ts +1 -1
  46. package/packages/sdk/dist/intelligence.js +1 -1
  47. package/packages/sdk/dist/safe.d.ts +49 -0
  48. package/packages/sdk/dist/safe.d.ts.map +1 -1
  49. package/packages/sdk/dist/safe.js +74 -0
  50. package/packages/sdk/dist/safe.js.map +1 -1
  51. package/packages/sdk/package.json +6 -2
  52. package/packages/ui/dist/assets/{add-BX5reKtK.js → add-CBbu2pe_.js} +2 -2
  53. package/packages/ui/dist/assets/{all-wallets-IZ3PlVF-.js → all-wallets-C1oy4NUu.js} +2 -2
  54. package/packages/ui/dist/assets/{app-store-ARgRdMdF.js → app-store-DKqyf0Zv.js} +1 -1
  55. package/packages/ui/dist/assets/{apple-VCaqsI9w.js → apple-DyoIbCFy.js} +2 -2
  56. package/packages/ui/dist/assets/{arrow-bottom-circle-h5vFiZ2I.js → arrow-bottom-circle-CMS-OqZQ.js} +2 -2
  57. package/packages/ui/dist/assets/arrow-bottom-pUWeEqYu.js +8 -0
  58. package/packages/ui/dist/assets/arrow-left-B81HMJNL.js +8 -0
  59. package/packages/ui/dist/assets/arrow-right-Z5czoKlC.js +8 -0
  60. package/packages/ui/dist/assets/arrow-top-C6ym1oll.js +8 -0
  61. package/packages/ui/dist/assets/{bank-6c28yOM9.js → bank-B41nlslf.js} +2 -2
  62. package/packages/ui/dist/assets/{basic-CJnbTfSd.js → basic-CWqOb7CV.js} +177 -177
  63. package/packages/ui/dist/assets/{browser-CDKlxM3D.js → browser-DDKdqoZ9.js} +2 -2
  64. package/packages/ui/dist/assets/{card-DQdED02q.js → card-BgOmTajq.js} +2 -2
  65. package/packages/ui/dist/assets/ccip-CtJUKWRd.js +1 -0
  66. package/packages/ui/dist/assets/{checkmark-JoRNVoFV.js → checkmark-CGqQipH4.js} +2 -2
  67. package/packages/ui/dist/assets/{checkmark-bold-B6bGHVIs.js → checkmark-bold-Ci5xf90j.js} +2 -2
  68. package/packages/ui/dist/assets/chevron-bottom-D7Xert17.js +8 -0
  69. package/packages/ui/dist/assets/chevron-left-BGbUebR2.js +8 -0
  70. package/packages/ui/dist/assets/chevron-right-xVsESnHU.js +8 -0
  71. package/packages/ui/dist/assets/chevron-top-DEA9cR6v.js +8 -0
  72. package/packages/ui/dist/assets/{chrome-store-D-fiJ9qJ.js → chrome-store-BsyI-4PI.js} +2 -2
  73. package/packages/ui/dist/assets/{clock-xCPkzwjj.js → clock-Dct9oQ3M.js} +2 -2
  74. package/packages/ui/dist/assets/{close-BkqmPpWG.js → close-BvSCnI2y.js} +2 -2
  75. package/packages/ui/dist/assets/{coinPlaceholder-PIvN9oq_.js → coinPlaceholder-p7RiWkon.js} +2 -2
  76. package/packages/ui/dist/assets/{compass-CW5kWcxW.js → compass-BUeeZqX4.js} +2 -2
  77. package/packages/ui/dist/assets/{copy-DUZlAQJJ.js → copy-C5jXbxLq.js} +2 -2
  78. package/packages/ui/dist/assets/core-CiAm42Mq.js +907 -0
  79. package/packages/ui/dist/assets/cursor-C0T7JWFO.js +3 -0
  80. package/packages/ui/dist/assets/{cursor-transparent-BUAMB0G1.js → cursor-transparent-BZp3-JmA.js} +2 -2
  81. package/packages/ui/dist/assets/{desktop-BxdlwV8P.js → desktop-BIui_Lzb.js} +2 -2
  82. package/packages/ui/dist/assets/{disconnect-GelNh3mn.js → disconnect-B3Bxdchp.js} +2 -2
  83. package/packages/ui/dist/assets/{discord-N0jnxkzC.js → discord-VWI0yDyx.js} +2 -2
  84. package/packages/ui/dist/assets/{etherscan-B41u7wF4.js → etherscan-BpBvUT9i.js} +2 -2
  85. package/packages/ui/dist/assets/events-DQ172AOg.js +1 -0
  86. package/packages/ui/dist/assets/{exclamation-triangle-BWJC56FC.js → exclamation-triangle-o1TJHIUw.js} +2 -2
  87. package/packages/ui/dist/assets/{extension-G6I6oogw.js → extension-4RaLvbh5.js} +2 -2
  88. package/packages/ui/dist/assets/external-link-BHIwjDgj.js +8 -0
  89. package/packages/ui/dist/assets/{facebook-47twy32b.js → facebook-Bes5g-wN.js} +2 -2
  90. package/packages/ui/dist/assets/{farcaster-DYqnqCqs.js → farcaster-CiJ-352m.js} +2 -2
  91. package/packages/ui/dist/assets/{filters-dF6xeJnE.js → filters-DMOteumb.js} +2 -2
  92. package/packages/ui/dist/assets/{github-DbW4Ihz2.js → github-D2nDec9X.js} +2 -2
  93. package/packages/ui/dist/assets/{google-TKm7G8LF.js → google-lZZlQZPQ.js} +2 -2
  94. package/packages/ui/dist/assets/{help-circle-CY3qTmsG.js → help-circle-D4cSSziR.js} +1 -1
  95. package/packages/ui/dist/assets/{id-JpHW4q14.js → id-BmhXZq5P.js} +2 -2
  96. package/packages/ui/dist/assets/{image-Dip2fG98.js → image-Dz4FPeET.js} +2 -2
  97. package/packages/ui/dist/assets/index-B07bCKJ8.css +1 -0
  98. package/packages/ui/dist/assets/index-BV8s6ez1.js +1 -0
  99. package/packages/ui/dist/assets/index-BeUp3nOq.js +1 -0
  100. package/packages/ui/dist/assets/index-CvRylmZJ.js +16 -0
  101. package/packages/ui/dist/assets/{index-q0mGElxZ.js → index-DeoQO45V.js} +3 -3
  102. package/packages/ui/dist/assets/index-Et5S3ZaW.js +47 -0
  103. package/packages/ui/dist/assets/index-LiKeH8Cd.js +1780 -0
  104. package/packages/ui/dist/assets/index.es-dfpEui-9.js +26 -0
  105. package/packages/ui/dist/assets/{info-B1enFsqh.js → info-77RSvIMg.js} +2 -2
  106. package/packages/ui/dist/assets/{info-circle-D4j3FISs.js → info-circle-CWsII-NN.js} +2 -2
  107. package/packages/ui/dist/assets/{lightbulb-DZbKr-vS.js → lightbulb-BZXzm7XE.js} +2 -2
  108. package/packages/ui/dist/assets/{mail-D-TO_uVQ.js → mail-DMG1YqB0.js} +2 -2
  109. package/packages/ui/dist/assets/metamask-sdk-K1x_iqcc.js +542 -0
  110. package/packages/ui/dist/assets/{mobile-Bkmwzav2.js → mobile-DuJfOBSk.js} +2 -2
  111. package/packages/ui/dist/assets/{more-D1_I_jiB.js → more-Cs9ySK2F.js} +2 -2
  112. package/packages/ui/dist/assets/{network-placeholder-DUaFjoXh.js → network-placeholder-CMDPmB1E.js} +2 -2
  113. package/packages/ui/dist/assets/{nftPlaceholder-Cc0iUryh.js → nftPlaceholder-Cj0f9y2u.js} +2 -2
  114. package/packages/ui/dist/assets/{off-BvX71sse.js → off-9zSSsm-e.js} +2 -2
  115. package/packages/ui/dist/assets/{play-store-XzLbtbTd.js → play-store-CPwIb8Gj.js} +2 -2
  116. package/packages/ui/dist/assets/{plus-B77_RmYR.js → plus-BOHQri8o.js} +2 -2
  117. package/packages/ui/dist/assets/{qr-code-BNeIhB1d.js → qr-code-DqhbK276.js} +1 -1
  118. package/packages/ui/dist/assets/{recycle-horizontal-B9sX-yty.js → recycle-horizontal-BpGM56Qz.js} +2 -2
  119. package/packages/ui/dist/assets/{refresh-BWRygnjm.js → refresh-BPzZlhTP.js} +2 -2
  120. package/packages/ui/dist/assets/{reown-logo-DCAEsGFm.js → reown-logo-OCkI0xzo.js} +2 -2
  121. package/packages/ui/dist/assets/{search-ZXIqrawS.js → search-B1JEJvwq.js} +2 -2
  122. package/packages/ui/dist/assets/{secp256k1-Doz05h4g.js → secp256k1-BqUeV9Cb.js} +1 -1
  123. package/packages/ui/dist/assets/{send-BQcO0jNC.js → send-JNsDlNoq.js} +2 -2
  124. package/packages/ui/dist/assets/{swapHorizontal-4yZEzz2K.js → swapHorizontal-C3Z1FImK.js} +2 -2
  125. package/packages/ui/dist/assets/{swapHorizontalBold-DxBKu3sl.js → swapHorizontalBold-Cmdy2iAn.js} +2 -2
  126. package/packages/ui/dist/assets/{swapHorizontalMedium-DAQYJ2JK.js → swapHorizontalMedium-DFX57Nn9.js} +2 -2
  127. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-CUlZzFJ6.js → swapHorizontalRoundedBold-baB389c8.js} +2 -2
  128. package/packages/ui/dist/assets/{swapVertical-CjWHAaDM.js → swapVertical-DmAYGfK1.js} +2 -2
  129. package/packages/ui/dist/assets/{telegram-JBeabiJP.js → telegram-BHbYE-nI.js} +2 -2
  130. package/packages/ui/dist/assets/{three-dots-D3R8OjSx.js → three-dots-BwRWl6qJ.js} +2 -2
  131. package/packages/ui/dist/assets/{twitch-1f9z7xVO.js → twitch-Drx0q6B6.js} +2 -2
  132. package/packages/ui/dist/assets/{twitterIcon-BW7n2xnp.js → twitterIcon-AuJ2KQ6p.js} +2 -2
  133. package/packages/ui/dist/assets/{verify-Cs5owI38.js → verify-Dkrd3q7o.js} +2 -2
  134. package/packages/ui/dist/assets/{verify-filled-Ci5sx5qS.js → verify-filled-B9e3-nz-.js} +2 -2
  135. package/packages/ui/dist/assets/{w3m-modal-DnWtss8u.js → w3m-modal-DtkEjQA7.js} +1 -1
  136. package/packages/ui/dist/assets/{wallet-MaGNqaW-.js → wallet-CqYzSiBd.js} +2 -2
  137. package/packages/ui/dist/assets/{wallet-placeholder-kTOy12_L.js → wallet-placeholder-B05kTN_K.js} +2 -2
  138. package/packages/ui/dist/assets/{walletconnect-CyuP92jg.js → walletconnect-BwoPXLaS.js} +3 -3
  139. package/packages/ui/dist/assets/{warning-circle-BImUCdpG.js → warning-circle-DK9Ai8rT.js} +2 -2
  140. package/packages/ui/dist/assets/{x-II45JHpV.js → x-BNdh5DVW.js} +2 -2
  141. package/packages/ui/dist/index.html +2 -2
  142. package/templates/default/.agents/skills/sail-mandates/SKILL.md +1 -1
  143. package/templates/default/.agents/skills/sail-mandates/references/approvals.md +3 -3
  144. package/templates/default/.agents/skills/sail-mandates/references/examples-index.md +3 -3
  145. package/templates/default/.agents/skills/sail-onboarding/SKILL.md +34 -33
  146. package/templates/default/.agents/skills/sail-servers/SKILL.md +1 -0
  147. package/templates/default/.agents/skills/sail-swap-quote/SKILL.md +76 -0
  148. package/templates/default/.agents/skills/sail-template-approve-batch/SKILL.md +98 -0
  149. package/templates/default/.agents/skills/sail-template-borrow/SKILL.md +68 -0
  150. package/templates/default/.agents/skills/sail-template-deposit/SKILL.md +61 -0
  151. package/templates/default/.agents/skills/sail-template-swap/SKILL.md +207 -0
  152. package/templates/default/.agents/skills/sail-template-swap-no-oracle/SKILL.md +100 -0
  153. package/templates/default/.agents/skills/sail-template-transfer/SKILL.md +52 -0
  154. package/templates/default/.agents/skills/sail-template-withdraw/SKILL.md +50 -0
  155. package/templates/default/.agents/skills/sail-templates/SKILL.md +142 -0
  156. package/templates/default/.agents/skills/sail-templates/catalog.mjs +200 -0
  157. package/templates/default/.agents/skills/sail-templates/deployed.json +133 -0
  158. package/templates/default/.agents/skills/sail-templates/oracles/IOracle.sol +13 -0
  159. package/templates/default/.agents/skills/sail-templates/oracles/UniV3TwapOracle.sol +114 -0
  160. package/templates/default/.agents/skills/sail-templates/oracles/libs/FullMath.sol +62 -0
  161. package/templates/default/.agents/skills/sail-templates/oracles/libs/TickMath.sol +44 -0
  162. package/templates/default/.agents/skills/sail-templates/references/config-schemas.md +139 -0
  163. package/templates/default/.agents/skills/sail-templates/references/reuse-flow.md +139 -0
  164. package/templates/default/.agents/skills/sail-token-resolve/SKILL.md +174 -0
  165. package/templates/default/.env.example +9 -0
  166. package/templates/default/AGENTS.md +107 -13
  167. package/templates/default/docs/PERMISSION_MODEL.md +2 -2
  168. package/{examples → templates/default/examples}/custom-mandate/.sail/contracts/interfaces/IPermission.sol +4 -0
  169. package/{examples → templates/default/examples}/custom-mandate/README.md +4 -1
  170. package/{examples → templates/default/examples}/permissions/interfaces/IBatchPermission.sol +2 -0
  171. package/{examples → templates/default/examples}/permissions/interfaces/IPermission.sol +4 -0
  172. package/templates/default/scripts/quote-swap.mjs +211 -0
  173. package/templates/default/scripts/resolve-token.mjs +992 -0
  174. package/templates/default/scripts/shared-template-addr.mjs +110 -0
  175. package/templates/default/test/BoundedCallPermission.t.sol +2 -1
  176. package/docs/PERMISSION_MODEL.md +0 -93
  177. package/examples/README.md +0 -24
  178. package/examples/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +0 -84
  179. package/examples/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +0 -97
  180. package/examples/lifi-permissions/README.md +0 -53
  181. package/packages/ui/dist/assets/arrow-bottom-Cwptw8fW.js +0 -8
  182. package/packages/ui/dist/assets/arrow-left-bM1TRcV9.js +0 -8
  183. package/packages/ui/dist/assets/arrow-right-BYd_N4Jr.js +0 -8
  184. package/packages/ui/dist/assets/arrow-top-CLUnwVE2.js +0 -8
  185. package/packages/ui/dist/assets/ccip-BL7RIPfY.js +0 -1
  186. package/packages/ui/dist/assets/chevron-bottom-BMCelIEu.js +0 -8
  187. package/packages/ui/dist/assets/chevron-left-ryPLhHCc.js +0 -8
  188. package/packages/ui/dist/assets/chevron-right-CdDNRv7f.js +0 -8
  189. package/packages/ui/dist/assets/chevron-top-BxWIjjGk.js +0 -8
  190. package/packages/ui/dist/assets/core-von2c1sc.js +0 -907
  191. package/packages/ui/dist/assets/cursor-BzRVY7BD.js +0 -3
  192. package/packages/ui/dist/assets/events-C9icG2YB.js +0 -1
  193. package/packages/ui/dist/assets/external-link-BPaGQf4a.js +0 -8
  194. package/packages/ui/dist/assets/fallback-BTfJeGjh.js +0 -1
  195. package/packages/ui/dist/assets/index-BGJ_8fMk.js +0 -1
  196. package/packages/ui/dist/assets/index-Bq3PAopp.js +0 -16
  197. package/packages/ui/dist/assets/index-C4_cA5XW.js +0 -1
  198. package/packages/ui/dist/assets/index-CaDN0gL4.js +0 -1
  199. package/packages/ui/dist/assets/index-DLElIcBr.js +0 -1789
  200. package/packages/ui/dist/assets/index-lhNfD4jA.css +0 -1
  201. package/packages/ui/dist/assets/index.es-BaCKS_tW.js +0 -26
  202. package/packages/ui/dist/assets/metamask-sdk-DljW3QD1.js +0 -542
  203. package/packages/ui/dist/assets/native-CJ5et6AR.js +0 -1
  204. package/packages/ui/dist/assets/parseSignature-BAS-wKoj.js +0 -1
  205. package/scripts/check-docs.mjs +0 -309
  206. package/scripts/check-init.mjs +0 -123
  207. package/scripts/check-update.mjs +0 -177
  208. package/scripts/clean.mjs +0 -17
  209. package/scripts/pack.mjs +0 -21
  210. package/templates/default/.agents/skills/sail-overview/SKILL.md +0 -100
  211. package/templates/default/.agents/skills/sail-sailor/SKILL.md +0 -190
  212. package/templates/default/.agents/skills/sail-setup/SKILL.md +0 -38
  213. package/templates/default/.gitlab-ci.yml +0 -58
  214. /package/{examples → templates/default/examples}/custom-mandate/foundry.toml +0 -0
  215. /package/{examples → templates/default/examples}/custom-mandate/mandates/BoundedCallPermission.sol +0 -0
  216. /package/{examples → templates/default/examples}/custom-mandate/mandates/README.md +0 -0
  217. /package/{examples → templates/default/examples}/custom-mandate/mandates/SailCalldata.sol +0 -0
  218. /package/{examples → templates/default/examples}/permissions/BoundedApproveAndCallBatch.sol +0 -0
  219. /package/{examples → templates/default/examples}/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +0 -0
  220. /package/{examples → templates/default/examples}/permissions/BoundedLimitless_Base.sol +0 -0
  221. /package/{examples → templates/default/examples}/permissions/BoundedPerp_GMXv2_Arbitrum.sol +0 -0
  222. /package/{examples → templates/default/examples}/permissions/BoundedStake_Venice_Base.sol +0 -0
  223. /package/{examples → templates/default/examples}/permissions/BoundedSupply_AaveV3_Arbitrum.sol +0 -0
  224. /package/{examples → templates/default/examples}/permissions/BoundedSwapNative_UniswapV3_Base.sol +0 -0
  225. /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV3_Base.sol +0 -0
  226. /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV4_Unichain.sol +0 -0
  227. /package/{examples → templates/default/examples}/permissions/BoundedTransfer_ERC20_Ethereum.sol +0 -0
  228. /package/{examples → templates/default/examples}/permissions/BoundedVault_ERC4626_Base.sol +0 -0
  229. /package/{examples → templates/default/examples}/permissions/README.md +0 -0
  230. /package/{examples → templates/default/examples}/permissions/SailCalldata.sol +0 -0
  231. /package/{examples → templates/default/examples}/permissions/foundry.toml +0 -0
@@ -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();
@@ -0,0 +1,133 @@
1
+ {
2
+ "_comment": "Reuse registry for the shared permission templates in Protocol/contracts/templates. These are CONFIGURABLE SINGLETONS: deployed ONCE, then every SMA reuses that address via attach + configure (no per-SMA deploy). All seven templates bind to the current CREATE2 kernel 0x38b508756c976e876EFF05a29E731A4d348BA6ED. Deploy is CREATE2 with a global salt (gitCommit 1dc1960, deploy version create2-2026-07-01), so the kernel and every template land at the SAME address on every chain — the per-chain blocks below are identical by construction, not independent deployments. Deployed by 0xB01dCE443d052e44b7D13726c0EC9fFB7f5815B6 on 2026-07-01. Supersedes the prior EOA-governed 2026-06-09 deploy (kernel 0x02ABC18B65A328de2e749F56ba79ACF2718a6659) — those addresses are dead; do not reuse them.",
3
+ "_schema": "sail.skills.templates.deployed/v1",
4
+ "kernel": "0x38b508756c976e876EFF05a29E731A4d348BA6ED",
5
+ "chains": {
6
+ "1": {
7
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
8
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
9
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
10
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
11
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
12
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
13
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
14
+ },
15
+ "8453": {
16
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
17
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
18
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
19
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
20
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
21
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
22
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
23
+ },
24
+ "42161": {
25
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
26
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
27
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
28
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
29
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
30
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
31
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
32
+ },
33
+ "10": {
34
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
35
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
36
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
37
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
38
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
39
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
40
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
41
+ },
42
+ "130": {
43
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
44
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
45
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
46
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
47
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
48
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
49
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
50
+ },
51
+ "56": {
52
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
53
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
54
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
55
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
56
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
57
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
58
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
59
+ },
60
+ "480": {
61
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
62
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
63
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
64
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
65
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
66
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
67
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
68
+ },
69
+ "999": {
70
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
71
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
72
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
73
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
74
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
75
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
76
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
77
+ },
78
+ "4326": {
79
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
80
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
81
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
82
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
83
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
84
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
85
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
86
+ },
87
+ "84532": {
88
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
89
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
90
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
91
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
92
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
93
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
94
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
95
+ },
96
+ "11155111": {
97
+ "ApproveAndCallBatchPermission": "0x0535A4D51333484ef583103DAB1a9449756ab732",
98
+ "BorrowPermission": "0x3e2666051599223cEAb10De55C89A0842857d8AF",
99
+ "DepositPermission": "0xBfB5e13a97b12Ee89d2F2b9B65eCf7e0E371911f",
100
+ "SwapPermission": "0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C",
101
+ "SwapPermissionNoOracle": "0x34Ba96CbEd1f46c88A5265E645DC5fe41662b519",
102
+ "TransferPermission": "0xda909a1CC584fb7559Ce4A828b008B473Da095e1",
103
+ "WithdrawPermission": "0xF5eF5dda450a130e3020d54f565E830e4a7531f8"
104
+ }
105
+ },
106
+ "_oraclesComment": "IOracle adapters (getPrice(base,quote)) usable as `priceOracle` in SwapPermission config. SwapPermission requires a non-zero IOracle adapter. Keyed chainId -> label. `pairs` lists the directional token pairs each adapter serves (both directions); any other pair reverts. catalog.mjs surfaces these so the swap skill can pick a default oracle. An IOracle adapter reads only its named pool/tokens — it has no kernel or template-address dependency, so it remains valid against the current CREATE2 SwapPermission singleton.",
107
+ "oracles": {
108
+ "130": {
109
+ "UniV3TwapOracle-USDC-WETH": {
110
+ "address": "0x9d84C11626d13C5DC9540fA12A3Ff7B85Ac3c1B9",
111
+ "kind": "uniswap-v3-twap",
112
+ "pool": "0x65081CB48d74A32e9CCfED75164b8c09972DBcF1",
113
+ "poolFeeTier": 500,
114
+ "twapWindowSec": 1800,
115
+ "priceDecimals": 18,
116
+ "pairs": [
117
+ { "base": "0x078D782b760474a361dDA0AF3839290b0EF57AD6", "quote": "0x4200000000000000000000000000000000000006", "label": "USDC/WETH" }
118
+ ],
119
+ "tokens": {
120
+ "USDC": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
121
+ "WETH": "0x4200000000000000000000000000000000000006"
122
+ },
123
+ "deployedBy": "0xB01dCE443d052e44b7D13726c0EC9fFB7f5815B6",
124
+ "tx": "0x3ca0d7232b623afda5e53c6d2319f4c3bbdc00a28b924c9f1d994bd3abfc2b3c",
125
+ "deployedAt": "2026-06-30",
126
+ "verified": "https://uniscan.xyz/address/0x9d84c11626d13c5dc9540fa12a3ff7b85ac3c1b9",
127
+ "source": "sail-templates/oracles/UniV3TwapOracle.sol",
128
+ "default": true,
129
+ "note": "Default priceOracle for the USDC<->WETH pair on Unichain. Live 30-min TWAP from the 0.05% V3 pool (cardinality 2000); updatedAt = block.timestamp, so it is always fresh against maxPriceAgeSec. Manipulation resistance scales with pool liquidity + window; reads a single pool. Use with maxPriceAgeSec >= a few minutes. Deployed before the create2-2026-07-01 migration but kernel-agnostic (reads only the named pool/tokens), so it remains a valid priceOracle for the current SwapPermission singleton (0x35cEEa0db96997Cc3CF3beB42FFa36A499342F7C)."
130
+ }
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,13 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ /// @title IOracle
5
+ /// @notice Minimal price oracle interface consumed by the oracle-gated permission templates.
6
+ /// Vendored verbatim from Protocol/contracts/interfaces/IOracle.sol so this adapter
7
+ /// compiles standalone; the consuming SwapPermission only cares about the ABI shape.
8
+ interface IOracle {
9
+ function getPrice(address base, address quote)
10
+ external
11
+ view
12
+ returns (uint256 price, uint8 decimals, uint256 updatedAt);
13
+ }
@@ -0,0 +1,114 @@
1
+ // SPDX-License-Identifier: GPL-2.0-or-later
2
+ pragma solidity 0.8.26;
3
+
4
+ import {IOracle} from "./IOracle.sol";
5
+ import {TickMath} from "./libs/TickMath.sol";
6
+ import {FullMath} from "./libs/FullMath.sol";
7
+
8
+ interface IUniswapV3Pool {
9
+ function token0() external view returns (address);
10
+ function token1() external view returns (address);
11
+ function fee() external view returns (uint24);
12
+ function observe(uint32[] calldata secondsAgos)
13
+ external
14
+ view
15
+ returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
16
+ }
17
+
18
+ /// @title UniV3TwapOracle — single-pool Uniswap V3 TWAP adapter implementing IOracle
19
+ /// @notice A read-only `IOracle` adapter for the oracle-gated launch templates (SwapPermission).
20
+ /// It serves ONE Uniswap V3 pool and the single directional token pair it contains
21
+ /// (both directions). The price is a time-weighted average over `twapWindow` seconds,
22
+ /// read from `pool.observe`, so it is manipulation-resistant over that window (a one-block
23
+ /// spot move barely shifts the average) and needs no external price provider or per-call
24
+ /// update fee.
25
+ ///
26
+ /// IORACLE SEMANTICS. getPrice(base, quote) returns (price, decimals, updatedAt) such that
27
+ /// 1 base base-unit = price / 10**decimals quote base-units. `decimals` is fixed at 18.
28
+ /// `updatedAt` is the current block timestamp: a TWAP is computed live at call time, so it
29
+ /// is always fresh against the consuming template's `maxPriceAgeSec`.
30
+ ///
31
+ /// BOUNDARIES (honest). This is NOT a USD oracle — it reports the relative price of the two
32
+ /// pool tokens, which is exactly what SwapPermission's band needs. Manipulation resistance
33
+ /// is only as strong as (a) the pool's liquidity and (b) the chosen window: a long-lived,
34
+ /// well-capitalised attacker can still drag a TWAP. It reads a SINGLE pool — if that pool is
35
+ /// drained of liquidity the average degrades. `observe` reverts ("OLD") if the pool's
36
+ /// observation buffer does not yet span `twapWindow`; that revert propagates and the
37
+ /// consuming template fails closed (deny). Unsupported pairs revert `UnsupportedPair`.
38
+ contract UniV3TwapOracle is IOracle {
39
+ /// @notice Quote precision returned by getPrice (price is scaled by 10**18).
40
+ uint8 public constant PRICE_DECIMALS = 18;
41
+
42
+ address public immutable pool;
43
+ address public immutable token0;
44
+ address public immutable token1;
45
+ uint32 public immutable twapWindow;
46
+
47
+ error UnsupportedPair(address base, address quote);
48
+ error WindowZero();
49
+
50
+ /// @param _pool Uniswap V3 pool to read.
51
+ /// @param _twapWindow TWAP averaging window in seconds (must be > 0 and supported by the pool's
52
+ /// observation cardinality, else getPrice reverts).
53
+ constructor(address _pool, uint32 _twapWindow) {
54
+ if (_twapWindow == 0) revert WindowZero();
55
+ pool = _pool;
56
+ token0 = IUniswapV3Pool(_pool).token0();
57
+ token1 = IUniswapV3Pool(_pool).token1();
58
+ twapWindow = _twapWindow;
59
+ }
60
+
61
+ /// @inheritdoc IOracle
62
+ function getPrice(address base, address quote)
63
+ external
64
+ view
65
+ returns (uint256 price, uint8 decimals, uint256 updatedAt)
66
+ {
67
+ bool supported = (base == token0 && quote == token1) || (base == token1 && quote == token0);
68
+ if (!supported) revert UnsupportedPair(base, quote);
69
+
70
+ int24 tick = _consultMeanTick();
71
+ // quoteAmount for a base input of 10**PRICE_DECIMALS == price scaled to PRICE_DECIMALS.
72
+ price = _getQuoteAtTick(tick, uint128(10 ** uint256(PRICE_DECIMALS)), base, quote);
73
+ decimals = PRICE_DECIMALS;
74
+ updatedAt = block.timestamp;
75
+ }
76
+
77
+ /// @dev Time-weighted mean tick over `twapWindow`, rounded toward negative infinity (Uniswap's
78
+ /// OracleLibrary convention).
79
+ function _consultMeanTick() internal view returns (int24) {
80
+ uint32[] memory secondsAgos = new uint32[](2);
81
+ secondsAgos[0] = twapWindow;
82
+ secondsAgos[1] = 0;
83
+
84
+ (int56[] memory tickCumulatives, ) = IUniswapV3Pool(pool).observe(secondsAgos);
85
+ int56 delta = tickCumulatives[1] - tickCumulatives[0];
86
+ int56 window = int56(uint56(twapWindow));
87
+
88
+ int24 meanTick = int24(delta / window);
89
+ if (delta < 0 && (delta % window != 0)) meanTick--;
90
+ return meanTick;
91
+ }
92
+
93
+ /// @dev Uniswap OracleLibrary.getQuoteAtTick: quote-token amount for `baseAmount` of base token
94
+ /// at `tick`, honouring token ordering (no decimal normalisation — raw base units).
95
+ function _getQuoteAtTick(int24 tick, uint128 baseAmount, address baseToken, address quoteToken)
96
+ internal
97
+ pure
98
+ returns (uint256 quoteAmount)
99
+ {
100
+ uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);
101
+
102
+ if (sqrtRatioX96 <= type(uint128).max) {
103
+ uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
104
+ quoteAmount = baseToken < quoteToken
105
+ ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
106
+ : FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
107
+ } else {
108
+ uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64);
109
+ quoteAmount = baseToken < quoteToken
110
+ ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
111
+ : FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
112
+ }
113
+ }
114
+ }
@@ -0,0 +1,62 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ /// @title Contains 512-bit math functions
5
+ /// @notice Vendored from Uniswap v3-core (0.8 port). Facilitates multiplication and division that
6
+ /// can have overflow of an intermediate value without any loss of precision.
7
+ library FullMath {
8
+ /// @notice Calculates floor(a×b÷denominator) with full precision.
9
+ function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
10
+ unchecked {
11
+ uint256 prod0;
12
+ uint256 prod1;
13
+ assembly {
14
+ let mm := mulmod(a, b, not(0))
15
+ prod0 := mul(a, b)
16
+ prod1 := sub(sub(mm, prod0), lt(mm, prod0))
17
+ }
18
+
19
+ if (prod1 == 0) {
20
+ require(denominator > 0);
21
+ assembly {
22
+ result := div(prod0, denominator)
23
+ }
24
+ return result;
25
+ }
26
+
27
+ require(denominator > prod1);
28
+
29
+ uint256 remainder;
30
+ assembly {
31
+ remainder := mulmod(a, b, denominator)
32
+ }
33
+ assembly {
34
+ prod1 := sub(prod1, gt(remainder, prod0))
35
+ prod0 := sub(prod0, remainder)
36
+ }
37
+
38
+ uint256 twos = denominator & (~denominator + 1);
39
+ assembly {
40
+ denominator := div(denominator, twos)
41
+ }
42
+ assembly {
43
+ prod0 := div(prod0, twos)
44
+ }
45
+ assembly {
46
+ twos := add(div(sub(0, twos), twos), 1)
47
+ }
48
+ prod0 |= prod1 * twos;
49
+
50
+ uint256 inv = (3 * denominator) ^ 2;
51
+ inv *= 2 - denominator * inv;
52
+ inv *= 2 - denominator * inv;
53
+ inv *= 2 - denominator * inv;
54
+ inv *= 2 - denominator * inv;
55
+ inv *= 2 - denominator * inv;
56
+ inv *= 2 - denominator * inv;
57
+
58
+ result = prod0 * inv;
59
+ return result;
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,44 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ /// @title Math library for computing sqrt prices from ticks
5
+ /// @notice Vendored from Uniswap v3-core (0.8 port). Only getSqrtRatioAtTick is needed here.
6
+ library TickMath {
7
+ int24 internal constant MIN_TICK = -887272;
8
+ int24 internal constant MAX_TICK = 887272;
9
+
10
+ /// @notice Calculates sqrt(1.0001^tick) * 2^96
11
+ function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
12
+ unchecked {
13
+ uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
14
+ require(absTick <= uint256(int256(MAX_TICK)), "T");
15
+
16
+ uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
17
+ if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
18
+ if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
19
+ if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
20
+ if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
21
+ if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
22
+ if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
23
+ if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
24
+ if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
25
+ if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
26
+ if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
27
+ if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
28
+ if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
29
+ if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
30
+ if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
31
+ if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
32
+ if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
33
+ if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
34
+ if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
35
+ if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
36
+
37
+ if (tick > 0) ratio = type(uint256).max / ratio;
38
+
39
+ // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
40
+ // we then downcast because the result always fits within 160 bits due to our tick input constraint
41
+ sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
42
+ }
43
+ }
44
+ }