@continuumdao/ctm-mpc-defi 0.1.4 → 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/dist/agent/catalog.cjs +878 -141
- package/dist/agent/catalog.cjs.map +1 -1
- package/dist/agent/catalog.d.cts +756 -12
- package/dist/agent/catalog.d.ts +756 -12
- package/dist/agent/catalog.js +829 -142
- package/dist/agent/catalog.js.map +1 -1
- package/dist/chains/evm/index.cjs +13 -0
- package/dist/chains/evm/index.cjs.map +1 -1
- package/dist/chains/evm/index.d.cts +3 -1
- package/dist/chains/evm/index.d.ts +3 -1
- package/dist/chains/evm/index.js +13 -1
- package/dist/chains/evm/index.js.map +1 -1
- package/dist/index.cjs +825 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +825 -142
- package/dist/index.js.map +1 -1
- package/dist/protocols/evm/aave-v4/index.cjs +1987 -0
- package/dist/protocols/evm/aave-v4/index.cjs.map +1 -0
- package/dist/protocols/evm/aave-v4/index.d.cts +500 -0
- package/dist/protocols/evm/aave-v4/index.d.ts +500 -0
- package/dist/protocols/evm/aave-v4/index.js +1943 -0
- package/dist/protocols/evm/aave-v4/index.js.map +1 -0
- package/dist/protocols/evm/ethena/index.cjs +965 -0
- package/dist/protocols/evm/ethena/index.cjs.map +1 -0
- package/dist/protocols/evm/ethena/index.d.cts +161 -0
- package/dist/protocols/evm/ethena/index.d.ts +161 -0
- package/dist/protocols/evm/ethena/index.js +943 -0
- package/dist/protocols/evm/ethena/index.js.map +1 -0
- package/dist/protocols/evm/euler-v2/index.cjs +2263 -0
- package/dist/protocols/evm/euler-v2/index.cjs.map +1 -0
- package/dist/protocols/evm/euler-v2/index.d.cts +317 -0
- package/dist/protocols/evm/euler-v2/index.d.ts +317 -0
- package/dist/protocols/evm/euler-v2/index.js +2238 -0
- package/dist/protocols/evm/euler-v2/index.js.map +1 -0
- package/dist/protocols/evm/lido/index.cjs +834 -0
- package/dist/protocols/evm/lido/index.cjs.map +1 -0
- package/dist/protocols/evm/lido/index.d.cts +120 -0
- package/dist/protocols/evm/lido/index.d.ts +120 -0
- package/dist/protocols/evm/lido/index.js +809 -0
- package/dist/protocols/evm/lido/index.js.map +1 -0
- package/dist/protocols/evm/maple/index.cjs +707 -0
- package/dist/protocols/evm/maple/index.cjs.map +1 -0
- package/dist/protocols/evm/maple/index.d.cts +109 -0
- package/dist/protocols/evm/maple/index.d.ts +109 -0
- package/dist/protocols/evm/maple/index.js +693 -0
- package/dist/protocols/evm/maple/index.js.map +1 -0
- package/dist/protocols/evm/sky/index.cjs +1254 -0
- package/dist/protocols/evm/sky/index.cjs.map +1 -0
- package/dist/protocols/evm/sky/index.d.cts +218 -0
- package/dist/protocols/evm/sky/index.d.ts +218 -0
- package/dist/protocols/evm/sky/index.js +1229 -0
- package/dist/protocols/evm/sky/index.js.map +1 -0
- package/package.json +37 -3
|
@@ -0,0 +1,1943 @@
|
|
|
1
|
+
import { parseAbi, isAddress, getAddress, defineChain, createPublicClient, http, parseUnits, encodeFunctionData, parseGwei, serializeTransaction, keccak256, formatUnits } from 'viem';
|
|
2
|
+
|
|
3
|
+
// src/core/registry.ts
|
|
4
|
+
var modules = [];
|
|
5
|
+
function registerProtocolModule(mod) {
|
|
6
|
+
const existing = modules.findIndex((m) => m.id === mod.id);
|
|
7
|
+
if (existing >= 0) {
|
|
8
|
+
modules[existing] = mod;
|
|
9
|
+
} else {
|
|
10
|
+
modules.push(mod);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
var AAVE_V4_GRAPHQL_URL = "https://api.v4.aave.com/graphql";
|
|
14
|
+
async function aaveV4Gql(query, variables) {
|
|
15
|
+
const r = await fetch(AAVE_V4_GRAPHQL_URL, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "content-type": "application/json" },
|
|
18
|
+
body: JSON.stringify({ query, variables: variables ?? {} })
|
|
19
|
+
});
|
|
20
|
+
if (!r.ok) {
|
|
21
|
+
const t = await r.text().catch(() => "");
|
|
22
|
+
throw new Error(t ? `Aave V4 API HTTP ${r.status}: ${t.slice(0, 200)}` : `Aave V4 API HTTP ${r.status}`);
|
|
23
|
+
}
|
|
24
|
+
const j = await r.json();
|
|
25
|
+
if (j.errors?.length) {
|
|
26
|
+
const msg = j.errors.map((e) => e.message ?? "Unknown").join("; ");
|
|
27
|
+
throw new Error(msg);
|
|
28
|
+
}
|
|
29
|
+
if (j.data == null) {
|
|
30
|
+
throw new Error("Aave V4 API: empty response");
|
|
31
|
+
}
|
|
32
|
+
return j.data;
|
|
33
|
+
}
|
|
34
|
+
async function fetchAaveV4Chains() {
|
|
35
|
+
const d = await aaveV4Gql(`
|
|
36
|
+
query C($c: ChainsRequest!) { chains(request: $c) { chainId name nativeWrappedToken } }
|
|
37
|
+
`, { c: { query: { filter: "ALL" } } });
|
|
38
|
+
return d.chains ?? [];
|
|
39
|
+
}
|
|
40
|
+
async function loadAaveV4SupportedChainIdsFromV4Api() {
|
|
41
|
+
const rows = await fetchAaveV4Chains();
|
|
42
|
+
const s = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const c of rows) s.add(c.chainId);
|
|
44
|
+
return s;
|
|
45
|
+
}
|
|
46
|
+
var RESERVES_ADDRESS_FRAGMENT = `
|
|
47
|
+
asset { underlying { address } }
|
|
48
|
+
`;
|
|
49
|
+
function aggregateV4SupplyDisplay(symbolForDisplay, rows) {
|
|
50
|
+
const sym = (symbolForDisplay ?? "").trim();
|
|
51
|
+
const withSuffix = (n) => n === "\u2014" ? "\u2014" : sym ? `${n} ${sym}` : n;
|
|
52
|
+
if (!rows.length) {
|
|
53
|
+
return { depositedAmount: "\u2014", apy: "\u2014", totalDeposits: "\u2014", availableLiquidity: "\u2014" };
|
|
54
|
+
}
|
|
55
|
+
const apy = formatV4SupplyApy(rows);
|
|
56
|
+
const sumKey = (key) => sumNumberValues(
|
|
57
|
+
rows.map((r) => {
|
|
58
|
+
const v = key === "supplied" ? r.summary?.supplied?.amount : r.summary?.borrowed?.amount;
|
|
59
|
+
return (v?.value ?? "").trim();
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
const totalSupN = sumKey("supplied");
|
|
63
|
+
const totalBorN = sumKey("borrowed");
|
|
64
|
+
const totalDeposits = totalSupN == null ? "\u2014" : withSuffix(formatAaveV4TokenAmount(totalSupN));
|
|
65
|
+
const availableLiquidity = (() => {
|
|
66
|
+
if (totalSupN == null || totalBorN == null) return "\u2014";
|
|
67
|
+
const a = totalSupN - totalBorN;
|
|
68
|
+
if (!Number.isFinite(a)) return "\u2014";
|
|
69
|
+
return withSuffix(formatAaveV4TokenAmount(a < 0 ? 0 : a));
|
|
70
|
+
})();
|
|
71
|
+
const userN = userSupplyBalanceNumber(rows);
|
|
72
|
+
const depositedAmount = userN == null ? "\u2014" : withSuffix(formatAaveV4TokenAmount(userN));
|
|
73
|
+
return { depositedAmount, apy, totalDeposits, availableLiquidity };
|
|
74
|
+
}
|
|
75
|
+
function sumNumberValues(values) {
|
|
76
|
+
if (!values.length) return null;
|
|
77
|
+
let s = 0;
|
|
78
|
+
for (const v of values) {
|
|
79
|
+
const n = parseFloat(v);
|
|
80
|
+
if (Number.isFinite(n)) s += n;
|
|
81
|
+
}
|
|
82
|
+
return s;
|
|
83
|
+
}
|
|
84
|
+
function formatAaveV4TokenAmount(n) {
|
|
85
|
+
if (!Number.isFinite(n)) return "\u2014";
|
|
86
|
+
if (n === 0) return "0";
|
|
87
|
+
const sign = n < 0 ? "-" : "";
|
|
88
|
+
const a = Math.abs(n);
|
|
89
|
+
const strip = (s) => s.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
|
|
90
|
+
if (a >= 1e6) {
|
|
91
|
+
return sign + strip((a / 1e6).toFixed(2)) + "M";
|
|
92
|
+
}
|
|
93
|
+
if (a >= 1e3) {
|
|
94
|
+
const k = a / 1e3;
|
|
95
|
+
const d = k >= 100 ? 1 : 2;
|
|
96
|
+
return sign + strip(k.toFixed(d)) + "k";
|
|
97
|
+
}
|
|
98
|
+
if (a >= 1) {
|
|
99
|
+
return sign + strip(a.toFixed(2));
|
|
100
|
+
}
|
|
101
|
+
if (a >= 1e-4) {
|
|
102
|
+
return sign + strip(a.toFixed(4));
|
|
103
|
+
}
|
|
104
|
+
return sign + a.toExponential(2);
|
|
105
|
+
}
|
|
106
|
+
function formatAaveV4AmountHumanFromApiString(raw) {
|
|
107
|
+
const s = (raw ?? "").trim();
|
|
108
|
+
if (!s) return "\u2014";
|
|
109
|
+
const n = parseFloat(s);
|
|
110
|
+
if (!Number.isFinite(n)) return "\u2014";
|
|
111
|
+
return formatAaveV4TokenAmount(n);
|
|
112
|
+
}
|
|
113
|
+
function formatAaveV4ReserveLiquidityFromSummary(args) {
|
|
114
|
+
const a = parseFloat(String(args.suppliedValue ?? "").trim());
|
|
115
|
+
const b = parseFloat(String(args.borrowedValue ?? "").trim());
|
|
116
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) return "\u2014";
|
|
117
|
+
return formatAaveV4TokenAmount(Math.max(0, a - b));
|
|
118
|
+
}
|
|
119
|
+
function userSupplyBalanceNumber(rows) {
|
|
120
|
+
const vals = rows.map((r) => (r.userState?.balance?.amount?.value ?? "").trim()).filter((v) => v.length > 0);
|
|
121
|
+
if (!vals.length) return null;
|
|
122
|
+
const u = new Set(vals);
|
|
123
|
+
if (u.size === 1) {
|
|
124
|
+
const n = parseFloat(vals[0]);
|
|
125
|
+
return Number.isFinite(n) ? n : null;
|
|
126
|
+
}
|
|
127
|
+
const sum = vals.reduce((acc, b) => acc + (parseFloat(b) || 0), 0);
|
|
128
|
+
return Number.isFinite(sum) ? sum : null;
|
|
129
|
+
}
|
|
130
|
+
function formatV4SupplyApy(rows) {
|
|
131
|
+
if (!rows.length) return "\u2014";
|
|
132
|
+
const weighted = [];
|
|
133
|
+
for (const r of rows) {
|
|
134
|
+
const sup = parseFloat((r.summary?.supplied?.amount?.value ?? "").trim());
|
|
135
|
+
const norm = (r.summary?.supplyApy?.normalized ?? "").trim();
|
|
136
|
+
const n2 = parseFloat(norm);
|
|
137
|
+
if (Number.isFinite(n2) && Number.isFinite(sup) && sup > 0) {
|
|
138
|
+
weighted.push({ w: sup, apy: n2 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (weighted.length) {
|
|
142
|
+
const tw = weighted.reduce((a, x) => a + x.w, 0);
|
|
143
|
+
if (tw > 0) {
|
|
144
|
+
const x = weighted.reduce((a, x2) => a + x2.w * x2.apy, 0) / tw;
|
|
145
|
+
if (x >= 0.01) return `${x.toFixed(2)}%`;
|
|
146
|
+
if (x > 0) return `${x.toFixed(4)}%`;
|
|
147
|
+
return "0%";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const any = (rows[0]?.summary?.supplyApy?.normalized ?? "").trim();
|
|
151
|
+
const n = parseFloat(any);
|
|
152
|
+
if (!Number.isFinite(n)) return "\u2014";
|
|
153
|
+
if (n >= 0.01) return `${n.toFixed(2)}%`;
|
|
154
|
+
if (n > 0) return `${n.toFixed(4)}%`;
|
|
155
|
+
return "0%";
|
|
156
|
+
}
|
|
157
|
+
async function fetchAaveV4NativeWrappedToken(chainId) {
|
|
158
|
+
const all = await fetchAaveV4Chains();
|
|
159
|
+
const c = all.find((x) => x.chainId === chainId);
|
|
160
|
+
const t = (c?.nativeWrappedToken ?? "").trim();
|
|
161
|
+
if (t && isAddress(t)) return getAddress(t);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
async function fetchAaveV4SupportedUnderlyingAddressSet(chainId) {
|
|
165
|
+
const d = await aaveV4Gql(
|
|
166
|
+
`
|
|
167
|
+
query A($r: ReservesRequest!) { reserves(request: $r) { ${RESERVES_ADDRESS_FRAGMENT} } }
|
|
168
|
+
`,
|
|
169
|
+
{
|
|
170
|
+
r: {
|
|
171
|
+
query: { chainIds: [chainId] },
|
|
172
|
+
filter: "ALL",
|
|
173
|
+
orderBy: { supplyApy: "DESC" }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
const s = /* @__PURE__ */ new Set();
|
|
178
|
+
for (const r of d.reserves ?? []) {
|
|
179
|
+
const a = (r.asset?.underlying?.address ?? "").trim();
|
|
180
|
+
if (a && isAddress(a)) s.add(a.toLowerCase());
|
|
181
|
+
}
|
|
182
|
+
return s;
|
|
183
|
+
}
|
|
184
|
+
function erc20Input(address, chainId) {
|
|
185
|
+
return { address, chainId };
|
|
186
|
+
}
|
|
187
|
+
async function fetchAaveV4ReservesForUnderlying(args) {
|
|
188
|
+
const d = await aaveV4Gql(
|
|
189
|
+
`
|
|
190
|
+
query S($r: ReservesRequest!) {
|
|
191
|
+
reserves(request: $r) {
|
|
192
|
+
id
|
|
193
|
+
onChainId
|
|
194
|
+
spoke { address name liquidationConfig { targetHealthFactor healthFactorForMaxBonus } }
|
|
195
|
+
summary {
|
|
196
|
+
supplied { amount { value decimals } }
|
|
197
|
+
borrowed { amount { value decimals } }
|
|
198
|
+
suppliable { amount { value decimals } }
|
|
199
|
+
supplyApy { normalized value }
|
|
200
|
+
}
|
|
201
|
+
userState {
|
|
202
|
+
balance { amount { value decimals } }
|
|
203
|
+
borrowable { amount { value decimals } }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
`,
|
|
208
|
+
{
|
|
209
|
+
r: {
|
|
210
|
+
query: { tokens: [erc20Input(args.underlying, args.chainId)] },
|
|
211
|
+
filter: "ALL",
|
|
212
|
+
orderBy: { supplyApy: "DESC" },
|
|
213
|
+
...args.user ? { user: args.user } : {}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
return d.reserves ?? [];
|
|
218
|
+
}
|
|
219
|
+
function pickAaveV4ReserveRowForSpoke(rows, spokeAddress) {
|
|
220
|
+
const want = getAddress(spokeAddress).toLowerCase();
|
|
221
|
+
for (const r of rows) {
|
|
222
|
+
const a = (r.spoke?.address ?? "").trim();
|
|
223
|
+
if (!a) continue;
|
|
224
|
+
try {
|
|
225
|
+
if (getAddress(a).toLowerCase() === want) return r;
|
|
226
|
+
} catch {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
function aaveV4KeyForNodeAssetRow(args) {
|
|
233
|
+
const raw = (args.contractAddress ?? "").trim();
|
|
234
|
+
try {
|
|
235
|
+
const a = getAddress(raw);
|
|
236
|
+
if (a.toLowerCase() === "0x0000000000000000000000000000000000000000" && args.nativeWrapped) {
|
|
237
|
+
return getAddress(args.nativeWrapped.trim()).toLowerCase();
|
|
238
|
+
}
|
|
239
|
+
return a.toLowerCase();
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
var aaveV4ChainTokenCache = /* @__PURE__ */ new Map();
|
|
245
|
+
function ensureAaveV4ChainTokenCache(chainId) {
|
|
246
|
+
const hit = aaveV4ChainTokenCache.get(chainId);
|
|
247
|
+
if (hit) return hit;
|
|
248
|
+
const p = (async () => {
|
|
249
|
+
const [nativeWrapped, supportedUnderlying] = await Promise.all([
|
|
250
|
+
fetchAaveV4NativeWrappedToken(chainId),
|
|
251
|
+
fetchAaveV4SupportedUnderlyingAddressSet(chainId)
|
|
252
|
+
]);
|
|
253
|
+
return { supportedUnderlying, nativeWrapped };
|
|
254
|
+
})();
|
|
255
|
+
aaveV4ChainTokenCache.set(chainId, p);
|
|
256
|
+
return p;
|
|
257
|
+
}
|
|
258
|
+
var aaveV4HubsByChain = /* @__PURE__ */ new Map();
|
|
259
|
+
var aaveV4HubReserves = /* @__PURE__ */ new Map();
|
|
260
|
+
function hubReservesKey(chainId, hub) {
|
|
261
|
+
return `v3spoke+summary:${chainId}:${hub.toLowerCase()}`;
|
|
262
|
+
}
|
|
263
|
+
async function fetchAaveV4HubsForChain(chainId) {
|
|
264
|
+
const hit = aaveV4HubsByChain.get(chainId);
|
|
265
|
+
if (hit) return hit;
|
|
266
|
+
const p = (async () => {
|
|
267
|
+
const d = await aaveV4Gql(
|
|
268
|
+
`query F($h: HubsRequest!) { hubs(request: $h) { name address chain { chainId name } } }`,
|
|
269
|
+
{ h: { query: { chainIds: [chainId] } } }
|
|
270
|
+
);
|
|
271
|
+
const out = (d.hubs ?? []).map((h) => ({
|
|
272
|
+
name: h.name,
|
|
273
|
+
address: getAddress((h.address ?? "").trim()),
|
|
274
|
+
chain: h.chain
|
|
275
|
+
}));
|
|
276
|
+
return out;
|
|
277
|
+
})();
|
|
278
|
+
aaveV4HubsByChain.set(chainId, p);
|
|
279
|
+
return p;
|
|
280
|
+
}
|
|
281
|
+
function resolveAaveV4HubForUiMarket(hubs, market, chainId) {
|
|
282
|
+
const on = hubs.filter((h) => h.chain.chainId === chainId);
|
|
283
|
+
if (!on.length) return null;
|
|
284
|
+
const find = (names) => {
|
|
285
|
+
const set = new Set(names.map((n) => n.toLowerCase()));
|
|
286
|
+
return on.find((h) => set.has((h.name ?? "").trim().toLowerCase())) ?? null;
|
|
287
|
+
};
|
|
288
|
+
if (market === "core") return find(["core"]);
|
|
289
|
+
if (market === "bluechip") return find(["prime", "bluechip", "institutional"]);
|
|
290
|
+
return find(["plus", "main", "default"]) ?? on[0] ?? null;
|
|
291
|
+
}
|
|
292
|
+
function aaveV4UiMarketIdForHubName(hubName) {
|
|
293
|
+
const n = (hubName ?? "").trim().toLowerCase();
|
|
294
|
+
if (n === "core") return "core";
|
|
295
|
+
if (n === "plus" || n === "main" || n === "default") return "main";
|
|
296
|
+
if (n === "prime" || n === "bluechip" || n === "institutional") return "bluechip";
|
|
297
|
+
return "main";
|
|
298
|
+
}
|
|
299
|
+
function aaveV4HubAutoPickTiebreakOrder(a, b) {
|
|
300
|
+
const rank = (name) => {
|
|
301
|
+
const n = (name ?? "").trim().toLowerCase();
|
|
302
|
+
if (n === "core") return 0;
|
|
303
|
+
if (n === "plus" || n === "main" || n === "default") return 1;
|
|
304
|
+
if (n === "prime" || n === "bluechip" || n === "institutional") return 2;
|
|
305
|
+
return 3;
|
|
306
|
+
};
|
|
307
|
+
const ra = rank(a.name);
|
|
308
|
+
const rb = rank(b.name);
|
|
309
|
+
if (ra !== rb) return ra - rb;
|
|
310
|
+
return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
|
|
311
|
+
}
|
|
312
|
+
function aaveV4HubReserveBorrowApyForSort(r) {
|
|
313
|
+
if (!r) return Number.POSITIVE_INFINITY;
|
|
314
|
+
const norm = parseFloat((r.summary?.borrowApy?.normalized ?? "").trim());
|
|
315
|
+
if (Number.isFinite(norm) && norm >= 0) return norm;
|
|
316
|
+
const v = parseFloat((r.summary?.borrowApy?.value ?? "").trim());
|
|
317
|
+
if (Number.isFinite(v) && v >= 0) {
|
|
318
|
+
if (v > 0 && v <= 1) return v * 100;
|
|
319
|
+
return v;
|
|
320
|
+
}
|
|
321
|
+
return Number.POSITIVE_INFINITY;
|
|
322
|
+
}
|
|
323
|
+
function aaveV4SuppliableHumanFromHubReserve(r) {
|
|
324
|
+
if (!r) return null;
|
|
325
|
+
const n = parseFloat((r.summary?.suppliable?.amount?.value ?? "").trim());
|
|
326
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
327
|
+
}
|
|
328
|
+
async function findAaveV4HubReserveForChainUnderlying(args) {
|
|
329
|
+
const onChain = args.hubs.filter((h) => h.chain.chainId === args.chainId);
|
|
330
|
+
if (!onChain.length) return null;
|
|
331
|
+
const prefer = getAddress(args.preferHub.address);
|
|
332
|
+
const order = (() => {
|
|
333
|
+
const rest = onChain.filter((h) => getAddress(h.address) !== prefer).sort(aaveV4HubAutoPickTiebreakOrder);
|
|
334
|
+
const first = onChain.find((h) => getAddress(h.address) === prefer);
|
|
335
|
+
return first ? [first, ...rest] : rest;
|
|
336
|
+
})();
|
|
337
|
+
const candidates = [];
|
|
338
|
+
for (const h of order) {
|
|
339
|
+
const rows = await fetchAaveV4HubReserves({ chainId: args.chainId, hubAddress: h.address });
|
|
340
|
+
const c = findHubReserveForUnderlying(rows, args.underlying);
|
|
341
|
+
if (c) candidates.push({ hub: h, hubReserves: rows, reserve: c });
|
|
342
|
+
}
|
|
343
|
+
if (!candidates.length) return null;
|
|
344
|
+
const noDebt = () => candidates[0];
|
|
345
|
+
const rawDebt = (args.debtUnderlying ?? "").trim();
|
|
346
|
+
if (!rawDebt || !isAddress(rawDebt)) {
|
|
347
|
+
return noDebt();
|
|
348
|
+
}
|
|
349
|
+
const debtU = getAddress(rawDebt);
|
|
350
|
+
const withDebt = candidates.map((c) => {
|
|
351
|
+
const dr = findHubReserveForUnderlying(c.hubReserves, debtU);
|
|
352
|
+
return { ...c, debtReserve: dr };
|
|
353
|
+
}).filter(
|
|
354
|
+
(x2) => x2.debtReserve != null && x2.debtReserve.canBorrow === true && x2.debtReserve.settings?.borrowable === true
|
|
355
|
+
);
|
|
356
|
+
if (!withDebt.length) {
|
|
357
|
+
return noDebt();
|
|
358
|
+
}
|
|
359
|
+
const amountRaw = (args.debtBorrowAmountHumanForLiquidity ?? "").trim().replace(/,/g, "");
|
|
360
|
+
const wantN = amountRaw && parseFloat(amountRaw) > 0 ? parseFloat(amountRaw) : null;
|
|
361
|
+
const sortKey = (a, b) => {
|
|
362
|
+
const d = aaveV4HubReserveBorrowApyForSort(a.debtReserve) - aaveV4HubReserveBorrowApyForSort(b.debtReserve);
|
|
363
|
+
if (d !== 0) return d;
|
|
364
|
+
const pa = getAddress(a.hub.address) === prefer ? 0 : 1;
|
|
365
|
+
const pb = getAddress(b.hub.address) === prefer ? 0 : 1;
|
|
366
|
+
if (pa !== pb) return pa - pb;
|
|
367
|
+
return aaveV4HubAutoPickTiebreakOrder(a.hub, b.hub);
|
|
368
|
+
};
|
|
369
|
+
if (wantN == null) {
|
|
370
|
+
withDebt.sort(sortKey);
|
|
371
|
+
const x2 = withDebt[0];
|
|
372
|
+
return { hub: x2.hub, hubReserves: x2.hubReserves, reserve: x2.reserve };
|
|
373
|
+
}
|
|
374
|
+
const sufficient = withDebt.filter((c) => {
|
|
375
|
+
const s = aaveV4SuppliableHumanFromHubReserve(c.debtReserve);
|
|
376
|
+
return s != null && s + 1e-9 >= wantN;
|
|
377
|
+
});
|
|
378
|
+
const pool = sufficient.length ? sufficient : withDebt;
|
|
379
|
+
pool.sort(sortKey);
|
|
380
|
+
const x = pool[0];
|
|
381
|
+
return { hub: x.hub, hubReserves: x.hubReserves, reserve: x.reserve };
|
|
382
|
+
}
|
|
383
|
+
async function fetchAaveV4HubReserves(args) {
|
|
384
|
+
const h = getAddress(args.hubAddress);
|
|
385
|
+
const k = hubReservesKey(args.chainId, h);
|
|
386
|
+
const hit = aaveV4HubReserves.get(k);
|
|
387
|
+
if (hit) return hit;
|
|
388
|
+
const p = (async () => {
|
|
389
|
+
const d = await aaveV4Gql(
|
|
390
|
+
`query R($r: ReservesRequest!) {
|
|
391
|
+
reserves(request: $r) {
|
|
392
|
+
canBorrow
|
|
393
|
+
canSupply
|
|
394
|
+
canUseAsCollateral
|
|
395
|
+
spoke { address }
|
|
396
|
+
settings {
|
|
397
|
+
borrowable
|
|
398
|
+
collateral
|
|
399
|
+
collateralFactor { normalized value }
|
|
400
|
+
collateralRisk { normalized value }
|
|
401
|
+
}
|
|
402
|
+
summary {
|
|
403
|
+
supplied { amount { value decimals } }
|
|
404
|
+
borrowed { amount { value decimals } }
|
|
405
|
+
suppliable { amount { value decimals } }
|
|
406
|
+
supplyApy { normalized value }
|
|
407
|
+
borrowApy { normalized value }
|
|
408
|
+
}
|
|
409
|
+
asset { underlying { address info { name symbol icon } } }
|
|
410
|
+
}
|
|
411
|
+
}`,
|
|
412
|
+
{
|
|
413
|
+
r: {
|
|
414
|
+
query: { hub: { address: h, chainId: args.chainId } },
|
|
415
|
+
filter: "ALL",
|
|
416
|
+
orderBy: { supplyApy: "DESC" }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
return d.reserves ?? [];
|
|
421
|
+
})();
|
|
422
|
+
aaveV4HubReserves.set(k, p);
|
|
423
|
+
return p;
|
|
424
|
+
}
|
|
425
|
+
function formatAaveV4PercentDisplay(normalized, value) {
|
|
426
|
+
const n = (normalized ?? "").trim();
|
|
427
|
+
if (n) {
|
|
428
|
+
const f = parseFloat(n);
|
|
429
|
+
if (!Number.isFinite(f)) return "\u2014";
|
|
430
|
+
if (f === 0) return "\u2014";
|
|
431
|
+
if (f % 1 === 0) return `${f.toFixed(0)}%`;
|
|
432
|
+
return `${f >= 10 ? f.toFixed(0) : f >= 1 ? f.toFixed(1).replace(/\.0$/, "") : f.toFixed(2).replace(/\.?0+$/, "")}%`;
|
|
433
|
+
}
|
|
434
|
+
const v = parseFloat((value ?? "").trim());
|
|
435
|
+
if (Number.isFinite(v) && v > 0) {
|
|
436
|
+
return `${Math.round(v * 100)}%`;
|
|
437
|
+
}
|
|
438
|
+
return "\u2014";
|
|
439
|
+
}
|
|
440
|
+
function formatAaveV4RiskDisplay(normalized, value) {
|
|
441
|
+
const n = parseFloat((normalized ?? "").trim());
|
|
442
|
+
if (Number.isFinite(n) && n > 0) {
|
|
443
|
+
return n >= 0.01 ? `${n.toFixed(2).replace(/\.0+$|(\.\d*?)0+$/, "$1")}%` : `${n.toFixed(4)}%`;
|
|
444
|
+
}
|
|
445
|
+
const v = parseFloat((value ?? "").trim());
|
|
446
|
+
if (Number.isFinite(v) && v > 0) {
|
|
447
|
+
return `${(v * 100).toFixed(2).replace(/\.0+$|(\.\d*?)0+$/, "$1")}%`;
|
|
448
|
+
}
|
|
449
|
+
return "Low";
|
|
450
|
+
}
|
|
451
|
+
function findHubReserveForUnderlying(list, underlying) {
|
|
452
|
+
const t = getAddress(underlying).toLowerCase();
|
|
453
|
+
return list.find((r) => (r.asset?.underlying?.address ?? "").toLowerCase() === t) ?? null;
|
|
454
|
+
}
|
|
455
|
+
function borrowableAssetsFromHubReserves(rows) {
|
|
456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
457
|
+
const out = [];
|
|
458
|
+
for (const r of rows) {
|
|
459
|
+
if (!r.canBorrow || !r.settings?.borrowable) continue;
|
|
460
|
+
const a = (r.asset?.underlying?.address ?? "").trim();
|
|
461
|
+
if (!a || !isAddress(a)) continue;
|
|
462
|
+
const lo = a.toLowerCase();
|
|
463
|
+
if (seen.has(lo)) continue;
|
|
464
|
+
seen.add(lo);
|
|
465
|
+
const info = r.asset?.underlying?.info;
|
|
466
|
+
out.push({
|
|
467
|
+
address: getAddress(a),
|
|
468
|
+
symbol: (info?.symbol ?? "\u2014").trim() || "\u2014",
|
|
469
|
+
name: (info?.name ?? "").trim() || "\u2014",
|
|
470
|
+
icon: (info?.icon ?? "").trim() || null
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
out.sort((p, q) => p.symbol.localeCompare(q.symbol, void 0, { sensitivity: "base" }));
|
|
474
|
+
return out;
|
|
475
|
+
}
|
|
476
|
+
var USER_BORROWS_HUB = `
|
|
477
|
+
query AaveV4UserBorrowsHub($r: UserBorrowsRequest!) {
|
|
478
|
+
userBorrows(request: $r) {
|
|
479
|
+
debt { amount { value } }
|
|
480
|
+
reserve {
|
|
481
|
+
asset { underlying { address } }
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
`;
|
|
486
|
+
async function fetchAaveV4UserBorrowsDebtByUnderlyingForHub(args) {
|
|
487
|
+
const u = getAddress(args.user);
|
|
488
|
+
const h = getAddress(args.hubAddress);
|
|
489
|
+
const d = await aaveV4Gql(USER_BORROWS_HUB, {
|
|
490
|
+
r: {
|
|
491
|
+
query: {
|
|
492
|
+
userHub: {
|
|
493
|
+
user: u,
|
|
494
|
+
hub: { input: { address: h, chainId: args.chainId } }
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
orderBy: { amount: "DESC" },
|
|
498
|
+
includeZeroBalances: false
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
const m = /* @__PURE__ */ new Map();
|
|
502
|
+
for (const row of d.userBorrows ?? []) {
|
|
503
|
+
const addr = (row?.reserve?.asset?.underlying?.address ?? "").trim();
|
|
504
|
+
if (!addr) continue;
|
|
505
|
+
const lo = getAddress(addr).toLowerCase();
|
|
506
|
+
const v = (row?.debt?.amount?.value ?? "").trim();
|
|
507
|
+
if (!v) continue;
|
|
508
|
+
const n = parseFloat(v);
|
|
509
|
+
if (!Number.isFinite(n) || n === 0) continue;
|
|
510
|
+
const prev = m.get(lo);
|
|
511
|
+
if (prev) {
|
|
512
|
+
const p = parseFloat(prev);
|
|
513
|
+
m.set(lo, String((Number.isFinite(p) ? p : 0) + n));
|
|
514
|
+
} else {
|
|
515
|
+
m.set(lo, v);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return m;
|
|
519
|
+
}
|
|
520
|
+
function buildAaveV4BorrowTableRowsFromHub(borrowable, hubReserves, debtByUnderlying) {
|
|
521
|
+
if (!hubReserves?.length) {
|
|
522
|
+
return borrowable.map((b) => ({
|
|
523
|
+
address: b.address,
|
|
524
|
+
symbol: b.symbol,
|
|
525
|
+
name: b.name,
|
|
526
|
+
icon: b.icon,
|
|
527
|
+
yourBorrowedDisplay: debtByUnderlying === void 0 ? "\u2014" : `0${b.symbol && b.symbol !== "\u2014" ? ` ${b.symbol}` : ""}`,
|
|
528
|
+
baseBorrowApyDisplay: "\u2014",
|
|
529
|
+
totalBorrowsDisplay: "\u2014",
|
|
530
|
+
liquidityDisplay: "\u2014"
|
|
531
|
+
}));
|
|
532
|
+
}
|
|
533
|
+
return borrowable.map((b) => {
|
|
534
|
+
const lo = b.address.toLowerCase();
|
|
535
|
+
const r = hubReserves.find((x) => (x.asset?.underlying?.address ?? "").toLowerCase() === lo) ?? null;
|
|
536
|
+
const s = r?.summary;
|
|
537
|
+
const debtRaw = debtByUnderlying?.get(lo) ?? null;
|
|
538
|
+
const symSuffix = b.symbol && b.symbol !== "\u2014" ? ` ${b.symbol}` : "";
|
|
539
|
+
return {
|
|
540
|
+
address: b.address,
|
|
541
|
+
symbol: b.symbol,
|
|
542
|
+
name: b.name,
|
|
543
|
+
icon: b.icon,
|
|
544
|
+
yourBorrowedDisplay: (() => {
|
|
545
|
+
if (debtByUnderlying === void 0) return "\u2014";
|
|
546
|
+
if (debtRaw == null || !String(debtRaw).trim()) return `0${symSuffix}`;
|
|
547
|
+
const n = parseFloat(String(debtRaw).trim());
|
|
548
|
+
if (!Number.isFinite(n) || n === 0) return `0${symSuffix}`;
|
|
549
|
+
return `${formatAaveV4AmountHumanFromApiString(debtRaw)}${symSuffix}`;
|
|
550
|
+
})(),
|
|
551
|
+
baseBorrowApyDisplay: formatAaveV4PercentDisplay(
|
|
552
|
+
s?.borrowApy?.normalized,
|
|
553
|
+
s?.borrowApy?.value
|
|
554
|
+
),
|
|
555
|
+
totalBorrowsDisplay: s ? `${formatAaveV4AmountHumanFromApiString(s.borrowed?.amount?.value)}${symSuffix}` : "\u2014",
|
|
556
|
+
liquidityDisplay: s ? `${formatAaveV4ReserveLiquidityFromSummary({
|
|
557
|
+
suppliedValue: s.supplied?.amount?.value,
|
|
558
|
+
borrowedValue: s.borrowed?.amount?.value
|
|
559
|
+
})}${symSuffix}` : "\u2014"
|
|
560
|
+
};
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/chains/evm/rpcUrl.ts
|
|
565
|
+
function isValidRpcUrl(url) {
|
|
566
|
+
const t = url.trim();
|
|
567
|
+
if (!t) return false;
|
|
568
|
+
try {
|
|
569
|
+
const u = new URL(t);
|
|
570
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
571
|
+
} catch {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/protocols/evm/aave-v4/spokeOnChain.ts
|
|
577
|
+
var spokeStaticAbi = [
|
|
578
|
+
{
|
|
579
|
+
name: "getReserveCount",
|
|
580
|
+
type: "function",
|
|
581
|
+
stateMutability: "view",
|
|
582
|
+
inputs: [],
|
|
583
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
name: "getReserve",
|
|
587
|
+
type: "function",
|
|
588
|
+
stateMutability: "view",
|
|
589
|
+
inputs: [{ name: "reserveId", type: "uint256" }],
|
|
590
|
+
outputs: [
|
|
591
|
+
{
|
|
592
|
+
name: "",
|
|
593
|
+
type: "tuple",
|
|
594
|
+
components: [
|
|
595
|
+
{ name: "underlying", type: "address" },
|
|
596
|
+
{ name: "hub", type: "address" },
|
|
597
|
+
{ name: "assetId", type: "uint16" },
|
|
598
|
+
{ name: "decimals", type: "uint8" },
|
|
599
|
+
{ name: "collateralRisk", type: "uint24" },
|
|
600
|
+
{ name: "flags", type: "uint8" },
|
|
601
|
+
{ name: "dynamicConfigKey", type: "uint32" }
|
|
602
|
+
]
|
|
603
|
+
}
|
|
604
|
+
]
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
name: "getReserveConfig",
|
|
608
|
+
type: "function",
|
|
609
|
+
stateMutability: "view",
|
|
610
|
+
inputs: [{ name: "reserveId", type: "uint256" }],
|
|
611
|
+
outputs: [
|
|
612
|
+
{
|
|
613
|
+
name: "",
|
|
614
|
+
type: "tuple",
|
|
615
|
+
components: [
|
|
616
|
+
{ name: "collateralRisk", type: "uint24" },
|
|
617
|
+
{ name: "paused", type: "bool" },
|
|
618
|
+
{ name: "frozen", type: "bool" },
|
|
619
|
+
{ name: "borrowable", type: "bool" },
|
|
620
|
+
{ name: "receiveSharesEnabled", type: "bool" }
|
|
621
|
+
]
|
|
622
|
+
}
|
|
623
|
+
]
|
|
624
|
+
}
|
|
625
|
+
];
|
|
626
|
+
async function fetchAaveV4SpokeReserveIdForUnderlying(args) {
|
|
627
|
+
const url = (args.rpcUrl ?? "").trim();
|
|
628
|
+
if (!isValidRpcUrl(url)) return null;
|
|
629
|
+
if (!Number.isFinite(args.chainId) || args.chainId < 0) return null;
|
|
630
|
+
const spoke = getAddress(args.spoke);
|
|
631
|
+
const want = getAddress(args.underlying).toLowerCase();
|
|
632
|
+
const chain = defineChain({
|
|
633
|
+
id: args.chainId,
|
|
634
|
+
name: "Rpc",
|
|
635
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
636
|
+
rpcUrls: { default: { http: [url] } }
|
|
637
|
+
});
|
|
638
|
+
const client = createPublicClient({ chain, transport: http(url) });
|
|
639
|
+
const count = await client.readContract({
|
|
640
|
+
address: spoke,
|
|
641
|
+
abi: spokeStaticAbi,
|
|
642
|
+
functionName: "getReserveCount"
|
|
643
|
+
});
|
|
644
|
+
const n = Number(count);
|
|
645
|
+
if (!Number.isFinite(n) || n < 0 || n > 256) return null;
|
|
646
|
+
for (let i = 0; i < n; i++) {
|
|
647
|
+
const r = await client.readContract({
|
|
648
|
+
address: spoke,
|
|
649
|
+
abi: spokeStaticAbi,
|
|
650
|
+
functionName: "getReserve",
|
|
651
|
+
args: [BigInt(i)]
|
|
652
|
+
});
|
|
653
|
+
if (r.underlying.toLowerCase() === want) return BigInt(i);
|
|
654
|
+
}
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
async function fetchAaveV4SpokeReserveStatusForUnderlying(args) {
|
|
658
|
+
const reserveId = await fetchAaveV4SpokeReserveIdForUnderlying(args);
|
|
659
|
+
if (reserveId == null) return null;
|
|
660
|
+
const url = (args.rpcUrl ?? "").trim();
|
|
661
|
+
const spoke = getAddress(args.spoke);
|
|
662
|
+
const chain = defineChain({
|
|
663
|
+
id: args.chainId,
|
|
664
|
+
name: "Rpc",
|
|
665
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
666
|
+
rpcUrls: { default: { http: [url] } }
|
|
667
|
+
});
|
|
668
|
+
const client = createPublicClient({ chain, transport: http(url) });
|
|
669
|
+
const cfg = await client.readContract({
|
|
670
|
+
address: spoke,
|
|
671
|
+
abi: spokeStaticAbi,
|
|
672
|
+
functionName: "getReserveConfig",
|
|
673
|
+
args: [reserveId]
|
|
674
|
+
});
|
|
675
|
+
return {
|
|
676
|
+
reserveId,
|
|
677
|
+
paused: cfg.paused,
|
|
678
|
+
frozen: cfg.frozen,
|
|
679
|
+
borrowable: cfg.borrowable,
|
|
680
|
+
receiveSharesEnabled: cfg.receiveSharesEnabled,
|
|
681
|
+
collateralRisk: Number(cfg.collateralRisk)
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/core/keygen.ts
|
|
686
|
+
function firstClientIdFromKeyGen(data) {
|
|
687
|
+
const map = data?.ClientKeys;
|
|
688
|
+
if (!map || typeof map !== "object") return null;
|
|
689
|
+
for (const v of Object.values(map)) {
|
|
690
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
691
|
+
}
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/chains/evm/txParams.ts
|
|
696
|
+
function gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit) {
|
|
697
|
+
if (chainGasLimit == null || !Number.isFinite(chainGasLimit) || chainGasLimit <= 0) {
|
|
698
|
+
return estimatedGas;
|
|
699
|
+
}
|
|
700
|
+
const cfg = BigInt(Math.floor(chainGasLimit));
|
|
701
|
+
return cfg > estimatedGas ? cfg : estimatedGas;
|
|
702
|
+
}
|
|
703
|
+
async function fetchChainFeeParams(rpcUrl, chainId) {
|
|
704
|
+
const url = rpcUrl.trim();
|
|
705
|
+
if (!url) return { isEip1559: false };
|
|
706
|
+
const chainIdNum = typeof chainId === "string" ? parseInt(chainId, 10) : chainId;
|
|
707
|
+
if (Number.isNaN(chainIdNum)) return { isEip1559: false };
|
|
708
|
+
const chain = defineChain({
|
|
709
|
+
id: chainIdNum,
|
|
710
|
+
name: "Discovery",
|
|
711
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
712
|
+
rpcUrls: { default: { http: [url] } }
|
|
713
|
+
});
|
|
714
|
+
const publicClient = createPublicClient({
|
|
715
|
+
chain,
|
|
716
|
+
transport: http(url)
|
|
717
|
+
});
|
|
718
|
+
const getGasPriceGwei = async () => {
|
|
719
|
+
const gasPriceWei = await publicClient.getGasPrice();
|
|
720
|
+
return parseFloat(formatUnits(gasPriceWei, 9));
|
|
721
|
+
};
|
|
722
|
+
try {
|
|
723
|
+
const block = await publicClient.getBlock({ blockTag: "latest" });
|
|
724
|
+
const baseFeePerGas = block?.baseFeePerGas;
|
|
725
|
+
if (baseFeePerGas == null || baseFeePerGas === void 0) {
|
|
726
|
+
const gasPriceGwei2 = await getGasPriceGwei();
|
|
727
|
+
return { isEip1559: false, gasPriceGwei: gasPriceGwei2 };
|
|
728
|
+
}
|
|
729
|
+
const baseFeeGwei = parseFloat(formatUnits(baseFeePerGas, 9));
|
|
730
|
+
let priorityFeeGwei;
|
|
731
|
+
try {
|
|
732
|
+
const priorityWei = await publicClient.estimateMaxPriorityFeePerGas();
|
|
733
|
+
priorityFeeGwei = parseFloat(formatUnits(priorityWei, 9));
|
|
734
|
+
} catch {
|
|
735
|
+
}
|
|
736
|
+
const gasPriceGwei = await getGasPriceGwei();
|
|
737
|
+
return {
|
|
738
|
+
isEip1559: true,
|
|
739
|
+
baseFeeGwei,
|
|
740
|
+
priorityFeeGwei,
|
|
741
|
+
gasPriceGwei
|
|
742
|
+
};
|
|
743
|
+
} catch {
|
|
744
|
+
try {
|
|
745
|
+
const gasPriceWei = await publicClient.getGasPrice();
|
|
746
|
+
const gasPriceGwei = parseFloat(formatUnits(gasPriceWei, 9));
|
|
747
|
+
return { isEip1559: false, gasPriceGwei };
|
|
748
|
+
} catch {
|
|
749
|
+
return { isEip1559: false };
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, floor, baseWei) {
|
|
754
|
+
let maxP = maxPriorityFeePerGas;
|
|
755
|
+
let maxF = maxFeePerGas;
|
|
756
|
+
if (baseWei > 0n && maxF < baseWei + maxP) {
|
|
757
|
+
maxF = baseWei + maxP + parseGwei("0.001");
|
|
758
|
+
}
|
|
759
|
+
if (maxF < maxP) {
|
|
760
|
+
maxF = baseWei > 0n ? baseWei + maxP + parseGwei("0.001") : maxP * 2n;
|
|
761
|
+
}
|
|
762
|
+
return { maxFeePerGas: maxF, maxPriorityFeePerGas: maxP };
|
|
763
|
+
}
|
|
764
|
+
function alignEip1559FeesWithLatestBase(maxFeePerGas, maxPriorityFeePerGas, latestBlockBaseFeeWei) {
|
|
765
|
+
return finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, null, latestBlockBaseFeeWei);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/protocols/evm/aave-v4/multisign.ts
|
|
769
|
+
var AAVE_V4_SPOKE_SUPPLY_DEFAULT_GAS_UNITS = 1200000n;
|
|
770
|
+
var AAVE_SPOKE_SUPPLY_ESTIMATE_FALLBACK = AAVE_V4_SPOKE_SUPPLY_DEFAULT_GAS_UNITS;
|
|
771
|
+
var AAVE_SPOKE_SET_USING_AS_COLLATERAL_FALLBACK = 450000n;
|
|
772
|
+
var AAVE_ERC20_APPROVE_FALLBACK = 100000n;
|
|
773
|
+
var AAVE_WETH_DEPOSIT_FALLBACK = 120000n;
|
|
774
|
+
var AAVE_V4_SPOKE_WITHDRAW_DEFAULT_GAS_UNITS = 800000n;
|
|
775
|
+
var AAVE_SPOKE_WITHDRAW_ESTIMATE_FALLBACK = AAVE_V4_SPOKE_WITHDRAW_DEFAULT_GAS_UNITS;
|
|
776
|
+
var AAVE_V4_SPOKE_BORROW_DEFAULT_GAS_UNITS = 900000n;
|
|
777
|
+
var AAVE_SPOKE_BORROW_ESTIMATE_FALLBACK = AAVE_V4_SPOKE_BORROW_DEFAULT_GAS_UNITS;
|
|
778
|
+
var AAVE_V4_SPOKE_REPAY_DEFAULT_GAS_UNITS = 1000000n;
|
|
779
|
+
var AAVE_SPOKE_REPAY_ESTIMATE_FALLBACK = AAVE_V4_SPOKE_REPAY_DEFAULT_GAS_UNITS;
|
|
780
|
+
var AAVE_MERKL_CLAIM_ESTIMATE_FALLBACK = 500000n;
|
|
781
|
+
var EULER_REUL_UNLOCK_ESTIMATE_FALLBACK = 500000n;
|
|
782
|
+
function gweiToDecimalString(n) {
|
|
783
|
+
if (!Number.isFinite(n)) return "0";
|
|
784
|
+
if (n === 0) return "0";
|
|
785
|
+
const s = String(n);
|
|
786
|
+
if (s.indexOf("e") !== -1 || s.indexOf("E") !== -1) return n.toFixed(9).replace(/\.?0+$/, "") || "0";
|
|
787
|
+
return s;
|
|
788
|
+
}
|
|
789
|
+
function txToViemStep(tx) {
|
|
790
|
+
return { to: getAddress(tx.to), data: tx.data, value: tx.value };
|
|
791
|
+
}
|
|
792
|
+
var wethDepositAbi = parseAbi(["function deposit() payable"]);
|
|
793
|
+
var erc20AllowanceAbi = parseAbi([
|
|
794
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
795
|
+
"function decimals() view returns (uint8)"
|
|
796
|
+
]);
|
|
797
|
+
var erc20ApproveAbi = parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]);
|
|
798
|
+
var spokeSupplyAbi = parseAbi(["function supply(uint256 reserveId, uint256 amount, address onBehalfOf)"]);
|
|
799
|
+
var spokeWithdrawAbi = parseAbi(["function withdraw(uint256 reserveId, uint256 amount, address onBehalfOf)"]);
|
|
800
|
+
var spokeBorrowAbi = parseAbi(["function borrow(uint256 reserveId, uint256 amount, address onBehalfOf)"]);
|
|
801
|
+
var spokeRepayAbi = parseAbi(["function repay(uint256 reserveId, uint256 amount, address onBehalfOf)"]);
|
|
802
|
+
var spokeSetUsingAsCollateralAbi = parseAbi([
|
|
803
|
+
"function setUsingAsCollateral(uint256 reserveId, bool usingAsCollateral, address onBehalfOf)"
|
|
804
|
+
]);
|
|
805
|
+
async function buildEvmMultisignBodyAaveV4DepositBatch(args) {
|
|
806
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
807
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
808
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
809
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
810
|
+
const asset = getAddress(args.asset);
|
|
811
|
+
const spoke = getAddress(args.spoke);
|
|
812
|
+
const weth = getAddress(args.nativeWrapped);
|
|
813
|
+
const executor = getAddress(args.executorAddress);
|
|
814
|
+
const onBehalf = getAddress(args.onBehalfOf);
|
|
815
|
+
if (args.isNativeIn && asset.toLowerCase() !== weth.toLowerCase()) {
|
|
816
|
+
throw new Error("Native deposit path: underlying asset must match the chain wrapped native token.");
|
|
817
|
+
}
|
|
818
|
+
const ch = defineChain({
|
|
819
|
+
id: args.chainId,
|
|
820
|
+
name: "Destination",
|
|
821
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
822
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
823
|
+
});
|
|
824
|
+
const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
|
|
825
|
+
const dec = await publicClient.readContract({
|
|
826
|
+
address: asset,
|
|
827
|
+
abi: erc20AllowanceAbi,
|
|
828
|
+
functionName: "decimals"
|
|
829
|
+
});
|
|
830
|
+
const amountWei = parseUnits(args.amountHuman, Number(dec));
|
|
831
|
+
if (amountWei === 0n) throw new Error("Amount is zero after converting with token decimals.");
|
|
832
|
+
const steps = [];
|
|
833
|
+
if (args.isNativeIn) {
|
|
834
|
+
const dataDeposit = encodeFunctionData({ abi: wethDepositAbi, functionName: "deposit", args: [] });
|
|
835
|
+
steps.push({ kind: "weth_deposit", to: weth, data: dataDeposit, value: amountWei });
|
|
836
|
+
const wethAllowance = await publicClient.readContract({
|
|
837
|
+
address: weth,
|
|
838
|
+
abi: erc20AllowanceAbi,
|
|
839
|
+
functionName: "allowance",
|
|
840
|
+
args: [executor, spoke]
|
|
841
|
+
});
|
|
842
|
+
if (wethAllowance < amountWei) {
|
|
843
|
+
if (wethAllowance > 0n) {
|
|
844
|
+
const dataReset = encodeFunctionData({
|
|
845
|
+
abi: erc20ApproveAbi,
|
|
846
|
+
functionName: "approve",
|
|
847
|
+
args: [spoke, 0n]
|
|
848
|
+
});
|
|
849
|
+
steps.push({ kind: "approve", to: weth, data: dataReset, value: 0n });
|
|
850
|
+
}
|
|
851
|
+
const dataApprove = encodeFunctionData({
|
|
852
|
+
abi: erc20ApproveAbi,
|
|
853
|
+
functionName: "approve",
|
|
854
|
+
args: [spoke, amountWei]
|
|
855
|
+
});
|
|
856
|
+
steps.push({ kind: "approve", to: weth, data: dataApprove, value: 0n });
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
const currentAllowance = await publicClient.readContract({
|
|
860
|
+
address: asset,
|
|
861
|
+
abi: erc20AllowanceAbi,
|
|
862
|
+
functionName: "allowance",
|
|
863
|
+
args: [executor, spoke]
|
|
864
|
+
});
|
|
865
|
+
if (currentAllowance < amountWei) {
|
|
866
|
+
if (currentAllowance > 0n) {
|
|
867
|
+
const dataReset = encodeFunctionData({
|
|
868
|
+
abi: erc20ApproveAbi,
|
|
869
|
+
functionName: "approve",
|
|
870
|
+
args: [spoke, 0n]
|
|
871
|
+
});
|
|
872
|
+
steps.push({ kind: "approve", to: asset, data: dataReset, value: 0n });
|
|
873
|
+
}
|
|
874
|
+
const dataApprove = encodeFunctionData({
|
|
875
|
+
abi: erc20ApproveAbi,
|
|
876
|
+
functionName: "approve",
|
|
877
|
+
args: [spoke, amountWei]
|
|
878
|
+
});
|
|
879
|
+
steps.push({ kind: "approve", to: asset, data: dataApprove, value: 0n });
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const reserveId = await fetchAaveV4SpokeReserveIdForUnderlying({
|
|
883
|
+
rpcUrl: args.rpcUrl,
|
|
884
|
+
chainId: args.chainId,
|
|
885
|
+
spoke,
|
|
886
|
+
underlying: asset
|
|
887
|
+
});
|
|
888
|
+
if (reserveId == null) {
|
|
889
|
+
throw new Error(
|
|
890
|
+
"Could not resolve Aave v4 reserve id for this asset on the Spoke (getReserve). Check RPC and the selected market."
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
const supplyData = encodeFunctionData({
|
|
894
|
+
abi: spokeSupplyAbi,
|
|
895
|
+
functionName: "supply",
|
|
896
|
+
args: [reserveId, amountWei, onBehalf]
|
|
897
|
+
});
|
|
898
|
+
steps.push({ kind: "supply", to: spoke, data: supplyData, value: 0n });
|
|
899
|
+
if (args.enableAsCollateralAfterSupply === true) {
|
|
900
|
+
const setCollateralData = encodeFunctionData({
|
|
901
|
+
abi: spokeSetUsingAsCollateralAbi,
|
|
902
|
+
functionName: "setUsingAsCollateral",
|
|
903
|
+
args: [reserveId, true, onBehalf]
|
|
904
|
+
});
|
|
905
|
+
steps.push({ kind: "set_collateral", to: spoke, data: setCollateralData, value: 0n });
|
|
906
|
+
}
|
|
907
|
+
const feeParams = await fetchChainFeeParams(args.rpcUrl, args.chainId);
|
|
908
|
+
const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
|
|
909
|
+
const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
|
|
910
|
+
const useCustomGas = args.useCustomGas;
|
|
911
|
+
const gasLimitConfig = useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
|
|
912
|
+
const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
|
|
913
|
+
const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
|
|
914
|
+
const messageHashes = [];
|
|
915
|
+
const messageRawBatch = [];
|
|
916
|
+
const proposalTxParamsBatch = [];
|
|
917
|
+
const batchMeta = [];
|
|
918
|
+
let firstTxFeePayload = {};
|
|
919
|
+
let firstDataNo0x = "";
|
|
920
|
+
const marketLabel = String(args.market);
|
|
921
|
+
for (let i = 0; i < steps.length; i++) {
|
|
922
|
+
const s = steps[i];
|
|
923
|
+
const v = txToViemStep(s);
|
|
924
|
+
const currentNonce = baseNonce + i;
|
|
925
|
+
let estimatedGas;
|
|
926
|
+
try {
|
|
927
|
+
estimatedGas = await publicClient.estimateGas({
|
|
928
|
+
to: v.to,
|
|
929
|
+
data: v.data,
|
|
930
|
+
value: v.value,
|
|
931
|
+
account: executor
|
|
932
|
+
});
|
|
933
|
+
} catch {
|
|
934
|
+
if (s.kind === "weth_deposit") estimatedGas = AAVE_WETH_DEPOSIT_FALLBACK;
|
|
935
|
+
else if (s.kind === "approve") estimatedGas = AAVE_ERC20_APPROVE_FALLBACK;
|
|
936
|
+
else if (s.kind === "set_collateral") estimatedGas = AAVE_SPOKE_SET_USING_AS_COLLATERAL_FALLBACK;
|
|
937
|
+
else estimatedGas = AAVE_SPOKE_SUPPLY_ESTIMATE_FALLBACK;
|
|
938
|
+
}
|
|
939
|
+
const gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
|
|
940
|
+
if (legacy) {
|
|
941
|
+
let gasPriceWei = await publicClient.getGasPrice();
|
|
942
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
943
|
+
gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
944
|
+
}
|
|
945
|
+
if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
|
|
946
|
+
const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
|
|
947
|
+
if (configured > gasPriceWei) gasPriceWei = configured;
|
|
948
|
+
}
|
|
949
|
+
const ser = serializeTransaction({
|
|
950
|
+
type: "legacy",
|
|
951
|
+
to: v.to,
|
|
952
|
+
data: v.data,
|
|
953
|
+
value: v.value,
|
|
954
|
+
gas: gasLimitI,
|
|
955
|
+
gasPrice: gasPriceWei,
|
|
956
|
+
nonce: currentNonce,
|
|
957
|
+
chainId: args.chainId
|
|
958
|
+
});
|
|
959
|
+
const h = keccak256(ser);
|
|
960
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
961
|
+
messageRawBatch.push(ser);
|
|
962
|
+
proposalTxParamsBatch.push({
|
|
963
|
+
nonce: currentNonce,
|
|
964
|
+
gasLimit: gasLimitI.toString(),
|
|
965
|
+
txType: "legacy",
|
|
966
|
+
gasPrice: gasPriceWei.toString()
|
|
967
|
+
});
|
|
968
|
+
if (i === 0) {
|
|
969
|
+
firstTxFeePayload = { txNonce: currentNonce, txGasLimit: gasLimitI.toString(), txGasPrice: gasPriceWei.toString() };
|
|
970
|
+
firstDataNo0x = v.data.startsWith("0x") ? v.data.slice(2) : v.data;
|
|
971
|
+
}
|
|
972
|
+
} else {
|
|
973
|
+
const fetchedBase = feeParams.baseFeeGwei ?? 0;
|
|
974
|
+
const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
|
|
975
|
+
const configuredBase = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
|
|
976
|
+
const configuredPriority = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
|
|
977
|
+
const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
|
|
978
|
+
const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
|
|
979
|
+
const baseFeeMultiplierPct = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
|
|
980
|
+
const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
|
|
981
|
+
const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
|
|
982
|
+
let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
|
|
983
|
+
let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
|
|
984
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
985
|
+
maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
986
|
+
maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
987
|
+
}
|
|
988
|
+
({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
|
|
989
|
+
maxFeePerGas,
|
|
990
|
+
maxPriorityFeePerGas,
|
|
991
|
+
latestBaseFeeWei
|
|
992
|
+
));
|
|
993
|
+
const ser = serializeTransaction({
|
|
994
|
+
type: "eip1559",
|
|
995
|
+
to: v.to,
|
|
996
|
+
data: v.data,
|
|
997
|
+
value: v.value,
|
|
998
|
+
gas: gasLimitI,
|
|
999
|
+
maxFeePerGas,
|
|
1000
|
+
maxPriorityFeePerGas,
|
|
1001
|
+
nonce: currentNonce,
|
|
1002
|
+
chainId: args.chainId
|
|
1003
|
+
});
|
|
1004
|
+
const h = keccak256(ser);
|
|
1005
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
1006
|
+
messageRawBatch.push(ser);
|
|
1007
|
+
proposalTxParamsBatch.push({
|
|
1008
|
+
nonce: currentNonce,
|
|
1009
|
+
gasLimit: gasLimitI.toString(),
|
|
1010
|
+
txType: "eip1559",
|
|
1011
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
1012
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
1013
|
+
});
|
|
1014
|
+
if (i === 0) {
|
|
1015
|
+
firstTxFeePayload = {
|
|
1016
|
+
txNonce: currentNonce,
|
|
1017
|
+
txGasLimit: gasLimitI.toString(),
|
|
1018
|
+
txMaxFeePerGas: maxFeePerGas.toString(),
|
|
1019
|
+
txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
1020
|
+
};
|
|
1021
|
+
firstDataNo0x = v.data.startsWith("0x") ? v.data.slice(2) : v.data;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (s.kind === "weth_deposit") {
|
|
1025
|
+
batchMeta.push({
|
|
1026
|
+
destinationAddress: weth,
|
|
1027
|
+
signatureText: JSON.stringify({
|
|
1028
|
+
kind: "AaveV4",
|
|
1029
|
+
name: "WETH.deposit",
|
|
1030
|
+
function: "deposit()",
|
|
1031
|
+
valueWei: amountWei.toString(),
|
|
1032
|
+
market: marketLabel,
|
|
1033
|
+
note: "Wrap native to WETH, then supply to Aave v4 (same batch)."
|
|
1034
|
+
}),
|
|
1035
|
+
evm: { type: "aave_v4_weth_deposit", version: 1, chainId: String(args.chainId) },
|
|
1036
|
+
aaveV4: { step: "weth_deposit", market: marketLabel, amountHuman: args.amountHuman, hubAsset: asset }
|
|
1037
|
+
});
|
|
1038
|
+
} else if (s.kind === "approve") {
|
|
1039
|
+
const tokenForApprove = s.to;
|
|
1040
|
+
batchMeta.push({
|
|
1041
|
+
destinationAddress: tokenForApprove,
|
|
1042
|
+
signatureText: JSON.stringify({
|
|
1043
|
+
kind: "AaveV4",
|
|
1044
|
+
name: "ERC20.approve",
|
|
1045
|
+
to: "Aave v4 spoke",
|
|
1046
|
+
function: "approve(address spender, uint256 amount)",
|
|
1047
|
+
spoke,
|
|
1048
|
+
amountHuman: args.amountHuman,
|
|
1049
|
+
note: "Allowance for this supply amount only (not unlimited)."
|
|
1050
|
+
}),
|
|
1051
|
+
evm: { type: "aave_v4_erc20_approve", version: 1, chainId: String(args.chainId) },
|
|
1052
|
+
aaveV4: { market: marketLabel, amountHuman: args.amountHuman, spoke }
|
|
1053
|
+
});
|
|
1054
|
+
} else if (s.kind === "set_collateral") {
|
|
1055
|
+
batchMeta.push({
|
|
1056
|
+
destinationAddress: spoke,
|
|
1057
|
+
signatureText: JSON.stringify({
|
|
1058
|
+
kind: "AaveV4",
|
|
1059
|
+
name: "Spoke.setUsingAsCollateral",
|
|
1060
|
+
function: "setUsingAsCollateral(uint256 reserveId, bool usingAsCollateral, address onBehalfOf)",
|
|
1061
|
+
reserveId: reserveId.toString(),
|
|
1062
|
+
usingAsCollateral: true,
|
|
1063
|
+
onBehalfOf: onBehalf,
|
|
1064
|
+
market: marketLabel,
|
|
1065
|
+
note: "Enables this supplied reserve as collateral for borrowing (after supply in the same batch)."
|
|
1066
|
+
}),
|
|
1067
|
+
evm: { type: "aave_v4_spoke_set_using_as_collateral", version: 1, chainId: String(args.chainId) },
|
|
1068
|
+
aaveV4: { market: marketLabel, asset, spoke, reserveId: reserveId.toString(), gasBuildSetCollateral: { baseGasUnits: gasLimitI.toString() } }
|
|
1069
|
+
});
|
|
1070
|
+
} else {
|
|
1071
|
+
batchMeta.push({
|
|
1072
|
+
destinationAddress: spoke,
|
|
1073
|
+
signatureText: JSON.stringify({
|
|
1074
|
+
kind: "AaveV4",
|
|
1075
|
+
name: "Spoke.supply",
|
|
1076
|
+
function: "supply(uint256 reserveId, uint256 amount, address onBehalfOf)",
|
|
1077
|
+
reserveId: reserveId.toString(),
|
|
1078
|
+
asset,
|
|
1079
|
+
onBehalfOf: onBehalf,
|
|
1080
|
+
market: marketLabel,
|
|
1081
|
+
amountHuman: args.amountHuman
|
|
1082
|
+
}),
|
|
1083
|
+
evm: { type: "aave_v4_spoke_supply", version: 1, chainId: String(args.chainId) },
|
|
1084
|
+
aaveV4: {
|
|
1085
|
+
market: marketLabel,
|
|
1086
|
+
amountHuman: args.amountHuman,
|
|
1087
|
+
asset,
|
|
1088
|
+
spoke,
|
|
1089
|
+
reserveId: reserveId.toString(),
|
|
1090
|
+
gasBuildSupply: { baseGasUnits: gasLimitI.toString() }
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
const extraPayload = { batchMeta };
|
|
1096
|
+
if (useCustomGas && args.customGasChainDetails && Object.keys(args.customGasChainDetails).length > 0) {
|
|
1097
|
+
extraPayload.customGasChainDetails = args.customGasChainDetails;
|
|
1098
|
+
}
|
|
1099
|
+
const extraJSON = JSON.stringify(extraPayload);
|
|
1100
|
+
const firstSigText = batchMeta[0].signatureText;
|
|
1101
|
+
const n = steps.length;
|
|
1102
|
+
const hasWrap = args.isNativeIn;
|
|
1103
|
+
const withCollateral = args.enableAsCollateralAfterSupply === true;
|
|
1104
|
+
const purposeSuffix = (() => {
|
|
1105
|
+
if (hasWrap) {
|
|
1106
|
+
return withCollateral ? `Aave v4: ${n}-tx batch \u2014 wrap native to WETH (if needed), approve spoke for the exact amount, supply, then setUsingAsCollateral.` : `Aave v4: ${n}-tx batch \u2014 wrap native to WETH (if needed), approve spoke for the exact amount, then supply.`;
|
|
1107
|
+
}
|
|
1108
|
+
if (n === 1) {
|
|
1109
|
+
return "Aave v4: 1-tx \u2014 supply (allowance already sufficient).";
|
|
1110
|
+
}
|
|
1111
|
+
if (withCollateral && n === 2) {
|
|
1112
|
+
return "Aave v4: 2-tx batch \u2014 supply (allowance already sufficient), then setUsingAsCollateral.";
|
|
1113
|
+
}
|
|
1114
|
+
if (withCollateral) {
|
|
1115
|
+
return `Aave v4: ${n}-tx batch \u2014 approve spoke for the exact amount, supply, then setUsingAsCollateral.`;
|
|
1116
|
+
}
|
|
1117
|
+
return `Aave v4: ${n}-tx batch \u2014 approve spoke for the exact amount, then supply.`;
|
|
1118
|
+
})();
|
|
1119
|
+
const firstValue = steps[0].value;
|
|
1120
|
+
const bodyForSign = {
|
|
1121
|
+
keyList,
|
|
1122
|
+
pubKey: ph,
|
|
1123
|
+
msgHash: messageHashes[0],
|
|
1124
|
+
msgRaw: firstDataNo0x,
|
|
1125
|
+
messageHashes,
|
|
1126
|
+
messageRawBatch,
|
|
1127
|
+
destinationChainID: String(args.chainId),
|
|
1128
|
+
destinationAddress: steps[0].to,
|
|
1129
|
+
extraJSON,
|
|
1130
|
+
signatureText: firstSigText,
|
|
1131
|
+
purpose: (() => {
|
|
1132
|
+
const t = args.purposeText.trim();
|
|
1133
|
+
return (t ? `${t}
|
|
1134
|
+
|
|
1135
|
+
` : "") + purposeSuffix;
|
|
1136
|
+
})(),
|
|
1137
|
+
...firstTxFeePayload,
|
|
1138
|
+
proposalTxParams: proposalTxParamsBatch
|
|
1139
|
+
};
|
|
1140
|
+
if (firstValue > 0n) {
|
|
1141
|
+
bodyForSign.value = firstValue.toString();
|
|
1142
|
+
}
|
|
1143
|
+
if (clientId) bodyForSign.clientId = clientId;
|
|
1144
|
+
return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
|
|
1145
|
+
}
|
|
1146
|
+
async function buildEvmMultisignBodyAaveV4SpokeWithdraw(args) {
|
|
1147
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
1148
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
1149
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
1150
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
1151
|
+
const asset = getAddress(args.underlying);
|
|
1152
|
+
const spoke = getAddress(args.spoke);
|
|
1153
|
+
const onBehalf = getAddress(args.onBehalfOf);
|
|
1154
|
+
const executor = getAddress(args.executorAddress);
|
|
1155
|
+
const ch = defineChain({
|
|
1156
|
+
id: args.chainId,
|
|
1157
|
+
name: "Destination",
|
|
1158
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1159
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1160
|
+
});
|
|
1161
|
+
const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
|
|
1162
|
+
const dec = await publicClient.readContract({
|
|
1163
|
+
address: asset,
|
|
1164
|
+
abi: erc20AllowanceAbi,
|
|
1165
|
+
functionName: "decimals"
|
|
1166
|
+
});
|
|
1167
|
+
const amountWei = parseUnits(args.amountHuman, Number(dec));
|
|
1168
|
+
if (amountWei === 0n) throw new Error("Withdraw amount is zero after converting with token decimals.");
|
|
1169
|
+
let reserveId;
|
|
1170
|
+
if (args.reserveIdOnChain != null && args.reserveIdOnChain >= 0n) {
|
|
1171
|
+
reserveId = args.reserveIdOnChain;
|
|
1172
|
+
} else {
|
|
1173
|
+
const r = await fetchAaveV4SpokeReserveIdForUnderlying({
|
|
1174
|
+
rpcUrl: args.rpcUrl,
|
|
1175
|
+
chainId: args.chainId,
|
|
1176
|
+
spoke,
|
|
1177
|
+
underlying: asset
|
|
1178
|
+
});
|
|
1179
|
+
if (r == null) {
|
|
1180
|
+
throw new Error("Could not resolve Aave v4 reserve id for this asset on the Spoke.");
|
|
1181
|
+
}
|
|
1182
|
+
reserveId = r;
|
|
1183
|
+
}
|
|
1184
|
+
const data = encodeFunctionData({
|
|
1185
|
+
abi: spokeWithdrawAbi,
|
|
1186
|
+
functionName: "withdraw",
|
|
1187
|
+
args: [reserveId, amountWei, onBehalf]
|
|
1188
|
+
});
|
|
1189
|
+
const tx = { to: spoke, data, value: 0n };
|
|
1190
|
+
return buildEvmMultisignBodyAaveV4OneStep({
|
|
1191
|
+
ph,
|
|
1192
|
+
keyList,
|
|
1193
|
+
clientId: clientId ?? void 0,
|
|
1194
|
+
chainId: args.chainId,
|
|
1195
|
+
rpcUrl: args.rpcUrl,
|
|
1196
|
+
chainDetail: args.chainDetail,
|
|
1197
|
+
useCustomGas: args.useCustomGas,
|
|
1198
|
+
customGasChainDetails: args.customGasChainDetails ?? void 0,
|
|
1199
|
+
purposeText: args.purposeText,
|
|
1200
|
+
purposeSuffix: `Aave v4: 1-tx \u2014 Spoke.withdraw for ${(args.amountHuman || "").trim() || "\u2026"} of underlying on this market (see signature text).`,
|
|
1201
|
+
executorAddress: executor,
|
|
1202
|
+
v: tx,
|
|
1203
|
+
estimateGasFallback: AAVE_SPOKE_WITHDRAW_ESTIMATE_FALLBACK,
|
|
1204
|
+
buildBatchMeta: (ctx) => ({
|
|
1205
|
+
destinationAddress: spoke,
|
|
1206
|
+
signatureText: JSON.stringify({
|
|
1207
|
+
kind: "AaveV4",
|
|
1208
|
+
name: "Spoke.withdraw",
|
|
1209
|
+
function: "withdraw(uint256 reserveId, uint256 amount, address onBehalfOf)",
|
|
1210
|
+
reserveId: reserveId.toString(),
|
|
1211
|
+
asset,
|
|
1212
|
+
onBehalfOf: onBehalf,
|
|
1213
|
+
market: args.marketLabel,
|
|
1214
|
+
amountHuman: args.amountHuman
|
|
1215
|
+
}),
|
|
1216
|
+
evm: { type: "aave_v4_spoke_withdraw", version: 1, chainId: String(args.chainId) },
|
|
1217
|
+
aaveV4: {
|
|
1218
|
+
market: args.marketLabel,
|
|
1219
|
+
amountHuman: args.amountHuman,
|
|
1220
|
+
asset,
|
|
1221
|
+
spoke,
|
|
1222
|
+
reserveId: reserveId.toString(),
|
|
1223
|
+
gasBuildWithdraw: { baseGasUnits: ctx.gasLimit.toString() }
|
|
1224
|
+
}
|
|
1225
|
+
})
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
async function buildEvmMultisignBodyAaveV4SpokeBorrow(args) {
|
|
1229
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
1230
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
1231
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
1232
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
1233
|
+
const asset = getAddress(args.underlying);
|
|
1234
|
+
const spoke = getAddress(args.spoke);
|
|
1235
|
+
const onBehalf = getAddress(args.onBehalfOf);
|
|
1236
|
+
const executor = getAddress(args.executorAddress);
|
|
1237
|
+
const ch = defineChain({
|
|
1238
|
+
id: args.chainId,
|
|
1239
|
+
name: "Destination",
|
|
1240
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1241
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1242
|
+
});
|
|
1243
|
+
const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
|
|
1244
|
+
const dec = await publicClient.readContract({
|
|
1245
|
+
address: asset,
|
|
1246
|
+
abi: erc20AllowanceAbi,
|
|
1247
|
+
functionName: "decimals"
|
|
1248
|
+
});
|
|
1249
|
+
const amountWei = parseUnits(args.amountHuman, Number(dec));
|
|
1250
|
+
if (amountWei === 0n) throw new Error("Borrow amount is zero after converting with token decimals.");
|
|
1251
|
+
let reserveId;
|
|
1252
|
+
if (args.reserveIdOnChain != null && args.reserveIdOnChain >= 0n) {
|
|
1253
|
+
reserveId = args.reserveIdOnChain;
|
|
1254
|
+
} else {
|
|
1255
|
+
const r = await fetchAaveV4SpokeReserveIdForUnderlying({
|
|
1256
|
+
rpcUrl: args.rpcUrl,
|
|
1257
|
+
chainId: args.chainId,
|
|
1258
|
+
spoke,
|
|
1259
|
+
underlying: asset
|
|
1260
|
+
});
|
|
1261
|
+
if (r == null) {
|
|
1262
|
+
throw new Error("Could not resolve Aave v4 reserve id for this asset on the Spoke.");
|
|
1263
|
+
}
|
|
1264
|
+
reserveId = r;
|
|
1265
|
+
}
|
|
1266
|
+
const data = encodeFunctionData({
|
|
1267
|
+
abi: spokeBorrowAbi,
|
|
1268
|
+
functionName: "borrow",
|
|
1269
|
+
args: [reserveId, amountWei, onBehalf]
|
|
1270
|
+
});
|
|
1271
|
+
const tx = { to: spoke, data, value: 0n };
|
|
1272
|
+
return buildEvmMultisignBodyAaveV4OneStep({
|
|
1273
|
+
ph,
|
|
1274
|
+
keyList,
|
|
1275
|
+
clientId: clientId ?? void 0,
|
|
1276
|
+
chainId: args.chainId,
|
|
1277
|
+
rpcUrl: args.rpcUrl,
|
|
1278
|
+
chainDetail: args.chainDetail,
|
|
1279
|
+
useCustomGas: args.useCustomGas,
|
|
1280
|
+
customGasChainDetails: args.customGasChainDetails ?? void 0,
|
|
1281
|
+
purposeText: args.purposeText,
|
|
1282
|
+
purposeSuffix: `Aave v4: 1-tx \u2014 Spoke.borrow for ${(args.amountHuman || "").trim() || "\u2026"} of underlying on this market (see signature text).`,
|
|
1283
|
+
executorAddress: executor,
|
|
1284
|
+
v: tx,
|
|
1285
|
+
estimateGasFallback: AAVE_SPOKE_BORROW_ESTIMATE_FALLBACK,
|
|
1286
|
+
buildBatchMeta: (ctx) => ({
|
|
1287
|
+
destinationAddress: spoke,
|
|
1288
|
+
signatureText: JSON.stringify({
|
|
1289
|
+
kind: "AaveV4",
|
|
1290
|
+
name: "Spoke.borrow",
|
|
1291
|
+
function: "borrow(uint256 reserveId, uint256 amount, address onBehalfOf)",
|
|
1292
|
+
reserveId: reserveId.toString(),
|
|
1293
|
+
asset,
|
|
1294
|
+
onBehalfOf: onBehalf,
|
|
1295
|
+
market: args.marketLabel,
|
|
1296
|
+
amountHuman: args.amountHuman
|
|
1297
|
+
}),
|
|
1298
|
+
evm: { type: "aave_v4_spoke_borrow", version: 1, chainId: String(args.chainId) },
|
|
1299
|
+
aaveV4: {
|
|
1300
|
+
market: args.marketLabel,
|
|
1301
|
+
amountHuman: args.amountHuman,
|
|
1302
|
+
asset,
|
|
1303
|
+
spoke,
|
|
1304
|
+
reserveId: reserveId.toString(),
|
|
1305
|
+
gasBuildBorrow: { baseGasUnits: ctx.gasLimit.toString() }
|
|
1306
|
+
}
|
|
1307
|
+
})
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
async function buildEvmMultisignBodyAaveV4SpokeRepay(args) {
|
|
1311
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
1312
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
1313
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
1314
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
1315
|
+
const asset = getAddress(args.underlying);
|
|
1316
|
+
const spoke = getAddress(args.spoke);
|
|
1317
|
+
const onBehalf = getAddress(args.onBehalfOf);
|
|
1318
|
+
const executor = getAddress(args.executorAddress);
|
|
1319
|
+
const ch = defineChain({
|
|
1320
|
+
id: args.chainId,
|
|
1321
|
+
name: "Destination",
|
|
1322
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1323
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1324
|
+
});
|
|
1325
|
+
const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
|
|
1326
|
+
const dec = await publicClient.readContract({
|
|
1327
|
+
address: asset,
|
|
1328
|
+
abi: erc20AllowanceAbi,
|
|
1329
|
+
functionName: "decimals"
|
|
1330
|
+
});
|
|
1331
|
+
const amountWei = parseUnits(args.amountHuman, Number(dec));
|
|
1332
|
+
if (amountWei === 0n) throw new Error("Repay amount is zero after converting with token decimals.");
|
|
1333
|
+
let reserveId;
|
|
1334
|
+
if (args.reserveIdOnChain != null && args.reserveIdOnChain >= 0n) {
|
|
1335
|
+
reserveId = args.reserveIdOnChain;
|
|
1336
|
+
} else {
|
|
1337
|
+
const r = await fetchAaveV4SpokeReserveIdForUnderlying({
|
|
1338
|
+
rpcUrl: args.rpcUrl,
|
|
1339
|
+
chainId: args.chainId,
|
|
1340
|
+
spoke,
|
|
1341
|
+
underlying: asset
|
|
1342
|
+
});
|
|
1343
|
+
if (r == null) {
|
|
1344
|
+
throw new Error("Could not resolve Aave v4 reserve id for this asset on the Spoke.");
|
|
1345
|
+
}
|
|
1346
|
+
reserveId = r;
|
|
1347
|
+
}
|
|
1348
|
+
const steps = [];
|
|
1349
|
+
const currentAllowance = await publicClient.readContract({
|
|
1350
|
+
address: asset,
|
|
1351
|
+
abi: erc20AllowanceAbi,
|
|
1352
|
+
functionName: "allowance",
|
|
1353
|
+
args: [executor, spoke]
|
|
1354
|
+
});
|
|
1355
|
+
if (currentAllowance < amountWei) {
|
|
1356
|
+
if (currentAllowance > 0n) {
|
|
1357
|
+
const dataReset = encodeFunctionData({
|
|
1358
|
+
abi: erc20ApproveAbi,
|
|
1359
|
+
functionName: "approve",
|
|
1360
|
+
args: [spoke, 0n]
|
|
1361
|
+
});
|
|
1362
|
+
steps.push({ kind: "approve", to: asset, data: dataReset, value: 0n });
|
|
1363
|
+
}
|
|
1364
|
+
const dataApprove = encodeFunctionData({
|
|
1365
|
+
abi: erc20ApproveAbi,
|
|
1366
|
+
functionName: "approve",
|
|
1367
|
+
args: [spoke, amountWei]
|
|
1368
|
+
});
|
|
1369
|
+
steps.push({ kind: "approve", to: asset, data: dataApprove, value: 0n });
|
|
1370
|
+
}
|
|
1371
|
+
const repayData = encodeFunctionData({
|
|
1372
|
+
abi: spokeRepayAbi,
|
|
1373
|
+
functionName: "repay",
|
|
1374
|
+
args: [reserveId, amountWei, onBehalf]
|
|
1375
|
+
});
|
|
1376
|
+
steps.push({ kind: "repay", to: spoke, data: repayData, value: 0n });
|
|
1377
|
+
const feeParams = await fetchChainFeeParams(args.rpcUrl, args.chainId);
|
|
1378
|
+
const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
|
|
1379
|
+
const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
|
|
1380
|
+
const useCustom = args.useCustomGas;
|
|
1381
|
+
const gasLimitConfig = useCustom && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
|
|
1382
|
+
const gasFeeMultiplier = useCustom && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
|
|
1383
|
+
const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
|
|
1384
|
+
const messageHashes = [];
|
|
1385
|
+
const messageRawBatch = [];
|
|
1386
|
+
const proposalTxParamsBatch = [];
|
|
1387
|
+
const batchMeta = [];
|
|
1388
|
+
let firstTxFeePayload = {};
|
|
1389
|
+
let firstDataNo0x = "";
|
|
1390
|
+
const marketLabel = String(args.market);
|
|
1391
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1392
|
+
const s = steps[i];
|
|
1393
|
+
const v = txToViemStep(s);
|
|
1394
|
+
const currentNonce = baseNonce + i;
|
|
1395
|
+
let estimatedGas;
|
|
1396
|
+
try {
|
|
1397
|
+
estimatedGas = await publicClient.estimateGas({
|
|
1398
|
+
to: v.to,
|
|
1399
|
+
data: v.data,
|
|
1400
|
+
value: v.value,
|
|
1401
|
+
account: executor
|
|
1402
|
+
});
|
|
1403
|
+
} catch {
|
|
1404
|
+
if (s.kind === "approve") estimatedGas = AAVE_ERC20_APPROVE_FALLBACK;
|
|
1405
|
+
else estimatedGas = AAVE_SPOKE_REPAY_ESTIMATE_FALLBACK;
|
|
1406
|
+
}
|
|
1407
|
+
const gasLimitI = useCustom ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
|
|
1408
|
+
if (legacy) {
|
|
1409
|
+
let gasPriceWei = await publicClient.getGasPrice();
|
|
1410
|
+
if (useCustom && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
1411
|
+
gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1412
|
+
}
|
|
1413
|
+
if (useCustom && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
|
|
1414
|
+
const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
|
|
1415
|
+
if (configured > gasPriceWei) gasPriceWei = configured;
|
|
1416
|
+
}
|
|
1417
|
+
const ser = serializeTransaction({
|
|
1418
|
+
type: "legacy",
|
|
1419
|
+
to: v.to,
|
|
1420
|
+
data: v.data,
|
|
1421
|
+
value: v.value,
|
|
1422
|
+
gas: gasLimitI,
|
|
1423
|
+
gasPrice: gasPriceWei,
|
|
1424
|
+
nonce: currentNonce,
|
|
1425
|
+
chainId: args.chainId
|
|
1426
|
+
});
|
|
1427
|
+
const h = keccak256(ser);
|
|
1428
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
1429
|
+
messageRawBatch.push(ser);
|
|
1430
|
+
proposalTxParamsBatch.push({
|
|
1431
|
+
nonce: currentNonce,
|
|
1432
|
+
gasLimit: gasLimitI.toString(),
|
|
1433
|
+
txType: "legacy",
|
|
1434
|
+
gasPrice: gasPriceWei.toString()
|
|
1435
|
+
});
|
|
1436
|
+
if (i === 0) {
|
|
1437
|
+
firstTxFeePayload = { txNonce: currentNonce, txGasLimit: gasLimitI.toString(), txGasPrice: gasPriceWei.toString() };
|
|
1438
|
+
firstDataNo0x = v.data.startsWith("0x") ? v.data.slice(2) : v.data;
|
|
1439
|
+
}
|
|
1440
|
+
} else {
|
|
1441
|
+
const fetchedBase = feeParams.baseFeeGwei ?? 0;
|
|
1442
|
+
const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
|
|
1443
|
+
const configuredBase = useCustom && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
|
|
1444
|
+
const configuredPriority = useCustom && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
|
|
1445
|
+
const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
|
|
1446
|
+
const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
|
|
1447
|
+
const baseFeeMultiplierPct = useCustom && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
|
|
1448
|
+
const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
|
|
1449
|
+
const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
|
|
1450
|
+
let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
|
|
1451
|
+
let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
|
|
1452
|
+
if (useCustom && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
1453
|
+
maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1454
|
+
maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1455
|
+
}
|
|
1456
|
+
({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
|
|
1457
|
+
maxFeePerGas,
|
|
1458
|
+
maxPriorityFeePerGas,
|
|
1459
|
+
latestBaseFeeWei
|
|
1460
|
+
));
|
|
1461
|
+
const ser = serializeTransaction({
|
|
1462
|
+
type: "eip1559",
|
|
1463
|
+
to: v.to,
|
|
1464
|
+
data: v.data,
|
|
1465
|
+
value: v.value,
|
|
1466
|
+
gas: gasLimitI,
|
|
1467
|
+
maxFeePerGas,
|
|
1468
|
+
maxPriorityFeePerGas,
|
|
1469
|
+
nonce: currentNonce,
|
|
1470
|
+
chainId: args.chainId
|
|
1471
|
+
});
|
|
1472
|
+
const h = keccak256(ser);
|
|
1473
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
1474
|
+
messageRawBatch.push(ser);
|
|
1475
|
+
proposalTxParamsBatch.push({
|
|
1476
|
+
nonce: currentNonce,
|
|
1477
|
+
gasLimit: gasLimitI.toString(),
|
|
1478
|
+
txType: "eip1559",
|
|
1479
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
1480
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
1481
|
+
});
|
|
1482
|
+
if (i === 0) {
|
|
1483
|
+
firstTxFeePayload = {
|
|
1484
|
+
txNonce: currentNonce,
|
|
1485
|
+
txGasLimit: gasLimitI.toString(),
|
|
1486
|
+
txMaxFeePerGas: maxFeePerGas.toString(),
|
|
1487
|
+
txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
1488
|
+
};
|
|
1489
|
+
firstDataNo0x = v.data.startsWith("0x") ? v.data.slice(2) : v.data;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (s.kind === "approve") {
|
|
1493
|
+
const tokenForApprove = s.to;
|
|
1494
|
+
batchMeta.push({
|
|
1495
|
+
destinationAddress: tokenForApprove,
|
|
1496
|
+
signatureText: JSON.stringify({
|
|
1497
|
+
kind: "AaveV4",
|
|
1498
|
+
name: "ERC20.approve (repay)",
|
|
1499
|
+
to: "Aave v4 spoke",
|
|
1500
|
+
function: "approve(address spender, uint256 amount)",
|
|
1501
|
+
spoke,
|
|
1502
|
+
amountHuman: args.amountHuman,
|
|
1503
|
+
note: "Allowance to repay this amount (or reset+approve to exact amount)."
|
|
1504
|
+
}),
|
|
1505
|
+
evm: { type: "aave_v4_erc20_approve", version: 1, chainId: String(args.chainId) },
|
|
1506
|
+
aaveV4: { market: marketLabel, amountHuman: args.amountHuman, spoke, step: "repay_prepare" }
|
|
1507
|
+
});
|
|
1508
|
+
} else {
|
|
1509
|
+
batchMeta.push({
|
|
1510
|
+
destinationAddress: spoke,
|
|
1511
|
+
signatureText: JSON.stringify({
|
|
1512
|
+
kind: "AaveV4",
|
|
1513
|
+
name: "Spoke.repay",
|
|
1514
|
+
function: "repay(uint256 reserveId, uint256 amount, address onBehalfOf)",
|
|
1515
|
+
reserveId: reserveId.toString(),
|
|
1516
|
+
asset,
|
|
1517
|
+
onBehalfOf: onBehalf,
|
|
1518
|
+
market: marketLabel,
|
|
1519
|
+
amountHuman: args.amountHuman
|
|
1520
|
+
}),
|
|
1521
|
+
evm: { type: "aave_v4_spoke_repay", version: 1, chainId: String(args.chainId) },
|
|
1522
|
+
aaveV4: {
|
|
1523
|
+
market: marketLabel,
|
|
1524
|
+
amountHuman: args.amountHuman,
|
|
1525
|
+
asset,
|
|
1526
|
+
spoke,
|
|
1527
|
+
reserveId: reserveId.toString(),
|
|
1528
|
+
gasBuildRepay: { baseGasUnits: gasLimitI.toString() }
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
const extraPayload = { batchMeta };
|
|
1534
|
+
if (useCustom && args.customGasChainDetails && Object.keys(args.customGasChainDetails).length > 0) {
|
|
1535
|
+
extraPayload.customGasChainDetails = args.customGasChainDetails;
|
|
1536
|
+
}
|
|
1537
|
+
const extraJSON = JSON.stringify(extraPayload);
|
|
1538
|
+
const firstSigText = batchMeta[0].signatureText;
|
|
1539
|
+
const n = steps.length;
|
|
1540
|
+
const purposeSuffix = `Aave v4: ${n}-tx batch \u2014 approve spoke if needed, then repay ${(args.amountHuman || "").trim() || "\u2026"} of debt.`;
|
|
1541
|
+
const firstValue = steps[0].value;
|
|
1542
|
+
const bodyForSign = {
|
|
1543
|
+
keyList,
|
|
1544
|
+
pubKey: ph,
|
|
1545
|
+
msgHash: messageHashes[0],
|
|
1546
|
+
msgRaw: firstDataNo0x,
|
|
1547
|
+
messageHashes,
|
|
1548
|
+
messageRawBatch,
|
|
1549
|
+
destinationChainID: String(args.chainId),
|
|
1550
|
+
destinationAddress: steps[0].to,
|
|
1551
|
+
extraJSON,
|
|
1552
|
+
signatureText: firstSigText,
|
|
1553
|
+
purpose: (() => {
|
|
1554
|
+
const t = args.purposeText.trim();
|
|
1555
|
+
return (t ? `${t}
|
|
1556
|
+
|
|
1557
|
+
` : "") + purposeSuffix;
|
|
1558
|
+
})(),
|
|
1559
|
+
...firstTxFeePayload,
|
|
1560
|
+
proposalTxParams: proposalTxParamsBatch
|
|
1561
|
+
};
|
|
1562
|
+
if (firstValue > 0n) {
|
|
1563
|
+
bodyForSign.value = firstValue.toString();
|
|
1564
|
+
}
|
|
1565
|
+
if (clientId) bodyForSign.clientId = clientId;
|
|
1566
|
+
return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
|
|
1567
|
+
}
|
|
1568
|
+
async function buildEvmMultisignBodyAaveV4MerklClaimRewards(args) {
|
|
1569
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
1570
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
1571
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
1572
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
1573
|
+
const to = getAddress(args.to);
|
|
1574
|
+
const executor = getAddress(args.executorAddress);
|
|
1575
|
+
return buildEvmMultisignBodyAaveV4OneStep({
|
|
1576
|
+
ph,
|
|
1577
|
+
keyList,
|
|
1578
|
+
clientId: clientId ?? void 0,
|
|
1579
|
+
chainId: args.chainId,
|
|
1580
|
+
rpcUrl: args.rpcUrl,
|
|
1581
|
+
chainDetail: args.chainDetail,
|
|
1582
|
+
useCustomGas: args.useCustomGas,
|
|
1583
|
+
customGasChainDetails: args.customGasChainDetails ?? void 0,
|
|
1584
|
+
purposeText: args.purposeText,
|
|
1585
|
+
purposeSuffix: `Aave v4: 1-tx \u2014 Merkl claim rewards (${args.rewardIdCount} id(s), see calldata in signature text).`,
|
|
1586
|
+
executorAddress: executor,
|
|
1587
|
+
v: { to, data: args.data, value: args.valueWei },
|
|
1588
|
+
estimateGasFallback: AAVE_MERKL_CLAIM_ESTIMATE_FALLBACK,
|
|
1589
|
+
buildBatchMeta: (ctx) => ({
|
|
1590
|
+
destinationAddress: to,
|
|
1591
|
+
signatureText: JSON.stringify({
|
|
1592
|
+
kind: "AaveV4",
|
|
1593
|
+
name: "claimRewards (Merkl)",
|
|
1594
|
+
rewardIdCount: args.rewardIdCount,
|
|
1595
|
+
chainId: args.chainId,
|
|
1596
|
+
valueWei: args.valueWei.toString(),
|
|
1597
|
+
note: "From Aave v4 claimRewards request."
|
|
1598
|
+
}),
|
|
1599
|
+
evm: { type: "aave_v4_merkl_claim_rewards", version: 1, chainId: String(args.chainId) },
|
|
1600
|
+
aaveV4: { step: "merkl_claim", rewardIdCount: args.rewardIdCount, gasBuildClaim: { baseGasUnits: ctx.gasLimit.toString() } }
|
|
1601
|
+
})
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
async function buildEvmMultisignBodyEulerV2MerklDistributorClaim(args) {
|
|
1605
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
1606
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
1607
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
1608
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
1609
|
+
const to = getAddress(args.to);
|
|
1610
|
+
const executor = getAddress(args.executorAddress);
|
|
1611
|
+
return buildEvmMultisignBodyAaveV4OneStep({
|
|
1612
|
+
ph,
|
|
1613
|
+
keyList,
|
|
1614
|
+
clientId: clientId ?? void 0,
|
|
1615
|
+
chainId: args.chainId,
|
|
1616
|
+
rpcUrl: args.rpcUrl,
|
|
1617
|
+
chainDetail: args.chainDetail,
|
|
1618
|
+
useCustomGas: args.useCustomGas,
|
|
1619
|
+
customGasChainDetails: args.customGasChainDetails ?? void 0,
|
|
1620
|
+
purposeText: args.purposeText,
|
|
1621
|
+
purposeSuffix: `Euler v2: 1-tx \u2014 Merkl distributor claim (${args.claimLeafCount} reward(s), see calldata in signature text).`,
|
|
1622
|
+
executorAddress: executor,
|
|
1623
|
+
v: { to, data: args.data, value: args.valueWei },
|
|
1624
|
+
estimateGasFallback: AAVE_MERKL_CLAIM_ESTIMATE_FALLBACK,
|
|
1625
|
+
buildBatchMeta: (ctx) => ({
|
|
1626
|
+
destinationAddress: to,
|
|
1627
|
+
signatureText: JSON.stringify({
|
|
1628
|
+
kind: "EulerV2",
|
|
1629
|
+
name: "Merkl Distributor claim",
|
|
1630
|
+
claimLeafCount: args.claimLeafCount,
|
|
1631
|
+
chainId: args.chainId,
|
|
1632
|
+
valueWei: args.valueWei.toString(),
|
|
1633
|
+
note: "Merkl API proofs; Distributor.claim(users,tokens,amounts,proofs)."
|
|
1634
|
+
}),
|
|
1635
|
+
evm: { type: "euler_v2_merkl_distributor_claim", version: 1, chainId: String(args.chainId) },
|
|
1636
|
+
eulerV2: { step: "merkl_distributor_claim", claimLeafCount: args.claimLeafCount, gasBuildClaim: { baseGasUnits: ctx.gasLimit.toString() } }
|
|
1637
|
+
})
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
async function buildEvmMultisignBodyEulerV2ReulUnlock(args) {
|
|
1641
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
1642
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
1643
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
1644
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
1645
|
+
const to = getAddress(args.to);
|
|
1646
|
+
const executor = getAddress(args.executorAddress);
|
|
1647
|
+
const lossLabel = args.allowRemainderLoss ? "early unlock (remainder to receiver)" : "vested only";
|
|
1648
|
+
return buildEvmMultisignBodyAaveV4OneStep({
|
|
1649
|
+
ph,
|
|
1650
|
+
keyList,
|
|
1651
|
+
clientId: clientId ?? void 0,
|
|
1652
|
+
chainId: args.chainId,
|
|
1653
|
+
rpcUrl: args.rpcUrl,
|
|
1654
|
+
chainDetail: args.chainDetail,
|
|
1655
|
+
useCustomGas: args.useCustomGas,
|
|
1656
|
+
customGasChainDetails: args.customGasChainDetails ?? void 0,
|
|
1657
|
+
purposeText: args.purposeText,
|
|
1658
|
+
purposeSuffix: `Euler v2: 1-tx \u2014 rEUL unlock (${args.lockTimestampCount} lock(s), ${lossLabel}).`,
|
|
1659
|
+
executorAddress: executor,
|
|
1660
|
+
v: { to, data: args.data, value: args.valueWei },
|
|
1661
|
+
estimateGasFallback: EULER_REUL_UNLOCK_ESTIMATE_FALLBACK,
|
|
1662
|
+
buildBatchMeta: (ctx) => ({
|
|
1663
|
+
destinationAddress: to,
|
|
1664
|
+
signatureText: JSON.stringify({
|
|
1665
|
+
kind: "EulerV2",
|
|
1666
|
+
name: "rEUL unlock (RewardToken)",
|
|
1667
|
+
lockTimestampCount: args.lockTimestampCount,
|
|
1668
|
+
allowRemainderLoss: args.allowRemainderLoss,
|
|
1669
|
+
chainId: args.chainId,
|
|
1670
|
+
valueWei: args.valueWei.toString(),
|
|
1671
|
+
note: "RewardToken.withdrawToByLockTimestamps(account, lockTimestamps, allowRemainderLoss)."
|
|
1672
|
+
}),
|
|
1673
|
+
evm: { type: "euler_v2_reul_unlock", version: 1, chainId: String(args.chainId) },
|
|
1674
|
+
eulerV2: {
|
|
1675
|
+
step: "reul_unlock",
|
|
1676
|
+
lockTimestampCount: args.lockTimestampCount,
|
|
1677
|
+
allowRemainderLoss: args.allowRemainderLoss,
|
|
1678
|
+
gasBuildClaim: { baseGasUnits: ctx.gasLimit.toString() }
|
|
1679
|
+
}
|
|
1680
|
+
})
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
async function buildEvmMultisignBodyAaveV4OneStep(args) {
|
|
1684
|
+
const ch = defineChain({
|
|
1685
|
+
id: args.chainId,
|
|
1686
|
+
name: "Destination",
|
|
1687
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
1688
|
+
rpcUrls: { default: { http: [args.rpcUrl] } }
|
|
1689
|
+
});
|
|
1690
|
+
const publicClient = createPublicClient({ chain: ch, transport: http(args.rpcUrl) });
|
|
1691
|
+
const v = args.v;
|
|
1692
|
+
const feeParams = await fetchChainFeeParams(args.rpcUrl, args.chainId);
|
|
1693
|
+
const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
|
|
1694
|
+
const latestBaseFeeWeiOneStep = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
|
|
1695
|
+
const useCustomGas = args.useCustomGas;
|
|
1696
|
+
const gasLimitConfig = useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
|
|
1697
|
+
const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
|
|
1698
|
+
const baseNonce = await publicClient.getTransactionCount({ address: args.executorAddress, blockTag: "pending" });
|
|
1699
|
+
const currentNonce = baseNonce;
|
|
1700
|
+
let estimatedGas;
|
|
1701
|
+
try {
|
|
1702
|
+
estimatedGas = await publicClient.estimateGas({
|
|
1703
|
+
to: v.to,
|
|
1704
|
+
data: v.data,
|
|
1705
|
+
value: v.value,
|
|
1706
|
+
account: args.executorAddress
|
|
1707
|
+
});
|
|
1708
|
+
} catch {
|
|
1709
|
+
estimatedGas = args.estimateGasFallback;
|
|
1710
|
+
}
|
|
1711
|
+
const gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
|
|
1712
|
+
const batchMeta0 = args.buildBatchMeta({ gasLimit: gasLimitI });
|
|
1713
|
+
let firstTxFeePayload = {};
|
|
1714
|
+
let firstDataNo0x = "";
|
|
1715
|
+
const messageHashes = [];
|
|
1716
|
+
const messageRawBatch = [];
|
|
1717
|
+
const proposalTxParamsBatch = [];
|
|
1718
|
+
if (legacy) {
|
|
1719
|
+
let gasPriceWei = await publicClient.getGasPrice();
|
|
1720
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
1721
|
+
gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1722
|
+
}
|
|
1723
|
+
if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
|
|
1724
|
+
const configured = parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
|
|
1725
|
+
if (configured > gasPriceWei) gasPriceWei = configured;
|
|
1726
|
+
}
|
|
1727
|
+
const ser = serializeTransaction({
|
|
1728
|
+
type: "legacy",
|
|
1729
|
+
to: v.to,
|
|
1730
|
+
data: v.data,
|
|
1731
|
+
value: v.value,
|
|
1732
|
+
gas: gasLimitI,
|
|
1733
|
+
gasPrice: gasPriceWei,
|
|
1734
|
+
nonce: currentNonce,
|
|
1735
|
+
chainId: args.chainId
|
|
1736
|
+
});
|
|
1737
|
+
const h = keccak256(ser);
|
|
1738
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
1739
|
+
messageRawBatch.push(ser);
|
|
1740
|
+
proposalTxParamsBatch.push({
|
|
1741
|
+
nonce: currentNonce,
|
|
1742
|
+
gasLimit: gasLimitI.toString(),
|
|
1743
|
+
txType: "legacy",
|
|
1744
|
+
gasPrice: gasPriceWei.toString()
|
|
1745
|
+
});
|
|
1746
|
+
firstTxFeePayload = { txNonce: currentNonce, txGasLimit: gasLimitI.toString(), txGasPrice: gasPriceWei.toString() };
|
|
1747
|
+
firstDataNo0x = v.data.startsWith("0x") ? v.data.slice(2) : v.data;
|
|
1748
|
+
} else {
|
|
1749
|
+
const fetchedBase = feeParams.baseFeeGwei ?? 0;
|
|
1750
|
+
const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
|
|
1751
|
+
const configuredBase = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
|
|
1752
|
+
const configuredPriority = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
|
|
1753
|
+
const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
|
|
1754
|
+
const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
|
|
1755
|
+
const baseFeeMultiplierPct = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
|
|
1756
|
+
const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
|
|
1757
|
+
const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
|
|
1758
|
+
let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
|
|
1759
|
+
let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
|
|
1760
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
1761
|
+
maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1762
|
+
maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
1763
|
+
}
|
|
1764
|
+
({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
|
|
1765
|
+
maxFeePerGas,
|
|
1766
|
+
maxPriorityFeePerGas,
|
|
1767
|
+
latestBaseFeeWeiOneStep
|
|
1768
|
+
));
|
|
1769
|
+
const ser = serializeTransaction({
|
|
1770
|
+
type: "eip1559",
|
|
1771
|
+
to: v.to,
|
|
1772
|
+
data: v.data,
|
|
1773
|
+
value: v.value,
|
|
1774
|
+
gas: gasLimitI,
|
|
1775
|
+
maxFeePerGas,
|
|
1776
|
+
maxPriorityFeePerGas,
|
|
1777
|
+
nonce: currentNonce,
|
|
1778
|
+
chainId: args.chainId
|
|
1779
|
+
});
|
|
1780
|
+
const h = keccak256(ser);
|
|
1781
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
1782
|
+
messageRawBatch.push(ser);
|
|
1783
|
+
proposalTxParamsBatch.push({
|
|
1784
|
+
nonce: currentNonce,
|
|
1785
|
+
gasLimit: gasLimitI.toString(),
|
|
1786
|
+
txType: "eip1559",
|
|
1787
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
1788
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
1789
|
+
});
|
|
1790
|
+
firstTxFeePayload = {
|
|
1791
|
+
txNonce: currentNonce,
|
|
1792
|
+
txGasLimit: gasLimitI.toString(),
|
|
1793
|
+
txMaxFeePerGas: maxFeePerGas.toString(),
|
|
1794
|
+
txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
1795
|
+
};
|
|
1796
|
+
firstDataNo0x = v.data.startsWith("0x") ? v.data.slice(2) : v.data;
|
|
1797
|
+
}
|
|
1798
|
+
const extraPayload = { batchMeta: [batchMeta0] };
|
|
1799
|
+
if (useCustomGas && args.customGasChainDetails && Object.keys(args.customGasChainDetails).length > 0) {
|
|
1800
|
+
extraPayload.customGasChainDetails = args.customGasChainDetails;
|
|
1801
|
+
}
|
|
1802
|
+
const extraJSON = JSON.stringify(extraPayload);
|
|
1803
|
+
const firstSigText = batchMeta0.signatureText;
|
|
1804
|
+
const bodyForSign = {
|
|
1805
|
+
keyList: args.keyList,
|
|
1806
|
+
pubKey: args.ph,
|
|
1807
|
+
msgHash: messageHashes[0],
|
|
1808
|
+
msgRaw: firstDataNo0x,
|
|
1809
|
+
messageHashes,
|
|
1810
|
+
messageRawBatch,
|
|
1811
|
+
destinationChainID: String(args.chainId),
|
|
1812
|
+
destinationAddress: v.to,
|
|
1813
|
+
extraJSON,
|
|
1814
|
+
signatureText: firstSigText,
|
|
1815
|
+
purpose: (() => {
|
|
1816
|
+
const t = args.purposeText.trim();
|
|
1817
|
+
return (t ? `${t}
|
|
1818
|
+
|
|
1819
|
+
` : "") + args.purposeSuffix;
|
|
1820
|
+
})(),
|
|
1821
|
+
...firstTxFeePayload,
|
|
1822
|
+
proposalTxParams: proposalTxParamsBatch
|
|
1823
|
+
};
|
|
1824
|
+
if (v.value > 0n) {
|
|
1825
|
+
bodyForSign.value = v.value.toString();
|
|
1826
|
+
}
|
|
1827
|
+
if (args.clientId) bodyForSign.clientId = args.clientId;
|
|
1828
|
+
return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
|
|
1829
|
+
}
|
|
1830
|
+
var MIN_AAVE_V4_DEPOSIT_GAS_EXEC = 400000n;
|
|
1831
|
+
var AAVE_V4_EVM_TYPES = /* @__PURE__ */ new Set([
|
|
1832
|
+
"aave_v4_weth_deposit",
|
|
1833
|
+
"aave_v4_erc20_approve",
|
|
1834
|
+
"aave_v4_spoke_supply",
|
|
1835
|
+
"aave_v4_spoke_set_using_as_collateral",
|
|
1836
|
+
"aave_v4_spoke_withdraw",
|
|
1837
|
+
"aave_v4_spoke_borrow",
|
|
1838
|
+
"aave_v4_spoke_repay",
|
|
1839
|
+
"aave_v4_merkl_claim_rewards"
|
|
1840
|
+
]);
|
|
1841
|
+
function parseExtraJsonObject(detail) {
|
|
1842
|
+
if (!detail) return null;
|
|
1843
|
+
const raw = detail.ExtraJSON ?? detail.extraJSON;
|
|
1844
|
+
if (raw == null) return null;
|
|
1845
|
+
if (typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
1846
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
1847
|
+
try {
|
|
1848
|
+
const p = JSON.parse(raw);
|
|
1849
|
+
if (p && typeof p === "object" && !Array.isArray(p)) return p;
|
|
1850
|
+
} catch {
|
|
1851
|
+
return null;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
function parseOptionalGasLimitString(raw) {
|
|
1857
|
+
if (raw == null) return null;
|
|
1858
|
+
const s = String(raw).trim();
|
|
1859
|
+
if (!s) return null;
|
|
1860
|
+
try {
|
|
1861
|
+
if (/^0x[0-9a-fA-F]+$/.test(s)) return BigInt(s);
|
|
1862
|
+
return BigInt(s);
|
|
1863
|
+
} catch {
|
|
1864
|
+
return null;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
function isAaveV4DepositEvmSignRequest(detail, batchIndex) {
|
|
1868
|
+
const ex = parseExtraJsonObject(detail);
|
|
1869
|
+
if (!ex) return false;
|
|
1870
|
+
const idx = batchIndex != null && batchIndex >= 0 ? batchIndex : 0;
|
|
1871
|
+
const bm = ex.batchMeta;
|
|
1872
|
+
if (Array.isArray(bm) && bm[idx] && typeof bm[idx] === "object" && !Array.isArray(bm[idx])) {
|
|
1873
|
+
const evm0 = bm[idx].evm;
|
|
1874
|
+
if (evm0 && typeof evm0 === "object" && !Array.isArray(evm0)) {
|
|
1875
|
+
if (AAVE_V4_EVM_TYPES.has(String(evm0.type ?? ""))) return true;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (batchIndex != null && batchIndex >= 0) return false;
|
|
1879
|
+
const evm = ex.evm;
|
|
1880
|
+
if (evm && typeof evm === "object" && !Array.isArray(evm)) {
|
|
1881
|
+
if (AAVE_V4_EVM_TYPES.has(String(evm.type ?? ""))) return true;
|
|
1882
|
+
}
|
|
1883
|
+
return false;
|
|
1884
|
+
}
|
|
1885
|
+
function resolveAaveV4DepositGasUnitsFromSignRequest(detail, batchIndex) {
|
|
1886
|
+
if (!detail) return null;
|
|
1887
|
+
const index = batchIndex != null && batchIndex >= 0 ? batchIndex : 0;
|
|
1888
|
+
const propBatch = detail.proposal_tx_params ?? detail.proposalTxParams ?? detail.ProposalTxParams;
|
|
1889
|
+
if (Array.isArray(propBatch) && propBatch.length > index) {
|
|
1890
|
+
const row = propBatch[index];
|
|
1891
|
+
if (row && typeof row === "object" && !Array.isArray(row)) {
|
|
1892
|
+
const r = row;
|
|
1893
|
+
const gl = parseOptionalGasLimitString(
|
|
1894
|
+
r.gas_limit ?? r.gasLimit ?? r.GasLimit
|
|
1895
|
+
);
|
|
1896
|
+
if (gl != null && gl > 0n) return gl;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (batchIndex == null || batchIndex === 0) {
|
|
1900
|
+
const tp = detail.txParams ?? detail.TxParams;
|
|
1901
|
+
if (tp && typeof tp === "object") {
|
|
1902
|
+
const gl = parseOptionalGasLimitString(
|
|
1903
|
+
tp.gasLimit ?? tp.GasLimit ?? tp.txGasLimit
|
|
1904
|
+
);
|
|
1905
|
+
if (gl != null && gl > 0n) return gl;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
const ex = parseExtraJsonObject(detail);
|
|
1909
|
+
const bm = ex?.batchMeta;
|
|
1910
|
+
if (Array.isArray(bm) && bm[index] && typeof bm[index] === "object" && !Array.isArray(bm[index])) {
|
|
1911
|
+
const a4 = bm[index].aaveV4;
|
|
1912
|
+
if (a4 && typeof a4 === "object") {
|
|
1913
|
+
const gs = a4.gasBuildSupply ?? a4.gasBuildSetCollateral ?? a4.gasBuildWithdraw ?? a4.gasBuildBorrow ?? a4.gasBuildRepay ?? a4.gasBuildClaim;
|
|
1914
|
+
const base = parseOptionalGasLimitString(gs?.baseGasUnits);
|
|
1915
|
+
if (base != null && base > 0n) return base;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
return null;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// src/protocols/evm/aave-v4/index.ts
|
|
1922
|
+
var AAVE_V4_PROTOCOL_ID = "aave-v4";
|
|
1923
|
+
var aaveV4ProtocolModule = {
|
|
1924
|
+
id: AAVE_V4_PROTOCOL_ID,
|
|
1925
|
+
chainCategory: "evm",
|
|
1926
|
+
isChainSupported(ctx) {
|
|
1927
|
+
return ctx.chainCategory === "evm";
|
|
1928
|
+
},
|
|
1929
|
+
isTokenSupported(token) {
|
|
1930
|
+
return token.category === "evm" && (token.kind === "native" || token.kind === "erc20");
|
|
1931
|
+
},
|
|
1932
|
+
actions: [
|
|
1933
|
+
{ id: "aave-v4.deposit", protocolId: AAVE_V4_PROTOCOL_ID, chainCategory: "evm", description: "Supply to Aave v4 Spoke", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1934
|
+
{ id: "aave-v4.withdraw", protocolId: AAVE_V4_PROTOCOL_ID, chainCategory: "evm", description: "Withdraw from Spoke", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1935
|
+
{ id: "aave-v4.borrow", protocolId: AAVE_V4_PROTOCOL_ID, chainCategory: "evm", description: "Borrow from Spoke", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
1936
|
+
{ id: "aave-v4.repay", protocolId: AAVE_V4_PROTOCOL_ID, chainCategory: "evm", description: "Repay Spoke debt", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} }
|
|
1937
|
+
]
|
|
1938
|
+
};
|
|
1939
|
+
registerProtocolModule(aaveV4ProtocolModule);
|
|
1940
|
+
|
|
1941
|
+
export { AAVE_V4_GRAPHQL_URL, AAVE_V4_PROTOCOL_ID, AAVE_V4_SPOKE_BORROW_DEFAULT_GAS_UNITS, AAVE_V4_SPOKE_REPAY_DEFAULT_GAS_UNITS, AAVE_V4_SPOKE_SUPPLY_DEFAULT_GAS_UNITS, AAVE_V4_SPOKE_WITHDRAW_DEFAULT_GAS_UNITS, MIN_AAVE_V4_DEPOSIT_GAS_EXEC, aaveV4Gql, aaveV4KeyForNodeAssetRow, aaveV4ProtocolModule, aaveV4UiMarketIdForHubName, aggregateV4SupplyDisplay, borrowableAssetsFromHubReserves, buildAaveV4BorrowTableRowsFromHub, buildEvmMultisignBodyAaveV4DepositBatch, buildEvmMultisignBodyAaveV4MerklClaimRewards, buildEvmMultisignBodyAaveV4SpokeBorrow, buildEvmMultisignBodyAaveV4SpokeRepay, buildEvmMultisignBodyAaveV4SpokeWithdraw, buildEvmMultisignBodyEulerV2MerklDistributorClaim, buildEvmMultisignBodyEulerV2ReulUnlock, ensureAaveV4ChainTokenCache, fetchAaveV4Chains, fetchAaveV4HubReserves, fetchAaveV4HubsForChain, fetchAaveV4NativeWrappedToken, fetchAaveV4ReservesForUnderlying, fetchAaveV4SpokeReserveIdForUnderlying, fetchAaveV4SpokeReserveStatusForUnderlying, fetchAaveV4SupportedUnderlyingAddressSet, fetchAaveV4UserBorrowsDebtByUnderlyingForHub, findAaveV4HubReserveForChainUnderlying, findHubReserveForUnderlying, formatAaveV4AmountHumanFromApiString, formatAaveV4PercentDisplay, formatAaveV4ReserveLiquidityFromSummary, formatAaveV4RiskDisplay, formatAaveV4TokenAmount, isAaveV4DepositEvmSignRequest, loadAaveV4SupportedChainIdsFromV4Api, pickAaveV4ReserveRowForSpoke, resolveAaveV4DepositGasUnitsFromSignRequest, resolveAaveV4HubForUiMarket };
|
|
1942
|
+
//# sourceMappingURL=index.js.map
|
|
1943
|
+
//# sourceMappingURL=index.js.map
|