@elizaos/plugin-aave 1.0.0
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/dist/index.d.ts +590 -0
- package/dist/index.js +2923 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2923 @@
|
|
|
1
|
+
import { Service, elizaLogger, ModelType } from '@elizaos/core';
|
|
2
|
+
import { Pool, UiPoolDataProvider } from '@aave/contract-helpers';
|
|
3
|
+
import { formatReserves, formatUserSummary } from '@aave/math-utils';
|
|
4
|
+
import { ethers } from 'ethers';
|
|
5
|
+
import BigNumber3, { BigNumber } from 'bignumber.js';
|
|
6
|
+
import { AaveV3BaseSepolia, AaveV3OptimismSepolia, AaveV3ArbitrumSepolia, AaveV3Fuji, AaveV3Sepolia, AaveV3ZkSync, AaveV3Scroll, AaveV3Metis, AaveV3Gnosis, AaveV3BNB, AaveV3Base, AaveV3Optimism, AaveV3Arbitrum, AaveV3Avalanche, AaveV3Polygon, AaveV3Ethereum } from '@bgd-labs/aave-address-book';
|
|
7
|
+
|
|
8
|
+
// src/services/aave-service.ts
|
|
9
|
+
|
|
10
|
+
// src/types/chains.ts
|
|
11
|
+
var SupportedChain = /* @__PURE__ */ ((SupportedChain2) => {
|
|
12
|
+
SupportedChain2["ETHEREUM"] = "ethereum";
|
|
13
|
+
SupportedChain2["POLYGON"] = "polygon";
|
|
14
|
+
SupportedChain2["AVALANCHE"] = "avalanche";
|
|
15
|
+
SupportedChain2["ARBITRUM"] = "arbitrum";
|
|
16
|
+
SupportedChain2["OPTIMISM"] = "optimism";
|
|
17
|
+
SupportedChain2["BASE"] = "base";
|
|
18
|
+
SupportedChain2["BNB"] = "bnb";
|
|
19
|
+
SupportedChain2["GNOSIS"] = "gnosis";
|
|
20
|
+
SupportedChain2["METIS"] = "metis";
|
|
21
|
+
SupportedChain2["SCROLL"] = "scroll";
|
|
22
|
+
SupportedChain2["ZKSYNC"] = "zksync";
|
|
23
|
+
SupportedChain2["SEPOLIA"] = "sepolia";
|
|
24
|
+
SupportedChain2["FUJI"] = "fuji";
|
|
25
|
+
SupportedChain2["ARBITRUM_SEPOLIA"] = "arbitrum-sepolia";
|
|
26
|
+
SupportedChain2["OPTIMISM_SEPOLIA"] = "optimism-sepolia";
|
|
27
|
+
SupportedChain2["BASE_SEPOLIA"] = "base-sepolia";
|
|
28
|
+
return SupportedChain2;
|
|
29
|
+
})(SupportedChain || {});
|
|
30
|
+
var CHAIN_CONFIGS = {
|
|
31
|
+
["ethereum" /* ETHEREUM */]: {
|
|
32
|
+
name: "Ethereum",
|
|
33
|
+
chainId: 1,
|
|
34
|
+
nativeCurrency: "ETH",
|
|
35
|
+
wrappedNative: "WETH",
|
|
36
|
+
defaultRpcUrl: "https://eth.llamarpc.com",
|
|
37
|
+
isTestnet: false,
|
|
38
|
+
popularAssets: ["USDC", "USDT", "DAI", "WETH", "WBTC", "LINK", "AAVE"]
|
|
39
|
+
},
|
|
40
|
+
["polygon" /* POLYGON */]: {
|
|
41
|
+
name: "Polygon",
|
|
42
|
+
chainId: 137,
|
|
43
|
+
nativeCurrency: "MATIC",
|
|
44
|
+
wrappedNative: "WMATIC",
|
|
45
|
+
defaultRpcUrl: "https://polygon-rpc.com",
|
|
46
|
+
isTestnet: false,
|
|
47
|
+
popularAssets: ["USDC", "USDT", "DAI", "WMATIC", "WETH", "WBTC", "AAVE"]
|
|
48
|
+
},
|
|
49
|
+
["avalanche" /* AVALANCHE */]: {
|
|
50
|
+
name: "Avalanche",
|
|
51
|
+
chainId: 43114,
|
|
52
|
+
nativeCurrency: "AVAX",
|
|
53
|
+
wrappedNative: "WAVAX",
|
|
54
|
+
defaultRpcUrl: "https://api.avax.network/ext/bc/C/rpc",
|
|
55
|
+
isTestnet: false,
|
|
56
|
+
popularAssets: ["USDC", "USDT", "DAI.e", "WAVAX", "WETH.e", "WBTC.e", "AAVE.e"]
|
|
57
|
+
},
|
|
58
|
+
["arbitrum" /* ARBITRUM */]: {
|
|
59
|
+
name: "Arbitrum One",
|
|
60
|
+
chainId: 42161,
|
|
61
|
+
nativeCurrency: "ETH",
|
|
62
|
+
wrappedNative: "WETH",
|
|
63
|
+
defaultRpcUrl: "https://arb1.arbitrum.io/rpc",
|
|
64
|
+
isTestnet: false,
|
|
65
|
+
popularAssets: ["USDC", "USDT", "DAI", "WETH", "WBTC", "LINK", "ARB"]
|
|
66
|
+
},
|
|
67
|
+
["optimism" /* OPTIMISM */]: {
|
|
68
|
+
name: "Optimism",
|
|
69
|
+
chainId: 10,
|
|
70
|
+
nativeCurrency: "ETH",
|
|
71
|
+
wrappedNative: "WETH",
|
|
72
|
+
defaultRpcUrl: "https://mainnet.optimism.io",
|
|
73
|
+
isTestnet: false,
|
|
74
|
+
popularAssets: ["USDC", "USDT", "DAI", "WETH", "WBTC", "LINK", "OP"]
|
|
75
|
+
},
|
|
76
|
+
["base" /* BASE */]: {
|
|
77
|
+
name: "Base",
|
|
78
|
+
chainId: 8453,
|
|
79
|
+
nativeCurrency: "ETH",
|
|
80
|
+
wrappedNative: "WETH",
|
|
81
|
+
defaultRpcUrl: "https://mainnet.base.org",
|
|
82
|
+
isTestnet: false,
|
|
83
|
+
popularAssets: ["USDC", "DAI", "WETH", "cbETH", "AERO"]
|
|
84
|
+
},
|
|
85
|
+
["bnb" /* BNB */]: {
|
|
86
|
+
name: "BNB Chain",
|
|
87
|
+
chainId: 56,
|
|
88
|
+
nativeCurrency: "BNB",
|
|
89
|
+
wrappedNative: "WBNB",
|
|
90
|
+
defaultRpcUrl: "https://bsc-dataseed1.binance.org",
|
|
91
|
+
isTestnet: false,
|
|
92
|
+
popularAssets: ["USDC", "USDT", "WBNB", "BTCB", "ETH", "FDUSD"]
|
|
93
|
+
},
|
|
94
|
+
["gnosis" /* GNOSIS */]: {
|
|
95
|
+
name: "Gnosis Chain",
|
|
96
|
+
chainId: 100,
|
|
97
|
+
nativeCurrency: "xDAI",
|
|
98
|
+
wrappedNative: "WXDAI",
|
|
99
|
+
defaultRpcUrl: "https://rpc.gnosischain.com",
|
|
100
|
+
isTestnet: false,
|
|
101
|
+
popularAssets: ["USDC", "WXDAI", "WETH", "GNO"]
|
|
102
|
+
},
|
|
103
|
+
["metis" /* METIS */]: {
|
|
104
|
+
name: "Metis",
|
|
105
|
+
chainId: 1088,
|
|
106
|
+
nativeCurrency: "METIS",
|
|
107
|
+
wrappedNative: "WMETIS",
|
|
108
|
+
defaultRpcUrl: "https://andromeda.metis.io/?owner=1088",
|
|
109
|
+
isTestnet: false,
|
|
110
|
+
popularAssets: ["m.USDC", "m.USDT", "m.DAI", "WMETIS", "METIS"]
|
|
111
|
+
},
|
|
112
|
+
["scroll" /* SCROLL */]: {
|
|
113
|
+
name: "Scroll",
|
|
114
|
+
chainId: 534352,
|
|
115
|
+
nativeCurrency: "ETH",
|
|
116
|
+
wrappedNative: "WETH",
|
|
117
|
+
defaultRpcUrl: "https://rpc.scroll.io",
|
|
118
|
+
isTestnet: false,
|
|
119
|
+
popularAssets: ["USDC", "USDT", "WETH", "WBTC"]
|
|
120
|
+
},
|
|
121
|
+
["zksync" /* ZKSYNC */]: {
|
|
122
|
+
name: "zkSync Era",
|
|
123
|
+
chainId: 324,
|
|
124
|
+
nativeCurrency: "ETH",
|
|
125
|
+
wrappedNative: "WETH",
|
|
126
|
+
defaultRpcUrl: "https://mainnet.era.zksync.io",
|
|
127
|
+
isTestnet: false,
|
|
128
|
+
popularAssets: ["USDC", "USDT", "WETH", "WBTC"]
|
|
129
|
+
},
|
|
130
|
+
// Testnets
|
|
131
|
+
["sepolia" /* SEPOLIA */]: {
|
|
132
|
+
name: "Ethereum Sepolia",
|
|
133
|
+
chainId: 11155111,
|
|
134
|
+
nativeCurrency: "ETH",
|
|
135
|
+
wrappedNative: "WETH",
|
|
136
|
+
defaultRpcUrl: "https://sepolia.infura.io/v3/demo",
|
|
137
|
+
isTestnet: true,
|
|
138
|
+
popularAssets: ["USDC", "USDT", "DAI", "WETH", "LINK", "AAVE"]
|
|
139
|
+
},
|
|
140
|
+
["fuji" /* FUJI */]: {
|
|
141
|
+
name: "Avalanche Fuji",
|
|
142
|
+
chainId: 43113,
|
|
143
|
+
nativeCurrency: "AVAX",
|
|
144
|
+
wrappedNative: "WAVAX",
|
|
145
|
+
defaultRpcUrl: "https://api.avax-test.network/ext/bc/C/rpc",
|
|
146
|
+
isTestnet: true,
|
|
147
|
+
popularAssets: ["USDC", "WAVAX", "WETH.e"]
|
|
148
|
+
},
|
|
149
|
+
["arbitrum-sepolia" /* ARBITRUM_SEPOLIA */]: {
|
|
150
|
+
name: "Arbitrum Sepolia",
|
|
151
|
+
chainId: 421614,
|
|
152
|
+
nativeCurrency: "ETH",
|
|
153
|
+
wrappedNative: "WETH",
|
|
154
|
+
defaultRpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
|
|
155
|
+
isTestnet: true,
|
|
156
|
+
popularAssets: ["USDC", "WETH"]
|
|
157
|
+
},
|
|
158
|
+
["optimism-sepolia" /* OPTIMISM_SEPOLIA */]: {
|
|
159
|
+
name: "Optimism Sepolia",
|
|
160
|
+
chainId: 11155420,
|
|
161
|
+
nativeCurrency: "ETH",
|
|
162
|
+
wrappedNative: "WETH",
|
|
163
|
+
defaultRpcUrl: "https://sepolia.optimism.io",
|
|
164
|
+
isTestnet: true,
|
|
165
|
+
popularAssets: ["USDC", "WETH"]
|
|
166
|
+
},
|
|
167
|
+
["base-sepolia" /* BASE_SEPOLIA */]: {
|
|
168
|
+
name: "Base Sepolia",
|
|
169
|
+
chainId: 84532,
|
|
170
|
+
nativeCurrency: "ETH",
|
|
171
|
+
wrappedNative: "WETH",
|
|
172
|
+
defaultRpcUrl: "https://sepolia.base.org",
|
|
173
|
+
isTestnet: true,
|
|
174
|
+
popularAssets: ["USDC", "WETH"]
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var InterestRateMode = /* @__PURE__ */ ((InterestRateMode2) => {
|
|
178
|
+
InterestRateMode2[InterestRateMode2["STABLE"] = 1] = "STABLE";
|
|
179
|
+
InterestRateMode2[InterestRateMode2["VARIABLE"] = 2] = "VARIABLE";
|
|
180
|
+
return InterestRateMode2;
|
|
181
|
+
})(InterestRateMode || {});
|
|
182
|
+
var AaveErrorCode = /* @__PURE__ */ ((AaveErrorCode2) => {
|
|
183
|
+
AaveErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
184
|
+
AaveErrorCode2["INSUFFICIENT_BALANCE"] = "INSUFFICIENT_BALANCE";
|
|
185
|
+
AaveErrorCode2["INSUFFICIENT_COLLATERAL"] = "INSUFFICIENT_COLLATERAL";
|
|
186
|
+
AaveErrorCode2["ASSET_NOT_SUPPORTED"] = "ASSET_NOT_SUPPORTED";
|
|
187
|
+
AaveErrorCode2["BORROWING_NOT_ENABLED"] = "BORROWING_NOT_ENABLED";
|
|
188
|
+
AaveErrorCode2["STABLE_BORROWING_NOT_ENABLED"] = "STABLE_BORROWING_NOT_ENABLED";
|
|
189
|
+
AaveErrorCode2["HEALTH_FACTOR_TOO_LOW"] = "HEALTH_FACTOR_TOO_LOW";
|
|
190
|
+
AaveErrorCode2["TRANSACTION_FAILED"] = "TRANSACTION_FAILED";
|
|
191
|
+
AaveErrorCode2["INVALID_PARAMETERS"] = "INVALID_PARAMETERS";
|
|
192
|
+
AaveErrorCode2["USER_HAS_STABLE_RATE_LOAN"] = "USER_HAS_STABLE_RATE_LOAN";
|
|
193
|
+
AaveErrorCode2["AMOUNT_TOO_HIGH"] = "AMOUNT_TOO_HIGH";
|
|
194
|
+
AaveErrorCode2["POOL_PAUSED"] = "POOL_PAUSED";
|
|
195
|
+
AaveErrorCode2["RESERVE_FROZEN"] = "RESERVE_FROZEN";
|
|
196
|
+
AaveErrorCode2["RESERVE_INACTIVE"] = "RESERVE_INACTIVE";
|
|
197
|
+
AaveErrorCode2["INITIALIZATION_FAILED"] = "INITIALIZATION_FAILED";
|
|
198
|
+
AaveErrorCode2["CONNECTION_FAILED"] = "CONNECTION_FAILED";
|
|
199
|
+
AaveErrorCode2["SERVICE_NOT_INITIALIZED"] = "SERVICE_NOT_INITIALIZED";
|
|
200
|
+
AaveErrorCode2["WALLET_NOT_CONNECTED"] = "WALLET_NOT_CONNECTED";
|
|
201
|
+
AaveErrorCode2["TRANSACTION_GENERATION_FAILED"] = "TRANSACTION_GENERATION_FAILED";
|
|
202
|
+
AaveErrorCode2["SUPPLY_FAILED"] = "SUPPLY_FAILED";
|
|
203
|
+
AaveErrorCode2["WITHDRAW_FAILED"] = "WITHDRAW_FAILED";
|
|
204
|
+
AaveErrorCode2["BORROW_FAILED"] = "BORROW_FAILED";
|
|
205
|
+
AaveErrorCode2["REPAY_FAILED"] = "REPAY_FAILED";
|
|
206
|
+
AaveErrorCode2["DATA_FETCH_FAILED"] = "DATA_FETCH_FAILED";
|
|
207
|
+
AaveErrorCode2["ASSET_NOT_FOUND"] = "ASSET_NOT_FOUND";
|
|
208
|
+
AaveErrorCode2["UNSUPPORTED_ASSET"] = "UNSUPPORTED_ASSET";
|
|
209
|
+
AaveErrorCode2["NO_BORROW_CAPACITY"] = "NO_BORROW_CAPACITY";
|
|
210
|
+
AaveErrorCode2["UNSUPPORTED_OPERATION"] = "UNSUPPORTED_OPERATION";
|
|
211
|
+
return AaveErrorCode2;
|
|
212
|
+
})(AaveErrorCode || {});
|
|
213
|
+
var AaveError = class extends Error {
|
|
214
|
+
/** Error code for programmatic handling */
|
|
215
|
+
code;
|
|
216
|
+
/** Original error that caused this error */
|
|
217
|
+
cause;
|
|
218
|
+
/** Additional context about the error */
|
|
219
|
+
context;
|
|
220
|
+
constructor(message, code = "UNKNOWN" /* UNKNOWN */, cause, context) {
|
|
221
|
+
super(message);
|
|
222
|
+
this.name = "AaveError";
|
|
223
|
+
this.code = code;
|
|
224
|
+
this.cause = cause;
|
|
225
|
+
this.context = context;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var AAVE_CONSTANTS = {
|
|
229
|
+
/** Maximum uint256 value for unlimited approvals/withdrawals */
|
|
230
|
+
MAX_UINT256: new BigNumber("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
|
231
|
+
/** Number of seconds in a year for APY calculations */
|
|
232
|
+
SECONDS_PER_YEAR: new BigNumber(31536e3),
|
|
233
|
+
/** Number of ray units (1e27) used by Aave for rate calculations */
|
|
234
|
+
RAY: new BigNumber("1000000000000000000000000000"),
|
|
235
|
+
/** Number of wei units (1e18) */
|
|
236
|
+
WAD: new BigNumber("1000000000000000000"),
|
|
237
|
+
/** Percentage multiplier (100 for percentage) */
|
|
238
|
+
PERCENTAGE_FACTOR: new BigNumber(1e4)
|
|
239
|
+
};
|
|
240
|
+
function isAaveError(error) {
|
|
241
|
+
return error instanceof AaveError;
|
|
242
|
+
}
|
|
243
|
+
function isValidInterestRateMode(mode) {
|
|
244
|
+
return mode === 1 /* STABLE */ || mode === 2 /* VARIABLE */;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/utils/chain-resolver.ts
|
|
248
|
+
var CHAIN_ADDRESS_MAP = {
|
|
249
|
+
// Mainnets
|
|
250
|
+
["ethereum" /* ETHEREUM */]: {
|
|
251
|
+
POOL: AaveV3Ethereum.POOL,
|
|
252
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Ethereum.POOL_ADDRESSES_PROVIDER,
|
|
253
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Ethereum.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
254
|
+
UI_POOL_DATA_PROVIDER: AaveV3Ethereum.UI_POOL_DATA_PROVIDER,
|
|
255
|
+
WETH_GATEWAY: AaveV3Ethereum.WETH_GATEWAY,
|
|
256
|
+
ACL_MANAGER: AaveV3Ethereum.ACL_MANAGER,
|
|
257
|
+
COLLECTOR: AaveV3Ethereum.COLLECTOR,
|
|
258
|
+
ORACLE: AaveV3Ethereum.ORACLE
|
|
259
|
+
},
|
|
260
|
+
["polygon" /* POLYGON */]: {
|
|
261
|
+
POOL: AaveV3Polygon.POOL,
|
|
262
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Polygon.POOL_ADDRESSES_PROVIDER,
|
|
263
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Polygon.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
264
|
+
UI_POOL_DATA_PROVIDER: AaveV3Polygon.UI_POOL_DATA_PROVIDER,
|
|
265
|
+
WETH_GATEWAY: AaveV3Polygon.WETH_GATEWAY,
|
|
266
|
+
ACL_MANAGER: AaveV3Polygon.ACL_MANAGER,
|
|
267
|
+
COLLECTOR: AaveV3Polygon.COLLECTOR,
|
|
268
|
+
ORACLE: AaveV3Polygon.ORACLE
|
|
269
|
+
},
|
|
270
|
+
["avalanche" /* AVALANCHE */]: {
|
|
271
|
+
POOL: AaveV3Avalanche.POOL,
|
|
272
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Avalanche.POOL_ADDRESSES_PROVIDER,
|
|
273
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Avalanche.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
274
|
+
UI_POOL_DATA_PROVIDER: AaveV3Avalanche.UI_POOL_DATA_PROVIDER,
|
|
275
|
+
WETH_GATEWAY: AaveV3Avalanche.WETH_GATEWAY,
|
|
276
|
+
ACL_MANAGER: AaveV3Avalanche.ACL_MANAGER,
|
|
277
|
+
COLLECTOR: AaveV3Avalanche.COLLECTOR,
|
|
278
|
+
ORACLE: AaveV3Avalanche.ORACLE
|
|
279
|
+
},
|
|
280
|
+
["arbitrum" /* ARBITRUM */]: {
|
|
281
|
+
POOL: AaveV3Arbitrum.POOL,
|
|
282
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Arbitrum.POOL_ADDRESSES_PROVIDER,
|
|
283
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Arbitrum.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
284
|
+
UI_POOL_DATA_PROVIDER: AaveV3Arbitrum.UI_POOL_DATA_PROVIDER,
|
|
285
|
+
WETH_GATEWAY: AaveV3Arbitrum.WETH_GATEWAY,
|
|
286
|
+
ACL_MANAGER: AaveV3Arbitrum.ACL_MANAGER,
|
|
287
|
+
COLLECTOR: AaveV3Arbitrum.COLLECTOR,
|
|
288
|
+
ORACLE: AaveV3Arbitrum.ORACLE
|
|
289
|
+
},
|
|
290
|
+
["optimism" /* OPTIMISM */]: {
|
|
291
|
+
POOL: AaveV3Optimism.POOL,
|
|
292
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Optimism.POOL_ADDRESSES_PROVIDER,
|
|
293
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Optimism.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
294
|
+
UI_POOL_DATA_PROVIDER: AaveV3Optimism.UI_POOL_DATA_PROVIDER,
|
|
295
|
+
WETH_GATEWAY: AaveV3Optimism.WETH_GATEWAY,
|
|
296
|
+
ACL_MANAGER: AaveV3Optimism.ACL_MANAGER,
|
|
297
|
+
COLLECTOR: AaveV3Optimism.COLLECTOR,
|
|
298
|
+
ORACLE: AaveV3Optimism.ORACLE
|
|
299
|
+
},
|
|
300
|
+
["base" /* BASE */]: {
|
|
301
|
+
POOL: AaveV3Base.POOL,
|
|
302
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Base.POOL_ADDRESSES_PROVIDER,
|
|
303
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Base.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
304
|
+
UI_POOL_DATA_PROVIDER: AaveV3Base.UI_POOL_DATA_PROVIDER,
|
|
305
|
+
WETH_GATEWAY: AaveV3Base.WETH_GATEWAY,
|
|
306
|
+
ACL_MANAGER: AaveV3Base.ACL_MANAGER,
|
|
307
|
+
COLLECTOR: AaveV3Base.COLLECTOR,
|
|
308
|
+
ORACLE: AaveV3Base.ORACLE
|
|
309
|
+
},
|
|
310
|
+
["bnb" /* BNB */]: {
|
|
311
|
+
POOL: AaveV3BNB.POOL,
|
|
312
|
+
POOL_ADDRESSES_PROVIDER: AaveV3BNB.POOL_ADDRESSES_PROVIDER,
|
|
313
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3BNB.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
314
|
+
UI_POOL_DATA_PROVIDER: AaveV3BNB.UI_POOL_DATA_PROVIDER,
|
|
315
|
+
WETH_GATEWAY: AaveV3BNB.WETH_GATEWAY,
|
|
316
|
+
ACL_MANAGER: AaveV3BNB.ACL_MANAGER,
|
|
317
|
+
COLLECTOR: AaveV3BNB.COLLECTOR,
|
|
318
|
+
ORACLE: AaveV3BNB.ORACLE
|
|
319
|
+
},
|
|
320
|
+
["gnosis" /* GNOSIS */]: {
|
|
321
|
+
POOL: AaveV3Gnosis.POOL,
|
|
322
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Gnosis.POOL_ADDRESSES_PROVIDER,
|
|
323
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Gnosis.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
324
|
+
UI_POOL_DATA_PROVIDER: AaveV3Gnosis.UI_POOL_DATA_PROVIDER,
|
|
325
|
+
WETH_GATEWAY: AaveV3Gnosis.WETH_GATEWAY,
|
|
326
|
+
ACL_MANAGER: AaveV3Gnosis.ACL_MANAGER,
|
|
327
|
+
COLLECTOR: AaveV3Gnosis.COLLECTOR,
|
|
328
|
+
ORACLE: AaveV3Gnosis.ORACLE
|
|
329
|
+
},
|
|
330
|
+
["metis" /* METIS */]: {
|
|
331
|
+
POOL: AaveV3Metis.POOL,
|
|
332
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Metis.POOL_ADDRESSES_PROVIDER,
|
|
333
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Metis.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
334
|
+
UI_POOL_DATA_PROVIDER: AaveV3Metis.UI_POOL_DATA_PROVIDER,
|
|
335
|
+
// WETH_GATEWAY: Not available on Metis
|
|
336
|
+
ACL_MANAGER: AaveV3Metis.ACL_MANAGER,
|
|
337
|
+
COLLECTOR: AaveV3Metis.COLLECTOR,
|
|
338
|
+
ORACLE: AaveV3Metis.ORACLE
|
|
339
|
+
},
|
|
340
|
+
["scroll" /* SCROLL */]: {
|
|
341
|
+
POOL: AaveV3Scroll.POOL,
|
|
342
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Scroll.POOL_ADDRESSES_PROVIDER,
|
|
343
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Scroll.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
344
|
+
UI_POOL_DATA_PROVIDER: AaveV3Scroll.UI_POOL_DATA_PROVIDER,
|
|
345
|
+
WETH_GATEWAY: AaveV3Scroll.WETH_GATEWAY,
|
|
346
|
+
ACL_MANAGER: AaveV3Scroll.ACL_MANAGER,
|
|
347
|
+
COLLECTOR: AaveV3Scroll.COLLECTOR,
|
|
348
|
+
ORACLE: AaveV3Scroll.ORACLE
|
|
349
|
+
},
|
|
350
|
+
["zksync" /* ZKSYNC */]: {
|
|
351
|
+
POOL: AaveV3ZkSync.POOL,
|
|
352
|
+
POOL_ADDRESSES_PROVIDER: AaveV3ZkSync.POOL_ADDRESSES_PROVIDER,
|
|
353
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3ZkSync.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
354
|
+
UI_POOL_DATA_PROVIDER: AaveV3ZkSync.UI_POOL_DATA_PROVIDER,
|
|
355
|
+
WETH_GATEWAY: AaveV3ZkSync.WETH_GATEWAY,
|
|
356
|
+
ACL_MANAGER: AaveV3ZkSync.ACL_MANAGER,
|
|
357
|
+
COLLECTOR: AaveV3ZkSync.COLLECTOR,
|
|
358
|
+
ORACLE: AaveV3ZkSync.ORACLE
|
|
359
|
+
},
|
|
360
|
+
// Testnets
|
|
361
|
+
["sepolia" /* SEPOLIA */]: {
|
|
362
|
+
POOL: AaveV3Sepolia.POOL,
|
|
363
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Sepolia.POOL_ADDRESSES_PROVIDER,
|
|
364
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Sepolia.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
365
|
+
UI_POOL_DATA_PROVIDER: AaveV3Sepolia.UI_POOL_DATA_PROVIDER,
|
|
366
|
+
WETH_GATEWAY: AaveV3Sepolia.WETH_GATEWAY,
|
|
367
|
+
ACL_MANAGER: AaveV3Sepolia.ACL_MANAGER,
|
|
368
|
+
COLLECTOR: AaveV3Sepolia.COLLECTOR,
|
|
369
|
+
ORACLE: AaveV3Sepolia.ORACLE
|
|
370
|
+
},
|
|
371
|
+
["fuji" /* FUJI */]: {
|
|
372
|
+
POOL: AaveV3Fuji.POOL,
|
|
373
|
+
POOL_ADDRESSES_PROVIDER: AaveV3Fuji.POOL_ADDRESSES_PROVIDER,
|
|
374
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3Fuji.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
375
|
+
// UI_POOL_DATA_PROVIDER: Not available on Fuji
|
|
376
|
+
WETH_GATEWAY: AaveV3Fuji.WETH_GATEWAY,
|
|
377
|
+
ACL_MANAGER: AaveV3Fuji.ACL_MANAGER,
|
|
378
|
+
COLLECTOR: AaveV3Fuji.COLLECTOR,
|
|
379
|
+
ORACLE: AaveV3Fuji.ORACLE
|
|
380
|
+
},
|
|
381
|
+
["arbitrum-sepolia" /* ARBITRUM_SEPOLIA */]: {
|
|
382
|
+
POOL: AaveV3ArbitrumSepolia.POOL,
|
|
383
|
+
POOL_ADDRESSES_PROVIDER: AaveV3ArbitrumSepolia.POOL_ADDRESSES_PROVIDER,
|
|
384
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3ArbitrumSepolia.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
385
|
+
UI_POOL_DATA_PROVIDER: AaveV3ArbitrumSepolia.UI_POOL_DATA_PROVIDER,
|
|
386
|
+
WETH_GATEWAY: AaveV3ArbitrumSepolia.WETH_GATEWAY,
|
|
387
|
+
ACL_MANAGER: AaveV3ArbitrumSepolia.ACL_MANAGER,
|
|
388
|
+
COLLECTOR: AaveV3ArbitrumSepolia.COLLECTOR,
|
|
389
|
+
ORACLE: AaveV3ArbitrumSepolia.ORACLE
|
|
390
|
+
},
|
|
391
|
+
["optimism-sepolia" /* OPTIMISM_SEPOLIA */]: {
|
|
392
|
+
POOL: AaveV3OptimismSepolia.POOL,
|
|
393
|
+
POOL_ADDRESSES_PROVIDER: AaveV3OptimismSepolia.POOL_ADDRESSES_PROVIDER,
|
|
394
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3OptimismSepolia.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
395
|
+
UI_POOL_DATA_PROVIDER: AaveV3OptimismSepolia.UI_POOL_DATA_PROVIDER,
|
|
396
|
+
WETH_GATEWAY: AaveV3OptimismSepolia.WETH_GATEWAY,
|
|
397
|
+
ACL_MANAGER: AaveV3OptimismSepolia.ACL_MANAGER,
|
|
398
|
+
COLLECTOR: AaveV3OptimismSepolia.COLLECTOR,
|
|
399
|
+
ORACLE: AaveV3OptimismSepolia.ORACLE
|
|
400
|
+
},
|
|
401
|
+
["base-sepolia" /* BASE_SEPOLIA */]: {
|
|
402
|
+
POOL: AaveV3BaseSepolia.POOL,
|
|
403
|
+
POOL_ADDRESSES_PROVIDER: AaveV3BaseSepolia.POOL_ADDRESSES_PROVIDER,
|
|
404
|
+
AAVE_PROTOCOL_DATA_PROVIDER: AaveV3BaseSepolia.AAVE_PROTOCOL_DATA_PROVIDER,
|
|
405
|
+
UI_POOL_DATA_PROVIDER: AaveV3BaseSepolia.UI_POOL_DATA_PROVIDER,
|
|
406
|
+
WETH_GATEWAY: AaveV3BaseSepolia.WETH_GATEWAY,
|
|
407
|
+
ACL_MANAGER: AaveV3BaseSepolia.ACL_MANAGER,
|
|
408
|
+
COLLECTOR: AaveV3BaseSepolia.COLLECTOR,
|
|
409
|
+
ORACLE: AaveV3BaseSepolia.ORACLE
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
function resolveChainContext(chainName) {
|
|
413
|
+
const normalizedChain = chainName.toLowerCase().trim();
|
|
414
|
+
if (!Object.values(SupportedChain).includes(normalizedChain)) {
|
|
415
|
+
throw new AaveError(
|
|
416
|
+
`Unsupported chain: ${chainName}. Supported chains: ${Object.values(SupportedChain).join(", ")}`,
|
|
417
|
+
"INVALID_PARAMETERS" /* INVALID_PARAMETERS */
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
const config = CHAIN_CONFIGS[normalizedChain];
|
|
421
|
+
if (!config) {
|
|
422
|
+
throw new AaveError(
|
|
423
|
+
`Chain configuration not found for: ${chainName}`,
|
|
424
|
+
"INVALID_PARAMETERS" /* INVALID_PARAMETERS */
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
const addresses = CHAIN_ADDRESS_MAP[normalizedChain];
|
|
428
|
+
if (!addresses) {
|
|
429
|
+
throw new AaveError(
|
|
430
|
+
`Aave V3 contracts not available on chain: ${chainName}`,
|
|
431
|
+
"INVALID_PARAMETERS" /* INVALID_PARAMETERS */
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
chain: normalizedChain,
|
|
436
|
+
config,
|
|
437
|
+
addresses
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function resolveRpcUrl(chainContext, userRpcUrl) {
|
|
441
|
+
if (userRpcUrl) {
|
|
442
|
+
return userRpcUrl;
|
|
443
|
+
}
|
|
444
|
+
return chainContext.config.defaultRpcUrl;
|
|
445
|
+
}
|
|
446
|
+
function isMaxAmount(amount) {
|
|
447
|
+
if (typeof amount === "string") {
|
|
448
|
+
const normalized = amount.toLowerCase().trim();
|
|
449
|
+
return ["max", "maximum", "all", "everything"].includes(normalized);
|
|
450
|
+
}
|
|
451
|
+
if (amount instanceof BigNumber3) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
function parseAmount(amount) {
|
|
457
|
+
if (amount === null || amount === void 0) {
|
|
458
|
+
throw new Error("Amount cannot be null or undefined");
|
|
459
|
+
}
|
|
460
|
+
if (typeof amount === "string") {
|
|
461
|
+
const trimmed = amount.trim();
|
|
462
|
+
if (!trimmed) {
|
|
463
|
+
throw new Error("Amount cannot be empty");
|
|
464
|
+
}
|
|
465
|
+
if (trimmed === "NaN" || trimmed === "Infinity" || trimmed === "-Infinity") {
|
|
466
|
+
throw new Error("Invalid amount format");
|
|
467
|
+
}
|
|
468
|
+
const num2 = new BigNumber3(trimmed);
|
|
469
|
+
if (num2.isNaN() || !num2.isFinite()) {
|
|
470
|
+
throw new Error("Invalid amount format");
|
|
471
|
+
}
|
|
472
|
+
if (num2.isLessThanOrEqualTo(0)) {
|
|
473
|
+
throw new Error("Amount must be greater than 0");
|
|
474
|
+
}
|
|
475
|
+
return num2;
|
|
476
|
+
}
|
|
477
|
+
if (typeof amount === "number") {
|
|
478
|
+
if (!isFinite(amount) || isNaN(amount)) {
|
|
479
|
+
throw new Error("Amount must be a finite number");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const num = new BigNumber3(amount);
|
|
483
|
+
if (num.isNaN() || !num.isFinite() || num.isLessThanOrEqualTo(0)) {
|
|
484
|
+
throw new Error("Amount must be a positive finite number");
|
|
485
|
+
}
|
|
486
|
+
return num;
|
|
487
|
+
}
|
|
488
|
+
function validateAddress(address) {
|
|
489
|
+
if (!address || typeof address !== "string") {
|
|
490
|
+
throw new Error("Address is required");
|
|
491
|
+
}
|
|
492
|
+
const trimmed = address.trim();
|
|
493
|
+
if (!trimmed.match(/^0x[a-fA-F0-9]{40}$/i)) {
|
|
494
|
+
throw new Error("Invalid address: must be a valid Ethereum address");
|
|
495
|
+
}
|
|
496
|
+
return trimmed;
|
|
497
|
+
}
|
|
498
|
+
function validateSupplyParams(params) {
|
|
499
|
+
if (!params.user || !params.asset || !params.amount) {
|
|
500
|
+
throw new Error("Missing required parameters: user, asset, amount");
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
user: validateAddress(params.user),
|
|
504
|
+
asset: params.asset.trim(),
|
|
505
|
+
amount: isMaxAmount(params.amount) ? params.amount : parseAmount(params.amount).toString()
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function validateWithdrawParams(params) {
|
|
509
|
+
if (!params.user || !params.asset || !params.amount) {
|
|
510
|
+
throw new Error("Missing required parameters: user, asset, amount");
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
user: validateAddress(params.user),
|
|
514
|
+
asset: params.asset.trim(),
|
|
515
|
+
amount: isMaxAmount(params.amount) ? params.amount : parseAmount(params.amount).toString()
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function validateBorrowParams(params) {
|
|
519
|
+
if (!params.user || !params.asset || !params.amount) {
|
|
520
|
+
throw new Error("Missing required parameters: user, asset, amount");
|
|
521
|
+
}
|
|
522
|
+
if (params.interestRateMode !== void 0) {
|
|
523
|
+
if (params.interestRateMode !== 1 /* STABLE */ && params.interestRateMode !== 2 /* VARIABLE */) {
|
|
524
|
+
throw new Error("Invalid interest rate mode: must be 1 (stable) or 2 (variable)");
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
user: validateAddress(params.user),
|
|
529
|
+
asset: params.asset.trim(),
|
|
530
|
+
amount: parseAmount(params.amount).toString(),
|
|
531
|
+
interestRateMode: params.interestRateMode || 2 /* VARIABLE */
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function validateRepayParams(params) {
|
|
535
|
+
if (!params.user || !params.asset || !params.amount) {
|
|
536
|
+
throw new Error("Missing required parameters: user, asset, amount");
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
user: validateAddress(params.user),
|
|
540
|
+
asset: params.asset.trim(),
|
|
541
|
+
amount: isMaxAmount(params.amount) ? params.amount : parseAmount(params.amount).toString(),
|
|
542
|
+
interestRateMode: params.interestRateMode || 2 /* VARIABLE */
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/utils/error-handler.ts
|
|
547
|
+
function getRateModeName(mode) {
|
|
548
|
+
switch (mode) {
|
|
549
|
+
case 1 /* STABLE */:
|
|
550
|
+
return "Stable";
|
|
551
|
+
case 2 /* VARIABLE */:
|
|
552
|
+
return "Variable";
|
|
553
|
+
default:
|
|
554
|
+
return "Unknown";
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
var AAVE_PROTOCOL_ERRORS = {
|
|
558
|
+
// Pool errors (P prefix)
|
|
559
|
+
"P_INVALID_FLASHLOAN_EXECUTOR_RETURN": {
|
|
560
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
561
|
+
description: "Invalid flashloan executor return",
|
|
562
|
+
userMessage: "Flash loan execution failed. Please check your flash loan logic.",
|
|
563
|
+
recovery: {
|
|
564
|
+
action: "Review and fix your flash loan implementation",
|
|
565
|
+
requiresUserAction: true
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
"P_VT_TRANSFER_NOT_SUPPORTED": {
|
|
569
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
570
|
+
description: "Variable debt token transfer not supported",
|
|
571
|
+
userMessage: "Cannot transfer debt tokens. Use repay and borrow operations instead.",
|
|
572
|
+
recovery: {
|
|
573
|
+
action: "Use repay() and borrow() functions for debt management",
|
|
574
|
+
requiresUserAction: true
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
"P_ASSET_NOT_LISTED": {
|
|
578
|
+
code: "ASSET_NOT_SUPPORTED" /* ASSET_NOT_SUPPORTED */,
|
|
579
|
+
description: "Asset is not listed in the pool",
|
|
580
|
+
userMessage: "This asset is not supported by Aave protocol.",
|
|
581
|
+
recovery: {
|
|
582
|
+
action: "Use a supported asset from the Aave markets",
|
|
583
|
+
requiresUserAction: true
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
"P_INVALID_AMOUNT": {
|
|
587
|
+
code: "INVALID_PARAMETERS" /* INVALID_PARAMETERS */,
|
|
588
|
+
description: "Invalid amount provided",
|
|
589
|
+
userMessage: "The amount provided is invalid. Please check the amount and try again.",
|
|
590
|
+
recovery: {
|
|
591
|
+
action: "Ensure amount is positive and within acceptable range",
|
|
592
|
+
requiresUserAction: true
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
// Reserve errors (R prefix)
|
|
596
|
+
"R_LIQUIDITY_INDEX_OVERFLOW": {
|
|
597
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
598
|
+
description: "Liquidity index overflow",
|
|
599
|
+
userMessage: "Internal calculation error. Please try again later.",
|
|
600
|
+
recovery: {
|
|
601
|
+
action: "Wait and retry the operation",
|
|
602
|
+
requiresUserAction: false
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
"R_VARIABLE_BORROW_INDEX_OVERFLOW": {
|
|
606
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
607
|
+
description: "Variable borrow index overflow",
|
|
608
|
+
userMessage: "Internal calculation error. Please try again later.",
|
|
609
|
+
recovery: {
|
|
610
|
+
action: "Wait and retry the operation",
|
|
611
|
+
requiresUserAction: false
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
"R_LIQUIDITY_RATE_OVERFLOW": {
|
|
615
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
616
|
+
description: "Liquidity rate overflow",
|
|
617
|
+
userMessage: "Interest rate calculation error. Please try again later.",
|
|
618
|
+
recovery: {
|
|
619
|
+
action: "Wait and retry the operation",
|
|
620
|
+
requiresUserAction: false
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
"R_VARIABLE_BORROW_RATE_OVERFLOW": {
|
|
624
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
625
|
+
description: "Variable borrow rate overflow",
|
|
626
|
+
userMessage: "Interest rate calculation error. Please try again later.",
|
|
627
|
+
recovery: {
|
|
628
|
+
action: "Wait and retry the operation",
|
|
629
|
+
requiresUserAction: false
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
// Validation errors (V prefix)
|
|
633
|
+
"V_INCONSISTENT_FLASHLOAN_PARAMS": {
|
|
634
|
+
code: "INVALID_PARAMETERS" /* INVALID_PARAMETERS */,
|
|
635
|
+
description: "Inconsistent flashloan parameters",
|
|
636
|
+
userMessage: "Flash loan parameters are inconsistent.",
|
|
637
|
+
recovery: {
|
|
638
|
+
action: "Check flash loan asset and amount parameters",
|
|
639
|
+
requiresUserAction: true
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
"V_COLLATERAL_BALANCE_IS_ZERO": {
|
|
643
|
+
code: "INSUFFICIENT_COLLATERAL" /* INSUFFICIENT_COLLATERAL */,
|
|
644
|
+
description: "Collateral balance is zero",
|
|
645
|
+
userMessage: "You have no collateral deposited. Please supply collateral first.",
|
|
646
|
+
recovery: {
|
|
647
|
+
action: "Supply assets as collateral before borrowing",
|
|
648
|
+
requiresUserAction: true
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
"V_HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD": {
|
|
652
|
+
code: "HEALTH_FACTOR_TOO_LOW" /* HEALTH_FACTOR_TOO_LOW */,
|
|
653
|
+
description: "Health factor below liquidation threshold",
|
|
654
|
+
userMessage: "This operation would put your position at risk of liquidation.",
|
|
655
|
+
recovery: {
|
|
656
|
+
action: "Supply more collateral or reduce borrowing amount",
|
|
657
|
+
requiresUserAction: true
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
"V_COLLATERAL_CANNOT_COVER_NEW_BORROW": {
|
|
661
|
+
code: "INSUFFICIENT_COLLATERAL" /* INSUFFICIENT_COLLATERAL */,
|
|
662
|
+
description: "Insufficient collateral for new borrow",
|
|
663
|
+
userMessage: "You don't have enough collateral to borrow this amount.",
|
|
664
|
+
recovery: {
|
|
665
|
+
action: "Supply more collateral or reduce borrow amount",
|
|
666
|
+
requiresUserAction: true
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
"V_STABLE_BORROWING_NOT_ENABLED": {
|
|
670
|
+
code: "STABLE_BORROWING_NOT_ENABLED" /* STABLE_BORROWING_NOT_ENABLED */,
|
|
671
|
+
description: "Stable borrowing not enabled for asset",
|
|
672
|
+
userMessage: "Stable rate borrowing is not available for this asset.",
|
|
673
|
+
recovery: {
|
|
674
|
+
action: "Use variable rate borrowing instead",
|
|
675
|
+
requiresUserAction: true
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
"V_NO_DEBT_OF_SELECTED_TYPE": {
|
|
679
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
680
|
+
description: "No debt of selected type",
|
|
681
|
+
userMessage: "You don't have debt in the selected interest rate mode.",
|
|
682
|
+
recovery: {
|
|
683
|
+
action: "Check your debt positions and select the correct rate mode",
|
|
684
|
+
requiresUserAction: true
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
"V_NO_STABLE_RATE_LOAN_IN_RESERVE": {
|
|
688
|
+
code: "STABLE_BORROWING_NOT_ENABLED" /* STABLE_BORROWING_NOT_ENABLED */,
|
|
689
|
+
description: "No stable rate loan exists",
|
|
690
|
+
userMessage: "No stable rate loan exists for this asset.",
|
|
691
|
+
recovery: {
|
|
692
|
+
action: "Use variable rate borrowing",
|
|
693
|
+
requiresUserAction: true
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
"V_NO_VARIABLE_RATE_LOAN_IN_RESERVE": {
|
|
697
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
698
|
+
description: "No variable rate loan exists",
|
|
699
|
+
userMessage: "No variable rate loan exists for this asset.",
|
|
700
|
+
recovery: {
|
|
701
|
+
action: "Check your borrow positions",
|
|
702
|
+
requiresUserAction: true
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
"V_UNDERLYING_BALANCE_ZERO": {
|
|
706
|
+
code: "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
|
|
707
|
+
description: "Underlying balance is zero",
|
|
708
|
+
userMessage: "You don't have any balance of this asset to supply.",
|
|
709
|
+
recovery: {
|
|
710
|
+
action: "Acquire the asset first or check your balance",
|
|
711
|
+
requiresUserAction: true
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
"V_INTEREST_RATE_REBALANCE_CONDITIONS_NOT_MET": {
|
|
715
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
716
|
+
description: "Interest rate rebalance conditions not met",
|
|
717
|
+
userMessage: "Cannot rebalance interest rate. Conditions not met.",
|
|
718
|
+
recovery: {
|
|
719
|
+
action: "Try again later when market conditions change",
|
|
720
|
+
requiresUserAction: false
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
// Asset configuration errors (A prefix)
|
|
724
|
+
"A_INTEREST_RATE_REBALANCE_CONDITIONS_NOT_MET": {
|
|
725
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
726
|
+
description: "Rebalance conditions not met",
|
|
727
|
+
userMessage: "Cannot rebalance stable rate. Market conditions not suitable.",
|
|
728
|
+
recovery: {
|
|
729
|
+
action: "Try again when utilization rate changes",
|
|
730
|
+
requiresUserAction: false
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
"A_NO_MORE_RESERVES_ALLOWED": {
|
|
734
|
+
code: "POOL_PAUSED" /* POOL_PAUSED */,
|
|
735
|
+
description: "No more reserves allowed",
|
|
736
|
+
userMessage: "Maximum number of assets reached in the pool.",
|
|
737
|
+
recovery: {
|
|
738
|
+
action: "Contact Aave governance to add more reserves",
|
|
739
|
+
requiresUserAction: true
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
// Supply/Withdraw specific
|
|
743
|
+
"S_NOT_ENOUGH_AVAILABLE_USER_BALANCE": {
|
|
744
|
+
code: "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
|
|
745
|
+
description: "Insufficient user balance",
|
|
746
|
+
userMessage: "You don't have enough balance to complete this operation.",
|
|
747
|
+
recovery: {
|
|
748
|
+
action: "Check your wallet balance and reduce the amount",
|
|
749
|
+
requiresUserAction: true
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
"W_NO_ATOKEN_BALANCE": {
|
|
753
|
+
code: "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
|
|
754
|
+
description: "No aToken balance to withdraw",
|
|
755
|
+
userMessage: "You don't have any supplied balance to withdraw.",
|
|
756
|
+
recovery: {
|
|
757
|
+
action: "Check your supplied positions",
|
|
758
|
+
requiresUserAction: true
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
// Borrow/Repay specific
|
|
762
|
+
"B_BORROW_CAP_EXCEEDED": {
|
|
763
|
+
code: "AMOUNT_TOO_HIGH" /* AMOUNT_TOO_HIGH */,
|
|
764
|
+
description: "Borrow cap exceeded",
|
|
765
|
+
userMessage: "Borrowing this amount would exceed the protocol's borrow cap.",
|
|
766
|
+
recovery: {
|
|
767
|
+
action: "Reduce the borrow amount",
|
|
768
|
+
requiresUserAction: true
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
"B_SUPPLY_CAP_EXCEEDED": {
|
|
772
|
+
code: "AMOUNT_TOO_HIGH" /* AMOUNT_TOO_HIGH */,
|
|
773
|
+
description: "Supply cap exceeded",
|
|
774
|
+
userMessage: "Supplying this amount would exceed the protocol's supply cap.",
|
|
775
|
+
recovery: {
|
|
776
|
+
action: "Reduce the supply amount",
|
|
777
|
+
requiresUserAction: true
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
// Reserve status errors
|
|
781
|
+
"RESERVE_INACTIVE": {
|
|
782
|
+
code: "RESERVE_INACTIVE" /* RESERVE_INACTIVE */,
|
|
783
|
+
description: "Reserve is inactive",
|
|
784
|
+
userMessage: "This asset is currently inactive in the Aave protocol.",
|
|
785
|
+
recovery: {
|
|
786
|
+
action: "Choose an active asset or wait for reactivation",
|
|
787
|
+
requiresUserAction: true
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
"RESERVE_FROZEN": {
|
|
791
|
+
code: "RESERVE_FROZEN" /* RESERVE_FROZEN */,
|
|
792
|
+
description: "Reserve is frozen",
|
|
793
|
+
userMessage: "This asset is currently frozen. Only repay and withdraw operations are allowed.",
|
|
794
|
+
recovery: {
|
|
795
|
+
action: "Choose an unfrozen asset or wait for unfreeze",
|
|
796
|
+
requiresUserAction: true
|
|
797
|
+
}
|
|
798
|
+
},
|
|
799
|
+
"RESERVE_PAUSED": {
|
|
800
|
+
code: "POOL_PAUSED" /* POOL_PAUSED */,
|
|
801
|
+
description: "Reserve is paused",
|
|
802
|
+
userMessage: "Operations on this asset are temporarily paused.",
|
|
803
|
+
recovery: {
|
|
804
|
+
action: "Wait for the asset to be unpaused",
|
|
805
|
+
requiresUserAction: false
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
var NETWORK_ERROR_PATTERNS = {
|
|
810
|
+
// RPC errors
|
|
811
|
+
"network timeout": {
|
|
812
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
813
|
+
userMessage: "Network connection timed out. Please try again.",
|
|
814
|
+
isRetryable: true
|
|
815
|
+
},
|
|
816
|
+
"connection refused": {
|
|
817
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
818
|
+
userMessage: "Unable to connect to the network. Please check your connection.",
|
|
819
|
+
isRetryable: true
|
|
820
|
+
},
|
|
821
|
+
"socket hang up": {
|
|
822
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
823
|
+
userMessage: "Network connection interrupted. Please try again.",
|
|
824
|
+
isRetryable: true
|
|
825
|
+
},
|
|
826
|
+
"request timeout": {
|
|
827
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
828
|
+
userMessage: "Request timed out. Please try again.",
|
|
829
|
+
isRetryable: true
|
|
830
|
+
},
|
|
831
|
+
"rate limit": {
|
|
832
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
833
|
+
userMessage: "Too many requests. Please wait a moment and try again.",
|
|
834
|
+
isRetryable: true
|
|
835
|
+
},
|
|
836
|
+
"insufficient funds for gas": {
|
|
837
|
+
code: "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
|
|
838
|
+
userMessage: "Insufficient ETH for gas fees. Please add more ETH to your wallet.",
|
|
839
|
+
isRetryable: false
|
|
840
|
+
},
|
|
841
|
+
"gas limit exceeded": {
|
|
842
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
843
|
+
userMessage: "Transaction requires too much gas. Try reducing the amount.",
|
|
844
|
+
isRetryable: false
|
|
845
|
+
},
|
|
846
|
+
"replacement transaction underpriced": {
|
|
847
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
848
|
+
userMessage: "Transaction replacement fee too low. Increase gas price.",
|
|
849
|
+
isRetryable: true
|
|
850
|
+
},
|
|
851
|
+
"nonce too low": {
|
|
852
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
853
|
+
userMessage: "Transaction nonce is outdated. Please refresh and try again.",
|
|
854
|
+
isRetryable: true
|
|
855
|
+
},
|
|
856
|
+
"already known": {
|
|
857
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
858
|
+
userMessage: "Transaction already pending. Please wait for confirmation.",
|
|
859
|
+
isRetryable: false
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
var GAS_ERROR_PATTERNS = {
|
|
863
|
+
"execution reverted": {
|
|
864
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
865
|
+
userMessage: "Transaction would fail. Please check your parameters.",
|
|
866
|
+
recovery: {
|
|
867
|
+
action: "Review transaction parameters and account balances",
|
|
868
|
+
requiresUserAction: true
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
"out of gas": {
|
|
872
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
873
|
+
userMessage: "Transaction ran out of gas. Increase gas limit.",
|
|
874
|
+
recovery: {
|
|
875
|
+
action: "Increase gas limit or reduce transaction complexity",
|
|
876
|
+
requiresUserAction: true
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
"invalid opcode": {
|
|
880
|
+
code: "TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
881
|
+
userMessage: "Invalid transaction. Please check your parameters.",
|
|
882
|
+
recovery: {
|
|
883
|
+
action: "Verify all transaction parameters are correct",
|
|
884
|
+
requiresUserAction: true
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
function mapAaveError(error, context = {}) {
|
|
889
|
+
const errorMessage = error.message.toLowerCase();
|
|
890
|
+
for (const [pattern, errorInfo] of Object.entries(AAVE_PROTOCOL_ERRORS)) {
|
|
891
|
+
if (errorMessage.includes(pattern.toLowerCase()) || errorMessage.includes(`execution reverted: ${pattern.toLowerCase()}`)) {
|
|
892
|
+
return new AaveError(
|
|
893
|
+
errorInfo.userMessage,
|
|
894
|
+
errorInfo.code,
|
|
895
|
+
error,
|
|
896
|
+
{
|
|
897
|
+
...context,
|
|
898
|
+
protocolError: pattern,
|
|
899
|
+
recovery: errorInfo.recovery
|
|
900
|
+
}
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
for (const [pattern, errorInfo] of Object.entries(NETWORK_ERROR_PATTERNS)) {
|
|
905
|
+
if (errorMessage.includes(pattern)) {
|
|
906
|
+
return new AaveError(
|
|
907
|
+
errorInfo.userMessage,
|
|
908
|
+
errorInfo.code,
|
|
909
|
+
error,
|
|
910
|
+
{
|
|
911
|
+
...context,
|
|
912
|
+
networkError: pattern,
|
|
913
|
+
isRetryable: errorInfo.isRetryable
|
|
914
|
+
}
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
for (const [pattern, errorInfo] of Object.entries(GAS_ERROR_PATTERNS)) {
|
|
919
|
+
if (errorMessage.includes(pattern)) {
|
|
920
|
+
return new AaveError(
|
|
921
|
+
errorInfo.userMessage,
|
|
922
|
+
errorInfo.code,
|
|
923
|
+
error,
|
|
924
|
+
{
|
|
925
|
+
...context,
|
|
926
|
+
gasError: pattern,
|
|
927
|
+
recovery: errorInfo.recovery
|
|
928
|
+
}
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (errorMessage.includes("insufficient")) {
|
|
933
|
+
if (errorMessage.includes("balance") || errorMessage.includes("funds")) {
|
|
934
|
+
return new AaveError(
|
|
935
|
+
"Insufficient balance to complete this operation.",
|
|
936
|
+
"INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
|
|
937
|
+
error,
|
|
938
|
+
context
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
if (errorMessage.includes("collateral")) {
|
|
942
|
+
return new AaveError(
|
|
943
|
+
"Insufficient collateral for this operation.",
|
|
944
|
+
"INSUFFICIENT_COLLATERAL" /* INSUFFICIENT_COLLATERAL */,
|
|
945
|
+
error,
|
|
946
|
+
context
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (errorMessage.includes("health factor")) {
|
|
951
|
+
return new AaveError(
|
|
952
|
+
"This operation would put your position at risk of liquidation.",
|
|
953
|
+
"HEALTH_FACTOR_TOO_LOW" /* HEALTH_FACTOR_TOO_LOW */,
|
|
954
|
+
error,
|
|
955
|
+
context
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
if (errorMessage.includes("not enabled") && errorMessage.includes("stable")) {
|
|
959
|
+
return new AaveError(
|
|
960
|
+
"Stable rate borrowing is not enabled for this asset.",
|
|
961
|
+
"STABLE_BORROWING_NOT_ENABLED" /* STABLE_BORROWING_NOT_ENABLED */,
|
|
962
|
+
error,
|
|
963
|
+
context
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
if (errorMessage.includes("paused")) {
|
|
967
|
+
return new AaveError(
|
|
968
|
+
"The pool or asset is currently paused.",
|
|
969
|
+
"POOL_PAUSED" /* POOL_PAUSED */,
|
|
970
|
+
error,
|
|
971
|
+
context
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
if (errorMessage.includes("frozen")) {
|
|
975
|
+
return new AaveError(
|
|
976
|
+
"The asset is currently frozen.",
|
|
977
|
+
"RESERVE_FROZEN" /* RESERVE_FROZEN */,
|
|
978
|
+
error,
|
|
979
|
+
context
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
return new AaveError(
|
|
983
|
+
"An unexpected error occurred. Please try again.",
|
|
984
|
+
"UNKNOWN" /* UNKNOWN */,
|
|
985
|
+
error,
|
|
986
|
+
context
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
function handleContractError(error, contractName, methodName, context = {}) {
|
|
990
|
+
const enhancedContext = {
|
|
991
|
+
...context,
|
|
992
|
+
contractName,
|
|
993
|
+
methodName
|
|
994
|
+
};
|
|
995
|
+
const aaveError = mapAaveError(error, enhancedContext);
|
|
996
|
+
return {
|
|
997
|
+
originalError: error,
|
|
998
|
+
aaveError,
|
|
999
|
+
userMessage: aaveError.message,
|
|
1000
|
+
technicalMessage: `Contract error in ${contractName}.${methodName}: ${error.message}`,
|
|
1001
|
+
context: enhancedContext,
|
|
1002
|
+
isRetryable: determineRetryability(aaveError),
|
|
1003
|
+
logLevel: determineLogLevel(aaveError),
|
|
1004
|
+
recovery: aaveError.context?.recovery
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
function handleTransactionError(error, transactionHash, context = {}) {
|
|
1008
|
+
const enhancedContext = {
|
|
1009
|
+
...context,
|
|
1010
|
+
transactionHash
|
|
1011
|
+
};
|
|
1012
|
+
const aaveError = mapAaveError(error, enhancedContext);
|
|
1013
|
+
let userMessage = aaveError.message;
|
|
1014
|
+
let technicalMessage = error.message;
|
|
1015
|
+
if (aaveError.code === "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */ && context.operation) {
|
|
1016
|
+
const operation = context.operation;
|
|
1017
|
+
const asset = context.asset || "the asset";
|
|
1018
|
+
switch (operation) {
|
|
1019
|
+
case "supply":
|
|
1020
|
+
userMessage = `You don't have enough ${asset} to supply this amount.`;
|
|
1021
|
+
break;
|
|
1022
|
+
case "borrow":
|
|
1023
|
+
userMessage = `You don't have enough collateral to borrow this amount of ${asset}.`;
|
|
1024
|
+
break;
|
|
1025
|
+
case "repay":
|
|
1026
|
+
userMessage = `You don't have enough ${asset} to repay this amount.`;
|
|
1027
|
+
break;
|
|
1028
|
+
case "withdraw":
|
|
1029
|
+
userMessage = `You don't have enough supplied ${asset} to withdraw this amount.`;
|
|
1030
|
+
break;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return {
|
|
1034
|
+
originalError: error,
|
|
1035
|
+
aaveError,
|
|
1036
|
+
userMessage,
|
|
1037
|
+
technicalMessage,
|
|
1038
|
+
context: enhancedContext,
|
|
1039
|
+
isRetryable: determineRetryability(aaveError),
|
|
1040
|
+
logLevel: determineLogLevel(aaveError),
|
|
1041
|
+
recovery: generateRecoverySuggestion(aaveError, context)
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
function handleBigNumberError(error, context = {}) {
|
|
1045
|
+
const errorMessage = error.message.toLowerCase();
|
|
1046
|
+
let aaveError;
|
|
1047
|
+
let userMessage;
|
|
1048
|
+
if (errorMessage.includes("invalid number") || errorMessage.includes("not a number")) {
|
|
1049
|
+
aaveError = new AaveError(
|
|
1050
|
+
"Invalid number format provided.",
|
|
1051
|
+
"INVALID_PARAMETERS" /* INVALID_PARAMETERS */,
|
|
1052
|
+
error,
|
|
1053
|
+
context
|
|
1054
|
+
);
|
|
1055
|
+
userMessage = "Please enter a valid number.";
|
|
1056
|
+
} else if (errorMessage.includes("overflow") || errorMessage.includes("too large")) {
|
|
1057
|
+
aaveError = new AaveError(
|
|
1058
|
+
"Number is too large to process.",
|
|
1059
|
+
"AMOUNT_TOO_HIGH" /* AMOUNT_TOO_HIGH */,
|
|
1060
|
+
error,
|
|
1061
|
+
context
|
|
1062
|
+
);
|
|
1063
|
+
userMessage = "The amount is too large. Please use a smaller number.";
|
|
1064
|
+
} else if (errorMessage.includes("underflow") || errorMessage.includes("negative")) {
|
|
1065
|
+
aaveError = new AaveError(
|
|
1066
|
+
"Negative numbers are not allowed.",
|
|
1067
|
+
"INVALID_PARAMETERS" /* INVALID_PARAMETERS */,
|
|
1068
|
+
error,
|
|
1069
|
+
context
|
|
1070
|
+
);
|
|
1071
|
+
userMessage = "Please enter a positive number.";
|
|
1072
|
+
} else if (errorMessage.includes("division by zero")) {
|
|
1073
|
+
aaveError = new AaveError(
|
|
1074
|
+
"Division by zero error in calculation.",
|
|
1075
|
+
"TRANSACTION_FAILED" /* TRANSACTION_FAILED */,
|
|
1076
|
+
error,
|
|
1077
|
+
context
|
|
1078
|
+
);
|
|
1079
|
+
userMessage = "Internal calculation error. Please try again.";
|
|
1080
|
+
} else {
|
|
1081
|
+
aaveError = new AaveError(
|
|
1082
|
+
"Number processing error.",
|
|
1083
|
+
"INVALID_PARAMETERS" /* INVALID_PARAMETERS */,
|
|
1084
|
+
error,
|
|
1085
|
+
context
|
|
1086
|
+
);
|
|
1087
|
+
userMessage = "There was an error processing the numbers. Please check your input.";
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
originalError: error,
|
|
1091
|
+
aaveError,
|
|
1092
|
+
userMessage,
|
|
1093
|
+
technicalMessage: `BigNumber/ethers error: ${error.message}`,
|
|
1094
|
+
context,
|
|
1095
|
+
isRetryable: false,
|
|
1096
|
+
logLevel: "warn",
|
|
1097
|
+
recovery: {
|
|
1098
|
+
action: "Check input values and try again",
|
|
1099
|
+
requiresUserAction: true
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function createSupplyErrorContext(params) {
|
|
1104
|
+
return {
|
|
1105
|
+
operation: "supply",
|
|
1106
|
+
asset: params.asset,
|
|
1107
|
+
amount: params.amount?.toString(),
|
|
1108
|
+
user: params.user
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function createWithdrawErrorContext(params) {
|
|
1112
|
+
return {
|
|
1113
|
+
operation: "withdraw",
|
|
1114
|
+
asset: params.asset,
|
|
1115
|
+
amount: params.amount?.toString(),
|
|
1116
|
+
user: params.user
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
function createBorrowErrorContext(params) {
|
|
1120
|
+
return {
|
|
1121
|
+
operation: "borrow",
|
|
1122
|
+
asset: params.asset,
|
|
1123
|
+
amount: params.amount?.toString(),
|
|
1124
|
+
user: params.user,
|
|
1125
|
+
interestRateMode: params.interestRateMode
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function createRepayErrorContext(params) {
|
|
1129
|
+
return {
|
|
1130
|
+
operation: "repay",
|
|
1131
|
+
asset: params.asset,
|
|
1132
|
+
amount: params.amount?.toString(),
|
|
1133
|
+
user: params.user,
|
|
1134
|
+
interestRateMode: params.interestRateMode
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function determineRetryability(error) {
|
|
1138
|
+
const retryableCodes = [
|
|
1139
|
+
"UNKNOWN" /* UNKNOWN */,
|
|
1140
|
+
"TRANSACTION_FAILED" /* TRANSACTION_FAILED */
|
|
1141
|
+
];
|
|
1142
|
+
if (retryableCodes.includes(error.code)) {
|
|
1143
|
+
if (error.context?.isRetryable !== void 0) {
|
|
1144
|
+
return error.context.isRetryable;
|
|
1145
|
+
}
|
|
1146
|
+
return true;
|
|
1147
|
+
}
|
|
1148
|
+
const nonRetryableCodes = [
|
|
1149
|
+
"INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
|
|
1150
|
+
"INSUFFICIENT_COLLATERAL" /* INSUFFICIENT_COLLATERAL */,
|
|
1151
|
+
"ASSET_NOT_SUPPORTED" /* ASSET_NOT_SUPPORTED */,
|
|
1152
|
+
"INVALID_PARAMETERS" /* INVALID_PARAMETERS */,
|
|
1153
|
+
"HEALTH_FACTOR_TOO_LOW" /* HEALTH_FACTOR_TOO_LOW */,
|
|
1154
|
+
"STABLE_BORROWING_NOT_ENABLED" /* STABLE_BORROWING_NOT_ENABLED */,
|
|
1155
|
+
"AMOUNT_TOO_HIGH" /* AMOUNT_TOO_HIGH */
|
|
1156
|
+
];
|
|
1157
|
+
return !nonRetryableCodes.includes(error.code);
|
|
1158
|
+
}
|
|
1159
|
+
function determineLogLevel(error) {
|
|
1160
|
+
switch (error.code) {
|
|
1161
|
+
case "INVALID_PARAMETERS" /* INVALID_PARAMETERS */:
|
|
1162
|
+
case "ASSET_NOT_SUPPORTED" /* ASSET_NOT_SUPPORTED */:
|
|
1163
|
+
return "warn";
|
|
1164
|
+
case "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */:
|
|
1165
|
+
case "INSUFFICIENT_COLLATERAL" /* INSUFFICIENT_COLLATERAL */:
|
|
1166
|
+
case "HEALTH_FACTOR_TOO_LOW" /* HEALTH_FACTOR_TOO_LOW */:
|
|
1167
|
+
return "info";
|
|
1168
|
+
default:
|
|
1169
|
+
return "error";
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
function generateRecoverySuggestion(error, context) {
|
|
1173
|
+
if (error.context?.recovery) {
|
|
1174
|
+
return error.context.recovery;
|
|
1175
|
+
}
|
|
1176
|
+
const { operation, asset, interestRateMode } = context;
|
|
1177
|
+
switch (error.code) {
|
|
1178
|
+
case "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */:
|
|
1179
|
+
if (operation === "supply" || operation === "repay") {
|
|
1180
|
+
return {
|
|
1181
|
+
action: `Acquire more ${asset || "tokens"} or reduce the amount`,
|
|
1182
|
+
requiresUserAction: true
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
action: "Check your wallet balance and try a smaller amount",
|
|
1187
|
+
requiresUserAction: true
|
|
1188
|
+
};
|
|
1189
|
+
case "INSUFFICIENT_COLLATERAL" /* INSUFFICIENT_COLLATERAL */:
|
|
1190
|
+
return {
|
|
1191
|
+
action: "Supply more collateral or reduce the borrow amount",
|
|
1192
|
+
details: "Your collateral is not sufficient for this borrow amount",
|
|
1193
|
+
requiresUserAction: true
|
|
1194
|
+
};
|
|
1195
|
+
case "HEALTH_FACTOR_TOO_LOW" /* HEALTH_FACTOR_TOO_LOW */:
|
|
1196
|
+
return {
|
|
1197
|
+
action: "Supply additional collateral or repay some debt first",
|
|
1198
|
+
details: "This operation would put your position at risk of liquidation",
|
|
1199
|
+
requiresUserAction: true
|
|
1200
|
+
};
|
|
1201
|
+
case "STABLE_BORROWING_NOT_ENABLED" /* STABLE_BORROWING_NOT_ENABLED */:
|
|
1202
|
+
return {
|
|
1203
|
+
action: "Use variable rate borrowing instead",
|
|
1204
|
+
details: `Stable rate is not available for ${asset || "this asset"}`,
|
|
1205
|
+
requiresUserAction: true
|
|
1206
|
+
};
|
|
1207
|
+
case "ASSET_NOT_SUPPORTED" /* ASSET_NOT_SUPPORTED */:
|
|
1208
|
+
return {
|
|
1209
|
+
action: "Choose a supported asset from the Aave markets",
|
|
1210
|
+
requiresUserAction: true
|
|
1211
|
+
};
|
|
1212
|
+
case "RESERVE_FROZEN" /* RESERVE_FROZEN */:
|
|
1213
|
+
return {
|
|
1214
|
+
action: "Only withdraw and repay operations are allowed for frozen assets",
|
|
1215
|
+
requiresUserAction: true
|
|
1216
|
+
};
|
|
1217
|
+
case "RESERVE_INACTIVE" /* RESERVE_INACTIVE */:
|
|
1218
|
+
return {
|
|
1219
|
+
action: "Choose an active asset or wait for this asset to be reactivated",
|
|
1220
|
+
requiresUserAction: true
|
|
1221
|
+
};
|
|
1222
|
+
case "POOL_PAUSED" /* POOL_PAUSED */:
|
|
1223
|
+
return {
|
|
1224
|
+
action: "Wait for the pool to be unpaused and try again",
|
|
1225
|
+
requiresUserAction: false
|
|
1226
|
+
};
|
|
1227
|
+
default:
|
|
1228
|
+
return {
|
|
1229
|
+
action: "Review the error details and try again",
|
|
1230
|
+
requiresUserAction: true
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function formatErrorForLogging(processedError) {
|
|
1235
|
+
return {
|
|
1236
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1237
|
+
level: processedError.logLevel,
|
|
1238
|
+
error: {
|
|
1239
|
+
type: processedError.aaveError.name,
|
|
1240
|
+
code: processedError.aaveError.code,
|
|
1241
|
+
message: processedError.technicalMessage,
|
|
1242
|
+
userMessage: processedError.userMessage,
|
|
1243
|
+
stack: processedError.originalError.stack
|
|
1244
|
+
},
|
|
1245
|
+
context: processedError.context,
|
|
1246
|
+
recovery: processedError.recovery,
|
|
1247
|
+
isRetryable: processedError.isRetryable
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
function logError(processedError, logger) {
|
|
1251
|
+
const logData = formatErrorForLogging(processedError);
|
|
1252
|
+
{
|
|
1253
|
+
const logPrefix = `[${processedError.logLevel.toUpperCase()}] Aave Error:`;
|
|
1254
|
+
console[processedError.logLevel === "error" ? "error" : "log"](
|
|
1255
|
+
logPrefix,
|
|
1256
|
+
JSON.stringify(logData, null, 2)
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
function processError(error, operation, params, transactionHash) {
|
|
1261
|
+
let context;
|
|
1262
|
+
switch (operation) {
|
|
1263
|
+
case "supply":
|
|
1264
|
+
context = createSupplyErrorContext(params || {});
|
|
1265
|
+
break;
|
|
1266
|
+
case "withdraw":
|
|
1267
|
+
context = createWithdrawErrorContext(params || {});
|
|
1268
|
+
break;
|
|
1269
|
+
case "borrow":
|
|
1270
|
+
context = createBorrowErrorContext(params || {});
|
|
1271
|
+
break;
|
|
1272
|
+
case "repay":
|
|
1273
|
+
context = createRepayErrorContext(params || {});
|
|
1274
|
+
break;
|
|
1275
|
+
default:
|
|
1276
|
+
context = { operation };
|
|
1277
|
+
}
|
|
1278
|
+
if (error.message.includes("BigNumber") || error.message.includes("invalid number")) {
|
|
1279
|
+
return handleBigNumberError(error, context);
|
|
1280
|
+
}
|
|
1281
|
+
if (error.message.includes("transaction")) {
|
|
1282
|
+
return handleTransactionError(error, transactionHash, context);
|
|
1283
|
+
}
|
|
1284
|
+
return handleContractError(error, "AavePool", operation, context);
|
|
1285
|
+
}
|
|
1286
|
+
function createUserFriendlyMessage(error) {
|
|
1287
|
+
const { aaveError, context } = error;
|
|
1288
|
+
const { operation, asset, amount, interestRateMode } = context;
|
|
1289
|
+
let baseMessage = aaveError.message;
|
|
1290
|
+
let operationContext = "";
|
|
1291
|
+
switch (operation) {
|
|
1292
|
+
case "supply":
|
|
1293
|
+
operationContext = `while supplying ${amount ? `${amount} ` : ""}${asset || "tokens"}`;
|
|
1294
|
+
break;
|
|
1295
|
+
case "withdraw":
|
|
1296
|
+
operationContext = `while withdrawing ${amount ? `${amount} ` : ""}${asset || "tokens"}`;
|
|
1297
|
+
break;
|
|
1298
|
+
case "borrow":
|
|
1299
|
+
const rateType = interestRateMode ? getRateModeName(interestRateMode).toLowerCase() : "";
|
|
1300
|
+
operationContext = `while borrowing ${amount ? `${amount} ` : ""}${asset || "tokens"}${rateType ? ` at ${rateType} rate` : ""}`;
|
|
1301
|
+
break;
|
|
1302
|
+
case "repay":
|
|
1303
|
+
const repayRateType = interestRateMode ? getRateModeName(interestRateMode).toLowerCase() : "";
|
|
1304
|
+
operationContext = `while repaying ${amount ? `${amount} ` : ""}${asset || "debt"}${repayRateType ? ` (${repayRateType} rate)` : ""}`;
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
if (operationContext) {
|
|
1308
|
+
baseMessage = `${baseMessage} This error occurred ${operationContext}.`;
|
|
1309
|
+
}
|
|
1310
|
+
if (error.recovery) {
|
|
1311
|
+
baseMessage += ` ${error.recovery.action}.`;
|
|
1312
|
+
if (error.recovery.details) {
|
|
1313
|
+
baseMessage += ` ${error.recovery.details}.`;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return baseMessage;
|
|
1317
|
+
}
|
|
1318
|
+
function handleError(error, operation, params, transactionHash, logger) {
|
|
1319
|
+
const processedError = processError(error, operation, params, transactionHash);
|
|
1320
|
+
processedError.userMessage = createUserFriendlyMessage(processedError);
|
|
1321
|
+
logError(processedError);
|
|
1322
|
+
return processedError;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// src/services/aave-service.ts
|
|
1326
|
+
var AaveService = class extends Service {
|
|
1327
|
+
static serviceType = "aave";
|
|
1328
|
+
provider;
|
|
1329
|
+
signer;
|
|
1330
|
+
poolService;
|
|
1331
|
+
uiPoolDataProvider;
|
|
1332
|
+
chainContext;
|
|
1333
|
+
isInitialized = false;
|
|
1334
|
+
constructor(runtime) {
|
|
1335
|
+
super(runtime);
|
|
1336
|
+
}
|
|
1337
|
+
get description() {
|
|
1338
|
+
return "Aave V3 Protocol service for lending, borrowing, and DeFi operations";
|
|
1339
|
+
}
|
|
1340
|
+
get capabilityDescription() {
|
|
1341
|
+
const currentChain = this.chainContext?.config?.name || "Multiple chains";
|
|
1342
|
+
return `Supports Aave V3 lending operations on ${currentChain}: supply, withdraw, borrow, repay with aTokens and variable/stable rates`;
|
|
1343
|
+
}
|
|
1344
|
+
async initialize(runtime) {
|
|
1345
|
+
try {
|
|
1346
|
+
elizaLogger.info("Initializing Aave V3 multi-chain service...");
|
|
1347
|
+
const targetChain = runtime.getSetting("AAVE_CHAIN") || "ethereum";
|
|
1348
|
+
elizaLogger.info(`Initializing for chain: ${targetChain}`);
|
|
1349
|
+
this.chainContext = resolveChainContext(targetChain);
|
|
1350
|
+
elizaLogger.info(`Chain context resolved: ${this.chainContext.config.name} (Chain ID: ${this.chainContext.config.chainId})`);
|
|
1351
|
+
const customRpcUrl = runtime.getSetting("AAVE_RPC_URL") || runtime.getSetting("RPC_URL");
|
|
1352
|
+
const rpcUrl = resolveRpcUrl(this.chainContext, customRpcUrl);
|
|
1353
|
+
elizaLogger.info(`Using RPC URL: ${rpcUrl.replace(/\/\/.*@/, "//***@")}`);
|
|
1354
|
+
const privateKey = runtime.getSetting("WALLET_PRIVATE_KEY");
|
|
1355
|
+
this.provider = new ethers.providers.JsonRpcProvider(rpcUrl);
|
|
1356
|
+
if (privateKey) {
|
|
1357
|
+
this.signer = new ethers.Wallet(privateKey, this.provider);
|
|
1358
|
+
elizaLogger.info(`Wallet connected: ${this.signer.address} on ${this.chainContext.config.name}`);
|
|
1359
|
+
const network = await this.provider.getNetwork();
|
|
1360
|
+
if (network.chainId !== this.chainContext.config.chainId) {
|
|
1361
|
+
elizaLogger.warn(`Network mismatch: RPC reports chain ID ${network.chainId}, expected ${this.chainContext.config.chainId}`);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
const poolConfig = {
|
|
1365
|
+
POOL: this.chainContext.addresses.POOL
|
|
1366
|
+
};
|
|
1367
|
+
if (this.chainContext.addresses.WETH_GATEWAY) {
|
|
1368
|
+
poolConfig.WETH_GATEWAY = this.chainContext.addresses.WETH_GATEWAY;
|
|
1369
|
+
}
|
|
1370
|
+
this.poolService = new Pool(this.provider, poolConfig);
|
|
1371
|
+
if (this.chainContext.addresses.UI_POOL_DATA_PROVIDER) {
|
|
1372
|
+
this.uiPoolDataProvider = new UiPoolDataProvider({
|
|
1373
|
+
uiPoolDataProviderAddress: this.chainContext.addresses.UI_POOL_DATA_PROVIDER,
|
|
1374
|
+
provider: this.provider,
|
|
1375
|
+
chainId: this.chainContext.config.chainId
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
await this.verifyConnection();
|
|
1379
|
+
this.isInitialized = true;
|
|
1380
|
+
elizaLogger.info(`Aave V3 service initialized successfully on ${this.chainContext.config.name}`);
|
|
1381
|
+
elizaLogger.info(`Available assets: ${this.chainContext.config.popularAssets.join(", ")}`);
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
elizaLogger.error("Failed to initialize Aave service:", error);
|
|
1384
|
+
throw new AaveError(
|
|
1385
|
+
`Failed to initialize Aave V3 service: ${error instanceof Error ? error.message : String(error)}`,
|
|
1386
|
+
"INITIALIZATION_FAILED" /* INITIALIZATION_FAILED */,
|
|
1387
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1388
|
+
{
|
|
1389
|
+
targetChain: runtime.getSetting("AAVE_CHAIN"),
|
|
1390
|
+
originalError: error
|
|
1391
|
+
}
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
async verifyConnection() {
|
|
1396
|
+
try {
|
|
1397
|
+
if (!this.uiPoolDataProvider) {
|
|
1398
|
+
elizaLogger.warn(`UI Pool Data Provider not available on ${this.chainContext.config.name} - some features may be limited`);
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
const reservesData = await this.uiPoolDataProvider.getReservesHumanized({
|
|
1402
|
+
lendingPoolAddressProvider: this.chainContext.addresses.POOL_ADDRESSES_PROVIDER
|
|
1403
|
+
});
|
|
1404
|
+
if (!reservesData || reservesData.reservesData.length === 0) {
|
|
1405
|
+
throw new Error("No reserves data received from Aave V3 contracts");
|
|
1406
|
+
}
|
|
1407
|
+
elizaLogger.info(`Connected to Aave V3 on ${this.chainContext.config.name} - found ${reservesData.reservesData.length} reserves`);
|
|
1408
|
+
const availableAssets = reservesData.reservesData.map((r) => r.symbol).slice(0, 5);
|
|
1409
|
+
elizaLogger.info(`Sample available assets: ${availableAssets.join(", ")}`);
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
elizaLogger.error("Connection verification failed:", error);
|
|
1412
|
+
throw new AaveError(
|
|
1413
|
+
`Failed to verify Aave V3 connection on ${this.chainContext.config.name}`,
|
|
1414
|
+
"CONNECTION_FAILED" /* CONNECTION_FAILED */,
|
|
1415
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1416
|
+
{
|
|
1417
|
+
chain: this.chainContext.config.name,
|
|
1418
|
+
chainId: this.chainContext.config.chainId,
|
|
1419
|
+
poolAddress: this.chainContext.addresses.POOL
|
|
1420
|
+
}
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
ensureInitialized() {
|
|
1425
|
+
if (!this.isInitialized || !this.poolService) {
|
|
1426
|
+
throw new AaveError(
|
|
1427
|
+
"Aave service not initialized",
|
|
1428
|
+
"SERVICE_NOT_INITIALIZED" /* SERVICE_NOT_INITIALIZED */
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
ensureSigner() {
|
|
1433
|
+
if (!this.signer) {
|
|
1434
|
+
throw new AaveError(
|
|
1435
|
+
"Wallet not connected - private key required for transactions",
|
|
1436
|
+
"WALLET_NOT_CONNECTED" /* WALLET_NOT_CONNECTED */
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
async supply(params) {
|
|
1441
|
+
try {
|
|
1442
|
+
this.ensureInitialized();
|
|
1443
|
+
this.ensureSigner();
|
|
1444
|
+
const validatedParams = validateSupplyParams(params);
|
|
1445
|
+
const amount = parseAmount(validatedParams.amount).toString();
|
|
1446
|
+
const userAddress = validatedParams.user;
|
|
1447
|
+
elizaLogger.info(`Supplying ${amount} ${validatedParams.asset} for ${userAddress}`);
|
|
1448
|
+
const reserveData = await this.getReserveData(validatedParams.asset);
|
|
1449
|
+
const supplyTxs = await this.poolService.supply({
|
|
1450
|
+
user: userAddress,
|
|
1451
|
+
reserve: reserveData.underlyingAsset,
|
|
1452
|
+
amount,
|
|
1453
|
+
onBehalfOf: userAddress
|
|
1454
|
+
});
|
|
1455
|
+
if (supplyTxs.length === 0) {
|
|
1456
|
+
throw new AaveError(
|
|
1457
|
+
"Failed to generate supply transaction",
|
|
1458
|
+
"TRANSACTION_GENERATION_FAILED" /* TRANSACTION_GENERATION_FAILED */
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
let approvalTxHash;
|
|
1462
|
+
let supplyTxHash;
|
|
1463
|
+
for (const tx of supplyTxs) {
|
|
1464
|
+
const ethersTransaction = {
|
|
1465
|
+
to: tx.to,
|
|
1466
|
+
data: tx.data,
|
|
1467
|
+
value: tx.value || "0",
|
|
1468
|
+
gasLimit: tx.gasLimit
|
|
1469
|
+
};
|
|
1470
|
+
const txResponse = await this.signer.sendTransaction(ethersTransaction);
|
|
1471
|
+
const receipt = await txResponse.wait();
|
|
1472
|
+
if (tx.txType === "ERC20_APPROVAL") {
|
|
1473
|
+
approvalTxHash = receipt.transactionHash;
|
|
1474
|
+
elizaLogger.info(`Token approval completed: ${approvalTxHash}`);
|
|
1475
|
+
} else {
|
|
1476
|
+
supplyTxHash = receipt.transactionHash;
|
|
1477
|
+
elizaLogger.info(`Supply transaction completed: ${supplyTxHash}`);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
const accountData = await this.getUserAccountData(userAddress);
|
|
1481
|
+
const aTokensReceived = new BigNumber3(amount);
|
|
1482
|
+
return {
|
|
1483
|
+
success: true,
|
|
1484
|
+
transactionHash: supplyTxHash,
|
|
1485
|
+
suppliedAmount: new BigNumber3(amount),
|
|
1486
|
+
aTokenAmount: aTokensReceived,
|
|
1487
|
+
newATokenBalance: aTokensReceived,
|
|
1488
|
+
// Simplified - would need to query actual balance
|
|
1489
|
+
gasUsed: new BigNumber3("0")
|
|
1490
|
+
// Would need to calculate from receipt
|
|
1491
|
+
};
|
|
1492
|
+
} catch (error) {
|
|
1493
|
+
elizaLogger.error("Supply operation failed:", error);
|
|
1494
|
+
const processedError = handleError(error instanceof Error ? error : new Error(String(error)), "supply", params);
|
|
1495
|
+
throw new AaveError(
|
|
1496
|
+
processedError.userMessage,
|
|
1497
|
+
"SUPPLY_FAILED" /* SUPPLY_FAILED */,
|
|
1498
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1499
|
+
{ operation: "supply", params }
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
async withdraw(params) {
|
|
1504
|
+
try {
|
|
1505
|
+
this.ensureInitialized();
|
|
1506
|
+
this.ensureSigner();
|
|
1507
|
+
const validatedParams = validateWithdrawParams(params);
|
|
1508
|
+
const userAddress = validatedParams.user;
|
|
1509
|
+
elizaLogger.info(`Withdrawing ${validatedParams.amount} ${validatedParams.asset} for ${userAddress}`);
|
|
1510
|
+
const reserveData = await this.getReserveData(validatedParams.asset);
|
|
1511
|
+
let amount;
|
|
1512
|
+
if (isMaxAmount(validatedParams.amount)) {
|
|
1513
|
+
amount = "-1";
|
|
1514
|
+
} else {
|
|
1515
|
+
amount = parseAmount(validatedParams.amount).toString();
|
|
1516
|
+
}
|
|
1517
|
+
const withdrawTxs = await this.poolService.withdraw({
|
|
1518
|
+
user: userAddress,
|
|
1519
|
+
reserve: reserveData.underlyingAsset,
|
|
1520
|
+
amount,
|
|
1521
|
+
onBehalfOf: userAddress
|
|
1522
|
+
});
|
|
1523
|
+
if (withdrawTxs.length === 0) {
|
|
1524
|
+
throw new AaveError(
|
|
1525
|
+
"Failed to generate withdraw transaction",
|
|
1526
|
+
"TRANSACTION_GENERATION_FAILED" /* TRANSACTION_GENERATION_FAILED */
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
const tx = withdrawTxs[0];
|
|
1530
|
+
const ethersTransaction = {
|
|
1531
|
+
to: tx.to,
|
|
1532
|
+
data: tx.data,
|
|
1533
|
+
value: tx.value || "0",
|
|
1534
|
+
gasLimit: tx.gasLimit
|
|
1535
|
+
};
|
|
1536
|
+
const txResponse = await this.signer.sendTransaction(ethersTransaction);
|
|
1537
|
+
const receipt = await txResponse.wait();
|
|
1538
|
+
const withdrawTxHash = receipt.transactionHash;
|
|
1539
|
+
elizaLogger.info(`Withdraw transaction completed: ${withdrawTxHash}`);
|
|
1540
|
+
const accountData = await this.getUserAccountData(userAddress);
|
|
1541
|
+
const amountWithdrawn = isMaxAmount(validatedParams.amount) ? new BigNumber3(amount) : parseAmount(validatedParams.amount);
|
|
1542
|
+
return {
|
|
1543
|
+
success: true,
|
|
1544
|
+
transactionHash: withdrawTxHash,
|
|
1545
|
+
amountWithdrawn,
|
|
1546
|
+
remainingATokenBalance: new BigNumber3(0),
|
|
1547
|
+
// Would need to query aToken contract
|
|
1548
|
+
newHealthFactor: accountData.healthFactor.toNumber(),
|
|
1549
|
+
gasUsed: new BigNumber3(receipt.gasUsed.toString())
|
|
1550
|
+
};
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
elizaLogger.error("Withdraw operation failed:", error);
|
|
1553
|
+
const processedError = handleError(error instanceof Error ? error : new Error(String(error)), "withdraw", params);
|
|
1554
|
+
throw new AaveError(
|
|
1555
|
+
processedError.userMessage,
|
|
1556
|
+
"WITHDRAW_FAILED" /* WITHDRAW_FAILED */,
|
|
1557
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1558
|
+
{ operation: "withdraw", params }
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
async borrow(params) {
|
|
1563
|
+
try {
|
|
1564
|
+
this.ensureInitialized();
|
|
1565
|
+
this.ensureSigner();
|
|
1566
|
+
const validatedParams = validateBorrowParams(params);
|
|
1567
|
+
const amount = parseAmount(validatedParams.amount).toString();
|
|
1568
|
+
const userAddress = validatedParams.user;
|
|
1569
|
+
const rateMode = validatedParams.interestRateMode;
|
|
1570
|
+
elizaLogger.info(`Borrowing ${amount} ${validatedParams.asset} for ${userAddress}`);
|
|
1571
|
+
const reserveData = await this.getReserveData(validatedParams.asset);
|
|
1572
|
+
const borrowTxs = await this.poolService.borrow({
|
|
1573
|
+
user: userAddress,
|
|
1574
|
+
reserve: reserveData.underlyingAsset,
|
|
1575
|
+
amount,
|
|
1576
|
+
interestRateMode: rateMode === 1 /* STABLE */ ? 1 : 2,
|
|
1577
|
+
onBehalfOf: userAddress
|
|
1578
|
+
});
|
|
1579
|
+
if (borrowTxs.length === 0) {
|
|
1580
|
+
throw new AaveError(
|
|
1581
|
+
"Failed to generate borrow transaction",
|
|
1582
|
+
"TRANSACTION_GENERATION_FAILED" /* TRANSACTION_GENERATION_FAILED */
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
const tx = borrowTxs[0];
|
|
1586
|
+
const ethersTransaction = {
|
|
1587
|
+
to: tx.to,
|
|
1588
|
+
data: tx.data,
|
|
1589
|
+
value: tx.value || "0",
|
|
1590
|
+
gasLimit: tx.gasLimit
|
|
1591
|
+
};
|
|
1592
|
+
const txResponse = await this.signer.sendTransaction(ethersTransaction);
|
|
1593
|
+
const receipt = await txResponse.wait();
|
|
1594
|
+
const borrowTxHash = receipt.transactionHash;
|
|
1595
|
+
elizaLogger.info(`Borrow transaction completed: ${borrowTxHash}`);
|
|
1596
|
+
const accountData = await this.getUserAccountData(userAddress);
|
|
1597
|
+
const borrowAPY = rateMode === 2 /* VARIABLE */ ? Number(reserveData.variableBorrowAPY) : Number(reserveData.stableBorrowAPY);
|
|
1598
|
+
return {
|
|
1599
|
+
success: true,
|
|
1600
|
+
transactionHash: borrowTxHash,
|
|
1601
|
+
amountBorrowed: new BigNumber3(amount),
|
|
1602
|
+
interestRateMode: rateMode,
|
|
1603
|
+
currentBorrowAPY: borrowAPY,
|
|
1604
|
+
newHealthFactor: accountData.healthFactor.toNumber(),
|
|
1605
|
+
gasUsed: new BigNumber3(receipt.gasUsed.toString())
|
|
1606
|
+
};
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
elizaLogger.error("Borrow operation failed:", error);
|
|
1609
|
+
const processedError = handleError(error instanceof Error ? error : new Error(String(error)), "borrow", params);
|
|
1610
|
+
throw new AaveError(
|
|
1611
|
+
processedError.userMessage,
|
|
1612
|
+
"BORROW_FAILED" /* BORROW_FAILED */,
|
|
1613
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1614
|
+
{ operation: "borrow", params }
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
async repay(params) {
|
|
1619
|
+
try {
|
|
1620
|
+
this.ensureInitialized();
|
|
1621
|
+
this.ensureSigner();
|
|
1622
|
+
const validatedParams = validateRepayParams(params);
|
|
1623
|
+
const userAddress = validatedParams.user;
|
|
1624
|
+
const rateMode = validatedParams.interestRateMode;
|
|
1625
|
+
elizaLogger.info(`Repaying ${validatedParams.amount} ${validatedParams.asset} for ${userAddress}`);
|
|
1626
|
+
const reserveData = await this.getReserveData(validatedParams.asset);
|
|
1627
|
+
let amount;
|
|
1628
|
+
if (isMaxAmount(validatedParams.amount)) {
|
|
1629
|
+
amount = "-1";
|
|
1630
|
+
} else {
|
|
1631
|
+
amount = parseAmount(validatedParams.amount).toString();
|
|
1632
|
+
}
|
|
1633
|
+
const repayTxs = await this.poolService.repay({
|
|
1634
|
+
user: userAddress,
|
|
1635
|
+
reserve: reserveData.underlyingAsset,
|
|
1636
|
+
amount,
|
|
1637
|
+
interestRateMode: rateMode === 1 /* STABLE */ ? 1 : 2,
|
|
1638
|
+
onBehalfOf: userAddress
|
|
1639
|
+
});
|
|
1640
|
+
if (repayTxs.length === 0) {
|
|
1641
|
+
throw new AaveError(
|
|
1642
|
+
"Failed to generate repay transaction",
|
|
1643
|
+
"TRANSACTION_GENERATION_FAILED" /* TRANSACTION_GENERATION_FAILED */
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
let approvalTxHash;
|
|
1647
|
+
let repayTxHash;
|
|
1648
|
+
for (const tx of repayTxs) {
|
|
1649
|
+
const ethersTransaction = {
|
|
1650
|
+
to: tx.to,
|
|
1651
|
+
data: tx.data,
|
|
1652
|
+
value: tx.value || "0",
|
|
1653
|
+
gasLimit: tx.gasLimit
|
|
1654
|
+
};
|
|
1655
|
+
const txResponse = await this.signer.sendTransaction(ethersTransaction);
|
|
1656
|
+
const receipt = await txResponse.wait();
|
|
1657
|
+
if (tx.txType === "ERC20_APPROVAL") {
|
|
1658
|
+
approvalTxHash = receipt.transactionHash;
|
|
1659
|
+
elizaLogger.info(`Token approval completed: ${approvalTxHash}`);
|
|
1660
|
+
} else {
|
|
1661
|
+
repayTxHash = receipt.transactionHash;
|
|
1662
|
+
elizaLogger.info(`Repay transaction completed: ${repayTxHash}`);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
const accountData = await this.getUserAccountData(userAddress);
|
|
1666
|
+
const amountRepaid = isMaxAmount(validatedParams.amount) ? new BigNumber3(amount === "-1" ? "0" : amount) : parseAmount(validatedParams.amount);
|
|
1667
|
+
return {
|
|
1668
|
+
success: true,
|
|
1669
|
+
transactionHash: repayTxHash,
|
|
1670
|
+
amountRepaid,
|
|
1671
|
+
interestRateMode: rateMode,
|
|
1672
|
+
remainingDebt: accountData.totalDebtETH,
|
|
1673
|
+
newHealthFactor: accountData.healthFactor.toNumber(),
|
|
1674
|
+
gasUsed: new BigNumber3("0"),
|
|
1675
|
+
// Would need to calculate from receipt
|
|
1676
|
+
approvalTransactionHash: approvalTxHash
|
|
1677
|
+
};
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
elizaLogger.error("Repay operation failed:", error);
|
|
1680
|
+
const processedError = handleError(error instanceof Error ? error : new Error(String(error)), "repay", params);
|
|
1681
|
+
throw new AaveError(
|
|
1682
|
+
processedError.userMessage,
|
|
1683
|
+
"REPAY_FAILED" /* REPAY_FAILED */,
|
|
1684
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1685
|
+
{ operation: "repay", params }
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
async getUserPosition(userAddress) {
|
|
1690
|
+
try {
|
|
1691
|
+
this.ensureInitialized();
|
|
1692
|
+
if (!this.uiPoolDataProvider) {
|
|
1693
|
+
throw new AaveError(
|
|
1694
|
+
"UI Pool Data Provider not available on this chain - cannot fetch user positions",
|
|
1695
|
+
"UNSUPPORTED_OPERATION" /* UNSUPPORTED_OPERATION */
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
const validatedAddress = validateAddress(userAddress);
|
|
1699
|
+
const userReservesData = await this.uiPoolDataProvider.getUserReservesHumanized({
|
|
1700
|
+
lendingPoolAddressProvider: this.chainContext.addresses.POOL_ADDRESSES_PROVIDER,
|
|
1701
|
+
user: validatedAddress
|
|
1702
|
+
});
|
|
1703
|
+
const reservesData = await this.uiPoolDataProvider.getReservesHumanized({
|
|
1704
|
+
lendingPoolAddressProvider: this.chainContext.addresses.POOL_ADDRESSES_PROVIDER
|
|
1705
|
+
});
|
|
1706
|
+
const formattedPoolReserves = formatReserves({
|
|
1707
|
+
reserves: reservesData.reservesData,
|
|
1708
|
+
currentTimestamp: Date.now() / 1e3,
|
|
1709
|
+
marketReferencePriceInUsd: reservesData.baseCurrencyData.marketReferenceCurrencyPriceInUsd,
|
|
1710
|
+
marketReferenceCurrencyDecimals: reservesData.baseCurrencyData.marketReferenceCurrencyDecimals
|
|
1711
|
+
});
|
|
1712
|
+
const userSummary = formatUserSummary({
|
|
1713
|
+
currentTimestamp: Date.now() / 1e3,
|
|
1714
|
+
marketReferencePriceInUsd: reservesData.baseCurrencyData.marketReferenceCurrencyPriceInUsd,
|
|
1715
|
+
marketReferenceCurrencyDecimals: reservesData.baseCurrencyData.marketReferenceCurrencyDecimals,
|
|
1716
|
+
userReserves: userReservesData.userReserves,
|
|
1717
|
+
formattedReserves: formattedPoolReserves,
|
|
1718
|
+
userEmodeCategoryId: userReservesData.userEmodeCategoryId
|
|
1719
|
+
});
|
|
1720
|
+
const positions = userReservesData.userReserves.filter(
|
|
1721
|
+
(reserve) => reserve.scaledATokenBalance !== "0" || reserve.scaledVariableDebt !== "0" || reserve.principalStableDebt !== "0"
|
|
1722
|
+
).map((reserve) => {
|
|
1723
|
+
const poolReserve = formattedPoolReserves.find(
|
|
1724
|
+
(r) => r.underlyingAsset.toLowerCase() === reserve.underlyingAsset.toLowerCase()
|
|
1725
|
+
);
|
|
1726
|
+
return {
|
|
1727
|
+
asset: poolReserve?.symbol || "Unknown",
|
|
1728
|
+
suppliedAmount: new BigNumber3(reserve.scaledATokenBalance || "0"),
|
|
1729
|
+
borrowedAmountVariable: new BigNumber3(reserve.scaledVariableDebt || "0"),
|
|
1730
|
+
borrowedAmountStable: new BigNumber3(reserve.principalStableDebt || "0"),
|
|
1731
|
+
supplyAPY: Number(poolReserve?.supplyAPY || "0"),
|
|
1732
|
+
variableBorrowAPY: Number(poolReserve?.variableBorrowAPY || "0"),
|
|
1733
|
+
stableBorrowAPY: Number(poolReserve?.variableBorrowAPY || "0"),
|
|
1734
|
+
// Use variableBorrowAPY since stableBorrowAPY doesn't exist
|
|
1735
|
+
isCollateral: reserve.usageAsCollateralEnabled,
|
|
1736
|
+
liquidationThreshold: Number(poolReserve?.reserveLiquidationThreshold || "0") / 1e4,
|
|
1737
|
+
ltv: Number(poolReserve?.baseLTVasCollateral || "0") / 1e4
|
|
1738
|
+
};
|
|
1739
|
+
});
|
|
1740
|
+
return {
|
|
1741
|
+
userAddress: validatedAddress,
|
|
1742
|
+
totalCollateralETH: new BigNumber3(userSummary.totalCollateralUSD),
|
|
1743
|
+
totalDebtETH: new BigNumber3(userSummary.totalBorrowsUSD),
|
|
1744
|
+
availableBorrowsETH: new BigNumber3(userSummary.availableBorrowsUSD),
|
|
1745
|
+
currentLiquidationThreshold: Number(userSummary.currentLiquidationThreshold),
|
|
1746
|
+
ltv: Number(userSummary.currentLtv || userSummary.totalLTV || 0),
|
|
1747
|
+
healthFactor: new BigNumber3(userSummary.healthFactor),
|
|
1748
|
+
positions,
|
|
1749
|
+
lastUpdated: Date.now()
|
|
1750
|
+
};
|
|
1751
|
+
} catch (error) {
|
|
1752
|
+
elizaLogger.error("Failed to get user position:", error);
|
|
1753
|
+
throw new AaveError(
|
|
1754
|
+
"Failed to fetch user position",
|
|
1755
|
+
"DATA_FETCH_FAILED" /* DATA_FETCH_FAILED */,
|
|
1756
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1757
|
+
{ userAddress }
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
async getUserAccountData(userAddress) {
|
|
1762
|
+
try {
|
|
1763
|
+
this.ensureInitialized();
|
|
1764
|
+
const position = await this.getUserPosition(userAddress);
|
|
1765
|
+
return {
|
|
1766
|
+
totalCollateralETH: position.totalCollateralETH,
|
|
1767
|
+
totalDebtETH: position.totalDebtETH,
|
|
1768
|
+
availableBorrowsETH: position.availableBorrowsETH,
|
|
1769
|
+
currentLiquidationThreshold: position.currentLiquidationThreshold,
|
|
1770
|
+
ltv: position.ltv,
|
|
1771
|
+
healthFactor: position.healthFactor
|
|
1772
|
+
};
|
|
1773
|
+
} catch (error) {
|
|
1774
|
+
elizaLogger.error("Failed to get user account data:", error);
|
|
1775
|
+
throw new AaveError(
|
|
1776
|
+
"Failed to fetch user account data",
|
|
1777
|
+
"DATA_FETCH_FAILED" /* DATA_FETCH_FAILED */,
|
|
1778
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1779
|
+
{ userAddress }
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
async getMarketData() {
|
|
1784
|
+
this.ensureInitialized();
|
|
1785
|
+
try {
|
|
1786
|
+
if (!this.uiPoolDataProvider) {
|
|
1787
|
+
throw new AaveError(
|
|
1788
|
+
"UI Pool Data Provider not available on this chain - cannot fetch market data",
|
|
1789
|
+
"UNSUPPORTED_OPERATION" /* UNSUPPORTED_OPERATION */
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
const reservesData = await this.uiPoolDataProvider.getReservesHumanized({
|
|
1793
|
+
lendingPoolAddressProvider: this.chainContext.addresses.POOL_ADDRESSES_PROVIDER
|
|
1794
|
+
});
|
|
1795
|
+
const formattedReserves = formatReserves({
|
|
1796
|
+
reserves: reservesData.reservesData,
|
|
1797
|
+
currentTimestamp: Date.now() / 1e3,
|
|
1798
|
+
marketReferencePriceInUsd: reservesData.baseCurrencyData.marketReferenceCurrencyPriceInUsd,
|
|
1799
|
+
marketReferenceCurrencyDecimals: reservesData.baseCurrencyData.marketReferenceCurrencyDecimals
|
|
1800
|
+
});
|
|
1801
|
+
return formattedReserves.map((reserve) => ({
|
|
1802
|
+
asset: reserve.symbol,
|
|
1803
|
+
aTokenAddress: reserve.aTokenAddress,
|
|
1804
|
+
stableDebtTokenAddress: reserve.stableDebtTokenAddress,
|
|
1805
|
+
variableDebtTokenAddress: reserve.variableDebtTokenAddress,
|
|
1806
|
+
underlyingAsset: reserve.underlyingAsset,
|
|
1807
|
+
decimals: reserve.decimals,
|
|
1808
|
+
supplyAPY: Number(reserve.supplyAPY),
|
|
1809
|
+
variableBorrowAPY: Number(reserve.variableBorrowAPY),
|
|
1810
|
+
stableBorrowAPY: Number(reserve.stableBorrowAPY),
|
|
1811
|
+
totalSupply: new BigNumber3(reserve.totalLiquidity),
|
|
1812
|
+
totalBorrow: new BigNumber3(reserve.totalDebt),
|
|
1813
|
+
utilizationRate: Number(reserve.utilizationRate),
|
|
1814
|
+
ltv: Number(reserve.baseLTVasCollateral) / 100,
|
|
1815
|
+
liquidationThreshold: Number(reserve.liquidationThreshold) / 100,
|
|
1816
|
+
liquidationBonus: Number(reserve.liquidationBonus) / 100,
|
|
1817
|
+
reserveFactor: Number(reserve.reserveFactor) / 100,
|
|
1818
|
+
priceInUSD: new BigNumber3(reserve.priceInUSD || "0"),
|
|
1819
|
+
isActive: reserve.isActive,
|
|
1820
|
+
isFrozen: reserve.isFrozen,
|
|
1821
|
+
isPaused: reserve.isPaused,
|
|
1822
|
+
supplyCap: new BigNumber3(reserve.supplyCap || "0"),
|
|
1823
|
+
borrowCap: new BigNumber3(reserve.borrowCap || "0"),
|
|
1824
|
+
lastUpdated: Date.now()
|
|
1825
|
+
}));
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
elizaLogger.error("Failed to get market data:", error);
|
|
1828
|
+
throw new AaveError(
|
|
1829
|
+
"Failed to fetch market data",
|
|
1830
|
+
"DATA_FETCH_FAILED" /* DATA_FETCH_FAILED */,
|
|
1831
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1832
|
+
{}
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
async getReserveData(asset) {
|
|
1837
|
+
const marketData = await this.getMarketData();
|
|
1838
|
+
const assetData = marketData.find(
|
|
1839
|
+
(m) => m.asset.toLowerCase() === asset.toLowerCase() || m.underlyingAsset.toLowerCase() === asset.toLowerCase()
|
|
1840
|
+
);
|
|
1841
|
+
if (!assetData) {
|
|
1842
|
+
throw new AaveError(
|
|
1843
|
+
`Asset not found in market data: ${asset}`,
|
|
1844
|
+
"ASSET_NOT_FOUND" /* ASSET_NOT_FOUND */,
|
|
1845
|
+
void 0,
|
|
1846
|
+
{ asset }
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
return assetData;
|
|
1850
|
+
}
|
|
1851
|
+
async stop() {
|
|
1852
|
+
elizaLogger.info("Stopping Aave service...");
|
|
1853
|
+
this.isInitialized = false;
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
var extractionTemplate = `Given the user's request to supply/deposit assets to Aave, extract the required information.
|
|
1857
|
+
|
|
1858
|
+
User request: "{{userMessage}}"
|
|
1859
|
+
|
|
1860
|
+
Extract and return ONLY a JSON object with the following structure:
|
|
1861
|
+
{
|
|
1862
|
+
"asset": "asset symbol (e.g., USDC, WETH, DAI) or address",
|
|
1863
|
+
"amount": "amount to supply as string (e.g., '100', '0.5')",
|
|
1864
|
+
"userAddress": "user's ethereum address if mentioned, otherwise null"
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
Important:
|
|
1868
|
+
- For ETH mentions, use "WETH" as the asset
|
|
1869
|
+
- Amount should be the numeric value only (no symbols)
|
|
1870
|
+
- If amount is "all", "max", or "everything", use "max"
|
|
1871
|
+
- Asset should be uppercase symbol if recognizable
|
|
1872
|
+
|
|
1873
|
+
Return only the JSON object, no other text.`;
|
|
1874
|
+
var supplyAction = {
|
|
1875
|
+
name: "SUPPLY_TO_AAVE",
|
|
1876
|
+
description: "Supply assets to Aave V3 to earn yield and receive aTokens",
|
|
1877
|
+
similes: ["DEPOSIT_TO_AAVE", "LEND_TO_AAVE", "SUPPLY_ASSET", "DEPOSIT_ASSET"],
|
|
1878
|
+
examples: [[
|
|
1879
|
+
{
|
|
1880
|
+
name: "User",
|
|
1881
|
+
content: { text: "I want to supply 1000 USDC to Aave" }
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
name: "Assistant",
|
|
1885
|
+
content: {
|
|
1886
|
+
text: "\u{1F3E6} Supplying 1000 USDC to Aave V3...\n\n\u2705 Successfully supplied 1000 USDC\n\u{1F4C4} Transaction: 0x123...abc\n\u{1FA99} Received: 1000 aUSDC (earning yield)\n\u{1F4B0} Current APY: 3.25%",
|
|
1887
|
+
actions: ["SUPPLY_TO_AAVE"]
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
], [
|
|
1891
|
+
{
|
|
1892
|
+
name: "User",
|
|
1893
|
+
content: { text: "deposit 0.5 ETH to start earning yield" }
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
name: "Assistant",
|
|
1897
|
+
content: {
|
|
1898
|
+
text: "\u{1F3E6} Supplying 0.5 WETH to Aave V3...\n\n\u2705 Successfully supplied 0.5 WETH\n\u{1F4C4} Transaction: 0x456...def\n\u{1FA99} Received: 0.5 aWETH (earning yield)\n\u{1F4B0} Current APY: 2.15%",
|
|
1899
|
+
actions: ["SUPPLY_TO_AAVE"]
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
], [
|
|
1903
|
+
{
|
|
1904
|
+
name: "User",
|
|
1905
|
+
content: { text: "supply all my DAI to aave for lending" }
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
name: "Assistant",
|
|
1909
|
+
content: {
|
|
1910
|
+
text: "\u{1F3E6} Supplying all available DAI to Aave V3...\n\n\u2705 Successfully supplied 2,500 DAI\n\u{1F4C4} Transaction: 0x789...ghi\n\u{1FA99} Received: 2,500 aDAI (earning yield)\n\u{1F4B0} Current APY: 4.80%",
|
|
1911
|
+
actions: ["SUPPLY_TO_AAVE"]
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
]],
|
|
1915
|
+
async validate(runtime, message, _state) {
|
|
1916
|
+
try {
|
|
1917
|
+
const rpcUrl = runtime.getSetting("AAVE_RPC_URL");
|
|
1918
|
+
if (!rpcUrl) {
|
|
1919
|
+
return false;
|
|
1920
|
+
}
|
|
1921
|
+
const text = message.content.text?.toLowerCase() || "";
|
|
1922
|
+
const supplyKeywords = [
|
|
1923
|
+
"supply",
|
|
1924
|
+
"deposit",
|
|
1925
|
+
"lend",
|
|
1926
|
+
"provide liquidity"
|
|
1927
|
+
];
|
|
1928
|
+
const hasSupplyKeyword = supplyKeywords.some((keyword) => text.includes(keyword));
|
|
1929
|
+
const hasAmount = /\d+/.test(text) || text.includes("all") || text.includes("max");
|
|
1930
|
+
const hasAsset = /(usdc|usdt|dai|eth|weth|btc|wbtc|aave|link)/i.test(text);
|
|
1931
|
+
return hasSupplyKeyword && (hasAmount || hasAsset);
|
|
1932
|
+
} catch {
|
|
1933
|
+
return false;
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
async handler(runtime, message, _state, _options, callback) {
|
|
1937
|
+
const content = message.content.text;
|
|
1938
|
+
if (!content) {
|
|
1939
|
+
const errorMessage = "Please specify the asset and amount to supply to Aave.";
|
|
1940
|
+
await callback?.({ text: errorMessage, source: message.content.source });
|
|
1941
|
+
return { text: errorMessage, success: false };
|
|
1942
|
+
}
|
|
1943
|
+
let asset = "";
|
|
1944
|
+
let amount = "";
|
|
1945
|
+
let userAddress;
|
|
1946
|
+
try {
|
|
1947
|
+
const aaveService = runtime.getService("aave");
|
|
1948
|
+
if (!aaveService) {
|
|
1949
|
+
throw new Error("Aave service not available");
|
|
1950
|
+
}
|
|
1951
|
+
const prompt = extractionTemplate.replace("{{userMessage}}", content);
|
|
1952
|
+
const response = await runtime.useModel(ModelType.TEXT_LARGE, { prompt });
|
|
1953
|
+
try {
|
|
1954
|
+
const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
1955
|
+
const parsed = JSON.parse(cleanedResponse);
|
|
1956
|
+
asset = parsed.asset;
|
|
1957
|
+
amount = parsed.amount;
|
|
1958
|
+
userAddress = parsed.userAddress;
|
|
1959
|
+
if (!asset || !amount) {
|
|
1960
|
+
throw new Error("Missing required parameters");
|
|
1961
|
+
}
|
|
1962
|
+
} catch (parseError) {
|
|
1963
|
+
elizaLogger.warn("Failed to parse LLM response, falling back to regex:", parseError);
|
|
1964
|
+
const amountAssetMatch = content.match(/(?:supply|deposit|lend)\s+([a-zA-Z\d.,]+)\s+(\w+)/i);
|
|
1965
|
+
const assetAmountMatch = content.match(/(\w+)\s+([a-zA-Z\d.,]+)/i);
|
|
1966
|
+
if (amountAssetMatch) {
|
|
1967
|
+
amount = amountAssetMatch[1].replace(/,/g, "");
|
|
1968
|
+
asset = amountAssetMatch[2].toUpperCase();
|
|
1969
|
+
} else if (assetAmountMatch && assetAmountMatch[2].match(/^\d/)) {
|
|
1970
|
+
asset = assetAmountMatch[1].toUpperCase();
|
|
1971
|
+
amount = assetAmountMatch[2].replace(/,/g, "");
|
|
1972
|
+
} else {
|
|
1973
|
+
throw new Error("Could not extract asset and amount from request");
|
|
1974
|
+
}
|
|
1975
|
+
if (asset === "ETH") {
|
|
1976
|
+
asset = "WETH";
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
const supplyParams = validateSupplyParams({
|
|
1980
|
+
asset,
|
|
1981
|
+
amount,
|
|
1982
|
+
// Let validation handle string to BigNumber conversion
|
|
1983
|
+
user: userAddress || runtime.getSetting("WALLET_ADDRESS")
|
|
1984
|
+
});
|
|
1985
|
+
const result = await aaveService.supply(supplyParams);
|
|
1986
|
+
const successMessage = `\u{1F3E6} **Successfully Supplied to Aave V3**
|
|
1987
|
+
|
|
1988
|
+
\u{1F4B0} **Amount**: ${result.suppliedAmount.toFixed(4)} ${asset}
|
|
1989
|
+
\u{1FA99} **Received**: ${result.aTokenAmount.toFixed(4)} a${asset}
|
|
1990
|
+
\u{1F4C4} **Transaction**: ${result.transactionHash}
|
|
1991
|
+
\u{1F4B0} **New aToken Balance**: ${result.newATokenBalance.toFixed(4)} a${asset}
|
|
1992
|
+
|
|
1993
|
+
Your a${asset} tokens will automatically earn yield! \u{1F331}`;
|
|
1994
|
+
await callback?.({
|
|
1995
|
+
text: successMessage,
|
|
1996
|
+
source: message.content.source
|
|
1997
|
+
});
|
|
1998
|
+
return {
|
|
1999
|
+
text: `Successfully supplied ${result.suppliedAmount.toFixed(4)} ${asset} to Aave`,
|
|
2000
|
+
success: true,
|
|
2001
|
+
data: {
|
|
2002
|
+
action: "supply",
|
|
2003
|
+
asset,
|
|
2004
|
+
amount: result.suppliedAmount.toString(),
|
|
2005
|
+
aTokensReceived: result.aTokenAmount.toString(),
|
|
2006
|
+
newBalance: result.newATokenBalance.toString(),
|
|
2007
|
+
transactionHash: result.transactionHash
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
2010
|
+
} catch (error) {
|
|
2011
|
+
elizaLogger.error("Supply action failed:", error);
|
|
2012
|
+
let errorMessage;
|
|
2013
|
+
if (error instanceof Error) {
|
|
2014
|
+
if (error.message.includes("insufficient balance")) {
|
|
2015
|
+
errorMessage = `\u274C Insufficient ${asset} balance. Please check your wallet balance and try again.`;
|
|
2016
|
+
} else if (error.message.includes("allowance")) {
|
|
2017
|
+
errorMessage = `\u274C Token approval required. The transaction will include approval for ${asset}.`;
|
|
2018
|
+
} else if (error.message.includes("reserve inactive")) {
|
|
2019
|
+
errorMessage = `\u274C ${asset} market is currently inactive on Aave. Please try a different asset.`;
|
|
2020
|
+
} else if (error.message.includes("supply cap")) {
|
|
2021
|
+
errorMessage = `\u274C ${asset} supply cap reached on Aave. Please try a different asset or smaller amount.`;
|
|
2022
|
+
} else {
|
|
2023
|
+
errorMessage = `\u274C Supply failed: ${error.message}`;
|
|
2024
|
+
}
|
|
2025
|
+
} else {
|
|
2026
|
+
errorMessage = "\u274C Supply failed due to an unknown error. Please try again.";
|
|
2027
|
+
}
|
|
2028
|
+
await callback?.({
|
|
2029
|
+
text: errorMessage,
|
|
2030
|
+
source: message.content.source
|
|
2031
|
+
});
|
|
2032
|
+
return {
|
|
2033
|
+
text: errorMessage,
|
|
2034
|
+
success: false,
|
|
2035
|
+
data: {
|
|
2036
|
+
action: "supply",
|
|
2037
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2038
|
+
}
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
var extractionTemplate2 = `Given the user's request to withdraw/redeem assets from Aave, extract the required information.
|
|
2044
|
+
|
|
2045
|
+
User request: "{{userMessage}}"
|
|
2046
|
+
|
|
2047
|
+
Extract and return ONLY a JSON object with the following structure:
|
|
2048
|
+
{
|
|
2049
|
+
"asset": "asset symbol (e.g., USDC, WETH, DAI) or address",
|
|
2050
|
+
"amount": "amount to withdraw as string (e.g., '100', '0.5', 'max')",
|
|
2051
|
+
"userAddress": "user's ethereum address if mentioned, otherwise null"
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
Important:
|
|
2055
|
+
- For ETH mentions, use "WETH" as the asset
|
|
2056
|
+
- Amount should be the numeric value only (no symbols)
|
|
2057
|
+
- If amount is "all", "max", "everything", or similar, use "max"
|
|
2058
|
+
- Asset should be uppercase symbol if recognizable
|
|
2059
|
+
|
|
2060
|
+
Return only the JSON object, no other text.`;
|
|
2061
|
+
var withdrawAction = {
|
|
2062
|
+
name: "WITHDRAW_FROM_AAVE",
|
|
2063
|
+
description: "Withdraw supplied assets from Aave V3 by redeeming aTokens",
|
|
2064
|
+
similes: ["REDEEM_FROM_AAVE", "UNSTAKE_FROM_AAVE", "WITHDRAW_ASSET", "REDEEM_ATOKEN"],
|
|
2065
|
+
examples: [[
|
|
2066
|
+
{
|
|
2067
|
+
name: "User",
|
|
2068
|
+
content: { text: "I want to withdraw 500 USDC from Aave" }
|
|
2069
|
+
},
|
|
2070
|
+
{
|
|
2071
|
+
name: "Assistant",
|
|
2072
|
+
content: {
|
|
2073
|
+
text: "\u{1F4B8} Withdrawing 500 USDC from Aave V3...\n\n\u2705 Successfully withdrew 500 USDC\n\u{1F4C4} Transaction: 0x123...abc\n\u{1F525} Burned: 500 aUSDC\n\u{1F4B0} Remaining balance: 1,500 aUSDC\n\u{1F4CA} Earning: 3.25% APY on remaining balance",
|
|
2074
|
+
actions: ["WITHDRAW_FROM_AAVE"]
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
], [
|
|
2078
|
+
{
|
|
2079
|
+
name: "User",
|
|
2080
|
+
content: { text: "redeem 0.1 ETH from my aave position" }
|
|
2081
|
+
},
|
|
2082
|
+
{
|
|
2083
|
+
name: "Assistant",
|
|
2084
|
+
content: {
|
|
2085
|
+
text: "\u{1F4B8} Withdrawing 0.1 WETH from Aave V3...\n\n\u2705 Successfully withdrew 0.1 WETH\n\u{1F4C4} Transaction: 0x456...def\n\u{1F525} Burned: 0.1 aWETH\n\u{1F4B0} Remaining balance: 0.9 aWETH\n\u{1F4CA} Earning: 2.15% APY on remaining balance",
|
|
2086
|
+
actions: ["WITHDRAW_FROM_AAVE"]
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
], [
|
|
2090
|
+
{
|
|
2091
|
+
name: "User",
|
|
2092
|
+
content: { text: "withdraw all my DAI from aave lending" }
|
|
2093
|
+
},
|
|
2094
|
+
{
|
|
2095
|
+
name: "Assistant",
|
|
2096
|
+
content: {
|
|
2097
|
+
text: "\u{1F4B8} Withdrawing all available DAI from Aave V3...\n\n\u2705 Successfully withdrew 2,500 DAI\n\u{1F4C4} Transaction: 0x789...ghi\n\u{1F525} Burned: 2,500 aDAI\n\u{1F4B0} Remaining balance: 0 aDAI\n\u{1F389} Position fully withdrawn!",
|
|
2098
|
+
actions: ["WITHDRAW_FROM_AAVE"]
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
], [
|
|
2102
|
+
{
|
|
2103
|
+
name: "User",
|
|
2104
|
+
content: { text: "can I get my 100 USDT back from aave?" }
|
|
2105
|
+
},
|
|
2106
|
+
{
|
|
2107
|
+
name: "Assistant",
|
|
2108
|
+
content: {
|
|
2109
|
+
text: "\u{1F4B8} Withdrawing 100 USDT from Aave V3...\n\n\u2705 Successfully withdrew 100 USDT\n\u{1F4C4} Transaction: 0xabc...xyz\n\u{1F525} Burned: 100 aUSDT\n\u{1F4B0} Remaining balance: 400 aUSDT\n\u{1F4CA} Earning: 4.10% APY on remaining balance",
|
|
2110
|
+
actions: ["WITHDRAW_FROM_AAVE"]
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
]],
|
|
2114
|
+
async validate(runtime, message, _state) {
|
|
2115
|
+
try {
|
|
2116
|
+
const rpcUrl = runtime.getSetting("AAVE_RPC_URL");
|
|
2117
|
+
if (!rpcUrl) {
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2120
|
+
const text = message.content.text?.toLowerCase() || "";
|
|
2121
|
+
const withdrawKeywords = [
|
|
2122
|
+
"withdraw",
|
|
2123
|
+
"redeem",
|
|
2124
|
+
"unstake",
|
|
2125
|
+
"take out",
|
|
2126
|
+
"get back",
|
|
2127
|
+
"withdraw from aave",
|
|
2128
|
+
"redeem from aave",
|
|
2129
|
+
"unstake from aave",
|
|
2130
|
+
"cash out",
|
|
2131
|
+
"exit position"
|
|
2132
|
+
];
|
|
2133
|
+
return withdrawKeywords.some((keyword) => text.includes(keyword));
|
|
2134
|
+
} catch {
|
|
2135
|
+
return false;
|
|
2136
|
+
}
|
|
2137
|
+
},
|
|
2138
|
+
async handler(runtime, message, _state, _options, callback) {
|
|
2139
|
+
const content = message.content.text;
|
|
2140
|
+
if (!content) {
|
|
2141
|
+
const errorMessage = "Please specify the asset and amount to withdraw from Aave.";
|
|
2142
|
+
await callback?.({ text: errorMessage, source: message.content.source });
|
|
2143
|
+
return { text: errorMessage, success: false };
|
|
2144
|
+
}
|
|
2145
|
+
let asset = "";
|
|
2146
|
+
let amount = "";
|
|
2147
|
+
let userAddress;
|
|
2148
|
+
try {
|
|
2149
|
+
const aaveService = runtime.getService("aave");
|
|
2150
|
+
if (!aaveService) {
|
|
2151
|
+
throw new Error("Aave service not available");
|
|
2152
|
+
}
|
|
2153
|
+
const prompt = extractionTemplate2.replace("{{userMessage}}", content);
|
|
2154
|
+
const response = await runtime.useModel(ModelType.TEXT_LARGE, { prompt });
|
|
2155
|
+
try {
|
|
2156
|
+
const cleanedResponse = response.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "").trim();
|
|
2157
|
+
const parsed = JSON.parse(cleanedResponse);
|
|
2158
|
+
asset = parsed.asset;
|
|
2159
|
+
amount = parsed.amount;
|
|
2160
|
+
userAddress = parsed.userAddress;
|
|
2161
|
+
if (!asset || !amount) {
|
|
2162
|
+
throw new Error("Missing required parameters");
|
|
2163
|
+
}
|
|
2164
|
+
} catch (parseError) {
|
|
2165
|
+
elizaLogger.warn("Failed to parse LLM response, falling back to regex:", parseError);
|
|
2166
|
+
const amountAssetMatch = content.match(/(?:withdraw|redeem|get.*back)\s+([a-zA-Z\d.,]+)\s+(\w+)/i);
|
|
2167
|
+
const assetAmountMatch = content.match(/(\w+)\s+([a-zA-Z\d.,]+)/i);
|
|
2168
|
+
const maxPatterns = /(?:withdraw|redeem|get).*(?:all|max|everything|full)/i;
|
|
2169
|
+
if (amountAssetMatch) {
|
|
2170
|
+
amount = amountAssetMatch[1].replace(/,/g, "");
|
|
2171
|
+
asset = amountAssetMatch[2].toUpperCase();
|
|
2172
|
+
} else if (assetAmountMatch && assetAmountMatch[2].match(/^\d/)) {
|
|
2173
|
+
asset = assetAmountMatch[1].toUpperCase();
|
|
2174
|
+
amount = assetAmountMatch[2].replace(/,/g, "");
|
|
2175
|
+
} else if (maxPatterns.test(content)) {
|
|
2176
|
+
const assetMatch = content.match(/(?:withdraw|redeem).*?(USDC|WETH|ETH|DAI|USDT|WBTC|LINK|UNI|AAVE|CRV|COMP)/i);
|
|
2177
|
+
if (assetMatch) {
|
|
2178
|
+
asset = assetMatch[1].toUpperCase();
|
|
2179
|
+
amount = "max";
|
|
2180
|
+
} else {
|
|
2181
|
+
throw new Error("Could not extract asset from request");
|
|
2182
|
+
}
|
|
2183
|
+
} else {
|
|
2184
|
+
throw new Error("Could not extract asset and amount from request");
|
|
2185
|
+
}
|
|
2186
|
+
if (asset === "ETH") {
|
|
2187
|
+
asset = "WETH";
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
const withdrawParams = validateWithdrawParams({
|
|
2191
|
+
asset,
|
|
2192
|
+
amount,
|
|
2193
|
+
// Let validation handle string to BigNumber conversion
|
|
2194
|
+
user: userAddress || runtime.getSetting("WALLET_ADDRESS")
|
|
2195
|
+
});
|
|
2196
|
+
const result = await aaveService.withdraw(withdrawParams);
|
|
2197
|
+
let successMessage;
|
|
2198
|
+
if (result.remainingATokenBalance.isZero()) {
|
|
2199
|
+
successMessage = `\u{1F4B8} **Successfully Withdrew from Aave V3**
|
|
2200
|
+
|
|
2201
|
+
\u{1F389} **Full Withdrawal Complete!**
|
|
2202
|
+
\u{1F4B0} **Amount**: ${result.amountWithdrawn.toFixed(4)} ${asset}
|
|
2203
|
+
\u{1F4C4} **Transaction**: ${result.transactionHash}
|
|
2204
|
+
\u{1F4B0} **Remaining Balance**: 0 a${asset}
|
|
2205
|
+
|
|
2206
|
+
Your position has been fully withdrawn! \u{1F3C1}`;
|
|
2207
|
+
} else {
|
|
2208
|
+
successMessage = `\u{1F4B8} **Successfully Withdrew from Aave V3**
|
|
2209
|
+
|
|
2210
|
+
\u{1F4B0} **Amount**: ${result.amountWithdrawn.toFixed(4)} ${asset}
|
|
2211
|
+
\u{1F4C4} **Transaction**: ${result.transactionHash}
|
|
2212
|
+
\u{1F4B0} **Remaining Balance**: ${result.remainingATokenBalance.toFixed(4)} a${asset}
|
|
2213
|
+
|
|
2214
|
+
Your remaining a${asset} tokens continue earning yield! \u{1F331}`;
|
|
2215
|
+
}
|
|
2216
|
+
await callback?.({
|
|
2217
|
+
text: successMessage,
|
|
2218
|
+
source: message.content.source
|
|
2219
|
+
});
|
|
2220
|
+
return {
|
|
2221
|
+
text: `Successfully withdrew ${result.amountWithdrawn.toFixed(4)} ${asset} from Aave`,
|
|
2222
|
+
success: true,
|
|
2223
|
+
data: {
|
|
2224
|
+
action: "withdraw",
|
|
2225
|
+
asset,
|
|
2226
|
+
amount: result.amountWithdrawn.toString(),
|
|
2227
|
+
remainingBalance: result.remainingATokenBalance.toString(),
|
|
2228
|
+
transactionHash: result.transactionHash,
|
|
2229
|
+
isFullWithdrawal: result.remainingATokenBalance.isZero()
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
elizaLogger.error("Withdraw action failed:", error);
|
|
2234
|
+
let errorMessage;
|
|
2235
|
+
if (error instanceof Error) {
|
|
2236
|
+
if (error.message.includes("insufficient atoken") || error.message.includes("no atoken balance")) {
|
|
2237
|
+
errorMessage = `\u274C Insufficient a${asset || "Token"} balance. You don't have enough supplied to withdraw this amount.`;
|
|
2238
|
+
} else if (error.message.includes("exceeds balance")) {
|
|
2239
|
+
errorMessage = `\u274C Withdrawal amount exceeds your a${asset || "Token"} balance. Check your supplied position and try a smaller amount.`;
|
|
2240
|
+
} else if (error.message.includes("reserve frozen")) {
|
|
2241
|
+
errorMessage = `\u274C ${asset || "Asset"} withdrawals are currently frozen on Aave. Only emergency withdrawals may be available.`;
|
|
2242
|
+
} else if (error.message.includes("reserve paused")) {
|
|
2243
|
+
errorMessage = `\u274C ${asset || "Asset"} operations are temporarily paused on Aave. Please try again later.`;
|
|
2244
|
+
} else if (error.message.includes("health factor")) {
|
|
2245
|
+
errorMessage = `\u274C Cannot withdraw - would leave position with insufficient collateral. Supply more collateral or reduce borrows first.`;
|
|
2246
|
+
} else if (error.message.includes("reserve inactive")) {
|
|
2247
|
+
errorMessage = `\u274C ${asset || "Asset"} market is currently inactive on Aave. Please try a different asset.`;
|
|
2248
|
+
} else {
|
|
2249
|
+
errorMessage = `\u274C Withdrawal failed: ${error.message}`;
|
|
2250
|
+
}
|
|
2251
|
+
} else {
|
|
2252
|
+
errorMessage = "\u274C Withdrawal failed due to an unknown error. Please try again.";
|
|
2253
|
+
}
|
|
2254
|
+
await callback?.({
|
|
2255
|
+
text: errorMessage,
|
|
2256
|
+
source: message.content.source
|
|
2257
|
+
});
|
|
2258
|
+
return {
|
|
2259
|
+
text: errorMessage,
|
|
2260
|
+
success: false,
|
|
2261
|
+
data: {
|
|
2262
|
+
action: "withdraw",
|
|
2263
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
var borrowAction = {
|
|
2270
|
+
name: "AAVE_BORROW",
|
|
2271
|
+
similes: [
|
|
2272
|
+
"BORROW_FROM_AAVE",
|
|
2273
|
+
"AAVE_LOAN",
|
|
2274
|
+
"TAKE_LOAN",
|
|
2275
|
+
"BORROW_ASSET",
|
|
2276
|
+
"DEFI_BORROW"
|
|
2277
|
+
],
|
|
2278
|
+
description: "Borrow assets from Aave V3 protocol with specified interest rate mode",
|
|
2279
|
+
validate: async (runtime, message) => {
|
|
2280
|
+
const content = message.content?.text?.toLowerCase();
|
|
2281
|
+
if (!content) return false;
|
|
2282
|
+
const borrowKeywords = [
|
|
2283
|
+
"borrow",
|
|
2284
|
+
"loan",
|
|
2285
|
+
"take loan",
|
|
2286
|
+
"get loan",
|
|
2287
|
+
"variable rate",
|
|
2288
|
+
"stable rate",
|
|
2289
|
+
"interest rate"
|
|
2290
|
+
];
|
|
2291
|
+
const hasBorrowKeyword = borrowKeywords.some(
|
|
2292
|
+
(keyword) => content.includes(keyword)
|
|
2293
|
+
);
|
|
2294
|
+
if (!hasBorrowKeyword) return false;
|
|
2295
|
+
const hasAssetOrAmount = /\b(usdc|usdt|dai|eth|weth|btc|wbtc|\d+\.?\d*)\b/i.test(content);
|
|
2296
|
+
return hasAssetOrAmount;
|
|
2297
|
+
},
|
|
2298
|
+
examples: [[
|
|
2299
|
+
{
|
|
2300
|
+
name: "User",
|
|
2301
|
+
content: { text: "I want to borrow 1000 USDC with variable rate from Aave" }
|
|
2302
|
+
},
|
|
2303
|
+
{
|
|
2304
|
+
name: "Assistant",
|
|
2305
|
+
content: {
|
|
2306
|
+
text: "I'll help you borrow 1000 USDC with variable interest rate from Aave V3.",
|
|
2307
|
+
actions: ["AAVE_BORROW"]
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
], [
|
|
2311
|
+
{
|
|
2312
|
+
name: "User",
|
|
2313
|
+
content: { text: "Take a loan of 0.5 ETH with stable rate" }
|
|
2314
|
+
},
|
|
2315
|
+
{
|
|
2316
|
+
name: "Assistant",
|
|
2317
|
+
content: {
|
|
2318
|
+
text: "I'll process your loan of 0.5 ETH with stable interest rate from Aave.",
|
|
2319
|
+
actions: ["AAVE_BORROW"]
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
]],
|
|
2323
|
+
handler: async (runtime, message, state, options, callback) => {
|
|
2324
|
+
try {
|
|
2325
|
+
elizaLogger.info("Starting Aave borrow operation...");
|
|
2326
|
+
const aaveService = runtime.getService("aave");
|
|
2327
|
+
if (!aaveService) {
|
|
2328
|
+
throw new Error("Aave service not available");
|
|
2329
|
+
}
|
|
2330
|
+
const content = message.content?.text;
|
|
2331
|
+
if (!content) {
|
|
2332
|
+
throw new Error("No message content provided");
|
|
2333
|
+
}
|
|
2334
|
+
let asset = "";
|
|
2335
|
+
let amount = "";
|
|
2336
|
+
let rateMode = 2 /* VARIABLE */;
|
|
2337
|
+
const amountAssetMatch = content.match(/(?:borrow|loan|take loan|get loan)(?:\s+of)?\s+([a-zA-Z\d.,]+)\s+(\w+)/i);
|
|
2338
|
+
const assetAmountMatch = content.match(/(\w+)\s+([a-zA-Z\d.,]+)/i);
|
|
2339
|
+
if (amountAssetMatch) {
|
|
2340
|
+
amount = amountAssetMatch[1].replace(/,/g, "");
|
|
2341
|
+
asset = amountAssetMatch[2].toUpperCase();
|
|
2342
|
+
} else if (assetAmountMatch && assetAmountMatch[2].match(/^\d/)) {
|
|
2343
|
+
asset = assetAmountMatch[1].toUpperCase();
|
|
2344
|
+
amount = assetAmountMatch[2].replace(/,/g, "");
|
|
2345
|
+
} else {
|
|
2346
|
+
throw new Error('Could not extract asset and amount from request. Please specify like "borrow 1000 USDC"');
|
|
2347
|
+
}
|
|
2348
|
+
if (content.toLowerCase().includes("stable")) {
|
|
2349
|
+
rateMode = 1 /* STABLE */;
|
|
2350
|
+
}
|
|
2351
|
+
if (asset === "ETH") {
|
|
2352
|
+
asset = "WETH";
|
|
2353
|
+
}
|
|
2354
|
+
const userAddress = runtime.getSetting("WALLET_ADDRESS");
|
|
2355
|
+
if (!userAddress) {
|
|
2356
|
+
throw new Error("Wallet address not configured. Please set WALLET_ADDRESS in settings.");
|
|
2357
|
+
}
|
|
2358
|
+
const parsedAmount = parseAmount(amount);
|
|
2359
|
+
elizaLogger.info(`Borrowing ${parsedAmount} ${asset} with ${rateMode === 1 /* STABLE */ ? "stable" : "variable"} rate for ${userAddress}`);
|
|
2360
|
+
const params = {
|
|
2361
|
+
asset,
|
|
2362
|
+
amount: parsedAmount,
|
|
2363
|
+
interestRateMode: rateMode,
|
|
2364
|
+
user: userAddress
|
|
2365
|
+
};
|
|
2366
|
+
const result = await aaveService.borrow(params);
|
|
2367
|
+
if (!result.success) {
|
|
2368
|
+
throw new Error(`Borrow operation failed: ${result.error?.message}`);
|
|
2369
|
+
}
|
|
2370
|
+
const rateTypeText = rateMode === 1 /* STABLE */ ? "stable" : "variable";
|
|
2371
|
+
const successMessage = `\u{1F4B0} **Successfully Borrowed from Aave V3**
|
|
2372
|
+
|
|
2373
|
+
\u{1F389} **Loan Approved!**
|
|
2374
|
+
\u{1F4B8} **Amount**: ${result.amountBorrowed.toFixed(4)} ${asset}
|
|
2375
|
+
\u{1F4C8} **Interest Rate**: ${rateTypeText.charAt(0).toUpperCase() + rateTypeText.slice(1)} (${result.currentBorrowAPY.toFixed(2)}% APY)
|
|
2376
|
+
\u{1F4C4} **Transaction**: ${result.transactionHash}
|
|
2377
|
+
\u{1F3E5} **Health Factor**: ${result.newHealthFactor.toFixed(3)}
|
|
2378
|
+
|
|
2379
|
+
${result.newHealthFactor < 1.5 ? "\u26A0\uFE0F **Warning**: Your health factor is getting low. Consider adding more collateral!" : "\u2705 Your position looks healthy! Monitor your health factor regularly."}`;
|
|
2380
|
+
await callback?.({
|
|
2381
|
+
text: successMessage,
|
|
2382
|
+
source: message.content.source
|
|
2383
|
+
});
|
|
2384
|
+
return {
|
|
2385
|
+
text: `Successfully borrowed ${result.amountBorrowed.toFixed(4)} ${asset} from Aave`,
|
|
2386
|
+
success: true,
|
|
2387
|
+
data: {
|
|
2388
|
+
action: "borrow",
|
|
2389
|
+
asset,
|
|
2390
|
+
amount: result.amountBorrowed.toString(),
|
|
2391
|
+
interestRateMode: rateTypeText,
|
|
2392
|
+
currentAPY: result.currentBorrowAPY,
|
|
2393
|
+
transactionHash: result.transactionHash,
|
|
2394
|
+
healthFactor: result.newHealthFactor
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
} catch (error) {
|
|
2398
|
+
elizaLogger.error("Borrow action failed:", error);
|
|
2399
|
+
let errorMessage;
|
|
2400
|
+
if (error instanceof Error) {
|
|
2401
|
+
if (error.message.includes("insufficient collateral")) {
|
|
2402
|
+
errorMessage = `\u274C **Insufficient Collateral**
|
|
2403
|
+
|
|
2404
|
+
You don't have enough collateral to borrow this amount.
|
|
2405
|
+
\u{1F4A1} **Try this:**
|
|
2406
|
+
\u2022 Supply more assets as collateral first
|
|
2407
|
+
\u2022 Reduce the borrow amount
|
|
2408
|
+
\u2022 Check your health factor`;
|
|
2409
|
+
} else if (error.message.includes("borrowing not enabled")) {
|
|
2410
|
+
errorMessage = `\u274C **Borrowing Not Available**
|
|
2411
|
+
|
|
2412
|
+
This asset cannot be borrowed on Aave V3 currently.
|
|
2413
|
+
\u{1F4A1} **Try borrowing a different asset like USDC, USDT, or DAI**`;
|
|
2414
|
+
} else if (error.message.includes("health factor")) {
|
|
2415
|
+
errorMessage = `\u274C **Health Factor Too Low**
|
|
2416
|
+
|
|
2417
|
+
This borrow would put your position at risk of liquidation.
|
|
2418
|
+
\u{1F4A1} **Add more collateral or reduce borrow amount**`;
|
|
2419
|
+
} else {
|
|
2420
|
+
errorMessage = `\u274C **Borrow Failed**: ${error.message}`;
|
|
2421
|
+
}
|
|
2422
|
+
} else {
|
|
2423
|
+
errorMessage = "\u274C **Borrow operation failed**. Please try again or check your parameters.";
|
|
2424
|
+
}
|
|
2425
|
+
await callback?.({
|
|
2426
|
+
text: errorMessage,
|
|
2427
|
+
error: true
|
|
2428
|
+
});
|
|
2429
|
+
return {
|
|
2430
|
+
text: error instanceof Error ? error.message : "Borrow operation failed",
|
|
2431
|
+
success: false,
|
|
2432
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
var repayAction = {
|
|
2438
|
+
name: "AAVE_REPAY",
|
|
2439
|
+
similes: [
|
|
2440
|
+
"REPAY_TO_AAVE",
|
|
2441
|
+
"AAVE_REPAYMENT",
|
|
2442
|
+
"PAY_BACK",
|
|
2443
|
+
"REPAY_DEBT",
|
|
2444
|
+
"DEFI_REPAY",
|
|
2445
|
+
"PAYBACK_LOAN"
|
|
2446
|
+
],
|
|
2447
|
+
description: "Repay borrowed assets to Aave V3 protocol",
|
|
2448
|
+
validate: async (runtime, message) => {
|
|
2449
|
+
const content = message.content?.text?.toLowerCase();
|
|
2450
|
+
if (!content) return false;
|
|
2451
|
+
const repayKeywords = [
|
|
2452
|
+
"repay",
|
|
2453
|
+
"pay back",
|
|
2454
|
+
"payback",
|
|
2455
|
+
"pay off",
|
|
2456
|
+
"repayment",
|
|
2457
|
+
"debt",
|
|
2458
|
+
"loan repay",
|
|
2459
|
+
"close position"
|
|
2460
|
+
];
|
|
2461
|
+
const hasRepayKeyword = repayKeywords.some(
|
|
2462
|
+
(keyword) => content.includes(keyword)
|
|
2463
|
+
);
|
|
2464
|
+
if (!hasRepayKeyword) return false;
|
|
2465
|
+
const hasAssetOrAmount = /\b(usdc|usdt|dai|eth|weth|btc|wbtc|\d+\.?\d*|all|everything|max|maximum)\b/i.test(content);
|
|
2466
|
+
return hasAssetOrAmount;
|
|
2467
|
+
},
|
|
2468
|
+
examples: [[
|
|
2469
|
+
{
|
|
2470
|
+
name: "User",
|
|
2471
|
+
content: { text: "I want to repay 1000 USDC variable debt to Aave" }
|
|
2472
|
+
},
|
|
2473
|
+
{
|
|
2474
|
+
name: "Assistant",
|
|
2475
|
+
content: {
|
|
2476
|
+
text: "I'll help you repay 1000 USDC variable debt to Aave V3.",
|
|
2477
|
+
actions: ["AAVE_REPAY"]
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
], [
|
|
2481
|
+
{
|
|
2482
|
+
name: "User",
|
|
2483
|
+
content: { text: "Pay back all my ETH debt with stable rate" }
|
|
2484
|
+
},
|
|
2485
|
+
{
|
|
2486
|
+
name: "Assistant",
|
|
2487
|
+
content: {
|
|
2488
|
+
text: "I'll process the full repayment of your ETH stable debt.",
|
|
2489
|
+
actions: ["AAVE_REPAY"]
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
]],
|
|
2493
|
+
handler: async (runtime, message, state, options, callback) => {
|
|
2494
|
+
let asset = "";
|
|
2495
|
+
let amount = "";
|
|
2496
|
+
try {
|
|
2497
|
+
elizaLogger.info("Starting Aave repay operation...");
|
|
2498
|
+
const aaveService = runtime.getService("aave");
|
|
2499
|
+
if (!aaveService) {
|
|
2500
|
+
throw new Error("Aave service not available");
|
|
2501
|
+
}
|
|
2502
|
+
const content = message.content?.text;
|
|
2503
|
+
if (!content) {
|
|
2504
|
+
throw new Error("No message content provided");
|
|
2505
|
+
}
|
|
2506
|
+
let rateMode = 2 /* VARIABLE */;
|
|
2507
|
+
const amountAssetMatch = content.match(/(?:repay|pay back|payback|pay off)(?:\s+(?:all|full|maximum|max))?\s+([a-zA-Z\d.,]+)\s+(\w+)/i);
|
|
2508
|
+
const assetAmountMatch = content.match(/(\w+)\s+([a-zA-Z\d.,]+)/i);
|
|
2509
|
+
const maxRepayMatch = content.match(/(?:repay|pay back|payback|pay off)\s+(?:all|full|maximum|max|everything)(?:\s+(\w+))?/i);
|
|
2510
|
+
if (maxRepayMatch) {
|
|
2511
|
+
amount = "max";
|
|
2512
|
+
asset = maxRepayMatch[1]?.toUpperCase() || "USDC";
|
|
2513
|
+
} else if (amountAssetMatch) {
|
|
2514
|
+
amount = amountAssetMatch[1].replace(/,/g, "");
|
|
2515
|
+
asset = amountAssetMatch[2].toUpperCase();
|
|
2516
|
+
} else if (assetAmountMatch && assetAmountMatch[2].match(/^\d/)) {
|
|
2517
|
+
asset = assetAmountMatch[1].toUpperCase();
|
|
2518
|
+
amount = assetAmountMatch[2].replace(/,/g, "");
|
|
2519
|
+
} else {
|
|
2520
|
+
throw new Error('Could not extract asset and amount from request. Please specify like "repay 1000 USDC"');
|
|
2521
|
+
}
|
|
2522
|
+
if (content.toLowerCase().includes("stable")) {
|
|
2523
|
+
rateMode = 1 /* STABLE */;
|
|
2524
|
+
}
|
|
2525
|
+
if (asset === "ETH") {
|
|
2526
|
+
asset = "WETH";
|
|
2527
|
+
}
|
|
2528
|
+
const userAddress = runtime.getSetting("WALLET_ADDRESS");
|
|
2529
|
+
if (!userAddress) {
|
|
2530
|
+
throw new Error("Wallet address not configured. Please set WALLET_ADDRESS in settings.");
|
|
2531
|
+
}
|
|
2532
|
+
const parsedAmount = parseAmount(amount);
|
|
2533
|
+
const isFullRepayment = isMaxAmount(parsedAmount);
|
|
2534
|
+
elizaLogger.info(`Repaying ${isFullRepayment ? "full" : amount} ${asset} ${rateMode === 1 /* STABLE */ ? "stable" : "variable"} debt for ${userAddress}`);
|
|
2535
|
+
const params = {
|
|
2536
|
+
asset,
|
|
2537
|
+
amount: parsedAmount,
|
|
2538
|
+
interestRateMode: rateMode,
|
|
2539
|
+
user: userAddress
|
|
2540
|
+
};
|
|
2541
|
+
const result = await aaveService.repay(params);
|
|
2542
|
+
if (!result.success) {
|
|
2543
|
+
throw new Error(`Repay operation failed: ${result.error?.message}`);
|
|
2544
|
+
}
|
|
2545
|
+
const rateTypeText = rateMode === 1 /* STABLE */ ? "stable" : "variable";
|
|
2546
|
+
let successMessage;
|
|
2547
|
+
if (result.remainingDebt.isZero()) {
|
|
2548
|
+
successMessage = `\u{1F4B8} **Successfully Repaid Debt to Aave V3**
|
|
2549
|
+
|
|
2550
|
+
\u{1F389} **Debt Fully Repaid!**
|
|
2551
|
+
\u{1F4B0} **Amount**: ${result.amountRepaid.toFixed(4)} ${asset}
|
|
2552
|
+
\u{1F4C8} **Debt Type**: ${rateTypeText.charAt(0).toUpperCase() + rateTypeText.slice(1)} rate
|
|
2553
|
+
\u{1F4C4} **Transaction**: ${result.transactionHash}
|
|
2554
|
+
${result.approvalTransactionHash ? `\u{1F513} **Approval**: ${result.approvalTransactionHash}` : ""}
|
|
2555
|
+
\u{1F3E5} **Health Factor**: ${result.newHealthFactor.toFixed(3)}
|
|
2556
|
+
\u{1F4B0} **Remaining Debt**: $0.00
|
|
2557
|
+
|
|
2558
|
+
\u{1F38A} Congratulations! You've completely paid off your ${asset} debt!`;
|
|
2559
|
+
} else {
|
|
2560
|
+
successMessage = `\u{1F4B8} **Successfully Repaid Debt to Aave V3**
|
|
2561
|
+
|
|
2562
|
+
\u{1F4B0} **Amount**: ${result.amountRepaid.toFixed(4)} ${asset}
|
|
2563
|
+
\u{1F4C8} **Debt Type**: ${rateTypeText.charAt(0).toUpperCase() + rateTypeText.slice(1)} rate
|
|
2564
|
+
\u{1F4C4} **Transaction**: ${result.transactionHash}
|
|
2565
|
+
${result.approvalTransactionHash ? `\u{1F513} **Approval**: ${result.approvalTransactionHash}` : ""}
|
|
2566
|
+
\u{1F3E5} **Health Factor**: ${result.newHealthFactor.toFixed(3)}
|
|
2567
|
+
\u{1F4B0} **Remaining Debt**: ${result.remainingDebt.toFixed(4)} ETH equivalent
|
|
2568
|
+
|
|
2569
|
+
\u2705 Your position health has improved!`;
|
|
2570
|
+
}
|
|
2571
|
+
await callback?.({
|
|
2572
|
+
text: successMessage,
|
|
2573
|
+
source: message.content.source
|
|
2574
|
+
});
|
|
2575
|
+
return {
|
|
2576
|
+
text: `Successfully repaid ${result.amountRepaid.toFixed(4)} ${asset} to Aave`,
|
|
2577
|
+
success: true,
|
|
2578
|
+
data: {
|
|
2579
|
+
action: "repay",
|
|
2580
|
+
asset,
|
|
2581
|
+
amount: result.amountRepaid.toString(),
|
|
2582
|
+
interestRateMode: rateTypeText,
|
|
2583
|
+
transactionHash: result.transactionHash,
|
|
2584
|
+
approvalTransactionHash: result.approvalTransactionHash,
|
|
2585
|
+
healthFactor: result.newHealthFactor,
|
|
2586
|
+
remainingDebt: result.remainingDebt.toString(),
|
|
2587
|
+
isFullRepayment: result.remainingDebt.isZero()
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
elizaLogger.error("Repay action failed:", error);
|
|
2592
|
+
let errorMessage;
|
|
2593
|
+
if (error instanceof Error) {
|
|
2594
|
+
if (error.message.includes("insufficient balance")) {
|
|
2595
|
+
errorMessage = `\u274C **Insufficient Balance**
|
|
2596
|
+
|
|
2597
|
+
You don't have enough ${asset || "tokens"} to repay this amount.
|
|
2598
|
+
\u{1F4A1} **Try this:**
|
|
2599
|
+
\u2022 Check your wallet balance
|
|
2600
|
+
\u2022 Reduce the repay amount
|
|
2601
|
+
\u2022 Get more tokens first`;
|
|
2602
|
+
} else if (error.message.includes("no debt")) {
|
|
2603
|
+
errorMessage = `\u274C **No Debt Found**
|
|
2604
|
+
|
|
2605
|
+
You don't have any debt for this asset/rate type on Aave V3.
|
|
2606
|
+
\u{1F4A1} **Check your position to see current debts**`;
|
|
2607
|
+
} else if (error.message.includes("allowance")) {
|
|
2608
|
+
errorMessage = `\u274C **Token Approval Failed**
|
|
2609
|
+
|
|
2610
|
+
Could not approve tokens for repayment.
|
|
2611
|
+
\u{1F4A1} **This might be a temporary issue - please try again**`;
|
|
2612
|
+
} else {
|
|
2613
|
+
errorMessage = `\u274C **Repay Failed**: ${error.message}`;
|
|
2614
|
+
}
|
|
2615
|
+
} else {
|
|
2616
|
+
errorMessage = "\u274C **Repayment operation failed**. Please try again or check your parameters.";
|
|
2617
|
+
}
|
|
2618
|
+
await callback?.({
|
|
2619
|
+
text: errorMessage,
|
|
2620
|
+
error: true
|
|
2621
|
+
});
|
|
2622
|
+
return {
|
|
2623
|
+
text: error instanceof Error ? error.message : "Repay operation failed",
|
|
2624
|
+
success: false,
|
|
2625
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
var marketDataProvider = {
|
|
2631
|
+
name: "AAVE_MARKET_DATA",
|
|
2632
|
+
get: async (runtime, message, state) => {
|
|
2633
|
+
try {
|
|
2634
|
+
elizaLogger.info("Fetching Aave V3 market data...");
|
|
2635
|
+
const aaveService = runtime.getService("aave");
|
|
2636
|
+
if (!aaveService) {
|
|
2637
|
+
return { text: "Aave service not available - cannot fetch market data." };
|
|
2638
|
+
}
|
|
2639
|
+
const marketData = await aaveService.getMarketData();
|
|
2640
|
+
if (!marketData || marketData.length === 0) {
|
|
2641
|
+
return { text: "No market data available for Aave V3 at this time." };
|
|
2642
|
+
}
|
|
2643
|
+
const majorAssets = marketData.filter((data) => ["USDC", "USDT", "DAI", "WETH", "ETH", "WBTC", "BTC"].includes(data.asset)).sort((a, b) => {
|
|
2644
|
+
const comparison = b.totalSupply.comparedTo(a.totalSupply);
|
|
2645
|
+
return comparison === null ? 0 : comparison;
|
|
2646
|
+
}).slice(0, 8);
|
|
2647
|
+
if (majorAssets.length === 0) {
|
|
2648
|
+
return { text: "Market data not available for major assets." };
|
|
2649
|
+
}
|
|
2650
|
+
let marketSummary = `\u{1F4CA} **Aave V3 Market Overview** (Ethereum)
|
|
2651
|
+
|
|
2652
|
+
`;
|
|
2653
|
+
const totalLiquidity = majorAssets.reduce(
|
|
2654
|
+
(sum, asset) => sum.plus(asset.totalSupply),
|
|
2655
|
+
new BigNumber3(0)
|
|
2656
|
+
);
|
|
2657
|
+
const avgSupplyAPY = majorAssets.reduce(
|
|
2658
|
+
(sum, asset) => sum + asset.supplyAPY,
|
|
2659
|
+
0
|
|
2660
|
+
) / majorAssets.length;
|
|
2661
|
+
marketSummary += `\u{1F4B0} **Total Liquidity**: $${formatLargeNumber(totalLiquidity)} across major assets
|
|
2662
|
+
`;
|
|
2663
|
+
marketSummary += `\u{1F4C8} **Avg Supply APY**: ${avgSupplyAPY.toFixed(2)}%
|
|
2664
|
+
|
|
2665
|
+
`;
|
|
2666
|
+
marketSummary += `**\u{1F3E6} Lending Rates:**
|
|
2667
|
+
`;
|
|
2668
|
+
marketSummary += `\`\`\`
|
|
2669
|
+
`;
|
|
2670
|
+
marketSummary += `Asset Supply APY Borrow APY Utilization
|
|
2671
|
+
`;
|
|
2672
|
+
marketSummary += `\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2673
|
+
`;
|
|
2674
|
+
for (const asset of majorAssets) {
|
|
2675
|
+
const assetSymbol = asset.asset.padEnd(6);
|
|
2676
|
+
const supplyRate = `${asset.supplyAPY.toFixed(2)}%`.padStart(9);
|
|
2677
|
+
const borrowRate = `${asset.variableBorrowAPY.toFixed(2)}%`.padStart(9);
|
|
2678
|
+
const utilization = `${(asset.utilizationRate * 100).toFixed(1)}%`.padStart(9);
|
|
2679
|
+
marketSummary += `${assetSymbol} ${supplyRate} ${borrowRate} ${utilization}
|
|
2680
|
+
`;
|
|
2681
|
+
}
|
|
2682
|
+
marketSummary += `\`\`\`
|
|
2683
|
+
|
|
2684
|
+
`;
|
|
2685
|
+
const highYieldAssets = majorAssets.filter((asset) => asset.supplyAPY > 3).sort((a, b) => b.supplyAPY - a.supplyAPY);
|
|
2686
|
+
const lowBorrowCostAssets = majorAssets.filter((asset) => asset.variableBorrowAPY < 5).sort((a, b) => a.variableBorrowAPY - b.variableBorrowAPY);
|
|
2687
|
+
if (highYieldAssets.length > 0) {
|
|
2688
|
+
marketSummary += `\u{1F31F} **High Yield Opportunities:**
|
|
2689
|
+
`;
|
|
2690
|
+
highYieldAssets.slice(0, 3).forEach((asset) => {
|
|
2691
|
+
marketSummary += `\u2022 ${asset.asset}: ${asset.supplyAPY.toFixed(2)}% APY
|
|
2692
|
+
`;
|
|
2693
|
+
});
|
|
2694
|
+
marketSummary += `
|
|
2695
|
+
`;
|
|
2696
|
+
}
|
|
2697
|
+
if (lowBorrowCostAssets.length > 0) {
|
|
2698
|
+
marketSummary += `\u{1F4B8} **Low Borrow Costs:**
|
|
2699
|
+
`;
|
|
2700
|
+
lowBorrowCostAssets.slice(0, 3).forEach((asset) => {
|
|
2701
|
+
marketSummary += `\u2022 ${asset.asset}: ${asset.variableBorrowAPY.toFixed(2)}% APY
|
|
2702
|
+
`;
|
|
2703
|
+
});
|
|
2704
|
+
marketSummary += `
|
|
2705
|
+
`;
|
|
2706
|
+
}
|
|
2707
|
+
const highUtilAssets = majorAssets.filter((asset) => asset.utilizationRate > 0.8);
|
|
2708
|
+
if (highUtilAssets.length > 0) {
|
|
2709
|
+
marketSummary += `\u26A0\uFE0F **High Utilization Warning:**
|
|
2710
|
+
`;
|
|
2711
|
+
highUtilAssets.forEach((asset) => {
|
|
2712
|
+
marketSummary += `\u2022 ${asset.asset}: ${(asset.utilizationRate * 100).toFixed(1)}% utilized
|
|
2713
|
+
`;
|
|
2714
|
+
});
|
|
2715
|
+
marketSummary += `Higher utilization may impact liquidity and rates.
|
|
2716
|
+
|
|
2717
|
+
`;
|
|
2718
|
+
}
|
|
2719
|
+
marketSummary += `\u{1F4C5} *Data updated: ${(/* @__PURE__ */ new Date()).toLocaleString()}*`;
|
|
2720
|
+
return { text: marketSummary };
|
|
2721
|
+
} catch (error) {
|
|
2722
|
+
elizaLogger.error("Failed to get market data:", error);
|
|
2723
|
+
return { text: "Unable to fetch current market data from Aave V3. Please try again later." };
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
function formatLargeNumber(num) {
|
|
2728
|
+
if (num.isGreaterThanOrEqualTo(1e9)) {
|
|
2729
|
+
return `${num.dividedBy(1e9).toFixed(1)}B`;
|
|
2730
|
+
} else if (num.isGreaterThanOrEqualTo(1e6)) {
|
|
2731
|
+
return `${num.dividedBy(1e6).toFixed(1)}M`;
|
|
2732
|
+
} else if (num.isGreaterThanOrEqualTo(1e3)) {
|
|
2733
|
+
return `${num.dividedBy(1e3).toFixed(1)}K`;
|
|
2734
|
+
}
|
|
2735
|
+
return num.toFixed(2);
|
|
2736
|
+
}
|
|
2737
|
+
var positionProvider = {
|
|
2738
|
+
name: "AAVE_POSITION",
|
|
2739
|
+
get: async (runtime, message, state) => {
|
|
2740
|
+
try {
|
|
2741
|
+
elizaLogger.info("Fetching user Aave V3 position...");
|
|
2742
|
+
const userAddress = runtime.getSetting("WALLET_ADDRESS");
|
|
2743
|
+
if (!userAddress) {
|
|
2744
|
+
return { text: "Wallet address not configured. Please set WALLET_ADDRESS to view your position." };
|
|
2745
|
+
}
|
|
2746
|
+
const aaveService = runtime.getService("aave");
|
|
2747
|
+
if (!aaveService) {
|
|
2748
|
+
return { text: "Aave service not available - cannot fetch position data." };
|
|
2749
|
+
}
|
|
2750
|
+
const position = await aaveService.getUserPosition(userAddress);
|
|
2751
|
+
if (!position) {
|
|
2752
|
+
return { text: "Unable to fetch your Aave V3 position at this time." };
|
|
2753
|
+
}
|
|
2754
|
+
const hasSupplyPositions = position.positions.some((p) => p.suppliedAmount.isGreaterThan(0));
|
|
2755
|
+
const hasBorrowPositions = position.positions.some(
|
|
2756
|
+
(p) => p.borrowedAmountVariable.isGreaterThan(0) || p.borrowedAmountStable.isGreaterThan(0)
|
|
2757
|
+
);
|
|
2758
|
+
if (!hasSupplyPositions && !hasBorrowPositions) {
|
|
2759
|
+
return { text: `\u{1F4BC} **Your Aave V3 Position**
|
|
2760
|
+
|
|
2761
|
+
\u{1F3E6} **No active positions found**
|
|
2762
|
+
|
|
2763
|
+
You don't have any supplies or borrows on Aave V3 yet.
|
|
2764
|
+
Ready to start your DeFi journey? Try:
|
|
2765
|
+
\u2022 "supply 1000 USDC" to start earning yield
|
|
2766
|
+
\u2022 Check current "market rates" for opportunities` };
|
|
2767
|
+
}
|
|
2768
|
+
let positionSummary = `\u{1F4BC} **Your Aave V3 Position**
|
|
2769
|
+
|
|
2770
|
+
`;
|
|
2771
|
+
const shortAddress = `${userAddress.slice(0, 6)}...${userAddress.slice(-4)}`;
|
|
2772
|
+
positionSummary += `\u{1F464} **Address**: ${shortAddress}
|
|
2773
|
+
|
|
2774
|
+
`;
|
|
2775
|
+
positionSummary += `\u{1F3E5} **Health Overview:**
|
|
2776
|
+
`;
|
|
2777
|
+
positionSummary += `\u2022 Health Factor: ${position.healthFactor.isFinite() ? position.healthFactor.toFixed(3) : "\u221E"}
|
|
2778
|
+
`;
|
|
2779
|
+
positionSummary += `\u2022 Total Collateral: $${position.totalCollateralETH.toFixed(2)}
|
|
2780
|
+
`;
|
|
2781
|
+
positionSummary += `\u2022 Total Debt: $${position.totalDebtETH.toFixed(2)}
|
|
2782
|
+
`;
|
|
2783
|
+
if (position.totalDebtETH.isGreaterThan(0)) {
|
|
2784
|
+
positionSummary += `\u2022 Available to Borrow: $${position.availableBorrowsETH.toFixed(2)}
|
|
2785
|
+
`;
|
|
2786
|
+
positionSummary += `\u2022 LTV Ratio: ${(position.ltv * 100).toFixed(1)}%
|
|
2787
|
+
`;
|
|
2788
|
+
}
|
|
2789
|
+
positionSummary += `
|
|
2790
|
+
`;
|
|
2791
|
+
if (position.healthFactor.isFinite() && position.healthFactor.isLessThan(1.5)) {
|
|
2792
|
+
positionSummary += `\u26A0\uFE0F **HEALTH FACTOR WARNING**
|
|
2793
|
+
`;
|
|
2794
|
+
if (position.healthFactor.isLessThan(1.1)) {
|
|
2795
|
+
positionSummary += `\u{1F6A8} **CRITICAL**: Your position is near liquidation!
|
|
2796
|
+
`;
|
|
2797
|
+
} else {
|
|
2798
|
+
positionSummary += `\u26A1 **CAUTION**: Consider adding collateral or reducing debt
|
|
2799
|
+
`;
|
|
2800
|
+
}
|
|
2801
|
+
positionSummary += `
|
|
2802
|
+
`;
|
|
2803
|
+
}
|
|
2804
|
+
const supplies = position.positions.filter((p) => p.suppliedAmount.isGreaterThan(0));
|
|
2805
|
+
if (supplies.length > 0) {
|
|
2806
|
+
positionSummary += `\u{1F4B0} **Supply Positions:**
|
|
2807
|
+
`;
|
|
2808
|
+
positionSummary += `\`\`\`
|
|
2809
|
+
`;
|
|
2810
|
+
positionSummary += `Asset Amount APY Collateral
|
|
2811
|
+
`;
|
|
2812
|
+
positionSummary += `\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2813
|
+
`;
|
|
2814
|
+
for (const supply of supplies) {
|
|
2815
|
+
const asset = supply.asset.padEnd(6);
|
|
2816
|
+
const amount = formatNumber(supply.suppliedAmount).padStart(10);
|
|
2817
|
+
const apy = `${supply.supplyAPY.toFixed(2)}%`.padStart(6);
|
|
2818
|
+
const collateral = (supply.isCollateral ? "\u2705" : "\u274C").padStart(10);
|
|
2819
|
+
positionSummary += `${asset} ${amount} ${apy} ${collateral}
|
|
2820
|
+
`;
|
|
2821
|
+
}
|
|
2822
|
+
positionSummary += `\`\`\`
|
|
2823
|
+
|
|
2824
|
+
`;
|
|
2825
|
+
}
|
|
2826
|
+
const borrows = position.positions.filter(
|
|
2827
|
+
(p) => p.borrowedAmountVariable.isGreaterThan(0) || p.borrowedAmountStable.isGreaterThan(0)
|
|
2828
|
+
);
|
|
2829
|
+
if (borrows.length > 0) {
|
|
2830
|
+
positionSummary += `\u{1F4B8} **Borrow Positions:**
|
|
2831
|
+
`;
|
|
2832
|
+
positionSummary += `\`\`\`
|
|
2833
|
+
`;
|
|
2834
|
+
positionSummary += `Asset Amount APY Type
|
|
2835
|
+
`;
|
|
2836
|
+
positionSummary += `\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2837
|
+
`;
|
|
2838
|
+
for (const borrow of borrows) {
|
|
2839
|
+
if (borrow.borrowedAmountVariable.isGreaterThan(0)) {
|
|
2840
|
+
const asset = borrow.asset.padEnd(6);
|
|
2841
|
+
const amount = formatNumber(borrow.borrowedAmountVariable).padStart(10);
|
|
2842
|
+
const apy = `${borrow.variableBorrowAPY.toFixed(2)}%`.padStart(6);
|
|
2843
|
+
const type = "Variable".padStart(8);
|
|
2844
|
+
positionSummary += `${asset} ${amount} ${apy} ${type}
|
|
2845
|
+
`;
|
|
2846
|
+
}
|
|
2847
|
+
if (borrow.borrowedAmountStable.isGreaterThan(0)) {
|
|
2848
|
+
const asset = borrow.asset.padEnd(6);
|
|
2849
|
+
const amount = formatNumber(borrow.borrowedAmountStable).padStart(10);
|
|
2850
|
+
const apy = `${borrow.stableBorrowAPY.toFixed(2)}%`.padStart(6);
|
|
2851
|
+
const type = "Stable".padStart(8);
|
|
2852
|
+
positionSummary += `${asset} ${amount} ${apy} ${type}
|
|
2853
|
+
`;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
positionSummary += `\`\`\`
|
|
2857
|
+
|
|
2858
|
+
`;
|
|
2859
|
+
}
|
|
2860
|
+
positionSummary += `\u{1F4A1} **Position Insights:**
|
|
2861
|
+
`;
|
|
2862
|
+
if (hasSupplyPositions && !hasBorrowPositions) {
|
|
2863
|
+
positionSummary += `\u2022 You're earning yield as a pure lender \u{1F3E6}
|
|
2864
|
+
`;
|
|
2865
|
+
positionSummary += `\u2022 Consider borrowing stablecoins for leverage or other opportunities
|
|
2866
|
+
`;
|
|
2867
|
+
} else if (hasBorrowPositions) {
|
|
2868
|
+
if (position.healthFactor.isGreaterThan(2)) {
|
|
2869
|
+
positionSummary += `\u2022 Healthy leveraged position \u{1F4AA}
|
|
2870
|
+
`;
|
|
2871
|
+
positionSummary += `\u2022 You have room to borrow more if needed
|
|
2872
|
+
`;
|
|
2873
|
+
} else if (position.healthFactor.isGreaterThan(1.5)) {
|
|
2874
|
+
positionSummary += `\u2022 Moderate risk leveraged position \u2696\uFE0F
|
|
2875
|
+
`;
|
|
2876
|
+
positionSummary += `\u2022 Monitor health factor closely
|
|
2877
|
+
`;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
const nonCollateralAssets = supplies.filter((s) => !s.isCollateral);
|
|
2881
|
+
if (nonCollateralAssets.length > 0) {
|
|
2882
|
+
positionSummary += `\u2022 Some assets not used as collateral - enable for more borrowing power
|
|
2883
|
+
`;
|
|
2884
|
+
}
|
|
2885
|
+
positionSummary += `
|
|
2886
|
+
\u{1F4C5} *Position updated: ${(/* @__PURE__ */ new Date()).toLocaleString()}*`;
|
|
2887
|
+
return { text: positionSummary };
|
|
2888
|
+
} catch (error) {
|
|
2889
|
+
elizaLogger.error("Failed to get user position:", error);
|
|
2890
|
+
return { text: "Unable to fetch your current position from Aave V3. Please try again later." };
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
function formatNumber(num) {
|
|
2895
|
+
if (num.isGreaterThanOrEqualTo(1e6)) {
|
|
2896
|
+
return `${num.dividedBy(1e6).toFixed(2)}M`;
|
|
2897
|
+
} else if (num.isGreaterThanOrEqualTo(1e3)) {
|
|
2898
|
+
return `${num.dividedBy(1e3).toFixed(1)}K`;
|
|
2899
|
+
} else if (num.isGreaterThanOrEqualTo(1)) {
|
|
2900
|
+
return num.toFixed(2);
|
|
2901
|
+
} else {
|
|
2902
|
+
return num.toFixed(4);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
// src/index.ts
|
|
2907
|
+
var plugin = {
|
|
2908
|
+
name: "aave",
|
|
2909
|
+
description: "Aave V3 Protocol integration for lending and borrowing operations",
|
|
2910
|
+
// Core services
|
|
2911
|
+
services: [AaveService],
|
|
2912
|
+
// User actions
|
|
2913
|
+
actions: [supplyAction, withdrawAction, borrowAction, repayAction],
|
|
2914
|
+
// Context providers
|
|
2915
|
+
providers: [marketDataProvider, positionProvider],
|
|
2916
|
+
// No evaluators for now
|
|
2917
|
+
evaluators: []
|
|
2918
|
+
};
|
|
2919
|
+
var index_default = plugin;
|
|
2920
|
+
|
|
2921
|
+
export { AAVE_CONSTANTS, AaveError, AaveErrorCode, AaveService, InterestRateMode, borrowAction, index_default as default, isAaveError, isValidInterestRateMode, marketDataProvider, plugin, positionProvider, repayAction, supplyAction, withdrawAction };
|
|
2922
|
+
//# sourceMappingURL=index.js.map
|
|
2923
|
+
//# sourceMappingURL=index.js.map
|