@peridot-agent/agent-kit 0.2.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/README.md +414 -0
- package/dist/adapters/langchain/index.cjs +1753 -0
- package/dist/adapters/langchain/index.cjs.map +1 -0
- package/dist/adapters/langchain/index.d.cts +29 -0
- package/dist/adapters/langchain/index.d.ts +29 -0
- package/dist/adapters/langchain/index.js +1725 -0
- package/dist/adapters/langchain/index.js.map +1 -0
- package/dist/adapters/mcp/server.cjs +1777 -0
- package/dist/adapters/mcp/server.cjs.map +1 -0
- package/dist/adapters/mcp/server.d.cts +1 -0
- package/dist/adapters/mcp/server.d.ts +1 -0
- package/dist/adapters/mcp/server.js +1756 -0
- package/dist/adapters/mcp/server.js.map +1 -0
- package/dist/adapters/vercel-ai/index.cjs +1751 -0
- package/dist/adapters/vercel-ai/index.cjs.map +1 -0
- package/dist/adapters/vercel-ai/index.d.cts +34 -0
- package/dist/adapters/vercel-ai/index.d.ts +34 -0
- package/dist/adapters/vercel-ai/index.js +1723 -0
- package/dist/adapters/vercel-ai/index.js.map +1 -0
- package/dist/index.cjs +1806 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +880 -0
- package/dist/index.d.ts +880 -0
- package/dist/index.js +1737 -0
- package/dist/index.js.map +1 -0
- package/dist/types-Cg95um6s.d.cts +212 -0
- package/dist/types-Cg95um6s.d.ts +212 -0
- package/package.json +95 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1806 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/shared/constants.ts
|
|
34
|
+
var constants_exports = {};
|
|
35
|
+
__export(constants_exports, {
|
|
36
|
+
ARBITRUM_CHAIN_ID: () => ARBITRUM_CHAIN_ID,
|
|
37
|
+
ASSET_DECIMALS: () => ASSET_DECIMALS,
|
|
38
|
+
AVALANCHE_CHAIN_ID: () => AVALANCHE_CHAIN_ID,
|
|
39
|
+
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
40
|
+
BICONOMY_API_URL: () => BICONOMY_API_URL,
|
|
41
|
+
BSC_MAINNET_CHAIN_ID: () => BSC_MAINNET_CHAIN_ID,
|
|
42
|
+
BSC_TESTNET_CHAIN_ID: () => BSC_TESTNET_CHAIN_ID,
|
|
43
|
+
BSC_UNDERLYING_TOKENS: () => BSC_UNDERLYING_TOKENS,
|
|
44
|
+
CHAIN_NAMES: () => CHAIN_NAMES,
|
|
45
|
+
DEFAULT_API_BASE_URL: () => DEFAULT_API_BASE_URL,
|
|
46
|
+
DEFAULT_RPC_URLS: () => DEFAULT_RPC_URLS,
|
|
47
|
+
ETHEREUM_CHAIN_ID: () => ETHEREUM_CHAIN_ID,
|
|
48
|
+
HUB_CHAIN_IDS: () => HUB_CHAIN_IDS,
|
|
49
|
+
HUB_UNDERLYING_TOKENS: () => HUB_UNDERLYING_TOKENS,
|
|
50
|
+
MONAD_MAINNET_CHAIN_ID: () => MONAD_MAINNET_CHAIN_ID,
|
|
51
|
+
MONAD_TESTNET_CHAIN_ID: () => MONAD_TESTNET_CHAIN_ID,
|
|
52
|
+
OPTIMISM_CHAIN_ID: () => OPTIMISM_CHAIN_ID,
|
|
53
|
+
PERIDOT_CONTROLLER: () => PERIDOT_CONTROLLER,
|
|
54
|
+
PERIDOT_MARKETS: () => PERIDOT_MARKETS,
|
|
55
|
+
POLYGON_CHAIN_ID: () => POLYGON_CHAIN_ID,
|
|
56
|
+
SOMNIA_MAINNET_CHAIN_ID: () => SOMNIA_MAINNET_CHAIN_ID,
|
|
57
|
+
SOMNIA_TESTNET_CHAIN_ID: () => SOMNIA_TESTNET_CHAIN_ID,
|
|
58
|
+
SPOKE_TOKENS: () => SPOKE_TOKENS,
|
|
59
|
+
getAssetDecimals: () => getAssetDecimals,
|
|
60
|
+
getControllerAddress: () => getControllerAddress,
|
|
61
|
+
getPTokenAddress: () => getPTokenAddress,
|
|
62
|
+
getUnderlyingTokenAddress: () => getUnderlyingTokenAddress,
|
|
63
|
+
isHubChain: () => isHubChain,
|
|
64
|
+
resolveHubChainId: () => resolveHubChainId
|
|
65
|
+
});
|
|
66
|
+
function isHubChain(chainId) {
|
|
67
|
+
return HUB_CHAIN_IDS.includes(chainId);
|
|
68
|
+
}
|
|
69
|
+
function resolveHubChainId(chainId, network = "mainnet") {
|
|
70
|
+
if (isHubChain(chainId)) return chainId;
|
|
71
|
+
return network === "testnet" ? BSC_TESTNET_CHAIN_ID : BSC_MAINNET_CHAIN_ID;
|
|
72
|
+
}
|
|
73
|
+
function getPTokenAddress(chainId, asset) {
|
|
74
|
+
const markets = PERIDOT_MARKETS[chainId];
|
|
75
|
+
if (!markets) {
|
|
76
|
+
throw new Error(`No pToken markets configured for chain ${chainId} (${CHAIN_NAMES[chainId] ?? "unknown"})`);
|
|
77
|
+
}
|
|
78
|
+
const address = markets[asset.toUpperCase()];
|
|
79
|
+
if (!address) {
|
|
80
|
+
const available = Object.keys(markets).join(", ");
|
|
81
|
+
throw new Error(`No pToken market for ${asset.toUpperCase()} on chain ${chainId}. Available: ${available}`);
|
|
82
|
+
}
|
|
83
|
+
return address;
|
|
84
|
+
}
|
|
85
|
+
function getUnderlyingTokenAddress(chainId, asset) {
|
|
86
|
+
const symbol = asset.toUpperCase();
|
|
87
|
+
const chainName = CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
88
|
+
if (isHubChain(chainId)) {
|
|
89
|
+
const hubTokens = HUB_UNDERLYING_TOKENS[chainId];
|
|
90
|
+
if (!hubTokens) throw new Error(`No underlying token config for hub chain ${chainName} \u2014 contracts may not be deployed yet`);
|
|
91
|
+
const address2 = hubTokens[symbol];
|
|
92
|
+
if (!address2) throw new Error(`No underlying token for ${symbol} on ${chainName}`);
|
|
93
|
+
return address2;
|
|
94
|
+
}
|
|
95
|
+
const spokeTokens = SPOKE_TOKENS[chainId];
|
|
96
|
+
if (!spokeTokens) throw new Error(`No token config for chain ${chainName}`);
|
|
97
|
+
const address = spokeTokens[symbol];
|
|
98
|
+
if (!address) throw new Error(`No token address for ${symbol} on ${chainName}`);
|
|
99
|
+
return address;
|
|
100
|
+
}
|
|
101
|
+
function getControllerAddress(chainId) {
|
|
102
|
+
const address = PERIDOT_CONTROLLER[chainId];
|
|
103
|
+
if (!address) throw new Error(`No Peridot controller for chain ${chainId}`);
|
|
104
|
+
return address;
|
|
105
|
+
}
|
|
106
|
+
function getAssetDecimals(asset) {
|
|
107
|
+
const symbol = asset.toUpperCase();
|
|
108
|
+
const decimals = ASSET_DECIMALS[symbol];
|
|
109
|
+
if (decimals === void 0) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Unknown asset "${symbol}": decimal precision not configured. Add it to ASSET_DECIMALS in constants.ts before use.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return decimals;
|
|
115
|
+
}
|
|
116
|
+
var DEFAULT_API_BASE_URL, BICONOMY_API_URL, HUB_CHAIN_IDS, BSC_MAINNET_CHAIN_ID, BSC_TESTNET_CHAIN_ID, MONAD_MAINNET_CHAIN_ID, MONAD_TESTNET_CHAIN_ID, SOMNIA_MAINNET_CHAIN_ID, SOMNIA_TESTNET_CHAIN_ID, ARBITRUM_CHAIN_ID, BASE_CHAIN_ID, ETHEREUM_CHAIN_ID, POLYGON_CHAIN_ID, OPTIMISM_CHAIN_ID, AVALANCHE_CHAIN_ID, CHAIN_NAMES, PERIDOT_CONTROLLER, PERIDOT_MARKETS, BSC_UNDERLYING_TOKENS, HUB_UNDERLYING_TOKENS, SPOKE_TOKENS, ASSET_DECIMALS, DEFAULT_RPC_URLS;
|
|
117
|
+
var init_constants = __esm({
|
|
118
|
+
"src/shared/constants.ts"() {
|
|
119
|
+
"use strict";
|
|
120
|
+
DEFAULT_API_BASE_URL = "https://app.peridot.finance";
|
|
121
|
+
BICONOMY_API_URL = "https://api.biconomy.io";
|
|
122
|
+
HUB_CHAIN_IDS = [56, 97, 143, 10143, 1868, 50312];
|
|
123
|
+
BSC_MAINNET_CHAIN_ID = 56;
|
|
124
|
+
BSC_TESTNET_CHAIN_ID = 97;
|
|
125
|
+
MONAD_MAINNET_CHAIN_ID = 143;
|
|
126
|
+
MONAD_TESTNET_CHAIN_ID = 10143;
|
|
127
|
+
SOMNIA_MAINNET_CHAIN_ID = 1868;
|
|
128
|
+
SOMNIA_TESTNET_CHAIN_ID = 50312;
|
|
129
|
+
ARBITRUM_CHAIN_ID = 42161;
|
|
130
|
+
BASE_CHAIN_ID = 8453;
|
|
131
|
+
ETHEREUM_CHAIN_ID = 1;
|
|
132
|
+
POLYGON_CHAIN_ID = 137;
|
|
133
|
+
OPTIMISM_CHAIN_ID = 10;
|
|
134
|
+
AVALANCHE_CHAIN_ID = 43114;
|
|
135
|
+
CHAIN_NAMES = {
|
|
136
|
+
[BSC_MAINNET_CHAIN_ID]: "BSC Mainnet",
|
|
137
|
+
[BSC_TESTNET_CHAIN_ID]: "BSC Testnet",
|
|
138
|
+
[MONAD_MAINNET_CHAIN_ID]: "Monad Mainnet",
|
|
139
|
+
[MONAD_TESTNET_CHAIN_ID]: "Monad Testnet",
|
|
140
|
+
[SOMNIA_MAINNET_CHAIN_ID]: "Somnia Mainnet",
|
|
141
|
+
[SOMNIA_TESTNET_CHAIN_ID]: "Somnia Testnet",
|
|
142
|
+
[ARBITRUM_CHAIN_ID]: "Arbitrum",
|
|
143
|
+
[BASE_CHAIN_ID]: "Base",
|
|
144
|
+
[ETHEREUM_CHAIN_ID]: "Ethereum",
|
|
145
|
+
[POLYGON_CHAIN_ID]: "Polygon",
|
|
146
|
+
[OPTIMISM_CHAIN_ID]: "Optimism",
|
|
147
|
+
[AVALANCHE_CHAIN_ID]: "Avalanche"
|
|
148
|
+
};
|
|
149
|
+
PERIDOT_CONTROLLER = {
|
|
150
|
+
[BSC_MAINNET_CHAIN_ID]: "0x6fC0c15531CB5901ac72aB3CFCd9dF6E99552e14"
|
|
151
|
+
// [MONAD_MAINNET_CHAIN_ID]: '0x...', // TODO: add when deployed
|
|
152
|
+
// [SOMNIA_MAINNET_CHAIN_ID]: '0x...', // TODO: add when deployed
|
|
153
|
+
};
|
|
154
|
+
PERIDOT_MARKETS = {
|
|
155
|
+
[BSC_MAINNET_CHAIN_ID]: {
|
|
156
|
+
WETH: "0x28E4F2Bb64ac79500ec3CAa074A3C30721B6bC84",
|
|
157
|
+
USDC: "0x1A726369Bfc60198A0ce19C66726C8046c0eC17e",
|
|
158
|
+
WBNB: "0xD9fDF5E2c7a2e7916E7f10Da276D95d4daC5a3c3",
|
|
159
|
+
USDT: "0xc37f3869720B672addFE5F9E22a9459e0E851372",
|
|
160
|
+
WBTC: "0xdCAbDc1F0B5e603b9191be044a912A8A2949e212",
|
|
161
|
+
AUSD: "0x7A9940B77c0B6DFCcA2028b9F3CCa88E5DC36ebb"
|
|
162
|
+
}
|
|
163
|
+
// [MONAD_MAINNET_CHAIN_ID]: { ... }, // TODO: add when deployed
|
|
164
|
+
// [SOMNIA_MAINNET_CHAIN_ID]: { ... }, // TODO: add when deployed
|
|
165
|
+
};
|
|
166
|
+
BSC_UNDERLYING_TOKENS = {
|
|
167
|
+
WETH: "0x2170Ed0880ac9A755fd29B2688956BD959F933F8",
|
|
168
|
+
WBTC: "0x0555E30da8f98308EdB960aa94C0Db47230d2B9c",
|
|
169
|
+
USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
|
|
170
|
+
AUSD: "0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a",
|
|
171
|
+
WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
|
172
|
+
USDT: "0x55d398326f99059fF775485246999027B3197955"
|
|
173
|
+
};
|
|
174
|
+
HUB_UNDERLYING_TOKENS = {
|
|
175
|
+
[BSC_MAINNET_CHAIN_ID]: BSC_UNDERLYING_TOKENS
|
|
176
|
+
// [MONAD_MAINNET_CHAIN_ID]: { USDC: '0x...', WETH: '0x...', ... }, // TODO
|
|
177
|
+
// [SOMNIA_MAINNET_CHAIN_ID]: { USDC: '0x...', WETH: '0x...', ... }, // TODO
|
|
178
|
+
};
|
|
179
|
+
SPOKE_TOKENS = {
|
|
180
|
+
[ARBITRUM_CHAIN_ID]: {
|
|
181
|
+
USDC: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
182
|
+
USDT: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
183
|
+
WETH: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
184
|
+
AUSD: "0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a",
|
|
185
|
+
WBTC: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"
|
|
186
|
+
},
|
|
187
|
+
[BASE_CHAIN_ID]: {
|
|
188
|
+
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
189
|
+
WETH: "0x4200000000000000000000000000000000000006",
|
|
190
|
+
USDT: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
|
|
191
|
+
WBTC: "0x0555E30da8f98308EdB960aa94C0Db47230d2B9c"
|
|
192
|
+
},
|
|
193
|
+
[ETHEREUM_CHAIN_ID]: {
|
|
194
|
+
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
195
|
+
USDT: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
196
|
+
WETH: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
197
|
+
WBTC: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"
|
|
198
|
+
},
|
|
199
|
+
[POLYGON_CHAIN_ID]: {
|
|
200
|
+
USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
201
|
+
USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
|
|
202
|
+
WETH: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
|
|
203
|
+
WBTC: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6"
|
|
204
|
+
},
|
|
205
|
+
[OPTIMISM_CHAIN_ID]: {
|
|
206
|
+
USDC: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
|
|
207
|
+
USDT: "0x94b008aA00579c1307B0eF2c499aD98a8ce58e58",
|
|
208
|
+
WETH: "0x4200000000000000000000000000000000000006"
|
|
209
|
+
},
|
|
210
|
+
[AVALANCHE_CHAIN_ID]: {
|
|
211
|
+
USDC: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
212
|
+
USDT: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
|
|
213
|
+
WETH: "0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB",
|
|
214
|
+
WBTC: "0x0555E30da8f98308EdB960aa94C0Db47230d2B9c"
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
ASSET_DECIMALS = {
|
|
218
|
+
USDC: 6,
|
|
219
|
+
USDT: 6,
|
|
220
|
+
WBTC: 8,
|
|
221
|
+
WETH: 18,
|
|
222
|
+
WBNB: 18,
|
|
223
|
+
AUSD: 18
|
|
224
|
+
};
|
|
225
|
+
DEFAULT_RPC_URLS = {
|
|
226
|
+
[BSC_MAINNET_CHAIN_ID]: "https://bsc-dataseed.binance.org",
|
|
227
|
+
[BSC_TESTNET_CHAIN_ID]: "https://data-seed-prebsc-1-s1.binance.org:8545",
|
|
228
|
+
[MONAD_MAINNET_CHAIN_ID]: "https://rpc.monad.xyz",
|
|
229
|
+
[MONAD_TESTNET_CHAIN_ID]: "https://testnet-rpc.monad.xyz",
|
|
230
|
+
[SOMNIA_MAINNET_CHAIN_ID]: "https://dream-rpc.somnia.network",
|
|
231
|
+
[SOMNIA_TESTNET_CHAIN_ID]: "https://testnet.rpc.somnia.network",
|
|
232
|
+
[ARBITRUM_CHAIN_ID]: "https://arb1.arbitrum.io/rpc",
|
|
233
|
+
[BASE_CHAIN_ID]: "https://mainnet.base.org",
|
|
234
|
+
[ETHEREUM_CHAIN_ID]: "https://eth.llamarpc.com"
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// src/index.ts
|
|
240
|
+
var src_exports = {};
|
|
241
|
+
__export(src_exports, {
|
|
242
|
+
ARBITRUM_CHAIN_ID: () => ARBITRUM_CHAIN_ID,
|
|
243
|
+
ASSET_DECIMALS: () => ASSET_DECIMALS,
|
|
244
|
+
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
245
|
+
BSC_MAINNET_CHAIN_ID: () => BSC_MAINNET_CHAIN_ID,
|
|
246
|
+
BSC_TESTNET_CHAIN_ID: () => BSC_TESTNET_CHAIN_ID,
|
|
247
|
+
BSC_UNDERLYING_TOKENS: () => BSC_UNDERLYING_TOKENS,
|
|
248
|
+
COMPTROLLER_ABI: () => COMPTROLLER_ABI,
|
|
249
|
+
ERC20_ABI: () => ERC20_ABI,
|
|
250
|
+
ETHEREUM_CHAIN_ID: () => ETHEREUM_CHAIN_ID,
|
|
251
|
+
MONAD_MAINNET_CHAIN_ID: () => MONAD_MAINNET_CHAIN_ID,
|
|
252
|
+
PERIDOT_CONTROLLER: () => PERIDOT_CONTROLLER,
|
|
253
|
+
PERIDOT_MARKETS: () => PERIDOT_MARKETS,
|
|
254
|
+
PTOKEN_ABI: () => PTOKEN_ABI,
|
|
255
|
+
PeridotApiClient: () => PeridotApiClient,
|
|
256
|
+
SOMNIA_MAINNET_CHAIN_ID: () => SOMNIA_MAINNET_CHAIN_ID,
|
|
257
|
+
SPOKE_TOKENS: () => SPOKE_TOKENS,
|
|
258
|
+
buildCrossChainBorrowIntent: () => buildCrossChainBorrowIntent,
|
|
259
|
+
buildCrossChainRepayIntent: () => buildCrossChainRepayIntent,
|
|
260
|
+
buildCrossChainSupplyIntent: () => buildCrossChainSupplyIntent,
|
|
261
|
+
buildCrossChainWithdrawIntent: () => buildCrossChainWithdrawIntent,
|
|
262
|
+
buildHubBorrowIntent: () => buildHubBorrowIntent,
|
|
263
|
+
buildHubDisableCollateralIntent: () => buildHubDisableCollateralIntent,
|
|
264
|
+
buildHubEnableCollateralIntent: () => buildHubEnableCollateralIntent,
|
|
265
|
+
buildHubRepayIntent: () => buildHubRepayIntent,
|
|
266
|
+
buildHubSupplyIntent: () => buildHubSupplyIntent,
|
|
267
|
+
buildHubWithdrawIntent: () => buildHubWithdrawIntent,
|
|
268
|
+
checkTransactionStatus: () => checkTransactionStatus,
|
|
269
|
+
getAccountLiquidity: () => getAccountLiquidity,
|
|
270
|
+
getAssetDecimals: () => getAssetDecimals,
|
|
271
|
+
getControllerAddress: () => getControllerAddress,
|
|
272
|
+
getLeaderboard: () => getLeaderboard,
|
|
273
|
+
getMarketRates: () => getMarketRates,
|
|
274
|
+
getPTokenAddress: () => getPTokenAddress,
|
|
275
|
+
getUnderlyingTokenAddress: () => getUnderlyingTokenAddress,
|
|
276
|
+
getUserPosition: () => getUserPosition,
|
|
277
|
+
isHubChain: () => isHubChain,
|
|
278
|
+
lendingTools: () => lendingTools,
|
|
279
|
+
listMarkets: () => listMarkets,
|
|
280
|
+
resolveHubChainId: () => resolveHubChainId,
|
|
281
|
+
simulateBorrow: () => simulateBorrow
|
|
282
|
+
});
|
|
283
|
+
module.exports = __toCommonJS(src_exports);
|
|
284
|
+
|
|
285
|
+
// src/features/lending/read/list-markets.ts
|
|
286
|
+
var import_zod = require("zod");
|
|
287
|
+
|
|
288
|
+
// src/shared/api-client.ts
|
|
289
|
+
init_constants();
|
|
290
|
+
var PLATFORM_TIMEOUT_MS = 1e4;
|
|
291
|
+
var BICONOMY_COMPOSE_TIMEOUT_MS = 3e4;
|
|
292
|
+
var RETRY_DELAY_MS = 200;
|
|
293
|
+
async function fetchWithRetry(url, init) {
|
|
294
|
+
const attempt = () => fetch(url, init);
|
|
295
|
+
let res;
|
|
296
|
+
try {
|
|
297
|
+
res = await attempt();
|
|
298
|
+
} catch (err) {
|
|
299
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
300
|
+
return attempt();
|
|
301
|
+
}
|
|
302
|
+
if (res.status >= 500) {
|
|
303
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
304
|
+
return attempt();
|
|
305
|
+
}
|
|
306
|
+
return res;
|
|
307
|
+
}
|
|
308
|
+
var PeridotApiClient = class {
|
|
309
|
+
baseUrl;
|
|
310
|
+
biconomyApiKey;
|
|
311
|
+
constructor(config) {
|
|
312
|
+
this.baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
313
|
+
this.biconomyApiKey = config.biconomyApiKey;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Fetches all market metrics. Returns a record keyed by `${ASSET}:${chainId}`.
|
|
317
|
+
* Example key: `USDC:56`
|
|
318
|
+
*/
|
|
319
|
+
async getMarketMetrics() {
|
|
320
|
+
const res = await fetchWithRetry(`${this.baseUrl}/api/markets/metrics`, { signal: AbortSignal.timeout(PLATFORM_TIMEOUT_MS) });
|
|
321
|
+
if (!res.ok) throw new Error(`Failed to fetch market metrics: ${res.status} ${res.statusText}`);
|
|
322
|
+
const json = await res.json();
|
|
323
|
+
if (!json.ok) throw new Error(`Market metrics API error: ${json.error ?? "unknown"}`);
|
|
324
|
+
return json.data;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Fetches the portfolio overview for a wallet address.
|
|
328
|
+
* Uses the platform's portfolio-data endpoint which aggregates DB snapshots.
|
|
329
|
+
*/
|
|
330
|
+
async getUserPortfolio(address) {
|
|
331
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
332
|
+
throw new Error(`Invalid address format: "${address}". Expected 0x followed by 40 hex characters.`);
|
|
333
|
+
}
|
|
334
|
+
const res = await fetchWithRetry(`${this.baseUrl}/api/user/portfolio-data?address=${address}`, { signal: AbortSignal.timeout(PLATFORM_TIMEOUT_MS) });
|
|
335
|
+
if (!res.ok) throw new Error(`Failed to fetch portfolio: ${res.status} ${res.statusText}`);
|
|
336
|
+
const json = await res.json();
|
|
337
|
+
if (!json.ok) throw new Error(`Portfolio API error: ${json.error ?? "unknown"}`);
|
|
338
|
+
return json.data;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Fetches the latest APY snapshot for all markets (or a specific chain).
|
|
342
|
+
* Calls `/api/apy` — backed by the `apy_latest_mainnet` DB table.
|
|
343
|
+
*
|
|
344
|
+
* Returns a record keyed by lowercase asset ID, then by chainId.
|
|
345
|
+
* Example: `data["usdc"][56].totalSupplyApy`
|
|
346
|
+
*/
|
|
347
|
+
async getMarketApy(chainId) {
|
|
348
|
+
const url = chainId ? `${this.baseUrl}/api/apy?chainId=${chainId}` : `${this.baseUrl}/api/apy`;
|
|
349
|
+
const res = await fetchWithRetry(url, { signal: AbortSignal.timeout(PLATFORM_TIMEOUT_MS) });
|
|
350
|
+
if (!res.ok) throw new Error(`Failed to fetch APY data: ${res.status} ${res.statusText}`);
|
|
351
|
+
const json = await res.json();
|
|
352
|
+
if (!json.ok) throw new Error(`APY API error: ${json.error ?? "unknown"}`);
|
|
353
|
+
return json.data;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Fetches the Peridot leaderboard (top users by points).
|
|
357
|
+
* Optionally filters by chainId and limits the result set.
|
|
358
|
+
*/
|
|
359
|
+
async getLeaderboard(options) {
|
|
360
|
+
const params = new URLSearchParams();
|
|
361
|
+
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
362
|
+
if (options?.chainId !== void 0) params.set("chainId", String(options.chainId));
|
|
363
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
364
|
+
const res = await fetchWithRetry(`${this.baseUrl}/api/leaderboard${query}`, { signal: AbortSignal.timeout(PLATFORM_TIMEOUT_MS) });
|
|
365
|
+
if (!res.ok) throw new Error(`Failed to fetch leaderboard: ${res.status} ${res.statusText}`);
|
|
366
|
+
const json = await res.json();
|
|
367
|
+
if (!json.ok) throw new Error(`Leaderboard API error: ${json.error ?? "unknown"}`);
|
|
368
|
+
return json.data;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Fetches accounts that are currently underwater (shortfall_usd > 0) and
|
|
372
|
+
* eligible for liquidation. Data is sourced from the account health scanner.
|
|
373
|
+
*/
|
|
374
|
+
async getLiquidatablePositions(options) {
|
|
375
|
+
const params = new URLSearchParams();
|
|
376
|
+
if (options?.chainId !== void 0) params.set("chainId", String(options.chainId));
|
|
377
|
+
if (options?.minShortfall !== void 0) params.set("minShortfall", String(options.minShortfall));
|
|
378
|
+
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
379
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
380
|
+
const res = await fetchWithRetry(`${this.baseUrl}/api/liquidations/at-risk${query}`, { signal: AbortSignal.timeout(PLATFORM_TIMEOUT_MS) });
|
|
381
|
+
if (!res.ok) throw new Error(`Failed to fetch liquidatable positions: ${res.status} ${res.statusText}`);
|
|
382
|
+
const json = await res.json();
|
|
383
|
+
if (!json.ok) throw new Error(`Liquidations API error: ${json.error ?? "unknown"}`);
|
|
384
|
+
return json.data;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Fetches verified transaction history for a wallet address.
|
|
388
|
+
* Optionally filters by chainId and/or actionType.
|
|
389
|
+
*/
|
|
390
|
+
async getUserTransactions(address, options) {
|
|
391
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
392
|
+
throw new Error(`Invalid address format: "${address}". Expected 0x followed by 40 hex characters.`);
|
|
393
|
+
}
|
|
394
|
+
const params = new URLSearchParams({ address });
|
|
395
|
+
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
396
|
+
if (options?.chainId !== void 0) params.set("chainId", String(options.chainId));
|
|
397
|
+
if (options?.actionType !== void 0) params.set("actionType", options.actionType);
|
|
398
|
+
const res = await fetchWithRetry(`${this.baseUrl}/api/user/transactions?${params.toString()}`, { signal: AbortSignal.timeout(PLATFORM_TIMEOUT_MS) });
|
|
399
|
+
if (!res.ok) throw new Error(`Failed to fetch transactions: ${res.status} ${res.statusText}`);
|
|
400
|
+
const json = await res.json();
|
|
401
|
+
if (!json.ok) throw new Error(`Transactions API error: ${json.error ?? "unknown"}`);
|
|
402
|
+
return json.data;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Calls Biconomy MEE /compose to build a cross-chain transaction payload.
|
|
406
|
+
* Does NOT execute — the returned instructions must be signed by the user.
|
|
407
|
+
*/
|
|
408
|
+
async biconomyCompose(request) {
|
|
409
|
+
if (!this.biconomyApiKey) {
|
|
410
|
+
throw new Error("biconomyApiKey is required in PeridotConfig for cross-chain operations");
|
|
411
|
+
}
|
|
412
|
+
const res = await fetch(`${BICONOMY_API_URL}/v1/instructions/compose`, {
|
|
413
|
+
method: "POST",
|
|
414
|
+
headers: {
|
|
415
|
+
"Content-Type": "application/json",
|
|
416
|
+
"X-API-Key": this.biconomyApiKey
|
|
417
|
+
},
|
|
418
|
+
body: JSON.stringify(request),
|
|
419
|
+
signal: AbortSignal.timeout(BICONOMY_COMPOSE_TIMEOUT_MS)
|
|
420
|
+
});
|
|
421
|
+
if (!res.ok) {
|
|
422
|
+
const error = await res.json().catch(() => ({}));
|
|
423
|
+
throw new Error(`Biconomy compose error: ${JSON.stringify(error)}`);
|
|
424
|
+
}
|
|
425
|
+
return res.json();
|
|
426
|
+
}
|
|
427
|
+
/** Polls Biconomy for the status of a submitted super-transaction. */
|
|
428
|
+
async biconomyGetStatus(superTxHash) {
|
|
429
|
+
const res = await fetchWithRetry(`${BICONOMY_API_URL}/v1/explorer/transaction/${superTxHash}`, { signal: AbortSignal.timeout(PLATFORM_TIMEOUT_MS) });
|
|
430
|
+
if (res.status === 404) return { superTxHash, status: "not_found" };
|
|
431
|
+
if (!res.ok) throw new Error(`Biconomy status error: ${res.status}`);
|
|
432
|
+
const data = await res.json();
|
|
433
|
+
return parseBiconomyStatus(superTxHash, data);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
function parseBiconomyStatus(superTxHash, data) {
|
|
437
|
+
const status = String(data["status"] ?? "").toLowerCase();
|
|
438
|
+
const txHashes = data["txHashes"] ?? [];
|
|
439
|
+
if (status.includes("success") || status.includes("completed")) {
|
|
440
|
+
return { superTxHash, status: "success", chainTxHashes: txHashes };
|
|
441
|
+
}
|
|
442
|
+
if (status.includes("fail") || status.includes("error")) {
|
|
443
|
+
return { superTxHash, status: "failed", error: String(data["message"] ?? "Unknown error") };
|
|
444
|
+
}
|
|
445
|
+
if (status.includes("process")) {
|
|
446
|
+
return { superTxHash, status: "processing", chainTxHashes: txHashes };
|
|
447
|
+
}
|
|
448
|
+
return { superTxHash, status: "pending" };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/features/lending/read/list-markets.ts
|
|
452
|
+
var listMarketsSchema = import_zod.z.object({
|
|
453
|
+
chainId: import_zod.z.number().optional().describe(
|
|
454
|
+
"Filter by hub chain ID. 56=BSC, 143=Monad, 1868=Somnia. Omit to list all markets across all chains."
|
|
455
|
+
)
|
|
456
|
+
});
|
|
457
|
+
async function listMarkets(input, config) {
|
|
458
|
+
const client = new PeridotApiClient(config);
|
|
459
|
+
const metrics = await client.getMarketMetrics();
|
|
460
|
+
const markets = Object.entries(metrics).filter(([, m]) => input.chainId === void 0 || m.chainId === input.chainId).map(([key, m]) => {
|
|
461
|
+
const [asset] = key.split(":");
|
|
462
|
+
const dataAgeSeconds = m.updatedAt ? Math.round((Date.now() - new Date(m.updatedAt).getTime()) / 1e3) : 0;
|
|
463
|
+
return {
|
|
464
|
+
asset: asset ?? key,
|
|
465
|
+
chainId: m.chainId,
|
|
466
|
+
priceUsd: m.priceUsd,
|
|
467
|
+
tvlUsd: m.tvlUsd,
|
|
468
|
+
utilizationPct: m.utilizationPct,
|
|
469
|
+
liquidityUsd: m.liquidityUsd,
|
|
470
|
+
collateralFactorPct: m.collateral_factor_pct,
|
|
471
|
+
updatedAt: m.updatedAt,
|
|
472
|
+
dataAgeSeconds
|
|
473
|
+
};
|
|
474
|
+
}).sort((a, b) => b.tvlUsd - a.tvlUsd);
|
|
475
|
+
return { markets, count: markets.length };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/features/lending/read/get-portfolio.ts
|
|
479
|
+
var import_zod3 = require("zod");
|
|
480
|
+
|
|
481
|
+
// src/shared/zod-utils.ts
|
|
482
|
+
var import_zod2 = require("zod");
|
|
483
|
+
var evmAddress = import_zod2.z.string().regex(/^0x[0-9a-fA-F]{40}$/, "Must be a valid Ethereum address (0x followed by 40 hex chars)");
|
|
484
|
+
var tokenAmount = import_zod2.z.string().regex(/^\d+(\.\d+)?$/, 'Amount must be a positive decimal number (e.g. "100" or "0.5")');
|
|
485
|
+
|
|
486
|
+
// src/shared/cache.ts
|
|
487
|
+
var Cache = class {
|
|
488
|
+
constructor(ttlMs) {
|
|
489
|
+
this.ttlMs = ttlMs;
|
|
490
|
+
}
|
|
491
|
+
store = /* @__PURE__ */ new Map();
|
|
492
|
+
get(key) {
|
|
493
|
+
const entry = this.store.get(key);
|
|
494
|
+
if (!entry) return void 0;
|
|
495
|
+
if (entry.inflight) return void 0;
|
|
496
|
+
if (Date.now() > entry.expiresAt) {
|
|
497
|
+
this.store.delete(key);
|
|
498
|
+
return void 0;
|
|
499
|
+
}
|
|
500
|
+
return entry.data;
|
|
501
|
+
}
|
|
502
|
+
set(key, data) {
|
|
503
|
+
this.store.set(key, { data, expiresAt: Date.now() + this.ttlMs });
|
|
504
|
+
}
|
|
505
|
+
clear() {
|
|
506
|
+
this.store.clear();
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Return the cached value if fresh, otherwise call `fetcher` once — even
|
|
510
|
+
* when multiple callers arrive simultaneously while the fetch is in-flight.
|
|
511
|
+
* If the fetch throws, the entry is deleted so subsequent callers retry.
|
|
512
|
+
*/
|
|
513
|
+
async getOrFetch(key, fetcher) {
|
|
514
|
+
const cached = this.get(key);
|
|
515
|
+
if (cached !== void 0) return cached;
|
|
516
|
+
const existing = this.store.get(key);
|
|
517
|
+
if (existing?.inflight) return existing.inflight;
|
|
518
|
+
const promise = fetcher().then((data) => {
|
|
519
|
+
this.set(key, data);
|
|
520
|
+
const entry = this.store.get(key);
|
|
521
|
+
if (entry) delete entry.inflight;
|
|
522
|
+
return data;
|
|
523
|
+
}).catch((err) => {
|
|
524
|
+
this.store.delete(key);
|
|
525
|
+
throw err;
|
|
526
|
+
});
|
|
527
|
+
this.store.set(key, { data: void 0, expiresAt: 0, inflight: promise });
|
|
528
|
+
return promise;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// src/features/lending/read/get-portfolio.ts
|
|
533
|
+
var portfolioCache = new Cache(3e4);
|
|
534
|
+
var getPortfolioSchema = import_zod3.z.object({
|
|
535
|
+
address: evmAddress.describe("The wallet address (0x...) to look up")
|
|
536
|
+
});
|
|
537
|
+
async function getPortfolio(input, config) {
|
|
538
|
+
const key = input.address.toLowerCase();
|
|
539
|
+
return portfolioCache.getOrFetch(key, async () => {
|
|
540
|
+
const client = new PeridotApiClient(config);
|
|
541
|
+
const data = await client.getUserPortfolio(input.address);
|
|
542
|
+
return {
|
|
543
|
+
address: input.address,
|
|
544
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
545
|
+
portfolio: {
|
|
546
|
+
currentValue: data.portfolio.currentValue,
|
|
547
|
+
totalSupplied: data.portfolio.totalSupplied,
|
|
548
|
+
totalBorrowed: data.portfolio.totalBorrowed,
|
|
549
|
+
netApy: data.portfolio.netApy,
|
|
550
|
+
healthFactor: data.portfolio.totalBorrowed > 0 ? data.portfolio.totalSupplied / data.portfolio.totalBorrowed : null
|
|
551
|
+
},
|
|
552
|
+
assets: data.assets,
|
|
553
|
+
transactions: data.transactions,
|
|
554
|
+
earnings: data.earnings
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/features/lending/read/get-leaderboard.ts
|
|
560
|
+
var import_zod4 = require("zod");
|
|
561
|
+
var getLeaderboardSchema = import_zod4.z.object({
|
|
562
|
+
limit: import_zod4.z.number().int().min(1).max(100).optional().describe("Number of entries to return (1\u2013100, default 50)."),
|
|
563
|
+
chainId: import_zod4.z.number().optional().describe("Filter by hub chain ID. 56=BSC, 143=Monad, 1868=Somnia. Omit to show all chains.")
|
|
564
|
+
});
|
|
565
|
+
async function getLeaderboard(input, config) {
|
|
566
|
+
const client = new PeridotApiClient(config);
|
|
567
|
+
const opts = {};
|
|
568
|
+
if (input.limit !== void 0) opts.limit = input.limit;
|
|
569
|
+
if (input.chainId !== void 0) opts.chainId = input.chainId;
|
|
570
|
+
const data = await client.getLeaderboard(opts);
|
|
571
|
+
return {
|
|
572
|
+
entries: data.entries,
|
|
573
|
+
total: data.total
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/features/lending/read/get-market-rates.ts
|
|
578
|
+
var import_zod5 = require("zod");
|
|
579
|
+
init_constants();
|
|
580
|
+
var getMarketRatesSchema = import_zod5.z.object({
|
|
581
|
+
asset: import_zod5.z.string().describe('Asset symbol, e.g. "USDC", "WETH", "WBTC", "WBNB", "USDT", "AUSD"'),
|
|
582
|
+
chainId: import_zod5.z.number().default(BSC_MAINNET_CHAIN_ID).describe("Hub chain ID where the market lives. 56=BSC (default), 143=Monad, 1868=Somnia")
|
|
583
|
+
});
|
|
584
|
+
async function getMarketRates(input, config) {
|
|
585
|
+
const client = new PeridotApiClient(config);
|
|
586
|
+
const assetUpper = input.asset.toUpperCase();
|
|
587
|
+
const [metrics, apyData] = await Promise.all([
|
|
588
|
+
client.getMarketMetrics(),
|
|
589
|
+
client.getMarketApy(input.chainId)
|
|
590
|
+
]);
|
|
591
|
+
const key = `${assetUpper}:${input.chainId}`;
|
|
592
|
+
const metric = metrics[key];
|
|
593
|
+
if (!metric) {
|
|
594
|
+
const available = Object.keys(metrics).filter((k) => k.endsWith(`:${input.chainId}`)).map((k) => k.split(":")[0]);
|
|
595
|
+
throw new Error(
|
|
596
|
+
`No market data found for "${assetUpper}" on chain ${input.chainId}. Available assets on this chain: ${available.join(", ")}`
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
const apy = apyData[assetUpper.toLowerCase()]?.[input.chainId];
|
|
600
|
+
const apyDataAvailable = apy !== void 0;
|
|
601
|
+
const dataAgeSeconds = metric.updatedAt ? Math.round((Date.now() - new Date(metric.updatedAt).getTime()) / 1e3) : 0;
|
|
602
|
+
const warnings = [];
|
|
603
|
+
if (!apyDataAvailable) {
|
|
604
|
+
warnings.push(
|
|
605
|
+
`APY data is not yet available for ${assetUpper} on chain ${input.chainId}. All yield figures (supplyApyPct, borrowApyPct, totalSupplyApyPct, etc.) are showing 0 as a placeholder \u2014 do NOT present them as real rates. The APY indexer may still be catching up.`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (dataAgeSeconds > 300) {
|
|
609
|
+
warnings.push(`Market data is ${Math.round(dataAgeSeconds / 60)} minutes old \u2014 figures may not reflect current conditions.`);
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
asset: assetUpper,
|
|
613
|
+
chainId: input.chainId,
|
|
614
|
+
supplyApyPct: apy?.supplyApy ?? 0,
|
|
615
|
+
borrowApyPct: apy?.borrowApy ?? 0,
|
|
616
|
+
peridotSupplyApyPct: apy?.peridotSupplyApy ?? 0,
|
|
617
|
+
peridotBorrowApyPct: apy?.peridotBorrowApy ?? 0,
|
|
618
|
+
boostSourceSupplyApyPct: apy?.boostSourceSupplyApy ?? 0,
|
|
619
|
+
boostRewardsSupplyApyPct: apy?.boostRewardsSupplyApy ?? 0,
|
|
620
|
+
totalSupplyApyPct: apy?.totalSupplyApy ?? 0,
|
|
621
|
+
netBorrowApyPct: apy?.netBorrowApy ?? 0,
|
|
622
|
+
tvlUsd: metric.tvlUsd,
|
|
623
|
+
utilizationPct: metric.utilizationPct,
|
|
624
|
+
liquidityUsd: metric.liquidityUsd,
|
|
625
|
+
priceUsd: metric.priceUsd,
|
|
626
|
+
collateralFactorPct: metric.collateral_factor_pct ?? 0,
|
|
627
|
+
updatedAt: metric.updatedAt,
|
|
628
|
+
dataAgeSeconds,
|
|
629
|
+
apyDataAvailable,
|
|
630
|
+
warning: warnings.length > 0 ? warnings.join(" ") : void 0
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/features/lending/read/get-user-position.ts
|
|
635
|
+
var import_zod6 = require("zod");
|
|
636
|
+
var getUserPositionSchema = import_zod6.z.object({
|
|
637
|
+
address: evmAddress.describe("The wallet address (0x...) to look up")
|
|
638
|
+
});
|
|
639
|
+
async function getUserPosition(input, config) {
|
|
640
|
+
const client = new PeridotApiClient(config);
|
|
641
|
+
const data = await client.getUserPortfolio(input.address);
|
|
642
|
+
const { portfolio, assets, transactions } = data;
|
|
643
|
+
const healthFactor = portfolio.totalBorrowed > 0 ? portfolio.totalSupplied / portfolio.totalBorrowed : null;
|
|
644
|
+
return {
|
|
645
|
+
address: input.address,
|
|
646
|
+
totalSuppliedUsd: portfolio.totalSupplied,
|
|
647
|
+
totalBorrowedUsd: portfolio.totalBorrowed,
|
|
648
|
+
netWorthUsd: portfolio.currentValue,
|
|
649
|
+
netApyPct: portfolio.netApy,
|
|
650
|
+
healthFactor,
|
|
651
|
+
assets: assets.map((a) => ({
|
|
652
|
+
assetId: a.assetId,
|
|
653
|
+
suppliedUsd: a.supplied,
|
|
654
|
+
borrowedUsd: a.borrowed,
|
|
655
|
+
netUsd: a.net
|
|
656
|
+
})),
|
|
657
|
+
transactions: {
|
|
658
|
+
supplyCount: transactions.supplyCount,
|
|
659
|
+
borrowCount: transactions.borrowCount,
|
|
660
|
+
repayCount: transactions.repayCount,
|
|
661
|
+
redeemCount: transactions.redeemCount
|
|
662
|
+
},
|
|
663
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/features/lending/read/simulate-borrow.ts
|
|
668
|
+
var import_decimal = __toESM(require("decimal.js"), 1);
|
|
669
|
+
var import_zod7 = require("zod");
|
|
670
|
+
init_constants();
|
|
671
|
+
var simulateBorrowSchema = import_zod7.z.object({
|
|
672
|
+
address: evmAddress.describe("The wallet address planning to borrow"),
|
|
673
|
+
asset: import_zod7.z.string().describe('The asset to borrow, e.g. "USDC", "WETH"'),
|
|
674
|
+
amount: tokenAmount.describe('Human-readable borrow amount, e.g. "500" for 500 USDC'),
|
|
675
|
+
chainId: import_zod7.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia)." }).describe("Hub chain ID. Defaults to BSC (56).")
|
|
676
|
+
});
|
|
677
|
+
var RISK_THRESHOLDS = {
|
|
678
|
+
safe: 2,
|
|
679
|
+
moderate: 1.5,
|
|
680
|
+
high: 1.2,
|
|
681
|
+
critical: 1
|
|
682
|
+
};
|
|
683
|
+
function classifyRisk(hf) {
|
|
684
|
+
if (hf >= RISK_THRESHOLDS.safe) return "safe";
|
|
685
|
+
if (hf >= RISK_THRESHOLDS.moderate) return "moderate";
|
|
686
|
+
if (hf >= RISK_THRESHOLDS.high) return "high";
|
|
687
|
+
if (hf >= RISK_THRESHOLDS.critical) return "critical";
|
|
688
|
+
return "liquidatable";
|
|
689
|
+
}
|
|
690
|
+
async function simulateBorrow(input, config) {
|
|
691
|
+
const client = new PeridotApiClient(config);
|
|
692
|
+
const [portfolioData, metricsData] = await Promise.all([
|
|
693
|
+
client.getUserPortfolio(input.address),
|
|
694
|
+
client.getMarketMetrics()
|
|
695
|
+
]);
|
|
696
|
+
const assetUpper = input.asset.toUpperCase();
|
|
697
|
+
const metricKey = `${assetUpper}:${input.chainId}`;
|
|
698
|
+
const metric = metricsData[metricKey];
|
|
699
|
+
if (!metric) {
|
|
700
|
+
throw new Error(`No market data for ${assetUpper} on chain ${input.chainId}`);
|
|
701
|
+
}
|
|
702
|
+
const borrowAmountRaw = parseFloat(input.amount);
|
|
703
|
+
if (isNaN(borrowAmountRaw) || borrowAmountRaw <= 0) {
|
|
704
|
+
throw new Error(`Invalid borrow amount: "${input.amount}"`);
|
|
705
|
+
}
|
|
706
|
+
const borrowAmount = new import_decimal.default(input.amount);
|
|
707
|
+
const borrowAmountUsd = borrowAmount.mul(metric.priceUsd).toNumber();
|
|
708
|
+
const { totalSupplied, totalBorrowed } = portfolioData.portfolio;
|
|
709
|
+
const currentHF = totalBorrowed > 0 ? new import_decimal.default(totalSupplied).div(totalBorrowed).toNumber() : null;
|
|
710
|
+
const projectedBorrowedUsd = new import_decimal.default(totalBorrowed).add(borrowAmountUsd);
|
|
711
|
+
if (totalSupplied === 0) {
|
|
712
|
+
return {
|
|
713
|
+
currentHealthFactor: null,
|
|
714
|
+
projectedHealthFactor: null,
|
|
715
|
+
borrowAmountUsd,
|
|
716
|
+
isSafe: false,
|
|
717
|
+
riskLevel: "liquidatable",
|
|
718
|
+
maxSafeBorrowUsd: 0,
|
|
719
|
+
warning: "Portfolio shows no supplied collateral (data is sourced from indexed DB snapshots and may not reflect activity from the last few minutes). If you recently supplied assets, wait for the next snapshot update. Otherwise, supply assets and enable them as collateral before borrowing."
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
const projectedHF = new import_decimal.default(totalSupplied).div(projectedBorrowedUsd).toNumber();
|
|
723
|
+
const maxSafeBorrowUsd = import_decimal.default.max(
|
|
724
|
+
0,
|
|
725
|
+
new import_decimal.default(totalSupplied).div(RISK_THRESHOLDS.safe).sub(totalBorrowed)
|
|
726
|
+
).toNumber();
|
|
727
|
+
const riskLevel = classifyRisk(projectedHF);
|
|
728
|
+
const isSafe = projectedHF >= RISK_THRESHOLDS.high;
|
|
729
|
+
const warnings = [];
|
|
730
|
+
if (riskLevel === "liquidatable") {
|
|
731
|
+
warnings.push("This borrow would immediately make you eligible for liquidation.");
|
|
732
|
+
} else if (riskLevel === "critical") {
|
|
733
|
+
warnings.push("Health factor would drop critically low. Any price movement risks liquidation.");
|
|
734
|
+
} else if (riskLevel === "high") {
|
|
735
|
+
warnings.push("Health factor would be dangerously low. Consider borrowing less.");
|
|
736
|
+
}
|
|
737
|
+
return {
|
|
738
|
+
currentHealthFactor: currentHF,
|
|
739
|
+
projectedHealthFactor: projectedHF,
|
|
740
|
+
borrowAmountUsd,
|
|
741
|
+
isSafe,
|
|
742
|
+
riskLevel,
|
|
743
|
+
maxSafeBorrowUsd,
|
|
744
|
+
warning: warnings[0]
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/features/lending/read/get-account-liquidity.ts
|
|
749
|
+
var import_decimal2 = __toESM(require("decimal.js"), 1);
|
|
750
|
+
var import_zod8 = require("zod");
|
|
751
|
+
var import_viem2 = require("viem");
|
|
752
|
+
|
|
753
|
+
// src/shared/abis.ts
|
|
754
|
+
var import_viem = require("viem");
|
|
755
|
+
var PTOKEN_ABI = (0, import_viem.parseAbi)([
|
|
756
|
+
"function mint(uint256 mintAmount) returns (uint256)",
|
|
757
|
+
"function redeem(uint256 redeemTokens) returns (uint256)",
|
|
758
|
+
"function redeemUnderlying(uint256 redeemAmount) returns (uint256)",
|
|
759
|
+
"function borrow(uint256 borrowAmount) returns (uint256)",
|
|
760
|
+
"function repayBorrow(uint256 repayAmount) returns (uint256)",
|
|
761
|
+
"function repayBorrowBehalf(address borrower, uint256 repayAmount) returns (uint256)",
|
|
762
|
+
"function liquidateBorrow(address borrower, uint256 repayAmount, address pTokenCollateral) returns (uint256)",
|
|
763
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
764
|
+
"function borrowBalanceStored(address account) view returns (uint256)",
|
|
765
|
+
"function supplyRatePerBlock() view returns (uint256)",
|
|
766
|
+
"function borrowRatePerBlock() view returns (uint256)",
|
|
767
|
+
"function exchangeRateStored() view returns (uint256)",
|
|
768
|
+
"function underlying() view returns (address)"
|
|
769
|
+
]);
|
|
770
|
+
var COMPTROLLER_ABI = (0, import_viem.parseAbi)([
|
|
771
|
+
"function enterMarkets(address[] calldata pTokens) returns (uint256[] memory)",
|
|
772
|
+
"function exitMarket(address pTokenAddress) returns (uint256)",
|
|
773
|
+
"function getAccountLiquidity(address account) view returns (uint256 errorCode, uint256 liquidity, uint256 shortfall)",
|
|
774
|
+
"function getAllMarkets() view returns (address[] memory)",
|
|
775
|
+
"function markets(address pToken) view returns (bool isListed, uint256 collateralFactorMantissa, bool isComped)",
|
|
776
|
+
"function checkMembership(address account, address pToken) view returns (bool)"
|
|
777
|
+
]);
|
|
778
|
+
var ERC20_ABI = (0, import_viem.parseAbi)([
|
|
779
|
+
"function approve(address spender, uint256 amount) returns (bool)",
|
|
780
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
781
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
782
|
+
"function transfer(address to, uint256 amount) returns (bool)"
|
|
783
|
+
]);
|
|
784
|
+
|
|
785
|
+
// src/features/lending/read/get-account-liquidity.ts
|
|
786
|
+
init_constants();
|
|
787
|
+
var getAccountLiquiditySchema = import_zod8.z.object({
|
|
788
|
+
address: evmAddress.describe("The wallet address to check"),
|
|
789
|
+
chainId: import_zod8.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia)." }).describe("Hub chain ID to query (must be a hub chain). Defaults to BSC (56).")
|
|
790
|
+
});
|
|
791
|
+
async function getAccountLiquidity(input, config) {
|
|
792
|
+
const rpcUrl = config.rpcUrls?.[input.chainId] ?? DEFAULT_RPC_URLS[input.chainId];
|
|
793
|
+
if (!rpcUrl) {
|
|
794
|
+
throw new Error(
|
|
795
|
+
`No RPC URL available for chain ${input.chainId}. Provide one via config.rpcUrls[${input.chainId}].`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
const controllerAddress = getControllerAddress(input.chainId);
|
|
799
|
+
const client = (0, import_viem2.createPublicClient)({ transport: (0, import_viem2.http)(rpcUrl) });
|
|
800
|
+
const [error, liquidity, shortfall] = await client.readContract({
|
|
801
|
+
address: controllerAddress,
|
|
802
|
+
abi: COMPTROLLER_ABI,
|
|
803
|
+
functionName: "getAccountLiquidity",
|
|
804
|
+
args: [input.address]
|
|
805
|
+
});
|
|
806
|
+
if (error !== 0n) {
|
|
807
|
+
throw new Error(`Comptroller getAccountLiquidity returned error code ${error.toString()}`);
|
|
808
|
+
}
|
|
809
|
+
const liquidityUsd = new import_decimal2.default(liquidity.toString()).div("1e18").toNumber();
|
|
810
|
+
const shortfallUsd = new import_decimal2.default(shortfall.toString()).div("1e18").toNumber();
|
|
811
|
+
return {
|
|
812
|
+
address: input.address,
|
|
813
|
+
chainId: input.chainId,
|
|
814
|
+
liquidityUsd,
|
|
815
|
+
shortfallUsd,
|
|
816
|
+
isHealthy: shortfallUsd === 0
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/features/lending/intents/hub/supply.ts
|
|
821
|
+
var import_zod9 = require("zod");
|
|
822
|
+
var import_viem3 = require("viem");
|
|
823
|
+
init_constants();
|
|
824
|
+
var hubSupplySchema = import_zod9.z.object({
|
|
825
|
+
userAddress: evmAddress.describe("The wallet address supplying assets"),
|
|
826
|
+
asset: import_zod9.z.string().describe('Asset to supply, e.g. "USDC", "WETH"'),
|
|
827
|
+
amount: tokenAmount.describe('Human-readable amount to supply, e.g. "100" for 100 USDC'),
|
|
828
|
+
chainId: import_zod9.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia). Use build_cross_chain_supply_intent for spoke chains." }).describe("Hub chain ID. Must be a chain with native Peridot markets. Defaults to BSC (56)."),
|
|
829
|
+
enableAsCollateral: import_zod9.z.boolean().default(true).describe("Whether to enable the supplied asset as collateral. Defaults to true.")
|
|
830
|
+
});
|
|
831
|
+
function buildHubSupplyIntent(input, _config) {
|
|
832
|
+
const chainId = input.chainId ?? BSC_MAINNET_CHAIN_ID;
|
|
833
|
+
const enableAsCollateral = input.enableAsCollateral ?? true;
|
|
834
|
+
const assetUpper = input.asset.toUpperCase();
|
|
835
|
+
const decimals = getAssetDecimals(assetUpper);
|
|
836
|
+
const amount = (0, import_viem3.parseUnits)(input.amount, decimals);
|
|
837
|
+
const pToken = getPTokenAddress(chainId, assetUpper);
|
|
838
|
+
const underlying = getUnderlyingTokenAddress(chainId, assetUpper);
|
|
839
|
+
const controller = getControllerAddress(chainId);
|
|
840
|
+
const calls = [
|
|
841
|
+
{
|
|
842
|
+
to: underlying,
|
|
843
|
+
data: (0, import_viem3.encodeFunctionData)({
|
|
844
|
+
abi: ERC20_ABI,
|
|
845
|
+
functionName: "approve",
|
|
846
|
+
args: [pToken, amount]
|
|
847
|
+
}),
|
|
848
|
+
value: 0n,
|
|
849
|
+
description: `Approve p${assetUpper} contract to spend ${input.amount} ${assetUpper}`
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
to: pToken,
|
|
853
|
+
data: (0, import_viem3.encodeFunctionData)({
|
|
854
|
+
abi: PTOKEN_ABI,
|
|
855
|
+
functionName: "mint",
|
|
856
|
+
args: [amount]
|
|
857
|
+
}),
|
|
858
|
+
value: 0n,
|
|
859
|
+
description: `Supply ${input.amount} ${assetUpper} to Peridot and receive p${assetUpper} tokens`
|
|
860
|
+
}
|
|
861
|
+
];
|
|
862
|
+
if (enableAsCollateral) {
|
|
863
|
+
calls.push({
|
|
864
|
+
to: controller,
|
|
865
|
+
data: (0, import_viem3.encodeFunctionData)({
|
|
866
|
+
abi: COMPTROLLER_ABI,
|
|
867
|
+
functionName: "enterMarkets",
|
|
868
|
+
args: [[pToken]]
|
|
869
|
+
}),
|
|
870
|
+
value: 0n,
|
|
871
|
+
description: `Enable ${assetUpper} position as collateral for borrowing`
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
const collateralNote = enableAsCollateral ? " and enable as collateral" : "";
|
|
875
|
+
return {
|
|
876
|
+
type: "hub",
|
|
877
|
+
chainId,
|
|
878
|
+
calls,
|
|
879
|
+
summary: `Supply ${input.amount} ${assetUpper} to Peridot on chain ${input.chainId}${collateralNote}`
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/features/lending/intents/hub/borrow.ts
|
|
884
|
+
var import_zod10 = require("zod");
|
|
885
|
+
var import_viem4 = require("viem");
|
|
886
|
+
init_constants();
|
|
887
|
+
var hubBorrowSchema = import_zod10.z.object({
|
|
888
|
+
userAddress: evmAddress.describe("The wallet address that will borrow"),
|
|
889
|
+
borrowAsset: import_zod10.z.string().describe('Asset to borrow, e.g. "USDC", "WETH"'),
|
|
890
|
+
borrowAmount: tokenAmount.describe('Human-readable amount to borrow, e.g. "500" for 500 USDC'),
|
|
891
|
+
collateralAssets: import_zod10.z.array(import_zod10.z.string()).min(1).describe(
|
|
892
|
+
'Assets already supplied that will act as collateral, e.g. ["WETH"]. These will have enterMarkets called to ensure they are active collateral.'
|
|
893
|
+
),
|
|
894
|
+
chainId: import_zod10.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia). Use build_cross_chain_borrow_intent for spoke chains." }).describe("Hub chain ID. Defaults to BSC (56).")
|
|
895
|
+
});
|
|
896
|
+
function buildHubBorrowIntent(input, _config) {
|
|
897
|
+
const borrowAssetUpper = input.borrowAsset.toUpperCase();
|
|
898
|
+
const decimals = getAssetDecimals(borrowAssetUpper);
|
|
899
|
+
const amount = (0, import_viem4.parseUnits)(input.borrowAmount, decimals);
|
|
900
|
+
const borrowPToken = getPTokenAddress(input.chainId, borrowAssetUpper);
|
|
901
|
+
const controller = getControllerAddress(input.chainId);
|
|
902
|
+
const collateralPTokens = input.collateralAssets.map(
|
|
903
|
+
(a) => getPTokenAddress(input.chainId, a.toUpperCase())
|
|
904
|
+
);
|
|
905
|
+
return {
|
|
906
|
+
type: "hub",
|
|
907
|
+
chainId: input.chainId,
|
|
908
|
+
calls: [
|
|
909
|
+
{
|
|
910
|
+
to: controller,
|
|
911
|
+
data: (0, import_viem4.encodeFunctionData)({
|
|
912
|
+
abi: COMPTROLLER_ABI,
|
|
913
|
+
functionName: "enterMarkets",
|
|
914
|
+
args: [collateralPTokens]
|
|
915
|
+
}),
|
|
916
|
+
value: 0n,
|
|
917
|
+
description: `Enable ${input.collateralAssets.join(", ")} as active collateral`
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
to: borrowPToken,
|
|
921
|
+
data: (0, import_viem4.encodeFunctionData)({
|
|
922
|
+
abi: PTOKEN_ABI,
|
|
923
|
+
functionName: "borrow",
|
|
924
|
+
args: [amount]
|
|
925
|
+
}),
|
|
926
|
+
value: 0n,
|
|
927
|
+
description: `Borrow ${input.borrowAmount} ${borrowAssetUpper} from Peridot`
|
|
928
|
+
}
|
|
929
|
+
],
|
|
930
|
+
summary: `Borrow ${input.borrowAmount} ${borrowAssetUpper} using ${input.collateralAssets.join(", ")} as collateral`,
|
|
931
|
+
warning: "Ensure your health factor stays above 1.2 after borrowing. Use simulate_borrow to verify."
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/features/lending/intents/hub/repay.ts
|
|
936
|
+
var import_zod11 = require("zod");
|
|
937
|
+
var import_viem5 = require("viem");
|
|
938
|
+
init_constants();
|
|
939
|
+
var hubRepaySchema = import_zod11.z.object({
|
|
940
|
+
userAddress: evmAddress.describe("The wallet address repaying the debt"),
|
|
941
|
+
asset: import_zod11.z.string().describe('Asset to repay, e.g. "USDC", "WETH"'),
|
|
942
|
+
amount: import_zod11.z.string().refine((v) => v.toLowerCase() === "max" || /^\d+(\.\d+)?$/.test(v), {
|
|
943
|
+
message: 'Amount must be a positive decimal number (e.g. "500") or "max" to repay all.'
|
|
944
|
+
}).describe(
|
|
945
|
+
'Human-readable amount to repay, e.g. "500" for 500 USDC. Use "max" to repay the full outstanding balance (sets uint256 max).'
|
|
946
|
+
),
|
|
947
|
+
chainId: import_zod11.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia)." }).describe("Hub chain ID. Defaults to BSC (56).")
|
|
948
|
+
});
|
|
949
|
+
function buildHubRepayIntent(input, _config) {
|
|
950
|
+
const assetUpper = input.asset.toUpperCase();
|
|
951
|
+
const decimals = getAssetDecimals(assetUpper);
|
|
952
|
+
const isMax = input.amount.toLowerCase() === "max";
|
|
953
|
+
const amount = isMax ? import_viem5.maxUint256 : (0, import_viem5.parseUnits)(input.amount, decimals);
|
|
954
|
+
const pToken = getPTokenAddress(input.chainId, assetUpper);
|
|
955
|
+
const underlying = getUnderlyingTokenAddress(input.chainId, assetUpper);
|
|
956
|
+
const displayAmount = isMax ? "full balance" : `${input.amount} ${assetUpper}`;
|
|
957
|
+
return {
|
|
958
|
+
type: "hub",
|
|
959
|
+
chainId: input.chainId,
|
|
960
|
+
calls: [
|
|
961
|
+
{
|
|
962
|
+
to: underlying,
|
|
963
|
+
data: (0, import_viem5.encodeFunctionData)({
|
|
964
|
+
abi: ERC20_ABI,
|
|
965
|
+
functionName: "approve",
|
|
966
|
+
args: [pToken, amount]
|
|
967
|
+
}),
|
|
968
|
+
value: 0n,
|
|
969
|
+
description: `Approve p${assetUpper} to pull repayment tokens`
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
to: pToken,
|
|
973
|
+
data: (0, import_viem5.encodeFunctionData)({
|
|
974
|
+
abi: PTOKEN_ABI,
|
|
975
|
+
functionName: "repayBorrow",
|
|
976
|
+
args: [amount]
|
|
977
|
+
}),
|
|
978
|
+
value: 0n,
|
|
979
|
+
description: `Repay ${displayAmount} of ${assetUpper} debt`
|
|
980
|
+
}
|
|
981
|
+
],
|
|
982
|
+
summary: `Repay ${displayAmount} of ${assetUpper} on Peridot (chain ${input.chainId})`
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/features/lending/intents/hub/withdraw.ts
|
|
987
|
+
var import_zod12 = require("zod");
|
|
988
|
+
var import_viem6 = require("viem");
|
|
989
|
+
init_constants();
|
|
990
|
+
var hubWithdrawSchema = import_zod12.z.object({
|
|
991
|
+
userAddress: evmAddress.describe("The wallet address withdrawing"),
|
|
992
|
+
asset: import_zod12.z.string().describe('Asset to withdraw, e.g. "USDC", "WETH"'),
|
|
993
|
+
amount: tokenAmount.describe('Human-readable underlying amount to withdraw, e.g. "100" for 100 USDC'),
|
|
994
|
+
chainId: import_zod12.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia)." }).describe("Hub chain ID. Defaults to BSC (56).")
|
|
995
|
+
});
|
|
996
|
+
function buildHubWithdrawIntent(input, _config) {
|
|
997
|
+
const assetUpper = input.asset.toUpperCase();
|
|
998
|
+
const decimals = getAssetDecimals(assetUpper);
|
|
999
|
+
const amount = (0, import_viem6.parseUnits)(input.amount, decimals);
|
|
1000
|
+
const pToken = getPTokenAddress(input.chainId, assetUpper);
|
|
1001
|
+
return {
|
|
1002
|
+
type: "hub",
|
|
1003
|
+
chainId: input.chainId,
|
|
1004
|
+
calls: [
|
|
1005
|
+
{
|
|
1006
|
+
to: pToken,
|
|
1007
|
+
data: (0, import_viem6.encodeFunctionData)({
|
|
1008
|
+
abi: PTOKEN_ABI,
|
|
1009
|
+
functionName: "redeemUnderlying",
|
|
1010
|
+
args: [amount]
|
|
1011
|
+
}),
|
|
1012
|
+
value: 0n,
|
|
1013
|
+
description: `Withdraw ${input.amount} ${assetUpper} from Peridot`
|
|
1014
|
+
}
|
|
1015
|
+
],
|
|
1016
|
+
summary: `Withdraw ${input.amount} ${assetUpper} from Peridot (chain ${input.chainId})`,
|
|
1017
|
+
warning: "This will revert if withdrawing would make your outstanding borrows undercollateralized."
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// src/features/lending/intents/hub/enable-collateral.ts
|
|
1022
|
+
var import_zod13 = require("zod");
|
|
1023
|
+
var import_viem7 = require("viem");
|
|
1024
|
+
init_constants();
|
|
1025
|
+
var hubEnableCollateralSchema = import_zod13.z.object({
|
|
1026
|
+
userAddress: evmAddress.describe("The wallet address enabling collateral"),
|
|
1027
|
+
assets: import_zod13.z.array(import_zod13.z.string()).min(1).describe('Assets to enable as collateral, e.g. ["WETH", "USDC"]'),
|
|
1028
|
+
chainId: import_zod13.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia)." }).describe("Hub chain ID. Defaults to BSC (56).")
|
|
1029
|
+
});
|
|
1030
|
+
function buildHubEnableCollateralIntent(input, _config) {
|
|
1031
|
+
const controller = getControllerAddress(input.chainId);
|
|
1032
|
+
const pTokens = input.assets.map((a) => getPTokenAddress(input.chainId, a.toUpperCase()));
|
|
1033
|
+
const assetsDisplay = input.assets.map((a) => a.toUpperCase()).join(", ");
|
|
1034
|
+
return {
|
|
1035
|
+
type: "hub",
|
|
1036
|
+
chainId: input.chainId,
|
|
1037
|
+
calls: [
|
|
1038
|
+
{
|
|
1039
|
+
to: controller,
|
|
1040
|
+
data: (0, import_viem7.encodeFunctionData)({
|
|
1041
|
+
abi: COMPTROLLER_ABI,
|
|
1042
|
+
functionName: "enterMarkets",
|
|
1043
|
+
args: [pTokens]
|
|
1044
|
+
}),
|
|
1045
|
+
value: 0n,
|
|
1046
|
+
description: `Enable ${assetsDisplay} as collateral on Peridot`
|
|
1047
|
+
}
|
|
1048
|
+
],
|
|
1049
|
+
summary: `Enable ${assetsDisplay} as collateral on chain ${input.chainId}`
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/features/lending/intents/hub/disable-collateral.ts
|
|
1054
|
+
var import_zod14 = require("zod");
|
|
1055
|
+
var import_viem8 = require("viem");
|
|
1056
|
+
init_constants();
|
|
1057
|
+
var hubDisableCollateralSchema = import_zod14.z.object({
|
|
1058
|
+
userAddress: evmAddress.describe("The wallet address disabling collateral"),
|
|
1059
|
+
asset: import_zod14.z.string().describe('Asset to disable as collateral, e.g. "USDC"'),
|
|
1060
|
+
chainId: import_zod14.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia)." }).describe("Hub chain ID. Defaults to BSC (56).")
|
|
1061
|
+
});
|
|
1062
|
+
function buildHubDisableCollateralIntent(input, _config) {
|
|
1063
|
+
const assetUpper = input.asset.toUpperCase();
|
|
1064
|
+
const controller = getControllerAddress(input.chainId);
|
|
1065
|
+
const pToken = getPTokenAddress(input.chainId, assetUpper);
|
|
1066
|
+
return {
|
|
1067
|
+
type: "hub",
|
|
1068
|
+
chainId: input.chainId,
|
|
1069
|
+
calls: [
|
|
1070
|
+
{
|
|
1071
|
+
to: controller,
|
|
1072
|
+
data: (0, import_viem8.encodeFunctionData)({
|
|
1073
|
+
abi: COMPTROLLER_ABI,
|
|
1074
|
+
functionName: "exitMarket",
|
|
1075
|
+
args: [pToken]
|
|
1076
|
+
}),
|
|
1077
|
+
value: 0n,
|
|
1078
|
+
description: `Disable ${assetUpper} as collateral on Peridot`
|
|
1079
|
+
}
|
|
1080
|
+
],
|
|
1081
|
+
summary: `Disable ${assetUpper} as collateral on chain ${input.chainId}`,
|
|
1082
|
+
warning: "This will revert if you have outstanding borrows that rely on this collateral."
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/features/lending/intents/cross-chain/supply.ts
|
|
1087
|
+
var import_zod15 = require("zod");
|
|
1088
|
+
var import_viem9 = require("viem");
|
|
1089
|
+
init_constants();
|
|
1090
|
+
var crossChainSupplySchema = import_zod15.z.object({
|
|
1091
|
+
userAddress: evmAddress.describe("The wallet address on the source chain"),
|
|
1092
|
+
sourceChainId: import_zod15.z.number().int().default(ARBITRUM_CHAIN_ID).refine((id) => !isHubChain(id), { message: "sourceChainId must be a spoke chain (e.g. 42161=Arbitrum, 8453=Base). Use build_hub_supply_intent for hub chains (56, 143, 1868)." }).describe(
|
|
1093
|
+
"The spoke chain the user is on, e.g. 42161=Arbitrum, 8453=Base, 1=Ethereum. This is where the user holds the tokens they want to supply."
|
|
1094
|
+
),
|
|
1095
|
+
asset: import_zod15.z.string().describe('Asset to supply, e.g. "USDC", "WETH"'),
|
|
1096
|
+
amount: tokenAmount.describe('Human-readable amount to supply, e.g. "100" for 100 USDC'),
|
|
1097
|
+
enableAsCollateral: import_zod15.z.boolean().default(true).describe("Whether to enable the supplied asset as collateral. Defaults to true."),
|
|
1098
|
+
slippage: import_zod15.z.number().default(0.01).describe("Bridge slippage tolerance as a decimal (0.01 = 1%). Defaults to 1%.")
|
|
1099
|
+
});
|
|
1100
|
+
async function buildCrossChainSupplyIntent(input, config) {
|
|
1101
|
+
const client = new PeridotApiClient(config);
|
|
1102
|
+
const assetUpper = input.asset.toUpperCase();
|
|
1103
|
+
const decimals = getAssetDecimals(assetUpper);
|
|
1104
|
+
const amount = (0, import_viem9.parseUnits)(input.amount, decimals);
|
|
1105
|
+
const sourceChainId = input.sourceChainId ?? ARBITRUM_CHAIN_ID;
|
|
1106
|
+
const enableAsCollateral = input.enableAsCollateral ?? true;
|
|
1107
|
+
const hubChainId = resolveHubChainId(sourceChainId, config.network ?? "mainnet");
|
|
1108
|
+
const sourceToken = getUnderlyingTokenAddress(sourceChainId, assetUpper);
|
|
1109
|
+
const hubUnderlying = getUnderlyingTokenAddress(hubChainId, assetUpper);
|
|
1110
|
+
const pToken = getPTokenAddress(hubChainId, assetUpper);
|
|
1111
|
+
const runtimeBalance = {
|
|
1112
|
+
type: "runtimeErc20Balance",
|
|
1113
|
+
tokenAddress: hubUnderlying
|
|
1114
|
+
};
|
|
1115
|
+
const composeFlows = [
|
|
1116
|
+
// Step 1: Bridge from spoke → hub
|
|
1117
|
+
{
|
|
1118
|
+
type: "/instructions/intent-simple",
|
|
1119
|
+
data: {
|
|
1120
|
+
srcToken: sourceToken,
|
|
1121
|
+
dstToken: hubUnderlying,
|
|
1122
|
+
srcChainId: sourceChainId,
|
|
1123
|
+
dstChainId: hubChainId,
|
|
1124
|
+
amount: amount.toString(),
|
|
1125
|
+
slippage: input.slippage ?? 0.01
|
|
1126
|
+
},
|
|
1127
|
+
batch: false
|
|
1128
|
+
},
|
|
1129
|
+
// Step 2: Approve pToken to spend underlying
|
|
1130
|
+
{
|
|
1131
|
+
type: "/instructions/build",
|
|
1132
|
+
data: {
|
|
1133
|
+
functionSignature: "function approve(address,uint256)",
|
|
1134
|
+
args: [pToken, runtimeBalance],
|
|
1135
|
+
to: hubUnderlying,
|
|
1136
|
+
chainId: hubChainId,
|
|
1137
|
+
value: "0"
|
|
1138
|
+
},
|
|
1139
|
+
batch: true
|
|
1140
|
+
},
|
|
1141
|
+
// Step 3: Mint pTokens
|
|
1142
|
+
{
|
|
1143
|
+
type: "/instructions/build",
|
|
1144
|
+
data: {
|
|
1145
|
+
functionSignature: "function mint(uint256)",
|
|
1146
|
+
args: [runtimeBalance],
|
|
1147
|
+
to: pToken,
|
|
1148
|
+
chainId: hubChainId,
|
|
1149
|
+
value: "0"
|
|
1150
|
+
},
|
|
1151
|
+
batch: true
|
|
1152
|
+
}
|
|
1153
|
+
];
|
|
1154
|
+
if (enableAsCollateral) {
|
|
1155
|
+
const { getControllerAddress: getControllerAddress2 } = await Promise.resolve().then(() => (init_constants(), constants_exports));
|
|
1156
|
+
const controller = getControllerAddress2(hubChainId);
|
|
1157
|
+
composeFlows.push({
|
|
1158
|
+
type: "/instructions/build",
|
|
1159
|
+
data: {
|
|
1160
|
+
functionSignature: "function enterMarkets(address[] memory)",
|
|
1161
|
+
args: [[pToken]],
|
|
1162
|
+
to: controller,
|
|
1163
|
+
chainId: hubChainId,
|
|
1164
|
+
value: "0"
|
|
1165
|
+
},
|
|
1166
|
+
batch: true
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
const pTokenRuntime = {
|
|
1170
|
+
type: "runtimeErc20Balance",
|
|
1171
|
+
tokenAddress: pToken,
|
|
1172
|
+
constraints: { gte: "1" }
|
|
1173
|
+
};
|
|
1174
|
+
composeFlows.push({
|
|
1175
|
+
type: "/instructions/build",
|
|
1176
|
+
data: {
|
|
1177
|
+
functionSignature: "function transfer(address,uint256)",
|
|
1178
|
+
args: [input.userAddress, pTokenRuntime],
|
|
1179
|
+
to: pToken,
|
|
1180
|
+
chainId: hubChainId,
|
|
1181
|
+
value: "0"
|
|
1182
|
+
},
|
|
1183
|
+
batch: true
|
|
1184
|
+
});
|
|
1185
|
+
const biconomyResponse = await client.biconomyCompose({
|
|
1186
|
+
ownerAddress: input.userAddress,
|
|
1187
|
+
mode: "eoa",
|
|
1188
|
+
composeFlows
|
|
1189
|
+
});
|
|
1190
|
+
const collateralNote = enableAsCollateral ? " and enable as collateral" : "";
|
|
1191
|
+
const userSteps = [
|
|
1192
|
+
`Bridge ${input.amount} ${assetUpper} from chain ${sourceChainId} \u2192 hub (chain ${hubChainId})`,
|
|
1193
|
+
`Approve Peridot p${assetUpper} market to spend ${assetUpper}`,
|
|
1194
|
+
`Supply ${input.amount} ${assetUpper} to Peridot, receiving p${assetUpper}`,
|
|
1195
|
+
...enableAsCollateral ? [`Enable ${assetUpper} as collateral`] : [],
|
|
1196
|
+
`Return p${assetUpper} tokens to your wallet`
|
|
1197
|
+
];
|
|
1198
|
+
return {
|
|
1199
|
+
type: "cross-chain",
|
|
1200
|
+
sourceChainId,
|
|
1201
|
+
destinationChainId: hubChainId,
|
|
1202
|
+
summary: `Cross-chain supply ${input.amount} ${assetUpper} from chain ${sourceChainId} to Peridot${collateralNote}`,
|
|
1203
|
+
userSteps,
|
|
1204
|
+
biconomyInstructions: biconomyResponse,
|
|
1205
|
+
estimatedGas: biconomyResponse.estimatedGas ?? "unknown"
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// src/features/lending/intents/cross-chain/borrow.ts
|
|
1210
|
+
var import_zod16 = require("zod");
|
|
1211
|
+
var import_viem10 = require("viem");
|
|
1212
|
+
init_constants();
|
|
1213
|
+
var crossChainBorrowSchema = import_zod16.z.object({
|
|
1214
|
+
userAddress: evmAddress.describe("The wallet address borrowing"),
|
|
1215
|
+
collateralAssets: import_zod16.z.array(import_zod16.z.string()).min(1).describe('Assets already supplied as collateral, e.g. ["WETH"]'),
|
|
1216
|
+
borrowAsset: import_zod16.z.string().describe('Asset to borrow, e.g. "USDC"'),
|
|
1217
|
+
borrowAmount: tokenAmount.describe('Human-readable amount, e.g. "500" for 500 USDC'),
|
|
1218
|
+
targetChainId: import_zod16.z.number().int().optional().refine((id) => id === void 0 || !isHubChain(id), {
|
|
1219
|
+
message: "targetChainId must be a spoke chain (e.g. 42161=Arbitrum, 8453=Base) or omitted to keep funds on the hub."
|
|
1220
|
+
}).describe(
|
|
1221
|
+
"Spoke chain to receive borrowed funds, e.g. 42161=Arbitrum. If omitted, borrowed funds remain on the hub chain (BSC)."
|
|
1222
|
+
),
|
|
1223
|
+
slippage: import_zod16.z.number().default(0.01).describe("Bridge slippage tolerance. Defaults to 1%.")
|
|
1224
|
+
});
|
|
1225
|
+
async function buildCrossChainBorrowIntent(input, config) {
|
|
1226
|
+
const client = new PeridotApiClient(config);
|
|
1227
|
+
const borrowAssetUpper = input.borrowAsset.toUpperCase();
|
|
1228
|
+
const decimals = getAssetDecimals(borrowAssetUpper);
|
|
1229
|
+
const amount = (0, import_viem10.parseUnits)(input.borrowAmount, decimals);
|
|
1230
|
+
const actualHubChainId = BSC_MAINNET_CHAIN_ID;
|
|
1231
|
+
const borrowPToken = getPTokenAddress(actualHubChainId, borrowAssetUpper);
|
|
1232
|
+
const controller = getControllerAddress(actualHubChainId);
|
|
1233
|
+
const hubUnderlying = getUnderlyingTokenAddress(actualHubChainId, borrowAssetUpper);
|
|
1234
|
+
const collateralPTokens = input.collateralAssets.map(
|
|
1235
|
+
(a) => getPTokenAddress(actualHubChainId, a.toUpperCase())
|
|
1236
|
+
);
|
|
1237
|
+
const composeFlows = [
|
|
1238
|
+
// Step 1: Enable collateral
|
|
1239
|
+
{
|
|
1240
|
+
type: "/instructions/build",
|
|
1241
|
+
data: {
|
|
1242
|
+
functionSignature: "function enterMarkets(address[] memory)",
|
|
1243
|
+
args: [collateralPTokens],
|
|
1244
|
+
to: controller,
|
|
1245
|
+
chainId: actualHubChainId,
|
|
1246
|
+
value: "0"
|
|
1247
|
+
},
|
|
1248
|
+
batch: true
|
|
1249
|
+
},
|
|
1250
|
+
// Step 2: Borrow
|
|
1251
|
+
{
|
|
1252
|
+
type: "/instructions/build",
|
|
1253
|
+
data: {
|
|
1254
|
+
functionSignature: "function borrow(uint256)",
|
|
1255
|
+
args: [amount.toString()],
|
|
1256
|
+
to: borrowPToken,
|
|
1257
|
+
chainId: actualHubChainId,
|
|
1258
|
+
value: "0"
|
|
1259
|
+
},
|
|
1260
|
+
batch: true
|
|
1261
|
+
}
|
|
1262
|
+
];
|
|
1263
|
+
const runtimeBalance = {
|
|
1264
|
+
type: "runtimeErc20Balance",
|
|
1265
|
+
tokenAddress: hubUnderlying,
|
|
1266
|
+
constraints: { gte: "1" }
|
|
1267
|
+
};
|
|
1268
|
+
const userSteps = [
|
|
1269
|
+
`Enable ${input.collateralAssets.join(", ")} as active collateral`,
|
|
1270
|
+
`Borrow ${input.borrowAmount} ${borrowAssetUpper} from Peridot`
|
|
1271
|
+
];
|
|
1272
|
+
if (input.targetChainId && input.targetChainId !== actualHubChainId) {
|
|
1273
|
+
const targetToken = getUnderlyingTokenAddress(input.targetChainId, borrowAssetUpper);
|
|
1274
|
+
composeFlows.push({
|
|
1275
|
+
type: "/instructions/intent-simple",
|
|
1276
|
+
data: {
|
|
1277
|
+
srcToken: hubUnderlying,
|
|
1278
|
+
dstToken: targetToken,
|
|
1279
|
+
srcChainId: actualHubChainId,
|
|
1280
|
+
dstChainId: input.targetChainId,
|
|
1281
|
+
amount: runtimeBalance,
|
|
1282
|
+
slippage: input.slippage ?? 0.01
|
|
1283
|
+
},
|
|
1284
|
+
batch: false
|
|
1285
|
+
});
|
|
1286
|
+
userSteps.push(`Bridge ${borrowAssetUpper} from hub \u2192 chain ${input.targetChainId}`);
|
|
1287
|
+
} else {
|
|
1288
|
+
composeFlows.push({
|
|
1289
|
+
type: "/instructions/build",
|
|
1290
|
+
data: {
|
|
1291
|
+
functionSignature: "function transfer(address,uint256)",
|
|
1292
|
+
args: [input.userAddress, runtimeBalance],
|
|
1293
|
+
to: hubUnderlying,
|
|
1294
|
+
chainId: actualHubChainId,
|
|
1295
|
+
value: "0"
|
|
1296
|
+
},
|
|
1297
|
+
batch: true
|
|
1298
|
+
});
|
|
1299
|
+
userSteps.push(`Receive ${borrowAssetUpper} in your wallet on chain ${actualHubChainId}`);
|
|
1300
|
+
}
|
|
1301
|
+
const biconomyResponse = await client.biconomyCompose({
|
|
1302
|
+
ownerAddress: input.userAddress,
|
|
1303
|
+
mode: "eoa",
|
|
1304
|
+
composeFlows
|
|
1305
|
+
});
|
|
1306
|
+
const destination = input.targetChainId ?? actualHubChainId;
|
|
1307
|
+
return {
|
|
1308
|
+
type: "cross-chain",
|
|
1309
|
+
sourceChainId: actualHubChainId,
|
|
1310
|
+
destinationChainId: destination,
|
|
1311
|
+
summary: `Borrow ${input.borrowAmount} ${borrowAssetUpper} from Peridot, receive on chain ${destination}`,
|
|
1312
|
+
userSteps,
|
|
1313
|
+
biconomyInstructions: biconomyResponse,
|
|
1314
|
+
estimatedGas: biconomyResponse.estimatedGas ?? "unknown"
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// src/features/lending/intents/cross-chain/repay.ts
|
|
1319
|
+
var import_zod17 = require("zod");
|
|
1320
|
+
var import_viem11 = require("viem");
|
|
1321
|
+
init_constants();
|
|
1322
|
+
var crossChainRepaySchema = import_zod17.z.object({
|
|
1323
|
+
userAddress: evmAddress.describe("The wallet address repaying the debt"),
|
|
1324
|
+
sourceChainId: import_zod17.z.number().int().default(ARBITRUM_CHAIN_ID).refine((id) => !isHubChain(id), {
|
|
1325
|
+
message: "sourceChainId must be a spoke chain (e.g. 42161=Arbitrum, 8453=Base). Use build_hub_repay_intent for hub chains (56, 143, 1868)."
|
|
1326
|
+
}).describe("Spoke chain the user holds repayment tokens on, e.g. 42161=Arbitrum, 8453=Base"),
|
|
1327
|
+
asset: import_zod17.z.string().describe('Asset to repay, e.g. "USDC", "USDT"'),
|
|
1328
|
+
amount: tokenAmount.describe('Human-readable amount to repay, e.g. "500" for 500 USDC'),
|
|
1329
|
+
repayForAddress: evmAddress.optional().describe("Repay on behalf of another address. Defaults to userAddress."),
|
|
1330
|
+
slippage: import_zod17.z.number().default(0.01).describe("Bridge slippage tolerance. Defaults to 1%.")
|
|
1331
|
+
});
|
|
1332
|
+
async function buildCrossChainRepayIntent(input, config) {
|
|
1333
|
+
const client = new PeridotApiClient(config);
|
|
1334
|
+
const assetUpper = input.asset.toUpperCase();
|
|
1335
|
+
const decimals = getAssetDecimals(assetUpper);
|
|
1336
|
+
const amount = (0, import_viem11.parseUnits)(input.amount, decimals);
|
|
1337
|
+
const hubChainId = BSC_MAINNET_CHAIN_ID;
|
|
1338
|
+
const sourceChainId = input.sourceChainId ?? ARBITRUM_CHAIN_ID;
|
|
1339
|
+
const sourceToken = getUnderlyingTokenAddress(sourceChainId, assetUpper);
|
|
1340
|
+
const hubUnderlying = getUnderlyingTokenAddress(hubChainId, assetUpper);
|
|
1341
|
+
const pToken = getPTokenAddress(hubChainId, assetUpper);
|
|
1342
|
+
const runtimeBalance = {
|
|
1343
|
+
type: "runtimeErc20Balance",
|
|
1344
|
+
tokenAddress: hubUnderlying
|
|
1345
|
+
};
|
|
1346
|
+
const repayForAddress = input.repayForAddress ?? input.userAddress;
|
|
1347
|
+
const isBehalf = input.repayForAddress !== void 0 && input.repayForAddress !== input.userAddress;
|
|
1348
|
+
const composeFlows = [
|
|
1349
|
+
// Step 1: Bridge from spoke → hub
|
|
1350
|
+
{
|
|
1351
|
+
type: "/instructions/intent-simple",
|
|
1352
|
+
data: {
|
|
1353
|
+
srcToken: sourceToken,
|
|
1354
|
+
dstToken: hubUnderlying,
|
|
1355
|
+
srcChainId: sourceChainId,
|
|
1356
|
+
dstChainId: hubChainId,
|
|
1357
|
+
amount: amount.toString(),
|
|
1358
|
+
slippage: input.slippage ?? 0.01
|
|
1359
|
+
},
|
|
1360
|
+
batch: false
|
|
1361
|
+
},
|
|
1362
|
+
// Step 2: Approve pToken to pull repayment
|
|
1363
|
+
{
|
|
1364
|
+
type: "/instructions/build",
|
|
1365
|
+
data: {
|
|
1366
|
+
functionSignature: "function approve(address,uint256)",
|
|
1367
|
+
args: [pToken, runtimeBalance],
|
|
1368
|
+
to: hubUnderlying,
|
|
1369
|
+
chainId: hubChainId,
|
|
1370
|
+
value: "0"
|
|
1371
|
+
},
|
|
1372
|
+
batch: true
|
|
1373
|
+
},
|
|
1374
|
+
// Step 3: Repay borrow
|
|
1375
|
+
{
|
|
1376
|
+
type: "/instructions/build",
|
|
1377
|
+
data: {
|
|
1378
|
+
functionSignature: isBehalf ? "function repayBorrowBehalf(address,uint256)" : "function repayBorrow(uint256)",
|
|
1379
|
+
args: isBehalf ? [repayForAddress, runtimeBalance] : [runtimeBalance],
|
|
1380
|
+
to: pToken,
|
|
1381
|
+
chainId: hubChainId,
|
|
1382
|
+
value: "0"
|
|
1383
|
+
},
|
|
1384
|
+
batch: true
|
|
1385
|
+
},
|
|
1386
|
+
// Step 4: Return excess to EOA
|
|
1387
|
+
{
|
|
1388
|
+
type: "/instructions/build",
|
|
1389
|
+
data: {
|
|
1390
|
+
functionSignature: "function transfer(address,uint256)",
|
|
1391
|
+
args: [
|
|
1392
|
+
input.userAddress,
|
|
1393
|
+
{ type: "runtimeErc20Balance", tokenAddress: hubUnderlying, constraints: { gte: "0" } }
|
|
1394
|
+
],
|
|
1395
|
+
to: hubUnderlying,
|
|
1396
|
+
chainId: hubChainId,
|
|
1397
|
+
value: "0"
|
|
1398
|
+
},
|
|
1399
|
+
batch: true
|
|
1400
|
+
}
|
|
1401
|
+
];
|
|
1402
|
+
const biconomyResponse = await client.biconomyCompose({
|
|
1403
|
+
ownerAddress: input.userAddress,
|
|
1404
|
+
mode: "eoa",
|
|
1405
|
+
composeFlows
|
|
1406
|
+
});
|
|
1407
|
+
const behalfNote = isBehalf ? ` on behalf of ${repayForAddress}` : "";
|
|
1408
|
+
return {
|
|
1409
|
+
type: "cross-chain",
|
|
1410
|
+
sourceChainId,
|
|
1411
|
+
destinationChainId: hubChainId,
|
|
1412
|
+
summary: `Repay ${input.amount} ${assetUpper}${behalfNote} from chain ${sourceChainId} to Peridot`,
|
|
1413
|
+
userSteps: [
|
|
1414
|
+
`Bridge ${input.amount} ${assetUpper} from chain ${sourceChainId} \u2192 BSC`,
|
|
1415
|
+
`Approve Peridot p${assetUpper} to pull repayment tokens`,
|
|
1416
|
+
`Repay ${input.amount} ${assetUpper} debt${behalfNote}`,
|
|
1417
|
+
`Return any excess ${assetUpper} to your BSC wallet`
|
|
1418
|
+
],
|
|
1419
|
+
biconomyInstructions: biconomyResponse,
|
|
1420
|
+
estimatedGas: biconomyResponse.estimatedGas ?? "unknown"
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// src/features/lending/intents/cross-chain/withdraw.ts
|
|
1425
|
+
var import_zod18 = require("zod");
|
|
1426
|
+
var import_viem12 = require("viem");
|
|
1427
|
+
init_constants();
|
|
1428
|
+
var crossChainWithdrawSchema = import_zod18.z.object({
|
|
1429
|
+
userAddress: evmAddress.describe("The wallet address withdrawing"),
|
|
1430
|
+
asset: import_zod18.z.string().describe('Asset to withdraw, e.g. "USDC", "WETH"'),
|
|
1431
|
+
amount: tokenAmount.describe('Human-readable underlying amount to withdraw, e.g. "100"'),
|
|
1432
|
+
targetChainId: import_zod18.z.number().int().optional().refine((id) => id === void 0 || !isHubChain(id), {
|
|
1433
|
+
message: "targetChainId must be a spoke chain (e.g. 42161=Arbitrum, 8453=Base) or omitted to keep funds on the hub."
|
|
1434
|
+
}).describe(
|
|
1435
|
+
"Spoke chain to receive the withdrawn funds, e.g. 42161=Arbitrum. If omitted, funds remain on the hub chain (BSC)."
|
|
1436
|
+
),
|
|
1437
|
+
slippage: import_zod18.z.number().default(0.01).describe("Bridge slippage tolerance. Defaults to 1%.")
|
|
1438
|
+
});
|
|
1439
|
+
async function buildCrossChainWithdrawIntent(input, config) {
|
|
1440
|
+
const client = new PeridotApiClient(config);
|
|
1441
|
+
const assetUpper = input.asset.toUpperCase();
|
|
1442
|
+
const decimals = getAssetDecimals(assetUpper);
|
|
1443
|
+
const amount = (0, import_viem12.parseUnits)(input.amount, decimals);
|
|
1444
|
+
const hubChainId = BSC_MAINNET_CHAIN_ID;
|
|
1445
|
+
const pToken = getPTokenAddress(hubChainId, assetUpper);
|
|
1446
|
+
const hubUnderlying = getUnderlyingTokenAddress(hubChainId, assetUpper);
|
|
1447
|
+
const runtimeBalance = {
|
|
1448
|
+
type: "runtimeErc20Balance",
|
|
1449
|
+
tokenAddress: hubUnderlying,
|
|
1450
|
+
constraints: { gte: "1" }
|
|
1451
|
+
};
|
|
1452
|
+
const composeFlows = [
|
|
1453
|
+
// Step 1: Redeem from Peridot
|
|
1454
|
+
{
|
|
1455
|
+
type: "/instructions/build",
|
|
1456
|
+
data: {
|
|
1457
|
+
functionSignature: "function redeemUnderlying(uint256)",
|
|
1458
|
+
args: [amount.toString()],
|
|
1459
|
+
to: pToken,
|
|
1460
|
+
chainId: hubChainId,
|
|
1461
|
+
value: "0"
|
|
1462
|
+
},
|
|
1463
|
+
batch: true
|
|
1464
|
+
}
|
|
1465
|
+
];
|
|
1466
|
+
const userSteps = [`Redeem ${input.amount} ${assetUpper} from Peridot (p${assetUpper} \u2192 ${assetUpper})`];
|
|
1467
|
+
if (input.targetChainId && input.targetChainId !== hubChainId) {
|
|
1468
|
+
const targetToken = getUnderlyingTokenAddress(input.targetChainId, assetUpper);
|
|
1469
|
+
composeFlows.push({
|
|
1470
|
+
type: "/instructions/intent-simple",
|
|
1471
|
+
data: {
|
|
1472
|
+
srcToken: hubUnderlying,
|
|
1473
|
+
dstToken: targetToken,
|
|
1474
|
+
srcChainId: hubChainId,
|
|
1475
|
+
dstChainId: input.targetChainId,
|
|
1476
|
+
amount: runtimeBalance,
|
|
1477
|
+
slippage: input.slippage ?? 0.01
|
|
1478
|
+
},
|
|
1479
|
+
batch: false
|
|
1480
|
+
});
|
|
1481
|
+
userSteps.push(`Bridge ${assetUpper} from hub \u2192 chain ${input.targetChainId}`);
|
|
1482
|
+
} else {
|
|
1483
|
+
composeFlows.push({
|
|
1484
|
+
type: "/instructions/build",
|
|
1485
|
+
data: {
|
|
1486
|
+
functionSignature: "function transfer(address,uint256)",
|
|
1487
|
+
args: [input.userAddress, runtimeBalance],
|
|
1488
|
+
to: hubUnderlying,
|
|
1489
|
+
chainId: hubChainId,
|
|
1490
|
+
value: "0"
|
|
1491
|
+
},
|
|
1492
|
+
batch: true
|
|
1493
|
+
});
|
|
1494
|
+
userSteps.push(`Receive ${assetUpper} in your wallet on BSC`);
|
|
1495
|
+
}
|
|
1496
|
+
const biconomyResponse = await client.biconomyCompose({
|
|
1497
|
+
ownerAddress: input.userAddress,
|
|
1498
|
+
mode: "eoa",
|
|
1499
|
+
composeFlows
|
|
1500
|
+
});
|
|
1501
|
+
const destination = input.targetChainId ?? hubChainId;
|
|
1502
|
+
return {
|
|
1503
|
+
type: "cross-chain",
|
|
1504
|
+
sourceChainId: hubChainId,
|
|
1505
|
+
destinationChainId: destination,
|
|
1506
|
+
summary: `Withdraw ${input.amount} ${assetUpper} from Peridot, receive on chain ${destination}`,
|
|
1507
|
+
userSteps,
|
|
1508
|
+
biconomyInstructions: biconomyResponse,
|
|
1509
|
+
estimatedGas: biconomyResponse.estimatedGas ?? "unknown"
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// src/features/lending/status/check-transaction.ts
|
|
1514
|
+
var import_zod19 = require("zod");
|
|
1515
|
+
var checkTransactionStatusSchema = import_zod19.z.object({
|
|
1516
|
+
superTxHash: import_zod19.z.string().describe(
|
|
1517
|
+
"The Biconomy super-transaction hash returned after executing a cross-chain intent. Looks like a regular tx hash (0x...)."
|
|
1518
|
+
)
|
|
1519
|
+
});
|
|
1520
|
+
async function checkTransactionStatus(input, config) {
|
|
1521
|
+
const client = new PeridotApiClient(config);
|
|
1522
|
+
return client.biconomyGetStatus(input.superTxHash);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// src/features/lending/read/get-liquidatable-positions.ts
|
|
1526
|
+
var import_zod20 = require("zod");
|
|
1527
|
+
var getLiquidatablePositionsSchema = import_zod20.z.object({
|
|
1528
|
+
chainId: import_zod20.z.number().int().optional().describe(
|
|
1529
|
+
"Filter to a single hub chain (56=BSC, 143=Monad, 1868=Somnia). Omit to return at-risk accounts across all chains."
|
|
1530
|
+
),
|
|
1531
|
+
minShortfall: import_zod20.z.number().min(0).optional().describe(
|
|
1532
|
+
"Minimum shortfall_usd threshold in USD (default: 0 \u2014 returns all underwater accounts). Use e.g. 100 to focus only on meaningfully undercollateralised positions."
|
|
1533
|
+
),
|
|
1534
|
+
limit: import_zod20.z.number().int().min(1).max(200).optional().describe("Maximum number of results (default: 50, max: 200). Results are ordered by shortfall descending.")
|
|
1535
|
+
});
|
|
1536
|
+
async function getLiquidatablePositions(input, config) {
|
|
1537
|
+
const client = new PeridotApiClient(config);
|
|
1538
|
+
const opts = {};
|
|
1539
|
+
if (input.chainId !== void 0) opts.chainId = input.chainId;
|
|
1540
|
+
if (input.minShortfall !== void 0) opts.minShortfall = input.minShortfall;
|
|
1541
|
+
if (input.limit !== void 0) opts.limit = input.limit;
|
|
1542
|
+
return client.getLiquidatablePositions(opts);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/features/lending/intents/hub/liquidate.ts
|
|
1546
|
+
var import_zod21 = require("zod");
|
|
1547
|
+
var import_viem13 = require("viem");
|
|
1548
|
+
init_constants();
|
|
1549
|
+
var hubLiquidateSchema = import_zod21.z.object({
|
|
1550
|
+
liquidatorAddress: evmAddress.describe("The wallet address executing the liquidation (your address)"),
|
|
1551
|
+
borrowerAddress: evmAddress.describe("The underwater borrower address to liquidate"),
|
|
1552
|
+
repayAsset: import_zod21.z.string().describe(
|
|
1553
|
+
'The asset you will repay on behalf of the borrower, e.g. "USDC". This must be an asset the borrower has borrowed.'
|
|
1554
|
+
),
|
|
1555
|
+
repayAmount: import_zod21.z.string().refine((v) => v.toLowerCase() === "max" || /^\d+(\.\d+)?$/.test(v), {
|
|
1556
|
+
message: 'Amount must be a positive decimal number (e.g. "500") or "max" for uint256 max.'
|
|
1557
|
+
}).describe(
|
|
1558
|
+
`Amount of repayAsset to repay (human-readable, e.g. "500" for 500 USDC). A liquidator may repay at most 50% of the borrower's outstanding debt per call (close factor). Use "max" to pass uint256 max \u2014 the protocol will cap it at the close factor automatically.`
|
|
1559
|
+
),
|
|
1560
|
+
collateralAsset: import_zod21.z.string().describe(
|
|
1561
|
+
'The collateral asset you want to seize in return, e.g. "WETH". This must be an asset the borrower has supplied as collateral. You receive the equivalent value in pTokens (+ liquidation incentive bonus).'
|
|
1562
|
+
),
|
|
1563
|
+
chainId: import_zod21.z.number().int().default(BSC_MAINNET_CHAIN_ID).refine(isHubChain, { message: "chainId must be a hub chain (56=BSC, 143=Monad, 1868=Somnia)." }).describe("Hub chain where the underwater position exists. Defaults to BSC (56).")
|
|
1564
|
+
});
|
|
1565
|
+
function buildHubLiquidateIntent(input, _config) {
|
|
1566
|
+
const repayAsset = input.repayAsset.toUpperCase();
|
|
1567
|
+
const collateralAsset = input.collateralAsset.toUpperCase();
|
|
1568
|
+
const repayDecimals = getAssetDecimals(repayAsset);
|
|
1569
|
+
const isMax = input.repayAmount.toLowerCase() === "max";
|
|
1570
|
+
const repayAmount = isMax ? import_viem13.maxUint256 : (0, import_viem13.parseUnits)(input.repayAmount, repayDecimals);
|
|
1571
|
+
const pRepayToken = getPTokenAddress(input.chainId, repayAsset);
|
|
1572
|
+
const pCollateralToken = getPTokenAddress(input.chainId, collateralAsset);
|
|
1573
|
+
const underlyingRepay = getUnderlyingTokenAddress(input.chainId, repayAsset);
|
|
1574
|
+
const displayAmount = isMax ? "max (capped at close factor)" : `${input.repayAmount} ${repayAsset}`;
|
|
1575
|
+
return {
|
|
1576
|
+
type: "hub",
|
|
1577
|
+
chainId: input.chainId,
|
|
1578
|
+
calls: [
|
|
1579
|
+
{
|
|
1580
|
+
to: underlyingRepay,
|
|
1581
|
+
data: (0, import_viem13.encodeFunctionData)({
|
|
1582
|
+
abi: ERC20_ABI,
|
|
1583
|
+
functionName: "approve",
|
|
1584
|
+
args: [pRepayToken, repayAmount]
|
|
1585
|
+
}),
|
|
1586
|
+
value: 0n,
|
|
1587
|
+
description: `Approve p${repayAsset} to pull ${displayAmount} for liquidation`
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
to: pRepayToken,
|
|
1591
|
+
data: (0, import_viem13.encodeFunctionData)({
|
|
1592
|
+
abi: PTOKEN_ABI,
|
|
1593
|
+
functionName: "liquidateBorrow",
|
|
1594
|
+
args: [input.borrowerAddress, repayAmount, pCollateralToken]
|
|
1595
|
+
}),
|
|
1596
|
+
value: 0n,
|
|
1597
|
+
description: `Liquidate ${input.borrowerAddress}: repay ${displayAmount} of ${repayAsset}, seize p${collateralAsset} collateral`
|
|
1598
|
+
}
|
|
1599
|
+
],
|
|
1600
|
+
summary: `Liquidate ${input.borrowerAddress} on chain ${input.chainId}: repay ${displayAmount} of ${repayAsset}, receive p${collateralAsset}`,
|
|
1601
|
+
warning: "Verify the borrower is still underwater (shortfallUsd > 0) immediately before submitting. The transaction will revert if the position has been partially repaid between blocks."
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// src/features/lending/tools.ts
|
|
1606
|
+
var lendingTools = [
|
|
1607
|
+
// ── Read / Simulate ──────────────────────────────────────────────────────
|
|
1608
|
+
{
|
|
1609
|
+
name: "list_markets",
|
|
1610
|
+
description: 'List all Peridot lending markets across all chains with key metrics: asset symbol, chainId, price (USD), TVL (USD), utilization %, available liquidity (USD), and collateral factor %. Results are sorted by TVL descending \u2014 the deepest, most liquid markets appear first. Call this when the user asks "what can I lend or borrow?", "which assets are available?", or before recommending a specific market when you do not already know what is available. Optionally pass chainId (56=BSC, 143=Monad, 1868=Somnia) to restrict results to one chain.',
|
|
1611
|
+
inputSchema: listMarketsSchema,
|
|
1612
|
+
execute: listMarkets,
|
|
1613
|
+
category: "lending"
|
|
1614
|
+
},
|
|
1615
|
+
{
|
|
1616
|
+
name: "get_leaderboard",
|
|
1617
|
+
description: 'Fetch the Peridot points leaderboard: ranked list of top users by total protocol points earned. Each entry contains: rank, wallet address, totalPoints, supplyCount, borrowCount, repayCount, redeemCount, and last-updated timestamp. Points reflect on-chain DeFi activity and daily logins. Call this when the user asks "who are the top users?", "show me the leaderboard", "how many points does address X have?", or "where do I rank?". Use limit (default 50, max 100) to control result size.',
|
|
1618
|
+
inputSchema: getLeaderboardSchema,
|
|
1619
|
+
execute: getLeaderboard,
|
|
1620
|
+
category: "lending"
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
name: "get_market_rates",
|
|
1624
|
+
description: "Fetch the full rate breakdown for a specific Peridot market (asset + chainId). Returns: base supply APY, base borrow APY, PERIDOT token reward APY (supply and borrow), boost source APY (Morpho vault / PancakeSwap LP / Magma staking), boost reward APY, total supply APY (= base + peridot + boost_source + boost_rewards), net borrow APY (= base borrow \u2212 peridot borrow reward), TVL (USD), utilization %, available liquidity (USD), asset price (USD), collateral factor %. Call this when the user asks about APY, yields, borrow rates, or liquidity for a specific asset. If you do not know which chainId the asset is on, call list_markets first.",
|
|
1625
|
+
inputSchema: getMarketRatesSchema,
|
|
1626
|
+
execute: getMarketRates,
|
|
1627
|
+
category: "lending"
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
name: "get_portfolio",
|
|
1631
|
+
description: `Fetch a wallet's full Peridot portfolio overview: portfolio summary (currentValue, totalSupplied, totalBorrowed, netApy, simplified healthFactor), per-asset breakdown with allocation percentages (supplied, borrowed, net, % of portfolio), all transaction counts (totalCount, supply/borrow/repay/redeem), and lifetime earnings (effectiveApy, totalLifetimeEarnings in USD). Call this when the user asks "how is my portfolio performing?", "what are my earnings?", "show me my full breakdown", "what percentage of my portfolio is in X?", or any question about lifetime yield or activity history. Results are cached for 30 s \u2014 concurrent calls for the same address share one request. For a quick pre-action exposure check before borrowing or withdrawing, use get_user_position instead (lighter, returns simplified health factor).`,
|
|
1632
|
+
inputSchema: getPortfolioSchema,
|
|
1633
|
+
execute: getPortfolio,
|
|
1634
|
+
category: "lending"
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
name: "get_user_position",
|
|
1638
|
+
description: "Fetch a user's current Peridot portfolio snapshot: totalSuppliedUsd, totalBorrowedUsd, netWorthUsd, netApyPct, per-asset breakdown, and transaction counts (supply/borrow/repay/redeem). Also returns a simplified healthFactor estimate (totalSupplied / totalBorrowed). IMPORTANT: this estimate ignores per-asset collateral factors and will OVERSTATE the real on-chain health \u2014 treat it as a quick indicator only. Rule of thumb: above 2.0 = low near-term risk; below 2.0 = call get_account_liquidity before recommending further borrows or large withdrawals. ALWAYS call this before building any borrow, withdraw, or repay intent so you know the user's current exposure and can explain the risk to them.",
|
|
1639
|
+
inputSchema: getUserPositionSchema,
|
|
1640
|
+
execute: getUserPosition,
|
|
1641
|
+
category: "lending"
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
name: "simulate_borrow",
|
|
1645
|
+
description: 'Simulate the health factor impact of a proposed borrow before submitting any transaction. Returns: currentHealthFactor, projectedHealthFactor, borrowAmountUsd, isSafe (bool), riskLevel (safe | moderate | high | critical | liquidatable), and maxSafeBorrowUsd. ALWAYS call this before build_hub_borrow_intent or build_cross_chain_borrow_intent. Rules: if isSafe=false \u2192 explain the risk and do NOT build the intent. If riskLevel is "high" or worse \u2192 warn the user and confirm before proceeding. If riskLevel is "moderate" \u2192 note the risk, suggest a smaller amount or adding more collateral. Never skip this step even if the user sounds confident \u2014 you cannot predict the liquidation threshold without it.',
|
|
1646
|
+
inputSchema: simulateBorrowSchema,
|
|
1647
|
+
execute: simulateBorrow,
|
|
1648
|
+
category: "lending"
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
name: "get_account_liquidity",
|
|
1652
|
+
description: "Read the authoritative on-chain borrow capacity and health directly from Peridot's Comptroller contract. Returns: liquidityUsd (how much more the user can safely borrow), shortfallUsd (how much underwater \u2014 non-zero means they are at risk of liquidation), and isHealthy (true when shortfall = 0). Use this instead of get_user_position when precision matters: before large withdrawals, when the simplified health factor is near 1.5, or when the user asks whether they are at risk of liquidation. This is the same value the protocol uses on-chain.",
|
|
1653
|
+
inputSchema: getAccountLiquiditySchema,
|
|
1654
|
+
execute: getAccountLiquidity,
|
|
1655
|
+
category: "lending"
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
name: "get_liquidatable_positions",
|
|
1659
|
+
description: "Fetch a list of borrowers currently underwater (shortfallUsd > 0) and eligible for liquidation on Peridot hub chains. Data is sourced from the on-chain health scanner that indexes borrow events and recomputes account health periodically. IMPORTANT: if this returns an empty list, the scanner pipeline (scan_borrow_events + scan_account_health) may not have run yet \u2014 the data table could be empty rather than meaning no positions are underwater. In that case, inform the user that liquidation data is not yet available and suggest checking back later. Each result contains: address, chainId, shortfallUsd (how far underwater in USD), liquidityUsd (0 when underwater), and checkedAt (when last scanned). Results are ordered by shortfallUsd descending \u2014 the most undercollateralised positions first. Use minShortfall to filter out dust positions (e.g. minShortfall=100 for $100+ shortfall). Always call get_account_liquidity to re-confirm a position is still underwater immediately before building a liquidation intent \u2014 health can change between scanner runs.",
|
|
1660
|
+
inputSchema: getLiquidatablePositionsSchema,
|
|
1661
|
+
execute: getLiquidatablePositions,
|
|
1662
|
+
category: "liquidations"
|
|
1663
|
+
},
|
|
1664
|
+
// ── Hub-chain intents (user on BSC / Monad / Somnia) ─────────────────────
|
|
1665
|
+
// Hub chains host Peridot lending pools natively.
|
|
1666
|
+
// Hub chainIds: BSC (56), Monad (143), Somnia (1868).
|
|
1667
|
+
// If the user is on any other chain, use the cross-chain tools instead.
|
|
1668
|
+
{
|
|
1669
|
+
name: "build_hub_supply_intent",
|
|
1670
|
+
description: "Build signed-ready calldata to supply an asset to Peridot on a hub chain. USE THIS TOOL ONLY when the user is already on a hub chain: BSC (56), Monad (143), or Somnia (1868). If the user is on Arbitrum (42161), Base (8453), Ethereum (1), Polygon (137), Optimism (10), or Avalanche (43114), use build_cross_chain_supply_intent instead. Returns an ordered `calls` array: approve ERC-20 \u2192 mint pToken \u2192 enterMarkets (enable collateral). Tell the user to sign and submit each call in sequence using their wallet.",
|
|
1671
|
+
inputSchema: hubSupplySchema,
|
|
1672
|
+
execute: buildHubSupplyIntent,
|
|
1673
|
+
category: "lending"
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
name: "build_hub_borrow_intent",
|
|
1677
|
+
description: "Build calldata to borrow from Peridot on a hub chain (BSC 56, Monad 143, or Somnia 1868). Returns ordered calls: enterMarkets (activate collateral if needed) \u2192 borrow. REQUIRED BEFORE CALLING THIS: run simulate_borrow and confirm isSafe=true. If the user is on a spoke chain (Arbitrum, Base, Ethereum, etc.), use build_cross_chain_borrow_intent instead. Tell the user to sign and submit each call in order.",
|
|
1678
|
+
inputSchema: hubBorrowSchema,
|
|
1679
|
+
execute: buildHubBorrowIntent,
|
|
1680
|
+
category: "lending"
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
name: "build_hub_repay_intent",
|
|
1684
|
+
description: 'Build calldata to repay an outstanding Peridot borrow on a hub chain (BSC 56, Monad 143, Somnia 1868). Returns ordered calls: approve ERC-20 \u2192 repayBorrow. Pass amount="max" to repay the entire outstanding debt (recommended to avoid dust). If the user is on a spoke chain, use build_cross_chain_repay_intent instead. Tell the user to sign and submit each call in order.',
|
|
1685
|
+
inputSchema: hubRepaySchema,
|
|
1686
|
+
execute: buildHubRepayIntent,
|
|
1687
|
+
category: "lending"
|
|
1688
|
+
},
|
|
1689
|
+
{
|
|
1690
|
+
name: "build_hub_withdraw_intent",
|
|
1691
|
+
description: "Build calldata to withdraw (redeem) supplied assets from Peridot on a hub chain. ALWAYS call get_user_position first to check for active borrows. If the user has outstanding debt, also call get_account_liquidity to verify the withdrawal will not cause a shortfall \u2014 the contract will revert on-chain if it does. If the user is on a spoke chain, use build_cross_chain_withdraw_intent instead.",
|
|
1692
|
+
inputSchema: hubWithdrawSchema,
|
|
1693
|
+
execute: buildHubWithdrawIntent,
|
|
1694
|
+
category: "lending"
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
name: "build_enable_collateral_intent",
|
|
1698
|
+
description: "Build the transaction call to enable a supplied asset as collateral in Peridot (enterMarkets). This is required before you can borrow against a supplied asset. NOTE: build_hub_supply_intent already calls enterMarkets by default \u2014 only call this separately if the user supplied an asset previously without enabling collateral, or if they disabled it and want to re-enable it.",
|
|
1699
|
+
inputSchema: hubEnableCollateralSchema,
|
|
1700
|
+
execute: buildHubEnableCollateralIntent,
|
|
1701
|
+
category: "lending"
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
name: "build_disable_collateral_intent",
|
|
1705
|
+
description: "Build the transaction call to stop using a supplied asset as collateral (exitMarket). ALWAYS call get_account_liquidity first and confirm shortfallUsd=0 after the removal \u2014 the contract will revert if any active borrow relies on this asset as collateral. Explain to the user that disabling collateral reduces their borrow capacity.",
|
|
1706
|
+
inputSchema: hubDisableCollateralSchema,
|
|
1707
|
+
execute: buildHubDisableCollateralIntent,
|
|
1708
|
+
category: "lending"
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
name: "build_liquidation_intent",
|
|
1712
|
+
description: "Build calldata to liquidate an underwater Peridot borrower on a hub chain. The liquidator repays part of the borrower's debt (up to 50% per call \u2014 the protocol close factor) and in return seizes an equivalent value of the borrower's collateral plus the liquidation bonus. Seized collateral is received as pToken shares \u2014 call redeem on the collateral pToken afterward to convert pTokens back to the underlying asset. REQUIRED BEFORE CALLING THIS: 1. Call get_liquidatable_positions or get_account_liquidity to confirm shortfallUsd > 0. 2. Verify the borrower has borrowed repayAsset and supplied collateralAsset as collateral. 3. Never build a liquidation intent without confirming the position is still underwater. Returns ordered calls: approve underlying \u2192 liquidateBorrow. Only works on hub chains (BSC 56, Monad 143, Somnia 1868).",
|
|
1713
|
+
inputSchema: hubLiquidateSchema,
|
|
1714
|
+
execute: buildHubLiquidateIntent,
|
|
1715
|
+
category: "liquidations"
|
|
1716
|
+
},
|
|
1717
|
+
// ── Cross-chain intents (user on a spoke chain) ───────────────────────────
|
|
1718
|
+
// Spoke chains: Arbitrum (42161), Base (8453), Ethereum (1),
|
|
1719
|
+
// Polygon (137), Optimism (10), Avalanche (43114).
|
|
1720
|
+
// These call Biconomy MEE /compose to produce a single cross-chain payload.
|
|
1721
|
+
// The user signs ONE transaction in their dApp; Biconomy executes the bridge + action.
|
|
1722
|
+
// Use check_transaction_status to track completion.
|
|
1723
|
+
{
|
|
1724
|
+
name: "build_cross_chain_supply_intent",
|
|
1725
|
+
description: "Build a cross-chain supply intent for a user whose tokens are on a spoke chain. USE THIS when the user is on Arbitrum (42161), Base (8453), Ethereum (1), Polygon (137), Optimism (10), or Avalanche (43114). Do NOT use this for BSC (56), Monad (143), or Somnia (1868) \u2014 those are hub chains, use build_hub_supply_intent instead. REQUIRES: biconomyApiKey must be set in config (BICONOMY_API_KEY env var on the MCP server). If it is not set, this tool will fail \u2014 inform the user that cross-chain operations are not available in this deployment. Bridges tokens from the spoke chain to BSC and deposits into Peridot atomically. Returns biconomyInstructions \u2014 the user signs one transaction in their dApp, which submits the payload to Biconomy /execute. After the user submits, call check_transaction_status with the returned superTxHash to track it.",
|
|
1726
|
+
inputSchema: crossChainSupplySchema,
|
|
1727
|
+
execute: buildCrossChainSupplyIntent,
|
|
1728
|
+
category: "lending"
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
name: "build_cross_chain_borrow_intent",
|
|
1732
|
+
description: "Borrow from Peridot on BSC and optionally bridge the borrowed amount back to a spoke chain, all in a single atomic cross-chain operation via Biconomy MEE. REQUIRED BEFORE CALLING THIS: run simulate_borrow and confirm isSafe=true. REQUIRES: biconomyApiKey must be set in config (BICONOMY_API_KEY env var on the MCP server). If it is not set, this tool will fail \u2014 inform the user that cross-chain operations are not available in this deployment. Returns biconomyInstructions for a single user signature in their dApp. Use check_transaction_status to track the cross-chain execution.",
|
|
1733
|
+
inputSchema: crossChainBorrowSchema,
|
|
1734
|
+
execute: buildCrossChainBorrowIntent,
|
|
1735
|
+
category: "lending"
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
name: "build_cross_chain_repay_intent",
|
|
1739
|
+
description: "Repay a Peridot borrow using tokens held on a spoke chain \u2014 for example, repay a BSC USDC debt by spending USDC on Arbitrum. Bridges and repays in one atomic operation. Useful when the user wants to repay but their tokens are not on BSC. REQUIRES: biconomyApiKey must be set in config (BICONOMY_API_KEY env var on the MCP server). If it is not set, this tool will fail \u2014 inform the user that cross-chain operations are not available in this deployment. Returns biconomyInstructions for a single user signature in their dApp.",
|
|
1740
|
+
inputSchema: crossChainRepaySchema,
|
|
1741
|
+
execute: buildCrossChainRepayIntent,
|
|
1742
|
+
category: "lending"
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
name: "build_cross_chain_withdraw_intent",
|
|
1746
|
+
description: "Withdraw supplied assets from Peridot on BSC and optionally bridge them to a spoke chain, all in one atomic cross-chain operation. ALWAYS call get_user_position first to check for active borrows; if present, also call get_account_liquidity to verify the withdrawal will not cause a shortfall. REQUIRES: biconomyApiKey must be set in config (BICONOMY_API_KEY env var on the MCP server). If it is not set, this tool will fail \u2014 inform the user that cross-chain operations are not available in this deployment. Returns biconomyInstructions for a single user signature in their dApp.",
|
|
1747
|
+
inputSchema: crossChainWithdrawSchema,
|
|
1748
|
+
execute: buildCrossChainWithdrawIntent,
|
|
1749
|
+
category: "lending"
|
|
1750
|
+
},
|
|
1751
|
+
// ── Status ───────────────────────────────────────────────────────────────
|
|
1752
|
+
{
|
|
1753
|
+
name: "check_transaction_status",
|
|
1754
|
+
description: 'Check the execution status of a submitted cross-chain Biconomy transaction by its superTxHash. Returns one of: pending (not yet picked up), processing (bridge/relay in progress), success (all cross-chain steps completed), failed (reverted or timed out), not_found (hash unknown \u2014 may not have been submitted yet). Call this after the user submits a cross-chain intent and poll every ~10s until status is "success" or "failed". Only cross-chain intents produce a superTxHash \u2014 hub-chain intents give standard on-chain tx hashes that can be checked on a block explorer.',
|
|
1755
|
+
inputSchema: checkTransactionStatusSchema,
|
|
1756
|
+
execute: checkTransactionStatus,
|
|
1757
|
+
category: "status"
|
|
1758
|
+
}
|
|
1759
|
+
];
|
|
1760
|
+
|
|
1761
|
+
// src/index.ts
|
|
1762
|
+
init_constants();
|
|
1763
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1764
|
+
0 && (module.exports = {
|
|
1765
|
+
ARBITRUM_CHAIN_ID,
|
|
1766
|
+
ASSET_DECIMALS,
|
|
1767
|
+
BASE_CHAIN_ID,
|
|
1768
|
+
BSC_MAINNET_CHAIN_ID,
|
|
1769
|
+
BSC_TESTNET_CHAIN_ID,
|
|
1770
|
+
BSC_UNDERLYING_TOKENS,
|
|
1771
|
+
COMPTROLLER_ABI,
|
|
1772
|
+
ERC20_ABI,
|
|
1773
|
+
ETHEREUM_CHAIN_ID,
|
|
1774
|
+
MONAD_MAINNET_CHAIN_ID,
|
|
1775
|
+
PERIDOT_CONTROLLER,
|
|
1776
|
+
PERIDOT_MARKETS,
|
|
1777
|
+
PTOKEN_ABI,
|
|
1778
|
+
PeridotApiClient,
|
|
1779
|
+
SOMNIA_MAINNET_CHAIN_ID,
|
|
1780
|
+
SPOKE_TOKENS,
|
|
1781
|
+
buildCrossChainBorrowIntent,
|
|
1782
|
+
buildCrossChainRepayIntent,
|
|
1783
|
+
buildCrossChainSupplyIntent,
|
|
1784
|
+
buildCrossChainWithdrawIntent,
|
|
1785
|
+
buildHubBorrowIntent,
|
|
1786
|
+
buildHubDisableCollateralIntent,
|
|
1787
|
+
buildHubEnableCollateralIntent,
|
|
1788
|
+
buildHubRepayIntent,
|
|
1789
|
+
buildHubSupplyIntent,
|
|
1790
|
+
buildHubWithdrawIntent,
|
|
1791
|
+
checkTransactionStatus,
|
|
1792
|
+
getAccountLiquidity,
|
|
1793
|
+
getAssetDecimals,
|
|
1794
|
+
getControllerAddress,
|
|
1795
|
+
getLeaderboard,
|
|
1796
|
+
getMarketRates,
|
|
1797
|
+
getPTokenAddress,
|
|
1798
|
+
getUnderlyingTokenAddress,
|
|
1799
|
+
getUserPosition,
|
|
1800
|
+
isHubChain,
|
|
1801
|
+
lendingTools,
|
|
1802
|
+
listMarkets,
|
|
1803
|
+
resolveHubChainId,
|
|
1804
|
+
simulateBorrow
|
|
1805
|
+
});
|
|
1806
|
+
//# sourceMappingURL=index.cjs.map
|