@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.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // src/stable.ts
2
2
  import {
3
- http as http4,
4
- createPublicClient as createPublicClient4,
5
- createWalletClient as createWalletClient3,
3
+ http as http5,
4
+ createPublicClient as createPublicClient5,
5
+ createWalletClient as createWalletClient4,
6
6
  erc20Abi as erc20Abi4,
7
- isAddress as isAddress3,
7
+ isAddress as isAddress4,
8
8
  parseUnits as parseUnits4
9
9
  } from "viem";
10
10
  import { stable as stableMainnet2, stableTestnet as stableTestnet2 } from "viem/chains";
@@ -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
- return err instanceof TypeError;
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 = opts.maxAttempts ?? 3;
@@ -166,6 +170,7 @@ var MorphoService = class {
166
170
  })
167
171
  });
168
172
  this.morpho = new MorphoClient(morphoWc, { supportSignature: true });
173
+ this.morphoNoSig = new MorphoClient(morphoWc, { supportSignature: false });
169
174
  this.pc = createPublicClient({
170
175
  chain: this.chain,
171
176
  transport: http(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 = params.vault ?? this.config.vault;
324
+ const vault = this.morphoNoSig.vaultV2(vaultAddr, this.config.chainId);
325
+ const vaultData = await vault.getData();
326
+ const decimals = tokenDecimals ?? await this._resolveDecimals(vaultData.asset, "prepareDepositCalldata");
327
+ const assetsRaw = parseUnits(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(formatUnits(maxAllowed, decimals));
333
+ const idleFormatted = Number(formatUnits(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 ? parseUnits(String(slippageTolerance), 18) : void 0
346
+ });
347
+ const requirements = await depositAction.getRequirements();
348
+ const steps = [];
349
+ for (const req of requirements) {
350
+ if (isRequirementApproval(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 = tokenDecimals ?? await this._resolveDecimals(vaultData.asset, "prepareWithdrawCalldata");
366
+ const assetsRaw = parseUnits(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: erc20Abi,
389
+ functionName: "decimals"
390
+ });
391
+ } catch {
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 = parseUnits(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 = tokenDecimals ?? await this._resolveDecimals(vaultData.asset, "earn withdraw");
392
500
  const assetsRaw = parseUnits(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") {
@@ -426,7 +542,17 @@ var MorphoService = class {
426
542
  }
427
543
  const sharesRaw = parseUnits(String(shares), decimals);
428
544
  const vault = this.morpho.vaultV2(vaultAddr, this.config.chainId);
429
- const tx = vault.redeem({ shares: sharesRaw, userAddress: from }).buildTx();
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") {
@@ -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 = 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,
@@ -843,14 +1014,241 @@ var EarnReader = class {
843
1014
  }
844
1015
  };
845
1016
 
846
- // src/services/lifi.ts
1017
+ // src/earn/merkl.ts
847
1018
  import {
848
1019
  http as http3,
849
1020
  createPublicClient as createPublicClient3,
850
1021
  createWalletClient as createWalletClient2,
851
- erc20Abi as erc20Abi3,
1022
+ encodeFunctionData,
852
1023
  formatUnits as formatUnits3,
853
- isAddress as isAddress2,
1024
+ isAddress as isAddress2
1025
+ } from "viem";
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 (!isAddress2(address)) {
1048
+ throw new StableValidationError(
1049
+ "address",
1050
+ address,
1051
+ `merkl: invalid address "${address}"`
1052
+ );
1053
+ }
1054
+ const base = (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 = this.config.walletClient ?? createWalletClient2({
1074
+ account: this.config.account,
1075
+ chain,
1076
+ transport: this.config.transport ?? http3(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 = createPublicClient3({
1113
+ chain,
1114
+ transport: http3(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 (!isAddress2(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 = encodeFunctionData({
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 = this.config.walletClient?.account?.address ?? this.config.account?.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 (entry?.chain?.id !== chainId) continue;
1182
+ for (const reward of 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 (!reward?.token?.address) return null;
1191
+ const net = BigInt(reward.amount ?? "0") - BigInt(reward.claimed ?? "0");
1192
+ if (net <= 0n) return null;
1193
+ const rawAmount = net.toString();
1194
+ const decimals = reward.token.decimals ?? 18;
1195
+ return {
1196
+ token: {
1197
+ address: reward.token.address,
1198
+ symbol: reward.token.symbol ?? "",
1199
+ decimals
1200
+ },
1201
+ rawAmount,
1202
+ amount: Number(formatUnits3(BigInt(rawAmount), decimals)),
1203
+ proofs: 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
+
1244
+ // src/services/lifi.ts
1245
+ import {
1246
+ http as http4,
1247
+ createPublicClient as createPublicClient4,
1248
+ createWalletClient as createWalletClient3,
1249
+ erc20Abi as erc20Abi3,
1250
+ formatUnits as formatUnits4,
1251
+ isAddress as isAddress3,
854
1252
  parseUnits as parseUnits3
855
1253
  } from "viem";
856
1254
 
@@ -1055,14 +1453,14 @@ var LiFiService = class {
1055
1453
  amount,
1056
1454
  toAddress
1057
1455
  }) {
1058
- if (!isAddress2(fromToken)) {
1456
+ if (!isAddress3(fromToken)) {
1059
1457
  throw new StableValidationError(
1060
1458
  "fromToken",
1061
1459
  fromToken,
1062
1460
  `swap: invalid fromToken address "${fromToken}"`
1063
1461
  );
1064
1462
  }
1065
- if (!isAddress2(toToken)) {
1463
+ if (!isAddress3(toToken)) {
1066
1464
  throw new StableValidationError(
1067
1465
  "toToken",
1068
1466
  toToken,
@@ -1083,7 +1481,7 @@ var LiFiService = class {
1083
1481
  "swap: amount must be a finite positive number"
1084
1482
  );
1085
1483
  }
1086
- if (toAddress !== void 0 && !isAddress2(toAddress)) {
1484
+ if (toAddress !== void 0 && !isAddress3(toAddress)) {
1087
1485
  throw new StableValidationError(
1088
1486
  "toAddress",
1089
1487
  toAddress,
@@ -1099,14 +1497,14 @@ var LiFiService = class {
1099
1497
  amount,
1100
1498
  recipient
1101
1499
  }) {
1102
- if (!isAddress2(fromToken)) {
1500
+ if (!isAddress3(fromToken)) {
1103
1501
  throw new StableValidationError(
1104
1502
  "fromToken",
1105
1503
  fromToken,
1106
1504
  `LI.FI bridge: invalid fromToken address "${fromToken}"`
1107
1505
  );
1108
1506
  }
1109
- if (!isAddress2(toToken)) {
1507
+ if (!isAddress3(toToken)) {
1110
1508
  throw new StableValidationError(
1111
1509
  "toToken",
1112
1510
  toToken,
@@ -1120,7 +1518,7 @@ var LiFiService = class {
1120
1518
  "LI.FI bridge: amount must be a finite positive number"
1121
1519
  );
1122
1520
  }
1123
- if (recipient !== void 0 && !isAddress2(recipient)) {
1521
+ if (recipient !== void 0 && !isAddress3(recipient)) {
1124
1522
  throw new StableValidationError(
1125
1523
  "recipient",
1126
1524
  recipient,
@@ -1201,7 +1599,7 @@ var LiFiService = class {
1201
1599
  _toSwapQuote(quote) {
1202
1600
  return {
1203
1601
  toAmount: Number(
1204
- formatUnits3(
1602
+ formatUnits4(
1205
1603
  BigInt(quote.estimate.toAmount),
1206
1604
  quote.action.toToken.decimals
1207
1605
  )
@@ -1225,10 +1623,10 @@ var LiFiService = class {
1225
1623
  });
1226
1624
  }
1227
1625
  const srcChain = makeChain(srcChainId, srcRpc);
1228
- const wc = this.config.walletClient ?? createWalletClient2({
1626
+ const wc = this.config.walletClient ?? createWalletClient3({
1229
1627
  account: this.config.account,
1230
1628
  chain: srcChain,
1231
- transport: this.config.transport ?? http3(srcRpc)
1629
+ transport: this.config.transport ?? http4(srcRpc)
1232
1630
  });
1233
1631
  try {
1234
1632
  await wc.switchChain({ id: srcChainId });
@@ -1272,9 +1670,9 @@ var LiFiService = class {
1272
1670
  const tokenAddr = quote.fromToken;
1273
1671
  const isNative = tokenAddr.toLowerCase() === "0x0000000000000000000000000000000000000000" || tokenAddr.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
1274
1672
  if (isNative || !quote.approvalAddress) return;
1275
- const pc = createPublicClient3({
1673
+ const pc = createPublicClient4({
1276
1674
  chain: srcChain,
1277
- transport: http3(srcRpc)
1675
+ transport: http4(srcRpc)
1278
1676
  });
1279
1677
  const allowance = await pc.readContract({
1280
1678
  address: tokenAddr,
@@ -1332,7 +1730,7 @@ function validateTransferParams({
1332
1730
  token,
1333
1731
  tokenDecimals
1334
1732
  }) {
1335
- if (!isAddress3(to)) {
1733
+ if (!isAddress4(to)) {
1336
1734
  throw new StableValidationError(
1337
1735
  "to",
1338
1736
  to,
@@ -1346,7 +1744,7 @@ function validateTransferParams({
1346
1744
  "transfer: amount must be a finite positive number"
1347
1745
  );
1348
1746
  }
1349
- if (token !== void 0 && !isAddress3(token)) {
1747
+ if (token !== void 0 && !isAddress4(token)) {
1350
1748
  throw new StableValidationError(
1351
1749
  "token",
1352
1750
  token,
@@ -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 */ ? stableMainnet2 : stableTestnet2;
1412
1811
  const rpc = (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,14 +1831,25 @@ function createStable({
1431
1831
  }
1432
1832
  return morpho;
1433
1833
  }
1834
+ function requireSignerAddress() {
1835
+ const from = walletClient?.account?.address ?? account?.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
1848
  const acc = walletClient?.account ?? account ?? from;
1438
- const wc = walletClient ?? createWalletClient3({
1849
+ const wc = walletClient ?? createWalletClient4({
1439
1850
  account: acc,
1440
1851
  chain,
1441
- transport: transport ?? http4(rpc)
1852
+ transport: transport ?? http5(rpc)
1442
1853
  });
1443
1854
  try {
1444
1855
  await wc.switchChain({ id: chain.id });
@@ -1450,9 +1861,9 @@ function createStable({
1450
1861
  cause: err
1451
1862
  });
1452
1863
  }
1453
- const publicClient = createPublicClient4({
1864
+ const publicClient = createPublicClient5({
1454
1865
  chain,
1455
- transport: http4(rpc)
1866
+ transport: http5(rpc)
1456
1867
  });
1457
1868
  const hash = token !== void 0 ? await executeTokenTransfer(
1458
1869
  wc,
@@ -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 */ ? stableTestnet2 : stableMainnet2;
1925
+ const rpc = (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 = "0x461eF73F640456138242398a39AB48a557A49293";
1942
+ var STABLE_VAULT_ADDRESS = "0xb7Df8db22A5DBBFA9ebeb94b3910aec6a4f05c08";
1519
1943
  export {
1520
1944
  CHAIN_CONFIGS,
1521
1945
  Chain,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stablechain/sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Stable SDK",
5
5
  "keywords": ["stable", "usdt", "gasless", "web3", "yield"],
6
6
  "license": "MIT",