@stake-dao/reader 0.4.57 → 0.4.59

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 (73) hide show
  1. package/dist/esm/bytecodes/votemarket/batchUserVotes.js +39 -0
  2. package/dist/esm/bytecodes/votemarket/batchUserVotes.js.map +1 -0
  3. package/dist/esm/endpoints.js +1 -0
  4. package/dist/esm/endpoints.js.map +1 -1
  5. package/dist/esm/index.js +7 -2
  6. package/dist/esm/index.js.map +1 -1
  7. package/dist/esm/lockers/fetch/index.js +1 -1
  8. package/dist/esm/lockers/fetch/index.js.map +1 -1
  9. package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
  10. package/dist/esm/votemarket/balancer/fetchBalancerUserVotes.js +42 -0
  11. package/dist/esm/votemarket/balancer/fetchBalancerUserVotes.js.map +1 -0
  12. package/dist/esm/votemarket/curve/fetchCurveUserVotes.js +8 -3
  13. package/dist/esm/votemarket/curve/fetchCurveUserVotes.js.map +1 -1
  14. package/dist/esm/votemarket/curve/fetchUserVlCvxClaimable.js +90 -40
  15. package/dist/esm/votemarket/curve/fetchUserVlCvxClaimable.js.map +1 -1
  16. package/dist/esm/votemarket/fetchCampaigns/index.js +65 -0
  17. package/dist/esm/votemarket/fetchCampaigns/index.js.map +1 -0
  18. package/dist/esm/votemarket/{fetchPendingRemoteCampaigns.js → fetchCampaigns/pendingRemote.js} +7 -2
  19. package/dist/esm/votemarket/fetchCampaigns/pendingRemote.js.map +1 -0
  20. package/dist/esm/votemarket/{fetchCampaigns.js → fetchCampaigns/processRawCampaigns.js} +16 -101
  21. package/dist/esm/votemarket/fetchCampaigns/processRawCampaigns.js.map +1 -0
  22. package/dist/esm/votemarket/fetchCampaigns/updateEpoch.js +41 -0
  23. package/dist/esm/votemarket/fetchCampaigns/updateEpoch.js.map +1 -0
  24. package/dist/esm/votemarket/fxn/fetchFxnUserVotes.js +42 -0
  25. package/dist/esm/votemarket/fxn/fetchFxnUserVotes.js.map +1 -0
  26. package/dist/types/bytecodes/votemarket/batchUserVotes.d.ts +4 -0
  27. package/dist/types/bytecodes/votemarket/batchUserVotes.d.ts.map +1 -0
  28. package/dist/types/endpoints.d.ts +1 -0
  29. package/dist/types/endpoints.d.ts.map +1 -1
  30. package/dist/types/index.d.ts +5 -2
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/lockers/fetch/index.d.ts +1 -0
  33. package/dist/types/lockers/fetch/index.d.ts.map +1 -1
  34. package/dist/types/votemarket/balancer/fetchBalancerUserVotes.d.ts +16 -0
  35. package/dist/types/votemarket/balancer/fetchBalancerUserVotes.d.ts.map +1 -0
  36. package/dist/types/votemarket/curve/fetchCurveUserVotes.d.ts.map +1 -1
  37. package/dist/types/votemarket/curve/fetchUserVlCvxClaimable.d.ts +17 -1
  38. package/dist/types/votemarket/curve/fetchUserVlCvxClaimable.d.ts.map +1 -1
  39. package/dist/types/votemarket/{fetchCampaigns.d.ts → fetchCampaigns/index.d.ts} +2 -2
  40. package/dist/types/votemarket/fetchCampaigns/index.d.ts.map +1 -0
  41. package/dist/types/votemarket/{fetchPendingRemoteCampaigns.d.ts → fetchCampaigns/pendingRemote.d.ts} +4 -2
  42. package/dist/types/votemarket/fetchCampaigns/pendingRemote.d.ts.map +1 -0
  43. package/dist/types/votemarket/fetchCampaigns/processRawCampaigns.d.ts +16 -0
  44. package/dist/types/votemarket/fetchCampaigns/processRawCampaigns.d.ts.map +1 -0
  45. package/dist/types/votemarket/fetchCampaigns/updateEpoch.d.ts +9 -0
  46. package/dist/types/votemarket/fetchCampaigns/updateEpoch.d.ts.map +1 -0
  47. package/dist/types/votemarket/fxn/fetchFxnUserVotes.d.ts +16 -0
  48. package/dist/types/votemarket/fxn/fetchFxnUserVotes.d.ts.map +1 -0
  49. package/dist/types/votemarket/types.d.ts +3 -1
  50. package/dist/types/votemarket/types.d.ts.map +1 -1
  51. package/package.json +5 -4
  52. package/src/bytecodes/votemarket/batchUserVotes.ts +41 -0
  53. package/src/endpoints.ts +2 -0
  54. package/src/index.ts +9 -2
  55. package/src/lockers/fetch/index.ts +1 -1
  56. package/src/votemarket/balancer/fetchBalancerUserVotes.ts +61 -0
  57. package/src/votemarket/curve/fetchCurveUserVotes.ts +9 -4
  58. package/src/votemarket/curve/fetchUserVlCvxClaimable.ts +114 -44
  59. package/src/votemarket/fetchCampaigns/index.ts +98 -0
  60. package/src/votemarket/{fetchPendingRemoteCampaigns.ts → fetchCampaigns/pendingRemote.ts} +7 -0
  61. package/src/votemarket/{fetchCampaigns.ts → fetchCampaigns/processRawCampaigns.ts} +20 -150
  62. package/src/votemarket/fetchCampaigns/updateEpoch.ts +63 -0
  63. package/src/votemarket/fxn/fetchFxnUserVotes.ts +61 -0
  64. package/src/votemarket/types.ts +3 -1
  65. package/dist/esm/bytecodes/votemarket/curve/batchCurveUserVotes.js +0 -39
  66. package/dist/esm/bytecodes/votemarket/curve/batchCurveUserVotes.js.map +0 -1
  67. package/dist/esm/votemarket/fetchCampaigns.js.map +0 -1
  68. package/dist/esm/votemarket/fetchPendingRemoteCampaigns.js.map +0 -1
  69. package/dist/types/bytecodes/votemarket/curve/batchCurveUserVotes.d.ts +0 -4
  70. package/dist/types/bytecodes/votemarket/curve/batchCurveUserVotes.d.ts.map +0 -1
  71. package/dist/types/votemarket/fetchCampaigns.d.ts.map +0 -1
  72. package/dist/types/votemarket/fetchPendingRemoteCampaigns.d.ts.map +0 -1
  73. package/src/bytecodes/votemarket/curve/batchCurveUserVotes.ts +0 -41
