@gearbox-protocol/sdk 17.2.0-next.1 → 17.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/onchain/accounts/intents/maxBorrow.js +10 -12
- package/dist/cjs/onchain/accounts/intents/testing/sdk-mock.js +0 -1
- package/dist/cjs/onchain/chain/chains.js +1 -0
- package/dist/cjs/onchain/market/credit/CreditSuite.js +3 -43
- package/dist/cjs/onchain/market/pool/PoolQuotaKeeperV310Contract.js +1 -4
- package/dist/cjs/rewards/RewardsService.js +48 -0
- package/dist/cjs/rewards/errors.js +15 -4
- package/dist/cjs/rewards/helpers.js +88 -0
- package/dist/cjs/rewards/index.js +3 -4
- package/dist/cjs/rewards/merkl-api.js +14 -25
- package/dist/cjs/rewards/toMerklRewards.js +21 -76
- package/dist/cjs/rewards/toTurtleRewards.js +61 -0
- package/dist/cjs/rewards/turtle-api.js +67 -0
- package/dist/cjs/rewards/types.js +1 -0
- package/dist/esm/onchain/accounts/intents/maxBorrow.js +10 -12
- package/dist/esm/onchain/accounts/intents/testing/sdk-mock.js +0 -1
- package/dist/esm/onchain/chain/chains.js +1 -0
- package/dist/esm/onchain/market/credit/CreditSuite.js +3 -43
- package/dist/esm/onchain/market/pool/PoolQuotaKeeperV310Contract.js +1 -4
- package/dist/esm/rewards/RewardsService.js +47 -0
- package/dist/esm/rewards/errors.js +15 -5
- package/dist/esm/rewards/helpers.js +83 -0
- package/dist/esm/rewards/index.js +3 -4
- package/dist/esm/rewards/merkl-api.js +15 -25
- package/dist/esm/rewards/toMerklRewards.js +22 -77
- package/dist/esm/rewards/toTurtleRewards.js +60 -0
- package/dist/esm/rewards/turtle-api.js +64 -0
- package/dist/esm/rewards/types.js +1 -0
- package/dist/types/model/errors/operation-errors.d.ts +1 -6
- package/dist/types/onchain/accounts/intents/maxBorrow.d.ts +9 -11
- package/dist/types/onchain/market/credit/CreditSuite.d.ts +1 -20
- package/dist/types/onchain/market/pool/PoolQuotaKeeperV310Contract.d.ts +0 -2
- package/dist/types/rewards/RewardsService.d.ts +29 -0
- package/dist/types/rewards/errors.d.ts +8 -3
- package/dist/types/rewards/helpers.d.ts +44 -0
- package/dist/types/rewards/index.d.ts +6 -4
- package/dist/types/rewards/merkl-api.d.ts +5 -10
- package/dist/types/rewards/toMerklRewards.d.ts +6 -37
- package/dist/types/rewards/toTurtleRewards.d.ts +10 -0
- package/dist/types/rewards/turtle-api.d.ts +66 -0
- package/dist/types/rewards/types.d.ts +41 -0
- package/package.json +1 -1
- package/dist/cjs/rewards/multichain.js +0 -40
- package/dist/esm/rewards/multichain.js +0 -39
- package/dist/types/rewards/multichain.d.ts +0 -33
|
@@ -21,18 +21,16 @@ const require_onchain_accounts_intents_collateral_valuation = require("./collate
|
|
|
21
21
|
* prices, under its liquidation threshold, capped by the quota the borrow
|
|
22
22
|
* buys for it, all of which is {@link collateralValuation}'s business. The ceiling
|
|
23
23
|
* is then held to what the market will actually lend: the pool's free
|
|
24
|
-
* liquidity, the manager's own allowance
|
|
25
|
-
*
|
|
24
|
+
* liquidity, the manager's own allowance and the facade's `maxDebt`, whichever
|
|
25
|
+
* binds first.
|
|
26
26
|
*
|
|
27
|
-
* The facade's `minDebt` is not applied
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* is under `minDebt` is different: `maxStrategyBorrowAmount` answers `0n`,
|
|
35
|
-
* because no loan of any size exists there.
|
|
27
|
+
* The facade's `minDebt` is deliberately not applied. It is a floor, and a
|
|
28
|
+
* ceiling answered as `0n` because the collateral is too small for this market
|
|
29
|
+
* would tell a form nothing about what it is holding — the number a user needs
|
|
30
|
+
* to see is the one they are short of. Collateral that carries something
|
|
31
|
+
* therefore answers with it, whether or not the market would lend that little;
|
|
32
|
+
* a loan under the floor is refused by `borrow` itself, with `debtOutOfRange`
|
|
33
|
+
* naming both ends.
|
|
36
34
|
*
|
|
37
35
|
* Nothing is fetched or simulated — the account does not exist yet and every
|
|
38
36
|
* input is loaded market state, so a form can call this on each keystroke.
|
|
@@ -83,7 +81,7 @@ function maxBorrow(props) {
|
|
|
83
81
|
const weighted = valuation.checkedUsd(holding) * valuation.lt(collateralToken);
|
|
84
82
|
const backed = quotas.some((q) => require_onchain_accounts_intents_utils_common.eq(q.token, collateralToken)) ? require_onchain_utils_bigint_math.BigIntMath.min(valuation.quotaValue(holding), weighted) : weighted;
|
|
85
83
|
if (backed <= 0n) return 0n;
|
|
86
|
-
const ceiling = require_onchain_utils_bigint_math.BigIntMath.min(priceOracle.safeConvertFromUSD(underlying, backed / targetHF).value, suite.
|
|
84
|
+
const ceiling = require_onchain_utils_bigint_math.BigIntMath.min(priceOracle.safeConvertFromUSD(underlying, backed / targetHF).value, suite.maxBorrowAmount().amount.value);
|
|
87
85
|
const unwrapsPayout = !!rwaAsset && require_onchain_accounts_intents_utils_common.eq(borrowToken, rwaAsset);
|
|
88
86
|
return require_onchain_accounts_intents_utils_common.eq(borrowToken, underlying) ? ceiling : unwrapsPayout ? require_onchain_accounts_intents_utils_common.toTargetDecimals(ceiling, underlying, borrowToken, sdk) : priceOracle.safeConvert(underlying, borrowToken, ceiling).value;
|
|
89
87
|
}
|
|
@@ -198,7 +198,6 @@ function buildMockSdk(args) {
|
|
|
198
198
|
creditOperationMarket: require_onchain_market_credit_CreditSuite.CreditSuite.prototype.creditOperationMarket,
|
|
199
199
|
isForbidden: require_onchain_market_credit_CreditSuite.CreditSuite.prototype.isForbidden,
|
|
200
200
|
maxBorrowAmount: require_onchain_market_credit_CreditSuite.CreditSuite.prototype.maxBorrowAmount,
|
|
201
|
-
maxStrategyBorrowAmount: require_onchain_market_credit_CreditSuite.CreditSuite.prototype.maxStrategyBorrowAmount,
|
|
202
201
|
creditManager: {
|
|
203
202
|
address: args.creditManager,
|
|
204
203
|
liquidationThresholds,
|
|
@@ -69,6 +69,7 @@ const chains = {
|
|
|
69
69
|
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": "ETH",
|
|
70
70
|
"0x18084fbA666a33d37592fA2633fD49a74DD93a88": "BTC",
|
|
71
71
|
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": "Stable",
|
|
72
|
+
"0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29": "Stable",
|
|
72
73
|
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599": "BTC"
|
|
73
74
|
}),
|
|
74
75
|
rwaTokens: new require_onchain_utils_AddressSet.AddressSet([
|
|
@@ -19,7 +19,7 @@ let viem = require("viem");
|
|
|
19
19
|
/**
|
|
20
20
|
* Amount of underlying seeded into each pool at market creation to protect
|
|
21
21
|
* from inflation attacks, in raw token units. A suite whose
|
|
22
|
-
* {@link CreditSuite.
|
|
22
|
+
* {@link CreditSuite.maxBorrowAmount} is at or below this is treated as
|
|
23
23
|
* having nothing left to lend.
|
|
24
24
|
**/
|
|
25
25
|
const MIN_STRATEGY_BORROW_AMOUNT = 100000n;
|
|
@@ -282,7 +282,7 @@ var CreditSuite = class extends require_onchain_base_SDKConstruct.SDKConstruct {
|
|
|
282
282
|
return this.forbiddenTokens.some((f) => (0, viem.isAddressEqual)(f, token));
|
|
283
283
|
}
|
|
284
284
|
/**
|
|
285
|
-
* Largest debt
|
|
285
|
+
* Largest debt one new position can take from this credit manager right now,
|
|
286
286
|
* and which limit set that number.
|
|
287
287
|
*
|
|
288
288
|
* Minimum of:
|
|
@@ -291,10 +291,6 @@ var CreditSuite = class extends require_onchain_base_SDKConstruct.SDKConstruct {
|
|
|
291
291
|
* - the facade's per-account `maxDebt`.
|
|
292
292
|
* While `maxDebtPerBlockMultiplier` is `0` the facade
|
|
293
293
|
* takes no new debt at all, so the answer is `0`.
|
|
294
|
-
*
|
|
295
|
-
* These are the bounds every debt increase answers to, an existing account's
|
|
296
|
-
* included, which is what the guards hold a simulation to. Opening a position
|
|
297
|
-
* answers to two more — see {@link maxStrategyBorrowAmount}.
|
|
298
294
|
*/
|
|
299
295
|
maxBorrowAmount() {
|
|
300
296
|
const { pool } = this.market.pool;
|
|
@@ -324,42 +320,6 @@ var CreditSuite = class extends require_onchain_base_SDKConstruct.SDKConstruct {
|
|
|
324
320
|
};
|
|
325
321
|
}
|
|
326
322
|
/**
|
|
327
|
-
* Largest debt one new position can take from this credit manager right now,
|
|
328
|
-
* and which limit set that number.
|
|
329
|
-
*
|
|
330
|
-
* {@link maxBorrowAmount} held to the two bounds only a position being opened
|
|
331
|
-
* answers to: the remaining quota of the strategy target collateral, which
|
|
332
|
-
* the position has to buy to be worth anything, and the facade's `minDebt`,
|
|
333
|
-
* which a first debt cannot sit under. `amount` is `0` whenever no position
|
|
334
|
-
* can be opened right now, and `limit` names why.
|
|
335
|
-
*
|
|
336
|
-
* An operation on an account that already exists is held to neither: its
|
|
337
|
-
* quota is weighed against the token its own plan buys, and its debt is
|
|
338
|
-
* already over the floor, so a top-up smaller than `minDebt` is legal.
|
|
339
|
-
*/
|
|
340
|
-
maxStrategyBorrowAmount() {
|
|
341
|
-
const lends = this.maxBorrowAmount();
|
|
342
|
-
if (lends.limit === "debtPerBlockLimit") return lends;
|
|
343
|
-
const collateral = this.strategyTargetCollateral;
|
|
344
|
-
let value = lends.amount.value;
|
|
345
|
-
let limit = lends.limit;
|
|
346
|
-
if (collateral !== void 0) {
|
|
347
|
-
const quota = this.market.pool.pqk.quotaAvailable(collateral);
|
|
348
|
-
if (quota < value) {
|
|
349
|
-
value = quota;
|
|
350
|
-
limit = "quotaAvailable";
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (value < this.creditFacade.minDebt) return {
|
|
354
|
-
amount: this.market.toUnderlyingAmount(0n),
|
|
355
|
-
limit: "minDebt"
|
|
356
|
-
};
|
|
357
|
-
return {
|
|
358
|
-
amount: this.market.toUnderlyingAmount(value),
|
|
359
|
-
limit
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
323
|
* The single target collateral of this suite's strategy, or `undefined` when
|
|
364
324
|
* none can be resolved.
|
|
365
325
|
*
|
|
@@ -416,7 +376,7 @@ var CreditSuite = class extends require_onchain_base_SDKConstruct.SDKConstruct {
|
|
|
416
376
|
* or `undefined` when credit suite does not offer a strategy opportunity.
|
|
417
377
|
*/
|
|
418
378
|
strategyOpportunity() {
|
|
419
|
-
const maxBorrowAmount = this.
|
|
379
|
+
const maxBorrowAmount = this.maxBorrowAmount().amount.value;
|
|
420
380
|
if (maxBorrowAmount <= MIN_STRATEGY_BORROW_AMOUNT) return;
|
|
421
381
|
const collateral = this.strategyTargetCollateral;
|
|
422
382
|
if (!collateral) return;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_abi_310_generated = require("../../../abi/310/generated.js");
|
|
3
3
|
const require_onchain_utils_AddressMap = require("../../utils/AddressMap.js");
|
|
4
|
-
const require_onchain_utils_bigint_math = require("../../utils/bigint-math.js");
|
|
5
4
|
const require_onchain_utils_formatter = require("../../utils/formatter.js");
|
|
6
5
|
require("../../utils/index.js");
|
|
7
6
|
const require_onchain_base_BaseContract = require("../../base/BaseContract.js");
|
|
@@ -42,12 +41,10 @@ var PoolQuotaKeeperV310Contract = class extends require_onchain_base_BaseContrac
|
|
|
42
41
|
/**
|
|
43
42
|
* How much more quota the market will take for a token, in the underlying.
|
|
44
43
|
* `0n` when the market has no quota entry; not the same as {@link hasActiveQuota}.
|
|
45
|
-
* Never negative: a limit lowered under what is already quoted leaves no
|
|
46
|
-
* room, not a debt.
|
|
47
44
|
*/
|
|
48
45
|
quotaAvailable(token) {
|
|
49
46
|
const quota = this.quotas.get(token);
|
|
50
|
-
return quota ?
|
|
47
|
+
return quota ? quota.limit - quota.totalQuoted : 0n;
|
|
51
48
|
}
|
|
52
49
|
/**
|
|
53
50
|
* Annual quota rate paid on a quoted token, in basis points, or `0` when the
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_onchain_base_MultichainConstruct = require("../onchain/base/MultichainConstruct.js");
|
|
3
|
+
require("../onchain/index.js");
|
|
4
|
+
const require_rewards_merkl_api = require("./merkl-api.js");
|
|
5
|
+
const require_rewards_toMerklRewards = require("./toMerklRewards.js");
|
|
6
|
+
const require_rewards_toTurtleRewards = require("./toTurtleRewards.js");
|
|
7
|
+
const require_rewards_turtle_api = require("./turtle-api.js");
|
|
8
|
+
let viem = require("viem");
|
|
9
|
+
//#region src/rewards/RewardsService.ts
|
|
10
|
+
var RewardsService = class extends require_onchain_base_MultichainConstruct.MultichainConstruct {
|
|
11
|
+
#keys;
|
|
12
|
+
constructor(sdk, keys = {}) {
|
|
13
|
+
super(sdk);
|
|
14
|
+
this.#keys = keys;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Every claimable reward a wallet holds — Merkl campaigns and the Gearbox
|
|
18
|
+
* organisation's Turtle streams — across the chains the handle carries.
|
|
19
|
+
* A chain is `status: "error"` only when every source failed on it; a chain
|
|
20
|
+
* with nothing to claim is a `"success"` with no rewards.
|
|
21
|
+
**/
|
|
22
|
+
async list(wallet, chainIds) {
|
|
23
|
+
const { merklApiKey, turtleApiKey } = this.#keys;
|
|
24
|
+
const user = (0, viem.getAddress)(wallet);
|
|
25
|
+
const turtle = turtleApiKey ? require_rewards_turtle_api.fetchTurtleWalletRewards(user, turtleApiKey) : void 0;
|
|
26
|
+
turtle?.catch(() => {});
|
|
27
|
+
return this.queryChains({
|
|
28
|
+
chainIds,
|
|
29
|
+
label: "list rewards",
|
|
30
|
+
block: "state",
|
|
31
|
+
run: async (sdk) => {
|
|
32
|
+
const sources = [require_rewards_merkl_api.fetchMerklUserRewards({
|
|
33
|
+
chainId: sdk.chainId,
|
|
34
|
+
user,
|
|
35
|
+
apiKey: merklApiKey
|
|
36
|
+
}).then((response) => require_rewards_toMerklRewards.toMerklRewards(sdk, response))];
|
|
37
|
+
if (turtle) sources.push(turtle.then(async (rewards) => require_rewards_toTurtleRewards.toTurtleRewards(sdk, rewards, await require_rewards_turtle_api.readTurtleClaimed(sdk, user, rewards))));
|
|
38
|
+
const settled = await Promise.allSettled(sources);
|
|
39
|
+
const failed = settled.flatMap((r) => r.status === "rejected" ? [r.reason] : []);
|
|
40
|
+
if (failed.length === settled.length) throw failed.length === 1 ? failed[0] : new AggregateError(failed, "no rewards source answered");
|
|
41
|
+
for (const reason of failed) (sdk.logger ?? this.sdk.logger)?.warn(reason, `rewards source failed on chain ${sdk.chainId}`);
|
|
42
|
+
return settled.flatMap((r) => r.status === "fulfilled" ? r.value : []);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
exports.RewardsService = RewardsService;
|
|
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let viem = require("viem");
|
|
3
3
|
//#region src/rewards/errors.ts
|
|
4
4
|
/**
|
|
5
|
-
* Thrown when
|
|
5
|
+
* Thrown when Merkl did not answer for a chain.
|
|
6
6
|
*
|
|
7
7
|
* Everything a reader needs is in the message: this error travels to consumers
|
|
8
8
|
* inside a chain's {@link ChainFailed} metadata, where it is typed `unknown`
|
|
@@ -11,10 +11,10 @@ let viem = require("viem");
|
|
|
11
11
|
var MerklRequestFailedError = class extends viem.BaseError {
|
|
12
12
|
name = "MerklRequestFailedError";
|
|
13
13
|
chainId;
|
|
14
|
-
constructor(chainId, path,
|
|
14
|
+
constructor(chainId, path, cause) {
|
|
15
15
|
super(`Merkl could not be reached for chain ${chainId}.`, {
|
|
16
|
-
cause:
|
|
17
|
-
metaMessages:
|
|
16
|
+
cause: cause instanceof Error ? cause : void 0,
|
|
17
|
+
metaMessages: [`${path} — ${describe(cause)}`]
|
|
18
18
|
});
|
|
19
19
|
this.chainId = chainId;
|
|
20
20
|
}
|
|
@@ -23,5 +23,16 @@ function describe(cause) {
|
|
|
23
23
|
if (cause instanceof Error) return cause.name === "TimeoutError" ? "timed out" : cause.message;
|
|
24
24
|
return String(cause);
|
|
25
25
|
}
|
|
26
|
+
/** Turtle answers for every chain at once, so this names no chain. */
|
|
27
|
+
var TurtleRequestFailedError = class extends viem.BaseError {
|
|
28
|
+
name = "TurtleRequestFailedError";
|
|
29
|
+
constructor(path, cause) {
|
|
30
|
+
super("Turtle could not be reached.", {
|
|
31
|
+
cause: cause instanceof Error ? cause : void 0,
|
|
32
|
+
metaMessages: [`${path} — ${describe(cause)}`]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
26
36
|
//#endregion
|
|
27
37
|
exports.MerklRequestFailedError = MerklRequestFailedError;
|
|
38
|
+
exports.TurtleRequestFailedError = TurtleRequestFailedError;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_onchain_utils_AddressMap = require("../onchain/utils/AddressMap.js");
|
|
3
|
+
const require_onchain_utils_formatter = require("../onchain/utils/formatter.js");
|
|
4
|
+
require("../onchain/index.js");
|
|
5
|
+
let viem = require("viem");
|
|
6
|
+
//#region src/rewards/helpers.ts
|
|
7
|
+
/**
|
|
8
|
+
* The pool a source names, and its share token. A v3.1 pool is its own
|
|
9
|
+
* ERC-4626 share token, so its address is the only token a campaign can name;
|
|
10
|
+
* the `AddressMap` owns the casing a source writes it in.
|
|
11
|
+
*/
|
|
12
|
+
function resolvePool(sdk) {
|
|
13
|
+
const pools = require_onchain_utils_AddressMap.AddressMap.fromMappedArray(sdk.marketRegister.pools.map(({ pool }) => pool.address), (address) => address);
|
|
14
|
+
return (address) => {
|
|
15
|
+
if (!address || !(0, viem.isAddress)(address, { strict: false })) return void 0;
|
|
16
|
+
const pool = pools.get(address);
|
|
17
|
+
const poolToken = pool && sdk.tokensMeta.getToken(pool);
|
|
18
|
+
return pool && poolToken ? {
|
|
19
|
+
pool,
|
|
20
|
+
poolToken
|
|
21
|
+
} : void 0;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Sources send amounts as free-form strings, which `toBigInt` throws on. */
|
|
25
|
+
function parseAmount(value) {
|
|
26
|
+
if (value === void 0) return void 0;
|
|
27
|
+
try {
|
|
28
|
+
return require_onchain_utils_formatter.toBigInt(value || 0);
|
|
29
|
+
} catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** What is left of `total` after `claimed`, when anything is. */
|
|
34
|
+
function remaining(total, claimed) {
|
|
35
|
+
if (total === void 0 || claimed === void 0) return void 0;
|
|
36
|
+
return total > claimed ? total - claimed : void 0;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* An incentive token is rarely protocol collateral, so the registry usually
|
|
40
|
+
* has no entry for it — and both sources always name it.
|
|
41
|
+
*/
|
|
42
|
+
function toRewardToken(sdk, address, named) {
|
|
43
|
+
return sdk.tokensMeta.getToken(address) ?? {
|
|
44
|
+
chainId: sdk.chainId,
|
|
45
|
+
address,
|
|
46
|
+
symbol: named.symbol,
|
|
47
|
+
name: named.name ?? named.symbol,
|
|
48
|
+
decimals: named.decimals || 18
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** A missing or unparsable price is `null`. */
|
|
52
|
+
function toPricedAmount(token, value, price) {
|
|
53
|
+
const usd = Number(price ?? NaN);
|
|
54
|
+
return {
|
|
55
|
+
token,
|
|
56
|
+
value,
|
|
57
|
+
valueUsd: Number.isFinite(usd) ? Number((0, viem.formatUnits)(value, token.decimals)) * usd : null
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One reward per pool and incentive token, at the last part's price. Amounts
|
|
62
|
+
* are summed raw and priced once at the end — pricing each part and adding the results would round
|
|
63
|
+
* every one of them.
|
|
64
|
+
*/
|
|
65
|
+
function mergeRewards(sdk, source, parts) {
|
|
66
|
+
const merged = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const part of parts) {
|
|
68
|
+
const key = `${part.pool}_${part.token.address}`;
|
|
69
|
+
const seen = merged.get(key);
|
|
70
|
+
merged.set(key, seen ? {
|
|
71
|
+
...part,
|
|
72
|
+
value: seen.value + part.value
|
|
73
|
+
} : part);
|
|
74
|
+
}
|
|
75
|
+
return [...merged.values()].map(({ pool, poolToken, token, value, price }) => ({
|
|
76
|
+
source,
|
|
77
|
+
chainId: sdk.chainId,
|
|
78
|
+
pool,
|
|
79
|
+
poolToken,
|
|
80
|
+
amount: toPricedAmount(token, value, price)
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
exports.mergeRewards = mergeRewards;
|
|
85
|
+
exports.parseAmount = parseAmount;
|
|
86
|
+
exports.remaining = remaining;
|
|
87
|
+
exports.resolvePool = resolvePool;
|
|
88
|
+
exports.toRewardToken = toRewardToken;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rewards_errors = require("./errors.js");
|
|
3
|
-
const
|
|
4
|
-
const require_rewards_multichain = require("./multichain.js");
|
|
3
|
+
const require_rewards_RewardsService = require("./RewardsService.js");
|
|
5
4
|
exports.MerklRequestFailedError = require_rewards_errors.MerklRequestFailedError;
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
5
|
+
exports.RewardsService = require_rewards_RewardsService.RewardsService;
|
|
6
|
+
exports.TurtleRequestFailedError = require_rewards_errors.TurtleRequestFailedError;
|
|
@@ -1,45 +1,34 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rewards_errors = require("./errors.js");
|
|
3
3
|
//#region src/rewards/merkl-api.ts
|
|
4
|
-
|
|
5
|
-
* Merkl's own host and the Angle mirror, tried in this order.
|
|
6
|
-
*/
|
|
7
|
-
const MERKL_DOMAINS = ["https://api.merkl.xyz", "https://api-merkl.angle.money"];
|
|
4
|
+
const MERKL_API_URL = "https://api.merkl.xyz";
|
|
8
5
|
const MERKL_API_KEY_HEADER = "X-API-Key";
|
|
9
6
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Merkl has no timeout of its own, and a hung connection would otherwise stall
|
|
8
|
+
* its leg of a fan-out for as long as the socket lives.
|
|
12
9
|
*/
|
|
13
|
-
const
|
|
10
|
+
const TIMEOUT = 1e4;
|
|
14
11
|
/**
|
|
15
12
|
* The wallet's raw Merkl rewards on one chain.
|
|
16
13
|
*
|
|
17
|
-
* Rejects with {@link MerklRequestFailedError} when
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* was never established.
|
|
14
|
+
* Rejects with {@link MerklRequestFailedError} when Merkl does not answer, so a
|
|
15
|
+
* caller can tell an unreachable Merkl from a wallet with nothing to claim. A
|
|
16
|
+
* non-2xx counts as no answer: it carries no rewards either way, and treating
|
|
17
|
+
* it as success would report emptiness that was never established.
|
|
22
18
|
*/
|
|
23
19
|
async function fetchMerklUserRewards({ chainId, user, apiKey }) {
|
|
24
20
|
const path = `/v4/users/${user}/rewards?chainId=${chainId}`;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
headers,
|
|
30
|
-
signal: AbortSignal.timeout(ATTEMPT_TIMEOUT)
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(`${MERKL_API_URL}${path}`, {
|
|
23
|
+
headers: apiKey ? { [MERKL_API_KEY_HEADER]: apiKey } : void 0,
|
|
24
|
+
signal: AbortSignal.timeout(TIMEOUT)
|
|
31
25
|
});
|
|
32
|
-
if (!response.ok) {
|
|
33
|
-
attempts.push([domain, /* @__PURE__ */ new Error(`answered ${response.status}`)]);
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
26
|
+
if (!response.ok) throw new Error(`answered ${response.status}`);
|
|
36
27
|
return await response.json();
|
|
37
28
|
} catch (error) {
|
|
38
|
-
|
|
29
|
+
throw new require_rewards_errors.MerklRequestFailedError(chainId, path, error);
|
|
39
30
|
}
|
|
40
|
-
throw new require_rewards_errors.MerklRequestFailedError(chainId, path, attempts);
|
|
41
31
|
}
|
|
42
32
|
//#endregion
|
|
43
33
|
exports.MERKL_API_KEY_HEADER = MERKL_API_KEY_HEADER;
|
|
44
|
-
exports.MERKL_DOMAINS = MERKL_DOMAINS;
|
|
45
34
|
exports.fetchMerklUserRewards = fetchMerklUserRewards;
|
|
@@ -1,85 +1,30 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
const require_onchain_utils_bigint_math = require("../onchain/utils/bigint-math.js");
|
|
4
|
-
const require_onchain_utils_formatter = require("../onchain/utils/formatter.js");
|
|
5
|
-
require("../onchain/index.js");
|
|
2
|
+
const require_rewards_helpers = require("./helpers.js");
|
|
6
3
|
let viem = require("viem");
|
|
7
4
|
//#region src/rewards/toMerklRewards.ts
|
|
8
5
|
/**
|
|
9
|
-
* Merkl's answer for one chain,
|
|
6
|
+
* Merkl's answer for one chain, one reward per pool and incentive token.
|
|
7
|
+
* A breakdown Merkl cannot be read from is skipped rather than thrown on: it
|
|
8
|
+
* must not sink the chain, which the caller would then be told is unreachable.
|
|
10
9
|
*/
|
|
11
10
|
function toMerklRewards(sdk, response) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
seen.value += amount;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const poolToken = sdk.tokensMeta.getToken(pool);
|
|
33
|
-
if (!poolToken) continue;
|
|
34
|
-
claimable.set(key, {
|
|
35
|
-
chainId: sdk.chainId,
|
|
36
|
-
pool,
|
|
37
|
-
poolToken,
|
|
38
|
-
token: toRewardToken(sdk, rewardTokenAddress, reward.token),
|
|
39
|
-
value: amount,
|
|
40
|
-
price: reward.token.price
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return [...claimable.values()].map(toReward);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* `toBigInt` throws on anything `BigInt()` cannot parse, and Merkl's amounts
|
|
48
|
-
* are free-form strings.
|
|
49
|
-
*/
|
|
50
|
-
function toAmounts(reason) {
|
|
51
|
-
try {
|
|
52
|
-
return {
|
|
53
|
-
total: require_onchain_utils_formatter.toBigInt(reason.amount || 0),
|
|
54
|
-
claimed: require_onchain_utils_formatter.toBigInt(reason.claimed || 0)
|
|
55
|
-
};
|
|
56
|
-
} catch {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function toReward({ price, token, value, ...rest }) {
|
|
61
|
-
return {
|
|
62
|
-
...rest,
|
|
63
|
-
amount: {
|
|
64
|
-
token,
|
|
65
|
-
value,
|
|
66
|
-
valueUsd: price === void 0 ? null : Number((0, viem.formatUnits)(value, token.decimals)) * price
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* A campaign's incentive token is not protocol collateral, so the registry
|
|
72
|
-
* usually has no entry for it — and Merkl always names it. The one place the
|
|
73
|
-
* two sources are reconciled.
|
|
74
|
-
*/
|
|
75
|
-
function toRewardToken(sdk, address, merkl) {
|
|
76
|
-
return sdk.tokensMeta.getToken(address) ?? {
|
|
77
|
-
chainId: sdk.chainId,
|
|
78
|
-
address,
|
|
79
|
-
symbol: merkl.symbol,
|
|
80
|
-
name: merkl.symbol,
|
|
81
|
-
decimals: merkl.decimals || 18
|
|
82
|
-
};
|
|
11
|
+
const poolOf = require_rewards_helpers.resolvePool(sdk);
|
|
12
|
+
const parts = response.flatMap(({ rewards }) => rewards).flatMap(({ token, breakdowns }) => {
|
|
13
|
+
if (!(0, viem.isAddress)(token.address, { strict: false })) return [];
|
|
14
|
+
const rewardToken = require_rewards_helpers.toRewardToken(sdk, (0, viem.getAddress)(token.address), token);
|
|
15
|
+
return breakdowns.flatMap(({ reason, amount, claimed }) => {
|
|
16
|
+
const pool = poolOf(reason?.split("_").find((p) => p.startsWith("0x")));
|
|
17
|
+
const value = require_rewards_helpers.remaining(require_rewards_helpers.parseAmount(amount), require_rewards_helpers.parseAmount(claimed));
|
|
18
|
+
if (!pool || !value) return [];
|
|
19
|
+
return [{
|
|
20
|
+
...pool,
|
|
21
|
+
token: rewardToken,
|
|
22
|
+
value,
|
|
23
|
+
price: token.price
|
|
24
|
+
}];
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
return require_rewards_helpers.mergeRewards(sdk, "merkl", parts);
|
|
83
28
|
}
|
|
84
29
|
//#endregion
|
|
85
30
|
exports.toMerklRewards = toMerklRewards;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rewards_helpers = require("./helpers.js");
|
|
3
|
+
let viem = require("viem");
|
|
4
|
+
//#region src/rewards/toTurtleRewards.ts
|
|
5
|
+
/**
|
|
6
|
+
* The streams targeting a pool on the SDK's chain, merged per pool and
|
|
7
|
+
* incentive. A token reward is what is committed and not yet claimed.
|
|
8
|
+
*/
|
|
9
|
+
function toTurtleRewards(sdk, { streams, proofs }, claimed) {
|
|
10
|
+
const poolOf = require_rewards_helpers.resolvePool(sdk);
|
|
11
|
+
const committed = new Map(proofs.map((p) => [p.streamId, p.amount]));
|
|
12
|
+
const targeted = streams.flatMap((stream) => {
|
|
13
|
+
const target = stream.stream.customArgs.targetToken;
|
|
14
|
+
if (Number(target?.chain.chainId) !== sdk.chainId) return [];
|
|
15
|
+
const pool = poolOf(target?.address);
|
|
16
|
+
return pool ? [{
|
|
17
|
+
stream,
|
|
18
|
+
...pool
|
|
19
|
+
}] : [];
|
|
20
|
+
});
|
|
21
|
+
const tokens = targeted.flatMap(({ stream, pool, poolToken }) => {
|
|
22
|
+
const { point, rewardToken, lastSnapshot } = stream.stream;
|
|
23
|
+
if (point || !rewardToken || !(0, viem.isAddress)(rewardToken.address, { strict: false })) return [];
|
|
24
|
+
const value = require_rewards_helpers.remaining(require_rewards_helpers.parseAmount(committed.get(stream.streamId)), claimed.get(stream.streamId));
|
|
25
|
+
if (!value) return [];
|
|
26
|
+
return [{
|
|
27
|
+
pool,
|
|
28
|
+
poolToken,
|
|
29
|
+
token: require_rewards_helpers.toRewardToken(sdk, (0, viem.getAddress)(rewardToken.address), rewardToken),
|
|
30
|
+
value,
|
|
31
|
+
price: lastSnapshot?.rewardTokenPrice
|
|
32
|
+
}];
|
|
33
|
+
});
|
|
34
|
+
return [...require_rewards_helpers.mergeRewards(sdk, "turtle", tokens), ...toPoints(sdk, targeted)];
|
|
35
|
+
}
|
|
36
|
+
/** Points accrue per snapshot; the last one holds the running total. */
|
|
37
|
+
function toPoints(sdk, targeted) {
|
|
38
|
+
const merged = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const { stream, pool, poolToken } of targeted) {
|
|
40
|
+
const { point } = stream.stream;
|
|
41
|
+
const raw = require_rewards_helpers.parseAmount(stream.snapshots.at(-1)?.rewardsAccumulated);
|
|
42
|
+
if (!point || !raw) continue;
|
|
43
|
+
const key = `${pool}_${point.id}`;
|
|
44
|
+
const value = (merged.get(key)?.points.value ?? 0) + Number((0, viem.formatUnits)(raw, point.decimals));
|
|
45
|
+
merged.set(key, {
|
|
46
|
+
source: "turtle",
|
|
47
|
+
chainId: sdk.chainId,
|
|
48
|
+
pool,
|
|
49
|
+
poolToken,
|
|
50
|
+
points: {
|
|
51
|
+
id: point.id,
|
|
52
|
+
name: point.name,
|
|
53
|
+
multiplier: null,
|
|
54
|
+
value
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return [...merged.values()];
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
exports.toTurtleRewards = toTurtleRewards;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rewards_errors = require("./errors.js");
|
|
3
|
+
//#region src/rewards/turtle-api.ts
|
|
4
|
+
const TURTLE_API_URL = "https://earn.turtle.xyz";
|
|
5
|
+
const GEARBOX_ORG_ID = "d171ba3d-ff89-4e6c-8f13-d94840b06edd";
|
|
6
|
+
const TIMEOUT = 1e4;
|
|
7
|
+
const turtleStreamAbi = [{
|
|
8
|
+
type: "function",
|
|
9
|
+
name: "getClaimedRewards",
|
|
10
|
+
stateMutability: "view",
|
|
11
|
+
inputs: [{
|
|
12
|
+
name: "user",
|
|
13
|
+
type: "address"
|
|
14
|
+
}],
|
|
15
|
+
outputs: [{
|
|
16
|
+
name: "",
|
|
17
|
+
type: "uint256"
|
|
18
|
+
}]
|
|
19
|
+
}];
|
|
20
|
+
async function fetchTurtleWalletRewards(user, apiKey) {
|
|
21
|
+
const { streams: all } = await get(`/v2/streams/wallets/${user}`, apiKey);
|
|
22
|
+
const streams = all.filter((s) => s.stream.orgId === GEARBOX_ORG_ID);
|
|
23
|
+
const tokenStreamIds = streams.filter((s) => s.stream.rewardToken && s.stream.contractAddress).map((s) => s.streamId);
|
|
24
|
+
if (tokenStreamIds.length === 0) return {
|
|
25
|
+
streams,
|
|
26
|
+
proofs: []
|
|
27
|
+
};
|
|
28
|
+
const query = new URLSearchParams({ wallet: user });
|
|
29
|
+
for (const id of tokenStreamIds) query.append("streamIds", id);
|
|
30
|
+
const { proofs } = await get(`/v2/streams/merkle_proofs?${query}`, apiKey);
|
|
31
|
+
return {
|
|
32
|
+
streams,
|
|
33
|
+
proofs
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function get(path, apiKey) {
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch(`${TURTLE_API_URL}${path}`, {
|
|
39
|
+
headers: { "X-API-Key": apiKey },
|
|
40
|
+
signal: AbortSignal.timeout(TIMEOUT)
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) throw new Error(`answered ${response.status}`);
|
|
43
|
+
return await response.json();
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new require_rewards_errors.TurtleRequestFailedError(path, error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** What each of the wallet's streams on the chain has paid out, at the latest block. */
|
|
49
|
+
async function readTurtleClaimed(sdk, user, { proofs }) {
|
|
50
|
+
const onChain = proofs.filter((p) => p.chainId === sdk.chainId);
|
|
51
|
+
if (onChain.length === 0) return /* @__PURE__ */ new Map();
|
|
52
|
+
const claimed = await sdk.client.multicall({
|
|
53
|
+
contracts: onChain.map((p) => ({
|
|
54
|
+
address: p.contractAddress,
|
|
55
|
+
abi: turtleStreamAbi,
|
|
56
|
+
functionName: "getClaimedRewards",
|
|
57
|
+
args: [user]
|
|
58
|
+
})),
|
|
59
|
+
allowFailure: false,
|
|
60
|
+
blockTag: "latest"
|
|
61
|
+
});
|
|
62
|
+
return new Map(onChain.map((p, i) => [p.streamId, claimed[i]]));
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
exports.fetchTurtleWalletRewards = fetchTurtleWalletRewards;
|
|
66
|
+
exports.readTurtleClaimed = readTurtleClaimed;
|
|
67
|
+
exports.turtleStreamAbi = turtleStreamAbi;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|