@stablechain/sdk 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +80 -13
- package/dist/index.d.cts +39 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +75 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -223,6 +223,9 @@ var EarnReader = class {
|
|
|
223
223
|
assetAddress: vaultData.asset
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
|
+
async getYield() {
|
|
227
|
+
return this._fetchYield();
|
|
228
|
+
}
|
|
226
229
|
async preview(params) {
|
|
227
230
|
const { amount, horizonDays } = params;
|
|
228
231
|
const apy = await this._fetchNetApy();
|
|
@@ -249,10 +252,21 @@ var EarnReader = class {
|
|
|
249
252
|
};
|
|
250
253
|
}
|
|
251
254
|
async _fetchNetApy() {
|
|
255
|
+
const { apy } = await this._fetchYield();
|
|
256
|
+
return apy;
|
|
257
|
+
}
|
|
258
|
+
async _fetchYield() {
|
|
252
259
|
const query = `
|
|
253
|
-
query
|
|
260
|
+
query VaultYield($address: String!, $chainId: Int!) {
|
|
254
261
|
vaultV2ByAddress(address: $address, chainId: $chainId) {
|
|
255
262
|
netApy
|
|
263
|
+
netApyExcludingRewards
|
|
264
|
+
performanceFee
|
|
265
|
+
managementFee
|
|
266
|
+
rewards {
|
|
267
|
+
asset { symbol address }
|
|
268
|
+
supplyApr
|
|
269
|
+
}
|
|
256
270
|
}
|
|
257
271
|
}
|
|
258
272
|
`;
|
|
@@ -271,7 +285,7 @@ var EarnReader = class {
|
|
|
271
285
|
if (!res.ok) {
|
|
272
286
|
const retryable = res.status === 429 || res.status >= 500;
|
|
273
287
|
throw new HttpError(
|
|
274
|
-
`earn
|
|
288
|
+
`earn: Morpho API returned ${res.status} \u2014 ${res.statusText}`,
|
|
275
289
|
res.status,
|
|
276
290
|
retryable
|
|
277
291
|
);
|
|
@@ -283,22 +297,29 @@ var EarnReader = class {
|
|
|
283
297
|
} catch (err) {
|
|
284
298
|
if (err instanceof TypeError || err instanceof HttpError) {
|
|
285
299
|
throw new StableNetworkError({
|
|
286
|
-
shortMessage: err instanceof HttpError ? `earn
|
|
300
|
+
shortMessage: err instanceof HttpError ? `earn: Morpho API returned ${err.status} after retries` : `earn: failed to reach Morpho API after retries \u2014 ${String(err)}`,
|
|
287
301
|
url: MORPHO_API_URL,
|
|
288
302
|
cause: err
|
|
289
303
|
});
|
|
290
304
|
}
|
|
291
305
|
throw err;
|
|
292
306
|
}
|
|
293
|
-
const
|
|
294
|
-
if (typeof netApy !== "number") {
|
|
307
|
+
const vault = _optionalChain([body, 'optionalAccess', _4 => _4.data, 'optionalAccess', _5 => _5.vaultV2ByAddress]);
|
|
308
|
+
if (!vault || typeof vault.netApy !== "number") {
|
|
295
309
|
throw new StableValidationError(
|
|
296
310
|
"apy",
|
|
297
311
|
void 0,
|
|
298
|
-
"earn
|
|
312
|
+
"earn: vault not found in Morpho API or unexpected response shape"
|
|
299
313
|
);
|
|
300
314
|
}
|
|
301
|
-
|
|
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
|
|
322
|
+
};
|
|
302
323
|
}
|
|
303
324
|
};
|
|
304
325
|
|
|
@@ -416,7 +437,7 @@ var LiFiService = class {
|
|
|
416
437
|
async quoteBridge(params) {
|
|
417
438
|
this._validateBridgeParams(params);
|
|
418
439
|
const { fromChain, toChain, fromToken, toToken, amount, fromDecimals = 6, recipient } = params;
|
|
419
|
-
const from = _nullishCoalesce(_nullishCoalesce(_optionalChain([this, 'access',
|
|
440
|
+
const from = _nullishCoalesce(_nullishCoalesce(_optionalChain([this, 'access', _6 => _6.config, 'access', _7 => _7.walletClient, 'optionalAccess', _8 => _8.account, 'optionalAccess', _9 => _9.address]), () => ( _optionalChain([this, 'access', _10 => _10.config, 'access', _11 => _11.account, 'optionalAccess', _12 => _12.address]))), () => ( "0x0000000000000000000000000000000000000000"));
|
|
420
441
|
const src = CHAIN_CONFIGS[fromChain];
|
|
421
442
|
const dst = CHAIN_CONFIGS[toChain];
|
|
422
443
|
if (!src) throw new StableValidationError("fromChain", fromChain, `LI.FI bridge: unsupported source chain "${fromChain}"`);
|
|
@@ -493,7 +514,7 @@ var LiFiService = class {
|
|
|
493
514
|
}
|
|
494
515
|
}
|
|
495
516
|
_requireFrom() {
|
|
496
|
-
const from = _nullishCoalesce(_optionalChain([this, 'access',
|
|
517
|
+
const from = _nullishCoalesce(_optionalChain([this, 'access', _13 => _13.config, 'access', _14 => _14.walletClient, 'optionalAccess', _15 => _15.account, 'optionalAccess', _16 => _16.address]), () => ( _optionalChain([this, 'access', _17 => _17.config, 'access', _18 => _18.account, 'optionalAccess', _19 => _19.address])));
|
|
497
518
|
if (!from) {
|
|
498
519
|
throw new StableValidationError(
|
|
499
520
|
"signer",
|
|
@@ -675,6 +696,7 @@ function isLiFiQuoteShape(v) {
|
|
|
675
696
|
|
|
676
697
|
|
|
677
698
|
|
|
699
|
+
|
|
678
700
|
var MorphoService = class {
|
|
679
701
|
constructor(config) {
|
|
680
702
|
this.config = config;
|
|
@@ -685,7 +707,7 @@ var MorphoService = class {
|
|
|
685
707
|
chain: this.chain,
|
|
686
708
|
transport: _nullishCoalesce(config.transport, () => ( _viem.http.call(void 0, config.rpc)))
|
|
687
709
|
})));
|
|
688
|
-
const morphoWc = _optionalChain([this, 'access',
|
|
710
|
+
const morphoWc = _optionalChain([this, 'access', _20 => _20.wc, 'access', _21 => _21.chain, 'optionalAccess', _22 => _22.id]) === config.chainId ? this.wc : _viem.createWalletClient.call(void 0, {
|
|
689
711
|
account: this.wc.account,
|
|
690
712
|
chain: this.chain,
|
|
691
713
|
transport: _viem.custom.call(void 0, { request: this.wc.request.bind(this.wc) })
|
|
@@ -695,6 +717,9 @@ var MorphoService = class {
|
|
|
695
717
|
}
|
|
696
718
|
deposit(params) {
|
|
697
719
|
this._validateAmount(params.amount, "earn deposit");
|
|
720
|
+
if (params.depositBuffer !== void 0 && (params.depositBuffer <= 0 || params.depositBuffer > 1)) {
|
|
721
|
+
throw new StableValidationError("depositBuffer", params.depositBuffer, "earn deposit: depositBuffer must be > 0 and \u2264 1");
|
|
722
|
+
}
|
|
698
723
|
return this._withIdempotency(params.idempotencyKey, () => this._deposit(params));
|
|
699
724
|
}
|
|
700
725
|
async bulkDeposit(items, options = {}) {
|
|
@@ -724,6 +749,33 @@ var MorphoService = class {
|
|
|
724
749
|
};
|
|
725
750
|
return this._withIdempotency(idempotencyKey, run);
|
|
726
751
|
}
|
|
752
|
+
async bulkWithdraw(items, options = {}) {
|
|
753
|
+
const { idempotencyKey, stopOnError = false } = options;
|
|
754
|
+
const run = async () => {
|
|
755
|
+
const results = [];
|
|
756
|
+
for (const item of items) {
|
|
757
|
+
try {
|
|
758
|
+
const result = await this.withdraw(item);
|
|
759
|
+
results.push({ status: "fulfilled", txHash: result.txHash, idempotencyKey: item.idempotencyKey });
|
|
760
|
+
} catch (err) {
|
|
761
|
+
const entry = { status: "rejected", error: String(err), idempotencyKey: item.idempotencyKey };
|
|
762
|
+
results.push(entry);
|
|
763
|
+
if (stopOnError) {
|
|
764
|
+
const succeeded2 = results.filter((r) => r.status === "fulfilled").length;
|
|
765
|
+
throw new StableBulkError({
|
|
766
|
+
shortMessage: "bulkWithdraw halted on first failure",
|
|
767
|
+
results,
|
|
768
|
+
succeeded: succeeded2,
|
|
769
|
+
failed: results.length - succeeded2
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
775
|
+
return { results, succeeded, failed: results.length - succeeded, idempotencyKey };
|
|
776
|
+
};
|
|
777
|
+
return this._withIdempotency(idempotencyKey, run);
|
|
778
|
+
}
|
|
727
779
|
withdraw(params) {
|
|
728
780
|
this._validateAmount(params.amount, "earn withdraw");
|
|
729
781
|
return this._withIdempotency(params.idempotencyKey, () => this._withdraw(params));
|
|
@@ -742,7 +794,7 @@ var MorphoService = class {
|
|
|
742
794
|
}
|
|
743
795
|
// ─── Private operations ──────────────────────────────────────────────────
|
|
744
796
|
async _deposit(params) {
|
|
745
|
-
const { amount, tokenDecimals, slippageTolerance, idempotencyKey } = params;
|
|
797
|
+
const { amount, tokenDecimals, slippageTolerance, idempotencyKey, depositBuffer } = params;
|
|
746
798
|
const from = this._requireFrom();
|
|
747
799
|
const vaultAddr = _nullishCoalesce(params.vault, () => ( this.config.vault));
|
|
748
800
|
await this._switchChain();
|
|
@@ -750,6 +802,19 @@ var MorphoService = class {
|
|
|
750
802
|
const vaultData = await vault.getData();
|
|
751
803
|
const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn deposit")));
|
|
752
804
|
const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
|
|
805
|
+
if (depositBuffer !== void 0) {
|
|
806
|
+
const bps = BigInt(Math.round(depositBuffer * 1e4));
|
|
807
|
+
const maxAllowed = vaultData.assetBalance * bps / 10000n;
|
|
808
|
+
if (assetsRaw > maxAllowed) {
|
|
809
|
+
const maxFormatted = Number(_viem.formatUnits.call(void 0, maxAllowed, decimals));
|
|
810
|
+
const idleFormatted = Number(_viem.formatUnits.call(void 0, vaultData.assetBalance, decimals));
|
|
811
|
+
throw new StableValidationError(
|
|
812
|
+
"amount",
|
|
813
|
+
amount,
|
|
814
|
+
`earn deposit: amount ${amount} exceeds deposit buffer \u2014 max ${maxFormatted} (${depositBuffer * 100}% of ${idleFormatted} idle liquidity)`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
753
818
|
const depositAction = vault.deposit({
|
|
754
819
|
amount: assetsRaw,
|
|
755
820
|
userAddress: from,
|
|
@@ -920,7 +985,7 @@ var MorphoService = class {
|
|
|
920
985
|
}
|
|
921
986
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
922
987
|
_requireFrom() {
|
|
923
|
-
const from = _optionalChain([this, 'access',
|
|
988
|
+
const from = _optionalChain([this, 'access', _23 => _23.wc, 'access', _24 => _24.account, 'optionalAccess', _25 => _25.address]);
|
|
924
989
|
if (!from) {
|
|
925
990
|
throw new StableValidationError(
|
|
926
991
|
"signer",
|
|
@@ -1061,7 +1126,7 @@ function createStable({
|
|
|
1061
1126
|
async function transfer(params) {
|
|
1062
1127
|
validateTransferParams(params);
|
|
1063
1128
|
const { from, to, amount, token, tokenDecimals } = params;
|
|
1064
|
-
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess',
|
|
1129
|
+
const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _26 => _26.account]), () => ( account)), () => ( from));
|
|
1065
1130
|
const wc = _nullishCoalesce(walletClient, () => ( _viem.createWalletClient.call(void 0, {
|
|
1066
1131
|
account: acc,
|
|
1067
1132
|
chain,
|
|
@@ -1100,6 +1165,7 @@ function createStable({
|
|
|
1100
1165
|
deposit: (params) => requireEarn().deposit(params),
|
|
1101
1166
|
bulkDeposit: (items, options) => requireEarn().bulkDeposit(items, options),
|
|
1102
1167
|
withdraw: (params) => requireEarn().withdraw(params),
|
|
1168
|
+
bulkWithdraw: (items, options) => requireEarn().bulkWithdraw(items, options),
|
|
1103
1169
|
redeem: (params) => requireEarn().redeem(params),
|
|
1104
1170
|
forceWithdraw: (params) => requireEarn().forceWithdraw(params),
|
|
1105
1171
|
forceRedeem: (params) => requireEarn().forceRedeem(params)
|
|
@@ -1110,6 +1176,7 @@ function createStableReader(config) {
|
|
|
1110
1176
|
const reader = new EarnReader(config);
|
|
1111
1177
|
return {
|
|
1112
1178
|
earn: {
|
|
1179
|
+
getYield: () => reader.getYield(),
|
|
1113
1180
|
position: () => reader.position(),
|
|
1114
1181
|
preview: (params) => reader.preview(params),
|
|
1115
1182
|
withdrawability: (params) => reader.withdrawability(params)
|
package/dist/index.d.cts
CHANGED
|
@@ -118,6 +118,12 @@ interface EarnDepositParams {
|
|
|
118
118
|
slippageTolerance?: number;
|
|
119
119
|
/** Deduplication key: if an operation with this key is already in flight, the same Promise is returned. */
|
|
120
120
|
idempotencyKey?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Maximum fraction of the vault's idle liquidity that this deposit may consume (e.g. 0.8 = 80%).
|
|
123
|
+
* The SDK throws StableValidationError before sending the transaction if the amount exceeds the cap.
|
|
124
|
+
* Must be > 0 and ≤ 1. Omit to disable the check.
|
|
125
|
+
*/
|
|
126
|
+
depositBuffer?: number;
|
|
121
127
|
}
|
|
122
128
|
interface EarnWithdrawParams {
|
|
123
129
|
/** Amount of underlying assets to withdraw (human-readable) */
|
|
@@ -167,6 +173,15 @@ interface BulkDepositOptions {
|
|
|
167
173
|
*/
|
|
168
174
|
stopOnError?: boolean;
|
|
169
175
|
}
|
|
176
|
+
interface BulkWithdrawOptions {
|
|
177
|
+
/** Batch-level deduplication key — if this batch is already in flight, the same Promise is returned. */
|
|
178
|
+
idempotencyKey?: string;
|
|
179
|
+
/**
|
|
180
|
+
* When true, the batch halts on the first failure and rejects.
|
|
181
|
+
* When false (default), all withdrawals are attempted and partial results are returned.
|
|
182
|
+
*/
|
|
183
|
+
stopOnError?: boolean;
|
|
184
|
+
}
|
|
170
185
|
/**
|
|
171
186
|
* A single adapter position to forcibly deallocate before a force withdraw/redeem.
|
|
172
187
|
* `amount` is in underlying asset units (human-readable).
|
|
@@ -206,6 +221,26 @@ interface ForceRedeemParams {
|
|
|
206
221
|
/** Deduplication key */
|
|
207
222
|
idempotencyKey?: string;
|
|
208
223
|
}
|
|
224
|
+
interface VaultYieldReward {
|
|
225
|
+
/** Reward token symbol (e.g. "MORPHO") */
|
|
226
|
+
symbol: string;
|
|
227
|
+
/** Reward token contract address */
|
|
228
|
+
address: string;
|
|
229
|
+
/** APR contribution from this reward token as a decimal (e.g. 0.02 = 2%) */
|
|
230
|
+
apr: number;
|
|
231
|
+
}
|
|
232
|
+
interface VaultYield {
|
|
233
|
+
/** Total net APY after all fees, including rewards, as a decimal (e.g. 0.05 = 5%) */
|
|
234
|
+
apy: number;
|
|
235
|
+
/** Native yield component — underlying market rates after fees, excluding reward boosts */
|
|
236
|
+
native: number;
|
|
237
|
+
/** Per-token reward boosts */
|
|
238
|
+
rewards: VaultYieldReward[];
|
|
239
|
+
/** Performance fee as a decimal (e.g. 0.1 = 10%) */
|
|
240
|
+
performanceFee: number;
|
|
241
|
+
/** Management fee as a decimal */
|
|
242
|
+
managementFee: number;
|
|
243
|
+
}
|
|
209
244
|
interface VaultPosition {
|
|
210
245
|
/** Raw vault share balance */
|
|
211
246
|
shares: bigint;
|
|
@@ -255,6 +290,7 @@ interface StableClient {
|
|
|
255
290
|
deposit(params: EarnDepositParams): Promise<EarnResult>;
|
|
256
291
|
bulkDeposit(items: EarnDepositParams[], options?: BulkDepositOptions): Promise<BulkEarnResult>;
|
|
257
292
|
withdraw(params: EarnWithdrawParams): Promise<EarnResult>;
|
|
293
|
+
bulkWithdraw(items: EarnWithdrawParams[], options?: BulkWithdrawOptions): Promise<BulkEarnResult>;
|
|
258
294
|
redeem(params: EarnRedeemParams): Promise<EarnResult>;
|
|
259
295
|
forceWithdraw(params: ForceWithdrawParams): Promise<EarnResult>;
|
|
260
296
|
forceRedeem(params: ForceRedeemParams): Promise<EarnResult>;
|
|
@@ -262,6 +298,8 @@ interface StableClient {
|
|
|
262
298
|
}
|
|
263
299
|
interface StableReader {
|
|
264
300
|
earn: {
|
|
301
|
+
/** Current net APY for the configured vault. */
|
|
302
|
+
getYield(): Promise<VaultYield>;
|
|
265
303
|
/** Current vault position (shares + asset value) for the configured user address. */
|
|
266
304
|
position(): Promise<VaultPosition>;
|
|
267
305
|
/** Projected yield over a given horizon, using Morpho's live net APY. */
|
|
@@ -348,4 +386,4 @@ declare class StableBulkError extends StableError {
|
|
|
348
386
|
});
|
|
349
387
|
}
|
|
350
388
|
|
|
351
|
-
export { type BridgeParams, type BridgeQuote, type BulkDepositOptions, type BulkEarnResult, type BulkEarnResultItem, CHAIN_CONFIGS, Chain, type ChainConfig, type EarnConfig, type EarnDepositParams, type EarnRedeemParams, type EarnResult, type EarnWithdrawParams, type ForceDeallocation, type ForceRedeemParams, type ForceWithdrawParams, Network, type OperationResult, STABLE_USDT_ADDRESS, STABLE_VAULT_ADDRESS, StableBulkError, type StableClient, type StableConfig, StableError, StableNetworkError, StableQuoteError, type StableReader, type StableReaderConfig, StableTransactionError, StableValidationError, type SwapParams, type SwapQuote, type TransferParams, type VaultPosition, type WithdrawStatus, type YieldPreview, createStable, createStableReader };
|
|
389
|
+
export { type BridgeParams, type BridgeQuote, type BulkDepositOptions, type BulkEarnResult, type BulkEarnResultItem, type BulkWithdrawOptions, CHAIN_CONFIGS, Chain, type ChainConfig, type EarnConfig, type EarnDepositParams, type EarnRedeemParams, type EarnResult, type EarnWithdrawParams, type ForceDeallocation, type ForceRedeemParams, type ForceWithdrawParams, Network, type OperationResult, STABLE_USDT_ADDRESS, STABLE_VAULT_ADDRESS, StableBulkError, type StableClient, type StableConfig, StableError, StableNetworkError, StableQuoteError, type StableReader, type StableReaderConfig, StableTransactionError, StableValidationError, type SwapParams, type SwapQuote, type TransferParams, type VaultPosition, type VaultYield, type VaultYieldReward, type WithdrawStatus, type YieldPreview, createStable, createStableReader };
|
package/dist/index.d.ts
CHANGED
|
@@ -118,6 +118,12 @@ interface EarnDepositParams {
|
|
|
118
118
|
slippageTolerance?: number;
|
|
119
119
|
/** Deduplication key: if an operation with this key is already in flight, the same Promise is returned. */
|
|
120
120
|
idempotencyKey?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Maximum fraction of the vault's idle liquidity that this deposit may consume (e.g. 0.8 = 80%).
|
|
123
|
+
* The SDK throws StableValidationError before sending the transaction if the amount exceeds the cap.
|
|
124
|
+
* Must be > 0 and ≤ 1. Omit to disable the check.
|
|
125
|
+
*/
|
|
126
|
+
depositBuffer?: number;
|
|
121
127
|
}
|
|
122
128
|
interface EarnWithdrawParams {
|
|
123
129
|
/** Amount of underlying assets to withdraw (human-readable) */
|
|
@@ -167,6 +173,15 @@ interface BulkDepositOptions {
|
|
|
167
173
|
*/
|
|
168
174
|
stopOnError?: boolean;
|
|
169
175
|
}
|
|
176
|
+
interface BulkWithdrawOptions {
|
|
177
|
+
/** Batch-level deduplication key — if this batch is already in flight, the same Promise is returned. */
|
|
178
|
+
idempotencyKey?: string;
|
|
179
|
+
/**
|
|
180
|
+
* When true, the batch halts on the first failure and rejects.
|
|
181
|
+
* When false (default), all withdrawals are attempted and partial results are returned.
|
|
182
|
+
*/
|
|
183
|
+
stopOnError?: boolean;
|
|
184
|
+
}
|
|
170
185
|
/**
|
|
171
186
|
* A single adapter position to forcibly deallocate before a force withdraw/redeem.
|
|
172
187
|
* `amount` is in underlying asset units (human-readable).
|
|
@@ -206,6 +221,26 @@ interface ForceRedeemParams {
|
|
|
206
221
|
/** Deduplication key */
|
|
207
222
|
idempotencyKey?: string;
|
|
208
223
|
}
|
|
224
|
+
interface VaultYieldReward {
|
|
225
|
+
/** Reward token symbol (e.g. "MORPHO") */
|
|
226
|
+
symbol: string;
|
|
227
|
+
/** Reward token contract address */
|
|
228
|
+
address: string;
|
|
229
|
+
/** APR contribution from this reward token as a decimal (e.g. 0.02 = 2%) */
|
|
230
|
+
apr: number;
|
|
231
|
+
}
|
|
232
|
+
interface VaultYield {
|
|
233
|
+
/** Total net APY after all fees, including rewards, as a decimal (e.g. 0.05 = 5%) */
|
|
234
|
+
apy: number;
|
|
235
|
+
/** Native yield component — underlying market rates after fees, excluding reward boosts */
|
|
236
|
+
native: number;
|
|
237
|
+
/** Per-token reward boosts */
|
|
238
|
+
rewards: VaultYieldReward[];
|
|
239
|
+
/** Performance fee as a decimal (e.g. 0.1 = 10%) */
|
|
240
|
+
performanceFee: number;
|
|
241
|
+
/** Management fee as a decimal */
|
|
242
|
+
managementFee: number;
|
|
243
|
+
}
|
|
209
244
|
interface VaultPosition {
|
|
210
245
|
/** Raw vault share balance */
|
|
211
246
|
shares: bigint;
|
|
@@ -255,6 +290,7 @@ interface StableClient {
|
|
|
255
290
|
deposit(params: EarnDepositParams): Promise<EarnResult>;
|
|
256
291
|
bulkDeposit(items: EarnDepositParams[], options?: BulkDepositOptions): Promise<BulkEarnResult>;
|
|
257
292
|
withdraw(params: EarnWithdrawParams): Promise<EarnResult>;
|
|
293
|
+
bulkWithdraw(items: EarnWithdrawParams[], options?: BulkWithdrawOptions): Promise<BulkEarnResult>;
|
|
258
294
|
redeem(params: EarnRedeemParams): Promise<EarnResult>;
|
|
259
295
|
forceWithdraw(params: ForceWithdrawParams): Promise<EarnResult>;
|
|
260
296
|
forceRedeem(params: ForceRedeemParams): Promise<EarnResult>;
|
|
@@ -262,6 +298,8 @@ interface StableClient {
|
|
|
262
298
|
}
|
|
263
299
|
interface StableReader {
|
|
264
300
|
earn: {
|
|
301
|
+
/** Current net APY for the configured vault. */
|
|
302
|
+
getYield(): Promise<VaultYield>;
|
|
265
303
|
/** Current vault position (shares + asset value) for the configured user address. */
|
|
266
304
|
position(): Promise<VaultPosition>;
|
|
267
305
|
/** Projected yield over a given horizon, using Morpho's live net APY. */
|
|
@@ -348,4 +386,4 @@ declare class StableBulkError extends StableError {
|
|
|
348
386
|
});
|
|
349
387
|
}
|
|
350
388
|
|
|
351
|
-
export { type BridgeParams, type BridgeQuote, type BulkDepositOptions, type BulkEarnResult, type BulkEarnResultItem, CHAIN_CONFIGS, Chain, type ChainConfig, type EarnConfig, type EarnDepositParams, type EarnRedeemParams, type EarnResult, type EarnWithdrawParams, type ForceDeallocation, type ForceRedeemParams, type ForceWithdrawParams, Network, type OperationResult, STABLE_USDT_ADDRESS, STABLE_VAULT_ADDRESS, StableBulkError, type StableClient, type StableConfig, StableError, StableNetworkError, StableQuoteError, type StableReader, type StableReaderConfig, StableTransactionError, StableValidationError, type SwapParams, type SwapQuote, type TransferParams, type VaultPosition, type WithdrawStatus, type YieldPreview, createStable, createStableReader };
|
|
389
|
+
export { type BridgeParams, type BridgeQuote, type BulkDepositOptions, type BulkEarnResult, type BulkEarnResultItem, type BulkWithdrawOptions, CHAIN_CONFIGS, Chain, type ChainConfig, type EarnConfig, type EarnDepositParams, type EarnRedeemParams, type EarnResult, type EarnWithdrawParams, type ForceDeallocation, type ForceRedeemParams, type ForceWithdrawParams, Network, type OperationResult, STABLE_USDT_ADDRESS, STABLE_VAULT_ADDRESS, StableBulkError, type StableClient, type StableConfig, StableError, StableNetworkError, StableQuoteError, type StableReader, type StableReaderConfig, StableTransactionError, StableValidationError, type SwapParams, type SwapQuote, type TransferParams, type VaultPosition, type VaultYield, type VaultYieldReward, type WithdrawStatus, type YieldPreview, createStable, createStableReader };
|
package/dist/index.js
CHANGED
|
@@ -223,6 +223,9 @@ var EarnReader = class {
|
|
|
223
223
|
assetAddress: vaultData.asset
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
|
+
async getYield() {
|
|
227
|
+
return this._fetchYield();
|
|
228
|
+
}
|
|
226
229
|
async preview(params) {
|
|
227
230
|
const { amount, horizonDays } = params;
|
|
228
231
|
const apy = await this._fetchNetApy();
|
|
@@ -249,10 +252,21 @@ var EarnReader = class {
|
|
|
249
252
|
};
|
|
250
253
|
}
|
|
251
254
|
async _fetchNetApy() {
|
|
255
|
+
const { apy } = await this._fetchYield();
|
|
256
|
+
return apy;
|
|
257
|
+
}
|
|
258
|
+
async _fetchYield() {
|
|
252
259
|
const query = `
|
|
253
|
-
query
|
|
260
|
+
query VaultYield($address: String!, $chainId: Int!) {
|
|
254
261
|
vaultV2ByAddress(address: $address, chainId: $chainId) {
|
|
255
262
|
netApy
|
|
263
|
+
netApyExcludingRewards
|
|
264
|
+
performanceFee
|
|
265
|
+
managementFee
|
|
266
|
+
rewards {
|
|
267
|
+
asset { symbol address }
|
|
268
|
+
supplyApr
|
|
269
|
+
}
|
|
256
270
|
}
|
|
257
271
|
}
|
|
258
272
|
`;
|
|
@@ -271,7 +285,7 @@ var EarnReader = class {
|
|
|
271
285
|
if (!res.ok) {
|
|
272
286
|
const retryable = res.status === 429 || res.status >= 500;
|
|
273
287
|
throw new HttpError(
|
|
274
|
-
`earn
|
|
288
|
+
`earn: Morpho API returned ${res.status} \u2014 ${res.statusText}`,
|
|
275
289
|
res.status,
|
|
276
290
|
retryable
|
|
277
291
|
);
|
|
@@ -283,22 +297,29 @@ var EarnReader = class {
|
|
|
283
297
|
} catch (err) {
|
|
284
298
|
if (err instanceof TypeError || err instanceof HttpError) {
|
|
285
299
|
throw new StableNetworkError({
|
|
286
|
-
shortMessage: err instanceof HttpError ? `earn
|
|
300
|
+
shortMessage: err instanceof HttpError ? `earn: Morpho API returned ${err.status} after retries` : `earn: failed to reach Morpho API after retries \u2014 ${String(err)}`,
|
|
287
301
|
url: MORPHO_API_URL,
|
|
288
302
|
cause: err
|
|
289
303
|
});
|
|
290
304
|
}
|
|
291
305
|
throw err;
|
|
292
306
|
}
|
|
293
|
-
const
|
|
294
|
-
if (typeof netApy !== "number") {
|
|
307
|
+
const vault = body?.data?.vaultV2ByAddress;
|
|
308
|
+
if (!vault || typeof vault.netApy !== "number") {
|
|
295
309
|
throw new StableValidationError(
|
|
296
310
|
"apy",
|
|
297
311
|
void 0,
|
|
298
|
-
"earn
|
|
312
|
+
"earn: vault not found in Morpho API or unexpected response shape"
|
|
299
313
|
);
|
|
300
314
|
}
|
|
301
|
-
|
|
315
|
+
const rewards = vault.rewards.filter((r) => typeof r.supplyApr === "number" && r.supplyApr > 0).map((r) => ({ symbol: r.asset.symbol, address: r.asset.address, apr: r.supplyApr }));
|
|
316
|
+
return {
|
|
317
|
+
apy: vault.netApy,
|
|
318
|
+
native: vault.netApyExcludingRewards ?? 0,
|
|
319
|
+
rewards,
|
|
320
|
+
performanceFee: vault.performanceFee,
|
|
321
|
+
managementFee: vault.managementFee
|
|
322
|
+
};
|
|
302
323
|
}
|
|
303
324
|
};
|
|
304
325
|
|
|
@@ -667,6 +688,7 @@ import {
|
|
|
667
688
|
createWalletClient as createWalletClient2,
|
|
668
689
|
custom,
|
|
669
690
|
erc20Abi as erc20Abi3,
|
|
691
|
+
formatUnits as formatUnits3,
|
|
670
692
|
http as http3,
|
|
671
693
|
parseUnits as parseUnits3
|
|
672
694
|
} from "viem";
|
|
@@ -695,6 +717,9 @@ var MorphoService = class {
|
|
|
695
717
|
}
|
|
696
718
|
deposit(params) {
|
|
697
719
|
this._validateAmount(params.amount, "earn deposit");
|
|
720
|
+
if (params.depositBuffer !== void 0 && (params.depositBuffer <= 0 || params.depositBuffer > 1)) {
|
|
721
|
+
throw new StableValidationError("depositBuffer", params.depositBuffer, "earn deposit: depositBuffer must be > 0 and \u2264 1");
|
|
722
|
+
}
|
|
698
723
|
return this._withIdempotency(params.idempotencyKey, () => this._deposit(params));
|
|
699
724
|
}
|
|
700
725
|
async bulkDeposit(items, options = {}) {
|
|
@@ -724,6 +749,33 @@ var MorphoService = class {
|
|
|
724
749
|
};
|
|
725
750
|
return this._withIdempotency(idempotencyKey, run);
|
|
726
751
|
}
|
|
752
|
+
async bulkWithdraw(items, options = {}) {
|
|
753
|
+
const { idempotencyKey, stopOnError = false } = options;
|
|
754
|
+
const run = async () => {
|
|
755
|
+
const results = [];
|
|
756
|
+
for (const item of items) {
|
|
757
|
+
try {
|
|
758
|
+
const result = await this.withdraw(item);
|
|
759
|
+
results.push({ status: "fulfilled", txHash: result.txHash, idempotencyKey: item.idempotencyKey });
|
|
760
|
+
} catch (err) {
|
|
761
|
+
const entry = { status: "rejected", error: String(err), idempotencyKey: item.idempotencyKey };
|
|
762
|
+
results.push(entry);
|
|
763
|
+
if (stopOnError) {
|
|
764
|
+
const succeeded2 = results.filter((r) => r.status === "fulfilled").length;
|
|
765
|
+
throw new StableBulkError({
|
|
766
|
+
shortMessage: "bulkWithdraw halted on first failure",
|
|
767
|
+
results,
|
|
768
|
+
succeeded: succeeded2,
|
|
769
|
+
failed: results.length - succeeded2
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
775
|
+
return { results, succeeded, failed: results.length - succeeded, idempotencyKey };
|
|
776
|
+
};
|
|
777
|
+
return this._withIdempotency(idempotencyKey, run);
|
|
778
|
+
}
|
|
727
779
|
withdraw(params) {
|
|
728
780
|
this._validateAmount(params.amount, "earn withdraw");
|
|
729
781
|
return this._withIdempotency(params.idempotencyKey, () => this._withdraw(params));
|
|
@@ -742,7 +794,7 @@ var MorphoService = class {
|
|
|
742
794
|
}
|
|
743
795
|
// ─── Private operations ──────────────────────────────────────────────────
|
|
744
796
|
async _deposit(params) {
|
|
745
|
-
const { amount, tokenDecimals, slippageTolerance, idempotencyKey } = params;
|
|
797
|
+
const { amount, tokenDecimals, slippageTolerance, idempotencyKey, depositBuffer } = params;
|
|
746
798
|
const from = this._requireFrom();
|
|
747
799
|
const vaultAddr = params.vault ?? this.config.vault;
|
|
748
800
|
await this._switchChain();
|
|
@@ -750,6 +802,19 @@ var MorphoService = class {
|
|
|
750
802
|
const vaultData = await vault.getData();
|
|
751
803
|
const decimals = tokenDecimals ?? await this._resolveDecimals(vaultData.asset, "earn deposit");
|
|
752
804
|
const assetsRaw = parseUnits3(String(amount), decimals);
|
|
805
|
+
if (depositBuffer !== void 0) {
|
|
806
|
+
const bps = BigInt(Math.round(depositBuffer * 1e4));
|
|
807
|
+
const maxAllowed = vaultData.assetBalance * bps / 10000n;
|
|
808
|
+
if (assetsRaw > maxAllowed) {
|
|
809
|
+
const maxFormatted = Number(formatUnits3(maxAllowed, decimals));
|
|
810
|
+
const idleFormatted = Number(formatUnits3(vaultData.assetBalance, decimals));
|
|
811
|
+
throw new StableValidationError(
|
|
812
|
+
"amount",
|
|
813
|
+
amount,
|
|
814
|
+
`earn deposit: amount ${amount} exceeds deposit buffer \u2014 max ${maxFormatted} (${depositBuffer * 100}% of ${idleFormatted} idle liquidity)`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
753
818
|
const depositAction = vault.deposit({
|
|
754
819
|
amount: assetsRaw,
|
|
755
820
|
userAddress: from,
|
|
@@ -1100,6 +1165,7 @@ function createStable({
|
|
|
1100
1165
|
deposit: (params) => requireEarn().deposit(params),
|
|
1101
1166
|
bulkDeposit: (items, options) => requireEarn().bulkDeposit(items, options),
|
|
1102
1167
|
withdraw: (params) => requireEarn().withdraw(params),
|
|
1168
|
+
bulkWithdraw: (items, options) => requireEarn().bulkWithdraw(items, options),
|
|
1103
1169
|
redeem: (params) => requireEarn().redeem(params),
|
|
1104
1170
|
forceWithdraw: (params) => requireEarn().forceWithdraw(params),
|
|
1105
1171
|
forceRedeem: (params) => requireEarn().forceRedeem(params)
|
|
@@ -1110,6 +1176,7 @@ function createStableReader(config) {
|
|
|
1110
1176
|
const reader = new EarnReader(config);
|
|
1111
1177
|
return {
|
|
1112
1178
|
earn: {
|
|
1179
|
+
getYield: () => reader.getYield(),
|
|
1113
1180
|
position: () => reader.position(),
|
|
1114
1181
|
preview: (params) => reader.preview(params),
|
|
1115
1182
|
withdrawability: (params) => reader.withdrawability(params)
|