@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,965 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var viem = require('viem');
|
|
4
|
+
|
|
5
|
+
// src/core/registry.ts
|
|
6
|
+
var modules = [];
|
|
7
|
+
function registerProtocolModule(mod) {
|
|
8
|
+
const existing = modules.findIndex((m) => m.id === mod.id);
|
|
9
|
+
if (existing >= 0) {
|
|
10
|
+
modules[existing] = mod;
|
|
11
|
+
} else {
|
|
12
|
+
modules.push(mod);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/chains/evm/chainIdParse.ts
|
|
17
|
+
function parseEvmChainIdToNumber(chainId) {
|
|
18
|
+
if (chainId == null) return Number.NaN;
|
|
19
|
+
if (typeof chainId === "bigint") {
|
|
20
|
+
const n = Number(chainId);
|
|
21
|
+
return Number.isSafeInteger(n) && n >= 0 ? n : Number.NaN;
|
|
22
|
+
}
|
|
23
|
+
if (typeof chainId === "number") {
|
|
24
|
+
return Number.isInteger(chainId) && chainId >= 0 ? chainId : Number.NaN;
|
|
25
|
+
}
|
|
26
|
+
const t = String(chainId).trim();
|
|
27
|
+
if (!t) return Number.NaN;
|
|
28
|
+
const low = t.toLowerCase();
|
|
29
|
+
if (low.startsWith("eip155:")) {
|
|
30
|
+
const rest = t.slice("eip155:".length).trim();
|
|
31
|
+
const n = Number.parseInt(rest, 10);
|
|
32
|
+
return Number.isNaN(n) || n < 0 ? Number.NaN : n;
|
|
33
|
+
}
|
|
34
|
+
if (low.startsWith("0x")) {
|
|
35
|
+
return Number.parseInt(t, 16);
|
|
36
|
+
}
|
|
37
|
+
return Number.parseInt(t, 10);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/protocols/evm/ethena/constants.ts
|
|
41
|
+
var ETHENA_LABS_GITHUB = "https://github.com/ethena-labs";
|
|
42
|
+
var ETHENA_KEY_ADDRESSES_DOC = "https://docs.ethena.fi/solution-design/key-addresses";
|
|
43
|
+
var USDE_ETHEREUM_MAINNET = "0x4c9edd5852cd905f086c759e8383e09bff1e68b3";
|
|
44
|
+
var SUSDE_ETHEREUM_MAINNET = "0x9d39a5de30e57443bff2a8307a4256c8797a3497";
|
|
45
|
+
var USDE_MOST_L2S = "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34";
|
|
46
|
+
var USDE_ZKSYNC_ERA = "0x39Fe7a0DACcE31Bd90418e3e659fb0b5f0B3Db0d";
|
|
47
|
+
var L2_SAME_ADDRESS_CHAIN_IDS = /* @__PURE__ */ new Set([
|
|
48
|
+
42161,
|
|
49
|
+
// Arbitrum One
|
|
50
|
+
10,
|
|
51
|
+
// Optimism
|
|
52
|
+
8453,
|
|
53
|
+
// Base
|
|
54
|
+
56,
|
|
55
|
+
// BNB Chain
|
|
56
|
+
59144,
|
|
57
|
+
// Linea
|
|
58
|
+
5e3,
|
|
59
|
+
// Mantle
|
|
60
|
+
81457,
|
|
61
|
+
// Blast
|
|
62
|
+
169,
|
|
63
|
+
// Manta Pacific
|
|
64
|
+
534352,
|
|
65
|
+
// Scroll
|
|
66
|
+
252,
|
|
67
|
+
// Fraxtal
|
|
68
|
+
34443,
|
|
69
|
+
// Mode
|
|
70
|
+
196,
|
|
71
|
+
// X Layer
|
|
72
|
+
1088,
|
|
73
|
+
// Metis
|
|
74
|
+
80084,
|
|
75
|
+
// Berachain
|
|
76
|
+
2222,
|
|
77
|
+
// Kava
|
|
78
|
+
2818,
|
|
79
|
+
// Morph
|
|
80
|
+
1923,
|
|
81
|
+
// Swell
|
|
82
|
+
48900
|
|
83
|
+
// Zircuit
|
|
84
|
+
]);
|
|
85
|
+
var FALLBACK_NAME_BY_ID = {
|
|
86
|
+
1: "Ethereum",
|
|
87
|
+
42161: "Arbitrum One",
|
|
88
|
+
10: "Optimism",
|
|
89
|
+
8453: "Base",
|
|
90
|
+
56: "BNB Chain",
|
|
91
|
+
59144: "Linea",
|
|
92
|
+
5e3: "Mantle",
|
|
93
|
+
81457: "Blast",
|
|
94
|
+
169: "Manta Pacific",
|
|
95
|
+
534352: "Scroll",
|
|
96
|
+
252: "Fraxtal",
|
|
97
|
+
34443: "Mode",
|
|
98
|
+
196: "X Layer",
|
|
99
|
+
1088: "Metis",
|
|
100
|
+
80084: "Berachain",
|
|
101
|
+
2222: "Kava",
|
|
102
|
+
2818: "Morph",
|
|
103
|
+
1923: "Swell",
|
|
104
|
+
48900: "Zircuit",
|
|
105
|
+
324: "ZKSync Era"
|
|
106
|
+
};
|
|
107
|
+
function usdeTokenAddressOnEvmChain(chainId) {
|
|
108
|
+
if (chainId === 1) return USDE_ETHEREUM_MAINNET;
|
|
109
|
+
if (chainId === 324) return USDE_ZKSYNC_ERA;
|
|
110
|
+
if (L2_SAME_ADDRESS_CHAIN_IDS.has(chainId)) return USDE_MOST_L2S;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function isEthenaUsdeOnAssetsChain(assetsChainId, contractAddress) {
|
|
114
|
+
const n = parseEvmChainIdToNumber(assetsChainId);
|
|
115
|
+
if (Number.isNaN(n) || n < 0) return false;
|
|
116
|
+
const expected = usdeTokenAddressOnEvmChain(n);
|
|
117
|
+
if (!expected) return false;
|
|
118
|
+
try {
|
|
119
|
+
return viem.getAddress(contractAddress) === viem.getAddress(expected);
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function listEthenaUsdeEvmNetworkRows() {
|
|
125
|
+
const rows = [
|
|
126
|
+
{ chainId: 1, label: FALLBACK_NAME_BY_ID[1], usde: USDE_ETHEREUM_MAINNET }
|
|
127
|
+
];
|
|
128
|
+
const l2 = [...L2_SAME_ADDRESS_CHAIN_IDS].sort((a, b) => a - b);
|
|
129
|
+
for (const id of l2) {
|
|
130
|
+
rows.push({
|
|
131
|
+
chainId: id,
|
|
132
|
+
label: FALLBACK_NAME_BY_ID[id] ?? `Chain ${id}`,
|
|
133
|
+
usde: USDE_MOST_L2S
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
rows.push({ chainId: 324, label: FALLBACK_NAME_BY_ID[324], usde: USDE_ZKSYNC_ERA });
|
|
137
|
+
return rows.sort((a, b) => {
|
|
138
|
+
if (a.chainId === 1) return -1;
|
|
139
|
+
if (b.chainId === 1) return 1;
|
|
140
|
+
return a.chainId - b.chainId;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function isEvmChainInEthenaUsdeList(chainId) {
|
|
144
|
+
return usdeTokenAddressOnEvmChain(chainId) != null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/core/keygen.ts
|
|
148
|
+
function firstClientIdFromKeyGen(data) {
|
|
149
|
+
const map = data?.ClientKeys;
|
|
150
|
+
if (!map || typeof map !== "object") return null;
|
|
151
|
+
for (const v of Object.values(map)) {
|
|
152
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/chains/evm/txParams.ts
|
|
158
|
+
function gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit) {
|
|
159
|
+
if (chainGasLimit == null || !Number.isFinite(chainGasLimit) || chainGasLimit <= 0) {
|
|
160
|
+
return estimatedGas;
|
|
161
|
+
}
|
|
162
|
+
const cfg = BigInt(Math.floor(chainGasLimit));
|
|
163
|
+
return cfg > estimatedGas ? cfg : estimatedGas;
|
|
164
|
+
}
|
|
165
|
+
async function fetchChainFeeParams(rpcUrl, chainId) {
|
|
166
|
+
const url = rpcUrl.trim();
|
|
167
|
+
if (!url) return { isEip1559: false };
|
|
168
|
+
const chainIdNum = chainId;
|
|
169
|
+
if (Number.isNaN(chainIdNum)) return { isEip1559: false };
|
|
170
|
+
const chain = viem.defineChain({
|
|
171
|
+
id: chainIdNum,
|
|
172
|
+
name: "Discovery",
|
|
173
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
174
|
+
rpcUrls: { default: { http: [url] } }
|
|
175
|
+
});
|
|
176
|
+
const publicClient = viem.createPublicClient({
|
|
177
|
+
chain,
|
|
178
|
+
transport: viem.http(url)
|
|
179
|
+
});
|
|
180
|
+
const getGasPriceGwei = async () => {
|
|
181
|
+
const gasPriceWei = await publicClient.getGasPrice();
|
|
182
|
+
return parseFloat(viem.formatUnits(gasPriceWei, 9));
|
|
183
|
+
};
|
|
184
|
+
try {
|
|
185
|
+
const block = await publicClient.getBlock({ blockTag: "latest" });
|
|
186
|
+
const baseFeePerGas = block?.baseFeePerGas;
|
|
187
|
+
if (baseFeePerGas == null || baseFeePerGas === void 0) {
|
|
188
|
+
const gasPriceGwei2 = await getGasPriceGwei();
|
|
189
|
+
return { isEip1559: false, gasPriceGwei: gasPriceGwei2 };
|
|
190
|
+
}
|
|
191
|
+
const baseFeeGwei = parseFloat(viem.formatUnits(baseFeePerGas, 9));
|
|
192
|
+
let priorityFeeGwei;
|
|
193
|
+
try {
|
|
194
|
+
const priorityWei = await publicClient.estimateMaxPriorityFeePerGas();
|
|
195
|
+
priorityFeeGwei = parseFloat(viem.formatUnits(priorityWei, 9));
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
const gasPriceGwei = await getGasPriceGwei();
|
|
199
|
+
return {
|
|
200
|
+
isEip1559: true,
|
|
201
|
+
baseFeeGwei,
|
|
202
|
+
priorityFeeGwei,
|
|
203
|
+
gasPriceGwei
|
|
204
|
+
};
|
|
205
|
+
} catch {
|
|
206
|
+
try {
|
|
207
|
+
const gasPriceWei = await publicClient.getGasPrice();
|
|
208
|
+
const gasPriceGwei = parseFloat(viem.formatUnits(gasPriceWei, 9));
|
|
209
|
+
return { isEip1559: false, gasPriceGwei };
|
|
210
|
+
} catch {
|
|
211
|
+
return { isEip1559: false };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, floor, baseWei) {
|
|
216
|
+
let maxP = maxPriorityFeePerGas;
|
|
217
|
+
let maxF = maxFeePerGas;
|
|
218
|
+
if (baseWei > 0n && maxF < baseWei + maxP) {
|
|
219
|
+
maxF = baseWei + maxP + viem.parseGwei("0.001");
|
|
220
|
+
}
|
|
221
|
+
if (maxF < maxP) {
|
|
222
|
+
maxF = baseWei > 0n ? baseWei + maxP + viem.parseGwei("0.001") : maxP * 2n;
|
|
223
|
+
}
|
|
224
|
+
return { maxFeePerGas: maxF, maxPriorityFeePerGas: maxP };
|
|
225
|
+
}
|
|
226
|
+
function alignEip1559FeesWithLatestBase(maxFeePerGas, maxPriorityFeePerGas, latestBlockBaseFeeWei) {
|
|
227
|
+
return finalizeEip1559Fees(maxFeePerGas, maxPriorityFeePerGas, null, latestBlockBaseFeeWei);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/protocols/evm/ethena/multisign.ts
|
|
231
|
+
var AAVE_ERC20_APPROVE_FALLBACK = 100000n;
|
|
232
|
+
var ETHENA_SUSDE_DEPOSIT_GET_SIG_GAS_FALLBACK = 1200000n;
|
|
233
|
+
var ETHENA_SUSDE_DEPOSIT_FALLBACK = ETHENA_SUSDE_DEPOSIT_GET_SIG_GAS_FALLBACK;
|
|
234
|
+
var ETHENA_SUSDE_REDEEM_FALLBACK = 1200000n;
|
|
235
|
+
function gweiToDecimalString(n) {
|
|
236
|
+
if (!Number.isFinite(n)) return "0";
|
|
237
|
+
if (n === 0) return "0";
|
|
238
|
+
const s = String(n);
|
|
239
|
+
if (s.indexOf("e") !== -1 || s.indexOf("E") !== -1) return n.toFixed(9).replace(/\.?0+$/, "") || "0";
|
|
240
|
+
return s;
|
|
241
|
+
}
|
|
242
|
+
var erc20AllowanceAbi = viem.parseAbi([
|
|
243
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
244
|
+
"function decimals() view returns (uint8)"
|
|
245
|
+
]);
|
|
246
|
+
var erc20ApproveAbi = viem.parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]);
|
|
247
|
+
var susdeErc4626Abi = viem.parseAbi([
|
|
248
|
+
"function deposit(uint256 assets, address receiver) returns (uint256 shares)",
|
|
249
|
+
"function previewDeposit(uint256 assets) view returns (uint256 shares)",
|
|
250
|
+
"function previewRedeem(uint256 shares) view returns (uint256 assets)",
|
|
251
|
+
"function redeem(uint256 shares, address receiver, address owner) returns (uint256 assets)",
|
|
252
|
+
"function decimals() view returns (uint8)"
|
|
253
|
+
]);
|
|
254
|
+
var stakedUsdeV2UnstakeAbi = viem.parseAbi([
|
|
255
|
+
"function cooldownShares(uint256 shares, address owner) returns (uint256 assets)",
|
|
256
|
+
"function unstake(address receiver)"
|
|
257
|
+
]);
|
|
258
|
+
async function readEthenaStakePreviewShares(args) {
|
|
259
|
+
const { rpcUrl, usdeAmountWei } = args;
|
|
260
|
+
if (usdeAmountWei <= 0n) return 0n;
|
|
261
|
+
const ch = viem.defineChain({
|
|
262
|
+
id: 1,
|
|
263
|
+
name: "Ethereum",
|
|
264
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
265
|
+
rpcUrls: { default: { http: [rpcUrl.trim()] } }
|
|
266
|
+
});
|
|
267
|
+
const client = viem.createPublicClient({ chain: ch, transport: viem.http(rpcUrl.trim()) });
|
|
268
|
+
const vault = viem.getAddress(SUSDE_ETHEREUM_MAINNET);
|
|
269
|
+
return client.readContract({
|
|
270
|
+
address: vault,
|
|
271
|
+
abi: susdeErc4626Abi,
|
|
272
|
+
functionName: "previewDeposit",
|
|
273
|
+
args: [usdeAmountWei]
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async function readEthenaUnstakePreviewUsde(args) {
|
|
277
|
+
const { rpcUrl, susdeSharesWei } = args;
|
|
278
|
+
if (susdeSharesWei <= 0n) return 0n;
|
|
279
|
+
const ch = viem.defineChain({
|
|
280
|
+
id: 1,
|
|
281
|
+
name: "Ethereum",
|
|
282
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
283
|
+
rpcUrls: { default: { http: [rpcUrl.trim()] } }
|
|
284
|
+
});
|
|
285
|
+
const client = viem.createPublicClient({ chain: ch, transport: viem.http(rpcUrl.trim()) });
|
|
286
|
+
const vault = viem.getAddress(SUSDE_ETHEREUM_MAINNET);
|
|
287
|
+
return client.readContract({
|
|
288
|
+
address: vault,
|
|
289
|
+
abi: susdeErc4626Abi,
|
|
290
|
+
functionName: "previewRedeem",
|
|
291
|
+
args: [susdeSharesWei]
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async function buildEvmMultisignBodyEthenaUsdeStakeToSusde(args) {
|
|
295
|
+
if (args.chainId !== 1) {
|
|
296
|
+
throw new Error("Ethena USDe \u2192 sUSDe stake is only supported on Ethereum mainnet (chain id 1).");
|
|
297
|
+
}
|
|
298
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
299
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
300
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
301
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
302
|
+
const usde = viem.getAddress(args.usde);
|
|
303
|
+
const mainnetUsde = viem.getAddress(USDE_ETHEREUM_MAINNET);
|
|
304
|
+
if (usde.toLowerCase() !== mainnetUsde.toLowerCase()) {
|
|
305
|
+
throw new Error("Token in must be mainnet USDe for this flow.");
|
|
306
|
+
}
|
|
307
|
+
const susde = viem.getAddress(args.susdeVault ?? SUSDE_ETHEREUM_MAINNET);
|
|
308
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
309
|
+
const receiver = viem.getAddress(args.receiver);
|
|
310
|
+
if (viem.getAddress(SUSDE_ETHEREUM_MAINNET).toLowerCase() !== susde.toLowerCase()) {
|
|
311
|
+
throw new Error("sUSDe vault address mismatch.");
|
|
312
|
+
}
|
|
313
|
+
const ch = viem.defineChain({
|
|
314
|
+
id: 1,
|
|
315
|
+
name: "Ethereum",
|
|
316
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
317
|
+
rpcUrls: { default: { http: [args.rpcUrl.trim()] } }
|
|
318
|
+
});
|
|
319
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl.trim()) });
|
|
320
|
+
const dec = await publicClient.readContract({
|
|
321
|
+
address: usde,
|
|
322
|
+
abi: erc20AllowanceAbi,
|
|
323
|
+
functionName: "decimals"
|
|
324
|
+
});
|
|
325
|
+
const amountWei = viem.parseUnits(args.amountHuman, Number(dec));
|
|
326
|
+
if (amountWei === 0n) throw new Error("Amount is zero after converting with token decimals.");
|
|
327
|
+
const steps = [];
|
|
328
|
+
const currentAllowance = await publicClient.readContract({
|
|
329
|
+
address: usde,
|
|
330
|
+
abi: erc20AllowanceAbi,
|
|
331
|
+
functionName: "allowance",
|
|
332
|
+
args: [executor, susde]
|
|
333
|
+
});
|
|
334
|
+
if (currentAllowance < amountWei) {
|
|
335
|
+
if (currentAllowance > 0n) {
|
|
336
|
+
const dataReset = viem.encodeFunctionData({
|
|
337
|
+
abi: erc20ApproveAbi,
|
|
338
|
+
functionName: "approve",
|
|
339
|
+
args: [susde, 0n]
|
|
340
|
+
});
|
|
341
|
+
steps.push({ kind: "approve", to: usde, data: dataReset, value: 0n });
|
|
342
|
+
}
|
|
343
|
+
const dataApprove = viem.encodeFunctionData({
|
|
344
|
+
abi: erc20ApproveAbi,
|
|
345
|
+
functionName: "approve",
|
|
346
|
+
args: [susde, amountWei]
|
|
347
|
+
});
|
|
348
|
+
steps.push({ kind: "approve", to: usde, data: dataApprove, value: 0n });
|
|
349
|
+
}
|
|
350
|
+
const depositData = viem.encodeFunctionData({
|
|
351
|
+
abi: susdeErc4626Abi,
|
|
352
|
+
functionName: "deposit",
|
|
353
|
+
args: [amountWei, receiver]
|
|
354
|
+
});
|
|
355
|
+
steps.push({ kind: "deposit", to: susde, data: depositData, value: 0n });
|
|
356
|
+
const feeParams = await fetchChainFeeParams(args.rpcUrl, 1);
|
|
357
|
+
const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
|
|
358
|
+
const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
|
|
359
|
+
const useCustomGas = args.useCustomGas;
|
|
360
|
+
const gasLimitConfig = useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
|
|
361
|
+
const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
|
|
362
|
+
const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
|
|
363
|
+
const messageHashes = [];
|
|
364
|
+
const messageRawBatch = [];
|
|
365
|
+
const proposalTxParamsBatch = [];
|
|
366
|
+
const batchMeta = [];
|
|
367
|
+
let firstTxFeePayload = {};
|
|
368
|
+
let firstDataNo0x = "";
|
|
369
|
+
for (let i = 0; i < steps.length; i++) {
|
|
370
|
+
const s = steps[i];
|
|
371
|
+
const currentNonce = baseNonce + i;
|
|
372
|
+
let estimatedGas;
|
|
373
|
+
const skipDepositEstimateBecausePriorApproves = s.kind === "deposit" && i > 0;
|
|
374
|
+
if (skipDepositEstimateBecausePriorApproves) {
|
|
375
|
+
estimatedGas = ETHENA_SUSDE_DEPOSIT_FALLBACK;
|
|
376
|
+
} else {
|
|
377
|
+
try {
|
|
378
|
+
estimatedGas = await publicClient.estimateGas({
|
|
379
|
+
to: s.to,
|
|
380
|
+
data: s.data,
|
|
381
|
+
value: s.value,
|
|
382
|
+
account: executor
|
|
383
|
+
});
|
|
384
|
+
} catch {
|
|
385
|
+
estimatedGas = s.kind === "approve" ? AAVE_ERC20_APPROVE_FALLBACK : ETHENA_SUSDE_DEPOSIT_FALLBACK;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
|
|
389
|
+
if (legacy) {
|
|
390
|
+
let gasPriceWei = await publicClient.getGasPrice();
|
|
391
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
392
|
+
gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
393
|
+
}
|
|
394
|
+
if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
|
|
395
|
+
const configured = viem.parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
|
|
396
|
+
if (configured > gasPriceWei) gasPriceWei = configured;
|
|
397
|
+
}
|
|
398
|
+
const ser = viem.serializeTransaction({
|
|
399
|
+
type: "legacy",
|
|
400
|
+
to: s.to,
|
|
401
|
+
data: s.data,
|
|
402
|
+
value: s.value,
|
|
403
|
+
gas: gasLimitI,
|
|
404
|
+
gasPrice: gasPriceWei,
|
|
405
|
+
nonce: currentNonce,
|
|
406
|
+
chainId: 1
|
|
407
|
+
});
|
|
408
|
+
const h = viem.keccak256(ser);
|
|
409
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
410
|
+
messageRawBatch.push(ser);
|
|
411
|
+
proposalTxParamsBatch.push({
|
|
412
|
+
nonce: currentNonce,
|
|
413
|
+
gasLimit: gasLimitI.toString(),
|
|
414
|
+
txType: "legacy",
|
|
415
|
+
gasPrice: gasPriceWei.toString()
|
|
416
|
+
});
|
|
417
|
+
if (i === 0) {
|
|
418
|
+
firstTxFeePayload = { txNonce: currentNonce, txGasLimit: gasLimitI.toString(), txGasPrice: gasPriceWei.toString() };
|
|
419
|
+
firstDataNo0x = s.data.startsWith("0x") ? s.data.slice(2) : s.data;
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
const fetchedBase = feeParams.baseFeeGwei ?? 0;
|
|
423
|
+
const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
|
|
424
|
+
const configuredBase = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
|
|
425
|
+
const configuredPriority = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
|
|
426
|
+
const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
|
|
427
|
+
const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
|
|
428
|
+
const baseFeeMultiplierPct = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
|
|
429
|
+
const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
|
|
430
|
+
const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
|
|
431
|
+
let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? viem.parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : viem.parseGwei("1");
|
|
432
|
+
let maxFeePerGas = viem.parseGwei(gweiToDecimalString(maxFeePerGasGwei));
|
|
433
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
434
|
+
maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
435
|
+
maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
436
|
+
}
|
|
437
|
+
({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
|
|
438
|
+
maxFeePerGas,
|
|
439
|
+
maxPriorityFeePerGas,
|
|
440
|
+
latestBaseFeeWei
|
|
441
|
+
));
|
|
442
|
+
const ser = viem.serializeTransaction({
|
|
443
|
+
type: "eip1559",
|
|
444
|
+
to: s.to,
|
|
445
|
+
data: s.data,
|
|
446
|
+
value: s.value,
|
|
447
|
+
gas: gasLimitI,
|
|
448
|
+
maxFeePerGas,
|
|
449
|
+
maxPriorityFeePerGas,
|
|
450
|
+
nonce: currentNonce,
|
|
451
|
+
chainId: 1
|
|
452
|
+
});
|
|
453
|
+
const h = viem.keccak256(ser);
|
|
454
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
455
|
+
messageRawBatch.push(ser);
|
|
456
|
+
proposalTxParamsBatch.push({
|
|
457
|
+
nonce: currentNonce,
|
|
458
|
+
gasLimit: gasLimitI.toString(),
|
|
459
|
+
txType: "eip1559",
|
|
460
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
461
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
462
|
+
});
|
|
463
|
+
if (i === 0) {
|
|
464
|
+
firstTxFeePayload = {
|
|
465
|
+
txNonce: currentNonce,
|
|
466
|
+
txGasLimit: gasLimitI.toString(),
|
|
467
|
+
txMaxFeePerGas: maxFeePerGas.toString(),
|
|
468
|
+
txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
469
|
+
};
|
|
470
|
+
firstDataNo0x = s.data.startsWith("0x") ? s.data.slice(2) : s.data;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (s.kind === "approve") {
|
|
474
|
+
batchMeta.push({
|
|
475
|
+
destinationAddress: usde,
|
|
476
|
+
signatureText: JSON.stringify({
|
|
477
|
+
kind: "Ethena",
|
|
478
|
+
name: "ERC20.approve",
|
|
479
|
+
to: "sUSDe vault",
|
|
480
|
+
function: "approve(address spender, uint256 amount)",
|
|
481
|
+
spender: susde,
|
|
482
|
+
amountHuman: args.amountHuman,
|
|
483
|
+
note: "Allowance to stake this USDe amount (not unlimited)."
|
|
484
|
+
}),
|
|
485
|
+
evm: { type: "ethena_erc20_approve", version: 1, chainId: "1" },
|
|
486
|
+
ethena: { step: "approve_usde", susde }
|
|
487
|
+
});
|
|
488
|
+
} else {
|
|
489
|
+
batchMeta.push({
|
|
490
|
+
destinationAddress: susde,
|
|
491
|
+
signatureText: JSON.stringify({
|
|
492
|
+
kind: "Ethena",
|
|
493
|
+
name: "StakedUSDe (ERC-4626).deposit",
|
|
494
|
+
function: "deposit(uint256 assets, address receiver)",
|
|
495
|
+
assets: amountWei.toString(),
|
|
496
|
+
receiver,
|
|
497
|
+
amountHuman: args.amountHuman
|
|
498
|
+
}),
|
|
499
|
+
evm: { type: "ethena_susde_deposit", version: 1, chainId: "1" },
|
|
500
|
+
ethena: { step: "deposit", vault: susde, gasBuildDeposit: { baseGasUnits: gasLimitI.toString() } }
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const extraPayload = { batchMeta };
|
|
505
|
+
if (useCustomGas && args.customGasChainDetails && Object.keys(args.customGasChainDetails).length > 0) {
|
|
506
|
+
extraPayload.customGasChainDetails = args.customGasChainDetails;
|
|
507
|
+
}
|
|
508
|
+
const extraJSON = JSON.stringify(extraPayload);
|
|
509
|
+
const firstSigText = batchMeta[0].signatureText;
|
|
510
|
+
const n = steps.length;
|
|
511
|
+
const purposeSuffix = n === 1 ? "Ethena: 1-tx \u2014 deposit USDe to sUSDe (allowance already set)." : `Ethena: ${n}-tx batch \u2014 approve USDe to sUSDe vault, then deposit.`;
|
|
512
|
+
const bodyForSign = {
|
|
513
|
+
keyList,
|
|
514
|
+
pubKey: ph,
|
|
515
|
+
msgHash: messageHashes[0],
|
|
516
|
+
msgRaw: firstDataNo0x,
|
|
517
|
+
messageHashes,
|
|
518
|
+
messageRawBatch,
|
|
519
|
+
destinationChainID: "1",
|
|
520
|
+
destinationAddress: steps[0].to,
|
|
521
|
+
extraJSON,
|
|
522
|
+
signatureText: firstSigText,
|
|
523
|
+
purpose: (() => {
|
|
524
|
+
const t = args.purposeText.trim();
|
|
525
|
+
return (t ? `${t}
|
|
526
|
+
|
|
527
|
+
` : "") + purposeSuffix;
|
|
528
|
+
})(),
|
|
529
|
+
...firstTxFeePayload,
|
|
530
|
+
proposalTxParams: proposalTxParamsBatch
|
|
531
|
+
};
|
|
532
|
+
if (clientId) bodyForSign.clientId = clientId;
|
|
533
|
+
return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
|
|
534
|
+
}
|
|
535
|
+
var ETHENA_SUSDE_COOLDOWN_SHARES_FALLBACK = 1200000n;
|
|
536
|
+
var ETHENA_SUSDE_UNSTAKE_CLAIM_FALLBACK = 500000n;
|
|
537
|
+
async function buildEthenaStakedUsdeOneTxMultisignBody(args) {
|
|
538
|
+
if (args.chainId !== 1) {
|
|
539
|
+
throw new Error("Ethena sUSDe exit is only supported on Ethereum mainnet (chain id 1).");
|
|
540
|
+
}
|
|
541
|
+
const ph = (args.keyGen.pubkeyhex ?? "").trim();
|
|
542
|
+
if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
|
|
543
|
+
const keyList = args.keyGen.keylist ?? [];
|
|
544
|
+
const clientId = firstClientIdFromKeyGen(args.keyGen);
|
|
545
|
+
const { executor } = args;
|
|
546
|
+
const steps = [args.step];
|
|
547
|
+
const feeParams = await fetchChainFeeParams(args.rpcUrl, 1);
|
|
548
|
+
const legacy = Boolean(args.chainDetail?.legacy) || !feeParams.isEip1559;
|
|
549
|
+
const useCustomGas = args.useCustomGas;
|
|
550
|
+
const gasLimitConfig = useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
|
|
551
|
+
const gasFeeMultiplier = useCustomGas && args.chainDetail?.gasMultiplier != null ? Number(args.chainDetail.gasMultiplier) : void 0;
|
|
552
|
+
const ch = viem.defineChain({
|
|
553
|
+
id: 1,
|
|
554
|
+
name: "Ethereum",
|
|
555
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
556
|
+
rpcUrls: { default: { http: [args.rpcUrl.trim()] } }
|
|
557
|
+
});
|
|
558
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl.trim()) });
|
|
559
|
+
const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
|
|
560
|
+
const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
|
|
561
|
+
const messageHashes = [];
|
|
562
|
+
const messageRawBatch = [];
|
|
563
|
+
const proposalTxParamsBatch = [];
|
|
564
|
+
const batchMeta = [];
|
|
565
|
+
let firstTxFeePayload = {};
|
|
566
|
+
let firstDataNo0x = "";
|
|
567
|
+
for (let i = 0; i < steps.length; i++) {
|
|
568
|
+
const s = steps[i];
|
|
569
|
+
const currentNonce = baseNonce + i;
|
|
570
|
+
let estimatedGas;
|
|
571
|
+
try {
|
|
572
|
+
estimatedGas = await publicClient.estimateGas({
|
|
573
|
+
to: s.to,
|
|
574
|
+
data: s.data,
|
|
575
|
+
value: s.value,
|
|
576
|
+
account: executor
|
|
577
|
+
});
|
|
578
|
+
} catch {
|
|
579
|
+
estimatedGas = args.gasFallback;
|
|
580
|
+
}
|
|
581
|
+
const gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
|
|
582
|
+
if (legacy) {
|
|
583
|
+
let gasPriceWei = await publicClient.getGasPrice();
|
|
584
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
585
|
+
gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
586
|
+
}
|
|
587
|
+
if (useCustomGas && args.chainDetail?.gasPrice != null && args.chainDetail.gasPrice > 0) {
|
|
588
|
+
const configured = viem.parseGwei(gweiToDecimalString(Number(args.chainDetail.gasPrice)));
|
|
589
|
+
if (configured > gasPriceWei) gasPriceWei = configured;
|
|
590
|
+
}
|
|
591
|
+
const ser = viem.serializeTransaction({
|
|
592
|
+
type: "legacy",
|
|
593
|
+
to: s.to,
|
|
594
|
+
data: s.data,
|
|
595
|
+
value: s.value,
|
|
596
|
+
gas: gasLimitI,
|
|
597
|
+
gasPrice: gasPriceWei,
|
|
598
|
+
nonce: currentNonce,
|
|
599
|
+
chainId: 1
|
|
600
|
+
});
|
|
601
|
+
const h = viem.keccak256(ser);
|
|
602
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
603
|
+
messageRawBatch.push(ser);
|
|
604
|
+
proposalTxParamsBatch.push({
|
|
605
|
+
nonce: currentNonce,
|
|
606
|
+
gasLimit: gasLimitI.toString(),
|
|
607
|
+
txType: "legacy",
|
|
608
|
+
gasPrice: gasPriceWei.toString()
|
|
609
|
+
});
|
|
610
|
+
if (i === 0) {
|
|
611
|
+
firstTxFeePayload = { txNonce: currentNonce, txGasLimit: gasLimitI.toString(), txGasPrice: gasPriceWei.toString() };
|
|
612
|
+
firstDataNo0x = s.data.startsWith("0x") ? s.data.slice(2) : s.data;
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
const fetchedBase = feeParams.baseFeeGwei ?? 0;
|
|
616
|
+
const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
|
|
617
|
+
const configuredBase = useCustomGas && args.chainDetail?.baseFee != null ? Number(args.chainDetail.baseFee) : 0;
|
|
618
|
+
const configuredPriority = useCustomGas && args.chainDetail?.priorityFee != null ? Number(args.chainDetail.priorityFee) : 0;
|
|
619
|
+
const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
|
|
620
|
+
const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
|
|
621
|
+
const baseFeeMultiplierPct = useCustomGas && args.chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(args.chainDetail.baseFeeMultiplier)) : 100;
|
|
622
|
+
const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
|
|
623
|
+
const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
|
|
624
|
+
let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? viem.parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : viem.parseGwei("1");
|
|
625
|
+
let maxFeePerGas = viem.parseGwei(gweiToDecimalString(maxFeePerGasGwei));
|
|
626
|
+
if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
|
|
627
|
+
maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
628
|
+
maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
|
|
629
|
+
}
|
|
630
|
+
({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
|
|
631
|
+
maxFeePerGas,
|
|
632
|
+
maxPriorityFeePerGas,
|
|
633
|
+
latestBaseFeeWei
|
|
634
|
+
));
|
|
635
|
+
const ser = viem.serializeTransaction({
|
|
636
|
+
type: "eip1559",
|
|
637
|
+
to: s.to,
|
|
638
|
+
data: s.data,
|
|
639
|
+
value: s.value,
|
|
640
|
+
gas: gasLimitI,
|
|
641
|
+
maxFeePerGas,
|
|
642
|
+
maxPriorityFeePerGas,
|
|
643
|
+
nonce: currentNonce,
|
|
644
|
+
chainId: 1
|
|
645
|
+
});
|
|
646
|
+
const h = viem.keccak256(ser);
|
|
647
|
+
messageHashes.push(h.startsWith("0x") ? h.slice(2) : h);
|
|
648
|
+
messageRawBatch.push(ser);
|
|
649
|
+
proposalTxParamsBatch.push({
|
|
650
|
+
nonce: currentNonce,
|
|
651
|
+
gasLimit: gasLimitI.toString(),
|
|
652
|
+
txType: "eip1559",
|
|
653
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
654
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
655
|
+
});
|
|
656
|
+
if (i === 0) {
|
|
657
|
+
firstTxFeePayload = {
|
|
658
|
+
txNonce: currentNonce,
|
|
659
|
+
txGasLimit: gasLimitI.toString(),
|
|
660
|
+
txMaxFeePerGas: maxFeePerGas.toString(),
|
|
661
|
+
txMaxPriorityFeePerGas: maxPriorityFeePerGas.toString()
|
|
662
|
+
};
|
|
663
|
+
firstDataNo0x = s.data.startsWith("0x") ? s.data.slice(2) : s.data;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
batchMeta.push(args.makeBatchMeta({ gasLimitI }));
|
|
667
|
+
}
|
|
668
|
+
const extraPayload = { batchMeta };
|
|
669
|
+
if (useCustomGas && args.customGasChainDetails && Object.keys(args.customGasChainDetails).length > 0) {
|
|
670
|
+
extraPayload.customGasChainDetails = args.customGasChainDetails;
|
|
671
|
+
}
|
|
672
|
+
const extraJSON = JSON.stringify(extraPayload);
|
|
673
|
+
const firstSigText = batchMeta[0].signatureText;
|
|
674
|
+
const bodyForSign = {
|
|
675
|
+
keyList,
|
|
676
|
+
pubKey: ph,
|
|
677
|
+
msgHash: messageHashes[0],
|
|
678
|
+
msgRaw: firstDataNo0x,
|
|
679
|
+
messageHashes,
|
|
680
|
+
messageRawBatch,
|
|
681
|
+
destinationChainID: "1",
|
|
682
|
+
destinationAddress: steps[0].to,
|
|
683
|
+
extraJSON,
|
|
684
|
+
signatureText: firstSigText,
|
|
685
|
+
purpose: (() => {
|
|
686
|
+
const t = args.purposeText.trim();
|
|
687
|
+
return (t ? `${t}
|
|
688
|
+
|
|
689
|
+
` : "") + args.purposeSuffix;
|
|
690
|
+
})(),
|
|
691
|
+
...firstTxFeePayload,
|
|
692
|
+
proposalTxParams: proposalTxParamsBatch
|
|
693
|
+
};
|
|
694
|
+
if (clientId) bodyForSign.clientId = clientId;
|
|
695
|
+
return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
|
|
696
|
+
}
|
|
697
|
+
async function buildEvmMultisignBodyEthenaSusdeRedeemToUsde(args) {
|
|
698
|
+
if (args.chainId !== 1) {
|
|
699
|
+
throw new Error("Ethena sUSDe \u2192 USDe redeem is only supported on Ethereum mainnet (chain id 1).");
|
|
700
|
+
}
|
|
701
|
+
const susde = viem.getAddress(args.susdeVault ?? SUSDE_ETHEREUM_MAINNET);
|
|
702
|
+
if (viem.getAddress(SUSDE_ETHEREUM_MAINNET).toLowerCase() !== susde.toLowerCase()) {
|
|
703
|
+
throw new Error("sUSDe vault address mismatch.");
|
|
704
|
+
}
|
|
705
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
706
|
+
const receiver = viem.getAddress(args.receiver);
|
|
707
|
+
const ch = viem.defineChain({
|
|
708
|
+
id: 1,
|
|
709
|
+
name: "Ethereum",
|
|
710
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
711
|
+
rpcUrls: { default: { http: [args.rpcUrl.trim()] } }
|
|
712
|
+
});
|
|
713
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl.trim()) });
|
|
714
|
+
const dec = await publicClient.readContract({
|
|
715
|
+
address: susde,
|
|
716
|
+
abi: susdeErc4626Abi,
|
|
717
|
+
functionName: "decimals"
|
|
718
|
+
});
|
|
719
|
+
const sharesWei = viem.parseUnits(args.sharesHuman, Number(dec));
|
|
720
|
+
if (sharesWei === 0n) throw new Error("Amount is zero after converting with share decimals.");
|
|
721
|
+
const redeemData = viem.encodeFunctionData({
|
|
722
|
+
abi: susdeErc4626Abi,
|
|
723
|
+
functionName: "redeem",
|
|
724
|
+
args: [sharesWei, receiver, executor]
|
|
725
|
+
});
|
|
726
|
+
const step = { to: susde, data: redeemData, value: 0n };
|
|
727
|
+
const purposeSuffix = "Ethena: 1-tx \u2014 redeem sUSDe shares to USDe (ERC-4626; no cooldown).";
|
|
728
|
+
return buildEthenaStakedUsdeOneTxMultisignBody({
|
|
729
|
+
keyGen: args.keyGen,
|
|
730
|
+
chainId: args.chainId,
|
|
731
|
+
rpcUrl: args.rpcUrl,
|
|
732
|
+
chainDetail: args.chainDetail,
|
|
733
|
+
useCustomGas: args.useCustomGas,
|
|
734
|
+
customGasChainDetails: args.customGasChainDetails,
|
|
735
|
+
susde,
|
|
736
|
+
executor,
|
|
737
|
+
step,
|
|
738
|
+
purposeText: args.purposeText,
|
|
739
|
+
purposeSuffix,
|
|
740
|
+
gasFallback: ETHENA_SUSDE_REDEEM_FALLBACK,
|
|
741
|
+
makeBatchMeta: ({ gasLimitI }) => ({
|
|
742
|
+
destinationAddress: susde,
|
|
743
|
+
signatureText: JSON.stringify({
|
|
744
|
+
kind: "Ethena",
|
|
745
|
+
name: "StakedUSDe (ERC-4626).redeem",
|
|
746
|
+
function: "redeem(uint256 shares, address receiver, address owner)",
|
|
747
|
+
shares: sharesWei.toString(),
|
|
748
|
+
receiver,
|
|
749
|
+
owner: executor,
|
|
750
|
+
amountHuman: args.sharesHuman
|
|
751
|
+
}),
|
|
752
|
+
evm: { type: "ethena_susde_redeem", version: 1, chainId: "1" },
|
|
753
|
+
ethena: { step: "redeem", vault: susde, gasBuildRedeem: { baseGasUnits: gasLimitI.toString() } }
|
|
754
|
+
})
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
async function buildEvmMultisignBodyEthenaSusdeCooldownShares(args) {
|
|
758
|
+
if (args.chainId !== 1) {
|
|
759
|
+
throw new Error("Ethena sUSDe cooldown is only supported on Ethereum mainnet (chain id 1).");
|
|
760
|
+
}
|
|
761
|
+
const susde = viem.getAddress(args.susdeVault ?? SUSDE_ETHEREUM_MAINNET);
|
|
762
|
+
if (viem.getAddress(SUSDE_ETHEREUM_MAINNET).toLowerCase() !== susde.toLowerCase()) {
|
|
763
|
+
throw new Error("sUSDe vault address mismatch.");
|
|
764
|
+
}
|
|
765
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
766
|
+
const ch = viem.defineChain({
|
|
767
|
+
id: 1,
|
|
768
|
+
name: "Ethereum",
|
|
769
|
+
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
|
770
|
+
rpcUrls: { default: { http: [args.rpcUrl.trim()] } }
|
|
771
|
+
});
|
|
772
|
+
const publicClient = viem.createPublicClient({ chain: ch, transport: viem.http(args.rpcUrl.trim()) });
|
|
773
|
+
const dec = await publicClient.readContract({
|
|
774
|
+
address: susde,
|
|
775
|
+
abi: susdeErc4626Abi,
|
|
776
|
+
functionName: "decimals"
|
|
777
|
+
});
|
|
778
|
+
const sharesWei = viem.parseUnits(args.sharesHuman, Number(dec));
|
|
779
|
+
if (sharesWei === 0n) throw new Error("Amount is zero after converting with share decimals.");
|
|
780
|
+
const data = viem.encodeFunctionData({
|
|
781
|
+
abi: stakedUsdeV2UnstakeAbi,
|
|
782
|
+
functionName: "cooldownShares",
|
|
783
|
+
args: [sharesWei, executor]
|
|
784
|
+
});
|
|
785
|
+
const step = { to: susde, data, value: 0n };
|
|
786
|
+
const purposeSuffix = "Ethena: 1-tx \u2014 cooldownShares (sUSDe \u2192 silo, claim USDe in Unlock after cooldown).";
|
|
787
|
+
return buildEthenaStakedUsdeOneTxMultisignBody({
|
|
788
|
+
keyGen: args.keyGen,
|
|
789
|
+
chainId: args.chainId,
|
|
790
|
+
rpcUrl: args.rpcUrl,
|
|
791
|
+
chainDetail: args.chainDetail,
|
|
792
|
+
useCustomGas: args.useCustomGas,
|
|
793
|
+
customGasChainDetails: args.customGasChainDetails,
|
|
794
|
+
susde,
|
|
795
|
+
executor,
|
|
796
|
+
step,
|
|
797
|
+
purposeText: args.purposeText,
|
|
798
|
+
purposeSuffix,
|
|
799
|
+
gasFallback: ETHENA_SUSDE_COOLDOWN_SHARES_FALLBACK,
|
|
800
|
+
makeBatchMeta: ({ gasLimitI }) => ({
|
|
801
|
+
destinationAddress: susde,
|
|
802
|
+
signatureText: JSON.stringify({
|
|
803
|
+
kind: "Ethena",
|
|
804
|
+
name: "StakedUSDeV2.cooldownShares",
|
|
805
|
+
function: "cooldownShares(uint256 shares, address owner)",
|
|
806
|
+
shares: sharesWei.toString(),
|
|
807
|
+
owner: executor,
|
|
808
|
+
amountHuman: args.sharesHuman
|
|
809
|
+
}),
|
|
810
|
+
evm: { type: "ethena_susde_cooldown_shares", version: 1, chainId: "1" },
|
|
811
|
+
ethena: { step: "cooldown_shares", vault: susde, gasBuildCooldown: { baseGasUnits: gasLimitI.toString() } }
|
|
812
|
+
})
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
async function buildEvmMultisignBodyEthenaUnstakeClaim(args) {
|
|
816
|
+
if (args.chainId !== 1) {
|
|
817
|
+
throw new Error("Ethena unstake claim is only supported on Ethereum mainnet (chain id 1).");
|
|
818
|
+
}
|
|
819
|
+
const susde = viem.getAddress(args.susdeVault ?? SUSDE_ETHEREUM_MAINNET);
|
|
820
|
+
if (viem.getAddress(SUSDE_ETHEREUM_MAINNET).toLowerCase() !== susde.toLowerCase()) {
|
|
821
|
+
throw new Error("sUSDe vault address mismatch.");
|
|
822
|
+
}
|
|
823
|
+
const executor = viem.getAddress(args.executorAddress);
|
|
824
|
+
const receiver = viem.getAddress(args.receiver);
|
|
825
|
+
const data = viem.encodeFunctionData({
|
|
826
|
+
abi: stakedUsdeV2UnstakeAbi,
|
|
827
|
+
functionName: "unstake",
|
|
828
|
+
args: [receiver]
|
|
829
|
+
});
|
|
830
|
+
const step = { to: susde, data, value: 0n };
|
|
831
|
+
const purposeSuffix = "Ethena: 1-tx \u2014 unstake (claim USDe after cooldown).";
|
|
832
|
+
return buildEthenaStakedUsdeOneTxMultisignBody({
|
|
833
|
+
keyGen: args.keyGen,
|
|
834
|
+
chainId: args.chainId,
|
|
835
|
+
rpcUrl: args.rpcUrl,
|
|
836
|
+
chainDetail: args.chainDetail,
|
|
837
|
+
useCustomGas: args.useCustomGas,
|
|
838
|
+
customGasChainDetails: args.customGasChainDetails,
|
|
839
|
+
susde,
|
|
840
|
+
executor,
|
|
841
|
+
step,
|
|
842
|
+
purposeText: args.purposeText,
|
|
843
|
+
purposeSuffix,
|
|
844
|
+
gasFallback: ETHENA_SUSDE_UNSTAKE_CLAIM_FALLBACK,
|
|
845
|
+
makeBatchMeta: ({ gasLimitI }) => ({
|
|
846
|
+
destinationAddress: susde,
|
|
847
|
+
signatureText: JSON.stringify({
|
|
848
|
+
kind: "Ethena",
|
|
849
|
+
name: "StakedUSDeV2.unstake",
|
|
850
|
+
function: "unstake(address receiver)",
|
|
851
|
+
receiver
|
|
852
|
+
}),
|
|
853
|
+
evm: { type: "ethena_susde_unstake", version: 1, chainId: "1" },
|
|
854
|
+
ethena: { step: "unstake", vault: susde, gasBuildUnstake: { baseGasUnits: gasLimitI.toString() } }
|
|
855
|
+
})
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
function parseExtraJsonObject(detail) {
|
|
859
|
+
if (!detail) return null;
|
|
860
|
+
const raw = detail.ExtraJSON ?? detail.extraJSON;
|
|
861
|
+
if (raw == null) return null;
|
|
862
|
+
if (typeof raw === "object" && !Array.isArray(raw)) return raw;
|
|
863
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
864
|
+
try {
|
|
865
|
+
const p = JSON.parse(raw);
|
|
866
|
+
if (p && typeof p === "object" && !Array.isArray(p)) return p;
|
|
867
|
+
} catch {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
function parseOptionalGasLimitString(raw) {
|
|
874
|
+
if (raw == null) return null;
|
|
875
|
+
const s = String(raw).trim();
|
|
876
|
+
if (!s) return null;
|
|
877
|
+
try {
|
|
878
|
+
if (/^0x[0-9a-fA-F]+$/.test(s)) return BigInt(s);
|
|
879
|
+
return BigInt(s);
|
|
880
|
+
} catch {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function isEthenaSusdeDepositBatchStep(detail, batchIndex) {
|
|
885
|
+
if (batchIndex < 0) return false;
|
|
886
|
+
const ex = parseExtraJsonObject(detail);
|
|
887
|
+
const bm = ex?.batchMeta;
|
|
888
|
+
if (!Array.isArray(bm) || !bm[batchIndex] || typeof bm[batchIndex] !== "object" || Array.isArray(bm[batchIndex])) {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
const evm = bm[batchIndex].evm;
|
|
892
|
+
return String(evm?.type ?? "") === "ethena_susde_deposit";
|
|
893
|
+
}
|
|
894
|
+
function resolveEthenaBatchStepGasFromSignRequest(detail, batchIndex) {
|
|
895
|
+
if (!detail || batchIndex < 0) return null;
|
|
896
|
+
const propBatch = detail.proposal_tx_params ?? detail.proposalTxParams ?? detail.ProposalTxParams;
|
|
897
|
+
if (Array.isArray(propBatch) && propBatch.length > batchIndex) {
|
|
898
|
+
const row = propBatch[batchIndex];
|
|
899
|
+
if (row && typeof row === "object" && !Array.isArray(row)) {
|
|
900
|
+
const gl = parseOptionalGasLimitString(row.gasLimit);
|
|
901
|
+
if (gl != null && gl > 0n) return gl;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const ex = parseExtraJsonObject(detail);
|
|
905
|
+
const bm = ex?.batchMeta;
|
|
906
|
+
if (!Array.isArray(bm) || !bm[batchIndex] || typeof bm[batchIndex] !== "object" || Array.isArray(bm[batchIndex])) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
const ethena = bm[batchIndex].ethena;
|
|
910
|
+
if (!ethena || typeof ethena !== "object") return null;
|
|
911
|
+
for (const key of ["gasBuildDeposit", "gasBuildCooldown", "gasBuildUnstake", "gasBuildRedeem"]) {
|
|
912
|
+
const g = ethena[key];
|
|
913
|
+
if (g && typeof g === "object") {
|
|
914
|
+
const p = parseOptionalGasLimitString(g.baseGasUnits);
|
|
915
|
+
if (p != null && p > 0n) return p;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/protocols/evm/ethena/index.ts
|
|
922
|
+
var ETHENA_PROTOCOL_ID = "ethena";
|
|
923
|
+
var ethenaProtocolModule = {
|
|
924
|
+
id: ETHENA_PROTOCOL_ID,
|
|
925
|
+
chainCategory: "evm",
|
|
926
|
+
isChainSupported(ctx) {
|
|
927
|
+
if (ctx.chainCategory !== "evm") return false;
|
|
928
|
+
const n = typeof ctx.chainId === "number" ? ctx.chainId : Number.parseInt(String(ctx.chainId), 10);
|
|
929
|
+
return isEvmChainInEthenaUsdeList(n);
|
|
930
|
+
},
|
|
931
|
+
isTokenSupported(token) {
|
|
932
|
+
return token.category === "evm" && token.kind === "erc20";
|
|
933
|
+
},
|
|
934
|
+
actions: [
|
|
935
|
+
{ id: "ethena.stake-usde", protocolId: ETHENA_PROTOCOL_ID, chainCategory: "evm", description: "Stake USDe \u2192 sUSDe", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: { amountHuman: { type: "string", required: true, description: "USDe amount" } } },
|
|
936
|
+
{ id: "ethena.redeem-susde", protocolId: ETHENA_PROTOCOL_ID, chainCategory: "evm", description: "Redeem sUSDe \u2192 USDe (no cooldown)", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
937
|
+
{ id: "ethena.cooldown-shares", protocolId: ETHENA_PROTOCOL_ID, chainCategory: "evm", description: "Start sUSDe cooldown", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
|
|
938
|
+
{ id: "ethena.claim-unstake", protocolId: ETHENA_PROTOCOL_ID, chainCategory: "evm", description: "Claim after cooldown", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} }
|
|
939
|
+
]
|
|
940
|
+
};
|
|
941
|
+
registerProtocolModule(ethenaProtocolModule);
|
|
942
|
+
|
|
943
|
+
exports.ETHENA_KEY_ADDRESSES_DOC = ETHENA_KEY_ADDRESSES_DOC;
|
|
944
|
+
exports.ETHENA_LABS_GITHUB = ETHENA_LABS_GITHUB;
|
|
945
|
+
exports.ETHENA_PROTOCOL_ID = ETHENA_PROTOCOL_ID;
|
|
946
|
+
exports.ETHENA_SUSDE_DEPOSIT_GET_SIG_GAS_FALLBACK = ETHENA_SUSDE_DEPOSIT_GET_SIG_GAS_FALLBACK;
|
|
947
|
+
exports.SUSDE_ETHEREUM_MAINNET = SUSDE_ETHEREUM_MAINNET;
|
|
948
|
+
exports.USDE_ETHEREUM_MAINNET = USDE_ETHEREUM_MAINNET;
|
|
949
|
+
exports.USDE_MOST_L2S = USDE_MOST_L2S;
|
|
950
|
+
exports.USDE_ZKSYNC_ERA = USDE_ZKSYNC_ERA;
|
|
951
|
+
exports.buildEvmMultisignBodyEthenaSusdeCooldownShares = buildEvmMultisignBodyEthenaSusdeCooldownShares;
|
|
952
|
+
exports.buildEvmMultisignBodyEthenaSusdeRedeemToUsde = buildEvmMultisignBodyEthenaSusdeRedeemToUsde;
|
|
953
|
+
exports.buildEvmMultisignBodyEthenaUnstakeClaim = buildEvmMultisignBodyEthenaUnstakeClaim;
|
|
954
|
+
exports.buildEvmMultisignBodyEthenaUsdeStakeToSusde = buildEvmMultisignBodyEthenaUsdeStakeToSusde;
|
|
955
|
+
exports.ethenaProtocolModule = ethenaProtocolModule;
|
|
956
|
+
exports.isEthenaSusdeDepositBatchStep = isEthenaSusdeDepositBatchStep;
|
|
957
|
+
exports.isEthenaUsdeOnAssetsChain = isEthenaUsdeOnAssetsChain;
|
|
958
|
+
exports.isEvmChainInEthenaUsdeList = isEvmChainInEthenaUsdeList;
|
|
959
|
+
exports.listEthenaUsdeEvmNetworkRows = listEthenaUsdeEvmNetworkRows;
|
|
960
|
+
exports.readEthenaStakePreviewShares = readEthenaStakePreviewShares;
|
|
961
|
+
exports.readEthenaUnstakePreviewUsde = readEthenaUnstakePreviewUsde;
|
|
962
|
+
exports.resolveEthenaBatchStepGasFromSignRequest = resolveEthenaBatchStepGasFromSignRequest;
|
|
963
|
+
exports.usdeTokenAddressOnEvmChain = usdeTokenAddressOnEvmChain;
|
|
964
|
+
//# sourceMappingURL=index.cjs.map
|
|
965
|
+
//# sourceMappingURL=index.cjs.map
|