@stablechain/sdk 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1557 -804
- package/dist/index.d.cts +71 -2
- package/dist/index.d.ts +71 -2
- package/dist/index.js +1567 -814
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
var _viem = require('viem');
|
|
10
10
|
var _chains = require('viem/chains');
|
|
11
11
|
|
|
12
|
-
// src/earn/
|
|
12
|
+
// src/earn/morpho.ts
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
var _morphosdk = require('@morpho-org/morpho-sdk');
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
|
|
@@ -19,7 +24,6 @@ var _chains = require('viem/chains');
|
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
|
|
22
|
-
var _morphosdk = require('@morpho-org/morpho-sdk');
|
|
23
27
|
|
|
24
28
|
// src/errors.ts
|
|
25
29
|
|
|
@@ -39,7 +43,8 @@ var StableValidationError = class extends StableError {
|
|
|
39
43
|
var StableQuoteError = class extends StableError {
|
|
40
44
|
constructor(args) {
|
|
41
45
|
const meta = [`Provider: ${args.provider}`];
|
|
42
|
-
if (args.httpStatus !== void 0)
|
|
46
|
+
if (args.httpStatus !== void 0)
|
|
47
|
+
meta.push(`HTTP status: ${args.httpStatus}`);
|
|
43
48
|
if (args.providerCode) meta.push(`Provider code: ${args.providerCode}`);
|
|
44
49
|
super(args.shortMessage, {
|
|
45
50
|
name: "StableQuoteError",
|
|
@@ -121,7 +126,11 @@ var HttpError = class extends Error {
|
|
|
121
126
|
};
|
|
122
127
|
function isRetryableError(err) {
|
|
123
128
|
if (err instanceof HttpError) return err.retryable;
|
|
124
|
-
|
|
129
|
+
if (err instanceof TypeError) {
|
|
130
|
+
const msg = err.message.toLowerCase();
|
|
131
|
+
return !(msg.includes("cors") || msg.includes("blocked"));
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
125
134
|
}
|
|
126
135
|
async function retryWithBackoff(fn, isRetryable = isRetryableError, opts = {}) {
|
|
127
136
|
const maxAttempts = _nullishCoalesce(opts.maxAttempts, () => ( 3));
|
|
@@ -142,550 +151,870 @@ async function retryWithBackoff(fn, isRetryable = isRetryableError, opts = {}) {
|
|
|
142
151
|
throw lastErr;
|
|
143
152
|
}
|
|
144
153
|
|
|
145
|
-
// src/
|
|
146
|
-
var
|
|
147
|
-
Chain2["Sepolia"] = "sepolia";
|
|
148
|
-
Chain2["StableTestnet"] = "stable-testnet";
|
|
149
|
-
Chain2["Stable"] = "stable";
|
|
150
|
-
Chain2["Ethereum"] = "ethereum";
|
|
151
|
-
Chain2["Arbitrum"] = "arbitrum";
|
|
152
|
-
Chain2["Ink"] = "ink";
|
|
153
|
-
Chain2["Bera"] = "bera";
|
|
154
|
-
Chain2["MegaETH"] = "megaeth";
|
|
155
|
-
Chain2["Base"] = "base";
|
|
156
|
-
Chain2["BSC"] = "bsc";
|
|
157
|
-
Chain2["HyperEVM"] = "hyperevm";
|
|
158
|
-
return Chain2;
|
|
159
|
-
})(Chain || {});
|
|
160
|
-
var Network = /* @__PURE__ */ ((Network2) => {
|
|
161
|
-
Network2["Mainnet"] = "mainnet";
|
|
162
|
-
Network2["Testnet"] = "testnet";
|
|
163
|
-
return Network2;
|
|
164
|
-
})(Network || {});
|
|
165
|
-
|
|
166
|
-
// src/earn/reader.ts
|
|
167
|
-
var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
|
|
168
|
-
var EarnReader = class {
|
|
154
|
+
// src/earn/morpho.ts
|
|
155
|
+
var MorphoService = class {
|
|
169
156
|
constructor(config) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
157
|
+
this.config = config;
|
|
158
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
159
|
+
this.chain = makeChain(config.chainId, config.rpc);
|
|
160
|
+
this.wc = _nullishCoalesce(config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
161
|
+
account: config.account,
|
|
162
|
+
chain: this.chain,
|
|
163
|
+
transport: _nullishCoalesce(config.transport, () => ( _viem.http.call(void 0, config.rpc)))
|
|
164
|
+
})));
|
|
165
|
+
const morphoWc = _optionalChain([this, 'access', _2 => _2.wc, 'access', _3 => _3.chain, 'optionalAccess', _4 => _4.id]) === config.chainId ? this.wc : _viem.createWalletClient.call(void 0, {
|
|
166
|
+
account: this.wc.account,
|
|
167
|
+
chain: this.chain,
|
|
168
|
+
transport: _viem.custom.call(void 0, {
|
|
169
|
+
request: this.wc.request.bind(this.wc)
|
|
170
|
+
})
|
|
171
|
+
});
|
|
172
|
+
this.morpho = new (0, _morphosdk.MorphoClient)(morphoWc, { supportSignature: true });
|
|
173
|
+
this.morphoNoSig = new (0, _morphosdk.MorphoClient)(morphoWc, { supportSignature: false });
|
|
174
|
+
this.pc = _viem.createPublicClient.call(void 0, {
|
|
175
|
+
chain: this.chain,
|
|
176
|
+
transport: _viem.http.call(void 0, config.rpc)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
deposit(params) {
|
|
180
|
+
this._validateAmount(params.amount, "earn deposit");
|
|
181
|
+
if (params.depositBuffer !== void 0 && (params.depositBuffer <= 0 || params.depositBuffer > 1)) {
|
|
185
182
|
throw new StableValidationError(
|
|
186
|
-
"
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
"depositBuffer",
|
|
184
|
+
params.depositBuffer,
|
|
185
|
+
"earn deposit: depositBuffer must be > 0 and \u2264 1"
|
|
189
186
|
);
|
|
190
187
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
this.vaultAddr = config.earn.vault;
|
|
196
|
-
this.userAddr = config.address;
|
|
197
|
-
const chain = makeChain(viemChain.id, rpc);
|
|
198
|
-
this.pc = _viem.createPublicClient.call(void 0, { chain, transport: _viem.http.call(void 0, rpc) });
|
|
199
|
-
this.morpho = new (0, _morphosdk.MorphoClient)(this.pc);
|
|
188
|
+
return this._withIdempotency(
|
|
189
|
+
params.idempotencyKey,
|
|
190
|
+
() => this._deposit(params)
|
|
191
|
+
);
|
|
200
192
|
}
|
|
201
|
-
async
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
193
|
+
async bulkDeposit(items, options = {}) {
|
|
194
|
+
const { idempotencyKey, stopOnError = false } = options;
|
|
195
|
+
const run = async () => {
|
|
196
|
+
const results = [];
|
|
197
|
+
for (const item of items) {
|
|
198
|
+
try {
|
|
199
|
+
const result = await this.deposit(item);
|
|
200
|
+
results.push({
|
|
201
|
+
status: "fulfilled",
|
|
202
|
+
txHash: result.txHash,
|
|
203
|
+
vault: item.vault,
|
|
204
|
+
idempotencyKey: item.idempotencyKey
|
|
205
|
+
});
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const entry = {
|
|
208
|
+
status: "rejected",
|
|
209
|
+
error: String(err),
|
|
210
|
+
vault: item.vault,
|
|
211
|
+
idempotencyKey: item.idempotencyKey
|
|
212
|
+
};
|
|
213
|
+
results.push(entry);
|
|
214
|
+
if (stopOnError) {
|
|
215
|
+
const succeeded2 = results.filter(
|
|
216
|
+
(r) => r.status === "fulfilled"
|
|
217
|
+
).length;
|
|
218
|
+
throw new StableBulkError({
|
|
219
|
+
shortMessage: "bulkDeposit halted on first failure",
|
|
220
|
+
results,
|
|
221
|
+
succeeded: succeeded2,
|
|
222
|
+
failed: results.length - succeeded2
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const succeeded = results.filter(
|
|
228
|
+
(r) => r.status === "fulfilled"
|
|
229
|
+
).length;
|
|
230
|
+
return {
|
|
231
|
+
results,
|
|
232
|
+
succeeded,
|
|
233
|
+
failed: results.length - succeeded,
|
|
234
|
+
idempotencyKey
|
|
235
|
+
};
|
|
224
236
|
};
|
|
237
|
+
return this._withIdempotency(idempotencyKey, run);
|
|
225
238
|
}
|
|
226
|
-
async
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
239
|
+
async bulkWithdraw(items, options = {}) {
|
|
240
|
+
const { idempotencyKey, stopOnError = false } = options;
|
|
241
|
+
const run = async () => {
|
|
242
|
+
const results = [];
|
|
243
|
+
for (const item of items) {
|
|
244
|
+
try {
|
|
245
|
+
const result = await this.withdraw(item);
|
|
246
|
+
results.push({
|
|
247
|
+
status: "fulfilled",
|
|
248
|
+
txHash: result.txHash,
|
|
249
|
+
idempotencyKey: item.idempotencyKey
|
|
250
|
+
});
|
|
251
|
+
} catch (err) {
|
|
252
|
+
const entry = {
|
|
253
|
+
status: "rejected",
|
|
254
|
+
error: String(err),
|
|
255
|
+
idempotencyKey: item.idempotencyKey
|
|
256
|
+
};
|
|
257
|
+
results.push(entry);
|
|
258
|
+
if (stopOnError) {
|
|
259
|
+
const succeeded2 = results.filter(
|
|
260
|
+
(r) => r.status === "fulfilled"
|
|
261
|
+
).length;
|
|
262
|
+
throw new StableBulkError({
|
|
263
|
+
shortMessage: "bulkWithdraw halted on first failure",
|
|
264
|
+
results,
|
|
265
|
+
succeeded: succeeded2,
|
|
266
|
+
failed: results.length - succeeded2
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const succeeded = results.filter(
|
|
272
|
+
(r) => r.status === "fulfilled"
|
|
273
|
+
).length;
|
|
274
|
+
return {
|
|
275
|
+
results,
|
|
276
|
+
succeeded,
|
|
277
|
+
failed: results.length - succeeded,
|
|
278
|
+
idempotencyKey
|
|
279
|
+
};
|
|
236
280
|
};
|
|
281
|
+
return this._withIdempotency(idempotencyKey, run);
|
|
237
282
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
functionName: "decimals"
|
|
245
|
-
})));
|
|
246
|
-
const requestedRaw = _viem.parseUnits.call(void 0, String(params.amount), tokenDecimals);
|
|
247
|
-
const availableLiquidityRaw = vaultData.assetBalance;
|
|
248
|
-
return {
|
|
249
|
-
isInstant: requestedRaw <= availableLiquidityRaw,
|
|
250
|
-
availableLiquidity: Number(_viem.formatUnits.call(void 0, availableLiquidityRaw, tokenDecimals)),
|
|
251
|
-
tokenDecimals
|
|
252
|
-
};
|
|
283
|
+
withdraw(params) {
|
|
284
|
+
this._validateAmount(params.amount, "earn withdraw");
|
|
285
|
+
return this._withIdempotency(
|
|
286
|
+
params.idempotencyKey,
|
|
287
|
+
() => this._withdraw(params)
|
|
288
|
+
);
|
|
253
289
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return
|
|
290
|
+
redeem(params) {
|
|
291
|
+
this._validateShares(params.shares, "earn redeem");
|
|
292
|
+
return this._withIdempotency(
|
|
293
|
+
params.idempotencyKey,
|
|
294
|
+
() => this._redeem(params)
|
|
295
|
+
);
|
|
257
296
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
body: JSON.stringify({
|
|
281
|
-
query,
|
|
282
|
-
variables: { address: this.vaultAddr, chainId: this.chainId }
|
|
283
|
-
})
|
|
284
|
-
});
|
|
285
|
-
if (!res.ok) {
|
|
286
|
-
const retryable = res.status === 429 || res.status >= 500;
|
|
287
|
-
throw new HttpError(
|
|
288
|
-
`earn: Morpho API returned ${res.status} \u2014 ${res.statusText}`,
|
|
289
|
-
res.status,
|
|
290
|
-
retryable
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
return res.json().catch(() => void 0);
|
|
294
|
-
},
|
|
295
|
-
(err) => err instanceof HttpError ? err.retryable : err instanceof TypeError
|
|
297
|
+
forceWithdraw(params) {
|
|
298
|
+
this._validateAmount(params.amount, "earn forceWithdraw");
|
|
299
|
+
return this._withIdempotency(
|
|
300
|
+
params.idempotencyKey,
|
|
301
|
+
() => this._forceWithdraw(params)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
forceRedeem(params) {
|
|
305
|
+
this._validateShares(params.shares, "earn forceRedeem");
|
|
306
|
+
return this._withIdempotency(
|
|
307
|
+
params.idempotencyKey,
|
|
308
|
+
() => this._forceRedeem(params)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
// ─── Calldata preparation (no signing) ───────────────────────────────────
|
|
312
|
+
async prepareDepositCalldata(params) {
|
|
313
|
+
this._validateAmount(params.amount, "prepareDepositCalldata");
|
|
314
|
+
if (params.depositBuffer !== void 0 && (params.depositBuffer <= 0 || params.depositBuffer > 1)) {
|
|
315
|
+
throw new StableValidationError(
|
|
316
|
+
"depositBuffer",
|
|
317
|
+
params.depositBuffer,
|
|
318
|
+
"prepareDepositCalldata: depositBuffer must be > 0 and \u2264 1"
|
|
296
319
|
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
320
|
+
}
|
|
321
|
+
const { amount, tokenDecimals, slippageTolerance, depositBuffer } = params;
|
|
322
|
+
const from = this._requireFrom();
|
|
323
|
+
const vaultAddr = _nullishCoalesce(params.vault, () => ( this.config.vault));
|
|
324
|
+
const vault = this.morphoNoSig.vaultV2(vaultAddr, this.config.chainId);
|
|
325
|
+
const vaultData = await vault.getData();
|
|
326
|
+
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "prepareDepositCalldata")));
|
|
327
|
+
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
328
|
+
if (depositBuffer !== void 0) {
|
|
329
|
+
const bps = BigInt(Math.round(depositBuffer * 1e4));
|
|
330
|
+
const maxAllowed = vaultData.assetBalance * bps / 10000n;
|
|
331
|
+
if (assetsRaw > maxAllowed) {
|
|
332
|
+
const maxFormatted = Number(_viem.formatUnits.call(void 0, maxAllowed, decimals));
|
|
333
|
+
const idleFormatted = Number(_viem.formatUnits.call(void 0, vaultData.assetBalance, decimals));
|
|
334
|
+
throw new StableValidationError(
|
|
335
|
+
"amount",
|
|
336
|
+
amount,
|
|
337
|
+
`prepareDepositCalldata: amount ${amount} exceeds deposit buffer \u2014 max ${maxFormatted} (${depositBuffer * 100}% of ${idleFormatted} idle liquidity)`
|
|
338
|
+
);
|
|
304
339
|
}
|
|
305
|
-
throw err;
|
|
306
340
|
}
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
341
|
+
const depositAction = vault.deposit({
|
|
342
|
+
amount: assetsRaw,
|
|
343
|
+
userAddress: from,
|
|
344
|
+
accrualVault: vaultData,
|
|
345
|
+
slippageTolerance: slippageTolerance !== void 0 ? _viem.parseUnits.call(void 0, String(slippageTolerance), 18) : void 0
|
|
346
|
+
});
|
|
347
|
+
const requirements = await depositAction.getRequirements();
|
|
348
|
+
const steps = [];
|
|
349
|
+
for (const req of requirements) {
|
|
350
|
+
if (_morphosdk.isRequirementApproval.call(void 0, req)) {
|
|
351
|
+
steps.push({ to: req.to, data: req.data, value: req.value });
|
|
352
|
+
}
|
|
314
353
|
}
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
native: _nullishCoalesce(vault.netApyExcludingRewards, () => ( 0)),
|
|
319
|
-
rewards,
|
|
320
|
-
performanceFee: vault.performanceFee,
|
|
321
|
-
managementFee: vault.managementFee
|
|
322
|
-
};
|
|
354
|
+
const tx = depositAction.buildTx(void 0);
|
|
355
|
+
steps.push({ to: tx.to, data: tx.data, value: tx.value });
|
|
356
|
+
return { steps, chainId: this.config.chainId };
|
|
323
357
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
usdt: "0xc4DCC311c028e341fd8602D8eB89c5de94625927",
|
|
344
|
-
decimals: 18
|
|
345
|
-
},
|
|
346
|
-
["stable-testnet" /* StableTestnet */]: {
|
|
347
|
-
id: 2201,
|
|
348
|
-
rpc: "https://rpc.testnet.stable.xyz",
|
|
349
|
-
usdt: "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9",
|
|
350
|
-
decimals: 6
|
|
351
|
-
},
|
|
352
|
-
// ─── Mainnet ──────────────────────────────────────────────────────────────
|
|
353
|
-
["stable" /* Stable */]: {
|
|
354
|
-
id: 988,
|
|
355
|
-
rpc: "https://rpc.stable.xyz",
|
|
356
|
-
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
357
|
-
decimals: 6
|
|
358
|
-
},
|
|
359
|
-
["ethereum" /* Ethereum */]: {
|
|
360
|
-
id: 1,
|
|
361
|
-
rpc: "https://ethereum-rpc.publicnode.com",
|
|
362
|
-
usdt: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
363
|
-
decimals: 6
|
|
364
|
-
},
|
|
365
|
-
["arbitrum" /* Arbitrum */]: {
|
|
366
|
-
id: 42161,
|
|
367
|
-
rpc: "https://arbitrum-one-rpc.publicnode.com",
|
|
368
|
-
usdt: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92",
|
|
369
|
-
decimals: 6
|
|
370
|
-
},
|
|
371
|
-
["ink" /* Ink */]: {
|
|
372
|
-
id: 57073,
|
|
373
|
-
rpc: "https://rpc-gel.inkonchain.com",
|
|
374
|
-
usdt: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
375
|
-
decimals: 6
|
|
376
|
-
},
|
|
377
|
-
["bera" /* Bera */]: {
|
|
378
|
-
id: 80094,
|
|
379
|
-
rpc: "https://rpc.berachain.com",
|
|
380
|
-
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
381
|
-
decimals: 6
|
|
382
|
-
},
|
|
383
|
-
["megaeth" /* MegaETH */]: {
|
|
384
|
-
id: 4326,
|
|
385
|
-
rpc: "https://carrot.megaeth.com",
|
|
386
|
-
usdt: "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb",
|
|
387
|
-
decimals: 6
|
|
388
|
-
},
|
|
389
|
-
["base" /* Base */]: {
|
|
390
|
-
id: 8453,
|
|
391
|
-
rpc: "https://mainnet.base.org",
|
|
392
|
-
usdt: "0x102d758f688a4C1C5a80b116bD945d4455460282",
|
|
393
|
-
decimals: 6
|
|
394
|
-
},
|
|
395
|
-
["bsc" /* BSC */]: {
|
|
396
|
-
id: 56,
|
|
397
|
-
rpc: "https://bsc-rpc.publicnode.com",
|
|
398
|
-
usdt: "0x55d398326f99059fF775485246999027B3197955",
|
|
399
|
-
decimals: 18
|
|
400
|
-
},
|
|
401
|
-
["hyperevm" /* HyperEVM */]: {
|
|
402
|
-
id: 999,
|
|
403
|
-
rpc: "https://rpc.hyperliquid.xyz/evm",
|
|
404
|
-
usdt: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
|
|
405
|
-
decimals: 6
|
|
358
|
+
async prepareWithdrawCalldata(params) {
|
|
359
|
+
this._validateAmount(params.amount, "prepareWithdrawCalldata");
|
|
360
|
+
const { amount, tokenDecimals } = params;
|
|
361
|
+
const from = this._requireFrom();
|
|
362
|
+
const vaultAddr = this.config.vault;
|
|
363
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
364
|
+
const vaultData = await vault.getData();
|
|
365
|
+
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "prepareWithdrawCalldata")));
|
|
366
|
+
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
367
|
+
const tx = vaultData.assetBalance >= assetsRaw ? vault.withdraw({ amount: assetsRaw, userAddress: from }).buildTx() : vault.forceWithdraw({
|
|
368
|
+
deallocations: this._buildAutoDeallocs(
|
|
369
|
+
vaultData.accrualAdapters,
|
|
370
|
+
assetsRaw - vaultData.assetBalance,
|
|
371
|
+
amount
|
|
372
|
+
),
|
|
373
|
+
withdraw: { amount: assetsRaw },
|
|
374
|
+
userAddress: from
|
|
375
|
+
}).buildTx();
|
|
376
|
+
return { to: tx.to, data: tx.data, value: tx.value, chainId: this.config.chainId };
|
|
406
377
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
378
|
+
async prepareRedeemCalldata(params) {
|
|
379
|
+
this._validateShares(params.shares, "prepareRedeemCalldata");
|
|
380
|
+
const { shares, shareDecimals } = params;
|
|
381
|
+
const from = this._requireFrom();
|
|
382
|
+
const vaultAddr = this.config.vault;
|
|
383
|
+
let decimals = shareDecimals;
|
|
384
|
+
if (decimals === void 0) {
|
|
385
|
+
try {
|
|
386
|
+
decimals = await this.pc.readContract({
|
|
387
|
+
address: vaultAddr,
|
|
388
|
+
abi: _viem.erc20Abi,
|
|
389
|
+
functionName: "decimals"
|
|
390
|
+
});
|
|
391
|
+
} catch (e2) {
|
|
392
|
+
throw new StableValidationError(
|
|
393
|
+
"shareDecimals",
|
|
394
|
+
void 0,
|
|
395
|
+
`prepareRedeemCalldata: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const sharesRaw = _viem.parseUnits.call(void 0, String(shares), decimals);
|
|
400
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
401
|
+
const vaultData = await vault.getData();
|
|
402
|
+
const assetsEquivalent = vaultData.toAssets(sharesRaw);
|
|
403
|
+
const tx = vaultData.assetBalance >= assetsEquivalent ? vault.redeem({ shares: sharesRaw, userAddress: from }).buildTx() : vault.forceRedeem({
|
|
404
|
+
deallocations: this._buildAutoDeallocs(
|
|
405
|
+
vaultData.accrualAdapters,
|
|
406
|
+
assetsEquivalent - vaultData.assetBalance,
|
|
407
|
+
shares
|
|
408
|
+
),
|
|
409
|
+
redeem: { shares: sharesRaw },
|
|
410
|
+
userAddress: from
|
|
411
|
+
}).buildTx();
|
|
412
|
+
return { to: tx.to, data: tx.data, value: tx.value, chainId: this.config.chainId };
|
|
413
413
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const {
|
|
414
|
+
// ─── Private operations ──────────────────────────────────────────────────
|
|
415
|
+
async _deposit(params) {
|
|
416
|
+
const {
|
|
417
|
+
amount,
|
|
418
|
+
tokenDecimals,
|
|
419
|
+
slippageTolerance,
|
|
420
|
+
idempotencyKey,
|
|
421
|
+
depositBuffer
|
|
422
|
+
} = params;
|
|
417
423
|
const from = this._requireFrom();
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
424
|
+
const vaultAddr = _nullishCoalesce(params.vault, () => ( this.config.vault));
|
|
425
|
+
await this._switchChain();
|
|
426
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
427
|
+
const vaultData = await vault.getData();
|
|
428
|
+
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn deposit")));
|
|
429
|
+
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
430
|
+
if (depositBuffer !== void 0) {
|
|
431
|
+
const bps = BigInt(Math.round(depositBuffer * 1e4));
|
|
432
|
+
const maxAllowed = vaultData.assetBalance * bps / 10000n;
|
|
433
|
+
if (assetsRaw > maxAllowed) {
|
|
434
|
+
const maxFormatted = Number(_viem.formatUnits.call(void 0, maxAllowed, decimals));
|
|
435
|
+
const idleFormatted = Number(
|
|
436
|
+
_viem.formatUnits.call(void 0, vaultData.assetBalance, decimals)
|
|
437
|
+
);
|
|
438
|
+
throw new StableValidationError(
|
|
439
|
+
"amount",
|
|
440
|
+
amount,
|
|
441
|
+
`earn deposit: amount ${amount} exceeds deposit buffer \u2014 max ${maxFormatted} (${depositBuffer * 100}% of ${idleFormatted} idle liquidity)`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const depositAction = vault.deposit({
|
|
446
|
+
amount: assetsRaw,
|
|
447
|
+
userAddress: from,
|
|
448
|
+
accrualVault: vaultData,
|
|
449
|
+
slippageTolerance: slippageTolerance !== void 0 ? (
|
|
450
|
+
// Morpho SDK expects slippage as a WAD-scaled fraction (1e18 = 100%)
|
|
451
|
+
_viem.parseUnits.call(void 0, String(slippageTolerance), 18)
|
|
452
|
+
) : void 0
|
|
428
453
|
});
|
|
429
|
-
const
|
|
430
|
-
|
|
454
|
+
const requirements = await depositAction.getRequirements();
|
|
455
|
+
let sig;
|
|
456
|
+
for (const req of requirements) {
|
|
457
|
+
if (_morphosdk.isRequirementApproval.call(void 0, req)) {
|
|
458
|
+
const approveHash = await this._sendTx(
|
|
459
|
+
req.to,
|
|
460
|
+
req.data,
|
|
461
|
+
req.value,
|
|
462
|
+
from
|
|
463
|
+
);
|
|
464
|
+
const approveReceipt = await this.pc.waitForTransactionReceipt({
|
|
465
|
+
hash: approveHash
|
|
466
|
+
});
|
|
467
|
+
if (approveReceipt.status === "reverted") {
|
|
468
|
+
throw new StableTransactionError({
|
|
469
|
+
phase: "approve",
|
|
470
|
+
shortMessage: "earn deposit: token approval reverted on-chain",
|
|
471
|
+
txHash: approveReceipt.transactionHash,
|
|
472
|
+
chainId: this.config.chainId
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
} else if (_morphosdk.isRequirementSignature.call(void 0, req)) {
|
|
476
|
+
sig = await req.sign(this.wc, from);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const tx = depositAction.buildTx(sig);
|
|
480
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
481
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
482
|
+
if (receipt.status === "reverted") {
|
|
483
|
+
throw new StableTransactionError({
|
|
484
|
+
phase: "receipt",
|
|
485
|
+
shortMessage: "earn deposit: vault deposit reverted on-chain",
|
|
486
|
+
txHash: receipt.transactionHash,
|
|
487
|
+
chainId: this.config.chainId
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
431
491
|
}
|
|
432
|
-
async
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
492
|
+
async _withdraw(params) {
|
|
493
|
+
const { amount, tokenDecimals, idempotencyKey } = params;
|
|
494
|
+
const from = this._requireFrom();
|
|
495
|
+
const vaultAddr = this.config.vault;
|
|
496
|
+
await this._switchChain();
|
|
497
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
498
|
+
const vaultData = await vault.getData();
|
|
499
|
+
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn withdraw")));
|
|
500
|
+
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
501
|
+
const tx = vaultData.assetBalance >= assetsRaw ? vault.withdraw({ amount: assetsRaw, userAddress: from }).buildTx() : vault.forceWithdraw({
|
|
502
|
+
deallocations: this._buildAutoDeallocs(
|
|
503
|
+
vaultData.accrualAdapters,
|
|
504
|
+
assetsRaw - vaultData.assetBalance,
|
|
505
|
+
amount
|
|
506
|
+
),
|
|
507
|
+
withdraw: { amount: assetsRaw },
|
|
508
|
+
userAddress: from
|
|
509
|
+
}).buildTx();
|
|
510
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
511
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
512
|
+
if (receipt.status === "reverted") {
|
|
513
|
+
throw new StableTransactionError({
|
|
514
|
+
phase: "receipt",
|
|
515
|
+
shortMessage: "earn withdraw: vault withdraw reverted on-chain",
|
|
516
|
+
txHash: receipt.transactionHash,
|
|
517
|
+
chainId: this.config.chainId
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
436
521
|
}
|
|
437
|
-
async
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
522
|
+
async _redeem(params) {
|
|
523
|
+
const { shares, shareDecimals, idempotencyKey } = params;
|
|
524
|
+
const from = this._requireFrom();
|
|
525
|
+
const vaultAddr = this.config.vault;
|
|
526
|
+
await this._switchChain();
|
|
527
|
+
let decimals = shareDecimals;
|
|
528
|
+
if (decimals === void 0) {
|
|
529
|
+
try {
|
|
530
|
+
decimals = await this.pc.readContract({
|
|
531
|
+
address: vaultAddr,
|
|
532
|
+
abi: _viem.erc20Abi,
|
|
533
|
+
functionName: "decimals"
|
|
534
|
+
});
|
|
535
|
+
} catch (e3) {
|
|
536
|
+
throw new StableValidationError(
|
|
537
|
+
"shareDecimals",
|
|
538
|
+
void 0,
|
|
539
|
+
`earn redeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const sharesRaw = _viem.parseUnits.call(void 0, String(shares), decimals);
|
|
544
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
545
|
+
const vaultData = await vault.getData();
|
|
546
|
+
const assetsEquivalent = vaultData.toAssets(sharesRaw);
|
|
547
|
+
const tx = vaultData.assetBalance >= assetsEquivalent ? vault.redeem({ shares: sharesRaw, userAddress: from }).buildTx() : vault.forceRedeem({
|
|
548
|
+
deallocations: this._buildAutoDeallocs(
|
|
549
|
+
vaultData.accrualAdapters,
|
|
550
|
+
assetsEquivalent - vaultData.assetBalance,
|
|
551
|
+
shares
|
|
552
|
+
),
|
|
553
|
+
redeem: { shares: sharesRaw },
|
|
554
|
+
userAddress: from
|
|
555
|
+
}).buildTx();
|
|
556
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
557
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
558
|
+
if (receipt.status === "reverted") {
|
|
559
|
+
throw new StableTransactionError({
|
|
560
|
+
phase: "receipt",
|
|
561
|
+
shortMessage: "earn redeem: vault redeem reverted on-chain",
|
|
562
|
+
txHash: receipt.transactionHash,
|
|
563
|
+
chainId: this.config.chainId
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
456
567
|
}
|
|
457
|
-
async
|
|
458
|
-
|
|
459
|
-
const { fromChain, toChain, fromToken, toToken, amount, fromDecimals = 6, recipient } = params;
|
|
568
|
+
async _forceWithdraw(params) {
|
|
569
|
+
const { amount, tokenDecimals, deallocations, idempotencyKey } = params;
|
|
460
570
|
const from = this._requireFrom();
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
571
|
+
const vaultAddr = this.config.vault;
|
|
572
|
+
await this._switchChain();
|
|
573
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
574
|
+
let decimals = tokenDecimals;
|
|
575
|
+
if (decimals === void 0) {
|
|
576
|
+
const vaultData = await vault.getData();
|
|
577
|
+
decimals = await this._resolveDecimals(
|
|
578
|
+
vaultData.asset,
|
|
579
|
+
"earn forceWithdraw"
|
|
580
|
+
);
|
|
465
581
|
}
|
|
466
|
-
|
|
467
|
-
|
|
582
|
+
const tx = vault.forceWithdraw({
|
|
583
|
+
deallocations: this._buildDeallocs(deallocations, decimals),
|
|
584
|
+
withdraw: { amount: _viem.parseUnits.call(void 0, String(amount), decimals) },
|
|
585
|
+
userAddress: from
|
|
586
|
+
}).buildTx();
|
|
587
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
588
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
589
|
+
if (receipt.status === "reverted") {
|
|
590
|
+
throw new StableTransactionError({
|
|
591
|
+
phase: "receipt",
|
|
592
|
+
shortMessage: "earn forceWithdraw: vault forceWithdraw reverted on-chain",
|
|
593
|
+
txHash: receipt.transactionHash,
|
|
594
|
+
chainId: this.config.chainId
|
|
595
|
+
});
|
|
468
596
|
}
|
|
469
|
-
|
|
470
|
-
const qs = new URLSearchParams({
|
|
471
|
-
fromChain: src.id.toString(),
|
|
472
|
-
toChain: dst.id.toString(),
|
|
473
|
-
fromToken,
|
|
474
|
-
toToken,
|
|
475
|
-
fromAmount: fromAmountRaw,
|
|
476
|
-
fromAddress: from,
|
|
477
|
-
...recipient && { toAddress: recipient }
|
|
478
|
-
});
|
|
479
|
-
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
480
|
-
return this._executeSwapQuote(quote, src.rpc, src.id);
|
|
597
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
481
598
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
599
|
+
async _forceRedeem(params) {
|
|
600
|
+
const {
|
|
601
|
+
shares,
|
|
602
|
+
shareDecimals,
|
|
603
|
+
tokenDecimals,
|
|
604
|
+
deallocations,
|
|
605
|
+
idempotencyKey
|
|
606
|
+
} = params;
|
|
607
|
+
const from = this._requireFrom();
|
|
608
|
+
const vaultAddr = this.config.vault;
|
|
609
|
+
await this._switchChain();
|
|
610
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
611
|
+
let shareDec = shareDecimals;
|
|
612
|
+
if (shareDec === void 0) {
|
|
613
|
+
try {
|
|
614
|
+
shareDec = await this.pc.readContract({
|
|
615
|
+
address: vaultAddr,
|
|
616
|
+
abi: _viem.erc20Abi,
|
|
617
|
+
functionName: "decimals"
|
|
618
|
+
});
|
|
619
|
+
} catch (e4) {
|
|
620
|
+
throw new StableValidationError(
|
|
621
|
+
"shareDecimals",
|
|
622
|
+
void 0,
|
|
623
|
+
`earn forceRedeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
624
|
+
);
|
|
625
|
+
}
|
|
485
626
|
}
|
|
486
|
-
|
|
487
|
-
|
|
627
|
+
let assetDec = tokenDecimals;
|
|
628
|
+
if (assetDec === void 0) {
|
|
629
|
+
const vaultData = await vault.getData();
|
|
630
|
+
assetDec = await this._resolveDecimals(
|
|
631
|
+
vaultData.asset,
|
|
632
|
+
"earn forceRedeem"
|
|
633
|
+
);
|
|
488
634
|
}
|
|
489
|
-
|
|
490
|
-
|
|
635
|
+
const tx = vault.forceRedeem({
|
|
636
|
+
deallocations: this._buildDeallocs(deallocations, assetDec),
|
|
637
|
+
redeem: { shares: _viem.parseUnits.call(void 0, String(shares), shareDec) },
|
|
638
|
+
userAddress: from
|
|
639
|
+
}).buildTx();
|
|
640
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
641
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
642
|
+
if (receipt.status === "reverted") {
|
|
643
|
+
throw new StableTransactionError({
|
|
644
|
+
phase: "receipt",
|
|
645
|
+
shortMessage: "earn forceRedeem: vault forceRedeem reverted on-chain",
|
|
646
|
+
txHash: receipt.transactionHash,
|
|
647
|
+
chainId: this.config.chainId
|
|
648
|
+
});
|
|
491
649
|
}
|
|
492
|
-
|
|
493
|
-
|
|
650
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
651
|
+
}
|
|
652
|
+
// Automatically derives the minimum set of adapter deallocations needed to cover `deficit`
|
|
653
|
+
// when the vault has insufficient idle cash for a direct withdraw/redeem. Adds a 0.5%
|
|
654
|
+
// buffer to account for interest accrual between vaultData read and tx execution.
|
|
655
|
+
_buildAutoDeallocs(adapters, deficit, amount) {
|
|
656
|
+
const needed = deficit + deficit * 50n / 10000n;
|
|
657
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
658
|
+
const result = [];
|
|
659
|
+
let remaining = needed;
|
|
660
|
+
for (const adapter of adapters) {
|
|
661
|
+
if (remaining === 0n) break;
|
|
662
|
+
remaining = this._pullFromAdapter(adapter, remaining, now, result);
|
|
494
663
|
}
|
|
495
|
-
if (
|
|
496
|
-
throw new StableValidationError(
|
|
664
|
+
if (remaining > 0n) {
|
|
665
|
+
throw new StableValidationError(
|
|
666
|
+
"amount",
|
|
667
|
+
amount,
|
|
668
|
+
"earn withdraw: insufficient adapter liquidity \u2014 reduce the amount or wait for market liquidity to free up"
|
|
669
|
+
);
|
|
497
670
|
}
|
|
671
|
+
return result;
|
|
498
672
|
}
|
|
499
|
-
|
|
500
|
-
if (
|
|
501
|
-
|
|
673
|
+
_pullFromAdapter(adapter, remaining, now, result) {
|
|
674
|
+
if (adapter.type !== "VaultV2MorphoMarketV1AdapterV2") {
|
|
675
|
+
const available = adapter.realAssets(now);
|
|
676
|
+
if (available === 0n) return remaining;
|
|
677
|
+
const pull = available < remaining ? available : remaining;
|
|
678
|
+
result.push({ adapter: adapter.address, amount: pull });
|
|
679
|
+
return remaining - pull;
|
|
502
680
|
}
|
|
503
|
-
|
|
504
|
-
|
|
681
|
+
const a = adapter;
|
|
682
|
+
return this._pullFromV2Markets(adapter.address, a.markets, a.supplyShares, remaining, result);
|
|
683
|
+
}
|
|
684
|
+
_pullFromV2Markets(adapterAddress, markets, supplyShares, remaining, result) {
|
|
685
|
+
let rem = remaining;
|
|
686
|
+
for (const mkt of markets) {
|
|
687
|
+
if (rem === 0n) break;
|
|
688
|
+
const adapterShares = _nullishCoalesce(supplyShares[mkt.id], () => ( 0n));
|
|
689
|
+
const available = mkt.totalSupplyShares > 0n ? adapterShares * mkt.totalSupplyAssets / mkt.totalSupplyShares : 0n;
|
|
690
|
+
if (available === 0n) continue;
|
|
691
|
+
const pull = available < rem ? available : rem;
|
|
692
|
+
result.push({ adapter: adapterAddress, marketParams: mkt.params, amount: pull });
|
|
693
|
+
rem -= pull;
|
|
505
694
|
}
|
|
506
|
-
|
|
507
|
-
|
|
695
|
+
return rem;
|
|
696
|
+
}
|
|
697
|
+
_buildDeallocs(deallocations, assetDecimals) {
|
|
698
|
+
return deallocations.map((d) => ({
|
|
699
|
+
adapter: d.adapter,
|
|
700
|
+
amount: _viem.parseUnits.call(void 0, String(d.amount), assetDecimals),
|
|
701
|
+
// MarketParams in the Morpho SDK has computed fields (id, liquidationIncentiveFactor)
|
|
702
|
+
// not present in our user-facing type; the plain object is structurally compatible for ABI encoding.
|
|
703
|
+
...d.marketParams && {
|
|
704
|
+
marketParams: d.marketParams
|
|
705
|
+
}
|
|
706
|
+
}));
|
|
707
|
+
}
|
|
708
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
709
|
+
_requireFrom() {
|
|
710
|
+
const from = _optionalChain([this, 'access', _5 => _5.wc, 'access', _6 => _6.account, 'optionalAccess', _7 => _7.address]);
|
|
711
|
+
if (!from) {
|
|
712
|
+
throw new StableValidationError(
|
|
713
|
+
"signer",
|
|
714
|
+
void 0,
|
|
715
|
+
"earn: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
716
|
+
);
|
|
508
717
|
}
|
|
509
|
-
|
|
510
|
-
|
|
718
|
+
return from;
|
|
719
|
+
}
|
|
720
|
+
async _switchChain() {
|
|
721
|
+
try {
|
|
722
|
+
await this.wc.switchChain({ id: this.config.chainId });
|
|
723
|
+
} catch (err) {
|
|
724
|
+
if (err.code === 4001) {
|
|
725
|
+
throw new StableTransactionError({
|
|
726
|
+
phase: "switch_chain",
|
|
727
|
+
shortMessage: `earn: wallet rejected switch to chain ${this.config.chainId}`,
|
|
728
|
+
chainId: this.config.chainId,
|
|
729
|
+
cause: err
|
|
730
|
+
});
|
|
731
|
+
}
|
|
511
732
|
}
|
|
512
|
-
|
|
513
|
-
|
|
733
|
+
}
|
|
734
|
+
async _sendTx(to, data, value, from) {
|
|
735
|
+
try {
|
|
736
|
+
return await this.wc.sendTransaction({
|
|
737
|
+
to,
|
|
738
|
+
data,
|
|
739
|
+
value,
|
|
740
|
+
account: from,
|
|
741
|
+
chain: this.chain
|
|
742
|
+
});
|
|
743
|
+
} catch (err) {
|
|
744
|
+
throw new StableTransactionError({
|
|
745
|
+
phase: "send",
|
|
746
|
+
shortMessage: "earn: transaction send failed",
|
|
747
|
+
chainId: this.config.chainId,
|
|
748
|
+
cause: err
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
async _resolveDecimals(asset, op) {
|
|
753
|
+
try {
|
|
754
|
+
return await retryWithBackoff(
|
|
755
|
+
() => this.pc.readContract({
|
|
756
|
+
address: asset,
|
|
757
|
+
abi: _viem.erc20Abi,
|
|
758
|
+
functionName: "decimals"
|
|
759
|
+
})
|
|
760
|
+
);
|
|
761
|
+
} catch (e5) {
|
|
762
|
+
throw new StableValidationError(
|
|
763
|
+
"tokenDecimals",
|
|
764
|
+
void 0,
|
|
765
|
+
`${op}: could not fetch decimals for asset "${asset}" \u2014 pass tokenDecimals explicitly`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
_withIdempotency(key, fn) {
|
|
770
|
+
if (!key) return fn();
|
|
771
|
+
const existing = this.inFlight.get(key);
|
|
772
|
+
if (existing) return existing;
|
|
773
|
+
const promise = fn().finally(() => this.inFlight.delete(key));
|
|
774
|
+
this.inFlight.set(key, promise);
|
|
775
|
+
return promise;
|
|
776
|
+
}
|
|
777
|
+
_validateAmount(amount, op) {
|
|
778
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
779
|
+
throw new StableValidationError(
|
|
780
|
+
"amount",
|
|
781
|
+
amount,
|
|
782
|
+
`${op}: amount must be a finite positive number`
|
|
783
|
+
);
|
|
514
784
|
}
|
|
515
785
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (!from) {
|
|
786
|
+
_validateShares(shares, op) {
|
|
787
|
+
if (!Number.isFinite(shares) || shares <= 0) {
|
|
519
788
|
throw new StableValidationError(
|
|
520
|
-
"
|
|
521
|
-
|
|
522
|
-
|
|
789
|
+
"shares",
|
|
790
|
+
shares,
|
|
791
|
+
`${op}: shares must be a finite positive number`
|
|
523
792
|
);
|
|
524
793
|
}
|
|
525
|
-
return from;
|
|
526
794
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// src/earn/reader.ts
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
// src/types.ts
|
|
810
|
+
var Chain = /* @__PURE__ */ ((Chain2) => {
|
|
811
|
+
Chain2["Sepolia"] = "sepolia";
|
|
812
|
+
Chain2["StableTestnet"] = "stable-testnet";
|
|
813
|
+
Chain2["Stable"] = "stable";
|
|
814
|
+
Chain2["Ethereum"] = "ethereum";
|
|
815
|
+
Chain2["Arbitrum"] = "arbitrum";
|
|
816
|
+
Chain2["Ink"] = "ink";
|
|
817
|
+
Chain2["Bera"] = "bera";
|
|
818
|
+
Chain2["MegaETH"] = "megaeth";
|
|
819
|
+
Chain2["Base"] = "base";
|
|
820
|
+
Chain2["BSC"] = "bsc";
|
|
821
|
+
Chain2["HyperEVM"] = "hyperevm";
|
|
822
|
+
return Chain2;
|
|
823
|
+
})(Chain || {});
|
|
824
|
+
var Network = /* @__PURE__ */ ((Network2) => {
|
|
825
|
+
Network2["Mainnet"] = "mainnet";
|
|
826
|
+
Network2["Testnet"] = "testnet";
|
|
827
|
+
return Network2;
|
|
828
|
+
})(Network || {});
|
|
829
|
+
|
|
830
|
+
// src/earn/reader.ts
|
|
831
|
+
var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
|
|
832
|
+
var EarnReader = class {
|
|
833
|
+
constructor(config) {
|
|
834
|
+
if (!_optionalChain([config, 'access', _8 => _8.earn, 'optionalAccess', _9 => _9.vault])) {
|
|
835
|
+
throw new StableValidationError(
|
|
836
|
+
"earn.vault",
|
|
837
|
+
void 0,
|
|
838
|
+
"createStableReader: earn.vault is required"
|
|
839
|
+
);
|
|
546
840
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const providerCode = isObject(body) && typeof body.code === "string" ? body.code : void 0;
|
|
554
|
-
if (res.status === 429 || res.status >= 500) {
|
|
555
|
-
throw new HttpError(`LI.FI: ${_nullishCoalesce(providerMessage, () => ( res.statusText))}`, res.status, true);
|
|
556
|
-
}
|
|
557
|
-
throw new StableQuoteError({
|
|
558
|
-
provider: "lifi",
|
|
559
|
-
shortMessage: `LI.FI: ${_nullishCoalesce(providerMessage, () => ( res.statusText))}`,
|
|
560
|
-
httpStatus: res.status,
|
|
561
|
-
providerCode,
|
|
562
|
-
body
|
|
563
|
-
});
|
|
841
|
+
if (!_viem.isAddress.call(void 0, config.address)) {
|
|
842
|
+
throw new StableValidationError(
|
|
843
|
+
"address",
|
|
844
|
+
config.address,
|
|
845
|
+
`createStableReader: invalid user address "${config.address}"`
|
|
846
|
+
);
|
|
564
847
|
}
|
|
565
|
-
if (!
|
|
566
|
-
throw new
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
});
|
|
848
|
+
if (!_viem.isAddress.call(void 0, config.earn.vault)) {
|
|
849
|
+
throw new StableValidationError(
|
|
850
|
+
"earn.vault",
|
|
851
|
+
config.earn.vault,
|
|
852
|
+
`createStableReader: invalid vault address "${config.earn.vault}"`
|
|
853
|
+
);
|
|
572
854
|
}
|
|
573
|
-
|
|
855
|
+
const network = _nullishCoalesce(config.network, () => ( "mainnet")) /* Mainnet */;
|
|
856
|
+
const viemChain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
|
|
857
|
+
const rpc = (_nullishCoalesce(config.rpc, () => ( viemChain.rpcUrls.default.http[0]))).replace(
|
|
858
|
+
/\/$/,
|
|
859
|
+
""
|
|
860
|
+
);
|
|
861
|
+
this.chainId = viemChain.id;
|
|
862
|
+
this.vaultAddr = config.earn.vault;
|
|
863
|
+
this.userAddr = config.address;
|
|
864
|
+
const chain = makeChain(viemChain.id, rpc);
|
|
865
|
+
this.pc = _viem.createPublicClient.call(void 0, { chain, transport: _viem.http.call(void 0, rpc) });
|
|
866
|
+
this.morpho = new (0, _morphosdk.MorphoClient)(this.pc);
|
|
574
867
|
}
|
|
575
|
-
|
|
868
|
+
async position() {
|
|
869
|
+
const vault = this.morpho.vaultV2(this.vaultAddr, this.chainId);
|
|
870
|
+
const [vaultData, shares] = await Promise.all([
|
|
871
|
+
vault.getData(),
|
|
872
|
+
this.pc.readContract({
|
|
873
|
+
address: this.vaultAddr,
|
|
874
|
+
abi: _viem.erc20Abi,
|
|
875
|
+
functionName: "balanceOf",
|
|
876
|
+
args: [this.userAddr]
|
|
877
|
+
})
|
|
878
|
+
]);
|
|
879
|
+
const [tokenDecimals, shareDecimals] = await Promise.all([
|
|
880
|
+
this.pc.readContract({
|
|
881
|
+
address: vaultData.asset,
|
|
882
|
+
abi: _viem.erc20Abi,
|
|
883
|
+
functionName: "decimals"
|
|
884
|
+
}),
|
|
885
|
+
this.pc.readContract({
|
|
886
|
+
address: this.vaultAddr,
|
|
887
|
+
abi: _viem.erc20Abi,
|
|
888
|
+
functionName: "decimals"
|
|
889
|
+
})
|
|
890
|
+
]);
|
|
891
|
+
const assetsRaw = vaultData.toAssets(shares);
|
|
576
892
|
return {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
893
|
+
shares,
|
|
894
|
+
sharesFormatted: Number(_viem.formatUnits.call(void 0, shares, shareDecimals)),
|
|
895
|
+
shareDecimals,
|
|
896
|
+
assets: Number(_viem.formatUnits.call(void 0, assetsRaw, tokenDecimals)),
|
|
897
|
+
tokenDecimals,
|
|
898
|
+
assetAddress: vaultData.asset
|
|
582
899
|
};
|
|
583
900
|
}
|
|
584
|
-
async
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
901
|
+
async getYield() {
|
|
902
|
+
return this._fetchYield();
|
|
903
|
+
}
|
|
904
|
+
async preview(params) {
|
|
905
|
+
const { amount, horizonDays } = params;
|
|
906
|
+
const apy = await this._fetchNetApy();
|
|
907
|
+
return {
|
|
908
|
+
projectedYield: amount * ((1 + apy) ** (horizonDays / 365) - 1),
|
|
909
|
+
apy,
|
|
910
|
+
horizonDays
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
async withdrawability(params) {
|
|
914
|
+
const vault = this.morpho.vaultV2(this.vaultAddr, this.chainId);
|
|
915
|
+
const vaultData = await vault.getData();
|
|
916
|
+
const tokenDecimals = await _asyncNullishCoalesce(params.tokenDecimals, async () => ( await this.pc.readContract({
|
|
917
|
+
address: vaultData.asset,
|
|
918
|
+
abi: _viem.erc20Abi,
|
|
919
|
+
functionName: "decimals"
|
|
601
920
|
})));
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
chainId: srcChainId,
|
|
609
|
-
cause: err
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
await this._ensureApproval(wc, srcChain, srcRpc, srcChainId, from, quote);
|
|
613
|
-
let hash;
|
|
614
|
-
try {
|
|
615
|
-
hash = await wc.sendTransaction({
|
|
616
|
-
to: transactionRequest.to,
|
|
617
|
-
data: transactionRequest.data,
|
|
618
|
-
value: transactionRequest.value && transactionRequest.value !== "0x" ? BigInt(transactionRequest.value) : 0n,
|
|
619
|
-
gas: transactionRequest.gasLimit ? BigInt(transactionRequest.gasLimit) : void 0,
|
|
620
|
-
account: wc.account,
|
|
621
|
-
chain: srcChain
|
|
622
|
-
});
|
|
623
|
-
} catch (err) {
|
|
624
|
-
throw new StableTransactionError({
|
|
625
|
-
phase: "send",
|
|
626
|
-
shortMessage: "LI.FI: swap/bridge transaction failed",
|
|
627
|
-
chainId: srcChainId,
|
|
628
|
-
cause: err
|
|
629
|
-
});
|
|
921
|
+
const requestedRaw = _viem.parseUnits.call(void 0, String(params.amount), tokenDecimals);
|
|
922
|
+
let availableLiquidityRaw = vaultData.assetBalance;
|
|
923
|
+
if (vaultData.accrualLiquidityAdapter != null) {
|
|
924
|
+
availableLiquidityRaw += vaultData.accrualLiquidityAdapter.maxWithdraw(
|
|
925
|
+
vaultData.liquidityData
|
|
926
|
+
).value;
|
|
630
927
|
}
|
|
631
|
-
return {
|
|
928
|
+
return {
|
|
929
|
+
isInstant: requestedRaw <= availableLiquidityRaw,
|
|
930
|
+
availableLiquidity: Number(
|
|
931
|
+
_viem.formatUnits.call(void 0, availableLiquidityRaw, tokenDecimals)
|
|
932
|
+
),
|
|
933
|
+
tokenDecimals
|
|
934
|
+
};
|
|
632
935
|
}
|
|
633
|
-
async
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
address:
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
936
|
+
async _fetchNetApy() {
|
|
937
|
+
const { apy } = await this._fetchYield();
|
|
938
|
+
return apy;
|
|
939
|
+
}
|
|
940
|
+
async _fetchYield() {
|
|
941
|
+
const query = `
|
|
942
|
+
query VaultYield($address: String!, $chainId: Int!) {
|
|
943
|
+
vaultV2ByAddress(address: $address, chainId: $chainId) {
|
|
944
|
+
avgNetApy
|
|
945
|
+
avgNetApyExcludingRewards
|
|
946
|
+
performanceFee
|
|
947
|
+
managementFee
|
|
948
|
+
rewards {
|
|
949
|
+
asset { symbol address }
|
|
950
|
+
supplyApr
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
`;
|
|
955
|
+
let body;
|
|
646
956
|
try {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
957
|
+
body = await retryWithBackoff(
|
|
958
|
+
async () => {
|
|
959
|
+
const res = await fetch(MORPHO_API_URL, {
|
|
960
|
+
method: "POST",
|
|
961
|
+
headers: { "Content-Type": "application/json" },
|
|
962
|
+
body: JSON.stringify({
|
|
963
|
+
query,
|
|
964
|
+
variables: {
|
|
965
|
+
address: this.vaultAddr,
|
|
966
|
+
chainId: this.chainId
|
|
967
|
+
}
|
|
968
|
+
})
|
|
969
|
+
});
|
|
970
|
+
if (!res.ok) {
|
|
971
|
+
const retryable = res.status === 429 || res.status >= 500;
|
|
972
|
+
throw new HttpError(
|
|
973
|
+
`earn: Morpho API returned ${res.status} \u2014 ${res.statusText}`,
|
|
974
|
+
res.status,
|
|
975
|
+
retryable
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
return res.json().catch(() => void 0);
|
|
979
|
+
},
|
|
980
|
+
(err) => err instanceof HttpError ? err.retryable : err instanceof TypeError
|
|
981
|
+
);
|
|
655
982
|
} catch (err) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
983
|
+
if (err instanceof TypeError || err instanceof HttpError) {
|
|
984
|
+
throw new StableNetworkError({
|
|
985
|
+
shortMessage: err instanceof HttpError ? `earn: Morpho API returned ${err.status} after retries` : `earn: failed to reach Morpho API after retries \u2014 ${String(
|
|
986
|
+
err
|
|
987
|
+
)}`,
|
|
988
|
+
url: MORPHO_API_URL,
|
|
989
|
+
cause: err
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
throw err;
|
|
662
993
|
}
|
|
663
|
-
const
|
|
664
|
-
if (
|
|
665
|
-
throw new
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
});
|
|
994
|
+
const vault = _optionalChain([body, 'optionalAccess', _10 => _10.data, 'optionalAccess', _11 => _11.vaultV2ByAddress]);
|
|
995
|
+
if (!vault || typeof vault.avgNetApy !== "number") {
|
|
996
|
+
throw new StableValidationError(
|
|
997
|
+
"apy",
|
|
998
|
+
void 0,
|
|
999
|
+
"earn: vault not found in Morpho API or unexpected response shape"
|
|
1000
|
+
);
|
|
671
1001
|
}
|
|
1002
|
+
const rewards = vault.rewards.filter((r) => typeof r.supplyApr === "number" && r.supplyApr > 0).map((r) => ({
|
|
1003
|
+
symbol: r.asset.symbol,
|
|
1004
|
+
address: r.asset.address,
|
|
1005
|
+
apr: r.supplyApr
|
|
1006
|
+
}));
|
|
1007
|
+
return {
|
|
1008
|
+
apy: vault.avgNetApy,
|
|
1009
|
+
native: _nullishCoalesce(vault.avgNetApyExcludingRewards, () => ( 0)),
|
|
1010
|
+
rewards,
|
|
1011
|
+
performanceFee: vault.performanceFee,
|
|
1012
|
+
managementFee: vault.managementFee
|
|
1013
|
+
};
|
|
672
1014
|
}
|
|
673
1015
|
};
|
|
674
|
-
function isObject(v) {
|
|
675
|
-
return typeof v === "object" && v !== null;
|
|
676
|
-
}
|
|
677
|
-
function isLiFiQuoteShape(v) {
|
|
678
|
-
if (!isObject(v)) return false;
|
|
679
|
-
const tx = v.transactionRequest;
|
|
680
|
-
const est = v.estimate;
|
|
681
|
-
const act = v.action;
|
|
682
|
-
return isObject(tx) && typeof tx.to === "string" && typeof tx.data === "string" && typeof tx.chainId === "number" && isObject(est) && typeof est.fromAmount === "string" && typeof est.toAmount === "string" && isObject(act) && isObject(act.fromToken) && isObject(act.toToken);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// src/earn/morpho.ts
|
|
686
|
-
|
|
687
|
-
|
|
688
1016
|
|
|
1017
|
+
// src/earn/merkl.ts
|
|
689
1018
|
|
|
690
1019
|
|
|
691
1020
|
|
|
@@ -694,386 +1023,751 @@ function isLiFiQuoteShape(v) {
|
|
|
694
1023
|
|
|
695
1024
|
|
|
696
1025
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1026
|
+
var MERKL_API_BASE_DEFAULT = "https://api.merkl.xyz";
|
|
1027
|
+
var DISTRIBUTOR_ADDRESS = "0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae";
|
|
1028
|
+
var DISTRIBUTOR_ABI = [
|
|
1029
|
+
{
|
|
1030
|
+
inputs: [
|
|
1031
|
+
{ name: "users", type: "address[]" },
|
|
1032
|
+
{ name: "tokens", type: "address[]" },
|
|
1033
|
+
{ name: "amounts", type: "uint256[]" },
|
|
1034
|
+
{ name: "proofs", type: "bytes32[][]" }
|
|
1035
|
+
],
|
|
1036
|
+
name: "claim",
|
|
1037
|
+
outputs: [],
|
|
1038
|
+
stateMutability: "nonpayable",
|
|
1039
|
+
type: "function"
|
|
1040
|
+
}
|
|
1041
|
+
];
|
|
1042
|
+
var MerklService = class {
|
|
701
1043
|
constructor(config) {
|
|
702
1044
|
this.config = config;
|
|
703
|
-
this.inFlight = /* @__PURE__ */ new Map();
|
|
704
|
-
this.chain = makeChain(config.chainId, config.rpc);
|
|
705
|
-
this.wc = _nullishCoalesce(config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
706
|
-
account: config.account,
|
|
707
|
-
chain: this.chain,
|
|
708
|
-
transport: _nullishCoalesce(config.transport, () => ( _viem.http.call(void 0, config.rpc)))
|
|
709
|
-
})));
|
|
710
|
-
const morphoWc = _optionalChain([this, 'access', _20 => _20.wc, 'access', _21 => _21.chain, 'optionalAccess', _22 => _22.id]) === config.chainId ? this.wc : _viem.createWalletClient.call(void 0, {
|
|
711
|
-
account: this.wc.account,
|
|
712
|
-
chain: this.chain,
|
|
713
|
-
transport: _viem.custom.call(void 0, { request: this.wc.request.bind(this.wc) })
|
|
714
|
-
});
|
|
715
|
-
this.morpho = new (0, _morphosdk.MorphoClient)(morphoWc, { supportSignature: true });
|
|
716
|
-
this.pc = _viem.createPublicClient.call(void 0, { chain: this.chain, transport: _viem.http.call(void 0, config.rpc) });
|
|
717
1045
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1046
|
+
async fetchRewards(address) {
|
|
1047
|
+
if (!_viem.isAddress.call(void 0, address)) {
|
|
1048
|
+
throw new StableValidationError(
|
|
1049
|
+
"address",
|
|
1050
|
+
address,
|
|
1051
|
+
`merkl: invalid address "${address}"`
|
|
1052
|
+
);
|
|
722
1053
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const run = async () => {
|
|
728
|
-
const results = [];
|
|
729
|
-
for (const item of items) {
|
|
730
|
-
try {
|
|
731
|
-
const result = await this.deposit(item);
|
|
732
|
-
results.push({ status: "fulfilled", txHash: result.txHash, vault: item.vault, idempotencyKey: item.idempotencyKey });
|
|
733
|
-
} catch (err) {
|
|
734
|
-
const entry = { status: "rejected", error: String(err), vault: item.vault, idempotencyKey: item.idempotencyKey };
|
|
735
|
-
results.push(entry);
|
|
736
|
-
if (stopOnError) {
|
|
737
|
-
const succeeded2 = results.filter((r) => r.status === "fulfilled").length;
|
|
738
|
-
throw new StableBulkError({
|
|
739
|
-
shortMessage: "bulkDeposit halted on first failure",
|
|
740
|
-
results,
|
|
741
|
-
succeeded: succeeded2,
|
|
742
|
-
failed: results.length - succeeded2
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
748
|
-
return { results, succeeded, failed: results.length - succeeded, idempotencyKey };
|
|
749
|
-
};
|
|
750
|
-
return this._withIdempotency(idempotencyKey, run);
|
|
751
|
-
}
|
|
752
|
-
async bulkWithdraw(items, options = {}) {
|
|
753
|
-
const { idempotencyKey, stopOnError = false } = options;
|
|
754
|
-
const run = async () => {
|
|
755
|
-
const results = [];
|
|
756
|
-
for (const item of items) {
|
|
757
|
-
try {
|
|
758
|
-
const result = await this.withdraw(item);
|
|
759
|
-
results.push({ status: "fulfilled", txHash: result.txHash, idempotencyKey: item.idempotencyKey });
|
|
760
|
-
} catch (err) {
|
|
761
|
-
const entry = { status: "rejected", error: String(err), idempotencyKey: item.idempotencyKey };
|
|
762
|
-
results.push(entry);
|
|
763
|
-
if (stopOnError) {
|
|
764
|
-
const succeeded2 = results.filter((r) => r.status === "fulfilled").length;
|
|
765
|
-
throw new StableBulkError({
|
|
766
|
-
shortMessage: "bulkWithdraw halted on first failure",
|
|
767
|
-
results,
|
|
768
|
-
succeeded: succeeded2,
|
|
769
|
-
failed: results.length - succeeded2
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
775
|
-
return { results, succeeded, failed: results.length - succeeded, idempotencyKey };
|
|
776
|
-
};
|
|
777
|
-
return this._withIdempotency(idempotencyKey, run);
|
|
778
|
-
}
|
|
779
|
-
withdraw(params) {
|
|
780
|
-
this._validateAmount(params.amount, "earn withdraw");
|
|
781
|
-
return this._withIdempotency(params.idempotencyKey, () => this._withdraw(params));
|
|
782
|
-
}
|
|
783
|
-
redeem(params) {
|
|
784
|
-
this._validateShares(params.shares, "earn redeem");
|
|
785
|
-
return this._withIdempotency(params.idempotencyKey, () => this._redeem(params));
|
|
1054
|
+
const base = (_nullishCoalesce(this.config.merklApiBase, () => ( MERKL_API_BASE_DEFAULT))).replace(/\/$/, "");
|
|
1055
|
+
const url = `${base}/v4/users/${address}/rewards?chainId=${this.config.chainId}`;
|
|
1056
|
+
const data = await this._fetch(url);
|
|
1057
|
+
return this._parseRewards(data);
|
|
786
1058
|
}
|
|
787
|
-
|
|
788
|
-
this.
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
const vaultAddr = _nullishCoalesce(params.vault, () => ( this.config.vault));
|
|
800
|
-
await this._switchChain();
|
|
801
|
-
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
802
|
-
const vaultData = await vault.getData();
|
|
803
|
-
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn deposit")));
|
|
804
|
-
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
805
|
-
if (depositBuffer !== void 0) {
|
|
806
|
-
const bps = BigInt(Math.round(depositBuffer * 1e4));
|
|
807
|
-
const maxAllowed = vaultData.assetBalance * bps / 10000n;
|
|
808
|
-
if (assetsRaw > maxAllowed) {
|
|
809
|
-
const maxFormatted = Number(_viem.formatUnits.call(void 0, maxAllowed, decimals));
|
|
810
|
-
const idleFormatted = Number(_viem.formatUnits.call(void 0, vaultData.assetBalance, decimals));
|
|
811
|
-
throw new StableValidationError(
|
|
812
|
-
"amount",
|
|
813
|
-
amount,
|
|
814
|
-
`earn deposit: amount ${amount} exceeds deposit buffer \u2014 max ${maxFormatted} (${depositBuffer * 100}% of ${idleFormatted} idle liquidity)`
|
|
815
|
-
);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
const depositAction = vault.deposit({
|
|
819
|
-
amount: assetsRaw,
|
|
820
|
-
userAddress: from,
|
|
821
|
-
accrualVault: vaultData,
|
|
822
|
-
slippageTolerance: slippageTolerance !== void 0 ? _viem.parseUnits.call(void 0, String(slippageTolerance), 18) : void 0
|
|
823
|
-
});
|
|
824
|
-
const requirements = await depositAction.getRequirements();
|
|
825
|
-
let sig;
|
|
826
|
-
for (const req of requirements) {
|
|
827
|
-
if (_morphosdk.isRequirementApproval.call(void 0, req)) {
|
|
828
|
-
const approveHash = await this._sendTx(req.to, req.data, req.value, from);
|
|
829
|
-
const approveReceipt = await this.pc.waitForTransactionReceipt({ hash: approveHash });
|
|
830
|
-
if (approveReceipt.status === "reverted") {
|
|
831
|
-
throw new StableTransactionError({
|
|
832
|
-
phase: "approve",
|
|
833
|
-
shortMessage: "earn deposit: token approval reverted on-chain",
|
|
834
|
-
txHash: approveReceipt.transactionHash,
|
|
835
|
-
chainId: this.config.chainId
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
} else if (_morphosdk.isRequirementSignature.call(void 0, req)) {
|
|
839
|
-
sig = await req.sign(this.wc, from);
|
|
840
|
-
}
|
|
1059
|
+
async claim() {
|
|
1060
|
+
const address = this._requireFrom();
|
|
1061
|
+
const { rewards } = await this.fetchRewards(address);
|
|
1062
|
+
const claimable = rewards.filter(
|
|
1063
|
+
(r) => r.rawAmount !== "0" && r.proofs.length > 0
|
|
1064
|
+
);
|
|
1065
|
+
if (claimable.length === 0) {
|
|
1066
|
+
throw new StableValidationError(
|
|
1067
|
+
"rewards",
|
|
1068
|
+
[],
|
|
1069
|
+
rewards.length > 0 ? "merkl: rewards are pending the next merkle tree update \u2014 try again in ~2 hours" : "merkl: no rewards to claim on this chain"
|
|
1070
|
+
);
|
|
841
1071
|
}
|
|
842
|
-
const
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
1072
|
+
const chain = makeChain(this.config.chainId, this.config.rpc);
|
|
1073
|
+
const wc = _nullishCoalesce(this.config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
1074
|
+
account: this.config.account,
|
|
1075
|
+
chain,
|
|
1076
|
+
transport: _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, this.config.rpc)))
|
|
1077
|
+
})));
|
|
1078
|
+
try {
|
|
1079
|
+
await wc.switchChain({ id: this.config.chainId });
|
|
1080
|
+
} catch (err) {
|
|
846
1081
|
throw new StableTransactionError({
|
|
847
|
-
phase: "
|
|
848
|
-
shortMessage:
|
|
849
|
-
|
|
850
|
-
|
|
1082
|
+
phase: "switch_chain",
|
|
1083
|
+
shortMessage: `merkl: wallet rejected or failed to switch to chain ${this.config.chainId}`,
|
|
1084
|
+
chainId: this.config.chainId,
|
|
1085
|
+
cause: err
|
|
851
1086
|
});
|
|
852
1087
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
const
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1088
|
+
const users = claimable.map(() => address);
|
|
1089
|
+
const tokens = claimable.map(
|
|
1090
|
+
(r) => r.token.address
|
|
1091
|
+
);
|
|
1092
|
+
const amounts = claimable.map((r) => BigInt(r.rawAmount));
|
|
1093
|
+
const proofs = claimable.map((r) => r.proofs);
|
|
1094
|
+
let hash;
|
|
1095
|
+
try {
|
|
1096
|
+
hash = await wc.writeContract({
|
|
1097
|
+
address: DISTRIBUTOR_ADDRESS,
|
|
1098
|
+
abi: DISTRIBUTOR_ABI,
|
|
1099
|
+
functionName: "claim",
|
|
1100
|
+
args: [users, tokens, amounts, proofs],
|
|
1101
|
+
account: wc.account,
|
|
1102
|
+
chain
|
|
1103
|
+
});
|
|
1104
|
+
} catch (err) {
|
|
868
1105
|
throw new StableTransactionError({
|
|
869
|
-
phase: "
|
|
870
|
-
shortMessage: "
|
|
871
|
-
|
|
872
|
-
|
|
1106
|
+
phase: "send",
|
|
1107
|
+
shortMessage: "merkl: claim transaction failed",
|
|
1108
|
+
chainId: this.config.chainId,
|
|
1109
|
+
cause: err
|
|
873
1110
|
});
|
|
874
1111
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const
|
|
880
|
-
const vaultAddr = this.config.vault;
|
|
881
|
-
await this._switchChain();
|
|
882
|
-
let decimals = shareDecimals;
|
|
883
|
-
if (decimals === void 0) {
|
|
884
|
-
try {
|
|
885
|
-
decimals = await this.pc.readContract({ address: vaultAddr, abi: _viem.erc20Abi, functionName: "decimals" });
|
|
886
|
-
} catch (e2) {
|
|
887
|
-
throw new StableValidationError(
|
|
888
|
-
"shareDecimals",
|
|
889
|
-
void 0,
|
|
890
|
-
`earn redeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
891
|
-
);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
const sharesRaw = _viem.parseUnits.call(void 0, String(shares), decimals);
|
|
895
|
-
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
896
|
-
const tx = vault.redeem({ shares: sharesRaw, userAddress: from }).buildTx();
|
|
897
|
-
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
898
|
-
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
1112
|
+
const pc = _viem.createPublicClient.call(void 0, {
|
|
1113
|
+
chain,
|
|
1114
|
+
transport: _viem.http.call(void 0, this.config.rpc)
|
|
1115
|
+
});
|
|
1116
|
+
const receipt = await pc.waitForTransactionReceipt({ hash });
|
|
899
1117
|
if (receipt.status === "reverted") {
|
|
900
1118
|
throw new StableTransactionError({
|
|
901
1119
|
phase: "receipt",
|
|
902
|
-
shortMessage: "
|
|
1120
|
+
shortMessage: "merkl: claim transaction reverted on-chain",
|
|
903
1121
|
txHash: receipt.transactionHash,
|
|
904
1122
|
chainId: this.config.chainId
|
|
905
1123
|
});
|
|
906
1124
|
}
|
|
907
|
-
return {
|
|
1125
|
+
return {
|
|
1126
|
+
txHash: receipt.transactionHash,
|
|
1127
|
+
tokenCount: claimable.length
|
|
1128
|
+
};
|
|
908
1129
|
}
|
|
909
|
-
async
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
if (decimals === void 0) {
|
|
917
|
-
const vaultData = await vault.getData();
|
|
918
|
-
decimals = await this._resolveDecimals(vaultData.asset, "earn forceWithdraw");
|
|
1130
|
+
async prepareClaimCalldata(address) {
|
|
1131
|
+
if (!_viem.isAddress.call(void 0, address)) {
|
|
1132
|
+
throw new StableValidationError(
|
|
1133
|
+
"address",
|
|
1134
|
+
address,
|
|
1135
|
+
`merkl: invalid address "${address}"`
|
|
1136
|
+
);
|
|
919
1137
|
}
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1138
|
+
const { rewards } = await this.fetchRewards(address);
|
|
1139
|
+
const claimable = rewards.filter(
|
|
1140
|
+
(r) => r.rawAmount !== "0" && r.proofs.length > 0
|
|
1141
|
+
);
|
|
1142
|
+
if (claimable.length === 0) {
|
|
1143
|
+
throw new StableValidationError(
|
|
1144
|
+
"rewards",
|
|
1145
|
+
[],
|
|
1146
|
+
rewards.length > 0 ? "merkl: rewards are pending the next merkle tree update \u2014 try again in ~2 hours" : "merkl: no rewards to claim on this chain"
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
const users = claimable.map(() => address);
|
|
1150
|
+
const tokens = claimable.map((r) => r.token.address);
|
|
1151
|
+
const amounts = claimable.map((r) => BigInt(r.rawAmount));
|
|
1152
|
+
const proofs = claimable.map((r) => r.proofs);
|
|
1153
|
+
const data = _viem.encodeFunctionData.call(void 0, {
|
|
1154
|
+
abi: DISTRIBUTOR_ABI,
|
|
1155
|
+
functionName: "claim",
|
|
1156
|
+
args: [users, tokens, amounts, proofs]
|
|
1157
|
+
});
|
|
1158
|
+
return {
|
|
1159
|
+
to: DISTRIBUTOR_ADDRESS,
|
|
1160
|
+
data,
|
|
1161
|
+
chainId: this.config.chainId,
|
|
1162
|
+
rewards: claimable
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
_requireFrom() {
|
|
1166
|
+
const from = _nullishCoalesce(_optionalChain([this, 'access', _12 => _12.config, 'access', _13 => _13.walletClient, 'optionalAccess', _14 => _14.account, 'optionalAccess', _15 => _15.address]), () => ( _optionalChain([this, 'access', _16 => _16.config, 'access', _17 => _17.account, 'optionalAccess', _18 => _18.address])));
|
|
1167
|
+
if (!from) {
|
|
1168
|
+
throw new StableValidationError(
|
|
1169
|
+
"signer",
|
|
1170
|
+
void 0,
|
|
1171
|
+
"merkl.claim: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
return from;
|
|
1175
|
+
}
|
|
1176
|
+
_parseRewards(data) {
|
|
1177
|
+
const { chainId } = this.config;
|
|
1178
|
+
const rewards = [];
|
|
1179
|
+
const entries = Array.isArray(data) ? data : [data];
|
|
1180
|
+
for (const entry of entries) {
|
|
1181
|
+
if (_optionalChain([entry, 'optionalAccess', _19 => _19.chain, 'optionalAccess', _20 => _20.id]) !== chainId) continue;
|
|
1182
|
+
for (const reward of _nullishCoalesce(entry.rewards, () => ( []))) {
|
|
1183
|
+
const parsed = this._parseReward(reward);
|
|
1184
|
+
if (parsed) rewards.push(parsed);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return { chainId, rewards };
|
|
1188
|
+
}
|
|
1189
|
+
_parseReward(reward) {
|
|
1190
|
+
if (!_optionalChain([reward, 'optionalAccess', _21 => _21.token, 'optionalAccess', _22 => _22.address])) return null;
|
|
1191
|
+
const net = BigInt(_nullishCoalesce(reward.amount, () => ( "0"))) - BigInt(_nullishCoalesce(reward.claimed, () => ( "0")));
|
|
1192
|
+
if (net <= 0n) return null;
|
|
1193
|
+
const rawAmount = net.toString();
|
|
1194
|
+
const decimals = _nullishCoalesce(reward.token.decimals, () => ( 18));
|
|
1195
|
+
return {
|
|
1196
|
+
token: {
|
|
1197
|
+
address: reward.token.address,
|
|
1198
|
+
symbol: _nullishCoalesce(reward.token.symbol, () => ( "")),
|
|
1199
|
+
decimals
|
|
1200
|
+
},
|
|
1201
|
+
rawAmount,
|
|
1202
|
+
amount: Number(_viem.formatUnits.call(void 0, BigInt(rawAmount), decimals)),
|
|
1203
|
+
proofs: _nullishCoalesce(reward.proofs, () => ( []))
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
async _fetch(url) {
|
|
1207
|
+
try {
|
|
1208
|
+
return await retryWithBackoff(() => this._fetchOnce(url));
|
|
1209
|
+
} catch (err) {
|
|
1210
|
+
if (err instanceof StableNetworkError) throw err;
|
|
1211
|
+
if (err instanceof HttpError) {
|
|
1212
|
+
throw new StableNetworkError({
|
|
1213
|
+
shortMessage: `merkl: request failed \u2014 ${err.message}`,
|
|
1214
|
+
url,
|
|
1215
|
+
cause: err
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
throw new StableNetworkError({
|
|
1219
|
+
shortMessage: "merkl: request failed before reaching the server",
|
|
1220
|
+
url,
|
|
1221
|
+
cause: err
|
|
933
1222
|
});
|
|
934
1223
|
}
|
|
935
|
-
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
936
1224
|
}
|
|
937
|
-
async
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
try {
|
|
946
|
-
shareDec = await this.pc.readContract({ address: vaultAddr, abi: _viem.erc20Abi, functionName: "decimals" });
|
|
947
|
-
} catch (e3) {
|
|
948
|
-
throw new StableValidationError(
|
|
949
|
-
"shareDecimals",
|
|
950
|
-
void 0,
|
|
951
|
-
`earn forceRedeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
1225
|
+
async _fetchOnce(url) {
|
|
1226
|
+
const res = await fetch(url);
|
|
1227
|
+
if (!res.ok) {
|
|
1228
|
+
if (res.status === 429 || res.status >= 500) {
|
|
1229
|
+
throw new HttpError(
|
|
1230
|
+
`merkl: ${res.statusText}`,
|
|
1231
|
+
res.status,
|
|
1232
|
+
true
|
|
952
1233
|
);
|
|
953
1234
|
}
|
|
1235
|
+
throw new StableNetworkError({
|
|
1236
|
+
shortMessage: `merkl: API error ${res.status} ${res.statusText}`,
|
|
1237
|
+
url
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
return res.json();
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// src/services/lifi.ts
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
// src/chains.ts
|
|
1256
|
+
var CHAIN_CONFIGS = {
|
|
1257
|
+
// ─── Testnet ──────────────────────────────────────────────────────────────
|
|
1258
|
+
["sepolia" /* Sepolia */]: {
|
|
1259
|
+
id: 11155111,
|
|
1260
|
+
rpc: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
1261
|
+
usdt: "0xc4DCC311c028e341fd8602D8eB89c5de94625927",
|
|
1262
|
+
decimals: 18
|
|
1263
|
+
},
|
|
1264
|
+
["stable-testnet" /* StableTestnet */]: {
|
|
1265
|
+
id: 2201,
|
|
1266
|
+
rpc: "https://rpc.testnet.stable.xyz",
|
|
1267
|
+
usdt: "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9",
|
|
1268
|
+
decimals: 6
|
|
1269
|
+
},
|
|
1270
|
+
// ─── Mainnet ──────────────────────────────────────────────────────────────
|
|
1271
|
+
["stable" /* Stable */]: {
|
|
1272
|
+
id: 988,
|
|
1273
|
+
rpc: "https://rpc.stable.xyz",
|
|
1274
|
+
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
1275
|
+
decimals: 6
|
|
1276
|
+
},
|
|
1277
|
+
["ethereum" /* Ethereum */]: {
|
|
1278
|
+
id: 1,
|
|
1279
|
+
rpc: "https://ethereum-rpc.publicnode.com",
|
|
1280
|
+
usdt: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
1281
|
+
decimals: 6
|
|
1282
|
+
},
|
|
1283
|
+
["arbitrum" /* Arbitrum */]: {
|
|
1284
|
+
id: 42161,
|
|
1285
|
+
rpc: "https://arbitrum-one-rpc.publicnode.com",
|
|
1286
|
+
usdt: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92",
|
|
1287
|
+
decimals: 6
|
|
1288
|
+
},
|
|
1289
|
+
["ink" /* Ink */]: {
|
|
1290
|
+
id: 57073,
|
|
1291
|
+
rpc: "https://rpc-gel.inkonchain.com",
|
|
1292
|
+
usdt: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
1293
|
+
decimals: 6
|
|
1294
|
+
},
|
|
1295
|
+
["bera" /* Bera */]: {
|
|
1296
|
+
id: 80094,
|
|
1297
|
+
rpc: "https://rpc.berachain.com",
|
|
1298
|
+
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
1299
|
+
decimals: 6
|
|
1300
|
+
},
|
|
1301
|
+
["megaeth" /* MegaETH */]: {
|
|
1302
|
+
id: 4326,
|
|
1303
|
+
rpc: "https://carrot.megaeth.com",
|
|
1304
|
+
usdt: "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb",
|
|
1305
|
+
decimals: 6
|
|
1306
|
+
},
|
|
1307
|
+
["base" /* Base */]: {
|
|
1308
|
+
id: 8453,
|
|
1309
|
+
rpc: "https://mainnet.base.org",
|
|
1310
|
+
usdt: "0x102d758f688a4C1C5a80b116bD945d4455460282",
|
|
1311
|
+
decimals: 6
|
|
1312
|
+
},
|
|
1313
|
+
["bsc" /* BSC */]: {
|
|
1314
|
+
id: 56,
|
|
1315
|
+
rpc: "https://bsc-rpc.publicnode.com",
|
|
1316
|
+
usdt: "0x55d398326f99059fF775485246999027B3197955",
|
|
1317
|
+
decimals: 18
|
|
1318
|
+
},
|
|
1319
|
+
["hyperevm" /* HyperEVM */]: {
|
|
1320
|
+
id: 999,
|
|
1321
|
+
rpc: "https://rpc.hyperliquid.xyz/evm",
|
|
1322
|
+
usdt: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
|
|
1323
|
+
decimals: 6
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
// src/services/lifi.ts
|
|
1328
|
+
var LiFiService = class {
|
|
1329
|
+
constructor(config) {
|
|
1330
|
+
this.config = config;
|
|
1331
|
+
}
|
|
1332
|
+
async quoteSwap(params) {
|
|
1333
|
+
this._validateSwapParams(params);
|
|
1334
|
+
const {
|
|
1335
|
+
fromToken,
|
|
1336
|
+
toToken,
|
|
1337
|
+
amount,
|
|
1338
|
+
fromDecimals = 6,
|
|
1339
|
+
toAddress
|
|
1340
|
+
} = params;
|
|
1341
|
+
const from = this._requireFrom();
|
|
1342
|
+
const chain = this.config.chainId.toString();
|
|
1343
|
+
const fromAmountRaw = _viem.parseUnits.call(void 0,
|
|
1344
|
+
String(amount),
|
|
1345
|
+
fromDecimals
|
|
1346
|
+
).toString();
|
|
1347
|
+
const qs = new URLSearchParams({
|
|
1348
|
+
fromChain: chain,
|
|
1349
|
+
toChain: chain,
|
|
1350
|
+
fromToken,
|
|
1351
|
+
toToken,
|
|
1352
|
+
fromAmount: fromAmountRaw,
|
|
1353
|
+
fromAddress: from,
|
|
1354
|
+
...toAddress && { toAddress }
|
|
1355
|
+
});
|
|
1356
|
+
const quote = await this._fetchQuote(qs);
|
|
1357
|
+
return this._toSwapQuote(quote);
|
|
1358
|
+
}
|
|
1359
|
+
async swap(params) {
|
|
1360
|
+
this._validateSwapParams(params);
|
|
1361
|
+
const quote = await _asyncNullishCoalesce(params.quote, async () => ( await this.quoteSwap(params)));
|
|
1362
|
+
return this._executeSwapQuote(
|
|
1363
|
+
quote,
|
|
1364
|
+
this.config.rpc,
|
|
1365
|
+
this.config.chainId
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
async quoteBridge(params) {
|
|
1369
|
+
this._validateBridgeParams(params);
|
|
1370
|
+
const {
|
|
1371
|
+
fromChain,
|
|
1372
|
+
toChain,
|
|
1373
|
+
fromToken,
|
|
1374
|
+
toToken,
|
|
1375
|
+
amount,
|
|
1376
|
+
fromDecimals = 6,
|
|
1377
|
+
recipient
|
|
1378
|
+
} = params;
|
|
1379
|
+
const from = _nullishCoalesce(_nullishCoalesce(_optionalChain([this, 'access', _23 => _23.config, 'access', _24 => _24.walletClient, 'optionalAccess', _25 => _25.account, 'optionalAccess', _26 => _26.address]), () => ( _optionalChain([this, 'access', _27 => _27.config, 'access', _28 => _28.account, 'optionalAccess', _29 => _29.address]))), () => ( "0x0000000000000000000000000000000000000000"));
|
|
1380
|
+
const src = CHAIN_CONFIGS[fromChain];
|
|
1381
|
+
const dst = CHAIN_CONFIGS[toChain];
|
|
1382
|
+
if (!src)
|
|
1383
|
+
throw new StableValidationError(
|
|
1384
|
+
"fromChain",
|
|
1385
|
+
fromChain,
|
|
1386
|
+
`LI.FI bridge: unsupported source chain "${fromChain}"`
|
|
1387
|
+
);
|
|
1388
|
+
if (!dst)
|
|
1389
|
+
throw new StableValidationError(
|
|
1390
|
+
"toChain",
|
|
1391
|
+
toChain,
|
|
1392
|
+
`LI.FI bridge: unsupported destination chain "${toChain}"`
|
|
1393
|
+
);
|
|
1394
|
+
const qs = new URLSearchParams({
|
|
1395
|
+
fromChain: src.id.toString(),
|
|
1396
|
+
toChain: dst.id.toString(),
|
|
1397
|
+
fromToken,
|
|
1398
|
+
toToken,
|
|
1399
|
+
fromAmount: _viem.parseUnits.call(void 0, String(amount), fromDecimals).toString(),
|
|
1400
|
+
fromAddress: from,
|
|
1401
|
+
...recipient && { toAddress: recipient }
|
|
1402
|
+
});
|
|
1403
|
+
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
1404
|
+
return { toAmount: quote.toAmount };
|
|
1405
|
+
}
|
|
1406
|
+
async bridge(params) {
|
|
1407
|
+
this._validateBridgeParams(params);
|
|
1408
|
+
const {
|
|
1409
|
+
fromChain,
|
|
1410
|
+
toChain,
|
|
1411
|
+
fromToken,
|
|
1412
|
+
toToken,
|
|
1413
|
+
amount,
|
|
1414
|
+
fromDecimals = 6,
|
|
1415
|
+
recipient
|
|
1416
|
+
} = params;
|
|
1417
|
+
const from = this._requireFrom();
|
|
1418
|
+
const src = CHAIN_CONFIGS[fromChain];
|
|
1419
|
+
const dst = CHAIN_CONFIGS[toChain];
|
|
1420
|
+
if (!src) {
|
|
1421
|
+
throw new StableValidationError(
|
|
1422
|
+
"fromChain",
|
|
1423
|
+
fromChain,
|
|
1424
|
+
`LI.FI bridge: unsupported source chain "${fromChain}"`
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
if (!dst) {
|
|
1428
|
+
throw new StableValidationError(
|
|
1429
|
+
"toChain",
|
|
1430
|
+
toChain,
|
|
1431
|
+
`LI.FI bridge: unsupported destination chain "${toChain}"`
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
const fromAmountRaw = _viem.parseUnits.call(void 0,
|
|
1435
|
+
String(amount),
|
|
1436
|
+
fromDecimals
|
|
1437
|
+
).toString();
|
|
1438
|
+
const qs = new URLSearchParams({
|
|
1439
|
+
fromChain: src.id.toString(),
|
|
1440
|
+
toChain: dst.id.toString(),
|
|
1441
|
+
fromToken,
|
|
1442
|
+
toToken,
|
|
1443
|
+
fromAmount: fromAmountRaw,
|
|
1444
|
+
fromAddress: from,
|
|
1445
|
+
...recipient && { toAddress: recipient }
|
|
1446
|
+
});
|
|
1447
|
+
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
1448
|
+
return this._executeSwapQuote(quote, src.rpc, src.id);
|
|
1449
|
+
}
|
|
1450
|
+
_validateSwapParams({
|
|
1451
|
+
fromToken,
|
|
1452
|
+
toToken,
|
|
1453
|
+
amount,
|
|
1454
|
+
toAddress
|
|
1455
|
+
}) {
|
|
1456
|
+
if (!_viem.isAddress.call(void 0, fromToken)) {
|
|
1457
|
+
throw new StableValidationError(
|
|
1458
|
+
"fromToken",
|
|
1459
|
+
fromToken,
|
|
1460
|
+
`swap: invalid fromToken address "${fromToken}"`
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
if (!_viem.isAddress.call(void 0, toToken)) {
|
|
1464
|
+
throw new StableValidationError(
|
|
1465
|
+
"toToken",
|
|
1466
|
+
toToken,
|
|
1467
|
+
`swap: invalid toToken address "${toToken}"`
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
if (fromToken.toLowerCase() === toToken.toLowerCase()) {
|
|
1471
|
+
throw new StableValidationError(
|
|
1472
|
+
"toToken",
|
|
1473
|
+
toToken,
|
|
1474
|
+
"swap: fromToken and toToken must differ"
|
|
1475
|
+
);
|
|
954
1476
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1477
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1478
|
+
throw new StableValidationError(
|
|
1479
|
+
"amount",
|
|
1480
|
+
amount,
|
|
1481
|
+
"swap: amount must be a finite positive number"
|
|
1482
|
+
);
|
|
959
1483
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
967
|
-
if (receipt.status === "reverted") {
|
|
968
|
-
throw new StableTransactionError({
|
|
969
|
-
phase: "receipt",
|
|
970
|
-
shortMessage: "earn forceRedeem: vault forceRedeem reverted on-chain",
|
|
971
|
-
txHash: receipt.transactionHash,
|
|
972
|
-
chainId: this.config.chainId
|
|
973
|
-
});
|
|
1484
|
+
if (toAddress !== void 0 && !_viem.isAddress.call(void 0, toAddress)) {
|
|
1485
|
+
throw new StableValidationError(
|
|
1486
|
+
"toAddress",
|
|
1487
|
+
toAddress,
|
|
1488
|
+
`swap: invalid toAddress "${toAddress}"`
|
|
1489
|
+
);
|
|
974
1490
|
}
|
|
975
|
-
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
976
1491
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1492
|
+
_validateBridgeParams({
|
|
1493
|
+
fromChain,
|
|
1494
|
+
toChain,
|
|
1495
|
+
fromToken,
|
|
1496
|
+
toToken,
|
|
1497
|
+
amount,
|
|
1498
|
+
recipient
|
|
1499
|
+
}) {
|
|
1500
|
+
if (!_viem.isAddress.call(void 0, fromToken)) {
|
|
1501
|
+
throw new StableValidationError(
|
|
1502
|
+
"fromToken",
|
|
1503
|
+
fromToken,
|
|
1504
|
+
`LI.FI bridge: invalid fromToken address "${fromToken}"`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
if (!_viem.isAddress.call(void 0, toToken)) {
|
|
1508
|
+
throw new StableValidationError(
|
|
1509
|
+
"toToken",
|
|
1510
|
+
toToken,
|
|
1511
|
+
`LI.FI bridge: invalid toToken address "${toToken}"`
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1515
|
+
throw new StableValidationError(
|
|
1516
|
+
"amount",
|
|
1517
|
+
amount,
|
|
1518
|
+
"LI.FI bridge: amount must be a finite positive number"
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
if (recipient !== void 0 && !_viem.isAddress.call(void 0, recipient)) {
|
|
1522
|
+
throw new StableValidationError(
|
|
1523
|
+
"recipient",
|
|
1524
|
+
recipient,
|
|
1525
|
+
`LI.FI bridge: invalid recipient address "${recipient}"`
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
if (fromChain === toChain) {
|
|
1529
|
+
throw new StableValidationError(
|
|
1530
|
+
"toChain",
|
|
1531
|
+
toChain,
|
|
1532
|
+
"LI.FI bridge: fromChain and toChain must differ"
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
985
1535
|
}
|
|
986
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
987
1536
|
_requireFrom() {
|
|
988
|
-
const from = _optionalChain([this, 'access',
|
|
1537
|
+
const from = _nullishCoalesce(_optionalChain([this, 'access', _30 => _30.config, 'access', _31 => _31.walletClient, 'optionalAccess', _32 => _32.account, 'optionalAccess', _33 => _33.address]), () => ( _optionalChain([this, 'access', _34 => _34.config, 'access', _35 => _35.account, 'optionalAccess', _36 => _36.address])));
|
|
989
1538
|
if (!from) {
|
|
990
1539
|
throw new StableValidationError(
|
|
991
1540
|
"signer",
|
|
992
1541
|
void 0,
|
|
993
|
-
"
|
|
1542
|
+
"LI.FI: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
994
1543
|
);
|
|
995
1544
|
}
|
|
996
1545
|
return from;
|
|
997
1546
|
}
|
|
998
|
-
async
|
|
1547
|
+
async _fetchQuote(qs) {
|
|
1548
|
+
const url = `https://li.quest/v1/quote?${qs}`;
|
|
999
1549
|
try {
|
|
1000
|
-
await
|
|
1550
|
+
return await retryWithBackoff(() => this._fetchQuoteOnce(url));
|
|
1001
1551
|
} catch (err) {
|
|
1002
|
-
if (err
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1552
|
+
if (err instanceof StableQuoteError) throw err;
|
|
1553
|
+
if (err instanceof HttpError) {
|
|
1554
|
+
throw new StableQuoteError({
|
|
1555
|
+
provider: "lifi",
|
|
1556
|
+
shortMessage: err.message,
|
|
1557
|
+
httpStatus: err.status,
|
|
1007
1558
|
cause: err
|
|
1008
1559
|
});
|
|
1009
1560
|
}
|
|
1561
|
+
throw new StableNetworkError({
|
|
1562
|
+
shortMessage: "LI.FI: quote request failed before reaching the server",
|
|
1563
|
+
url,
|
|
1564
|
+
cause: err
|
|
1565
|
+
});
|
|
1010
1566
|
}
|
|
1011
1567
|
}
|
|
1012
|
-
async
|
|
1568
|
+
async _fetchQuoteOnce(url) {
|
|
1569
|
+
const res = await fetch(url);
|
|
1570
|
+
const body = await res.json().catch(() => void 0);
|
|
1571
|
+
if (!res.ok) {
|
|
1572
|
+
const providerMessage = isObject(body) && typeof body.message === "string" ? body.message : void 0;
|
|
1573
|
+
const providerCode = isObject(body) && typeof body.code === "string" ? body.code : void 0;
|
|
1574
|
+
if (res.status === 429 || res.status >= 500) {
|
|
1575
|
+
throw new HttpError(
|
|
1576
|
+
`LI.FI: ${_nullishCoalesce(providerMessage, () => ( res.statusText))}`,
|
|
1577
|
+
res.status,
|
|
1578
|
+
true
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
throw new StableQuoteError({
|
|
1582
|
+
provider: "lifi",
|
|
1583
|
+
shortMessage: `LI.FI: ${_nullishCoalesce(providerMessage, () => ( res.statusText))}`,
|
|
1584
|
+
httpStatus: res.status,
|
|
1585
|
+
providerCode,
|
|
1586
|
+
body
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
if (!isLiFiQuoteShape(body)) {
|
|
1590
|
+
throw new StableQuoteError({
|
|
1591
|
+
provider: "lifi",
|
|
1592
|
+
shortMessage: "LI.FI: unexpected quote response shape",
|
|
1593
|
+
httpStatus: res.status,
|
|
1594
|
+
body
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
return body;
|
|
1598
|
+
}
|
|
1599
|
+
_toSwapQuote(quote) {
|
|
1600
|
+
return {
|
|
1601
|
+
toAmount: Number(
|
|
1602
|
+
_viem.formatUnits.call(void 0,
|
|
1603
|
+
BigInt(quote.estimate.toAmount),
|
|
1604
|
+
quote.action.toToken.decimals
|
|
1605
|
+
)
|
|
1606
|
+
),
|
|
1607
|
+
fromAmount: BigInt(quote.estimate.fromAmount),
|
|
1608
|
+
fromToken: quote.action.fromToken.address,
|
|
1609
|
+
approvalAddress: quote.estimate.approvalAddress,
|
|
1610
|
+
transactionRequest: quote.transactionRequest
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
async _executeSwapQuote(quote, srcRpc, expectedChainId) {
|
|
1614
|
+
const from = this._requireFrom();
|
|
1615
|
+
const { transactionRequest } = quote;
|
|
1616
|
+
const srcChainId = transactionRequest.chainId;
|
|
1617
|
+
if (srcChainId !== expectedChainId) {
|
|
1618
|
+
throw new StableQuoteError({
|
|
1619
|
+
provider: "lifi",
|
|
1620
|
+
shortMessage: `LI.FI: returned tx for chain ${srcChainId} but expected ${expectedChainId}`,
|
|
1621
|
+
providerCode: "chain_mismatch",
|
|
1622
|
+
body: quote
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
const srcChain = makeChain(srcChainId, srcRpc);
|
|
1626
|
+
const wc = _nullishCoalesce(this.config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
1627
|
+
account: this.config.account,
|
|
1628
|
+
chain: srcChain,
|
|
1629
|
+
transport: _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, srcRpc)))
|
|
1630
|
+
})));
|
|
1013
1631
|
try {
|
|
1014
|
-
|
|
1632
|
+
await wc.switchChain({ id: srcChainId });
|
|
1015
1633
|
} catch (err) {
|
|
1016
1634
|
throw new StableTransactionError({
|
|
1017
|
-
phase: "
|
|
1018
|
-
shortMessage:
|
|
1019
|
-
chainId:
|
|
1635
|
+
phase: "switch_chain",
|
|
1636
|
+
shortMessage: `LI.FI: wallet rejected or failed to switch to chain ${srcChainId}`,
|
|
1637
|
+
chainId: srcChainId,
|
|
1020
1638
|
cause: err
|
|
1021
1639
|
});
|
|
1022
1640
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1641
|
+
await this._ensureApproval(
|
|
1642
|
+
wc,
|
|
1643
|
+
srcChain,
|
|
1644
|
+
srcRpc,
|
|
1645
|
+
srcChainId,
|
|
1646
|
+
from,
|
|
1647
|
+
quote
|
|
1648
|
+
);
|
|
1649
|
+
let hash;
|
|
1025
1650
|
try {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1651
|
+
hash = await wc.sendTransaction({
|
|
1652
|
+
to: transactionRequest.to,
|
|
1653
|
+
data: transactionRequest.data,
|
|
1654
|
+
value: transactionRequest.value && transactionRequest.value !== "0x" ? BigInt(transactionRequest.value) : 0n,
|
|
1655
|
+
gas: transactionRequest.gasLimit ? BigInt(transactionRequest.gasLimit) : void 0,
|
|
1656
|
+
account: wc.account,
|
|
1657
|
+
chain: srcChain
|
|
1658
|
+
});
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
throw new StableTransactionError({
|
|
1661
|
+
phase: "send",
|
|
1662
|
+
shortMessage: "LI.FI: swap/bridge transaction failed",
|
|
1663
|
+
chainId: srcChainId,
|
|
1664
|
+
cause: err
|
|
1665
|
+
});
|
|
1035
1666
|
}
|
|
1667
|
+
return { txHash: hash, toAmount: quote.toAmount };
|
|
1036
1668
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
if (
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1669
|
+
async _ensureApproval(wc, srcChain, srcRpc, srcChainId, from, quote) {
|
|
1670
|
+
const tokenAddr = quote.fromToken;
|
|
1671
|
+
const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000" || tokenAddr.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
1672
|
+
if (isNative || !quote.approvalAddress) return;
|
|
1673
|
+
const pc = _viem.createPublicClient.call(void 0, {
|
|
1674
|
+
chain: srcChain,
|
|
1675
|
+
transport: _viem.http.call(void 0, srcRpc)
|
|
1676
|
+
});
|
|
1677
|
+
const allowance = await pc.readContract({
|
|
1678
|
+
address: tokenAddr,
|
|
1679
|
+
abi: _viem.erc20Abi,
|
|
1680
|
+
functionName: "allowance",
|
|
1681
|
+
args: [from, quote.approvalAddress]
|
|
1682
|
+
});
|
|
1683
|
+
if (allowance >= quote.fromAmount) return;
|
|
1684
|
+
let approveHash;
|
|
1685
|
+
try {
|
|
1686
|
+
approveHash = await wc.writeContract({
|
|
1687
|
+
address: tokenAddr,
|
|
1688
|
+
abi: _viem.erc20Abi,
|
|
1689
|
+
functionName: "approve",
|
|
1690
|
+
args: [quote.approvalAddress, quote.fromAmount],
|
|
1691
|
+
account: wc.account,
|
|
1692
|
+
chain: srcChain
|
|
1693
|
+
});
|
|
1694
|
+
} catch (err) {
|
|
1695
|
+
throw new StableTransactionError({
|
|
1696
|
+
phase: "approve",
|
|
1697
|
+
shortMessage: "LI.FI: token approve failed",
|
|
1698
|
+
chainId: srcChainId,
|
|
1699
|
+
cause: err
|
|
1700
|
+
});
|
|
1048
1701
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1702
|
+
const approveReceipt = await pc.waitForTransactionReceipt({
|
|
1703
|
+
hash: approveHash
|
|
1704
|
+
});
|
|
1705
|
+
if (approveReceipt.status === "reverted") {
|
|
1706
|
+
throw new StableTransactionError({
|
|
1707
|
+
phase: "approve",
|
|
1708
|
+
shortMessage: "LI.FI: token approve reverted on-chain",
|
|
1709
|
+
txHash: approveReceipt.transactionHash,
|
|
1710
|
+
chainId: srcChainId
|
|
1711
|
+
});
|
|
1053
1712
|
}
|
|
1054
1713
|
}
|
|
1055
1714
|
};
|
|
1715
|
+
function isObject(v) {
|
|
1716
|
+
return typeof v === "object" && v !== null;
|
|
1717
|
+
}
|
|
1718
|
+
function isLiFiQuoteShape(v) {
|
|
1719
|
+
if (!isObject(v)) return false;
|
|
1720
|
+
const tx = v.transactionRequest;
|
|
1721
|
+
const est = v.estimate;
|
|
1722
|
+
const act = v.action;
|
|
1723
|
+
return isObject(tx) && typeof tx.to === "string" && typeof tx.data === "string" && typeof tx.chainId === "number" && isObject(est) && typeof est.fromAmount === "string" && typeof est.toAmount === "string" && isObject(act) && isObject(act.fromToken) && isObject(act.toToken);
|
|
1724
|
+
}
|
|
1056
1725
|
|
|
1057
1726
|
// src/stable.ts
|
|
1058
|
-
function validateTransferParams({
|
|
1727
|
+
function validateTransferParams({
|
|
1728
|
+
to,
|
|
1729
|
+
amount,
|
|
1730
|
+
token,
|
|
1731
|
+
tokenDecimals
|
|
1732
|
+
}) {
|
|
1059
1733
|
if (!_viem.isAddress.call(void 0, to)) {
|
|
1060
|
-
throw new StableValidationError(
|
|
1734
|
+
throw new StableValidationError(
|
|
1735
|
+
"to",
|
|
1736
|
+
to,
|
|
1737
|
+
`transfer: invalid recipient address "${to}"`
|
|
1738
|
+
);
|
|
1061
1739
|
}
|
|
1062
1740
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1063
|
-
throw new StableValidationError(
|
|
1741
|
+
throw new StableValidationError(
|
|
1742
|
+
"amount",
|
|
1743
|
+
amount,
|
|
1744
|
+
"transfer: amount must be a finite positive number"
|
|
1745
|
+
);
|
|
1064
1746
|
}
|
|
1065
1747
|
if (token !== void 0 && !_viem.isAddress.call(void 0, token)) {
|
|
1066
|
-
throw new StableValidationError(
|
|
1748
|
+
throw new StableValidationError(
|
|
1749
|
+
"token",
|
|
1750
|
+
token,
|
|
1751
|
+
`transfer: invalid token address "${token}"`
|
|
1752
|
+
);
|
|
1067
1753
|
}
|
|
1068
1754
|
if (tokenDecimals !== void 0 && (!Number.isInteger(tokenDecimals) || tokenDecimals < 0 || tokenDecimals > 77)) {
|
|
1069
|
-
throw new StableValidationError(
|
|
1755
|
+
throw new StableValidationError(
|
|
1756
|
+
"tokenDecimals",
|
|
1757
|
+
tokenDecimals,
|
|
1758
|
+
"transfer: tokenDecimals must be an integer between 0 and 77"
|
|
1759
|
+
);
|
|
1070
1760
|
}
|
|
1071
1761
|
}
|
|
1072
1762
|
async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
|
|
1073
1763
|
if (tokenDecimals !== void 0) return tokenDecimals;
|
|
1074
1764
|
try {
|
|
1075
|
-
return await publicClient.readContract({
|
|
1076
|
-
|
|
1765
|
+
return await publicClient.readContract({
|
|
1766
|
+
address: token,
|
|
1767
|
+
abi: _viem.erc20Abi,
|
|
1768
|
+
functionName: "decimals"
|
|
1769
|
+
});
|
|
1770
|
+
} catch (e6) {
|
|
1077
1771
|
throw new StableValidationError(
|
|
1078
1772
|
"tokenDecimals",
|
|
1079
1773
|
void 0,
|
|
@@ -1090,7 +1784,11 @@ async function executeTokenTransfer(wc, publicClient, token, to, amount, tokenDe
|
|
|
1090
1784
|
`transfer: no contract found at "${token}" on chain ${chain.id} \u2014 check the address and network`
|
|
1091
1785
|
);
|
|
1092
1786
|
}
|
|
1093
|
-
const decimals = await resolveTokenDecimals(
|
|
1787
|
+
const decimals = await resolveTokenDecimals(
|
|
1788
|
+
publicClient,
|
|
1789
|
+
token,
|
|
1790
|
+
tokenDecimals
|
|
1791
|
+
);
|
|
1094
1792
|
return wc.writeContract({
|
|
1095
1793
|
address: token,
|
|
1096
1794
|
abi: _viem.erc20Abi,
|
|
@@ -1106,13 +1804,23 @@ function createStable({
|
|
|
1106
1804
|
account,
|
|
1107
1805
|
transport,
|
|
1108
1806
|
walletClient,
|
|
1109
|
-
earn: earnConfig
|
|
1807
|
+
earn: earnConfig,
|
|
1808
|
+
merklApiBase
|
|
1110
1809
|
}) {
|
|
1111
1810
|
const chain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
|
|
1112
|
-
const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(
|
|
1811
|
+
const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(
|
|
1812
|
+
/\/$/,
|
|
1813
|
+
""
|
|
1814
|
+
);
|
|
1113
1815
|
const signerConfig = { account, transport, walletClient };
|
|
1114
1816
|
const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
|
|
1115
|
-
const
|
|
1817
|
+
const merkl = new MerklService({ ...signerConfig, rpc, chainId: chain.id, merklApiBase });
|
|
1818
|
+
const morpho = earnConfig ? new MorphoService({
|
|
1819
|
+
...signerConfig,
|
|
1820
|
+
rpc,
|
|
1821
|
+
chainId: chain.id,
|
|
1822
|
+
vault: earnConfig.vault
|
|
1823
|
+
}) : null;
|
|
1116
1824
|
function requireEarn() {
|
|
1117
1825
|
if (!morpho) {
|
|
1118
1826
|
throw new StableValidationError(
|
|
@@ -1123,10 +1831,21 @@ function createStable({
|
|
|
1123
1831
|
}
|
|
1124
1832
|
return morpho;
|
|
1125
1833
|
}
|
|
1834
|
+
function requireSignerAddress() {
|
|
1835
|
+
const from = _nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _37 => _37.account, 'optionalAccess', _38 => _38.address]), () => ( _optionalChain([account, 'optionalAccess', _39 => _39.address])));
|
|
1836
|
+
if (!from) {
|
|
1837
|
+
throw new StableValidationError(
|
|
1838
|
+
"signer",
|
|
1839
|
+
void 0,
|
|
1840
|
+
"stable.rewards.*: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
return from;
|
|
1844
|
+
}
|
|
1126
1845
|
async function transfer(params) {
|
|
1127
1846
|
validateTransferParams(params);
|
|
1128
1847
|
const { from, to, amount, token, tokenDecimals } = params;
|
|
1129
|
-
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess',
|
|
1848
|
+
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _40 => _40.account]), () => ( account)), () => ( from));
|
|
1130
1849
|
const wc = _nullishCoalesce(walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
1131
1850
|
account: acc,
|
|
1132
1851
|
chain,
|
|
@@ -1142,8 +1861,29 @@ function createStable({
|
|
|
1142
1861
|
cause: err
|
|
1143
1862
|
});
|
|
1144
1863
|
}
|
|
1145
|
-
const publicClient = _viem.createPublicClient.call(void 0, {
|
|
1146
|
-
|
|
1864
|
+
const publicClient = _viem.createPublicClient.call(void 0, {
|
|
1865
|
+
chain,
|
|
1866
|
+
transport: _viem.http.call(void 0, rpc)
|
|
1867
|
+
});
|
|
1868
|
+
const hash = token !== void 0 ? await executeTokenTransfer(
|
|
1869
|
+
wc,
|
|
1870
|
+
publicClient,
|
|
1871
|
+
token,
|
|
1872
|
+
to,
|
|
1873
|
+
amount,
|
|
1874
|
+
tokenDecimals,
|
|
1875
|
+
acc,
|
|
1876
|
+
chain
|
|
1877
|
+
) : (
|
|
1878
|
+
// Native transfer on Stable mainnet/testnet. Native USDT0 uses 18 decimals
|
|
1879
|
+
// on both networks, matching standard EVM native-token decimals.
|
|
1880
|
+
await wc.sendTransaction({
|
|
1881
|
+
account: acc,
|
|
1882
|
+
chain,
|
|
1883
|
+
to,
|
|
1884
|
+
value: _viem.parseUnits.call(void 0, String(amount), 18)
|
|
1885
|
+
})
|
|
1886
|
+
);
|
|
1147
1887
|
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
1148
1888
|
if (receipt.status === "reverted") {
|
|
1149
1889
|
throw new StableTransactionError({
|
|
@@ -1168,25 +1908,38 @@ function createStable({
|
|
|
1168
1908
|
bulkWithdraw: (items, options) => requireEarn().bulkWithdraw(items, options),
|
|
1169
1909
|
redeem: (params) => requireEarn().redeem(params),
|
|
1170
1910
|
forceWithdraw: (params) => requireEarn().forceWithdraw(params),
|
|
1171
|
-
forceRedeem: (params) => requireEarn().forceRedeem(params)
|
|
1911
|
+
forceRedeem: (params) => requireEarn().forceRedeem(params),
|
|
1912
|
+
prepareDepositCalldata: (params) => requireEarn().prepareDepositCalldata(params),
|
|
1913
|
+
prepareWithdrawCalldata: (params) => requireEarn().prepareWithdrawCalldata(params),
|
|
1914
|
+
prepareRedeemCalldata: (params) => requireEarn().prepareRedeemCalldata(params),
|
|
1915
|
+
incentiveRewards: {
|
|
1916
|
+
fetch: () => merkl.fetchRewards(requireSignerAddress()),
|
|
1917
|
+
claim: () => merkl.claim()
|
|
1918
|
+
}
|
|
1172
1919
|
}
|
|
1173
1920
|
};
|
|
1174
1921
|
}
|
|
1175
1922
|
function createStableReader(config) {
|
|
1176
1923
|
const reader = new EarnReader(config);
|
|
1924
|
+
const chain = config.network === "testnet" /* Testnet */ ? _chains.stableTestnet : _chains.stable;
|
|
1925
|
+
const rpc = (_nullishCoalesce(config.rpc, () => ( chain.rpcUrls.default.http[0]))).replace(/\/$/, "");
|
|
1926
|
+
const merkl = new MerklService({ chainId: chain.id, rpc, merklApiBase: config.merklApiBase });
|
|
1177
1927
|
return {
|
|
1178
1928
|
earn: {
|
|
1179
1929
|
getYield: () => reader.getYield(),
|
|
1180
1930
|
position: () => reader.position(),
|
|
1181
1931
|
preview: (params) => reader.preview(params),
|
|
1182
|
-
withdrawability: (params) => reader.withdrawability(params)
|
|
1932
|
+
withdrawability: (params) => reader.withdrawability(params),
|
|
1933
|
+
incentiveRewards: {
|
|
1934
|
+
fetch: () => merkl.fetchRewards(config.address)
|
|
1935
|
+
}
|
|
1183
1936
|
}
|
|
1184
1937
|
};
|
|
1185
1938
|
}
|
|
1186
1939
|
|
|
1187
1940
|
// src/constants.ts
|
|
1188
1941
|
var STABLE_USDT_ADDRESS = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
|
|
1189
|
-
var STABLE_VAULT_ADDRESS = "
|
|
1942
|
+
var STABLE_VAULT_ADDRESS = "0xb7Df8db22A5DBBFA9ebeb94b3910aec6a4f05c08";
|
|
1190
1943
|
|
|
1191
1944
|
|
|
1192
1945
|
|