@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.
Files changed (104) hide show
  1. package/dist/esm/bytecodes/gaugeManager/curveGaugeRewardsData.js +36 -0
  2. package/dist/esm/bytecodes/gaugeManager/curveGaugeRewardsData.js.map +1 -0
  3. package/dist/esm/bytecodes/strategies/batches/batchPancakeSwapPositions.js +2 -2
  4. package/dist/esm/bytecodes/strategies/batches/batchPancakeSwapPositions.js.map +1 -1
  5. package/dist/esm/bytecodes/votemarket/batchCampaigns.js +11 -1
  6. package/dist/esm/bytecodes/votemarket/batchCampaigns.js.map +1 -1
  7. package/dist/esm/bytecodes/votemarket/batchClaimableData.js +21 -0
  8. package/dist/esm/bytecodes/votemarket/batchClaimableData.js.map +1 -0
  9. package/dist/esm/bytecodes/votemarket/batchGaugesWeight.js +13 -0
  10. package/dist/esm/bytecodes/votemarket/batchGaugesWeight.js.map +1 -0
  11. package/dist/esm/bytecodes/votemarket/batchVotes.js +13 -0
  12. package/dist/esm/bytecodes/votemarket/batchVotes.js.map +1 -0
  13. package/dist/esm/bytecodes/votemarket/curve/batchCurveGaugesMetadata.js +4 -3
  14. package/dist/esm/bytecodes/votemarket/curve/batchCurveGaugesMetadata.js.map +1 -1
  15. package/dist/esm/gaugeManager/curve/gmFetchGauge.js +34 -0
  16. package/dist/esm/gaugeManager/curve/gmFetchGauge.js.map +1 -0
  17. package/dist/esm/index.js +14 -0
  18. package/dist/esm/index.js.map +1 -1
  19. package/dist/esm/strategies/pancakeswap/endpoints.js +1 -1
  20. package/dist/esm/strategies/pancakeswap/fetch/getPancakeV3Positions.js +24 -5
  21. package/dist/esm/strategies/pancakeswap/fetch/getPancakeV3Positions.js.map +1 -1
  22. package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
  23. package/dist/esm/utils.js +43 -1
  24. package/dist/esm/utils.js.map +1 -1
  25. package/dist/esm/votemarket/curve/config.js +1 -1
  26. package/dist/esm/votemarket/curve/fetchCurveGauges.js +2 -0
  27. package/dist/esm/votemarket/curve/fetchCurveGauges.js.map +1 -1
  28. package/dist/esm/votemarket/curve/fetchSnapshotUserData.js.map +1 -1
  29. package/dist/esm/votemarket/fetchCampaigns.js +115 -12
  30. package/dist/esm/votemarket/fetchCampaigns.js.map +1 -1
  31. package/dist/esm/votemarket/fetchClaimableData.js +36 -0
  32. package/dist/esm/votemarket/fetchClaimableData.js.map +1 -0
  33. package/dist/esm/votemarket/fetchEpochVotes.js +30 -0
  34. package/dist/esm/votemarket/fetchEpochVotes.js.map +1 -0
  35. package/dist/esm/votemarket/generateProofs/config.js +53 -0
  36. package/dist/esm/votemarket/generateProofs/config.js.map +1 -0
  37. package/dist/esm/votemarket/generateProofs/getBlockProof.js +33 -0
  38. package/dist/esm/votemarket/generateProofs/getBlockProof.js.map +1 -0
  39. package/dist/esm/votemarket/generateProofs/getGaugeProof.js +50 -0
  40. package/dist/esm/votemarket/generateProofs/getGaugeProof.js.map +1 -0
  41. package/dist/esm/votemarket/generateProofs/getUserProof.js +49 -0
  42. package/dist/esm/votemarket/generateProofs/getUserProof.js.map +1 -0
  43. package/dist/esm/votemarket/types.js +2 -0
  44. package/dist/esm/votemarket/types.js.map +1 -0
  45. package/dist/types/bytecodes/gaugeManager/curveGaugeRewardsData.d.ts +4 -0
  46. package/dist/types/bytecodes/gaugeManager/curveGaugeRewardsData.d.ts.map +1 -0
  47. package/dist/types/bytecodes/votemarket/batchCampaigns.d.ts.map +1 -1
  48. package/dist/types/bytecodes/votemarket/batchClaimableData.d.ts +4 -0
  49. package/dist/types/bytecodes/votemarket/batchClaimableData.d.ts.map +1 -0
  50. package/dist/types/bytecodes/votemarket/batchGaugesWeight.d.ts +4 -0
  51. package/dist/types/bytecodes/votemarket/batchGaugesWeight.d.ts.map +1 -0
  52. package/dist/types/bytecodes/votemarket/batchVotes.d.ts +4 -0
  53. package/dist/types/bytecodes/votemarket/batchVotes.d.ts.map +1 -0
  54. package/dist/types/bytecodes/votemarket/curve/batchCurveGaugesMetadata.d.ts.map +1 -1
  55. package/dist/types/gaugeManager/curve/gmFetchGauge.d.ts +25 -0
  56. package/dist/types/gaugeManager/curve/gmFetchGauge.d.ts.map +1 -0
  57. package/dist/types/index.d.ts +10 -0
  58. package/dist/types/index.d.ts.map +1 -1
  59. package/dist/types/strategies/pancakeswap/fetch/getPancakeV3Positions.d.ts.map +1 -1
  60. package/dist/types/utils.d.ts +14 -1
  61. package/dist/types/utils.d.ts.map +1 -1
  62. package/dist/types/votemarket/curve/config.d.ts +1 -1
  63. package/dist/types/votemarket/curve/fetchCurveGauges.d.ts.map +1 -1
  64. package/dist/types/votemarket/curve/fetchSnapshotUserData.d.ts.map +1 -1
  65. package/dist/types/votemarket/fetchCampaigns.d.ts +4 -11
  66. package/dist/types/votemarket/fetchCampaigns.d.ts.map +1 -1
  67. package/dist/types/votemarket/fetchClaimableData.d.ts +6 -0
  68. package/dist/types/votemarket/fetchClaimableData.d.ts.map +1 -0
  69. package/dist/types/votemarket/fetchEpochVotes.d.ts +9 -0
  70. package/dist/types/votemarket/fetchEpochVotes.d.ts.map +1 -0
  71. package/dist/types/votemarket/generateProofs/config.d.ts +32 -0
  72. package/dist/types/votemarket/generateProofs/config.d.ts.map +1 -0
  73. package/dist/types/votemarket/generateProofs/getBlockProof.d.ts +6 -0
  74. package/dist/types/votemarket/generateProofs/getBlockProof.d.ts.map +1 -0
  75. package/dist/types/votemarket/generateProofs/getGaugeProof.d.ts +6 -0
  76. package/dist/types/votemarket/generateProofs/getGaugeProof.d.ts.map +1 -0
  77. package/dist/types/votemarket/generateProofs/getUserProof.d.ts +6 -0
  78. package/dist/types/votemarket/generateProofs/getUserProof.d.ts.map +1 -0
  79. package/dist/types/votemarket/types.d.ts +63 -0
  80. package/dist/types/votemarket/types.d.ts.map +1 -0
  81. package/package.json +1 -1
  82. package/src/bytecodes/gaugeManager/curveGaugeRewardsData.ts +39 -0
  83. package/src/bytecodes/strategies/batches/batchPancakeSwapPositions.ts +2 -2
  84. package/src/bytecodes/votemarket/batchCampaigns.ts +11 -1
  85. package/src/bytecodes/votemarket/batchClaimableData.ts +24 -0
  86. package/src/bytecodes/votemarket/batchGaugesWeight.ts +15 -0
  87. package/src/bytecodes/votemarket/batchVotes.ts +15 -0
  88. package/src/bytecodes/votemarket/curve/batchCurveGaugesMetadata.ts +4 -3
  89. package/src/gaugeManager/curve/gmFetchGauge.ts +68 -0
  90. package/src/index.ts +23 -0
  91. package/src/strategies/pancakeswap/endpoints.ts +1 -1
  92. package/src/strategies/pancakeswap/fetch/getPancakeV3Positions.ts +33 -8
  93. package/src/utils.ts +67 -2
  94. package/src/votemarket/curve/config.ts +1 -1
  95. package/src/votemarket/curve/fetchCurveGauges.ts +3 -0
  96. package/src/votemarket/curve/fetchSnapshotUserData.ts +1 -0
  97. package/src/votemarket/fetchCampaigns.ts +149 -24
  98. package/src/votemarket/fetchClaimableData.ts +50 -0
  99. package/src/votemarket/fetchEpochVotes.ts +48 -0
  100. package/src/votemarket/generateProofs/config.ts +53 -0
  101. package/src/votemarket/generateProofs/getBlockProof.ts +48 -0
  102. package/src/votemarket/generateProofs/getGaugeProof.ts +75 -0
  103. package/src/votemarket/generateProofs/getUserProof.ts +74 -0
  104. 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 { batchJsonRpc, concatBytecode, equalTlc, rpcCall } from '../utils.js'
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
- export type Gauge = {
17
- gauge: string
18
- inflationRate: string
19
- relativeWeight: string
20
- futureRelativeWeight: string
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
- export type GaugesData = { totalGaugesWeight: string; gauges: Gauge[] }
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<any> => {
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 tokensToFetch: string[] = remove(
51
- uniq(rawCampaigns.map((rc) => rc.campaign.rewardToken)),
52
- (t) => !tokenWithAddress(t, chainId),
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: Number(rc.campaign.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
- numberOfPeriods: Number(rc.campaign.numberOfPeriods),
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: Number(rc.campaign.startTimestamp),
98
- endTimestamp: Number(rc.campaign.endTimestamp),
210
+ startTimestamp,
211
+ endTimestamp,
99
212
  hook: rc.campaign.hook,
100
- isKilled: rc.isKilled,
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
- periodLeft: Number(rc.periodLeft),
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
+ }