@dev.sail.money/sailor 0.1.0-local → 1.0.0-39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/AGENTS.md +139 -140
  2. package/LICENSE +21 -21
  3. package/README.md +428 -430
  4. package/docs/PERMISSION_MODEL.md +93 -93
  5. package/examples/permissions/BoundedApproveAndCallBatch.sol +179 -179
  6. package/examples/permissions/BoundedBet_Limitless_Base.sol +97 -97
  7. package/examples/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +94 -94
  8. package/examples/permissions/BoundedPerp_GMXv2_Arbitrum.sol +154 -154
  9. package/examples/permissions/BoundedStake_Venice_Base.sol +85 -85
  10. package/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +82 -82
  11. package/examples/permissions/BoundedSwap_UniswapV3_Base.sol +116 -116
  12. package/examples/permissions/BoundedSwap_UniswapV4_Unichain.sol +150 -150
  13. package/examples/permissions/BoundedTransfer_ERC20_Ethereum.sol +73 -73
  14. package/examples/permissions/BoundedVault_ERC4626_Base.sol +97 -97
  15. package/examples/permissions/README.md +79 -79
  16. package/examples/permissions/SailCalldata.sol +118 -118
  17. package/examples/permissions/foundry.toml +10 -10
  18. package/examples/permissions/interfaces/IBatchPermission.sol +38 -38
  19. package/examples/permissions/interfaces/IPermission.sol +18 -18
  20. package/package.json +44 -45
  21. package/packages/cli/README.md +34 -34
  22. package/packages/cli/dist/index.cjs +734 -705
  23. package/packages/cli/dist/server.cjs +627 -538
  24. package/packages/sdk/README.md +65 -65
  25. package/packages/sdk/dist/intelligence.d.ts +1 -1
  26. package/packages/sdk/dist/intelligence.js +1 -1
  27. package/packages/sdk/package.json +80 -80
  28. package/packages/ui/dist/assets/{add-BxpXfVWe.js → add-Gzf62xlX.js} +1 -1
  29. package/packages/ui/dist/assets/{all-wallets-BKTn_sWK.js → all-wallets-O-pI4o8v.js} +1 -1
  30. package/packages/ui/dist/assets/{app-store-CfuKbwxR.js → app-store-CeSLaOaQ.js} +1 -1
  31. package/packages/ui/dist/assets/{apple-BKSBbNYg.js → apple-FGNyQM-D.js} +1 -1
  32. package/packages/ui/dist/assets/{arrow-bottom-D4bG6gZi.js → arrow-bottom-C1fusORF.js} +1 -1
  33. package/packages/ui/dist/assets/{arrow-bottom-circle-BNTs1p0T.js → arrow-bottom-circle-AvK1VEpN.js} +1 -1
  34. package/packages/ui/dist/assets/{arrow-left-2uee3vYv.js → arrow-left-Bu-hq4Ep.js} +1 -1
  35. package/packages/ui/dist/assets/{arrow-right-BktjMV6h.js → arrow-right-XbZESmct.js} +1 -1
  36. package/packages/ui/dist/assets/{arrow-top-Izu28fX4.js → arrow-top-DvkVHbhX.js} +1 -1
  37. package/packages/ui/dist/assets/{bank-USBaAyFM.js → bank-DTThWRvC.js} +1 -1
  38. package/packages/ui/dist/assets/{basic-C_9KjTEH.js → basic-B9AMgqFE.js} +1 -1
  39. package/packages/ui/dist/assets/{browser-DAEMAKV7.js → browser-Bhnivm4i.js} +1 -1
  40. package/packages/ui/dist/assets/{card-DT8yDkKN.js → card-DjIlyU55.js} +1 -1
  41. package/packages/ui/dist/assets/{ccip-CkqfGSxX.js → ccip-DPAiKntc.js} +1 -1
  42. package/packages/ui/dist/assets/{checkmark-CsgdEXFj.js → checkmark-DSVYfoVl.js} +1 -1
  43. package/packages/ui/dist/assets/{checkmark-bold-D2gjOQo2.js → checkmark-bold-BFkw_Q5g.js} +1 -1
  44. package/packages/ui/dist/assets/{chevron-bottom-tprFynYV.js → chevron-bottom-CyCgyOwY.js} +1 -1
  45. package/packages/ui/dist/assets/{chevron-left-D2Zj1gNB.js → chevron-left-DTuO2WLr.js} +1 -1
  46. package/packages/ui/dist/assets/{chevron-right-D1rRuAVe.js → chevron-right-DwB5FZj8.js} +1 -1
  47. package/packages/ui/dist/assets/{chevron-top-24dL1mbL.js → chevron-top-DKukdWvg.js} +1 -1
  48. package/packages/ui/dist/assets/{chrome-store-Vy-5niYX.js → chrome-store-Csz4L9Ls.js} +1 -1
  49. package/packages/ui/dist/assets/{clock-qBjLnVdJ.js → clock-Bg6488Gw.js} +1 -1
  50. package/packages/ui/dist/assets/{close-DARDwgcu.js → close-BxAJGBxP.js} +1 -1
  51. package/packages/ui/dist/assets/{coinPlaceholder-BvpIbPlD.js → coinPlaceholder-CCJVgW9w.js} +1 -1
  52. package/packages/ui/dist/assets/{compass-BMTO0ayt.js → compass-CSQSZaqJ.js} +1 -1
  53. package/packages/ui/dist/assets/{copy-PaXeRHza.js → copy-CqlzXVB-.js} +1 -1
  54. package/packages/ui/dist/assets/{core-BFnStQd-.js → core-ClvdTrpG.js} +3 -3
  55. package/packages/ui/dist/assets/cursor-CKKwWhGQ.js +3 -0
  56. package/packages/ui/dist/assets/{cursor-transparent-BEMdi-8q.js → cursor-transparent-C1VOGz11.js} +1 -1
  57. package/packages/ui/dist/assets/{desktop-CfuLLThw.js → desktop-QiLednKV.js} +1 -1
  58. package/packages/ui/dist/assets/{disconnect-DhwgJMiR.js → disconnect-Bx2TgkML.js} +1 -1
  59. package/packages/ui/dist/assets/{discord-po8qoN1s.js → discord-6MWX5Rbb.js} +1 -1
  60. package/packages/ui/dist/assets/{etherscan-BEsz0_yx.js → etherscan-CodIrmJK.js} +1 -1
  61. package/packages/ui/dist/assets/{events-Bz33Unzu.js → events-DOEm-LTy.js} +1 -1
  62. package/packages/ui/dist/assets/{exclamation-triangle-7CjTAGOQ.js → exclamation-triangle-Dwr5oCsh.js} +1 -1
  63. package/packages/ui/dist/assets/{extension-CmxjEWEt.js → extension-C-SoZx1s.js} +1 -1
  64. package/packages/ui/dist/assets/{external-link-CmQ--bNS.js → external-link-BiDYH90C.js} +1 -1
  65. package/packages/ui/dist/assets/{facebook-CIBn9b65.js → facebook-Bm27AlfS.js} +1 -1
  66. package/packages/ui/dist/assets/{fallback-DATyrQlb.js → fallback-Bwpmpy13.js} +1 -1
  67. package/packages/ui/dist/assets/{farcaster-OJ3Jasxg.js → farcaster-CSW-SjzS.js} +1 -1
  68. package/packages/ui/dist/assets/{filters-D4x09zeL.js → filters-j3dR7AJK.js} +1 -1
  69. package/packages/ui/dist/assets/{github-ZlIuMArp.js → github-CQMTSSgW.js} +1 -1
  70. package/packages/ui/dist/assets/{google-Gwg85sfv.js → google-BBIVBfAd.js} +1 -1
  71. package/packages/ui/dist/assets/{help-circle-D1uOWYcX.js → help-circle-CEvTLelF.js} +1 -1
  72. package/packages/ui/dist/assets/{id-C0-5UdYk.js → id-CYRVgSgN.js} +1 -1
  73. package/packages/ui/dist/assets/{image-D_DUsv8-.js → image-Cm9Ep5G0.js} +1 -1
  74. package/packages/ui/dist/assets/{index-DdbJhIdl.js → index-4wdo7Ga_.js} +3 -3
  75. package/packages/ui/dist/assets/{index-CrYzBWfD.js → index-BrP8m1ZI.js} +1 -1
  76. package/packages/ui/dist/assets/index-C2PQCECq.css +1 -0
  77. package/packages/ui/dist/assets/{index-DiojfeVM.js → index-DZ07nuwB.js} +1 -1
  78. package/packages/ui/dist/assets/{index-BCzex_R6.js → index-De_P6mNS.js} +1 -1
  79. package/packages/ui/dist/assets/index-DrQ9A8dp.js +1775 -0
  80. package/packages/ui/dist/assets/{index-izd7vu_r.js → index-Z55BVE94.js} +1 -1
  81. package/packages/ui/dist/assets/{index.es-DdkHhQAj.js → index.es-DnT9Uzwt.js} +4 -4
  82. package/packages/ui/dist/assets/{info-CiRd_kEG.js → info-DdYqiFMu.js} +1 -1
  83. package/packages/ui/dist/assets/{info-circle-ypxjqarK.js → info-circle-DkD9oY-S.js} +1 -1
  84. package/packages/ui/dist/assets/{lightbulb-B-pxLxd8.js → lightbulb-7Q3AhpSP.js} +1 -1
  85. package/packages/ui/dist/assets/{mail-BYmicuVZ.js → mail-CPagdnfp.js} +1 -1
  86. package/packages/ui/dist/assets/{metamask-sdk-Ccl6DG7Q.js → metamask-sdk-CQ4tzR6A.js} +1 -1
  87. package/packages/ui/dist/assets/{mobile-CtP5PqVT.js → mobile-01GVSpey.js} +1 -1
  88. package/packages/ui/dist/assets/{more-6C2733we.js → more-BPXPYrvy.js} +1 -1
  89. package/packages/ui/dist/assets/{network-placeholder-CdhxMzqd.js → network-placeholder-CII8WrTF.js} +1 -1
  90. package/packages/ui/dist/assets/{nftPlaceholder-DVmTWEAY.js → nftPlaceholder-DG5rjRzx.js} +1 -1
  91. package/packages/ui/dist/assets/{off-DNYLughs.js → off-eemo7R2q.js} +1 -1
  92. package/packages/ui/dist/assets/{parseSignature-Dq2B5Bu3.js → parseSignature-CaRSntRi.js} +1 -1
  93. package/packages/ui/dist/assets/{play-store-D7Qut5ta.js → play-store-SIqMwLur.js} +1 -1
  94. package/packages/ui/dist/assets/{plus-kqMyjt3q.js → plus-DemTM2Nx.js} +1 -1
  95. package/packages/ui/dist/assets/{qr-code-DiUCWRbz.js → qr-code-BjolKhQv.js} +1 -1
  96. package/packages/ui/dist/assets/{recycle-horizontal-Boe3XiS-.js → recycle-horizontal-BluxSqWj.js} +1 -1
  97. package/packages/ui/dist/assets/{refresh-CrBgBQYO.js → refresh-Cto5auO0.js} +1 -1
  98. package/packages/ui/dist/assets/{reown-logo-CFZCCHSx.js → reown-logo-lrWmaeNj.js} +1 -1
  99. package/packages/ui/dist/assets/{search-ChTDrghU.js → search-SNmrxLL7.js} +1 -1
  100. package/packages/ui/dist/assets/{secp256k1-DAV5Q_FR.js → secp256k1-SI0Bxirn.js} +1 -1
  101. package/packages/ui/dist/assets/{send-DLFbBFe1.js → send-Do8kdKTu.js} +1 -1
  102. package/packages/ui/dist/assets/{swapHorizontal-BEs3emfG.js → swapHorizontal-HBP1koQV.js} +1 -1
  103. package/packages/ui/dist/assets/{swapHorizontalBold-CC-Hfa7W.js → swapHorizontalBold-B9g1LqXn.js} +1 -1
  104. package/packages/ui/dist/assets/{swapHorizontalMedium-BmR0H8DC.js → swapHorizontalMedium-C8JebI_2.js} +1 -1
  105. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-BdP5NGIH.js → swapHorizontalRoundedBold-37eEYoAp.js} +1 -1
  106. package/packages/ui/dist/assets/{swapVertical-CPrGEJPY.js → swapVertical-DmacpIGs.js} +1 -1
  107. package/packages/ui/dist/assets/{telegram-CxNoZ80Q.js → telegram-Dq_CUch4.js} +1 -1
  108. package/packages/ui/dist/assets/{three-dots-BRa6SBpL.js → three-dots-DbkqGUCU.js} +1 -1
  109. package/packages/ui/dist/assets/{twitch-BC338bG5.js → twitch-iG0Ncwwy.js} +1 -1
  110. package/packages/ui/dist/assets/{twitterIcon-BGZmt2i9.js → twitterIcon-CNbKQx87.js} +1 -1
  111. package/packages/ui/dist/assets/{verify-CEstW0zw.js → verify-BfXT7L3L.js} +1 -1
  112. package/packages/ui/dist/assets/{verify-filled-OkZb0weU.js → verify-filled-BxqxA6xc.js} +1 -1
  113. package/packages/ui/dist/assets/{w3m-modal-pS09ECwE.js → w3m-modal-DeDYqwYJ.js} +1 -1
  114. package/packages/ui/dist/assets/{wallet-BXVKCgC9.js → wallet-BXsUR9Tj.js} +1 -1
  115. package/packages/ui/dist/assets/{wallet-placeholder-C_kNhB1c.js → wallet-placeholder-bbWbfkZu.js} +1 -1
  116. package/packages/ui/dist/assets/{walletconnect-CRKIuUHH.js → walletconnect-WsTWE17z.js} +1 -1
  117. package/packages/ui/dist/assets/{warning-circle-DB2NnwlJ.js → warning-circle-cNSUigh6.js} +1 -1
  118. package/packages/ui/dist/assets/{x-DT4RmwL5.js → x-gNtNGE0N.js} +1 -1
  119. package/packages/ui/dist/index.html +14 -14
  120. package/scripts/check-docs.mjs +262 -262
  121. package/scripts/check-init.mjs +108 -108
  122. package/templates/custom-mandate/.sail/contracts/interfaces/IPermission.sol +18 -18
  123. package/templates/custom-mandate/README.md +116 -116
  124. package/templates/custom-mandate/foundry.toml +8 -8
  125. package/templates/custom-mandate/mandates/BoundedCallPermission.sol +41 -41
  126. package/templates/custom-mandate/mandates/README.md +16 -16
  127. package/templates/custom-mandate/mandates/SailCalldata.sol +118 -118
  128. package/templates/default/.cursor/rules +25 -25
  129. package/templates/default/.env.example +20 -20
  130. package/templates/default/.github/workflows/agent-tick.yml +33 -33
  131. package/templates/default/.sail/README.md +13 -13
  132. package/templates/default/.sail/config.json +10 -10
  133. package/templates/default/AGENTS.md +171 -171
  134. package/templates/default/CLAUDE.md +2 -2
  135. package/templates/default/README.md +16 -16
  136. package/templates/default/_gitignore +13 -13
  137. package/templates/default/docs/PERMISSION_MODEL.md +93 -93
  138. package/templates/default/examples/dca/README.md +16 -16
  139. package/templates/default/examples/dca/agent.ts +174 -174
  140. package/templates/default/examples/dca/mandate.ts +45 -45
  141. package/templates/default/package.json +17 -17
  142. package/templates/default/src/agent.ts +37 -37
  143. package/templates/default/src/config.ts +24 -24
  144. package/templates/default/src/mandate.ts +22 -22
  145. package/templates/default/tsconfig.json +17 -17
  146. package/templates/default/ui/README.md +3 -3
  147. package/templates/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +84 -84
  148. package/templates/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +97 -97
  149. package/templates/lifi-permissions/README.md +53 -53
  150. package/packages/ui/dist/assets/cursor-BDvw-B17.js +0 -3
  151. package/packages/ui/dist/assets/index-BUhrHLpY.js +0 -1775
  152. package/packages/ui/dist/assets/index-Cq02kQmy.css +0 -1
  153. package/scripts/postinstall.js +0 -81
