@stake-dao/reader 0.4.26 → 0.4.28
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/esm/bytecodes/gaugeManager/curveGaugeRewardsData.js +36 -0
- package/dist/esm/bytecodes/gaugeManager/curveGaugeRewardsData.js.map +1 -0
- package/dist/esm/bytecodes/strategies/batches/batchPancakeSwapPositions.js +2 -2
- package/dist/esm/bytecodes/strategies/batches/batchPancakeSwapPositions.js.map +1 -1
- package/dist/esm/bytecodes/votemarket/batchCampaigns.js +11 -1
- package/dist/esm/bytecodes/votemarket/batchCampaigns.js.map +1 -1
- package/dist/esm/bytecodes/votemarket/batchClaimableData.js +21 -0
- package/dist/esm/bytecodes/votemarket/batchClaimableData.js.map +1 -0
- package/dist/esm/bytecodes/votemarket/batchGaugesWeight.js +13 -0
- package/dist/esm/bytecodes/votemarket/batchGaugesWeight.js.map +1 -0
- package/dist/esm/bytecodes/votemarket/batchVotes.js +13 -0
- package/dist/esm/bytecodes/votemarket/batchVotes.js.map +1 -0
- package/dist/esm/bytecodes/votemarket/curve/batchCurveGaugesMetadata.js +4 -3
- package/dist/esm/bytecodes/votemarket/curve/batchCurveGaugesMetadata.js.map +1 -1
- package/dist/esm/gaugeManager/curve/gmFetchGauge.js +34 -0
- package/dist/esm/gaugeManager/curve/gmFetchGauge.js.map +1 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/strategies/pancakeswap/endpoints.js +1 -1
- package/dist/esm/strategies/pancakeswap/fetch/getPancakeV3Positions.js +24 -5
- package/dist/esm/strategies/pancakeswap/fetch/getPancakeV3Positions.js.map +1 -1
- package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
- package/dist/esm/utils.js +43 -1
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/votemarket/curve/config.js +1 -1
- package/dist/esm/votemarket/curve/fetchCurveGauges.js +2 -0
- package/dist/esm/votemarket/curve/fetchCurveGauges.js.map +1 -1
- package/dist/esm/votemarket/curve/fetchSnapshotUserData.js.map +1 -1
- package/dist/esm/votemarket/fetchCampaigns.js +115 -12
- package/dist/esm/votemarket/fetchCampaigns.js.map +1 -1
- package/dist/esm/votemarket/fetchClaimableData.js +36 -0
- package/dist/esm/votemarket/fetchClaimableData.js.map +1 -0
- package/dist/esm/votemarket/fetchEpochVotes.js +30 -0
- package/dist/esm/votemarket/fetchEpochVotes.js.map +1 -0
- package/dist/esm/votemarket/generateProofs/config.js +53 -0
- package/dist/esm/votemarket/generateProofs/config.js.map +1 -0
- package/dist/esm/votemarket/generateProofs/getBlockProof.js +33 -0
- package/dist/esm/votemarket/generateProofs/getBlockProof.js.map +1 -0
- package/dist/esm/votemarket/generateProofs/getGaugeProof.js +50 -0
- package/dist/esm/votemarket/generateProofs/getGaugeProof.js.map +1 -0
- package/dist/esm/votemarket/generateProofs/getUserProof.js +49 -0
- package/dist/esm/votemarket/generateProofs/getUserProof.js.map +1 -0
- package/dist/esm/votemarket/types.js +2 -0
- package/dist/esm/votemarket/types.js.map +1 -0
- package/dist/types/bytecodes/gaugeManager/curveGaugeRewardsData.d.ts +4 -0
- package/dist/types/bytecodes/gaugeManager/curveGaugeRewardsData.d.ts.map +1 -0
- package/dist/types/bytecodes/votemarket/batchCampaigns.d.ts.map +1 -1
- package/dist/types/bytecodes/votemarket/batchClaimableData.d.ts +4 -0
- package/dist/types/bytecodes/votemarket/batchClaimableData.d.ts.map +1 -0
- package/dist/types/bytecodes/votemarket/batchGaugesWeight.d.ts +4 -0
- package/dist/types/bytecodes/votemarket/batchGaugesWeight.d.ts.map +1 -0
- package/dist/types/bytecodes/votemarket/batchVotes.d.ts +4 -0
- package/dist/types/bytecodes/votemarket/batchVotes.d.ts.map +1 -0
- package/dist/types/bytecodes/votemarket/curve/batchCurveGaugesMetadata.d.ts.map +1 -1
- package/dist/types/gaugeManager/curve/gmFetchGauge.d.ts +25 -0
- package/dist/types/gaugeManager/curve/gmFetchGauge.d.ts.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/strategies/pancakeswap/fetch/getPancakeV3Positions.d.ts.map +1 -1
- package/dist/types/utils.d.ts +14 -1
- package/dist/types/utils.d.ts.map +1 -1
- package/dist/types/votemarket/curve/config.d.ts +1 -1
- package/dist/types/votemarket/curve/fetchCurveGauges.d.ts.map +1 -1
- package/dist/types/votemarket/curve/fetchSnapshotUserData.d.ts.map +1 -1
- package/dist/types/votemarket/fetchCampaigns.d.ts +4 -11
- package/dist/types/votemarket/fetchCampaigns.d.ts.map +1 -1
- package/dist/types/votemarket/fetchClaimableData.d.ts +6 -0
- package/dist/types/votemarket/fetchClaimableData.d.ts.map +1 -0
- package/dist/types/votemarket/fetchEpochVotes.d.ts +9 -0
- package/dist/types/votemarket/fetchEpochVotes.d.ts.map +1 -0
- package/dist/types/votemarket/generateProofs/config.d.ts +32 -0
- package/dist/types/votemarket/generateProofs/config.d.ts.map +1 -0
- package/dist/types/votemarket/generateProofs/getBlockProof.d.ts +6 -0
- package/dist/types/votemarket/generateProofs/getBlockProof.d.ts.map +1 -0
- package/dist/types/votemarket/generateProofs/getGaugeProof.d.ts +6 -0
- package/dist/types/votemarket/generateProofs/getGaugeProof.d.ts.map +1 -0
- package/dist/types/votemarket/generateProofs/getUserProof.d.ts +6 -0
- package/dist/types/votemarket/generateProofs/getUserProof.d.ts.map +1 -0
- package/dist/types/votemarket/types.d.ts +63 -0
- package/dist/types/votemarket/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/bytecodes/gaugeManager/curveGaugeRewardsData.ts +39 -0
- package/src/bytecodes/strategies/batches/batchPancakeSwapPositions.ts +2 -2
- package/src/bytecodes/votemarket/batchCampaigns.ts +11 -1
- package/src/bytecodes/votemarket/batchClaimableData.ts +24 -0
- package/src/bytecodes/votemarket/batchGaugesWeight.ts +15 -0
- package/src/bytecodes/votemarket/batchVotes.ts +15 -0
- package/src/bytecodes/votemarket/curve/batchCurveGaugesMetadata.ts +4 -3
- package/src/gaugeManager/curve/gmFetchGauge.ts +68 -0
- package/src/index.ts +23 -0
- package/src/strategies/pancakeswap/endpoints.ts +1 -1
- package/src/strategies/pancakeswap/fetch/getPancakeV3Positions.ts +33 -8
- package/src/utils.ts +67 -2
- package/src/votemarket/curve/config.ts +1 -1
- package/src/votemarket/curve/fetchCurveGauges.ts +3 -0
- package/src/votemarket/curve/fetchSnapshotUserData.ts +1 -0
- package/src/votemarket/fetchCampaigns.ts +149 -24
- package/src/votemarket/fetchClaimableData.ts +50 -0
- package/src/votemarket/fetchEpochVotes.ts +48 -0
- package/src/votemarket/generateProofs/config.ts +53 -0
- package/src/votemarket/generateProofs/getBlockProof.ts +48 -0
- package/src/votemarket/generateProofs/getGaugeProof.ts +75 -0
- package/src/votemarket/generateProofs/getUserProof.ts +74 -0
- package/src/votemarket/types.ts +67 -0
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { tokenWithAddress, tokens } from '@stake-dao/constants'
|
|
2
|
-
import { chunk, range, remove, uniq } from 'lodash-es'
|
|
3
|
-
import { decodeAbiParameters, encodeAbiParameters, formatUnits, parseAbiParameters } from 'viem'
|
|
1
|
+
import { ONE_WEEK, RPC, contracts, tokenWithAddress, tokens } from '@stake-dao/constants'
|
|
2
|
+
import { chunk, filter, range, remove, uniq } from 'lodash-es'
|
|
3
|
+
import { decodeAbiParameters, encodeAbiParameters, formatUnits, parseAbiParameters, zeroAddress } from 'viem'
|
|
4
|
+
import { mainnet } from 'viem/chains'
|
|
4
5
|
import { batchTokenData } from '../bytecodes/index.js'
|
|
5
6
|
import batchCampaigns from '../bytecodes/votemarket/batchCampaigns.js'
|
|
6
|
-
import
|
|
7
|
+
import batchGaugesWeight from '../bytecodes/votemarket/batchGaugesWeight.js'
|
|
8
|
+
import { getPrices } from '../prices.js'
|
|
9
|
+
import { Zero } from '../strategies/utils/index.js'
|
|
10
|
+
import { batchJsonRpc, concatBytecode, equalTlc, rpcCall, rpcGetLastBlockTimetstamp } from '../utils.js'
|
|
11
|
+
import type { Campaign, RawCampaign, RawPeriod } from './types.js'
|
|
7
12
|
|
|
8
13
|
export const CAMPAIGNS_CHUNK_SIZE = 10
|
|
14
|
+
export const CLAIM_WINDOW_LENGTH = 24 // weeks
|
|
15
|
+
export const CLOSE_WINDOW_LENGTH = 4 // weeks
|
|
9
16
|
|
|
10
17
|
interface FetchCampaignsProps {
|
|
11
18
|
platform: string
|
|
@@ -13,16 +20,68 @@ interface FetchCampaignsProps {
|
|
|
13
20
|
rpc: string
|
|
14
21
|
}
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
const updateEpoch = ({
|
|
24
|
+
period,
|
|
25
|
+
previousPeriod,
|
|
26
|
+
campaign,
|
|
27
|
+
totalVotes,
|
|
28
|
+
remainingPeriods,
|
|
29
|
+
}: {
|
|
30
|
+
period: RawPeriod
|
|
31
|
+
previousPeriod?: RawPeriod
|
|
32
|
+
campaign: RawCampaign
|
|
33
|
+
totalVotes: bigint
|
|
34
|
+
remainingPeriods: number
|
|
35
|
+
}) => {
|
|
36
|
+
// -- _checkForUpgrade is handled in solidity BatchCampaigns.sol
|
|
37
|
+
// -- If not first period, check if previousPeriod is updated
|
|
38
|
+
if (!period.updated && (previousPeriod?.updated || typeof previousPeriod === 'undefined')) {
|
|
39
|
+
// Calculate remaining periods and total reward.
|
|
40
|
+
const totalRewardForRemainingPeriods: bigint = campaign.totalRewardAmount - campaign.totalDistributed
|
|
41
|
+
|
|
42
|
+
// -- Update the period data.
|
|
43
|
+
const newRewardPerPeriod =
|
|
44
|
+
remainingPeriods > 0 ? totalRewardForRemainingPeriods / BigInt(remainingPeriods) : totalRewardForRemainingPeriods
|
|
45
|
+
period.rewardPerPeriod = newRewardPerPeriod
|
|
46
|
+
|
|
47
|
+
// -- Update the reward per vote
|
|
48
|
+
// If no votes, rollover the leftover to the next epoch.
|
|
49
|
+
if (totalVotes === Zero) {
|
|
50
|
+
period.leftover = period.rewardPerPeriod
|
|
51
|
+
} else {
|
|
52
|
+
// Calculate reward per vote
|
|
53
|
+
let rewardPerVote = (newRewardPerPeriod * BigInt(1e18)) / totalVotes
|
|
54
|
+
|
|
55
|
+
// Cap reward per vote at max reward per vote
|
|
56
|
+
if (rewardPerVote > campaign.maxRewardPerVote) {
|
|
57
|
+
rewardPerVote = campaign.maxRewardPerVote
|
|
58
|
+
|
|
59
|
+
// Calculate leftover rewards
|
|
60
|
+
const leftOver = newRewardPerPeriod - (rewardPerVote * totalVotes) / BigInt(1e18)
|
|
61
|
+
|
|
62
|
+
// Handle leftover rewards
|
|
63
|
+
if (campaign.hook === zeroAddress) {
|
|
64
|
+
// Store leftover in the period if there is no hook.
|
|
65
|
+
period.leftover = period.leftover + leftOver
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Save the calculated reward per vote.
|
|
70
|
+
period.rewardPerVote = rewardPerVote
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// -- Update the total distributed amount
|
|
74
|
+
campaign.totalDistributed = campaign.totalDistributed + newRewardPerPeriod - period.leftover
|
|
22
75
|
|
|
23
|
-
|
|
76
|
+
// -- Mark the period as updated
|
|
77
|
+
period.updated = true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return campaign
|
|
81
|
+
}
|
|
24
82
|
|
|
25
|
-
export const fetchCampaigns = async ({ platform, chainId, rpc }: FetchCampaignsProps): Promise<
|
|
83
|
+
export const fetchCampaigns = async ({ platform, chainId, rpc }: FetchCampaignsProps): Promise<Campaign[]> => {
|
|
84
|
+
const block = await rpcGetLastBlockTimetstamp(rpc)
|
|
26
85
|
const nCampaignsRequest = await rpcCall(rpc, [{ to: platform, data: '0x7274e30d' }]) // campaignCount
|
|
27
86
|
const nCampaigns = Number(
|
|
28
87
|
decodeAbiParameters([{ type: 'uint256', name: 'campaignCount' }], nCampaignsRequest[0].result),
|
|
@@ -47,10 +106,24 @@ export const fetchCampaigns = async ({ platform, chainId, rpc }: FetchCampaignsP
|
|
|
47
106
|
callsKey: `votemarket/fetchCampaigns.ts: rawCampaigns - chainId ${chainId} - rpc ${rpc}`,
|
|
48
107
|
})
|
|
49
108
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
109
|
+
const campaignsGauges: string[] = uniq(rawCampaigns.map((rc) => rc.campaign.gauge))
|
|
110
|
+
const rawGaugesWeights = await batchJsonRpc({
|
|
111
|
+
rpc: RPC[mainnet.id],
|
|
112
|
+
calls: [
|
|
113
|
+
concatBytecode(
|
|
114
|
+
batchGaugesWeight.bytecode[mainnet.id]!,
|
|
115
|
+
encodeAbiParameters(parseAbiParameters(batchGaugesWeight.inputType[mainnet.id]!) as any, [
|
|
116
|
+
contracts.curveGaugeController![1]!,
|
|
117
|
+
campaignsGauges,
|
|
118
|
+
]),
|
|
119
|
+
),
|
|
120
|
+
],
|
|
121
|
+
outputTypeAbi: batchGaugesWeight.outputTypeHr[mainnet.id]!,
|
|
122
|
+
callsKey: `votemarket/fetchCampaigns.ts: rawGaugesWeights - chainId ${mainnet.id} - rpc ${RPC[mainnet.id]}`,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const campaignsTokens: string[] = uniq(rawCampaigns.map((rc) => rc.campaign.rewardToken))
|
|
126
|
+
const tokensToFetch: string[] = remove(campaignsTokens, (t) => !tokenWithAddress(t, chainId))
|
|
54
127
|
|
|
55
128
|
let rawTokensData: any[] = []
|
|
56
129
|
if (tokensToFetch.length > 0) {
|
|
@@ -69,8 +142,8 @@ export const fetchCampaigns = async ({ platform, chainId, rpc }: FetchCampaignsP
|
|
|
69
142
|
})
|
|
70
143
|
}
|
|
71
144
|
|
|
72
|
-
const fetchedTokens = [
|
|
73
|
-
...tokens,
|
|
145
|
+
const fetchedTokens: any = [
|
|
146
|
+
...filter(tokens, (t) => campaignsTokens.find((ct) => equalTlc(ct, t.address))),
|
|
74
147
|
...rawTokensData.map((t) => ({
|
|
75
148
|
name: t.name,
|
|
76
149
|
symbol: t.symbol,
|
|
@@ -80,34 +153,86 @@ export const fetchCampaigns = async ({ platform, chainId, rpc }: FetchCampaignsP
|
|
|
80
153
|
})),
|
|
81
154
|
]
|
|
82
155
|
|
|
156
|
+
// const prices = await getPrices(fetchedTokens as any, chainId)
|
|
157
|
+
// TMP FOR TEST PURPOSE REMOVE ONCE IN PROD
|
|
158
|
+
const prices = [
|
|
159
|
+
...(await getPrices(fetchedTokens as any, chainId)),
|
|
160
|
+
{ address: '0x9E49A0314AE61e9C9E34e4Af62a73FBFfB6DE95A', symbol: 'ARR', usdPrice: 10 },
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
const currentTimestamp = block.timestamp
|
|
164
|
+
|
|
83
165
|
const campaigns = rawCampaigns.map((rc) => {
|
|
166
|
+
const gaugeWeightData = rawGaugesWeights.find((g) => equalTlc(g.gauge, rc.campaign.gauge))
|
|
84
167
|
const rewardToken = fetchedTokens.find((t) => equalTlc(t.address, rc.campaign.rewardToken) && t.chainId === chainId)
|
|
85
168
|
const decimals = rewardToken ? rewardToken.decimals : 18
|
|
86
169
|
|
|
170
|
+
const rewardTokenPrice = prices.find((p) => equalTlc(rc.campaign.rewardToken, p.address))?.usdPrice || 0
|
|
171
|
+
|
|
172
|
+
const startTimestamp = Number(rc.campaign.startTimestamp)
|
|
173
|
+
const endTimestamp = Number(rc.campaign.endTimestamp)
|
|
174
|
+
const numberOfPeriods = Number(rc.campaign.numberOfPeriods)
|
|
175
|
+
|
|
176
|
+
const totalVotes = gaugeWeightData.weight
|
|
177
|
+
|
|
178
|
+
// Simulate previous period update if not updated
|
|
179
|
+
if (rc.previousPeriod && !rc.previousPeriod.updated) {
|
|
180
|
+
updateEpoch({
|
|
181
|
+
period: rc.previousPeriod,
|
|
182
|
+
campaign: rc.campaign,
|
|
183
|
+
remainingPeriods: Number(rc.periodLeft) + 1,
|
|
184
|
+
totalVotes,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Simulate current period update to simulate reward and apr
|
|
189
|
+
updateEpoch({
|
|
190
|
+
period: rc.currentPeriod,
|
|
191
|
+
previousPeriod: rc.previousPeriod,
|
|
192
|
+
campaign: rc.campaign,
|
|
193
|
+
remainingPeriods: Number(rc.periodLeft),
|
|
194
|
+
totalVotes,
|
|
195
|
+
})
|
|
196
|
+
|
|
87
197
|
return {
|
|
88
198
|
id: Number(rc.id),
|
|
89
|
-
chainId
|
|
199
|
+
chainId,
|
|
90
200
|
gauge: rc.campaign.gauge,
|
|
201
|
+
gaugeChainId: Number(rc.campaign.chainId),
|
|
91
202
|
manager: rc.campaign.manager,
|
|
92
203
|
rewardToken,
|
|
93
|
-
|
|
204
|
+
rewardTokenPrice,
|
|
205
|
+
numberOfPeriods,
|
|
206
|
+
periodLeft: Number(rc.periodLeft),
|
|
94
207
|
maxRewardPerVote: formatUnits(rc.campaign.maxRewardPerVote, decimals),
|
|
95
208
|
totalRewardAmount: formatUnits(rc.campaign.totalRewardAmount, decimals),
|
|
96
209
|
totalDistributed: formatUnits(rc.campaign.totalDistributed, decimals),
|
|
97
|
-
startTimestamp
|
|
98
|
-
endTimestamp
|
|
210
|
+
startTimestamp,
|
|
211
|
+
endTimestamp,
|
|
99
212
|
hook: rc.campaign.hook,
|
|
100
|
-
|
|
213
|
+
isClosed: rc.isClosed,
|
|
101
214
|
addresses: rc.addresses,
|
|
102
215
|
isWhitelist: rc.isWhitelistOnly,
|
|
103
216
|
isBlacklist: !rc.isWhitelistOnly && rc.addresses.length > 0,
|
|
217
|
+
previousPeriod: {
|
|
218
|
+
rewardPerPeriod: formatUnits(rc.previousPeriod.rewardPerPeriod, decimals),
|
|
219
|
+
rewardPerVote: formatUnits(rc.previousPeriod.rewardPerVote, decimals),
|
|
220
|
+
leftover: formatUnits(rc.previousPeriod.leftover, decimals),
|
|
221
|
+
updated: rc.previousPeriod.updated,
|
|
222
|
+
},
|
|
104
223
|
currentPeriod: {
|
|
105
224
|
rewardPerPeriod: formatUnits(rc.currentPeriod.rewardPerPeriod, decimals),
|
|
106
225
|
rewardPerVote: formatUnits(rc.currentPeriod.rewardPerVote, decimals),
|
|
107
226
|
leftover: formatUnits(rc.currentPeriod.leftover, decimals),
|
|
108
227
|
updated: rc.currentPeriod.updated,
|
|
109
228
|
},
|
|
110
|
-
|
|
229
|
+
status: {
|
|
230
|
+
voteOpen: endTimestamp - ONE_WEEK > currentTimestamp,
|
|
231
|
+
voteClosed: currentTimestamp > endTimestamp - ONE_WEEK,
|
|
232
|
+
claimOpen: currentTimestamp > startTimestamp,
|
|
233
|
+
claimClosed: currentTimestamp > endTimestamp + CLAIM_WINDOW_LENGTH * ONE_WEEK,
|
|
234
|
+
expired: currentTimestamp > endTimestamp + (CLAIM_WINDOW_LENGTH + CLOSE_WINDOW_LENGTH) * ONE_WEEK,
|
|
235
|
+
},
|
|
111
236
|
}
|
|
112
237
|
})
|
|
113
238
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { arbitrum } from '@stake-dao/constants'
|
|
2
|
+
import { chunk } from 'lodash-es'
|
|
3
|
+
import { encodeAbiParameters, formatUnits, parseAbiParameters } from 'viem'
|
|
4
|
+
import batchClaimableData from '../bytecodes/votemarket/batchClaimableData.js'
|
|
5
|
+
import { concatBytecode } from '../index.js'
|
|
6
|
+
import { batchJsonRpc } from '../utils.js'
|
|
7
|
+
|
|
8
|
+
type Rpcs = {
|
|
9
|
+
[chainId: number]: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const CLAIMABLE_CHUNK_SIZE = 10
|
|
13
|
+
|
|
14
|
+
export const fetchClaimableData = async (
|
|
15
|
+
rpc: Rpcs,
|
|
16
|
+
platform: string,
|
|
17
|
+
user: string,
|
|
18
|
+
campaigns: number[],
|
|
19
|
+
userVotes: any[][],
|
|
20
|
+
) => {
|
|
21
|
+
const inputCampaignsChunks = chunk(campaigns, CLAIMABLE_CHUNK_SIZE)
|
|
22
|
+
const inputVotesChunks = chunk(userVotes, CLAIMABLE_CHUNK_SIZE)
|
|
23
|
+
|
|
24
|
+
const claimableCalls = inputCampaignsChunks.map((campaignsArgs, index) => {
|
|
25
|
+
const inputData = encodeAbiParameters(parseAbiParameters(batchClaimableData.inputType[arbitrum.id]!) as any, [
|
|
26
|
+
platform,
|
|
27
|
+
user,
|
|
28
|
+
campaignsArgs,
|
|
29
|
+
inputVotesChunks[index],
|
|
30
|
+
])
|
|
31
|
+
return concatBytecode(batchClaimableData.bytecode[arbitrum.id]!, inputData)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const claimable = await batchJsonRpc({
|
|
35
|
+
rpc: rpc[arbitrum.id]!,
|
|
36
|
+
calls: claimableCalls,
|
|
37
|
+
outputTypeAbi: batchClaimableData.outputTypeHr[arbitrum.id],
|
|
38
|
+
callsKey: `Batch claimable data - chainId ${arbitrum.id} - rpc ${rpc[arbitrum.id]}`,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
return claimable.map((c) => ({
|
|
42
|
+
campaignId: Number(c.campaignId),
|
|
43
|
+
claimInfo: c.claimInfo.map((ci) => ({
|
|
44
|
+
...ci,
|
|
45
|
+
epoch: Number(ci.epoch),
|
|
46
|
+
blockNumber: Number(ci.blockNumber),
|
|
47
|
+
claimable: formatUnits(ci.claimable, 0),
|
|
48
|
+
})),
|
|
49
|
+
}))
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { chunk } from 'lodash-es'
|
|
2
|
+
import { encodeAbiParameters, parseAbiParameters } from 'viem'
|
|
3
|
+
import { mainnet } from 'viem/chains'
|
|
4
|
+
import batchVotes from '../bytecodes/votemarket/batchVotes.js'
|
|
5
|
+
import { concatBytecode } from '../index.js'
|
|
6
|
+
import { batchJsonRpcWithBlocks } from '../utils.js'
|
|
7
|
+
|
|
8
|
+
type Rpcs = {
|
|
9
|
+
[chainId: number]: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const fetchEpochsVotes = async (rpc: Rpcs, gaugeController: string, user: string, epochs: number[]) => {
|
|
13
|
+
const blocksForEpoch = (
|
|
14
|
+
await Promise.allSettled(
|
|
15
|
+
epochs.map((epoch) => fetch(`https://coins.llama.fi/block/ethereum/${epoch}`).then((res) => res.json())),
|
|
16
|
+
)
|
|
17
|
+
).map((r) => (r.status === 'fulfilled' ? Number(r.value.height) : 0))
|
|
18
|
+
|
|
19
|
+
if (blocksForEpoch.includes(0)) {
|
|
20
|
+
throw Error(`Block not found for epoch at timestamp ${epochs[blocksForEpoch.indexOf(0)]}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const inputData = encodeAbiParameters(parseAbiParameters(batchVotes.inputType[mainnet.id]!) as any, [
|
|
24
|
+
gaugeController,
|
|
25
|
+
user,
|
|
26
|
+
])
|
|
27
|
+
const call = concatBytecode(batchVotes.bytecode[mainnet.id]!, inputData)
|
|
28
|
+
|
|
29
|
+
const blocksChunks = chunk(blocksForEpoch, 10)
|
|
30
|
+
const votes = (
|
|
31
|
+
await Promise.all(
|
|
32
|
+
blocksChunks.map((b) =>
|
|
33
|
+
batchJsonRpcWithBlocks({
|
|
34
|
+
rpc: rpc[1]!,
|
|
35
|
+
call,
|
|
36
|
+
outputTypeAbi: batchVotes.outputTypeHr[mainnet.id],
|
|
37
|
+
callsKey: `Batch block votes data - chainId ${mainnet.id} - rpc ${rpc[mainnet.id]}`,
|
|
38
|
+
blocks: b,
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
).flat()
|
|
43
|
+
|
|
44
|
+
return votes.map((v, index) => ({
|
|
45
|
+
votes: v,
|
|
46
|
+
epoch: epochs[index],
|
|
47
|
+
}))
|
|
48
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const GaugeControllerConstants = {
|
|
2
|
+
GAUGES_SLOTS: {
|
|
3
|
+
curve: {
|
|
4
|
+
pointWeights: 12,
|
|
5
|
+
lastUserVote: 11,
|
|
6
|
+
voteUserSlope: 9,
|
|
7
|
+
},
|
|
8
|
+
balancer: {
|
|
9
|
+
pointWeights: 1000000008,
|
|
10
|
+
lastUserVote: 1000000007,
|
|
11
|
+
voteUserSlope: 1000000005,
|
|
12
|
+
},
|
|
13
|
+
frax: {
|
|
14
|
+
pointWeights: 10000000011,
|
|
15
|
+
lastUserVote: 1000000010,
|
|
16
|
+
voteUserSlope: 1000000008,
|
|
17
|
+
},
|
|
18
|
+
fxn: {
|
|
19
|
+
pointWeights: 10000000011,
|
|
20
|
+
lastUserVote: 1000000010,
|
|
21
|
+
voteUserSlope: 1000000008,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
GAUGE_CONTROLLER: {
|
|
25
|
+
curve: '0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB',
|
|
26
|
+
balancer: '0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD',
|
|
27
|
+
frax: '0x3669C421b77340B2979d1A00a792CC2ee0FcE737',
|
|
28
|
+
fxn: '0xe60eB8098B34eD775ac44B1ddE864e098C6d7f37',
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const BLOCK_HEADER = [
|
|
33
|
+
'parentHash',
|
|
34
|
+
'sha3Uncles',
|
|
35
|
+
'miner',
|
|
36
|
+
'stateRoot',
|
|
37
|
+
'transactionsRoot',
|
|
38
|
+
'receiptsRoot',
|
|
39
|
+
'logsBloom',
|
|
40
|
+
'difficulty',
|
|
41
|
+
'number',
|
|
42
|
+
'gasLimit',
|
|
43
|
+
'gasUsed',
|
|
44
|
+
'timestamp',
|
|
45
|
+
'extraData',
|
|
46
|
+
'mixHash',
|
|
47
|
+
'nonce',
|
|
48
|
+
'baseFeePerGas',
|
|
49
|
+
'withdrawalsRoot',
|
|
50
|
+
'blobGasUsed',
|
|
51
|
+
'excessBlobGas',
|
|
52
|
+
'parentBeaconBlockRoot',
|
|
53
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { http, createPublicClient, toHex, toRlp } from 'viem'
|
|
2
|
+
import { mainnet } from 'viem/chains'
|
|
3
|
+
import { BLOCK_HEADER } from './config.js'
|
|
4
|
+
import { generateGaugeProof } from './getGaugeProof.js'
|
|
5
|
+
|
|
6
|
+
type Rpcs = {
|
|
7
|
+
[chainId: number]: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function encodeRlpBlock(block: Record<string, any>): string {
|
|
11
|
+
const blockHeader = BLOCK_HEADER.map((key) => {
|
|
12
|
+
if (key in block) {
|
|
13
|
+
if (typeof block[key] === 'bigint' && block[key] === BigInt(0)) {
|
|
14
|
+
return '0x'
|
|
15
|
+
}
|
|
16
|
+
if (typeof block[key] === 'string') {
|
|
17
|
+
return block[key]
|
|
18
|
+
}
|
|
19
|
+
return toHex(block[key])
|
|
20
|
+
}
|
|
21
|
+
return undefined
|
|
22
|
+
}).filter((value): value is `0x${string}` => value !== undefined)
|
|
23
|
+
|
|
24
|
+
return toRlp(blockHeader)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Main function to generate block proof
|
|
28
|
+
export async function generateBlockProof(
|
|
29
|
+
rpc: Rpcs,
|
|
30
|
+
protocol: string,
|
|
31
|
+
gaugeAddress: `0x${string}`,
|
|
32
|
+
currentPeriod: bigint,
|
|
33
|
+
blockNumber: bigint,
|
|
34
|
+
): Promise<[string, string]> {
|
|
35
|
+
const publicClient = createPublicClient({
|
|
36
|
+
chain: mainnet,
|
|
37
|
+
transport: http(rpc[mainnet.id]),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const block = await publicClient.getBlock({
|
|
41
|
+
blockNumber,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const blockHeaderRlp = encodeRlpBlock(block)
|
|
45
|
+
|
|
46
|
+
const [proof, _] = await generateGaugeProof(rpc, protocol, gaugeAddress, currentPeriod, blockNumber)
|
|
47
|
+
return [blockHeaderRlp, proof]
|
|
48
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { http, createPublicClient, encodeAbiParameters, fromRlp, keccak256, toHex, toRlp } from 'viem'
|
|
2
|
+
import { mainnet } from 'viem/chains'
|
|
3
|
+
import { GaugeControllerConstants } from './config.js'
|
|
4
|
+
|
|
5
|
+
type Rpcs = {
|
|
6
|
+
[chainId: number]: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Helper function to encode gauge time
|
|
10
|
+
function _encodeGaugeTime(gauge: `0x${string}`, time: bigint, baseSlot: number): `0x${string}` {
|
|
11
|
+
const gaugeEncoded = keccak256(
|
|
12
|
+
encodeAbiParameters([{ type: 'uint256' }, { type: 'address' }], [BigInt(baseSlot), gauge]),
|
|
13
|
+
)
|
|
14
|
+
return keccak256(encodeAbiParameters([{ type: 'bytes32' }, { type: 'uint256' }], [gaugeEncoded, time]))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Function to get storage slot for non-Curve protocols
|
|
18
|
+
function getGaugeTimeStorageSlot(gauge: `0x${string}`, time: bigint, baseSlot: number): bigint {
|
|
19
|
+
const finalSlot = _encodeGaugeTime(gauge, time, baseSlot)
|
|
20
|
+
return BigInt(finalSlot)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Function to get storage slot for Curve protocol (pre-Vyper 0.3)
|
|
24
|
+
function getGaugeTimeStorageSlotPreVyper03(gauge: `0x${string}`, time: bigint, baseSlot: number): bigint {
|
|
25
|
+
const intermediateHash = _encodeGaugeTime(gauge, time, baseSlot)
|
|
26
|
+
const finalSlot = keccak256(encodeAbiParameters([{ type: 'bytes32' }], [intermediateHash]))
|
|
27
|
+
return BigInt(finalSlot)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Main function to generate gauge proof
|
|
31
|
+
export async function generateGaugeProof(
|
|
32
|
+
rpc: Rpcs,
|
|
33
|
+
protocol: string,
|
|
34
|
+
gaugeAddress: `0x${string}`,
|
|
35
|
+
currentPeriod: bigint,
|
|
36
|
+
blockNumber: bigint,
|
|
37
|
+
): Promise<[string, string]> {
|
|
38
|
+
const publicClient = createPublicClient({
|
|
39
|
+
chain: mainnet,
|
|
40
|
+
transport: http(rpc[mainnet.id]),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const pointWeightsBaseSlot = GaugeControllerConstants.GAUGES_SLOTS[protocol].pointWeights
|
|
44
|
+
|
|
45
|
+
const positionFunctions: Record<string, any> = {
|
|
46
|
+
curve: getGaugeTimeStorageSlotPreVyper03,
|
|
47
|
+
default: getGaugeTimeStorageSlot,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const getPosition = positionFunctions[protocol] || positionFunctions.default
|
|
51
|
+
const pointWeightsPosition = getPosition!(gaugeAddress, currentPeriod, pointWeightsBaseSlot)
|
|
52
|
+
|
|
53
|
+
const slots = [toHex(pointWeightsPosition)]
|
|
54
|
+
|
|
55
|
+
const proof = await publicClient.getProof({
|
|
56
|
+
address: GaugeControllerConstants.GAUGE_CONTROLLER[protocol] as `0x${string}`,
|
|
57
|
+
storageKeys: slots,
|
|
58
|
+
blockNumber,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const decodedAccountProof = proof.accountProof.map((p) => fromRlp(p))
|
|
62
|
+
const accountProof = toRlp(decodedAccountProof)
|
|
63
|
+
|
|
64
|
+
const decodedStorageProof = proof.storageProof[0]!.proof.map((p) => fromRlp(p))
|
|
65
|
+
let storageProof = toRlp(decodedStorageProof)
|
|
66
|
+
|
|
67
|
+
// Add length prefix to match Python output for storage proof
|
|
68
|
+
const storageProofLength = (storageProof.length - 2) / 2 // subtract 2 for '0x' prefix
|
|
69
|
+
const storageProofLengthHex = storageProofLength.toString(16).padStart(4, '0')
|
|
70
|
+
|
|
71
|
+
// 0xf9 is the RLP prefix for a list longer than 55 bytes, followed by the length of the length (2 bytes), and then the length itself
|
|
72
|
+
storageProof = `0xf9${storageProofLengthHex}${storageProof.slice(2)}`
|
|
73
|
+
|
|
74
|
+
return [accountProof, storageProof]
|
|
75
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { http, createPublicClient, encodeAbiParameters, fromRlp, keccak256, toHex, toRlp } from 'viem'
|
|
2
|
+
import { mainnet } from 'viem/chains'
|
|
3
|
+
import { GaugeControllerConstants } from './config.js'
|
|
4
|
+
|
|
5
|
+
type Rpcs = {
|
|
6
|
+
[chainId: number]: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Helper function to encode user gauge data
|
|
10
|
+
function _encodeUserGaugeData(user: `0x${string}`, gauge: `0x${string}`, baseSlot: number): `0x${string}` {
|
|
11
|
+
const userEncoded = keccak256(
|
|
12
|
+
encodeAbiParameters([{ type: 'uint256' }, { type: 'address' }], [BigInt(baseSlot), user]),
|
|
13
|
+
)
|
|
14
|
+
return keccak256(encodeAbiParameters([{ type: 'bytes32' }, { type: 'address' }], [userEncoded, gauge]))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Function to get storage slot for non-Curve protocols
|
|
18
|
+
function getUserGaugeStorageSlot(user: `0x${string}`, gauge: `0x${string}`, baseSlot: number): bigint {
|
|
19
|
+
const finalSlot = _encodeUserGaugeData(user, gauge, baseSlot)
|
|
20
|
+
return BigInt(finalSlot)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Function to get storage slot for Curve protocol (pre-Vyper 0.3)
|
|
24
|
+
function getUserGaugeStorageSlotPreVyper03(user: `0x${string}`, gauge: `0x${string}`, baseSlot: number): bigint {
|
|
25
|
+
const intermediateHash = _encodeUserGaugeData(user, gauge, baseSlot)
|
|
26
|
+
const finalSlot = keccak256(encodeAbiParameters([{ type: 'bytes32' }], [intermediateHash]))
|
|
27
|
+
return BigInt(finalSlot)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Main function to generate user proof
|
|
31
|
+
export async function generateUserProof(
|
|
32
|
+
rpc: Rpcs,
|
|
33
|
+
protocol: string,
|
|
34
|
+
gaugeAddress: `0x${string}`,
|
|
35
|
+
user: `0x${string}`,
|
|
36
|
+
blockNumber: bigint,
|
|
37
|
+
): Promise<[string, string]> {
|
|
38
|
+
const publicClient = createPublicClient({
|
|
39
|
+
chain: mainnet,
|
|
40
|
+
transport: http(rpc[mainnet.id]),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const lastUserVoteBaseSlot = GaugeControllerConstants.GAUGES_SLOTS[protocol].lastUserVote
|
|
44
|
+
const voteUserSlopeBaseSlot = GaugeControllerConstants.GAUGES_SLOTS[protocol].voteUserSlope
|
|
45
|
+
|
|
46
|
+
const lastUserVoteSlot = getUserGaugeStorageSlot(user, gaugeAddress, lastUserVoteBaseSlot)
|
|
47
|
+
|
|
48
|
+
const positionFunctions: Record<string, any> = {
|
|
49
|
+
curve: getUserGaugeStorageSlotPreVyper03,
|
|
50
|
+
default: getUserGaugeStorageSlot,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const getPosition = positionFunctions[protocol] || positionFunctions.default
|
|
54
|
+
const voteUserSlopeSlot = getPosition(user, gaugeAddress, voteUserSlopeBaseSlot)
|
|
55
|
+
|
|
56
|
+
const voteUserSlopeSlope = voteUserSlopeSlot
|
|
57
|
+
const voteUserSlopeEnd = voteUserSlopeSlot + 2n
|
|
58
|
+
|
|
59
|
+
const slots = [toHex(lastUserVoteSlot), toHex(voteUserSlopeSlope), toHex(voteUserSlopeEnd)]
|
|
60
|
+
|
|
61
|
+
const proof = await publicClient.getProof({
|
|
62
|
+
address: GaugeControllerConstants.GAUGE_CONTROLLER[protocol] as `0x${string}`,
|
|
63
|
+
storageKeys: slots,
|
|
64
|
+
blockNumber,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const decodedAccountProof = proof.accountProof.map((p) => fromRlp(p))
|
|
68
|
+
const accountProof = toRlp(decodedAccountProof)
|
|
69
|
+
|
|
70
|
+
const decodedStorageProof = proof.storageProof.map((sp) => sp.proof.map((p) => fromRlp(p)))
|
|
71
|
+
const storageProof = toRlp(decodedStorageProof)
|
|
72
|
+
|
|
73
|
+
return [accountProof, storageProof]
|
|
74
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type Period = {
|
|
2
|
+
rewardPerPeriod: string
|
|
3
|
+
rewardPerVote: string
|
|
4
|
+
leftover: string
|
|
5
|
+
updated: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type Campaign = {
|
|
9
|
+
id: number
|
|
10
|
+
chainId: number
|
|
11
|
+
gauge: string
|
|
12
|
+
gaugeChainId: number
|
|
13
|
+
manager: string
|
|
14
|
+
rewardToken: {
|
|
15
|
+
name: string
|
|
16
|
+
symbol: string
|
|
17
|
+
address: string
|
|
18
|
+
decimals: number
|
|
19
|
+
chainId: number
|
|
20
|
+
}
|
|
21
|
+
rewardTokenPrice: number
|
|
22
|
+
numberOfPeriods: number
|
|
23
|
+
maxRewardPerVote: string
|
|
24
|
+
totalRewardAmount: string
|
|
25
|
+
totalDistributed: string
|
|
26
|
+
startTimestamp: number
|
|
27
|
+
endTimestamp: number
|
|
28
|
+
hook: string
|
|
29
|
+
isClosed: boolean
|
|
30
|
+
addresses: string[]
|
|
31
|
+
isWhitelist: boolean
|
|
32
|
+
isBlacklist: boolean
|
|
33
|
+
previousPeriod: Period
|
|
34
|
+
currentPeriod: Period
|
|
35
|
+
periodLeft: number
|
|
36
|
+
status: {
|
|
37
|
+
voteOpen: boolean
|
|
38
|
+
voteClosed: boolean
|
|
39
|
+
claimOpen: boolean
|
|
40
|
+
claimClosed: boolean
|
|
41
|
+
expired: boolean
|
|
42
|
+
}
|
|
43
|
+
claimableAmount?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// RAW
|
|
47
|
+
|
|
48
|
+
export type RawPeriod = {
|
|
49
|
+
rewardPerPeriod: bigint
|
|
50
|
+
rewardPerVote: bigint
|
|
51
|
+
leftover: bigint
|
|
52
|
+
updated: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type RawCampaign = {
|
|
56
|
+
chainId: bigint
|
|
57
|
+
endTimestamp: bigint
|
|
58
|
+
gauge: string
|
|
59
|
+
hook: string
|
|
60
|
+
manager: string
|
|
61
|
+
maxRewardPerVote: bigint
|
|
62
|
+
numberOfPeriods: number
|
|
63
|
+
rewardToken: bigint
|
|
64
|
+
startTimestamp: bigint
|
|
65
|
+
totalDistributed: bigint
|
|
66
|
+
totalRewardAmount: bigint
|
|
67
|
+
}
|