@stablechain/sdk 0.1.1 → 1.0.0

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
@@ -9,6 +9,18 @@
9
9
  var _viem = require('viem');
10
10
  var _chains = require('viem/chains');
11
11
 
12
+ // src/earn/reader.ts
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+ var _morphosdk = require('@morpho-org/morpho-sdk');
23
+
12
24
  // src/errors.ts
13
25
 
14
26
  var StableError = class extends _viem.BaseError {
@@ -67,6 +79,14 @@ var StableNetworkError = class extends StableError {
67
79
  this.url = args.url;
68
80
  }
69
81
  };
82
+ var StableBulkError = class extends StableError {
83
+ constructor(args) {
84
+ super(args.shortMessage, { name: "StableBulkError" });
85
+ this.results = args.results;
86
+ this.succeeded = args.succeeded;
87
+ this.failed = args.failed;
88
+ }
89
+ };
70
90
  function formatValue(v) {
71
91
  if (typeof v === "bigint") return `${v}n`;
72
92
  if (typeof v === "string") return JSON.stringify(v);
@@ -79,17 +99,6 @@ function formatValue(v) {
79
99
  }
80
100
  }
81
101
 
82
- // src/services/lifi.ts
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
102
  // src/helpers/chain.ts
94
103
 
95
104
  function makeChain(id, rpc) {
@@ -154,6 +163,156 @@ var Network = /* @__PURE__ */ ((Network2) => {
154
163
  return Network2;
155
164
  })(Network || {});
156
165
 
166
+ // src/earn/reader.ts
167
+ var MORPHO_API_URL = "https://blue-api.morpho.org/graphql";
168
+ var EarnReader = class {
169
+ constructor(config) {
170
+ if (!_optionalChain([config, 'access', _2 => _2.earn, 'optionalAccess', _3 => _3.vault])) {
171
+ throw new StableValidationError(
172
+ "earn.vault",
173
+ void 0,
174
+ "createStableReader: earn.vault is required"
175
+ );
176
+ }
177
+ if (!_viem.isAddress.call(void 0, config.address)) {
178
+ throw new StableValidationError(
179
+ "address",
180
+ config.address,
181
+ `createStableReader: invalid user address "${config.address}"`
182
+ );
183
+ }
184
+ if (!_viem.isAddress.call(void 0, config.earn.vault)) {
185
+ throw new StableValidationError(
186
+ "earn.vault",
187
+ config.earn.vault,
188
+ `createStableReader: invalid vault address "${config.earn.vault}"`
189
+ );
190
+ }
191
+ const network = _nullishCoalesce(config.network, () => ( "mainnet")) /* Mainnet */;
192
+ const viemChain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
193
+ const rpc = (_nullishCoalesce(config.rpc, () => ( viemChain.rpcUrls.default.http[0]))).replace(/\/$/, "");
194
+ this.chainId = viemChain.id;
195
+ this.vaultAddr = config.earn.vault;
196
+ this.userAddr = config.address;
197
+ const chain = makeChain(viemChain.id, rpc);
198
+ this.pc = _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 preview(params) {
227
+ const { amount, horizonDays } = params;
228
+ const apy = await this._fetchNetApy();
229
+ return {
230
+ projectedYield: amount * apy * (horizonDays / 365),
231
+ apy,
232
+ horizonDays
233
+ };
234
+ }
235
+ async withdrawability(params) {
236
+ const vault = this.morpho.vaultV2(this.vaultAddr, this.chainId);
237
+ const vaultData = await vault.getData();
238
+ const tokenDecimals = await _asyncNullishCoalesce(params.tokenDecimals, async () => ( await this.pc.readContract({
239
+ address: vaultData.asset,
240
+ abi: _viem.erc20Abi,
241
+ functionName: "decimals"
242
+ })));
243
+ const requestedRaw = _viem.parseUnits.call(void 0, String(params.amount), tokenDecimals);
244
+ const availableLiquidityRaw = vaultData.assetBalance;
245
+ return {
246
+ isInstant: requestedRaw <= availableLiquidityRaw,
247
+ availableLiquidity: Number(_viem.formatUnits.call(void 0, availableLiquidityRaw, tokenDecimals)),
248
+ tokenDecimals
249
+ };
250
+ }
251
+ async _fetchNetApy() {
252
+ const query = `
253
+ query VaultNetApy($address: String!, $chainId: Int!) {
254
+ vaultV2ByAddress(address: $address, chainId: $chainId) {
255
+ netApy
256
+ }
257
+ }
258
+ `;
259
+ let body;
260
+ try {
261
+ body = await retryWithBackoff(
262
+ async () => {
263
+ const res = await fetch(MORPHO_API_URL, {
264
+ method: "POST",
265
+ headers: { "Content-Type": "application/json" },
266
+ body: JSON.stringify({
267
+ query,
268
+ variables: { address: this.vaultAddr, chainId: this.chainId }
269
+ })
270
+ });
271
+ if (!res.ok) {
272
+ const retryable = res.status === 429 || res.status >= 500;
273
+ throw new HttpError(
274
+ `earn preview: Morpho API returned ${res.status} \u2014 ${res.statusText}`,
275
+ res.status,
276
+ retryable
277
+ );
278
+ }
279
+ return res.json().catch(() => void 0);
280
+ },
281
+ (err) => err instanceof HttpError ? err.retryable : err instanceof TypeError
282
+ );
283
+ } catch (err) {
284
+ if (err instanceof TypeError || err instanceof HttpError) {
285
+ 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)}`,
287
+ url: MORPHO_API_URL,
288
+ cause: err
289
+ });
290
+ }
291
+ throw err;
292
+ }
293
+ const netApy = _optionalChain([body, 'optionalAccess', _4 => _4.data, 'optionalAccess', _5 => _5.vaultV2ByAddress, 'optionalAccess', _6 => _6.netApy]);
294
+ if (typeof netApy !== "number") {
295
+ throw new StableValidationError(
296
+ "apy",
297
+ void 0,
298
+ "earn preview: vault not found in Morpho API or unexpected response shape"
299
+ );
300
+ }
301
+ return netApy;
302
+ }
303
+ };
304
+
305
+ // src/services/lifi.ts
306
+
307
+
308
+
309
+
310
+
311
+
312
+
313
+
314
+
315
+
157
316
  // src/chains.ts
158
317
  var CHAIN_CONFIGS = {
159
318
  // ─── Testnet ──────────────────────────────────────────────────────────────
@@ -257,7 +416,7 @@ var LiFiService = class {
257
416
  async quoteBridge(params) {
258
417
  this._validateBridgeParams(params);
259
418
  const { fromChain, toChain, fromToken, toToken, amount, fromDecimals = 6, recipient } = params;
260
- const from = _nullishCoalesce(_nullishCoalesce(_optionalChain([this, 'access', _2 => _2.config, 'access', _3 => _3.walletClient, 'optionalAccess', _4 => _4.account, 'optionalAccess', _5 => _5.address]), () => ( _optionalChain([this, 'access', _6 => _6.config, 'access', _7 => _7.account, 'optionalAccess', _8 => _8.address]))), () => ( "0x0000000000000000000000000000000000000000"));
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"));
261
420
  const src = CHAIN_CONFIGS[fromChain];
262
421
  const dst = CHAIN_CONFIGS[toChain];
263
422
  if (!src) throw new StableValidationError("fromChain", fromChain, `LI.FI bridge: unsupported source chain "${fromChain}"`);
@@ -334,7 +493,7 @@ var LiFiService = class {
334
493
  }
335
494
  }
336
495
  _requireFrom() {
337
- const from = _nullishCoalesce(_optionalChain([this, 'access', _9 => _9.config, 'access', _10 => _10.walletClient, 'optionalAccess', _11 => _11.account, 'optionalAccess', _12 => _12.address]), () => ( _optionalChain([this, 'access', _13 => _13.config, 'access', _14 => _14.account, 'optionalAccess', _15 => _15.address])));
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])));
338
497
  if (!from) {
339
498
  throw new StableValidationError(
340
499
  "signer",
@@ -502,6 +661,334 @@ function isLiFiQuoteShape(v) {
502
661
  return isObject(tx) && typeof tx.to === "string" && typeof tx.data === "string" && typeof tx.chainId === "number" && isObject(est) && typeof est.fromAmount === "string" && typeof est.toAmount === "string" && isObject(act) && isObject(act.fromToken) && isObject(act.toToken);
503
662
  }
504
663
 
664
+ // src/earn/morpho.ts
665
+
666
+
667
+
668
+
669
+
670
+
671
+
672
+
673
+
674
+
675
+
676
+
677
+
678
+ var MorphoService = class {
679
+ constructor(config) {
680
+ this.config = config;
681
+ this.inFlight = /* @__PURE__ */ new Map();
682
+ this.chain = makeChain(config.chainId, config.rpc);
683
+ this.wc = _nullishCoalesce(config.walletClient, () => ( _viem.createWalletClient.call(void 0, {
684
+ account: config.account,
685
+ chain: this.chain,
686
+ transport: _nullishCoalesce(config.transport, () => ( _viem.http.call(void 0, config.rpc)))
687
+ })));
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, {
689
+ account: this.wc.account,
690
+ chain: this.chain,
691
+ transport: _viem.custom.call(void 0, { request: this.wc.request.bind(this.wc) })
692
+ });
693
+ this.morpho = new (0, _morphosdk.MorphoClient)(morphoWc, { supportSignature: true });
694
+ this.pc = _viem.createPublicClient.call(void 0, { chain: this.chain, transport: _viem.http.call(void 0, config.rpc) });
695
+ }
696
+ deposit(params) {
697
+ this._validateAmount(params.amount, "earn deposit");
698
+ return this._withIdempotency(params.idempotencyKey, () => this._deposit(params));
699
+ }
700
+ async bulkDeposit(items, options = {}) {
701
+ const { idempotencyKey, stopOnError = false } = options;
702
+ const run = async () => {
703
+ const results = [];
704
+ for (const item of items) {
705
+ try {
706
+ const result = await this.deposit(item);
707
+ results.push({ status: "fulfilled", txHash: result.txHash, vault: item.vault, idempotencyKey: item.idempotencyKey });
708
+ } catch (err) {
709
+ const entry = { status: "rejected", error: String(err), vault: item.vault, idempotencyKey: item.idempotencyKey };
710
+ results.push(entry);
711
+ if (stopOnError) {
712
+ const succeeded2 = results.filter((r) => r.status === "fulfilled").length;
713
+ throw new StableBulkError({
714
+ shortMessage: "bulkDeposit halted on first failure",
715
+ results,
716
+ succeeded: succeeded2,
717
+ failed: results.length - succeeded2
718
+ });
719
+ }
720
+ }
721
+ }
722
+ const succeeded = results.filter((r) => r.status === "fulfilled").length;
723
+ return { results, succeeded, failed: results.length - succeeded, idempotencyKey };
724
+ };
725
+ return this._withIdempotency(idempotencyKey, run);
726
+ }
727
+ withdraw(params) {
728
+ this._validateAmount(params.amount, "earn withdraw");
729
+ return this._withIdempotency(params.idempotencyKey, () => this._withdraw(params));
730
+ }
731
+ redeem(params) {
732
+ this._validateShares(params.shares, "earn redeem");
733
+ return this._withIdempotency(params.idempotencyKey, () => this._redeem(params));
734
+ }
735
+ forceWithdraw(params) {
736
+ this._validateAmount(params.amount, "earn forceWithdraw");
737
+ return this._withIdempotency(params.idempotencyKey, () => this._forceWithdraw(params));
738
+ }
739
+ forceRedeem(params) {
740
+ this._validateShares(params.shares, "earn forceRedeem");
741
+ return this._withIdempotency(params.idempotencyKey, () => this._forceRedeem(params));
742
+ }
743
+ // ─── Private operations ──────────────────────────────────────────────────
744
+ async _deposit(params) {
745
+ const { amount, tokenDecimals, slippageTolerance, idempotencyKey } = params;
746
+ const from = this._requireFrom();
747
+ const vaultAddr = _nullishCoalesce(params.vault, () => ( this.config.vault));
748
+ await this._switchChain();
749
+ const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
750
+ const vaultData = await vault.getData();
751
+ const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn deposit")));
752
+ const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
753
+ const depositAction = vault.deposit({
754
+ amount: assetsRaw,
755
+ userAddress: from,
756
+ accrualVault: vaultData,
757
+ slippageTolerance: slippageTolerance !== void 0 ? _viem.parseUnits.call(void 0, String(slippageTolerance), 18) : void 0
758
+ });
759
+ const requirements = await depositAction.getRequirements();
760
+ let sig;
761
+ for (const req of requirements) {
762
+ if (_morphosdk.isRequirementApproval.call(void 0, req)) {
763
+ const approveHash = await this._sendTx(req.to, req.data, req.value, from);
764
+ const approveReceipt = await this.pc.waitForTransactionReceipt({ hash: approveHash });
765
+ if (approveReceipt.status === "reverted") {
766
+ throw new StableTransactionError({
767
+ phase: "approve",
768
+ shortMessage: "earn deposit: token approval reverted on-chain",
769
+ txHash: approveReceipt.transactionHash,
770
+ chainId: this.config.chainId
771
+ });
772
+ }
773
+ } else if (_morphosdk.isRequirementSignature.call(void 0, req)) {
774
+ sig = await req.sign(this.wc, from);
775
+ }
776
+ }
777
+ const tx = depositAction.buildTx(sig);
778
+ const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
779
+ const receipt = await this.pc.waitForTransactionReceipt({ hash });
780
+ if (receipt.status === "reverted") {
781
+ throw new StableTransactionError({
782
+ phase: "receipt",
783
+ shortMessage: "earn deposit: vault deposit reverted on-chain",
784
+ txHash: receipt.transactionHash,
785
+ chainId: this.config.chainId
786
+ });
787
+ }
788
+ return { txHash: receipt.transactionHash, idempotencyKey };
789
+ }
790
+ async _withdraw(params) {
791
+ const { amount, tokenDecimals, idempotencyKey } = params;
792
+ const from = this._requireFrom();
793
+ const vaultAddr = this.config.vault;
794
+ await this._switchChain();
795
+ const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
796
+ const vaultData = await vault.getData();
797
+ const decimals = await _asyncNullishCoalesce(tokenDecimals, async () => ( await this._resolveDecimals(vaultData.asset, "earn withdraw")));
798
+ const assetsRaw = _viem.parseUnits.call(void 0, String(amount), decimals);
799
+ const tx = vault.withdraw({ amount: assetsRaw, userAddress: from }).buildTx();
800
+ const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
801
+ const receipt = await this.pc.waitForTransactionReceipt({ hash });
802
+ if (receipt.status === "reverted") {
803
+ throw new StableTransactionError({
804
+ phase: "receipt",
805
+ shortMessage: "earn withdraw: vault withdraw reverted on-chain",
806
+ txHash: receipt.transactionHash,
807
+ chainId: this.config.chainId
808
+ });
809
+ }
810
+ return { txHash: receipt.transactionHash, idempotencyKey };
811
+ }
812
+ async _redeem(params) {
813
+ const { shares, shareDecimals, idempotencyKey } = params;
814
+ const from = this._requireFrom();
815
+ const vaultAddr = this.config.vault;
816
+ await this._switchChain();
817
+ let decimals = shareDecimals;
818
+ if (decimals === void 0) {
819
+ try {
820
+ decimals = await this.pc.readContract({ address: vaultAddr, abi: _viem.erc20Abi, functionName: "decimals" });
821
+ } catch (e2) {
822
+ throw new StableValidationError(
823
+ "shareDecimals",
824
+ void 0,
825
+ `earn redeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
826
+ );
827
+ }
828
+ }
829
+ const sharesRaw = _viem.parseUnits.call(void 0, String(shares), decimals);
830
+ const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
831
+ const tx = vault.redeem({ shares: sharesRaw, userAddress: from }).buildTx();
832
+ const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
833
+ const receipt = await this.pc.waitForTransactionReceipt({ hash });
834
+ if (receipt.status === "reverted") {
835
+ throw new StableTransactionError({
836
+ phase: "receipt",
837
+ shortMessage: "earn redeem: vault redeem reverted on-chain",
838
+ txHash: receipt.transactionHash,
839
+ chainId: this.config.chainId
840
+ });
841
+ }
842
+ return { txHash: receipt.transactionHash, idempotencyKey };
843
+ }
844
+ async _forceWithdraw(params) {
845
+ const { amount, tokenDecimals, deallocations, idempotencyKey } = params;
846
+ const from = this._requireFrom();
847
+ const vaultAddr = this.config.vault;
848
+ await this._switchChain();
849
+ const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
850
+ let decimals = tokenDecimals;
851
+ if (decimals === void 0) {
852
+ const vaultData = await vault.getData();
853
+ decimals = await this._resolveDecimals(vaultData.asset, "earn forceWithdraw");
854
+ }
855
+ const tx = vault.forceWithdraw({
856
+ deallocations: this._buildDeallocs(deallocations, decimals),
857
+ withdraw: { amount: _viem.parseUnits.call(void 0, String(amount), decimals) },
858
+ userAddress: from
859
+ }).buildTx();
860
+ const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
861
+ const receipt = await this.pc.waitForTransactionReceipt({ hash });
862
+ if (receipt.status === "reverted") {
863
+ throw new StableTransactionError({
864
+ phase: "receipt",
865
+ shortMessage: "earn forceWithdraw: vault forceWithdraw reverted on-chain",
866
+ txHash: receipt.transactionHash,
867
+ chainId: this.config.chainId
868
+ });
869
+ }
870
+ return { txHash: receipt.transactionHash, idempotencyKey };
871
+ }
872
+ async _forceRedeem(params) {
873
+ const { shares, shareDecimals, tokenDecimals, deallocations, idempotencyKey } = params;
874
+ const from = this._requireFrom();
875
+ const vaultAddr = this.config.vault;
876
+ await this._switchChain();
877
+ const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
878
+ let shareDec = shareDecimals;
879
+ if (shareDec === void 0) {
880
+ try {
881
+ shareDec = await this.pc.readContract({ address: vaultAddr, abi: _viem.erc20Abi, functionName: "decimals" });
882
+ } catch (e3) {
883
+ throw new StableValidationError(
884
+ "shareDecimals",
885
+ void 0,
886
+ `earn forceRedeem: could not fetch share decimals for vault "${vaultAddr}" \u2014 pass shareDecimals explicitly`
887
+ );
888
+ }
889
+ }
890
+ let assetDec = tokenDecimals;
891
+ if (assetDec === void 0) {
892
+ const vaultData = await vault.getData();
893
+ assetDec = await this._resolveDecimals(vaultData.asset, "earn forceRedeem");
894
+ }
895
+ const tx = vault.forceRedeem({
896
+ deallocations: this._buildDeallocs(deallocations, assetDec),
897
+ redeem: { shares: _viem.parseUnits.call(void 0, String(shares), shareDec) },
898
+ userAddress: from
899
+ }).buildTx();
900
+ const hash = await this._sendTx(tx.to, tx.data, tx.value, from);
901
+ const receipt = await this.pc.waitForTransactionReceipt({ hash });
902
+ if (receipt.status === "reverted") {
903
+ throw new StableTransactionError({
904
+ phase: "receipt",
905
+ shortMessage: "earn forceRedeem: vault forceRedeem reverted on-chain",
906
+ txHash: receipt.transactionHash,
907
+ chainId: this.config.chainId
908
+ });
909
+ }
910
+ return { txHash: receipt.transactionHash, idempotencyKey };
911
+ }
912
+ _buildDeallocs(deallocations, assetDecimals) {
913
+ return deallocations.map((d) => ({
914
+ adapter: d.adapter,
915
+ amount: _viem.parseUnits.call(void 0, String(d.amount), assetDecimals),
916
+ // MarketParams in the Morpho SDK has computed fields (id, liquidationIncentiveFactor)
917
+ // not present in our user-facing type; the plain object is structurally compatible for ABI encoding.
918
+ ...d.marketParams && { marketParams: d.marketParams }
919
+ }));
920
+ }
921
+ // ─── Helpers ─────────────────────────────────────────────────────────────
922
+ _requireFrom() {
923
+ const from = _optionalChain([this, 'access', _24 => _24.wc, 'access', _25 => _25.account, 'optionalAccess', _26 => _26.address]);
924
+ if (!from) {
925
+ throw new StableValidationError(
926
+ "signer",
927
+ void 0,
928
+ "earn: requires a signer \u2014 set walletClient or account in StableConfig"
929
+ );
930
+ }
931
+ return from;
932
+ }
933
+ async _switchChain() {
934
+ try {
935
+ await this.wc.switchChain({ id: this.config.chainId });
936
+ } catch (err) {
937
+ if (err.code === 4001) {
938
+ throw new StableTransactionError({
939
+ phase: "switch_chain",
940
+ shortMessage: `earn: wallet rejected switch to chain ${this.config.chainId}`,
941
+ chainId: this.config.chainId,
942
+ cause: err
943
+ });
944
+ }
945
+ }
946
+ }
947
+ async _sendTx(to, data, value, from) {
948
+ try {
949
+ return await this.wc.sendTransaction({ to, data, value, account: from, chain: this.chain });
950
+ } catch (err) {
951
+ throw new StableTransactionError({
952
+ phase: "send",
953
+ shortMessage: "earn: transaction send failed",
954
+ chainId: this.config.chainId,
955
+ cause: err
956
+ });
957
+ }
958
+ }
959
+ async _resolveDecimals(asset, op) {
960
+ try {
961
+ return await retryWithBackoff(
962
+ () => this.pc.readContract({ address: asset, abi: _viem.erc20Abi, functionName: "decimals" })
963
+ );
964
+ } catch (e4) {
965
+ throw new StableValidationError(
966
+ "tokenDecimals",
967
+ void 0,
968
+ `${op}: could not fetch decimals for asset "${asset}" \u2014 pass tokenDecimals explicitly`
969
+ );
970
+ }
971
+ }
972
+ _withIdempotency(key, fn) {
973
+ if (!key) return fn();
974
+ const existing = this.inFlight.get(key);
975
+ if (existing) return existing;
976
+ const promise = fn().finally(() => this.inFlight.delete(key));
977
+ this.inFlight.set(key, promise);
978
+ return promise;
979
+ }
980
+ _validateAmount(amount, op) {
981
+ if (!Number.isFinite(amount) || amount <= 0) {
982
+ throw new StableValidationError("amount", amount, `${op}: amount must be a finite positive number`);
983
+ }
984
+ }
985
+ _validateShares(shares, op) {
986
+ if (!Number.isFinite(shares) || shares <= 0) {
987
+ throw new StableValidationError("shares", shares, `${op}: shares must be a finite positive number`);
988
+ }
989
+ }
990
+ };
991
+
505
992
  // src/stable.ts
