@gearbox-protocol/sdk 16.0.0-next.16 → 16.0.0-next.18

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.
Files changed (29) hide show
  1. package/dist/cjs/onchain/accounts/intents/index.js +19 -10
  2. package/dist/cjs/onchain/accounts/intents/leverage-band.js +2 -2
  3. package/dist/cjs/onchain/accounts/intents/testing/sdk-mock.js +1 -1
  4. package/dist/cjs/onchain/market/credit/CreditManagerV310Contract.js +2 -2
  5. package/dist/cjs/onchain/market/math.js +23 -11
  6. package/dist/cjs/rewards/index.js +1 -1
  7. package/dist/cjs/rewards/rewards/api.js +67 -57
  8. package/dist/cjs/rewards/rewards/index.js +1 -1
  9. package/dist/cjs/sdk/prepare/PrepareApi.js +6 -4
  10. package/dist/esm/onchain/accounts/intents/index.js +19 -10
  11. package/dist/esm/onchain/accounts/intents/leverage-band.js +2 -2
  12. package/dist/esm/onchain/accounts/intents/testing/sdk-mock.js +1 -1
  13. package/dist/esm/onchain/market/credit/CreditManagerV310Contract.js +2 -2
  14. package/dist/esm/onchain/market/math.js +23 -11
  15. package/dist/esm/rewards/index.js +2 -2
  16. package/dist/esm/rewards/rewards/api.js +68 -58
  17. package/dist/esm/rewards/rewards/index.js +2 -2
  18. package/dist/esm/sdk/prepare/PrepareApi.js +6 -4
  19. package/dist/types/onchain/accounts/intents/index.d.ts +17 -8
  20. package/dist/types/onchain/accounts/intents/leverage-band.d.ts +7 -2
  21. package/dist/types/onchain/market/credit/CreditManagerV310Contract.d.ts +1 -1
  22. package/dist/types/onchain/market/credit/types.d.ts +4 -3
  23. package/dist/types/onchain/market/math.d.ts +20 -9
  24. package/dist/types/rewards/index.d.ts +2 -2
  25. package/dist/types/rewards/rewards/api.d.ts +44 -29
  26. package/dist/types/rewards/rewards/index.d.ts +2 -2
  27. package/dist/types/sdk/prepare/PrepareApi.d.ts +3 -3
  28. package/dist/types/sdk/prepare/types.d.ts +6 -2
  29. package/package.json +1 -1
@@ -50,6 +50,10 @@ var CreditAccountOperationsService = class extends require_onchain_base_SDKConst
50
50
  * withdraw form should offer. Taking everything out is the same intent with
51
51
  * `MAX_UINT256` for an amount, and needs none of this arithmetic.
52
52
  *
53
+ * Takes no target health factor, unlike {@link maxWithdrawCollateral}: a
54
+ * proportional withdrawal leaves the factor where it found it, and the
55
+ * facade's `minDebt` is what bounds it.
56
+ *
53
57
  * @param props - Account slice and the SDK holding its market
54
58
  * @returns Amount in underlying units; `0n` when nothing can leave
55
59
  */
@@ -85,8 +89,9 @@ var CreditAccountOperationsService = class extends require_onchain_base_SDKConst
85
89
  * yet, and adjusting measures against the net value the caller already
86
90
  * holds. Nothing is fetched, so a form can ask on every keystroke.
87
91
  *
88
- * @param props - The manager, the SDK holding its market, and what stands
89
- * behind the position
92
+ * @param props - The manager, the SDK holding its market, what stands
93
+ * behind the position, and optionally the health factor the ceiling should
94
+ * leave
90
95
  * @returns The band, or nothing when the market has none to offer
91
96
  */
92
97
  leverageBand(props) {
@@ -94,19 +99,23 @@ var CreditAccountOperationsService = class extends require_onchain_base_SDKConst
94
99
  }
95
100
  /**
96
101
  * Largest `WITHDRAW_ASSET` amount of one token the account can take out
97
- * while its health factor stays at {@link MIN_HF_LIMITED} plus a basis
98
- * point — the ceiling a withdraw-collateral form should offer. Thresholds,
99
- * prices and quota activity come from the account's market, valued the way
100
- * the facade values a call that pays out; zero debt frees the whole balance.
102
+ * while its health factor stays at `targetHF` the ceiling a
103
+ * withdraw-collateral form should offer. Thresholds, prices and quota
104
+ * activity come from the account's market, valued the way the facade values
105
+ * a call that pays out; zero debt frees the whole balance.
106
+ *
107
+ * The default is {@link MIN_HF_LIMITED}, the bar `validateHF` holds an
108
+ * account to.
101
109
  *
102
- * @param props - Account slice, the SDK holding its market, and the
103
- * collateral to withdraw
110
+ * @param props - Account slice, the SDK holding its market, the collateral
111
+ * to withdraw, and optionally the health factor to leave behind
104
112
  * @returns Amount in the token's units; `0n` when nothing can leave
105
113
  */
106
114
  maxWithdrawCollateral(props) {
115
+ const { targetHF = require_common_utils_utils_validation_validate_hf.MIN_HF_LIMITED, ...rest } = props;
107
116
  return require_onchain_accounts_intents_maxWithdrawCollateral.maxWithdrawCollateral({
108
- ...props,
109
- targetHF: require_common_utils_utils_validation_validate_hf.MIN_HF_LIMITED + 2n
117
+ ...rest,
118
+ targetHF: targetHF + 2n
110
119
  });
111
120
  }
112
121
  /**
@@ -31,13 +31,13 @@ require("./utils/index.js");
31
31
  * calcLeverageBand({ sdk, creditManager, collateral }) // { min: 1.1, max: 9 }
32
32
  * ```
33
33
  **/
