@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.
- package/AGENTS.md +10 -7
- package/LICENSE +1 -1
- package/README.md +64 -472
- package/package.json +12 -8
- package/packages/cli/README.md +1 -1
- package/packages/cli/dist/index.cjs +1633 -535
- package/packages/cli/dist/server.cjs +19791 -21738
- package/packages/sdk/README.md +1 -1
- package/packages/sdk/dist/abis/ConfigurablePermission.d.ts +130 -0
- package/packages/sdk/dist/abis/ConfigurablePermission.d.ts.map +1 -0
- package/packages/sdk/dist/abis/ConfigurablePermission.js +79 -0
- package/packages/sdk/dist/abis/ConfigurablePermission.js.map +1 -0
- package/packages/sdk/dist/abis/SailGovernance.d.ts +6 -5
- package/packages/sdk/dist/abis/SailGovernance.d.ts.map +1 -1
- package/packages/sdk/dist/abis/SailGovernance.js +6 -5
- package/packages/sdk/dist/abis/SailGovernance.js.map +1 -1
- package/packages/sdk/dist/abis/SailKernel.d.ts +42 -0
- package/packages/sdk/dist/abis/SailKernel.d.ts.map +1 -1
- package/packages/sdk/dist/abis/SailKernel.js +34 -0
- package/packages/sdk/dist/abis/SailKernel.js.map +1 -1
- package/packages/sdk/dist/abis/index.d.ts +1 -0
- package/packages/sdk/dist/abis/index.d.ts.map +1 -1
- package/packages/sdk/dist/abis/index.js +1 -0
- package/packages/sdk/dist/abis/index.js.map +1 -1
- package/packages/sdk/dist/chains.d.ts +0 -7
- package/packages/sdk/dist/chains.d.ts.map +1 -1
- package/packages/sdk/dist/chains.js +81 -32
- package/packages/sdk/dist/chains.js.map +1 -1
- package/packages/sdk/dist/client.d.ts +1 -1
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +137 -16
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/deployments.d.ts +6 -4
- package/packages/sdk/dist/deployments.d.ts.map +1 -1
- package/packages/sdk/dist/deployments.js +120 -118
- package/packages/sdk/dist/deployments.js.map +1 -1
- package/packages/sdk/dist/eip712.d.ts +84 -0
- package/packages/sdk/dist/eip712.d.ts.map +1 -1
- package/packages/sdk/dist/eip712.js +148 -0
- package/packages/sdk/dist/eip712.js.map +1 -1
- package/packages/sdk/dist/fees.d.ts +59 -10
- package/packages/sdk/dist/fees.d.ts.map +1 -1
- package/packages/sdk/dist/fees.js +74 -41
- package/packages/sdk/dist/fees.js.map +1 -1
- package/packages/sdk/dist/index.d.ts +5 -3
- package/packages/sdk/dist/index.d.ts.map +1 -1
- package/packages/sdk/dist/index.js +4 -3
- package/packages/sdk/dist/index.js.map +1 -1
- package/packages/sdk/dist/intelligence.d.ts +7 -2
- package/packages/sdk/dist/intelligence.d.ts.map +1 -1
- package/packages/sdk/dist/intelligence.js +2 -2
- package/packages/sdk/dist/intelligence.js.map +1 -1
- package/packages/sdk/dist/safe.d.ts +49 -0
- package/packages/sdk/dist/safe.d.ts.map +1 -1
- package/packages/sdk/dist/safe.js +74 -0
- package/packages/sdk/dist/safe.js.map +1 -1
- package/packages/sdk/dist/signing.d.ts +14 -0
- package/packages/sdk/dist/signing.d.ts.map +1 -1
- package/packages/sdk/package.json +6 -2
- package/packages/ui/dist/assets/{add-CLs0-B0p.js → add-CBbu2pe_.js} +2 -2
- package/packages/ui/dist/assets/{all-wallets-CDOk28sj.js → all-wallets-C1oy4NUu.js} +2 -2
- package/packages/ui/dist/assets/{app-store-DVzpi1qN.js → app-store-DKqyf0Zv.js} +1 -1
- package/packages/ui/dist/assets/{apple-C9U9iH1l.js → apple-DyoIbCFy.js} +2 -2
- package/packages/ui/dist/assets/{arrow-bottom-circle-jxial9Si.js → arrow-bottom-circle-CMS-OqZQ.js} +2 -2
- package/packages/ui/dist/assets/arrow-bottom-pUWeEqYu.js +8 -0
- package/packages/ui/dist/assets/arrow-left-B81HMJNL.js +8 -0
- package/packages/ui/dist/assets/arrow-right-Z5czoKlC.js +8 -0
- package/packages/ui/dist/assets/arrow-top-C6ym1oll.js +8 -0
- package/packages/ui/dist/assets/{bank-B98it1qN.js → bank-B41nlslf.js} +2 -2
- package/packages/ui/dist/assets/{basic-DsgeyPNu.js → basic-CWqOb7CV.js} +177 -177
- package/packages/ui/dist/assets/{browser-j4vCeHwE.js → browser-DDKdqoZ9.js} +2 -2
- package/packages/ui/dist/assets/{card-CzxbMvEd.js → card-BgOmTajq.js} +2 -2
- package/packages/ui/dist/assets/ccip-CtJUKWRd.js +1 -0
- package/packages/ui/dist/assets/{checkmark-D5sWBbk3.js → checkmark-CGqQipH4.js} +2 -2
- package/packages/ui/dist/assets/{checkmark-bold-BW6w3KCC.js → checkmark-bold-Ci5xf90j.js} +2 -2
- package/packages/ui/dist/assets/chevron-bottom-D7Xert17.js +8 -0
- package/packages/ui/dist/assets/chevron-left-BGbUebR2.js +8 -0
- package/packages/ui/dist/assets/chevron-right-xVsESnHU.js +8 -0
- package/packages/ui/dist/assets/chevron-top-DEA9cR6v.js +8 -0
- package/packages/ui/dist/assets/{chrome-store-B1OMtzC-.js → chrome-store-BsyI-4PI.js} +2 -2
- package/packages/ui/dist/assets/{clock-e0HueehS.js → clock-Dct9oQ3M.js} +2 -2
- package/packages/ui/dist/assets/{close-Cy1ytr26.js → close-BvSCnI2y.js} +2 -2
- package/packages/ui/dist/assets/{coinPlaceholder-DqJu3ZPV.js → coinPlaceholder-p7RiWkon.js} +2 -2
- package/packages/ui/dist/assets/{compass-XDz_N2Hg.js → compass-BUeeZqX4.js} +2 -2
- package/packages/ui/dist/assets/{copy-DmDo3Nob.js → copy-C5jXbxLq.js} +2 -2
- package/packages/ui/dist/assets/core-CiAm42Mq.js +907 -0
- package/packages/ui/dist/assets/cursor-C0T7JWFO.js +3 -0
- package/packages/ui/dist/assets/{cursor-transparent-DF2aA10D.js → cursor-transparent-BZp3-JmA.js} +2 -2
- package/packages/ui/dist/assets/{desktop-CVydOWLn.js → desktop-BIui_Lzb.js} +2 -2
- package/packages/ui/dist/assets/{disconnect-BuN2Whnz.js → disconnect-B3Bxdchp.js} +2 -2
- package/packages/ui/dist/assets/{discord-DMNPvTB2.js → discord-VWI0yDyx.js} +2 -2
- package/packages/ui/dist/assets/{etherscan-CacW-93f.js → etherscan-BpBvUT9i.js} +2 -2
- package/packages/ui/dist/assets/events-DQ172AOg.js +1 -0
- package/packages/ui/dist/assets/{exclamation-triangle-B26hIvqO.js → exclamation-triangle-o1TJHIUw.js} +2 -2
- package/packages/ui/dist/assets/{extension-B7rI0gv7.js → extension-4RaLvbh5.js} +2 -2
- package/packages/ui/dist/assets/external-link-BHIwjDgj.js +8 -0
- package/packages/ui/dist/assets/{facebook-BDqbE6kl.js → facebook-Bes5g-wN.js} +2 -2
- package/packages/ui/dist/assets/{farcaster-zQ3oQzZ8.js → farcaster-CiJ-352m.js} +2 -2
- package/packages/ui/dist/assets/{filters-DCglZYsM.js → filters-DMOteumb.js} +2 -2
- package/packages/ui/dist/assets/{github-BH-BYjss.js → github-D2nDec9X.js} +2 -2
- package/packages/ui/dist/assets/{google-DYFWxZbC.js → google-lZZlQZPQ.js} +2 -2
- package/packages/ui/dist/assets/{help-circle-BXH_DtZ_.js → help-circle-D4cSSziR.js} +1 -1
- package/packages/ui/dist/assets/{id-Dr2yTeX1.js → id-BmhXZq5P.js} +2 -2
- package/packages/ui/dist/assets/{image-DeJey1Eg.js → image-Dz4FPeET.js} +2 -2
- package/packages/ui/dist/assets/index-B07bCKJ8.css +1 -0
- package/packages/ui/dist/assets/index-BV8s6ez1.js +1 -0
- package/packages/ui/dist/assets/index-BeUp3nOq.js +1 -0
- package/packages/ui/dist/assets/index-CvRylmZJ.js +16 -0
- package/packages/ui/dist/assets/{index-DVjAiPHI.js → index-DeoQO45V.js} +3 -3
- package/packages/ui/dist/assets/index-Et5S3ZaW.js +47 -0
- package/packages/ui/dist/assets/index-LiKeH8Cd.js +1780 -0
- package/packages/ui/dist/assets/index.es-dfpEui-9.js +26 -0
- package/packages/ui/dist/assets/{info-B91pD5Rw.js → info-77RSvIMg.js} +2 -2
- package/packages/ui/dist/assets/{info-circle-CySl3_on.js → info-circle-CWsII-NN.js} +2 -2
- package/packages/ui/dist/assets/{lightbulb-wwG8LQLo.js → lightbulb-BZXzm7XE.js} +2 -2
- package/packages/ui/dist/assets/{mail-D617YZ8y.js → mail-DMG1YqB0.js} +2 -2
- package/packages/ui/dist/assets/metamask-sdk-K1x_iqcc.js +542 -0
- package/packages/ui/dist/assets/{mobile-CD9KlA0m.js → mobile-DuJfOBSk.js} +2 -2
- package/packages/ui/dist/assets/{more-BQ_rpPXo.js → more-Cs9ySK2F.js} +2 -2
- package/packages/ui/dist/assets/{network-placeholder-BA7nl9e6.js → network-placeholder-CMDPmB1E.js} +2 -2
- package/packages/ui/dist/assets/{nftPlaceholder-D1EaaeDn.js → nftPlaceholder-Cj0f9y2u.js} +2 -2
- package/packages/ui/dist/assets/{off-DnGG35_w.js → off-9zSSsm-e.js} +2 -2
- package/packages/ui/dist/assets/{play-store-C5u2FvYB.js → play-store-CPwIb8Gj.js} +2 -2
- package/packages/ui/dist/assets/{plus-CM4xJkvI.js → plus-BOHQri8o.js} +2 -2
- package/packages/ui/dist/assets/{qr-code-kM3FUIdR.js → qr-code-DqhbK276.js} +1 -1
- package/packages/ui/dist/assets/{recycle-horizontal-DWrRWIBM.js → recycle-horizontal-BpGM56Qz.js} +2 -2
- package/packages/ui/dist/assets/{refresh-CztbjK2d.js → refresh-BPzZlhTP.js} +2 -2
- package/packages/ui/dist/assets/{reown-logo-DUHEIodF.js → reown-logo-OCkI0xzo.js} +2 -2
- package/packages/ui/dist/assets/{search-BJtb4mn1.js → search-B1JEJvwq.js} +2 -2
- package/packages/ui/dist/assets/{secp256k1-BjrTZpQx.js → secp256k1-BqUeV9Cb.js} +1 -1
- package/packages/ui/dist/assets/{send-B3_kavG1.js → send-JNsDlNoq.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontal-BrF9POiv.js → swapHorizontal-C3Z1FImK.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalBold-Dgf8OjXj.js → swapHorizontalBold-Cmdy2iAn.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalMedium-C6wX0JZj.js → swapHorizontalMedium-DFX57Nn9.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalRoundedBold-DTAVw6oF.js → swapHorizontalRoundedBold-baB389c8.js} +2 -2
- package/packages/ui/dist/assets/{swapVertical-KGlTFNoi.js → swapVertical-DmAYGfK1.js} +2 -2
- package/packages/ui/dist/assets/{telegram-DnLrK-4y.js → telegram-BHbYE-nI.js} +2 -2
- package/packages/ui/dist/assets/{three-dots-C6_oMJfw.js → three-dots-BwRWl6qJ.js} +2 -2
- package/packages/ui/dist/assets/{twitch-DvPqPD4f.js → twitch-Drx0q6B6.js} +2 -2
- package/packages/ui/dist/assets/{twitterIcon-BZdk82Am.js → twitterIcon-AuJ2KQ6p.js} +2 -2
- package/packages/ui/dist/assets/{verify-CbESD5G0.js → verify-Dkrd3q7o.js} +2 -2
- package/packages/ui/dist/assets/{verify-filled-BRjTrLxw.js → verify-filled-B9e3-nz-.js} +2 -2
- package/packages/ui/dist/assets/{w3m-modal-D9b2TGHc.js → w3m-modal-DtkEjQA7.js} +1 -1
- package/packages/ui/dist/assets/{wallet-B87E5UId.js → wallet-CqYzSiBd.js} +2 -2
- package/packages/ui/dist/assets/{wallet-placeholder-DmssSbYf.js → wallet-placeholder-B05kTN_K.js} +2 -2
- package/packages/ui/dist/assets/{walletconnect-CZkLKWMZ.js → walletconnect-BwoPXLaS.js} +3 -3
- package/packages/ui/dist/assets/{warning-circle-BPTpHB1P.js → warning-circle-DK9Ai8rT.js} +2 -2
- package/packages/ui/dist/assets/{x-D_dEtEDU.js → x-BNdh5DVW.js} +2 -2
- package/packages/ui/dist/index.html +2 -2
- package/templates/default/.agents/skills/sail-mandates/SKILL.md +13 -1
- package/templates/default/.agents/skills/sail-mandates/references/approvals.md +3 -3
- package/templates/default/.agents/skills/sail-mandates/references/examples-index.md +3 -3
- package/templates/default/.agents/skills/sail-onboarding/SKILL.md +2 -2
- package/templates/default/.agents/skills/sail-project-info/SKILL.md +2 -0
- package/templates/default/.agents/skills/sail-servers/SKILL.md +1 -0
- package/templates/default/.agents/skills/sail-swap-quote/SKILL.md +76 -0
- package/templates/default/.agents/skills/sail-template-approve-batch/SKILL.md +98 -0
- package/templates/default/.agents/skills/sail-template-borrow/SKILL.md +68 -0
- package/templates/default/.agents/skills/sail-template-deposit/SKILL.md +61 -0
- package/templates/default/.agents/skills/sail-template-swap/SKILL.md +207 -0
- package/templates/default/.agents/skills/sail-template-swap-no-oracle/SKILL.md +100 -0
- package/templates/default/.agents/skills/sail-template-transfer/SKILL.md +52 -0
- package/templates/default/.agents/skills/sail-template-withdraw/SKILL.md +50 -0
- package/templates/default/.agents/skills/sail-templates/SKILL.md +142 -0
- package/templates/default/.agents/skills/sail-templates/catalog.mjs +200 -0
- package/templates/default/.agents/skills/sail-templates/deployed.json +133 -0
- package/templates/default/.agents/skills/sail-templates/oracles/IOracle.sol +13 -0
- package/templates/default/.agents/skills/sail-templates/oracles/UniV3TwapOracle.sol +114 -0
- package/templates/default/.agents/skills/sail-templates/oracles/libs/FullMath.sol +62 -0
- package/templates/default/.agents/skills/sail-templates/oracles/libs/TickMath.sol +44 -0
- package/templates/default/.agents/skills/sail-templates/references/config-schemas.md +139 -0
- package/templates/default/.agents/skills/sail-templates/references/reuse-flow.md +139 -0
- package/templates/default/.agents/skills/sail-token-resolve/SKILL.md +174 -0
- package/templates/default/.env.example +9 -0
- package/templates/default/AGENTS.md +21 -2
- package/templates/default/docs/PERMISSION_MODEL.md +2 -2
- package/{examples → templates/default/examples}/custom-mandate/.sail/contracts/interfaces/IPermission.sol +4 -0
- package/{examples → templates/default/examples}/custom-mandate/README.md +4 -1
- package/{examples → templates/default/examples}/permissions/interfaces/IBatchPermission.sol +2 -0
- package/{examples → templates/default/examples}/permissions/interfaces/IPermission.sol +4 -0
- package/templates/default/scripts/quote-swap.mjs +211 -0
- package/templates/default/scripts/resolve-token.mjs +992 -0
- package/templates/default/scripts/shared-template-addr.mjs +110 -0
- package/templates/default/test/BoundedCallPermission.t.sol +2 -1
- package/docs/PERMISSION_MODEL.md +0 -93
- package/examples/README.md +0 -24
- package/examples/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +0 -84
- package/examples/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +0 -97
- package/examples/lifi-permissions/README.md +0 -53
- package/packages/cli/dist/mcp.cjs +0 -15534
- package/packages/ui/dist/assets/arrow-bottom-DjQ4jGMe.js +0 -8
- package/packages/ui/dist/assets/arrow-left-BjAyI8D1.js +0 -8
- package/packages/ui/dist/assets/arrow-right-BlFO4Tcx.js +0 -8
- package/packages/ui/dist/assets/arrow-top-DJ3oh3DH.js +0 -8
- package/packages/ui/dist/assets/ccip-U9yiu_EK.js +0 -1
- package/packages/ui/dist/assets/chevron-bottom-BbHhmTQq.js +0 -8
- package/packages/ui/dist/assets/chevron-left-D8BDH4Wo.js +0 -8
- package/packages/ui/dist/assets/chevron-right-BQuQk5zc.js +0 -8
- package/packages/ui/dist/assets/chevron-top-1oeSXkx4.js +0 -8
- package/packages/ui/dist/assets/core-DbkMGSYW.js +0 -907
- package/packages/ui/dist/assets/cursor-DRFqL8nZ.js +0 -3
- package/packages/ui/dist/assets/events-CZkVDFih.js +0 -1
- package/packages/ui/dist/assets/external-link-CX8skCbH.js +0 -8
- package/packages/ui/dist/assets/fallback-DWQYKf3l.js +0 -1
- package/packages/ui/dist/assets/index-BP-UuQ8g.js +0 -1
- package/packages/ui/dist/assets/index-BksbyFjf.js +0 -1
- package/packages/ui/dist/assets/index-C55-Ne92.js +0 -16
- package/packages/ui/dist/assets/index-D6_Zei-R.js +0 -1790
- package/packages/ui/dist/assets/index-DZ2C2rHq.css +0 -1
- package/packages/ui/dist/assets/index-uSdXE5b_.js +0 -1
- package/packages/ui/dist/assets/index.es-dZlNPpfR.js +0 -26
- package/packages/ui/dist/assets/metamask-sdk-RbLbxUgF.js +0 -542
- package/packages/ui/dist/assets/native-CJ5et6AR.js +0 -1
- package/packages/ui/dist/assets/parseSignature-E29x8poM.js +0 -1
- package/scripts/check-docs.mjs +0 -309
- package/scripts/check-init.mjs +0 -123
- package/scripts/check-update.mjs +0 -177
- package/scripts/clean.mjs +0 -17
- package/scripts/pack.mjs +0 -21
- /package/{examples → templates/default/examples}/custom-mandate/foundry.toml +0 -0
- /package/{examples → templates/default/examples}/custom-mandate/mandates/BoundedCallPermission.sol +0 -0
- /package/{examples → templates/default/examples}/custom-mandate/mandates/README.md +0 -0
- /package/{examples → templates/default/examples}/custom-mandate/mandates/SailCalldata.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedApproveAndCallBatch.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedLimitless_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedPerp_GMXv2_Arbitrum.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedStake_Venice_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedSupply_AaveV3_Arbitrum.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedSwapNative_UniswapV3_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV3_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV4_Unichain.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedTransfer_ERC20_Ethereum.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedVault_ERC4626_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/README.md +0 -0
- /package/{examples → templates/default/examples}/permissions/SailCalldata.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/foundry.toml +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Config schemas & enforced invariants — authoritative (from source)
|
|
2
|
+
|
|
3
|
+
Each shared template stores per-account config under `mapping(address => …)` and decodes a
|
|
4
|
+
`configure(...)` blob in `_applyConfig`. The tuples below are taken **directly from
|
|
5
|
+
`Protocol/contracts/templates/*.sol`** and are the source of truth — encode with
|
|
6
|
+
`abi.encode(...)` in that exact order. (The `@sail/sdk/templates` builders track a
|
|
7
|
+
previously-deployed set; verify a builder's params match the tuple here before using it.)
|
|
8
|
+
|
|
9
|
+
All caps/amounts are in the relevant token's **base units**. Every template runs under the
|
|
10
|
+
kernel's `staticcall` + gas cap + fail-closed semantics: a revert ⇒ deny.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## SwapPermission
|
|
15
|
+
```
|
|
16
|
+
abi.encode(address[] routers, address[] tokensIn, address[] tokensOut,
|
|
17
|
+
uint256 maxAmountPerTx, uint256 maxSlippageBps,
|
|
18
|
+
address priceOracle, uint256 maxPriceAgeSec)
|
|
19
|
+
```
|
|
20
|
+
Selectors: `0x414bf389` (V3 `exactInputSingle` w/ deadline), `0x04e45aaf` (V3-02 no deadline),
|
|
21
|
+
`0x38ed1739` (V2 `swapExactTokensForTokens`). Invariants: `value == 0` (native value rejected);
|
|
22
|
+
`target ∈ routers`; **`tokenIn != tokenOut` / `path[0] != path[last]`** (self-routes denied — a
|
|
23
|
+
round-trip would burn AMM fees while an oracle reporting base==quote clears the band);
|
|
24
|
+
`tokenIn`/`path[0] ∈ tokensIn`; `tokenOut`/`path[last] ∈ tokensOut`;
|
|
25
|
+
`recipient`/`to == account`; `amountIn ≤ maxAmountPerTx`; **oracle slippage band ALWAYS enforced**.
|
|
26
|
+
The oracle is **mandatory** (contract `v2`): `_applyConfig` reverts `OracleRequired` if
|
|
27
|
+
`priceOracle == 0`, `MissingPriceAge` if `maxPriceAgeSec == 0`, and `SlippageBpsTooLarge` if
|
|
28
|
+
`maxSlippageBps > 9_999`. `maxSlippageBps == 0` ⇒ zero tolerance (strictest), NOT a bypass.
|
|
29
|
+
`priceOracle` is an `IOracle` adapter (`getPrice(base,quote) → (price, dec, updatedAt)`), not a raw
|
|
30
|
+
feed; dust trades whose floor truncates to 0 are denied. V2 intermediate hops are NOT checked.
|
|
31
|
+
(For no-oracle tokens use `SwapPermissionNoOracle`.)
|
|
32
|
+
|
|
33
|
+
## SwapPermissionNoOracle
|
|
34
|
+
```
|
|
35
|
+
abi.encode(address[] routers, address[] tokensIn, address[] tokensOut,
|
|
36
|
+
uint256 maxAmountPerTx, ReferencePool[] referencePools)
|
|
37
|
+
|
|
38
|
+
struct ReferencePool { address tokenIn; address tokenOut; address pool;
|
|
39
|
+
PoolKind kind /* 0=V2, 1=V3 */; uint256 toleranceBps; }
|
|
40
|
+
```
|
|
41
|
+
Same selectors and structural checks as `SwapPermission` (`0x414bf389`, `0x04e45aaf`,
|
|
42
|
+
`0x38ed1739`): `target ∈ routers`; `tokenIn`/`path[0] ∈ tokensIn`; `tokenOut`/`path[last] ∈
|
|
43
|
+
tokensOut`; `recipient`/`to == account`; `amountIn ≤ maxAmountPerTx`. **Differs** from
|
|
44
|
+
`SwapPermission` in the price judgement: instead of an oracle band it enforces a **pool-referenced
|
|
45
|
+
hallucination band** — `amountOutMin` must be ≥ the output implied by the live spot price of the
|
|
46
|
+
operator-named `referencePool` for that directional pair, minus `toleranceBps`, and `amountOutMin
|
|
47
|
+
> 0`. Config-time invariants (`_applyConfig` reverts ⇒ configure fails): `toleranceBps ≤ 5_000`
|
|
48
|
+
(50% max); `pool != 0`; pool's `token0()`/`token1()` must match the pair (orientation precomputed);
|
|
49
|
+
**strict coverage** — every non-self directional `(tokenIn, tokenOut)` combination MUST have a
|
|
50
|
+
reference pool or configure reverts `MissingReferencePool`. Fail-closed at evaluate if the pool is
|
|
51
|
+
missing/unreadable/illiquid or the floor truncates to zero.
|
|
52
|
+
|
|
53
|
+
> ⚠️ **NOT manipulation-resistant.** The reference is a single pool's LIVE spot price, movable
|
|
54
|
+
> within the same transaction (flash-loan / sandwich). This band only catches an *honest* mistake
|
|
55
|
+
> (a confused agent quoting a wildly wrong price); it is NOT slippage protection. For
|
|
56
|
+
> manipulation-resistant protection use the oracle-gated `SwapPermission`. Cap is per-tx, not
|
|
57
|
+
> cumulative.
|
|
58
|
+
|
|
59
|
+
## BorrowPermission
|
|
60
|
+
```
|
|
61
|
+
abi.encode(address[] protocols, address[] assets, uint256 maxAmountPerTx,
|
|
62
|
+
uint256 maxLtvBps, address collateralOracle, address borrowOracle,
|
|
63
|
+
uint256 maxPriceAgeSec)
|
|
64
|
+
```
|
|
65
|
+
Selectors: Aave `borrow(address,uint256,uint256,uint16,address)` (variable-rate only —
|
|
66
|
+
`rateMode != 2` denies; stable-rate debt is rejected), Morpho **Optimizer/Morpho-Aave**
|
|
67
|
+
`borrow(address,uint256,address,address)` (NOT Morpho Blue — its ABI differs and simply won't
|
|
68
|
+
match, i.e. fails closed), Compound `borrow(uint256)` (target is the cToken; the underlying is
|
|
69
|
+
resolved via `cToken.underlying()` and both the allowlist and LTV are underlying-denominated —
|
|
70
|
+
targets with no `underlying()`, e.g. cETH, are denied). Invariants: `protocols`/`assets` must be
|
|
71
|
+
non-empty with no zero addresses (`EmptyAllowlist`/`ZeroAddress` revert otherwise);
|
|
72
|
+
`target ∈ protocols`; `asset ∈ assets`; `amount ≤ maxAmountPerTx`; `onBehalfOf`/`receiver ==
|
|
73
|
+
account`. **Oracle modes:** zero oracles ⇒ amount-cap-only (no LTV ceiling applied at all, despite
|
|
74
|
+
`maxLtvBps` being stored); both oracles set ⇒ `_ltvCheck` enforces the LTV bound. Exactly one
|
|
75
|
+
oracle set reverts `OracleConfigInconsistent` at configure — it's an all-or-nothing pair.
|
|
76
|
+
|
|
77
|
+
## TransferPermission
|
|
78
|
+
```
|
|
79
|
+
abi.encode(address[] allowedRecipients, address[] allowedTokens, uint256 maxAmountPerTx)
|
|
80
|
+
```
|
|
81
|
+
Selectors: `0xa9059cbb` `transfer`, `0x23b872dd` `transferFrom`. Invariants: `value == 0`;
|
|
82
|
+
`allowedRecipients`/`allowedTokens` must be non-empty with no zero addresses
|
|
83
|
+
(`EmptyAllowlist`/`ZeroAddress` revert at configure otherwise); `target (token) ∈ allowedTokens`;
|
|
84
|
+
`to ∈ allowedRecipients`; `amount ≤ maxAmountPerTx`;
|
|
85
|
+
**`transferFrom` requires `from == account`** (stricter than the old TransferTarget).
|
|
86
|
+
Max 50 entries per allowlist.
|
|
87
|
+
|
|
88
|
+
## DepositPermission
|
|
89
|
+
```
|
|
90
|
+
abi.encode(address[] targets, address[] tokens, uint256 maxAmountPerTx)
|
|
91
|
+
```
|
|
92
|
+
Selectors: `deposit(uint256,address)` (ERC-4626), `mint(uint256,address)`,
|
|
93
|
+
`deposit(address,uint256,address,uint16)` (Aave v2), `supply(address,uint256,address,uint16)`
|
|
94
|
+
(Aave v3). Invariants: `value == 0`; `targets`/`tokens` must be non-empty with no zero addresses
|
|
95
|
+
(`EmptyAllowlist`/`ZeroAddress` revert at configure otherwise); `target ∈ targets`; token
|
|
96
|
+
allowlist enforced (Aave: the `asset` arg; ERC-4626: `target` itself must be in `tokens`);
|
|
97
|
+
`amount`/`shares ≤ maxAmountPerTx`; `receiver`/`onBehalfOf == account`. `mint` cap is in
|
|
98
|
+
**shares** — account for share price.
|
|
99
|
+
|
|
100
|
+
## WithdrawPermission
|
|
101
|
+
```
|
|
102
|
+
abi.encode(address[] tokens, address allowedRecipient, uint256 maxAmountPerTx)
|
|
103
|
+
```
|
|
104
|
+
Selectors: `0xa9059cbb` `transfer`, `0x23b872dd` `transferFrom`. Invariants: `value == 0`;
|
|
105
|
+
`tokens` must be non-empty and `allowedRecipient` non-zero, with no zero-address tokens
|
|
106
|
+
(`EmptyAllowlist`/`ZeroAddress` revert at configure otherwise); `target (token) ∈ tokens`;
|
|
107
|
+
`to == allowedRecipient` (single address per config); `amount ≤ maxAmountPerTx`;
|
|
108
|
+
**`transferFrom` requires `from == account`**. To change the recipient, `reconfigure` with a new
|
|
109
|
+
blob.
|
|
110
|
+
|
|
111
|
+
## ApproveAndCallBatchPermission
|
|
112
|
+
```
|
|
113
|
+
abi.encode(Config{
|
|
114
|
+
address[] tokens; address[] spenders; ConsumingPair[] consumingPairs;
|
|
115
|
+
uint256[] maxApprovalAmounts; bool requireAmountMatch; bool allowUnconstrainedRecipient;
|
|
116
|
+
})
|
|
117
|
+
struct ConsumingPair { address target; bytes4 selector; }
|
|
118
|
+
```
|
|
119
|
+
ABI tuple: `(address[],address[],(address,bytes4)[],uint256[],bool,bool)`. `consumingPairs` is a
|
|
120
|
+
**struct array**, not two flat arrays — each `(target, selector)` is bound together, so a selector
|
|
121
|
+
is authorised only on its paired target. Authorises exactly the 3-call batch:
|
|
122
|
+
`approve(spender, amount)` → consuming call → `approve(spender, 0)`. Invariants: `tokens`,
|
|
123
|
+
`spenders`, and `consumingPairs` must each be non-empty and contain no zero addresses/selectors
|
|
124
|
+
(`EmptyAllowlist` reverts otherwise); token ∈ `tokens` with `amount ≤ maxApprovalAmount[token]`;
|
|
125
|
+
`spender ∈ spenders`; `(target, selector)` ∈ `consumingPairs`; the pre-batch allowance on
|
|
126
|
+
`(token, spender)` must be zero; the consuming call must target the approved `spender` and pull the
|
|
127
|
+
approved `token`; allowance reset to zero in the same batch; if `requireAmountMatch`, the consuming
|
|
128
|
+
call's leading `uint256` arg must equal the approve amount. `maxApprovalAmounts` is index-parallel
|
|
129
|
+
with `tokens` (length mismatch reverts).
|
|
130
|
+
|
|
131
|
+
> ⚠️ **Field name/polarity — `allowUnconstrainedRecipient`, NOT `requireRecipientIsAccount`.**
|
|
132
|
+
> The field is an **opt-out**, default-safe: `false` (the default, i.e. an omitted/zero-value
|
|
133
|
+
> bool) means the consuming call's output recipient is decoded and **must equal the account** —
|
|
134
|
+
> the safe posture. `true` is a deliberate opt-out that leaves the recipient **unconstrained**.
|
|
135
|
+
> Setting the bool `true` thinking it "requires/pins the recipient" gets the **opposite**
|
|
136
|
+
> behaviour on-chain. Either way, the recipient (and the consumed asset) can only be decoded for
|
|
137
|
+
> selectors in the fixed-offset set below — any other selector is denied under the default pin:
|
|
138
|
+
> `swapExactTokensForTokens`, V3 `exactInputSingle` (both SwapRouter layouts), Aave V2/V3
|
|
139
|
+
> `deposit`/`supply`, ERC-4626 `deposit`/`mint`.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Reuse-first flow — register + configure a shared template
|
|
2
|
+
|
|
3
|
+
The whole point of a **shared** template: the logic is already deployed, so you skip the
|
|
4
|
+
deploy entirely. You give one SMA its own private config inside the singleton's
|
|
5
|
+
`mapping(address => …)` and register it on the kernel.
|
|
6
|
+
|
|
7
|
+
> **Two steps, not one (today).** The on-chain **intended** design is a single signed call —
|
|
8
|
+
> `MandateFactory.attach(account, template, params, configureDeadline, configureSig, kernelSig)`
|
|
9
|
+
> — that registers the template on the kernel AND writes its per-account config together.
|
|
10
|
+
> **The shipped `sailor` CLI does not implement that combined call yet.** `sailor mandate
|
|
11
|
+
> attach` performs **only the kernel registration** (`RegisterPermission` / the batch
|
|
12
|
+
> `registerPermissions`); it never calls `configure`/`configureDirect`, carries no `--params`,
|
|
13
|
+
> and builds no `Configure` EIP-712 signature. The SDK's `client.mandate.attach(...)` is a
|
|
14
|
+
> `notImplemented()` stub.
|
|
15
|
+
>
|
|
16
|
+
> A registered-but-unconfigured singleton has `isConfigured == false`, so the kernel's
|
|
17
|
+
> `evaluate()` **denies every call** — a registered-but-dead permission. You MUST configure
|
|
18
|
+
> separately, with `sailor mandate configure` (step 4 below). The flow below describes what
|
|
19
|
+
> actually works now.
|
|
20
|
+
|
|
21
|
+
## On-chain mechanics (`MandateFactory` + `ConfigurablePermission`)
|
|
22
|
+
|
|
23
|
+
These are the **on-chain** entry points — the contract truth for what the combined/intended
|
|
24
|
+
flow does. The shipped CLI uses a subset of them today (registration only); the configure
|
|
25
|
+
half is driven directly for now.
|
|
26
|
+
|
|
27
|
+
Registration (kernel side — what `sailor mandate attach` does today):
|
|
28
|
+
|
|
29
|
+
| Function | Use |
|
|
30
|
+
|---|---|
|
|
31
|
+
| kernel `registerPermission(account, permission, deadline, sig)` | Register one template address on an SMA |
|
|
32
|
+
| kernel `registerPermissions(account, permissions[], deadline, sig)` | Register several in one signature (the `--address a,b,c` batch path) |
|
|
33
|
+
| kernel `revokePermissions(account, permissions[], deadline, sig)` | Remove (`sailor mandate revoke`) |
|
|
34
|
+
|
|
35
|
+
Configuration (singleton side — the half the CLI does NOT do today):
|
|
36
|
+
|
|
37
|
+
| Function | Use |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `configure(account, params, deadline, sig)` | Set per-account config, gated by an EIP-712 `Configure(account, paramsHash, nonce, deadline, epoch)` signature from the account's `permissionSigner` (ECDSA or ERC-1271). |
|
|
40
|
+
| `configureDirect(account, params)` | Set per-account config, gated by `msg.sender == permissionSigner`. The working path when the owner IS the permissionSigner — a plain owner tx, no signature. |
|
|
41
|
+
| `reconfigure(...)` | Intended batch re-config path (`MandateFactory.reconfigure`); functionally `configure` again with a new blob. |
|
|
42
|
+
|
|
43
|
+
The intended combined call (`MandateFactory.attach`) and its batch/replace/detach siblings are
|
|
44
|
+
defined on the factory contract; they are the target state for a future one-step CLI command,
|
|
45
|
+
not something the current CLI drives.
|
|
46
|
+
|
|
47
|
+
The kernel evaluates a registered permission via `staticcall` with a per-permission gas cap;
|
|
48
|
+
a revert or over-gas is treated as `false` (fail-closed), never a kernel revert.
|
|
49
|
+
|
|
50
|
+
## The steps (as they work today)
|
|
51
|
+
|
|
52
|
+
### 1. Discover the address
|
|
53
|
+
```bash
|
|
54
|
+
node SKILLS/sail-templates/catalog.mjs --chain <id>
|
|
55
|
+
```
|
|
56
|
+
The address comes from [`deployed.json`](../deployed.json). **If the template isn't deployed on
|
|
57
|
+
your chain yet** (e.g. `SwapPermissionNoOracle` is not deployed anywhere), that's the
|
|
58
|
+
prerequisite: deploy the singleton once (Protocol team / `DeploySharedTemplates`) and record
|
|
59
|
+
its address there. A template address is only meaningful paired with that chain's kernel.
|
|
60
|
+
|
|
61
|
+
### 2. Build the config blob
|
|
62
|
+
Encode the config tuple **exactly as the contract's `_applyConfig` decodes it** — the
|
|
63
|
+
authoritative tuples are in [config-schemas.md](config-schemas.md). Use `abi.encode(...)` in
|
|
64
|
+
that order, or the typed builder under `@sail/sdk/templates` **only after** verifying its param
|
|
65
|
+
tuple equals the source tuple (the SDK builders track a previously-deployed set and may differ
|
|
66
|
+
from these source contracts):
|
|
67
|
+
```ts
|
|
68
|
+
import { boundedSwapTemplate } from "@sail/sdk/templates"; // verify params vs config-schemas.md first
|
|
69
|
+
|
|
70
|
+
const params = { routers, tokensIn, tokensOut, maxAmountPerTx, maxSlippageBps,
|
|
71
|
+
priceOracle, maxPriceAgeSec };
|
|
72
|
+
const blob = boundedSwapTemplate.encoder.encode(params);
|
|
73
|
+
|
|
74
|
+
// Always surface the human-readable summary + warnings to the user first:
|
|
75
|
+
const explanation = boundedSwapTemplate.explainer.explain(params);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> ⚠️ **Encoding gotcha (fails closed).** Templates do NOT all encode the same way.
|
|
79
|
+
> `TransferPermission` / `WithdrawPermission` / `DepositPermission` decode **flat top-level
|
|
80
|
+
> params** (`abi.encode(address[], address[], uint256)`). `ApproveAndCallBatchPermission`
|
|
81
|
+
> decodes a **single wrapped struct** (`abi.encode(Config{…})`) — its blob starts with a `0x20`
|
|
82
|
+
> tuple-offset word. Wrapping a flat-params blob (or vice-versa) reverts at configure. **Always
|
|
83
|
+
> round-trip-decode the blob and `cast call`-simulate `configureDirect` before sending.**
|
|
84
|
+
|
|
85
|
+
### 3. Register the singleton on the kernel (does NOT configure)
|
|
86
|
+
```bash
|
|
87
|
+
sailor mandate attach --address <SHARED_ADDRESS> --sma <SMA> --label "<primitive>"
|
|
88
|
+
```
|
|
89
|
+
This builds and submits the kernel `RegisterPermission` (or `registerPermissions` for a
|
|
90
|
+
comma-separated `--address` list). It does NOT call `configure`. After this step the address is
|
|
91
|
+
in `getPermissions(<SMA>)` but `isConfigured(<SMA>) == false`, so every dispatch is still
|
|
92
|
+
denied. Proceed to step 4.
|
|
93
|
+
|
|
94
|
+
### 4. Configure the per-account bounds (the half that makes it live)
|
|
95
|
+
When the owner IS the SMA's `permissionSigner` (the default Sailor onboarding), drive
|
|
96
|
+
`configureDirect(account, <config blob>)` as a plain owner transaction — `msg.sender ==
|
|
97
|
+
permissionSigner` holds and no EIP-712 signature is needed — via the shipped command:
|
|
98
|
+
```bash
|
|
99
|
+
sailor mandate configure --address <SHARED_ADDRESS> --sma <SMA> \
|
|
100
|
+
--template <TemplateName> --args-file ./config.json
|
|
101
|
+
# or with a pre-encoded blob:
|
|
102
|
+
sailor mandate configure --address <SHARED_ADDRESS> --sma <SMA> --params <0x-blob>
|
|
103
|
+
```
|
|
104
|
+
It does, in order:
|
|
105
|
+
1. **Pre-flight (no gas):** an `eth_call` simulation of `configureDirect` from `permissionSigner`. A revert here means the config is invalid before any gas is spent — fix the blob (see the encoding gotcha) and retry. Pass `--simulate-only` to stop here.
|
|
106
|
+
2. **Send it:** pushes the `configureDirect` call to the owner wallet as an `arbitrary-tx` request through the signing station; the owner approves in the browser and the owner wallet sends the transaction.
|
|
107
|
+
3. **Verify:** reads `isConfigured(<SMA>)` on the singleton and errors if it isn't `true`.
|
|
108
|
+
|
|
109
|
+
If the owner is **not** the `permissionSigner` (e.g. a separate mandate-signer / multisig), use
|
|
110
|
+
`configure(account, params, deadline, sig)` instead: construct the EIP-712 `Configure` digest
|
|
111
|
+
(bound to the kernel's current `registrationEpoch`), have the `permissionSigner` sign it, and
|
|
112
|
+
submit. `sailor mandate configure` does not build this signature for you — that path is still
|
|
113
|
+
manual.
|
|
114
|
+
|
|
115
|
+
### 5. Verify off-chain (no gas)
|
|
116
|
+
Prove it accepts what you want and rejects what you don't, before trusting it:
|
|
117
|
+
```bash
|
|
118
|
+
sailor mandate simulate --address <SHARED_ADDRESS> --sma <SMA> \
|
|
119
|
+
--target <router> --calldata <hex> --expect pass
|
|
120
|
+
# or a batch file:
|
|
121
|
+
sailor mandate simulate --address <SHARED_ADDRESS> --sma <SMA> --calls ./probe.json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 6. Reconfigure when bounds change
|
|
125
|
+
New cap or allowlist? Re-encode the blob and repeat step 4 (`sailor mandate configure --force`,
|
|
126
|
+
or the manual `configure` signature path) — the address stays registered, no re-attach. (On-chain
|
|
127
|
+
`MandateFactory.reconfigure` is the intended batch path for this.)
|
|
128
|
+
|
|
129
|
+
## Gotchas
|
|
130
|
+
|
|
131
|
+
- **Unconfigured = deny.** A freshly-registered shared template with no `configure()` has
|
|
132
|
+
`isConfigured == false` and denies everything. Register AND configure — don't stop at
|
|
133
|
+
`sailor mandate attach`.
|
|
134
|
+
- **`sailor mandate attach` is register-only.** It does not configure. This is the single most
|
|
135
|
+
common trap; see steps 3 and 4.
|
|
136
|
+
- **Caps are in base units** of the relevant token (e.g. `25_000_000` = 25 USDC).
|
|
137
|
+
- **permissionSigner is the trust anchor** for config. In production use a multisig / timelock
|
|
138
|
+
— whoever holds it can widen allowlists and caps on every account they signed for.
|
|
139
|
+
- The shared singletons are **unaudited examples**. Simulate (Step 5) is not optional.
|