@stablechain/sdk 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +438 -14
- package/dist/index.d.cts +71 -2
- package/dist/index.d.ts +71 -2
- package/dist/index.js +455 -31
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -126,7 +126,11 @@ var HttpError = class extends Error {
|
|
|
126
126
|
};
|
|
127
127
|
function isRetryableError(err) {
|
|
128
128
|
if (err instanceof HttpError) return err.retryable;
|
|
129
|
-
|
|
129
|
+
if (err instanceof TypeError) {
|
|
130
|
+
const msg = err.message.toLowerCase();
|
|
131
|
+
return !(msg.includes("cors") || msg.includes("blocked"));
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
130
134
|
}
|
|
131
135
|
async function retryWithBackoff(fn, isRetryable = isRetryableError, opts = {}) {
|
|
132
136
|
const maxAttempts = _nullishCoalesce(opts.maxAttempts, () => ( 3));
|
|
@@ -166,6 +170,7 @@ var MorphoService = class {
|
|
|
166
170
|
})
|
|
167
171
|
});
|
|
168
172
|
this.morpho = new (0, _morphosdk.MorphoClient)(morphoWc, { supportSignature: true });
|
|
173
|
+
this.morphoNoSig = new (0, _morphosdk.MorphoClient)(morphoWc, { supportSignature: false });
|
|
169
174
|
this.pc = _viem.createPublicClient.call(void 0, {
|
|
170
175
|
chain: this.chain,
|
|
171
176
|
transport: _viem.http.call(void 0, config.rpc)
|
|
@@ -303,6 +308,109 @@ var MorphoService = class {
|
|
|
303
308
|
() => this._forceRedeem(params)
|
|
304
309
|
);
|
|
305
310
|
}
|
|
311
|
+
// ─── Calldata preparation (no signing) ───────────────────────────────────
|
|
312
|
+
async prepareDepositCalldata(params) {
|
|
313
|
+
this._validateAmount(params.amount, "prepareDepositCalldata");
|
|
314
|
+
if (params.depositBuffer !== void 0 && (params.depositBuffer <= 0 || params.depositBuffer > 1)) {
|
|
315
|
+
throw new StableValidationError(
|
|
316
|
+
"depositBuffer",
|
|
317
|
+
params.depositBuffer,
|
|
318
|
+
"prepareDepositCalldata: depositBuffer must be > 0 and \u2264 1"
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
const { amount, tokenDecimals, slippageTolerance, depositBuffer } = params;
|
|
322
|
+
const from = this._requireFrom();
|
|
323
|
+
const vaultAddr = _nullishCoalesce(params.vault, () => ( this.config.vault));
|
|
324
|
+
const vault = this.morphoNoSig.vaultV2(vaultAddr, this.config.chainId);
|
|
325
|
+
const vaultData = await vault.getData();
|
|
326
|
+
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "prepareDepositCalldata")));
|
|
327
|
+
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
328
|
+
if (depositBuffer !== void 0) {
|
|
329
|
+
const bps = BigInt(Math.round(depositBuffer * 1e4));
|
|
330
|
+
const maxAllowed = vaultData.assetBalance * bps / 10000n;
|
|
331
|
+
if (assetsRaw > maxAllowed) {
|
|
332
|
+
const maxFormatted = Number(_viem.formatUnits.call(void 0, maxAllowed, decimals));
|
|
333
|
+
const idleFormatted = Number(_viem.formatUnits.call(void 0, vaultData.assetBalance, decimals));
|
|
334
|
+
throw new StableValidationError(
|
|
335
|
+
"amount",
|
|
336
|
+
amount,
|
|
337
|
+
`prepareDepositCalldata: amount ${amount} exceeds deposit buffer \u2014 max ${maxFormatted} (${depositBuffer * 100}% of ${idleFormatted} idle liquidity)`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const depositAction = vault.deposit({
|
|
342
|
+
amount: assetsRaw,
|
|
343
|
+
userAddress: from,
|
|
344
|
+
accrualVault: vaultData,
|
|
345
|
+
slippageTolerance: slippageTolerance !== void 0 ? _viem.parseUnits.call(void 0, String(slippageTolerance), 18) : void 0
|
|
346
|
+
});
|
|
347
|
+
const requirements = await depositAction.getRequirements();
|
|
348
|
+
const steps = [];
|
|
349
|
+
for (const req of requirements) {
|
|
350
|
+
if (_morphosdk.isRequirementApproval.call(void 0, req)) {
|
|
351
|
+
steps.push({ to: req.to, data: req.data, value: req.value });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const tx = depositAction.buildTx(void 0);
|
|
355
|
+
steps.push({ to: tx.to, data: tx.data, value: tx.value });
|
|
356
|
+
return { steps, chainId: this.config.chainId };
|
|
357
|
+
}
|
|
358
|
+
async prepareWithdrawCalldata(params) {
|
|
359
|
+
this._validateAmount(params.amount, "prepareWithdrawCalldata");
|
|
360
|
+
const { amount, tokenDecimals } = params;
|
|
361
|
+
const from = this._requireFrom();
|
|
362
|
+
const vaultAddr = this.config.vault;
|
|
363
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
364
|
+
const vaultData = await vault.getData();
|
|
365
|
+
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "prepareWithdrawCalldata")));
|
|
366
|
+
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
367
|
+
const tx = vaultData.assetBalance >= assetsRaw ? vault.withdraw({ amount: assetsRaw, userAddress: from }).buildTx() : vault.forceWithdraw({
|
|
368
|
+
deallocations: this._buildAutoDeallocs(
|
|
369
|
+
vaultData.accrualAdapters,
|
|
370
|
+
assetsRaw - vaultData.assetBalance,
|
|
371
|
+
amount
|
|
372
|
+
),
|
|
373
|
+
withdraw: { amount: assetsRaw },
|
|
374
|
+
userAddress: from
|
|
375
|
+
}).buildTx();
|
|
376
|
+
return { to: tx.to, data: tx.data, value: tx.value, chainId: this.config.chainId };
|
|
377
|
+
}
|
|
378
|
+
async prepareRedeemCalldata(params) {
|
|
379
|
+
this._validateShares(params.shares, "prepareRedeemCalldata");
|
|
380
|
+
const { shares, shareDecimals } = params;
|
|
381
|
+
const from = this._requireFrom();
|
|
382
|
+
const vaultAddr = this.config.vault;
|
|
383
|
+
let decimals = shareDecimals;
|
|
384
|
+
if (decimals === void 0) {
|
|
385
|
+
try {
|
|
386
|
+
decimals = await this.pc.readContract({
|
|
387
|
+
address: vaultAddr,
|
|
388
|
+
abi: _viem.erc20Abi,
|
|
389
|
+
functionName: "decimals"
|
|
390
|
+
});
|
|
391
|
+
} catch (e2) {
|
|
392
|
+
throw new StableValidationError(
|
|
393
|
+
"shareDecimals",
|
|
394
|
+
void 0,
|
|
395
|
+
`prepareRedeemCalldata: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const sharesRaw = _viem.parseUnits.call(void 0, String(shares), decimals);
|
|
400
|
+
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
401
|
+
const vaultData = await vault.getData();
|
|
402
|
+
const assetsEquivalent = vaultData.toAssets(sharesRaw);
|
|
403
|
+
const tx = vaultData.assetBalance >= assetsEquivalent ? vault.redeem({ shares: sharesRaw, userAddress: from }).buildTx() : vault.forceRedeem({
|
|
404
|
+
deallocations: this._buildAutoDeallocs(
|
|
405
|
+
vaultData.accrualAdapters,
|
|
406
|
+
assetsEquivalent - vaultData.assetBalance,
|
|
407
|
+
shares
|
|
408
|
+
),
|
|
409
|
+
redeem: { shares: sharesRaw },
|
|
410
|
+
userAddress: from
|
|
411
|
+
}).buildTx();
|
|
412
|
+
return { to: tx.to, data: tx.data, value: tx.value, chainId: this.config.chainId };
|
|
413
|
+
}
|
|
306
414
|
// ─── Private operations ──────────────────────────────────────────────────
|
|
307
415
|
async _deposit(params) {
|
|
308
416
|
const {
|
|
@@ -390,7 +498,15 @@ var MorphoService = class {
|
|
|
390
498
|
const vaultData = await vault.getData();
|
|
391
499
|
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn withdraw")));
|
|
392
500
|
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
393
|
-
const tx = vault.withdraw({ amount: assetsRaw, userAddress: from }).buildTx()
|
|
501
|
+
const tx = vaultData.assetBalance >= assetsRaw ? vault.withdraw({ amount: assetsRaw, userAddress: from }).buildTx() : vault.forceWithdraw({
|
|
502
|
+
deallocations: this._buildAutoDeallocs(
|
|
503
|
+
vaultData.accrualAdapters,
|
|
504
|
+
assetsRaw - vaultData.assetBalance,
|
|
505
|
+
amount
|
|
506
|
+
),
|
|
507
|
+
withdraw: { amount: assetsRaw },
|
|
508
|
+
userAddress: from
|
|
509
|
+
}).buildTx();
|
|
394
510
|
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
395
511
|
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
396
512
|
if (receipt.status === "reverted") {
|
|
@@ -416,7 +532,7 @@ var MorphoService = class {
|
|
|
416
532
|
abi: _viem.erc20Abi,
|
|
417
533
|
functionName: "decimals"
|
|
418
534
|
});
|
|
419
|
-
} catch (
|
|
535
|
+
} catch (e3) {
|
|
420
536
|
throw new StableValidationError(
|
|
421
537
|
"shareDecimals",
|
|
422
538
|
void 0,
|
|
@@ -426,7 +542,17 @@ var MorphoService = class {
|
|
|
426
542
|
}
|
|
427
543
|
const sharesRaw = _viem.parseUnits.call(void 0, String(shares), decimals);
|
|
428
544
|
const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
|
|
429
|
-
const
|
|
545
|
+
const vaultData = await vault.getData();
|
|
546
|
+
const assetsEquivalent = vaultData.toAssets(sharesRaw);
|
|
547
|
+
const tx = vaultData.assetBalance >= assetsEquivalent ? vault.redeem({ shares: sharesRaw, userAddress: from }).buildTx() : vault.forceRedeem({
|
|
548
|
+
deallocations: this._buildAutoDeallocs(
|
|
549
|
+
vaultData.accrualAdapters,
|
|
550
|
+
assetsEquivalent - vaultData.assetBalance,
|
|
551
|
+
shares
|
|
552
|
+
),
|
|
553
|
+
redeem: { shares: sharesRaw },
|
|
554
|
+
userAddress: from
|
|
555
|
+
}).buildTx();
|
|
430
556
|
const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
|
|
431
557
|
const receipt = await this.pc.waitForTransactionReceipt({ hash });
|
|
432
558
|
if (receipt.status === "reverted") {
|
|
@@ -490,7 +616,7 @@ var MorphoService = class {
|
|
|
490
616
|
abi: _viem.erc20Abi,
|
|
491
617
|
functionName: "decimals"
|
|
492
618
|
});
|
|
493
|
-
} catch (
|
|
619
|
+
} catch (e4) {
|
|
494
620
|
throw new StableValidationError(
|
|
495
621
|
"shareDecimals",
|
|
496
622
|
void 0,
|
|
@@ -523,6 +649,51 @@ var MorphoService = class {
|
|
|
523
649
|
}
|
|
524
650
|
return { txHash: receipt.transactionHash, idempotencyKey };
|
|
525
651
|
}
|
|
652
|
+
// Automatically derives the minimum set of adapter deallocations needed to cover `deficit`
|
|
653
|
+
// when the vault has insufficient idle cash for a direct withdraw/redeem. Adds a 0.5%
|
|
654
|
+
// buffer to account for interest accrual between vaultData read and tx execution.
|
|
655
|
+
_buildAutoDeallocs(adapters, deficit, amount) {
|
|
656
|
+
const needed = deficit + deficit * 50n / 10000n;
|
|
657
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
658
|
+
const result = [];
|
|
659
|
+
let remaining = needed;
|
|
660
|
+
for (const adapter of adapters) {
|
|
661
|
+
if (remaining === 0n) break;
|
|
662
|
+
remaining = this._pullFromAdapter(adapter, remaining, now, result);
|
|
663
|
+
}
|
|
664
|
+
if (remaining > 0n) {
|
|
665
|
+
throw new StableValidationError(
|
|
666
|
+
"amount",
|
|
667
|
+
amount,
|
|
668
|
+
"earn withdraw: insufficient adapter liquidity \u2014 reduce the amount or wait for market liquidity to free up"
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
return result;
|
|
672
|
+
}
|
|
673
|
+
_pullFromAdapter(adapter, remaining, now, result) {
|
|
674
|
+
if (adapter.type !== "VaultV2MorphoMarketV1AdapterV2") {
|
|
675
|
+
const available = adapter.realAssets(now);
|
|
676
|
+
if (available === 0n) return remaining;
|
|
677
|
+
const pull = available < remaining ? available : remaining;
|
|
678
|
+
result.push({ adapter: adapter.address, amount: pull });
|
|
679
|
+
return remaining - pull;
|
|
680
|
+
}
|
|
681
|
+
const a = adapter;
|
|
682
|
+
return this._pullFromV2Markets(adapter.address, a.markets, a.supplyShares, remaining, result);
|
|
683
|
+
}
|
|
684
|
+
_pullFromV2Markets(adapterAddress, markets, supplyShares, remaining, result) {
|
|
685
|
+
let rem = remaining;
|
|
686
|
+
for (const mkt of markets) {
|
|
687
|
+
if (rem === 0n) break;
|
|
688
|
+
const adapterShares = _nullishCoalesce(supplyShares[mkt.id], () => ( 0n));
|
|
689
|
+
const available = mkt.totalSupplyShares > 0n ? adapterShares * mkt.totalSupplyAssets / mkt.totalSupplyShares : 0n;
|
|
690
|
+
if (available === 0n) continue;
|
|
691
|
+
const pull = available < rem ? available : rem;
|
|
692
|
+
result.push({ adapter: adapterAddress, marketParams: mkt.params, amount: pull });
|
|
693
|
+
rem -= pull;
|
|
694
|
+
}
|
|
695
|
+
return rem;
|
|
696
|
+
}
|
|
526
697
|
_buildDeallocs(deallocations, assetDecimals) {
|
|
527
698
|
return deallocations.map((d) => ({
|
|
528
699
|
adapter: d.adapter,
|
|
@@ -587,7 +758,7 @@ var MorphoService = class {
|
|
|
587
758
|
functionName: "decimals"
|
|
588
759
|
})
|
|
589
760
|
);
|
|
590
|
-
} catch (
|
|
761
|
+
} catch (e5) {
|
|
591
762
|
throw new StableValidationError(
|
|
592
763
|
"tokenDecimals",
|
|
593
764
|
void 0,
|
|
@@ -843,6 +1014,233 @@ var EarnReader = class {
|
|
|
843
1014
|
}
|
|
844
1015
|
};
|
|
845
1016
|
|
|
1017
|
+
// src/earn/merkl.ts
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
var MERKL_API_BASE_DEFAULT = "https://api.merkl.xyz";
|
|
1027
|
+
var DISTRIBUTOR_ADDRESS = "0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae";
|
|
1028
|
+
var DISTRIBUTOR_ABI = [
|
|
1029
|
+
{
|
|
1030
|
+
inputs: [
|
|
1031
|
+
{ name: "users", type: "address[]" },
|
|
1032
|
+
{ name: "tokens", type: "address[]" },
|
|
1033
|
+
{ name: "amounts", type: "uint256[]" },
|
|
1034
|
+
{ name: "proofs", type: "bytes32[][]" }
|
|
1035
|
+
],
|
|
1036
|
+
name: "claim",
|
|
1037
|
+
outputs: [],
|
|
1038
|
+
stateMutability: "nonpayable",
|
|
1039
|
+
type: "function"
|
|
1040
|
+
}
|
|
1041
|
+
];
|
|
1042
|
+
var MerklService = class {
|
|
1043
|
+
constructor(config) {
|
|
1044
|
+
this.config = config;
|
|
1045
|
+
}
|
|
1046
|
+
async fetchRewards(address) {
|
|
1047
|
+
if (!_viem.isAddress.call(void 0, address)) {
|
|
1048
|
+
throw new StableValidationError(
|
|
1049
|
+
"address",
|
|
1050
|
+
address,
|
|
1051
|
+
`merkl: invalid address "${address}"`
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
const base = (_nullishCoalesce(this.config.merklApiBase, () => ( MERKL_API_BASE_DEFAULT))).replace(/\/$/, "");
|
|
1055
|
+
const url = `${base}/v4/users/${address}/rewards?chainId=${this.config.chainId}`;
|
|
1056
|
+
const data = await this._fetch(url);
|
|
1057
|
+
return this._parseRewards(data);
|
|
1058
|
+
}
|
|
1059
|
+
async claim() {
|
|
1060
|
+
const address = this._requireFrom();
|
|
1061
|
+
const { rewards } = await this.fetchRewards(address);
|
|
1062
|
+
const claimable = rewards.filter(
|
|
1063
|
+
(r) => r.rawAmount !== "0" && r.proofs.length > 0
|
|
1064
|
+
);
|
|
1065
|
+
if (claimable.length === 0) {
|
|
1066
|
+
throw new StableValidationError(
|
|
1067
|
+
"rewards",
|
|
1068
|
+
[],
|
|
1069
|
+
rewards.length > 0 ? "merkl: rewards are pending the next merkle tree update \u2014 try again in ~2 hours" : "merkl: no rewards to claim on this chain"
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
const chain = makeChain(this.config.chainId, this.config.rpc);
|
|
1073
|
+
const wc = _nullishCoalesce(this.config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
1074
|
+
account: this.config.account,
|
|
1075
|
+
chain,
|
|
1076
|
+
transport: _nullishCoalesce(this.config.transport, () => ( _viem.http.call(void 0, this.config.rpc)))
|
|
1077
|
+
})));
|
|
1078
|
+
try {
|
|
1079
|
+
await wc.switchChain({ id: this.config.chainId });
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
throw new StableTransactionError({
|
|
1082
|
+
phase: "switch_chain",
|
|
1083
|
+
shortMessage: `merkl: wallet rejected or failed to switch to chain ${this.config.chainId}`,
|
|
1084
|
+
chainId: this.config.chainId,
|
|
1085
|
+
cause: err
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
const users = claimable.map(() => address);
|
|
1089
|
+
const tokens = claimable.map(
|
|
1090
|
+
(r) => r.token.address
|
|
1091
|
+
);
|
|
1092
|
+
const amounts = claimable.map((r) => BigInt(r.rawAmount));
|
|
1093
|
+
const proofs = claimable.map((r) => r.proofs);
|
|
1094
|
+
let hash;
|
|
1095
|
+
try {
|
|
1096
|
+
hash = await wc.writeContract({
|
|
1097
|
+
address: DISTRIBUTOR_ADDRESS,
|
|
1098
|
+
abi: DISTRIBUTOR_ABI,
|
|
1099
|
+
functionName: "claim",
|
|
1100
|
+
args: [users, tokens, amounts, proofs],
|
|
1101
|
+
account: wc.account,
|
|
1102
|
+
chain
|
|
1103
|
+
});
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
throw new StableTransactionError({
|
|
1106
|
+
phase: "send",
|
|
1107
|
+
shortMessage: "merkl: claim transaction failed",
|
|
1108
|
+
chainId: this.config.chainId,
|
|
1109
|
+
cause: err
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
const pc = _viem.createPublicClient.call(void 0, {
|
|
1113
|
+
chain,
|
|
1114
|
+
transport: _viem.http.call(void 0, this.config.rpc)
|
|
1115
|
+
});
|
|
1116
|
+
const receipt = await pc.waitForTransactionReceipt({ hash });
|
|
1117
|
+
if (receipt.status === "reverted") {
|
|
1118
|
+
throw new StableTransactionError({
|
|
1119
|
+
phase: "receipt",
|
|
1120
|
+
shortMessage: "merkl: claim transaction reverted on-chain",
|
|
1121
|
+
txHash: receipt.transactionHash,
|
|
1122
|
+
chainId: this.config.chainId
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
txHash: receipt.transactionHash,
|
|
1127
|
+
tokenCount: claimable.length
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
async prepareClaimCalldata(address) {
|
|
1131
|
+
if (!_viem.isAddress.call(void 0, address)) {
|
|
1132
|
+
throw new StableValidationError(
|
|
1133
|
+
"address",
|
|
1134
|
+
address,
|
|
1135
|
+
`merkl: invalid address "${address}"`
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
const { rewards } = await this.fetchRewards(address);
|
|
1139
|
+
const claimable = rewards.filter(
|
|
1140
|
+
(r) => r.rawAmount !== "0" && r.proofs.length > 0
|
|
1141
|
+
);
|
|
1142
|
+
if (claimable.length === 0) {
|
|
1143
|
+
throw new StableValidationError(
|
|
1144
|
+
"rewards",
|
|
1145
|
+
[],
|
|
1146
|
+
rewards.length > 0 ? "merkl: rewards are pending the next merkle tree update \u2014 try again in ~2 hours" : "merkl: no rewards to claim on this chain"
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
const users = claimable.map(() => address);
|
|
1150
|
+
const tokens = claimable.map((r) => r.token.address);
|
|
1151
|
+
const amounts = claimable.map((r) => BigInt(r.rawAmount));
|
|
1152
|
+
const proofs = claimable.map((r) => r.proofs);
|
|
1153
|
+
const data = _viem.encodeFunctionData.call(void 0, {
|
|
1154
|
+
abi: DISTRIBUTOR_ABI,
|
|
1155
|
+
functionName: "claim",
|
|
1156
|
+
args: [users, tokens, amounts, proofs]
|
|
1157
|
+
});
|
|
1158
|
+
return {
|
|
1159
|
+
to: DISTRIBUTOR_ADDRESS,
|
|
1160
|
+
data,
|
|
1161
|
+
chainId: this.config.chainId,
|
|
1162
|
+
rewards: claimable
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
_requireFrom() {
|
|
1166
|
+
const from = _nullishCoalesce(_optionalChain([this, 'access', _12 => _12.config, 'access', _13 => _13.walletClient, 'optionalAccess', _14 => _14.account, 'optionalAccess', _15 => _15.address]), () => ( _optionalChain([this, 'access', _16 => _16.config, 'access', _17 => _17.account, 'optionalAccess', _18 => _18.address])));
|
|
1167
|
+
if (!from) {
|
|
1168
|
+
throw new StableValidationError(
|
|
1169
|
+
"signer",
|
|
1170
|
+
void 0,
|
|
1171
|
+
"merkl.claim: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
return from;
|
|
1175
|
+
}
|
|
1176
|
+
_parseRewards(data) {
|
|
1177
|
+
const { chainId } = this.config;
|
|
1178
|
+
const rewards = [];
|
|
1179
|
+
const entries = Array.isArray(data) ? data : [data];
|
|
1180
|
+
for (const entry of entries) {
|
|
1181
|
+
if (_optionalChain([entry, 'optionalAccess', _19 => _19.chain, 'optionalAccess', _20 => _20.id]) !== chainId) continue;
|
|
1182
|
+
for (const reward of _nullishCoalesce(entry.rewards, () => ( []))) {
|
|
1183
|
+
const parsed = this._parseReward(reward);
|
|
1184
|
+
if (parsed) rewards.push(parsed);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return { chainId, rewards };
|
|
1188
|
+
}
|
|
1189
|
+
_parseReward(reward) {
|
|
1190
|
+
if (!_optionalChain([reward, 'optionalAccess', _21 => _21.token, 'optionalAccess', _22 => _22.address])) return null;
|
|
1191
|
+
const net = BigInt(_nullishCoalesce(reward.amount, () => ( "0"))) - BigInt(_nullishCoalesce(reward.claimed, () => ( "0")));
|
|
1192
|
+
if (net <= 0n) return null;
|
|
1193
|
+
const rawAmount = net.toString();
|
|
1194
|
+
const decimals = _nullishCoalesce(reward.token.decimals, () => ( 18));
|
|
1195
|
+
return {
|
|
1196
|
+
token: {
|
|
1197
|
+
address: reward.token.address,
|
|
1198
|
+
symbol: _nullishCoalesce(reward.token.symbol, () => ( "")),
|
|
1199
|
+
decimals
|
|
1200
|
+
},
|
|
1201
|
+
rawAmount,
|
|
1202
|
+
amount: Number(_viem.formatUnits.call(void 0, BigInt(rawAmount), decimals)),
|
|
1203
|
+
proofs: _nullishCoalesce(reward.proofs, () => ( []))
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
async _fetch(url) {
|
|
1207
|
+
try {
|
|
1208
|
+
return await retryWithBackoff(() => this._fetchOnce(url));
|
|
1209
|
+
} catch (err) {
|
|
1210
|
+
if (err instanceof StableNetworkError) throw err;
|
|
1211
|
+
if (err instanceof HttpError) {
|
|
1212
|
+
throw new StableNetworkError({
|
|
1213
|
+
shortMessage: `merkl: request failed \u2014 ${err.message}`,
|
|
1214
|
+
url,
|
|
1215
|
+
cause: err
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
throw new StableNetworkError({
|
|
1219
|
+
shortMessage: "merkl: request failed before reaching the server",
|
|
1220
|
+
url,
|
|
1221
|
+
cause: err
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
async _fetchOnce(url) {
|
|
1226
|
+
const res = await fetch(url);
|
|
1227
|
+
if (!res.ok) {
|
|
1228
|
+
if (res.status === 429 || res.status >= 500) {
|
|
1229
|
+
throw new HttpError(
|
|
1230
|
+
`merkl: ${res.statusText}`,
|
|
1231
|
+
res.status,
|
|
1232
|
+
true
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
throw new StableNetworkError({
|
|
1236
|
+
shortMessage: `merkl: API error ${res.status} ${res.statusText}`,
|
|
1237
|
+
url
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
return res.json();
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
|
|
846
1244
|
// src/services/lifi.ts
|
|
847
1245
|
|
|
848
1246
|
|
|
@@ -978,7 +1376,7 @@ var LiFiService = class {
|
|
|
978
1376
|
fromDecimals = 6,
|
|
979
1377
|
recipient
|
|
980
1378
|
} = params;
|
|
981
|
-
const from = _nullishCoalesce(_nullishCoalesce(_optionalChain([this, 'access',
|
|
1379
|
+
const from = _nullishCoalesce(_nullishCoalesce(_optionalChain([this, 'access', _23 => _23.config, 'access', _24 => _24.walletClient, 'optionalAccess', _25 => _25.account, 'optionalAccess', _26 => _26.address]), () => ( _optionalChain([this, 'access', _27 => _27.config, 'access', _28 => _28.account, 'optionalAccess', _29 => _29.address]))), () => ( "0x0000000000000000000000000000000000000000"));
|
|
982
1380
|
const src = CHAIN_CONFIGS[fromChain];
|
|
983
1381
|
const dst = CHAIN_CONFIGS[toChain];
|
|
984
1382
|
if (!src)
|
|
@@ -1136,7 +1534,7 @@ var LiFiService = class {
|
|
|
1136
1534
|
}
|
|
1137
1535
|
}
|
|
1138
1536
|
_requireFrom() {
|
|
1139
|
-
const from = _nullishCoalesce(_optionalChain([this, 'access',
|
|
1537
|
+
const from = _nullishCoalesce(_optionalChain([this, 'access', _30 => _30.config, 'access', _31 => _31.walletClient, 'optionalAccess', _32 => _32.account, 'optionalAccess', _33 => _33.address]), () => ( _optionalChain([this, 'access', _34 => _34.config, 'access', _35 => _35.account, 'optionalAccess', _36 => _36.address])));
|
|
1140
1538
|
if (!from) {
|
|
1141
1539
|
throw new StableValidationError(
|
|
1142
1540
|
"signer",
|
|
@@ -1369,7 +1767,7 @@ async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
|
|
|
1369
1767
|
abi: _viem.erc20Abi,
|
|
1370
1768
|
functionName: "decimals"
|
|
1371
1769
|
});
|
|
1372
|
-
} catch (
|
|
1770
|
+
} catch (e6) {
|
|
1373
1771
|
throw new StableValidationError(
|
|
1374
1772
|
"tokenDecimals",
|
|
1375
1773
|
void 0,
|
|
@@ -1406,7 +1804,8 @@ function createStable({
|
|
|
1406
1804
|
account,
|
|
1407
1805
|
transport,
|
|
1408
1806
|
walletClient,
|
|
1409
|
-
earn: earnConfig
|
|
1807
|
+
earn: earnConfig,
|
|
1808
|
+
merklApiBase
|
|
1410
1809
|
}) {
|
|
1411
1810
|
const chain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
|
|
1412
1811
|
const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(
|
|
@@ -1415,6 +1814,7 @@ function createStable({
|
|
|
1415
1814
|
);
|
|
1416
1815
|
const signerConfig = { account, transport, walletClient };
|
|
1417
1816
|
const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
|
|
1817
|
+
const merkl = new MerklService({ ...signerConfig, rpc, chainId: chain.id, merklApiBase });
|
|
1418
1818
|
const morpho = earnConfig ? new MorphoService({
|
|
1419
1819
|
...signerConfig,
|
|
1420
1820
|
rpc,
|
|
@@ -1431,10 +1831,21 @@ function createStable({
|
|
|
1431
1831
|
}
|
|
1432
1832
|
return morpho;
|
|
1433
1833
|
}
|
|
1834
|
+
function requireSignerAddress() {
|
|
1835
|
+
const from = _nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _37 => _37.account, 'optionalAccess', _38 => _38.address]), () => ( _optionalChain([account, 'optionalAccess', _39 => _39.address])));
|
|
1836
|
+
if (!from) {
|
|
1837
|
+
throw new StableValidationError(
|
|
1838
|
+
"signer",
|
|
1839
|
+
void 0,
|
|
1840
|
+
"stable.rewards.*: requires a signer \u2014 set walletClient or account in StableConfig"
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
return from;
|
|
1844
|
+
}
|
|
1434
1845
|
async function transfer(params) {
|
|
1435
1846
|
validateTransferParams(params);
|
|
1436
1847
|
const { from, to, amount, token, tokenDecimals } = params;
|
|
1437
|
-
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess',
|
|
1848
|
+
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _40 => _40.account]), () => ( account)), () => ( from));
|
|
1438
1849
|
const wc = _nullishCoalesce(walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
1439
1850
|
account: acc,
|
|
1440
1851
|
chain,
|
|
@@ -1497,25 +1908,38 @@ function createStable({
|
|
|
1497
1908
|
bulkWithdraw: (items, options) => requireEarn().bulkWithdraw(items, options),
|
|
1498
1909
|
redeem: (params) => requireEarn().redeem(params),
|
|
1499
1910
|
forceWithdraw: (params) => requireEarn().forceWithdraw(params),
|
|
1500
|
-
forceRedeem: (params) => requireEarn().forceRedeem(params)
|
|
1911
|
+
forceRedeem: (params) => requireEarn().forceRedeem(params),
|
|
1912
|
+
prepareDepositCalldata: (params) => requireEarn().prepareDepositCalldata(params),
|
|
1913
|
+
prepareWithdrawCalldata: (params) => requireEarn().prepareWithdrawCalldata(params),
|
|
1914
|
+
prepareRedeemCalldata: (params) => requireEarn().prepareRedeemCalldata(params),
|
|
1915
|
+
incentiveRewards: {
|
|
1916
|
+
fetch: () => merkl.fetchRewards(requireSignerAddress()),
|
|
1917
|
+
claim: () => merkl.claim()
|
|
1918
|
+
}
|
|
1501
1919
|
}
|
|
1502
1920
|
};
|
|
1503
1921
|
}
|
|
1504
1922
|
function createStableReader(config) {
|
|
1505
1923
|
const reader = new EarnReader(config);
|
|
1924
|
+
const chain = config.network === "testnet" /* Testnet */ ? _chains.stableTestnet : _chains.stable;
|
|
1925
|
+
const rpc = (_nullishCoalesce(config.rpc, () => ( chain.rpcUrls.default.http[0]))).replace(/\/$/, "");
|
|
1926
|
+
const merkl = new MerklService({ chainId: chain.id, rpc, merklApiBase: config.merklApiBase });
|
|
1506
1927
|
return {
|
|
1507
1928
|
earn: {
|
|
1508
1929
|
getYield: () => reader.getYield(),
|
|
1509
1930
|
position: () => reader.position(),
|
|
1510
1931
|
preview: (params) => reader.preview(params),
|
|
1511
|
-
withdrawability: (params) => reader.withdrawability(params)
|
|
1932
|
+
withdrawability: (params) => reader.withdrawability(params),
|
|
1933
|
+
incentiveRewards: {
|
|
1934
|
+
fetch: () => merkl.fetchRewards(config.address)
|
|
1935
|
+
}
|
|
1512
1936
|
}
|
|
1513
1937
|
};
|
|
1514
1938
|
}
|
|
1515
1939
|
|
|
1516
1940
|
// src/constants.ts
|
|
1517
1941
|
var STABLE_USDT_ADDRESS = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
|
|
1518
|
-
var STABLE_VAULT_ADDRESS = "
|
|
1942
|
+
var STABLE_VAULT_ADDRESS = "0xb7Df8db22A5DBBFA9ebeb94b3910aec6a4f05c08";
|
|
1519
1943
|
|
|
1520
1944
|
|
|
1521
1945
|
|