@stablechain/sdk 1.0.1 → 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 +1129 -800
- package/dist/index.js +1145 -816
- 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,548 +147,483 @@ 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);
|
|
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: _viem.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: _viem.erc20Abi, functionName: "decimals" }),
|
|
214
|
-
this.pc.readContract({ address: this.vaultAddr, abi: _viem.erc20Abi, functionName: "decimals" })
|
|
215
|
-
]);
|
|
216
|
-
const assetsRaw = vaultData.toAssets(shares);
|
|
217
|
-
return {
|
|
218
|
-
shares,
|
|
219
|
-
sharesFormatted: Number(_viem.formatUnits.call(void 0, shares, shareDecimals)),
|
|
220
|
-
shareDecimals,
|
|
221
|
-
assets: Number(_viem.formatUnits.call(void 0, 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 = await _asyncNullishCoalesce(params.tokenDecimals, async () => ( await this.pc.readContract({
|
|
242
|
-
address: vaultData.asset,
|
|
243
|
-
abi: _viem.erc20Abi,
|
|
244
|
-
functionName: "decimals"
|
|
245
|
-
})));
|
|
246
|
-
const requestedRaw = _viem.parseUnits.call(void 0, String(params.amount), tokenDecimals);
|
|
247
|
-
const availableLiquidityRaw = vaultData.assetBalance;
|
|
248
|
-
return {
|
|
249
|
-
isInstant: requestedRaw <= availableLiquidityRaw,
|
|
250
|
-
availableLiquidity: Number(_viem.formatUnits.call(void 0, availableLiquidityRaw, tokenDecimals)),
|
|
251
|
-
tokenDecimals
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
async _fetchNetApy() {
|
|
255
|
-
const { apy } = await this._fetchYield();
|
|
256
|
-
return apy;
|
|
183
|
+
return this._withIdempotency(
|
|
184
|
+
params.idempotencyKey,
|
|
185
|
+
() => this._deposit(params)
|
|
186
|
+
);
|
|
257
187
|
}
|
|
258
|
-
async
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
+
});
|
|
269
219
|
}
|
|
270
220
|
}
|
|
271
221
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
+
};
|
|
231
|
+
};
|
|
232
|
+
return this._withIdempotency(idempotencyKey, run);
|
|
233
|
+
}
|
|
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
|
|
284
245
|
});
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
});
|
|
292
263
|
}
|
|
293
|
-
|
|
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
|
-
});
|
|
264
|
+
}
|
|
304
265
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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: _nullishCoalesce(vault.netApyExcludingRewards, () => ( 0)),
|
|
319
|
-
rewards,
|
|
320
|
-
performanceFee: vault.performanceFee,
|
|
321
|
-
managementFee: vault.managementFee
|
|
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
|
+
};
|
|
322
275
|
};
|
|
276
|
+
return this._withIdempotency(idempotencyKey, run);
|
|
323
277
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// src/chains.ts
|
|
338
|
-
var CHAIN_CONFIGS = {
|
|
339
|
-
// ─── Testnet ──────────────────────────────────────────────────────────────
|
|
340
|
-
["sepolia" /* Sepolia */]: {
|
|
341
|
-
id: 11155111,
|
|
342
|
-
rpc: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
343
|
-
usdt: "0xc4DCC311c028e341fd8602D8eB89c5de94625927",
|
|
344
|
-
decimals: 18
|
|
345
|
-
},
|
|
346
|
-
["stable-testnet" /* StableTestnet */]: {
|
|
347
|
-
id: 2201,
|
|
348
|
-
rpc: "https://rpc.testnet.stable.xyz",
|
|
349
|
-
usdt: "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9",
|
|
350
|
-
decimals: 6
|
|
351
|
-
},
|
|
352
|
-
// ─── Mainnet ──────────────────────────────────────────────────────────────
|
|
353
|
-
["stable" /* Stable */]: {
|
|
354
|
-
id: 988,
|
|
355
|
-
rpc: "https://rpc.stable.xyz",
|
|
356
|
-
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
357
|
-
decimals: 6
|
|
358
|
-
},
|
|
359
|
-
["ethereum" /* Ethereum */]: {
|
|
360
|
-
id: 1,
|
|
361
|
-
rpc: "https://ethereum-rpc.publicnode.com",
|
|
362
|
-
usdt: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
363
|
-
decimals: 6
|
|
364
|
-
},
|
|
365
|
-
["arbitrum" /* Arbitrum */]: {
|
|
366
|
-
id: 42161,
|
|
367
|
-
rpc: "https://arbitrum-one-rpc.publicnode.com",
|
|
368
|
-
usdt: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92",
|
|
369
|
-
decimals: 6
|
|
370
|
-
},
|
|
371
|
-
["ink" /* Ink */]: {
|
|
372
|
-
id: 57073,
|
|
373
|
-
rpc: "https://rpc-gel.inkonchain.com",
|
|
374
|
-
usdt: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
375
|
-
decimals: 6
|
|
376
|
-
},
|
|
377
|
-
["bera" /* Bera */]: {
|
|
378
|
-
id: 80094,
|
|
379
|
-
rpc: "https://rpc.berachain.com",
|
|
380
|
-
usdt: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
381
|
-
decimals: 6
|
|
382
|
-
},
|
|
383
|
-
["megaeth" /* MegaETH */]: {
|
|
384
|
-
id: 4326,
|
|
385
|
-
rpc: "https://carrot.megaeth.com",
|
|
386
|
-
usdt: "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb",
|
|
387
|
-
decimals: 6
|
|
388
|
-
},
|
|
389
|
-
["base" /* Base */]: {
|
|
390
|
-
id: 8453,
|
|
391
|
-
rpc: "https://mainnet.base.org",
|
|
392
|
-
usdt: "0x102d758f688a4C1C5a80b116bD945d4455460282",
|
|
393
|
-
decimals: 6
|
|
394
|
-
},
|
|
395
|
-
["bsc" /* BSC */]: {
|
|
396
|
-
id: 56,
|
|
397
|
-
rpc: "https://bsc-rpc.publicnode.com",
|
|
398
|
-
usdt: "0x55d398326f99059fF775485246999027B3197955",
|
|
399
|
-
decimals: 18
|
|
400
|
-
},
|
|
401
|
-
["hyperevm" /* HyperEVM */]: {
|
|
402
|
-
id: 999,
|
|
403
|
-
rpc: "https://rpc.hyperliquid.xyz/evm",
|
|
404
|
-
usdt: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
|
|
405
|
-
decimals: 6
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
// src/services/lifi.ts
|
|
410
|
-
var LiFiService = class {
|
|
411
|
-
constructor(config) {
|
|
412
|
-
this.config = config;
|
|
278
|
+
withdraw(params) {
|
|
279
|
+
this._validateAmount(params.amount, "earn withdraw");
|
|
280
|
+
return this._withIdempotency(
|
|
281
|
+
params.idempotencyKey,
|
|
282
|
+
() => this._withdraw(params)
|
|
283
|
+
);
|
|
413
284
|
}
|
|
414
|
-
|
|
415
|
-
this.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const qs = new URLSearchParams({
|
|
421
|
-
fromChain: chain,
|
|
422
|
-
toChain: chain,
|
|
423
|
-
fromToken,
|
|
424
|
-
toToken,
|
|
425
|
-
fromAmount: fromAmountRaw,
|
|
426
|
-
fromAddress: from,
|
|
427
|
-
...toAddress && { toAddress }
|
|
428
|
-
});
|
|
429
|
-
const quote = await this._fetchQuote(qs);
|
|
430
|
-
return this._toSwapQuote(quote);
|
|
285
|
+
redeem(params) {
|
|
286
|
+
this._validateShares(params.shares, "earn redeem");
|
|
287
|
+
return this._withIdempotency(
|
|
288
|
+
params.idempotencyKey,
|
|
289
|
+
() => this._redeem(params)
|
|
290
|
+
);
|
|
431
291
|
}
|
|
432
|
-
|
|
433
|
-
this.
|
|
434
|
-
|
|
435
|
-
|
|
292
|
+
forceWithdraw(params) {
|
|
293
|
+
this._validateAmount(params.amount, "earn forceWithdraw");
|
|
294
|
+
return this._withIdempotency(
|
|
295
|
+
params.idempotencyKey,
|
|
296
|
+
() => this._forceWithdraw(params)
|
|
297
|
+
);
|
|
436
298
|
}
|
|
437
|
-
|
|
438
|
-
this.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (!src) throw new StableValidationError("fromChain", fromChain, `LI.FI bridge: unsupported source chain "${fromChain}"`);
|
|
444
|
-
if (!dst) throw new StableValidationError("toChain", toChain, `LI.FI bridge: unsupported destination chain "${toChain}"`);
|
|
445
|
-
const qs = new URLSearchParams({
|
|
446
|
-
fromChain: src.id.toString(),
|
|
447
|
-
toChain: dst.id.toString(),
|
|
448
|
-
fromToken,
|
|
449
|
-
toToken,
|
|
450
|
-
fromAmount: _viem.parseUnits.call(void 0, String(amount), fromDecimals).toString(),
|
|
451
|
-
fromAddress: from,
|
|
452
|
-
...recipient && { toAddress: recipient }
|
|
453
|
-
});
|
|
454
|
-
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
455
|
-
return { toAmount: quote.toAmount };
|
|
299
|
+
forceRedeem(params) {
|
|
300
|
+
this._validateShares(params.shares, "earn forceRedeem");
|
|
301
|
+
return this._withIdempotency(
|
|
302
|
+
params.idempotencyKey,
|
|
303
|
+
() => this._forceRedeem(params)
|
|
304
|
+
);
|
|
456
305
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const {
|
|
306
|
+
// ─── Private operations ──────────────────────────────────────────────────
|
|
307
|
+
async _deposit(params) {
|
|
308
|
+
const {
|
|
309
|
+
amount,
|
|
310
|
+
tokenDecimals,
|
|
311
|
+
slippageTolerance,
|
|
312
|
+
idempotencyKey,
|
|
313
|
+
depositBuffer
|
|
314
|
+
} = params;
|
|
460
315
|
const from = this._requireFrom();
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
+
);
|
|
335
|
+
}
|
|
468
336
|
}
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
...recipient && { toAddress: recipient }
|
|
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
|
|
478
345
|
});
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (!_viem.isAddress.call(void 0, toToken)) {
|
|
504
|
-
throw new StableValidationError("toToken", toToken, `LI.FI bridge: invalid toToken address "${toToken}"`);
|
|
505
|
-
}
|
|
506
|
-
if (!Number.isFinite(amount) || amount <= 0) {
|
|
507
|
-
throw new StableValidationError("amount", amount, "LI.FI bridge: amount must be a finite positive number");
|
|
508
|
-
}
|
|
509
|
-
if (recipient !== void 0 && !_viem.isAddress.call(void 0, recipient)) {
|
|
510
|
-
throw new StableValidationError("recipient", recipient, `LI.FI bridge: invalid recipient address "${recipient}"`);
|
|
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
|
|
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);
|
|
369
|
+
}
|
|
511
370
|
}
|
|
512
|
-
|
|
513
|
-
|
|
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
|
+
});
|
|
514
381
|
}
|
|
382
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
515
383
|
}
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
384
|
+
async _withdraw(params) {
|
|
385
|
+
const { amount, tokenDecimals, idempotencyKey } = params;
|
|
386
|
+
const from = this._requireFrom();
|
|
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
|
+
});
|
|
524
403
|
}
|
|
525
|
-
return
|
|
404
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
526
405
|
}
|
|
527
|
-
async
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
406
|
+
async _redeem(params) {
|
|
407
|
+
const { shares, shareDecimals, idempotencyKey } = params;
|
|
408
|
+
const from = this._requireFrom();
|
|
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"
|
|
539
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
|
+
);
|
|
540
425
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
426
|
+
}
|
|
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
|
|
545
438
|
});
|
|
546
439
|
}
|
|
440
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
547
441
|
}
|
|
548
|
-
async
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
providerCode,
|
|
562
|
-
body
|
|
563
|
-
});
|
|
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
|
+
);
|
|
564
455
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
|
571
469
|
});
|
|
572
470
|
}
|
|
573
|
-
return
|
|
574
|
-
}
|
|
575
|
-
_toSwapQuote(quote) {
|
|
576
|
-
return {
|
|
577
|
-
toAmount: Number(_viem.formatUnits.call(void 0, BigInt(quote.estimate.toAmount), quote.action.toToken.decimals)),
|
|
578
|
-
fromAmount: BigInt(quote.estimate.fromAmount),
|
|
579
|
-
fromToken: quote.action.fromToken.address,
|
|
580
|
-
approvalAddress: quote.estimate.approvalAddress,
|
|
581
|
-
transactionRequest: quote.transactionRequest
|
|
582
|
-
};
|
|
471
|
+
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
583
472
|
}
|
|
584
|
-
async
|
|
473
|
+
async _forceRedeem(params) {
|
|
474
|
+
const {
|
|
475
|
+
shares,
|
|
476
|
+
shareDecimals,
|
|
477
|
+
tokenDecimals,
|
|
478
|
+
deallocations,
|
|
479
|
+
idempotencyKey
|
|
480
|
+
} = params;
|
|
585
481
|
const from = this._requireFrom();
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
+
}
|
|
500
|
+
}
|
|
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
|
+
);
|
|
508
|
+
}
|
|
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
|
|
594
522
|
});
|
|
595
523
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
+
}));
|
|
536
|
+
}
|
|
537
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
538
|
+
_requireFrom() {
|
|
539
|
+
const from = _optionalChain([this, 'access', _5 => _5.wc, 'access', _6 => _6.account, 'optionalAccess', _7 => _7.address]);
|
|
540
|
+
if (!from) {
|
|
541
|
+
throw new StableValidationError(
|
|
542
|
+
"signer",
|
|
543
|
+
void 0,
|
|
544
|
+
"earn: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
return from;
|
|
548
|
+
}
|
|
549
|
+
async _switchChain() {
|
|
602
550
|
try {
|
|
603
|
-
await wc.switchChain({ id:
|
|
551
|
+
await this.wc.switchChain({ id: this.config.chainId });
|
|
604
552
|
} catch (err) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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,
|
|
558
|
+
cause: err
|
|
559
|
+
});
|
|
560
|
+
}
|
|
611
561
|
}
|
|
612
|
-
|
|
613
|
-
|
|
562
|
+
}
|
|
563
|
+
async _sendTx(to, data, value, from) {
|
|
614
564
|
try {
|
|
615
|
-
|
|
616
|
-
to
|
|
617
|
-
data
|
|
618
|
-
value
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
chain: srcChain
|
|
565
|
+
return await this.wc.sendTransaction({
|
|
566
|
+
to,
|
|
567
|
+
data,
|
|
568
|
+
value,
|
|
569
|
+
account: from,
|
|
570
|
+
chain: this.chain
|
|
622
571
|
});
|
|
623
572
|
} catch (err) {
|
|
624
573
|
throw new StableTransactionError({
|
|
625
574
|
phase: "send",
|
|
626
|
-
shortMessage: "
|
|
627
|
-
chainId:
|
|
575
|
+
shortMessage: "earn: transaction send failed",
|
|
576
|
+
chainId: this.config.chainId,
|
|
628
577
|
cause: err
|
|
629
578
|
});
|
|
630
579
|
}
|
|
631
|
-
return { txHash: hash, toAmount: quote.toAmount };
|
|
632
580
|
}
|
|
633
|
-
async
|
|
634
|
-
const tokenAddr = quote.fromToken;
|
|
635
|
-
const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000" || tokenAddr.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
636
|
-
if (isNative || !quote.approvalAddress) return;
|
|
637
|
-
const pc = _viem.createPublicClient.call(void 0, { chain: srcChain, transport: _viem.http.call(void 0, srcRpc) });
|
|
638
|
-
const allowance = await pc.readContract({
|
|
639
|
-
address: tokenAddr,
|
|
640
|
-
abi: _viem.erc20Abi,
|
|
641
|
-
functionName: "allowance",
|
|
642
|
-
args: [from, quote.approvalAddress]
|
|
643
|
-
});
|
|
644
|
-
if (allowance >= quote.fromAmount) return;
|
|
645
|
-
let approveHash;
|
|
581
|
+
async _resolveDecimals(asset, op) {
|
|
646
582
|
try {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
cause: err
|
|
661
|
-
});
|
|
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
|
+
);
|
|
662
596
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
+
);
|
|
671
622
|
}
|
|
672
623
|
}
|
|
673
624
|
};
|
|
674
|
-
function isObject(v) {
|
|
675
|
-
return typeof v === "object" && v !== null;
|
|
676
|
-
}
|
|
677
|
-
function isLiFiQuoteShape(v) {
|
|
678
|
-
if (!isObject(v)) return false;
|
|
679
|
-
const tx = v.transactionRequest;
|
|
680
|
-
const est = v.estimate;
|
|
681
|
-
const act = v.action;
|
|
682
|
-
return isObject(tx) && typeof tx.to === "string" && typeof tx.data === "string" && typeof tx.chainId === "number" && isObject(est) && typeof est.fromAmount === "string" && typeof est.toAmount === "string" && isObject(act) && isObject(act.fromToken) && isObject(act.toToken);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// src/earn/morpho.ts
|
|
686
625
|
|
|
626
|
+
// src/earn/reader.ts
|
|
687
627
|
|
|
688
628
|
|
|
689
629
|
|
|
@@ -695,384 +635,740 @@ function isLiFiQuoteShape(v) {
|
|
|
695
635
|
|
|
696
636
|
|
|
697
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 || {});
|
|
698
658
|
|
|
699
|
-
|
|
700
|
-
var
|
|
659
|
+
// src/earn/reader.ts
|
|
660
|
+
var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
|
|
661
|
+
var EarnReader = class {
|
|
701
662
|
constructor(config) {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
+
);
|
|
683
|
+
}
|
|
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);
|
|
717
696
|
}
|
|
718
|
-
|
|
719
|
-
this.
|
|
720
|
-
|
|
721
|
-
|
|
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);
|
|
721
|
+
return {
|
|
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
|
|
728
|
+
};
|
|
729
|
+
}
|
|
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"
|
|
749
|
+
})));
|
|
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;
|
|
722
756
|
}
|
|
723
|
-
return
|
|
757
|
+
return {
|
|
758
|
+
isInstant: requestedRaw <= availableLiquidityRaw,
|
|
759
|
+
availableLiquidity: Number(
|
|
760
|
+
_viem.formatUnits.call(void 0, availableLiquidityRaw, tokenDecimals)
|
|
761
|
+
),
|
|
762
|
+
tokenDecimals
|
|
763
|
+
};
|
|
724
764
|
}
|
|
725
|
-
async
|
|
726
|
-
const {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
results,
|
|
741
|
-
succeeded: succeeded2,
|
|
742
|
-
failed: results.length - succeeded2
|
|
743
|
-
});
|
|
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
|
|
744
780
|
}
|
|
745
781
|
}
|
|
746
782
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
if (
|
|
764
|
-
const
|
|
765
|
-
throw new
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
});
|
|
783
|
+
`;
|
|
784
|
+
let body;
|
|
785
|
+
try {
|
|
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
|
+
);
|
|
771
806
|
}
|
|
772
|
-
|
|
807
|
+
return res.json().catch(() => void 0);
|
|
808
|
+
},
|
|
809
|
+
(err) => err instanceof HttpError ? err.retryable : err instanceof TypeError
|
|
810
|
+
);
|
|
811
|
+
} catch (err) {
|
|
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
|
+
});
|
|
773
820
|
}
|
|
774
|
-
|
|
775
|
-
|
|
821
|
+
throw err;
|
|
822
|
+
}
|
|
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
|
+
);
|
|
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
|
|
776
842
|
};
|
|
777
|
-
return this._withIdempotency(idempotencyKey, run);
|
|
778
843
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
// src/services/lifi.ts
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
|
|
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
|
|
782
926
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
// src/services/lifi.ts
|
|
930
|
+
var LiFiService = class {
|
|
931
|
+
constructor(config) {
|
|
932
|
+
this.config = config;
|
|
786
933
|
}
|
|
787
|
-
|
|
788
|
-
this.
|
|
789
|
-
|
|
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);
|
|
790
960
|
}
|
|
791
|
-
|
|
792
|
-
this.
|
|
793
|
-
|
|
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
|
+
);
|
|
794
969
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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 }
|
|
823
1004
|
});
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
for (const req of requirements) {
|
|
827
|
-
if (_morphosdk.isRequirementApproval.call(void 0, req)) {
|
|
828
|
-
const approveHash = await this._sendTx(req.to, req.data, req.value, from);
|
|
829
|
-
const approveReceipt = await this.pc.waitForTransactionReceipt({ hash: approveHash });
|
|
830
|
-
if (approveReceipt.status === "reverted") {
|
|
831
|
-
throw new StableTransactionError({
|
|
832
|
-
phase: "approve",
|
|
833
|
-
shortMessage: "earn deposit: token approval reverted on-chain",
|
|
834
|
-
txHash: approveReceipt.transactionHash,
|
|
835
|
-
chainId: this.config.chainId
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
} else if (_morphosdk.isRequirementSignature.call(void 0, req)) {
|
|
839
|
-
sig = await req.sign(this.wc, from);
|
|
840
|
-
}
|
|
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 };
|
|
1005
|
+
const quote = this._toSwapQuote(await this._fetchQuote(qs));
|
|
1006
|
+
return { toAmount: quote.toAmount };
|
|
854
1007
|
}
|
|
855
|
-
async
|
|
856
|
-
|
|
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;
|
|
857
1019
|
const from = this._requireFrom();
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
-
});
|
|
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
|
+
);
|
|
874
1028
|
}
|
|
875
|
-
|
|
1029
|
+
if (!dst) {
|
|
1030
|
+
throw new StableValidationError(
|
|
1031
|
+
"toChain",
|
|
1032
|
+
toChain,
|
|
1033
|
+
`LI.FI bridge: unsupported destination chain "${toChain}"`
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
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);
|
|
876
1051
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
if (
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
void 0,
|
|
890
|
-
`earn redeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
891
|
-
);
|
|
892
|
-
}
|
|
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
|
+
);
|
|
893
1064
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
-
});
|
|
1065
|
+
if (!_viem.isAddress.call(void 0, toToken)) {
|
|
1066
|
+
throw new StableValidationError(
|
|
1067
|
+
"toToken",
|
|
1068
|
+
toToken,
|
|
1069
|
+
`swap: invalid toToken address "${toToken}"`
|
|
1070
|
+
);
|
|
906
1071
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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");
|
|
1072
|
+
if (fromToken.toLowerCase() === toToken.toLowerCase()) {
|
|
1073
|
+
throw new StableValidationError(
|
|
1074
|
+
"toToken",
|
|
1075
|
+
toToken,
|
|
1076
|
+
"swap: fromToken and toToken must differ"
|
|
1077
|
+
);
|
|
919
1078
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
if (
|
|
928
|
-
throw new
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
});
|
|
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
|
+
);
|
|
1085
|
+
}
|
|
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
|
+
);
|
|
934
1092
|
}
|
|
935
|
-
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
936
1093
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
`earn forceRedeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
952
|
-
);
|
|
953
|
-
}
|
|
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
|
+
);
|
|
954
1108
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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
|
+
);
|
|
959
1115
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
if (
|
|
968
|
-
throw new
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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
|
+
);
|
|
974
1136
|
}
|
|
975
|
-
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
976
|
-
}
|
|
977
|
-
_buildDeallocs(deallocations, assetDecimals) {
|
|
978
|
-
return deallocations.map((d) => ({
|
|
979
|
-
adapter: d.adapter,
|
|
980
|
-
amount: _viem.parseUnits.call(void 0, 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
1137
|
}
|
|
986
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
987
1138
|
_requireFrom() {
|
|
988
|
-
const from = _optionalChain([this, 'access', _23 => _23.
|
|
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])));
|
|
989
1140
|
if (!from) {
|
|
990
1141
|
throw new StableValidationError(
|
|
991
1142
|
"signer",
|
|
992
1143
|
void 0,
|
|
993
|
-
"
|
|
1144
|
+
"LI.FI: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
994
1145
|
);
|
|
995
1146
|
}
|
|
996
1147
|
return from;
|
|
997
1148
|
}
|
|
998
|
-
async
|
|
1149
|
+
async _fetchQuote(qs) {
|
|
1150
|
+
const url = `https://li.quest/v1/quote?${qs}`;
|
|
999
1151
|
try {
|
|
1000
|
-
await
|
|
1152
|
+
return await retryWithBackoff(() => this._fetchQuoteOnce(url));
|
|
1001
1153
|
} catch (err) {
|
|
1002
|
-
if (err
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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,
|
|
1007
1160
|
cause: err
|
|
1008
1161
|
});
|
|
1009
1162
|
}
|
|
1163
|
+
throw new StableNetworkError({
|
|
1164
|
+
shortMessage: "LI.FI: quote request failed before reaching the server",
|
|
1165
|
+
url,
|
|
1166
|
+
cause: err
|
|
1167
|
+
});
|
|
1010
1168
|
}
|
|
1011
1169
|
}
|
|
1012
|
-
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
|
+
})));
|
|
1013
1233
|
try {
|
|
1014
|
-
|
|
1234
|
+
await wc.switchChain({ id: srcChainId });
|
|
1015
1235
|
} catch (err) {
|
|
1016
1236
|
throw new StableTransactionError({
|
|
1017
|
-
phase: "
|
|
1018
|
-
shortMessage:
|
|
1019
|
-
chainId:
|
|
1237
|
+
phase: "switch_chain",
|
|
1238
|
+
shortMessage: `LI.FI: wallet rejected or failed to switch to chain ${srcChainId}`,
|
|
1239
|
+
chainId: srcChainId,
|
|
1020
1240
|
cause: err
|
|
1021
1241
|
});
|
|
1022
1242
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1243
|
+
await this._ensureApproval(
|
|
1244
|
+
wc,
|
|
1245
|
+
srcChain,
|
|
1246
|
+
srcRpc,
|
|
1247
|
+
srcChainId,
|
|
1248
|
+
from,
|
|
1249
|
+
quote
|
|
1250
|
+
);
|
|
1251
|
+
let hash;
|
|
1025
1252
|
try {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
+
});
|
|
1035
1268
|
}
|
|
1269
|
+
return { txHash: hash, toAmount: quote.toAmount };
|
|
1036
1270
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
if (
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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
|
+
});
|
|
1048
1303
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
+
});
|
|
1053
1314
|
}
|
|
1054
1315
|
}
|
|
1055
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
|
+
}
|
|
1056
1327
|
|
|
1057
1328
|
// src/stable.ts
|
|
1058
|
-
function validateTransferParams({
|
|
1329
|
+
function validateTransferParams({
|
|
1330
|
+
to,
|
|
1331
|
+
amount,
|
|
1332
|
+
token,
|
|
1333
|
+
tokenDecimals
|
|
1334
|
+
}) {
|
|
1059
1335
|
if (!_viem.isAddress.call(void 0, to)) {
|
|
1060
|
-
throw new StableValidationError(
|
|
1336
|
+
throw new StableValidationError(
|
|
1337
|
+
"to",
|
|
1338
|
+
to,
|
|
1339
|
+
`transfer: invalid recipient address "${to}"`
|
|
1340
|
+
);
|
|
1061
1341
|
}
|
|
1062
1342
|
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1063
|
-
throw new StableValidationError(
|
|
1343
|
+
throw new StableValidationError(
|
|
1344
|
+
"amount",
|
|
1345
|
+
amount,
|
|
1346
|
+
"transfer: amount must be a finite positive number"
|
|
1347
|
+
);
|
|
1064
1348
|
}
|
|
1065
1349
|
if (token !== void 0 && !_viem.isAddress.call(void 0, token)) {
|
|
1066
|
-
throw new StableValidationError(
|
|
1350
|
+
throw new StableValidationError(
|
|
1351
|
+
"token",
|
|
1352
|
+
token,
|
|
1353
|
+
`transfer: invalid token address "${token}"`
|
|
1354
|
+
);
|
|
1067
1355
|
}
|
|
1068
1356
|
if (tokenDecimals !== void 0 && (!Number.isInteger(tokenDecimals) || tokenDecimals < 0 || tokenDecimals > 77)) {
|
|
1069
|
-
throw new StableValidationError(
|
|
1357
|
+
throw new StableValidationError(
|
|
1358
|
+
"tokenDecimals",
|
|
1359
|
+
tokenDecimals,
|
|
1360
|
+
"transfer: tokenDecimals must be an integer between 0 and 77"
|
|
1361
|
+
);
|
|
1070
1362
|
}
|
|
1071
1363
|
}
|
|
1072
1364
|
async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
|
|
1073
1365
|
if (tokenDecimals !== void 0) return tokenDecimals;
|
|
1074
1366
|
try {
|
|
1075
|
-
return await publicClient.readContract({
|
|
1367
|
+
return await publicClient.readContract({
|
|
1368
|
+
address: token,
|
|
1369
|
+
abi: _viem.erc20Abi,
|
|
1370
|
+
functionName: "decimals"
|
|
1371
|
+
});
|
|
1076
1372
|
} catch (e5) {
|
|
1077
1373
|
throw new StableValidationError(
|
|
1078
1374
|
"tokenDecimals",
|
|
@@ -1090,7 +1386,11 @@ async function executeTokenTransfer(wc, publicClient, token, to, amount, tokenDe
|
|
|
1090
1386
|
`transfer: no contract found at "${token}" on chain ${chain.id} \u2014 check the address and network`
|
|
1091
1387
|
);
|
|
1092
1388
|
}
|
|
1093
|
-
const decimals = await resolveTokenDecimals(
|
|
1389
|
+
const decimals = await resolveTokenDecimals(
|
|
1390
|
+
publicClient,
|
|
1391
|
+
token,
|
|
1392
|
+
tokenDecimals
|
|
1393
|
+
);
|
|
1094
1394
|
return wc.writeContract({
|
|
1095
1395
|
address: token,
|
|
1096
1396
|
abi: _viem.erc20Abi,
|
|
@@ -1109,10 +1409,18 @@ function createStable({
|
|
|
1109
1409
|
earn: earnConfig
|
|
1110
1410
|
}) {
|
|
1111
1411
|
const chain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
|
|
1112
|
-
const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(
|
|
1412
|
+
const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(
|
|
1413
|
+
/\/$/,
|
|
1414
|
+
""
|
|
1415
|
+
);
|
|
1113
1416
|
const signerConfig = { account, transport, walletClient };
|
|
1114
1417
|
const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
|
|
1115
|
-
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;
|
|
1116
1424
|
function requireEarn() {
|
|
1117
1425
|
if (!morpho) {
|
|
1118
1426
|
throw new StableValidationError(
|
|
@@ -1142,8 +1450,29 @@ function createStable({
|
|
|
1142
1450
|
cause: err
|
|
1143
1451
|
});
|
|
1144
1452
|
}
|
|
1145
|
-
const publicClient = _viem.createPublicClient.call(void 0, {
|
|
1146
|
-
|
|
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
|
+
);
|
|
1147
1476
|
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
1148
1477
|
if (receipt.status === "reverted") {
|
|
1149
1478
|
throw new StableTransactionError({
|