@stablechain/sdk 0.1.1 → 1.0.1
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 +612 -26
- package/dist/index.d.cts +225 -2
- package/dist/index.d.ts +225 -2
- package/dist/index.js +638 -52
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
// src/stable.ts
|
|
2
2
|
import {
|
|
3
|
-
createPublicClient as
|
|
4
|
-
createWalletClient as
|
|
5
|
-
erc20Abi as
|
|
6
|
-
http as
|
|
7
|
-
isAddress as
|
|
8
|
-
parseUnits as
|
|
3
|
+
createPublicClient as createPublicClient4,
|
|
4
|
+
createWalletClient as createWalletClient3,
|
|
5
|
+
erc20Abi as erc20Abi4,
|
|
6
|
+
http as http4,
|
|
7
|
+
isAddress as isAddress3,
|
|
8
|
+
parseUnits as parseUnits4
|
|
9
|
+
} from "viem";
|
|
10
|
+
import { stable as stableMainnet2, stableTestnet as stableTestnet2 } from "viem/chains";
|
|
11
|
+
|
|
12
|
+
// src/earn/reader.ts
|
|
13
|
+
import {
|
|
14
|
+
createPublicClient,
|
|
15
|
+
erc20Abi,
|
|
16
|
+
formatUnits,
|
|
17
|
+
http,
|
|
18
|
+
isAddress,
|
|
19
|
+
parseUnits
|
|
9
20
|
} from "viem";
|
|
10
21
|
import { stable as stableMainnet, stableTestnet } from "viem/chains";
|
|
22
|
+
import { MorphoClient } from "@morpho-org/morpho-sdk";
|
|
11
23
|
|
|
12
24
|
// src/errors.ts
|
|
13
25
|
import { BaseError } from "viem";
|
|
@@ -67,6 +79,14 @@ var StableNetworkError = class extends StableError {
|
|
|
67
79
|
this.url = args.url;
|
|
68
80
|
}
|
|
69
81
|
};
|
|
82
|
+
var StableBulkError = class extends StableError {
|
|
83
|
+
constructor(args) {
|
|
84
|
+
super(args.shortMessage, { name: "StableBulkError" });
|
|
85
|
+
this.results = args.results;
|
|
86
|
+
this.succeeded = args.succeeded;
|
|
87
|
+
this.failed = args.failed;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
70
90
|
function formatValue(v) {
|
|
71
91
|
if (typeof v === "bigint") return `${v}n`;
|
|
72
92
|
if (typeof v === "string") return JSON.stringify(v);
|
|
@@ -79,17 +99,6 @@ function formatValue(v) {
|
|
|
79
99
|
}
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
// src/services/lifi.ts
|
|
83
|
-
import {
|
|
84
|
-
createPublicClient,
|
|
85
|
-
createWalletClient,
|
|
86
|
-
erc20Abi,
|
|
87
|
-
formatUnits,
|
|
88
|
-
http,
|
|
89
|
-
isAddress,
|
|
90
|
-
parseUnits
|
|
91
|
-
} from "viem";
|
|
92
|
-
|
|
93
102
|
// src/helpers/chain.ts
|
|
94
103
|
import { defineChain } from "viem";
|
|
95
104
|
function makeChain(id, rpc) {
|
|
@@ -154,6 +163,177 @@ var Network = /* @__PURE__ */ ((Network2) => {
|
|
|
154
163
|
return Network2;
|
|
155
164
|
})(Network || {});
|
|
156
165
|
|
|
166
|
+
// src/earn/reader.ts
|
|
167
|
+
var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
|
|
168
|
+
var EarnReader = class {
|
|
169
|
+
constructor(config) {
|
|
170
|
+
if (!config.earn?.vault) {
|
|
171
|
+
throw new StableValidationError(
|
|
172
|
+
"earn.vault",
|
|
173
|
+
void 0,
|
|
174
|
+
"createStableReader: earn.vault is required"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (!isAddress(config.address)) {
|
|
178
|
+
throw new StableValidationError(
|
|
179
|
+
"address",
|
|
180
|
+
config.address,
|
|
181
|
+
`createStableReader: invalid user address "${config.address}"`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (!isAddress(config.earn.vault)) {
|
|
185
|
+
throw new StableValidationError(
|
|
186
|
+
"earn.vault",
|
|
187
|
+
config.earn.vault,
|
|
188
|
+
`createStableReader: invalid vault address "${config.earn.vault}"`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const network = config.network ?? "mainnet" /* Mainnet */;
|
|
192
|
+
const viemChain = network === "mainnet" /* Mainnet */ ? stableMainnet : stableTestnet;
|
|
193
|
+
const rpc = (config.rpc ?? viemChain.rpcUrls.default.http[0]).replace(/\/$/, "");
|
|
194
|
+
this.chainId = viemChain.id;
|
|
195
|
+
this.vaultAddr = config.earn.vault;
|
|
196
|
+
this.userAddr = config.address;
|
|
197
|
+
const chain = makeChain(viemChain.id, rpc);
|
|
198
|
+
this.pc = createPublicClient({ chain, transport: http(rpc) });
|
|
199
|
+
this.morpho = new MorphoClient(this.pc);
|
|
200
|
+
}
|
|
201
|
+
async position() {
|
|
202
|
+
const vault = this.morpho.vaultV2(this.vaultAddr, this.chainId);
|
|
203
|
+
const [vaultData, shares] = await Promise.all([
|
|
204
|
+
vault.getData(),
|
|
205
|
+
this.pc.readContract({
|
|
206
|
+
address: this.vaultAddr,
|
|
207
|
+
abi: erc20Abi,
|
|
208
|
+
functionName: "balanceOf",
|
|
209
|
+
args: [this.userAddr]
|
|
210
|
+
})
|
|
211
|
+
]);
|
|
212
|
+
const [tokenDecimals, shareDecimals] = await Promise.all([
|
|
213
|
+
this.pc.readContract({ address: vaultData.asset, abi: erc20Abi, functionName: "decimals" }),
|
|
214
|
+
this.pc.readContract({ address: this.vaultAddr, abi: erc20Abi, functionName: "decimals" })
|
|
215
|
+
]);
|
|
216
|
+
const assetsRaw = vaultData.toAssets(shares);
|
|
217
|
+
return {
|
|
218
|
+
shares,
|
|
219
|
+
sharesFormatted: Number(formatUnits(shares, shareDecimals)),
|
|
220
|
+
shareDecimals,
|
|
221
|
+
assets: Number(formatUnits(assetsRaw, tokenDecimals)),
|
|
222
|
+
tokenDecimals,
|
|
223
|
+
assetAddress: vaultData.asset
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async getYield() {
|
|
227
|
+
return this._fetchYield();
|
|
228
|
+
}
|
|
229
|
+
async preview(params) {
|
|
230
|
+
const { amount, horizonDays } = params;
|
|
231
|
+
const apy = await this._fetchNetApy();
|
|
232
|
+
return {
|
|
233
|
+
projectedYield: amount * apy * (horizonDays / 365),
|
|
234
|
+
apy,
|
|
235
|
+
horizonDays
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async withdrawability(params) {
|
|
239
|
+
const vault = this.morpho.vaultV2(this.vaultAddr, this.chainId);
|
|
240
|
+
const vaultData = await vault.getData();
|
|
241
|
+
const tokenDecimals = params.tokenDecimals ?? await this.pc.readContract({
|
|
242
|
+
address: vaultData.asset,
|
|
243
|
+
abi: erc20Abi,
|
|
244
|
+
functionName: "decimals"
|
|
245
|
+
});
|
|
246
|
+
const requestedRaw = parseUnits(String(params.amount), tokenDecimals);
|
|
247
|
+
const availableLiquidityRaw = vaultData.assetBalance;
|
|
248
|
+
return {
|
|
249
|
+
isInstant: requestedRaw <= availableLiquidityRaw,
|
|
250
|
+
availableLiquidity: Number(formatUnits(availableLiquidityRaw, tokenDecimals)),
|
|
251
|
+
tokenDecimals
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
async _fetchNetApy() {
|
|
255
|
+
const { apy } = await this._fetchYield();
|
|
256
|
+
return apy;
|
|
257
|
+
}
|
|
258
|
+
async _fetchYield() {
|
|
259
|
+
const query = `
|
|
260
|
+
query VaultYield($address: String!, $chainId: Int!) {
|
|
261
|
+
vaultV2ByAddress(address: $address, chainId: $chainId) {
|
|
262
|
+
netApy
|
|
263
|
+
netApyExcludingRewards
|
|
264
|
+
performanceFee
|
|
265
|
+
managementFee
|
|
266
|
+
rewards {
|
|
267
|
+
asset { symbol address }
|
|
268
|
+
supplyApr
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
`;
|
|
273
|
+
let body;
|
|
274
|
+
try {
|
|
275
|
+
body = await retryWithBackoff(
|
|
276
|
+
async () => {
|
|
277
|
+
const res = await fetch(MORPHO_API_URL, {
|
|
278
|
+
method: "POST",
|
|
279
|
+
headers: { "Content-Type": "application/json" },
|
|
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
|
|
296
|
+
);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
if (err instanceof TypeError || err instanceof HttpError) {
|
|
299
|
+
throw new StableNetworkError({
|
|
300
|
+
shortMessage: err instanceof HttpError ? `earn: Morpho API returned ${err.status} after retries` : `earn: failed to reach Morpho API after retries \u2014 ${String(err)}`,
|
|
301
|
+
url: MORPHO_API_URL,
|
|
302
|
+
cause: err
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
307
|
+
const vault = body?.data?.vaultV2ByAddress;
|
|
308
|
+
if (!vault || typeof vault.netApy !== "number") {
|
|
309
|
+
throw new StableValidationError(
|
|
310
|
+
"apy",
|
|
311
|
+
void 0,
|
|
312
|
+
"earn: vault not found in Morpho API or unexpected response shape"
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const rewards = vault.rewards.filter((r) => typeof r.supplyApr === "number" && r.supplyApr > 0).map((r) => ({ symbol: r.asset.symbol, address: r.asset.address, apr: r.supplyApr }));
|
|
316
|
+
return {
|
|
317
|
+
apy: vault.netApy,
|
|
318
|
+
native: vault.netApyExcludingRewards ?? 0,
|
|
319
|
+
rewards,
|
|
320
|
+
performanceFee: vault.performanceFee,
|
|
321
|
+
managementFee: vault.managementFee
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// src/services/lifi.ts
|
|
327
|
+
import {
|
|
328
|
+
createPublicClient as createPublicClient2,
|
|
329
|
+
createWalletClient,
|
|
330
|
+
erc20Abi as erc20Abi2,
|
|
331
|
+
formatUnits as formatUnits2,
|
|
332
|
+
http as http2,
|
|
333
|
+
isAddress as isAddress2,
|
|
334
|
+
parseUnits as parseUnits2
|
|
335
|
+
} from "viem";
|
|
336
|
+
|
|
157
337
|
// src/chains.ts
|
|
158
338
|
var CHAIN_CONFIGS = {
|
|
159
339
|
// ─── Testnet ──────────────────────────────────────────────────────────────
|
|
@@ -236,7 +416,7 @@ var LiFiService = class {
|
|
|
236
416
|
const { fromToken, toToken, amount, fromDecimals = 6, toAddress } = params;
|
|
237
417
|
const from = this._requireFrom();
|
|
238
418
|
const chain = this.config.chainId.toString();
|
|
239
|
-
const fromAmountRaw =
|
|
419
|
+
const fromAmountRaw = parseUnits2(String(amount), fromDecimals).toString();
|
|
240
420
|
const qs = new URLSearchParams({
|
|
241
421
|
fromChain: chain,
|
|
242
422
|
toChain: chain,
|
|
@@ -267,7 +447,7 @@ var LiFiService = class {
|
|
|
267
447
|
toChain: dst.id.toString(),
|
|
268
448
|
fromToken,
|
|
269
449
|
toToken,
|
|
270
|
-
fromAmount:
|
|
450
|
+
fromAmount: parseUnits2(String(amount), fromDecimals).toString(),
|
|
271
451
|
fromAddress: from,
|
|
272
452
|
...recipient && { toAddress: recipient }
|
|
273
453
|
});
|
|
@@ -286,7 +466,7 @@ var LiFiService = class {
|
|
|
286
466
|
if (!dst) {
|
|
287
467
|
throw new StableValidationError("toChain", toChain, `LI.FI bridge: unsupported destination chain "${toChain}"`);
|
|
288
468
|
}
|
|
289
|
-
const fromAmountRaw =
|
|
469
|
+
const fromAmountRaw = parseUnits2(String(amount), fromDecimals).toString();
|
|
290
470
|
const qs = new URLSearchParams({
|
|
291
471
|
fromChain: src.id.toString(),
|
|
292
472
|
toChain: dst.id.toString(),
|
|
@@ -300,10 +480,10 @@ var LiFiService = class {
|
|
|
300
480
|
return this._executeSwapQuote(quote, src.rpc, src.id);
|
|
301
481
|
}
|
|
302
482
|
_validateSwapParams({ fromToken, toToken, amount, toAddress }) {
|
|
303
|
-
if (!
|
|
483
|
+
if (!isAddress2(fromToken)) {
|
|
304
484
|
throw new StableValidationError("fromToken", fromToken, `swap: invalid fromToken address "${fromToken}"`);
|
|
305
485
|
}
|
|
306
|
-
if (!
|
|
486
|
+
if (!isAddress2(toToken)) {
|
|
307
487
|
throw new StableValidationError("toToken", toToken, `swap: invalid toToken address "${toToken}"`);
|
|
308
488
|
}
|
|
309
489
|
if (fromToken.toLowerCase() === toToken.toLowerCase()) {
|
|
@@ -312,21 +492,21 @@ var LiFiService = class {
|
|
|
312
492
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
313
493
|
throw new StableValidationError("amount", amount, "swap: amount must be a finite positive number");
|
|
314
494
|
}
|
|
315
|
-
if (toAddress !== void 0 && !
|
|
495
|
+
if (toAddress !== void 0 && !isAddress2(toAddress)) {
|
|
316
496
|
throw new StableValidationError("toAddress", toAddress, `swap: invalid toAddress "${toAddress}"`);
|
|
317
497
|
}
|
|
318
498
|
}
|
|
319
499
|
_validateBridgeParams({ fromChain, toChain, fromToken, toToken, amount, recipient }) {
|
|
320
|
-
if (!
|
|
500
|
+
if (!isAddress2(fromToken)) {
|
|
321
501
|
throw new StableValidationError("fromToken", fromToken, `LI.FI bridge: invalid fromToken address "${fromToken}"`);
|
|
322
502
|
}
|
|
323
|
-
if (!
|
|
503
|
+
if (!isAddress2(toToken)) {
|
|
324
504
|
throw new StableValidationError("toToken", toToken, `LI.FI bridge: invalid toToken address "${toToken}"`);
|
|
325
505
|
}
|
|
326
506
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
327
507
|
throw new StableValidationError("amount", amount, "LI.FI bridge: amount must be a finite positive number");
|
|
328
508
|
}
|
|
329
|
-
if (recipient !== void 0 && !
|
|
509
|
+
if (recipient !== void 0 && !isAddress2(recipient)) {
|
|
330
510
|
throw new StableValidationError("recipient", recipient, `LI.FI bridge: invalid recipient address "${recipient}"`);
|
|
331
511
|
}
|
|
332
512
|
if (fromChain === toChain) {
|
|
@@ -394,7 +574,7 @@ var LiFiService = class {
|
|
|
394
574
|
}
|
|
395
575
|
_toSwapQuote(quote) {
|
|
396
576
|
return {
|
|
397
|
-
toAmount: Number(
|
|
577
|
+
toAmount: Number(formatUnits2(BigInt(quote.estimate.toAmount), quote.action.toToken.decimals)),
|
|
398
578
|
fromAmount: BigInt(quote.estimate.fromAmount),
|
|
399
579
|
fromToken: quote.action.fromToken.address,
|
|
400
580
|
approvalAddress: quote.estimate.approvalAddress,
|
|
@@ -417,7 +597,7 @@ var LiFiService = class {
|
|
|
417
597
|
const wc = this.config.walletClient ?? createWalletClient({
|
|
418
598
|
account: this.config.account,
|
|
419
599
|
chain: srcChain,
|
|
420
|
-
transport: this.config.transport ??
|
|
600
|
+
transport: this.config.transport ?? http2(srcRpc)
|
|
421
601
|
});
|
|
422
602
|
try {
|
|
423
603
|
await wc.switchChain({ id: srcChainId });
|
|
@@ -454,10 +634,10 @@ var LiFiService = class {
|
|
|
454
634
|
const tokenAddr = quote.fromToken;
|
|
455
635
|
const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000" || tokenAddr.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
456
636
|
if (isNative || !quote.approvalAddress) return;
|
|
457
|
-
const pc =
|
|
637
|
+
const pc = createPublicClient2({ chain: srcChain, transport: http2(srcRpc) });
|
|
458
638
|
const allowance = await pc.readContract({
|
|
459
639
|
address: tokenAddr,
|
|
460
|
-
abi:
|
|
640
|
+
abi: erc20Abi2,
|
|
461
641
|
functionName: "allowance",
|
|
462
642
|
args: [from, quote.approvalAddress]
|
|
463
643
|
});
|
|
@@ -466,7 +646,7 @@ var LiFiService = class {
|
|
|
466
646
|
try {
|
|
467
647
|
approveHash = await wc.writeContract({
|
|
468
648
|
address: tokenAddr,
|
|
469
|
-
abi:
|
|
649
|
+
abi: erc20Abi2,
|
|
470
650
|
functionName: "approve",
|
|
471
651
|
args: [quote.approvalAddress, quote.fromAmount],
|
|
472
652
|
account: wc.account,
|
|
@@ -502,15 +682,387 @@ function isLiFiQuoteShape(v) {
|
|
|
502
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);
|
|
503
683
|
}
|
|
504
684
|
|
|
685
|
+
// src/earn/morpho.ts
|
|
686
|
+
import {
|
|
687
|
+
createPublicClient as createPublicClient3,
|
|
688
|
+
createWalletClient as createWalletClient2,
|
|
689
|
+
custom,
|
|
690
|
+
erc20Abi as erc20Abi3,
|
|
691
|
+
formatUnits as formatUnits3,
|
|
692
|
+
http as http3,
|
|
693
|
+
parseUnits as parseUnits3
|
|
694
|
+
} from "viem";
|
|
695
|
+
import {
|
|
696
|
+
MorphoClient as MorphoClient2,
|
|
697
|
+
isRequirementApproval,
|
|
698
|
+
isRequirementSignature
|
|
699
|
+
} from "@morpho-org/morpho-sdk";
|
|
700
|
+
var MorphoService = class {
|
|
701
|
+
constructor(config) {
|
|
702
|
+
this.config = config;
|
|
703
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
704
|
+
this.chain = makeChain(config.chainId, config.rpc);
|
|
705
|
+
this.wc = config.walletClient ?? createWalletClient2({
|
|
706
|
+
account: config.account,
|
|
707
|
+
chain: this.chain,
|
|
708
|
+
transport: config.transport ?? http3(config.rpc)
|
|
709
|
+
});
|
|
710
|
+
const morphoWc = this.wc.chain?.id === config.chainId ? this.wc : createWalletClient2({
|
|
711
|
+
account: this.wc.account,
|
|
712
|
+
chain: this.chain,
|
|
713
|
+
transport: custom({ request: this.wc.request.bind(this.wc) })
|
|
714
|
+
});
|
|
715
|
+
this.morpho = new MorphoClient2(morphoWc, { supportSignature: true });
|
|
716
|
+
this.pc = createPublicClient3({ chain: this.chain, transport: http3(config.rpc) });
|
|
717
|
+
}
|
|
718
|
+
deposit(params) {
|
|
719
|
+
this._validateAmount(params.amount, "earn deposit");
|
|
720
|
+
if (params.depositBuffer !== void 0 && (params.depositBuffer <= 0 || params.depositBuffer > 1)) {
|
|
721
|
+
throw new StableValidationError("depositBuffer", params.depositBuffer, "earn deposit: depositBuffer must be > 0 and \u2264 1");
|
|
722
|
+
}
|
|
723
|
+
return this._withIdempotency(params.idempotencyKey, () => this._deposit(params));
|
|
724
|
+
}
|
|
725
|
+
async bulkDeposit(items, options = {}) {
|
|
726
|
+
const { idempotencyKey, stopOnError = false } = options;
|
|
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));
|
|
786
|
+
}
|
|
787
|
+
forceWithdraw(params) {
|
|
788
|
+
this._validateAmount(params.amount, "earn forceWithdraw");
|
|
789
|
+
return this._withIdempotency(params.idempotencyKey, () => this._forceWithdraw(params));
|
|
790
|
+
}
|
|
791
|
+
forceRedeem(params) {
|
|
792
|
+
this._validateShares(params.shares, "earn forceRedeem");
|
|
793
|
+
return this._withIdempotency(params.idempotencyKey, () => this._forceRedeem(params));
|
|
794
|
+
}
|
|
795
|
+
// ─── Private operations ──────────────────────────────────────────────────
|
|
796
|
+
async _deposit(params) {
|
|
797
|
+
const { amount, tokenDecimals, slippageTolerance, idempotencyKey, depositBuffer } = params;
|
|
798
|
+
const from = this._requireFrom();
|
|
799
|
+
const vaultAddr = 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 = tokenDecimals ?? await this._resolveDecimals(vaultData.asset, "earn deposit");
|
|
804
|
+
const assetsRaw = parseUnits3(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(formatUnits3(maxAllowed, decimals));
|
|
810
|
+
const idleFormatted = Number(formatUnits3(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 ? parseUnits3(String(slippageTolerance), 18) : void 0
|
|
823
|
+
});
|
|
824
|
+
const requirements = await depositAction.getRequirements();
|
|
825
|
+
let sig;
|
|
826
|
+
for (const req of requirements) {
|
|
827
|
+
if (isRequirementApproval(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 (isRequirementSignature(req)) {
|
|
839
|
+
sig = await req.sign(this.wc, from);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const tx = depositAction.buildTx(sig);
|
|
843
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
844
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
845
|
+
if (receipt.status === "reverted") {
|
|
846
|
+
throw new StableTransactionError({
|
|
847
|
+
phase: "receipt",
|
|
848
|
+
shortMessage: "earn deposit: vault deposit reverted on-chain",
|
|
849
|
+
txHash: receipt.transactionHash,
|
|
850
|
+
chainId: this.config.chainId
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
854
|
+
}
|
|
855
|
+
async _withdraw(params) {
|
|
856
|
+
const { amount, tokenDecimals, idempotencyKey } = params;
|
|
857
|
+
const from = this._requireFrom();
|
|
858
|
+
const vaultAddr = this.config.vault;
|
|
859
|
+
await this._switchChain();
|
|
860
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
861
|
+
const vaultData = await vault.getData();
|
|
862
|
+
const decimals = tokenDecimals ?? await this._resolveDecimals(vaultData.asset, "earn withdraw");
|
|
863
|
+
const assetsRaw = parseUnits3(String(amount), decimals);
|
|
864
|
+
const tx = vault.withdraw({ amount: assetsRaw, userAddress: from }).buildTx();
|
|
865
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
866
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
867
|
+
if (receipt.status === "reverted") {
|
|
868
|
+
throw new StableTransactionError({
|
|
869
|
+
phase: "receipt",
|
|
870
|
+
shortMessage: "earn withdraw: vault withdraw reverted on-chain",
|
|
871
|
+
txHash: receipt.transactionHash,
|
|
872
|
+
chainId: this.config.chainId
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
876
|
+
}
|
|
877
|
+
async _redeem(params) {
|
|
878
|
+
const { shares, shareDecimals, idempotencyKey } = params;
|
|
879
|
+
const from = this._requireFrom();
|
|
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: erc20Abi3, functionName: "decimals" });
|
|
886
|
+
} catch {
|
|
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 = parseUnits3(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 });
|
|
899
|
+
if (receipt.status === "reverted") {
|
|
900
|
+
throw new StableTransactionError({
|
|
901
|
+
phase: "receipt",
|
|
902
|
+
shortMessage: "earn redeem: vault redeem reverted on-chain",
|
|
903
|
+
txHash: receipt.transactionHash,
|
|
904
|
+
chainId: this.config.chainId
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
908
|
+
}
|
|
909
|
+
async _forceWithdraw(params) {
|
|
910
|
+
const { amount, tokenDecimals, deallocations, idempotencyKey } = params;
|
|
911
|
+
const from = this._requireFrom();
|
|
912
|
+
const vaultAddr = this.config.vault;
|
|
913
|
+
await this._switchChain();
|
|
914
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
915
|
+
let decimals = tokenDecimals;
|
|
916
|
+
if (decimals === void 0) {
|
|
917
|
+
const vaultData = await vault.getData();
|
|
918
|
+
decimals = await this._resolveDecimals(vaultData.asset, "earn forceWithdraw");
|
|
919
|
+
}
|
|
920
|
+
const tx = vault.forceWithdraw({
|
|
921
|
+
deallocations: this._buildDeallocs(deallocations, decimals),
|
|
922
|
+
withdraw: { amount: parseUnits3(String(amount), decimals) },
|
|
923
|
+
userAddress: from
|
|
924
|
+
}).buildTx();
|
|
925
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
926
|
+
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
927
|
+
if (receipt.status === "reverted") {
|
|
928
|
+
throw new StableTransactionError({
|
|
929
|
+
phase: "receipt",
|
|
930
|
+
shortMessage: "earn forceWithdraw: vault forceWithdraw reverted on-chain",
|
|
931
|
+
txHash: receipt.transactionHash,
|
|
932
|
+
chainId: this.config.chainId
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
936
|
+
}
|
|
937
|
+
async _forceRedeem(params) {
|
|
938
|
+
const { shares, shareDecimals, tokenDecimals, deallocations, idempotencyKey } = params;
|
|
939
|
+
const from = this._requireFrom();
|
|
940
|
+
const vaultAddr = this.config.vault;
|
|
941
|
+
await this._switchChain();
|
|
942
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
943
|
+
let shareDec = shareDecimals;
|
|
944
|
+
if (shareDec === void 0) {
|
|
945
|
+
try {
|
|
946
|
+
shareDec = await this.pc.readContract({ address: vaultAddr, abi: erc20Abi3, functionName: "decimals" });
|
|
947
|
+
} catch {
|
|
948
|
+
throw new StableValidationError(
|
|
949
|
+
"shareDecimals",
|
|
950
|
+
void 0,
|
|
951
|
+
`earn forceRedeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
let assetDec = tokenDecimals;
|
|
956
|
+
if (assetDec === void 0) {
|
|
957
|
+
const vaultData = await vault.getData();
|
|
958
|
+
assetDec = await this._resolveDecimals(vaultData.asset, "earn forceRedeem");
|
|
959
|
+
}
|
|
960
|
+
const tx = vault.forceRedeem({
|
|
961
|
+
deallocations: this._buildDeallocs(deallocations, assetDec),
|
|
962
|
+
redeem: { shares: parseUnits3(String(shares), shareDec) },
|
|
963
|
+
userAddress: from
|
|
964
|
+
}).buildTx();
|
|
965
|
+
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
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
|
+
});
|
|
974
|
+
}
|
|
975
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
976
|
+
}
|
|
977
|
+
_buildDeallocs(deallocations, assetDecimals) {
|
|
978
|
+
return deallocations.map((d) => ({
|
|
979
|
+
adapter: d.adapter,
|
|
980
|
+
amount: parseUnits3(String(d.amount), assetDecimals),
|
|
981
|
+
// MarketParams in the Morpho SDK has computed fields (id, liquidationIncentiveFactor)
|
|
982
|
+
// not present in our user-facing type; the plain object is structurally compatible for ABI encoding.
|
|
983
|
+
...d.marketParams && { marketParams: d.marketParams }
|
|
984
|
+
}));
|
|
985
|
+
}
|
|
986
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
987
|
+
_requireFrom() {
|
|
988
|
+
const from = this.wc.account?.address;
|
|
989
|
+
if (!from) {
|
|
990
|
+
throw new StableValidationError(
|
|
991
|
+
"signer",
|
|
992
|
+
void 0,
|
|
993
|
+
"earn: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
return from;
|
|
997
|
+
}
|
|
998
|
+
async _switchChain() {
|
|
999
|
+
try {
|
|
1000
|
+
await this.wc.switchChain({ id: this.config.chainId });
|
|
1001
|
+
} catch (err) {
|
|
1002
|
+
if (err.code === 4001) {
|
|
1003
|
+
throw new StableTransactionError({
|
|
1004
|
+
phase: "switch_chain",
|
|
1005
|
+
shortMessage: `earn: wallet rejected switch to chain ${this.config.chainId}`,
|
|
1006
|
+
chainId: this.config.chainId,
|
|
1007
|
+
cause: err
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
async _sendTx(to, data, value, from) {
|
|
1013
|
+
try {
|
|
1014
|
+
return await this.wc.sendTransaction({ to, data, value, account: from, chain: this.chain });
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
throw new StableTransactionError({
|
|
1017
|
+
phase: "send",
|
|
1018
|
+
shortMessage: "earn: transaction send failed",
|
|
1019
|
+
chainId: this.config.chainId,
|
|
1020
|
+
cause: err
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
async _resolveDecimals(asset, op) {
|
|
1025
|
+
try {
|
|
1026
|
+
return await retryWithBackoff(
|
|
1027
|
+
() => this.pc.readContract({ address: asset, abi: erc20Abi3, functionName: "decimals" })
|
|
1028
|
+
);
|
|
1029
|
+
} catch {
|
|
1030
|
+
throw new StableValidationError(
|
|
1031
|
+
"tokenDecimals",
|
|
1032
|
+
void 0,
|
|
1033
|
+
`${op}: could not fetch decimals for asset "${asset}" \u2014 pass tokenDecimals explicitly`
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
_withIdempotency(key, fn) {
|
|
1038
|
+
if (!key) return fn();
|
|
1039
|
+
const existing = this.inFlight.get(key);
|
|
1040
|
+
if (existing) return existing;
|
|
1041
|
+
const promise = fn().finally(() => this.inFlight.delete(key));
|
|
1042
|
+
this.inFlight.set(key, promise);
|
|
1043
|
+
return promise;
|
|
1044
|
+
}
|
|
1045
|
+
_validateAmount(amount, op) {
|
|
1046
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1047
|
+
throw new StableValidationError("amount", amount, `${op}: amount must be a finite positive number`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
_validateShares(shares, op) {
|
|
1051
|
+
if (!Number.isFinite(shares) || shares <= 0) {
|
|
1052
|
+
throw new StableValidationError("shares", shares, `${op}: shares must be a finite positive number`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
|
|
505
1057
|
// src/stable.ts
|
|
506
1058
|
function validateTransferParams({ to, amount, token, tokenDecimals }) {
|
|
507
|
-
if (!
|
|
1059
|
+
if (!isAddress3(to)) {
|
|
508
1060
|
throw new StableValidationError("to", to, `transfer: invalid recipient address "${to}"`);
|
|
509
1061
|
}
|
|
510
1062
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
511
1063
|
throw new StableValidationError("amount", amount, "transfer: amount must be a finite positive number");
|
|
512
1064
|
}
|
|
513
|
-
if (token !== void 0 && !
|
|
1065
|
+
if (token !== void 0 && !isAddress3(token)) {
|
|
514
1066
|
throw new StableValidationError("token", token, `transfer: invalid token address "${token}"`);
|
|
515
1067
|
}
|
|
516
1068
|
if (tokenDecimals !== void 0 && (!Number.isInteger(tokenDecimals) || tokenDecimals < 0 || tokenDecimals > 77)) {
|
|
@@ -520,7 +1072,7 @@ function validateTransferParams({ to, amount, token, tokenDecimals }) {
|
|
|
520
1072
|
async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
|
|
521
1073
|
if (tokenDecimals !== void 0) return tokenDecimals;
|
|
522
1074
|
try {
|
|
523
|
-
return await publicClient.readContract({ address: token, abi:
|
|
1075
|
+
return await publicClient.readContract({ address: token, abi: erc20Abi4, functionName: "decimals" });
|
|
524
1076
|
} catch {
|
|
525
1077
|
throw new StableValidationError(
|
|
526
1078
|
"tokenDecimals",
|
|
@@ -541,9 +1093,9 @@ async function executeTokenTransfer(wc, publicClient, token, to, amount, tokenDe
|
|
|
541
1093
|
const decimals = await resolveTokenDecimals(publicClient, token, tokenDecimals);
|
|
542
1094
|
return wc.writeContract({
|
|
543
1095
|
address: token,
|
|
544
|
-
abi:
|
|
1096
|
+
abi: erc20Abi4,
|
|
545
1097
|
functionName: "transfer",
|
|
546
|
-
args: [to,
|
|
1098
|
+
args: [to, parseUnits4(String(amount), decimals)],
|
|
547
1099
|
account: acc,
|
|
548
1100
|
chain
|
|
549
1101
|
});
|
|
@@ -553,20 +1105,32 @@ function createStable({
|
|
|
553
1105
|
rpc: rpcOverride,
|
|
554
1106
|
account,
|
|
555
1107
|
transport,
|
|
556
|
-
walletClient
|
|
1108
|
+
walletClient,
|
|
1109
|
+
earn: earnConfig
|
|
557
1110
|
}) {
|
|
558
|
-
const chain = network === "mainnet" /* Mainnet */ ?
|
|
1111
|
+
const chain = network === "mainnet" /* Mainnet */ ? stableMainnet2 : stableTestnet2;
|
|
559
1112
|
const rpc = (rpcOverride ?? chain.rpcUrls.default.http[0]).replace(/\/$/, "");
|
|
560
1113
|
const signerConfig = { account, transport, walletClient };
|
|
561
1114
|
const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
|
|
1115
|
+
const morpho = earnConfig ? new MorphoService({ ...signerConfig, rpc, chainId: chain.id, vault: earnConfig.vault }) : null;
|
|
1116
|
+
function requireEarn() {
|
|
1117
|
+
if (!morpho) {
|
|
1118
|
+
throw new StableValidationError(
|
|
1119
|
+
"earn",
|
|
1120
|
+
void 0,
|
|
1121
|
+
"stable.earn.*: pass earn: { vault: '0x...' } to createStable() to enable earn operations"
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
return morpho;
|
|
1125
|
+
}
|
|
562
1126
|
async function transfer(params) {
|
|
563
1127
|
validateTransferParams(params);
|
|
564
1128
|
const { from, to, amount, token, tokenDecimals } = params;
|
|
565
1129
|
const acc = walletClient?.account ?? account ?? from;
|
|
566
|
-
const wc = walletClient ??
|
|
1130
|
+
const wc = walletClient ?? createWalletClient3({
|
|
567
1131
|
account: acc,
|
|
568
1132
|
chain,
|
|
569
|
-
transport: transport ??
|
|
1133
|
+
transport: transport ?? http4(rpc)
|
|
570
1134
|
});
|
|
571
1135
|
try {
|
|
572
1136
|
await wc.switchChain({ id: chain.id });
|
|
@@ -578,8 +1142,8 @@ function createStable({
|
|
|
578
1142
|
cause: err
|
|
579
1143
|
});
|
|
580
1144
|
}
|
|
581
|
-
const publicClient =
|
|
582
|
-
const hash = token !== void 0 ? await executeTokenTransfer(wc, publicClient, token, to, amount, tokenDecimals, acc, chain) : await wc.sendTransaction({ account: acc, chain, to, value:
|
|
1145
|
+
const publicClient = createPublicClient4({ chain, transport: http4(rpc) });
|
|
1146
|
+
const hash = token !== void 0 ? await executeTokenTransfer(wc, publicClient, token, to, amount, tokenDecimals, acc, chain) : await wc.sendTransaction({ account: acc, chain, to, value: parseUnits4(String(amount), 18) });
|
|
583
1147
|
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
584
1148
|
if (receipt.status === "reverted") {
|
|
585
1149
|
throw new StableTransactionError({
|
|
@@ -591,28 +1155,50 @@ function createStable({
|
|
|
591
1155
|
}
|
|
592
1156
|
return { txHash: receipt.transactionHash };
|
|
593
1157
|
}
|
|
594
|
-
function quoteBridge(params) {
|
|
595
|
-
return lifi.quoteBridge(params);
|
|
596
|
-
}
|
|
597
|
-
function bridge(params) {
|
|
598
|
-
return lifi.bridge(params);
|
|
599
|
-
}
|
|
600
1158
|
return {
|
|
601
1159
|
transfer,
|
|
602
|
-
quoteBridge,
|
|
603
|
-
bridge,
|
|
1160
|
+
quoteBridge: (params) => lifi.quoteBridge(params),
|
|
1161
|
+
bridge: (params) => lifi.bridge(params),
|
|
604
1162
|
quoteSwap: (params) => lifi.quoteSwap(params),
|
|
605
|
-
swap: (params) => lifi.swap(params)
|
|
1163
|
+
swap: (params) => lifi.swap(params),
|
|
1164
|
+
earn: {
|
|
1165
|
+
deposit: (params) => requireEarn().deposit(params),
|
|
1166
|
+
bulkDeposit: (items, options) => requireEarn().bulkDeposit(items, options),
|
|
1167
|
+
withdraw: (params) => requireEarn().withdraw(params),
|
|
1168
|
+
bulkWithdraw: (items, options) => requireEarn().bulkWithdraw(items, options),
|
|
1169
|
+
redeem: (params) => requireEarn().redeem(params),
|
|
1170
|
+
forceWithdraw: (params) => requireEarn().forceWithdraw(params),
|
|
1171
|
+
forceRedeem: (params) => requireEarn().forceRedeem(params)
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
function createStableReader(config) {
|
|
1176
|
+
const reader = new EarnReader(config);
|
|
1177
|
+
return {
|
|
1178
|
+
earn: {
|
|
1179
|
+
getYield: () => reader.getYield(),
|
|
1180
|
+
position: () => reader.position(),
|
|
1181
|
+
preview: (params) => reader.preview(params),
|
|
1182
|
+
withdrawability: (params) => reader.withdrawability(params)
|
|
1183
|
+
}
|
|
606
1184
|
};
|
|
607
1185
|
}
|
|
1186
|
+
|
|
1187
|
+
// src/constants.ts
|
|
1188
|
+
var STABLE_USDT_ADDRESS = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
|
|
1189
|
+
var STABLE_VAULT_ADDRESS = "0x461eF73F640456138242398a39AB48a557A49293";
|
|
608
1190
|
export {
|
|
609
1191
|
CHAIN_CONFIGS,
|
|
610
1192
|
Chain,
|
|
611
1193
|
Network,
|
|
1194
|
+
STABLE_USDT_ADDRESS,
|
|
1195
|
+
STABLE_VAULT_ADDRESS,
|
|
1196
|
+
StableBulkError,
|
|
612
1197
|
StableError,
|
|
613
1198
|
StableNetworkError,
|
|
614
1199
|
StableQuoteError,
|
|
615
1200
|
StableTransactionError,
|
|
616
1201
|
StableValidationError,
|
|
617
|
-
createStable
|
|
1202
|
+
createStable,
|
|
1203
|
+
createStableReader
|
|
618
1204
|
};
|