@gearbox-protocol/sdk 17.2.0 → 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/rewards/{multichain.js → RewardsService.js} +18 -30
- package/dist/cjs/rewards/errors.js +4 -4
- package/dist/cjs/rewards/helpers.js +88 -0
- package/dist/cjs/rewards/index.js +2 -2
- package/dist/cjs/rewards/merkl-api.js +14 -25
- package/dist/cjs/rewards/toMerklRewards.js +21 -77
- package/dist/cjs/rewards/toTurtleRewards.js +46 -62
- package/dist/cjs/rewards/turtle-api.js +17 -0
- package/dist/cjs/rewards/types.js +1 -0
- package/dist/esm/rewards/{multichain.js → RewardsService.js} +19 -31
- package/dist/esm/rewards/errors.js +4 -4
- package/dist/esm/rewards/helpers.js +83 -0
- package/dist/esm/rewards/index.js +2 -2
- package/dist/esm/rewards/merkl-api.js +15 -25
- package/dist/esm/rewards/toMerklRewards.js +23 -78
- package/dist/esm/rewards/toTurtleRewards.js +46 -62
- package/dist/esm/rewards/turtle-api.js +17 -1
- package/dist/esm/rewards/types.js +1 -0
- package/dist/types/rewards/RewardsService.d.ts +29 -0
- package/dist/types/rewards/errors.d.ts +2 -2
- package/dist/types/rewards/helpers.d.ts +44 -0
- package/dist/types/rewards/index.d.ts +3 -3
- package/dist/types/rewards/merkl-api.d.ts +5 -10
- package/dist/types/rewards/toMerklRewards.d.ts +5 -48
- package/dist/types/rewards/toTurtleRewards.d.ts +3 -3
- package/dist/types/rewards/turtle-api.d.ts +5 -1
- package/dist/types/rewards/types.d.ts +41 -0
- package/package.json +1 -1
- package/dist/types/rewards/multichain.d.ts +0 -28
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { AddressMap } from "../onchain/utils/AddressMap.js";
|
|
2
|
+
import { toBigInt } from "../onchain/utils/formatter.js";
|
|
3
|
+
import "../onchain/index.js";
|
|
4
|
+
import { formatUnits, isAddress } from "viem";
|
|
5
|
+
//#region src/rewards/helpers.ts
|
|
6
|
+
/**
|
|
7
|
+
* The pool a source names, and its share token. A v3.1 pool is its own
|
|
8
|
+
* ERC-4626 share token, so its address is the only token a campaign can name;
|
|
9
|
+
* the `AddressMap` owns the casing a source writes it in.
|
|
10
|
+
*/
|
|
11
|
+
function resolvePool(sdk) {
|
|
12
|
+
const pools = AddressMap.fromMappedArray(sdk.marketRegister.pools.map(({ pool }) => pool.address), (address) => address);
|
|
13
|
+
return (address) => {
|
|
14
|
+
if (!address || !isAddress(address, { strict: false })) return void 0;
|
|
15
|
+
const pool = pools.get(address);
|
|
16
|
+
const poolToken = pool && sdk.tokensMeta.getToken(pool);
|
|
17
|
+
return pool && poolToken ? {
|
|
18
|
+
pool,
|
|
19
|
+
poolToken
|
|
20
|
+
} : void 0;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Sources send amounts as free-form strings, which `toBigInt` throws on. */
|
|
24
|
+
function parseAmount(value) {
|
|
25
|
+
if (value === void 0) return void 0;
|
|
26
|
+
try {
|
|
27
|
+
return toBigInt(value || 0);
|
|
28
|
+
} catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** What is left of `total` after `claimed`, when anything is. */
|
|
33
|
+
function remaining(total, claimed) {
|
|
34
|
+
if (total === void 0 || claimed === void 0) return void 0;
|
|
35
|
+
return total > claimed ? total - claimed : void 0;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* An incentive token is rarely protocol collateral, so the registry usually
|
|
39
|
+
* has no entry for it — and both sources always name it.
|
|
40
|
+
*/
|
|
41
|
+
function toRewardToken(sdk, address, named) {
|
|
42
|
+
return sdk.tokensMeta.getToken(address) ?? {
|
|
43
|
+
chainId: sdk.chainId,
|
|
44
|
+
address,
|
|
45
|
+
symbol: named.symbol,
|
|
46
|
+
name: named.name ?? named.symbol,
|
|
47
|
+
decimals: named.decimals || 18
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** A missing or unparsable price is `null`. */
|
|
51
|
+
function toPricedAmount(token, value, price) {
|
|
52
|
+
const usd = Number(price ?? NaN);
|
|
53
|
+
return {
|
|
54
|
+
token,
|
|
55
|
+
value,
|
|
56
|
+
valueUsd: Number.isFinite(usd) ? Number(formatUnits(value, token.decimals)) * usd : null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* One reward per pool and incentive token, at the last part's price. Amounts
|
|
61
|
+
* are summed raw and priced once at the end — pricing each part and adding the results would round
|
|
62
|
+
* every one of them.
|
|
63
|
+
*/
|
|
64
|
+
function mergeRewards(sdk, source, parts) {
|
|
65
|
+
const merged = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const part of parts) {
|
|
67
|
+
const key = `${part.pool}_${part.token.address}`;
|
|
68
|
+
const seen = merged.get(key);
|
|
69
|
+
merged.set(key, seen ? {
|
|
70
|
+
...part,
|
|
71
|
+
value: seen.value + part.value
|
|
72
|
+
} : part);
|
|
73
|
+
}
|
|
74
|
+
return [...merged.values()].map(({ pool, poolToken, token, value, price }) => ({
|
|
75
|
+
source,
|
|
76
|
+
chainId: sdk.chainId,
|
|
77
|
+
pool,
|
|
78
|
+
poolToken,
|
|
79
|
+
amount: toPricedAmount(token, value, price)
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { mergeRewards, parseAmount, remaining, resolvePool, toRewardToken };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { MerklRequestFailedError, TurtleRequestFailedError } from "./errors.js";
|
|
2
|
-
import {
|
|
3
|
-
export { MerklRequestFailedError,
|
|
2
|
+
import { RewardsService } from "./RewardsService.js";
|
|
3
|
+
export { MerklRequestFailedError, RewardsService, TurtleRequestFailedError };
|
|
@@ -1,42 +1,32 @@
|
|
|
1
1
|
import { MerklRequestFailedError } from "./errors.js";
|
|
2
2
|
//#region src/rewards/merkl-api.ts
|
|
3
|
-
|
|
4
|
-
* Merkl's own host and the Angle mirror, tried in this order.
|
|
5
|
-
*/
|
|
6
|
-
const MERKL_DOMAINS = ["https://api.merkl.xyz", "https://api-merkl.angle.money"];
|
|
3
|
+
const MERKL_API_URL = "https://api.merkl.xyz";
|
|
7
4
|
const MERKL_API_KEY_HEADER = "X-API-Key";
|
|
8
5
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* Merkl has no timeout of its own, and a hung connection would otherwise stall
|
|
7
|
+
* its leg of a fan-out for as long as the socket lives.
|
|
11
8
|
*/
|
|
12
|
-
const
|
|
9
|
+
const TIMEOUT = 1e4;
|
|
13
10
|
/**
|
|
14
11
|
* The wallet's raw Merkl rewards on one chain.
|
|
15
12
|
*
|
|
16
|
-
* Rejects with {@link MerklRequestFailedError} when
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* was never established.
|
|
13
|
+
* Rejects with {@link MerklRequestFailedError} when Merkl does not answer, so a
|
|
14
|
+
* caller can tell an unreachable Merkl from a wallet with nothing to claim. A
|
|
15
|
+
* non-2xx counts as no answer: it carries no rewards either way, and treating
|
|
16
|
+
* it as success would report emptiness that was never established.
|
|
21
17
|
*/
|
|
22
18
|
async function fetchMerklUserRewards({ chainId, user, apiKey }) {
|
|
23
19
|
const path = `/v4/users/${user}/rewards?chainId=${chainId}`;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
headers,
|
|
29
|
-
signal: AbortSignal.timeout(ATTEMPT_TIMEOUT)
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(`${MERKL_API_URL}${path}`, {
|
|
22
|
+
headers: apiKey ? { [MERKL_API_KEY_HEADER]: apiKey } : void 0,
|
|
23
|
+
signal: AbortSignal.timeout(TIMEOUT)
|
|
30
24
|
});
|
|
31
|
-
if (!response.ok) {
|
|
32
|
-
attempts.push([domain, /* @__PURE__ */ new Error(`answered ${response.status}`)]);
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
25
|
+
if (!response.ok) throw new Error(`answered ${response.status}`);
|
|
35
26
|
return await response.json();
|
|
36
27
|
} catch (error) {
|
|
37
|
-
|
|
28
|
+
throw new MerklRequestFailedError(chainId, path, error);
|
|
38
29
|
}
|
|
39
|
-
throw new MerklRequestFailedError(chainId, path, attempts);
|
|
40
30
|
}
|
|
41
31
|
//#endregion
|
|
42
|
-
export { MERKL_API_KEY_HEADER,
|
|
32
|
+
export { MERKL_API_KEY_HEADER, fetchMerklUserRewards };
|
|
@@ -1,84 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { toBigInt } from "../onchain/utils/formatter.js";
|
|
4
|
-
import "../onchain/index.js";
|
|
5
|
-
import { formatUnits, getAddress, isAddress } from "viem";
|
|
1
|
+
import { mergeRewards, parseAmount, remaining, resolvePool, toRewardToken } from "./helpers.js";
|
|
2
|
+
import { getAddress, isAddress } from "viem";
|
|
6
3
|
//#region src/rewards/toMerklRewards.ts
|
|
7
4
|
/**
|
|
8
|
-
* Merkl's answer for one chain,
|
|
5
|
+
* Merkl's answer for one chain, one reward per pool and incentive token.
|
|
6
|
+
* A breakdown Merkl cannot be read from is skipped rather than thrown on: it
|
|
7
|
+
* must not sink the chain, which the caller would then be told is unreachable.
|
|
9
8
|
*/
|
|
10
9
|
function toMerklRewards(sdk, response) {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
if (!
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
seen.value += amount;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
const poolToken = sdk.tokensMeta.getToken(pool);
|
|
32
|
-
if (!poolToken) continue;
|
|
33
|
-
claimable.set(key, {
|
|
34
|
-
chainId: sdk.chainId,
|
|
35
|
-
pool,
|
|
36
|
-
poolToken,
|
|
37
|
-
token: toRewardToken(sdk, rewardTokenAddress, reward.token),
|
|
38
|
-
value: amount,
|
|
39
|
-
price: reward.token.price
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return [...claimable.values()].map(toReward);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* `toBigInt` throws on anything `BigInt()` cannot parse, and Merkl's amounts
|
|
47
|
-
* are free-form strings.
|
|
48
|
-
*/
|
|
49
|
-
function toAmounts(reason) {
|
|
50
|
-
try {
|
|
51
|
-
return {
|
|
52
|
-
total: toBigInt(reason.amount || 0),
|
|
53
|
-
claimed: toBigInt(reason.claimed || 0)
|
|
54
|
-
};
|
|
55
|
-
} catch {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function toReward({ price, token, value, ...rest }) {
|
|
60
|
-
return {
|
|
61
|
-
...rest,
|
|
62
|
-
source: "merkl",
|
|
63
|
-
amount: {
|
|
64
|
-
token,
|
|
65
|
-
value,
|
|
66
|
-
valueUsd: price === void 0 ? null : Number(formatUnits(value, token.decimals)) * price
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* An incentive token is rarely protocol collateral, so the registry usually
|
|
72
|
-
* has no entry for it — and both sources always name it.
|
|
73
|
-
*/
|
|
74
|
-
function toRewardToken(sdk, address, named) {
|
|
75
|
-
return sdk.tokensMeta.getToken(address) ?? {
|
|
76
|
-
chainId: sdk.chainId,
|
|
77
|
-
address,
|
|
78
|
-
symbol: named.symbol,
|
|
79
|
-
name: named.name ?? named.symbol,
|
|
80
|
-
decimals: named.decimals || 18
|
|
81
|
-
};
|
|
10
|
+
const poolOf = resolvePool(sdk);
|
|
11
|
+
const parts = response.flatMap(({ rewards }) => rewards).flatMap(({ token, breakdowns }) => {
|
|
12
|
+
if (!isAddress(token.address, { strict: false })) return [];
|
|
13
|
+
const rewardToken = toRewardToken(sdk, getAddress(token.address), token);
|
|
14
|
+
return breakdowns.flatMap(({ reason, amount, claimed }) => {
|
|
15
|
+
const pool = poolOf(reason?.split("_").find((p) => p.startsWith("0x")));
|
|
16
|
+
const value = remaining(parseAmount(amount), parseAmount(claimed));
|
|
17
|
+
if (!pool || !value) return [];
|
|
18
|
+
return [{
|
|
19
|
+
...pool,
|
|
20
|
+
token: rewardToken,
|
|
21
|
+
value,
|
|
22
|
+
price: token.price
|
|
23
|
+
}];
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
return mergeRewards(sdk, "merkl", parts);
|
|
82
27
|
}
|
|
83
28
|
//#endregion
|
|
84
|
-
export { toMerklRewards
|
|
29
|
+
export { toMerklRewards };
|
|
@@ -1,76 +1,60 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BigIntMath } from "../onchain/utils/bigint-math.js";
|
|
3
|
-
import { toBigInt } from "../onchain/utils/formatter.js";
|
|
4
|
-
import "../onchain/index.js";
|
|
5
|
-
import { toRewardToken } from "./toMerklRewards.js";
|
|
1
|
+
import { mergeRewards, parseAmount, remaining, resolvePool, toRewardToken } from "./helpers.js";
|
|
6
2
|
import { formatUnits, getAddress, isAddress } from "viem";
|
|
7
3
|
//#region src/rewards/toTurtleRewards.ts
|
|
8
4
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* The streams targeting a pool on the SDK's chain, merged per pool and
|
|
6
|
+
* incentive. A token reward is what is committed and not yet claimed.
|
|
11
7
|
*/
|
|
12
8
|
function toTurtleRewards(sdk, { streams, proofs }, claimed) {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
const poolOf = resolvePool(sdk);
|
|
10
|
+
const committed = new Map(proofs.map((p) => [p.streamId, p.amount]));
|
|
11
|
+
const targeted = streams.flatMap((stream) => {
|
|
12
|
+
const target = stream.stream.customArgs.targetToken;
|
|
13
|
+
if (Number(target?.chain.chainId) !== sdk.chainId) return [];
|
|
14
|
+
const pool = poolOf(target?.address);
|
|
15
|
+
return pool ? [{
|
|
16
|
+
stream,
|
|
17
|
+
...pool
|
|
18
|
+
}] : [];
|
|
19
|
+
});
|
|
20
|
+
const tokens = targeted.flatMap(({ stream, pool, poolToken }) => {
|
|
21
|
+
const { point, rewardToken, lastSnapshot } = stream.stream;
|
|
22
|
+
if (point || !rewardToken || !isAddress(rewardToken.address, { strict: false })) return [];
|
|
23
|
+
const value = remaining(parseAmount(committed.get(stream.streamId)), claimed.get(stream.streamId));
|
|
24
|
+
if (!value) return [];
|
|
25
|
+
return [{
|
|
26
|
+
pool,
|
|
27
|
+
poolToken,
|
|
28
|
+
token: toRewardToken(sdk, getAddress(rewardToken.address), rewardToken),
|
|
29
|
+
value,
|
|
30
|
+
price: lastSnapshot?.rewardTokenPrice
|
|
31
|
+
}];
|
|
32
|
+
});
|
|
33
|
+
return [...mergeRewards(sdk, "turtle", tokens), ...toPoints(sdk, targeted)];
|
|
34
|
+
}
|
|
35
|
+
/** Points accrue per snapshot; the last one holds the running total. */
|
|
36
|
+
function toPoints(sdk, targeted) {
|
|
37
|
+
const merged = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const { stream, pool, poolToken } of targeted) {
|
|
39
|
+
const { point } = stream.stream;
|
|
40
|
+
const raw = parseAmount(stream.snapshots.at(-1)?.rewardsAccumulated);
|
|
41
|
+
if (!point || !raw) continue;
|
|
42
|
+
const key = `${pool}_${point.id}`;
|
|
43
|
+
const value = (merged.get(key)?.points.value ?? 0) + Number(formatUnits(raw, point.decimals));
|
|
44
|
+
merged.set(key, {
|
|
24
45
|
source: "turtle",
|
|
25
46
|
chainId: sdk.chainId,
|
|
26
47
|
pool,
|
|
27
|
-
poolToken
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const key = `${pool}_${point.id}`;
|
|
34
|
-
const seen = rows.get(key);
|
|
35
|
-
rows.set(key, {
|
|
36
|
-
...base,
|
|
37
|
-
points: {
|
|
38
|
-
id: point.id,
|
|
39
|
-
name: point.name,
|
|
40
|
-
multiplier: null,
|
|
41
|
-
value: (seen?.points.value ?? 0) + Number(formatUnits(value, point.decimals))
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (!rewardToken || !isAddress(rewardToken.address, { strict: false })) continue;
|
|
47
|
-
const committed = parse(proofByStream.get(streamId)?.amount);
|
|
48
|
-
const done = claimed.get(streamId);
|
|
49
|
-
if (committed === void 0 || done === void 0) continue;
|
|
50
|
-
const value = BigIntMath.max(committed - done, 0n);
|
|
51
|
-
if (value === 0n) continue;
|
|
52
|
-
const token = toRewardToken(sdk, getAddress(rewardToken.address), rewardToken);
|
|
53
|
-
const key = `${pool}_${token.address}`;
|
|
54
|
-
const total = (rows.get(key)?.amount.value ?? 0n) + value;
|
|
55
|
-
const price = Number(stream.lastSnapshot?.rewardTokenPrice ?? NaN);
|
|
56
|
-
rows.set(key, {
|
|
57
|
-
...base,
|
|
58
|
-
amount: {
|
|
59
|
-
token,
|
|
60
|
-
value: total,
|
|
61
|
-
valueUsd: Number.isFinite(price) ? Number(formatUnits(total, token.decimals)) * price : null
|
|
48
|
+
poolToken,
|
|
49
|
+
points: {
|
|
50
|
+
id: point.id,
|
|
51
|
+
name: point.name,
|
|
52
|
+
multiplier: null,
|
|
53
|
+
value
|
|
62
54
|
}
|
|
63
55
|
});
|
|
64
56
|
}
|
|
65
|
-
return [...
|
|
66
|
-
}
|
|
67
|
-
function parse(amount) {
|
|
68
|
-
if (amount === void 0) return void 0;
|
|
69
|
-
try {
|
|
70
|
-
return toBigInt(amount);
|
|
71
|
-
} catch {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
57
|
+
return [...merged.values()];
|
|
74
58
|
}
|
|
75
59
|
//#endregion
|
|
76
60
|
export { toTurtleRewards };
|
|
@@ -44,5 +44,21 @@ async function get(path, apiKey) {
|
|
|
44
44
|
throw new TurtleRequestFailedError(path, error);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
/** What each of the wallet's streams on the chain has paid out, at the latest block. */
|
|
48
|
+
async function readTurtleClaimed(sdk, user, { proofs }) {
|
|
49
|
+
const onChain = proofs.filter((p) => p.chainId === sdk.chainId);
|
|
50
|
+
if (onChain.length === 0) return /* @__PURE__ */ new Map();
|
|
51
|
+
const claimed = await sdk.client.multicall({
|
|
52
|
+
contracts: onChain.map((p) => ({
|
|
53
|
+
address: p.contractAddress,
|
|
54
|
+
abi: turtleStreamAbi,
|
|
55
|
+
functionName: "getClaimedRewards",
|
|
56
|
+
args: [user]
|
|
57
|
+
})),
|
|
58
|
+
allowFailure: false,
|
|
59
|
+
blockTag: "latest"
|
|
60
|
+
});
|
|
61
|
+
return new Map(onChain.map((p, i) => [p.streamId, claimed[i]]));
|
|
62
|
+
}
|
|
47
63
|
//#endregion
|
|
48
|
-
export { fetchTurtleWalletRewards, turtleStreamAbi };
|
|
64
|
+
export { fetchTurtleWalletRewards, readTurtleClaimed, turtleStreamAbi };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ChainId } from "../model/primitives.js";
|
|
2
|
+
import { DataResponse } from "../model/response.js";
|
|
3
|
+
import "../model/index.js";
|
|
4
|
+
import { PluginsMap } from "../onchain/plugins/types.js";
|
|
5
|
+
import { MultichainSDK } from "../onchain/MultichainSDK.js";
|
|
6
|
+
import { MultichainConstruct } from "../onchain/base/MultichainConstruct.js";
|
|
7
|
+
import "../onchain/index.js";
|
|
8
|
+
import { Reward } from "./types.js";
|
|
9
|
+
import { Address } from "viem";
|
|
10
|
+
//#region src/rewards/RewardsService.d.ts
|
|
11
|
+
interface RewardsServiceKeys {
|
|
12
|
+
/** Raises Merkl's rate limit; the keyless path answers too. */
|
|
13
|
+
merklApiKey?: string;
|
|
14
|
+
/** Turtle is skipped without one: its API answers no keyless request. */
|
|
15
|
+
turtleApiKey?: string;
|
|
16
|
+
}
|
|
17
|
+
declare class RewardsService<const Plugins extends PluginsMap = {}> extends MultichainConstruct<Plugins> {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(sdk: MultichainSDK<Plugins>, keys?: RewardsServiceKeys);
|
|
20
|
+
/**
|
|
21
|
+
* Every claimable reward a wallet holds — Merkl campaigns and the Gearbox
|
|
22
|
+
* organisation's Turtle streams — across the chains the handle carries.
|
|
23
|
+
* A chain is `status: "error"` only when every source failed on it; a chain
|
|
24
|
+
* with nothing to claim is a `"success"` with no rewards.
|
|
25
|
+
**/
|
|
26
|
+
list(wallet: Address, chainIds?: ChainId[]): Promise<DataResponse<Reward[]>>;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { RewardsService };
|
|
@@ -3,7 +3,7 @@ import "../model/index.js";
|
|
|
3
3
|
import { BaseError } from "viem";
|
|
4
4
|
//#region src/rewards/errors.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Thrown when
|
|
6
|
+
* Thrown when Merkl did not answer for a chain.
|
|
7
7
|
*
|
|
8
8
|
* Everything a reader needs is in the message: this error travels to consumers
|
|
9
9
|
* inside a chain's {@link ChainFailed} metadata, where it is typed `unknown`
|
|
@@ -12,7 +12,7 @@ import { BaseError } from "viem";
|
|
|
12
12
|
declare class MerklRequestFailedError extends BaseError {
|
|
13
13
|
name: string;
|
|
14
14
|
readonly chainId: ChainId;
|
|
15
|
-
constructor(chainId: ChainId, path: string,
|
|
15
|
+
constructor(chainId: ChainId, path: string, cause: unknown);
|
|
16
16
|
}
|
|
17
17
|
/** Turtle answers for every chain at once, so this names no chain. */
|
|
18
18
|
declare class TurtleRequestFailedError extends BaseError {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Token } from "../model/primitives.js";
|
|
2
|
+
import "../model/index.js";
|
|
3
|
+
import { MerklReward, RewardsSdk } from "./types.js";
|
|
4
|
+
import { Address } from "viem";
|
|
5
|
+
//#region src/rewards/helpers.d.ts
|
|
6
|
+
/** A claimable amount of one token in one pool, before it is merged. */
|
|
7
|
+
interface RewardPart {
|
|
8
|
+
pool: Address;
|
|
9
|
+
poolToken: Token;
|
|
10
|
+
token: Token;
|
|
11
|
+
value: bigint;
|
|
12
|
+
/** USD price of one whole token, as the source sends it. */
|
|
13
|
+
price: number | string | null | undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The pool a source names, and its share token. A v3.1 pool is its own
|
|
17
|
+
* ERC-4626 share token, so its address is the only token a campaign can name;
|
|
18
|
+
* the `AddressMap` owns the casing a source writes it in.
|
|
19
|
+
*/
|
|
20
|
+
declare function resolvePool(sdk: RewardsSdk): (address: string | undefined) => {
|
|
21
|
+
pool: `0x${string}`;
|
|
22
|
+
poolToken: Token;
|
|
23
|
+
} | undefined;
|
|
24
|
+
/** Sources send amounts as free-form strings, which `toBigInt` throws on. */
|
|
25
|
+
declare function parseAmount(value: string | undefined): bigint | undefined;
|
|
26
|
+
/** What is left of `total` after `claimed`, when anything is. */
|
|
27
|
+
declare function remaining(total: bigint | undefined, claimed: bigint | undefined): bigint | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* An incentive token is rarely protocol collateral, so the registry usually
|
|
30
|
+
* has no entry for it — and both sources always name it.
|
|
31
|
+
*/
|
|
32
|
+
declare function toRewardToken(sdk: RewardsSdk, address: Address, named: {
|
|
33
|
+
symbol: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
decimals: number;
|
|
36
|
+
}): Token;
|
|
37
|
+
/**
|
|
38
|
+
* One reward per pool and incentive token, at the last part's price. Amounts
|
|
39
|
+
* are summed raw and priced once at the end — pricing each part and adding the results would round
|
|
40
|
+
* every one of them.
|
|
41
|
+
*/
|
|
42
|
+
declare function mergeRewards(sdk: RewardsSdk, source: MerklReward["source"], parts: readonly RewardPart[]): MerklReward[];
|
|
43
|
+
//#endregion
|
|
44
|
+
export { RewardPart, mergeRewards, parseAmount, remaining, resolvePool, toRewardToken };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { MerklReward, PointsReward, Reward } from "./types.js";
|
|
2
|
+
import { RewardsService } from "./RewardsService.js";
|
|
1
3
|
import { MerklRequestFailedError, TurtleRequestFailedError } from "./errors.js";
|
|
2
4
|
import { MerkleXYZUserRewardsV4Response } from "./merkl-api.js";
|
|
3
|
-
import { MerklReward, PointsReward, Reward } from "./toMerklRewards.js";
|
|
4
|
-
import { GetRewardsMultichainProps, getRewardsMultichain } from "./multichain.js";
|
|
5
5
|
import { TurtleWalletRewards } from "./turtle-api.js";
|
|
6
|
-
export {
|
|
6
|
+
export { MerklRequestFailedError, type MerklReward, type MerkleXYZUserRewardsV4Response, type PointsReward, type Reward, RewardsService, TurtleRequestFailedError, type TurtleWalletRewards };
|
|
@@ -38,10 +38,6 @@ interface MerkleXYZChain {
|
|
|
38
38
|
name: string;
|
|
39
39
|
icon: string;
|
|
40
40
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Merkl's own host and the Angle mirror, tried in this order.
|
|
43
|
-
*/
|
|
44
|
-
declare const MERKL_DOMAINS: readonly ["https://api.merkl.xyz", "https://api-merkl.angle.money"];
|
|
45
41
|
declare const MERKL_API_KEY_HEADER = "X-API-Key";
|
|
46
42
|
interface FetchMerklUserRewardsProps {
|
|
47
43
|
chainId: ChainId;
|
|
@@ -53,12 +49,11 @@ interface FetchMerklUserRewardsProps {
|
|
|
53
49
|
/**
|
|
54
50
|
* The wallet's raw Merkl rewards on one chain.
|
|
55
51
|
*
|
|
56
|
-
* Rejects with {@link MerklRequestFailedError} when
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* was never established.
|
|
52
|
+
* Rejects with {@link MerklRequestFailedError} when Merkl does not answer, so a
|
|
53
|
+
* caller can tell an unreachable Merkl from a wallet with nothing to claim. A
|
|
54
|
+
* non-2xx counts as no answer: it carries no rewards either way, and treating
|
|
55
|
+
* it as success would report emptiness that was never established.
|
|
61
56
|
*/
|
|
62
57
|
declare function fetchMerklUserRewards({ chainId, user, apiKey }: FetchMerklUserRewardsProps): Promise<MerkleXYZUserRewardsV4Response>;
|
|
63
58
|
//#endregion
|
|
64
|
-
export { FetchMerklUserRewardsProps, MERKL_API_KEY_HEADER,
|
|
59
|
+
export { FetchMerklUserRewardsProps, MERKL_API_KEY_HEADER, MerkleXYZUserRewardsV4, MerkleXYZUserRewardsV4Response, fetchMerklUserRewards };
|
|
@@ -1,54 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PointsProgramPnL } from "../model/positions.js";
|
|
3
|
-
import "../model/index.js";
|
|
4
|
-
import { OnchainSDK } from "../onchain/OnchainSDK.js";
|
|
5
|
-
import "../onchain/index.js";
|
|
1
|
+
import { MerklReward, RewardsSdk } from "./types.js";
|
|
6
2
|
import { MerkleXYZUserRewardsV4Response } from "./merkl-api.js";
|
|
7
|
-
import { Address } from "viem";
|
|
8
3
|
//#region src/rewards/toMerklRewards.d.ts
|
|
9
4
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* reassembles one out of the loose fields Merkl sends.
|
|
14
|
-
*/
|
|
15
|
-
interface MerklReward {
|
|
16
|
-
/** Where the reward is claimed. */
|
|
17
|
-
readonly source: "merkl" | "turtle";
|
|
18
|
-
readonly chainId: ChainId;
|
|
19
|
-
/** Market pool whose depositors the campaign rewards. */
|
|
20
|
-
readonly pool: Address;
|
|
21
|
-
/** That pool's share token — what the campaign is keyed on. */
|
|
22
|
-
readonly poolToken: Token;
|
|
23
|
-
/**
|
|
24
|
-
* The incentive token being handed out, how much of it is claimable
|
|
25
|
-
* (distributed minus already claimed, always > 0) and what that is worth.
|
|
26
|
-
*
|
|
27
|
-
* Priced by Merkl, not by the market oracles: a campaign's incentive token
|
|
28
|
-
* is rarely collateral in the pool it incentivises, so the oracles usually
|
|
29
|
-
* do not know it — GEAR, which most Gearbox campaigns pay in, among them.
|
|
30
|
-
* `valueUsd` is `null` for a token Merkl does not price either.
|
|
31
|
-
*/
|
|
32
|
-
readonly amount: TokenAmount;
|
|
33
|
-
}
|
|
34
|
-
/** Points have no token and no price, and are not claimed. */
|
|
35
|
-
interface PointsReward extends Omit<MerklReward, "amount"> {
|
|
36
|
-
readonly points: PointsProgramPnL;
|
|
37
|
-
}
|
|
38
|
-
type Reward = MerklReward | PointsReward;
|
|
39
|
-
type RewardsSdk = Pick<OnchainSDK, "chainId" | "marketRegister" | "tokensMeta">;
|
|
40
|
-
/**
|
|
41
|
-
* Merkl's answer for one chain, turned into rows of the read model.
|
|
5
|
+
* Merkl's answer for one chain, one reward per pool and incentive token.
|
|
6
|
+
* A breakdown Merkl cannot be read from is skipped rather than thrown on: it
|
|
7
|
+
* must not sink the chain, which the caller would then be told is unreachable.
|
|
42
8
|
*/
|
|
43
9
|
declare function toMerklRewards(sdk: RewardsSdk, response: MerkleXYZUserRewardsV4Response): MerklReward[];
|
|
44
|
-
/**
|
|
45
|
-
* An incentive token is rarely protocol collateral, so the registry usually
|
|
46
|
-
* has no entry for it — and both sources always name it.
|
|
47
|
-
*/
|
|
48
|
-
declare function toRewardToken(sdk: RewardsSdk, address: Address, named: {
|
|
49
|
-
symbol: string;
|
|
50
|
-
name?: string;
|
|
51
|
-
decimals: number;
|
|
52
|
-
}): Token;
|
|
53
10
|
//#endregion
|
|
54
|
-
export {
|
|
11
|
+
export { toMerklRewards };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Reward, RewardsSdk } from "./
|
|
1
|
+
import { Reward, RewardsSdk } from "./types.js";
|
|
2
2
|
import { TurtleWalletRewards } from "./turtle-api.js";
|
|
3
3
|
//#region src/rewards/toTurtleRewards.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The streams targeting a pool on the SDK's chain, merged per pool and
|
|
6
|
+
* incentive. A token reward is what is committed and not yet claimed.
|
|
7
7
|
*/
|
|
8
8
|
declare function toTurtleRewards(sdk: RewardsSdk, { streams, proofs }: TurtleWalletRewards, claimed: ReadonlyMap<string, bigint>): Reward[];
|
|
9
9
|
//#endregion
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { OnchainSDK } from "../onchain/OnchainSDK.js";
|
|
2
|
+
import "../onchain/index.js";
|
|
1
3
|
import { Address } from "viem";
|
|
2
4
|
//#region src/rewards/turtle-api.d.ts
|
|
3
5
|
interface TurtleWalletStream {
|
|
@@ -58,5 +60,7 @@ declare const turtleStreamAbi: readonly [{
|
|
|
58
60
|
}];
|
|
59
61
|
}];
|
|
60
62
|
declare function fetchTurtleWalletRewards(user: Address, apiKey: string): Promise<TurtleWalletRewards>;
|
|
63
|
+
/** What each of the wallet's streams on the chain has paid out, at the latest block. */
|
|
64
|
+
declare function readTurtleClaimed(sdk: OnchainSDK, user: Address, { proofs }: TurtleWalletRewards): Promise<Map<string, bigint>>;
|
|
61
65
|
//#endregion
|
|
62
|
-
export { TurtleMerkleProof, TurtleWalletRewards, TurtleWalletStream, fetchTurtleWalletRewards, turtleStreamAbi };
|
|
66
|
+
export { TurtleMerkleProof, TurtleWalletRewards, TurtleWalletStream, fetchTurtleWalletRewards, readTurtleClaimed, turtleStreamAbi };
|