@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,668 @@
1
+ import type { IAgentRuntime, ITokenDataService } from "@elizaos/core";
2
+ import {
3
+ createAssociatedTokenAccountInstruction,
4
+ createTransferInstruction,
5
+ getAssociatedTokenAddressSync,
6
+ } from "@solana/spl-token";
7
+ import {
8
+ Connection,
9
+ LAMPORTS_PER_SOL,
10
+ PublicKey,
11
+ SystemProgram,
12
+ type TransactionInstruction,
13
+ TransactionMessage,
14
+ VersionedTransaction,
15
+ } from "@solana/web3.js";
16
+ import {
17
+ type Address,
18
+ type Chain,
19
+ encodeFunctionData,
20
+ parseAbi,
21
+ parseUnits,
22
+ } from "viem";
23
+ import * as viemChains from "viem/chains";
24
+ import type { WalletBackendService } from "../services/wallet-backend-service.js";
25
+ import type {
26
+ WalletChainHandler,
27
+ WalletRouterContext,
28
+ WalletRouterExecution,
29
+ WalletRouterParams,
30
+ } from "../types/wallet-router.js";
31
+ import { buildSendTxParams } from "./evm/actions/helpers";
32
+ import { SwapAction } from "./evm/actions/swap";
33
+ import { TransferAction } from "./evm/actions/transfer";
34
+ import { routeEvmBridge } from "./evm/bridge-router";
35
+ import { DEFAULT_CHAINS, NATIVE_TOKEN_ADDRESS } from "./evm/constants";
36
+ import { initWalletProvider } from "./evm/providers/wallet";
37
+ import type { SupportedChain, Transaction } from "./evm/types";
38
+ import BigNumber from "./solana/bn";
39
+ import { SOLANA_SERVICE_NAME } from "./solana/constants";
40
+ import { getWalletKey } from "./solana/keypairUtils";
41
+ import type { SolanaService } from "./solana/service";
42
+
43
+ const SOL_MINT = "So11111111111111111111111111111111111111112";
44
+ const SOLANA_DEFAULT_RPC = "https://api.mainnet-beta.solana.com";
45
+
46
+ function getRuntimeSetting(runtime: IAgentRuntime, key: string): string | null {
47
+ const value = runtime.getSetting(key);
48
+ return typeof value === "string" && value.length > 0 ? value : null;
49
+ }
50
+
51
+ function parseBoolSetting(value: string | number | boolean | null): boolean {
52
+ if (value === null || value === undefined) return false;
53
+ if (typeof value === "boolean") return value;
54
+ if (typeof value === "number") return value !== 0;
55
+ const str = String(value).toLowerCase().trim();
56
+ return str === "true" || str === "1" || str === "yes";
57
+ }
58
+
59
+ function configuredEvmChains(runtime: IAgentRuntime): Array<{
60
+ readonly key: string;
61
+ readonly chain: Chain;
62
+ }> {
63
+ const settings = runtime.character?.settings;
64
+ const configured =
65
+ settings &&
66
+ typeof settings === "object" &&
67
+ "chains" in settings &&
68
+ settings.chains &&
69
+ typeof settings.chains === "object" &&
70
+ "evm" in settings.chains &&
71
+ Array.isArray(settings.chains.evm)
72
+ ? settings.chains.evm.filter(
73
+ (chain): chain is string => typeof chain === "string",
74
+ )
75
+ : [...DEFAULT_CHAINS];
76
+
77
+ const out: Array<{ key: string; chain: Chain }> = [];
78
+ for (const key of configured) {
79
+ const chain = (viemChains as Record<string, Chain | undefined>)[key];
80
+ if (chain?.id) {
81
+ out.push({ key, chain });
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+
87
+ function evmAliases(key: string, chain: Chain): string[] {
88
+ const aliases = new Set<string>([
89
+ key,
90
+ chain.name,
91
+ String(chain.id),
92
+ chain.nativeCurrency.symbol,
93
+ ]);
94
+ if (key === "mainnet") {
95
+ aliases.add("ethereum");
96
+ aliases.add("eth");
97
+ }
98
+ return [...aliases];
99
+ }
100
+
101
+ function isEvmAddress(value: string): value is Address {
102
+ return /^0x[a-fA-F0-9]{40}$/.test(value);
103
+ }
104
+
105
+ function isNativeEvmToken(value: string | undefined, chain: Chain): boolean {
106
+ if (!value) return true;
107
+ const normalized = value.toLowerCase();
108
+ return (
109
+ normalized === "native" ||
110
+ normalized === "eth" ||
111
+ normalized === chain.nativeCurrency.symbol.toLowerCase() ||
112
+ normalized === NATIVE_TOKEN_ADDRESS.toLowerCase()
113
+ );
114
+ }
115
+
116
+ async function resolveTokenViaService(
117
+ service: ITokenDataService | null,
118
+ token: string,
119
+ chain: string,
120
+ ): Promise<string | null> {
121
+ if (!service) return null;
122
+ const results = await service.searchTokens(token, chain, 5);
123
+ const match = results.find((candidate) => {
124
+ const maybe = candidate as { address?: unknown; symbol?: unknown };
125
+ return (
126
+ typeof maybe.address === "string" &&
127
+ (String(maybe.symbol ?? "").toLowerCase() === token.toLowerCase() ||
128
+ maybe.address.toLowerCase() === token.toLowerCase())
129
+ );
130
+ });
131
+ const address = (match as { address?: unknown } | undefined)?.address;
132
+ return typeof address === "string" ? address : null;
133
+ }
134
+
135
+ async function resolveEvmTokenAddress(
136
+ token: string | undefined,
137
+ chainKey: string,
138
+ chain: Chain,
139
+ context: WalletRouterContext,
140
+ ): Promise<Address> {
141
+ if (isNativeEvmToken(token, chain)) {
142
+ return NATIVE_TOKEN_ADDRESS;
143
+ }
144
+ if (token && isEvmAddress(token)) {
145
+ return token;
146
+ }
147
+ if (token) {
148
+ const resolved = await resolveTokenViaService(
149
+ context.tokenDataService,
150
+ token,
151
+ chainKey,
152
+ );
153
+ if (resolved && isEvmAddress(resolved)) {
154
+ return resolved;
155
+ }
156
+ }
157
+ throw new Error(
158
+ `Token "${token ?? "native"}" must be an EVM address or a resolvable ${chain.name} token symbol.`,
159
+ );
160
+ }
161
+
162
+ function transactionToExecution(
163
+ tx: Transaction,
164
+ params: WalletRouterParams,
165
+ chainKey: string,
166
+ chain: Chain,
167
+ ): WalletRouterExecution {
168
+ return {
169
+ status: "submitted",
170
+ chain: chainKey,
171
+ chainId: String(chain.id),
172
+ subaction: params.subaction,
173
+ dryRun: false,
174
+ mode: params.mode,
175
+ transactionHash: tx.hash,
176
+ from: tx.from,
177
+ to: tx.to,
178
+ amount: params.amount,
179
+ fromToken: params.fromToken,
180
+ toToken: params.toToken,
181
+ metadata: {
182
+ value: tx.value.toString(),
183
+ data: tx.data,
184
+ chainId: tx.chainId ?? chain.id,
185
+ },
186
+ };
187
+ }
188
+
189
+ async function executeEvmTransfer(
190
+ params: WalletRouterParams,
191
+ context: WalletRouterContext,
192
+ chainKey: string,
193
+ chain: Chain,
194
+ ): Promise<WalletRouterExecution> {
195
+ const recipient = params.recipient;
196
+ if (!recipient || !isEvmAddress(recipient)) {
197
+ throw new Error("recipient must be a valid EVM address.");
198
+ }
199
+
200
+ const walletProvider = await initWalletProvider(context.runtime);
201
+ const token = await resolveEvmTokenAddress(
202
+ params.fromToken,
203
+ chainKey,
204
+ chain,
205
+ context,
206
+ );
207
+
208
+ if (token === NATIVE_TOKEN_ADDRESS) {
209
+ const action = new TransferAction(walletProvider);
210
+ const tx = await action.transfer({
211
+ fromChain: chainKey as SupportedChain,
212
+ toAddress: recipient,
213
+ amount: params.amount ?? "",
214
+ token: params.fromToken,
215
+ });
216
+ return transactionToExecution(tx, params, chainKey, chain);
217
+ }
218
+
219
+ const walletClient = walletProvider.getWalletClient(
220
+ chainKey as SupportedChain,
221
+ );
222
+ const account = walletClient.account;
223
+ if (!account) {
224
+ throw new Error("Wallet account is not available.");
225
+ }
226
+
227
+ const publicClient = walletProvider.getPublicClient(
228
+ chainKey as SupportedChain,
229
+ );
230
+ const decimalsAbi = parseAbi(["function decimals() view returns (uint8)"]);
231
+ const decimals = Number(
232
+ await publicClient.readContract({
233
+ address: token,
234
+ abi: decimalsAbi,
235
+ functionName: "decimals",
236
+ authorizationList: undefined,
237
+ }),
238
+ );
239
+ const transferAbi = parseAbi([
240
+ "function transfer(address,uint256) returns (bool)",
241
+ ]);
242
+ const data = encodeFunctionData({
243
+ abi: transferAbi,
244
+ functionName: "transfer",
245
+ args: [recipient, parseUnits(params.amount ?? "", decimals)],
246
+ });
247
+
248
+ const hash = await walletClient.sendTransaction(
249
+ buildSendTxParams({
250
+ account,
251
+ to: token,
252
+ value: 0n,
253
+ data,
254
+ chain,
255
+ }),
256
+ );
257
+
258
+ return {
259
+ status: "submitted",
260
+ chain: chainKey,
261
+ chainId: String(chain.id),
262
+ subaction: params.subaction,
263
+ dryRun: false,
264
+ mode: params.mode,
265
+ transactionHash: hash,
266
+ from: account.address,
267
+ to: recipient,
268
+ amount: params.amount,
269
+ fromToken: token,
270
+ metadata: {
271
+ token,
272
+ decimals,
273
+ value: "0",
274
+ data,
275
+ },
276
+ };
277
+ }
278
+
279
+ async function executeEvmSwap(
280
+ params: WalletRouterParams,
281
+ context: WalletRouterContext,
282
+ chainKey: string,
283
+ chain: Chain,
284
+ ): Promise<WalletRouterExecution> {
285
+ const walletProvider = await initWalletProvider(context.runtime);
286
+ const fromToken = await resolveEvmTokenAddress(
287
+ params.fromToken,
288
+ chainKey,
289
+ chain,
290
+ context,
291
+ );
292
+ const toToken = await resolveEvmTokenAddress(
293
+ params.toToken,
294
+ chainKey,
295
+ chain,
296
+ context,
297
+ );
298
+
299
+ const action = new SwapAction(walletProvider);
300
+ const tx = await action.swap({
301
+ chain: chainKey as SupportedChain,
302
+ fromToken,
303
+ toToken,
304
+ amount: params.amount ?? "",
305
+ });
306
+ return transactionToExecution(
307
+ {
308
+ ...tx,
309
+ chainId: tx.chainId ?? chain.id,
310
+ },
311
+ {
312
+ ...params,
313
+ fromToken,
314
+ toToken,
315
+ },
316
+ chainKey,
317
+ chain,
318
+ );
319
+ }
320
+
321
+ function createEvmHandler(key: string, chain: Chain): WalletChainHandler {
322
+ const aliases = evmAliases(key, chain);
323
+ return {
324
+ chainId: String(chain.id),
325
+ chain: key,
326
+ name: chain.name,
327
+ aliases,
328
+ supportedActions: ["transfer", "swap", "bridge"],
329
+ tokens: [
330
+ {
331
+ symbol: chain.nativeCurrency.symbol,
332
+ address: NATIVE_TOKEN_ADDRESS,
333
+ decimals: chain.nativeCurrency.decimals,
334
+ native: true,
335
+ },
336
+ ],
337
+ signer: {
338
+ required: true,
339
+ kind: "evm",
340
+ source: "WalletBackend EVM signer or EVM_PRIVATE_KEY",
341
+ description: "Required only for execute mode.",
342
+ },
343
+ dryRun: {
344
+ supported: true,
345
+ supportedActions: ["transfer", "swap", "bridge"],
346
+ description:
347
+ "Prepare mode and dry-run return route metadata without signing.",
348
+ },
349
+ async execute(params, context) {
350
+ if (params.subaction === "transfer") {
351
+ return executeEvmTransfer(params, context, key, chain);
352
+ }
353
+ if (params.subaction === "swap") {
354
+ return executeEvmSwap(params, context, key, chain);
355
+ }
356
+ if (params.subaction === "bridge") {
357
+ return routeEvmBridge(params, context, key, chain);
358
+ }
359
+ throw new Error(`${chain.name} does not support ${params.subaction}.`);
360
+ },
361
+ };
362
+ }
363
+
364
+ function resolveSolanaMint(token: string | undefined): string {
365
+ if (!token) return SOL_MINT;
366
+ const normalized = token.trim();
367
+ if (
368
+ normalized.toLowerCase() === "sol" ||
369
+ normalized.toLowerCase() === "native" ||
370
+ normalized === SOL_MINT
371
+ ) {
372
+ return SOL_MINT;
373
+ }
374
+ return normalized;
375
+ }
376
+
377
+ function getSolanaConnection(runtime: IAgentRuntime): Connection {
378
+ const service = runtime.getService(
379
+ SOLANA_SERVICE_NAME,
380
+ ) as SolanaService | null;
381
+ if (service) {
382
+ return service.getConnection();
383
+ }
384
+ const rpcUrl =
385
+ getRuntimeSetting(runtime, "SOLANA_RPC_URL") ?? SOLANA_DEFAULT_RPC;
386
+ return new Connection(rpcUrl);
387
+ }
388
+
389
+ async function getSolanaTokenDecimals(
390
+ connection: Connection,
391
+ mintAddress: string,
392
+ ): Promise<number> {
393
+ if (mintAddress === SOL_MINT) {
394
+ return 9;
395
+ }
396
+ const mintPublicKey = new PublicKey(mintAddress);
397
+ const tokenAccountInfo = await connection.getParsedAccountInfo(mintPublicKey);
398
+
399
+ if (
400
+ tokenAccountInfo.value &&
401
+ typeof tokenAccountInfo.value.data === "object" &&
402
+ "parsed" in tokenAccountInfo.value.data
403
+ ) {
404
+ const parsedInfo = tokenAccountInfo.value.data.parsed?.info;
405
+ if (parsedInfo && typeof parsedInfo.decimals === "number") {
406
+ return parsedInfo.decimals;
407
+ }
408
+ }
409
+
410
+ throw new Error(`Unable to fetch token decimals for ${mintAddress}`);
411
+ }
412
+
413
+ async function executeSolanaTransfer(
414
+ params: WalletRouterParams,
415
+ context: WalletRouterContext,
416
+ ): Promise<WalletRouterExecution> {
417
+ if (!params.recipient) {
418
+ throw new Error("recipient is required for Solana transfer.");
419
+ }
420
+ const { keypair: senderKeypair } = await getWalletKey(context.runtime, true);
421
+ if (!senderKeypair) {
422
+ throw new Error("Solana keypair is not available.");
423
+ }
424
+ const connection = getSolanaConnection(context.runtime);
425
+ const recipientPubkey = new PublicKey(params.recipient);
426
+ const tokenMint = resolveSolanaMint(params.fromToken);
427
+ const instructions: TransactionInstruction[] = [];
428
+
429
+ if (tokenMint === SOL_MINT) {
430
+ instructions.push(
431
+ SystemProgram.transfer({
432
+ fromPubkey: senderKeypair.publicKey,
433
+ toPubkey: recipientPubkey,
434
+ lamports: Math.round(Number(params.amount) * LAMPORTS_PER_SOL),
435
+ }),
436
+ );
437
+ } else {
438
+ const mintPubkey = new PublicKey(tokenMint);
439
+ const decimals = await getSolanaTokenDecimals(connection, tokenMint);
440
+ const adjustedAmount = BigInt(
441
+ new BigNumber(params.amount ?? "0")
442
+ .multipliedBy(new BigNumber(10).pow(decimals))
443
+ .integerValue()
444
+ .toFixed(0),
445
+ );
446
+ const senderAta = getAssociatedTokenAddressSync(
447
+ mintPubkey,
448
+ senderKeypair.publicKey,
449
+ );
450
+ const recipientAta = getAssociatedTokenAddressSync(
451
+ mintPubkey,
452
+ recipientPubkey,
453
+ );
454
+ const recipientAtaInfo = await connection.getAccountInfo(recipientAta);
455
+ if (!recipientAtaInfo) {
456
+ instructions.push(
457
+ createAssociatedTokenAccountInstruction(
458
+ senderKeypair.publicKey,
459
+ recipientAta,
460
+ recipientPubkey,
461
+ mintPubkey,
462
+ ),
463
+ );
464
+ }
465
+ instructions.push(
466
+ createTransferInstruction(
467
+ senderAta,
468
+ recipientAta,
469
+ senderKeypair.publicKey,
470
+ adjustedAmount,
471
+ ),
472
+ );
473
+ }
474
+
475
+ const latestBlockhash = await connection.getLatestBlockhash();
476
+ const message = new TransactionMessage({
477
+ payerKey: senderKeypair.publicKey,
478
+ recentBlockhash: latestBlockhash.blockhash,
479
+ instructions,
480
+ }).compileToV0Message();
481
+ const transaction = new VersionedTransaction(message);
482
+ transaction.sign([senderKeypair]);
483
+ const signature = await connection.sendTransaction(transaction, {
484
+ skipPreflight: false,
485
+ });
486
+
487
+ return {
488
+ status: "submitted",
489
+ chain: "solana",
490
+ chainId: "solana-mainnet",
491
+ subaction: "transfer",
492
+ dryRun: false,
493
+ mode: params.mode,
494
+ signature,
495
+ from: senderKeypair.publicKey.toBase58(),
496
+ to: params.recipient,
497
+ amount: params.amount,
498
+ fromToken: tokenMint,
499
+ };
500
+ }
501
+
502
+ async function executeSolanaSwap(
503
+ params: WalletRouterParams,
504
+ context: WalletRouterContext,
505
+ ): Promise<WalletRouterExecution> {
506
+ const connection = getSolanaConnection(context.runtime);
507
+ const inputMint = resolveSolanaMint(params.fromToken);
508
+ const outputMint = resolveSolanaMint(params.toToken);
509
+ const decimals = await getSolanaTokenDecimals(connection, inputMint);
510
+ const adjustedAmount = new BigNumber(params.amount ?? "0")
511
+ .multipliedBy(new BigNumber(10).pow(decimals))
512
+ .integerValue()
513
+ .toFixed(0);
514
+
515
+ const quoteParams = new URLSearchParams({
516
+ inputMint,
517
+ outputMint,
518
+ amount: adjustedAmount,
519
+ maxAccounts: "64",
520
+ });
521
+ if (params.slippageBps !== undefined) {
522
+ quoteParams.set("slippageBps", String(params.slippageBps));
523
+ } else {
524
+ quoteParams.set("dynamicSlippage", "true");
525
+ }
526
+
527
+ const quoteResponse = await fetch(
528
+ `https://quote-api.jup.ag/v6/quote?${quoteParams.toString()}`,
529
+ );
530
+ const quoteData = (await quoteResponse.json()) as {
531
+ error?: string;
532
+ [key: string]: unknown;
533
+ };
534
+ if (quoteData.error) {
535
+ throw new Error(`Failed to get Jupiter quote: ${quoteData.error}`);
536
+ }
537
+
538
+ const { publicKey: walletPublicKey } = await getWalletKey(
539
+ context.runtime,
540
+ false,
541
+ );
542
+ if (!walletPublicKey) {
543
+ throw new Error("Solana public key is not available.");
544
+ }
545
+
546
+ const swapResponse = await fetch("https://quote-api.jup.ag/v6/swap", {
547
+ method: "POST",
548
+ headers: { "Content-Type": "application/json" },
549
+ body: JSON.stringify({
550
+ quoteResponse: quoteData,
551
+ userPublicKey: walletPublicKey.toBase58(),
552
+ dynamicComputeUnitLimit: true,
553
+ dynamicSlippage: params.slippageBps === undefined,
554
+ priorityLevelWithMaxLamports: {
555
+ maxLamports: 4_000_000,
556
+ priorityLevel: "veryHigh",
557
+ },
558
+ }),
559
+ });
560
+ const swapData = (await swapResponse.json()) as {
561
+ error?: string;
562
+ swapTransaction?: string;
563
+ };
564
+ if (!swapData.swapTransaction) {
565
+ throw new Error(
566
+ `Failed to build Jupiter swap: ${swapData.error ?? "No swap transaction returned"}`,
567
+ );
568
+ }
569
+
570
+ const transaction = VersionedTransaction.deserialize(
571
+ Buffer.from(swapData.swapTransaction, "base64"),
572
+ );
573
+ const { keypair } = await getWalletKey(context.runtime, true);
574
+ if (!keypair) {
575
+ throw new Error("Solana keypair is not available.");
576
+ }
577
+ transaction.sign([keypair]);
578
+
579
+ const latestBlockhash = await connection.getLatestBlockhash();
580
+ const signature = await connection.sendTransaction(transaction, {
581
+ skipPreflight: false,
582
+ maxRetries: 3,
583
+ preflightCommitment: "confirmed",
584
+ });
585
+
586
+ const confirmation = await connection.confirmTransaction(
587
+ {
588
+ signature,
589
+ blockhash: latestBlockhash.blockhash,
590
+ lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
591
+ },
592
+ "confirmed",
593
+ );
594
+ if (confirmation.value.err) {
595
+ throw new Error(
596
+ `Transaction failed: ${JSON.stringify(confirmation.value.err)}`,
597
+ );
598
+ }
599
+
600
+ return {
601
+ status: "submitted",
602
+ chain: "solana",
603
+ chainId: "solana-mainnet",
604
+ subaction: "swap",
605
+ dryRun: false,
606
+ mode: params.mode,
607
+ signature,
608
+ from: keypair.publicKey.toBase58(),
609
+ amount: params.amount,
610
+ fromToken: inputMint,
611
+ toToken: outputMint,
612
+ };
613
+ }
614
+
615
+ function createSolanaHandler(): WalletChainHandler {
616
+ return {
617
+ chainId: "solana-mainnet",
618
+ chain: "solana",
619
+ name: "Solana",
620
+ aliases: ["solana", "sol", "mainnet-beta", "solana-mainnet"],
621
+ supportedActions: ["transfer", "swap"],
622
+ tokens: [
623
+ {
624
+ symbol: "SOL",
625
+ address: SOL_MINT,
626
+ decimals: 9,
627
+ native: true,
628
+ },
629
+ ],
630
+ signer: {
631
+ required: true,
632
+ kind: "solana",
633
+ source: "WalletBackend Solana signer or SOLANA_PRIVATE_KEY",
634
+ description: "Required only for execute mode.",
635
+ },
636
+ dryRun: {
637
+ supported: true,
638
+ supportedActions: ["transfer", "swap"],
639
+ description:
640
+ "Prepare mode and dry-run return route metadata without signing.",
641
+ },
642
+ async execute(params, context) {
643
+ if (params.subaction === "transfer") {
644
+ return executeSolanaTransfer(params, context);
645
+ }
646
+ if (params.subaction === "swap") {
647
+ return executeSolanaSwap(params, context);
648
+ }
649
+ throw new Error(`Solana does not support ${params.subaction}.`);
650
+ },
651
+ };
652
+ }
653
+
654
+ export function registerDefaultWalletChainHandlers(
655
+ service: WalletBackendService,
656
+ runtime: IAgentRuntime,
657
+ ): void {
658
+ for (const { key, chain } of configuredEvmChains(runtime)) {
659
+ service.registerChainHandler(createEvmHandler(key, chain));
660
+ }
661
+
662
+ const solanaNoActions = parseBoolSetting(
663
+ runtime.getSetting("SOLANA_NO_ACTIONS"),
664
+ );
665
+ if (!solanaNoActions) {
666
+ service.registerChainHandler(createSolanaHandler());
667
+ }
668
+ }