@@ -1,7 +1,7 @@
1
1
  import { Zero, contract, tokenWithAddress } from '@stake-dao/constants'
2
2
  import { type Address, formatUnits, parseAbi } from 'viem'
3
3
  import { mainnet } from 'viem/chains'
4
- import { GH_STAKE_DAO_BOUNTIES_REPORT } from '../../endpoints.js'
4
+ import { GH_STAKE_DAO_BOUNTIES_REPORT, STAKE_DAO_API } from '../../endpoints.js'
5
5
  import { type Price, getPrices } from '../../prices.js'
6
6
  import { rpcFetchTokensData } from '../../tokens.js'
7
7
  import { equalTlc, multicall } from '../../utils.js'
@@ -18,44 +18,52 @@ type UnknownToken = {
18
18
  chainId: number
19
19
  }
20
20
 
21
- const getMerkleData = async () => {
22
- const [delegators, nonDelegators] = await Promise.all([
21
+ const getMerkleData = async (chainId: number) => {
22
+ const [delegators, nonDelegators, votiumMerkle] = await Promise.all([
23
+ chainId === mainnet.id
24
+ ? fetch(
25
+ `${GH_STAKE_DAO_BOUNTIES_REPORT}/refs/heads/main/bounties-reports/latest/vlCVX/vlcvx_merkle_delegators.json`,
26
+ ).then((res) => res.json())
27
+ : {},
23
28
  fetch(
24
- `${GH_STAKE_DAO_BOUNTIES_REPORT}/refs/heads/main/bounties-reports/latest/vlCVX/vlcvx_merkle_delegators.json`,
29
+ `${GH_STAKE_DAO_BOUNTIES_REPORT}/refs/heads/main/bounties-reports/latest/vlCVX/vlcvx_merkle${
30
+ chainId === mainnet.id ? '' : `_${chainId}`
31
+ }.json`,
25
32
  ).then((res) => res.json()),
26
- fetch(`${GH_STAKE_DAO_BOUNTIES_REPORT}/refs/heads/main/bounties-reports/latest/vlCVX/vlcvx_merkle.json`).then(
27
- (res) => res.json(),
28
- ),
33
+ chainId === mainnet.id
34
+ ? fetch(`${STAKE_DAO_API}/votemarket/votium_merkles.json`).then((res) => res.json())
35
+ : { claims: {} },
29
36
  ])
30
37
 
31
- return { delegators, nonDelegators }
38
+ return { delegators, nonDelegators, votiumMerkle }
32
39
  }
33
40
 
34
- const isRootMatch = (contractRoot: string, fileRoot: string) =>
35
- (contractRoot === ZERO_ROOT && fileRoot === ZERO_ROOT) || equalTlc(contractRoot, fileRoot)
36
-
37
- const validateMerkleRoots = (delegatorRoot: string, nonDelegatorRoot: string, merkleData: any) => {
38
- const delegatorMatches = isRootMatch(delegatorRoot, merkleData.delegators.merkleRoot)
39
- const nonDelegatorMatches = isRootMatch(nonDelegatorRoot, merkleData.nonDelegators.merkleRoot)
41
+ const validateMerkleRoots = (delegatorRoot: string, nonDelegatorRoot: string, merkleData: any, isMainnet: boolean) => {
42
+ const delegatorMatches = isMainnet ? equalTlc(delegatorRoot, merkleData.delegators.merkleRoot) : false
43
+ const nonDelegatorMatches = equalTlc(nonDelegatorRoot, merkleData.nonDelegators.merkleRoot)
40
44
  return delegatorMatches || nonDelegatorMatches
41
45
  }
42
46
 
43
- const getMerkleRoots = async (provider: any) => {
47
+ const getMerkleRoots = async (provider: any, user: string) => {
44
48
  const contracts = [contract('vlCvxDelegatorMerkle', mainnet.id), contract('vlCvxNotDelegatorMerkle', mainnet.id)]
45
49
 
46
50
  const result = await multicall(
47
51
  provider,
48
- contracts.map((address) => ({
49
- address,
50
- name: 'root',
51
- params: [],
52
- })),
53
- parseAbi(['function root() external view returns (bytes32)']),
52
+ contracts.flatMap((address) => [
53
+ { address, name: 'root', params: [] },
54
+ { address, name: 'recipients', params: [user] },
55
+ ]),
56
+ parseAbi([
57
+ 'function root() external view returns (bytes32)',
58
+ 'function recipients(address) external view returns (address)',
59
+ ]),
54
60
  )
55
61
 
56
62
  return {
57
63
  delegatorRoot: result[0].result,
58
- nonDelegatorRoot: result[1].result,
64
+ delegatorRecipient: result[1].result,
65
+ nonDelegatorRoot: result[2].result,
66
+ nonDelegatorRecipient: result[3].result,
59
67
  }
60
68
  }
61
69
 
@@ -64,11 +72,7 @@ const getClaimedData = async (provider: any, tokens: string[], user: string, con
64
72
  tokens.length > 0
65
73
  ? await multicall(
66
74
  provider,
67
- tokens.map((t) => ({
68
- address: contract,
69
- name: 'claimed',
70
- params: [user, t],
71
- })),
75
+ tokens.flatMap((t) => [{ address: contract, name: 'claimed', params: [user, t] }]),
72
76
  parseAbi(['function claimed(address,address) external view returns (uint256)']),
73
77
  )
74
78
  : []
@@ -79,12 +83,33 @@ const getClaimedData = async (provider: any, tokens: string[], user: string, con
79
83
  }))
80
84
  }
81
85
 
86
+ const getVotiumClaimedData = async (provider: any, merkle: any) => {
87
+ const rawClaimed = await multicall(
88
+ provider,
89
+ Object.keys(merkle).map((t) => ({
90
+ address: contract('votiumMerkle'),
91
+ name: 'isClaimed',
92
+ params: [t, merkle[t].index],
93
+ })),
94
+ parseAbi(['function isClaimed(address,uint256) external view returns(bool)']),
95
+ )
96
+
97
+ return Object.keys(merkle).map((t, index) => ({
98
+ ...merkle[t],
99
+ token: t,
100
+ claimed: rawClaimed[index].result,
101
+ }))
102
+ }
103
+
82
104
  const getParsedData = (
105
+ user: string,
106
+ recipient: string,
83
107
  merkleAddress: string,
84
108
  userData: any,
85
109
  unknownTokensData: any[],
86
110
  claimedData: any[],
87
111
  prices: Price[],
112
+ chainId: number,
88
113
  ) => {
89
114
  return userData
90
115
  ? Object.keys(userData.tokens).map((token) => {
@@ -95,6 +120,8 @@ const getParsedData = (
95
120
  const claimable = BigInt(userData.tokens[token].amount) - claimed
96
121
 
97
122
  return {
123
+ user,
124
+ recipient,
98
125
  token: rewardToken,
99
126
  claimable: formatUnits(claimable, 0),
100
127
  claimableUsd: Number(formatUnits(claimable, rewardToken?.decimals || 18)) * tokenPrice,
@@ -104,71 +131,114 @@ const getParsedData = (
104
131
  proof: userData.tokens[token].proof,
105
132
  merkleAddress: merkleAddress as Address,
106
133
  isClaimed: claimable === Zero,
134
+ chainId,
107
135
  }
108
136
  })
109
137
  : undefined
110
138
  }
111
139
 
112
- const ZERO_ROOT = '0x0000000000000000000000000000000000000000000000000000000000000000'
140
+ const getVotiumParsedData = (user: string, userData: any[], unknownTokensData: any[], prices: Price[]) => {
141
+ return userData
142
+ ? userData.map((ud) => {
143
+ const rewardToken = tokenWithAddress(ud.token) || unknownTokensData.find((t) => equalTlc(t.address, ud.token))
144
+
145
+ const tokenPrice = prices.find((p) => equalTlc(ud.token, p.address))?.usdPrice || 0
113
146
 
114
- export const fetchUserVlCvxClaimable = async (provider: any, rpc: Rpcs, user: string) => {
115
- const { delegatorRoot, nonDelegatorRoot } = await getMerkleRoots(provider)
147
+ return {
148
+ user,
149
+ recipient: user,
150
+ token: rewardToken,
151
+ claimable: ud.claimed ? '0' : formatUnits(BigInt(ud.amount), 0),
152
+ claimableUsd: ud.claimed
153
+ ? 0
154
+ : Number(formatUnits(BigInt(ud.amount), rewardToken?.decimals || 18)) * tokenPrice,
155
+ amount: formatUnits(BigInt(ud.amount), 0),
156
+ amountUsd: Number(formatUnits(BigInt(ud.amount), rewardToken?.decimals || 18)) * tokenPrice,
157
+ proof: ud.proof,
158
+ merkleAddress: contract('votiumMerkle'),
159
+ isClaimed: ud.claimed,
160
+ index: ud.index,
161
+ chainId: mainnet.id,
162
+ }
163
+ })
164
+ : undefined
165
+ }
166
+
167
+ export const fetchUserVlCvxClaimable = async (provider: any, rpc: Rpcs, user: string, chainId: number) => {
168
+ const { delegatorRoot, delegatorRecipient, nonDelegatorRoot, nonDelegatorRecipient } = await getMerkleRoots(
169
+ provider,
170
+ user,
171
+ )
116
172
 
117
173
  let vlCvxClaimable: any
174
+ let votiumMerkleData: any
118
175
 
119
176
  try {
120
- const merkleData = await getMerkleData()
121
- if (validateMerkleRoots(delegatorRoot, nonDelegatorRoot, merkleData)) {
122
- vlCvxClaimable = merkleData
177
+ const merkleData = await getMerkleData(chainId)
178
+ if (validateMerkleRoots(delegatorRoot, nonDelegatorRoot, merkleData, chainId === mainnet.id)) {
179
+ vlCvxClaimable = { delegators: merkleData.delegators, nonDelegators: merkleData.nonDelegators }
123
180
  }
181
+ votiumMerkleData = merkleData.votiumMerkle.claims[user]
124
182
  } catch {}
125
183
 
126
- if (!vlCvxClaimable) {
127
- throw new Error('Could not find valid merkle data')
184
+ if (!vlCvxClaimable && !votiumMerkleData) {
185
+ console.error('Could not find valid merkle data')
186
+ return []
128
187
  }
129
188
 
130
- const vlCvxUserDelegated = vlCvxClaimable?.delegators.claims[user]
131
- const vlCvxUserNotDelegated = vlCvxClaimable?.nonDelegators.claims[user]
189
+ const vlCvxUserDelegated = vlCvxClaimable.delegators?.claims?.[user]
190
+ const vlCvxUserNotDelegated = vlCvxClaimable.nonDelegators.claims[user]
132
191
 
133
192
  const tokensDelegated = Object.keys(vlCvxUserDelegated?.tokens || {})
134
193
  const tokensNotDelegated = Object.keys(vlCvxUserNotDelegated?.tokens || {})
194
+ const tokensVotium = Object.keys(votiumMerkleData?.tokens || {})
135
195
 
136
- const tokens = [...tokensDelegated, ...tokensNotDelegated]
196
+ const tokens = [...tokensDelegated, ...tokensNotDelegated, ...tokensVotium]
137
197
 
138
198
  if (!tokens.length) return []
139
199
 
140
- const [delegatedClaimed, notDelegatedClaimed] = await Promise.all([
200
+ const [delegatedClaimed, notDelegatedClaimed, votiumClaimed] = await Promise.all([
141
201
  getClaimedData(provider, tokensDelegated, user, contract('vlCvxDelegatorMerkle')),
142
202
  getClaimedData(provider, tokensNotDelegated, user, contract('vlCvxNotDelegatorMerkle')),
203
+ votiumMerkleData?.tokens && Object.keys(votiumMerkleData?.tokens).length > 0
204
+ ? getVotiumClaimedData(provider, votiumMerkleData?.tokens)
205
+ : [],
143
206
  ])
144
207
 
145
208
  const unknownTokens = tokens.filter((t) => !tokenWithAddress(t))
146
- const rawUnknownTokensData = await rpcFetchTokensData(mainnet.id, rpc[mainnet.id]!, unknownTokens)
209
+ const rawUnknownTokensData = await rpcFetchTokensData(chainId, rpc[chainId]!, unknownTokens)
147
210
  const unknownTokensData: UnknownToken[] = rawUnknownTokensData.map((t) => ({
148
211
  name: t.name,
149
212
  symbol: t.symbol,
150
213
  address: t.tokenAddress,
151
214
  decimals: Number(t.decimals),
152
- chainId: mainnet.id,
215
+ chainId,
153
216
  }))
154
217
 
155
- const prices = await getPrices(tokens.map((t) => ({ address: t })) as any[], mainnet.id)
218
+ const prices = await getPrices(tokens.map((t) => ({ address: t })) as any[], chainId)
156
219
 
157
220
  return [
158
221
  getParsedData(
159
- contract('vlCvxDelegatorMerkle', mainnet.id),
222
+ user,
223
+ delegatorRecipient,
224
+ contract('vlCvxDelegatorMerkle'),
160
225
  vlCvxUserDelegated,
161
226
  unknownTokensData,
162
227
  delegatedClaimed,
163
228
  prices,
229
+ chainId,
164
230
  ),
165
231
  getParsedData(
166
- contract('vlCvxNotDelegatorMerkle', mainnet.id),
232
+ user,
233
+ nonDelegatorRecipient,
234
+ contract('vlCvxNotDelegatorMerkle', chainId),
167
235
  vlCvxUserNotDelegated,
168
236
  unknownTokensData,
169
237
  notDelegatedClaimed,
170
238
  prices,
239
+ chainId,
171
240
  ),
241
+ getVotiumParsedData(user, votiumClaimed, unknownTokensData, prices),
172
242
  ]
173
243
  .filter(Boolean)
174
244
  .flat()
@@ -0,0 +1,98 @@
1
+ import { contract } from '@stake-dao/constants'
2
+ import { range } from 'lodash-es'
3
+ import { decodeAbiParameters, encodeAbiParameters, parseAbiParameters } from 'viem'
4
+ import { arbitrum } from 'viem/chains'
5
+ import batchCampaigns from '../../bytecodes/votemarket/batchCampaigns.js'
6
+ import { batchJsonRpc, concatBytecode, equalTlc, rpcCall } from '../../utils.js'
7
+ import type { Campaign } from '../types.js'
8
+ import { fetchPendingRemoteCampaigns } from './pendingRemote.js'
9
+ import { processRawCampaigns } from './processRawCampaigns.js'
10
+
11
+ export const CAMPAIGNS_CHUNK_SIZE = 10
12
+ export const CLAIM_WINDOW_LENGTH = 24 // weeks
13
+ export const CLOSE_WINDOW_LENGTH = 4 // weeks
14
+
15
+ interface FetchCampaignsProps {
16
+ platform: string
17
+ gaugeController: string
18
+ chainId: number
19
+ rpcs: { [chainId: number]: string }
20
+ etherscanApiKey?: string
21
+ fromBlock?: number
22
+ mainnetVmRemote?: string
23
+ }
24
+
25
+ export const fetchCampaigns = async ({
26
+ platform,
27
+ gaugeController,
28
+ chainId,
29
+ rpcs,
30
+ etherscanApiKey,
31
+ fromBlock,
32
+ mainnetVmRemote,
33
+ }: FetchCampaignsProps): Promise<Campaign[]> => {
34
+ const rpc = rpcs[chainId]!
35
+
36
+ const nCampaignsRequest = await rpcCall(rpc, [{ to: platform, data: '0x7274e30d' }]) // campaignCount
37
+ const nCampaigns = Number(
38
+ decodeAbiParameters([{ type: 'uint256', name: 'campaignCount' }], nCampaignsRequest[0].result),
39
+ )
40
+
41
+ const calls = range(0, nCampaigns, CAMPAIGNS_CHUNK_SIZE).map((skip) => {
42
+ const inputParams = [
43
+ chainId,
44
+ platform,
45
+ contract('laPosteTokenFactory', chainId),
46
+ skip,
47
+ skip + CAMPAIGNS_CHUNK_SIZE > nCampaigns ? nCampaigns : skip + CAMPAIGNS_CHUNK_SIZE,
48
+ ]
49
+
50
+ const inputData = encodeAbiParameters(parseAbiParameters(batchCampaigns.inputType[1]!) as any, inputParams)
51
+ return concatBytecode(batchCampaigns.bytecode[1]!, inputData)
52
+ })
53
+
54
+ let rawCampaigns = await batchJsonRpc({
55
+ rpc,
56
+ calls,
57
+ outputTypeAbi: batchCampaigns.outputType![1]!,
58
+ parse: false,
59
+ callsKey: `votemarket/fetchCampaigns.ts: rawCampaigns - chainId ${chainId} - rpc ${rpc}`,
60
+ })
61
+
62
+ if (chainId === arbitrum.id && etherscanApiKey && fromBlock && mainnetVmRemote) {
63
+ const nextIndex = rawCampaigns.length > 0 ? Math.max(...rawCampaigns.map((rc) => Number(rc.id))) + 1 : 0
64
+ const { remoteCampaigns: pendingRemoteCampaigns } = await fetchPendingRemoteCampaigns({
65
+ mainnetVmRemote,
66
+ chainId,
67
+ fromBlock,
68
+ etherscanApiKey,
69
+ nextIndex,
70
+ votemarket: platform,
71
+ })
72
+
73
+ rawCampaigns = [
74
+ ...rawCampaigns,
75
+ ...pendingRemoteCampaigns.filter((rmtC) => {
76
+ const existingCampaign = rawCampaigns.find(
77
+ (rc) =>
78
+ rc.addresses.length === rmtC.addresses.length &&
79
+ rc.addresses.every((rca, index) => equalTlc(rca, rmtC.addresses[index])) &&
80
+ equalTlc(rc.rewardAddress, rmtC.rewardAddress) &&
81
+ rc.rewardChainId === rmtC.rewardChainId &&
82
+ rc.campaign.startTimestamp === rmtC.campaign.startTimestamp &&
83
+ rc.campaign.endTimestamp === rmtC.campaign.endTimestamp &&
84
+ rc.campaign.numberOfPeriods === rmtC.campaign.numberOfPeriods &&
85
+ rc.campaign.maxRewardPerVote === rmtC.campaign.maxRewardPerVote &&
86
+ rc.campaign.totalRewardAmount === rmtC.campaign.totalRewardAmount &&
87
+ equalTlc(rc.campaign.gauge, rmtC.campaign.gauge) &&
88
+ equalTlc(rc.campaign.hook, rmtC.campaign.hook) &&
89
+ equalTlc(rc.campaign.manager, rmtC.campaign.manager),
90
+ )
91
+
92
+ return typeof existingCampaign === 'undefined'
93
+ }),
94
+ ]
95
+ }
96
+
97
+ return await processRawCampaigns({ rawCampaigns, platform, chainId, gaugeController, rpcs })
98
+ }
@@ -2,6 +2,7 @@ import { LA_POSTE_MESSAGE_SENT_EVENT, ONE_WEEK, One, Zero, contract } from '@sta
2
2
  import { range } from 'lodash-es'
3
3
  import { decodeAbiParameters, decodeEventLog, parseAbiParameters, zeroAddress } from 'viem'
4
4
  import { mainnet } from 'viem/chains'
5
+ import { equalTlc } from '../../utils.js'
5
6
 
6
7
  export const CAMPAIGNS_CHUNK_SIZE = 10
7
8
  export const CLAIM_WINDOW_LENGTH = 24 // weeks
@@ -13,6 +14,7 @@ interface FetchPendingRemoteCampaignsProps {
13
14
  fromBlock: number
14
15
  etherscanApiKey: string
15
16
  nextIndex: number
17
+ votemarket: string
16
18
  }
17
19
 
18
20
  export const fetchPendingRemoteCampaigns = async ({
@@ -21,6 +23,7 @@ export const fetchPendingRemoteCampaigns = async ({
21
23
  fromBlock,
22
24
  etherscanApiKey,
23
25
  nextIndex,
26
+ votemarket,
24
27
  }: FetchPendingRemoteCampaignsProps) => {
25
28
  const queryParams = {
26
29
  chainid: `${mainnet.id}`,
@@ -59,6 +62,9 @@ export const fetchPendingRemoteCampaigns = async ({
59
62
  decodedEvent.args.message.payload,
60
63
  )
61
64
 
65
+ // Skip if the target is not the protocol's votmarket
66
+ if (!equalTlc(payload[0].votemarket, votemarket)) continue
67
+
62
68
  // Create Campaign (actionType === 0)
63
69
  if (payload[0].actionType === Zero) {
64
70
  const decodedParams = decodeAbiParameters(
@@ -131,6 +137,7 @@ export const fetchPendingRemoteCampaigns = async ({
131
137
  })),
132
138
  rewardAddress: campaignParams.rewardToken,
133
139
  rewardChainId: BigInt(mainnet.id),
140
+ eventTimestamp: element.eventTimestamp,
134
141
  }
135
142
 
136
143
  nextIndex++
@@ -1,96 +1,26 @@
1
- import { ONE_WEEK, Zero, contract, contracts, tokenWithAddress, tokens } from '@stake-dao/constants'
2
- import { chunk, filter, range, remove, uniq } from 'lodash-es'
3
- import {
4
- decodeAbiParameters,
5
- encodeAbiParameters,
6
- formatEther,
7
- formatUnits,
8
- parseAbiParameters,
9
- zeroAddress,
10
- } from 'viem'
11
- import { arbitrum, mainnet } from 'viem/chains'
12
- import batchCampaigns from '../bytecodes/votemarket/batchCampaigns.js'
13
- import batchCampaignsRestrictedVotes from '../bytecodes/votemarket/batchCampaignsRestrictedVotes.js'
14
- import batchGaugesWeight from '../bytecodes/votemarket/batchGaugesWeight.js'
15
- import { getPrices } from '../prices.js'
16
- import { rpcFetchTokensData } from '../tokens.js'
17
- import { batchJsonRpc, concatBytecode, equalTlc, rpcCall, rpcGetLastBlockTimetstamp } from '../utils.js'
18
- import campaignsUnderlyingTokens from './campaignsUnderlyingTokens.js'
19
- import { fetchPendingRemoteCampaigns } from './fetchPendingRemoteCampaigns.js'
20
- import type { Campaign, RawCampaign, RawPeriod } from './types.js'
1
+ import { ONE_WEEK, Zero, tokenWithAddress, tokens } from '@stake-dao/constants'
2
+ import { chunk, filter, remove, uniq } from 'lodash-es'
3
+ import { encodeAbiParameters, formatEther, formatUnits, parseAbiParameters } from 'viem'
4
+ import { mainnet } from 'viem/chains'
5
+ import batchCampaignsRestrictedVotes from '../../bytecodes/votemarket/batchCampaignsRestrictedVotes.js'
6
+ import batchGaugesWeight from '../../bytecodes/votemarket/batchGaugesWeight.js'
7
+ import { getPrices } from '../../prices.js'
8
+ import { rpcFetchTokensData } from '../../tokens.js'
9
+ import { batchJsonRpc, concatBytecode, equalTlc, lc, rpcGetLastBlockTimetstamp } from '../../utils.js'
10
+ import campaignsUnderlyingTokens from '../campaignsUnderlyingTokens.js'
11
+ import type { Campaign } from '../types.js'
12
+ import { updateEpoch } from './updateEpoch.js'
21
13
 
22
14
  export const CAMPAIGNS_CHUNK_SIZE = 10
23
15
  export const CLAIM_WINDOW_LENGTH = 24 // weeks
24
16
  export const CLOSE_WINDOW_LENGTH = 4 // weeks
25
17
 
26
- interface FetchCampaignsProps {
18
+ interface ProcessRawCampaignsProps {
19
+ rawCampaigns: any[]
27
20
  platform: string
28
21
  gaugeController: string
29
22
  chainId: number
30
23
  rpcs: { [chainId: number]: string }
31
- etherscanApiKey?: string
32
- fromBlock?: number
33
- mainnetVmRemote?: string
34
- }
35
-
36
- const updateEpoch = ({
37
- period,
38
- previousPeriod,
39
- campaign,
40
- totalVotes,
41
- remainingPeriods,
42
- }: {
43
- period: RawPeriod
44
- previousPeriod?: RawPeriod
45
- campaign: RawCampaign
46
- totalVotes: bigint
47
- remainingPeriods: number
48
- }) => {
49
- // -- _checkForUpgrade is handled in solidity BatchCampaigns.sol
50
- // -- If not first period, check if previousPeriod is updated
51
- if (!period.updated && (previousPeriod?.updated || typeof previousPeriod === 'undefined')) {
52
- // Calculate remaining periods and total reward.
53
- const totalRewardForRemainingPeriods: bigint = campaign.totalRewardAmount - campaign.totalDistributed
54
-
55
- // -- Update the period data.
56
- const newRewardPerPeriod =
57
- remainingPeriods > 0 ? totalRewardForRemainingPeriods / BigInt(remainingPeriods) : totalRewardForRemainingPeriods
58
- period.rewardPerPeriod = newRewardPerPeriod
59
-
60
- // -- Update the reward per vote
61
- // If no votes, rollover the leftover to the next epoch.
62
- if (totalVotes === Zero) {
63
- period.leftover = period.rewardPerPeriod
64
- } else {
65
- // Calculate reward per vote
66
- let rewardPerVote = (newRewardPerPeriod * BigInt(1e18)) / totalVotes
67
-
68
- // Cap reward per vote at max reward per vote
69
- if (rewardPerVote > campaign.maxRewardPerVote) {
70
- rewardPerVote = campaign.maxRewardPerVote
71
-
72
- // Calculate leftover rewards
73
- const leftOver = newRewardPerPeriod - (rewardPerVote * totalVotes) / BigInt(1e18)
74
-
75
- // Handle leftover rewards
76
- if (campaign.hook === zeroAddress) {
77
- // Store leftover in the period if there is no hook.
78
- period.leftover = period.leftover + leftOver
79
- }
80
- }
81
-
82
- // Save the calculated reward per vote.
83
- period.rewardPerVote = rewardPerVote
84
- }
85
-
86
- // -- Update the total distributed amount
87
- campaign.totalDistributed = campaign.totalDistributed + newRewardPerPeriod - period.leftover
88
-
89
- // -- Mark the period as updated
90
- period.updated = true
91
- }
92
-
93
- return campaign
94
24
  }
95
25
 
96
26
  const formatPeriod = (rawPeriod: any, decimals: number) => ({
@@ -100,77 +30,15 @@ const formatPeriod = (rawPeriod: any, decimals: number) => ({
100
30
  updated: rawPeriod.updated,
101
31
  })
102
32
 
103
- export const fetchCampaigns = async ({
33
+ export const processRawCampaigns = async ({
34
+ rawCampaigns,
104
35
  platform,
105
36
  gaugeController,
106
37
  chainId,
107
38
  rpcs,
108
- etherscanApiKey,
109
- fromBlock,
110
- mainnetVmRemote,
111
- }: FetchCampaignsProps): Promise<Campaign[]> => {
39
+ }: ProcessRawCampaignsProps): Promise<Campaign[]> => {
112
40
  const rpc = rpcs[chainId]!
113
-
114
41
  const block = await rpcGetLastBlockTimetstamp(rpc)
115
- const nCampaignsRequest = await rpcCall(rpc, [{ to: platform, data: '0x7274e30d' }]) // campaignCount
116
- const nCampaigns = Number(
117
- decodeAbiParameters([{ type: 'uint256', name: 'campaignCount' }], nCampaignsRequest[0].result),
118
- )
119
-
120
- const calls = range(0, nCampaigns, CAMPAIGNS_CHUNK_SIZE).map((skip) => {
121
- const inputParams = [
122
- chainId,
123
- platform,
124
- contract('laPosteTokenFactory', chainId),
125
- skip,
126
- skip + CAMPAIGNS_CHUNK_SIZE > nCampaigns ? nCampaigns : skip + CAMPAIGNS_CHUNK_SIZE,
127
- ]
128
-
129
- const inputData = encodeAbiParameters(parseAbiParameters(batchCampaigns.inputType[1]!) as any, inputParams)
130
- return concatBytecode(batchCampaigns.bytecode[1]!, inputData)
131
- })
132
-
133
- let rawCampaigns = await batchJsonRpc({
134
- rpc,
135
- calls,
136
- outputTypeAbi: batchCampaigns.outputType![1]!,
137
- parse: false,
138
- callsKey: `votemarket/fetchCampaigns.ts: rawCampaigns - chainId ${chainId} - rpc ${rpc}`,
139
- })
140
-
141
- if (chainId === arbitrum.id && etherscanApiKey && fromBlock && mainnetVmRemote) {
142
- const nextIndex = rawCampaigns.length > 0 ? Math.max(...rawCampaigns.map((rc) => Number(rc.id))) + 1 : 0
143
- const { remoteCampaigns: pendingRemoteCampaigns } = await fetchPendingRemoteCampaigns({
144
- mainnetVmRemote,
145
- chainId,
146
- fromBlock,
147
- etherscanApiKey,
148
- nextIndex,
149
- })
150
-
151
- rawCampaigns = [
152
- ...rawCampaigns,
153
- ...pendingRemoteCampaigns.filter((rmtC) => {
154
- const existingCampaign = rawCampaigns.find(
155
- (rc) =>
156
- rc.addresses.length === rmtC.addresses.length &&
157
- rc.addresses.every((rca, index) => equalTlc(rca, rmtC.addresses[index])) &&
158
- equalTlc(rc.rewardAddress, rmtC.rewardAddress) &&
159
- rc.rewardChainId === rmtC.rewardChainId &&
160
- rc.campaign.startTimestamp === rmtC.campaign.startTimestamp &&
161
- rc.campaign.endTimestamp === rmtC.campaign.endTimestamp &&
162
- rc.campaign.numberOfPeriods === rmtC.campaign.numberOfPeriods &&
163
- rc.campaign.maxRewardPerVote === rmtC.campaign.maxRewardPerVote &&
164
- rc.campaign.totalRewardAmount === rmtC.campaign.totalRewardAmount &&
165
- equalTlc(rc.campaign.gauge, rmtC.campaign.gauge) &&
166
- equalTlc(rc.campaign.hook, rmtC.campaign.hook) &&
167
- equalTlc(rc.campaign.manager, rmtC.campaign.manager),
168
- )
169
-
170
- return typeof existingCampaign === 'undefined'
171
- }),
172
- ]
173
- }
174
42
 
175
43
  const callsRestrictedVotes = rawCampaigns
176
44
  .filter((rc) => rc.addresses.length > 0)
@@ -200,7 +68,7 @@ export const fetchCampaigns = async ({
200
68
  concatBytecode(
201
69
  batchGaugesWeight.bytecode[mainnet.id]!,
202
70
  encodeAbiParameters(parseAbiParameters(batchGaugesWeight.inputType[mainnet.id]!) as any, [
203
- contracts.curveGaugeController![1]!,
71
+ gaugeController,
204
72
  campaignsGauges,
205
73
  ]),
206
74
  ),
@@ -324,7 +192,9 @@ export const fetchCampaigns = async ({
324
192
  )
325
193
 
326
194
  return {
195
+ key: `${chainId}-${lc(platform.slice(2, 6))}-${lc(platform.slice(-4))}-${Number(rc.id)}`,
327
196
  id: Number(rc.id),
197
+ platform,
328
198
  chainId,
329
199
  rewardChainId: Number(rc.rewardChainId),
330
200
  gaugeChainId: Number(rc.campaign.chainId),
@@ -0,0 +1,63 @@
1
+ import { Zero } from '@stake-dao/constants'
2
+ import { zeroAddress } from 'viem'
3
+ import type { RawCampaign, RawPeriod } from '../types.js'
4
+
5
+ export const updateEpoch = ({
6
+ period,
7
+ previousPeriod,
8
+ campaign,
9
+ totalVotes,
10
+ remainingPeriods,
11
+ }: {
12
+ period: RawPeriod
13
+ previousPeriod?: RawPeriod
14
+ campaign: RawCampaign
15
+ totalVotes: bigint
16
+ remainingPeriods: number
17
+ }) => {
18
+ // -- _checkForUpgrade is handled in solidity BatchCampaigns.sol
19
+ // -- If not first period, check if previousPeriod is updated
20
+ if (!period.updated && (previousPeriod?.updated || typeof previousPeriod === 'undefined')) {
21
+ // Calculate remaining periods and total reward.
22
+ const totalRewardForRemainingPeriods: bigint = campaign.totalRewardAmount - campaign.totalDistributed
23
+
24
+ // -- Update the period data.
25
+ const newRewardPerPeriod =
26
+ remainingPeriods > 0 ? totalRewardForRemainingPeriods / BigInt(remainingPeriods) : totalRewardForRemainingPeriods
27
+ period.rewardPerPeriod = newRewardPerPeriod
28
+
29
+ // -- Update the reward per vote
30
+ // If no votes, rollover the leftover to the next epoch.
31
+ if (totalVotes === Zero) {
32
+ period.leftover = period.rewardPerPeriod
33
+ } else {
34
+ // Calculate reward per vote
35
+ let rewardPerVote = (newRewardPerPeriod * BigInt(1e18)) / totalVotes
36
+
37
+ // Cap reward per vote at max reward per vote
38
+ if (rewardPerVote > campaign.maxRewardPerVote) {
39
+ rewardPerVote = campaign.maxRewardPerVote
40
+
41
+ // Calculate leftover rewards
42
+ const leftOver = newRewardPerPeriod - (rewardPerVote * totalVotes) / BigInt(1e18)
43
+
44
+ // Handle leftover rewards
45
+ if (campaign.hook === zeroAddress) {
46
+ // Store leftover in the period if there is no hook.
47
+ period.leftover = period.leftover + leftOver
48
+ }
49
+ }
50
+
51
+ // Save the calculated reward per vote.
52
+ period.rewardPerVote = rewardPerVote
53
+ }
54
+
55
+ // -- Update the total distributed amount
56
+ campaign.totalDistributed = campaign.totalDistributed + newRewardPerPeriod - period.leftover
57
+
58
+ // -- Mark the period as updated
59
+ period.updated = true
60
+ }
61
+
62
+ return campaign
63
+ }