@moonwell-fi/moonwell-sdk 0.12.1 → 0.13.0
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 +18 -0
- package/_cjs/actions/governance/getDelegates.js.map +1 -1
- package/_cjs/actions/governance/getUserVotingPowers.js +21 -6
- package/_cjs/actions/governance/getUserVotingPowers.js.map +1 -1
- package/_cjs/actions/governance/proposals/common.js +30 -3
- package/_cjs/actions/governance/proposals/common.js.map +1 -1
- package/_cjs/actions/morpho/user-rewards/common.js +95 -199
- package/_cjs/actions/morpho/user-rewards/common.js.map +1 -1
- package/_cjs/actions/morpho/user-rewards/getMorphoUserRewards.js +46 -10
- package/_cjs/actions/morpho/user-rewards/getMorphoUserRewards.js.map +1 -1
- package/_cjs/environments/definitions/ethereum/contracts.js +12 -0
- package/_cjs/environments/definitions/ethereum/contracts.js.map +1 -0
- package/_cjs/environments/definitions/ethereum/custom.js +18 -0
- package/_cjs/environments/definitions/ethereum/custom.js.map +1 -0
- package/_cjs/environments/definitions/ethereum/environment.js +9 -5
- package/_cjs/environments/definitions/ethereum/environment.js.map +1 -1
- package/_cjs/environments/definitions/ethereum/tokens.js +12 -0
- package/_cjs/environments/definitions/ethereum/tokens.js.map +1 -1
- package/_cjs/environments/definitions/governance.js +1 -1
- package/_cjs/environments/definitions/governance.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_cjs/index.js +5 -1
- package/_cjs/index.js.map +1 -1
- package/_esm/actions/governance/getDelegates.js +2 -0
- package/_esm/actions/governance/getDelegates.js.map +1 -1
- package/_esm/actions/governance/getUserVotingPowers.js +24 -6
- package/_esm/actions/governance/getUserVotingPowers.js.map +1 -1
- package/_esm/actions/governance/proposals/common.js +43 -2
- package/_esm/actions/governance/proposals/common.js.map +1 -1
- package/_esm/actions/morpho/user-rewards/common.js +110 -203
- package/_esm/actions/morpho/user-rewards/common.js.map +1 -1
- package/_esm/actions/morpho/user-rewards/getMorphoUserRewards.js +51 -11
- package/_esm/actions/morpho/user-rewards/getMorphoUserRewards.js.map +1 -1
- package/_esm/environments/definitions/ethereum/contracts.js +10 -0
- package/_esm/environments/definitions/ethereum/contracts.js.map +1 -0
- package/_esm/environments/definitions/ethereum/custom.js +15 -0
- package/_esm/environments/definitions/ethereum/custom.js.map +1 -0
- package/_esm/environments/definitions/ethereum/environment.js +11 -5
- package/_esm/environments/definitions/ethereum/environment.js.map +1 -1
- package/_esm/environments/definitions/ethereum/tokens.js +12 -0
- package/_esm/environments/definitions/ethereum/tokens.js.map +1 -1
- package/_esm/environments/definitions/governance.js +2 -2
- package/_esm/environments/definitions/governance.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_esm/index.js +2 -0
- package/_esm/index.js.map +1 -1
- package/_types/actions/governance/getDelegates.d.ts.map +1 -1
- package/_types/actions/governance/getUserVotingPowers.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/morpho/user-rewards/common.d.ts +23 -0
- package/_types/actions/morpho/user-rewards/common.d.ts.map +1 -1
- package/_types/actions/morpho/user-rewards/getMorphoUserRewards.d.ts +22 -0
- package/_types/actions/morpho/user-rewards/getMorphoUserRewards.d.ts.map +1 -1
- package/_types/client/createMoonwellClient.d.ts +62 -2
- package/_types/client/createMoonwellClient.d.ts.map +1 -1
- package/_types/environments/definitions/ethereum/contracts.d.ts +4 -0
- package/_types/environments/definitions/ethereum/contracts.d.ts.map +1 -0
- package/_types/environments/definitions/ethereum/custom.d.ts +18 -0
- package/_types/environments/definitions/ethereum/custom.d.ts.map +1 -0
- package/_types/environments/definitions/ethereum/environment.d.ts +52 -2
- package/_types/environments/definitions/ethereum/environment.d.ts.map +1 -1
- package/_types/environments/definitions/ethereum/tokens.d.ts +12 -0
- package/_types/environments/definitions/ethereum/tokens.d.ts.map +1 -1
- package/_types/environments/definitions/governance.d.ts.map +1 -1
- package/_types/environments/index.d.ts +34 -4
- package/_types/environments/index.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/_types/index.d.ts +2 -0
- package/_types/index.d.ts.map +1 -1
- package/actions/governance/getDelegates.ts +2 -0
- package/actions/governance/getUserVotingPowers.ts +30 -15
- package/actions/governance/proposals/common.ts +56 -2
- package/actions/morpho/user-rewards/common.ts +133 -355
- package/actions/morpho/user-rewards/getMorphoUserRewards.ts +77 -12
- package/environments/definitions/ethereum/contracts.ts +10 -0
- package/environments/definitions/ethereum/custom.ts +15 -0
- package/environments/definitions/ethereum/environment.ts +15 -6
- package/environments/definitions/ethereum/tokens.ts +12 -0
- package/environments/definitions/governance.ts +2 -2
- package/errors/version.ts +1 -1
- package/index.ts +3 -0
- package/package.json +1 -1
|
@@ -99,6 +99,24 @@ export const isMultichainProposal = (targets?: string[]): boolean => {
|
|
|
99
99
|
);
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Routes a proposal to the multichain governor when:
|
|
104
|
+
* - its targets include the Wormhole bridge (legacy detection), OR
|
|
105
|
+
* - its proposalId is past the legacy Artemis governor's `proposalCount`,
|
|
106
|
+
* which means it could only have been created on the multichain governor
|
|
107
|
+
* (proposals migrated to the multichain governor after the cutoff but
|
|
108
|
+
* can have local-only targets, e.g. Moonbeam-internal contract calls).
|
|
109
|
+
*
|
|
110
|
+
* `legacyArtemisMaxId === 0` indicates the count read failed; in that case we
|
|
111
|
+
* fall back to the targets-only heuristic.
|
|
112
|
+
*/
|
|
113
|
+
export const isMultichainAware = (
|
|
114
|
+
proposal: { targets?: string[]; proposalId: number },
|
|
115
|
+
legacyArtemisMaxId: number,
|
|
116
|
+
): boolean =>
|
|
117
|
+
isMultichainProposal(proposal.targets) ||
|
|
118
|
+
(legacyArtemisMaxId > 0 && proposal.proposalId > legacyArtemisMaxId);
|
|
119
|
+
|
|
102
120
|
export type ApiProposalFormatted = {
|
|
103
121
|
forVotes: Amount;
|
|
104
122
|
againstVotes: Amount;
|
|
@@ -181,6 +199,40 @@ export type ProposalOnChainData = {
|
|
|
181
199
|
quorum: bigint;
|
|
182
200
|
};
|
|
183
201
|
|
|
202
|
+
// Cached per chain: highest proposalId held by the legacy Artemis governor.
|
|
203
|
+
// Anything with a higher proposalId belongs to the multichain governor, even
|
|
204
|
+
// if its targets don't include the Wormhole bridge. The legacy governor only
|
|
205
|
+
// receives new proposals during chain migrations, so a 5-minute TTL is plenty.
|
|
206
|
+
const LEGACY_ARTEMIS_MAX_ID_TTL_MS = 5 * 60 * 1000;
|
|
207
|
+
const legacyArtemisMaxIdCache = new Map<
|
|
208
|
+
number,
|
|
209
|
+
{ value: number; fetchedAt: number }
|
|
210
|
+
>();
|
|
211
|
+
|
|
212
|
+
const getLegacyArtemisMaxId = async (
|
|
213
|
+
governanceEnvironment: Environment,
|
|
214
|
+
): Promise<number> => {
|
|
215
|
+
const governor = governanceEnvironment.contracts.governor;
|
|
216
|
+
if (!governor) return 0;
|
|
217
|
+
|
|
218
|
+
const cached = legacyArtemisMaxIdCache.get(governanceEnvironment.chainId);
|
|
219
|
+
if (cached && Date.now() - cached.fetchedAt < LEGACY_ARTEMIS_MAX_ID_TTL_MS) {
|
|
220
|
+
return cached.value;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const value = Number(await governor.read.proposalCount());
|
|
225
|
+
legacyArtemisMaxIdCache.set(governanceEnvironment.chainId, {
|
|
226
|
+
value,
|
|
227
|
+
fetchedAt: Date.now(),
|
|
228
|
+
});
|
|
229
|
+
return value;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.warn("Failed to fetch legacy governor proposalCount:", error);
|
|
232
|
+
return cached?.value ?? 0;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
184
236
|
/**
|
|
185
237
|
* Fetches on-chain data for multiple proposals
|
|
186
238
|
*/
|
|
@@ -201,9 +253,11 @@ export const getProposalsOnChainData = async (
|
|
|
201
253
|
}
|
|
202
254
|
}
|
|
203
255
|
|
|
256
|
+
const legacyArtemisMaxId = await getLegacyArtemisMaxId(governanceEnvironment);
|
|
257
|
+
|
|
204
258
|
const onChainDataList = await Promise.all(
|
|
205
259
|
apiProposals.map(async (p) => {
|
|
206
|
-
const isMultichain =
|
|
260
|
+
const isMultichain = isMultichainAware(p, legacyArtemisMaxId);
|
|
207
261
|
|
|
208
262
|
const governorContract = isMultichain
|
|
209
263
|
? governanceEnvironment.contracts.multichainGovernor
|
|
@@ -242,7 +296,7 @@ export const getProposalsOnChainData = async (
|
|
|
242
296
|
|
|
243
297
|
const votesCollectedList = await Promise.all(
|
|
244
298
|
apiProposals.map(async (apiProposal) => {
|
|
245
|
-
const isMultichain =
|
|
299
|
+
const isMultichain = isMultichainAware(apiProposal, legacyArtemisMaxId);
|
|
246
300
|
|
|
247
301
|
if (
|
|
248
302
|
!isMultichain ||
|
|
@@ -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,90 @@ 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
|
-
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown for any failure communicating with the Merkl API: non-ok HTTP
|
|
18
|
+
* responses, network rejections (fetch threw), and response-body parse errors.
|
|
19
|
+
*
|
|
20
|
+
* - HTTP failures populate `status` and `statusText`.
|
|
21
|
+
* - Network and parse failures leave `status`/`statusText` undefined and
|
|
22
|
+
* carry the original error via `cause`.
|
|
23
|
+
*/
|
|
24
|
+
export class MerklApiError extends Error {
|
|
25
|
+
readonly status: number | undefined;
|
|
26
|
+
readonly statusText: string | undefined;
|
|
27
|
+
readonly url: string;
|
|
28
|
+
readonly chainId: number;
|
|
29
|
+
|
|
30
|
+
constructor(params: {
|
|
31
|
+
message: string;
|
|
32
|
+
url: string;
|
|
33
|
+
chainId: number;
|
|
34
|
+
status?: number | undefined;
|
|
35
|
+
statusText?: string | undefined;
|
|
36
|
+
cause?: unknown;
|
|
37
|
+
}) {
|
|
38
|
+
super(
|
|
39
|
+
params.message,
|
|
40
|
+
params.cause !== undefined ? { cause: params.cause } : undefined,
|
|
41
|
+
);
|
|
42
|
+
this.name = "MerklApiError";
|
|
43
|
+
this.url = params.url;
|
|
44
|
+
this.chainId = params.chainId;
|
|
45
|
+
this.status = params.status;
|
|
46
|
+
this.statusText = params.statusText;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
18
49
|
|
|
19
50
|
export async function getUserMorphoRewardsData(params: {
|
|
20
51
|
environment: Environment;
|
|
21
52
|
account: `0x${string}`;
|
|
53
|
+
throwOnExternalApiError?: boolean;
|
|
22
54
|
}): Promise<MorphoUserReward[]> {
|
|
55
|
+
// The Morpho URD distributions endpoint (rewards.morpho.org) was
|
|
56
|
+
// deprecated and now 301-redirects to a SPA, so JSON parsing fails.
|
|
57
|
+
// Surface only Merkl rewards.
|
|
58
|
+
const merklRewards = await getMerklRewardsData(
|
|
59
|
+
params.environment,
|
|
60
|
+
params.account,
|
|
61
|
+
{ throwOnError: params.throwOnExternalApiError ?? false },
|
|
62
|
+
);
|
|
63
|
+
|
|
23
64
|
const isFullDeployment =
|
|
24
65
|
params.environment.custom.morpho?.minimalDeployment === false;
|
|
25
66
|
|
|
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
|
-
),
|
|
67
|
+
// For full deployments (Base), restrict to Moonwell vault campaigns so the
|
|
68
|
+
// result excludes staking and other Moonwell campaigns; those are returned
|
|
69
|
+
// by their own actions (e.g. getUserStakingInfo). On other chains, surface
|
|
70
|
+
// every Merkl reward we get back.
|
|
71
|
+
const vaultCampaignIds = isFullDeployment
|
|
72
|
+
? new Set<string>(
|
|
73
|
+
(Object.values(publicEnvironments) as Environment[]).flatMap(
|
|
74
|
+
(environment) =>
|
|
75
|
+
Object.values(environment.config.vaults ?? {})
|
|
76
|
+
.map((vault) => vault.campaignId)
|
|
77
|
+
.filter((id): id is string => id !== undefined),
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
const sumBreakdowns = (
|
|
83
|
+
breakdowns: {
|
|
84
|
+
campaignId: string;
|
|
85
|
+
amount: string;
|
|
86
|
+
claimed: string;
|
|
87
|
+
pending: string;
|
|
88
|
+
}[],
|
|
89
|
+
field: "amount" | "claimed" | "pending",
|
|
90
|
+
): bigint =>
|
|
91
|
+
breakdowns.reduce(
|
|
92
|
+
(acc, curr) =>
|
|
93
|
+
vaultCampaignIds === null || vaultCampaignIds.has(curr.campaignId)
|
|
94
|
+
? acc + BigInt(curr[field])
|
|
95
|
+
: acc,
|
|
96
|
+
0n,
|
|
201
97
|
);
|
|
202
98
|
|
|
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
99
|
const merklResult: MorphoUserReward[] = [];
|
|
270
100
|
|
|
271
101
|
for (const chainData of merklRewards) {
|
|
@@ -277,19 +107,23 @@ export async function getUserMorphoRewardsData(params: {
|
|
|
277
107
|
name: reward.token.symbol,
|
|
278
108
|
};
|
|
279
109
|
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
110
|
+
const amount = vaultCampaignIds
|
|
111
|
+
? sumBreakdowns(reward.breakdowns, "amount")
|
|
112
|
+
: BigInt(reward.amount);
|
|
113
|
+
const claimed = vaultCampaignIds
|
|
114
|
+
? sumBreakdowns(reward.breakdowns, "claimed")
|
|
115
|
+
: BigInt(reward.claimed);
|
|
116
|
+
const pending = vaultCampaignIds
|
|
117
|
+
? sumBreakdowns(reward.breakdowns, "pending")
|
|
118
|
+
: BigInt(reward.pending);
|
|
119
|
+
|
|
120
|
+
const claimableNow = new Amount(amount - claimed, rewardToken.decimals);
|
|
284
121
|
const claimableNowUsd = claimableNow.value * (reward.token.price ?? 0);
|
|
285
|
-
const claimableFuture = new Amount(
|
|
286
|
-
BigInt(reward.pending),
|
|
287
|
-
rewardToken.decimals,
|
|
288
|
-
);
|
|
122
|
+
const claimableFuture = new Amount(pending, rewardToken.decimals);
|
|
289
123
|
const claimableFutureUsd =
|
|
290
124
|
claimableFuture.value * (reward.token.price ?? 0);
|
|
291
125
|
|
|
292
|
-
|
|
126
|
+
merklResult.push({
|
|
293
127
|
type: "merkl-reward",
|
|
294
128
|
chainId: chainData.chain.id,
|
|
295
129
|
account: params.account,
|
|
@@ -298,9 +132,7 @@ export async function getUserMorphoRewardsData(params: {
|
|
|
298
132
|
claimableNowUsd,
|
|
299
133
|
claimableFuture,
|
|
300
134
|
claimableFutureUsd,
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
merklResult.push(merklReward);
|
|
135
|
+
});
|
|
304
136
|
}
|
|
305
137
|
}
|
|
306
138
|
|
|
@@ -456,94 +288,6 @@ const getRewardsEarnedData = async (
|
|
|
456
288
|
return rewards.filter(Boolean);
|
|
457
289
|
};
|
|
458
290
|
|
|
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
291
|
type MerklRewardsResponse = {
|
|
548
292
|
chain: {
|
|
549
293
|
id: number;
|
|
@@ -586,30 +330,64 @@ type MerklRewardsResponse = {
|
|
|
586
330
|
async function getMerklRewardsData(
|
|
587
331
|
environment: Environment,
|
|
588
332
|
account: Address,
|
|
333
|
+
options: { throwOnError: boolean } = { throwOnError: false },
|
|
589
334
|
): Promise<MerklRewardsResponse[]> {
|
|
335
|
+
const url = `https://api.merkl.xyz/v4/users/${account}/rewards?chainId=${environment.chainId}&test=false&breakdownPage=0&reloadChainId=${environment.chainId}`;
|
|
336
|
+
|
|
337
|
+
let response: Response;
|
|
590
338
|
try {
|
|
591
339
|
// Merkl campaigns always distribute rewards on the same chain as the
|
|
592
340
|
// opportunity, so environment.chainId is the only chain we need to query.
|
|
593
341
|
// The previous two-phase approach (fetch opportunities per vault → extract
|
|
594
342
|
// chain IDs → fetch rewards per chain) made N+1 HTTP calls to discover
|
|
595
343
|
// a chain ID we already know.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
344
|
+
response = await fetch(url, { headers: MOONWELL_FETCH_JSON_HEADERS });
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (options.throwOnError) {
|
|
347
|
+
throw new MerklApiError({
|
|
348
|
+
message: `Merkl API network error for chain ${environment.chainId}`,
|
|
349
|
+
url,
|
|
350
|
+
chainId: environment.chainId,
|
|
351
|
+
cause: error,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
console.error(
|
|
355
|
+
`[getMerklRewardsData:network] chain=${environment.chainId} url=${url}`,
|
|
356
|
+
error,
|
|
601
357
|
);
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
602
360
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
361
|
+
if (!response.ok) {
|
|
362
|
+
const message = `Merkl API request failed for chain ${environment.chainId}: ${response.status} ${response.statusText}`;
|
|
363
|
+
if (options.throwOnError) {
|
|
364
|
+
throw new MerklApiError({
|
|
365
|
+
message,
|
|
366
|
+
url,
|
|
367
|
+
chainId: environment.chainId,
|
|
368
|
+
status: response.status,
|
|
369
|
+
statusText: response.statusText,
|
|
370
|
+
});
|
|
608
371
|
}
|
|
372
|
+
console.warn(`${message} (url=${url})`);
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
609
375
|
|
|
376
|
+
try {
|
|
610
377
|
return (await response.json()) as MerklRewardsResponse[];
|
|
611
378
|
} catch (error) {
|
|
612
|
-
|
|
379
|
+
if (options.throwOnError) {
|
|
380
|
+
throw new MerklApiError({
|
|
381
|
+
message: `Merkl API response parse error for chain ${environment.chainId}`,
|
|
382
|
+
url,
|
|
383
|
+
chainId: environment.chainId,
|
|
384
|
+
cause: error,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
console.error(
|
|
388
|
+
`[getMerklRewardsData:parse] chain=${environment.chainId} url=${url}`,
|
|
389
|
+
error,
|
|
390
|
+
);
|
|
613
391
|
return [];
|
|
614
392
|
}
|
|
615
393
|
}
|