@continuumdao/ctm-mpc-defi 0.2.35 → 0.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/catalog.cjs +1015 -6
- package/dist/agent/catalog.cjs.map +1 -1
- package/dist/agent/catalog.d.ts +1787 -1
- package/dist/agent/catalog.js +976 -7
- package/dist/agent/catalog.js.map +1 -1
- package/dist/agent/skills/continuum-dao/SKILL.md +6 -1
- package/dist/agent/skills/pendle/SKILL.md +63 -0
- package/dist/agent/skills/yield-compare/SKILL.md +7 -4
- package/dist/core/index.cjs +2 -1
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +129 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +129 -2
- package/dist/index.js.map +1 -1
- package/dist/protocols/evm/aerodrome/index.cjs.map +1 -1
- package/dist/protocols/evm/aerodrome/index.js.map +1 -1
- package/dist/protocols/evm/continuum-dao/index.cjs +92 -0
- package/dist/protocols/evm/continuum-dao/index.cjs.map +1 -1
- package/dist/protocols/evm/continuum-dao/index.d.ts +49 -1
- package/dist/protocols/evm/continuum-dao/index.js +86 -1
- package/dist/protocols/evm/continuum-dao/index.js.map +1 -1
- package/dist/protocols/evm/pendle/index.cjs +1530 -0
- package/dist/protocols/evm/pendle/index.cjs.map +1 -0
- package/dist/protocols/evm/pendle/index.d.ts +463 -0
- package/dist/protocols/evm/pendle/index.js +1463 -0
- package/dist/protocols/evm/pendle/index.js.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,1463 @@
|
|
|
1
|
+
import { parseAbi, isAddress, getAddress, defineChain, createPublicClient, http, parseUnits, formatUnits, encodeFunctionData, parseGwei, serializeTransaction, keccak256 } from 'viem';
|
|
2
|
+
import { fetchChainFeeParams, gasLimitFromEstimateAndChainConfig, gweiToDecimalString, proposalTxParamsToFeeSnapshot, alignEip1559FeesWithLatestBase, getClientIdFromKeyGenResult } from '@continuumdao/continuum-node-sdk';
|
|
3
|
+
|
|
4
|
+
// src/core/registry.ts
|
|
5
|
+
var modules = [];
|
|
6
|
+
function registerProtocolModule(mod) {
|
|
7
|
+
const existing = modules.findIndex((m) => m.id === mod.id);
|
|
8
|
+
if (existing >= 0) {
|
|
9
|
+
modules[existing] = mod;
|
|
10
|
+
} else {
|
|
11
|
+
modules.push(mod);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function matchesAssetFilter(opts) {
|
|
15
|
+
const f = (opts.filter ?? "").trim();
|
|
16
|
+
if (!f) return true;
|
|
17
|
+
const addr = (opts.address ?? "").trim();
|
|
18
|
+
if (isAddress(f) && isAddress(addr)) {
|
|
19
|
+
return getAddress(addr) === getAddress(f);
|
|
20
|
+
}
|
|
21
|
+
const sym = (opts.symbol ?? "").trim();
|
|
22
|
+
if (!sym) return false;
|
|
23
|
+
return sym.toLowerCase() === f.toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/protocols/evm/pendle/support.ts
|
|
27
|
+
var PENDLE_PROTOCOL_ID = "pendle";
|
|
28
|
+
var PENDLE_CORE_API_BASE = "https://api-v2.pendle.finance/core";
|
|
29
|
+
var PENDLE_NATIVE_PLACEHOLDER = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
30
|
+
var PENDLE_LISTING_CAP = 100;
|
|
31
|
+
var PENDLE_LISTING_MAX_PAGES = 8;
|
|
32
|
+
var PENDLE_SEARCH_EXTRA_PAGES = 3;
|
|
33
|
+
var PENDLE_ICON_CACHE_MS = 12 * 60 * 1e3;
|
|
34
|
+
var PENDLE_CHAIN_CACHE_MS = 30 * 60 * 1e3;
|
|
35
|
+
var PENDLE_CONVERT_DEFAULT_GAS_UNITS = 650000n;
|
|
36
|
+
function parsePendleAssetId(id) {
|
|
37
|
+
const raw = (id ?? "").trim();
|
|
38
|
+
const dash = raw.indexOf("-");
|
|
39
|
+
if (dash <= 0) return null;
|
|
40
|
+
const chainId = Number.parseInt(raw.slice(0, dash), 10);
|
|
41
|
+
const address = raw.slice(dash + 1).trim();
|
|
42
|
+
if (!Number.isFinite(chainId) || chainId <= 0 || !address) return null;
|
|
43
|
+
return { chainId, address };
|
|
44
|
+
}
|
|
45
|
+
function pendleAssetId(chainId, address) {
|
|
46
|
+
return `${chainId}-${address.trim().toLowerCase()}`;
|
|
47
|
+
}
|
|
48
|
+
function isPendleNativeToken(token) {
|
|
49
|
+
const t = token.trim().toLowerCase();
|
|
50
|
+
return t === "eth" || t === "native" || t === PENDLE_NATIVE_PLACEHOLDER.toLowerCase() || t === "0x0000000000000000000000000000000000000000";
|
|
51
|
+
}
|
|
52
|
+
function normalizePendleTokenAddress(token) {
|
|
53
|
+
if (isPendleNativeToken(token)) return PENDLE_NATIVE_PLACEHOLDER.toLowerCase();
|
|
54
|
+
return token.trim().toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/protocols/evm/pendle/api.ts
|
|
58
|
+
var FETCH_TIMEOUT_MS = 12e4;
|
|
59
|
+
var PendleApiError = class extends Error {
|
|
60
|
+
status;
|
|
61
|
+
path;
|
|
62
|
+
constructor(message, status, path) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "PendleApiError";
|
|
65
|
+
this.status = status;
|
|
66
|
+
this.path = path;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
function sleep(ms) {
|
|
70
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
71
|
+
}
|
|
72
|
+
function queryString(params) {
|
|
73
|
+
const q = new URLSearchParams();
|
|
74
|
+
for (const [k, v] of Object.entries(params)) {
|
|
75
|
+
if (v === void 0 || v === "") continue;
|
|
76
|
+
q.set(k, String(v));
|
|
77
|
+
}
|
|
78
|
+
const s = q.toString();
|
|
79
|
+
return s ? `?${s}` : "";
|
|
80
|
+
}
|
|
81
|
+
async function pendleCoreFetch(args) {
|
|
82
|
+
const path = args.path.startsWith("/") ? args.path : `/${args.path}`;
|
|
83
|
+
const url = `${PENDLE_CORE_API_BASE}${path}${queryString(args.query ?? {})}`;
|
|
84
|
+
const headers = { Accept: "application/json" };
|
|
85
|
+
if (args.apiKey?.trim()) headers.Authorization = `Bearer ${args.apiKey.trim()}`;
|
|
86
|
+
if (args.body !== void 0) headers["Content-Type"] = "application/json";
|
|
87
|
+
let lastError;
|
|
88
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
89
|
+
const ac = new AbortController();
|
|
90
|
+
const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(url, {
|
|
93
|
+
method: args.method ?? "GET",
|
|
94
|
+
headers,
|
|
95
|
+
body: args.body === void 0 ? void 0 : JSON.stringify(args.body),
|
|
96
|
+
signal: ac.signal
|
|
97
|
+
});
|
|
98
|
+
if (res.status === 429) {
|
|
99
|
+
const wait = Math.min(8e3, 500 * 2 ** attempt);
|
|
100
|
+
await sleep(wait);
|
|
101
|
+
lastError = new PendleApiError(`Pendle API rate limited (429) on ${path}`, 429, path);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const text = await res.text();
|
|
105
|
+
if (!res.ok) {
|
|
106
|
+
throw new PendleApiError(
|
|
107
|
+
`Pendle API ${res.status} ${path}: ${text.slice(0, 400)}`,
|
|
108
|
+
res.status,
|
|
109
|
+
path
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (!text.trim()) return {};
|
|
113
|
+
return JSON.parse(text);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
lastError = err;
|
|
116
|
+
if (err instanceof PendleApiError && err.status !== 429) throw err;
|
|
117
|
+
if (attempt < 3 && (err instanceof PendleApiError || err?.name === "AbortError")) {
|
|
118
|
+
await sleep(Math.min(8e3, 400 * 2 ** attempt));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
throw err;
|
|
122
|
+
} finally {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/protocols/evm/pendle/discover.ts
|
|
130
|
+
var iconCacheByChain = /* @__PURE__ */ new Map();
|
|
131
|
+
var marketsCacheByChain = /* @__PURE__ */ new Map();
|
|
132
|
+
var chainIdsCache = null;
|
|
133
|
+
function asRecord(v) {
|
|
134
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
135
|
+
}
|
|
136
|
+
function asNumber(v) {
|
|
137
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
138
|
+
if (typeof v === "string" && v.trim()) {
|
|
139
|
+
const n = Number.parseFloat(v);
|
|
140
|
+
return Number.isFinite(n) ? n : null;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
function asString(v) {
|
|
145
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function asExpiry(v) {
|
|
149
|
+
const s = asString(v);
|
|
150
|
+
if (s) return s;
|
|
151
|
+
const n = asNumber(v);
|
|
152
|
+
if (n == null) return null;
|
|
153
|
+
const ms = n < 1e12 ? n * 1e3 : n;
|
|
154
|
+
return new Date(ms).toISOString();
|
|
155
|
+
}
|
|
156
|
+
function pendleMarketExpiryMs(expiry) {
|
|
157
|
+
const raw = (expiry ?? "").trim();
|
|
158
|
+
if (!raw) return null;
|
|
159
|
+
if (/^\d+$/.test(raw)) {
|
|
160
|
+
const n = Number(raw);
|
|
161
|
+
return n < 1e12 ? n * 1e3 : n;
|
|
162
|
+
}
|
|
163
|
+
const t = Date.parse(raw);
|
|
164
|
+
return Number.isFinite(t) ? t : null;
|
|
165
|
+
}
|
|
166
|
+
function isPendleMarketExpired(row, nowMs = Date.now()) {
|
|
167
|
+
const t = pendleMarketExpiryMs(row.expiry);
|
|
168
|
+
if (t == null) return false;
|
|
169
|
+
return t <= nowMs;
|
|
170
|
+
}
|
|
171
|
+
function pendleActionNeedsLiveMarket(kind) {
|
|
172
|
+
return kind === "swap" || kind === "mintPy" || kind === "addLiquidity";
|
|
173
|
+
}
|
|
174
|
+
function marketMatchesPin(row, address) {
|
|
175
|
+
const a = address.trim().toLowerCase();
|
|
176
|
+
if (!a) return false;
|
|
177
|
+
return [row.address, row.pt, row.yt, row.sy].some((x) => x.toLowerCase() === a);
|
|
178
|
+
}
|
|
179
|
+
function selectPendleListingMarkets(rows, args = {}) {
|
|
180
|
+
const nowMs = args.nowMs ?? Date.now();
|
|
181
|
+
const cap = args.cap ?? PENDLE_LISTING_CAP;
|
|
182
|
+
const pins = (args.includeAddresses ?? []).map((a) => a.trim().toLowerCase()).filter(Boolean);
|
|
183
|
+
const q = (args.query ?? "").trim();
|
|
184
|
+
let out = rows.filter((r) => {
|
|
185
|
+
const pinned = pins.some((p) => marketMatchesPin(r, p));
|
|
186
|
+
if (!args.includeExpired && !pinned && isPendleMarketExpired(r, nowMs)) return false;
|
|
187
|
+
if (args.pointsOnly && r.points.length === 0) return false;
|
|
188
|
+
if (q && !marketMatchesQuery(r, q)) return false;
|
|
189
|
+
return true;
|
|
190
|
+
});
|
|
191
|
+
out.sort(sortPendleMarketsByTvl);
|
|
192
|
+
return out.slice(0, cap);
|
|
193
|
+
}
|
|
194
|
+
function addressFromAssetIdOrHex(raw, fallbackChainId) {
|
|
195
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
196
|
+
const parsed = parsePendleAssetId(raw);
|
|
197
|
+
if (parsed) {
|
|
198
|
+
try {
|
|
199
|
+
return getAddress(parsed.address);
|
|
200
|
+
} catch {
|
|
201
|
+
return parsed.address.toLowerCase();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (isAddress(raw)) return getAddress(raw);
|
|
205
|
+
if (raw.startsWith(`${fallbackChainId}-`)) {
|
|
206
|
+
const rest = raw.slice(String(fallbackChainId).length + 1);
|
|
207
|
+
if (isAddress(rest)) return getAddress(rest);
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function parsePoints(raw) {
|
|
212
|
+
if (!Array.isArray(raw)) return [];
|
|
213
|
+
const out = [];
|
|
214
|
+
for (const item of raw) {
|
|
215
|
+
const o = asRecord(item);
|
|
216
|
+
if (!o) continue;
|
|
217
|
+
const name = asString(o.name) ?? asString(o.key) ?? asString(o.pointName);
|
|
218
|
+
if (!name) continue;
|
|
219
|
+
out.push({
|
|
220
|
+
name,
|
|
221
|
+
type: asString(o.type),
|
|
222
|
+
pendleAsset: asString(o.pendleAsset),
|
|
223
|
+
value: asNumber(o.value)
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
function marketTvlUsd(row) {
|
|
229
|
+
return row.tvlUsd ?? row.liquidityUsd ?? 0;
|
|
230
|
+
}
|
|
231
|
+
function sortPendleMarketsByTvl(a, b) {
|
|
232
|
+
const d = marketTvlUsd(b) - marketTvlUsd(a);
|
|
233
|
+
if (d !== 0) return d;
|
|
234
|
+
return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
|
|
235
|
+
}
|
|
236
|
+
function parsePendleMarket(raw) {
|
|
237
|
+
const o = asRecord(raw);
|
|
238
|
+
if (!o) return null;
|
|
239
|
+
const chainId = asNumber(o.chainId);
|
|
240
|
+
const address = addressFromAssetIdOrHex(o.address, chainId ?? 0);
|
|
241
|
+
if (chainId == null || !address) return null;
|
|
242
|
+
const details = asRecord(o.details);
|
|
243
|
+
const tvl = asNumber(o.tvl) ?? asNumber(details?.tvl) ?? asNumber(details?.totalTvl) ?? asNumber(asRecord(details?.liquidity)?.usd);
|
|
244
|
+
const liquidity = asNumber(o.liquidity) ?? asNumber(details?.liquidity) ?? asNumber(asRecord(details?.liquidity)?.usd);
|
|
245
|
+
const impliedApy = asNumber(o.impliedApy) ?? asNumber(details?.impliedApy) ?? asNumber(asRecord(o.pt)?.impliedApy);
|
|
246
|
+
const pt = addressFromAssetIdOrHex(o.pt ?? asRecord(o.pt)?.address ?? asRecord(o.pt)?.id, chainId);
|
|
247
|
+
const yt = addressFromAssetIdOrHex(o.yt ?? asRecord(o.yt)?.address ?? asRecord(o.yt)?.id, chainId);
|
|
248
|
+
const sy = addressFromAssetIdOrHex(o.sy ?? asRecord(o.sy)?.address ?? asRecord(o.sy)?.id, chainId);
|
|
249
|
+
const underlying = addressFromAssetIdOrHex(
|
|
250
|
+
o.underlyingAsset ?? o.underlying ?? asRecord(o.underlyingAsset)?.address,
|
|
251
|
+
chainId
|
|
252
|
+
);
|
|
253
|
+
if (!pt || !yt || !sy || !underlying) return null;
|
|
254
|
+
const expired = asExpiry(o.expiry) ?? asExpiry(details?.expiry);
|
|
255
|
+
return {
|
|
256
|
+
chainId,
|
|
257
|
+
name: asString(o.name) ?? asString(o.proSymbol) ?? `${address.slice(0, 8)}\u2026`,
|
|
258
|
+
address,
|
|
259
|
+
expiry: expired,
|
|
260
|
+
pt,
|
|
261
|
+
yt,
|
|
262
|
+
sy,
|
|
263
|
+
underlying,
|
|
264
|
+
underlyingSymbol: asString(asRecord(o.underlyingAsset)?.symbol) ?? asString(o.underlyingSymbol),
|
|
265
|
+
ptSymbol: asString(asRecord(o.pt)?.symbol) ?? asString(o.ptSymbol),
|
|
266
|
+
ytSymbol: asString(asRecord(o.yt)?.symbol) ?? asString(o.ytSymbol),
|
|
267
|
+
impliedApy,
|
|
268
|
+
tvlUsd: tvl,
|
|
269
|
+
liquidityUsd: liquidity,
|
|
270
|
+
points: parsePoints(o.points ?? details?.points)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function parsePendleAsset(raw) {
|
|
274
|
+
const o = asRecord(raw);
|
|
275
|
+
if (!o) return null;
|
|
276
|
+
const chainId = asNumber(o.chainId);
|
|
277
|
+
const address = addressFromAssetIdOrHex(o.address ?? o.id, chainId ?? 0);
|
|
278
|
+
if (chainId == null || !address) return null;
|
|
279
|
+
const decimals = asNumber(o.decimals);
|
|
280
|
+
return {
|
|
281
|
+
id: asString(o.id) ?? pendleAssetId(chainId, address),
|
|
282
|
+
chainId,
|
|
283
|
+
address,
|
|
284
|
+
symbol: asString(o.symbol) ?? "\u2014",
|
|
285
|
+
name: asString(o.name) ?? asString(o.symbol) ?? "\u2014",
|
|
286
|
+
decimals: decimals != null && decimals >= 0 ? Math.floor(decimals) : 18,
|
|
287
|
+
expiry: asString(o.expiry)
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
async function pendleFetchSupportedChainIds() {
|
|
291
|
+
const now = Date.now();
|
|
292
|
+
if (chainIdsCache && now - chainIdsCache.at < PENDLE_CHAIN_CACHE_MS) return chainIdsCache.chainIds;
|
|
293
|
+
const json = await pendleCoreFetch({ path: "/v1/chains" });
|
|
294
|
+
const ids = Array.isArray(json.chainIds) ? json.chainIds.map((n) => Number(n)).filter((n) => Number.isFinite(n) && n > 0) : [];
|
|
295
|
+
chainIdsCache = { at: now, chainIds: ids };
|
|
296
|
+
return ids;
|
|
297
|
+
}
|
|
298
|
+
async function isPendleChainSupported(chainId) {
|
|
299
|
+
try {
|
|
300
|
+
const ids = await pendleFetchSupportedChainIds();
|
|
301
|
+
if (ids.length === 0) return true;
|
|
302
|
+
return ids.includes(chainId);
|
|
303
|
+
} catch {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function pendleFetchMarketsPage(args) {
|
|
308
|
+
const limit = Math.min(PENDLE_LISTING_CAP, Math.max(1, args.limit ?? PENDLE_LISTING_CAP));
|
|
309
|
+
const json = await pendleCoreFetch({
|
|
310
|
+
path: "/v2/markets/all",
|
|
311
|
+
query: {
|
|
312
|
+
skip: args.skip ?? 0,
|
|
313
|
+
limit,
|
|
314
|
+
...args.chainId != null ? { chainId: args.chainId } : {}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
const raw = Array.isArray(json.markets) ? json.markets : Array.isArray(json.results) ? json.results : [];
|
|
318
|
+
const rows = [];
|
|
319
|
+
for (const item of raw) {
|
|
320
|
+
const row = parsePendleMarket(item);
|
|
321
|
+
if (row) rows.push(row);
|
|
322
|
+
}
|
|
323
|
+
return rows;
|
|
324
|
+
}
|
|
325
|
+
async function pendleFetchMarketsForChain(chainId) {
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
const cached = marketsCacheByChain.get(chainId);
|
|
328
|
+
if (cached && now - cached.at < PENDLE_ICON_CACHE_MS) return cached.rows;
|
|
329
|
+
const rows = [];
|
|
330
|
+
for (let page = 0; page < PENDLE_LISTING_MAX_PAGES; page++) {
|
|
331
|
+
const batch = await pendleFetchMarketsPage({
|
|
332
|
+
skip: page * PENDLE_LISTING_CAP,
|
|
333
|
+
limit: PENDLE_LISTING_CAP,
|
|
334
|
+
chainId
|
|
335
|
+
});
|
|
336
|
+
const onChain = batch.filter((r) => r.chainId === chainId);
|
|
337
|
+
rows.push(...onChain);
|
|
338
|
+
if (batch.length < PENDLE_LISTING_CAP || onChain.length === 0) break;
|
|
339
|
+
}
|
|
340
|
+
marketsCacheByChain.set(chainId, { at: now, rows });
|
|
341
|
+
return rows;
|
|
342
|
+
}
|
|
343
|
+
function marketMatchesQuery(row, query) {
|
|
344
|
+
const q = query.trim();
|
|
345
|
+
if (!q) return true;
|
|
346
|
+
if (matchesAssetFilter({ address: row.address, symbol: row.name, filter: q }) || matchesAssetFilter({ address: row.pt, symbol: row.ptSymbol ?? void 0, filter: q }) || matchesAssetFilter({ address: row.yt, symbol: row.ytSymbol ?? void 0, filter: q }) || matchesAssetFilter({ address: row.sy, filter: q }) || matchesAssetFilter({ address: row.underlying, symbol: row.underlyingSymbol ?? void 0, filter: q })) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
const hay = `${row.name} ${row.ptSymbol ?? ""} ${row.ytSymbol ?? ""} ${row.underlyingSymbol ?? ""}`.toLowerCase();
|
|
350
|
+
return hay.includes(q.toLowerCase());
|
|
351
|
+
}
|
|
352
|
+
async function pendleFetchMarketsSummary(args) {
|
|
353
|
+
if (!await isPendleChainSupported(args.chainId)) {
|
|
354
|
+
return {
|
|
355
|
+
chainId: args.chainId,
|
|
356
|
+
markets: [],
|
|
357
|
+
notes: `Pendle Core API does not list chain ${args.chainId}.`
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const scanned = await pendleFetchMarketsForChain(args.chainId);
|
|
361
|
+
const rows = selectPendleListingMarkets(scanned, {
|
|
362
|
+
query: args.query,
|
|
363
|
+
pointsOnly: args.pointsOnly,
|
|
364
|
+
includeAddresses: args.includeAddresses
|
|
365
|
+
});
|
|
366
|
+
const expiredOmitted = scanned.filter((r) => isPendleMarketExpired(r)).length;
|
|
367
|
+
return {
|
|
368
|
+
chainId: args.chainId,
|
|
369
|
+
markets: rows,
|
|
370
|
+
notes: `Top ${rows.length} active Pendle markets on chain ${args.chainId} by TVL/liquidity (scanned ${scanned.length}, omitted ${expiredOmitted} matured). Use ctm_pendle_search_assets for long-tail or expired markets (PT redeem / remove LP). Do not web-search Pendle APYs.`
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function pendleFetchAssetsByIds(ids) {
|
|
374
|
+
if (ids.length === 0) return [];
|
|
375
|
+
const json = await pendleCoreFetch({
|
|
376
|
+
path: "/v1/assets/all",
|
|
377
|
+
query: { ids: ids.slice(0, 20).join(",") }
|
|
378
|
+
});
|
|
379
|
+
const raw = Array.isArray(json.assets) ? json.assets : [];
|
|
380
|
+
const out = [];
|
|
381
|
+
for (const item of raw) {
|
|
382
|
+
const row = parsePendleAsset(item);
|
|
383
|
+
if (row) out.push(row);
|
|
384
|
+
}
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
387
|
+
async function pendleFetchPricesSummary(args) {
|
|
388
|
+
let ids = (args.ids ?? []).map((s) => s.trim()).filter(Boolean);
|
|
389
|
+
if (ids.length === 0) {
|
|
390
|
+
const listed = await pendleFetchMarketsSummary({ chainId: args.chainId, query: args.query });
|
|
391
|
+
ids = listed.markets.flatMap((m) => [
|
|
392
|
+
pendleAssetId(m.chainId, m.pt),
|
|
393
|
+
pendleAssetId(m.chainId, m.yt),
|
|
394
|
+
pendleAssetId(m.chainId, m.sy),
|
|
395
|
+
pendleAssetId(m.chainId, m.address),
|
|
396
|
+
pendleAssetId(m.chainId, m.underlying)
|
|
397
|
+
]);
|
|
398
|
+
}
|
|
399
|
+
if (ids.length === 0) {
|
|
400
|
+
return { chainId: args.chainId, prices: [], notes: "No asset ids to price." };
|
|
401
|
+
}
|
|
402
|
+
const json = await pendleCoreFetch({
|
|
403
|
+
path: "/v1/prices/assets",
|
|
404
|
+
query: { ids: ids.slice(0, 80).join(",") }
|
|
405
|
+
});
|
|
406
|
+
const prices = [];
|
|
407
|
+
const map = json.priceMap ?? {};
|
|
408
|
+
for (const id of ids.slice(0, 80)) {
|
|
409
|
+
const entry = map[id] ?? map[id.toLowerCase()];
|
|
410
|
+
if (typeof entry === "number") {
|
|
411
|
+
prices.push({ id, usd: Number.isFinite(entry) ? entry : null });
|
|
412
|
+
} else if (entry && typeof entry === "object") {
|
|
413
|
+
prices.push({ id, usd: asNumber(entry.usd ?? entry.price) });
|
|
414
|
+
} else {
|
|
415
|
+
prices.push({ id, usd: null });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
chainId: args.chainId,
|
|
420
|
+
prices,
|
|
421
|
+
notes: "USD prices from GET /v1/prices/assets for the current listing or supplied ids only."
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function collectPendleMarketAddresses(row) {
|
|
425
|
+
return [row.address, row.pt, row.yt, row.sy, row.underlying].map((a) => a.toLowerCase());
|
|
426
|
+
}
|
|
427
|
+
async function pendleIconAddressesForChain(chainId) {
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
const cached = iconCacheByChain.get(chainId);
|
|
430
|
+
if (cached && now - cached.at < PENDLE_ICON_CACHE_MS) return [...cached.addresses];
|
|
431
|
+
const addrs = /* @__PURE__ */ new Set();
|
|
432
|
+
const rows = await pendleFetchMarketsForChain(chainId);
|
|
433
|
+
for (const row of rows) {
|
|
434
|
+
for (const a of collectPendleMarketAddresses(row)) addrs.add(a);
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
const pendleHits = await pendleSearchAssets({ chainId, query: "PENDLE" });
|
|
438
|
+
for (const row of pendleHits.results) {
|
|
439
|
+
for (const a of collectPendleMarketAddresses(row)) addrs.add(a);
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
iconCacheByChain.set(chainId, { at: now, addresses: addrs });
|
|
444
|
+
return [...addrs];
|
|
445
|
+
}
|
|
446
|
+
async function pendleSearchAssets(args) {
|
|
447
|
+
const q = args.query.trim();
|
|
448
|
+
if (!q) throw new Error("query is required.");
|
|
449
|
+
if (!await isPendleChainSupported(args.chainId)) {
|
|
450
|
+
return { chainId: args.chainId, results: [], notes: `Pendle is not listed on chain ${args.chainId}.` };
|
|
451
|
+
}
|
|
452
|
+
const parsedId = parsePendleAssetId(q);
|
|
453
|
+
if (parsedId || isAddress(q)) {
|
|
454
|
+
const id = parsedId ? pendleAssetId(parsedId.chainId, parsedId.address) : pendleAssetId(args.chainId, q);
|
|
455
|
+
try {
|
|
456
|
+
const json = await pendleCoreFetch({
|
|
457
|
+
path: "/v1/markets/all",
|
|
458
|
+
query: { ids: id, chainId: args.chainId }
|
|
459
|
+
});
|
|
460
|
+
const raw = Array.isArray(json.markets) ? json.markets : [];
|
|
461
|
+
const exact = [];
|
|
462
|
+
for (const item of raw) {
|
|
463
|
+
const row = parsePendleMarket(item);
|
|
464
|
+
if (row && row.chainId === args.chainId) exact.push(row);
|
|
465
|
+
}
|
|
466
|
+
if (exact.length) {
|
|
467
|
+
exact.sort(sortPendleMarketsByTvl);
|
|
468
|
+
const live = exact.filter((r) => !isPendleMarketExpired(r));
|
|
469
|
+
if (live.length) {
|
|
470
|
+
return {
|
|
471
|
+
chainId: args.chainId,
|
|
472
|
+
results: live,
|
|
473
|
+
notes: "Exact market/asset id lookup via GET /v1/markets/all?ids= (active only)."
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
chainId: args.chainId,
|
|
478
|
+
results: exact,
|
|
479
|
+
notes: "Exact market/asset id lookup \u2014 matured (redeem PT or remove LP only)."
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const scanned = await pendleFetchMarketsForChain(args.chainId);
|
|
486
|
+
const activeHits = selectPendleListingMarkets(scanned, { query: q });
|
|
487
|
+
if (activeHits.length > 0) {
|
|
488
|
+
return {
|
|
489
|
+
chainId: args.chainId,
|
|
490
|
+
results: activeHits,
|
|
491
|
+
notes: `Matched ${activeHits.length} active market(s) for ${q} on chain ${args.chainId}.`
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
const expiredHits = selectPendleListingMarkets(scanned, { query: q, includeExpired: true }).filter(
|
|
495
|
+
(r) => isPendleMarketExpired(r)
|
|
496
|
+
);
|
|
497
|
+
if (expiredHits.length > 0) {
|
|
498
|
+
return {
|
|
499
|
+
chainId: args.chainId,
|
|
500
|
+
results: expiredHits,
|
|
501
|
+
notes: `No live market matched ${q}. Showing ${expiredHits.length} matured market(s) (redeem PT or remove LP only \u2014 mint / YT swap / add LP will fail).`
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
chainId: args.chainId,
|
|
506
|
+
results: [],
|
|
507
|
+
notes: `No Pendle market on chain ${args.chainId} matched ${q}.`
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
var erc20DecimalsAbi = parseAbi(["function decimals() view returns (uint8)"]);
|
|
511
|
+
function asRecord2(v) {
|
|
512
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
513
|
+
}
|
|
514
|
+
function asString2(v) {
|
|
515
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
516
|
+
}
|
|
517
|
+
function asNumber2(v) {
|
|
518
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
519
|
+
if (typeof v === "string" && v.trim()) {
|
|
520
|
+
const n = Number.parseFloat(v);
|
|
521
|
+
return Number.isFinite(n) ? n : null;
|
|
522
|
+
}
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
function classifyPendleConvertAction(args) {
|
|
526
|
+
const norm = (s) => s.trim().toLowerCase();
|
|
527
|
+
const ins = args.tokensIn.map(norm);
|
|
528
|
+
const outs = args.tokensOut.map(norm);
|
|
529
|
+
const market = args.roles?.market?.trim().toLowerCase();
|
|
530
|
+
const pt = args.roles?.pt?.trim().toLowerCase();
|
|
531
|
+
const yt = args.roles?.yt?.trim().toLowerCase();
|
|
532
|
+
const sy = args.roles?.sy?.trim().toLowerCase();
|
|
533
|
+
if (market && ins.includes(market)) return "remove_liquidity";
|
|
534
|
+
if (market && outs.includes(market)) return "add_liquidity";
|
|
535
|
+
if (pt && yt && outs.includes(pt) && outs.includes(yt)) return "mint_py";
|
|
536
|
+
if (pt && yt && ins.includes(pt) && ins.includes(yt)) return "redeem_py";
|
|
537
|
+
if (sy && outs.includes(sy) && !ins.includes(sy)) return "mint_sy";
|
|
538
|
+
if (sy && ins.includes(sy) && !outs.includes(sy)) return "redeem_sy";
|
|
539
|
+
if (ins.length === 1 && outs.length === 1) return "swap";
|
|
540
|
+
return "convert";
|
|
541
|
+
}
|
|
542
|
+
function slippagePercentToConvert(slippagePercent) {
|
|
543
|
+
if (!Number.isFinite(slippagePercent) || slippagePercent <= 0 || slippagePercent >= 100) {
|
|
544
|
+
throw new Error("Slippage must be between 0 and 100 (exclusive).");
|
|
545
|
+
}
|
|
546
|
+
return slippagePercent / 100;
|
|
547
|
+
}
|
|
548
|
+
async function resolvePendleTokenDecimals(args) {
|
|
549
|
+
if (args.decimals != null && Number.isInteger(args.decimals) && args.decimals >= 0 && args.decimals <= 18) {
|
|
550
|
+
return args.decimals;
|
|
551
|
+
}
|
|
552
|
+
if (isPendleNativeToken(args.token)) return 18;
|
|
553
|
+
const id = pendleAssetId(args.chainId, normalizePendleTokenAddress(args.token));
|
|
554
|
+
try {
|
|
555
|
+
const assets = await pendleFetchAssetsByIds([id]);
|
|
556
|
+
if (assets[0]) return assets[0].decimals;
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
if (!args.rpcUrl?.trim()) return 18;
|
|
560
|
+
const addr = getAddress(args.token);
|
|
561
|
+
const ch = defineChain({
|
|
562
|
+
id: args.chainId,
|
|
563
|
+
name: "PendleDecimals",
|
|
564
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
565
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
566
|
+
});
|
|
567
|
+
const client = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
|
|
568
|
+
const d = await client.readContract({ address: addr, abi: erc20DecimalsAbi, functionName: "decimals" });
|
|
569
|
+
return Number(d);
|
|
570
|
+
}
|
|
571
|
+
function parsePendleConvertResponse(raw) {
|
|
572
|
+
const root = asRecord2(raw);
|
|
573
|
+
if (!root) throw new Error("Pendle convert returned an empty body.");
|
|
574
|
+
const routes = Array.isArray(root.routes) ? root.routes : [];
|
|
575
|
+
const route = asRecord2(routes[0]) ?? root;
|
|
576
|
+
const txRaw = asRecord2(route.tx) ?? asRecord2(root.tx);
|
|
577
|
+
if (!txRaw) throw new Error("Pendle convert response missing routes[0].tx.");
|
|
578
|
+
const to = asString2(txRaw.to);
|
|
579
|
+
const data = asString2(txRaw.data);
|
|
580
|
+
if (!to || !data) throw new Error("Pendle convert tx is missing to/data. Use the API tx \u2014 do not hardcode a router.");
|
|
581
|
+
const dataHex = data.startsWith("0x") ? data : `0x${data}`;
|
|
582
|
+
const value = asString2(txRaw.value) ?? "0";
|
|
583
|
+
const dataObj = asRecord2(route.data) ?? asRecord2(root.data);
|
|
584
|
+
const implied = asRecord2(dataObj?.impliedApy);
|
|
585
|
+
const parseAmounts = (v) => {
|
|
586
|
+
if (!Array.isArray(v)) return [];
|
|
587
|
+
const out = [];
|
|
588
|
+
for (const item of v) {
|
|
589
|
+
const o = asRecord2(item);
|
|
590
|
+
const token = asString2(o?.token);
|
|
591
|
+
const amount = asString2(o?.amount);
|
|
592
|
+
if (token && amount) out.push({ token, amount });
|
|
593
|
+
}
|
|
594
|
+
return out;
|
|
595
|
+
};
|
|
596
|
+
return {
|
|
597
|
+
action: asString2(root.action) ?? asString2(route.action) ?? "convert",
|
|
598
|
+
inputs: parseAmounts(root.inputs ?? route.inputs),
|
|
599
|
+
outputs: parseAmounts(route.outputs ?? root.outputs),
|
|
600
|
+
requiredApprovals: parseAmounts(root.requiredApprovals ?? route.requiredApprovals),
|
|
601
|
+
priceImpact: asNumber2(dataObj?.priceImpact),
|
|
602
|
+
impliedApyBefore: asNumber2(implied?.before),
|
|
603
|
+
impliedApyAfter: asNumber2(implied?.after),
|
|
604
|
+
effectiveApy: asNumber2(dataObj?.effectiveApy),
|
|
605
|
+
tx: {
|
|
606
|
+
to: getAddress(to),
|
|
607
|
+
data: dataHex,
|
|
608
|
+
value,
|
|
609
|
+
from: asString2(txRaw.from) ?? void 0
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
async function pendleConvert(args) {
|
|
614
|
+
if (args.tokensIn.length === 0) throw new Error("tokensIn is required.");
|
|
615
|
+
if (args.tokensOut.length === 0) throw new Error("tokensOut is required.");
|
|
616
|
+
const receiver = getAddress(args.receiver);
|
|
617
|
+
const body = {
|
|
618
|
+
receiver,
|
|
619
|
+
slippage: slippagePercentToConvert(args.slippagePercent),
|
|
620
|
+
enableAggregator: args.enableAggregator === true,
|
|
621
|
+
inputs: args.tokensIn.map((t) => ({
|
|
622
|
+
token: normalizePendleTokenAddress(t.token),
|
|
623
|
+
amount: t.amountWei
|
|
624
|
+
})),
|
|
625
|
+
outputs: args.tokensOut.map((t) => normalizePendleTokenAddress(t)),
|
|
626
|
+
additionalData: args.additionalData ?? "impliedApy,effectiveApy"
|
|
627
|
+
};
|
|
628
|
+
const json = await pendleCoreFetch({
|
|
629
|
+
path: `/v3/sdk/${args.chainId}/convert`,
|
|
630
|
+
method: "POST",
|
|
631
|
+
body
|
|
632
|
+
});
|
|
633
|
+
return parsePendleConvertResponse(json);
|
|
634
|
+
}
|
|
635
|
+
async function pendleQuoteConvert(args) {
|
|
636
|
+
const receiver = args.receiver?.trim() || PENDLE_NATIVE_PLACEHOLDER;
|
|
637
|
+
const tokensIn = [];
|
|
638
|
+
for (const t of args.tokensIn) {
|
|
639
|
+
const decimals = await resolvePendleTokenDecimals({
|
|
640
|
+
chainId: args.chainId,
|
|
641
|
+
rpcUrl: args.rpcUrl,
|
|
642
|
+
token: t.token,
|
|
643
|
+
decimals: t.decimals
|
|
644
|
+
});
|
|
645
|
+
tokensIn.push({
|
|
646
|
+
token: t.token,
|
|
647
|
+
amountWei: parseUnits(t.amountHuman.trim(), decimals).toString()
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
return pendleConvert({
|
|
651
|
+
chainId: args.chainId,
|
|
652
|
+
receiver,
|
|
653
|
+
slippagePercent: args.slippagePercent,
|
|
654
|
+
tokensIn,
|
|
655
|
+
tokensOut: args.tokensOut,
|
|
656
|
+
enableAggregator: args.enableAggregator
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
async function pendleQuoteSwap(args) {
|
|
660
|
+
return pendleQuoteConvert({
|
|
661
|
+
chainId: args.chainId,
|
|
662
|
+
rpcUrl: args.rpcUrl,
|
|
663
|
+
receiver: args.receiver,
|
|
664
|
+
slippagePercent: args.slippagePercent,
|
|
665
|
+
tokensIn: [{ token: args.tokenIn, amountHuman: args.amountHuman, decimals: args.tokenInDecimals }],
|
|
666
|
+
tokensOut: [args.tokenOut],
|
|
667
|
+
enableAggregator: args.enableAggregator
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
async function pendleQuoteMintPy(args) {
|
|
671
|
+
return pendleQuoteConvert({
|
|
672
|
+
chainId: args.chainId,
|
|
673
|
+
rpcUrl: args.rpcUrl,
|
|
674
|
+
receiver: args.receiver,
|
|
675
|
+
slippagePercent: args.slippagePercent,
|
|
676
|
+
tokensIn: [{ token: args.tokenIn, amountHuman: args.amountHuman, decimals: args.tokenInDecimals }],
|
|
677
|
+
tokensOut: [args.pt, args.yt]
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
async function pendleQuoteRedeemPy(args) {
|
|
681
|
+
const tokensIn = [{ token: args.pt, amountHuman: args.amountPtHuman }];
|
|
682
|
+
const ytAmt = (args.amountYtHuman ?? args.amountPtHuman).trim();
|
|
683
|
+
if (ytAmt && ytAmt !== "0") tokensIn.push({ token: args.yt, amountHuman: ytAmt });
|
|
684
|
+
return pendleQuoteConvert({
|
|
685
|
+
chainId: args.chainId,
|
|
686
|
+
rpcUrl: args.rpcUrl,
|
|
687
|
+
receiver: args.receiver,
|
|
688
|
+
slippagePercent: args.slippagePercent,
|
|
689
|
+
tokensIn,
|
|
690
|
+
tokensOut: [args.tokenOut]
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
async function pendleQuoteMintSy(args) {
|
|
694
|
+
return pendleQuoteConvert({
|
|
695
|
+
chainId: args.chainId,
|
|
696
|
+
rpcUrl: args.rpcUrl,
|
|
697
|
+
receiver: args.receiver,
|
|
698
|
+
slippagePercent: args.slippagePercent,
|
|
699
|
+
tokensIn: [{ token: args.tokenIn, amountHuman: args.amountHuman }],
|
|
700
|
+
tokensOut: [args.sy]
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
async function pendleQuoteRedeemSy(args) {
|
|
704
|
+
return pendleQuoteConvert({
|
|
705
|
+
chainId: args.chainId,
|
|
706
|
+
rpcUrl: args.rpcUrl,
|
|
707
|
+
receiver: args.receiver,
|
|
708
|
+
slippagePercent: args.slippagePercent,
|
|
709
|
+
tokensIn: [{ token: args.sy, amountHuman: args.amountHuman }],
|
|
710
|
+
tokensOut: [args.tokenOut]
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
async function pendleQuoteAddLiquidity(args) {
|
|
714
|
+
const tokensOut = [args.market];
|
|
715
|
+
if (args.keepYt) {
|
|
716
|
+
if (!args.yt?.trim()) throw new Error("yt is required for zero-price-impact (keep YT) add liquidity.");
|
|
717
|
+
tokensOut.push(args.yt);
|
|
718
|
+
}
|
|
719
|
+
return pendleQuoteConvert({
|
|
720
|
+
chainId: args.chainId,
|
|
721
|
+
rpcUrl: args.rpcUrl,
|
|
722
|
+
receiver: args.receiver,
|
|
723
|
+
slippagePercent: args.slippagePercent,
|
|
724
|
+
tokensIn: args.tokensIn,
|
|
725
|
+
tokensOut
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
async function pendleQuoteRemoveLiquidity(args) {
|
|
729
|
+
return pendleQuoteConvert({
|
|
730
|
+
chainId: args.chainId,
|
|
731
|
+
rpcUrl: args.rpcUrl,
|
|
732
|
+
receiver: args.receiver,
|
|
733
|
+
slippagePercent: args.slippagePercent,
|
|
734
|
+
tokensIn: [{ token: args.market, amountHuman: args.lpAmountHuman }],
|
|
735
|
+
tokensOut: args.tokensOut,
|
|
736
|
+
enableAggregator: args.enableAggregator
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/protocols/evm/pendle/purpose.ts
|
|
741
|
+
function buildPendlePurposePrefill(args) {
|
|
742
|
+
const act = (args.action || "convert").trim();
|
|
743
|
+
const detail = (args.detail || "").trim();
|
|
744
|
+
const slip = args.slippagePercent != null && Number.isFinite(args.slippagePercent) ? ` (max slippage ${args.slippagePercent}%)` : "";
|
|
745
|
+
return `Pendle: ${act} ${detail}${slip} on chain ${args.chainId}`;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/core/purpose.ts
|
|
749
|
+
function mergePurposeText(purposeText, purposeSuffix) {
|
|
750
|
+
const t = (purposeText ?? "").trim();
|
|
751
|
+
const suffix = (purposeSuffix ?? "").trim();
|
|
752
|
+
if (!suffix) return t;
|
|
753
|
+
return t ? `${t}
|
|
754
|
+
|
|
755
|
+
${suffix}` : suffix;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/core/envelope.ts
|
|
759
|
+
function finalizeMultisign(input) {
|
|
760
|
+
const { keyGen, destinationChainID, legs } = input;
|
|
761
|
+
if (legs.length === 0) {
|
|
762
|
+
throw new Error("finalizeMultisign requires at least one leg");
|
|
763
|
+
}
|
|
764
|
+
const ph = (keyGen.pubkeyhex ?? "").trim();
|
|
765
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
766
|
+
const keyList = keyGen.keylist ?? [];
|
|
767
|
+
const clientId = getClientIdFromKeyGenResult(keyGen);
|
|
768
|
+
const first = legs[0];
|
|
769
|
+
const messageHashes = legs.map((l) => l.msgHash);
|
|
770
|
+
const messageRawBatch = legs.map((l) => l.msgRaw);
|
|
771
|
+
const batchMeta = legs.map((l) => ({
|
|
772
|
+
destinationAddress: l.destinationAddress,
|
|
773
|
+
signatureText: l.signatureText,
|
|
774
|
+
...l.audit
|
|
775
|
+
}));
|
|
776
|
+
const proposalTxParams = legs.map((l) => l.proposalTxParams).filter((p) => p != null && typeof p === "object");
|
|
777
|
+
const extraPayload = {
|
|
778
|
+
batchMeta,
|
|
779
|
+
...input.extraJSON ?? {}
|
|
780
|
+
};
|
|
781
|
+
const extraJSON = JSON.stringify(extraPayload, (_, v) => typeof v === "bigint" ? v.toString() : v);
|
|
782
|
+
const bodyForSign = {
|
|
783
|
+
keyList,
|
|
784
|
+
pubKey: ph,
|
|
785
|
+
msgHash: messageHashes[0],
|
|
786
|
+
msgRaw: first.msgRaw,
|
|
787
|
+
destinationChainID,
|
|
788
|
+
destinationAddress: input.destinationAddress ?? first.destinationAddress,
|
|
789
|
+
extraJSON,
|
|
790
|
+
signatureText: first.signatureText,
|
|
791
|
+
purpose: mergePurposeText(input.purposeText, input.purposeSuffix),
|
|
792
|
+
...first.feeSnapshot
|
|
793
|
+
};
|
|
794
|
+
if (legs.length > 1) {
|
|
795
|
+
bodyForSign.messageHashes = messageHashes;
|
|
796
|
+
bodyForSign.messageRawBatch = messageRawBatch;
|
|
797
|
+
}
|
|
798
|
+
if (proposalTxParams.length > 0) {
|
|
799
|
+
bodyForSign.proposalTxParams = proposalTxParams;
|
|
800
|
+
}
|
|
801
|
+
const valueWei = first.valueWei;
|
|
802
|
+
if (valueWei != null && valueWei > 0n) {
|
|
803
|
+
bodyForSign.value = valueWei.toString();
|
|
804
|
+
}
|
|
805
|
+
if (clientId) bodyForSign.clientId = clientId;
|
|
806
|
+
if (input.expiryDate != null && input.expiryDate > 0) {
|
|
807
|
+
bodyForSign.expiryDate = Math.floor(input.expiryDate);
|
|
808
|
+
}
|
|
809
|
+
return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
|
|
810
|
+
}
|
|
811
|
+
function routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimit) {
|
|
812
|
+
if (chainGasLimit != null && Number.isFinite(chainGasLimit) && chainGasLimit > 0) {
|
|
813
|
+
return gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit);
|
|
814
|
+
}
|
|
815
|
+
return (estimatedGas * 12n + 9n) / 10n;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/chains/evm/buildBatch.ts
|
|
819
|
+
async function buildEvmMultisignBatch(args) {
|
|
820
|
+
const { context, steps } = args;
|
|
821
|
+
const {
|
|
822
|
+
chainId,
|
|
823
|
+
rpcUrl,
|
|
824
|
+
executorAddress,
|
|
825
|
+
chainDetail,
|
|
826
|
+
useCustomGas,
|
|
827
|
+
customGasChainDetails,
|
|
828
|
+
keyGen,
|
|
829
|
+
purposeText
|
|
830
|
+
} = context;
|
|
831
|
+
if (steps.length === 0) throw new Error("buildEvmMultisignBatch requires at least one step");
|
|
832
|
+
const ch = defineChain({
|
|
833
|
+
id: chainId,
|
|
834
|
+
name: "Destination",
|
|
835
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
836
|
+
rpcUrls: { default: { http: [rpcUrl] } }
|
|
837
|
+
});
|
|
838
|
+
const publicClient = createPublicClient({ chain: ch, transport: http(rpcUrl) });
|
|
839
|
+
const feeParams = await fetchChainFeeParams(rpcUrl, chainId);
|
|
840
|
+
const legacy = Boolean(chainDetail?.legacy) || !feeParams.isEip1559;
|
|
841
|
+
const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
|
|
842
|
+
const gasLimitConfig = useCustomGas && chainDetail?.gasLimit != null ? Number(chainDetail.gasLimit) : void 0;
|
|
843
|
+
const chainGasLimitRouter = chainDetail?.gasLimit != null && Number.isFinite(Number(chainDetail.gasLimit)) && Number(chainDetail.gasLimit) > 0 ? Number(chainDetail.gasLimit) : void 0;
|
|
844
|
+
const gasFeeMultiplier = useCustomGas && chainDetail?.gasMultiplier != null ? Number(chainDetail.gasMultiplier) : void 0;
|
|
845
|
+
const executor = getAddress(executorAddress);
|
|
846
|
+
const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
|
|
847
|
+
const legs = [];
|
|
848
|
+
for (let i = 0; i < steps.length; i++) {
|
|
849
|
+
const step = steps[i];
|
|
850
|
+
const currentNonce = baseNonce + i;
|
|
851
|
+
let estimatedGas;
|
|
852
|
+
if (args.estimateGasForStep) {
|
|
853
|
+
estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient, executor });
|
|
854
|
+
} else {
|
|
855
|
+
try {
|
|
856
|
+
estimatedGas = await publicClient.estimateGas({
|
|
857
|
+
to: step.to,
|
|
858
|
+
data: step.data,
|
|
859
|
+
value: step.value,
|
|
860
|
+
account: executor
|
|
861
|
+
});
|
|
862
|
+
} catch {
|
|
863
|
+
estimatedGas = step.fallbackGas ?? 100000n;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
let gasLimitI;
|
|
867
|
+
if (args.resolveGasLimit) {
|
|
868
|
+
gasLimitI = await args.resolveGasLimit({ step, index: i, estimatedGas, publicClient });
|
|
869
|
+
} else if (step.routerSwap) {
|
|
870
|
+
gasLimitI = routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
|
|
871
|
+
} else {
|
|
872
|
+
gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
|
|
873
|
+
}
|
|
874
|
+
let proposalTxParams;
|
|
875
|
+
let feeSnapshot;
|
|
876
|
+
let serialized;
|
|
877
|
+
if (legacy) {
|
|
878
|
+
let gasPriceWei = await publicClient.getGasPrice();
|
|
879
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
880
|
+
gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
881
|
+
}
|
|
882
|
+
if (useCustomGas && chainDetail?.gasPrice != null && chainDetail.gasPrice > 0) {
|
|
883
|
+
const configured = parseGwei(gweiToDecimalString(Number(chainDetail.gasPrice)));
|
|
884
|
+
if (configured > gasPriceWei) gasPriceWei = configured;
|
|
885
|
+
}
|
|
886
|
+
serialized = serializeTransaction({
|
|
887
|
+
type: "legacy",
|
|
888
|
+
to: step.to,
|
|
889
|
+
data: step.data,
|
|
890
|
+
value: step.value,
|
|
891
|
+
gas: gasLimitI,
|
|
892
|
+
gasPrice: gasPriceWei,
|
|
893
|
+
nonce: currentNonce,
|
|
894
|
+
chainId
|
|
895
|
+
});
|
|
896
|
+
proposalTxParams = {
|
|
897
|
+
nonce: currentNonce,
|
|
898
|
+
gasLimit: gasLimitI.toString(),
|
|
899
|
+
txType: "legacy",
|
|
900
|
+
gasPrice: gasPriceWei.toString()
|
|
901
|
+
};
|
|
902
|
+
feeSnapshot = proposalTxParamsToFeeSnapshot(proposalTxParams);
|
|
903
|
+
} else {
|
|
904
|
+
const fetchedBase = feeParams.baseFeeGwei ?? 0;
|
|
905
|
+
const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
|
|
906
|
+
const configuredBase = useCustomGas && chainDetail?.baseFee != null ? Number(chainDetail.baseFee) : 0;
|
|
907
|
+
const configuredPriority = useCustomGas && chainDetail?.priorityFee != null ? Number(chainDetail.priorityFee) : 0;
|
|
908
|
+
const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
|
|
909
|
+
const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
|
|
910
|
+
const baseFeeMultiplierPct = useCustomGas && chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(chainDetail.baseFeeMultiplier)) : 100;
|
|
911
|
+
const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
|
|
912
|
+
const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
|
|
913
|
+
let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
|
|
914
|
+
let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
|
|
915
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
916
|
+
maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
917
|
+
maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
918
|
+
}
|
|
919
|
+
({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
|
|
920
|
+
maxFeePerGas,
|
|
921
|
+
maxPriorityFeePerGas,
|
|
922
|
+
latestBaseFeeWei
|
|
923
|
+
));
|
|
924
|
+
serialized = serializeTransaction({
|
|
925
|
+
type: "eip1559",
|
|
926
|
+
to: step.to,
|
|
927
|
+
data: step.data,
|
|
928
|
+
value: step.value,
|
|
929
|
+
gas: gasLimitI,
|
|
930
|
+
maxFeePerGas,
|
|
931
|
+
maxPriorityFeePerGas,
|
|
932
|
+
nonce: currentNonce,
|
|
933
|
+
chainId
|
|
934
|
+
});
|
|
935
|
+
proposalTxParams = {
|
|
936
|
+
nonce: currentNonce,
|
|
937
|
+
gasLimit: gasLimitI.toString(),
|
|
938
|
+
txType: "eip1559",
|
|
939
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
940
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
941
|
+
};
|
|
942
|
+
feeSnapshot = i === 0 ? proposalTxParamsToFeeSnapshot(proposalTxParams) : {};
|
|
943
|
+
}
|
|
944
|
+
const h = keccak256(serialized);
|
|
945
|
+
const msgHash = h.startsWith("0x") ? h.slice(2) : h;
|
|
946
|
+
const batchMetaExtra = args.buildBatchMeta({ step, index: i, gasLimit: gasLimitI });
|
|
947
|
+
legs.push({
|
|
948
|
+
msgHash,
|
|
949
|
+
msgRaw: i === 0 && args.firstMsgRawNo0x != null ? args.firstMsgRawNo0x : serialized,
|
|
950
|
+
destinationAddress: step.to,
|
|
951
|
+
signatureText: typeof batchMetaExtra.signatureText === "string" ? batchMetaExtra.signatureText : JSON.stringify(batchMetaExtra.signatureText ?? {}),
|
|
952
|
+
audit: batchMetaExtra,
|
|
953
|
+
feeSnapshot: i === 0 ? feeSnapshot : {},
|
|
954
|
+
proposalTxParams,
|
|
955
|
+
valueWei: i === 0 ? step.value : void 0
|
|
956
|
+
});
|
|
957
|
+
if (i === 0 && args.firstMsgRawNo0x != null) {
|
|
958
|
+
legs[0].msgRaw = args.firstMsgRawNo0x;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
const extraJSON = {};
|
|
962
|
+
if (useCustomGas && customGasChainDetails && Object.keys(customGasChainDetails).length > 0) {
|
|
963
|
+
extraJSON.customGasChainDetails = customGasChainDetails;
|
|
964
|
+
}
|
|
965
|
+
const result = finalizeMultisign({
|
|
966
|
+
keyGen,
|
|
967
|
+
purposeText,
|
|
968
|
+
purposeSuffix: args.purposeSuffix,
|
|
969
|
+
destinationChainID: String(chainId),
|
|
970
|
+
destinationAddress: args.destinationAddress ?? steps[0].to,
|
|
971
|
+
legs,
|
|
972
|
+
extraJSON: Object.keys(extraJSON).length > 0 ? extraJSON : void 0,
|
|
973
|
+
expiryDate: context.expiryDate
|
|
974
|
+
});
|
|
975
|
+
const pv = args.payableValueWei;
|
|
976
|
+
if (pv != null && pv > 0n) {
|
|
977
|
+
result.bodyForSign.value = pv.toString();
|
|
978
|
+
}
|
|
979
|
+
return result;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/core/expiry.ts
|
|
983
|
+
var DEFAULT_DEFI_MULTISIGN_EXPIRY_SECONDS = 30 * 60;
|
|
984
|
+
var DEFI_PROTOCOLS_WITH_30MIN_EXPIRY = /* @__PURE__ */ new Set([
|
|
985
|
+
"uniswap-v4",
|
|
986
|
+
"gmx",
|
|
987
|
+
"hyperliquid",
|
|
988
|
+
"arcus",
|
|
989
|
+
"curve-dao",
|
|
990
|
+
"aerodrome",
|
|
991
|
+
"pendle"
|
|
992
|
+
]);
|
|
993
|
+
function defiMultisignExpiryUnixSeconds(protocolId, explicitExpiryDate) {
|
|
994
|
+
if (typeof explicitExpiryDate === "number" && Number.isFinite(explicitExpiryDate) && explicitExpiryDate > 0) {
|
|
995
|
+
return Math.floor(explicitExpiryDate);
|
|
996
|
+
}
|
|
997
|
+
if (DEFI_PROTOCOLS_WITH_30MIN_EXPIRY.has(protocolId)) {
|
|
998
|
+
return Math.floor(Date.now() / 1e3) + DEFAULT_DEFI_MULTISIGN_EXPIRY_SECONDS;
|
|
999
|
+
}
|
|
1000
|
+
return void 0;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/protocols/evm/pendle/multisign.ts
|
|
1004
|
+
var erc20Abi = parseAbi([
|
|
1005
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
1006
|
+
"function approve(address spender, uint256 amount) returns (bool)"
|
|
1007
|
+
]);
|
|
1008
|
+
function txValueWei(raw) {
|
|
1009
|
+
try {
|
|
1010
|
+
return BigInt(raw);
|
|
1011
|
+
} catch {
|
|
1012
|
+
return 0n;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
async function approveStepsIfNeeded(args) {
|
|
1016
|
+
const steps = [];
|
|
1017
|
+
const ch = defineChain({
|
|
1018
|
+
id: args.chainId,
|
|
1019
|
+
name: "PendleApprove",
|
|
1020
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1021
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1022
|
+
});
|
|
1023
|
+
const client = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
|
|
1024
|
+
for (const a of args.approvals) {
|
|
1025
|
+
if (isPendleNativeToken(a.token)) continue;
|
|
1026
|
+
const token = getAddress(a.token);
|
|
1027
|
+
const amountWei = BigInt(a.amount);
|
|
1028
|
+
const allowance = await client.readContract({
|
|
1029
|
+
address: token,
|
|
1030
|
+
abi: erc20Abi,
|
|
1031
|
+
functionName: "allowance",
|
|
1032
|
+
args: [args.owner, args.spender]
|
|
1033
|
+
});
|
|
1034
|
+
if (allowance >= amountWei) continue;
|
|
1035
|
+
if (allowance > 0n) {
|
|
1036
|
+
steps.push({
|
|
1037
|
+
to: token,
|
|
1038
|
+
data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [args.spender, 0n] }),
|
|
1039
|
+
value: 0n,
|
|
1040
|
+
kind: "approve",
|
|
1041
|
+
fallbackGas: 80000n
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
steps.push({
|
|
1045
|
+
to: token,
|
|
1046
|
+
data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [args.spender, amountWei] }),
|
|
1047
|
+
value: 0n,
|
|
1048
|
+
kind: "approve",
|
|
1049
|
+
fallbackGas: 80000n
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
return steps;
|
|
1053
|
+
}
|
|
1054
|
+
function isPendleConvertEvmSignRequest(extra) {
|
|
1055
|
+
const evm = extra?.evm;
|
|
1056
|
+
if (!evm || typeof evm !== "object" || Array.isArray(evm)) return false;
|
|
1057
|
+
const t = String(evm.type ?? "");
|
|
1058
|
+
return t.startsWith("pendle_");
|
|
1059
|
+
}
|
|
1060
|
+
async function buildEvmMultisignBodyPendleFromConvert(args) {
|
|
1061
|
+
const executor = getAddress(args.executorAddress);
|
|
1062
|
+
const spender = getAddress(args.quote.tx.to);
|
|
1063
|
+
const steps = [
|
|
1064
|
+
...await approveStepsIfNeeded({
|
|
1065
|
+
rpcUrl: args.rpcUrl,
|
|
1066
|
+
chainId: args.chainId,
|
|
1067
|
+
owner: executor,
|
|
1068
|
+
spender,
|
|
1069
|
+
approvals: args.quote.requiredApprovals
|
|
1070
|
+
}),
|
|
1071
|
+
{
|
|
1072
|
+
to: spender,
|
|
1073
|
+
data: args.quote.tx.data,
|
|
1074
|
+
value: txValueWei(args.quote.tx.value),
|
|
1075
|
+
kind: "convert",
|
|
1076
|
+
routerSwap: true,
|
|
1077
|
+
fallbackGas: PENDLE_CONVERT_DEFAULT_GAS_UNITS
|
|
1078
|
+
}
|
|
1079
|
+
];
|
|
1080
|
+
const expiryDate = defiMultisignExpiryUnixSeconds(PENDLE_PROTOCOL_ID, args.expiryDate);
|
|
1081
|
+
const purposeText = args.purposeText.trim() || buildPendlePurposePrefill({
|
|
1082
|
+
action: args.purposeAction,
|
|
1083
|
+
detail: args.quote.action,
|
|
1084
|
+
chainId: args.chainId
|
|
1085
|
+
});
|
|
1086
|
+
const chainGasLimitRouter = args.chainDetail?.gasLimit != null && Number.isFinite(Number(args.chainDetail.gasLimit)) && Number(args.chainDetail.gasLimit) > 0 ? Number(args.chainDetail.gasLimit) : void 0;
|
|
1087
|
+
const first = steps[0];
|
|
1088
|
+
const firstDataNo0x = first.data.startsWith("0x") ? first.data.slice(2) : first.data;
|
|
1089
|
+
const payableValueWei = steps.reduce((a, s) => a + s.value, 0n);
|
|
1090
|
+
return buildEvmMultisignBatch({
|
|
1091
|
+
context: {
|
|
1092
|
+
chainCategory: "evm",
|
|
1093
|
+
keyGen: args.keyGen,
|
|
1094
|
+
purposeText,
|
|
1095
|
+
chainId: args.chainId,
|
|
1096
|
+
rpcUrl: args.rpcUrl,
|
|
1097
|
+
executorAddress: executor,
|
|
1098
|
+
chainDetail: args.chainDetail,
|
|
1099
|
+
useCustomGas: args.useCustomGas,
|
|
1100
|
+
customGasChainDetails: args.customGasChainDetails,
|
|
1101
|
+
expiryDate
|
|
1102
|
+
},
|
|
1103
|
+
steps,
|
|
1104
|
+
purposeSuffix: steps.length === 1 ? `Pendle: ${args.purposeAction} via Hosted SDK convert.` : `Pendle: approve router from convert tx.to, then ${args.purposeAction}.`,
|
|
1105
|
+
firstMsgRawNo0x: firstDataNo0x,
|
|
1106
|
+
destinationAddress: first.to,
|
|
1107
|
+
payableValueWei: payableValueWei > 0n ? payableValueWei : void 0,
|
|
1108
|
+
resolveGasLimit: async ({ step, estimatedGas }) => {
|
|
1109
|
+
const s = step;
|
|
1110
|
+
if (s.routerSwap) return routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
|
|
1111
|
+
return estimatedGas;
|
|
1112
|
+
},
|
|
1113
|
+
buildBatchMeta: ({ step }) => {
|
|
1114
|
+
const s = step;
|
|
1115
|
+
return {
|
|
1116
|
+
evm: {
|
|
1117
|
+
type: s.kind === "approve" ? "pendle_approve" : `pendle_${args.purposeAction.replace(/\s+/g, "_")}`,
|
|
1118
|
+
version: 1,
|
|
1119
|
+
chainId: String(args.chainId),
|
|
1120
|
+
convertAction: args.quote.action
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
async function buildEvmMultisignBodyPendleSwapBatch(args) {
|
|
1127
|
+
const quote = args.convertSnapshot ?? await pendleQuoteSwap({
|
|
1128
|
+
chainId: args.chainId,
|
|
1129
|
+
rpcUrl: args.rpcUrl,
|
|
1130
|
+
tokenIn: args.tokenIn,
|
|
1131
|
+
tokenOut: args.tokenOut,
|
|
1132
|
+
amountHuman: args.amountHuman,
|
|
1133
|
+
slippagePercent: args.slippagePercent,
|
|
1134
|
+
receiver: String(args.executorAddress),
|
|
1135
|
+
enableAggregator: args.enableAggregator,
|
|
1136
|
+
tokenInDecimals: args.tokenInDecimals
|
|
1137
|
+
});
|
|
1138
|
+
return buildEvmMultisignBodyPendleFromConvert({ ...args, quote, purposeAction: "swap" });
|
|
1139
|
+
}
|
|
1140
|
+
async function buildEvmMultisignBodyPendleMintPyBatch(args) {
|
|
1141
|
+
const quote = args.convertSnapshot ?? await pendleQuoteMintPy({
|
|
1142
|
+
chainId: args.chainId,
|
|
1143
|
+
rpcUrl: args.rpcUrl,
|
|
1144
|
+
tokenIn: args.tokenIn,
|
|
1145
|
+
pt: args.pt,
|
|
1146
|
+
yt: args.yt,
|
|
1147
|
+
amountHuman: args.amountHuman,
|
|
1148
|
+
slippagePercent: args.slippagePercent,
|
|
1149
|
+
receiver: String(args.executorAddress),
|
|
1150
|
+
tokenInDecimals: args.tokenInDecimals
|
|
1151
|
+
});
|
|
1152
|
+
return buildEvmMultisignBodyPendleFromConvert({ ...args, quote, purposeAction: "mint_py" });
|
|
1153
|
+
}
|
|
1154
|
+
async function buildEvmMultisignBodyPendleRedeemPyBatch(args) {
|
|
1155
|
+
const quote = args.convertSnapshot ?? await pendleQuoteRedeemPy({
|
|
1156
|
+
chainId: args.chainId,
|
|
1157
|
+
rpcUrl: args.rpcUrl,
|
|
1158
|
+
pt: args.pt,
|
|
1159
|
+
yt: args.yt,
|
|
1160
|
+
tokenOut: args.tokenOut,
|
|
1161
|
+
amountPtHuman: args.amountPtHuman,
|
|
1162
|
+
amountYtHuman: args.amountYtHuman,
|
|
1163
|
+
slippagePercent: args.slippagePercent,
|
|
1164
|
+
receiver: String(args.executorAddress)
|
|
1165
|
+
});
|
|
1166
|
+
return buildEvmMultisignBodyPendleFromConvert({ ...args, quote, purposeAction: "redeem_py" });
|
|
1167
|
+
}
|
|
1168
|
+
async function buildEvmMultisignBodyPendleMintSyBatch(args) {
|
|
1169
|
+
const quote = args.convertSnapshot ?? await pendleQuoteMintSy({
|
|
1170
|
+
chainId: args.chainId,
|
|
1171
|
+
rpcUrl: args.rpcUrl,
|
|
1172
|
+
tokenIn: args.tokenIn,
|
|
1173
|
+
sy: args.sy,
|
|
1174
|
+
amountHuman: args.amountHuman,
|
|
1175
|
+
slippagePercent: args.slippagePercent,
|
|
1176
|
+
receiver: String(args.executorAddress)
|
|
1177
|
+
});
|
|
1178
|
+
return buildEvmMultisignBodyPendleFromConvert({ ...args, quote, purposeAction: "mint_sy" });
|
|
1179
|
+
}
|
|
1180
|
+
async function buildEvmMultisignBodyPendleRedeemSyBatch(args) {
|
|
1181
|
+
const quote = args.convertSnapshot ?? await pendleQuoteRedeemSy({
|
|
1182
|
+
chainId: args.chainId,
|
|
1183
|
+
rpcUrl: args.rpcUrl,
|
|
1184
|
+
sy: args.sy,
|
|
1185
|
+
tokenOut: args.tokenOut,
|
|
1186
|
+
amountHuman: args.amountHuman,
|
|
1187
|
+
slippagePercent: args.slippagePercent,
|
|
1188
|
+
receiver: String(args.executorAddress)
|
|
1189
|
+
});
|
|
1190
|
+
return buildEvmMultisignBodyPendleFromConvert({ ...args, quote, purposeAction: "redeem_sy" });
|
|
1191
|
+
}
|
|
1192
|
+
async function buildEvmMultisignBodyPendleAddLiquidityBatch(args) {
|
|
1193
|
+
const quote = args.convertSnapshot ?? await pendleQuoteAddLiquidity({
|
|
1194
|
+
chainId: args.chainId,
|
|
1195
|
+
rpcUrl: args.rpcUrl,
|
|
1196
|
+
tokensIn: args.tokensIn,
|
|
1197
|
+
market: args.market,
|
|
1198
|
+
keepYt: args.keepYt,
|
|
1199
|
+
yt: args.yt,
|
|
1200
|
+
slippagePercent: args.slippagePercent,
|
|
1201
|
+
receiver: String(args.executorAddress)
|
|
1202
|
+
});
|
|
1203
|
+
return buildEvmMultisignBodyPendleFromConvert({ ...args, quote, purposeAction: "add_liquidity" });
|
|
1204
|
+
}
|
|
1205
|
+
async function buildEvmMultisignBodyPendleRemoveLiquidityBatch(args) {
|
|
1206
|
+
const quote = args.convertSnapshot ?? await pendleQuoteRemoveLiquidity({
|
|
1207
|
+
chainId: args.chainId,
|
|
1208
|
+
rpcUrl: args.rpcUrl,
|
|
1209
|
+
market: args.market,
|
|
1210
|
+
lpAmountHuman: args.lpAmountHuman,
|
|
1211
|
+
tokensOut: args.tokensOut,
|
|
1212
|
+
slippagePercent: args.slippagePercent,
|
|
1213
|
+
receiver: String(args.executorAddress),
|
|
1214
|
+
enableAggregator: args.enableAggregator
|
|
1215
|
+
});
|
|
1216
|
+
return buildEvmMultisignBodyPendleFromConvert({ ...args, quote, purposeAction: "remove_liquidity" });
|
|
1217
|
+
}
|
|
1218
|
+
async function buildEvmMultisignBodyPendleConvertBatch(args) {
|
|
1219
|
+
const quote = args.convertSnapshot ?? await pendleQuoteConvert({
|
|
1220
|
+
chainId: args.chainId,
|
|
1221
|
+
rpcUrl: args.rpcUrl,
|
|
1222
|
+
receiver: String(args.executorAddress),
|
|
1223
|
+
slippagePercent: args.slippagePercent,
|
|
1224
|
+
tokensIn: args.tokensIn,
|
|
1225
|
+
tokensOut: args.tokensOut,
|
|
1226
|
+
enableAggregator: args.enableAggregator
|
|
1227
|
+
});
|
|
1228
|
+
return buildEvmMultisignBodyPendleFromConvert({ ...args, quote, purposeAction: quote.action });
|
|
1229
|
+
}
|
|
1230
|
+
function asRecord3(v) {
|
|
1231
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
1232
|
+
}
|
|
1233
|
+
function parsePendleMerkleRewardRow(raw) {
|
|
1234
|
+
const o = asRecord3(raw);
|
|
1235
|
+
if (!o) return null;
|
|
1236
|
+
const token = typeof o.token === "string" ? o.token : "";
|
|
1237
|
+
const chainId = Number(o.chainId);
|
|
1238
|
+
if (!token || !Number.isFinite(chainId)) return null;
|
|
1239
|
+
return {
|
|
1240
|
+
user: typeof o.user === "string" ? o.user : "",
|
|
1241
|
+
token,
|
|
1242
|
+
merkleRoot: typeof o.merkleRoot === "string" ? o.merkleRoot : "",
|
|
1243
|
+
chainId,
|
|
1244
|
+
assetId: typeof o.assetId === "string" ? o.assetId : "",
|
|
1245
|
+
amount: String(o.amount ?? ""),
|
|
1246
|
+
fromTimestamp: typeof o.fromTimestamp === "string" ? o.fromTimestamp : null,
|
|
1247
|
+
toTimestamp: typeof o.toTimestamp === "string" ? o.toTimestamp : null
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
async function pendleFetchMerkleRewards(args) {
|
|
1251
|
+
if (!isAddress(args.user)) throw new Error("user must be an EVM address.");
|
|
1252
|
+
const user = getAddress(args.user);
|
|
1253
|
+
const json = await pendleCoreFetch({
|
|
1254
|
+
path: `/v1/dashboard/merkle-rewards/${user}`
|
|
1255
|
+
});
|
|
1256
|
+
const claimable = Array.isArray(json.claimableRewards) ? json.claimableRewards.map(parsePendleMerkleRewardRow).filter((r) => r != null) : [];
|
|
1257
|
+
const claimed = Array.isArray(json.claimedRewards) ? json.claimedRewards.map(parsePendleMerkleRewardRow).filter((r) => r != null) : [];
|
|
1258
|
+
return {
|
|
1259
|
+
user,
|
|
1260
|
+
claimableRewards: claimable,
|
|
1261
|
+
claimedRewards: claimed,
|
|
1262
|
+
notes: "Informational airdrop list only. Core does not include claim proofs. Tell the user to claim airdrops on app.pendle.finance. Redeem on-chain SY/YT/LP incentives with ctm_pendle_build_redeem_rewards_multisign."
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
function asRecord4(v) {
|
|
1266
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
1267
|
+
}
|
|
1268
|
+
function asNumber3(v) {
|
|
1269
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1270
|
+
if (typeof v === "string" && v.trim()) {
|
|
1271
|
+
const n = Number.parseFloat(v);
|
|
1272
|
+
return Number.isFinite(n) ? n : null;
|
|
1273
|
+
}
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
function weiToHumanAmount(wei, decimals) {
|
|
1277
|
+
try {
|
|
1278
|
+
const raw = formatUnits(BigInt(wei), decimals);
|
|
1279
|
+
if (!raw.includes(".")) return raw;
|
|
1280
|
+
return raw.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
|
|
1281
|
+
} catch {
|
|
1282
|
+
return wei;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
function parsePendlePositionToken(raw) {
|
|
1286
|
+
const o = asRecord4(raw);
|
|
1287
|
+
if (!o) return { balanceWei: "0", usd: null };
|
|
1288
|
+
const balance = o.balance == null ? "0" : String(o.balance);
|
|
1289
|
+
return { balanceWei: /^\d+$/.test(balance) ? balance : "0", usd: asNumber3(o.valuation) };
|
|
1290
|
+
}
|
|
1291
|
+
function positiveWei(wei) {
|
|
1292
|
+
try {
|
|
1293
|
+
return BigInt(wei) > 0n;
|
|
1294
|
+
} catch {
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
function collectPendleMarketPositions(raw, closed) {
|
|
1299
|
+
const o = asRecord4(raw);
|
|
1300
|
+
if (!o) return [];
|
|
1301
|
+
const marketId = typeof o.marketId === "string" ? o.marketId : "";
|
|
1302
|
+
const parsed = parsePendleAssetId(marketId);
|
|
1303
|
+
if (!parsed) return [];
|
|
1304
|
+
let market;
|
|
1305
|
+
try {
|
|
1306
|
+
market = getAddress(parsed.address);
|
|
1307
|
+
} catch {
|
|
1308
|
+
return [];
|
|
1309
|
+
}
|
|
1310
|
+
const lp = parsePendlePositionToken(o.lp);
|
|
1311
|
+
if (!positiveWei(lp.balanceWei)) return [];
|
|
1312
|
+
return [
|
|
1313
|
+
{
|
|
1314
|
+
marketId,
|
|
1315
|
+
chainId: parsed.chainId,
|
|
1316
|
+
market,
|
|
1317
|
+
closed,
|
|
1318
|
+
lp,
|
|
1319
|
+
pt: parsePendlePositionToken(o.pt),
|
|
1320
|
+
yt: parsePendlePositionToken(o.yt)
|
|
1321
|
+
}
|
|
1322
|
+
];
|
|
1323
|
+
}
|
|
1324
|
+
async function assetsByIds(ids) {
|
|
1325
|
+
const out = /* @__PURE__ */ new Map();
|
|
1326
|
+
const unique = [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
|
|
1327
|
+
for (let i = 0; i < unique.length; i += 20) {
|
|
1328
|
+
const batch = await pendleFetchAssetsByIds(unique.slice(i, i + 20));
|
|
1329
|
+
for (const row of batch) out.set(row.id.toLowerCase(), row);
|
|
1330
|
+
}
|
|
1331
|
+
return out;
|
|
1332
|
+
}
|
|
1333
|
+
async function pendleFetchPositions(args) {
|
|
1334
|
+
if (!isAddress(args.user)) throw new Error("user must be an EVM address.");
|
|
1335
|
+
const user = getAddress(args.user);
|
|
1336
|
+
const chainId = args.chainId != null && Number.isFinite(args.chainId) ? args.chainId : void 0;
|
|
1337
|
+
const json = await pendleCoreFetch({
|
|
1338
|
+
path: `/v1/dashboard/positions/database/${user}`,
|
|
1339
|
+
query: {
|
|
1340
|
+
...chainId != null ? { chainId } : {},
|
|
1341
|
+
...args.filterUsd != null ? { filterUsd: args.filterUsd } : {}
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
const groups = Array.isArray(json.positions) ? json.positions : [];
|
|
1345
|
+
const collected = [];
|
|
1346
|
+
for (const group of groups) {
|
|
1347
|
+
const g = asRecord4(group);
|
|
1348
|
+
if (!g) continue;
|
|
1349
|
+
const groupChain = asNumber3(g.chainId);
|
|
1350
|
+
if (chainId != null && groupChain != null && groupChain !== chainId) continue;
|
|
1351
|
+
for (const row of Array.isArray(g.openPositions) ? g.openPositions : []) {
|
|
1352
|
+
collected.push(...collectPendleMarketPositions(row, false));
|
|
1353
|
+
}
|
|
1354
|
+
for (const row of Array.isArray(g.closedPositions) ? g.closedPositions : []) {
|
|
1355
|
+
collected.push(...collectPendleMarketPositions(row, true));
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const scoped = (chainId != null ? collected.filter((r) => r.chainId === chainId) : collected).filter((r) => {
|
|
1359
|
+
if (args.filterUsd != null && r.lp.usd != null && r.lp.usd < args.filterUsd) return false;
|
|
1360
|
+
try {
|
|
1361
|
+
return BigInt(r.lp.balanceWei) >= 10n ** 12n || r.lp.usd != null && r.lp.usd >= (args.filterUsd ?? 0.1);
|
|
1362
|
+
} catch {
|
|
1363
|
+
return false;
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
const meta = await assetsByIds(scoped.map((r) => r.marketId));
|
|
1367
|
+
const minUsd = args.filterUsd;
|
|
1368
|
+
const lp = [];
|
|
1369
|
+
for (const row of scoped) {
|
|
1370
|
+
const asset = meta.get(row.marketId.toLowerCase());
|
|
1371
|
+
const decimals = asset?.decimals ?? 18;
|
|
1372
|
+
const expiry = asset?.expiry ?? null;
|
|
1373
|
+
const usd = row.lp.usd;
|
|
1374
|
+
if (minUsd != null && (usd == null || usd < minUsd)) continue;
|
|
1375
|
+
lp.push({
|
|
1376
|
+
chainId: row.chainId,
|
|
1377
|
+
marketId: row.marketId,
|
|
1378
|
+
market: row.market,
|
|
1379
|
+
name: asset?.name || asset?.symbol || `${row.market.slice(0, 8)}\u2026`,
|
|
1380
|
+
symbol: asset?.symbol || "LP",
|
|
1381
|
+
decimals,
|
|
1382
|
+
expiry,
|
|
1383
|
+
matured: isPendleMarketExpired({ expiry }),
|
|
1384
|
+
closed: row.closed,
|
|
1385
|
+
lpBalanceWei: row.lp.balanceWei,
|
|
1386
|
+
lpAmountHuman: weiToHumanAmount(row.lp.balanceWei, decimals),
|
|
1387
|
+
lpUsd: usd,
|
|
1388
|
+
ptBalanceWei: row.pt.balanceWei,
|
|
1389
|
+
ytBalanceWei: row.yt.balanceWei
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
lp.sort((a, b) => {
|
|
1393
|
+
if (a.matured !== b.matured) return a.matured ? 1 : -1;
|
|
1394
|
+
return (b.lpUsd ?? 0) - (a.lpUsd ?? 0);
|
|
1395
|
+
});
|
|
1396
|
+
const capped = lp.slice(0, 80);
|
|
1397
|
+
const matured = capped.filter((r) => r.matured).length;
|
|
1398
|
+
return {
|
|
1399
|
+
user,
|
|
1400
|
+
chainId: chainId ?? null,
|
|
1401
|
+
lp: capped,
|
|
1402
|
+
notes: capped.length === 0 ? `No Pendle LP for ${user}${chainId != null ? ` on chain ${chainId}` : ""}.` : `${capped.length} Pendle LP holding(s)${chainId != null ? ` on chain ${chainId}` : ""}` + (lp.length > capped.length ? ` (showing top ${capped.length} of ${lp.length})` : "") + (matured ? ` (${matured} matured \u2014 remove LP or redeem PT only)` : "") + ". Amounts from Core dashboard (not the node token list). Claimable rewards on this snapshot may be up to 24h stale \u2014 redeem with ctm_pendle_build_redeem_rewards_multisign."
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
async function pendleRedeemInterestsAndRewards(args) {
|
|
1406
|
+
const receiver = getAddress(args.receiver);
|
|
1407
|
+
const json = await pendleCoreFetch({
|
|
1408
|
+
path: `/v1/sdk/${args.chainId}/redeem-interests-and-rewards`,
|
|
1409
|
+
query: {
|
|
1410
|
+
receiver,
|
|
1411
|
+
sys: args.sys?.length ? args.sys.join(",") : void 0,
|
|
1412
|
+
yts: args.yts?.length ? args.yts.join(",") : void 0,
|
|
1413
|
+
markets: args.markets?.length ? args.markets.join(",") : void 0
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
return parsePendleConvertResponse(json);
|
|
1417
|
+
}
|
|
1418
|
+
async function buildEvmMultisignBodyPendleRedeemRewardsBatch(args) {
|
|
1419
|
+
const quote = args.convertSnapshot ?? await pendleRedeemInterestsAndRewards({
|
|
1420
|
+
chainId: args.chainId,
|
|
1421
|
+
receiver: String(args.executorAddress),
|
|
1422
|
+
sys: args.sys,
|
|
1423
|
+
yts: args.yts,
|
|
1424
|
+
markets: args.markets
|
|
1425
|
+
});
|
|
1426
|
+
return buildEvmMultisignBodyPendleFromConvert({
|
|
1427
|
+
...args,
|
|
1428
|
+
quote,
|
|
1429
|
+
purposeAction: "redeem_rewards"
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// src/protocols/evm/pendle/index.ts
|
|
1434
|
+
var pendleProtocolModule = {
|
|
1435
|
+
id: PENDLE_PROTOCOL_ID,
|
|
1436
|
+
chainCategory: "evm",
|
|
1437
|
+
isChainSupported(ctx) {
|
|
1438
|
+
if (ctx.chainCategory !== "evm") return false;
|
|
1439
|
+
const n = typeof ctx.chainId === "number" ? ctx.chainId : Number.parseInt(String(ctx.chainId), 10);
|
|
1440
|
+
if (!Number.isFinite(n)) return false;
|
|
1441
|
+
return isPendleChainSupported(n);
|
|
1442
|
+
},
|
|
1443
|
+
isTokenSupported(token) {
|
|
1444
|
+
if (token.category !== "evm") return false;
|
|
1445
|
+
return token.kind === "native" || token.kind === "erc20";
|
|
1446
|
+
},
|
|
1447
|
+
actions: [
|
|
1448
|
+
{ id: "pendle.fetch-markets", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "List live Pendle markets (TVL-ranked, cap 100, matured omitted)", commonParams: [], params: {} },
|
|
1449
|
+
{ id: "pendle.search-assets", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "Search live PT/YT/SY/underlying (matured only if address-pinned)", commonParams: [], params: {} },
|
|
1450
|
+
{ id: "pendle.swap", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "Swap token \u2194 PT/YT via Hosted SDK convert", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1451
|
+
{ id: "pendle.mint-py", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "Mint PT + YT", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1452
|
+
{ id: "pendle.redeem-py", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "Redeem PT + YT (PT only after expiry)", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1453
|
+
{ id: "pendle.add-liquidity", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "Add Pendle AMM liquidity", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1454
|
+
{ id: "pendle.fetch-positions", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "List this wallet\u2019s Pendle LP (including matured)", commonParams: [], params: {} },
|
|
1455
|
+
{ id: "pendle.remove-liquidity", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "Remove Pendle AMM liquidity", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1456
|
+
{ id: "pendle.redeem-rewards", protocolId: PENDLE_PROTOCOL_ID, chainCategory: "evm", description: "Redeem on-chain SY/YT/LP interest and incentives (not merkle airdrops)", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} }
|
|
1457
|
+
]
|
|
1458
|
+
};
|
|
1459
|
+
registerProtocolModule(pendleProtocolModule);
|
|
1460
|
+
|
|
1461
|
+
export { PENDLE_CHAIN_CACHE_MS, PENDLE_CONVERT_DEFAULT_GAS_UNITS, PENDLE_CORE_API_BASE, PENDLE_ICON_CACHE_MS, PENDLE_LISTING_CAP, PENDLE_LISTING_MAX_PAGES, PENDLE_NATIVE_PLACEHOLDER, PENDLE_PROTOCOL_ID, PENDLE_SEARCH_EXTRA_PAGES, PendleApiError, buildEvmMultisignBodyPendleAddLiquidityBatch, buildEvmMultisignBodyPendleConvertBatch, buildEvmMultisignBodyPendleFromConvert, buildEvmMultisignBodyPendleMintPyBatch, buildEvmMultisignBodyPendleMintSyBatch, buildEvmMultisignBodyPendleRedeemPyBatch, buildEvmMultisignBodyPendleRedeemRewardsBatch, buildEvmMultisignBodyPendleRedeemSyBatch, buildEvmMultisignBodyPendleRemoveLiquidityBatch, buildEvmMultisignBodyPendleSwapBatch, buildPendlePurposePrefill, classifyPendleConvertAction, collectPendleMarketAddresses, collectPendleMarketPositions, isPendleChainSupported, isPendleConvertEvmSignRequest, isPendleMarketExpired, isPendleNativeToken, marketTvlUsd, normalizePendleTokenAddress, parsePendleAsset, parsePendleAssetId, parsePendleConvertResponse, parsePendleMarket, parsePendleMerkleRewardRow, parsePendlePositionToken, pendleActionNeedsLiveMarket, pendleAssetId, pendleConvert, pendleCoreFetch, pendleFetchAssetsByIds, pendleFetchMarketsForChain, pendleFetchMarketsPage, pendleFetchMarketsSummary, pendleFetchMerkleRewards, pendleFetchPositions, pendleFetchPricesSummary, pendleFetchSupportedChainIds, pendleIconAddressesForChain, pendleMarketExpiryMs, pendleProtocolModule, pendleQuoteAddLiquidity, pendleQuoteConvert, pendleQuoteMintPy, pendleQuoteMintSy, pendleQuoteRedeemPy, pendleQuoteRedeemSy, pendleQuoteRemoveLiquidity, pendleQuoteSwap, pendleRedeemInterestsAndRewards, pendleSearchAssets, resolvePendleTokenDecimals, selectPendleListingMarkets, slippagePercentToConvert, sortPendleMarketsByTvl, weiToHumanAmount };
|
|
1462
|
+
//# sourceMappingURL=index.js.map
|
|
1463
|
+
//# sourceMappingURL=index.js.map
|