34
- function calcLeverageBand({ sdk, creditManager, collateral }) {
34
+ function calcLeverageBand({ sdk, creditManager, collateral, targetHF }) {
35
35
  const found = resolve(sdk, creditManager);
36
36
  if (!found) return;
37
37
  const { suite, market } = found;
38
38
  const target = suite.strategyTargetCollateral;
39
39
  if (!target) return;
40
- const ceiling = suite.creditManager.maxLeverage(target);
40
+ const ceiling = suite.creditManager.maxLeverage(target, targetHF);
41
41
  const underlying = market.pool.underlying;
42
42
  const convert = require_onchain_accounts_intents_utils_convert_amount.convertAmount(sdk, creditManager);
43
43
  const netValue = collateral.reduce((acc, a) => acc + convert(a.token, underlying, a.balance), 0n);
@@ -182,7 +182,7 @@ function buildMockSdk(args) {
182
182
  liquidationThresholds,
183
183
  collateralTokens,
184
184
  feeInterest: args.feeInterest ?? 0,
185
- maxLeverage: (collateral) => require_onchain_market_math.calcMaxLeverage(liquidationThresholds.get(collateral) ?? 0)
185
+ maxLeverage: (collateral, targetHF) => require_onchain_market_math.calcMaxLeverage(liquidationThresholds.get(collateral) ?? 0, targetHF)
186
186
  },
187
187
  creditFacade: {
188
188
  address: args.creditFacade,
@@ -66,8 +66,8 @@ var CreditManagerV310Contract = class extends require_onchain_base_BaseContract.
66
66
  /**
67
67
  * {@inheritDoc ICreditManagerContract.maxLeverage}
68
68
  */
69
- maxLeverage(collateral) {
70
- return require_onchain_market_math.calcMaxLeverage(this.liquidationThresholds.mustGet(collateral));
69
+ maxLeverage(collateral, targetHF) {
70
+ return require_onchain_market_math.calcMaxLeverage(this.liquidationThresholds.mustGet(collateral), targetHF);
71
71
  }
72
72
  /**
73
73
  * {@inheritDoc ICreditManagerContract.liquidationPremium}
@@ -163,24 +163,36 @@ function calcNetStrategyApy(opportunity, totalCollateralApy, leverage, mode = "s
163
163
  **/
164
164
  const MAX_LEVERAGE_BUFFER_BPS = 500;
165
165
  /**
166
- * Highest total-value leverage a liquidation threshold allows, floored:
167
- * `floor((100% − buffer) / (100% − liquidationThreshold))`. At HF = 1, debt is
168
- * `liquidationThreshold × totalValue`, leaving `1 − liquidationThreshold` of
169
- * equity per unit of exposure; the {@link MAX_LEVERAGE_BUFFER_BPS} buffer
170
- * keeps the maxed position slightly away from that boundary.
166
+ * Highest total-value leverage a liquidation threshold allows, floored.
167
+ *
168
+ * At HF = 1, debt is `liquidationThreshold × totalValue`, leaving
169
+ * `1 − liquidationThreshold` of equity per unit of exposure; a maxed position
170
+ * has to stay some way off that boundary. Given a `targetHF`, that distance is
171
+ * solved for — `HF = liquidationThreshold × L / (L − 1)` inverts to
172
+ * `L = targetHF / (targetHF − liquidationThreshold)`.
173
+ *
174
+ * Without one it falls back on a flat {@link MAX_LEVERAGE_BUFFER_BPS}, which
175
+ * under-buffers as the threshold rises — at 95% it allows 19x, or HF ≈ 1.0028.
176
+ * That branch is scaffolding, kept so this parameter moves no number before
177
+ * the callers name a target, and goes away with the constant.
178
+ *
179
+ * @param targetHF - Health factor the maxed position should leave, in basis
180
+ * points. Omitted keeps the legacy buffer.
171
181
  *
172
182
  * @example
173
183
  * ```ts
174
184
  * // liquidationThreshold: 9000 bps = 90%
175
- * calcMaxLeverage(9000) // floor((1 − 0.05) / (1 − 0.9)) = 9x total exposure
185
+ * calcMaxLeverage(9000) // floor((1 − 0.05) / (1 − 0.9)) = 9x
186
+ * calcMaxLeverage(9000, 10100) // floor(1.01 / (1.01 − 0.9)) = 9x
176
187
  * ```
177
- * @throws If `liquidationThreshold` is 100% or more, which would make
178
- * leverage unbounded.
188
+ * @throws If `liquidationThreshold` is 100% or more, or reaches a named
189
+ * `targetHF` — either way no leverage clears the bar.
179
190
  **/
180
- function calcMaxLeverage(liquidationThreshold) {
191
+ function calcMaxLeverage(liquidationThreshold, targetHF) {
181
192
  if (liquidationThreshold >= FULL) throw new Error("cannot compute max leverage: liquidation threshold is 100% or more");
182
- const leverage = Math.floor((FULL - 500) / (FULL - liquidationThreshold));
183
- return Math.max(leverage, 1);
193
+ if (targetHF === void 0) return Math.max(Math.floor((FULL - 500) / (FULL - liquidationThreshold)), 1);
194
+ if (liquidationThreshold >= targetHF) throw new Error("cannot compute max leverage: liquidation threshold reaches the target health factor");
195
+ return Math.max(Math.floor(targetHF / (targetHF - liquidationThreshold)), 1);
184
196
  }
185
197
  /**
186
198
  * Converts a credit account's health factor from the 18-decimal fixed point the
@@ -4,5 +4,5 @@ const require_rewards_rewards_api = require("./rewards/api.js");
4
4
  const require_rewards_rewards_extra_apy = require("./rewards/extra-apy.js");
5
5
  require("./rewards/index.js");
6
6
  exports.PoolPointsAPI = require_rewards_rewards_extra_apy.PoolPointsAPI;
7
- exports.RewardAmountAPI = require_rewards_rewards_api.RewardAmountAPI;
8
7
  exports.getKeyForPoolPointsInfo = require_rewards_rewards_extra_apy.getKeyForPoolPointsInfo;
8
+ exports.getMerklRewards = require_rewards_rewards_api.getMerklRewards;
@@ -1,70 +1,80 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_onchain_utils_AddressMap = require("../../onchain/utils/AddressMap.js");
2
3
  const require_onchain_utils_bigint_math = require("../../onchain/utils/bigint-math.js");
3
- const require_onchain_chain_chains = require("../../onchain/chain/chains.js");
4
4
  const require_onchain_utils_formatter = require("../../onchain/utils/formatter.js");
5
5
  require("../../onchain/index.js");
6
6
  require("../../common-utils/index.js");
7
7
  const require_rewards_rewards_merkl_api = require("./merkl-api.js");
8
8
  let viem = require("viem");
9
9
  //#region src/rewards/rewards/api.ts
10
- var RewardAmountAPI = class RewardAmountAPI {
11
- constructor() {}
12
- static async getLmRewardsMerkle({ pools, account, network, reportError, apiKey }) {
13
- const [merkleXYZLMResponse] = await Promise.allSettled([require_rewards_rewards_merkl_api.MerkleXYZApi.fetchWithFallback(require_rewards_rewards_merkl_api.MerkleXYZApi.getUserRewardsUrl({ params: {
14
- chainId: require_onchain_chain_chains.chains[network].id,
15
- user: (0, viem.getAddress)(account)
16
- } }), apiKey)]);
17
- const merkleXYZLm = RewardAmountAPI.extractFulfilled(merkleXYZLMResponse, reportError, "merkleXYZLm")?.data;
18
- const poolByItsToken = Object.values(pools).reduce((acc, p) => {
19
- p.stakedDieselToken.forEach((t) => {
20
- if (t) acc[t] = p.address;
21
- });
22
- p.stakedDieselToken_old.forEach((t) => {
23
- if (t) acc[t] = p.address;
24
- });
25
- acc[p.dieselToken] = p.address;
26
- return acc;
27
- }, {});
28
- const extraRewards = (merkleXYZLm || []).reduce((acc, chainRewards) => {
29
- chainRewards.rewards.forEach((reward) => {
30
- const rewardToken = reward.token.address.toLowerCase();
31
- reward.breakdowns.forEach((reason) => {
32
- const poolToken = ((reason.reason || "").split("_").find((part) => part.startsWith("0x")) || "").toLowerCase();
33
- const pool = poolByItsToken[poolToken];
34
- const total = require_onchain_utils_formatter.toBigInt(reason.amount || 0);
35
- const claimed = require_onchain_utils_formatter.toBigInt(reason.claimed || 0);
36
- const claimable = require_onchain_utils_bigint_math.BigIntMath.max(total - claimed, 0n);
37
- const key = [
38
- pool,
39
- poolToken,
40
- rewardToken
41
- ].join("_");
42
- if (pool && claimable > 0n) {
43
- const prevAmount = acc[key]?.amount || 0n;
44
- acc[key] = {
45
- pool,
46
- poolToken,
47
- rewardToken,
48
- rewardTokenSymbol: reward.token.symbol,
49
- rewardTokenDecimals: reward.token.decimals || 18,
50
- amount: prevAmount + claimable,
51
- type: "extraMerkle"
52
- };
53
- }
10
+ /**
11
+ * The wallet's claimable Merkl rewards on one chain.
12
+ *
13
+ * Never rejects on a transport failure: the fetch is settled rather than
14
+ * awaited, and a failure goes to `reportError` and yields an empty list. A
15
+ * caller that must tell "this chain is down" from "this chain has no rewards"
16
+ * has to watch that callback.
17
+ */
18
+ async function getMerklRewards({ sdk, account, reportError, apiKey }) {
19
+ const [merkleXYZLMResponse] = await Promise.allSettled([require_rewards_rewards_merkl_api.MerkleXYZApi.fetchWithFallback(require_rewards_rewards_merkl_api.MerkleXYZApi.getUserRewardsUrl({ params: {
20
+ chainId: sdk.chainId,
21
+ user: (0, viem.getAddress)(account)
22
+ } }), apiKey)]);
23
+ const merkleXYZLm = extractFulfilled(merkleXYZLMResponse, reportError, "merkleXYZLm")?.data;
24
+ const poolByItsToken = require_onchain_utils_AddressMap.AddressMap.fromMappedArray(sdk.marketRegister.pools.map(({ pool }) => pool.address), (address) => address);
25
+ const claimable = /* @__PURE__ */ new Map();
26
+ for (const chainRewards of merkleXYZLm || []) for (const reward of chainRewards.rewards) {
27
+ if (!(0, viem.isAddress)(reward.token.address, { strict: false })) continue;
28
+ const rewardTokenAddress = (0, viem.getAddress)(reward.token.address);
29
+ for (const reason of reward.breakdowns) {
30
+ const poolTokenAddress = (reason.reason || "").split("_").find((part) => part.startsWith("0x")) ?? "";
31
+ if (!(0, viem.isAddress)(poolTokenAddress, { strict: false })) continue;
32
+ const pool = poolByItsToken.get(poolTokenAddress);
33
+ if (!pool) continue;
34
+ const total = require_onchain_utils_formatter.toBigInt(reason.amount || 0);
35
+ const claimed = require_onchain_utils_formatter.toBigInt(reason.claimed || 0);
36
+ const amount = require_onchain_utils_bigint_math.BigIntMath.max(total - claimed, 0n);
37
+ if (amount === 0n) continue;
38
+ const key = `${pool}_${rewardTokenAddress}`;
39
+ const seen = claimable.get(key);
40
+ if (seen) {
41
+ claimable.set(key, {
42
+ ...seen,
43
+ amount: seen.amount + amount
54
44
  });
45
+ continue;
46
+ }
47
+ const poolToken = sdk.tokensMeta.getToken(pool);
48
+ if (!poolToken) continue;
49
+ claimable.set(key, {
50
+ chainId: sdk.chainId,
51
+ pool,
52
+ poolToken,
53
+ rewardToken: toRewardToken(sdk, rewardTokenAddress, reward.token),
54
+ amount
55
55
  });
56
- return acc;
57
- }, {});
58
- return Object.values(extraRewards);
59
- }
60
- static extractFulfilled(r, reportError, description) {
61
- if (r.status === "fulfilled") return r.value;
62
- else {
63
- if (reportError) reportError(r.reason, description);
64
- else console.error(r.reason);
65
- return;
66
56
  }
67
57
  }
68
- };
58
+ return [...claimable.values()];
59
+ }
60
+ /**
61
+ * A campaign's incentive token is not protocol collateral, so the registry
62
+ * usually has no entry for it — and Merkl always names it. The one place the
63
+ * two sources are reconciled.
64
+ */
65
+ function toRewardToken(sdk, address, merkl) {
66
+ return sdk.tokensMeta.getToken(address) ?? {
67
+ chainId: sdk.chainId,
68
+ address,
69
+ symbol: merkl.symbol,
70
+ name: merkl.symbol,
71
+ decimals: merkl.decimals || 18
72
+ };
73
+ }
74
+ function extractFulfilled(r, reportError, description) {
75
+ if (r.status === "fulfilled") return r.value;
76
+ if (reportError) reportError(r.reason, description);
77
+ else console.error(r.reason);
78
+ }
69
79
  //#endregion
70
- exports.RewardAmountAPI = RewardAmountAPI;
80
+ exports.getMerklRewards = getMerklRewards;
@@ -2,5 +2,5 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rewards_rewards_api = require("./api.js");
3
3
  const require_rewards_rewards_extra_apy = require("./extra-apy.js");
4
4
  exports.PoolPointsAPI = require_rewards_rewards_extra_apy.PoolPointsAPI;
5
- exports.RewardAmountAPI = require_rewards_rewards_api.RewardAmountAPI;
6
5
  exports.getKeyForPoolPointsInfo = require_rewards_rewards_extra_apy.getKeyForPoolPointsInfo;
6
+ exports.getMerklRewards = require_rewards_rewards_api.getMerklRewards;
@@ -252,24 +252,26 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
252
252
  /**
253
253
  * {@inheritDoc OpportunitiesPrepare.leverageBand}
254
254
  **/
255
- leverageBand(strategy, collateral) {
255
+ leverageBand(strategy, collateral, targetHF) {
256
256
  const sdk = this.sdk.chain(strategy.chainId);
257
257
  return service(sdk).leverageBand({
258
258
  sdk,
259
259
  creditManager: strategy.creditManager,
260
- collateral
260
+ collateral,
261
+ targetHF
261
262
  });
262
263
  }
263
264
  /**
264
265
  * {@inheritDoc OpportunitiesPrepare.maxWithdrawCollateral}
265
266
  **/
266
- async maxWithdrawCollateral(position, token) {
267
+ async maxWithdrawCollateral(position, token, targetHF) {
267
268
  return this.queryChain({
268
269
  network: position.chainId,
269
270
  run: async (sdk) => service(sdk).maxWithdrawCollateral({
270
271
  creditAccount: await slice(sdk, position.creditAccount),
271
272
  sdk,
272
- token
273
+ token,
274
+ targetHF
273
275
  })
274
276
  });
275
277
  }
@@ -49,6 +49,10 @@ var CreditAccountOperationsService = class extends SDKConstruct {
49
49
  * withdraw form should offer. Taking everything out is the same intent with
50
50
  * `MAX_UINT256` for an amount, and needs none of this arithmetic.
51
51
  *
52
+ * Takes no target health factor, unlike {@link maxWithdrawCollateral}: a
53
+ * proportional withdrawal leaves the factor where it found it, and the
54
+ * facade's `minDebt` is what bounds it.
55
+ *
52
56
  * @param props - Account slice and the SDK holding its market
53
57
  * @returns Amount in underlying units; `0n` when nothing can leave
54
58
  */
@@ -84,8 +88,9 @@ var CreditAccountOperationsService = class extends SDKConstruct {
84
88
  * yet, and adjusting measures against the net value the caller already
85
89
  * holds. Nothing is fetched, so a form can ask on every keystroke.
86
90
  *
87
- * @param props - The manager, the SDK holding its market, and what stands
88
- * behind the position
91
+ * @param props - The manager, the SDK holding its market, what stands
92
+ * behind the position, and optionally the health factor the ceiling should
93
+ * leave
89
94
  * @returns The band, or nothing when the market has none to offer
90
95
  */
91
96
  leverageBand(props) {
@@ -93,19 +98,23 @@ var CreditAccountOperationsService = class extends SDKConstruct {
93
98
  }
94
99
  /**
95
100
  * Largest `WITHDRAW_ASSET` amount of one token the account can take out
96
- * while its health factor stays at {@link MIN_HF_LIMITED} plus a basis
97
- * point — the ceiling a withdraw-collateral form should offer. Thresholds,
98
- * prices and quota activity come from the account's market, valued the way
99
- * the facade values a call that pays out; zero debt frees the whole balance.
101
+ * while its health factor stays at `targetHF` the ceiling a
102
+ * withdraw-collateral form should offer. Thresholds, prices and quota
103
+ * activity come from the account's market, valued the way the facade values
104
+ * a call that pays out; zero debt frees the whole balance.
105
+ *
106
+ * The default is {@link MIN_HF_LIMITED}, the bar `validateHF` holds an
107
+ * account to.
100
108
  *
101
- * @param props - Account slice, the SDK holding its market, and the
102
- * collateral to withdraw
109
+ * @param props - Account slice, the SDK holding its market, the collateral
110
+ * to withdraw, and optionally the health factor to leave behind
103
111
  * @returns Amount in the token's units; `0n` when nothing can leave
104
112
  */
105
113
  maxWithdrawCollateral(props) {
114
+ const { targetHF = MIN_HF_LIMITED, ...rest } = props;
106
115
  return maxWithdrawCollateral({
107
- ...props,
108
- targetHF: MIN_HF_LIMITED + 2n
116
+ ...rest,
117
+ targetHF: targetHF + 2n
109
118
  });
110
119
  }
111
120
  /**
@@ -30,13 +30,13 @@ import "./utils/index.js";
30
30
  * calcLeverageBand({ sdk, creditManager, collateral }) // { min: 1.1, max: 9 }
31
31
  * ```
32
32
  **/
33
- function calcLeverageBand({ sdk, creditManager, collateral }) {
33
+ function calcLeverageBand({ sdk, creditManager, collateral, targetHF }) {
34
34
  const found = resolve(sdk, creditManager);
35
35
  if (!found) return;
36
36
  const { suite, market } = found;
37
37
  const target = suite.strategyTargetCollateral;
38
38
  if (!target) return;
39
- const ceiling = suite.creditManager.maxLeverage(target);
39
+ const ceiling = suite.creditManager.maxLeverage(target, targetHF);
40
40
  const underlying = market.pool.underlying;
41
41
  const convert = convertAmount(sdk, creditManager);
42
42
  const netValue = collateral.reduce((acc, a) => acc + convert(a.token, underlying, a.balance), 0n);
@@ -182,7 +182,7 @@ function buildMockSdk(args) {
182
182
  liquidationThresholds,
183
183
  collateralTokens,
184
184
  feeInterest: args.feeInterest ?? 0,
185
- maxLeverage: (collateral) => calcMaxLeverage(liquidationThresholds.get(collateral) ?? 0)
185
+ maxLeverage: (collateral, targetHF) => calcMaxLeverage(liquidationThresholds.get(collateral) ?? 0, targetHF)
186
186
  },
187
187
  creditFacade: {
188
188
  address: args.creditFacade,
@@ -65,8 +65,8 @@ var CreditManagerV310Contract = class extends BaseContract {
65
65
  /**
66
66
  * {@inheritDoc ICreditManagerContract.maxLeverage}
67
67
  */
68
- maxLeverage(collateral) {
69
- return calcMaxLeverage(this.liquidationThresholds.mustGet(collateral));
68
+ maxLeverage(collateral, targetHF) {
69
+ return calcMaxLeverage(this.liquidationThresholds.mustGet(collateral), targetHF);
70
70
  }
71
71
  /**
72
72
  * {@inheritDoc ICreditManagerContract.liquidationPremium}
@@ -162,24 +162,36 @@ function calcNetStrategyApy(opportunity, totalCollateralApy, leverage, mode = "s
162
162
  **/
163
163
  const MAX_LEVERAGE_BUFFER_BPS = 500;
164
164
  /**
165
- * Highest total-value leverage a liquidation threshold allows, floored:
166
- * `floor((100% − buffer) / (100% − liquidationThreshold))`. At HF = 1, debt is
167
- * `liquidationThreshold × totalValue`, leaving `1 − liquidationThreshold` of
168
- * equity per unit of exposure; the {@link MAX_LEVERAGE_BUFFER_BPS} buffer
169
- * keeps the maxed position slightly away from that boundary.
165
+ * Highest total-value leverage a liquidation threshold allows, floored.
166
+ *
167
+ * At HF = 1, debt is `liquidationThreshold × totalValue`, leaving
168
+ * `1 − liquidationThreshold` of equity per unit of exposure; a maxed position
169
+ * has to stay some way off that boundary. Given a `targetHF`, that distance is
170
+ * solved for — `HF = liquidationThreshold × L / (L − 1)` inverts to
171
+ * `L = targetHF / (targetHF − liquidationThreshold)`.
172
+ *
173
+ * Without one it falls back on a flat {@link MAX_LEVERAGE_BUFFER_BPS}, which
174
+ * under-buffers as the threshold rises — at 95% it allows 19x, or HF ≈ 1.0028.
175
+ * That branch is scaffolding, kept so this parameter moves no number before
176
+ * the callers name a target, and goes away with the constant.
177
+ *
178
+ * @param targetHF - Health factor the maxed position should leave, in basis
179
+ * points. Omitted keeps the legacy buffer.
170
180
  *
171
181
  * @example
172
182
  * ```ts
173
183
  * // liquidationThreshold: 9000 bps = 90%
174
- * calcMaxLeverage(9000) // floor((1 − 0.05) / (1 − 0.9)) = 9x total exposure
184
+ * calcMaxLeverage(9000) // floor((1 − 0.05) / (1 − 0.9)) = 9x
185
+ * calcMaxLeverage(9000, 10100) // floor(1.01 / (1.01 − 0.9)) = 9x
175
186
  * ```
176
- * @throws If `liquidationThreshold` is 100% or more, which would make
177
- * leverage unbounded.
187
+ * @throws If `liquidationThreshold` is 100% or more, or reaches a named
188
+ * `targetHF` — either way no leverage clears the bar.
178
189
  **/
179
- function calcMaxLeverage(liquidationThreshold) {
190
+ function calcMaxLeverage(liquidationThreshold, targetHF) {
180
191
  if (liquidationThreshold >= FULL) throw new Error("cannot compute max leverage: liquidation threshold is 100% or more");
181
- const leverage = Math.floor((FULL - 500) / (FULL - liquidationThreshold));
182
- return Math.max(leverage, 1);
192
+ if (targetHF === void 0) return Math.max(Math.floor((FULL - 500) / (FULL - liquidationThreshold)), 1);
193
+ if (liquidationThreshold >= targetHF) throw new Error("cannot compute max leverage: liquidation threshold reaches the target health factor");
194
+ return Math.max(Math.floor(targetHF / (targetHF - liquidationThreshold)), 1);
183
195
  }
184
196
  /**
185
197
  * Converts a credit account's health factor from the 18-decimal fixed point the
@@ -1,5 +1,5 @@
1
1
  import "./apy/index.js";
2
- import { RewardAmountAPI } from "./rewards/api.js";
2
+ import { getMerklRewards } from "./rewards/api.js";
3
3
  import { PoolPointsAPI, getKeyForPoolPointsInfo } from "./rewards/extra-apy.js";
4
4
  import "./rewards/index.js";
5
- export { PoolPointsAPI, RewardAmountAPI, getKeyForPoolPointsInfo };
5
+ export { PoolPointsAPI, getKeyForPoolPointsInfo, getMerklRewards };
@@ -1,69 +1,79 @@
1
+ import { AddressMap } from "../../onchain/utils/AddressMap.js";
1
2
  import { BigIntMath } from "../../onchain/utils/bigint-math.js";
2
- import { chains } from "../../onchain/chain/chains.js";
3
3
  import { toBigInt } from "../../onchain/utils/formatter.js";
4
4
  import "../../onchain/index.js";
5
5
  import "../../common-utils/index.js";
6
6
  import { MerkleXYZApi } from "./merkl-api.js";
7
- import { getAddress } from "viem";
7
+ import { getAddress, isAddress } from "viem";
8
8
  //#region src/rewards/rewards/api.ts
9
- var RewardAmountAPI = class RewardAmountAPI {
10
- constructor() {}
11
- static async getLmRewardsMerkle({ pools, account, network, reportError, apiKey }) {
12
- const [merkleXYZLMResponse] = await Promise.allSettled([MerkleXYZApi.fetchWithFallback(MerkleXYZApi.getUserRewardsUrl({ params: {
13
- chainId: chains[network].id,
14
- user: getAddress(account)
15
- } }), apiKey)]);
16
- const merkleXYZLm = RewardAmountAPI.extractFulfilled(merkleXYZLMResponse, reportError, "merkleXYZLm")?.data;
17
- const poolByItsToken = Object.values(pools).reduce((acc, p) => {
18
- p.stakedDieselToken.forEach((t) => {
19
- if (t) acc[t] = p.address;
20
- });
21
- p.stakedDieselToken_old.forEach((t) => {
22
- if (t) acc[t] = p.address;
23
- });
24
- acc[p.dieselToken] = p.address;
25
- return acc;
26
- }, {});
27
- const extraRewards = (merkleXYZLm || []).reduce((acc, chainRewards) => {
28
- chainRewards.rewards.forEach((reward) => {
29
- const rewardToken = reward.token.address.toLowerCase();
30
- reward.breakdowns.forEach((reason) => {
31
- const poolToken = ((reason.reason || "").split("_").find((part) => part.startsWith("0x")) || "").toLowerCase();
32
- const pool = poolByItsToken[poolToken];
33
- const total = toBigInt(reason.amount || 0);
34
- const claimed = toBigInt(reason.claimed || 0);
35
- const claimable = BigIntMath.max(total - claimed, 0n);
36
- const key = [
37
- pool,
38
- poolToken,
39
- rewardToken
40
- ].join("_");
41
- if (pool && claimable > 0n) {
42
- const prevAmount = acc[key]?.amount || 0n;
43
- acc[key] = {
44
- pool,
45
- poolToken,
46
- rewardToken,
47
- rewardTokenSymbol: reward.token.symbol,
48
- rewardTokenDecimals: reward.token.decimals || 18,
49
- amount: prevAmount + claimable,
50
- type: "extraMerkle"
51
- };
52
- }
9
+ /**
10
+ * The wallet's claimable Merkl rewards on one chain.
11
+ *
12
+ * Never rejects on a transport failure: the fetch is settled rather than
13
+ * awaited, and a failure goes to `reportError` and yields an empty list. A
14
+ * caller that must tell "this chain is down" from "this chain has no rewards"
15
+ * has to watch that callback.
16
+ */
17
+ async function getMerklRewards({ sdk, account, reportError, apiKey }) {
18
+ const [merkleXYZLMResponse] = await Promise.allSettled([MerkleXYZApi.fetchWithFallback(MerkleXYZApi.getUserRewardsUrl({ params: {
19
+ chainId: sdk.chainId,
20
+ user: getAddress(account)
21
+ } }), apiKey)]);
22
+ const merkleXYZLm = extractFulfilled(merkleXYZLMResponse, reportError, "merkleXYZLm")?.data;
23
+ const poolByItsToken = AddressMap.fromMappedArray(sdk.marketRegister.pools.map(({ pool }) => pool.address), (address) => address);
24
+ const claimable = /* @__PURE__ */ new Map();
25
+ for (const chainRewards of merkleXYZLm || []) for (const reward of chainRewards.rewards) {
26
+ if (!isAddress(reward.token.address, { strict: false })) continue;
27
+ const rewardTokenAddress = getAddress(reward.token.address);
28
+ for (const reason of reward.breakdowns) {
29
+ const poolTokenAddress = (reason.reason || "").split("_").find((part) => part.startsWith("0x")) ?? "";
30
+ if (!isAddress(poolTokenAddress, { strict: false })) continue;
31
+ const pool = poolByItsToken.get(poolTokenAddress);
32
+ if (!pool) continue;
33
+ const total = toBigInt(reason.amount || 0);
34
+ const claimed = toBigInt(reason.claimed || 0);
35
+ const amount = BigIntMath.max(total - claimed, 0n);
36
+ if (amount === 0n) continue;
37
+ const key = `${pool}_${rewardTokenAddress}`;
38
+ const seen = claimable.get(key);
39
+ if (seen) {
40
+ claimable.set(key, {
41
+ ...seen,
42
+ amount: seen.amount + amount
53
43
  });
44
+ continue;
45
+ }
46
+ const poolToken = sdk.tokensMeta.getToken(pool);
47
+ if (!poolToken) continue;
48
+ claimable.set(key, {
49
+ chainId: sdk.chainId,
50
+ pool,
51
+ poolToken,
52
+ rewardToken: toRewardToken(sdk, rewardTokenAddress, reward.token),
53
+ amount
54
54
  });
55
- return acc;
56
- }, {});
57
- return Object.values(extraRewards);
58
- }
59
- static extractFulfilled(r, reportError, description) {
60
- if (r.status === "fulfilled") return r.value;
61
- else {
62
- if (reportError) reportError(r.reason, description);
63
- else console.error(r.reason);
64
- return;
65
55
  }
66
56
  }
67
- };
57
+ return [...claimable.values()];
58
+ }
59
+ /**
60
+ * A campaign's incentive token is not protocol collateral, so the registry
61
+ * usually has no entry for it — and Merkl always names it. The one place the
62
+ * two sources are reconciled.
63
+ */
64
+ function toRewardToken(sdk, address, merkl) {
65
+ return sdk.tokensMeta.getToken(address) ?? {
66
+ chainId: sdk.chainId,
67
+ address,
68
+ symbol: merkl.symbol,
69
+ name: merkl.symbol,
70
+ decimals: merkl.decimals || 18
71
+ };
72
+ }
73
+ function extractFulfilled(r, reportError, description) {
74
+ if (r.status === "fulfilled") return r.value;
75
+ if (reportError) reportError(r.reason, description);
76
+ else console.error(r.reason);
77
+ }
68
78
  //#endregion
69
- export { RewardAmountAPI };
79
+ export { getMerklRewards };
@@ -1,3 +1,3 @@
1
- import { RewardAmountAPI } from "./api.js";
1
+ import { getMerklRewards } from "./api.js";
2
2
  import { PoolPointsAPI, getKeyForPoolPointsInfo } from "./extra-apy.js";
3
- export { PoolPointsAPI, RewardAmountAPI, getKeyForPoolPointsInfo };
3
+ export { PoolPointsAPI, getKeyForPoolPointsInfo, getMerklRewards };
@@ -251,24 +251,26 @@ var PrepareApi = class extends MultichainConstruct {
251
251
  /**
252
252
  * {@inheritDoc OpportunitiesPrepare.leverageBand}
253
253
  **/
254
- leverageBand(strategy, collateral) {
254
+ leverageBand(strategy, collateral, targetHF) {
255
255
  const sdk = this.sdk.chain(strategy.chainId);
256
256
  return service(sdk).leverageBand({
257
257
  sdk,
258
258
  creditManager: strategy.creditManager,
259
- collateral
259
+ collateral,
260
+ targetHF
260
261
  });
261
262
  }
262
263
  /**
263
264
  * {@inheritDoc OpportunitiesPrepare.maxWithdrawCollateral}
264
265
  **/
265
- async maxWithdrawCollateral(position, token) {
266
+ async maxWithdrawCollateral(position, token, targetHF) {
266
267
  return this.queryChain({
267
268
  network: position.chainId,
268
269
  run: async (sdk) => service(sdk).maxWithdrawCollateral({
269
270
  creditAccount: await slice(sdk, position.creditAccount),
270
271
  sdk,
271
- token
272
+ token,
273
+ targetHF
272
274
  })
273
275
  });
274
276
  }
@@ -46,6 +46,10 @@ declare class CreditAccountOperationsService extends SDKConstruct {
46
46
  * withdraw form should offer. Taking everything out is the same intent with
47
47
  * `MAX_UINT256` for an amount, and needs none of this arithmetic.
48
48
  *
49
+ * Takes no target health factor, unlike {@link maxWithdrawCollateral}: a
50
+ * proportional withdrawal leaves the factor where it found it, and the
51
+ * facade's `minDebt` is what bounds it.
52
+ *
49
53
  * @param props - Account slice and the SDK holding its market
50
54
  * @returns Amount in underlying units; `0n` when nothing can leave
51
55
  */
@@ -76,24 +80,29 @@ declare class CreditAccountOperationsService extends SDKConstruct {
76
80
  * yet, and adjusting measures against the net value the caller already
77
81
  * holds. Nothing is fetched, so a form can ask on every keystroke.
78
82
  *
79
- * @param props - The manager, the SDK holding its market, and what stands
80
- * behind the position
83
+ * @param props - The manager, the SDK holding its market, what stands
84
+ * behind the position, and optionally the health factor the ceiling should
85
+ * leave
81
86
  * @returns The band, or nothing when the market has none to offer
82
87
  */
83
88
  leverageBand(props: LeverageBandProps): LeverageBand | undefined;
84
89
  /**
85
90
  * Largest `WITHDRAW_ASSET` amount of one token the account can take out
86
- * while its health factor stays at {@link MIN_HF_LIMITED} plus a basis
87
- * point — the ceiling a withdraw-collateral form should offer. Thresholds,
88
- * prices and quota activity come from the account's market, valued the way
89
- * the facade values a call that pays out; zero debt frees the whole balance.
91
+ * while its health factor stays at `targetHF` the ceiling a
92
+ * withdraw-collateral form should offer. Thresholds, prices and quota
93
+ * activity come from the account's market, valued the way the facade values
94
+ * a call that pays out; zero debt frees the whole balance.
95
+ *
96
+ * The default is {@link MIN_HF_LIMITED}, the bar `validateHF` holds an
97
+ * account to.
90
98
  *
91
- * @param props - Account slice, the SDK holding its market, and the
92
- * collateral to withdraw
99
+ * @param props - Account slice, the SDK holding its market, the collateral
100
+ * to withdraw, and optionally the health factor to leave behind
93
101
  * @returns Amount in the token's units; `0n` when nothing can leave
94
102
  */
95
103
  maxWithdrawCollateral(props: Pick<StartIntentProps, "creditAccount" | "sdk"> & {
96
104
  token: Address;
105
+ targetHF?: bigint;
97
106
  }): bigint;
98
107
  /**
99
108
  * Previews the same operation when its source only redeems through its
@@ -1,4 +1,4 @@
1
- import { Leverage } from "../../../model/primitives.js";
1
+ import { Bps, Leverage } from "../../../model/primitives.js";
2
2
  import "../../../model/index.js";
3
3
  import { Asset } from "../../base/types.js";
4
4
  import { OnchainSDK } from "../../OnchainSDK.js";
@@ -20,6 +20,11 @@ interface LeverageBandProps {
20
20
  * underlying here, so a caller hands over amounts and no exchange rates.
21
21
  **/
22
22
  readonly collateral: readonly Asset[];
23
+ /**
24
+ * Health factor the maxed leverage should leave the position at, in basis
25
+ * points. Omitted keeps `calcMaxLeverage` on its flat buffer.
26
+ **/
27
+ readonly targetHF?: Bps;
23
28
  }
24
29
  /**
25
30
  * The leverages this market will actually fund for a position of this size.
@@ -48,6 +53,6 @@ interface LeverageBandProps {
48
53
  * calcLeverageBand({ sdk, creditManager, collateral }) // { min: 1.1, max: 9 }
49
54
  * ```
50
55
  **/
51
- declare function calcLeverageBand({ sdk, creditManager, collateral }: LeverageBandProps): LeverageBand | undefined;
56
+ declare function calcLeverageBand({ sdk, creditManager, collateral, targetHF }: LeverageBandProps): LeverageBand | undefined;
52
57
  //#endregion
53
58
  export { LeverageBand, LeverageBandProps, calcLeverageBand };
@@ -1004,7 +1004,7 @@ declare class CreditManagerV310Contract extends BaseContract<abi> implements ICr
1004
1004
  /**
1005
1005
  * {@inheritDoc ICreditManagerContract.maxLeverage}
1006
1006
  */
1007
- maxLeverage(collateral: Address): Leverage;
1007
+ maxLeverage(collateral: Address, targetHF?: Bps): Leverage;
1008
1008
  /**
1009
1009
  * {@inheritDoc ICreditManagerContract.liquidationPremium}
1010
1010
  */
@@ -133,13 +133,14 @@ interface ICreditManagerContract extends IBaseContract {
133
133
  */
134
134
  readonly liquidationPremium: Bps;
135
135
  /**
136
- * Highest total-value leverage a collateral's liquidation threshold allows:
137
- * `(1 − 0.05) / (1 − lt)`.
136
+ * Highest total-value leverage a collateral's liquidation threshold allows.
138
137
  *
139
138
  * @param collateral - Collateral token address.
139
+ * @param targetHF - Health factor the maxed position should leave, in basis
140
+ * points. Omitted keeps the flat buffer.
140
141
  * @throws If the credit manager does not value the token.
141
142
  */
142
- maxLeverage: (collateral: Address) => Leverage;
143
+ maxLeverage: (collateral: Address, targetHF?: Bps) => Leverage;
143
144
  stateHuman: (raw?: boolean) => CreditManagerStateHuman;
144
145
  }
145
146
  /**
@@ -132,21 +132,32 @@ declare function calcNetStrategyApy(opportunity: StrategyRateInputs, totalCollat
132
132
  **/
133
133
  declare const MAX_LEVERAGE_BUFFER_BPS = 500;
134
134
  /**
135
- * Highest total-value leverage a liquidation threshold allows, floored:
136
- * `floor((100% − buffer) / (100% − liquidationThreshold))`. At HF = 1, debt is
137
- * `liquidationThreshold × totalValue`, leaving `1 − liquidationThreshold` of
138
- * equity per unit of exposure; the {@link MAX_LEVERAGE_BUFFER_BPS} buffer
139
- * keeps the maxed position slightly away from that boundary.
135
+ * Highest total-value leverage a liquidation threshold allows, floored.
136
+ *
137
+ * At HF = 1, debt is `liquidationThreshold × totalValue`, leaving
138
+ * `1 − liquidationThreshold` of equity per unit of exposure; a maxed position
139
+ * has to stay some way off that boundary. Given a `targetHF`, that distance is
140
+ * solved for — `HF = liquidationThreshold × L / (L − 1)` inverts to
141
+ * `L = targetHF / (targetHF − liquidationThreshold)`.
142
+ *
143
+ * Without one it falls back on a flat {@link MAX_LEVERAGE_BUFFER_BPS}, which
144
+ * under-buffers as the threshold rises — at 95% it allows 19x, or HF ≈ 1.0028.
145
+ * That branch is scaffolding, kept so this parameter moves no number before
146
+ * the callers name a target, and goes away with the constant.
147
+ *
148
+ * @param targetHF - Health factor the maxed position should leave, in basis
149
+ * points. Omitted keeps the legacy buffer.
140
150
  *
141
151
  * @example
142
152
  * ```ts
143
153
  * // liquidationThreshold: 9000 bps = 90%
144
- * calcMaxLeverage(9000) // floor((1 − 0.05) / (1 − 0.9)) = 9x total exposure
154
+ * calcMaxLeverage(9000) // floor((1 − 0.05) / (1 − 0.9)) = 9x
155
+ * calcMaxLeverage(9000, 10100) // floor(1.01 / (1.01 − 0.9)) = 9x
145
156
  * ```
146
- * @throws If `liquidationThreshold` is 100% or more, which would make
147
- * leverage unbounded.
157
+ * @throws If `liquidationThreshold` is 100% or more, or reaches a named
158
+ * `targetHF` — either way no leverage clears the bar.
148
159
  **/
149
- declare function calcMaxLeverage(liquidationThreshold: Bps): Leverage;
160
+ declare function calcMaxLeverage(liquidationThreshold: Bps, targetHF?: Bps): Leverage;
150
161
  /**
151
162
  * Converts a credit account's health factor from the 18-decimal fixed point the
152
163
  * contracts store to basis points.
@@ -1,7 +1,7 @@
1
1
  import { Apy, ApyDetails, DebtReward, ExternalApy, ExtraCollateralAPY, ExtraCollateralPointsInfo, FarmInfo, GearAPY, GearAPYDetails, PointsInfo, PointsReward, PoolExtraApy, PoolOutputDetails, PoolPointsInfo, TokenOutputDetails } from "./apy/output-details.js";
2
2
  import { DataResult, Output } from "./apy/output.js";
3
3
  import "./apy/index.js";
4
- import { GearboxExtraMerkleLmReward, GearboxLmReward, GetLmRewardsMerkleProps, PoolData, RewardAmountAPI } from "./rewards/api.js";
4
+ import { GetMerklRewardsProps, MerklReward, MerklRewardsSdk, getMerklRewards } from "./rewards/api.js";
5
5
  import { GetPointsByPoolProps, GetTotalTokensOnProtocolProps, PoolPointsAPI, PoolPointsBase, getKeyForPoolPointsInfo } from "./rewards/extra-apy.js";
6
6
  import "./rewards/index.js";
7
- export { Apy, ApyDetails, DataResult, DebtReward, ExternalApy, ExtraCollateralAPY, ExtraCollateralPointsInfo, FarmInfo, GearAPY, GearAPYDetails, GearboxExtraMerkleLmReward, GearboxLmReward, GetLmRewardsMerkleProps, GetPointsByPoolProps, GetTotalTokensOnProtocolProps, Output, PointsInfo, PointsReward, PoolData, PoolExtraApy, PoolOutputDetails, PoolPointsAPI, PoolPointsBase, PoolPointsInfo, RewardAmountAPI, TokenOutputDetails, getKeyForPoolPointsInfo };
7
+ export { Apy, ApyDetails, DataResult, DebtReward, ExternalApy, ExtraCollateralAPY, ExtraCollateralPointsInfo, FarmInfo, GearAPY, GearAPYDetails, GetMerklRewardsProps, GetPointsByPoolProps, GetTotalTokensOnProtocolProps, MerklReward, MerklRewardsSdk, Output, PointsInfo, PointsReward, PoolExtraApy, PoolOutputDetails, PoolPointsAPI, PoolPointsBase, PoolPointsInfo, TokenOutputDetails, getKeyForPoolPointsInfo, getMerklRewards };
@@ -1,39 +1,54 @@
1
- import { NetworkType } from "../../onchain/chain/chains.js";
1
+ import { ChainId, Token } from "../../model/primitives.js";
2
+ import "../../model/index.js";
3
+ import { OnchainSDK } from "../../onchain/OnchainSDK.js";
2
4
  import "../../onchain/index.js";
3
5
  import { Address } from "viem";
4
6
  //#region src/rewards/rewards/api.d.ts
5
- interface PoolData {
6
- address: Address;
7
- version: number;
8
- underlyingToken: Address;
9
- dieselRateRay: bigint;
10
- dieselToken: Address;
11
- stakedDieselToken: Address[];
12
- stakedDieselToken_old: Address[];
13
- expectedLiquidity: bigint;
7
+ /**
8
+ * One claimable Merkl liquidity-mining reward, denominated.
9
+ *
10
+ * Both tokens arrive resolved, so a consumer neither looks one up nor
11
+ * reassembles one out of the loose fields Merkl sends.
12
+ */
13
+ interface MerklReward {
14
+ readonly chainId: ChainId;
15
+ /** Market pool whose depositors the campaign rewards. */
16
+ readonly pool: Address;
17
+ /** That pool's share token — what the campaign is keyed on. */
18
+ readonly poolToken: Token;
19
+ /** The incentive token being handed out. */
20
+ readonly rewardToken: Token;
21
+ /** Claimable amount, i.e. distributed minus already claimed. Always > 0. */
22
+ readonly amount: bigint;
14
23
  }
15
- interface GearboxExtraMerkleLmReward {
16
- pool: Address;
17
- poolToken: Address;
18
- rewardTokenSymbol: string;
19
- rewardTokenDecimals: number;
20
- rewardToken: Address;
21
- amount: bigint;
22
- type: "extraMerkle";
23
- }
24
- type GearboxLmReward = GearboxExtraMerkleLmReward;
25
24
  type ReportHandler = (e: unknown, description?: string) => void;
26
- interface GetLmRewardsMerkleProps {
27
- pools: Record<Address, PoolData>;
25
+ /**
26
+ * What this read needs off a chain's SDK: which chain to ask Merkl about, the
27
+ * pools a campaign can be keyed on, and the registry that names their tokens.
28
+ *
29
+ * Sliced rather than taking the whole {@link OnchainSDK} so a caller can hand
30
+ * over a narrowed object — a test fixture included — without casting.
31
+ */
32
+ type MerklRewardsSdk = Pick<OnchainSDK, "chainId" | "marketRegister" | "tokensMeta">;
33
+ interface GetMerklRewardsProps {
34
+ /**
35
+ * The chain's SDK, attached. Reading `marketRegister` before attach throws,
36
+ * which the slice above cannot express.
37
+ */
38
+ sdk: MerklRewardsSdk;
28
39
  account: Address;
29
- network: NetworkType;
30
40
  reportError?: ReportHandler;
41
+ /** Raises Merkl's rate limit; the keyless path answers too. */
31
42
  apiKey?: string;
32
43
  }
33
- declare class RewardAmountAPI {
34
- private constructor();
35
- static getLmRewardsMerkle({ pools, account, network, reportError, apiKey }: GetLmRewardsMerkleProps): Promise<GearboxExtraMerkleLmReward[]>;
36
- private static extractFulfilled;
37
- }
44
+ /**
45
+ * The wallet's claimable Merkl rewards on one chain.
46
+ *
47
+ * Never rejects on a transport failure: the fetch is settled rather than
48
+ * awaited, and a failure goes to `reportError` and yields an empty list. A
49
+ * caller that must tell "this chain is down" from "this chain has no rewards"
50
+ * has to watch that callback.
51
+ */
52
+ declare function getMerklRewards({ sdk, account, reportError, apiKey }: GetMerklRewardsProps): Promise<MerklReward[]>;
38
53
  //#endregion
39
- export { GearboxExtraMerkleLmReward, GearboxLmReward, GetLmRewardsMerkleProps, PoolData, RewardAmountAPI };
54
+ export { GetMerklRewardsProps, MerklReward, MerklRewardsSdk, getMerklRewards };
@@ -1,3 +1,3 @@
1
- import { GearboxExtraMerkleLmReward, GearboxLmReward, GetLmRewardsMerkleProps, PoolData, RewardAmountAPI } from "./api.js";
1
+ import { GetMerklRewardsProps, MerklReward, MerklRewardsSdk, getMerklRewards } from "./api.js";
2
2
  import { GetPointsByPoolProps, GetTotalTokensOnProtocolProps, PoolPointsAPI, PoolPointsBase, getKeyForPoolPointsInfo } from "./extra-apy.js";
3
- export { GearboxExtraMerkleLmReward, GearboxLmReward, GetLmRewardsMerkleProps, GetPointsByPoolProps, GetTotalTokensOnProtocolProps, PoolData, PoolPointsAPI, PoolPointsBase, RewardAmountAPI, getKeyForPoolPointsInfo };
3
+ export { GetMerklRewardsProps, GetPointsByPoolProps, GetTotalTokensOnProtocolProps, MerklReward, MerklRewardsSdk, PoolPointsAPI, PoolPointsBase, getKeyForPoolPointsInfo, getMerklRewards };
@@ -1,4 +1,4 @@
1
- import { ChainId } from "../../model/primitives.js";
1
+ import { Bps, ChainId } from "../../model/primitives.js";
2
2
  import { DataResponse } from "../../model/response.js";
3
3
  import "../../model/index.js";
4
4
  import { Asset } from "../../onchain/base/types.js";
@@ -91,11 +91,11 @@ declare class PrepareApi extends MultichainConstruct implements OpportunitiesPre
91
91
  /**
92
92
  * {@inheritDoc OpportunitiesPrepare.leverageBand}
93
93
  **/
94
- leverageBand(strategy: StrategyInput, collateral: readonly Asset[]): LeverageBand | undefined;
94
+ leverageBand(strategy: StrategyInput, collateral: readonly Asset[], targetHF?: Bps): LeverageBand | undefined;
95
95
  /**
96
96
  * {@inheritDoc OpportunitiesPrepare.maxWithdrawCollateral}
97
97
  **/
98
- maxWithdrawCollateral(position: PositionInput, token: Address): Promise<DataResponse<bigint>>;
98
+ maxWithdrawCollateral(position: PositionInput, token: Address, targetHF?: bigint): Promise<DataResponse<bigint>>;
99
99
  }
100
100
  //#endregion
101
101
  export { ChainOf, PrepareApi };
@@ -1,3 +1,4 @@
1
+ import { Bps } from "../../model/primitives.js";
1
2
  import { PoolOpportunityKey, StrategyOpportunityKey } from "../../model/opportunities.js";
2
3
  import { StrategyPositionKey } from "../../model/positions.js";
3
4
  import { DataResponse } from "../../model/response.js";
@@ -483,7 +484,7 @@ interface OpportunitiesPrepare {
483
484
  * no borrowing room left. Not the same answer as "every leverage works", and
484
485
  * a caller must not mark a range for it.
485
486
  **/
486
- leverageBand(strategy: StrategyInput, collateral: readonly Asset[]): LeverageBand | undefined;
487
+ leverageBand(strategy: StrategyInput, collateral: readonly Asset[], targetHF?: Bps): LeverageBand | undefined;
487
488
  /**
488
489
  * Largest amount of one collateral {@link withdrawCollateral} can move out
489
490
  * while the account stays safely collateralised, in the token's units: the
@@ -491,8 +492,11 @@ interface OpportunitiesPrepare {
491
492
  * capped by their quotas) and the target keeps covering what the debt still
492
493
  * requires. Zero debt frees the whole balance — the ceiling a
493
494
  * withdraw-collateral form should offer.
495
+ *
496
+ * `targetHF` names the health factor to leave the account at, in basis
497
+ * points; omitted, the SDK holds it to the bar a form would.
494
498
  **/
495
- maxWithdrawCollateral(position: PositionInput, token: Address): Promise<DataResponse<bigint>>;
499
+ maxWithdrawCollateral(position: PositionInput, token: Address, targetHF?: bigint): Promise<DataResponse<bigint>>;
496
500
  /**
497
501
  * The tail of a delayed route: claim the matured withdrawal, then whatever the
498
502
  * operation that requested it still owes — repaying debt and paying the wallet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/sdk",
3
- "version": "16.0.0-next.16",
3
+ "version": "16.0.0-next.18",
4
4
  "description": "Gearbox SDK",
5
5
  "license": "MIT",
6
6
  "repository": {