@dev.sail.money/sailor 0.0.2-23 → 0.0.2-24
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/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +6 -8
- package/examples/permissions/SailCalldata.sol +118 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +152 -19
- package/packages/sdk/dist/index.d.ts +1 -1
- package/packages/sdk/dist/index.d.ts.map +1 -1
- package/packages/sdk/dist/index.js +1 -1
- 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/lifi.d.ts +17 -0
- package/packages/sdk/dist/lifi.d.ts.map +1 -1
- package/packages/sdk/dist/lifi.js +24 -0
- package/packages/sdk/dist/lifi.js.map +1 -1
- package/packages/sdk/dist/types.d.ts +17 -1
- package/packages/sdk/dist/types.d.ts.map +1 -1
- package/packages/ui/dist/assets/{add-BcGCle88.js → add--OaWHMEX.js} +1 -1
- package/packages/ui/dist/assets/{all-wallets-8Jcfw5Qj.js → all-wallets-BH_4qsJ0.js} +1 -1
- package/packages/ui/dist/assets/{app-store-R1-af7b6.js → app-store-j8XNWdo_.js} +1 -1
- package/packages/ui/dist/assets/{apple-Cxitz1aK.js → apple-DoNsugim.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-n-1aGCMa.js → arrow-bottom-D_enDpNq.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-circle-BGQ9w6zi.js → arrow-bottom-circle-WWFGXKiz.js} +1 -1
- package/packages/ui/dist/assets/{arrow-left-BngH26cQ.js → arrow-left-BUJGpX55.js} +1 -1
- package/packages/ui/dist/assets/{arrow-right-Bd1vyPNZ.js → arrow-right-D5mkI_SK.js} +1 -1
- package/packages/ui/dist/assets/{arrow-top-nst6Ttr2.js → arrow-top-BXhKZNjN.js} +1 -1
- package/packages/ui/dist/assets/{bank-3aeQOng_.js → bank-Dkkf0tum.js} +1 -1
- package/packages/ui/dist/assets/{basic-Ds-ESc-H.js → basic-DOdQ4iGr.js} +1 -1
- package/packages/ui/dist/assets/{browser-COcw6X9p.js → browser-NOeeokaH.js} +1 -1
- package/packages/ui/dist/assets/{card-B8clsijS.js → card-cdDhvSTA.js} +1 -1
- package/packages/ui/dist/assets/{ccip-SgLYYQWq.js → ccip-Dsn_0RCo.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-CxpPWG40.js → checkmark-9fzIA8S7.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-bold-3THr8VIu.js → checkmark-bold-DDvnKkth.js} +1 -1
- package/packages/ui/dist/assets/{chevron-bottom-HfWcF1bE.js → chevron-bottom-CZ0cAj9w.js} +1 -1
- package/packages/ui/dist/assets/{chevron-left-CkXbcOst.js → chevron-left-cbciRp76.js} +1 -1
- package/packages/ui/dist/assets/{chevron-right-Bt7ATlLQ.js → chevron-right-aaIM8CMM.js} +1 -1
- package/packages/ui/dist/assets/{chevron-top-BZc4xycM.js → chevron-top-Cv9x0gjk.js} +1 -1
- package/packages/ui/dist/assets/{chrome-store-IT1ftwJy.js → chrome-store-DlOKVEJe.js} +1 -1
- package/packages/ui/dist/assets/{clock-DpBvmJiH.js → clock-5QviMwVt.js} +1 -1
- package/packages/ui/dist/assets/{close-BoRwHijk.js → close-myWpcxkH.js} +1 -1
- package/packages/ui/dist/assets/{coinPlaceholder-Dba1nosr.js → coinPlaceholder-D3UzHxQZ.js} +1 -1
- package/packages/ui/dist/assets/{compass-D4QnmoWi.js → compass-DN7a9rAJ.js} +1 -1
- package/packages/ui/dist/assets/{copy-iBRvR2f1.js → copy-5dhhqdUd.js} +1 -1
- package/packages/ui/dist/assets/{core-B0JxSbAV.js → core-BlknpGLl.js} +3 -3
- package/packages/ui/dist/assets/cursor-Ckhq1uuc.js +3 -0
- package/packages/ui/dist/assets/{cursor-transparent-Djp2Lulv.js → cursor-transparent-hWLSc0KZ.js} +1 -1
- package/packages/ui/dist/assets/{desktop-DF6t43QS.js → desktop-CZ-Yyu9E.js} +1 -1
- package/packages/ui/dist/assets/{disconnect-BUeUrh3r.js → disconnect-BkFpHyPA.js} +1 -1
- package/packages/ui/dist/assets/{discord-DWtvIwBI.js → discord-BQr8vCO7.js} +1 -1
- package/packages/ui/dist/assets/{etherscan-CrPMVPiO.js → etherscan-BRQnPWan.js} +1 -1
- package/packages/ui/dist/assets/{events-DC84dMPF.js → events-BPX61gpp.js} +1 -1
- package/packages/ui/dist/assets/{exclamation-triangle-9IcrwUru.js → exclamation-triangle-DgiBPDRx.js} +1 -1
- package/packages/ui/dist/assets/{extension-_qe-89Jo.js → extension-DqOhrhYM.js} +1 -1
- package/packages/ui/dist/assets/{external-link-Bsar6cmv.js → external-link-CN3oX5O9.js} +1 -1
- package/packages/ui/dist/assets/{facebook-CZnM2tIe.js → facebook-Y4EwFoke.js} +1 -1
- package/packages/ui/dist/assets/{fallback-BOfZ_bwu.js → fallback-DTghxJb4.js} +1 -1
- package/packages/ui/dist/assets/{farcaster-DyBlkt7c.js → farcaster-Bk5F07SC.js} +1 -1
- package/packages/ui/dist/assets/{filters-DwCepBxH.js → filters-Cz-QDvgT.js} +1 -1
- package/packages/ui/dist/assets/{github-DgFcdJPt.js → github-Cxkp89Tq.js} +1 -1
- package/packages/ui/dist/assets/{google-Csj-pWAm.js → google-DYqw-KYU.js} +1 -1
- package/packages/ui/dist/assets/{help-circle-CF8XrZBx.js → help-circle-BXMNYoIA.js} +1 -1
- package/packages/ui/dist/assets/{id-YqFg_Hnr.js → id-BaD71KYD.js} +1 -1
- package/packages/ui/dist/assets/{image-DvX7Dg9U.js → image-B23hGUdW.js} +1 -1
- package/packages/ui/dist/assets/{index-BiN726SD.js → index-BN6XqPDp.js} +1 -1
- package/packages/ui/dist/assets/{index-3OLndEW6.js → index-BQ4JZ1HB.js} +3 -3
- package/packages/ui/dist/assets/{index-DsS2DJdh.js → index-C3IX4alt.js} +1 -1
- package/packages/ui/dist/assets/index-CL1Pp-W8.js +1775 -0
- package/packages/ui/dist/assets/{index-Da0fOMbp.js → index-DXwK5tlD.js} +1 -1
- package/packages/ui/dist/assets/{index-BR_6qS4k.js → index-Diq2KQvQ.js} +1 -1
- package/packages/ui/dist/assets/{index.es-BiIWW5o1.js → index.es-5g6Py2iz.js} +4 -4
- package/packages/ui/dist/assets/{info-zvmQXfcd.js → info-DXuKXV9d.js} +1 -1
- package/packages/ui/dist/assets/{info-circle-poBAGMBs.js → info-circle-CYkuEbJC.js} +1 -1
- package/packages/ui/dist/assets/{lightbulb-Dce_oD8K.js → lightbulb-CuI54_aI.js} +1 -1
- package/packages/ui/dist/assets/{mail-dmvvhqN6.js → mail--8E_kt-f.js} +1 -1
- package/packages/ui/dist/assets/{metamask-sdk-BnCx3I3r.js → metamask-sdk-yzZWWcs3.js} +1 -1
- package/packages/ui/dist/assets/{mobile-B8JxvnR1.js → mobile-CZymO9zO.js} +1 -1
- package/packages/ui/dist/assets/{more-BwL7SLTo.js → more-BYAwsmOZ.js} +1 -1
- package/packages/ui/dist/assets/{network-placeholder-BGXRLaM_.js → network-placeholder-08UzyBww.js} +1 -1
- package/packages/ui/dist/assets/{nftPlaceholder-YKLlOwZU.js → nftPlaceholder-BYQcom9C.js} +1 -1
- package/packages/ui/dist/assets/{off-BdCZJUYq.js → off-CYMrTsdm.js} +1 -1
- package/packages/ui/dist/assets/{parseSignature-D9i4CKzP.js → parseSignature-WNjhiGa-.js} +1 -1
- package/packages/ui/dist/assets/{play-store-3UXzYs09.js → play-store-C8qwnge4.js} +1 -1
- package/packages/ui/dist/assets/{plus-42VUC7wg.js → plus-D44RqQir.js} +1 -1
- package/packages/ui/dist/assets/{qr-code-W99JyxAx.js → qr-code-B2Zo3ucm.js} +1 -1
- package/packages/ui/dist/assets/{recycle-horizontal-B2t10e4m.js → recycle-horizontal-GWYpxQB9.js} +1 -1
- package/packages/ui/dist/assets/{refresh-DEFqVlG3.js → refresh-MgpEHHa3.js} +1 -1
- package/packages/ui/dist/assets/{reown-logo-BpYt7vTo.js → reown-logo-CteGf0y0.js} +1 -1
- package/packages/ui/dist/assets/{search-C38Hy_cf.js → search-tUzIsCFS.js} +1 -1
- package/packages/ui/dist/assets/{secp256k1-IKD5pd64.js → secp256k1-DmFqeUJ_.js} +1 -1
- package/packages/ui/dist/assets/{send-gSwkftFg.js → send-C9UNMMEg.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontal-C1XbgPr5.js → swapHorizontal-Bhb2KCgj.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalBold-CtEf5r93.js → swapHorizontalBold-Dx8XHsp8.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalMedium-CHP_nvzn.js → swapHorizontalMedium-BU5Bg66C.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalRoundedBold-Crnco-Af.js → swapHorizontalRoundedBold-DZtiRbnr.js} +1 -1
- package/packages/ui/dist/assets/{swapVertical-Ozkl0BQE.js → swapVertical-Da4jr_Iy.js} +1 -1
- package/packages/ui/dist/assets/{telegram-BdfzLFDW.js → telegram-2JwKMtW5.js} +1 -1
- package/packages/ui/dist/assets/{three-dots-H2XRHPIG.js → three-dots-BvVnpuAg.js} +1 -1
- package/packages/ui/dist/assets/{twitch-DvbgB-BL.js → twitch-C6DOrjYX.js} +1 -1
- package/packages/ui/dist/assets/{twitterIcon-BBsTYQzn.js → twitterIcon-BQu41-nP.js} +1 -1
- package/packages/ui/dist/assets/{verify-3A_7IJxL.js → verify-CFZQJntQ.js} +1 -1
- package/packages/ui/dist/assets/{verify-filled-B4sP0yi_.js → verify-filled-Cy6vpeJk.js} +1 -1
- package/packages/ui/dist/assets/{w3m-modal-Bfua7kiP.js → w3m-modal-De9EZPA2.js} +1 -1
- package/packages/ui/dist/assets/{wallet-BbJ989Xh.js → wallet-BfWc3N5d.js} +1 -1
- package/packages/ui/dist/assets/{wallet-placeholder-CQ2v8t-c.js → wallet-placeholder-BuxnWFqL.js} +1 -1
- package/packages/ui/dist/assets/{walletconnect-BuShZt17.js → walletconnect-CjirfANF.js} +1 -1
- package/packages/ui/dist/assets/{warning-circle-DMs8QNCr.js → warning-circle-CqS0eXEs.js} +1 -1
- package/packages/ui/dist/assets/{x-DzP75KD1.js → x-DQeWAjll.js} +1 -1
- package/packages/ui/dist/index.html +1 -1
- package/templates/custom-mandate/README.md +31 -0
- package/templates/custom-mandate/mandates/BoundedCallPermission.sol +8 -2
- package/templates/custom-mandate/mandates/SailCalldata.sol +118 -0
- package/templates/default/AGENTS.md +51 -2
- package/packages/ui/dist/assets/cursor-D3cYdnOt.js +0 -3
- package/packages/ui/dist/assets/index-yQSvDbVa.js +0 -1775
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{F as l}from"./core-
|
|
1
|
+
import{F as l}from"./core-BlknpGLl.js";import"./index-CL1Pp-W8.js";import"./events-BPX61gpp.js";import"./index.es-5g6Py2iz.js";import"./fallback-DTghxJb4.js";const o=l`<svg fill="none" viewBox="0 0 96 67">
|
|
2
2
|
<path
|
|
3
3
|
fill="currentColor"
|
|
4
4
|
d="M25.32 18.8a32.56 32.56 0 0 1 45.36 0l1.5 1.47c.63.62.63 1.61 0 2.22l-5.15 5.05c-.31.3-.82.3-1.14 0l-2.07-2.03a22.71 22.71 0 0 0-31.64 0l-2.22 2.18c-.31.3-.82.3-1.14 0l-5.15-5.05a1.55 1.55 0 0 1 0-2.22l1.65-1.62Zm56.02 10.44 4.59 4.5c.63.6.63 1.6 0 2.21l-20.7 20.26c-.62.61-1.63.61-2.26 0L48.28 41.83a.4.4 0 0 0-.56 0L33.03 56.21c-.63.61-1.64.61-2.27 0L10.07 35.95a1.55 1.55 0 0 1 0-2.22l4.59-4.5a1.63 1.63 0 0 1 2.27 0L31.6 43.63a.4.4 0 0 0 .57 0l14.69-14.38a1.63 1.63 0 0 1 2.26 0l14.69 14.38a.4.4 0 0 0 .57 0l14.68-14.38a1.63 1.63 0 0 1 2.27 0Z"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{F as r}from"./core-
|
|
1
|
+
import{F as r}from"./core-BlknpGLl.js";import"./index-CL1Pp-W8.js";import"./events-BPX61gpp.js";import"./index.es-5g6Py2iz.js";import"./fallback-DTghxJb4.js";const a=r`<svg fill="none" viewBox="0 0 20 20">
|
|
2
2
|
<path
|
|
3
3
|
fill="currentColor"
|
|
4
4
|
d="M11 6.67a1 1 0 1 0-2 0v2.66a1 1 0 0 0 2 0V6.67ZM10 14.5a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{F as l}from"./core-
|
|
1
|
+
import{F as l}from"./core-BlknpGLl.js";import"./index-CL1Pp-W8.js";import"./events-BPX61gpp.js";import"./index.es-5g6Py2iz.js";import"./fallback-DTghxJb4.js";const a=l`<svg fill="none" viewBox="0 0 41 40">
|
|
2
2
|
<g clip-path="url(#a)">
|
|
3
3
|
<path fill="#000" d="M.8 0h40v40H.8z" />
|
|
4
4
|
<path
|
|
@@ -5,7 +5,7 @@
|
|
|
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-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CL1Pp-W8.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-DCnJ64lX.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -78,8 +78,39 @@ Both attach paths open the browser signing station so the owner authorizes the r
|
|
|
78
78
|
> contract address you provide. A bug can block all agent activity or authorize transactions you did
|
|
79
79
|
> not intend. Review carefully before attaching.**
|
|
80
80
|
|
|
81
|
+
## Extracting calldata parameters safely
|
|
82
|
+
|
|
83
|
+
When you need to bound a specific call argument (amount cap, recipient check, slippage floor),
|
|
84
|
+
use `SailCalldata` instead of manual `abi.decode`. The two common bugs it prevents:
|
|
85
|
+
|
|
86
|
+
1. **Forgetting the length check** — decoding before checking `txData.length` can revert or
|
|
87
|
+
silently return wrong values. `SailCalldata.hasParams(txData, N)` is the one-line guard.
|
|
88
|
+
2. **Wrong slot index** — off-by-one decodes the wrong parameter. Named helpers make the
|
|
89
|
+
intent explicit: `asAddress(txData, 0)`, `asUint256(txData, 1)`, `asAddress(txData, 2)`.
|
|
90
|
+
|
|
91
|
+
```solidity
|
|
92
|
+
import {SailCalldata} from "./SailCalldata.sol";
|
|
93
|
+
|
|
94
|
+
function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
|
|
95
|
+
if (ctx.target != POOL) return false;
|
|
96
|
+
if (ctx.selector != SEL_SUPPLY) return false;
|
|
97
|
+
// supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)
|
|
98
|
+
if (!SailCalldata.hasParams(txData, 4)) return false;
|
|
99
|
+
address asset = SailCalldata.asAddress(txData, 0);
|
|
100
|
+
uint256 amount = SailCalldata.asUint256(txData, 1);
|
|
101
|
+
address onBehalfOf = SailCalldata.asAddress(txData, 2);
|
|
102
|
+
// ...
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Available helpers: `asAddress`, `asUint256`, `asInt256`, `asBytes32`, `asBool`,
|
|
107
|
+
`asUint128`, `asUint64`, `asUint32`, `asUint24`, `asUint16`, `asBytes4`.
|
|
108
|
+
Only covers static (fixed-size) types. For `bytes`, `string`, or dynamic arrays,
|
|
109
|
+
use `abi.decode(txData[4:], ...)` after the `hasParams` guard.
|
|
110
|
+
|
|
81
111
|
## Structure
|
|
82
112
|
|
|
83
113
|
- `foundry.toml` — Foundry config with `@sail/` remapping to `.sail/contracts/`
|
|
84
114
|
- `.sail/contracts/interfaces/IPermission.sol` — interface copy (matches SailProtocol)
|
|
85
115
|
- `mandates/BoundedCallPermission.sol` — general primitive: allowlisted targets, optional selector filter, max ETH value
|
|
116
|
+
- `mandates/SailCalldata.sol` — safe calldata parameter extraction helpers
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
4
|
import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
|
|
5
|
+
// SailCalldata: safe helpers for extracting calldata parameters inside evaluate().
|
|
6
|
+
// Use SailCalldata.hasParams(txData, N) + SailCalldata.asAddress/asUint256/... instead of
|
|
7
|
+
// manual abi.decode when you need to bound specific call arguments (amounts, recipients, etc.).
|
|
8
|
+
// See SailCalldata.sol for the full API and examples/permissions/ for protocol examples.
|
|
9
|
+
import {SailCalldata} from "./SailCalldata.sol";
|
|
5
10
|
|
|
6
11
|
/// @title BoundedCallPermission
|
|
7
12
|
/// @notice General-purpose IPermission primitive. Bounds the universal properties of any call:
|
|
8
13
|
/// allowed targets, allowed selectors, and max ETH value. Protocol-agnostic.
|
|
9
|
-
/// For calldata-parameter bounds (amount caps, recipient checks, slippage),
|
|
10
|
-
///
|
|
14
|
+
/// For calldata-parameter bounds (amount caps, recipient checks, slippage), use
|
|
15
|
+
/// SailCalldata (imported above) and write a protocol-specific permission —
|
|
16
|
+
/// see examples/permissions/ for the pattern per protocol.
|
|
11
17
|
/// @dev Deploy one instance per SMA with constructor-configured parameters.
|
|
12
18
|
contract BoundedCallPermission is IPermission {
|
|
13
19
|
bytes32 private constant DISCRIMINATOR = keccak256("BoundedCallPermission");
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// SailCalldata — safe static-parameter extraction for IPermission.evaluate()
|
|
6
|
+
//
|
|
7
|
+
// PROBLEM
|
|
8
|
+
// evaluate(bytes calldata txData, Context calldata ctx) receives raw ABI-
|
|
9
|
+
// encoded calldata. Extracting parameters by hand is the most dangerous part
|
|
10
|
+
// of permission writing:
|
|
11
|
+
// • Forgetting the length check before decoding → silent wrong value or
|
|
12
|
+
// revert instead of clean `return false`.
|
|
13
|
+
// • Wrong slot index → decoding the wrong parameter (type-checks, still wrong).
|
|
14
|
+
// • Truncation bugs when casting uint256 slots to address (padding bits).
|
|
15
|
+
//
|
|
16
|
+
// SOLUTION
|
|
17
|
+
// This library centralises the three operations into named helpers:
|
|
18
|
+
// 1. hasParams() — explicit length guard (call once, at the top of evaluate)
|
|
19
|
+
// 2. asAddress() — extract + mask to 20 bytes
|
|
20
|
+
// 3. asUint256(), asInt256(), asBytes32(), asBool(), asUint128() — typed slots
|
|
21
|
+
//
|
|
22
|
+
// All helpers are view/pure and add zero gas overhead beyond the slice itself.
|
|
23
|
+
//
|
|
24
|
+
// USAGE (replace abi.decode pattern)
|
|
25
|
+
//
|
|
26
|
+
// // Before:
|
|
27
|
+
// if (txData.length < 4 + 3 * 32) return false;
|
|
28
|
+
// (address asset, uint256 amount, address onBehalfOf) =
|
|
29
|
+
// abi.decode(txData[4:], (address, uint256, address));
|
|
30
|
+
//
|
|
31
|
+
// // After:
|
|
32
|
+
// if (!SailCalldata.hasParams(txData, 3)) return false;
|
|
33
|
+
// address asset = SailCalldata.asAddress(txData, 0);
|
|
34
|
+
// uint256 amount = SailCalldata.asUint256(txData, 1);
|
|
35
|
+
// address onBehalfOf = SailCalldata.asAddress(txData, 2);
|
|
36
|
+
//
|
|
37
|
+
// LIMITATIONS
|
|
38
|
+
// Only covers static (fixed-size) ABI types. Dynamic types (bytes, string,
|
|
39
|
+
// arrays) use pointer indirection — decode them with abi.decode(txData[4:], ...)
|
|
40
|
+
// after the hasParams() guard, or write a dedicated extractor.
|
|
41
|
+
//
|
|
42
|
+
// SLOT INDEXING
|
|
43
|
+
// Slots are 0-indexed from the first constructor parameter (after the 4-byte
|
|
44
|
+
// selector). Slot 0 = bytes [4..35], slot 1 = bytes [36..67], etc.
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
library SailCalldata {
|
|
48
|
+
// ── Guards ────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/// @notice Returns true when txData is long enough to hold `params` static
|
|
51
|
+
/// 32-byte ABI slots after the 4-byte selector.
|
|
52
|
+
/// @dev Call this once at the top of evaluate() before any slot access.
|
|
53
|
+
/// Returns false (not revert) so evaluate() can return false cleanly.
|
|
54
|
+
function hasParams(bytes calldata txData, uint256 params) internal pure returns (bool) {
|
|
55
|
+
return txData.length >= 4 + params * 32;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Static-type extractors ────────────────────────────────────────────────
|
|
59
|
+
// All assume hasParams() has already been checked for the relevant slot.
|
|
60
|
+
// Accessing an out-of-bounds slot will cause the calldata slice to revert —
|
|
61
|
+
// always guard with hasParams() first.
|
|
62
|
+
|
|
63
|
+
/// @notice Extract an address from ABI slot `i` (0-indexed after selector).
|
|
64
|
+
/// Masks to 20 bytes, discarding the ABI zero-padding in the upper 12.
|
|
65
|
+
function asAddress(bytes calldata txData, uint256 i) internal pure returns (address) {
|
|
66
|
+
return address(uint160(uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]))));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// @notice Extract a uint256 from ABI slot `i`.
|
|
70
|
+
function asUint256(bytes calldata txData, uint256 i) internal pure returns (uint256) {
|
|
71
|
+
return uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// @notice Extract an int256 from ABI slot `i`.
|
|
75
|
+
function asInt256(bytes calldata txData, uint256 i) internal pure returns (int256) {
|
|
76
|
+
return int256(uint256(bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32])));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// @notice Extract a bytes32 from ABI slot `i`.
|
|
80
|
+
function asBytes32(bytes calldata txData, uint256 i) internal pure returns (bytes32) {
|
|
81
|
+
return bytes32(txData[4 + i * 32 : 4 + (i + 1) * 32]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// @notice Extract a bool from ABI slot `i` (true when slot value is non-zero).
|
|
85
|
+
function asBool(bytes calldata txData, uint256 i) internal pure returns (bool) {
|
|
86
|
+
return asUint256(txData, i) != 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// @notice Extract a uint128 from ABI slot `i` (lower 128 bits of the slot).
|
|
90
|
+
function asUint128(bytes calldata txData, uint256 i) internal pure returns (uint128) {
|
|
91
|
+
return uint128(asUint256(txData, i));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// @notice Extract a uint64 from ABI slot `i`.
|
|
95
|
+
function asUint64(bytes calldata txData, uint256 i) internal pure returns (uint64) {
|
|
96
|
+
return uint64(asUint256(txData, i));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// @notice Extract a uint32 from ABI slot `i`.
|
|
100
|
+
function asUint32(bytes calldata txData, uint256 i) internal pure returns (uint32) {
|
|
101
|
+
return uint32(asUint256(txData, i));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// @notice Extract a uint24 from ABI slot `i` (e.g. Uniswap fee tier).
|
|
105
|
+
function asUint24(bytes calldata txData, uint256 i) internal pure returns (uint24) {
|
|
106
|
+
return uint24(asUint256(txData, i));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// @notice Extract a uint16 from ABI slot `i` (e.g. Aave referral code).
|
|
110
|
+
function asUint16(bytes calldata txData, uint256 i) internal pure returns (uint16) {
|
|
111
|
+
return uint16(asUint256(txData, i));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// @notice Extract bytes4 (a function selector) from ABI slot `i`.
|
|
115
|
+
function asBytes4(bytes calldata txData, uint256 i) internal pure returns (bytes4) {
|
|
116
|
+
return bytes4(asBytes32(txData, i));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -56,11 +56,41 @@ I'll write the permission contracts that bound your agent, prove in plain Englis
|
|
|
56
56
|
|
|
57
57
|
Permission contracts live in `mandates/`. The user authors, reviews, and owns them. For examples by protocol and chain, see `examples/permissions/`.
|
|
58
58
|
|
|
59
|
+
**Prerequisite — Foundry:** `forge build` requires the Foundry toolchain. If `forge` is not found, install it:
|
|
60
|
+
```bash
|
|
61
|
+
curl -L https://foundry.paradigm.xyz | bash # then restart shell
|
|
62
|
+
foundryup
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Approve coverage — mandatory:** ERC-20 `approve()` calls are NOT covered by supply or deposit permission contracts. If your strategy approves a token before supplying, you MUST deploy a separate bounded-approve permission that covers that specific `(token, spender, maxAmount)` combination, and authorize it alongside the supply permission. An agent that calls `approve()` without a matching permission will be rejected by the kernel.
|
|
66
|
+
|
|
67
|
+
**Batching:** if a strategy tick needs to approve before supplying, build both calls into a single dispatch array — `[approveCall, supplyCall]` — not two separate ticks. Splitting them wastes a tick and the approval sits exposed until the next run.
|
|
68
|
+
|
|
59
69
|
```bash
|
|
60
70
|
forge build
|
|
61
71
|
sailor mandate deploy --contract <Name> --sma <SMA> # deploy only — do NOT --attach yet
|
|
62
72
|
```
|
|
63
73
|
|
|
74
|
+
**Constructor args:** quoting rules differ by shell.
|
|
75
|
+
|
|
76
|
+
Bash / Git Bash:
|
|
77
|
+
```bash
|
|
78
|
+
sailor mandate deploy --contract <Name> --args '["0xToken","1000000"]' --sma <SMA>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
PowerShell — use escaped inner quotes inside single quotes:
|
|
82
|
+
```powershell
|
|
83
|
+
sailor mandate deploy --contract <Name> --args '[\"0xToken\",\"1000000\"]' --sma <SMA>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Any shell — `--args-file` avoids quoting entirely:
|
|
87
|
+
```json
|
|
88
|
+
["0xToken", "1000000"]
|
|
89
|
+
```
|
|
90
|
+
```bash
|
|
91
|
+
sailor mandate deploy --contract <Name> --args-file args.json --sma <SMA>
|
|
92
|
+
```
|
|
93
|
+
|
|
64
94
|
Before AUTHORIZING (attaching) the permission, BACK the plain-English claims with an actual on-chain probe. `evaluate()` lives on the deployed contract, so deploy first, then — before the irreversible authorization — generate sample calls from the user's stated strategy (ones the permission MUST accept and ones it MUST reject) and run them through `sailor mandate simulate`. This is an off-chain `eth_call` (no gas, no signing) that reports what the permission's `evaluate()` returns for each call, and flags any target with no contract code (a wrong or wrong-chain address):
|
|
65
95
|
|
|
66
96
|
```bash
|
|
@@ -90,13 +120,30 @@ sailor run --once # single tick — confirm it works before automating
|
|
|
90
120
|
For GitHub Actions:
|
|
91
121
|
|
|
92
122
|
1. Run `sailor keys export-ci` — copies your encrypted agent wallet to `ci-keystore.json` in the project root and adds it to `.gitignore` as an allowed file. The keystore is geth v3 encrypted; the raw private key is never exposed.
|
|
93
|
-
2. Commit
|
|
123
|
+
2. Commit the required files. CI needs these non-secret files to be in the repo:
|
|
94
124
|
```bash
|
|
95
|
-
|
|
125
|
+
npm install # generate package-lock.json if it doesn't exist
|
|
126
|
+
git add ci-keystore.json package-lock.json .sail/account.json .sail/config.json .sail/mandate.json
|
|
127
|
+
git commit -m "chore: add CI keystore and sail state" && git push
|
|
96
128
|
```
|
|
129
|
+
`package-lock.json` is required by `npm ci` (used in the workflow). `.sail/account.json`, `.sail/config.json`, and `.sail/mandate.json` contain only public addresses and flags — no secrets. The `.gitignore` already has `!` exceptions for all of these.
|
|
97
130
|
3. Add two secrets in GitHub (Settings → Secrets → Actions):
|
|
98
131
|
- `SAIL_PASSPHRASE` — the passphrase that encrypts your agent wallet
|
|
99
132
|
- `RPC_URL` — your RPC endpoint
|
|
133
|
+
4. Install the `gh` CLI — required to manage the workflow from the terminal (trigger runs, check logs, add secrets without opening the browser):
|
|
134
|
+
- macOS: `brew install gh`
|
|
135
|
+
- Windows: `winget install --id GitHub.cli` or `scoop install gh`
|
|
136
|
+
- Linux: see https://github.com/cli/cli/blob/trunk/docs/install_linux.md
|
|
137
|
+
Then authenticate with the `workflow` scope:
|
|
138
|
+
```bash
|
|
139
|
+
gh auth login --scopes workflow
|
|
140
|
+
```
|
|
141
|
+
The `workflow` scope is required — without it, `gh` cannot trigger or inspect Actions runs. Verify with:
|
|
142
|
+
```bash
|
|
143
|
+
gh auth status # confirm workflow scope is listed
|
|
144
|
+
gh workflow run agent-tick.yml # manual trigger
|
|
145
|
+
gh run list --workflow agent-tick.yml # check run history
|
|
146
|
+
```
|
|
100
147
|
|
|
101
148
|
The scaffolded workflow at `.github/workflows/agent-tick.yml` picks up `ci-keystore.json`, unlocks it with `SAIL_PASSPHRASE`, and runs on the configured schedule. No private key ever appears in the workflow or in secrets.
|
|
102
149
|
|
|
@@ -120,3 +167,5 @@ Use `buildDispatchSignature` from `@sail.money/sdk` — it reads the on-chain `D
|
|
|
120
167
|
- Do not hardcode the dispatch model — detect it on-chain
|
|
121
168
|
- Do not present example permissions as audited or as a supported menu
|
|
122
169
|
- Do not commit `SAIL_PASSPHRASE` or private keys
|
|
170
|
+
- Do not write a supply or deposit permission without also deploying a bounded-approve permission for each token the agent will approve — approve calls have no mandate coverage otherwise
|
|
171
|
+
- Do not pass `--args` inline JSON from PowerShell — use `--args-file` instead
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{F as o}from"./core-B0JxSbAV.js";import"./index-yQSvDbVa.js";import"./events-DC84dMPF.js";import"./index.es-BiIWW5o1.js";import"./fallback-BOfZ_bwu.js";const l=o` <svg fill="none" viewBox="0 0 13 4">
|
|
2
|
-
<path fill="currentColor" d="M.5 0h12L8.9 3.13a3.76 3.76 0 0 1-4.8 0L.5 0Z" />
|
|
3
|
-
</svg>`;export{l as cursorSvg};
|