@elizaos/plugin-wallet 2.0.0-beta.1

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 (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/auto-enable.ts +76 -0
  4. package/dist/LpManagementService-BWrQ5-cO.mjs +353 -0
  5. package/dist/MockLpService-D_Apn4Fd.mjs +99 -0
  6. package/dist/aerodrome-CfnESC32.mjs +890 -0
  7. package/dist/chunk-hT5z_Zn9.mjs +35 -0
  8. package/dist/index.d.mts +34727 -0
  9. package/dist/index.mjs +21590 -0
  10. package/dist/lib/server-wallet-trade.d.mts +34 -0
  11. package/dist/lib/server-wallet-trade.mjs +306 -0
  12. package/dist/meteora-BPX39hZo.mjs +22640 -0
  13. package/dist/orca-Bybp1HXO.mjs +249 -0
  14. package/dist/pancakeswp-CkEXlXti.mjs +604 -0
  15. package/dist/plugin-ZO_MTyd0.mjs +529 -0
  16. package/dist/raydium-rfaM9yEf.mjs +539 -0
  17. package/dist/sdk/index.d.mts +32492 -0
  18. package/dist/sdk/index.mjs +6415 -0
  19. package/dist/types-D5252NZk.mjs +487 -0
  20. package/dist/uniswap-CReXgXVN.mjs +573 -0
  21. package/dist/wallet-action.d.mts +6 -0
  22. package/dist/wallet-action.mjs +820 -0
  23. package/package.json +152 -0
  24. package/src/actions/failure-codes.ts +79 -0
  25. package/src/actions/index.ts +1 -0
  26. package/src/analytics/birdeye/actions/wallet-search-address.ts +9 -0
  27. package/src/analytics/birdeye/birdeye-task.ts +175 -0
  28. package/src/analytics/birdeye/birdeye.ts +813 -0
  29. package/src/analytics/birdeye/constants.ts +74 -0
  30. package/src/analytics/birdeye/providers/agent-portfolio-provider.ts +18 -0
  31. package/src/analytics/birdeye/providers/market.ts +227 -0
  32. package/src/analytics/birdeye/providers/portfolio-factory.test.ts +138 -0
  33. package/src/analytics/birdeye/providers/portfolio-factory.ts +252 -0
  34. package/src/analytics/birdeye/providers/trending.ts +365 -0
  35. package/src/analytics/birdeye/providers/wallet.ts +14 -0
  36. package/src/analytics/birdeye/search-category.test.ts +207 -0
  37. package/src/analytics/birdeye/search-category.ts +506 -0
  38. package/src/analytics/birdeye/service.ts +992 -0
  39. package/src/analytics/birdeye/tasks/birdeye.ts +232 -0
  40. package/src/analytics/birdeye/types/api/common.ts +305 -0
  41. package/src/analytics/birdeye/types/api/defi.ts +220 -0
  42. package/src/analytics/birdeye/types/api/pair.ts +200 -0
  43. package/src/analytics/birdeye/types/api/search.ts +86 -0
  44. package/src/analytics/birdeye/types/api/token.ts +635 -0
  45. package/src/analytics/birdeye/types/api/trader.ts +76 -0
  46. package/src/analytics/birdeye/types/api/wallet.ts +181 -0
  47. package/src/analytics/birdeye/types/shared.ts +106 -0
  48. package/src/analytics/birdeye/utils.ts +700 -0
  49. package/src/analytics/dexscreener/errors.ts +28 -0
  50. package/src/analytics/dexscreener/index.ts +3 -0
  51. package/src/analytics/dexscreener/search-category.test.ts +49 -0
  52. package/src/analytics/dexscreener/search-category.ts +42 -0
  53. package/src/analytics/dexscreener/service.ts +595 -0
  54. package/src/analytics/dexscreener/types.ts +128 -0
  55. package/src/analytics/lpinfo/index.d.ts +7 -0
  56. package/src/analytics/lpinfo/index.ts +52 -0
  57. package/src/analytics/lpinfo/kamino/README.md +102 -0
  58. package/src/analytics/lpinfo/kamino/index.ts +24 -0
  59. package/src/analytics/lpinfo/kamino/providers/kaminoLiquidityProvider.ts +422 -0
  60. package/src/analytics/lpinfo/kamino/providers/kaminoPoolProvider.ts +365 -0
  61. package/src/analytics/lpinfo/kamino/providers/kaminoProvider.ts +496 -0
  62. package/src/analytics/lpinfo/kamino/services/kaminoLiquidityService.ts +1123 -0
  63. package/src/analytics/lpinfo/kamino/services/kaminoService.ts +758 -0
  64. package/src/analytics/lpinfo/steer/README.md +169 -0
  65. package/src/analytics/lpinfo/steer/index.ts +23 -0
  66. package/src/analytics/lpinfo/steer/providers/steerLiquidityProvider.ts +544 -0
  67. package/src/analytics/lpinfo/steer/services/steerLiquidityService.ts +1690 -0
  68. package/src/analytics/lpinfo/steer/steer-display-types.ts +99 -0
  69. package/src/analytics/news/index.ts +52 -0
  70. package/src/analytics/news/interfaces/types.ts +222 -0
  71. package/src/analytics/news/providers/defiNewsProvider.ts +734 -0
  72. package/src/analytics/news/services/newsDataService.ts +332 -0
  73. package/src/analytics/news/utils/formatters.ts +151 -0
  74. package/src/analytics/token-info/action.ts +240 -0
  75. package/src/analytics/token-info/index.ts +3 -0
  76. package/src/analytics/token-info/params.ts +215 -0
  77. package/src/analytics/token-info/providers.ts +681 -0
  78. package/src/analytics/token-info/service.ts +168 -0
  79. package/src/analytics/token-info/types.ts +74 -0
  80. package/src/audit/audit-log.ts +45 -0
  81. package/src/browser-shim/build-shim.ts +123 -0
  82. package/src/browser-shim/index.ts +5 -0
  83. package/src/browser-shim/shim.template.js +563 -0
  84. package/src/chains/evm/.github/workflows/npm-deploy.yml +112 -0
  85. package/src/chains/evm/LICENSE +21 -0
  86. package/src/chains/evm/README.md +106 -0
  87. package/src/chains/evm/actions/helpers.ts +147 -0
  88. package/src/chains/evm/actions/swap.ts +839 -0
  89. package/src/chains/evm/actions/transfer.ts +254 -0
  90. package/src/chains/evm/biome.json +61 -0
  91. package/src/chains/evm/bridge-router.ts +660 -0
  92. package/src/chains/evm/build.ts +89 -0
  93. package/src/chains/evm/chain-handler.ts +416 -0
  94. package/src/chains/evm/constants.ts +23 -0
  95. package/src/chains/evm/contracts/artifacts/OZGovernor.json +1707 -0
  96. package/src/chains/evm/contracts/artifacts/TimelockController.json +1007 -0
  97. package/src/chains/evm/contracts/artifacts/VoteToken.json +895 -0
  98. package/src/chains/evm/dex/aerodrome/index.ts +34 -0
  99. package/src/chains/evm/dex/aerodrome/services/AerodromeLpService.ts +558 -0
  100. package/src/chains/evm/dex/aerodrome/types.ts +318 -0
  101. package/src/chains/evm/dex/pancakeswp/index.ts +35 -0
  102. package/src/chains/evm/dex/pancakeswp/services/PancakeSwapV3LpService.ts +743 -0
  103. package/src/chains/evm/dex/pancakeswp/types.ts +65 -0
  104. package/src/chains/evm/dex/uniswap/index.ts +35 -0
  105. package/src/chains/evm/dex/uniswap/services/UniswapV3LpService.ts +759 -0
  106. package/src/chains/evm/dex/uniswap/types.ts +390 -0
  107. package/src/chains/evm/generated/specs/spec-helpers.ts +73 -0
  108. package/src/chains/evm/generated/specs/specs.ts +151 -0
  109. package/src/chains/evm/gov-router.ts +250 -0
  110. package/src/chains/evm/index.browser.ts +16 -0
  111. package/src/chains/evm/index.ts +31 -0
  112. package/src/chains/evm/prompts.ts +193 -0
  113. package/src/chains/evm/providers/get-balance.ts +123 -0
  114. package/src/chains/evm/providers/wallet.ts +715 -0
  115. package/src/chains/evm/routes/sign.ts +333 -0
  116. package/src/chains/evm/rpc-providers.ts +410 -0
  117. package/src/chains/evm/service.ts +140 -0
  118. package/src/chains/evm/templates/index.ts +10 -0
  119. package/src/chains/evm/types/index.ts +432 -0
  120. package/src/chains/evm/vitest.config.ts +18 -0
  121. package/src/chains/registry.ts +668 -0
  122. package/src/chains/solana/README.md +367 -0
  123. package/src/chains/wallet-action.ts +533 -0
  124. package/src/chains/wallet-router.test.ts +296 -0
  125. package/src/contracts.ts +65 -0
  126. package/src/core-augmentation.ts +10 -0
  127. package/src/index.ts +71 -0
  128. package/src/lib/server-wallet-trade.ts +192 -0
  129. package/src/lib/wallet-export-guard.ts +330 -0
  130. package/src/lp/actions/liquidity.ts +827 -0
  131. package/src/lp/e2e/real-token-tests.ts +428 -0
  132. package/src/lp/e2e/scenarios.ts +470 -0
  133. package/src/lp/e2e/test-utils.ts +145 -0
  134. package/src/lp/lp-manager-entry.ts +303 -0
  135. package/src/lp/services/ConcentratedLiquidityService.ts +120 -0
  136. package/src/lp/services/DexInteractionService.ts +226 -0
  137. package/src/lp/services/LpManagementService.test.ts +148 -0
  138. package/src/lp/services/LpManagementService.ts +632 -0
  139. package/src/lp/services/UserLpProfileService.ts +163 -0
  140. package/src/lp/services/VaultService.ts +153 -0
  141. package/src/lp/services/YieldOptimizationService.ts +344 -0
  142. package/src/lp/services/__tests__/MockLpService.ts +146 -0
  143. package/src/lp/tasks/LpAutoRebalanceTask.ts +117 -0
  144. package/src/lp/tasks/__tests__/LpAutoRebalanceTask.test.ts +370 -0
  145. package/src/lp/types.ts +582 -0
  146. package/src/lp/utils/solanaClient.ts +143 -0
  147. package/src/plugin.ts +125 -0
  148. package/src/policy/policy.ts +19 -0
  149. package/src/providers/canonical-provider.ts +27 -0
  150. package/src/providers/unified-wallet-provider.ts +79 -0
  151. package/src/register-routes.ts +11 -0
  152. package/src/routes/plugin.ts +47 -0
  153. package/src/routes/wallet-market-overview-route.ts +869 -0
  154. package/src/sdk/abi.ts +258 -0
  155. package/src/sdk/bridge/abis.ts +126 -0
  156. package/src/sdk/bridge/client.ts +518 -0
  157. package/src/sdk/bridge/index.ts +56 -0
  158. package/src/sdk/bridge/solana.ts +604 -0
  159. package/src/sdk/bridge/types.ts +202 -0
  160. package/src/sdk/convenience.ts +347 -0
  161. package/src/sdk/escrow/MutualStakeEscrow.ts +480 -0
  162. package/src/sdk/escrow/types.ts +64 -0
  163. package/src/sdk/escrow/verifiers.ts +73 -0
  164. package/src/sdk/identity/erc8004.ts +692 -0
  165. package/src/sdk/identity/reputation.ts +449 -0
  166. package/src/sdk/identity/uaid.ts +497 -0
  167. package/src/sdk/identity/validation.ts +372 -0
  168. package/src/sdk/index.ts +763 -0
  169. package/src/sdk/policy/SpendingPolicy.ts +260 -0
  170. package/src/sdk/policy/UptoBillingPolicy.ts +320 -0
  171. package/src/sdk/router/PaymentRouter.ts +215 -0
  172. package/src/sdk/router/index.ts +8 -0
  173. package/src/sdk/swap/SwapModule.ts +310 -0
  174. package/src/sdk/swap/abi.ts +117 -0
  175. package/src/sdk/swap/index.ts +34 -0
  176. package/src/sdk/swap/types.ts +135 -0
  177. package/src/sdk/tokens/decimals.ts +140 -0
  178. package/src/sdk/tokens/registry.ts +911 -0
  179. package/src/sdk/tokens/solana.ts +419 -0
  180. package/src/sdk/tokens/transfers.ts +327 -0
  181. package/src/sdk/types.ts +158 -0
  182. package/src/sdk/wallet-core.ts +115 -0
  183. package/src/sdk/x402/budget.ts +168 -0
  184. package/src/sdk/x402/chains/abstract/index.ts +280 -0
  185. package/src/sdk/x402/client.ts +320 -0
  186. package/src/sdk/x402/index.ts +46 -0
  187. package/src/sdk/x402/middleware.ts +92 -0
  188. package/src/sdk/x402/multi-asset.ts +144 -0
  189. package/src/sdk/x402/types.ts +156 -0
  190. package/src/services/wallet-backend-service.ts +328 -0
  191. package/src/types/wallet-router.ts +227 -0
  192. package/src/utils/intent-trajectory.ts +106 -0
  193. package/src/wallet/backend.ts +62 -0
  194. package/src/wallet/errors.ts +49 -0
  195. package/src/wallet/index.ts +27 -0
  196. package/src/wallet/local-eoa-backend.ts +201 -0
  197. package/src/wallet/pending.ts +60 -0
  198. package/src/wallet/select-backend.ts +47 -0
  199. package/src/wallet/steward-backend.ts +161 -0
  200. package/src/wallet-action.ts +1 -0
@@ -0,0 +1,215 @@
1
+ /**
2
+ * PaymentRouter — Three-rail payment architecture.
3
+ *
4
+ * Routes payments across three rails:
5
+ * 1. **x402** (crypto-native) — on-chain USDC payments via the x402 protocol. Optimal for
6
+ * micropayments, autonomous agents, and scenarios requiring on-chain audit trails.
7
+ * 2. **MPP** (Managed Payment Protocol / Stripe) — fiat-based payments with dispute resolution.
8
+ * Optimal for larger amounts, supervised agents, and high-frequency session batching.
9
+ * 3. **Google AP2** (Agent Payment Protocol) — Google's managed agent payment rail combining
10
+ * identity verification and payment in a single flow. Roadmap; activates when Google
11
+ * publishes the full AP2 spec. Settlement on Base.
12
+ *
13
+ * Routing criteria:
14
+ * - Transaction amount (micropayments → x402, high-frequency → MPP)
15
+ * - Session context (ongoing session → MPP for batching efficiency)
16
+ * - Agent autonomy level (autonomous → x402, supervised → MPP)
17
+ * - Chain/ecosystem preference (Google ecosystem → AP2, Solana → x402-solana)
18
+ * - Rail availability (live vs. roadmap)
19
+ *
20
+ * @module router/PaymentRouter
21
+ */
22
+
23
+ // ─── Types ────────────────────────────────────────────────────────────────────
24
+
25
+ export type PaymentRail = "x402" | "mpp" | "x402-solana" | "google-ap2";
26
+
27
+ export type RailStatus = "live" | "roadmap" | "disabled";
28
+
29
+ export interface RailConfig {
30
+ rail: PaymentRail;
31
+ status: RailStatus;
32
+ /** Maximum per-transaction amount in USDC micro-units (6 decimals) */
33
+ maxAmount?: number;
34
+ /** Minimum per-transaction amount in USDC micro-units */
35
+ minAmount?: number;
36
+ }
37
+
38
+ export interface PaymentContext {
39
+ /** Amount in USDC micro-units (6 decimals). e.g. 1_000_000 = $1.00 */
40
+ amount: number;
41
+ /** Recipient address or merchant domain */
42
+ recipient: string;
43
+ /** Whether this payment is part of an active session with repeated transactions */
44
+ isSessionContext?: boolean;
45
+ /** Number of transactions in the current session (if applicable) */
46
+ sessionTxCount?: number;
47
+ /** Whether the agent is operating autonomously (no human in loop) */
48
+ autonomous?: boolean;
49
+ /** Preferred chain, if any */
50
+ preferredChain?: "base" | "solana" | "google-ap2";
51
+ }
52
+
53
+ export interface RoutingDecision {
54
+ rail: PaymentRail;
55
+ reason: string;
56
+ /** Estimated cost overhead (basis points) */
57
+ estimatedOverheadBps: number;
58
+ /** Whether this rail is currently live */
59
+ isLive: boolean;
60
+ }
61
+
62
+ // ─── Default Rail Configuration ───────────────────────────────────────────────
63
+
64
+ const DEFAULT_RAILS: RailConfig[] = [
65
+ {
66
+ rail: "x402",
67
+ status: "live",
68
+ minAmount: 1_000, // $0.001 minimum
69
+ maxAmount: 100_000_000, // $100 max per tx (soft limit)
70
+ },
71
+ {
72
+ rail: "mpp",
73
+ status: "live",
74
+ minAmount: 100_000, // $0.10 minimum (Stripe floor)
75
+ maxAmount: 10_000_000_000, // $10,000 max
76
+ },
77
+ {
78
+ rail: "x402-solana",
79
+ status: "roadmap",
80
+ },
81
+ {
82
+ rail: "google-ap2",
83
+ status: "roadmap",
84
+ minAmount: 1_000, // $0.001 minimum (aligned with x402)
85
+ maxAmount: 50_000_000, // $50 max per tx (conservative until spec finalized)
86
+ },
87
+ ];
88
+
89
+ // ─── Router ───────────────────────────────────────────────────────────────────
90
+
91
+ export class PaymentRouter {
92
+ private rails: RailConfig[];
93
+ /** Amount threshold (micro-units) below which x402 is preferred. Default: $1.00 */
94
+ private microPaymentThreshold: number;
95
+ /** Session tx count above which MPP is preferred for batching. Default: 5 */
96
+ private highFrequencyThreshold: number;
97
+
98
+ constructor(options?: {
99
+ rails?: RailConfig[];
100
+ microPaymentThreshold?: number;
101
+ highFrequencyThreshold?: number;
102
+ }) {
103
+ this.rails = options?.rails ?? DEFAULT_RAILS;
104
+ this.microPaymentThreshold = options?.microPaymentThreshold ?? 1_000_000; // $1.00
105
+ this.highFrequencyThreshold = options?.highFrequencyThreshold ?? 5;
106
+ }
107
+
108
+ /**
109
+ * Select the optimal payment rail for a given context.
110
+ */
111
+ route(context: PaymentContext): RoutingDecision {
112
+ const {
113
+ amount,
114
+ isSessionContext,
115
+ sessionTxCount,
116
+ autonomous,
117
+ preferredChain,
118
+ } = context;
119
+
120
+ // If Google AP2 rail is live and context indicates Google ecosystem preference
121
+ // Google AP2 (Agent Payment Protocol) — Google's native agent payment rail
122
+ // Currently roadmap; will activate when Google publishes the full AP2 spec
123
+ const googleRail = this.getRail("google-ap2");
124
+ if (googleRail?.status === "live" && preferredChain === "base") {
125
+ // Google AP2 routes through Base for on-chain settlement
126
+ // When live, AP2 provides Google-managed identity + payment in a single flow
127
+ return {
128
+ rail: "google-ap2",
129
+ reason: "Google AP2 rail — managed identity + payment bundle",
130
+ estimatedOverheadBps: 100, // Estimated Google platform fee
131
+ isLive: true,
132
+ };
133
+ }
134
+
135
+ // If Solana is explicitly preferred and x402-solana is available
136
+ if (preferredChain === "solana") {
137
+ const solanaRail = this.getRail("x402-solana");
138
+ if (solanaRail?.status === "live") {
139
+ return {
140
+ rail: "x402-solana",
141
+ reason: "Preferred chain: Solana x402",
142
+ estimatedOverheadBps: 0,
143
+ isLive: true,
144
+ };
145
+ }
146
+ // Solana not yet live — fall through to other rails
147
+ }
148
+
149
+ // Rule 1: High-frequency session context → MPP (batching efficiency)
150
+ if (
151
+ isSessionContext &&
152
+ (sessionTxCount ?? 0) >= this.highFrequencyThreshold
153
+ ) {
154
+ const mppRail = this.getRail("mpp");
155
+ if (mppRail?.status === "live") {
156
+ return {
157
+ rail: "mpp",
158
+ reason: `High-frequency session (${sessionTxCount} txns) — MPP batching is more efficient`,
159
+ estimatedOverheadBps: 290, // Stripe's ~2.9%
160
+ isLive: true,
161
+ };
162
+ }
163
+ }
164
+
165
+ // Rule 2: Micropayments + autonomous → x402 (lower overhead, no session needed)
166
+ if (amount < this.microPaymentThreshold && autonomous !== false) {
167
+ const x402Rail = this.getRail("x402");
168
+ if (x402Rail?.status === "live") {
169
+ return {
170
+ rail: "x402",
171
+ reason: `Micropayment ($${(amount / 1_000_000).toFixed(2)}) + autonomous — x402 optimal`,
172
+ estimatedOverheadBps: 0, // x402 has no protocol fee
173
+ isLive: true,
174
+ };
175
+ }
176
+ }
177
+
178
+ // Rule 3: Larger amounts or supervised → MPP (better dispute resolution, fiat rails)
179
+ const mppRail = this.getRail("mpp");
180
+ if (mppRail?.status === "live" && amount >= (mppRail.minAmount ?? 0)) {
181
+ return {
182
+ rail: "mpp",
183
+ reason: `Amount $${(amount / 1_000_000).toFixed(2)} — MPP provides fiat rails and dispute resolution`,
184
+ estimatedOverheadBps: 290,
185
+ isLive: true,
186
+ };
187
+ }
188
+
189
+ // Default: x402 on Base
190
+ return {
191
+ rail: "x402",
192
+ reason: "Default rail — Base x402",
193
+ estimatedOverheadBps: 0,
194
+ isLive: this.getRail("x402")?.status === "live",
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Get all available (live) rails.
200
+ */
201
+ getLiveRails(): RailConfig[] {
202
+ return this.rails.filter((r) => r.status === "live");
203
+ }
204
+
205
+ /**
206
+ * Check if a specific rail is available.
207
+ */
208
+ isRailLive(rail: PaymentRail): boolean {
209
+ return this.getRail(rail)?.status === "live";
210
+ }
211
+
212
+ private getRail(rail: PaymentRail): RailConfig | undefined {
213
+ return this.rails.find((r) => r.rail === rail);
214
+ }
215
+ }
@@ -0,0 +1,8 @@
1
+ export type {
2
+ PaymentContext,
3
+ PaymentRail,
4
+ RailConfig,
5
+ RailStatus,
6
+ RoutingDecision,
7
+ } from "./PaymentRouter.js";
8
+ export { PaymentRouter } from "./PaymentRouter.js";
@@ -0,0 +1,310 @@
1
+ /**
2
+ * @module swap/SwapModule
3
+ * SwapModule — multi-chain Uniswap V3 token swap aggregator with 0.875% protocol fee.
4
+ *
5
+ * Supports Base, Arbitrum, Optimism, and Polygon. Automatically selects the best
6
+ * Uniswap V3 fee tier (0.01%, 0.05%, 0.3%, 1%) by quoting all tiers and choosing
7
+ * the highest output. Applies slippage protection and an optional protocol fee.
8
+ *
9
+ * Usage: construct with `chain` param or use `attachSwap(wallet, { chain: 'arbitrum' })`.
10
+ */
11
+ // SwapModule — Uniswap V3 token swap aggregator with 0.875% protocol fee
12
+ import {
13
+ type Address,
14
+ encodeFunctionData,
15
+ type Hash,
16
+ type PublicClient,
17
+ type WalletClient,
18
+ } from "viem";
19
+ import { ERC20Abi, UniswapV3QuoterV2Abi, UniswapV3RouterAbi } from "./abi.js";
20
+ import {
21
+ DEFAULT_SLIPPAGE_BPS,
22
+ PROTOCOL_FEE_BPS,
23
+ PROTOCOL_FEE_COLLECTOR,
24
+ type SwapChain,
25
+ type SwapModuleConfig,
26
+ type SwapOptions,
27
+ type SwapQuote,
28
+ type SwapResult,
29
+ UNISWAP_V3_ADDRESSES,
30
+ type UniswapFeeTier,
31
+ } from "./types.js";
32
+
33
+ const FEE_TIERS: UniswapFeeTier[] = [100, 500, 3000, 10000];
34
+
35
+ /** Calculate protocol fee: floor(amount * feeBps / 100_000) */
36
+ export function calcProtocolFee(amount: bigint, feeBps: number): bigint {
37
+ return (amount * BigInt(feeBps)) / 100000n;
38
+ }
39
+
40
+ /** Apply slippage: floor(amount * (10_000 - slippageBps) / 10_000) */
41
+ export function applySlippage(amount: bigint, slippageBps: number): bigint {
42
+ return (amount * BigInt(10000 - slippageBps)) / 10000n;
43
+ }
44
+
45
+ /** Encode deadline: block.timestamp + deadlineSecs */
46
+ export function calcDeadline(deadlineSecs: number): bigint {
47
+ return BigInt(Math.floor(Date.now() / 1000) + deadlineSecs);
48
+ }
49
+
50
+ export class SwapModule {
51
+ private readonly publicClient: PublicClient;
52
+ private readonly walletClient: WalletClient;
53
+ private readonly accountAddress: Address;
54
+ private readonly config: SwapModuleConfig;
55
+
56
+ constructor(
57
+ publicClient: PublicClient,
58
+ walletClient: WalletClient,
59
+ accountAddress: Address,
60
+ config?: Partial<SwapModuleConfig> & { chain?: SwapChain },
61
+ ) {
62
+ this.publicClient = publicClient;
63
+ this.walletClient = walletClient;
64
+ this.accountAddress = accountAddress;
65
+ const chain: SwapChain = config?.chain ?? "base";
66
+ const chainAddresses = UNISWAP_V3_ADDRESSES[chain];
67
+ this.config = {
68
+ routerAddress: chainAddresses.ROUTER,
69
+ quoterAddress: chainAddresses.QUOTER_V2,
70
+ feeBps: PROTOCOL_FEE_BPS,
71
+ feeWallet: PROTOCOL_FEE_COLLECTOR,
72
+ chain,
73
+ ...config,
74
+ // Re-apply chain-derived addresses if chain was specified but addresses were not
75
+ ...(config?.routerAddress
76
+ ? {}
77
+ : { routerAddress: chainAddresses.ROUTER }),
78
+ ...(config?.quoterAddress
79
+ ? {}
80
+ : { quoterAddress: chainAddresses.QUOTER_V2 }),
81
+ };
82
+ }
83
+
84
+ async getQuote(
85
+ tokenIn: Address,
86
+ tokenOut: Address,
87
+ amountIn: bigint,
88
+ options: Pick<SwapOptions, "slippageBps" | "feeTiers"> = {},
89
+ ): Promise<SwapQuote> {
90
+ const slippageBps = options.slippageBps ?? DEFAULT_SLIPPAGE_BPS;
91
+ const tiersToTry = options.feeTiers ?? FEE_TIERS;
92
+ const feeAmount = calcProtocolFee(amountIn, this.config.feeBps);
93
+ const amountInNet = amountIn - feeAmount;
94
+ if (amountInNet <= 0n) {
95
+ throw new Error(
96
+ `SwapModule: amountIn (${amountIn}) is too small — fee (${feeAmount}) exceeds input`,
97
+ );
98
+ }
99
+
100
+ let bestQuote: {
101
+ amountOut: bigint;
102
+ gasEstimate: bigint;
103
+ feeTier: UniswapFeeTier;
104
+ } | null = null;
105
+ for (const fee of tiersToTry) {
106
+ try {
107
+ const result = await this.publicClient.readContract({
108
+ address: this.config.quoterAddress,
109
+ abi: UniswapV3QuoterV2Abi,
110
+ functionName: "quoteExactInputSingle",
111
+ args: [
112
+ {
113
+ tokenIn,
114
+ tokenOut,
115
+ amount: amountInNet,
116
+ fee,
117
+ sqrtPriceLimitX96: 0n,
118
+ },
119
+ ],
120
+ });
121
+ const [amountOut, , , gasEstimate] = result as [
122
+ bigint,
123
+ bigint,
124
+ number,
125
+ bigint,
126
+ ];
127
+ if (!bestQuote || amountOut > bestQuote.amountOut) {
128
+ bestQuote = {
129
+ amountOut,
130
+ gasEstimate,
131
+ feeTier: fee as UniswapFeeTier,
132
+ };
133
+ }
134
+ } catch {
135
+ // Pool doesn't exist for this fee tier
136
+ }
137
+ }
138
+
139
+ if (!bestQuote) {
140
+ throw new Error(
141
+ `SwapModule: No Uniswap V3 pool found for ${tokenIn} → ${tokenOut} on ${this.config.chain}.`,
142
+ );
143
+ }
144
+
145
+ const amountOutMinimum = applySlippage(bestQuote.amountOut, slippageBps);
146
+ const effectiveRate =
147
+ amountInNet > 0n ? Number(bestQuote.amountOut) / Number(amountInNet) : 0;
148
+
149
+ return {
150
+ tokenIn,
151
+ tokenOut,
152
+ amountInRaw: amountIn,
153
+ amountInNet,
154
+ feeAmount,
155
+ amountOut: bestQuote.amountOut,
156
+ amountOutMinimum,
157
+ poolFeeTier: bestQuote.feeTier,
158
+ effectiveRate,
159
+ gasEstimate: bestQuote.gasEstimate,
160
+ };
161
+ }
162
+
163
+ async ensureApproval(
164
+ token: Address,
165
+ spender: Address,
166
+ amount: bigint,
167
+ ): Promise<Hash | undefined> {
168
+ const currentAllowance = (await this.publicClient.readContract({
169
+ address: token,
170
+ abi: ERC20Abi,
171
+ functionName: "allowance",
172
+ args: [this.accountAddress, spender],
173
+ })) as bigint;
174
+
175
+ if (currentAllowance >= amount) return undefined;
176
+
177
+ const MAX_UINT256 =
178
+ 115792089237316195423570985008687907853269984665640564039457584007913129639935n;
179
+ const account = this.walletClient.account;
180
+ if (!account) throw new Error("SwapModule: walletClient has no account");
181
+
182
+ const approveData = encodeFunctionData({
183
+ abi: ERC20Abi,
184
+ functionName: "approve",
185
+ args: [spender, MAX_UINT256],
186
+ });
187
+
188
+ return this.walletClient.sendTransaction({
189
+ account,
190
+ to: token,
191
+ data: approveData,
192
+ chain: this.walletClient.chain ?? null,
193
+ });
194
+ }
195
+
196
+ private async transferFee(
197
+ token: Address,
198
+ feeAmount: bigint,
199
+ feeWallet: Address,
200
+ ): Promise<Hash> {
201
+ const account = this.walletClient.account;
202
+ if (!account) throw new Error("SwapModule: walletClient has no account");
203
+
204
+ const transferData = encodeFunctionData({
205
+ abi: ERC20Abi,
206
+ functionName: "transfer",
207
+ args: [feeWallet, feeAmount],
208
+ });
209
+
210
+ return this.walletClient.sendTransaction({
211
+ account,
212
+ to: token,
213
+ data: transferData,
214
+ chain: this.walletClient.chain ?? null,
215
+ });
216
+ }
217
+
218
+ async swap(
219
+ tokenIn: Address,
220
+ tokenOut: Address,
221
+ amountIn: bigint,
222
+ options: SwapOptions = {},
223
+ ): Promise<SwapResult> {
224
+ const slippageBps = options.slippageBps ?? DEFAULT_SLIPPAGE_BPS;
225
+ const deadlineSecs = options.deadlineSecs ?? 300;
226
+ const feeWallet = options.feeWallet ?? this.config.feeWallet;
227
+
228
+ const quote = await this.getQuote(tokenIn, tokenOut, amountIn, {
229
+ slippageBps,
230
+ feeTiers: options.feeTiers,
231
+ });
232
+
233
+ if (quote.gasEstimate > 2000000n) {
234
+ console.warn(
235
+ `SwapModule: High gas estimate (${quote.gasEstimate}). Pool may be illiquid.`,
236
+ );
237
+ }
238
+
239
+ const approvalTxHash = await this.ensureApproval(
240
+ tokenIn,
241
+ this.config.routerAddress,
242
+ amountIn,
243
+ );
244
+ const approvalRequired = approvalTxHash !== undefined;
245
+
246
+ let feeTxHash: Hash | undefined;
247
+ if (quote.feeAmount > 0n && feeWallet !== this.accountAddress) {
248
+ feeTxHash = await this.transferFee(tokenIn, quote.feeAmount, feeWallet);
249
+ }
250
+
251
+ const account = this.walletClient.account;
252
+ if (!account) throw new Error("SwapModule: walletClient has no account");
253
+
254
+ const _deadline = calcDeadline(deadlineSecs); // reserved for future router versions
255
+ const swapData = encodeFunctionData({
256
+ abi: UniswapV3RouterAbi,
257
+ functionName: "exactInputSingle",
258
+ args: [
259
+ {
260
+ tokenIn,
261
+ tokenOut,
262
+ fee: quote.poolFeeTier,
263
+ recipient: this.accountAddress,
264
+ amountIn: quote.amountInNet,
265
+ amountOutMinimum: quote.amountOutMinimum,
266
+ sqrtPriceLimitX96: 0n,
267
+ },
268
+ ],
269
+ });
270
+
271
+ const txHash = await this.walletClient.sendTransaction({
272
+ account,
273
+ to: this.config.routerAddress,
274
+ data: swapData,
275
+ chain: this.walletClient.chain ?? null,
276
+ });
277
+
278
+ return { txHash, feeTxHash, quote, approvalRequired, approvalTxHash };
279
+ }
280
+
281
+ getConfig(): SwapModuleConfig {
282
+ return { ...this.config };
283
+ }
284
+
285
+ setFeeWallet(address: Address): void {
286
+ this.config.feeWallet = address;
287
+ }
288
+ }
289
+
290
+ export function attachSwap(
291
+ wallet: {
292
+ address: Address;
293
+ publicClient: PublicClient;
294
+ walletClient: WalletClient;
295
+ },
296
+ config?: Partial<SwapModuleConfig> & { chain?: SwapChain },
297
+ ) {
298
+ const swapModule = new SwapModule(
299
+ wallet.publicClient,
300
+ wallet.walletClient,
301
+ wallet.address,
302
+ config,
303
+ );
304
+ return {
305
+ ...wallet,
306
+ swapModule,
307
+ swap: swapModule.swap.bind(swapModule),
308
+ getQuote: swapModule.getQuote.bind(swapModule),
309
+ };
310
+ }
@@ -0,0 +1,117 @@
1
+ // [MAX-ADDED] ABIs for Uniswap V3 SwapRouter02 and QuoterV2 on Base Mainnet
2
+ /** Uniswap V3 SwapRouter02 ABI — exactInputSingle */
3
+ export const UniswapV3RouterAbi = [
4
+ {
5
+ name: "exactInputSingle",
6
+ type: "function",
7
+ stateMutability: "payable",
8
+ inputs: [
9
+ {
10
+ name: "params",
11
+ type: "tuple",
12
+ components: [
13
+ { name: "tokenIn", type: "address" },
14
+ { name: "tokenOut", type: "address" },
15
+ { name: "fee", type: "uint24" },
16
+ { name: "recipient", type: "address" },
17
+ { name: "amountIn", type: "uint256" },
18
+ { name: "amountOutMinimum", type: "uint256" },
19
+ { name: "sqrtPriceLimitX96", type: "uint160" },
20
+ ],
21
+ },
22
+ ],
23
+ outputs: [{ name: "amountOut", type: "uint256" }],
24
+ },
25
+ {
26
+ name: "exactInput",
27
+ type: "function",
28
+ stateMutability: "payable",
29
+ inputs: [
30
+ {
31
+ name: "params",
32
+ type: "tuple",
33
+ components: [
34
+ { name: "path", type: "bytes" },
35
+ { name: "recipient", type: "address" },
36
+ { name: "amountIn", type: "uint256" },
37
+ { name: "amountOutMinimum", type: "uint256" },
38
+ ],
39
+ },
40
+ ],
41
+ outputs: [{ name: "amountOut", type: "uint256" }],
42
+ },
43
+ ];
44
+ /** Uniswap V3 QuoterV2 ABI — quoteExactInputSingle */
45
+ export const UniswapV3QuoterV2Abi = [
46
+ {
47
+ name: "quoteExactInputSingle",
48
+ type: "function",
49
+ stateMutability: "nonpayable",
50
+ inputs: [
51
+ {
52
+ name: "params",
53
+ type: "tuple",
54
+ components: [
55
+ { name: "tokenIn", type: "address" },
56
+ { name: "tokenOut", type: "address" },
57
+ { name: "amount", type: "uint256" },
58
+ { name: "fee", type: "uint24" },
59
+ { name: "sqrtPriceLimitX96", type: "uint160" },
60
+ ],
61
+ },
62
+ ],
63
+ outputs: [
64
+ { name: "amountOut", type: "uint256" },
65
+ { name: "sqrtPriceX96After", type: "uint160" },
66
+ { name: "initializedTicksCrossed", type: "uint32" },
67
+ { name: "gasEstimate", type: "uint256" },
68
+ ],
69
+ },
70
+ ];
71
+ /** Minimal ERC-20 ABI for allowance, approve, transferFrom */
72
+ export const ERC20Abi = [
73
+ {
74
+ name: "allowance",
75
+ type: "function",
76
+ stateMutability: "view",
77
+ inputs: [
78
+ { name: "owner", type: "address" },
79
+ { name: "spender", type: "address" },
80
+ ],
81
+ outputs: [{ name: "", type: "uint256" }],
82
+ },
83
+ {
84
+ name: "approve",
85
+ type: "function",
86
+ stateMutability: "nonpayable",
87
+ inputs: [
88
+ { name: "spender", type: "address" },
89
+ { name: "amount", type: "uint256" },
90
+ ],
91
+ outputs: [{ name: "", type: "bool" }],
92
+ },
93
+ {
94
+ name: "transfer",
95
+ type: "function",
96
+ stateMutability: "nonpayable",
97
+ inputs: [
98
+ { name: "to", type: "address" },
99
+ { name: "amount", type: "uint256" },
100
+ ],
101
+ outputs: [{ name: "", type: "bool" }],
102
+ },
103
+ {
104
+ name: "balanceOf",
105
+ type: "function",
106
+ stateMutability: "view",
107
+ inputs: [{ name: "account", type: "address" }],
108
+ outputs: [{ name: "", type: "uint256" }],
109
+ },
110
+ {
111
+ name: "decimals",
112
+ type: "function",
113
+ stateMutability: "view",
114
+ inputs: [],
115
+ outputs: [{ name: "", type: "uint8" }],
116
+ },
117
+ ];
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @module swap
3
+ * Re-exports for the SwapModule: multi-chain Uniswap V3 swaps.
4
+ *
5
+ * Supported chains: base, arbitrum, optimism, polygon.
6
+ * Use SwapModule directly or attachSwap(wallet, { chain }) for a wallet-bound instance.
7
+ */
8
+
9
+ export { ERC20Abi, UniswapV3QuoterV2Abi, UniswapV3RouterAbi } from "./abi.js";
10
+ export {
11
+ applySlippage,
12
+ attachSwap,
13
+ calcDeadline,
14
+ calcProtocolFee,
15
+ SwapModule,
16
+ } from "./SwapModule.js";
17
+ export type {
18
+ SwapChain,
19
+ SwapModuleConfig,
20
+ SwapOptions,
21
+ SwapQuote,
22
+ SwapResult,
23
+ UniswapFeeTier,
24
+ } from "./types.js";
25
+ export {
26
+ ARBITRUM_TOKENS,
27
+ BASE_TOKENS,
28
+ DEFAULT_SLIPPAGE_BPS,
29
+ OPTIMISM_TOKENS,
30
+ POLYGON_TOKENS,
31
+ PROTOCOL_FEE_BPS,
32
+ PROTOCOL_FEE_COLLECTOR,
33
+ UNISWAP_V3_ADDRESSES,
34
+ } from "./types.js";