@dev.sail.money/sailor 1.3.0-98 → 1.4.0-244
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 +7 -7
- package/LICENSE +1 -1
- package/README.md +64 -472
- package/package.json +5 -6
- package/packages/cli/README.md +1 -1
- package/packages/cli/dist/index.cjs +1481 -463
- package/packages/cli/dist/server.cjs +16088 -17900
- 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/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 +9 -7
- package/packages/sdk/dist/chains.d.ts.map +1 -1
- package/packages/sdk/dist/chains.js +103 -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 +2 -1
- package/packages/sdk/dist/fees.d.ts.map +1 -1
- package/packages/sdk/dist/fees.js +2 -1
- package/packages/sdk/dist/fees.js.map +1 -1
- package/packages/sdk/dist/index.d.ts +4 -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 +1 -1
- package/packages/sdk/dist/intelligence.js +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/types.d.ts +7 -0
- package/packages/sdk/dist/types.d.ts.map +1 -1
- package/packages/sdk/package.json +6 -2
- package/packages/ui/dist/assets/{add-BX5reKtK.js → add-sHg4oAtj.js} +2 -2
- package/packages/ui/dist/assets/{all-wallets-IZ3PlVF-.js → all-wallets-BhPcqrMm.js} +2 -2
- package/packages/ui/dist/assets/{app-store-ARgRdMdF.js → app-store-B_PQMwCf.js} +1 -1
- package/packages/ui/dist/assets/{apple-VCaqsI9w.js → apple-CqAFK7Fj.js} +2 -2
- package/packages/ui/dist/assets/arrow-bottom-CbxIflTv.js +8 -0
- package/packages/ui/dist/assets/{arrow-bottom-circle-h5vFiZ2I.js → arrow-bottom-circle-BFgY35YA.js} +2 -2
- package/packages/ui/dist/assets/arrow-left-DCG2m2BY.js +8 -0
- package/packages/ui/dist/assets/arrow-right-NrPOhG6W.js +8 -0
- package/packages/ui/dist/assets/arrow-top-BHTmGsDP.js +8 -0
- package/packages/ui/dist/assets/{bank-6c28yOM9.js → bank-BqDvUazs.js} +2 -2
- package/packages/ui/dist/assets/{basic-CJnbTfSd.js → basic-CHFjQMod.js} +177 -177
- package/packages/ui/dist/assets/{browser-CDKlxM3D.js → browser-C_VdKE7p.js} +2 -2
- package/packages/ui/dist/assets/{card-DQdED02q.js → card-w-1brQoV.js} +2 -2
- package/packages/ui/dist/assets/ccip-jvDTeGaI.js +1 -0
- package/packages/ui/dist/assets/{checkmark-JoRNVoFV.js → checkmark-CLBmxdAF.js} +2 -2
- package/packages/ui/dist/assets/{checkmark-bold-B6bGHVIs.js → checkmark-bold-Ca_e1PMs.js} +2 -2
- package/packages/ui/dist/assets/chevron-bottom-BhvFRdSL.js +8 -0
- package/packages/ui/dist/assets/chevron-left-cjMaNbHC.js +8 -0
- package/packages/ui/dist/assets/chevron-right-CRuHR0b-.js +8 -0
- package/packages/ui/dist/assets/chevron-top-Bq0a2jj-.js +8 -0
- package/packages/ui/dist/assets/{chrome-store-D-fiJ9qJ.js → chrome-store-Cieq6j7x.js} +2 -2
- package/packages/ui/dist/assets/{clock-xCPkzwjj.js → clock-Dkj5WbVg.js} +2 -2
- package/packages/ui/dist/assets/{close-BkqmPpWG.js → close-C8eE6Dfz.js} +2 -2
- package/packages/ui/dist/assets/{coinPlaceholder-PIvN9oq_.js → coinPlaceholder-DndKH-8C.js} +2 -2
- package/packages/ui/dist/assets/{compass-CW5kWcxW.js → compass-DzoLByzm.js} +2 -2
- package/packages/ui/dist/assets/{copy-DUZlAQJJ.js → copy-Y7UMmVLt.js} +2 -2
- package/packages/ui/dist/assets/core-BkWdAWjX.js +907 -0
- package/packages/ui/dist/assets/cursor-BAQNxqYB.js +3 -0
- package/packages/ui/dist/assets/{cursor-transparent-BUAMB0G1.js → cursor-transparent-BUHY29Re.js} +2 -2
- package/packages/ui/dist/assets/{desktop-BxdlwV8P.js → desktop-DkZLCs5P.js} +2 -2
- package/packages/ui/dist/assets/{disconnect-GelNh3mn.js → disconnect-CphsO-wg.js} +2 -2
- package/packages/ui/dist/assets/{discord-N0jnxkzC.js → discord-CYH46bgt.js} +2 -2
- package/packages/ui/dist/assets/{etherscan-B41u7wF4.js → etherscan-2-3-Icbx.js} +2 -2
- package/packages/ui/dist/assets/events-DQ172AOg.js +1 -0
- package/packages/ui/dist/assets/{exclamation-triangle-BWJC56FC.js → exclamation-triangle-Bx9-1mpn.js} +2 -2
- package/packages/ui/dist/assets/{extension-G6I6oogw.js → extension-BuDZDNPV.js} +2 -2
- package/packages/ui/dist/assets/external-link-k6oTiNch.js +8 -0
- package/packages/ui/dist/assets/{facebook-47twy32b.js → facebook-BVB4qCju.js} +2 -2
- package/packages/ui/dist/assets/{farcaster-DYqnqCqs.js → farcaster-CxfWmDK3.js} +2 -2
- package/packages/ui/dist/assets/{filters-dF6xeJnE.js → filters-CXyIzkBz.js} +2 -2
- package/packages/ui/dist/assets/{github-DbW4Ihz2.js → github-Bga1HQTm.js} +2 -2
- package/packages/ui/dist/assets/{google-TKm7G8LF.js → google-CKLjkN5t.js} +2 -2
- package/packages/ui/dist/assets/{help-circle-CY3qTmsG.js → help-circle-CW4A601v.js} +1 -1
- package/packages/ui/dist/assets/{id-JpHW4q14.js → id-BaRFLHqy.js} +2 -2
- package/packages/ui/dist/assets/{image-Dip2fG98.js → image-4pE_c0pP.js} +2 -2
- package/packages/ui/dist/assets/index-B07bCKJ8.css +1 -0
- package/packages/ui/dist/assets/index-BKPTb_0U.js +1 -0
- package/packages/ui/dist/assets/index-BV8s6ez1.js +1 -0
- package/packages/ui/dist/assets/index-BaLMTrx4.js +47 -0
- package/packages/ui/dist/assets/index-CguqCMNp.js +16 -0
- package/packages/ui/dist/assets/index-RMrT9ktE.js +1780 -0
- package/packages/ui/dist/assets/{index-q0mGElxZ.js → index-_n5fH6AD.js} +3 -3
- package/packages/ui/dist/assets/index.es-rQvUXQTw.js +26 -0
- package/packages/ui/dist/assets/{info-B1enFsqh.js → info-UMAWEFh_.js} +2 -2
- package/packages/ui/dist/assets/{info-circle-D4j3FISs.js → info-circle-DuTp8NTC.js} +2 -2
- package/packages/ui/dist/assets/{lightbulb-DZbKr-vS.js → lightbulb-CkYYJbAa.js} +2 -2
- package/packages/ui/dist/assets/{mail-D-TO_uVQ.js → mail-CntZNMWz.js} +2 -2
- package/packages/ui/dist/assets/metamask-sdk-DIj7UxWU.js +542 -0
- package/packages/ui/dist/assets/{mobile-Bkmwzav2.js → mobile-DdyrzTfW.js} +2 -2
- package/packages/ui/dist/assets/{more-D1_I_jiB.js → more-DpZ1Bqpv.js} +2 -2
- package/packages/ui/dist/assets/{network-placeholder-DUaFjoXh.js → network-placeholder-B8yy8U56.js} +2 -2
- package/packages/ui/dist/assets/{nftPlaceholder-Cc0iUryh.js → nftPlaceholder-BXFbCJ-Z.js} +2 -2
- package/packages/ui/dist/assets/{off-BvX71sse.js → off-DavoQSqH.js} +2 -2
- package/packages/ui/dist/assets/{play-store-XzLbtbTd.js → play-store-BJeHSxBp.js} +2 -2
- package/packages/ui/dist/assets/{plus-B77_RmYR.js → plus-B-L3uTWl.js} +2 -2
- package/packages/ui/dist/assets/{qr-code-BNeIhB1d.js → qr-code-CcQBy6o3.js} +1 -1
- package/packages/ui/dist/assets/{recycle-horizontal-B9sX-yty.js → recycle-horizontal-BLC3_x1m.js} +2 -2
- package/packages/ui/dist/assets/{refresh-BWRygnjm.js → refresh-78cbzBlS.js} +2 -2
- package/packages/ui/dist/assets/{reown-logo-DCAEsGFm.js → reown-logo-Bx5RmN6G.js} +2 -2
- package/packages/ui/dist/assets/{search-ZXIqrawS.js → search-XWB8l5E2.js} +2 -2
- package/packages/ui/dist/assets/{secp256k1-Doz05h4g.js → secp256k1-u9EmDRXd.js} +1 -1
- package/packages/ui/dist/assets/{send-BQcO0jNC.js → send-BjGwACTt.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontal-4yZEzz2K.js → swapHorizontal-DJWcWn3A.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalBold-DxBKu3sl.js → swapHorizontalBold-CG930oAl.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalMedium-DAQYJ2JK.js → swapHorizontalMedium-CHIdtnCI.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalRoundedBold-CUlZzFJ6.js → swapHorizontalRoundedBold-B2oGt4O4.js} +2 -2
- package/packages/ui/dist/assets/{swapVertical-CjWHAaDM.js → swapVertical-CnjOFXH3.js} +2 -2
- package/packages/ui/dist/assets/{telegram-JBeabiJP.js → telegram-BtOilYdH.js} +2 -2
- package/packages/ui/dist/assets/{three-dots-D3R8OjSx.js → three-dots-D_VsAJlo.js} +2 -2
- package/packages/ui/dist/assets/{twitch-1f9z7xVO.js → twitch-BIX3n7hL.js} +2 -2
- package/packages/ui/dist/assets/{twitterIcon-BW7n2xnp.js → twitterIcon-C3DDaPem.js} +2 -2
- package/packages/ui/dist/assets/{verify-Cs5owI38.js → verify-CtzMScZy.js} +2 -2
- package/packages/ui/dist/assets/{verify-filled-Ci5sx5qS.js → verify-filled-CVjdmBEW.js} +2 -2
- package/packages/ui/dist/assets/{w3m-modal-DnWtss8u.js → w3m-modal-BZ3BUhSD.js} +1 -1
- package/packages/ui/dist/assets/{wallet-MaGNqaW-.js → wallet-hURaFgJp.js} +2 -2
- package/packages/ui/dist/assets/{wallet-placeholder-kTOy12_L.js → wallet-placeholder-BcF2KreB.js} +2 -2
- package/packages/ui/dist/assets/{walletconnect-CyuP92jg.js → walletconnect-C8XCABRo.js} +3 -3
- package/packages/ui/dist/assets/{warning-circle-BImUCdpG.js → warning-circle-BMz6LCU4.js} +2 -2
- package/packages/ui/dist/assets/{x-II45JHpV.js → x-deAHZeLU.js} +2 -2
- package/packages/ui/dist/index.html +2 -2
- package/templates/default/.agents/skills/sail-mandates/SKILL.md +1 -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 +34 -33
- 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 +107 -13
- 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/ui/dist/assets/arrow-bottom-Cwptw8fW.js +0 -8
- package/packages/ui/dist/assets/arrow-left-bM1TRcV9.js +0 -8
- package/packages/ui/dist/assets/arrow-right-BYd_N4Jr.js +0 -8
- package/packages/ui/dist/assets/arrow-top-CLUnwVE2.js +0 -8
- package/packages/ui/dist/assets/ccip-BL7RIPfY.js +0 -1
- package/packages/ui/dist/assets/chevron-bottom-BMCelIEu.js +0 -8
- package/packages/ui/dist/assets/chevron-left-ryPLhHCc.js +0 -8
- package/packages/ui/dist/assets/chevron-right-CdDNRv7f.js +0 -8
- package/packages/ui/dist/assets/chevron-top-BxWIjjGk.js +0 -8
- package/packages/ui/dist/assets/core-von2c1sc.js +0 -907
- package/packages/ui/dist/assets/cursor-BzRVY7BD.js +0 -3
- package/packages/ui/dist/assets/events-C9icG2YB.js +0 -1
- package/packages/ui/dist/assets/external-link-BPaGQf4a.js +0 -8
- package/packages/ui/dist/assets/fallback-BTfJeGjh.js +0 -1
- package/packages/ui/dist/assets/index-BGJ_8fMk.js +0 -1
- package/packages/ui/dist/assets/index-Bq3PAopp.js +0 -16
- package/packages/ui/dist/assets/index-C4_cA5XW.js +0 -1
- package/packages/ui/dist/assets/index-CaDN0gL4.js +0 -1
- package/packages/ui/dist/assets/index-DLElIcBr.js +0 -1789
- package/packages/ui/dist/assets/index-lhNfD4jA.css +0 -1
- package/packages/ui/dist/assets/index.es-BaCKS_tW.js +0 -26
- package/packages/ui/dist/assets/metamask-sdk-DljW3QD1.js +0 -542
- package/packages/ui/dist/assets/native-CJ5et6AR.js +0 -1
- package/packages/ui/dist/assets/parseSignature-BAS-wKoj.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/templates/default/.agents/skills/sail-overview/SKILL.md +0 -100
- package/templates/default/.agents/skills/sail-sailor/SKILL.md +0 -190
- package/templates/default/.agents/skills/sail-setup/SKILL.md +0 -38
- package/templates/default/.gitlab-ci.yml +0 -58
- /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
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#040b16" />
|
|
7
7
|
<title>Sail - Unlocking Personalized Money</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-RMrT9ktE.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B07bCKJ8.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -116,4 +116,4 @@ The fee is read via `readPermissionRegistrationFee()` in `packages/sdk/src/fees.
|
|
|
116
116
|
|
|
117
117
|
## Clone templates (deploy-clone)
|
|
118
118
|
|
|
119
|
-
`sailor mandate deploy-clone --template boundedApprove --sma <SMA> --tokens <csv> --spenders <csv> --max <wei> --json` deploys + registers an EIP-1167 clone of a published implementation in one transaction (owner signs `RegisterPermission` for the predicted clone address — BLOCKS; agent submits `deployAndAttach`). The only template key is `boundedApprove`. Implementations come from the SDK deployment registry (`standaloneTemplates`) —
|
|
119
|
+
`sailor mandate deploy-clone --template boundedApprove --sma <SMA> --tokens <csv> --spenders <csv> --max <wei> --json` deploys + registers an EIP-1167 clone of a published implementation in one transaction (owner signs `RegisterPermission` for the predicted clone address — BLOCKS; agent submits `deployAndAttach`). The only template key is `boundedApprove`. Implementations come from the SDK deployment registry (`standaloneTemplates`) — that map is now populated with the shared swap/borrow/deposit/withdraw/transfer/approve-and-call-batch templates on all eleven chains, but no `boundedApprove` clone implementation is deployed yet, so deploy-clone errors with a clear message and you should write and deploy a bounded-approve permission with `sailor mandate deploy` instead. Check availability with `sailor mandate templates --json`.
|
|
@@ -25,12 +25,12 @@ This needs no batch support and works on every kernel.
|
|
|
25
25
|
|
|
26
26
|
Approve + action run as one `dispatchBatch` — atomic, single transaction. A batch dispatch consults **exactly one batch-aware `IBatchPermission`**, never a pair of `IPermission`s:
|
|
27
27
|
|
|
28
|
-
- The permission implements `@sail/interfaces/IBatchPermission.sol` — `evaluateBatch(Call[] calls, BatchContext ctx)` validates the WHOLE sequence (ordering, the approve's spender/amount, the action, and mandatory allowance cleanup) and `isBatchPermission()` returns true. `examples/permissions/BoundedApproveAndCallBatch.sol`
|
|
28
|
+
- The permission implements `@sail/interfaces/IBatchPermission.sol` — `evaluateBatch(Call[] calls, BatchContext ctx)` validates the WHOLE sequence (ordering, the approve's spender/amount, the action, and mandatory allowance cleanup) and `isBatchPermission()` returns true. **For the canonical `approve → swap/deposit/supply → reset` shape, a shared `ApproveAndCallBatchPermission` singleton is deployed** on Base, Arbitrum, Unichain, Sepolia, and Base Sepolia (resolve it with `node scripts/shared-template-addr.mjs ApproveAndCallBatchPermission`) — reuse it via `attach` + `configure` rather than authoring one. `examples/permissions/BoundedApproveAndCallBatch.sol` remains the model for a non-standard shape you must author yourself.
|
|
29
29
|
- A normal `IPermission` placed in a batch is rejected by the kernel with `PermissionNotBatchAware`. You cannot assemble a batch from two narrow per-call permissions — that was the trap.
|
|
30
30
|
- Batch is a **selective-kernel** feature (`dispatchBatch` / `previewBatch`); conjunctive kernels have neither. Confirm the model with `sailor doctor`; details in `docs/PERMISSION_MODEL.md`.
|
|
31
|
-
- At runtime, return one `Dispatch` whose `calls` array is `[
|
|
31
|
+
- At runtime, return one `Dispatch` whose `calls` array is the batch. The shared `ApproveAndCallBatchPermission` requires **exactly** `[approve(spender, amount), action, approve(spender, 0)]` — the trailing reset to zero is mandatory, and the pre-batch allowance on that `(token, spender)` pair must already be zero (so never combine it with a standing approve). The runner detects `calls.length > 1` and routes through `dispatch.batch`.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
**For autonomous recurring actions (a DCA or rebalancer the agent runs on its own), Model B is the default — not an advanced option.** Model A's separate approve dispatch needs the owner back in the loop every time the bounded allowance runs out, which a recurring agent cannot do; on the first tick after the allowance is consumed it stalls (or, in a simulation, is tempted to cheat the allowance in). Reach for Model A when the owner is genuinely in the loop per action anyway. For bounded swaps specifically, [`sail-template-swap`'s "Approve coverage"](../../sail-template-swap/SKILL.md) works through the choice with concrete addresses.
|
|
34
34
|
|
|
35
35
|
## Verifying each model
|
|
36
36
|
|
|
@@ -15,7 +15,7 @@ Permissions only bound on-chain actions: venues with off-chain order matching (P
|
|
|
15
15
|
| `BoundedStake_Venice_Base.sol` | Venice (VVV) staking · Base | Recipient binding on stake; claim() structurally routes rewards to caller (the SMA) | `0xadc9772e` stake(address,uint256), `0x4e71d92d` claim() |
|
|
16
16
|
| `BoundedTransfer_ERC20_Ethereum.sol` | ERC-20 · any EVM | Token allowlist + recipient allowlist + per-transfer cap; caps are per-token base units (USDC 6 decimals, WETH 18) — one permission per token if amounts differ | `0xa9059cbb` transfer(address,uint256) |
|
|
17
17
|
| `BoundedPerp_GMXv2_Arbitrum.sol` | GMX v2 ExchangeRouter · Arbitrum | Market allowlist + collateral cap + size cap (USD is 1e30-scaled) + long/short flags on a struct-typed call | `0x212234c3` createOrder(CreateOrderParams) — MUST re-verify, see below |
|
|
18
|
-
| `
|
|
18
|
+
| `BoundedLimitless_Base.sol` | Limitless CTF exchange · Base | Condition allowlist + stake cap + outcome allowlist on a prediction market | buy(bytes32,uint256,uint256) — **ABI UNVERIFIED**, see below |
|
|
19
19
|
| `BoundedApproveAndCallBatch.sol` | Sail batch dispatch · any selective kernel | `IBatchPermission`: atomic approve → consume → reset-to-0 in exactly 3 calls; single-call evaluate() deliberately returns false | `0x095ea7b3` approve (calls 0 and 2) |
|
|
20
20
|
|
|
21
21
|
## Lessons the examples encode
|
|
@@ -29,6 +29,6 @@ Permissions only bound on-chain actions: venues with off-chain order matching (P
|
|
|
29
29
|
|
|
30
30
|
## Interfaces (vendored in `examples/permissions/interfaces/`)
|
|
31
31
|
|
|
32
|
-
- `IPermission.evaluate(bytes txData, Context ctx) → bool`; Context: `account, manager, submitter, target, selector, value, blockTimestamp, blockNumber`.
|
|
33
|
-
- `IBatchPermission.evaluateBatch(Call[] calls, BatchContext ctx) → bool` + `isBatchPermission() → true`; Call: `(target, value, data)`; BatchContext: `account, manager, submitter, permission, batchHash, blockTimestamp, blockNumber`.
|
|
32
|
+
- `IPermission.evaluate(bytes txData, Context ctx) → bool`; Context: `account, manager, submitter, target, selector, value, blockTimestamp, blockNumber, configEpoch`.
|
|
33
|
+
- `IBatchPermission.evaluateBatch(Call[] calls, BatchContext ctx) → bool` + `isBatchPermission() → true`; Call: `(target, value, data)`; BatchContext: `account, manager, submitter, permission, batchHash, blockTimestamp, blockNumber, configEpoch`.
|
|
34
34
|
- `SailCalldata.sol`: bounded slot-indexed calldata readers (`hasParams`, `asAddress`, `asUint256`, …) — use these instead of `abi.decode` to avoid wrong-offset reads.
|
|
@@ -5,46 +5,47 @@ description: Walks the agent through setting up a new Sailor project or resuming
|
|
|
5
5
|
|
|
6
6
|
# Sail onboarding
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Running the CLI
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**Determine the installation mode first** — read `.sail/config.json → installMode` before running any command:
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
- `"local"` (or field absent) — `sailor` is on the PATH. Run commands directly: `sailor <command>`
|
|
13
|
+
- `"docker"` — sailor runs in a container. Read `containerName` from the same config. Prefix every command:
|
|
14
|
+
`docker exec <containerName> sailor <command>`
|
|
15
|
+
Project files are on your **local filesystem** (mounted at `/workspace` inside the container) — read and write them normally from local paths. Do NOT use `docker exec` to read files; the volume mount makes them directly accessible.
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
>
|
|
16
|
-
> Here is the complete path. Read it before we begin — each stage requires something from you.
|
|
17
|
-
>
|
|
18
|
-
> ---
|
|
19
|
-
>
|
|
20
|
-
> **Stage 1 — Deploy your SMA and create your agent wallet**
|
|
21
|
-
> We open a browser interface where you connect your owner wallet (MetaMask or any WalletConnect wallet), choose your chain, and deploy your SMA — a smart contract account you own. We also create a separate agent wallet: a signing key the agent uses to submit transactions on your behalf. Both wallets need gas. The owner wallet signs the deployment; the agent wallet pays for everything after that.
|
|
22
|
-
>
|
|
23
|
-
> **Stage 2 — Define your strategy**
|
|
24
|
-
> You tell me what you want the agent to do: which protocol, which tokens, what conditions, what limits. I help you separate the constraints that must be enforced on-chain (amount caps, venue allowlists, slippage floors) from the ones that live in agent logic (cadence, schedule, timing). Nothing is deployed yet — this is design.
|
|
25
|
-
>
|
|
26
|
-
> **Stage 3 — Build, test, and sign your mandate**
|
|
27
|
-
> I write the Solidity permission contracts that encode the strategy bounds from Stage 2. You review them. We run Foundry tests against your actual strategy parameters (must-pass and must-fail cases). We deploy the contracts on-chain and simulate them before you sign anything. You authorize the permission set in the browser — one signature. After this, the agent can act autonomously within those bounds.
|
|
28
|
-
>
|
|
29
|
-
> **Stage 4 — Run your agent**
|
|
30
|
-
> We run the agent locally first (`sailor run --once`) to confirm a full tick works. Then we automate it — GitHub Actions, a self-hosted runner, a local daemon, or Docker — matched to your latency needs and infrastructure comfort.
|
|
31
|
-
>
|
|
32
|
-
> **Stage 5 — Extend**
|
|
33
|
-
> Once the agent is live, I offer to add run and transaction notifications (Telegram, email) and a strategy-specific dashboard. This is optional, but I will offer it — you opt out explicitly if you don't want it.
|
|
34
|
-
>
|
|
35
|
-
> ---
|
|
36
|
-
>
|
|
37
|
-
> Ready to begin? Say **start** and I'll open the setup interface in your browser for Stage 1.
|
|
17
|
+
### Starting the Docker container
|
|
38
18
|
|
|
39
|
-
|
|
19
|
+
If the container is not running, start it from the project root:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
docker run -d --name agent -P -v "${PWD}:/workspace" sailmoney/sailor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- `-d` — detached, runs in the background
|
|
26
|
+
- `--name agent` — names the container; use a different name with `-e SAILOR_CONTAINER_NAME=<name>` if needed
|
|
27
|
+
- `-P` — publishes all exposed ports to random available host ports (UI: 3334, station: 3141)
|
|
28
|
+
- `-v "${PWD}:/workspace"` — mounts the current project directory into the container
|
|
40
29
|
|
|
41
|
-
After
|
|
30
|
+
After starting, resolve the host port before opening the UI in the browser:
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
```bash
|
|
33
|
+
docker port agent 3334
|
|
34
|
+
# → 0.0.0.0:49201 (use 49201, not 3334, in the browser URL)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
All `sailor` commands run via `docker exec`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
docker exec agent sailor --version
|
|
41
|
+
docker exec agent sailor init
|
|
42
|
+
docker exec agent sailor ui start
|
|
43
|
+
docker exec agent sailor station start --json &
|
|
44
|
+
```
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
The published package is **`@sail.money/sailor`** — always use the scoped name with the registry. The bare name `sailor` is a different, unrelated npm package; never `npx sailor@<version>` or `npm i sailor`. Install it (`npm i -g @sail.money/sailor`, or as a project dep), after which the `sailor` bin works bare (`sailor <command>`) and `npx sailor <command>` resolves the installed bin. Every `sailor …` command in these skills assumes it is installed. Confirm the toolchain up front and pin a recent version — `npx @sail.money/sailor@latest --version` — because an old cached `npx` build can be missing newer commands (e.g. `mandate simulate`); if a documented command reports "unknown command", you are on a stale version, not hitting a missing feature.
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
After upgrading the CLI, run `sailor update` from the project root to pull in updated skills, `AGENTS.md`, `Dockerfile`, and other tooling files. User files (`src/`, `mandates/`, `.sail/`, `package.json`) are never touched.
|
|
48
49
|
|
|
49
50
|
Stage machine keyed off `.sail/`. Read the state, enter at the right stage, never re-run completed stages.
|
|
50
51
|
|
|
@@ -57,7 +58,7 @@ Stage machine keyed off `.sail/`. Read the state, enter at the right stage, neve
|
|
|
57
58
|
| `account.json` exists, `state/mandates.json` empty or absent | SMA live, no permissions — hand off to **sail-mandates** |
|
|
58
59
|
| `account.json` + tracked mandates | SMA live with permissions — hand off to **sail-transactions** to run. Once the agent is live, the flow is not done: **offer Extend (stage 5 — notifications + a custom dashboard) by default**, unless the operator opts out, then hand off to **sail-extend**. The end state is "stage 5 offered," not "running" |
|
|
59
60
|
|
|
60
|
-
Supported chains: Ethereum (1), Base (8453), Arbitrum (42161), Unichain (130), Base Sepolia (84532), Eth Sepolia (11155111). `sailor chains --json` lists them with kernel addresses.
|
|
61
|
+
Supported chains: Ethereum (1), Base (8453), Arbitrum (42161), Optimism (10), Unichain (130), BSC (56), World Chain (480), HyperEVM (999), MegaETH (4326), Base Sepolia (84532), Eth Sepolia (11155111). `sailor chains --json` lists them with kernel addresses.
|
|
61
62
|
|
|
62
63
|
## Stage 1 — Deploy the SMA and create the agent wallet
|
|
63
64
|
|
|
@@ -20,6 +20,7 @@ sailor ui start --expose tailscale # also serve it on the tailnet over HTTPS (
|
|
|
20
20
|
- The server serves the pre-built React app (`SERVE_DIST=1`) and a local `/api` that reads `.sail/` state (`SAIL_DIR` env).
|
|
21
21
|
- `ui start` does not block — no `&` needed.
|
|
22
22
|
- `--expose tailscale` (optional): proxies the dashboard onto the operator's tailnet over HTTPS via `tailscale serve` (tailnet-private, never `funnel`). Requires `tailscale` installed + logged in, and Serve + HTTPS enabled for the tailnet (else the command prints the enable link). `ui stop` tears the proxy down. To allow extra browser origins, set `SAILOR_CORS_ORIGINS` (comma-separated; the local origin is always allowed).
|
|
23
|
+
- Binds `127.0.0.1` by default (local only). To expose without `--expose tailscale` — e.g. behind a reverse proxy on a domain — set `SAILOR_HOST=0.0.0.0`. The `/api` key-management endpoints are **unauthenticated**, so only do this behind your own auth (reverse-proxy basic-auth or a private tailnet), and throttle them with `SAILOR_RATE_LIMIT_PER_MIN` (default 100; or `rateLimitPerMin` in `.sail/config.json`).
|
|
23
24
|
|
|
24
25
|
## Signing station — `sailor station`
|
|
25
26
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sail-swap-quote
|
|
3
|
+
description: Fetch a live Uniswap V3 quote via QuoterV2 and compute a slippage-adjusted amountOutMinimum for a swap. Use after sail-token-resolve to show the user the current price and to produce the amountOutMinimum floor the agent must embed in every swap dispatch. Runs the bundled `scripts/quote-swap.mjs` (no dependencies, no gas).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# sail-swap-quote — live price + amountOutMinimum floor
|
|
7
|
+
|
|
8
|
+
After [`sail-token-resolve`](../sail-token-resolve/SKILL.md) confirms a token is
|
|
9
|
+
swap-ready, get the actual number: how much tokenOut the user's amountIn buys
|
|
10
|
+
right now, and the `amountOutMinimum` floor that protects against slippage.
|
|
11
|
+
|
|
12
|
+
## When to load
|
|
13
|
+
|
|
14
|
+
- After `sail-token-resolve` returned `swapReady: true`.
|
|
15
|
+
- Before presenting a mandate to the user (show them the current price).
|
|
16
|
+
- To validate `amountOutMinimum` before the agent dispatches a real swap.
|
|
17
|
+
- Whenever the user asks "what's the price of X?" or "how much WETH for 25 USDC?".
|
|
18
|
+
|
|
19
|
+
## Run it
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# From the project root. Inputs come from sail-token-resolve's output.
|
|
23
|
+
node scripts/quote-swap.mjs \
|
|
24
|
+
--token-in 0x078D782b760474a361dDA0AF3839290b0EF57AD6 --decimals-in 6 \
|
|
25
|
+
--token-out 0x4200000000000000000000000000000000000006 --decimals-out 18 \
|
|
26
|
+
--amount 25000000 --fee 3000 --slippage-bps 100
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- `--amount` is in **base units** of tokenIn (25 USDC = `25000000`, not `25`).
|
|
30
|
+
- `--fee` is the tier from `sail-token-resolve` (deepest pool).
|
|
31
|
+
- `--slippage-bps` defaults to `100` (1%). Tighten (e.g. `50`) for stable pairs,
|
|
32
|
+
loosen for volatile ones. `0` removes the floor — testing only.
|
|
33
|
+
- `--chain` / `--rpc` override the project's active chain/RPC.
|
|
34
|
+
|
|
35
|
+
Output: one JSON object on stdout; human notes on stderr.
|
|
36
|
+
|
|
37
|
+
## What it returns
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"amountIn": "25000000", "amountOut": "15805313193992907",
|
|
42
|
+
"amountOutMinimum": "15647260062052977", // amountOut × (10000-slip)/10000
|
|
43
|
+
"slippageBps": 100, "fee": 3000,
|
|
44
|
+
"human": { "amountIn": "25.0", "amountOut": "0.015805313193992907",
|
|
45
|
+
"amountOutMinimum": "0.015647260062052977",
|
|
46
|
+
"price": "0.015805313193992907 out per 25.0 in" }
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## How the floor is computed
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
amountOutMinimum = amountOut × (10000 − slippageBps) / 10000
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
At 1% slippage, `amountOut × 9900 / 10000`. The agent must embed a value **at
|
|
57
|
+
least this high** in its swap calldata's `amountOutMinimum` field — the
|
|
58
|
+
`SwapPermission` mandate rejects any swap whose `amountOutMinimum` is below the
|
|
59
|
+
pool-implied floor (fail-closed). Re-quote close to dispatch time; a stale quote
|
|
60
|
+
is the most common reason a legitimate swap gets rejected.
|
|
61
|
+
|
|
62
|
+
## What to do with it
|
|
63
|
+
|
|
64
|
+
1. **Show the user** the `human.price` line before they approve the mandate.
|
|
65
|
+
2. **Record `amountOutMinimum`** as the floor the agent's dispatch logic must
|
|
66
|
+
respect. Hand the full quote to [`sail-template-swap`](../sail-template-swap/SKILL.md)
|
|
67
|
+
— the mandate's `maxSlippageBps` bound is the on-chain enforcement of this.
|
|
68
|
+
3. **Re-quote at dispatch time** — pool state moves; a quote taken minutes ago may
|
|
69
|
+
no longer match.
|
|
70
|
+
|
|
71
|
+
## If it fails
|
|
72
|
+
|
|
73
|
+
| Error | Meaning |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `QuoterV2 reverted` / `amountOut == 0` | No pool at this fee tier (despite resolve-token). Try the next tier from resolve-token's `probedTiers`, or re-run resolve-token — liquidity may have shifted. |
|
|
76
|
+
| `No RPC for chain` | Run from the project root, or pass `--rpc`. |
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sail-template-approve-batch
|
|
3
|
+
description: Gate an SMA's "approve → call → reset" interactions by REUSING the shared ApproveAndCallBatchPermission singleton (Protocol/contracts/templates/ApproveAndCallBatchPermission.sol) — register + configure, no per-SMA deploy. Use when an agent must approve an ERC-20 to a protocol, make one consuming call, and reset the allowance to zero — all in one atomic batch — with token/spender/target/selector allowlists and a per-token approval cap. NOTE: `sailor mandate attach` only registers — you must also configure per-account (see steps).
|
|
4
|
+
compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires ApproveAndCallBatchPermission deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
|
|
5
|
+
metadata:
|
|
6
|
+
workspace: sailor-harness
|
|
7
|
+
classification: generic
|
|
8
|
+
status: draft
|
|
9
|
+
origin: Protocol/contracts/templates/ApproveAndCallBatchPermission.sol
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# sail-template-approve-batch — atomic approve/call/reset via the shared singleton
|
|
13
|
+
|
|
14
|
+
Reuse the shared **`ApproveAndCallBatchPermission`** singleton — the safest way to bracket a
|
|
15
|
+
single protocol interaction that needs an allowance. Family overview + flow:
|
|
16
|
+
[`sail-templates`](../sail-templates/SKILL.md).
|
|
17
|
+
|
|
18
|
+
## What it enforces (per account, from source)
|
|
19
|
+
|
|
20
|
+
Authorises exactly this 3-call batch:
|
|
21
|
+
```
|
|
22
|
+
[0] approve(spender, amount) on an allowlisted ERC-20
|
|
23
|
+
[1] consuming call on an allowlisted (target, selector)
|
|
24
|
+
[2] approve(spender, 0) reset to zero (same atomic batch)
|
|
25
|
+
```
|
|
26
|
+
Invariants: `tokens`, `spenders`, and `consumingPairs` must each be non-empty with no zero
|
|
27
|
+
addresses/selectors (`EmptyAllowlist` reverts at configure otherwise); `token ∈ tokens` and
|
|
28
|
+
`amount ≤ maxApprovalAmount[token]`; `spender ∈ spenders`; `(target, selector) ∈ consumingPairs`;
|
|
29
|
+
the pre-batch allowance on `(token, spender)` must be zero; the consuming call must target the
|
|
30
|
+
approved `spender` and pull the approved `token`; the reset-to-zero is required; if
|
|
31
|
+
`requireAmountMatch`, the consuming call's leading `uint256` arg must equal the approve amount.
|
|
32
|
+
Malformed calldata reverts (kernel treats revert as deny).
|
|
33
|
+
|
|
34
|
+
> **Output recipient — `allowUnconstrainedRecipient` (opt-out, default-safe).** By DEFAULT
|
|
35
|
+
> (`allowUnconstrainedRecipient = false`, i.e. omitted) the consuming call's output recipient is
|
|
36
|
+
> decoded and **must equal the SMA** — the safe posture — and any selector outside the decodable
|
|
37
|
+
> set below is denied (fail-closed). Set `allowUnconstrainedRecipient = true` ONLY to deliberately
|
|
38
|
+
> leave the recipient unconstrained (e.g. to authorize a selector outside the decodable set); then
|
|
39
|
+
> only allowlist `(target, selector)` pairs whose semantics you trust to deliver output to the SMA.
|
|
40
|
+
> ⚠️ This is an **opt-out** flag, not an opt-in — do not set it `true` thinking that "requires"
|
|
41
|
+
> or "pins" the recipient; that gets you the opposite (unconstrained) behavior on-chain.
|
|
42
|
+
>
|
|
43
|
+
> **Decodable set** (recipient AND consumed-asset both resolvable at a fixed calldata offset —
|
|
44
|
+
> any other selector is denied regardless of `allowUnconstrainedRecipient`, since the consumed
|
|
45
|
+
> asset can never be bound to the approved token): `swapExactTokensForTokens` (V2), V3
|
|
46
|
+
> `exactInputSingle` (both the SwapRouter and SwapRouter02 layouts), Aave V2 `deposit` / Aave V3
|
|
47
|
+
> `supply`, ERC-4626 `deposit` / `mint`.
|
|
48
|
+
|
|
49
|
+
## Config blob (authoritative — `config-schemas.md`)
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
abi.encode(Config{
|
|
53
|
+
address[] tokens; // approvable ERC-20s
|
|
54
|
+
address[] spenders; // allowed allowance recipients
|
|
55
|
+
ConsumingPair[] consumingPairs; // {address target; bytes4 selector} — pair bound together
|
|
56
|
+
uint256[] maxApprovalAmounts; // index-parallel with tokens; per-token cap
|
|
57
|
+
bool requireAmountMatch; // call[1] leading uint256 must equal approve amount
|
|
58
|
+
bool allowUnconstrainedRecipient; // OPT-OUT (default false = call[1] output recipient decoded, must == account)
|
|
59
|
+
})
|
|
60
|
+
struct ConsumingPair { address target; bytes4 selector; }
|
|
61
|
+
```
|
|
62
|
+
`maxApprovalAmounts.length` must equal `tokens.length` (configure reverts otherwise).
|
|
63
|
+
`consumingPairs` is a **struct array** `(address target, bytes4 selector)[]` — NOT two flat
|
|
64
|
+
`address[]`/`bytes4[]` arrays. A selector is authorised only on the target it is paired with.
|
|
65
|
+
|
|
66
|
+
**ABI tuple (verified round-trip):** `(address[],address[],(address,bytes4)[],uint256[],bool,bool)`.
|
|
67
|
+
Encode with cast — note `selector` is a `bytes4` (left-aligned), and both trailing bools are required:
|
|
68
|
+
```bash
|
|
69
|
+
cast abi-encode 'cfg((address[],address[],(address,bytes4)[],uint256[],bool,bool))' \
|
|
70
|
+
'([<token>],[<spender>],[(<target>,<selector>)],[<maxApproval>],<requireAmountMatch>,<allowUnconstrainedRecipient>)'
|
|
71
|
+
```
|
|
72
|
+
`allowUnconstrainedRecipient` is an **opt-out** — leave it `false` unless you deliberately want
|
|
73
|
+
the consuming call's output recipient unconstrained (see the field note above).
|
|
74
|
+
The leading word is the `0x20` struct offset — that is correct, not flat-param corruption. A
|
|
75
|
+
generic (un-named) revert inside `_applyConfig` means the blob did not decode to this exact tuple
|
|
76
|
+
(usually: flat params with no `0x20` offset, the two flat arrays instead of `ConsumingPair[]`, or a
|
|
77
|
+
missing trailing bool). The named errors (`TokensAndAmountsLengthMismatch`, `EmptyAllowlist`,
|
|
78
|
+
`AllowlistTooLong`) fire only after a successful decode.
|
|
79
|
+
|
|
80
|
+
## Steps
|
|
81
|
+
|
|
82
|
+
Register → configure → simulate → reconfigure mechanics live in
|
|
83
|
+
[`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
|
|
84
|
+
`sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
|
|
85
|
+
permission live. Template-specific bits:
|
|
86
|
+
|
|
87
|
+
- **Singleton:** `ApproveAndCallBatchPermission` — `node SKILLS/sail-templates/catalog.mjs --chain
|
|
88
|
+
<id>`.
|
|
89
|
+
- **Spec to confirm:** tokens+caps, spenders, `(target, selector)` consuming pairs,
|
|
90
|
+
`requireAmountMatch`, `allowUnconstrainedRecipient`. Get each selector with `cast sig` and verify
|
|
91
|
+
it delivers output to the SMA (leave `allowUnconstrainedRecipient = false`, the default, to
|
|
92
|
+
enforce that on-chain — set `true` only to deliberately opt out).
|
|
93
|
+
- **Blob — ⚠️ NOT flat params.** `abi.encode(Config{ … })` — a **single wrapped struct**, so the
|
|
94
|
+
blob starts with a `0x20` offset word. `consumingPairs` is `(address,bytes4)[]` (a struct array),
|
|
95
|
+
and both trailing bools are required — see the verified `cast abi-encode` form above. Unlike
|
|
96
|
+
transfer/withdraw/deposit/borrow/swap, wrapping is required here; a flat blob reverts at configure.
|
|
97
|
+
- **Simulate (mandatory — unaudited example):** the full allowed approve/call/reset batch passes;
|
|
98
|
+
missing reset, off-list spender/target/selector, or over-cap approve is rejected.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sail-template-borrow
|
|
3
|
+
description: Gate an SMA's lending borrows by REUSING the shared BorrowPermission singleton (Protocol/contracts/templates/BorrowPermission.sol) — register + configure, no per-SMA deploy. Use for a bounded borrow mandate against Aave / Morpho / Compound with a protocol + asset allowlist, per-tx cap, and an on-chain LTV check via collateral + borrow oracles. NOTE: `sailor mandate attach` only registers — you must also configure per-account (see steps).
|
|
4
|
+
compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires BorrowPermission deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
|
|
5
|
+
metadata:
|
|
6
|
+
workspace: sailor-harness
|
|
7
|
+
classification: generic
|
|
8
|
+
status: draft
|
|
9
|
+
origin: Protocol/contracts/templates/BorrowPermission.sol
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# sail-template-borrow — bounded lending borrow via the shared singleton
|
|
13
|
+
|
|
14
|
+
Reuse the shared **`BorrowPermission`** singleton. Family overview + flow:
|
|
15
|
+
[`sail-templates`](../sail-templates/SKILL.md).
|
|
16
|
+
|
|
17
|
+
## What it enforces (per account, from source)
|
|
18
|
+
|
|
19
|
+
Selectors (any other ⇒ `false`): Aave `borrow(address,uint256,uint256,uint16,address)`
|
|
20
|
+
(**variable-rate only** — a stable-rate borrow, `rateMode != 2`, is rejected), Morpho
|
|
21
|
+
**Optimizer/Morpho-Aave** `borrow(address,uint256,address,address)` (**NOT Morpho Blue** — its
|
|
22
|
+
ABI differs and simply won't match this selector, i.e. fails closed; target a Blue-specific
|
|
23
|
+
permission instead), Compound `borrow(uint256)` (the call target is the **cToken**; the template
|
|
24
|
+
resolves `cToken.underlying()` and keys the allowlist/cap/LTV on the underlying — a target with
|
|
25
|
+
no `underlying()`, e.g. cETH, is denied). Invariants: `target ∈ protocols`; `asset ∈ assets`
|
|
26
|
+
(Aave/Morpho decode the asset; Compound resolves it via `underlying()`); `amount ≤
|
|
27
|
+
maxAmountPerTx`; `onBehalfOf`/`receiver == SMA`; **`_ltvCheck` passes** — the projected debt
|
|
28
|
+
value (via `borrowOracle`) against collateral value (via `collateralOracle`) must stay within
|
|
29
|
+
`maxLtvBps`, when oracles are configured (see ORACLE MODES below).
|
|
30
|
+
|
|
31
|
+
> **Oracle modes:** configure **zero** oracles for amount-cap-only borrowing (no LTV ceiling is
|
|
32
|
+
> applied at all, despite `maxLtvBps` being stored) or **both** — exactly one oracle reverts
|
|
33
|
+
> `OracleConfigInconsistent` at configure (a single feed can't price a ratio). When both are set,
|
|
34
|
+
> the check still depends on their honesty/freshness — see staleness via `maxPriceAgeSec`.
|
|
35
|
+
|
|
36
|
+
## Config blob (authoritative — `config-schemas.md`)
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
abi.encode(address[] protocols, address[] assets, uint256 maxAmountPerTx,
|
|
40
|
+
uint256 maxLtvBps, address collateralOracle, address borrowOracle,
|
|
41
|
+
uint256 maxPriceAgeSec)
|
|
42
|
+
```
|
|
43
|
+
| Field | Notes |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `protocols` | lending pools the agent may borrow from |
|
|
46
|
+
| `assets` | borrowable ERC-20 allowlist |
|
|
47
|
+
| `maxAmountPerTx` | per-borrow cap, base units |
|
|
48
|
+
| `maxLtvBps` | max loan-to-value, bps (e.g. `5000` = 50%) |
|
|
49
|
+
| `collateralOracle` / `borrowOracle` | price sources for the LTV check |
|
|
50
|
+
| `maxPriceAgeSec` | oracle freshness bound; `> 0` when oracles are set |
|
|
51
|
+
|
|
52
|
+
> `maxLtvBps` is a **per-transaction** projection, not a tracked lifetime LTV. For
|
|
53
|
+
> portfolio-level exposure, rely on the protocol's health factor or pair with a
|
|
54
|
+
> position-monitoring permission.
|
|
55
|
+
|
|
56
|
+
## Steps
|
|
57
|
+
|
|
58
|
+
Register → configure → simulate → reconfigure mechanics (and the encoding gotcha) live in
|
|
59
|
+
[`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
|
|
60
|
+
`sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
|
|
61
|
+
permission live. Template-specific bits:
|
|
62
|
+
|
|
63
|
+
- **Singleton:** `BorrowPermission` — `node SKILLS/sail-templates/catalog.mjs --chain <id>`.
|
|
64
|
+
- **Spec to confirm:** protocols, assets, cap, max LTV, oracles.
|
|
65
|
+
- **Blob:** `abi.encode(protocols[], assets[], maxAmountPerTx, maxLtvBps, collateralOracle,
|
|
66
|
+
borrowOracle, maxPriceAgeSec)` — **flat params, no wrapper**.
|
|
67
|
+
- **Simulate (mandatory — unaudited example):** allowed borrow within LTV passes; over-cap,
|
|
68
|
+
over-LTV, or wrong recipient is rejected.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sail-template-deposit
|
|
3
|
+
description: Gate an SMA's deposits into vaults / lending pools by REUSING the shared DepositPermission singleton (Protocol/contracts/templates/DepositPermission.sol) — register + configure, no per-SMA deploy. Use for a bounded deposit mandate into ERC-4626 vaults (deposit/mint) or Aave v2/v3 (deposit/supply) with a target + token allowlist and a per-tx cap; the resulting position always accrues to the SMA. NOTE: `sailor mandate attach` only registers — you must also configure per-account (see steps).
|
|
4
|
+
compatibility: A Sailor project (`@sail/sdk`, `sailor` CLI). Requires DepositPermission deployed on the target chain (recorded in sail-templates/deployed.json); run sail-templates first.
|
|
5
|
+
metadata:
|
|
6
|
+
workspace: sailor-harness
|
|
7
|
+
classification: generic
|
|
8
|
+
status: draft
|
|
9
|
+
origin: Protocol/contracts/templates/DepositPermission.sol
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# sail-template-deposit — bounded vault/lending deposit via the shared singleton
|
|
13
|
+
|
|
14
|
+
Reuse the shared **`DepositPermission`** singleton. Family overview + flow:
|
|
15
|
+
[`sail-templates`](../sail-templates/SKILL.md). Discover yield targets first with
|
|
16
|
+
`sail-defillama-yields`.
|
|
17
|
+
|
|
18
|
+
## What it enforces (per account, from source)
|
|
19
|
+
|
|
20
|
+
Selectors (any other ⇒ `false`):
|
|
21
|
+
|
|
22
|
+
| Function | Venue |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `deposit(uint256 assets, address receiver)` | ERC-4626 / simple vault |
|
|
25
|
+
| `mint(uint256 shares, address receiver)` | ERC-4626 |
|
|
26
|
+
| `deposit(address asset, uint256 amount, address onBehalfOf, uint16)` | Aave v2 |
|
|
27
|
+
| `supply(address asset, uint256 amount, address onBehalfOf, uint16)` | Aave v3 |
|
|
28
|
+
|
|
29
|
+
Invariants: `value == 0`; `targets`/`tokens` must be non-empty with no zero addresses
|
|
30
|
+
(`EmptyAllowlist`/`ZeroAddress` revert at configure otherwise); `target ∈ targets`; token
|
|
31
|
+
allowlist enforced — Aave checks the `asset` arg, ERC-4626 requires the `target` (vault) itself
|
|
32
|
+
∈ `tokens`; `amount`/`shares ≤ maxAmountPerTx`; `receiver`/`onBehalfOf == SMA`.
|
|
33
|
+
|
|
34
|
+
> `mint`'s cap is in **shares**, not underlying. At a high share price the effective asset cap
|
|
35
|
+
> is `maxAmountPerTx × sharePrice` — size accordingly. For ERC-4626 `deposit`/`mint` the token
|
|
36
|
+
> allowlist is the last line of defence (no asset in calldata) — only allowlist vaults whose
|
|
37
|
+
> accepted token you trust.
|
|
38
|
+
|
|
39
|
+
## Config blob (authoritative — `config-schemas.md`)
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
abi.encode(address[] targets, address[] tokens, uint256 maxAmountPerTx)
|
|
43
|
+
```
|
|
44
|
+
| Field | Notes |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `targets` | vault / lending-pool addresses |
|
|
47
|
+
| `tokens` | ERC-20 allowlist (the asset for Aave; the vault itself for ERC-4626) |
|
|
48
|
+
| `maxAmountPerTx` | per-deposit cap (assets, or shares for `mint`), base units |
|
|
49
|
+
|
|
50
|
+
## Steps
|
|
51
|
+
|
|
52
|
+
Register → configure → simulate → reconfigure mechanics (and the encoding gotcha) live in
|
|
53
|
+
[`sail-templates` reuse-flow](../sail-templates/references/reuse-flow.md) — follow it.
|
|
54
|
+
`sailor mandate attach` registers only; `configureDirect` (owner tx) is the half that makes the
|
|
55
|
+
permission live. Template-specific bits:
|
|
56
|
+
|
|
57
|
+
- **Singleton:** `DepositPermission` — `node SKILLS/sail-templates/catalog.mjs --chain <id>`.
|
|
58
|
+
- **Spec to confirm:** targets, tokens, cap (note the share-price caveat for `mint`).
|
|
59
|
+
- **Blob:** `abi.encode(targets[], tokens[], maxAmountPerTx)` — **flat params, no wrapper**.
|
|
60
|
+
- **Simulate (mandatory — unaudited example):** allowed deposit within cap passes; off-list
|
|
61
|
+
target/token, over-cap, or `receiver != SMA` is rejected.
|