@moonwell-fi/moonwell-sdk 0.13.0 → 0.14.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 +20 -0
- package/_cjs/actions/core/markets/common.js +11 -4
- package/_cjs/actions/core/markets/common.js.map +1 -1
- package/_cjs/actions/core/user-rewards/common.js +6 -5
- package/_cjs/actions/core/user-rewards/common.js.map +1 -1
- package/_cjs/actions/governance/getStakingInfo.js +113 -31
- package/_cjs/actions/governance/getStakingInfo.js.map +1 -1
- package/_cjs/actions/governance/getUserStakingInfo.js +106 -18
- package/_cjs/actions/governance/getUserStakingInfo.js.map +1 -1
- package/_cjs/actions/governance/getUserVoteReceipt.js +39 -25
- package/_cjs/actions/governance/getUserVoteReceipt.js.map +1 -1
- package/_cjs/actions/governance/getWellPrice.js +26 -0
- package/_cjs/actions/governance/getWellPrice.js.map +1 -0
- package/_cjs/actions/governance/governor-api-client.js +70 -33
- package/_cjs/actions/governance/governor-api-client.js.map +1 -1
- package/_cjs/actions/governance/proposals/common.js +29 -1
- package/_cjs/actions/governance/proposals/common.js.map +1 -1
- package/_cjs/actions/governance/proposals/getProposal.js +24 -17
- package/_cjs/actions/governance/proposals/getProposal.js.map +1 -1
- package/_cjs/actions/governance/proposals/getProposals.js +27 -9
- package/_cjs/actions/governance/proposals/getProposals.js.map +1 -1
- package/_cjs/actions/morpho/user-rewards/common.js +10 -3
- package/_cjs/actions/morpho/user-rewards/common.js.map +1 -1
- package/_cjs/actions/morpho/vaults/common.js +19 -6
- package/_cjs/actions/morpho/vaults/common.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_esm/actions/core/markets/common.js +11 -4
- package/_esm/actions/core/markets/common.js.map +1 -1
- package/_esm/actions/core/user-rewards/common.js +6 -5
- package/_esm/actions/core/user-rewards/common.js.map +1 -1
- package/_esm/actions/governance/getStakingInfo.js +136 -32
- package/_esm/actions/governance/getStakingInfo.js.map +1 -1
- package/_esm/actions/governance/getUserStakingInfo.js +120 -19
- package/_esm/actions/governance/getUserStakingInfo.js.map +1 -1
- package/_esm/actions/governance/getUserVoteReceipt.js +48 -26
- package/_esm/actions/governance/getUserVoteReceipt.js.map +1 -1
- package/_esm/actions/governance/getWellPrice.js +50 -0
- package/_esm/actions/governance/getWellPrice.js.map +1 -0
- package/_esm/actions/governance/governor-api-client.js +87 -35
- package/_esm/actions/governance/governor-api-client.js.map +1 -1
- package/_esm/actions/governance/proposals/common.js +44 -1
- package/_esm/actions/governance/proposals/common.js.map +1 -1
- package/_esm/actions/governance/proposals/getProposal.js +36 -23
- package/_esm/actions/governance/proposals/getProposal.js.map +1 -1
- package/_esm/actions/governance/proposals/getProposals.js +44 -10
- package/_esm/actions/governance/proposals/getProposals.js.map +1 -1
- package/_esm/actions/morpho/user-rewards/common.js +10 -3
- package/_esm/actions/morpho/user-rewards/common.js.map +1 -1
- package/_esm/actions/morpho/vaults/common.js +19 -6
- package/_esm/actions/morpho/vaults/common.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_types/actions/core/markets/common.d.ts.map +1 -1
- package/_types/actions/core/user-rewards/common.d.ts.map +1 -1
- package/_types/actions/governance/getStakingInfo.d.ts +1 -1
- package/_types/actions/governance/getStakingInfo.d.ts.map +1 -1
- package/_types/actions/governance/getUserStakingInfo.d.ts.map +1 -1
- package/_types/actions/governance/getUserVoteReceipt.d.ts +16 -0
- package/_types/actions/governance/getUserVoteReceipt.d.ts.map +1 -1
- package/_types/actions/governance/getWellPrice.d.ts +29 -0
- package/_types/actions/governance/getWellPrice.d.ts.map +1 -0
- package/_types/actions/governance/governor-api-client.d.ts +37 -12
- package/_types/actions/governance/governor-api-client.d.ts.map +1 -1
- package/_types/actions/governance/proposals/common.d.ts +14 -1
- package/_types/actions/governance/proposals/common.d.ts.map +1 -1
- package/_types/actions/governance/proposals/getProposal.d.ts +6 -1
- package/_types/actions/governance/proposals/getProposal.d.ts.map +1 -1
- package/_types/actions/governance/proposals/getProposals.d.ts +1 -1
- package/_types/actions/governance/proposals/getProposals.d.ts.map +1 -1
- package/_types/actions/morpho/user-rewards/common.d.ts.map +1 -1
- package/_types/actions/morpho/vaults/common.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/actions/core/markets/common.ts +11 -6
- package/actions/core/user-rewards/common.ts +6 -5
- package/actions/governance/getStakingInfo.ts +195 -87
- package/actions/governance/getUserStakingInfo.ts +168 -54
- package/actions/governance/getUserVoteReceipt.ts +71 -31
- package/actions/governance/getWellPrice.ts +66 -0
- package/actions/governance/governor-api-client.ts +136 -62
- package/actions/governance/proposals/common.ts +51 -1
- package/actions/governance/proposals/getProposal.ts +46 -26
- package/actions/governance/proposals/getProposals.ts +48 -14
- package/actions/morpho/user-rewards/common.ts +10 -3
- package/actions/morpho/vaults/common.ts +19 -12
- package/errors/version.ts +1 -1
- package/package.json +1 -1
|
@@ -3,13 +3,10 @@ import { base } from "viem/chains";
|
|
|
3
3
|
import type { MoonwellClient } from "../../client/createMoonwellClient.js";
|
|
4
4
|
import { Amount, getEnvironmentsFromArgs } from "../../common/index.js";
|
|
5
5
|
import type { OptionalNetworkParameterType } from "../../common/types.js";
|
|
6
|
-
import {
|
|
7
|
-
type Environment,
|
|
8
|
-
type TokensType,
|
|
9
|
-
publicEnvironments,
|
|
10
|
-
} from "../../environments/index.js";
|
|
6
|
+
import type { Environment } from "../../environments/index.js";
|
|
11
7
|
import type { UserStakingInfo } from "../../types/staking.js";
|
|
12
8
|
import { getMerklCampaignIds, getMerklRewardsData } from "./common.js";
|
|
9
|
+
import { getGovernanceTokenPriceFor } from "./getWellPrice.js";
|
|
13
10
|
|
|
14
11
|
export type GetUserStakingInfoParameters<
|
|
15
12
|
environments,
|
|
@@ -21,6 +18,113 @@ export type GetUserStakingInfoParameters<
|
|
|
21
18
|
|
|
22
19
|
export type GetUserStakingInfoReturnType = Promise<UserStakingInfo[]>;
|
|
23
20
|
|
|
21
|
+
type UserStakingFields = {
|
|
22
|
+
cooldown: bigint;
|
|
23
|
+
pendingRewards: bigint;
|
|
24
|
+
totalStaked: bigint;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type StakingScheduleFields = {
|
|
28
|
+
cooldown: bigint;
|
|
29
|
+
unstakeWindow: bigint;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reads per-user staking fields directly from stkWELL when views.getUserStakingInfo()
|
|
34
|
+
* is unavailable (e.g. reverts on Moonbeam).
|
|
35
|
+
*/
|
|
36
|
+
async function readUserStakingFromStkWell(
|
|
37
|
+
environment: Environment,
|
|
38
|
+
userAddress: Address,
|
|
39
|
+
): Promise<UserStakingFields | undefined> {
|
|
40
|
+
const stakingToken = environment.contracts.stakingToken;
|
|
41
|
+
if (!stakingToken) return undefined;
|
|
42
|
+
|
|
43
|
+
const [cooldownR, rewardsR, balanceR] = await Promise.allSettled([
|
|
44
|
+
stakingToken.read.stakersCooldowns([userAddress]),
|
|
45
|
+
stakingToken.read.getTotalRewardsBalance([userAddress]),
|
|
46
|
+
stakingToken.read.balanceOf([userAddress]),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// Surface every rejection so a single failed read (e.g. balanceOf throttled)
|
|
50
|
+
// doesn't silently zero a field that the UI would then display as "no stake".
|
|
51
|
+
for (const r of [cooldownR, rewardsR, balanceR]) {
|
|
52
|
+
if (r.status === "rejected") {
|
|
53
|
+
environment.onError?.(r.reason, {
|
|
54
|
+
source: "user-staking-fallback",
|
|
55
|
+
chainId: environment.chainId,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
cooldownR.status === "rejected" &&
|
|
62
|
+
rewardsR.status === "rejected" &&
|
|
63
|
+
balanceR.status === "rejected"
|
|
64
|
+
) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
cooldown: cooldownR.status === "fulfilled" ? cooldownR.value : 0n,
|
|
70
|
+
pendingRewards: rewardsR.status === "fulfilled" ? rewardsR.value : 0n,
|
|
71
|
+
totalStaked: balanceR.status === "fulfilled" ? balanceR.value : 0n,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Reads global cooldown/unstakeWindow constants from stkWELL when
|
|
77
|
+
* views.getStakingInfo() is unavailable.
|
|
78
|
+
*/
|
|
79
|
+
async function readScheduleFromStkWell(
|
|
80
|
+
environment: Environment,
|
|
81
|
+
): Promise<StakingScheduleFields | undefined> {
|
|
82
|
+
const stakingToken = environment.contracts.stakingToken;
|
|
83
|
+
if (!stakingToken) return undefined;
|
|
84
|
+
|
|
85
|
+
const [cooldownR, unstakeWindowR] = await Promise.allSettled([
|
|
86
|
+
stakingToken.read.COOLDOWN_SECONDS(),
|
|
87
|
+
stakingToken.read.UNSTAKE_WINDOW(),
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
// Surface every rejection. A silent cooldown=0n would corrupt downstream
|
|
91
|
+
// cooldownEnding math even if the unstakeWindow read succeeded.
|
|
92
|
+
for (const r of [cooldownR, unstakeWindowR]) {
|
|
93
|
+
if (r.status === "rejected") {
|
|
94
|
+
environment.onError?.(r.reason, {
|
|
95
|
+
source: "user-staking-schedule-fallback",
|
|
96
|
+
chainId: environment.chainId,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (cooldownR.status === "rejected" && unstakeWindowR.status === "rejected") {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
cooldown: cooldownR.status === "fulfilled" ? cooldownR.value : 0n,
|
|
107
|
+
unstakeWindow:
|
|
108
|
+
unstakeWindowR.status === "fulfilled" ? unstakeWindowR.value : 0n,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const isUserStakingShape = (value: unknown): value is UserStakingFields => {
|
|
113
|
+
if (typeof value !== "object" || value === null) return false;
|
|
114
|
+
const v = value as Record<string, unknown>;
|
|
115
|
+
return (
|
|
116
|
+
typeof v.cooldown === "bigint" &&
|
|
117
|
+
typeof v.pendingRewards === "bigint" &&
|
|
118
|
+
typeof v.totalStaked === "bigint"
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const isScheduleShape = (value: unknown): value is StakingScheduleFields => {
|
|
123
|
+
if (typeof value !== "object" || value === null) return false;
|
|
124
|
+
const v = value as Record<string, unknown>;
|
|
125
|
+
return typeof v.cooldown === "bigint" && typeof v.unstakeWindow === "bigint";
|
|
126
|
+
};
|
|
127
|
+
|
|
24
128
|
export async function getUserStakingInfo<
|
|
25
129
|
environments,
|
|
26
130
|
Network extends Chain | undefined,
|
|
@@ -35,23 +139,50 @@ export async function getUserStakingInfo<
|
|
|
35
139
|
const envsWithStaking = environments.filter(
|
|
36
140
|
(env) => env.contracts.stakingToken,
|
|
37
141
|
);
|
|
142
|
+
|
|
143
|
+
const baseEnvironment = (
|
|
144
|
+
client.environments as { base?: Environment } | undefined
|
|
145
|
+
)?.base;
|
|
146
|
+
|
|
38
147
|
const envStakingInfo = await Promise.all(
|
|
39
148
|
envsWithStaking.map(async (environment) => {
|
|
40
|
-
const homeEnvironment =
|
|
41
|
-
(Object.values(publicEnvironments) as Environment[]).find((e) =>
|
|
42
|
-
e.custom?.governance?.chainIds?.includes(environment.chainId),
|
|
43
|
-
) || environment;
|
|
44
|
-
|
|
45
149
|
const settled = await Promise.allSettled([
|
|
46
150
|
environment.contracts.views?.read.getUserStakingInfo([userAddress]),
|
|
47
151
|
environment.contracts.governanceToken?.read.balanceOf([userAddress]),
|
|
48
|
-
homeEnvironment.contracts.views?.read.getGovernanceTokenPrice(),
|
|
49
152
|
environment.contracts.views?.read.getStakingInfo(),
|
|
153
|
+
getGovernanceTokenPriceFor(environment, baseEnvironment),
|
|
50
154
|
]);
|
|
51
155
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
156
|
+
const [userStakingR, balanceR, stakingScheduleR, priceR] = settled;
|
|
157
|
+
|
|
158
|
+
const viewsUserStaking =
|
|
159
|
+
userStakingR.status === "fulfilled" ? userStakingR.value : undefined;
|
|
160
|
+
const userStaking = isUserStakingShape(viewsUserStaking)
|
|
161
|
+
? viewsUserStaking
|
|
162
|
+
: await readUserStakingFromStkWell(environment, userAddress);
|
|
163
|
+
|
|
164
|
+
const viewsSchedule =
|
|
165
|
+
stakingScheduleR.status === "fulfilled"
|
|
166
|
+
? stakingScheduleR.value
|
|
167
|
+
: undefined;
|
|
168
|
+
const schedule = isScheduleShape(viewsSchedule)
|
|
169
|
+
? viewsSchedule
|
|
170
|
+
: await readScheduleFromStkWell(environment);
|
|
171
|
+
|
|
172
|
+
const tokenBalance =
|
|
173
|
+
balanceR.status === "fulfilled" && balanceR.value !== undefined
|
|
174
|
+
? balanceR.value
|
|
175
|
+
: 0n;
|
|
176
|
+
|
|
177
|
+
const price = priceR.status === "fulfilled" ? priceR.value : 0n;
|
|
178
|
+
if (priceR.status === "rejected") {
|
|
179
|
+
environment.onError?.(priceR.reason, {
|
|
180
|
+
source: "governance-token-price",
|
|
181
|
+
chainId: environment.chainId,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { userStaking, schedule, tokenBalance, price };
|
|
55
186
|
}),
|
|
56
187
|
);
|
|
57
188
|
|
|
@@ -63,28 +194,27 @@ export async function getUserStakingInfo<
|
|
|
63
194
|
);
|
|
64
195
|
|
|
65
196
|
const result = envsWithStaking.flatMap((curr, index) => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const { cooldown
|
|
86
|
-
|
|
87
|
-
// merkl rewards (only for base)
|
|
197
|
+
const govKey = curr.config.contracts.governanceToken;
|
|
198
|
+
const stkKey = curr.config.contracts.stakingToken;
|
|
199
|
+
const currTokens = curr.config.tokens as Record<
|
|
200
|
+
string,
|
|
201
|
+
{ address: `0x${string}`; decimals: number; name: string; symbol: string }
|
|
202
|
+
>;
|
|
203
|
+
if (!govKey || !stkKey) return [];
|
|
204
|
+
const token = currTokens[govKey];
|
|
205
|
+
const stakingToken = currTokens[stkKey];
|
|
206
|
+
if (!token || !stakingToken) return [];
|
|
207
|
+
|
|
208
|
+
// envStakingInfo is built via Promise.all over the same envsWithStaking
|
|
209
|
+
// array, so index access is always defined.
|
|
210
|
+
const { userStaking, schedule, tokenBalance, price } =
|
|
211
|
+
envStakingInfo[index];
|
|
212
|
+
|
|
213
|
+
if (!userStaking || !schedule) return [];
|
|
214
|
+
|
|
215
|
+
const { cooldown, pendingRewards, totalStaked } = userStaking;
|
|
216
|
+
const { cooldown: cooldownSeconds, unstakeWindow } = schedule;
|
|
217
|
+
|
|
88
218
|
const isBase = curr.chainId === base.id;
|
|
89
219
|
const merklReward = merklRewards.reduce((acc, r) => {
|
|
90
220
|
if (r.chain === curr.chainId) {
|
|
@@ -94,27 +224,11 @@ export async function getUserStakingInfo<
|
|
|
94
224
|
}, 0n);
|
|
95
225
|
const merklPendingRewards = isBase ? merklReward : 0n;
|
|
96
226
|
|
|
97
|
-
const tokenBalance = (envStakingInfo[index]?.[1] ?? 0n) as bigint;
|
|
98
|
-
|
|
99
|
-
const governanceTokenPriceRaw = (envStakingInfo[index]?.[2] ??
|
|
100
|
-
0n) as bigint;
|
|
101
|
-
|
|
102
|
-
const stakingInfoData = envStakingInfo[index]?.[3] as
|
|
103
|
-
| {
|
|
104
|
-
cooldown: bigint;
|
|
105
|
-
unstakeWindow: bigint;
|
|
106
|
-
}
|
|
107
|
-
| undefined;
|
|
108
|
-
|
|
109
|
-
if (!stakingInfoData) return [];
|
|
110
|
-
|
|
111
|
-
const { cooldown: cooldownSeconds, unstakeWindow } = stakingInfoData;
|
|
112
|
-
|
|
113
227
|
const cooldownEnding = cooldown > 0n ? cooldown + cooldownSeconds : 0n;
|
|
114
228
|
const unstakingEnding =
|
|
115
229
|
cooldown > 0n ? cooldown + cooldownSeconds + unstakeWindow : 0n;
|
|
116
230
|
|
|
117
|
-
const
|
|
231
|
+
const tokenPrice = new Amount(price, 18);
|
|
118
232
|
|
|
119
233
|
const userStakingInfo: UserStakingInfo = {
|
|
120
234
|
chainId: curr.chainId,
|
|
@@ -128,7 +242,7 @@ export async function getUserStakingInfo<
|
|
|
128
242
|
: new Amount(pendingRewards, 18),
|
|
129
243
|
token,
|
|
130
244
|
tokenBalance: new Amount(tokenBalance, 18),
|
|
131
|
-
tokenPrice:
|
|
245
|
+
tokenPrice: tokenPrice.value,
|
|
132
246
|
stakingToken,
|
|
133
247
|
stakingTokenBalance: new Amount(totalStaked, 18),
|
|
134
248
|
};
|
|
@@ -3,7 +3,13 @@ import type { MoonwellClient } from "../../client/createMoonwellClient.js";
|
|
|
3
3
|
import { Amount, getEnvironmentFromArgs } from "../../common/index.js";
|
|
4
4
|
import type { NetworkParameterType } from "../../common/types.js";
|
|
5
5
|
import type { VoteReceipt } from "../../types/voteReceipt.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type ApiVoteReceipt,
|
|
8
|
+
GovernorNotFoundError,
|
|
9
|
+
SUPPORTED_GOVERNOR_CHAIN_IDS,
|
|
10
|
+
fetchUserVoteReceipt,
|
|
11
|
+
isNotFoundError,
|
|
12
|
+
} from "./governor-api-client.js";
|
|
7
13
|
|
|
8
14
|
export type GetUserVoteReceiptParameters<
|
|
9
15
|
environments,
|
|
@@ -14,10 +20,27 @@ export type GetUserVoteReceiptParameters<
|
|
|
14
20
|
|
|
15
21
|
/** User address*/
|
|
16
22
|
userAddress: Address;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The chain the proposal lives on (1 = Ethereum multigov,
|
|
26
|
+
* 1284 = Moonbeam historical). When omitted, every supported chain is queried
|
|
27
|
+
* and non-empty receipts are concatenated — proposalIds may collide across
|
|
28
|
+
* chains (they represent different proposals), so a single bare proposalId
|
|
29
|
+
* can have votes on both Ethereum and Moonbeam.
|
|
30
|
+
*/
|
|
31
|
+
chainId?: number;
|
|
17
32
|
};
|
|
18
33
|
|
|
19
34
|
export type GetUserVoteReceiptReturnType = Promise<VoteReceipt[]>;
|
|
20
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Fetch a user's vote receipts for a proposal.
|
|
38
|
+
*
|
|
39
|
+
* Returns the "didn't vote" stub when the proposal exists on at least one
|
|
40
|
+
* queried chain but the user hasn't voted there. Throws `GovernorNotFoundError`
|
|
41
|
+
* when the proposal doesn't exist on any queried chain — callers can use that
|
|
42
|
+
* to distinguish "the user didn't vote" from "this proposal isn't visible".
|
|
43
|
+
*/
|
|
21
44
|
export async function getUserVoteReceipt<
|
|
22
45
|
environments,
|
|
23
46
|
Network extends Chain | undefined,
|
|
@@ -25,44 +48,61 @@ export async function getUserVoteReceipt<
|
|
|
25
48
|
client: MoonwellClient,
|
|
26
49
|
args: GetUserVoteReceiptParameters<environments, Network>,
|
|
27
50
|
): GetUserVoteReceiptReturnType {
|
|
28
|
-
const { proposalId, userAddress } = args;
|
|
51
|
+
const { proposalId, userAddress, chainId } = args;
|
|
29
52
|
|
|
30
53
|
const environment = getEnvironmentFromArgs(client, args);
|
|
31
|
-
|
|
32
54
|
if (!environment) {
|
|
33
55
|
return [];
|
|
34
56
|
}
|
|
35
57
|
|
|
36
|
-
|
|
37
|
-
const apiVoteReceipts = await fetchUserVoteReceipt(
|
|
38
|
-
environment,
|
|
39
|
-
`${proposalId}`,
|
|
40
|
-
userAddress,
|
|
41
|
-
);
|
|
58
|
+
const tryChains = chainId ? [chainId] : SUPPORTED_GOVERNOR_CHAIN_IDS;
|
|
42
59
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
const collected: ApiVoteReceipt[] = [];
|
|
61
|
+
let anyChainAcknowledged = false;
|
|
62
|
+
const notFoundChainIds: number[] = [];
|
|
63
|
+
|
|
64
|
+
for (const cid of tryChains) {
|
|
65
|
+
try {
|
|
66
|
+
const apiVoteReceipts = await fetchUserVoteReceipt(
|
|
67
|
+
environment,
|
|
68
|
+
cid,
|
|
69
|
+
proposalId,
|
|
70
|
+
userAddress,
|
|
71
|
+
);
|
|
72
|
+
anyChainAcknowledged = true;
|
|
73
|
+
collected.push(...apiVoteReceipts);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (isNotFoundError(error)) {
|
|
76
|
+
notFoundChainIds.push(cid);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
54
80
|
}
|
|
81
|
+
}
|
|
55
82
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
proposalId,
|
|
59
|
-
account: userAddress as Address,
|
|
60
|
-
voted: true,
|
|
61
|
-
option: apiReceipt.voteValue,
|
|
62
|
-
votes: new Amount(BigInt(apiReceipt.votes), 18),
|
|
63
|
-
}));
|
|
64
|
-
} catch (error) {
|
|
65
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
-
throw new Error(`Failed to fetch user vote receipt: ${message}`);
|
|
83
|
+
if (!anyChainAcknowledged) {
|
|
84
|
+
throw new GovernorNotFoundError(notFoundChainIds[0] ?? 0, proposalId);
|
|
67
85
|
}
|
|
86
|
+
|
|
87
|
+
if (collected.length === 0) {
|
|
88
|
+
return [
|
|
89
|
+
{
|
|
90
|
+
chainId: environment.chainId,
|
|
91
|
+
proposalId,
|
|
92
|
+
account: userAddress,
|
|
93
|
+
voted: false,
|
|
94
|
+
option: 0,
|
|
95
|
+
votes: new Amount(0, 18),
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return collected.map((apiReceipt) => ({
|
|
101
|
+
chainId: apiReceipt.chainId,
|
|
102
|
+
proposalId,
|
|
103
|
+
account: userAddress,
|
|
104
|
+
voted: true,
|
|
105
|
+
option: apiReceipt.voteValue,
|
|
106
|
+
votes: new Amount(BigInt(apiReceipt.votes), 18),
|
|
107
|
+
}));
|
|
68
108
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Environment,
|
|
3
|
+
publicEnvironments,
|
|
4
|
+
} from "../../environments/index.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reads WELL/USD from Base's lending oracle via getUnderlyingPrice(mWELL).
|
|
8
|
+
*
|
|
9
|
+
* The Base oracle is Chainlink-fed and shared by the lending markets, so it's
|
|
10
|
+
* the authoritative WELL/USD source. Used in place of the per-chain
|
|
11
|
+
* views.getGovernanceTokenPrice() which is unreliable on Moonbeam (returns
|
|
12
|
+
* stale data) and Base (returns 0).
|
|
13
|
+
*
|
|
14
|
+
* Returns a uint256 already scaled to 18 decimals.
|
|
15
|
+
*
|
|
16
|
+
* @param baseEnvironment Pass the caller's Base environment when available so
|
|
17
|
+
* user-configured RPCs / onError handlers are honored. Falls back to the
|
|
18
|
+
* SDK's default public Base environment otherwise.
|
|
19
|
+
*/
|
|
20
|
+
export async function getWellPriceFromBaseOracle(
|
|
21
|
+
baseEnvironment?: Environment,
|
|
22
|
+
): Promise<bigint> {
|
|
23
|
+
const baseEnv = baseEnvironment ?? publicEnvironments.base;
|
|
24
|
+
const tokens = baseEnv.config.tokens as Record<
|
|
25
|
+
string,
|
|
26
|
+
{ address: `0x${string}` } | undefined
|
|
27
|
+
>;
|
|
28
|
+
const mWELL = tokens.MOONWELL_WELL?.address;
|
|
29
|
+
const oracle = baseEnv.contracts.oracle;
|
|
30
|
+
if (!mWELL || !oracle) {
|
|
31
|
+
// A custom Base env without MOONWELL_WELL or an oracle would silently
|
|
32
|
+
// zero out every WELL-priced read across the SDK. Surface it instead.
|
|
33
|
+
baseEnv.onError?.(
|
|
34
|
+
new Error(
|
|
35
|
+
`getWellPriceFromBaseOracle: missing ${!mWELL ? "MOONWELL_WELL token" : "oracle contract"} on Base env`,
|
|
36
|
+
),
|
|
37
|
+
{ source: "well-price", chainId: baseEnv.chainId },
|
|
38
|
+
);
|
|
39
|
+
return 0n;
|
|
40
|
+
}
|
|
41
|
+
return await oracle.read.getUnderlyingPrice([mWELL]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns the governance-token-in-USD price for an environment.
|
|
46
|
+
*
|
|
47
|
+
* - For WELL-governed chains (Base, Optimism, Moonbeam), reads from the Base
|
|
48
|
+
* lending oracle's mWELL underlying price (authoritative, Chainlink-fed).
|
|
49
|
+
* - For non-WELL chains (currently only Moonriver / MFAM), reads from the
|
|
50
|
+
* env's own views.getGovernanceTokenPrice() — Moonriver has its own MFAM
|
|
51
|
+
* oracle and isn't priced from Base.
|
|
52
|
+
*
|
|
53
|
+
* Returns 0n if the lookup fails.
|
|
54
|
+
*/
|
|
55
|
+
export async function getGovernanceTokenPriceFor(
|
|
56
|
+
environment: Environment,
|
|
57
|
+
baseEnvironment?: Environment,
|
|
58
|
+
): Promise<bigint> {
|
|
59
|
+
if (environment.custom?.governance?.token === "WELL") {
|
|
60
|
+
return getWellPriceFromBaseOracle(baseEnvironment);
|
|
61
|
+
}
|
|
62
|
+
// Non-WELL (e.g. Moonriver / MFAM): the env itself is the governance "home".
|
|
63
|
+
const views = environment.contracts.views;
|
|
64
|
+
if (!views) return 0n;
|
|
65
|
+
return (await views.read.getGovernanceTokenPrice()) ?? 0n;
|
|
66
|
+
}
|