@stablechain/sdk 1.0.0 → 1.0.2
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 +1134 -738
- package/dist/index.d.cts +39 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +1123 -727
- 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",
|
|
@@ -142,528 +147,703 @@ async function retryWithBackoff(fn, isRetryable = isRetryableError, opts = {}) {
|
|
|
142
147
|
throw lastErr;
|
|
143
148
|
}
|
|
144
149
|
|
|
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 {
|
|
150
|
+
// src/earn/morpho.ts
|
|
151
|
+
var MorphoService = class {
|
|
169
152
|
constructor(config) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
153
|
+
this.config = config;
|
|
154
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
155
|
+
this.chain = makeChain(config.chainId, config.rpc);
|
|
156
|
+
this.wc = _nullishCoalesce(config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
157
|
+
account: config.account,
|
|
158
|
+
chain: this.chain,
|
|
159
|
+
transport: _nullishCoalesce(config.transport, () => ( _viem.http.call(void 0, config.rpc)))
|
|
160
|
+
})));
|
|
161
|
+
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, {
|
|
162
|
+
account: this.wc.account,
|
|
163
|
+
chain: this.chain,
|
|
164
|
+
transport: _viem.custom.call(void 0, {
|
|
165
|
+
request: this.wc.request.bind(this.wc)
|
|
166
|
+
})
|
|
167
|
+
});
|
|
168
|
+
this.morpho = new (0, _morphosdk.MorphoClient)(morphoWc, { supportSignature: true });
|
|
169
|
+
this.pc = _viem.createPublicClient.call(void 0, {
|
|
170
|
+
chain: this.chain,
|
|
171
|
+
transport: _viem.http.call(void 0, config.rpc)
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
deposit(params) {
|
|
175
|
+
this._validateAmount(params.amount, "earn deposit");
|
|
176
|
+
if (params.depositBuffer !== void 0 && (params.depositBuffer <= 0 || params.depositBuffer > 1)) {
|
|
185
177
|
throw new StableValidationError(
|
|
186
|
-
"
|
|
187
|
-
|
|
188
|
-
|
|
178
|
+
"depositBuffer",
|
|
179
|
+
params.depositBuffer,
|
|
180
|
+
"earn deposit: depositBuffer must be > 0 and \u2264 1"
|
|
189
181
|
);
|
|
190
182
|
}
|
|
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);
|
|
183
|
+
return this._withIdempotency(
|
|
184
|
+
params.idempotencyKey,
|
|
185
|
+
() => this._deposit(params)
|
|
186
|
+
);
|
|
200
187
|
}
|
|
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
|
-
|
|
188
|
+
async bulkDeposit(items, options = {}) {
|
|
189
|
+
const { idempotencyKey, stopOnError = false } = options;
|
|
190
|
+
const run = async () => {
|
|
191
|
+
const results = [];
|
|
192
|
+
for (const item of items) {
|
|
193
|
+
try {
|
|
194
|
+
const result = await this.deposit(item);
|
|
195
|
+
results.push({
|
|
196
|
+
status: "fulfilled",
|
|
197
|
+
txHash: result.txHash,
|
|
198
|
+
vault: item.vault,
|
|
199
|
+
idempotencyKey: item.idempotencyKey
|
|
200
|
+
});
|
|
201
|
+
} catch (err) {
|
|
202
|
+
const entry = {
|
|
203
|
+
status: "rejected",
|
|
204
|
+
error: String(err),
|
|
205
|
+
vault: item.vault,
|
|
206
|
+
idempotencyKey: item.idempotencyKey
|
|
207
|
+
};
|
|
208
|
+
results.push(entry);
|
|
209
|
+
if (stopOnError) {
|
|
210
|
+
const succeeded2 = results.filter(
|
|
211
|
+
(r) => r.status === "fulfilled"
|
|
212
|
+
).length;
|
|
213
|
+
throw new StableBulkError({
|
|
214
|
+
shortMessage: "bulkDeposit halted on first failure",
|
|
215
|
+
results,
|
|
216
|
+
succeeded: succeeded2,
|
|
217
|
+
failed: results.length - succeeded2
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const succeeded = results.filter(
|
|
223
|
+
(r) => r.status === "fulfilled"
|
|
224
|
+
).length;
|
|
225
|
+
return {
|
|
226
|
+
results,
|
|
227
|
+
succeeded,
|
|
228
|
+
failed: results.length - succeeded,
|
|
229
|
+
idempotencyKey
|
|
230
|
+
};
|
|
224
231
|
};
|
|
232
|
+
return this._withIdempotency(idempotencyKey, run);
|
|
225
233
|
}
|
|
226
|
-
async
|
|
227
|
-
const {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
async bulkWithdraw(items, options = {}) {
|
|
235
|
+
const { idempotencyKey, stopOnError = false } = options;
|
|
236
|
+
const run = async () => {
|
|
237
|
+
const results = [];
|
|
238
|
+
for (const item of items) {
|
|
239
|
+
try {
|
|
240
|
+
const result = await this.withdraw(item);
|
|
241
|
+
results.push({
|
|
242
|
+
status: "fulfilled",
|
|
243
|
+
txHash: result.txHash,
|
|
244
|
+
idempotencyKey: item.idempotencyKey
|
|
245
|
+
});
|
|
246
|
+
} catch (err) {
|
|
247
|
+
const entry = {
|
|
248
|
+
status: "rejected",
|
|
249
|
+
error: String(err),
|
|
250
|
+
idempotencyKey: item.idempotencyKey
|
|
251
|
+
};
|
|
252
|
+
results.push(entry);
|
|
253
|
+
if (stopOnError) {
|
|
254
|
+
const succeeded2 = results.filter(
|
|
255
|
+
(r) => r.status === "fulfilled"
|
|
256
|
+
).length;
|
|
257
|
+
throw new StableBulkError({
|
|
258
|
+
shortMessage: "bulkWithdraw halted on first failure",
|
|
259
|
+
results,
|
|
260
|
+
succeeded: succeeded2,
|
|
261
|
+
failed: results.length - succeeded2
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const succeeded = results.filter(
|
|
267
|
+
(r) => r.status === "fulfilled"
|
|
268
|
+
).length;
|
|
269
|
+
return {
|
|
270
|
+
results,
|
|
271
|
+
succeeded,
|
|
272
|
+
failed: results.length - succeeded,
|
|
273
|
+
idempotencyKey
|
|
274
|
+
};
|
|
233
275
|
};
|
|
276
|
+
return this._withIdempotency(idempotencyKey, run);
|
|
234
277
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
functionName: "decimals"
|
|
242
|
-
})));
|
|
243
|
-
const requestedRaw = _viem.parseUnits.call(void 0, String(params.amount), tokenDecimals);
|
|
244
|
-
const availableLiquidityRaw = vaultData.assetBalance;
|
|
245
|
-
return {
|
|
246
|
-
isInstant: requestedRaw <= availableLiquidityRaw,
|
|
247
|
-
availableLiquidity: Number(_viem.formatUnits.call(void 0, availableLiquidityRaw, tokenDecimals)),
|
|
248
|
-
tokenDecimals
|
|
249
|
-
};
|
|
278
|
+
withdraw(params) {
|
|
279
|
+
this._validateAmount(params.amount, "earn withdraw");
|
|
280
|
+
return this._withIdempotency(
|
|
281
|
+
params.idempotencyKey,
|
|
282
|
+
() => this._withdraw(params)
|
|
283
|
+
);
|
|
250
284
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
285
|
+
redeem(params) {
|
|
286
|
+
this._validateShares(params.shares, "earn redeem");
|
|
287
|
+
return this._withIdempotency(
|
|
288
|
+
params.idempotencyKey,
|
|
289
|
+
() => this._redeem(params)
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
forceWithdraw(params) {
|
|
293
|
+
this._validateAmount(params.amount, "earn forceWithdraw");
|
|
294
|
+
return this._withIdempotency(
|
|
295
|
+
params.idempotencyKey,
|
|
296
|
+
() => this._forceWithdraw(params)
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
forceRedeem(params) {
|
|
300
|
+
this._validateShares(params.shares, "earn forceRedeem");
|
|
301
|
+
return this._withIdempotency(
|
|
302
|
+
params.idempotencyKey,
|
|
303
|
+
() => this._forceRedeem(params)
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
// ─── Private operations ──────────────────────────────────────────────────
|
|
307
|
+
async _deposit(params) {
|
|
308
|
+
const {
|
|
309
|
+
amount,
|
|
310
|
+
tokenDecimals,
|
|
311
|
+
slippageTolerance,
|
|
312
|
+
idempotencyKey,
|
|
313
|
+
depositBuffer
|
|
314
|
+
} = params;
|
|
315
|
+
const from = this._requireFrom();
|
|
316
|
+
const vaultAddr = _nullishCoalesce(params.vault, () => ( this.config.vault));
|
|
317
|
+
await this._switchChain();
|
|
318
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
319
|
+
const vaultData = await vault.getData();
|
|
320
|
+
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn deposit")));
|
|
321
|
+
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
322
|
+
if (depositBuffer !== void 0) {
|
|
323
|
+
const bps = BigInt(Math.round(depositBuffer * 1e4));
|
|
324
|
+
const maxAllowed = vaultData.assetBalance * bps / 10000n;
|
|
325
|
+
if (assetsRaw > maxAllowed) {
|
|
326
|
+
const maxFormatted = Number(_viem.formatUnits.call(void 0, maxAllowed, decimals));
|
|
327
|
+
const idleFormatted = Number(
|
|
328
|
+
_viem.formatUnits.call(void 0, vaultData.assetBalance, decimals)
|
|
329
|
+
);
|
|
330
|
+
throw new StableValidationError(
|
|
331
|
+
"amount",
|
|
332
|
+
amount,
|
|
333
|
+
`earn deposit: amount ${amount} exceeds deposit buffer \u2014 max ${maxFormatted} (${depositBuffer * 100}% of ${idleFormatted} idle liquidity)`
|
|
334
|
+
);
|
|
257
335
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
},
|
|
281
|
-
(err) => err instanceof HttpError ? err.retryable : err instanceof TypeError
|
|
282
|
-
);
|
|
283
|
-
} catch (err) {
|
|
284
|
-
if (err instanceof TypeError || err instanceof HttpError) {
|
|
285
|
-
throw new StableNetworkError({
|
|
286
|
-
shortMessage: err instanceof HttpError ? `earn preview: Morpho API returned ${err.status} after retries` : `earn preview: failed to reach Morpho API after retries \u2014 ${String(err)}`,
|
|
287
|
-
url: MORPHO_API_URL,
|
|
288
|
-
cause: err
|
|
336
|
+
}
|
|
337
|
+
const depositAction = vault.deposit({
|
|
338
|
+
amount: assetsRaw,
|
|
339
|
+
userAddress: from,
|
|
340
|
+
accrualVault: vaultData,
|
|
341
|
+
slippageTolerance: slippageTolerance !== void 0 ? (
|
|
342
|
+
// Morpho SDK expects slippage as a WAD-scaled fraction (1e18 = 100%)
|
|
343
|
+
_viem.parseUnits.call(void 0, String(slippageTolerance), 18)
|
|
344
|
+
) : void 0
|
|
345
|
+
});
|
|
346
|
+
const requirements = await depositAction.getRequirements();
|
|
347
|
+
let sig;
|
|
348
|
+
for (const req of requirements) {
|
|
349
|
+
if (_morphosdk.isRequirementApproval.call(void 0, req)) {
|
|
350
|
+
const approveHash = await this._sendTx(
|
|
351
|
+
req.to,
|
|
352
|
+
req.data,
|
|
353
|
+
req.value,
|
|
354
|
+
from
|
|
355
|
+
);
|
|
356
|
+
const approveReceipt = await this.pc.waitForTransactionReceipt({
|
|
357
|
+
hash: approveHash
|
|
289
358
|
});
|
|
359
|
+
if (approveReceipt.status === "reverted") {
|
|
360
|
+
throw new StableTransactionError({
|
|
361
|
+
phase: "approve",
|
|
362
|
+
shortMessage: "earn deposit: token approval reverted on-chain",
|
|
363
|
+
txHash: approveReceipt.transactionHash,
|
|
364
|
+
chainId: this.config.chainId
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
} else if (_morphosdk.isRequirementSignature.call(void 0, req)) {
|
|
368
|
+
sig = await req.sign(this.wc, from);
|
|
290
369
|
}
|
|
291
|
-
throw err;
|
|
292
370
|
}
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
371
|
+
const tx = depositAction.buildTx(sig);
|
|
372
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
373
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
374
|
+
if (receipt.status === "reverted") {
|
|
375
|
+
throw new StableTransactionError({
|
|
376
|
+
phase: "receipt",
|
|
377
|
+
shortMessage: "earn deposit: vault deposit reverted on-chain",
|
|
378
|
+
txHash: receipt.transactionHash,
|
|
379
|
+
chainId: this.config.chainId
|
|
380
|
+
});
|
|
300
381
|
}
|
|
301
|
-
return
|
|
382
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
302
383
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// src/services/lifi.ts
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// src/chains.ts
|
|
317
|
-
var CHAIN_CONFIGS = {
|
|
318
|
-
// ─── Testnet ──────────────────────────────────────────────────────────────
|
|
319
|
-
["sepolia" /* Sepolia */]: {
|
|
320
|
-
id: 11155111,
|
|
321
|
-
rpc: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
322
|
-
usdt: "0xc4DCC311c028e341fd8602D8eB89c5de94625927",
|
|
323
|
-
decimals: 18
|
|
324
|
-
},
|
|
325
|
-
["stable-testnet" /* StableTestnet */]: {
|
|
326
|
-
id: 2201,
|
|
327
|
-
rpc: "https://rpc.testnet.stable.xyz",
|
|
328
|
-
usdt: "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9",
|
|
329
|
-
decimals: 6
|
|
330
|
-
},
|
|
331
|
-
// ─── Mainnet ──────────────────────────────────────────────────────────────
|
|
332
|
-
["stable" /* Stable */]: {
|
|
333
|
-
id: 988,
|
|
334
|
-
rpc: "https://rpc.stable.xyz",
|
|
335
|
-
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
336
|
-
decimals: 6
|
|
337
|
-
},
|
|
338
|
-
["ethereum" /* Ethereum */]: {
|
|
339
|
-
id: 1,
|
|
340
|
-
rpc: "https://ethereum-rpc.publicnode.com",
|
|
341
|
-
usdt: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
342
|
-
decimals: 6
|
|
343
|
-
},
|
|
344
|
-
["arbitrum" /* Arbitrum */]: {
|
|
345
|
-
id: 42161,
|
|
346
|
-
rpc: "https://arbitrum-one-rpc.publicnode.com",
|
|
347
|
-
usdt: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92",
|
|
348
|
-
decimals: 6
|
|
349
|
-
},
|
|
350
|
-
["ink" /* Ink */]: {
|
|
351
|
-
id: 57073,
|
|
352
|
-
rpc: "https://rpc-gel.inkonchain.com",
|
|
353
|
-
usdt: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
354
|
-
decimals: 6
|
|
355
|
-
},
|
|
356
|
-
["bera" /* Bera */]: {
|
|
357
|
-
id: 80094,
|
|
358
|
-
rpc: "https://rpc.berachain.com",
|
|
359
|
-
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
360
|
-
decimals: 6
|
|
361
|
-
},
|
|
362
|
-
["megaeth" /* MegaETH */]: {
|
|
363
|
-
id: 4326,
|
|
364
|
-
rpc: "https://carrot.megaeth.com",
|
|
365
|
-
usdt: "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb",
|
|
366
|
-
decimals: 6
|
|
367
|
-
},
|
|
368
|
-
["base" /* Base */]: {
|
|
369
|
-
id: 8453,
|
|
370
|
-
rpc: "https://mainnet.base.org",
|
|
371
|
-
usdt: "0x102d758f688a4C1C5a80b116bD945d4455460282",
|
|
372
|
-
decimals: 6
|
|
373
|
-
},
|
|
374
|
-
["bsc" /* BSC */]: {
|
|
375
|
-
id: 56,
|
|
376
|
-
rpc: "https://bsc-rpc.publicnode.com",
|
|
377
|
-
usdt: "0x55d398326f99059fF775485246999027B3197955",
|
|
378
|
-
decimals: 18
|
|
379
|
-
},
|
|
380
|
-
["hyperevm" /* HyperEVM */]: {
|
|
381
|
-
id: 999,
|
|
382
|
-
rpc: "https://rpc.hyperliquid.xyz/evm",
|
|
383
|
-
usdt: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
|
|
384
|
-
decimals: 6
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
// src/services/lifi.ts
|
|
389
|
-
var LiFiService = class {
|
|
390
|
-
constructor(config) {
|
|
391
|
-
this.config = config;
|
|
392
|
-
}
|
|
393
|
-
async quoteSwap(params) {
|
|
394
|
-
this._validateSwapParams(params);
|
|
395
|
-
const { fromToken, toToken, amount, fromDecimals = 6, toAddress } = params;
|
|
384
|
+
async _withdraw(params) {
|
|
385
|
+
const { amount, tokenDecimals, idempotencyKey } = params;
|
|
396
386
|
const from = this._requireFrom();
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return
|
|
415
|
-
}
|
|
416
|
-
async quoteBridge(params) {
|
|
417
|
-
this._validateBridgeParams(params);
|
|
418
|
-
const { fromChain, toChain, fromToken, toToken, amount, fromDecimals = 6, recipient } = params;
|
|
419
|
-
const from = _nullishCoalesce(_nullishCoalesce(_optionalChain([this, 'access', _7 => _7.config, 'access', _8 => _8.walletClient, 'optionalAccess', _9 => _9.account, 'optionalAccess', _10 => _10.address]), () => ( _optionalChain([this, 'access', _11 => _11.config, 'access', _12 => _12.account, 'optionalAccess', _13 => _13.address]))), () => ( "0x0000000000000000000000000000000000000000"));
|
|
420
|
-
const src = CHAIN_CONFIGS[fromChain];
|
|
421
|
-
const dst = CHAIN_CONFIGS[toChain];
|
|
422
|
-
if (!src) throw new StableValidationError("fromChain", fromChain, `LI.FI bridge: unsupported source chain "${fromChain}"`);
|
|
423
|
-
if (!dst) throw new StableValidationError("toChain", toChain, `LI.FI bridge: unsupported destination chain "${toChain}"`);
|
|
424
|
-
const qs = new URLSearchParams({
|
|
425
|
-
fromChain: src.id.toString(),
|
|
426
|
-
toChain: dst.id.toString(),
|
|
427
|
-
fromToken,
|
|
428
|
-
toToken,
|
|
429
|
-
fromAmount: _viem.parseUnits.call(void 0, String(amount), fromDecimals).toString(),
|
|
430
|
-
fromAddress: from,
|
|
431
|
-
...recipient && { toAddress: recipient }
|
|
432
|
-
});
|
|
433
|
-
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
434
|
-
return { toAmount: quote.toAmount };
|
|
387
|
+
const vaultAddr = this.config.vault;
|
|
388
|
+
await this._switchChain();
|
|
389
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
390
|
+
const vaultData = await vault.getData();
|
|
391
|
+
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn withdraw")));
|
|
392
|
+
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
393
|
+
const tx = vault.withdraw({ amount: assetsRaw, userAddress: from }).buildTx();
|
|
394
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
395
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
396
|
+
if (receipt.status === "reverted") {
|
|
397
|
+
throw new StableTransactionError({
|
|
398
|
+
phase: "receipt",
|
|
399
|
+
shortMessage: "earn withdraw: vault withdraw reverted on-chain",
|
|
400
|
+
txHash: receipt.transactionHash,
|
|
401
|
+
chainId: this.config.chainId
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
435
405
|
}
|
|
436
|
-
async
|
|
437
|
-
|
|
438
|
-
const { fromChain, toChain, fromToken, toToken, amount, fromDecimals = 6, recipient } = params;
|
|
406
|
+
async _redeem(params) {
|
|
407
|
+
const { shares, shareDecimals, idempotencyKey } = params;
|
|
439
408
|
const from = this._requireFrom();
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
409
|
+
const vaultAddr = this.config.vault;
|
|
410
|
+
await this._switchChain();
|
|
411
|
+
let decimals = shareDecimals;
|
|
412
|
+
if (decimals === void 0) {
|
|
413
|
+
try {
|
|
414
|
+
decimals = await this.pc.readContract({
|
|
415
|
+
address: vaultAddr,
|
|
416
|
+
abi: _viem.erc20Abi,
|
|
417
|
+
functionName: "decimals"
|
|
418
|
+
});
|
|
419
|
+
} catch (e2) {
|
|
420
|
+
throw new StableValidationError(
|
|
421
|
+
"shareDecimals",
|
|
422
|
+
void 0,
|
|
423
|
+
`earn redeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
444
426
|
}
|
|
445
|
-
|
|
446
|
-
|
|
427
|
+
const sharesRaw = _viem.parseUnits.call(void 0, String(shares), decimals);
|
|
428
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
429
|
+
const tx = vault.redeem({ shares: sharesRaw, userAddress: from }).buildTx();
|
|
430
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
431
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
432
|
+
if (receipt.status === "reverted") {
|
|
433
|
+
throw new StableTransactionError({
|
|
434
|
+
phase: "receipt",
|
|
435
|
+
shortMessage: "earn redeem: vault redeem reverted on-chain",
|
|
436
|
+
txHash: receipt.transactionHash,
|
|
437
|
+
chainId: this.config.chainId
|
|
438
|
+
});
|
|
447
439
|
}
|
|
448
|
-
|
|
449
|
-
const qs = new URLSearchParams({
|
|
450
|
-
fromChain: src.id.toString(),
|
|
451
|
-
toChain: dst.id.toString(),
|
|
452
|
-
fromToken,
|
|
453
|
-
toToken,
|
|
454
|
-
fromAmount: fromAmountRaw,
|
|
455
|
-
fromAddress: from,
|
|
456
|
-
...recipient && { toAddress: recipient }
|
|
457
|
-
});
|
|
458
|
-
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
459
|
-
return this._executeSwapQuote(quote, src.rpc, src.id);
|
|
440
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
460
441
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
442
|
+
async _forceWithdraw(params) {
|
|
443
|
+
const { amount, tokenDecimals, deallocations, idempotencyKey } = params;
|
|
444
|
+
const from = this._requireFrom();
|
|
445
|
+
const vaultAddr = this.config.vault;
|
|
446
|
+
await this._switchChain();
|
|
447
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
448
|
+
let decimals = tokenDecimals;
|
|
449
|
+
if (decimals === void 0) {
|
|
450
|
+
const vaultData = await vault.getData();
|
|
451
|
+
decimals = await this._resolveDecimals(
|
|
452
|
+
vaultData.asset,
|
|
453
|
+
"earn forceWithdraw"
|
|
454
|
+
);
|
|
473
455
|
}
|
|
474
|
-
|
|
475
|
-
|
|
456
|
+
const tx = vault.forceWithdraw({
|
|
457
|
+
deallocations: this._buildDeallocs(deallocations, decimals),
|
|
458
|
+
withdraw: { amount: _viem.parseUnits.call(void 0, String(amount), decimals) },
|
|
459
|
+
userAddress: from
|
|
460
|
+
}).buildTx();
|
|
461
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
462
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
463
|
+
if (receipt.status === "reverted") {
|
|
464
|
+
throw new StableTransactionError({
|
|
465
|
+
phase: "receipt",
|
|
466
|
+
shortMessage: "earn forceWithdraw: vault forceWithdraw reverted on-chain",
|
|
467
|
+
txHash: receipt.transactionHash,
|
|
468
|
+
chainId: this.config.chainId
|
|
469
|
+
});
|
|
476
470
|
}
|
|
471
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
477
472
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
473
|
+
async _forceRedeem(params) {
|
|
474
|
+
const {
|
|
475
|
+
shares,
|
|
476
|
+
shareDecimals,
|
|
477
|
+
tokenDecimals,
|
|
478
|
+
deallocations,
|
|
479
|
+
idempotencyKey
|
|
480
|
+
} = params;
|
|
481
|
+
const from = this._requireFrom();
|
|
482
|
+
const vaultAddr = this.config.vault;
|
|
483
|
+
await this._switchChain();
|
|
484
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
485
|
+
let shareDec = shareDecimals;
|
|
486
|
+
if (shareDec === void 0) {
|
|
487
|
+
try {
|
|
488
|
+
shareDec = await this.pc.readContract({
|
|
489
|
+
address: vaultAddr,
|
|
490
|
+
abi: _viem.erc20Abi,
|
|
491
|
+
functionName: "decimals"
|
|
492
|
+
});
|
|
493
|
+
} catch (e3) {
|
|
494
|
+
throw new StableValidationError(
|
|
495
|
+
"shareDecimals",
|
|
496
|
+
void 0,
|
|
497
|
+
`earn forceRedeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
487
500
|
}
|
|
488
|
-
|
|
489
|
-
|
|
501
|
+
let assetDec = tokenDecimals;
|
|
502
|
+
if (assetDec === void 0) {
|
|
503
|
+
const vaultData = await vault.getData();
|
|
504
|
+
assetDec = await this._resolveDecimals(
|
|
505
|
+
vaultData.asset,
|
|
506
|
+
"earn forceRedeem"
|
|
507
|
+
);
|
|
490
508
|
}
|
|
491
|
-
|
|
492
|
-
|
|
509
|
+
const tx = vault.forceRedeem({
|
|
510
|
+
deallocations: this._buildDeallocs(deallocations, assetDec),
|
|
511
|
+
redeem: { shares: _viem.parseUnits.call(void 0, String(shares), shareDec) },
|
|
512
|
+
userAddress: from
|
|
513
|
+
}).buildTx();
|
|
514
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
515
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
516
|
+
if (receipt.status === "reverted") {
|
|
517
|
+
throw new StableTransactionError({
|
|
518
|
+
phase: "receipt",
|
|
519
|
+
shortMessage: "earn forceRedeem: vault forceRedeem reverted on-chain",
|
|
520
|
+
txHash: receipt.transactionHash,
|
|
521
|
+
chainId: this.config.chainId
|
|
522
|
+
});
|
|
493
523
|
}
|
|
524
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
525
|
+
}
|
|
526
|
+
_buildDeallocs(deallocations, assetDecimals) {
|
|
527
|
+
return deallocations.map((d) => ({
|
|
528
|
+
adapter: d.adapter,
|
|
529
|
+
amount: _viem.parseUnits.call(void 0, String(d.amount), assetDecimals),
|
|
530
|
+
// MarketParams in the Morpho SDK has computed fields (id, liquidationIncentiveFactor)
|
|
531
|
+
// not present in our user-facing type; the plain object is structurally compatible for ABI encoding.
|
|
532
|
+
...d.marketParams && {
|
|
533
|
+
marketParams: d.marketParams
|
|
534
|
+
}
|
|
535
|
+
}));
|
|
494
536
|
}
|
|
537
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
495
538
|
_requireFrom() {
|
|
496
|
-
const from =
|
|
539
|
+
const from = _optionalChain([this, 'access', _5 => _5.wc, 'access', _6 => _6.account, 'optionalAccess', _7 => _7.address]);
|
|
497
540
|
if (!from) {
|
|
498
541
|
throw new StableValidationError(
|
|
499
542
|
"signer",
|
|
500
543
|
void 0,
|
|
501
|
-
"
|
|
544
|
+
"earn: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
502
545
|
);
|
|
503
546
|
}
|
|
504
547
|
return from;
|
|
505
548
|
}
|
|
506
|
-
async
|
|
507
|
-
const url = `https://li.quest/v1/quote?${qs}`;
|
|
549
|
+
async _switchChain() {
|
|
508
550
|
try {
|
|
509
|
-
|
|
551
|
+
await this.wc.switchChain({ id: this.config.chainId });
|
|
510
552
|
} catch (err) {
|
|
511
|
-
if (err
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
httpStatus: err.status,
|
|
553
|
+
if (err.code === 4001) {
|
|
554
|
+
throw new StableTransactionError({
|
|
555
|
+
phase: "switch_chain",
|
|
556
|
+
shortMessage: `earn: wallet rejected switch to chain ${this.config.chainId}`,
|
|
557
|
+
chainId: this.config.chainId,
|
|
517
558
|
cause: err
|
|
518
559
|
});
|
|
519
560
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async _sendTx(to, data, value, from) {
|
|
564
|
+
try {
|
|
565
|
+
return await this.wc.sendTransaction({
|
|
566
|
+
to,
|
|
567
|
+
data,
|
|
568
|
+
value,
|
|
569
|
+
account: from,
|
|
570
|
+
chain: this.chain
|
|
571
|
+
});
|
|
572
|
+
} catch (err) {
|
|
573
|
+
throw new StableTransactionError({
|
|
574
|
+
phase: "send",
|
|
575
|
+
shortMessage: "earn: transaction send failed",
|
|
576
|
+
chainId: this.config.chainId,
|
|
523
577
|
cause: err
|
|
524
578
|
});
|
|
525
579
|
}
|
|
526
580
|
}
|
|
527
|
-
async
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
});
|
|
581
|
+
async _resolveDecimals(asset, op) {
|
|
582
|
+
try {
|
|
583
|
+
return await retryWithBackoff(
|
|
584
|
+
() => this.pc.readContract({
|
|
585
|
+
address: asset,
|
|
586
|
+
abi: _viem.erc20Abi,
|
|
587
|
+
functionName: "decimals"
|
|
588
|
+
})
|
|
589
|
+
);
|
|
590
|
+
} catch (e4) {
|
|
591
|
+
throw new StableValidationError(
|
|
592
|
+
"tokenDecimals",
|
|
593
|
+
void 0,
|
|
594
|
+
`${op}: could not fetch decimals for asset "${asset}" \u2014 pass tokenDecimals explicitly`
|
|
595
|
+
);
|
|
543
596
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
597
|
+
}
|
|
598
|
+
_withIdempotency(key, fn) {
|
|
599
|
+
if (!key) return fn();
|
|
600
|
+
const existing = this.inFlight.get(key);
|
|
601
|
+
if (existing) return existing;
|
|
602
|
+
const promise = fn().finally(() => this.inFlight.delete(key));
|
|
603
|
+
this.inFlight.set(key, promise);
|
|
604
|
+
return promise;
|
|
605
|
+
}
|
|
606
|
+
_validateAmount(amount, op) {
|
|
607
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
608
|
+
throw new StableValidationError(
|
|
609
|
+
"amount",
|
|
610
|
+
amount,
|
|
611
|
+
`${op}: amount must be a finite positive number`
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
_validateShares(shares, op) {
|
|
616
|
+
if (!Number.isFinite(shares) || shares <= 0) {
|
|
617
|
+
throw new StableValidationError(
|
|
618
|
+
"shares",
|
|
619
|
+
shares,
|
|
620
|
+
`${op}: shares must be a finite positive number`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
// src/earn/reader.ts
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
// src/types.ts
|
|
639
|
+
var Chain = /* @__PURE__ */ ((Chain2) => {
|
|
640
|
+
Chain2["Sepolia"] = "sepolia";
|
|
641
|
+
Chain2["StableTestnet"] = "stable-testnet";
|
|
642
|
+
Chain2["Stable"] = "stable";
|
|
643
|
+
Chain2["Ethereum"] = "ethereum";
|
|
644
|
+
Chain2["Arbitrum"] = "arbitrum";
|
|
645
|
+
Chain2["Ink"] = "ink";
|
|
646
|
+
Chain2["Bera"] = "bera";
|
|
647
|
+
Chain2["MegaETH"] = "megaeth";
|
|
648
|
+
Chain2["Base"] = "base";
|
|
649
|
+
Chain2["BSC"] = "bsc";
|
|
650
|
+
Chain2["HyperEVM"] = "hyperevm";
|
|
651
|
+
return Chain2;
|
|
652
|
+
})(Chain || {});
|
|
653
|
+
var Network = /* @__PURE__ */ ((Network2) => {
|
|
654
|
+
Network2["Mainnet"] = "mainnet";
|
|
655
|
+
Network2["Testnet"] = "testnet";
|
|
656
|
+
return Network2;
|
|
657
|
+
})(Network || {});
|
|
658
|
+
|
|
659
|
+
// src/earn/reader.ts
|
|
660
|
+
var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
|
|
661
|
+
var EarnReader = class {
|
|
662
|
+
constructor(config) {
|
|
663
|
+
if (!_optionalChain([config, 'access', _8 => _8.earn, 'optionalAccess', _9 => _9.vault])) {
|
|
664
|
+
throw new StableValidationError(
|
|
665
|
+
"earn.vault",
|
|
666
|
+
void 0,
|
|
667
|
+
"createStableReader: earn.vault is required"
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
if (!_viem.isAddress.call(void 0, config.address)) {
|
|
671
|
+
throw new StableValidationError(
|
|
672
|
+
"address",
|
|
673
|
+
config.address,
|
|
674
|
+
`createStableReader: invalid user address "${config.address}"`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
if (!_viem.isAddress.call(void 0, config.earn.vault)) {
|
|
678
|
+
throw new StableValidationError(
|
|
679
|
+
"earn.vault",
|
|
680
|
+
config.earn.vault,
|
|
681
|
+
`createStableReader: invalid vault address "${config.earn.vault}"`
|
|
682
|
+
);
|
|
551
683
|
}
|
|
552
|
-
|
|
684
|
+
const network = _nullishCoalesce(config.network, () => ( "mainnet")) /* Mainnet */;
|
|
685
|
+
const viemChain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
|
|
686
|
+
const rpc = (_nullishCoalesce(config.rpc, () => ( viemChain.rpcUrls.default.http[0]))).replace(
|
|
687
|
+
/\/$/,
|
|
688
|
+
""
|
|
689
|
+
);
|
|
690
|
+
this.chainId = viemChain.id;
|
|
691
|
+
this.vaultAddr = config.earn.vault;
|
|
692
|
+
this.userAddr = config.address;
|
|
693
|
+
const chain = makeChain(viemChain.id, rpc);
|
|
694
|
+
this.pc = _viem.createPublicClient.call(void 0, { chain, transport: _viem.http.call(void 0, rpc) });
|
|
695
|
+
this.morpho = new (0, _morphosdk.MorphoClient)(this.pc);
|
|
553
696
|
}
|
|
554
|
-
|
|
697
|
+
async position() {
|
|
698
|
+
const vault = this.morpho.vaultV2(this.vaultAddr, this.chainId);
|
|
699
|
+
const [vaultData, shares] = await Promise.all([
|
|
700
|
+
vault.getData(),
|
|
701
|
+
this.pc.readContract({
|
|
702
|
+
address: this.vaultAddr,
|
|
703
|
+
abi: _viem.erc20Abi,
|
|
704
|
+
functionName: "balanceOf",
|
|
705
|
+
args: [this.userAddr]
|
|
706
|
+
})
|
|
707
|
+
]);
|
|
708
|
+
const [tokenDecimals, shareDecimals] = await Promise.all([
|
|
709
|
+
this.pc.readContract({
|
|
710
|
+
address: vaultData.asset,
|
|
711
|
+
abi: _viem.erc20Abi,
|
|
712
|
+
functionName: "decimals"
|
|
713
|
+
}),
|
|
714
|
+
this.pc.readContract({
|
|
715
|
+
address: this.vaultAddr,
|
|
716
|
+
abi: _viem.erc20Abi,
|
|
717
|
+
functionName: "decimals"
|
|
718
|
+
})
|
|
719
|
+
]);
|
|
720
|
+
const assetsRaw = vaultData.toAssets(shares);
|
|
555
721
|
return {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
722
|
+
shares,
|
|
723
|
+
sharesFormatted: Number(_viem.formatUnits.call(void 0, shares, shareDecimals)),
|
|
724
|
+
shareDecimals,
|
|
725
|
+
assets: Number(_viem.formatUnits.call(void 0, assetsRaw, tokenDecimals)),
|
|
726
|
+
tokenDecimals,
|
|
727
|
+
assetAddress: vaultData.asset
|
|
561
728
|
};
|
|
562
729
|
}
|
|
563
|
-
async
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
730
|
+
async getYield() {
|
|
731
|
+
return this._fetchYield();
|
|
732
|
+
}
|
|
733
|
+
async preview(params) {
|
|
734
|
+
const { amount, horizonDays } = params;
|
|
735
|
+
const apy = await this._fetchNetApy();
|
|
736
|
+
return {
|
|
737
|
+
projectedYield: amount * ((1 + apy) ** (horizonDays / 365) - 1),
|
|
738
|
+
apy,
|
|
739
|
+
horizonDays
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
async withdrawability(params) {
|
|
743
|
+
const vault = this.morpho.vaultV2(this.vaultAddr, this.chainId);
|
|
744
|
+
const vaultData = await vault.getData();
|
|
745
|
+
const tokenDecimals = await _asyncNullishCoalesce(params.tokenDecimals, async () => ( await this.pc.readContract({
|
|
746
|
+
address: vaultData.asset,
|
|
747
|
+
abi: _viem.erc20Abi,
|
|
748
|
+
functionName: "decimals"
|
|
580
749
|
})));
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
chainId: srcChainId,
|
|
588
|
-
cause: err
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
await this._ensureApproval(wc, srcChain, srcRpc, srcChainId, from, quote);
|
|
592
|
-
let hash;
|
|
593
|
-
try {
|
|
594
|
-
hash = await wc.sendTransaction({
|
|
595
|
-
to: transactionRequest.to,
|
|
596
|
-
data: transactionRequest.data,
|
|
597
|
-
value: transactionRequest.value && transactionRequest.value !== "0x" ? BigInt(transactionRequest.value) : 0n,
|
|
598
|
-
gas: transactionRequest.gasLimit ? BigInt(transactionRequest.gasLimit) : void 0,
|
|
599
|
-
account: wc.account,
|
|
600
|
-
chain: srcChain
|
|
601
|
-
});
|
|
602
|
-
} catch (err) {
|
|
603
|
-
throw new StableTransactionError({
|
|
604
|
-
phase: "send",
|
|
605
|
-
shortMessage: "LI.FI: swap/bridge transaction failed",
|
|
606
|
-
chainId: srcChainId,
|
|
607
|
-
cause: err
|
|
608
|
-
});
|
|
750
|
+
const requestedRaw = _viem.parseUnits.call(void 0, String(params.amount), tokenDecimals);
|
|
751
|
+
let availableLiquidityRaw = vaultData.assetBalance;
|
|
752
|
+
if (vaultData.accrualLiquidityAdapter != null) {
|
|
753
|
+
availableLiquidityRaw += vaultData.accrualLiquidityAdapter.maxWithdraw(
|
|
754
|
+
vaultData.liquidityData
|
|
755
|
+
).value;
|
|
609
756
|
}
|
|
610
|
-
return {
|
|
757
|
+
return {
|
|
758
|
+
isInstant: requestedRaw <= availableLiquidityRaw,
|
|
759
|
+
availableLiquidity: Number(
|
|
760
|
+
_viem.formatUnits.call(void 0, availableLiquidityRaw, tokenDecimals)
|
|
761
|
+
),
|
|
762
|
+
tokenDecimals
|
|
763
|
+
};
|
|
611
764
|
}
|
|
612
|
-
async
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
address:
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
765
|
+
async _fetchNetApy() {
|
|
766
|
+
const { apy } = await this._fetchYield();
|
|
767
|
+
return apy;
|
|
768
|
+
}
|
|
769
|
+
async _fetchYield() {
|
|
770
|
+
const query = `
|
|
771
|
+
query VaultYield($address: String!, $chainId: Int!) {
|
|
772
|
+
vaultV2ByAddress(address: $address, chainId: $chainId) {
|
|
773
|
+
avgNetApy
|
|
774
|
+
avgNetApyExcludingRewards
|
|
775
|
+
performanceFee
|
|
776
|
+
managementFee
|
|
777
|
+
rewards {
|
|
778
|
+
asset { symbol address }
|
|
779
|
+
supplyApr
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
`;
|
|
784
|
+
let body;
|
|
625
785
|
try {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
786
|
+
body = await retryWithBackoff(
|
|
787
|
+
async () => {
|
|
788
|
+
const res = await fetch(MORPHO_API_URL, {
|
|
789
|
+
method: "POST",
|
|
790
|
+
headers: { "Content-Type": "application/json" },
|
|
791
|
+
body: JSON.stringify({
|
|
792
|
+
query,
|
|
793
|
+
variables: {
|
|
794
|
+
address: this.vaultAddr,
|
|
795
|
+
chainId: this.chainId
|
|
796
|
+
}
|
|
797
|
+
})
|
|
798
|
+
});
|
|
799
|
+
if (!res.ok) {
|
|
800
|
+
const retryable = res.status === 429 || res.status >= 500;
|
|
801
|
+
throw new HttpError(
|
|
802
|
+
`earn: Morpho API returned ${res.status} \u2014 ${res.statusText}`,
|
|
803
|
+
res.status,
|
|
804
|
+
retryable
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
return res.json().catch(() => void 0);
|
|
808
|
+
},
|
|
809
|
+
(err) => err instanceof HttpError ? err.retryable : err instanceof TypeError
|
|
810
|
+
);
|
|
634
811
|
} catch (err) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
812
|
+
if (err instanceof TypeError || err instanceof HttpError) {
|
|
813
|
+
throw new StableNetworkError({
|
|
814
|
+
shortMessage: err instanceof HttpError ? `earn: Morpho API returned ${err.status} after retries` : `earn: failed to reach Morpho API after retries \u2014 ${String(
|
|
815
|
+
err
|
|
816
|
+
)}`,
|
|
817
|
+
url: MORPHO_API_URL,
|
|
818
|
+
cause: err
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
throw err;
|
|
641
822
|
}
|
|
642
|
-
const
|
|
643
|
-
if (
|
|
644
|
-
throw new
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
});
|
|
823
|
+
const vault = _optionalChain([body, 'optionalAccess', _10 => _10.data, 'optionalAccess', _11 => _11.vaultV2ByAddress]);
|
|
824
|
+
if (!vault || typeof vault.avgNetApy !== "number") {
|
|
825
|
+
throw new StableValidationError(
|
|
826
|
+
"apy",
|
|
827
|
+
void 0,
|
|
828
|
+
"earn: vault not found in Morpho API or unexpected response shape"
|
|
829
|
+
);
|
|
650
830
|
}
|
|
831
|
+
const rewards = vault.rewards.filter((r) => typeof r.supplyApr === "number" && r.supplyApr > 0).map((r) => ({
|
|
832
|
+
symbol: r.asset.symbol,
|
|
833
|
+
address: r.asset.address,
|
|
834
|
+
apr: r.supplyApr
|
|
835
|
+
}));
|
|
836
|
+
return {
|
|
837
|
+
apy: vault.avgNetApy,
|
|
838
|
+
native: _nullishCoalesce(vault.avgNetApyExcludingRewards, () => ( 0)),
|
|
839
|
+
rewards,
|
|
840
|
+
performanceFee: vault.performanceFee,
|
|
841
|
+
managementFee: vault.managementFee
|
|
842
|
+
};
|
|
651
843
|
}
|
|
652
844
|
};
|
|
653
|
-
function isObject(v) {
|
|
654
|
-
return typeof v === "object" && v !== null;
|
|
655
|
-
}
|
|
656
|
-
function isLiFiQuoteShape(v) {
|
|
657
|
-
if (!isObject(v)) return false;
|
|
658
|
-
const tx = v.transactionRequest;
|
|
659
|
-
const est = v.estimate;
|
|
660
|
-
const act = v.action;
|
|
661
|
-
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);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// src/earn/morpho.ts
|
|
665
|
-
|
|
666
845
|
|
|
846
|
+
// src/services/lifi.ts
|
|
667
847
|
|
|
668
848
|
|
|
669
849
|
|
|
@@ -674,340 +854,521 @@ function isLiFiQuoteShape(v) {
|
|
|
674
854
|
|
|
675
855
|
|
|
676
856
|
|
|
857
|
+
// src/chains.ts
|
|
858
|
+
var CHAIN_CONFIGS = {
|
|
859
|
+
// ─── Testnet ──────────────────────────────────────────────────────────────
|
|
860
|
+
["sepolia" /* Sepolia */]: {
|
|
861
|
+
id: 11155111,
|
|
862
|
+
rpc: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
863
|
+
usdt: "0xc4DCC311c028e341fd8602D8eB89c5de94625927",
|
|
864
|
+
decimals: 18
|
|
865
|
+
},
|
|
866
|
+
["stable-testnet" /* StableTestnet */]: {
|
|
867
|
+
id: 2201,
|
|
868
|
+
rpc: "https://rpc.testnet.stable.xyz",
|
|
869
|
+
usdt: "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9",
|
|
870
|
+
decimals: 6
|
|
871
|
+
},
|
|
872
|
+
// ─── Mainnet ──────────────────────────────────────────────────────────────
|
|
873
|
+
["stable" /* Stable */]: {
|
|
874
|
+
id: 988,
|
|
875
|
+
rpc: "https://rpc.stable.xyz",
|
|
876
|
+
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
877
|
+
decimals: 6
|
|
878
|
+
},
|
|
879
|
+
["ethereum" /* Ethereum */]: {
|
|
880
|
+
id: 1,
|
|
881
|
+
rpc: "https://ethereum-rpc.publicnode.com",
|
|
882
|
+
usdt: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
883
|
+
decimals: 6
|
|
884
|
+
},
|
|
885
|
+
["arbitrum" /* Arbitrum */]: {
|
|
886
|
+
id: 42161,
|
|
887
|
+
rpc: "https://arbitrum-one-rpc.publicnode.com",
|
|
888
|
+
usdt: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92",
|
|
889
|
+
decimals: 6
|
|
890
|
+
},
|
|
891
|
+
["ink" /* Ink */]: {
|
|
892
|
+
id: 57073,
|
|
893
|
+
rpc: "https://rpc-gel.inkonchain.com",
|
|
894
|
+
usdt: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
895
|
+
decimals: 6
|
|
896
|
+
},
|
|
897
|
+
["bera" /* Bera */]: {
|
|
898
|
+
id: 80094,
|
|
899
|
+
rpc: "https://rpc.berachain.com",
|
|
900
|
+
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
901
|
+
decimals: 6
|
|
902
|
+
},
|
|
903
|
+
["megaeth" /* MegaETH */]: {
|
|
904
|
+
id: 4326,
|
|
905
|
+
rpc: "https://carrot.megaeth.com",
|
|
906
|
+
usdt: "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb",
|
|
907
|
+
decimals: 6
|
|
908
|
+
},
|
|
909
|
+
["base" /* Base */]: {
|
|
910
|
+
id: 8453,
|
|
911
|
+
rpc: "https://mainnet.base.org",
|
|
912
|
+
usdt: "0x102d758f688a4C1C5a80b116bD945d4455460282",
|
|
913
|
+
decimals: 6
|
|
914
|
+
},
|
|
915
|
+
["bsc" /* BSC */]: {
|
|
916
|
+
id: 56,
|
|
917
|
+
rpc: "https://bsc-rpc.publicnode.com",
|
|
918
|
+
usdt: "0x55d398326f99059fF775485246999027B3197955",
|
|
919
|
+
decimals: 18
|
|
920
|
+
},
|
|
921
|
+
["hyperevm" /* HyperEVM */]: {
|
|
922
|
+
id: 999,
|
|
923
|
+
rpc: "https://rpc.hyperliquid.xyz/evm",
|
|
924
|
+
usdt: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
|
|
925
|
+
decimals: 6
|
|
926
|
+
}
|
|
927
|
+
};
|
|
677
928
|
|
|
678
|
-
|
|
929
|
+
// src/services/lifi.ts
|
|
930
|
+
var LiFiService = class {
|
|
679
931
|
constructor(config) {
|
|
680
932
|
this.config = config;
|
|
681
|
-
this.inFlight = /* @__PURE__ */ new Map();
|
|
682
|
-
this.chain = makeChain(config.chainId, config.rpc);
|
|
683
|
-
this.wc = _nullishCoalesce(config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
684
|
-
account: config.account,
|
|
685
|
-
chain: this.chain,
|
|
686
|
-
transport: _nullishCoalesce(config.transport, () => ( _viem.http.call(void 0, config.rpc)))
|
|
687
|
-
})));
|
|
688
|
-
const morphoWc = _optionalChain([this, 'access', _21 => _21.wc, 'access', _22 => _22.chain, 'optionalAccess', _23 => _23.id]) === config.chainId ? this.wc : _viem.createWalletClient.call(void 0, {
|
|
689
|
-
account: this.wc.account,
|
|
690
|
-
chain: this.chain,
|
|
691
|
-
transport: _viem.custom.call(void 0, { request: this.wc.request.bind(this.wc) })
|
|
692
|
-
});
|
|
693
|
-
this.morpho = new (0, _morphosdk.MorphoClient)(morphoWc, { supportSignature: true });
|
|
694
|
-
this.pc = _viem.createPublicClient.call(void 0, { chain: this.chain, transport: _viem.http.call(void 0, config.rpc) });
|
|
695
|
-
}
|
|
696
|
-
deposit(params) {
|
|
697
|
-
this._validateAmount(params.amount, "earn deposit");
|
|
698
|
-
return this._withIdempotency(params.idempotencyKey, () => this._deposit(params));
|
|
699
|
-
}
|
|
700
|
-
async bulkDeposit(items, options = {}) {
|
|
701
|
-
const { idempotencyKey, stopOnError = false } = options;
|
|
702
|
-
const run = async () => {
|
|
703
|
-
const results = [];
|
|
704
|
-
for (const item of items) {
|
|
705
|
-
try {
|
|
706
|
-
const result = await this.deposit(item);
|
|
707
|
-
results.push({ status: "fulfilled", txHash: result.txHash, vault: item.vault, idempotencyKey: item.idempotencyKey });
|
|
708
|
-
} catch (err) {
|
|
709
|
-
const entry = { status: "rejected", error: String(err), vault: item.vault, idempotencyKey: item.idempotencyKey };
|
|
710
|
-
results.push(entry);
|
|
711
|
-
if (stopOnError) {
|
|
712
|
-
const succeeded2 = results.filter((r) => r.status === "fulfilled").length;
|
|
713
|
-
throw new StableBulkError({
|
|
714
|
-
shortMessage: "bulkDeposit halted on first failure",
|
|
715
|
-
results,
|
|
716
|
-
succeeded: succeeded2,
|
|
717
|
-
failed: results.length - succeeded2
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
723
|
-
return { results, succeeded, failed: results.length - succeeded, idempotencyKey };
|
|
724
|
-
};
|
|
725
|
-
return this._withIdempotency(idempotencyKey, run);
|
|
726
933
|
}
|
|
727
|
-
|
|
728
|
-
this.
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
934
|
+
async quoteSwap(params) {
|
|
935
|
+
this._validateSwapParams(params);
|
|
936
|
+
const {
|
|
937
|
+
fromToken,
|
|
938
|
+
toToken,
|
|
939
|
+
amount,
|
|
940
|
+
fromDecimals = 6,
|
|
941
|
+
toAddress
|
|
942
|
+
} = params;
|
|
943
|
+
const from = this._requireFrom();
|
|
944
|
+
const chain = this.config.chainId.toString();
|
|
945
|
+
const fromAmountRaw = _viem.parseUnits.call(void 0,
|
|
946
|
+
String(amount),
|
|
947
|
+
fromDecimals
|
|
948
|
+
).toString();
|
|
949
|
+
const qs = new URLSearchParams({
|
|
950
|
+
fromChain: chain,
|
|
951
|
+
toChain: chain,
|
|
952
|
+
fromToken,
|
|
953
|
+
toToken,
|
|
954
|
+
fromAmount: fromAmountRaw,
|
|
955
|
+
fromAddress: from,
|
|
956
|
+
...toAddress && { toAddress }
|
|
957
|
+
});
|
|
958
|
+
const quote = await this._fetchQuote(qs);
|
|
959
|
+
return this._toSwapQuote(quote);
|
|
734
960
|
}
|
|
735
|
-
|
|
736
|
-
this.
|
|
737
|
-
|
|
961
|
+
async swap(params) {
|
|
962
|
+
this._validateSwapParams(params);
|
|
963
|
+
const quote = await _asyncNullishCoalesce(params.quote, async () => ( await this.quoteSwap(params)));
|
|
964
|
+
return this._executeSwapQuote(
|
|
965
|
+
quote,
|
|
966
|
+
this.config.rpc,
|
|
967
|
+
this.config.chainId
|
|
968
|
+
);
|
|
738
969
|
}
|
|
739
|
-
|
|
740
|
-
this.
|
|
741
|
-
|
|
970
|
+
async quoteBridge(params) {
|
|
971
|
+
this._validateBridgeParams(params);
|
|
972
|
+
const {
|
|
973
|
+
fromChain,
|
|
974
|
+
toChain,
|
|
975
|
+
fromToken,
|
|
976
|
+
toToken,
|
|
977
|
+
amount,
|
|
978
|
+
fromDecimals = 6,
|
|
979
|
+
recipient
|
|
980
|
+
} = params;
|
|
981
|
+
const from = _nullishCoalesce(_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]))), () => ( "0x0000000000000000000000000000000000000000"));
|
|
982
|
+
const src = CHAIN_CONFIGS[fromChain];
|
|
983
|
+
const dst = CHAIN_CONFIGS[toChain];
|
|
984
|
+
if (!src)
|
|
985
|
+
throw new StableValidationError(
|
|
986
|
+
"fromChain",
|
|
987
|
+
fromChain,
|
|
988
|
+
`LI.FI bridge: unsupported source chain "${fromChain}"`
|
|
989
|
+
);
|
|
990
|
+
if (!dst)
|
|
991
|
+
throw new StableValidationError(
|
|
992
|
+
"toChain",
|
|
993
|
+
toChain,
|
|
994
|
+
`LI.FI bridge: unsupported destination chain "${toChain}"`
|
|
995
|
+
);
|
|
996
|
+
const qs = new URLSearchParams({
|
|
997
|
+
fromChain: src.id.toString(),
|
|
998
|
+
toChain: dst.id.toString(),
|
|
999
|
+
fromToken,
|
|
1000
|
+
toToken,
|
|
1001
|
+
fromAmount: _viem.parseUnits.call(void 0, String(amount), fromDecimals).toString(),
|
|
1002
|
+
fromAddress: from,
|
|
1003
|
+
...recipient && { toAddress: recipient }
|
|
1004
|
+
});
|
|
1005
|
+
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
1006
|
+
return { toAmount: quote.toAmount };
|
|
742
1007
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const {
|
|
1008
|
+
async bridge(params) {
|
|
1009
|
+
this._validateBridgeParams(params);
|
|
1010
|
+
const {
|
|
1011
|
+
fromChain,
|
|
1012
|
+
toChain,
|
|
1013
|
+
fromToken,
|
|
1014
|
+
toToken,
|
|
1015
|
+
amount,
|
|
1016
|
+
fromDecimals = 6,
|
|
1017
|
+
recipient
|
|
1018
|
+
} = params;
|
|
746
1019
|
const from = this._requireFrom();
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
userAddress: from,
|
|
756
|
-
accrualVault: vaultData,
|
|
757
|
-
slippageTolerance: slippageTolerance !== void 0 ? _viem.parseUnits.call(void 0, String(slippageTolerance), 18) : void 0
|
|
758
|
-
});
|
|
759
|
-
const requirements = await depositAction.getRequirements();
|
|
760
|
-
let sig;
|
|
761
|
-
for (const req of requirements) {
|
|
762
|
-
if (_morphosdk.isRequirementApproval.call(void 0, req)) {
|
|
763
|
-
const approveHash = await this._sendTx(req.to, req.data, req.value, from);
|
|
764
|
-
const approveReceipt = await this.pc.waitForTransactionReceipt({ hash: approveHash });
|
|
765
|
-
if (approveReceipt.status === "reverted") {
|
|
766
|
-
throw new StableTransactionError({
|
|
767
|
-
phase: "approve",
|
|
768
|
-
shortMessage: "earn deposit: token approval reverted on-chain",
|
|
769
|
-
txHash: approveReceipt.transactionHash,
|
|
770
|
-
chainId: this.config.chainId
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
} else if (_morphosdk.isRequirementSignature.call(void 0, req)) {
|
|
774
|
-
sig = await req.sign(this.wc, from);
|
|
775
|
-
}
|
|
1020
|
+
const src = CHAIN_CONFIGS[fromChain];
|
|
1021
|
+
const dst = CHAIN_CONFIGS[toChain];
|
|
1022
|
+
if (!src) {
|
|
1023
|
+
throw new StableValidationError(
|
|
1024
|
+
"fromChain",
|
|
1025
|
+
fromChain,
|
|
1026
|
+
`LI.FI bridge: unsupported source chain "${fromChain}"`
|
|
1027
|
+
);
|
|
776
1028
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
shortMessage: "earn deposit: vault deposit reverted on-chain",
|
|
784
|
-
txHash: receipt.transactionHash,
|
|
785
|
-
chainId: this.config.chainId
|
|
786
|
-
});
|
|
1029
|
+
if (!dst) {
|
|
1030
|
+
throw new StableValidationError(
|
|
1031
|
+
"toChain",
|
|
1032
|
+
toChain,
|
|
1033
|
+
`LI.FI bridge: unsupported destination chain "${toChain}"`
|
|
1034
|
+
);
|
|
787
1035
|
}
|
|
788
|
-
|
|
1036
|
+
const fromAmountRaw = _viem.parseUnits.call(void 0,
|
|
1037
|
+
String(amount),
|
|
1038
|
+
fromDecimals
|
|
1039
|
+
).toString();
|
|
1040
|
+
const qs = new URLSearchParams({
|
|
1041
|
+
fromChain: src.id.toString(),
|
|
1042
|
+
toChain: dst.id.toString(),
|
|
1043
|
+
fromToken,
|
|
1044
|
+
toToken,
|
|
1045
|
+
fromAmount: fromAmountRaw,
|
|
1046
|
+
fromAddress: from,
|
|
1047
|
+
...recipient && { toAddress: recipient }
|
|
1048
|
+
});
|
|
1049
|
+
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
1050
|
+
return this._executeSwapQuote(quote, src.rpc, src.id);
|
|
789
1051
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
if (receipt.status === "reverted") {
|
|
803
|
-
throw new StableTransactionError({
|
|
804
|
-
phase: "receipt",
|
|
805
|
-
shortMessage: "earn withdraw: vault withdraw reverted on-chain",
|
|
806
|
-
txHash: receipt.transactionHash,
|
|
807
|
-
chainId: this.config.chainId
|
|
808
|
-
});
|
|
1052
|
+
_validateSwapParams({
|
|
1053
|
+
fromToken,
|
|
1054
|
+
toToken,
|
|
1055
|
+
amount,
|
|
1056
|
+
toAddress
|
|
1057
|
+
}) {
|
|
1058
|
+
if (!_viem.isAddress.call(void 0, fromToken)) {
|
|
1059
|
+
throw new StableValidationError(
|
|
1060
|
+
"fromToken",
|
|
1061
|
+
fromToken,
|
|
1062
|
+
`swap: invalid fromToken address "${fromToken}"`
|
|
1063
|
+
);
|
|
809
1064
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
await this._switchChain();
|
|
817
|
-
let decimals = shareDecimals;
|
|
818
|
-
if (decimals === void 0) {
|
|
819
|
-
try {
|
|
820
|
-
decimals = await this.pc.readContract({ address: vaultAddr, abi: _viem.erc20Abi, functionName: "decimals" });
|
|
821
|
-
} catch (e2) {
|
|
822
|
-
throw new StableValidationError(
|
|
823
|
-
"shareDecimals",
|
|
824
|
-
void 0,
|
|
825
|
-
`earn redeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
826
|
-
);
|
|
827
|
-
}
|
|
1065
|
+
if (!_viem.isAddress.call(void 0, toToken)) {
|
|
1066
|
+
throw new StableValidationError(
|
|
1067
|
+
"toToken",
|
|
1068
|
+
toToken,
|
|
1069
|
+
`swap: invalid toToken address "${toToken}"`
|
|
1070
|
+
);
|
|
828
1071
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
throw new StableTransactionError({
|
|
836
|
-
phase: "receipt",
|
|
837
|
-
shortMessage: "earn redeem: vault redeem reverted on-chain",
|
|
838
|
-
txHash: receipt.transactionHash,
|
|
839
|
-
chainId: this.config.chainId
|
|
840
|
-
});
|
|
1072
|
+
if (fromToken.toLowerCase() === toToken.toLowerCase()) {
|
|
1073
|
+
throw new StableValidationError(
|
|
1074
|
+
"toToken",
|
|
1075
|
+
toToken,
|
|
1076
|
+
"swap: fromToken and toToken must differ"
|
|
1077
|
+
);
|
|
841
1078
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
await this._switchChain();
|
|
849
|
-
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
850
|
-
let decimals = tokenDecimals;
|
|
851
|
-
if (decimals === void 0) {
|
|
852
|
-
const vaultData = await vault.getData();
|
|
853
|
-
decimals = await this._resolveDecimals(vaultData.asset, "earn forceWithdraw");
|
|
1079
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1080
|
+
throw new StableValidationError(
|
|
1081
|
+
"amount",
|
|
1082
|
+
amount,
|
|
1083
|
+
"swap: amount must be a finite positive number"
|
|
1084
|
+
);
|
|
854
1085
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
862
|
-
if (receipt.status === "reverted") {
|
|
863
|
-
throw new StableTransactionError({
|
|
864
|
-
phase: "receipt",
|
|
865
|
-
shortMessage: "earn forceWithdraw: vault forceWithdraw reverted on-chain",
|
|
866
|
-
txHash: receipt.transactionHash,
|
|
867
|
-
chainId: this.config.chainId
|
|
868
|
-
});
|
|
1086
|
+
if (toAddress !== void 0 && !_viem.isAddress.call(void 0, toAddress)) {
|
|
1087
|
+
throw new StableValidationError(
|
|
1088
|
+
"toAddress",
|
|
1089
|
+
toAddress,
|
|
1090
|
+
`swap: invalid toAddress "${toAddress}"`
|
|
1091
|
+
);
|
|
869
1092
|
}
|
|
870
|
-
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
871
1093
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
`earn forceRedeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
887
|
-
);
|
|
888
|
-
}
|
|
1094
|
+
_validateBridgeParams({
|
|
1095
|
+
fromChain,
|
|
1096
|
+
toChain,
|
|
1097
|
+
fromToken,
|
|
1098
|
+
toToken,
|
|
1099
|
+
amount,
|
|
1100
|
+
recipient
|
|
1101
|
+
}) {
|
|
1102
|
+
if (!_viem.isAddress.call(void 0, fromToken)) {
|
|
1103
|
+
throw new StableValidationError(
|
|
1104
|
+
"fromToken",
|
|
1105
|
+
fromToken,
|
|
1106
|
+
`LI.FI bridge: invalid fromToken address "${fromToken}"`
|
|
1107
|
+
);
|
|
889
1108
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1109
|
+
if (!_viem.isAddress.call(void 0, toToken)) {
|
|
1110
|
+
throw new StableValidationError(
|
|
1111
|
+
"toToken",
|
|
1112
|
+
toToken,
|
|
1113
|
+
`LI.FI bridge: invalid toToken address "${toToken}"`
|
|
1114
|
+
);
|
|
894
1115
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
if (
|
|
903
|
-
throw new
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1116
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1117
|
+
throw new StableValidationError(
|
|
1118
|
+
"amount",
|
|
1119
|
+
amount,
|
|
1120
|
+
"LI.FI bridge: amount must be a finite positive number"
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
if (recipient !== void 0 && !_viem.isAddress.call(void 0, recipient)) {
|
|
1124
|
+
throw new StableValidationError(
|
|
1125
|
+
"recipient",
|
|
1126
|
+
recipient,
|
|
1127
|
+
`LI.FI bridge: invalid recipient address "${recipient}"`
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
if (fromChain === toChain) {
|
|
1131
|
+
throw new StableValidationError(
|
|
1132
|
+
"toChain",
|
|
1133
|
+
toChain,
|
|
1134
|
+
"LI.FI bridge: fromChain and toChain must differ"
|
|
1135
|
+
);
|
|
909
1136
|
}
|
|
910
|
-
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
911
|
-
}
|
|
912
|
-
_buildDeallocs(deallocations, assetDecimals) {
|
|
913
|
-
return deallocations.map((d) => ({
|
|
914
|
-
adapter: d.adapter,
|
|
915
|
-
amount: _viem.parseUnits.call(void 0, String(d.amount), assetDecimals),
|
|
916
|
-
// MarketParams in the Morpho SDK has computed fields (id, liquidationIncentiveFactor)
|
|
917
|
-
// not present in our user-facing type; the plain object is structurally compatible for ABI encoding.
|
|
918
|
-
...d.marketParams && { marketParams: d.marketParams }
|
|
919
|
-
}));
|
|
920
1137
|
}
|
|
921
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
922
1138
|
_requireFrom() {
|
|
923
|
-
const from = _optionalChain([this, 'access',
|
|
1139
|
+
const from = _nullishCoalesce(_optionalChain([this, 'access', _19 => _19.config, 'access', _20 => _20.walletClient, 'optionalAccess', _21 => _21.account, 'optionalAccess', _22 => _22.address]), () => ( _optionalChain([this, 'access', _23 => _23.config, 'access', _24 => _24.account, 'optionalAccess', _25 => _25.address])));
|
|
924
1140
|
if (!from) {
|
|
925
1141
|
throw new StableValidationError(
|
|
926
1142
|
"signer",
|
|
927
1143
|
void 0,
|
|
928
|
-
"
|
|
1144
|
+
"LI.FI: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
929
1145
|
);
|
|
930
1146
|
}
|
|
931
1147
|
return from;
|
|
932
1148
|
}
|
|
933
|
-
async
|
|
1149
|
+
async _fetchQuote(qs) {
|
|
1150
|
+
const url = `https://li.quest/v1/quote?${qs}`;
|
|
934
1151
|
try {
|
|
935
|
-
await
|
|
1152
|
+
return await retryWithBackoff(() => this._fetchQuoteOnce(url));
|
|
936
1153
|
} catch (err) {
|
|
937
|
-
if (err
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1154
|
+
if (err instanceof StableQuoteError) throw err;
|
|
1155
|
+
if (err instanceof HttpError) {
|
|
1156
|
+
throw new StableQuoteError({
|
|
1157
|
+
provider: "lifi",
|
|
1158
|
+
shortMessage: err.message,
|
|
1159
|
+
httpStatus: err.status,
|
|
942
1160
|
cause: err
|
|
943
1161
|
});
|
|
944
1162
|
}
|
|
1163
|
+
throw new StableNetworkError({
|
|
1164
|
+
shortMessage: "LI.FI: quote request failed before reaching the server",
|
|
1165
|
+
url,
|
|
1166
|
+
cause: err
|
|
1167
|
+
});
|
|
945
1168
|
}
|
|
946
1169
|
}
|
|
947
|
-
async
|
|
1170
|
+
async _fetchQuoteOnce(url) {
|
|
1171
|
+
const res = await fetch(url);
|
|
1172
|
+
const body = await res.json().catch(() => void 0);
|
|
1173
|
+
if (!res.ok) {
|
|
1174
|
+
const providerMessage = isObject(body) && typeof body.message === "string" ? body.message : void 0;
|
|
1175
|
+
const providerCode = isObject(body) && typeof body.code === "string" ? body.code : void 0;
|
|
1176
|
+
if (res.status === 429 || res.status >= 500) {
|
|
1177
|
+
throw new HttpError(
|
|
1178
|
+
`LI.FI: ${_nullishCoalesce(providerMessage, () => ( res.statusText))}`,
|
|
1179
|
+
res.status,
|
|
1180
|
+
true
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
throw new StableQuoteError({
|
|
1184
|
+
provider: "lifi",
|
|
1185
|
+
shortMessage: `LI.FI: ${_nullishCoalesce(providerMessage, () => ( res.statusText))}`,
|
|
1186
|
+
httpStatus: res.status,
|
|
1187
|
+
providerCode,
|
|
1188
|
+
body
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
if (!isLiFiQuoteShape(body)) {
|
|
1192
|
+
throw new StableQuoteError({
|
|
1193
|
+
provider: "lifi",
|
|
1194
|
+
shortMessage: "LI.FI: unexpected quote response shape",
|
|
1195
|
+
httpStatus: res.status,
|
|
1196
|
+
body
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
return body;
|
|
1200
|
+
}
|
|
1201
|
+
_toSwapQuote(quote) {
|
|
1202
|
+
return {
|
|
1203
|
+
toAmount: Number(
|
|
1204
|
+
_viem.formatUnits.call(void 0,
|
|
1205
|
+
BigInt(quote.estimate.toAmount),
|
|
1206
|
+
quote.action.toToken.decimals
|
|
1207
|
+
)
|
|
1208
|
+
),
|
|
1209
|
+
fromAmount: BigInt(quote.estimate.fromAmount),
|
|
1210
|
+
fromToken: quote.action.fromToken.address,
|
|
1211
|
+
approvalAddress: quote.estimate.approvalAddress,
|
|
1212
|
+
transactionRequest: quote.transactionRequest
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
async _executeSwapQuote(quote, srcRpc, expectedChainId) {
|
|
1216
|
+
const from = this._requireFrom();
|
|
1217
|
+
const { transactionRequest } = quote;
|
|
1218
|
+
const srcChainId = transactionRequest.chainId;
|
|
1219
|
+
if (srcChainId !== expectedChainId) {
|
|
1220
|
+
throw new StableQuoteError({
|
|
1221
|
+
provider: "lifi",
|
|
1222
|
+
shortMessage: `LI.FI: returned tx for chain ${srcChainId} but expected ${expectedChainId}`,
|
|
1223
|
+
providerCode: "chain_mismatch",
|
|
1224
|
+
body: quote
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
const srcChain = makeChain(srcChainId, srcRpc);
|
|
1228
|
+
const wc = _nullishCoalesce(this.config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
1229
|
+
account: this.config.account,
|
|
1230
|
+
chain: srcChain,
|
|
1231
|
+
transport: _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, srcRpc)))
|
|
1232
|
+
})));
|
|
948
1233
|
try {
|
|
949
|
-
|
|
1234
|
+
await wc.switchChain({ id: srcChainId });
|
|
950
1235
|
} catch (err) {
|
|
951
1236
|
throw new StableTransactionError({
|
|
952
|
-
phase: "
|
|
953
|
-
shortMessage:
|
|
954
|
-
chainId:
|
|
1237
|
+
phase: "switch_chain",
|
|
1238
|
+
shortMessage: `LI.FI: wallet rejected or failed to switch to chain ${srcChainId}`,
|
|
1239
|
+
chainId: srcChainId,
|
|
955
1240
|
cause: err
|
|
956
1241
|
});
|
|
957
1242
|
}
|
|
958
|
-
|
|
959
|
-
|
|
1243
|
+
await this._ensureApproval(
|
|
1244
|
+
wc,
|
|
1245
|
+
srcChain,
|
|
1246
|
+
srcRpc,
|
|
1247
|
+
srcChainId,
|
|
1248
|
+
from,
|
|
1249
|
+
quote
|
|
1250
|
+
);
|
|
1251
|
+
let hash;
|
|
960
1252
|
try {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1253
|
+
hash = await wc.sendTransaction({
|
|
1254
|
+
to: transactionRequest.to,
|
|
1255
|
+
data: transactionRequest.data,
|
|
1256
|
+
value: transactionRequest.value && transactionRequest.value !== "0x" ? BigInt(transactionRequest.value) : 0n,
|
|
1257
|
+
gas: transactionRequest.gasLimit ? BigInt(transactionRequest.gasLimit) : void 0,
|
|
1258
|
+
account: wc.account,
|
|
1259
|
+
chain: srcChain
|
|
1260
|
+
});
|
|
1261
|
+
} catch (err) {
|
|
1262
|
+
throw new StableTransactionError({
|
|
1263
|
+
phase: "send",
|
|
1264
|
+
shortMessage: "LI.FI: swap/bridge transaction failed",
|
|
1265
|
+
chainId: srcChainId,
|
|
1266
|
+
cause: err
|
|
1267
|
+
});
|
|
970
1268
|
}
|
|
1269
|
+
return { txHash: hash, toAmount: quote.toAmount };
|
|
971
1270
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
if (
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1271
|
+
async _ensureApproval(wc, srcChain, srcRpc, srcChainId, from, quote) {
|
|
1272
|
+
const tokenAddr = quote.fromToken;
|
|
1273
|
+
const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000" || tokenAddr.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
1274
|
+
if (isNative || !quote.approvalAddress) return;
|
|
1275
|
+
const pc = _viem.createPublicClient.call(void 0, {
|
|
1276
|
+
chain: srcChain,
|
|
1277
|
+
transport: _viem.http.call(void 0, srcRpc)
|
|
1278
|
+
});
|
|
1279
|
+
const allowance = await pc.readContract({
|
|
1280
|
+
address: tokenAddr,
|
|
1281
|
+
abi: _viem.erc20Abi,
|
|
1282
|
+
functionName: "allowance",
|
|
1283
|
+
args: [from, quote.approvalAddress]
|
|
1284
|
+
});
|
|
1285
|
+
if (allowance >= quote.fromAmount) return;
|
|
1286
|
+
let approveHash;
|
|
1287
|
+
try {
|
|
1288
|
+
approveHash = await wc.writeContract({
|
|
1289
|
+
address: tokenAddr,
|
|
1290
|
+
abi: _viem.erc20Abi,
|
|
1291
|
+
functionName: "approve",
|
|
1292
|
+
args: [quote.approvalAddress, quote.fromAmount],
|
|
1293
|
+
account: wc.account,
|
|
1294
|
+
chain: srcChain
|
|
1295
|
+
});
|
|
1296
|
+
} catch (err) {
|
|
1297
|
+
throw new StableTransactionError({
|
|
1298
|
+
phase: "approve",
|
|
1299
|
+
shortMessage: "LI.FI: token approve failed",
|
|
1300
|
+
chainId: srcChainId,
|
|
1301
|
+
cause: err
|
|
1302
|
+
});
|
|
983
1303
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1304
|
+
const approveReceipt = await pc.waitForTransactionReceipt({
|
|
1305
|
+
hash: approveHash
|
|
1306
|
+
});
|
|
1307
|
+
if (approveReceipt.status === "reverted") {
|
|
1308
|
+
throw new StableTransactionError({
|
|
1309
|
+
phase: "approve",
|
|
1310
|
+
shortMessage: "LI.FI: token approve reverted on-chain",
|
|
1311
|
+
txHash: approveReceipt.transactionHash,
|
|
1312
|
+
chainId: srcChainId
|
|
1313
|
+
});
|
|
988
1314
|
}
|
|
989
1315
|
}
|
|
990
1316
|
};
|
|
1317
|
+
function isObject(v) {
|
|
1318
|
+
return typeof v === "object" && v !== null;
|
|
1319
|
+
}
|
|
1320
|
+
function isLiFiQuoteShape(v) {
|
|
1321
|
+
if (!isObject(v)) return false;
|
|
1322
|
+
const tx = v.transactionRequest;
|
|
1323
|
+
const est = v.estimate;
|
|
1324
|
+
const act = v.action;
|
|
1325
|
+
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);
|
|
1326
|
+
}
|
|
991
1327
|
|
|
992
1328
|
// src/stable.ts
|
|
993
|
-
function validateTransferParams({
|
|
1329
|
+
function validateTransferParams({
|
|
1330
|
+
to,
|
|
1331
|
+
amount,
|
|
1332
|
+
token,
|
|
1333
|
+
tokenDecimals
|
|
1334
|
+
}) {
|
|
994
1335
|
if (!_viem.isAddress.call(void 0, to)) {
|
|
995
|
-
throw new StableValidationError(
|
|
1336
|
+
throw new StableValidationError(
|
|
1337
|
+
"to",
|
|
1338
|
+
to,
|
|
1339
|
+
`transfer: invalid recipient address "${to}"`
|
|
1340
|
+
);
|
|
996
1341
|
}
|
|
997
1342
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
998
|
-
throw new StableValidationError(
|
|
1343
|
+
throw new StableValidationError(
|
|
1344
|
+
"amount",
|
|
1345
|
+
amount,
|
|
1346
|
+
"transfer: amount must be a finite positive number"
|
|
1347
|
+
);
|
|
999
1348
|
}
|
|
1000
1349
|
if (token !== void 0 && !_viem.isAddress.call(void 0, token)) {
|
|
1001
|
-
throw new StableValidationError(
|
|
1350
|
+
throw new StableValidationError(
|
|
1351
|
+
"token",
|
|
1352
|
+
token,
|
|
1353
|
+
`transfer: invalid token address "${token}"`
|
|
1354
|
+
);
|
|
1002
1355
|
}
|
|
1003
1356
|
if (tokenDecimals !== void 0 && (!Number.isInteger(tokenDecimals) || tokenDecimals < 0 || tokenDecimals > 77)) {
|
|
1004
|
-
throw new StableValidationError(
|
|
1357
|
+
throw new StableValidationError(
|
|
1358
|
+
"tokenDecimals",
|
|
1359
|
+
tokenDecimals,
|
|
1360
|
+
"transfer: tokenDecimals must be an integer between 0 and 77"
|
|
1361
|
+
);
|
|
1005
1362
|
}
|
|
1006
1363
|
}
|
|
1007
1364
|
async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
|
|
1008
1365
|
if (tokenDecimals !== void 0) return tokenDecimals;
|
|
1009
1366
|
try {
|
|
1010
|
-
return await publicClient.readContract({
|
|
1367
|
+
return await publicClient.readContract({
|
|
1368
|
+
address: token,
|
|
1369
|
+
abi: _viem.erc20Abi,
|
|
1370
|
+
functionName: "decimals"
|
|
1371
|
+
});
|
|
1011
1372
|
} catch (e5) {
|
|
1012
1373
|
throw new StableValidationError(
|
|
1013
1374
|
"tokenDecimals",
|
|
@@ -1025,7 +1386,11 @@ async function executeTokenTransfer(wc, publicClient, token, to, amount, tokenDe
|
|
|
1025
1386
|
`transfer: no contract found at "${token}" on chain ${chain.id} \u2014 check the address and network`
|
|
1026
1387
|
);
|
|
1027
1388
|
}
|
|
1028
|
-
const decimals = await resolveTokenDecimals(
|
|
1389
|
+
const decimals = await resolveTokenDecimals(
|
|
1390
|
+
publicClient,
|
|
1391
|
+
token,
|
|
1392
|
+
tokenDecimals
|
|
1393
|
+
);
|
|
1029
1394
|
return wc.writeContract({
|
|
1030
1395
|
address: token,
|
|
1031
1396
|
abi: _viem.erc20Abi,
|
|
@@ -1044,10 +1409,18 @@ function createStable({
|
|
|
1044
1409
|
earn: earnConfig
|
|
1045
1410
|
}) {
|
|
1046
1411
|
const chain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
|
|
1047
|
-
const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(
|
|
1412
|
+
const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(
|
|
1413
|
+
/\/$/,
|
|
1414
|
+
""
|
|
1415
|
+
);
|
|
1048
1416
|
const signerConfig = { account, transport, walletClient };
|
|
1049
1417
|
const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
|
|
1050
|
-
const morpho = earnConfig ? new MorphoService({
|
|
1418
|
+
const morpho = earnConfig ? new MorphoService({
|
|
1419
|
+
...signerConfig,
|
|
1420
|
+
rpc,
|
|
1421
|
+
chainId: chain.id,
|
|
1422
|
+
vault: earnConfig.vault
|
|
1423
|
+
}) : null;
|
|
1051
1424
|
function requireEarn() {
|
|
1052
1425
|
if (!morpho) {
|
|
1053
1426
|
throw new StableValidationError(
|
|
@@ -1061,7 +1434,7 @@ function createStable({
|
|
|
1061
1434
|
async function transfer(params) {
|
|
1062
1435
|
validateTransferParams(params);
|
|
1063
1436
|
const { from, to, amount, token, tokenDecimals } = params;
|
|
1064
|
-
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess',
|
|
1437
|
+
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _26 => _26.account]), () => ( account)), () => ( from));
|
|
1065
1438
|
const wc = _nullishCoalesce(walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
1066
1439
|
account: acc,
|
|
1067
1440
|
chain,
|
|
@@ -1077,8 +1450,29 @@ function createStable({
|
|
|
1077
1450
|
cause: err
|
|
1078
1451
|
});
|
|
1079
1452
|
}
|
|
1080
|
-
const publicClient = _viem.createPublicClient.call(void 0, {
|
|
1081
|
-
|
|
1453
|
+
const publicClient = _viem.createPublicClient.call(void 0, {
|
|
1454
|
+
chain,
|
|
1455
|
+
transport: _viem.http.call(void 0, rpc)
|
|
1456
|
+
});
|
|
1457
|
+
const hash = token !== void 0 ? await executeTokenTransfer(
|
|
1458
|
+
wc,
|
|
1459
|
+
publicClient,
|
|
1460
|
+
token,
|
|
1461
|
+
to,
|
|
1462
|
+
amount,
|
|
1463
|
+
tokenDecimals,
|
|
1464
|
+
acc,
|
|
1465
|
+
chain
|
|
1466
|
+
) : (
|
|
1467
|
+
// Native transfer on Stable mainnet/testnet. Native USDT0 uses 18 decimals
|
|
1468
|
+
// on both networks, matching standard EVM native-token decimals.
|
|
1469
|
+
await wc.sendTransaction({
|
|
1470
|
+
account: acc,
|
|
1471
|
+
chain,
|
|
1472
|
+
to,
|
|
1473
|
+
value: _viem.parseUnits.call(void 0, String(amount), 18)
|
|
1474
|
+
})
|
|
1475
|
+
);
|
|
1082
1476
|
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
1083
1477
|
if (receipt.status === "reverted") {
|
|
1084
1478
|
throw new StableTransactionError({
|
|
@@ -1100,6 +1494,7 @@ function createStable({
|
|
|
1100
1494
|
deposit: (params) => requireEarn().deposit(params),
|
|
1101
1495
|
bulkDeposit: (items, options) => requireEarn().bulkDeposit(items, options),
|
|
1102
1496
|
withdraw: (params) => requireEarn().withdraw(params),
|
|
1497
|
+
bulkWithdraw: (items, options) => requireEarn().bulkWithdraw(items, options),
|
|
1103
1498
|
redeem: (params) => requireEarn().redeem(params),
|
|
1104
1499
|
forceWithdraw: (params) => requireEarn().forceWithdraw(params),
|
|
1105
1500
|
forceRedeem: (params) => requireEarn().forceRedeem(params)
|
|
@@ -1110,6 +1505,7 @@ function createStableReader(config) {
|
|
|
1110
1505
|
const reader = new EarnReader(config);
|
|
1111
1506
|
return {
|
|
1112
1507
|
earn: {
|
|
1508
|
+
getYield: () => reader.getYield(),
|
|
1113
1509
|
position: () => reader.position(),
|
|
1114
1510
|
preview: (params) => reader.preview(params),
|
|
1115
1511
|
withdrawability: (params) => reader.withdrawability(params)
|