@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.
- package/dist/cjs/onchain/accounts/intents/index.js +19 -10
- package/dist/cjs/onchain/accounts/intents/leverage-band.js +2 -2
- package/dist/cjs/onchain/accounts/intents/testing/sdk-mock.js +1 -1
- package/dist/cjs/onchain/market/credit/CreditManagerV310Contract.js +2 -2
- package/dist/cjs/onchain/market/math.js +23 -11
- package/dist/cjs/rewards/index.js +1 -1
- package/dist/cjs/rewards/rewards/api.js +67 -57
- package/dist/cjs/rewards/rewards/index.js +1 -1
- package/dist/cjs/sdk/prepare/PrepareApi.js +6 -4
- package/dist/esm/onchain/accounts/intents/index.js +19 -10
- package/dist/esm/onchain/accounts/intents/leverage-band.js +2 -2
- package/dist/esm/onchain/accounts/intents/testing/sdk-mock.js +1 -1
- package/dist/esm/onchain/market/credit/CreditManagerV310Contract.js +2 -2
- package/dist/esm/onchain/market/math.js +23 -11
- package/dist/esm/rewards/index.js +2 -2
- package/dist/esm/rewards/rewards/api.js +68 -58
- package/dist/esm/rewards/rewards/index.js +2 -2
- package/dist/esm/sdk/prepare/PrepareApi.js +6 -4
- package/dist/types/onchain/accounts/intents/index.d.ts +17 -8
- package/dist/types/onchain/accounts/intents/leverage-band.d.ts +7 -2
- package/dist/types/onchain/market/credit/CreditManagerV310Contract.d.ts +1 -1
- package/dist/types/onchain/market/credit/types.d.ts +4 -3
- package/dist/types/onchain/market/math.d.ts +20 -9
- package/dist/types/rewards/index.d.ts +2 -2
- package/dist/types/rewards/rewards/api.d.ts +44 -29
- package/dist/types/rewards/rewards/index.d.ts +2 -2
- package/dist/types/sdk/prepare/PrepareApi.d.ts +3 -3
- package/dist/types/sdk/prepare/types.d.ts +6 -2
- 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,
|
|
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
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
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,
|
|
103
|
-
*
|
|
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
|
-
...
|
|
109
|
-
targetHF:
|
|
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
|
-
*
|
|
168
|
-
* `liquidationThreshold × totalValue`, leaving
|
|
169
|
-
* equity per unit of exposure;
|
|
170
|
-
*
|
|
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)
|
|
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,
|
|
178
|
-
* leverage
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
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,
|
|
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
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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,
|
|
102
|
-
*
|
|
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
|
-
...
|
|
108
|
-
targetHF:
|
|
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
|
-
*
|
|
167
|
-
* `liquidationThreshold × totalValue`, leaving
|
|
168
|
-
* equity per unit of exposure;
|
|
169
|
-
*
|
|
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)
|
|
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,
|
|
177
|
-
* leverage
|
|
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
|
-
|
|
182
|
-
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 {
|
|
79
|
+
export { getMerklRewards };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getMerklRewards } from "./api.js";
|
|
2
2
|
import { PoolPointsAPI, getKeyForPoolPointsInfo } from "./extra-apy.js";
|
|
3
|
-
export { PoolPointsAPI,
|
|
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,
|
|
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
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
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,
|
|
92
|
-
*
|
|
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
|
-
*
|
|
137
|
-
* `liquidationThreshold × totalValue`, leaving
|
|
138
|
-
* equity per unit of exposure;
|
|
139
|
-
*
|
|
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)
|
|
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,
|
|
147
|
-
* leverage
|
|
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 {
|
|
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,
|
|
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 {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 {
|
|
54
|
+
export { GetMerklRewardsProps, MerklReward, MerklRewardsSdk, getMerklRewards };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GetMerklRewardsProps, MerklReward, MerklRewardsSdk, getMerklRewards } from "./api.js";
|
|
2
2
|
import { GetPointsByPoolProps, GetTotalTokensOnProtocolProps, PoolPointsAPI, PoolPointsBase, getKeyForPoolPointsInfo } from "./extra-apy.js";
|
|
3
|
-
export {
|
|
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
|