@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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/auto-enable.ts +76 -0
- package/dist/LpManagementService-BWrQ5-cO.mjs +353 -0
- package/dist/MockLpService-D_Apn4Fd.mjs +99 -0
- package/dist/aerodrome-CfnESC32.mjs +890 -0
- package/dist/chunk-hT5z_Zn9.mjs +35 -0
- package/dist/index.d.mts +34727 -0
- package/dist/index.mjs +21590 -0
- package/dist/lib/server-wallet-trade.d.mts +34 -0
- package/dist/lib/server-wallet-trade.mjs +306 -0
- package/dist/meteora-BPX39hZo.mjs +22640 -0
- package/dist/orca-Bybp1HXO.mjs +249 -0
- package/dist/pancakeswp-CkEXlXti.mjs +604 -0
- package/dist/plugin-ZO_MTyd0.mjs +529 -0
- package/dist/raydium-rfaM9yEf.mjs +539 -0
- package/dist/sdk/index.d.mts +32492 -0
- package/dist/sdk/index.mjs +6415 -0
- package/dist/types-D5252NZk.mjs +487 -0
- package/dist/uniswap-CReXgXVN.mjs +573 -0
- package/dist/wallet-action.d.mts +6 -0
- package/dist/wallet-action.mjs +820 -0
- package/package.json +152 -0
- package/src/actions/failure-codes.ts +79 -0
- package/src/actions/index.ts +1 -0
- package/src/analytics/birdeye/actions/wallet-search-address.ts +9 -0
- package/src/analytics/birdeye/birdeye-task.ts +175 -0
- package/src/analytics/birdeye/birdeye.ts +813 -0
- package/src/analytics/birdeye/constants.ts +74 -0
- package/src/analytics/birdeye/providers/agent-portfolio-provider.ts +18 -0
- package/src/analytics/birdeye/providers/market.ts +227 -0
- package/src/analytics/birdeye/providers/portfolio-factory.test.ts +138 -0
- package/src/analytics/birdeye/providers/portfolio-factory.ts +252 -0
- package/src/analytics/birdeye/providers/trending.ts +365 -0
- package/src/analytics/birdeye/providers/wallet.ts +14 -0
- package/src/analytics/birdeye/search-category.test.ts +207 -0
- package/src/analytics/birdeye/search-category.ts +506 -0
- package/src/analytics/birdeye/service.ts +992 -0
- package/src/analytics/birdeye/tasks/birdeye.ts +232 -0
- package/src/analytics/birdeye/types/api/common.ts +305 -0
- package/src/analytics/birdeye/types/api/defi.ts +220 -0
- package/src/analytics/birdeye/types/api/pair.ts +200 -0
- package/src/analytics/birdeye/types/api/search.ts +86 -0
- package/src/analytics/birdeye/types/api/token.ts +635 -0
- package/src/analytics/birdeye/types/api/trader.ts +76 -0
- package/src/analytics/birdeye/types/api/wallet.ts +181 -0
- package/src/analytics/birdeye/types/shared.ts +106 -0
- package/src/analytics/birdeye/utils.ts +700 -0
- package/src/analytics/dexscreener/errors.ts +28 -0
- package/src/analytics/dexscreener/index.ts +3 -0
- package/src/analytics/dexscreener/search-category.test.ts +49 -0
- package/src/analytics/dexscreener/search-category.ts +42 -0
- package/src/analytics/dexscreener/service.ts +595 -0
- package/src/analytics/dexscreener/types.ts +128 -0
- package/src/analytics/lpinfo/index.d.ts +7 -0
- package/src/analytics/lpinfo/index.ts +52 -0
- package/src/analytics/lpinfo/kamino/README.md +102 -0
- package/src/analytics/lpinfo/kamino/index.ts +24 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoLiquidityProvider.ts +422 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoPoolProvider.ts +365 -0
- package/src/analytics/lpinfo/kamino/providers/kaminoProvider.ts +496 -0
- package/src/analytics/lpinfo/kamino/services/kaminoLiquidityService.ts +1123 -0
- package/src/analytics/lpinfo/kamino/services/kaminoService.ts +758 -0
- package/src/analytics/lpinfo/steer/README.md +169 -0
- package/src/analytics/lpinfo/steer/index.ts +23 -0
- package/src/analytics/lpinfo/steer/providers/steerLiquidityProvider.ts +544 -0
- package/src/analytics/lpinfo/steer/services/steerLiquidityService.ts +1690 -0
- package/src/analytics/lpinfo/steer/steer-display-types.ts +99 -0
- package/src/analytics/news/index.ts +52 -0
- package/src/analytics/news/interfaces/types.ts +222 -0
- package/src/analytics/news/providers/defiNewsProvider.ts +734 -0
- package/src/analytics/news/services/newsDataService.ts +332 -0
- package/src/analytics/news/utils/formatters.ts +151 -0
- package/src/analytics/token-info/action.ts +240 -0
- package/src/analytics/token-info/index.ts +3 -0
- package/src/analytics/token-info/params.ts +215 -0
- package/src/analytics/token-info/providers.ts +681 -0
- package/src/analytics/token-info/service.ts +168 -0
- package/src/analytics/token-info/types.ts +74 -0
- package/src/audit/audit-log.ts +45 -0
- package/src/browser-shim/build-shim.ts +123 -0
- package/src/browser-shim/index.ts +5 -0
- package/src/browser-shim/shim.template.js +563 -0
- package/src/chains/evm/.github/workflows/npm-deploy.yml +112 -0
- package/src/chains/evm/LICENSE +21 -0
- package/src/chains/evm/README.md +106 -0
- package/src/chains/evm/actions/helpers.ts +147 -0
- package/src/chains/evm/actions/swap.ts +839 -0
- package/src/chains/evm/actions/transfer.ts +254 -0
- package/src/chains/evm/biome.json +61 -0
- package/src/chains/evm/bridge-router.ts +660 -0
- package/src/chains/evm/build.ts +89 -0
- package/src/chains/evm/chain-handler.ts +416 -0
- package/src/chains/evm/constants.ts +23 -0
- package/src/chains/evm/contracts/artifacts/OZGovernor.json +1707 -0
- package/src/chains/evm/contracts/artifacts/TimelockController.json +1007 -0
- package/src/chains/evm/contracts/artifacts/VoteToken.json +895 -0
- package/src/chains/evm/dex/aerodrome/index.ts +34 -0
- package/src/chains/evm/dex/aerodrome/services/AerodromeLpService.ts +558 -0
- package/src/chains/evm/dex/aerodrome/types.ts +318 -0
- package/src/chains/evm/dex/pancakeswp/index.ts +35 -0
- package/src/chains/evm/dex/pancakeswp/services/PancakeSwapV3LpService.ts +743 -0
- package/src/chains/evm/dex/pancakeswp/types.ts +65 -0
- package/src/chains/evm/dex/uniswap/index.ts +35 -0
- package/src/chains/evm/dex/uniswap/services/UniswapV3LpService.ts +759 -0
- package/src/chains/evm/dex/uniswap/types.ts +390 -0
- package/src/chains/evm/generated/specs/spec-helpers.ts +73 -0
- package/src/chains/evm/generated/specs/specs.ts +151 -0
- package/src/chains/evm/gov-router.ts +250 -0
- package/src/chains/evm/index.browser.ts +16 -0
- package/src/chains/evm/index.ts +31 -0
- package/src/chains/evm/prompts.ts +193 -0
- package/src/chains/evm/providers/get-balance.ts +123 -0
- package/src/chains/evm/providers/wallet.ts +715 -0
- package/src/chains/evm/routes/sign.ts +333 -0
- package/src/chains/evm/rpc-providers.ts +410 -0
- package/src/chains/evm/service.ts +140 -0
- package/src/chains/evm/templates/index.ts +10 -0
- package/src/chains/evm/types/index.ts +432 -0
- package/src/chains/evm/vitest.config.ts +18 -0
- package/src/chains/registry.ts +668 -0
- package/src/chains/solana/README.md +367 -0
- package/src/chains/wallet-action.ts +533 -0
- package/src/chains/wallet-router.test.ts +296 -0
- package/src/contracts.ts +65 -0
- package/src/core-augmentation.ts +10 -0
- package/src/index.ts +71 -0
- package/src/lib/server-wallet-trade.ts +192 -0
- package/src/lib/wallet-export-guard.ts +330 -0
- package/src/lp/actions/liquidity.ts +827 -0
- package/src/lp/e2e/real-token-tests.ts +428 -0
- package/src/lp/e2e/scenarios.ts +470 -0
- package/src/lp/e2e/test-utils.ts +145 -0
- package/src/lp/lp-manager-entry.ts +303 -0
- package/src/lp/services/ConcentratedLiquidityService.ts +120 -0
- package/src/lp/services/DexInteractionService.ts +226 -0
- package/src/lp/services/LpManagementService.test.ts +148 -0
- package/src/lp/services/LpManagementService.ts +632 -0
- package/src/lp/services/UserLpProfileService.ts +163 -0
- package/src/lp/services/VaultService.ts +153 -0
- package/src/lp/services/YieldOptimizationService.ts +344 -0
- package/src/lp/services/__tests__/MockLpService.ts +146 -0
- package/src/lp/tasks/LpAutoRebalanceTask.ts +117 -0
- package/src/lp/tasks/__tests__/LpAutoRebalanceTask.test.ts +370 -0
- package/src/lp/types.ts +582 -0
- package/src/lp/utils/solanaClient.ts +143 -0
- package/src/plugin.ts +125 -0
- package/src/policy/policy.ts +19 -0
- package/src/providers/canonical-provider.ts +27 -0
- package/src/providers/unified-wallet-provider.ts +79 -0
- package/src/register-routes.ts +11 -0
- package/src/routes/plugin.ts +47 -0
- package/src/routes/wallet-market-overview-route.ts +869 -0
- package/src/sdk/abi.ts +258 -0
- package/src/sdk/bridge/abis.ts +126 -0
- package/src/sdk/bridge/client.ts +518 -0
- package/src/sdk/bridge/index.ts +56 -0
- package/src/sdk/bridge/solana.ts +604 -0
- package/src/sdk/bridge/types.ts +202 -0
- package/src/sdk/convenience.ts +347 -0
- package/src/sdk/escrow/MutualStakeEscrow.ts +480 -0
- package/src/sdk/escrow/types.ts +64 -0
- package/src/sdk/escrow/verifiers.ts +73 -0
- package/src/sdk/identity/erc8004.ts +692 -0
- package/src/sdk/identity/reputation.ts +449 -0
- package/src/sdk/identity/uaid.ts +497 -0
- package/src/sdk/identity/validation.ts +372 -0
- package/src/sdk/index.ts +763 -0
- package/src/sdk/policy/SpendingPolicy.ts +260 -0
- package/src/sdk/policy/UptoBillingPolicy.ts +320 -0
- package/src/sdk/router/PaymentRouter.ts +215 -0
- package/src/sdk/router/index.ts +8 -0
- package/src/sdk/swap/SwapModule.ts +310 -0
- package/src/sdk/swap/abi.ts +117 -0
- package/src/sdk/swap/index.ts +34 -0
- package/src/sdk/swap/types.ts +135 -0
- package/src/sdk/tokens/decimals.ts +140 -0
- package/src/sdk/tokens/registry.ts +911 -0
- package/src/sdk/tokens/solana.ts +419 -0
- package/src/sdk/tokens/transfers.ts +327 -0
- package/src/sdk/types.ts +158 -0
- package/src/sdk/wallet-core.ts +115 -0
- package/src/sdk/x402/budget.ts +168 -0
- package/src/sdk/x402/chains/abstract/index.ts +280 -0
- package/src/sdk/x402/client.ts +320 -0
- package/src/sdk/x402/index.ts +46 -0
- package/src/sdk/x402/middleware.ts +92 -0
- package/src/sdk/x402/multi-asset.ts +144 -0
- package/src/sdk/x402/types.ts +156 -0
- package/src/services/wallet-backend-service.ts +328 -0
- package/src/types/wallet-router.ts +227 -0
- package/src/utils/intent-trajectory.ts +106 -0
- package/src/wallet/backend.ts +62 -0
- package/src/wallet/errors.ts +49 -0
- package/src/wallet/index.ts +27 -0
- package/src/wallet/local-eoa-backend.ts +201 -0
- package/src/wallet/pending.ts +60 -0
- package/src/wallet/select-backend.ts +47 -0
- package/src/wallet/steward-backend.ts +161 -0
- package/src/wallet-action.ts +1 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
import { logger } from "@elizaos/core";
|
|
2
|
+
import {
|
|
3
|
+
createConfig,
|
|
4
|
+
EVM,
|
|
5
|
+
type ExecutionOptions,
|
|
6
|
+
type ExtendedChain,
|
|
7
|
+
executeRoute,
|
|
8
|
+
getRoutes,
|
|
9
|
+
getStatus,
|
|
10
|
+
getToken,
|
|
11
|
+
type RouteExtended,
|
|
12
|
+
resumeRoute,
|
|
13
|
+
} from "@lifi/sdk";
|
|
14
|
+
import { type Address, type Chain, parseAbi, parseUnits } from "viem";
|
|
15
|
+
import * as viemChains from "viem/chains";
|
|
16
|
+
import {
|
|
17
|
+
BRIDGE_POLL_INTERVAL_MS,
|
|
18
|
+
DEFAULT_SLIPPAGE_PERCENT,
|
|
19
|
+
MAX_BRIDGE_POLL_ATTEMPTS,
|
|
20
|
+
MAX_PRICE_IMPACT,
|
|
21
|
+
NATIVE_TOKEN_ADDRESS,
|
|
22
|
+
} from "./constants";
|
|
23
|
+
import { initWalletProvider, type WalletProvider } from "./providers/wallet";
|
|
24
|
+
import {
|
|
25
|
+
type BridgeParams,
|
|
26
|
+
EVMError,
|
|
27
|
+
EVMErrorCode,
|
|
28
|
+
type SupportedChain,
|
|
29
|
+
} from "./types";
|
|
30
|
+
import type {
|
|
31
|
+
WalletRouterContext,
|
|
32
|
+
WalletRouterExecution,
|
|
33
|
+
WalletRouterParams,
|
|
34
|
+
} from "../../types/wallet-router.js";
|
|
35
|
+
|
|
36
|
+
type LiFiGetWalletClient = NonNullable<
|
|
37
|
+
Parameters<typeof EVM>[0]
|
|
38
|
+
>["getWalletClient"];
|
|
39
|
+
type LiFiSwitchChain = NonNullable<Parameters<typeof EVM>[0]>["switchChain"];
|
|
40
|
+
|
|
41
|
+
function createLiFiGetWalletClientAdapter(
|
|
42
|
+
walletProvider: WalletProvider,
|
|
43
|
+
getFirstChain: () => string,
|
|
44
|
+
): LiFiGetWalletClient {
|
|
45
|
+
return (async () => {
|
|
46
|
+
const firstChain = getFirstChain();
|
|
47
|
+
return walletProvider.getWalletClient(firstChain as SupportedChain);
|
|
48
|
+
}) as LiFiGetWalletClient;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createLiFiSwitchChainAdapter(
|
|
52
|
+
walletProvider: WalletProvider,
|
|
53
|
+
getChainNameById: (chainId: number) => string,
|
|
54
|
+
): LiFiSwitchChain {
|
|
55
|
+
return (async (chainId: number) => {
|
|
56
|
+
const chainName = getChainNameById(chainId);
|
|
57
|
+
return walletProvider.getWalletClient(chainName as SupportedChain);
|
|
58
|
+
}) as LiFiSwitchChain;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createExecutionSwitchChainHookAdapter(
|
|
62
|
+
walletProvider: WalletProvider,
|
|
63
|
+
getChainNameById: (chainId: number) => string,
|
|
64
|
+
): ExecutionOptions["switchChainHook"] {
|
|
65
|
+
return (async (chainId: number) => {
|
|
66
|
+
const chainName = getChainNameById(chainId);
|
|
67
|
+
return walletProvider.getWalletClient(chainName as SupportedChain);
|
|
68
|
+
}) as ExecutionOptions["switchChainHook"];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface BridgeExecutionStatus {
|
|
72
|
+
readonly route: RouteExtended;
|
|
73
|
+
readonly isComplete: boolean;
|
|
74
|
+
readonly error?: string;
|
|
75
|
+
readonly transactionHashes: readonly string[];
|
|
76
|
+
readonly currentStep: number;
|
|
77
|
+
readonly totalSteps: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class BridgeAction {
|
|
81
|
+
private readonly activeRoutes: Map<string, BridgeExecutionStatus> = new Map();
|
|
82
|
+
|
|
83
|
+
constructor(private readonly walletProvider: WalletProvider) {
|
|
84
|
+
const evmProvider = EVM({
|
|
85
|
+
getWalletClient: createLiFiGetWalletClientAdapter(
|
|
86
|
+
this.walletProvider,
|
|
87
|
+
() => Object.keys(this.walletProvider.chains)[0],
|
|
88
|
+
),
|
|
89
|
+
switchChain: createLiFiSwitchChainAdapter(
|
|
90
|
+
this.walletProvider,
|
|
91
|
+
(chainId: number) => this.getChainNameById(chainId),
|
|
92
|
+
),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
createConfig({
|
|
96
|
+
integrator: "eliza-agent",
|
|
97
|
+
providers: [evmProvider],
|
|
98
|
+
chains: Object.values(this.walletProvider.chains).map((config) => ({
|
|
99
|
+
id: config.id,
|
|
100
|
+
name: config.name,
|
|
101
|
+
key: config.name.toLowerCase(),
|
|
102
|
+
chainType: "EVM",
|
|
103
|
+
nativeToken: {
|
|
104
|
+
...config.nativeCurrency,
|
|
105
|
+
chainId: config.id,
|
|
106
|
+
address: NATIVE_TOKEN_ADDRESS,
|
|
107
|
+
coinKey: config.nativeCurrency.symbol,
|
|
108
|
+
},
|
|
109
|
+
metamask: {
|
|
110
|
+
chainId: `0x${config.id.toString(16)}`,
|
|
111
|
+
chainName: config.name,
|
|
112
|
+
nativeCurrency: config.nativeCurrency,
|
|
113
|
+
rpcUrls: [config.rpcUrls.default.http[0]],
|
|
114
|
+
blockExplorerUrls: config.blockExplorers?.default?.url
|
|
115
|
+
? [config.blockExplorers.default.url]
|
|
116
|
+
: [],
|
|
117
|
+
},
|
|
118
|
+
diamondAddress: NATIVE_TOKEN_ADDRESS,
|
|
119
|
+
coin: config.nativeCurrency.symbol,
|
|
120
|
+
mainnet: true,
|
|
121
|
+
})) as ExtendedChain[],
|
|
122
|
+
routeOptions: {
|
|
123
|
+
maxPriceImpact: MAX_PRICE_IMPACT,
|
|
124
|
+
slippage: DEFAULT_SLIPPAGE_PERCENT,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private getChainNameById(chainId: number): string {
|
|
130
|
+
const chain = Object.entries(this.walletProvider.chains).find(
|
|
131
|
+
([_, config]) => config.id === chainId,
|
|
132
|
+
);
|
|
133
|
+
if (!chain) {
|
|
134
|
+
throw new EVMError(
|
|
135
|
+
EVMErrorCode.CHAIN_NOT_CONFIGURED,
|
|
136
|
+
`Chain with ID ${chainId} not found`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return chain[0];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async resolveTokenAddress(
|
|
143
|
+
tokenSymbolOrAddress: string,
|
|
144
|
+
chainId: number,
|
|
145
|
+
): Promise<string> {
|
|
146
|
+
if (
|
|
147
|
+
tokenSymbolOrAddress.startsWith("0x") &&
|
|
148
|
+
tokenSymbolOrAddress.length === 42
|
|
149
|
+
) {
|
|
150
|
+
return tokenSymbolOrAddress;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (tokenSymbolOrAddress === NATIVE_TOKEN_ADDRESS) {
|
|
154
|
+
return tokenSymbolOrAddress;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const token = await getToken(chainId, tokenSymbolOrAddress);
|
|
158
|
+
return token.address;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private async getTokenDecimals(
|
|
162
|
+
tokenAddress: string,
|
|
163
|
+
chainName: string,
|
|
164
|
+
): Promise<number> {
|
|
165
|
+
const chainConfig = this.walletProvider.getChainConfigs(
|
|
166
|
+
chainName as SupportedChain,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
tokenAddress === NATIVE_TOKEN_ADDRESS ||
|
|
171
|
+
tokenAddress.toUpperCase() ===
|
|
172
|
+
chainConfig.nativeCurrency.symbol.toUpperCase()
|
|
173
|
+
) {
|
|
174
|
+
return chainConfig.nativeCurrency.decimals;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const decimalsAbi = parseAbi(["function decimals() view returns (uint8)"]);
|
|
178
|
+
|
|
179
|
+
const publicClient = this.walletProvider.getPublicClient(
|
|
180
|
+
chainName as SupportedChain,
|
|
181
|
+
);
|
|
182
|
+
const decimals = await publicClient.readContract({
|
|
183
|
+
address: tokenAddress as Address,
|
|
184
|
+
abi: decimalsAbi,
|
|
185
|
+
functionName: "decimals",
|
|
186
|
+
authorizationList: undefined,
|
|
187
|
+
});
|
|
188
|
+
return Number(decimals);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private createExecutionOptions(routeId: string): ExecutionOptions {
|
|
192
|
+
return {
|
|
193
|
+
updateTransactionRequestHook: async (txRequest) => {
|
|
194
|
+
if (txRequest.gas) {
|
|
195
|
+
txRequest.gas = (BigInt(txRequest.gas) * BigInt(110)) / BigInt(100);
|
|
196
|
+
}
|
|
197
|
+
if (txRequest.gasPrice) {
|
|
198
|
+
txRequest.gasPrice =
|
|
199
|
+
(BigInt(txRequest.gasPrice) * BigInt(105)) / BigInt(100);
|
|
200
|
+
}
|
|
201
|
+
return txRequest;
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
acceptExchangeRateUpdateHook: async (params: {
|
|
205
|
+
toToken: { decimals: number; symbol: string };
|
|
206
|
+
oldToAmount: string;
|
|
207
|
+
newToAmount: string;
|
|
208
|
+
}) => {
|
|
209
|
+
const priceChange =
|
|
210
|
+
((Number(params.newToAmount) - Number(params.oldToAmount)) /
|
|
211
|
+
Number(params.oldToAmount)) *
|
|
212
|
+
100;
|
|
213
|
+
return Math.abs(priceChange) < 5;
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
updateRouteHook: (updatedRoute: RouteExtended) => {
|
|
217
|
+
this.updateRouteStatus(routeId, updatedRoute);
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
switchChainHook: createExecutionSwitchChainHookAdapter(
|
|
221
|
+
this.walletProvider,
|
|
222
|
+
(chainId: number) => this.getChainNameById(chainId),
|
|
223
|
+
),
|
|
224
|
+
|
|
225
|
+
executeInBackground: false,
|
|
226
|
+
disableMessageSigning: false,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private updateRouteStatus(
|
|
231
|
+
routeId: string,
|
|
232
|
+
route: RouteExtended,
|
|
233
|
+
): BridgeExecutionStatus {
|
|
234
|
+
const transactionHashes: string[] = [];
|
|
235
|
+
let currentStep = 0;
|
|
236
|
+
let isComplete = false;
|
|
237
|
+
let error: string | undefined;
|
|
238
|
+
|
|
239
|
+
route.steps.forEach((step, stepIndex) => {
|
|
240
|
+
const stepExecution = step.execution;
|
|
241
|
+
if (stepExecution?.process) {
|
|
242
|
+
stepExecution.process.forEach((process) => {
|
|
243
|
+
if (process.txHash) {
|
|
244
|
+
transactionHashes.push(process.txHash);
|
|
245
|
+
}
|
|
246
|
+
if (process.status === "DONE") {
|
|
247
|
+
currentStep = Math.max(currentStep, stepIndex + 1);
|
|
248
|
+
}
|
|
249
|
+
if (process.status === "FAILED") {
|
|
250
|
+
error = `Step ${stepIndex + 1} failed: ${
|
|
251
|
+
process.error ?? "Unknown error"
|
|
252
|
+
}`;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
isComplete = currentStep === route.steps.length && !error;
|
|
259
|
+
|
|
260
|
+
const status: BridgeExecutionStatus = {
|
|
261
|
+
route,
|
|
262
|
+
isComplete,
|
|
263
|
+
error,
|
|
264
|
+
transactionHashes,
|
|
265
|
+
currentStep,
|
|
266
|
+
totalSteps: route.steps.length,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
this.activeRoutes.set(routeId, status);
|
|
270
|
+
return status;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private async pollBridgeStatus(
|
|
274
|
+
txHash: string,
|
|
275
|
+
fromChainId: number,
|
|
276
|
+
toChainId: number,
|
|
277
|
+
tool: string,
|
|
278
|
+
routeId: string,
|
|
279
|
+
): Promise<BridgeExecutionStatus> {
|
|
280
|
+
for (let attempt = 1; attempt <= MAX_BRIDGE_POLL_ATTEMPTS; attempt++) {
|
|
281
|
+
await new Promise((resolve) =>
|
|
282
|
+
setTimeout(resolve, BRIDGE_POLL_INTERVAL_MS),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const status = await getStatus({
|
|
286
|
+
txHash,
|
|
287
|
+
fromChain: fromChainId,
|
|
288
|
+
toChain: toChainId,
|
|
289
|
+
bridge: tool,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const routeStatus = this.activeRoutes.get(routeId);
|
|
293
|
+
if (!routeStatus) {
|
|
294
|
+
throw new EVMError(
|
|
295
|
+
EVMErrorCode.INVALID_PARAMS,
|
|
296
|
+
`Route ${routeId} not found`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let isComplete = false;
|
|
301
|
+
let error: string | undefined;
|
|
302
|
+
|
|
303
|
+
if (status.status === "DONE") {
|
|
304
|
+
isComplete = true;
|
|
305
|
+
} else if (status.status === "FAILED") {
|
|
306
|
+
error = `Bridge failed: ${status.substatus ?? "Unknown error"}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const updatedStatus: BridgeExecutionStatus = {
|
|
310
|
+
...routeStatus,
|
|
311
|
+
isComplete,
|
|
312
|
+
error,
|
|
313
|
+
currentStep: isComplete
|
|
314
|
+
? routeStatus.totalSteps
|
|
315
|
+
: routeStatus.currentStep,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
this.activeRoutes.set(routeId, updatedStatus);
|
|
319
|
+
|
|
320
|
+
if (isComplete || error) {
|
|
321
|
+
return updatedStatus;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const routeStatus = this.activeRoutes.get(routeId);
|
|
326
|
+
if (routeStatus) {
|
|
327
|
+
const timeoutStatus: BridgeExecutionStatus = {
|
|
328
|
+
...routeStatus,
|
|
329
|
+
error: `Bridge status polling timed out after ${
|
|
330
|
+
(MAX_BRIDGE_POLL_ATTEMPTS * BRIDGE_POLL_INTERVAL_MS) / 1000
|
|
331
|
+
}s`,
|
|
332
|
+
};
|
|
333
|
+
this.activeRoutes.set(routeId, timeoutStatus);
|
|
334
|
+
return timeoutStatus;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
throw new EVMError(
|
|
338
|
+
EVMErrorCode.NETWORK_ERROR,
|
|
339
|
+
"Route status polling failed",
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async getQuote(params: BridgeParams) {
|
|
344
|
+
const fromChainConfig = this.walletProvider.getChainConfigs(
|
|
345
|
+
params.fromChain,
|
|
346
|
+
);
|
|
347
|
+
const toChainConfig = this.walletProvider.getChainConfigs(params.toChain);
|
|
348
|
+
|
|
349
|
+
const resolvedFromToken = await this.resolveTokenAddress(
|
|
350
|
+
params.fromToken,
|
|
351
|
+
fromChainConfig.id,
|
|
352
|
+
);
|
|
353
|
+
const resolvedToToken = await this.resolveTokenAddress(
|
|
354
|
+
params.toToken,
|
|
355
|
+
toChainConfig.id,
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const fromTokenDecimals = await this.getTokenDecimals(
|
|
359
|
+
resolvedFromToken,
|
|
360
|
+
params.fromChain,
|
|
361
|
+
);
|
|
362
|
+
const fromAmountParsed = parseUnits(params.amount, fromTokenDecimals);
|
|
363
|
+
|
|
364
|
+
const walletClient = this.walletProvider.getWalletClient(params.fromChain);
|
|
365
|
+
const [fromAddress] = await walletClient.getAddresses();
|
|
366
|
+
|
|
367
|
+
const routesResult = await getRoutes({
|
|
368
|
+
fromChainId: fromChainConfig.id,
|
|
369
|
+
toChainId: toChainConfig.id,
|
|
370
|
+
fromTokenAddress: resolvedFromToken,
|
|
371
|
+
toTokenAddress: resolvedToToken,
|
|
372
|
+
fromAmount: fromAmountParsed.toString(),
|
|
373
|
+
fromAddress,
|
|
374
|
+
toAddress: params.toAddress ?? fromAddress,
|
|
375
|
+
options: {
|
|
376
|
+
order: "RECOMMENDED",
|
|
377
|
+
slippage: DEFAULT_SLIPPAGE_PERCENT,
|
|
378
|
+
maxPriceImpact: MAX_PRICE_IMPACT,
|
|
379
|
+
allowSwitchChain: true,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
routes: routesResult.routes,
|
|
385
|
+
fromChainId: fromChainConfig.id,
|
|
386
|
+
toChainId: toChainConfig.id,
|
|
387
|
+
resolvedFromToken,
|
|
388
|
+
resolvedToToken,
|
|
389
|
+
fromAmountParsed,
|
|
390
|
+
fromAddress,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async bridge(params: BridgeParams) {
|
|
395
|
+
const amount = parseFloat(params.amount);
|
|
396
|
+
if (Number.isNaN(amount) || amount <= 0) {
|
|
397
|
+
throw new EVMError(
|
|
398
|
+
EVMErrorCode.INVALID_PARAMS,
|
|
399
|
+
"Amount must be a positive number",
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (params.fromChain === params.toChain) {
|
|
404
|
+
throw new EVMError(
|
|
405
|
+
EVMErrorCode.INVALID_PARAMS,
|
|
406
|
+
"Source and destination chains must be different for bridging",
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (
|
|
411
|
+
params.toAddress &&
|
|
412
|
+
(!params.toAddress.startsWith("0x") || params.toAddress.length !== 42)
|
|
413
|
+
) {
|
|
414
|
+
throw new EVMError(
|
|
415
|
+
EVMErrorCode.INVALID_PARAMS,
|
|
416
|
+
`Invalid recipient address: ${params.toAddress}`,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const quote = await this.getQuote(params);
|
|
421
|
+
|
|
422
|
+
if (!quote.routes.length) {
|
|
423
|
+
throw new EVMError(
|
|
424
|
+
EVMErrorCode.INVALID_PARAMS,
|
|
425
|
+
`No bridge routes found for ${params.fromToken} on ${params.fromChain} to ${params.toToken} on ${params.toChain}`,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const selectedRoute = quote.routes[0];
|
|
430
|
+
const routeId = `bridge_${Date.now()}_${Math.random()
|
|
431
|
+
.toString(36)
|
|
432
|
+
.slice(2, 11)}`;
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const executionOptions = this.createExecutionOptions(routeId);
|
|
436
|
+
const executedRoute = await executeRoute(selectedRoute, executionOptions);
|
|
437
|
+
|
|
438
|
+
const sourceSteps = executedRoute.steps.filter((step) =>
|
|
439
|
+
step.execution?.process?.some((p) => p.txHash),
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
if (!sourceSteps.length) {
|
|
443
|
+
throw new EVMError(
|
|
444
|
+
EVMErrorCode.NETWORK_ERROR,
|
|
445
|
+
"No transaction hashes found",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const mainTxHash = sourceSteps[0]?.execution?.process?.find(
|
|
450
|
+
(p) => p.txHash,
|
|
451
|
+
)?.txHash;
|
|
452
|
+
|
|
453
|
+
if (!mainTxHash) {
|
|
454
|
+
throw new EVMError(
|
|
455
|
+
EVMErrorCode.NETWORK_ERROR,
|
|
456
|
+
"No transaction hash found",
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const bridgeTool = selectedRoute.steps[0].tool;
|
|
461
|
+
const finalStatus = await this.pollBridgeStatus(
|
|
462
|
+
mainTxHash,
|
|
463
|
+
quote.fromChainId,
|
|
464
|
+
quote.toChainId,
|
|
465
|
+
bridgeTool,
|
|
466
|
+
routeId,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
if (finalStatus.error) {
|
|
470
|
+
throw new EVMError(EVMErrorCode.CONTRACT_REVERT, finalStatus.error);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
hash: mainTxHash as `0x${string}`,
|
|
475
|
+
from: quote.fromAddress,
|
|
476
|
+
to: (params.toAddress ?? quote.fromAddress) as `0x${string}`,
|
|
477
|
+
value: quote.fromAmountParsed,
|
|
478
|
+
chainId: quote.toChainId,
|
|
479
|
+
route: selectedRoute,
|
|
480
|
+
};
|
|
481
|
+
} finally {
|
|
482
|
+
this.activeRoutes.delete(routeId);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
async resumeBridge(route: RouteExtended) {
|
|
487
|
+
const routeId = `resume_${Date.now()}_${Math.random()
|
|
488
|
+
.toString(36)
|
|
489
|
+
.slice(2, 11)}`;
|
|
490
|
+
const executionOptions = this.createExecutionOptions(routeId);
|
|
491
|
+
try {
|
|
492
|
+
return await resumeRoute(route, executionOptions);
|
|
493
|
+
} finally {
|
|
494
|
+
this.activeRoutes.delete(routeId);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export async function checkBridgeStatus(
|
|
500
|
+
txHash: string,
|
|
501
|
+
fromChainId: number,
|
|
502
|
+
toChainId: number,
|
|
503
|
+
tool: string = "stargateV2Bus",
|
|
504
|
+
) {
|
|
505
|
+
const status = await getStatus({
|
|
506
|
+
txHash,
|
|
507
|
+
fromChain: fromChainId,
|
|
508
|
+
toChain: toChainId,
|
|
509
|
+
bridge: tool,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
status: status.status,
|
|
514
|
+
substatus: status.substatus,
|
|
515
|
+
isComplete: status.status === "DONE",
|
|
516
|
+
isFailed: status.status === "FAILED",
|
|
517
|
+
isPending: status.status === "PENDING",
|
|
518
|
+
error: status.status === "FAILED" ? status.substatus : undefined,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function viemChainByName(name: string): Chain | null {
|
|
523
|
+
const chain = (viemChains as Record<string, Chain | undefined>)[name];
|
|
524
|
+
return chain?.id ? chain : null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export function validateWalletBridgeParams(
|
|
528
|
+
params: WalletRouterParams,
|
|
529
|
+
): string | null {
|
|
530
|
+
if (!params.amount) {
|
|
531
|
+
return "amount is required for bridge.";
|
|
532
|
+
}
|
|
533
|
+
if (!params.fromToken) {
|
|
534
|
+
return "fromToken is required for bridge.";
|
|
535
|
+
}
|
|
536
|
+
if (!params.chain) {
|
|
537
|
+
return "chain (source) is required for bridge.";
|
|
538
|
+
}
|
|
539
|
+
if (!params.toChain) {
|
|
540
|
+
return "toChain (destination) is required for bridge.";
|
|
541
|
+
}
|
|
542
|
+
if (params.chain === params.toChain) {
|
|
543
|
+
return "Source and destination chains must be different for bridge.";
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export async function routeEvmBridge(
|
|
549
|
+
params: WalletRouterParams,
|
|
550
|
+
context: WalletRouterContext,
|
|
551
|
+
fromChainKey: string,
|
|
552
|
+
fromChain: Chain,
|
|
553
|
+
): Promise<WalletRouterExecution> {
|
|
554
|
+
const validationError = validateWalletBridgeParams(params);
|
|
555
|
+
if (validationError) {
|
|
556
|
+
throw new Error(validationError);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const toChainKey = params.toChain as string;
|
|
560
|
+
const toChain = viemChainByName(toChainKey);
|
|
561
|
+
if (!toChain) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
`Unsupported destination chain "${toChainKey}" for bridge.`,
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (params.mode === "prepare" || params.dryRun) {
|
|
568
|
+
let routeMetadata: Record<string, unknown> | undefined;
|
|
569
|
+
try {
|
|
570
|
+
const walletProvider = await initWalletProvider(context.runtime);
|
|
571
|
+
const action = new BridgeAction(walletProvider);
|
|
572
|
+
const quote = await action.getQuote({
|
|
573
|
+
fromChain: fromChainKey as SupportedChain,
|
|
574
|
+
toChain: toChainKey as SupportedChain,
|
|
575
|
+
fromToken: params.fromToken as Address,
|
|
576
|
+
toToken: (params.toToken ?? params.fromToken) as Address,
|
|
577
|
+
amount: params.amount as string,
|
|
578
|
+
toAddress: params.recipient as Address | undefined,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const topRoute = quote.routes[0];
|
|
582
|
+
routeMetadata = topRoute
|
|
583
|
+
? {
|
|
584
|
+
tool: topRoute.steps[0]?.tool,
|
|
585
|
+
steps: topRoute.steps.length,
|
|
586
|
+
fromAmount: topRoute.fromAmount,
|
|
587
|
+
toAmount: topRoute.toAmount,
|
|
588
|
+
fromChainId: topRoute.fromChainId,
|
|
589
|
+
toChainId: topRoute.toChainId,
|
|
590
|
+
gasCostUSD: topRoute.gasCostUSD,
|
|
591
|
+
}
|
|
592
|
+
: { routes: 0 };
|
|
593
|
+
} catch (error) {
|
|
594
|
+
routeMetadata = {
|
|
595
|
+
quoteUnavailable:
|
|
596
|
+
error instanceof Error ? error.message : String(error),
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
status: "prepared",
|
|
602
|
+
chain: fromChainKey,
|
|
603
|
+
chainId: String(fromChain.id),
|
|
604
|
+
subaction: "bridge",
|
|
605
|
+
dryRun: params.dryRun,
|
|
606
|
+
mode: params.mode,
|
|
607
|
+
amount: params.amount,
|
|
608
|
+
fromToken: params.fromToken,
|
|
609
|
+
toToken: params.toToken,
|
|
610
|
+
to: params.recipient,
|
|
611
|
+
metadata: {
|
|
612
|
+
fromChain: fromChainKey,
|
|
613
|
+
fromChainId: fromChain.id,
|
|
614
|
+
toChain: toChainKey,
|
|
615
|
+
toChainId: toChain.id,
|
|
616
|
+
recipient: params.recipient,
|
|
617
|
+
lifiQuote: routeMetadata,
|
|
618
|
+
requiresConfirmation: true,
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const walletProvider = await initWalletProvider(context.runtime);
|
|
624
|
+
const action = new BridgeAction(walletProvider);
|
|
625
|
+
const tx = await action.bridge({
|
|
626
|
+
fromChain: fromChainKey as SupportedChain,
|
|
627
|
+
toChain: toChainKey as SupportedChain,
|
|
628
|
+
fromToken: params.fromToken as Address,
|
|
629
|
+
toToken: (params.toToken ?? params.fromToken) as Address,
|
|
630
|
+
amount: params.amount as string,
|
|
631
|
+
toAddress: params.recipient as Address | undefined,
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
logger.debug(
|
|
635
|
+
{ fromChainKey, toChainKey, hash: tx.hash },
|
|
636
|
+
"[plugin-wallet] Bridge submitted",
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
return {
|
|
640
|
+
status: "submitted",
|
|
641
|
+
chain: fromChainKey,
|
|
642
|
+
chainId: String(fromChain.id),
|
|
643
|
+
subaction: "bridge",
|
|
644
|
+
dryRun: false,
|
|
645
|
+
mode: params.mode,
|
|
646
|
+
transactionHash: tx.hash,
|
|
647
|
+
from: tx.from,
|
|
648
|
+
to: tx.to,
|
|
649
|
+
amount: params.amount,
|
|
650
|
+
fromToken: params.fromToken,
|
|
651
|
+
toToken: params.toToken ?? params.fromToken,
|
|
652
|
+
metadata: {
|
|
653
|
+
fromChain: fromChainKey,
|
|
654
|
+
fromChainId: fromChain.id,
|
|
655
|
+
toChain: toChainKey,
|
|
656
|
+
toChainId: toChain.id,
|
|
657
|
+
tool: tx.route.steps[0]?.tool,
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
}
|