@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 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 VaultNetApy($address: String!, $chainId: Int!) {
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 preview: Morpho API returned ${res.status} \u2014 ${res.statusText}`,
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 preview: Morpho API returned ${err.status} after retries` : `earn preview: failed to reach Morpho API after retries \u2014 ${String(err)}`,
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 netApy = _optionalChain([body, 'optionalAccess', _4 => _4.data, 'optionalAccess', _5 => _5.vaultV2ByAddress, 'optionalAccess', _6 => _6.netApy]);
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 preview: vault not found in Morpho API or unexpected response shape"
312
+ "earn: vault not found in Morpho API or unexpected response shape"
299
313
  );
300
314
  }
301
- return netApy;
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', _7 => _7.config, 'access', _8 => _8.walletClient, 'optionalAccess', _9 => _9.account, 'optionalAccess', _10 => _10.address]), () => ( _optionalChain([this, 'access', _11 => _11.config, 'access', _12 => _12.account, 'optionalAccess', _13 => _13.address]))), () => ( "0x0000000000000000000000000000000000000000"));
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', _14 => _14.config, 'access', _15 => _15.walletClient, 'optionalAccess', _16 => _16.account, 'optionalAccess', _17 => _17.address]), () => ( _optionalChain([this, 'access', _18 => _18.config, 'access', _19 => _19.account, 'optionalAccess', _20 => _20.address])));
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', _21 => _21.wc, 'access', _22 => _22.chain, 'optionalAccess', _23 => _23.id]) === config.chainId ? this.wc : _viem.createWalletClient.call(void 0, {
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', _24 => _24.wc, 'access', _25 => _25.account, 'optionalAccess', _26 => _26.address]);
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', _27 => _27.account]), () => ( account)), () => ( from));
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 VaultNetApy($address: String!, $chainId: Int!) {
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 preview: Morpho API returned ${res.status} \u2014 ${res.statusText}`,
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 preview: Morpho API returned ${err.status} after retries` : `earn preview: failed to reach Morpho API after retries \u2014 ${String(err)}`,
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 netApy = body?.data?.vaultV2ByAddress?.netApy;
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 preview: vault not found in Morpho API or unexpected response shape"
312
+ "earn: vault not found in Morpho API or unexpected response shape"
299
313
  );
300
314
  }
301
- return netApy;
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stablechain/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Stable SDK",
5
5
  "keywords": ["stable", "usdt", "gasless", "web3", "yield"],
6
6
  "license": "MIT",