506
993
  function validateTransferParams({ to, amount, token, tokenDecimals }) {
507
994
  if (!_viem.isAddress.call(void 0, to)) {
@@ -521,7 +1008,7 @@ async function resolveTokenDecimals(publicClient, token, tokenDecimals) {
521
1008
  if (tokenDecimals !== void 0) return tokenDecimals;
522
1009
  try {
523
1010
  return await publicClient.readContract({ address: token, abi: _viem.erc20Abi, functionName: "decimals" });
524
- } catch (e2) {
1011
+ } catch (e5) {
525
1012
  throw new StableValidationError(
526
1013
  "tokenDecimals",
527
1014
  void 0,
@@ -553,16 +1040,28 @@ function createStable({
553
1040
  rpc: rpcOverride,
554
1041
  account,
555
1042
  transport,
556
- walletClient
1043
+ walletClient,
1044
+ earn: earnConfig
557
1045
  }) {
558
1046
  const chain = network === "mainnet" /* Mainnet */ ? _chains.stable : _chains.stableTestnet;
559
1047
  const rpc = (_nullishCoalesce(rpcOverride, () => ( chain.rpcUrls.default.http[0]))).replace(/\/$/, "");
560
1048
  const signerConfig = { account, transport, walletClient };
561
1049
  const lifi = new LiFiService({ ...signerConfig, rpc, chainId: chain.id });
1050
+ const morpho = earnConfig ? new MorphoService({ ...signerConfig, rpc, chainId: chain.id, vault: earnConfig.vault }) : null;
1051
+ function requireEarn() {
1052
+ if (!morpho) {
1053
+ throw new StableValidationError(
1054
+ "earn",
1055
+ void 0,
1056
+ "stable.earn.*: pass earn: { vault: '0x...' } to createStable() to enable earn operations"
1057
+ );
1058
+ }
1059
+ return morpho;
1060
+ }
562
1061
  async function transfer(params) {
563
1062
  validateTransferParams(params);
564
1063
  const { from, to, amount, token, tokenDecimals } = params;
565
- const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _16 => _16.account]), () => ( account)), () => ( from));
1064
+ const acc = _nullishCoalesce(_nullishCoalesce(_optionalChain([walletClient, 'optionalAccess', _27 => _27.account]), () => ( account)), () => ( from));
566
1065
  const wc = _nullishCoalesce(walletClient, () => ( _viem.createWalletClient.call(void 0, {
567
1066
  account: acc,
568
1067
  chain,
@@ -591,21 +1090,41 @@ function createStable({
591
1090
  }
592
1091
  return { txHash: receipt.transactionHash };
593
1092
  }
594
- function quoteBridge(params) {
595
- return lifi.quoteBridge(params);
596
- }
597
- function bridge(params) {
598
- return lifi.bridge(params);
599
- }
600
1093
  return {
601
1094
  transfer,
602
- quoteBridge,
603
- bridge,
1095
+ quoteBridge: (params) => lifi.quoteBridge(params),
1096
+ bridge: (params) => lifi.bridge(params),
604
1097
  quoteSwap: (params) => lifi.quoteSwap(params),
605
- swap: (params) => lifi.swap(params)
1098
+ swap: (params) => lifi.swap(params),
1099
+ earn: {
1100
+ deposit: (params) => requireEarn().deposit(params),
1101
+ bulkDeposit: (items, options) => requireEarn().bulkDeposit(items, options),
1102
+ withdraw: (params) => requireEarn().withdraw(params),
1103
+ redeem: (params) => requireEarn().redeem(params),
1104
+ forceWithdraw: (params) => requireEarn().forceWithdraw(params),
1105
+ forceRedeem: (params) => requireEarn().forceRedeem(params)
1106
+ }
1107
+ };
1108
+ }
1109
+ function createStableReader(config) {
1110
+ const reader = new EarnReader(config);
1111
+ return {
1112
+ earn: {
1113
+ position: () => reader.position(),
1114
+ preview: (params) => reader.preview(params),
1115
+ withdrawability: (params) => reader.withdrawability(params)
1116
+ }
606
1117
  };
607
1118
  }
608
1119
 
1120
+ // src/constants.ts
1121
+ var STABLE_USDT_ADDRESS = "0x779ded0c9e1022225f8e0630b35a9b54be713736";
1122
+ var STABLE_VAULT_ADDRESS = "0x461eF73F640456138242398a39AB48a557A49293";
1123
+
1124
+
1125
+
1126
+
1127
+
609
1128
 
610
1129
 
611
1130
 
@@ -615,4 +1134,4 @@ function createStable({
615
1134
 
616
1135
 
617
1136
 
618
- exports.CHAIN_CONFIGS = CHAIN_CONFIGS; exports.Chain = Chain; exports.Network = Network; exports.StableError = StableError; exports.StableNetworkError = StableNetworkError; exports.StableQuoteError = StableQuoteError; exports.StableTransactionError = StableTransactionError; exports.StableValidationError = StableValidationError; exports.createStable = createStable;
1137
+ exports.CHAIN_CONFIGS = CHAIN_CONFIGS; exports.Chain = Chain; exports.Network = Network; exports.STABLE_USDT_ADDRESS = STABLE_USDT_ADDRESS; exports.STABLE_VAULT_ADDRESS = STABLE_VAULT_ADDRESS; exports.StableBulkError = StableBulkError; exports.StableError = StableError; exports.StableNetworkError = StableNetworkError; exports.StableQuoteError = StableQuoteError; exports.StableTransactionError = StableTransactionError; exports.StableValidationError = StableValidationError; exports.createStable = createStable; exports.createStableReader = createStableReader;