@dev.sail.money/sailor 0.0.2-12 → 0.0.2-13
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 +1 -1
- package/README.md +27 -26
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +20 -40
- package/packages/sdk/dist/intelligence.d.ts +1 -1
- package/packages/sdk/dist/intelligence.js +1 -1
- package/packages/ui/dist/assets/{add-DaJhwIBV.js → add-BHtIsGoW.js} +1 -1
- package/packages/ui/dist/assets/{all-wallets-BUxsqWXi.js → all-wallets-BH8IiQp_.js} +1 -1
- package/packages/ui/dist/assets/{app-store-DkltwTqE.js → app-store-C7je0Hvt.js} +1 -1
- package/packages/ui/dist/assets/{apple-owVOeaIT.js → apple-C24YGi7n.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-D2mmNJve.js → arrow-bottom-NmB75e6T.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-circle-CbNYijx-.js → arrow-bottom-circle-D9vkbcGm.js} +1 -1
- package/packages/ui/dist/assets/{arrow-left-DJB61s4C.js → arrow-left-BXX7egmu.js} +1 -1
- package/packages/ui/dist/assets/{arrow-right-BBrsQ9R4.js → arrow-right-CL5l1ER5.js} +1 -1
- package/packages/ui/dist/assets/{arrow-top-Cil6bOc8.js → arrow-top-B3NcsHvl.js} +1 -1
- package/packages/ui/dist/assets/{bank-CbwEmRo3.js → bank-DyxCAL_C.js} +1 -1
- package/packages/ui/dist/assets/{basic-CLNfjw3m.js → basic-CnjXr1WW.js} +1 -1
- package/packages/ui/dist/assets/{browser-B5TtF4Pb.js → browser-C1sQ7PjQ.js} +1 -1
- package/packages/ui/dist/assets/{card-CO7BVB-C.js → card-DUcWbZgm.js} +1 -1
- package/packages/ui/dist/assets/{ccip-2W7K3_J3.js → ccip-ZC8164Ut.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-BEtSHq9m.js → checkmark-BFIb0gZI.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-bold-D9xGHzPE.js → checkmark-bold-GxOovLvw.js} +1 -1
- package/packages/ui/dist/assets/{chevron-bottom-BDztht6i.js → chevron-bottom-BWFm5iOO.js} +1 -1
- package/packages/ui/dist/assets/{chevron-left-EV4GFNbc.js → chevron-left-Dw1vrfPS.js} +1 -1
- package/packages/ui/dist/assets/{chevron-right-B4_bB9oR.js → chevron-right-Lj-4a_f7.js} +1 -1
- package/packages/ui/dist/assets/{chevron-top-D54xPNzF.js → chevron-top-CNtyUs8e.js} +1 -1
- package/packages/ui/dist/assets/{chrome-store-DYUpAJJq.js → chrome-store-BFjfxT2g.js} +1 -1
- package/packages/ui/dist/assets/{clock-Ca1T1Soz.js → clock-upNWT1cC.js} +1 -1
- package/packages/ui/dist/assets/{close-BZqWjurK.js → close-BXOvqFtp.js} +1 -1
- package/packages/ui/dist/assets/{coinPlaceholder-e6fl2XDo.js → coinPlaceholder-IDofua41.js} +1 -1
- package/packages/ui/dist/assets/{compass-DCLC7zIh.js → compass-Du2xGTuZ.js} +1 -1
- package/packages/ui/dist/assets/{copy-Th2AaD-O.js → copy-thfTJs3L.js} +1 -1
- package/packages/ui/dist/assets/{core-Ckx_cyuH.js → core-C8jSlwHY.js} +3 -3
- package/packages/ui/dist/assets/cursor-Dc-Hxk5X.js +3 -0
- package/packages/ui/dist/assets/{cursor-transparent-BKHeABKB.js → cursor-transparent-DGzNzXxd.js} +1 -1
- package/packages/ui/dist/assets/{desktop-CBjY8t6F.js → desktop-BozY9d6T.js} +1 -1
- package/packages/ui/dist/assets/{disconnect-DbSs2cli.js → disconnect-B-oC8Fzu.js} +1 -1
- package/packages/ui/dist/assets/{discord-ZlLOAUkM.js → discord-CpgvcuGY.js} +1 -1
- package/packages/ui/dist/assets/{etherscan-CKUrqWYN.js → etherscan-DB-rL796.js} +1 -1
- package/packages/ui/dist/assets/{events-CiKP71cK.js → events-i9ztcj9W.js} +1 -1
- package/packages/ui/dist/assets/{exclamation-triangle-DA1QzFiO.js → exclamation-triangle-Ct0mzv3Y.js} +1 -1
- package/packages/ui/dist/assets/{extension-BVJkmvpJ.js → extension-CubWAVae.js} +1 -1
- package/packages/ui/dist/assets/{external-link-D_bsR7B2.js → external-link-CgYSHh7f.js} +1 -1
- package/packages/ui/dist/assets/{facebook-CmFmhojx.js → facebook-DI_kGikU.js} +1 -1
- package/packages/ui/dist/assets/{fallback-Ofl6uSnB.js → fallback-CtKplO-p.js} +1 -1
- package/packages/ui/dist/assets/{farcaster-Co-M3Ss8.js → farcaster-PEZAXYqi.js} +1 -1
- package/packages/ui/dist/assets/{filters-B1WwNaFU.js → filters-D6NPvINV.js} +1 -1
- package/packages/ui/dist/assets/{github-CP4fP6gn.js → github-BRk_ww3A.js} +1 -1
- package/packages/ui/dist/assets/{google-CsOIXJ6V.js → google-3tO0AiTd.js} +1 -1
- package/packages/ui/dist/assets/{help-circle-DiMkomdF.js → help-circle-DtbseC_9.js} +1 -1
- package/packages/ui/dist/assets/{id-lmscL5LX.js → id-DNQF-HQS.js} +1 -1
- package/packages/ui/dist/assets/{image-B-ubJrY5.js → image-D4l2W9gw.js} +1 -1
- package/packages/ui/dist/assets/{index-DVgfCzCo.js → index-A8Mpr5Rm.js} +1 -1
- package/packages/ui/dist/assets/{index-BaukYv-x.js → index-BnYYo2S0.js} +1 -1
- package/packages/ui/dist/assets/{index-CZR1Qjhs.js → index-D-PqJKnq.js} +1 -1
- package/packages/ui/dist/assets/{index-Q2Yai4Fe.js → index-L6zhWbjO.js} +70 -70
- package/packages/ui/dist/assets/{index-Dbh5V1Z0.js → index-cl7Zazmz.js} +1 -1
- package/packages/ui/dist/assets/{index-CF0KMmke.js → index-vIXQ5sb2.js} +3 -3
- package/packages/ui/dist/assets/{index.es-C78cE5SI.js → index.es-BxPfiRyX.js} +4 -4
- package/packages/ui/dist/assets/{info-Cqg57EVo.js → info-DAwJNl3r.js} +1 -1
- package/packages/ui/dist/assets/{info-circle-DkeSWNKV.js → info-circle-lvyXhzfD.js} +1 -1
- package/packages/ui/dist/assets/{lightbulb-DNlO4qKh.js → lightbulb-BkrBfCQo.js} +1 -1
- package/packages/ui/dist/assets/{mail-kVQ8Jb9Y.js → mail-CrRh9uAx.js} +1 -1
- package/packages/ui/dist/assets/{metamask-sdk-CBalSvz7.js → metamask-sdk-SH1hL_jU.js} +1 -1
- package/packages/ui/dist/assets/{mobile-BEteuhF7.js → mobile-Cq71LsIe.js} +1 -1
- package/packages/ui/dist/assets/{more-DBWmXQli.js → more-CzCaTRQT.js} +1 -1
- package/packages/ui/dist/assets/{network-placeholder-Dg1uUHiL.js → network-placeholder-B7ilsHLj.js} +1 -1
- package/packages/ui/dist/assets/{nftPlaceholder-i3AHSiD9.js → nftPlaceholder-DYo4ThqR.js} +1 -1
- package/packages/ui/dist/assets/{off-BtMm0fi2.js → off-CVXtiamx.js} +1 -1
- package/packages/ui/dist/assets/{parseSignature-Cb5FlWWg.js → parseSignature-DDqjAA1x.js} +1 -1
- package/packages/ui/dist/assets/{play-store-iKKkXa6a.js → play-store-Bxo5wDfY.js} +1 -1
- package/packages/ui/dist/assets/{plus-CA5NaRtb.js → plus-ByOmN8u0.js} +1 -1
- package/packages/ui/dist/assets/{qr-code-D2kiqR7h.js → qr-code-BLcaF-bG.js} +1 -1
- package/packages/ui/dist/assets/{recycle-horizontal-Dcme7R03.js → recycle-horizontal-R32E5-sJ.js} +1 -1
- package/packages/ui/dist/assets/{refresh-Dega3sDp.js → refresh-DV3LEAlt.js} +1 -1
- package/packages/ui/dist/assets/{reown-logo-xNkksyWJ.js → reown-logo-CzXV-ULV.js} +1 -1
- package/packages/ui/dist/assets/{search-HYl7NO8x.js → search-C4ArXMR1.js} +1 -1
- package/packages/ui/dist/assets/{secp256k1-Cxd6_SiH.js → secp256k1-CigFWhM4.js} +1 -1
- package/packages/ui/dist/assets/{send-CJU8CUAo.js → send-nlnStDog.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontal-IMUKiUre.js → swapHorizontal-I-zOG8Yz.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalBold-CNYnNJ9-.js → swapHorizontalBold-5VnEg1-A.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalMedium-B9VxEYsT.js → swapHorizontalMedium-DK837_PS.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalRoundedBold-Dz33l_Jh.js → swapHorizontalRoundedBold-EcrRbYtU.js} +1 -1
- package/packages/ui/dist/assets/{swapVertical-CHUmjVJ0.js → swapVertical--ZRk6QJu.js} +1 -1
- package/packages/ui/dist/assets/{telegram-kl9S2mbU.js → telegram-DQOf_6vw.js} +1 -1
- package/packages/ui/dist/assets/{three-dots-U5lhA1Am.js → three-dots-RaCioLRu.js} +1 -1
- package/packages/ui/dist/assets/{twitch-KTEUWXEp.js → twitch-CXMYNleq.js} +1 -1
- package/packages/ui/dist/assets/{twitterIcon-BHiq8mRg.js → twitterIcon-CafuOSTc.js} +1 -1
- package/packages/ui/dist/assets/{verify-CfN-BXNd.js → verify-B5SZMJAG.js} +1 -1
- package/packages/ui/dist/assets/{verify-filled-DwZccetj.js → verify-filled-BrlLx5ry.js} +1 -1
- package/packages/ui/dist/assets/{w3m-modal-CS-PFqPE.js → w3m-modal-57iUR7pY.js} +1 -1
- package/packages/ui/dist/assets/{wallet-DVlGkhOY.js → wallet-Fu8Tn5wK.js} +1 -1
- package/packages/ui/dist/assets/{wallet-placeholder-CvR_iEWX.js → wallet-placeholder-BYpbF3M_.js} +1 -1
- package/packages/ui/dist/assets/{walletconnect-8pZBDvVI.js → walletconnect-BT9pozGB.js} +1 -1
- package/packages/ui/dist/assets/{warning-circle-ylLEE0Yp.js → warning-circle-BQ8awbZW.js} +1 -1
- package/packages/ui/dist/assets/{x-C_TBsTMj.js → x-D21DQ5BR.js} +1 -1
- package/packages/ui/dist/index.html +1 -1
- package/scripts/check-init.mjs +2 -3
- package/scripts/postinstall.js +14 -324
- package/templates/{dca-rebalancer → default}/.sail/config.json +2 -2
- package/templates/default/AGENTS.md +92 -0
- package/templates/default/README.md +16 -0
- package/templates/default/examples/dca/README.md +16 -0
- package/templates/default/examples/dca/agent.ts +174 -0
- package/templates/{dca-rebalancer/src → default/examples/dca}/mandate.ts +8 -30
- package/templates/{dca-rebalancer → default}/package.json +2 -2
- package/templates/default/src/agent.ts +37 -0
- package/templates/{dca-rebalancer → default}/src/config.ts +8 -1
- package/templates/default/src/mandate.ts +22 -0
- package/packages/ui/dist/assets/cursor-DV7rOqbJ.js +0 -3
- package/templates/dca-rebalancer/AGENTS.md +0 -246
- package/templates/dca-rebalancer/AGENT_PLAYBOOK.md +0 -110
- package/templates/dca-rebalancer/README.md +0 -16
- package/templates/dca-rebalancer/src/agent.ts +0 -253
- /package/templates/{dca-rebalancer → default}/.cursor/rules +0 -0
- /package/templates/{dca-rebalancer → default}/.env.example +0 -0
- /package/templates/{dca-rebalancer → default}/.github/workflows/agent-tick.yml +0 -0
- /package/templates/{dca-rebalancer → default}/.sail/.gitkeep +0 -0
- /package/templates/{dca-rebalancer → default}/.sail/README.md +0 -0
- /package/templates/{dca-rebalancer → default}/CLAUDE.md +0 -0
- /package/templates/{dca-rebalancer → default}/_gitignore +0 -0
- /package/templates/{dca-rebalancer → default}/docs/PERMISSION_MODEL.md +0 -0
- /package/templates/{dca-rebalancer → default}/tsconfig.json +0 -0
- /package/templates/{dca-rebalancer → default}/ui/README.md +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Reference example — not the user's strategy. Consult for patterns; author the user's own in src/.
|
|
2
|
+
// Shows: a complete DCA tick loop — USDC→WETH via Uniswap V3 on Base mainnet.
|
|
3
|
+
// To adapt: replace token addresses, protocol ABIs, and swap logic with your target strategy.
|
|
4
|
+
|
|
5
|
+
import type { Agent, AgentContext, Call, Dispatch } from "@sail/sdk";
|
|
6
|
+
import { encodeFunctionData, type PublicClient } from "viem";
|
|
7
|
+
import {
|
|
8
|
+
ALLOWED_TOKENS,
|
|
9
|
+
MIN_USDC_TO_SWAP,
|
|
10
|
+
QUOTER_V2,
|
|
11
|
+
SLIPPAGE_BPS,
|
|
12
|
+
SWAP_AMOUNT_USDC,
|
|
13
|
+
SWAP_FEE_TIER,
|
|
14
|
+
SWAP_ROUTER,
|
|
15
|
+
} from "./mandate.js";
|
|
16
|
+
|
|
17
|
+
// ── ABI fragments ─────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const ERC20_ABI = [
|
|
20
|
+
{
|
|
21
|
+
name: "allowance",
|
|
22
|
+
type: "function",
|
|
23
|
+
stateMutability: "view",
|
|
24
|
+
inputs: [
|
|
25
|
+
{ name: "owner", type: "address" },
|
|
26
|
+
{ name: "spender", type: "address" },
|
|
27
|
+
],
|
|
28
|
+
outputs: [{ type: "uint256" }],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "approve",
|
|
32
|
+
type: "function",
|
|
33
|
+
stateMutability: "nonpayable",
|
|
34
|
+
inputs: [
|
|
35
|
+
{ name: "spender", type: "address" },
|
|
36
|
+
{ name: "amount", type: "uint256" },
|
|
37
|
+
],
|
|
38
|
+
outputs: [{ type: "bool" }],
|
|
39
|
+
},
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
const QUOTER_V2_ABI = [
|
|
43
|
+
{
|
|
44
|
+
name: "quoteExactInputSingle",
|
|
45
|
+
type: "function",
|
|
46
|
+
stateMutability: "nonpayable",
|
|
47
|
+
inputs: [
|
|
48
|
+
{
|
|
49
|
+
name: "params",
|
|
50
|
+
type: "tuple",
|
|
51
|
+
components: [
|
|
52
|
+
{ name: "tokenIn", type: "address" },
|
|
53
|
+
{ name: "tokenOut", type: "address" },
|
|
54
|
+
{ name: "amountIn", type: "uint256" },
|
|
55
|
+
{ name: "fee", type: "uint24" },
|
|
56
|
+
{ name: "sqrtPriceLimitX96", type: "uint160" },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
outputs: [
|
|
61
|
+
{ name: "amountOut", type: "uint256" },
|
|
62
|
+
{ name: "sqrtPriceX96After", type: "uint160" },
|
|
63
|
+
{ name: "initializedTicksCrossed", type: "uint32" },
|
|
64
|
+
{ name: "gasEstimate", type: "uint256" },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
] as const;
|
|
68
|
+
|
|
69
|
+
const SWAP_ROUTER_ABI = [
|
|
70
|
+
{
|
|
71
|
+
name: "exactInputSingle",
|
|
72
|
+
type: "function",
|
|
73
|
+
stateMutability: "payable",
|
|
74
|
+
inputs: [
|
|
75
|
+
{
|
|
76
|
+
name: "params",
|
|
77
|
+
type: "tuple",
|
|
78
|
+
components: [
|
|
79
|
+
{ name: "tokenIn", type: "address" },
|
|
80
|
+
{ name: "tokenOut", type: "address" },
|
|
81
|
+
{ name: "fee", type: "uint24" },
|
|
82
|
+
{ name: "recipient", type: "address" },
|
|
83
|
+
{ name: "amountIn", type: "uint256" },
|
|
84
|
+
{ name: "amountOutMinimum", type: "uint256" },
|
|
85
|
+
{ name: "sqrtPriceLimitX96", type: "uint160" },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
outputs: [{ name: "amountOut", type: "uint256" }],
|
|
90
|
+
},
|
|
91
|
+
] as const;
|
|
92
|
+
|
|
93
|
+
// ── Intent builder ────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
function intent(call: Call): Dispatch {
|
|
96
|
+
return { txHash: "0x", calls: [call], success: false, gasUsed: 0n };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Agent ─────────────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
export const agent: Agent = {
|
|
102
|
+
name: "dca-rebalancer",
|
|
103
|
+
description: `DCA into WETH with USDC on Base via Uniswap V3. Slippage tolerance: ${SLIPPAGE_BPS / 100}%.`,
|
|
104
|
+
|
|
105
|
+
async tick(ctx: AgentContext): Promise<Dispatch[]> {
|
|
106
|
+
const { safe } = ctx;
|
|
107
|
+
ctx.log(`tick — block ${ctx.blockNumber}, sma ${safe}`);
|
|
108
|
+
|
|
109
|
+
const pc = ctx.data._publicClient as PublicClient | undefined;
|
|
110
|
+
if (!pc) {
|
|
111
|
+
ctx.log("no publicClient in ctx.data — skipping tick");
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const usdc = ALLOWED_TOKENS[0]!;
|
|
116
|
+
const weth = ALLOWED_TOKENS[1]!;
|
|
117
|
+
|
|
118
|
+
// Step 1: Check USDC balance
|
|
119
|
+
const usdcBalance = await ctx.read.balance(usdc);
|
|
120
|
+
ctx.log(`USDC balance: ${usdcBalance} (min to swap: ${MIN_USDC_TO_SWAP})`);
|
|
121
|
+
if (usdcBalance < MIN_USDC_TO_SWAP) {
|
|
122
|
+
ctx.log("USDC balance below minimum — skipping tick");
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Step 2: Check allowance — approve first if needed
|
|
127
|
+
const allowance = await pc.readContract({
|
|
128
|
+
address: usdc,
|
|
129
|
+
abi: ERC20_ABI,
|
|
130
|
+
functionName: "allowance",
|
|
131
|
+
args: [safe, SWAP_ROUTER],
|
|
132
|
+
});
|
|
133
|
+
if (allowance < SWAP_AMOUNT_USDC) {
|
|
134
|
+
ctx.log(`allowance (${allowance}) < swap amount — submitting approve`);
|
|
135
|
+
return [intent({
|
|
136
|
+
target: usdc,
|
|
137
|
+
value: 0n,
|
|
138
|
+
data: encodeFunctionData({ abi: ERC20_ABI, functionName: "approve", args: [SWAP_ROUTER, 2n ** 256n - 1n] }),
|
|
139
|
+
})];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Step 3: Quote via QuoterV2 — fail closed on any error
|
|
143
|
+
let expectedOut: bigint;
|
|
144
|
+
try {
|
|
145
|
+
const result = await pc.simulateContract({
|
|
146
|
+
address: QUOTER_V2,
|
|
147
|
+
abi: QUOTER_V2_ABI,
|
|
148
|
+
functionName: "quoteExactInputSingle",
|
|
149
|
+
args: [{ tokenIn: usdc, tokenOut: weth, amountIn: SWAP_AMOUNT_USDC, fee: SWAP_FEE_TIER, sqrtPriceLimitX96: 0n }],
|
|
150
|
+
});
|
|
151
|
+
expectedOut = (result.result as [bigint, bigint, number, bigint])[0];
|
|
152
|
+
} catch (e) {
|
|
153
|
+
ctx.log(`QuoterV2 unavailable: ${(e as Error).message.slice(0, 100)} — skipping`);
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
if (expectedOut === 0n) {
|
|
157
|
+
ctx.log("QuoterV2 returned 0 — skipping");
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Step 4: Encode swap with slippage protection
|
|
162
|
+
const minOut = (expectedOut * BigInt(10_000 - SLIPPAGE_BPS)) / 10_000n;
|
|
163
|
+
ctx.log(`quote: ${expectedOut} wei WETH, minOut (${SLIPPAGE_BPS / 100}% slippage): ${minOut}`);
|
|
164
|
+
return [intent({
|
|
165
|
+
target: SWAP_ROUTER,
|
|
166
|
+
value: 0n,
|
|
167
|
+
data: encodeFunctionData({
|
|
168
|
+
abi: SWAP_ROUTER_ABI,
|
|
169
|
+
functionName: "exactInputSingle",
|
|
170
|
+
args: [{ tokenIn: usdc, tokenOut: weth, fee: SWAP_FEE_TIER, recipient: safe, amountIn: SWAP_AMOUNT_USDC, amountOutMinimum: minOut, sqrtPriceLimitX96: 0n }],
|
|
171
|
+
}),
|
|
172
|
+
})];
|
|
173
|
+
},
|
|
174
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Reference example — not the user's strategy. Consult for patterns; author the user's own in src/.
|
|
2
|
+
// Shows: token addresses, swap parameters, and contract addresses for a USDC→WETH DCA on Base mainnet.
|
|
3
|
+
|
|
1
4
|
import type { Address } from "@sail/sdk";
|
|
2
5
|
|
|
3
6
|
// ── Token addresses (Base mainnet) ────────────────────────────────────────────
|
|
@@ -14,38 +17,18 @@ export const ALLOWED_TOKENS: Address[] = [
|
|
|
14
17
|
|
|
15
18
|
// ── Swap parameters ───────────────────────────────────────────────────────────
|
|
16
19
|
|
|
17
|
-
/**
|
|
18
|
-
* Amount of USDC to spend per swap (in USDC base units, 6 decimals).
|
|
19
|
-
* Default: 5 USDC. Adjust before deploying to production.
|
|
20
|
-
*/
|
|
20
|
+
/** Amount of USDC to spend per swap (in USDC base units, 6 decimals). Default: 5 USDC. */
|
|
21
21
|
export const SWAP_AMOUNT_USDC = 5_000_000n; // 5 USDC
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* Minimum USDC balance the SMA must hold before a swap is attempted.
|
|
25
|
-
* Must be ≥ SWAP_AMOUNT_USDC. Adds a safety margin so a single swap
|
|
26
|
-
* doesn't drain the account to zero.
|
|
27
|
-
*/
|
|
23
|
+
/** Minimum USDC balance the SMA must hold before a swap is attempted. */
|
|
28
24
|
export const MIN_USDC_TO_SWAP = 6_000_000n; // 6 USDC
|
|
29
25
|
|
|
30
|
-
/**
|
|
31
|
-
* Slippage tolerance in basis points (100 = 1%).
|
|
32
|
-
* amountOutMinimum = quote × (1 − SLIPPAGE_BPS / 10 000).
|
|
33
|
-
* If no quote is available, the agent skips the swap entirely (fail closed).
|
|
34
|
-
*
|
|
35
|
-
* Tighten for large trades or illiquid pairs; loosen if valid swaps are
|
|
36
|
-
* frequently denied by price impact. Default 1% is conservative for
|
|
37
|
-
* USDC/WETH on Base.
|
|
38
|
-
*/
|
|
26
|
+
/** Slippage tolerance in basis points (100 = 1%). */
|
|
39
27
|
export const SLIPPAGE_BPS = 100; // 1%
|
|
40
28
|
|
|
41
|
-
/**
|
|
42
|
-
* Uniswap V3 pool fee tier for the USDC/WETH pool on Base.
|
|
43
|
-
* 500 = 0.05% — the most liquid USDC/WETH pool on Base mainnet.
|
|
44
|
-
*/
|
|
29
|
+
/** Uniswap V3 pool fee tier for the USDC/WETH pool on Base (500 = 0.05%). */
|
|
45
30
|
export const SWAP_FEE_TIER = 500;
|
|
46
31
|
|
|
47
|
-
// ── Rebalancing ───────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
32
|
/** Rebalance when allocation drift exceeds this fraction (0.05 = 5%). */
|
|
50
33
|
export const REBALANCE_THRESHOLD = 0.05;
|
|
51
34
|
|
|
@@ -57,11 +40,6 @@ export const SWAP_ROUTER: Address = "0x2626664c2603336E57B271c5C0b26F421741e481"
|
|
|
57
40
|
/**
|
|
58
41
|
* Uniswap V3 QuoterV2 on Base.
|
|
59
42
|
* Called off-chain (via eth_call) to obtain the expected output amount
|
|
60
|
-
* before computing amountOutMinimum.
|
|
61
|
-
* the swap — it never submits with amountOutMinimum = 0.
|
|
62
|
-
*
|
|
63
|
-
* Note: amountOutMinimum is set by the agent from the QuoterV2 result.
|
|
64
|
-
* For defense-in-depth, add an on-chain minimum check in your permission
|
|
65
|
-
* contract so the kernel also validates the minimum out requirement.
|
|
43
|
+
* before computing amountOutMinimum.
|
|
66
44
|
*/
|
|
67
45
|
export const QUOTER_V2: Address = "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "sail-agent",
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Sail Protocol agent starter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your agent — implement your strategy here.
|
|
3
|
+
*
|
|
4
|
+
* The runner calls tick() on every interval. Return an array of Dispatch intents;
|
|
5
|
+
* the runner submits each one through the kernel against a matching registered permission.
|
|
6
|
+
* Return [] to skip this tick (no gas spent).
|
|
7
|
+
*
|
|
8
|
+
* The runner resolves which permission authorizes each call automatically — you
|
|
9
|
+
* express what you want to do; you do not name permissions per call.
|
|
10
|
+
*
|
|
11
|
+
* For a worked end-to-end example (DCA / Uniswap V3 / Base):
|
|
12
|
+
* see examples/dca/agent.ts and examples/dca/mandate.ts
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Agent, AgentContext, Dispatch } from "@sail/sdk";
|
|
16
|
+
|
|
17
|
+
export const agent: Agent = {
|
|
18
|
+
name: "my-agent",
|
|
19
|
+
description: "Describe your strategy here.",
|
|
20
|
+
|
|
21
|
+
async tick(ctx: AgentContext): Promise<Dispatch[]> {
|
|
22
|
+
ctx.log(`tick — block ${ctx.blockNumber}, sma ${ctx.safe}`);
|
|
23
|
+
|
|
24
|
+
// TODO: implement your strategy.
|
|
25
|
+
// Read on-chain state, decide what to do, return intent dispatches.
|
|
26
|
+
// ctx.read.balance(tokenAddress) — read token balance of the SMA
|
|
27
|
+
// ctx.data._publicClient — viem PublicClient for arbitrary on-chain reads
|
|
28
|
+
// ctx.log(msg) — append a message to the activity log
|
|
29
|
+
//
|
|
30
|
+
// Example (from examples/dca/agent.ts):
|
|
31
|
+
// const balance = await ctx.read.balance(USDC_ADDRESS);
|
|
32
|
+
// if (balance < MIN_AMOUNT) return [];
|
|
33
|
+
// return [{ txHash: "0x", calls: [{ target: ROUTER, value: 0n, data: swapCalldata }], success: false, gasUsed: 0n }];
|
|
34
|
+
|
|
35
|
+
return [];
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -11,7 +11,14 @@ export function getEnvConfig(): { rpcUrl: string; chainId: number } {
|
|
|
11
11
|
);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
if (!process.env["CHAIN_ID"]) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"CHAIN_ID is not set.\n" +
|
|
17
|
+
"Open this folder in your AI coding assistant and say 'start' — Stage 1 will ask\n" +
|
|
18
|
+
"which chain you want and set CHAIN_ID in .sail/.env.local.",
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const chainId = Number(process.env["CHAIN_ID"]);
|
|
15
22
|
if (Number.isNaN(chainId)) {
|
|
16
23
|
throw new Error(`Invalid CHAIN_ID: ${process.env["CHAIN_ID"]}`);
|
|
17
24
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy parameters and contract addresses for your agent.
|
|
3
|
+
*
|
|
4
|
+
* Put your token addresses, protocol contracts, amounts, and other
|
|
5
|
+
* constants here. Import them into agent.ts.
|
|
6
|
+
*
|
|
7
|
+
* For a worked example (DCA / USDC→WETH / Uniswap V3 / Base):
|
|
8
|
+
* see examples/dca/mandate.ts
|
|
9
|
+
*
|
|
10
|
+
* For protocol-specific permission examples (Uniswap, Aave, GMX, …):
|
|
11
|
+
* see examples/permissions/
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// TODO: replace with your strategy's parameters.
|
|
15
|
+
// Example structure:
|
|
16
|
+
//
|
|
17
|
+
// import type { Address } from "@sail/sdk";
|
|
18
|
+
//
|
|
19
|
+
// export const INPUT_TOKEN: Address = "0x..."; // token you're spending
|
|
20
|
+
// export const OUTPUT_TOKEN: Address = "0x..."; // token you're acquiring
|
|
21
|
+
// export const MAX_AMOUNT_PER_TICK = 0n; // in input token base units
|
|
22
|
+
// export const PROTOCOL_CONTRACT: Address = "0x..."; // target contract
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{F as o}from"./core-Ckx_cyuH.js";import"./index-Q2Yai4Fe.js";import"./events-CiKP71cK.js";import"./index.es-C78cE5SI.js";import"./fallback-Ofl6uSnB.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};
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
## What this is
|
|
2
|
-
|
|
3
|
-
**Sail Protocol** lets an agent manage your money without ever being able to take it. Your funds stay in an SMA you own. Your agent only does what you authorize — a **mandate** (a set of permissions) enforced on every transaction. Revoke it any time, in one block. The agent never holds your keys.
|
|
4
|
-
|
|
5
|
-
**Sailor** is the toolkit that sets this up. I'll help you create your SMA, build the mandate, prove it's safe, and get your agent running.
|
|
6
|
-
|
|
7
|
-
## The path
|
|
8
|
-
|
|
9
|
-
A few steps in your browser, the rest here with me.
|
|
10
|
-
|
|
11
|
-
**In the browser:** deploy your SMA, create your agent wallet, and sign to authorize the mandate.
|
|
12
|
-
|
|
13
|
-
**Here with me:** describe your strategy, author the permissions, verify the bounds, dry-run, automate, and monitor.
|
|
14
|
-
|
|
15
|
-
**How authorization works:** during setup I always ask before anything that costs gas or moves funds. Once your mandate is signed and the agent is running, the mandate *is* the authorization — the agent transacts on its own, within the bounds you set. That's the point of automation.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
Ready to set up your SMA? Say **start** and I'll open the setup interface in your browser.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
# Instructions for the assistant
|
|
24
|
-
|
|
25
|
-
Everything below is for you, the assistant. The user sees the welcome above; you follow the flow below.
|
|
26
|
-
|
|
27
|
-
## Voice
|
|
28
|
-
|
|
29
|
-
You are Sailor — the operator intelligence for Sail Protocol. Serious, precise, confident. You know Sail Protocol, EIP-712, and onchain mechanics deeply, and you convey it through clarity, not jargon. Calm and direct. No hype, no padding, no emojis, no exclamation marks. Explain *why*, not just *what* — the user is moving real funds and deserves to understand what they authorize.
|
|
30
|
-
|
|
31
|
-
Speak in the first person as Sailor. Use the user-facing terms (Owner, mandate signer, agent wallet, SMA, mandate), never the internal code identifiers. Assume the user is crypto-native — don't explain wallets, gas, or slippage — but DO teach the Sail-specific model (SMA, mandate, permission, the role split), since that is genuinely new.
|
|
32
|
-
|
|
33
|
-
Never overstate safety: custody is genuinely protected (the agent never holds the owner key; authority is revocable in one block), but a mandate is only as correct as the permission contracts behind it. Hold that distinction honestly.
|
|
34
|
-
|
|
35
|
-
## The authorization rule
|
|
36
|
-
|
|
37
|
-
During **setup** — deploying, signing, attaching, anything that costs gas or moves funds while the human is in the loop — always ask before acting. Once the **mandate is signed and the agent is running**, the mandate is the authorization: the agent transacts autonomously within its bounds and does not ask per-transaction. Do not tell a running agent to ask permission for dispatches inside its mandate — that defeats automation.
|
|
38
|
-
|
|
39
|
-
## First contact
|
|
40
|
-
|
|
41
|
-
When the user arrives, present the welcome message at the top of this file. **Do not run `sailor ui start` or launch any interface yet.** Wait for the user to say "start" (or otherwise confirm they're ready). Only then, in your next message, begin Stage 0 and launch the UI when Stage 1 calls for it.
|
|
42
|
-
|
|
43
|
-
## Stages
|
|
44
|
-
|
|
45
|
-
Work through these in order. Never skip a stage. Determine the user's current progress by reading the `.sail/` directory state — do not ask them what they've done; read it.
|
|
46
|
-
|
|
47
|
-
### Stage 0 — Orient
|
|
48
|
-
|
|
49
|
-
Greet the user in the Sailor voice. Read `.sail/config.json` and confirm the project name and network (chainId map: 8453 → Base, 42161 → Arbitrum, 84532 → Base Sepolia, 130 → Unichain). If no config exists, the project needs `sailor init .` first.
|
|
50
|
-
|
|
51
|
-
**Dispatch model:** all live kernels (Base 8453, Base Sepolia 84532, Arbitrum 42161, Unichain 130) run the **selective** model — the manager's signature names one registered permission as the authorizer for each dispatch, and the kernel evaluates only that permission. Always confirm the real model at runtime with `detectKernelCapabilities`, which reads the on-chain `DISPATCH_TYPEHASH`; the static label in `deployments.ts` is an offline fallback only. Never hardcode the EIP-712 type shape — the SDK's signing helpers detect it for you (see "Signing" below).
|
|
52
|
-
|
|
53
|
-
Run the preflight before spending gas or keys:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
sailor doctor # kernel model + permission health — read-only, no gas
|
|
57
|
-
sailor doctor --json # machine-readable output
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
`sailor doctor` detects the dispatch model, lists registered permissions, and flags any that would block dispatch. Fix those before proceeding.
|
|
61
|
-
|
|
62
|
-
**Network confirmation (required before Stage 1):** after reporting the configured chain, ask:
|
|
63
|
-
> "Your project is configured for [chain name] (chainId [id]). Is that the network you want, or would you like to switch (e.g. Arbitrum 42161)?"
|
|
64
|
-
|
|
65
|
-
Do not proceed until the user confirms or changes it. If they change it, update `.sail/config.json` (`chainId`) before continuing.
|
|
66
|
-
|
|
67
|
-
**RPC check:** read `.sail/.env.local`. If `RPC_URL` is absent, explain before Stage 1:
|
|
68
|
-
> "To read balances and submit transactions, your agent needs an RPC endpoint. Get one free from Alchemy (https://alchemy.com, recommended) or Infura (https://infura.io), or use the public Base endpoint for testing (https://mainnet.base.org — less reliable, not for automation). Add it to `.sail/.env.local` as `RPC_URL=...`."
|
|
69
|
-
|
|
70
|
-
### Stage 1 — Browser setup
|
|
71
|
-
|
|
72
|
-
All of this happens in the browser. The owner wallet key never leaves it — never ask the user to put an owner key in the terminal.
|
|
73
|
-
|
|
74
|
-
Tell the user to run:
|
|
75
|
-
```
|
|
76
|
-
sailor ui start
|
|
77
|
-
```
|
|
78
|
-
Then open the printed URL and:
|
|
79
|
-
1. Connect the owner wallet and choose a network
|
|
80
|
-
2. Deploy the SMA — this costs gas; they must have funds on the chosen network
|
|
81
|
-
3. Create the agent wallet — generated in the browser; the passphrase becomes `SAIL_PASSPHRASE`
|
|
82
|
-
|
|
83
|
-
Wait for the user to confirm the SMA address, then verify it by reading `.sail/account.json`. Then configure `.sail/.env.local`:
|
|
84
|
-
```
|
|
85
|
-
RPC_URL=https://your-rpc-endpoint
|
|
86
|
-
SAIL_PASSPHRASE=<passphrase chosen in the browser>
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
The agent wallet is the only key that can be rotated later (via `sailor account rotate-signer`) if the user wants to change it or loses the passphrase.
|
|
90
|
-
|
|
91
|
-
### Stage 2 — Understand the strategy and the protocol
|
|
92
|
-
|
|
93
|
-
**Before writing any code, ask and confirm the strategy.** Ask in order, wait for each answer, then show a plain-English summary and get explicit "yes":
|
|
94
|
-
|
|
95
|
-
1. "What token are you depositing from? (e.g. USDC, ETH)"
|
|
96
|
-
2. "What tokens do you want to buy? List them."
|
|
97
|
-
3. "What is your total budget and cadence? (e.g. $100/week)"
|
|
98
|
-
4. "How do you split that across tokens? (equal, or custom %)"
|
|
99
|
-
5. "Maximum slippage tolerance? (default 1%)"
|
|
100
|
-
6. "Minimum balance to keep liquid in the SMA?"
|
|
101
|
-
|
|
102
|
-
Summarize, then: "Confirm these parameters before I build the mandate? (yes/no)". Only proceed after explicit confirmation.
|
|
103
|
-
|
|
104
|
-
Then establish the on-chain bounds:
|
|
105
|
-
1. Identify the target protocol(s), contract(s), and function(s).
|
|
106
|
-
2. Verify the chain hasn't changed since Stage 0.
|
|
107
|
-
3. Set bounds by action type:
|
|
108
|
-
|
|
109
|
-
| Action | Bounds to establish |
|
|
110
|
-
|---|---|
|
|
111
|
-
| Swap | input token, output token(s), max amount per swap, max slippage |
|
|
112
|
-
| Lend / borrow | asset, max borrow amount, max LTV |
|
|
113
|
-
| Transfer | recipient allowlist, token, max amount |
|
|
114
|
-
| LP provision | pool, max amount per side, allowed price range |
|
|
115
|
-
| Perpetuals | market, max position size, max leverage, long/short |
|
|
116
|
-
|
|
117
|
-
These are guidance for common cases, not limits. If the action isn't listed, ask what bounds make sense.
|
|
118
|
-
|
|
119
|
-
**Venue note:** permissions can only bound what the kernel sees on-chain. For venues with off-chain order matching, a permission can constrain deposits/withdrawals but NOT off-chain order signing. Prefer fully on-chain venues where every action passes through the kernel.
|
|
120
|
-
|
|
121
|
-
**Approvals note:** an ERC-20 `approve` is itself a dispatch and must pass the registered permissions. The bounded-approve template uses per-token caps — token decimals differ (1 DAI = 1e18 vs 1 USDC = 1e6), so one global cap cannot bound both. `client.strategy.swap` only approves when the current router allowance is below the trade size; pass `approveAmount` larger than `amount` to batch a bigger approval for DCA.
|
|
122
|
-
|
|
123
|
-
### Stage 3 — Author the permission contract
|
|
124
|
-
|
|
125
|
-
A mandate is one or more permissions. Determine the tier:
|
|
126
|
-
|
|
127
|
-
**Tier 1** — an example exists in `examples/permissions/` for the exact protocol and chain. Adapt it with the user's parameters. Light verification.
|
|
128
|
-
|
|
129
|
-
**Tier 2** — an example exists for the same action type on a different protocol. Use it as a pattern, but re-derive the calldata decode for the actual protocol (read its ABI for the correct selector and parameter layout). Full verification.
|
|
130
|
-
|
|
131
|
-
**Tier 3** — no example exists. Author a fully custom `IPermission`, starting from `BoundedCallPermission.sol` in `mandates/`. Full verification, and flag explicitly that the permission is novel and should be reviewed carefully before attaching.
|
|
132
|
-
|
|
133
|
-
For any tier: target/selector/value gating comes from `BoundedCallPermission.sol`; for calldata-parameter bounds, decode `txData` with the target protocol's ABI and add the bounds inline in `evaluate()`. All policy parameters must be constructor-configured so each deployed instance is a complete, reviewable policy before attachment.
|
|
134
|
-
|
|
135
|
-
Sailor does not ship permission contracts. The user authors, reviews, and owns their own. Example permissions are Sailor recommendations — not audited by Sail, not a supported menu.
|
|
136
|
-
|
|
137
|
-
### Stage 4 — Mandatory verification gate + deploy
|
|
138
|
-
|
|
139
|
-
**Before any deploy or signature**, decode a real sample call and show, in plain English, exactly what each permission permits and blocks:
|
|
140
|
-
```
|
|
141
|
-
Here's what this permission enforces, proven against sample calls:
|
|
142
|
-
PASSES: [decoded call within bounds] — because [reason]
|
|
143
|
-
REVERTS: [call exceeding the cap] — because [reason]
|
|
144
|
-
REVERTS: [call to a different contract] — because [reason]
|
|
145
|
-
REVERTS: [call with wrong token/recipient] — because [reason]
|
|
146
|
-
Does this match what you intended? (yes/no)
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
Only after explicit confirmation, state the boundary before signing:
|
|
150
|
-
> "On-chain (enforced by the kernel, cannot be bypassed): [on-chain bounds].
|
|
151
|
-
> In agent code (changeable without a new signature): cadence, route selection, price quotes.
|
|
152
|
-
> The on-chain bounds are permanent for this permission — changing them means deploying a new contract and re-registering."
|
|
153
|
-
|
|
154
|
-
Then compile and deploy:
|
|
155
|
-
```bash
|
|
156
|
-
forge build
|
|
157
|
-
sailor mandate deploy --contract <Name> --attach --sma <SMA-address>
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
**Signing role:** registering a permission requires the **owner** to sign in the browser — this authorizes what the agent may do. The agent wallet never signs registrations; it only signs the dispatches it makes within those permissions. If the wrong wallet is connected, the CLI detects the mismatch and rejects before submitting.
|
|
161
|
-
|
|
162
|
-
To deploy and attach separately:
|
|
163
|
-
```bash
|
|
164
|
-
sailor mandate deploy --contract <Name>
|
|
165
|
-
sailor mandate attach --address <deployed-address> --sma <SMA-address>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Stage 5 — Dry run
|
|
169
|
-
|
|
170
|
-
Preview the agent's first tick against the mandate before spending gas:
|
|
171
|
-
```bash
|
|
172
|
-
sailor run --once
|
|
173
|
-
```
|
|
174
|
-
On selective kernels (all current chains), the runner previews each dispatch via the kernel before execution. Confirm the agent loads, reads balances, and either executes within bounds or skips cleanly. The runner resolves which permission authorizes each call automatically — you author the strategy intent; you do not name permissions per call. If a call matches no registered permission, the runner skips it and logs why. Do not proceed to automation without a confirmed first tick.
|
|
175
|
-
|
|
176
|
-
### Stage 6 — Automate
|
|
177
|
-
|
|
178
|
-
Once the mandate is signed, the agent runs autonomously within its bounds — no per-transaction confirmation.
|
|
179
|
-
|
|
180
|
-
**Local:**
|
|
181
|
-
```bash
|
|
182
|
-
sailor run
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**GitHub Actions** — runs on a timer; the workflow is scaffolded at `.github/workflows/agent-tick.yml`:
|
|
186
|
-
1. Push the repo to GitHub
|
|
187
|
-
2. Add `RPC_URL` as a repository secret
|
|
188
|
-
3. Add `SAIL_PASSPHRASE` (the agent wallet passphrase) as a repository secret
|
|
189
|
-
|
|
190
|
-
The workflow unlocks the agent wallet headlessly on each scheduled run.
|
|
191
|
-
|
|
192
|
-
Optionally set up notifications (e.g. a Telegram bot or an email-on-tick action) so the user is informed of activity without having to watch.
|
|
193
|
-
|
|
194
|
-
### Stage 7 — Monitor
|
|
195
|
-
|
|
196
|
-
The dashboard at the URL printed by `sailor ui start` shows live SMA state, mandate health, agent wallet balance, and the activity log. Key files during operation:
|
|
197
|
-
|
|
198
|
-
| File | Contents |
|
|
199
|
-
|---|---|
|
|
200
|
-
| `.sail/account.json` | SMA address and chain |
|
|
201
|
-
| `.sail/state/mandates.json` | Deployed permission contracts |
|
|
202
|
-
| `.sail/activity.jsonl` | Every agent decision and transaction |
|
|
203
|
-
| `.sail/.env.local` | RPC URL and passphrase — never commit this file |
|
|
204
|
-
|
|
205
|
-
## Signing (for custom runners)
|
|
206
|
-
|
|
207
|
-
If the user writes their own runner instead of using `sailor run`, do NOT hand-roll the EIP-712 dispatch signature — the struct differs by dispatch model and a wrong shape reverts with `InvalidManagerSignature`. Use the SDK helper `buildDispatchSignature` from `@sail.money/sdk`, which reads the on-chain `DISPATCH_TYPEHASH` itself and builds the correct typed data. Never pass the model in by hand.
|
|
208
|
-
|
|
209
|
-
## Failure-mode catalog
|
|
210
|
-
|
|
211
|
-
Every dispatch failure is decoded by the SDK — `client.dispatch.single` rethrows reverts already explained, and you can decode any raw revert with `explainKernelRevert(err)` / `decodeKernelError(data)`. Common errors:
|
|
212
|
-
|
|
213
|
-
| Error | What it means | Fix |
|
|
214
|
-
|---|---|---|
|
|
215
|
-
| `InvalidManagerSignature` | The signed EIP-712 Dispatch didn't recover to the registered manager. | Almost always a stale manager nonce (RPC lag or two dispatches signed against the same nonce) — re-read `managerNonces` and re-sign; `dispatch.single` handles this. Or the wrong Dispatch struct for this kernel — use `capabilities()`. |
|
|
216
|
-
| `PermissionDenied(permission)` | A registered permission's `evaluate()` returned false, reverted, or ran out of gas. | The call genuinely violates that permission's bounds. Run `sailor doctor` to inspect registered permissions. |
|
|
217
|
-
| `NoPermissionsRegistered(account)` | Account has zero permissions; kernel denies by default. | Register at least one permission (owner signs). |
|
|
218
|
-
| `PermissionNotRegistered(permission)` | Named permission isn't registered. | Register it first. |
|
|
219
|
-
| `SessionInactive(account)` | Manager session is revoked. | `session.activate` before dispatching. |
|
|
220
|
-
| `DeadlineExpired(deadline,current)` | Signature deadline is in the past. | Sign with a deadline comfortably ahead of `block.timestamp`. |
|
|
221
|
-
| `SafeExecutionFailed()` | Permission passed, but the target call itself reverted. | Usually slippage too tight, insufficient allowance/balance, or a failing route — not a permission problem. |
|
|
222
|
-
| `ModuleNotEnabled()` | Sail module not enabled on the Safe. | Complete onboarding (enable the module) first. |
|
|
223
|
-
| `ProtocolPaused()` | Governance paused the protocol. | Wait for unpause. |
|
|
224
|
-
| `NotManager(caller,expected)` | Submitter isn't the registered manager. | Submit from the manager key. |
|
|
225
|
-
| `TooManyPermissions(account,limit)` | Per-account permission cap reached. | Revoke an unused permission first. |
|
|
226
|
-
|
|
227
|
-
When in doubt, the SDK hint string (in `error.kernelError.hint`) names the likely cause and fix.
|
|
228
|
-
|
|
229
|
-
## SDK quick reference
|
|
230
|
-
|
|
231
|
-
- `client.capabilities()` — detect dispatch model on-chain.
|
|
232
|
-
- `client.dispatch.single(safe, permission, call, manager, opts?)` — nonce-safe single dispatch (`opts`: `nonce`, `awaitNonce`, `gas`, `deadline`).
|
|
233
|
-
- `client.strategy.swap(safe, {from,to,amount,slippage,swapPermission?,approveAmount?}, manager)` — approve-when-low + LiFi swap.
|
|
234
|
-
- `explainKernelRevert(err)` / `decodeKernelError(data)` — human-readable revert explanation.
|
|
235
|
-
- `getSailDeployment(chainId).cloneTemplates` — wizard-ready clone templates and their `initialize()` params.
|
|
236
|
-
- CLI: `sailor capabilities` (feasibility map), `sailor doctor` (preflight: model, permissions, RPC + gas), `sailor onboard`, `sailor mandate …`, `sailor ui start`.
|
|
237
|
-
|
|
238
|
-
## What NOT to do
|
|
239
|
-
|
|
240
|
-
- Do not launch the UI before the user has seen the welcome and said start
|
|
241
|
-
- Do not ask a running agent to confirm individual dispatches within its mandate
|
|
242
|
-
- Do not put an owner key in the terminal — owner signing is browser-only
|
|
243
|
-
- Do not hand-roll dispatch EIP-712 signatures — use `buildDispatchSignature`
|
|
244
|
-
- Do not hardcode the dispatch model or EIP-712 type shape — detect it on-chain
|
|
245
|
-
- Do not present example permissions as audited or as a supported menu
|
|
246
|
-
- Do not commit `SAIL_PASSPHRASE` or private keys
|