@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,715 @@
1
+ import * as path from "node:path";
2
+ import {
3
+ type IAgentRuntime,
4
+ logger,
5
+ type Memory,
6
+ type Provider,
7
+ type ProviderResult,
8
+ ServiceType,
9
+ type State,
10
+ } from "@elizaos/core";
11
+ import type {
12
+ Account,
13
+ Address,
14
+ Chain,
15
+ HttpTransport,
16
+ PrivateKeyAccount,
17
+ PublicClient,
18
+ TestClient,
19
+ WalletClient,
20
+ } from "viem";
21
+ import {
22
+ createPublicClient,
23
+ createTestClient,
24
+ createWalletClient,
25
+ formatUnits,
26
+ http,
27
+ publicActions,
28
+ walletActions,
29
+ } from "viem";
30
+ import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
31
+ import * as viemChains from "viem/chains";
32
+ import { DEFAULT_CHAINS, EVM_SERVICE_NAME } from "../constants";
33
+ import { requireProviderSpec } from "../generated/specs/spec-helpers";
34
+ import {
35
+ initRPCProviderManager,
36
+ type RPCProviderName,
37
+ validateRPCProviderConfig,
38
+ } from "../rpc-providers";
39
+ import {
40
+ assertChainConfigured,
41
+ assertDefined,
42
+ EVMError,
43
+ EVMErrorCode,
44
+ PrivateKeySchema,
45
+ type SupportedChain,
46
+ } from "../types";
47
+
48
+ export interface ChainRpcConfig {
49
+ headers?: Record<string, string>;
50
+ providerName?: RPCProviderName;
51
+ rpcUrl: string;
52
+ }
53
+
54
+ function headersWithoutAuthorization(headersInit?: HeadersInit): Headers {
55
+ const headers = new Headers(headersInit);
56
+ headers.delete("authorization");
57
+ headers.delete("Authorization");
58
+ return headers;
59
+ }
60
+
61
+ function isRetryableManagedRpcStatus(status: number): boolean {
62
+ return (
63
+ status === 401 ||
64
+ status === 402 ||
65
+ status === 403 ||
66
+ status === 408 ||
67
+ status === 429 ||
68
+ status >= 500
69
+ );
70
+ }
71
+
72
+ async function getManagedRpcFallbackReason(response: Response): Promise<string | null> {
73
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
74
+ const bodyText = await response.clone().text();
75
+ let hasValidJsonBody = false;
76
+
77
+ if (contentType.startsWith("application/json")) {
78
+ try {
79
+ JSON.parse(bodyText);
80
+ hasValidJsonBody = true;
81
+ } catch {
82
+ hasValidJsonBody = false;
83
+ }
84
+ }
85
+
86
+ if (response.ok) {
87
+ if (!hasValidJsonBody) {
88
+ return bodyText.trim()
89
+ ? "received a non-JSON or malformed JSON response"
90
+ : "received an empty response body";
91
+ }
92
+ return null;
93
+ }
94
+
95
+ if (isRetryableManagedRpcStatus(response.status)) {
96
+ return `received HTTP ${response.status}`;
97
+ }
98
+
99
+ if (!hasValidJsonBody) {
100
+ return `received HTTP ${response.status} with a non-JSON or malformed JSON body`;
101
+ }
102
+
103
+ return null;
104
+ }
105
+
106
+ export class WalletProvider {
107
+ private readonly cacheKey = "evm/wallet";
108
+ private _chains: Record<string, Chain>;
109
+ private _account: PrivateKeyAccount;
110
+ private readonly _runtime: IAgentRuntime;
111
+ private readonly _rpcConfigs: Record<string, ChainRpcConfig>;
112
+
113
+ constructor(
114
+ accountOrPrivateKey: PrivateKeyAccount | `0x${string}`,
115
+ runtime: IAgentRuntime,
116
+ chains?: Record<string, Chain>,
117
+ rpcConfigs: Record<string, ChainRpcConfig> = {}
118
+ ) {
119
+ this._runtime = runtime;
120
+ this._chains = chains ?? {};
121
+ this._account = this.initializeAccount(accountOrPrivateKey);
122
+ this._rpcConfigs = rpcConfigs;
123
+ }
124
+
125
+ getAddress(): Address {
126
+ return this._account.address;
127
+ }
128
+
129
+ get chains(): Record<string, Chain> {
130
+ return this._chains;
131
+ }
132
+
133
+ get account(): PrivateKeyAccount {
134
+ return this._account;
135
+ }
136
+
137
+ getPublicClient(
138
+ chainName: SupportedChain
139
+ ): PublicClient<HttpTransport, Chain, Account | undefined> {
140
+ assertChainConfigured(this._chains, chainName);
141
+ const transport = this.createHttpTransport(chainName);
142
+ const publicClientFactory = createPublicClient as (parameters: {
143
+ chain: Chain;
144
+ transport: HttpTransport;
145
+ }) => PublicClient<HttpTransport, Chain, Account | undefined>;
146
+
147
+ return publicClientFactory({
148
+ chain: this._chains[chainName],
149
+ transport,
150
+ });
151
+ }
152
+
153
+ getWalletClient(chainName: SupportedChain): WalletClient {
154
+ assertChainConfigured(this._chains, chainName);
155
+ const transport = this.createHttpTransport(chainName);
156
+
157
+ return createWalletClient({
158
+ chain: this._chains[chainName],
159
+ transport,
160
+ account: this._account,
161
+ });
162
+ }
163
+
164
+ getTestClient(): TestClient {
165
+ return createTestClient({
166
+ chain: viemChains.hardhat,
167
+ mode: "hardhat",
168
+ transport: http(),
169
+ })
170
+ .extend(publicActions)
171
+ .extend(walletActions);
172
+ }
173
+
174
+ getChainConfigs(chainName: SupportedChain): Chain {
175
+ const chain = this._chains[chainName];
176
+ if (!chain?.id) {
177
+ throw new EVMError(EVMErrorCode.CHAIN_NOT_CONFIGURED, `Invalid chain name: ${chainName}`);
178
+ }
179
+ return chain;
180
+ }
181
+
182
+ getSupportedChains(): SupportedChain[] {
183
+ return Object.keys(this._chains) as SupportedChain[];
184
+ }
185
+
186
+ async getWalletBalances(): Promise<Record<SupportedChain, string>> {
187
+ const cacheKey = path.join(this.cacheKey, "walletBalances");
188
+ const cachedData = await this._runtime.getCache<Record<SupportedChain, string>>(cacheKey);
189
+
190
+ if (cachedData) {
191
+ logger.log(`Returning cached wallet balances`);
192
+ return cachedData;
193
+ }
194
+
195
+ const balances = {} as Record<SupportedChain, string>;
196
+ const chainNames = this.getSupportedChains();
197
+
198
+ const results = await Promise.allSettled(
199
+ chainNames.map(async (chainName) => {
200
+ const balance = await this.getWalletBalanceForChain(chainName);
201
+ return { chainName, balance };
202
+ })
203
+ );
204
+
205
+ for (const result of results) {
206
+ if (result.status === "fulfilled" && result.value.balance !== null) {
207
+ balances[result.value.chainName] = result.value.balance;
208
+ } else if (result.status === "rejected") {
209
+ logger.error(`Error getting balance:`, result.reason);
210
+ }
211
+ }
212
+
213
+ await this._runtime.setCache(cacheKey, balances);
214
+ logger.log("Wallet balances cached");
215
+ return balances;
216
+ }
217
+
218
+ async getWalletBalanceForChain(chainName: SupportedChain): Promise<string | null> {
219
+ try {
220
+ const client = this.getPublicClient(chainName);
221
+ const balance = await client.getBalance({
222
+ address: this._account.address,
223
+ });
224
+ return formatUnits(balance, 18);
225
+ } catch (error) {
226
+ logger.error(
227
+ `Error getting wallet balance for ${chainName}:`,
228
+ error instanceof Error ? error.message : String(error)
229
+ );
230
+ return null;
231
+ }
232
+ }
233
+
234
+ addChain(chain: Record<string, Chain>): void {
235
+ this._chains = { ...this._chains, ...chain };
236
+ }
237
+
238
+ private initializeAccount(
239
+ accountOrPrivateKey: PrivateKeyAccount | `0x${string}`
240
+ ): PrivateKeyAccount {
241
+ if (typeof accountOrPrivateKey === "string") {
242
+ const result = PrivateKeySchema.safeParse(accountOrPrivateKey);
243
+ if (!result.success) {
244
+ const zodError = result.error as {
245
+ errors?: Array<{ message?: string }>;
246
+ issues?: Array<{ message?: string }>;
247
+ };
248
+ const errorList = zodError.errors ?? zodError.issues ?? [];
249
+ const firstError = Array.isArray(errorList) ? errorList[0] : undefined;
250
+ const errorMessage = firstError?.message ?? "Validation failed";
251
+ throw new EVMError(
252
+ EVMErrorCode.INVALID_PARAMS,
253
+ `Invalid private key format: ${errorMessage}`
254
+ );
255
+ }
256
+ return privateKeyToAccount(result.data);
257
+ }
258
+ return accountOrPrivateKey;
259
+ }
260
+
261
+ private createHttpTransport(chainName: SupportedChain) {
262
+ const chain = this._chains[chainName];
263
+ if (!chain) {
264
+ throw new EVMError(EVMErrorCode.CHAIN_NOT_CONFIGURED, `Chain not found: ${chainName}`);
265
+ }
266
+
267
+ const managedRpc = this._rpcConfigs[chainName];
268
+ if (managedRpc) {
269
+ const fallbackRpcUrl =
270
+ managedRpc.providerName === "elizacloud" ? chain.rpcUrls.default.http[0] : null;
271
+
272
+ return http(managedRpc.rpcUrl, {
273
+ fetchFn:
274
+ managedRpc.providerName === "elizacloud" && fallbackRpcUrl
275
+ ? async (input, init) => {
276
+ try {
277
+ const response = await fetch(input, init);
278
+ const fallbackReason = await getManagedRpcFallbackReason(response);
279
+
280
+ if (!fallbackReason) {
281
+ return response;
282
+ }
283
+
284
+ logger.warn(
285
+ `[WalletProvider] Eliza Cloud RPC failed for ${chainName}: ${fallbackReason}. Falling back to ${fallbackRpcUrl}.`
286
+ );
287
+
288
+ return await fetch(fallbackRpcUrl, {
289
+ ...init,
290
+ headers: headersWithoutAuthorization(init?.headers),
291
+ });
292
+ } catch (error) {
293
+ logger.warn(
294
+ `[WalletProvider] Eliza Cloud RPC request threw for ${chainName}. Falling back to ${fallbackRpcUrl}.`,
295
+ error instanceof Error ? error.message : String(error)
296
+ );
297
+
298
+ return await fetch(fallbackRpcUrl, {
299
+ ...init,
300
+ headers: headersWithoutAuthorization(init?.headers),
301
+ });
302
+ }
303
+ }
304
+ : undefined,
305
+ fetchOptions:
306
+ managedRpc.headers && Object.keys(managedRpc.headers).length > 0
307
+ ? { headers: managedRpc.headers }
308
+ : undefined,
309
+ });
310
+ }
311
+
312
+ const customRpc = chain.rpcUrls.custom;
313
+ if (customRpc) {
314
+ return http(customRpc.http[0]);
315
+ }
316
+ return http(chain.rpcUrls.default.http[0]);
317
+ }
318
+
319
+ static genChainFromName(chainName: string, customRpcUrl?: string | null): Chain {
320
+ const baseChain = (viemChains as Record<string, Chain | undefined>)[chainName];
321
+
322
+ if (!baseChain?.id) {
323
+ throw new EVMError(EVMErrorCode.CHAIN_NOT_CONFIGURED, `Invalid chain name: ${chainName}`);
324
+ }
325
+
326
+ if (customRpcUrl) {
327
+ return {
328
+ ...baseChain,
329
+ rpcUrls: {
330
+ ...baseChain.rpcUrls,
331
+ custom: {
332
+ http: [customRpcUrl],
333
+ },
334
+ },
335
+ };
336
+ }
337
+
338
+ return baseChain;
339
+ }
340
+ }
341
+
342
+ function genChainsFromRuntime(runtime: IAgentRuntime): {
343
+ chains: Record<string, Chain>;
344
+ rpcConfigs: Record<string, ChainRpcConfig>;
345
+ } {
346
+ const settings = runtime.character?.settings;
347
+ let configuredChains: string[] = [];
348
+ if (
349
+ typeof settings === "object" &&
350
+ settings !== null &&
351
+ "chains" in settings &&
352
+ typeof settings.chains === "object" &&
353
+ settings.chains !== null &&
354
+ "evm" in settings.chains &&
355
+ Array.isArray(settings.chains.evm)
356
+ ) {
357
+ configuredChains = settings.chains.evm.filter(
358
+ (chain): chain is string => typeof chain === "string"
359
+ );
360
+ }
361
+
362
+ const chainsToUse = configuredChains.length > 0 ? configuredChains : [...DEFAULT_CHAINS];
363
+
364
+ // Validate RPC provider configuration
365
+ const validation = validateRPCProviderConfig(runtime);
366
+ for (const warning of validation.warnings) {
367
+ logger.warn(warning);
368
+ }
369
+ if (validation.providers.length > 0) {
370
+ logger.info(`EVM RPC providers available: ${validation.providers.join(", ")}`);
371
+ }
372
+
373
+ // Initialize the multi-provider RPC manager
374
+ const rpcManager = initRPCProviderManager(runtime);
375
+
376
+ const chains: Record<string, Chain> = {};
377
+ const rpcConfigs: Record<string, ChainRpcConfig> = {};
378
+
379
+ for (const chainName of chainsToUse) {
380
+ if (!(chainName in viemChains)) {
381
+ logger.warn(`Chain ${chainName} not found in viem chains, skipping`);
382
+ continue;
383
+ }
384
+
385
+ // Resolve RPC URL through the provider manager (handles per-chain overrides,
386
+ // provider priority, and fallbacks automatically)
387
+ const resolved = rpcManager.resolveForChain(chainName);
388
+ const rpcUrl = resolved?.rpcUrl ?? null;
389
+
390
+ const chain = WalletProvider.genChainFromName(chainName, rpcUrl);
391
+ chains[chainName] = chain;
392
+
393
+ if (resolved) {
394
+ rpcConfigs[chainName] = {
395
+ providerName: resolved.providerName,
396
+ rpcUrl: resolved.rpcUrl,
397
+ headers: resolved.headers,
398
+ };
399
+ logger.log(`Configured chain: ${chainName} (via ${resolved.providerName})`);
400
+ } else {
401
+ logger.log(`Configured chain: ${chainName} (using viem default RPC)`);
402
+ }
403
+ }
404
+
405
+ return { chains, rpcConfigs };
406
+ }
407
+
408
+ async function generateAndStorePrivateKey(runtime: IAgentRuntime): Promise<`0x${string}`> {
409
+ const newPrivateKey = generatePrivateKey();
410
+ const account = privateKeyToAccount(newPrivateKey);
411
+
412
+ logger.warn("═══════════════════════════════════════════════════════════════════");
413
+ logger.warn("⚠️ EVM_PRIVATE_KEY not found - generating new wallet");
414
+ logger.warn(`📍 New wallet address: ${account.address}`);
415
+ logger.warn("💾 Private key will be stored in agent secrets automatically");
416
+ logger.warn("⚠️ IMPORTANT: Back up your private key for production use!");
417
+ logger.warn("═══════════════════════════════════════════════════════════════════");
418
+
419
+ runtime.setSetting("EVM_PRIVATE_KEY", newPrivateKey, true);
420
+
421
+ try {
422
+ await runtime.updateAgent(runtime.agentId, {
423
+ settings: {
424
+ ...runtime.character.settings,
425
+ secrets: {
426
+ ...((runtime.character.settings?.secrets as Record<string, string>) || {}),
427
+ EVM_PRIVATE_KEY: newPrivateKey,
428
+ },
429
+ },
430
+ });
431
+ logger.log("EVM private key persisted to agent settings");
432
+ } catch (error) {
433
+ logger.warn(
434
+ "Could not persist EVM private key to database - key is only in memory",
435
+ error instanceof Error ? error.message : String(error)
436
+ );
437
+ }
438
+
439
+ return newPrivateKey;
440
+ }
441
+
442
+ export async function initWalletProvider(runtime: IAgentRuntime): Promise<WalletProvider> {
443
+ const teeModeRaw = runtime.getSetting("TEE_MODE");
444
+ const teeMode = typeof teeModeRaw === "string" ? teeModeRaw : "OFF";
445
+ const { chains, rpcConfigs } = genChainsFromRuntime(runtime);
446
+
447
+ if (teeMode !== "OFF") {
448
+ const walletSecretSaltRaw = runtime.getSetting("WALLET_SECRET_SALT");
449
+ if (!walletSecretSaltRaw || typeof walletSecretSaltRaw !== "string") {
450
+ throw new EVMError(
451
+ EVMErrorCode.INVALID_PARAMS,
452
+ "WALLET_SECRET_SALT required when TEE_MODE is enabled"
453
+ );
454
+ }
455
+
456
+ return new LazyTeeWalletProvider(runtime, walletSecretSaltRaw, chains, rpcConfigs);
457
+ }
458
+
459
+ const privateKeyRaw = runtime.getSetting("EVM_PRIVATE_KEY");
460
+ let privateKey: string;
461
+ if (!privateKeyRaw || typeof privateKeyRaw !== "string") {
462
+ privateKey = await generateAndStorePrivateKey(runtime);
463
+ } else {
464
+ privateKey = privateKeyRaw;
465
+ }
466
+
467
+ const validatedKey = PrivateKeySchema.parse(privateKey);
468
+ return new WalletProvider(validatedKey, runtime, chains, rpcConfigs);
469
+ }
470
+
471
+ class LazyTeeWalletProvider extends WalletProvider {
472
+ private teeWallet: WalletProvider | null = null;
473
+ private initPromise: Promise<void> | null = null;
474
+ private readonly walletSecretSalt: string;
475
+ private readonly teeRpcConfigs: Record<string, ChainRpcConfig>;
476
+ private readonly teeRuntime: IAgentRuntime;
477
+ private readonly teeChains: Record<string, Chain>;
478
+
479
+ constructor(
480
+ runtime: IAgentRuntime,
481
+ walletSecretSalt: string,
482
+ chains: Record<string, Chain>,
483
+ rpcConfigs: Record<string, ChainRpcConfig>
484
+ ) {
485
+ const dummyKey = "0x0000000000000000000000000000000000000000000000000000000000000001" as const;
486
+ super(dummyKey, runtime, chains, rpcConfigs);
487
+ this.walletSecretSalt = walletSecretSalt;
488
+ this.teeRuntime = runtime;
489
+ this.teeChains = chains;
490
+ this.teeRpcConfigs = rpcConfigs;
491
+ }
492
+
493
+ private async ensureInitialized(): Promise<void> {
494
+ if (this.teeWallet) return;
495
+
496
+ if (!this.initPromise) {
497
+ this.initPromise = this.initializeTeeWallet();
498
+ }
499
+
500
+ await this.initPromise;
501
+ }
502
+
503
+ private async initializeTeeWallet(): Promise<void> {
504
+ const teeService = this.teeRuntime.getService(ServiceType.TEE);
505
+
506
+ if (!teeService) {
507
+ throw new EVMError(
508
+ EVMErrorCode.WALLET_NOT_INITIALIZED,
509
+ "TEE service not found - ensure TEE plugin is registered"
510
+ );
511
+ }
512
+
513
+ const teeWithDerive = teeService as {
514
+ deriveEcdsaKeypair?: (
515
+ salt: string,
516
+ path: string,
517
+ agentId: string
518
+ ) => Promise<{ keypair: `0x${string}`; attestation: unknown }>;
519
+ };
520
+
521
+ if (typeof teeWithDerive.deriveEcdsaKeypair !== "function") {
522
+ throw new EVMError(
523
+ EVMErrorCode.WALLET_NOT_INITIALIZED,
524
+ "TEE service does not implement deriveEcdsaKeypair method"
525
+ );
526
+ }
527
+
528
+ const { keypair } = await teeWithDerive.deriveEcdsaKeypair(
529
+ this.walletSecretSalt,
530
+ "evm",
531
+ this.teeRuntime.agentId
532
+ );
533
+
534
+ this.teeWallet = new WalletProvider(
535
+ keypair,
536
+ this.teeRuntime,
537
+ this.teeChains,
538
+ this.teeRpcConfigs
539
+ );
540
+ }
541
+
542
+ override getAddress(): Address {
543
+ if (!this.teeWallet) {
544
+ throw new EVMError(
545
+ EVMErrorCode.WALLET_NOT_INITIALIZED,
546
+ "TEE wallet not initialized yet. Ensure async operations complete first."
547
+ );
548
+ }
549
+ return this.teeWallet.getAddress();
550
+ }
551
+
552
+ override getPublicClient(
553
+ chainName: SupportedChain
554
+ ): PublicClient<HttpTransport, Chain, Account | undefined> {
555
+ if (!this.teeWallet) {
556
+ return super.getPublicClient(chainName);
557
+ }
558
+ return this.teeWallet.getPublicClient(chainName);
559
+ }
560
+
561
+ override getWalletClient(chainName: SupportedChain): WalletClient {
562
+ if (!this.teeWallet) {
563
+ throw new EVMError(
564
+ EVMErrorCode.WALLET_NOT_INITIALIZED,
565
+ "TEE wallet not initialized yet. Ensure async operations complete first."
566
+ );
567
+ }
568
+ return this.teeWallet.getWalletClient(chainName);
569
+ }
570
+
571
+ override async getWalletBalances(): Promise<Record<SupportedChain, string>> {
572
+ await this.ensureInitialized();
573
+ assertDefined(this.teeWallet, "TEE wallet failed to initialize");
574
+ return this.teeWallet.getWalletBalances();
575
+ }
576
+
577
+ override async getWalletBalanceForChain(chainName: SupportedChain): Promise<string | null> {
578
+ await this.ensureInitialized();
579
+ assertDefined(this.teeWallet, "TEE wallet failed to initialize");
580
+ return this.teeWallet.getWalletBalanceForChain(chainName);
581
+ }
582
+ }
583
+
584
+ const spec = requireProviderSpec("wallet");
585
+ const MAX_EVM_CHAIN_BALANCES = 20;
586
+
587
+ export const evmWalletProvider: Provider = {
588
+ name: spec.name,
589
+ description: "EVM wallet address and balances",
590
+ descriptionCompressed: "EVM wallet address and balances.",
591
+ contexts: ["finance", "crypto", "wallet"],
592
+ contextGate: { anyOf: ["finance", "crypto", "wallet"] },
593
+ cacheStable: false,
594
+ cacheScope: "turn",
595
+ roleGate: { minRole: "OWNER" },
596
+ dynamic: true,
597
+ async get(runtime: IAgentRuntime, _message: Memory, state?: State): Promise<ProviderResult> {
598
+ try {
599
+ const evmService = runtime.getService(EVM_SERVICE_NAME);
600
+
601
+ if (!evmService) {
602
+ logger.warn("EVM service not found, falling back to direct fetching");
603
+ return await directFetchWalletData(runtime, state);
604
+ }
605
+
606
+ const serviceWithCache = evmService as {
607
+ getCachedData?: () => Promise<
608
+ | {
609
+ address: string;
610
+ chains: Array<{
611
+ name: string;
612
+ balance: string;
613
+ symbol: string;
614
+ }>;
615
+ }
616
+ | undefined
617
+ >;
618
+ };
619
+
620
+ if (typeof serviceWithCache.getCachedData !== "function") {
621
+ logger.warn("EVM service missing getCachedData, falling back to direct fetching");
622
+ return await directFetchWalletData(runtime, state);
623
+ }
624
+
625
+ const walletData = await serviceWithCache.getCachedData();
626
+ if (!walletData) {
627
+ logger.warn("No cached wallet data available, falling back to direct fetching");
628
+ return await directFetchWalletData(runtime, state);
629
+ }
630
+
631
+ const agentName = state?.agentName ?? "The agent";
632
+ const chains = walletData.chains.slice(0, MAX_EVM_CHAIN_BALANCES);
633
+ const balanceText = chains
634
+ .map((chain) => `${chain.name}: ${chain.balance} ${chain.symbol}`)
635
+ .join("\n");
636
+ const truncationText =
637
+ walletData.chains.length > chains.length
638
+ ? `\n... and ${walletData.chains.length - chains.length} more chains`
639
+ : "";
640
+
641
+ return {
642
+ text: `${agentName}'s EVM Wallet Address: ${walletData.address}\n\nBalances:\n${balanceText}${truncationText}`,
643
+ data: {
644
+ address: walletData.address,
645
+ chains,
646
+ chainCount: walletData.chains.length,
647
+ displayedChainCount: chains.length,
648
+ },
649
+ values: {
650
+ address: walletData.address,
651
+ chains: `${balanceText}${truncationText}`,
652
+ },
653
+ };
654
+ } catch (error) {
655
+ logger.error(
656
+ "Error in EVM wallet provider:",
657
+ error instanceof Error ? error.message : String(error)
658
+ );
659
+ return {
660
+ text: `EVM wallet data unavailable: ${
661
+ error instanceof Error ? error.message : String(error)
662
+ }`,
663
+ data: {},
664
+ values: {
665
+ walletReady: false,
666
+ walletError: error instanceof Error ? error.name : "EVMWalletProviderError",
667
+ },
668
+ };
669
+ }
670
+ },
671
+ };
672
+
673
+ async function directFetchWalletData(
674
+ runtime: IAgentRuntime,
675
+ state?: State
676
+ ): Promise<ProviderResult> {
677
+ const walletProvider = await initWalletProvider(runtime);
678
+ const address = walletProvider.getAddress();
679
+ const balances = await walletProvider.getWalletBalances();
680
+ const agentName = state?.agentName ?? "The agent";
681
+
682
+ const allChainDetails = Object.entries(balances).map(([chainName, balance]) => {
683
+ const chain = walletProvider.getChainConfigs(chainName as SupportedChain);
684
+ return {
685
+ chainName,
686
+ balance,
687
+ symbol: chain.nativeCurrency.symbol,
688
+ chainId: chain.id,
689
+ name: chain.name,
690
+ };
691
+ });
692
+ const chainDetails = allChainDetails.slice(0, MAX_EVM_CHAIN_BALANCES);
693
+
694
+ const balanceText = chainDetails
695
+ .map((chain) => `${chain.name}: ${chain.balance} ${chain.symbol}`)
696
+ .join("\n");
697
+ const truncationText =
698
+ allChainDetails.length > chainDetails.length
699
+ ? `\n... and ${allChainDetails.length - chainDetails.length} more chains`
700
+ : "";
701
+
702
+ return {
703
+ text: `${agentName}'s EVM Wallet Address: ${address}\n\nBalances:\n${balanceText}${truncationText}`,
704
+ data: {
705
+ address,
706
+ chains: chainDetails,
707
+ chainCount: allChainDetails.length,
708
+ displayedChainCount: chainDetails.length,
709
+ },
710
+ values: {
711
+ address: address as string,
712
+ chains: `${balanceText}${truncationText}`,
713
+ },
714
+ };
715
+ }