@@ -1,174 +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.money/sailor/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
+ // 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.money/sailor/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,45 +1,45 @@
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
-
4
- import type { Address } from "@sail.money/sailor/sdk";
5
-
6
- // ── Token addresses (Base mainnet) ────────────────────────────────────────────
7
-
8
- /**
9
- * Tokens in the DCA basket.
10
- * ALLOWED_TOKENS[0] = USDC (input — what the agent spends)
11
- * ALLOWED_TOKENS[1] = WETH (output — what the agent accumulates)
12
- */
13
- export const ALLOWED_TOKENS: Address[] = [
14
- "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base (6 decimals)
15
- "0x4200000000000000000000000000000000000006", // WETH on Base (18 decimals)
16
- ];
17
-
18
- // ── Swap parameters ───────────────────────────────────────────────────────────
19
-
20
- /** Amount of USDC to spend per swap (in USDC base units, 6 decimals). Default: 5 USDC. */
21
- export const SWAP_AMOUNT_USDC = 5_000_000n; // 5 USDC
22
-
23
- /** Minimum USDC balance the SMA must hold before a swap is attempted. */
24
- export const MIN_USDC_TO_SWAP = 6_000_000n; // 6 USDC
25
-
26
- /** Slippage tolerance in basis points (100 = 1%). */
27
- export const SLIPPAGE_BPS = 100; // 1%
28
-
29
- /** Uniswap V3 pool fee tier for the USDC/WETH pool on Base (500 = 0.05%). */
30
- export const SWAP_FEE_TIER = 500;
31
-
32
- /** Rebalance when allocation drift exceeds this fraction (0.05 = 5%). */
33
- export const REBALANCE_THRESHOLD = 0.05;
34
-
35
- // ── Contract addresses (Base mainnet) ─────────────────────────────────────────
36
-
37
- /** Uniswap SwapRouter02 on Base. Target for exactInputSingle swaps. */
38
- export const SWAP_ROUTER: Address = "0x2626664c2603336E57B271c5C0b26F421741e481";
39
-
40
- /**
41
- * Uniswap V3 QuoterV2 on Base.
42
- * Called off-chain (via eth_call) to obtain the expected output amount
43
- * before computing amountOutMinimum.
44
- */
45
- export const QUOTER_V2: Address = "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a";
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
+
4
+ import type { Address } from "@sail.money/sailor/sdk";
5
+
6
+ // ── Token addresses (Base mainnet) ────────────────────────────────────────────
7
+
8
+ /**
9
+ * Tokens in the DCA basket.
10
+ * ALLOWED_TOKENS[0] = USDC (input — what the agent spends)
11
+ * ALLOWED_TOKENS[1] = WETH (output — what the agent accumulates)
12
+ */
13
+ export const ALLOWED_TOKENS: Address[] = [
14
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base (6 decimals)
15
+ "0x4200000000000000000000000000000000000006", // WETH on Base (18 decimals)
16
+ ];
17
+
18
+ // ── Swap parameters ───────────────────────────────────────────────────────────
19
+
20
+ /** Amount of USDC to spend per swap (in USDC base units, 6 decimals). Default: 5 USDC. */
21
+ export const SWAP_AMOUNT_USDC = 5_000_000n; // 5 USDC
22
+
23
+ /** Minimum USDC balance the SMA must hold before a swap is attempted. */
24
+ export const MIN_USDC_TO_SWAP = 6_000_000n; // 6 USDC
25
+
26
+ /** Slippage tolerance in basis points (100 = 1%). */
27
+ export const SLIPPAGE_BPS = 100; // 1%
28
+
29
+ /** Uniswap V3 pool fee tier for the USDC/WETH pool on Base (500 = 0.05%). */
30
+ export const SWAP_FEE_TIER = 500;
31
+
32
+ /** Rebalance when allocation drift exceeds this fraction (0.05 = 5%). */
33
+ export const REBALANCE_THRESHOLD = 0.05;
34
+
35
+ // ── Contract addresses (Base mainnet) ─────────────────────────────────────────
36
+
37
+ /** Uniswap SwapRouter02 on Base. Target for exactInputSingle swaps. */
38
+ export const SWAP_ROUTER: Address = "0x2626664c2603336E57B271c5C0b26F421741e481";
39
+
40
+ /**
41
+ * Uniswap V3 QuoterV2 on Base.
42
+ * Called off-chain (via eth_call) to obtain the expected output amount
43
+ * before computing amountOutMinimum.
44
+ */
45
+ export const QUOTER_V2: Address = "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a";
@@ -1,17 +1,17 @@
1
- {
2
- "name": "sail-agent",
3
- "version": "0.1.0",
4
- "description": "Sail Protocol agent starter",
5
- "type": "module",
6
- "scripts": {
7
- "typecheck": "tsc -p tsconfig.json --noEmit"
8
- },
9
- "dependencies": {
10
- "@sail/sdk": "workspace:*",
11
- "viem": "^2.21.0"
12
- },
13
- "devDependencies": {
14
- "@types/node": "^22.0.0",
15
- "typescript": "^5.5.0"
16
- }
17
- }
1
+ {
2
+ "name": "sail-agent",
3
+ "version": "0.1.0",
4
+ "description": "Sail Protocol agent starter",
5
+ "type": "module",
6
+ "scripts": {
7
+ "typecheck": "tsc -p tsconfig.json --noEmit"
8
+ },
9
+ "dependencies": {
10
+ "@sail/sdk": "workspace:*",
11
+ "viem": "^2.21.0"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "^22.0.0",
15
+ "typescript": "^5.5.0"
16
+ }
17
+ }
@@ -1,37 +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.money/sailor/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
- };
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.money/sailor/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
+ };
@@ -1,24 +1,24 @@
1
- /** Reads RPC_URL and CHAIN_ID from environment (set via .sail/.env.local or GitHub Secrets). */
2
- export function getEnvConfig(): { rpcUrl: string; chainId: number } {
3
- const rpcUrl = process.env["RPC_URL"];
4
- if (!rpcUrl) {
5
- throw new Error(
6
- "RPC_URL is not set.\n" +
7
- "Add RPC_URL to .sail/.env.local or set it as an environment variable.",
8
- );
9
- }
10
-
11
- if (!process.env["CHAIN_ID"]) {
12
- throw new Error(
13
- "CHAIN_ID is not set.\n" +
14
- "Open this folder in your AI coding assistant and say 'start' — Stage 1 will ask\n" +
15
- "which chain you want and set CHAIN_ID in .sail/.env.local.",
16
- );
17
- }
18
- const chainId = Number(process.env["CHAIN_ID"]);
19
- if (Number.isNaN(chainId)) {
20
- throw new Error(`Invalid CHAIN_ID: ${process.env["CHAIN_ID"]}`);
21
- }
22
-
23
- return { rpcUrl, chainId };
24
- }
1
+ /** Reads RPC_URL and CHAIN_ID from environment (set via .sail/.env.local or GitHub Secrets). */
2
+ export function getEnvConfig(): { rpcUrl: string; chainId: number } {
3
+ const rpcUrl = process.env["RPC_URL"];
4
+ if (!rpcUrl) {
5
+ throw new Error(
6
+ "RPC_URL is not set.\n" +
7
+ "Add RPC_URL to .sail/.env.local or set it as an environment variable.",
8
+ );
9
+ }
10
+
11
+ if (!process.env["CHAIN_ID"]) {
12
+ throw new Error(
13
+ "CHAIN_ID is not set.\n" +
14
+ "Open this folder in your AI coding assistant and say 'start' — Stage 1 will ask\n" +
15
+ "which chain you want and set CHAIN_ID in .sail/.env.local.",
16
+ );
17
+ }
18
+ const chainId = Number(process.env["CHAIN_ID"]);
19
+ if (Number.isNaN(chainId)) {
20
+ throw new Error(`Invalid CHAIN_ID: ${process.env["CHAIN_ID"]}`);
21
+ }
22
+
23
+ return { rpcUrl, chainId };
24
+ }
@@ -1,22 +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
+ /**
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