@moonwell-fi/moonwell-sdk 0.12.0 → 0.12.2
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/CHANGELOG.md +14 -0
- package/_cjs/actions/axiosWithRetry.js +25 -0
- package/_cjs/actions/axiosWithRetry.js.map +1 -0
- package/_cjs/actions/beam/getBeamTokenLimits.js +2 -5
- package/_cjs/actions/beam/getBeamTokenLimits.js.map +1 -1
- package/_cjs/actions/beam/getBeamTokenRoutes.js +3 -6
- package/_cjs/actions/beam/getBeamTokenRoutes.js.map +1 -1
- package/_cjs/actions/core/markets/getMarketSnapshots.js +2 -5
- package/_cjs/actions/core/markets/getMarketSnapshots.js.map +1 -1
- package/_cjs/actions/core/user-positions/getUserPositionSnapshots.js +2 -5
- package/_cjs/actions/core/user-positions/getUserPositionSnapshots.js.map +1 -1
- package/_cjs/actions/governance/getCirculatingSupplySnapshots.js +3 -6
- package/_cjs/actions/governance/getCirculatingSupplySnapshots.js.map +1 -1
- package/_cjs/actions/governance/getDelegates.js +2 -5
- package/_cjs/actions/governance/getDelegates.js.map +1 -1
- package/_cjs/actions/governance/getDiscussions.js +3 -3
- package/_cjs/actions/governance/getDiscussions.js.map +1 -1
- package/_cjs/actions/governance/getStakingSnapshots.js +2 -5
- package/_cjs/actions/governance/getStakingSnapshots.js.map +1 -1
- package/_cjs/actions/governance/getUserVotingPowers.js +12 -4
- package/_cjs/actions/governance/getUserVotingPowers.js.map +1 -1
- package/_cjs/actions/governance/governor-api-client.js +10 -13
- package/_cjs/actions/governance/governor-api-client.js.map +1 -1
- package/_cjs/actions/governance/proposals/common.js +32 -4
- package/_cjs/actions/governance/proposals/common.js.map +1 -1
- package/_cjs/actions/governance/snapshot/common.js +2 -5
- package/_cjs/actions/governance/snapshot/common.js.map +1 -1
- package/_cjs/actions/lunar-indexer-client.js +4 -0
- package/_cjs/actions/lunar-indexer-client.js.map +1 -1
- package/_cjs/actions/morpho/markets/common.js +2 -5
- package/_cjs/actions/morpho/markets/common.js.map +1 -1
- package/_cjs/actions/morpho/markets/lunarIndexerTransform.js +5 -8
- package/_cjs/actions/morpho/markets/lunarIndexerTransform.js.map +1 -1
- package/_cjs/actions/morpho/user-rewards/common.js +21 -193
- package/_cjs/actions/morpho/user-rewards/common.js.map +1 -1
- package/_cjs/actions/retry.js +63 -0
- package/_cjs/actions/retry.js.map +1 -0
- package/_cjs/common/getBlockNumberAtTimestamp.js +42 -0
- package/_cjs/common/getBlockNumberAtTimestamp.js.map +1 -0
- package/_cjs/common/index.js +3 -1
- package/_cjs/common/index.js.map +1 -1
- package/_cjs/environments/types/config.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_esm/actions/axiosWithRetry.js +32 -0
- package/_esm/actions/axiosWithRetry.js.map +1 -0
- package/_esm/actions/beam/getBeamTokenLimits.js +2 -2
- package/_esm/actions/beam/getBeamTokenLimits.js.map +1 -1
- package/_esm/actions/beam/getBeamTokenRoutes.js +3 -3
- package/_esm/actions/beam/getBeamTokenRoutes.js.map +1 -1
- package/_esm/actions/core/markets/getMarketSnapshots.js +2 -2
- package/_esm/actions/core/markets/getMarketSnapshots.js.map +1 -1
- package/_esm/actions/core/user-positions/getUserPositionSnapshots.js +2 -2
- package/_esm/actions/core/user-positions/getUserPositionSnapshots.js.map +1 -1
- package/_esm/actions/governance/getCirculatingSupplySnapshots.js +3 -3
- package/_esm/actions/governance/getCirculatingSupplySnapshots.js.map +1 -1
- package/_esm/actions/governance/getDelegates.js +2 -2
- package/_esm/actions/governance/getDelegates.js.map +1 -1
- package/_esm/actions/governance/getDiscussions.js +3 -3
- package/_esm/actions/governance/getDiscussions.js.map +1 -1
- package/_esm/actions/governance/getStakingSnapshots.js +2 -2
- package/_esm/actions/governance/getStakingSnapshots.js.map +1 -1
- package/_esm/actions/governance/getUserVotingPowers.js +13 -5
- package/_esm/actions/governance/getUserVotingPowers.js.map +1 -1
- package/_esm/actions/governance/governor-api-client.js +10 -10
- package/_esm/actions/governance/governor-api-client.js.map +1 -1
- package/_esm/actions/governance/proposals/common.js +45 -3
- package/_esm/actions/governance/proposals/common.js.map +1 -1
- package/_esm/actions/governance/snapshot/common.js +2 -2
- package/_esm/actions/governance/snapshot/common.js.map +1 -1
- package/_esm/actions/lunar-indexer-client.js +6 -0
- package/_esm/actions/lunar-indexer-client.js.map +1 -1
- package/_esm/actions/morpho/markets/common.js +2 -2
- package/_esm/actions/morpho/markets/common.js.map +1 -1
- package/_esm/actions/morpho/markets/lunarIndexerTransform.js +5 -5
- package/_esm/actions/morpho/markets/lunarIndexerTransform.js.map +1 -1
- package/_esm/actions/morpho/user-rewards/common.js +28 -195
- package/_esm/actions/morpho/user-rewards/common.js.map +1 -1
- package/_esm/actions/retry.js +90 -0
- package/_esm/actions/retry.js.map +1 -0
- package/_esm/common/getBlockNumberAtTimestamp.js +56 -0
- package/_esm/common/getBlockNumberAtTimestamp.js.map +1 -0
- package/_esm/common/index.js +1 -0
- package/_esm/common/index.js.map +1 -1
- package/_esm/environments/types/config.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_types/actions/axiosWithRetry.d.ts +16 -0
- package/_types/actions/axiosWithRetry.d.ts.map +1 -0
- package/_types/actions/beam/getBeamTokenLimits.d.ts.map +1 -1
- package/_types/actions/beam/getBeamTokenRoutes.d.ts.map +1 -1
- package/_types/actions/core/markets/getMarketSnapshots.d.ts.map +1 -1
- package/_types/actions/core/user-positions/getUserPositionSnapshots.d.ts.map +1 -1
- package/_types/actions/governance/getCirculatingSupplySnapshots.d.ts.map +1 -1
- package/_types/actions/governance/getDelegates.d.ts.map +1 -1
- package/_types/actions/governance/getDiscussions.d.ts.map +1 -1
- package/_types/actions/governance/getStakingSnapshots.d.ts.map +1 -1
- package/_types/actions/governance/getUserVotingPowers.d.ts +13 -1
- package/_types/actions/governance/getUserVotingPowers.d.ts.map +1 -1
- package/_types/actions/governance/governor-api-client.d.ts.map +1 -1
- package/_types/actions/governance/proposals/common.d.ts +15 -0
- package/_types/actions/governance/proposals/common.d.ts.map +1 -1
- package/_types/actions/governance/snapshot/common.d.ts.map +1 -1
- package/_types/actions/lunar-indexer-client.d.ts.map +1 -1
- package/_types/actions/morpho/markets/common.d.ts.map +1 -1
- package/_types/actions/morpho/markets/lunarIndexerTransform.d.ts.map +1 -1
- package/_types/actions/morpho/user-rewards/common.d.ts.map +1 -1
- package/_types/actions/retry.d.ts +45 -0
- package/_types/actions/retry.d.ts.map +1 -0
- package/_types/client/createMoonwellClient.d.ts +2 -2
- package/_types/common/getBlockNumberAtTimestamp.d.ts +15 -0
- package/_types/common/getBlockNumberAtTimestamp.d.ts.map +1 -0
- package/_types/common/index.d.ts +1 -0
- package/_types/common/index.d.ts.map +1 -1
- package/_types/environments/definitions/base/environment.d.ts +21 -3
- package/_types/environments/definitions/base/environment.d.ts.map +1 -1
- package/_types/environments/definitions/moonbeam/custom.d.ts +1 -1
- package/_types/environments/definitions/moonbeam/environment.d.ts +1 -1
- package/_types/environments/index.d.ts +119 -20
- package/_types/environments/index.d.ts.map +1 -1
- package/_types/environments/types/config.d.ts +27 -41
- package/_types/environments/types/config.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/actions/axiosWithRetry.ts +42 -0
- package/actions/beam/getBeamTokenLimits.ts +2 -2
- package/actions/beam/getBeamTokenRoutes.ts +3 -3
- package/actions/core/markets/getMarketSnapshots.ts +2 -2
- package/actions/core/user-positions/getUserPositionSnapshots.ts +2 -2
- package/actions/governance/getCirculatingSupplySnapshots.ts +3 -3
- package/actions/governance/getDelegates.ts +2 -2
- package/actions/governance/getDiscussions.ts +6 -5
- package/actions/governance/getStakingSnapshots.ts +2 -2
- package/actions/governance/getUserVotingPowers.ts +43 -8
- package/actions/governance/governor-api-client.ts +12 -10
- package/actions/governance/proposals/common.ts +58 -3
- package/actions/governance/snapshot/common.ts +2 -2
- package/actions/lunar-indexer-client.ts +8 -0
- package/actions/morpho/markets/common.ts +2 -2
- package/actions/morpho/markets/lunarIndexerTransform.ts +6 -5
- package/actions/morpho/user-rewards/common.ts +52 -344
- package/actions/retry.ts +121 -0
- package/common/getBlockNumberAtTimestamp.ts +59 -0
- package/common/index.ts +1 -0
- package/environments/types/config.ts +34 -207
- package/errors/version.ts +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import lodash from "lodash";
|
|
2
|
-
const { uniq } = lodash;
|
|
3
1
|
import { type Address, getContract, parseAbi, zeroAddress } from "viem";
|
|
4
2
|
import { Amount } from "../../../common/amount.js";
|
|
5
3
|
import { MOONWELL_FETCH_JSON_HEADERS } from "../../../common/fetch-headers.js";
|
|
@@ -14,258 +12,54 @@ import {
|
|
|
14
12
|
} from "../../../environments/utils/index.js";
|
|
15
13
|
import type { MorphoUserReward } from "../../../types/morphoUserReward.js";
|
|
16
14
|
import type { MorphoUserStakingReward } from "../../../types/morphoUserStakingReward.js";
|
|
17
|
-
import { getGraphQL } from "../utils/graphql.js";
|
|
18
15
|
|
|
19
16
|
export async function getUserMorphoRewardsData(params: {
|
|
20
17
|
environment: Environment;
|
|
21
18
|
account: `0x${string}`;
|
|
22
19
|
}): Promise<MorphoUserReward[]> {
|
|
20
|
+
// The Morpho URD distributions endpoint (rewards.morpho.org) was
|
|
21
|
+
// deprecated and now 301-redirects to a SPA, so JSON parsing fails.
|
|
22
|
+
// Surface only Merkl rewards.
|
|
23
|
+
const merklRewards = await getMerklRewardsData(
|
|
24
|
+
params.environment,
|
|
25
|
+
params.account,
|
|
26
|
+
);
|
|
27
|
+
|
|
23
28
|
const isFullDeployment =
|
|
24
29
|
params.environment.custom.morpho?.minimalDeployment === false;
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
switch (r.type) {
|
|
59
|
-
case "uniform-reward": {
|
|
60
|
-
const claimableNow = new Amount(
|
|
61
|
-
BigInt(r.amount?.claimable_now || 0),
|
|
62
|
-
rewardToken.decimals,
|
|
63
|
-
);
|
|
64
|
-
const claimableNowUsd = claimableNow.value * (asset.priceUsd || 0);
|
|
65
|
-
const claimableFuture = new Amount(
|
|
66
|
-
BigInt(r.amount?.claimable_next || 0),
|
|
67
|
-
rewardToken.decimals,
|
|
68
|
-
);
|
|
69
|
-
const claimableFutureUsd =
|
|
70
|
-
claimableFuture.value * (asset.priceUsd || 0);
|
|
71
|
-
|
|
72
|
-
const uniformReward: MorphoUserReward = {
|
|
73
|
-
type: "uniform-reward",
|
|
74
|
-
chainId: r.asset.chain_id,
|
|
75
|
-
account: r.user,
|
|
76
|
-
rewardToken,
|
|
77
|
-
claimableNow,
|
|
78
|
-
claimableNowUsd,
|
|
79
|
-
claimableFuture,
|
|
80
|
-
claimableFutureUsd,
|
|
81
|
-
};
|
|
82
|
-
return uniformReward;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
case "market-reward": {
|
|
86
|
-
const claimableNow = new Amount(
|
|
87
|
-
BigInt(r.for_supply?.claimable_now || 0),
|
|
88
|
-
rewardToken.decimals,
|
|
89
|
-
);
|
|
90
|
-
const claimableNowUsd = claimableNow.value * (asset.priceUsd || 0);
|
|
91
|
-
|
|
92
|
-
const claimableFuture = new Amount(
|
|
93
|
-
BigInt(r.for_supply?.claimable_next || 0),
|
|
94
|
-
rewardToken.decimals,
|
|
95
|
-
);
|
|
96
|
-
const claimableFutureUsd =
|
|
97
|
-
claimableFuture.value * (asset.priceUsd || 0);
|
|
98
|
-
|
|
99
|
-
const collateralClaimableNow = new Amount(
|
|
100
|
-
BigInt(r.for_collateral?.claimable_now || 0),
|
|
101
|
-
rewardToken.decimals,
|
|
102
|
-
);
|
|
103
|
-
const collateralClaimableNowUsd =
|
|
104
|
-
collateralClaimableNow.value * (asset.priceUsd || 0);
|
|
105
|
-
const collateralClaimableFuture = new Amount(
|
|
106
|
-
BigInt(r.for_collateral?.claimable_next || 0),
|
|
107
|
-
rewardToken.decimals,
|
|
108
|
-
);
|
|
109
|
-
const collateralClaimableFutureUsd =
|
|
110
|
-
collateralClaimableFuture.value * (asset.priceUsd || 0);
|
|
111
|
-
|
|
112
|
-
const borrowClaimableNow = new Amount(
|
|
113
|
-
BigInt(r.for_borrow?.claimable_now || 0),
|
|
114
|
-
rewardToken.decimals,
|
|
115
|
-
);
|
|
116
|
-
const borrowClaimableNowUsd =
|
|
117
|
-
borrowClaimableNow.value * (asset.priceUsd || 0);
|
|
118
|
-
const borrowClaimableFuture = new Amount(
|
|
119
|
-
BigInt(r.for_borrow?.claimable_next || 0),
|
|
120
|
-
rewardToken.decimals,
|
|
121
|
-
);
|
|
122
|
-
const borrowClaimableFutureUsd =
|
|
123
|
-
borrowClaimableFuture.value * (asset.priceUsd || 0);
|
|
124
|
-
|
|
125
|
-
//Rewards reallocated to vaults are reported as vault rewards
|
|
126
|
-
if (r.reallocated_from) {
|
|
127
|
-
const vaultReward: MorphoUserReward = {
|
|
128
|
-
type: "vault-reward",
|
|
129
|
-
chainId: r.program.chain_id,
|
|
130
|
-
account: r.user,
|
|
131
|
-
vaultId: r.reallocated_from,
|
|
132
|
-
rewardToken,
|
|
133
|
-
claimableNow,
|
|
134
|
-
claimableNowUsd,
|
|
135
|
-
claimableFuture,
|
|
136
|
-
claimableFutureUsd,
|
|
137
|
-
};
|
|
138
|
-
return vaultReward;
|
|
139
|
-
} else {
|
|
140
|
-
const marketReward: MorphoUserReward = {
|
|
141
|
-
type: "market-reward",
|
|
142
|
-
chainId: r.program.chain_id,
|
|
143
|
-
account: r.user,
|
|
144
|
-
marketId: r.program.market_id || "",
|
|
145
|
-
rewardToken,
|
|
146
|
-
collateralRewards: {
|
|
147
|
-
claimableNow: collateralClaimableNow,
|
|
148
|
-
claimableNowUsd: collateralClaimableNowUsd,
|
|
149
|
-
claimableFuture: collateralClaimableFuture,
|
|
150
|
-
claimableFutureUsd: collateralClaimableFutureUsd,
|
|
151
|
-
},
|
|
152
|
-
borrowRewards: {
|
|
153
|
-
claimableNow: borrowClaimableNow,
|
|
154
|
-
claimableNowUsd: borrowClaimableNowUsd,
|
|
155
|
-
claimableFuture: borrowClaimableFuture,
|
|
156
|
-
claimableFutureUsd: borrowClaimableFutureUsd,
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
return marketReward;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
case "vault-reward": {
|
|
163
|
-
const claimableNow = new Amount(
|
|
164
|
-
BigInt(r.for_supply?.claimable_now || 0),
|
|
165
|
-
rewardToken.decimals,
|
|
166
|
-
);
|
|
167
|
-
const claimableNowUsd = claimableNow.value * (asset.priceUsd || 0);
|
|
168
|
-
const claimableFuture = new Amount(
|
|
169
|
-
BigInt(r.for_supply?.claimable_next || 0),
|
|
170
|
-
rewardToken.decimals,
|
|
171
|
-
);
|
|
172
|
-
const claimableFutureUsd =
|
|
173
|
-
claimableFuture.value * (asset.priceUsd || 0);
|
|
174
|
-
|
|
175
|
-
const vaultReward: MorphoUserReward = {
|
|
176
|
-
type: "vault-reward",
|
|
177
|
-
chainId: r.program.chain_id,
|
|
178
|
-
account: r.user,
|
|
179
|
-
vaultId: r.program.vault,
|
|
180
|
-
rewardToken,
|
|
181
|
-
claimableNow,
|
|
182
|
-
claimableNowUsd,
|
|
183
|
-
claimableFuture,
|
|
184
|
-
claimableFutureUsd,
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
return vaultReward;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
// Process Merkl rewards
|
|
194
|
-
const vaultCampaignIds = new Set<string>(
|
|
195
|
-
(Object.values(publicEnvironments) as Environment[]).flatMap(
|
|
196
|
-
(environment) =>
|
|
197
|
-
Object.values(environment.config.vaults ?? {})
|
|
198
|
-
.map((vault) => vault.campaignId)
|
|
199
|
-
.filter((id): id is string => id !== undefined),
|
|
200
|
-
),
|
|
31
|
+
// For full deployments (Base), restrict to Moonwell vault campaigns so the
|
|
32
|
+
// result excludes staking and other Moonwell campaigns; those are returned
|
|
33
|
+
// by their own actions (e.g. getUserStakingInfo). On other chains, surface
|
|
34
|
+
// every Merkl reward we get back.
|
|
35
|
+
const vaultCampaignIds = isFullDeployment
|
|
36
|
+
? new Set<string>(
|
|
37
|
+
(Object.values(publicEnvironments) as Environment[]).flatMap(
|
|
38
|
+
(environment) =>
|
|
39
|
+
Object.values(environment.config.vaults ?? {})
|
|
40
|
+
.map((vault) => vault.campaignId)
|
|
41
|
+
.filter((id): id is string => id !== undefined),
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
: null;
|
|
45
|
+
|
|
46
|
+
const sumBreakdowns = (
|
|
47
|
+
breakdowns: {
|
|
48
|
+
campaignId: string;
|
|
49
|
+
amount: string;
|
|
50
|
+
claimed: string;
|
|
51
|
+
pending: string;
|
|
52
|
+
}[],
|
|
53
|
+
field: "amount" | "claimed" | "pending",
|
|
54
|
+
): bigint =>
|
|
55
|
+
breakdowns.reduce(
|
|
56
|
+
(acc, curr) =>
|
|
57
|
+
vaultCampaignIds === null || vaultCampaignIds.has(curr.campaignId)
|
|
58
|
+
? acc + BigInt(curr[field])
|
|
59
|
+
: acc,
|
|
60
|
+
0n,
|
|
201
61
|
);
|
|
202
62
|
|
|
203
|
-
const getVaultRewardAmount = (
|
|
204
|
-
breakdowns: any[],
|
|
205
|
-
field: "amount" | "claimed" | "pending",
|
|
206
|
-
) => {
|
|
207
|
-
return breakdowns.reduce(
|
|
208
|
-
(acc, curr) =>
|
|
209
|
-
vaultCampaignIds.has(curr.campaignId)
|
|
210
|
-
? acc + BigInt(curr[field])
|
|
211
|
-
: acc,
|
|
212
|
-
0n,
|
|
213
|
-
);
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const merklResult: MorphoUserReward[] = [];
|
|
217
|
-
|
|
218
|
-
for (const chainData of merklRewards) {
|
|
219
|
-
for (const reward of chainData.rewards) {
|
|
220
|
-
// Try to find token info in morphoAssets first
|
|
221
|
-
const morphoAsset = morphoAssets.find(
|
|
222
|
-
(a) => a.address.toLowerCase() === reward.token.address.toLowerCase(),
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
const rewardToken: TokenConfig = {
|
|
226
|
-
address: reward.token.address as Address,
|
|
227
|
-
decimals: morphoAsset?.decimals ?? reward.token.decimals,
|
|
228
|
-
symbol: morphoAsset?.symbol ?? reward.token.symbol,
|
|
229
|
-
name: morphoAsset?.name ?? reward.token.symbol,
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const amount = getVaultRewardAmount(reward.breakdowns, "amount");
|
|
233
|
-
const claimed = getVaultRewardAmount(reward.breakdowns, "claimed");
|
|
234
|
-
const pending = getVaultRewardAmount(reward.breakdowns, "pending");
|
|
235
|
-
|
|
236
|
-
const claimableNow = new Amount(amount - claimed, rewardToken.decimals);
|
|
237
|
-
const claimableNowUsd =
|
|
238
|
-
claimableNow.value *
|
|
239
|
-
(morphoAsset?.priceUsd ?? reward.token.price ?? 0);
|
|
240
|
-
const claimableFuture = new Amount(pending, rewardToken.decimals);
|
|
241
|
-
const claimableFutureUsd =
|
|
242
|
-
claimableFuture.value *
|
|
243
|
-
(morphoAsset?.priceUsd ?? reward.token.price ?? 0);
|
|
244
|
-
|
|
245
|
-
const merklReward: MorphoUserReward = {
|
|
246
|
-
type: "merkl-reward",
|
|
247
|
-
chainId: chainData.chain.id,
|
|
248
|
-
account: params.account,
|
|
249
|
-
rewardToken,
|
|
250
|
-
claimableNow,
|
|
251
|
-
claimableNowUsd,
|
|
252
|
-
claimableFuture,
|
|
253
|
-
claimableFutureUsd,
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
merklResult.push(merklReward);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Combine both results
|
|
261
|
-
const allResults = [
|
|
262
|
-
...(morphoResult.filter((r) => r !== undefined) as MorphoUserReward[]),
|
|
263
|
-
...merklResult,
|
|
264
|
-
];
|
|
265
|
-
|
|
266
|
-
return allResults;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
63
|
const merklResult: MorphoUserReward[] = [];
|
|
270
64
|
|
|
271
65
|
for (const chainData of merklRewards) {
|
|
@@ -277,19 +71,23 @@ export async function getUserMorphoRewardsData(params: {
|
|
|
277
71
|
name: reward.token.symbol,
|
|
278
72
|
};
|
|
279
73
|
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
74
|
+
const amount = vaultCampaignIds
|
|
75
|
+
? sumBreakdowns(reward.breakdowns, "amount")
|
|
76
|
+
: BigInt(reward.amount);
|
|
77
|
+
const claimed = vaultCampaignIds
|
|
78
|
+
? sumBreakdowns(reward.breakdowns, "claimed")
|
|
79
|
+
: BigInt(reward.claimed);
|
|
80
|
+
const pending = vaultCampaignIds
|
|
81
|
+
? sumBreakdowns(reward.breakdowns, "pending")
|
|
82
|
+
: BigInt(reward.pending);
|
|
83
|
+
|
|
84
|
+
const claimableNow = new Amount(amount - claimed, rewardToken.decimals);
|
|
284
85
|
const claimableNowUsd = claimableNow.value * (reward.token.price ?? 0);
|
|
285
|
-
const claimableFuture = new Amount(
|
|
286
|
-
BigInt(reward.pending),
|
|
287
|
-
rewardToken.decimals,
|
|
288
|
-
);
|
|
86
|
+
const claimableFuture = new Amount(pending, rewardToken.decimals);
|
|
289
87
|
const claimableFutureUsd =
|
|
290
88
|
claimableFuture.value * (reward.token.price ?? 0);
|
|
291
89
|
|
|
292
|
-
|
|
90
|
+
merklResult.push({
|
|
293
91
|
type: "merkl-reward",
|
|
294
92
|
chainId: chainData.chain.id,
|
|
295
93
|
account: params.account,
|
|
@@ -298,9 +96,7 @@ export async function getUserMorphoRewardsData(params: {
|
|
|
298
96
|
claimableNowUsd,
|
|
299
97
|
claimableFuture,
|
|
300
98
|
claimableFutureUsd,
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
merklResult.push(merklReward);
|
|
99
|
+
});
|
|
304
100
|
}
|
|
305
101
|
}
|
|
306
102
|
|
|
@@ -456,94 +252,6 @@ const getRewardsEarnedData = async (
|
|
|
456
252
|
return rewards.filter(Boolean);
|
|
457
253
|
};
|
|
458
254
|
|
|
459
|
-
type MorphoRewardsResponse = {
|
|
460
|
-
user: Address;
|
|
461
|
-
for_borrow: {
|
|
462
|
-
claimable_next: string;
|
|
463
|
-
claimable_now: string;
|
|
464
|
-
claimed: string;
|
|
465
|
-
total: string;
|
|
466
|
-
};
|
|
467
|
-
for_collateral: {
|
|
468
|
-
claimable_next: string;
|
|
469
|
-
claimable_now: string;
|
|
470
|
-
claimed: string;
|
|
471
|
-
total: string;
|
|
472
|
-
};
|
|
473
|
-
for_supply: {
|
|
474
|
-
claimable_next: string;
|
|
475
|
-
claimable_now: string;
|
|
476
|
-
claimed: string;
|
|
477
|
-
total: string;
|
|
478
|
-
};
|
|
479
|
-
program: {
|
|
480
|
-
asset: { address: Address };
|
|
481
|
-
market_id?: string;
|
|
482
|
-
chain_id: number;
|
|
483
|
-
vault: Address;
|
|
484
|
-
};
|
|
485
|
-
asset: { address: Address; chain_id: number };
|
|
486
|
-
amount?: { claimable_next: string; claimable_now: string };
|
|
487
|
-
type: "vault-reward" | "market-reward" | "uniform-reward";
|
|
488
|
-
reallocated_from: Address;
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
type MorphoAssetResponse = {
|
|
492
|
-
address: Address;
|
|
493
|
-
symbol: string;
|
|
494
|
-
priceUsd: number | undefined;
|
|
495
|
-
name: string;
|
|
496
|
-
decimals: number;
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
async function getMorphoRewardsData(
|
|
500
|
-
environment: Environment,
|
|
501
|
-
account: Address,
|
|
502
|
-
): Promise<MorphoRewardsResponse[]> {
|
|
503
|
-
const baseUrl =
|
|
504
|
-
environment.custom.morpho?.rewardsApiUrl || "https://rewards.morpho.org";
|
|
505
|
-
const rewardsRequest = await fetch(
|
|
506
|
-
`${baseUrl}/v1/users/${account}/rewards?chain_id=${environment.chainId}&trusted=true&exclude_merkl_programs=true`,
|
|
507
|
-
{
|
|
508
|
-
headers: MOONWELL_FETCH_JSON_HEADERS,
|
|
509
|
-
},
|
|
510
|
-
);
|
|
511
|
-
const rewards = await rewardsRequest.json();
|
|
512
|
-
return (rewards.data || []) as MorphoRewardsResponse[];
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async function getMorphoAssetsData(
|
|
516
|
-
environment: Environment,
|
|
517
|
-
addresses: Address[],
|
|
518
|
-
): Promise<MorphoAssetResponse[]> {
|
|
519
|
-
const rewardsRequest = await getGraphQL<{
|
|
520
|
-
assets: {
|
|
521
|
-
items: MorphoAssetResponse[];
|
|
522
|
-
};
|
|
523
|
-
}>(
|
|
524
|
-
environment,
|
|
525
|
-
`
|
|
526
|
-
query {
|
|
527
|
-
assets(where: { address_in:[${uniq(addresses)
|
|
528
|
-
.map((a: string) => `"${a.toLowerCase()}"`)
|
|
529
|
-
.join(",")}]}) {
|
|
530
|
-
items {
|
|
531
|
-
address
|
|
532
|
-
symbol
|
|
533
|
-
priceUsd
|
|
534
|
-
name
|
|
535
|
-
decimals
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
`,
|
|
540
|
-
);
|
|
541
|
-
if (rewardsRequest) {
|
|
542
|
-
return rewardsRequest.assets.items;
|
|
543
|
-
}
|
|
544
|
-
return [];
|
|
545
|
-
}
|
|
546
|
-
|
|
547
255
|
type MerklRewardsResponse = {
|
|
548
256
|
chain: {
|
|
549
257
|
id: number;
|
package/actions/retry.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry helpers for SDK API calls.
|
|
3
|
+
*
|
|
4
|
+
* Two consumers:
|
|
5
|
+
* 1. `attachRetryInterceptor` — axios response interceptor used inside
|
|
6
|
+
* LunarIndexerClient. One install per axios instance covers every method.
|
|
7
|
+
* 2. `retry` — generic wrapper for ad-hoc `axios.post(...)` callsites in
|
|
8
|
+
* actions that don't go through a shared axios instance.
|
|
9
|
+
*
|
|
10
|
+
* Policy:
|
|
11
|
+
* - 3 attempts (initial + 2 retries) by default
|
|
12
|
+
* - Exponential backoff: 250ms → 500ms (capped at 5s)
|
|
13
|
+
* - Retry only network errors / timeouts / 5xx; never retry 4xx (incl. 404 —
|
|
14
|
+
* retrying a deterministic "not found" wastes time and amplifies log noise)
|
|
15
|
+
* - After retries exhaust, the last error propagates to the caller's try/catch
|
|
16
|
+
* where the existing `environment.onError(...)` wiring picks it up
|
|
17
|
+
*/
|
|
18
|
+
import axios, {
|
|
19
|
+
type AxiosError,
|
|
20
|
+
type AxiosInstance,
|
|
21
|
+
type InternalAxiosRequestConfig,
|
|
22
|
+
} from "axios";
|
|
23
|
+
|
|
24
|
+
export interface RetryOptions {
|
|
25
|
+
maxAttempts?: number;
|
|
26
|
+
initialDelay?: number;
|
|
27
|
+
maxDelay?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
31
|
+
const DEFAULT_INITIAL_DELAY_MS = 250;
|
|
32
|
+
const DEFAULT_MAX_DELAY_MS = 5_000;
|
|
33
|
+
|
|
34
|
+
async function sleep(ms: number): Promise<void> {
|
|
35
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function backoffDelay(
|
|
39
|
+
attemptIndex: number,
|
|
40
|
+
initialDelay: number,
|
|
41
|
+
maxDelay: number,
|
|
42
|
+
): number {
|
|
43
|
+
return Math.min(initialDelay * 2 ** attemptIndex, maxDelay);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Decide whether an error represents a transient failure worth retrying.
|
|
48
|
+
* - Axios network errors (no `response`) → retry (server unreachable, timeout)
|
|
49
|
+
* - 5xx responses → retry (server hiccup)
|
|
50
|
+
* - 4xx responses (incl. 404) → do NOT retry (deterministic, won't change)
|
|
51
|
+
* - Non-axios errors (parse errors, code bugs) → do NOT retry
|
|
52
|
+
*/
|
|
53
|
+
export function isRetriableError(error: unknown): boolean {
|
|
54
|
+
if (!axios.isAxiosError(error)) return false;
|
|
55
|
+
if (!error.response) return true;
|
|
56
|
+
return error.response.status >= 500;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Attach a retry-on-failure interceptor to an axios instance. Every request
|
|
61
|
+
* made through this instance is retried up to `maxAttempts` times when the
|
|
62
|
+
* failure is retriable. Per-config state is tracked on a WeakMap so we don't
|
|
63
|
+
* pollute the public axios config shape.
|
|
64
|
+
*/
|
|
65
|
+
export function attachRetryInterceptor(
|
|
66
|
+
instance: AxiosInstance,
|
|
67
|
+
options: RetryOptions = {},
|
|
68
|
+
): void {
|
|
69
|
+
const {
|
|
70
|
+
maxAttempts = DEFAULT_MAX_ATTEMPTS,
|
|
71
|
+
initialDelay = DEFAULT_INITIAL_DELAY_MS,
|
|
72
|
+
maxDelay = DEFAULT_MAX_DELAY_MS,
|
|
73
|
+
} = options;
|
|
74
|
+
const attemptsByConfig = new WeakMap<InternalAxiosRequestConfig, number>();
|
|
75
|
+
|
|
76
|
+
instance.interceptors.response.use(
|
|
77
|
+
(response) => response,
|
|
78
|
+
async (error: AxiosError) => {
|
|
79
|
+
const config = error.config;
|
|
80
|
+
if (!config) throw error;
|
|
81
|
+
if (!isRetriableError(error)) throw error;
|
|
82
|
+
|
|
83
|
+
const previousAttempts = attemptsByConfig.get(config) ?? 0;
|
|
84
|
+
if (previousAttempts >= maxAttempts - 1) throw error;
|
|
85
|
+
|
|
86
|
+
attemptsByConfig.set(config, previousAttempts + 1);
|
|
87
|
+
await sleep(backoffDelay(previousAttempts, initialDelay, maxDelay));
|
|
88
|
+
return instance.request(config);
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generic wrapper for individual axios calls (e.g. raw `axios.post(...)` in
|
|
95
|
+
* action files that don't share a configured instance). Mirrors the
|
|
96
|
+
* interceptor's policy: retry transient failures, fail fast on 4xx.
|
|
97
|
+
*/
|
|
98
|
+
export async function retry<T>(
|
|
99
|
+
fn: () => Promise<T>,
|
|
100
|
+
options: RetryOptions = {},
|
|
101
|
+
): Promise<T> {
|
|
102
|
+
const {
|
|
103
|
+
maxAttempts = DEFAULT_MAX_ATTEMPTS,
|
|
104
|
+
initialDelay = DEFAULT_INITIAL_DELAY_MS,
|
|
105
|
+
maxDelay = DEFAULT_MAX_DELAY_MS,
|
|
106
|
+
} = options;
|
|
107
|
+
let attempt = 0;
|
|
108
|
+
let lastError: unknown;
|
|
109
|
+
while (attempt < maxAttempts) {
|
|
110
|
+
try {
|
|
111
|
+
return await fn();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
lastError = error;
|
|
114
|
+
attempt++;
|
|
115
|
+
if (!isRetriableError(error)) throw error;
|
|
116
|
+
if (attempt >= maxAttempts) break;
|
|
117
|
+
await sleep(backoffDelay(attempt - 1, initialDelay, maxDelay));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
throw lastError;
|
|
121
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { PublicClient } from "viem";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Safety cap on interpolation iterations. With near-linear `ts(block)` the search
|
|
5
|
+
* converges in ~3–5 reads; 8 leaves headroom for chains with variable block times
|
|
6
|
+
* (e.g. Moonbeam) without unbounded RPC fan-out on pathological inputs.
|
|
7
|
+
*/
|
|
8
|
+
const MAX_ITERATIONS = 8;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Find the block number on a chain whose timestamp is the latest one ≤ the target unix timestamp.
|
|
12
|
+
*
|
|
13
|
+
* Uses interpolation search anchored on the latest block and block 1: each iteration
|
|
14
|
+
* narrows the range by reading one block and projecting the target via the slope of the
|
|
15
|
+
* remaining range. Converges in ~3–5 RPC calls even on chains with variable block times.
|
|
16
|
+
*
|
|
17
|
+
* Assumes block 1 exists on the chain — true for every EVM chain Moonwell supports.
|
|
18
|
+
*
|
|
19
|
+
* Returns the latest block if `targetTimestamp` is at or after the head, and block 1
|
|
20
|
+
* if it is before block 1's timestamp. On a chain with only block 0, returns block 0.
|
|
21
|
+
*/
|
|
22
|
+
export async function getBlockNumberAtTimestamp(
|
|
23
|
+
publicClient: PublicClient,
|
|
24
|
+
targetTimestamp: bigint,
|
|
25
|
+
): Promise<bigint> {
|
|
26
|
+
const latest = await publicClient.getBlock({ blockTag: "latest" });
|
|
27
|
+
if (targetTimestamp >= latest.timestamp) return latest.number;
|
|
28
|
+
if (latest.number === 0n) return latest.number;
|
|
29
|
+
|
|
30
|
+
const first = await publicClient.getBlock({ blockNumber: 1n });
|
|
31
|
+
if (targetTimestamp <= first.timestamp) return first.number;
|
|
32
|
+
|
|
33
|
+
let lo = first.number;
|
|
34
|
+
let loTs = first.timestamp;
|
|
35
|
+
let hi = latest.number;
|
|
36
|
+
let hiTs = latest.timestamp;
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < MAX_ITERATIONS; i += 1) {
|
|
39
|
+
if (hi - lo <= 1n) break;
|
|
40
|
+
const tsRange = hiTs - loTs;
|
|
41
|
+
if (tsRange <= 0n) break;
|
|
42
|
+
|
|
43
|
+
const offset = ((targetTimestamp - loTs) * (hi - lo)) / tsRange;
|
|
44
|
+
let mid = lo + offset;
|
|
45
|
+
if (mid <= lo) mid = lo + 1n;
|
|
46
|
+
if (mid >= hi) mid = hi - 1n;
|
|
47
|
+
|
|
48
|
+
const block = await publicClient.getBlock({ blockNumber: mid });
|
|
49
|
+
if (block.timestamp <= targetTimestamp) {
|
|
50
|
+
lo = mid;
|
|
51
|
+
loTs = block.timestamp;
|
|
52
|
+
} else {
|
|
53
|
+
hi = mid;
|
|
54
|
+
hiTs = block.timestamp;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return lo;
|
|
59
|
+
}
|
package/common/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ dayjs.extend(utc);
|
|
|
7
7
|
export { Amount } from "./amount.js";
|
|
8
8
|
export { BaseError, HttpRequestError } from "./error.js";
|
|
9
9
|
export type { HttpRequestErrorType } from "./error.js";
|
|
10
|
+
export { getBlockNumberAtTimestamp } from "./getBlockNumberAtTimestamp.js";
|
|
10
11
|
export type { MultichainReturnType } from "./types.js";
|
|
11
12
|
|
|
12
13
|
export const SECONDS_PER_DAY = 86400;
|