@factordao/governance 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,686 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createClientForChain = createClientForChain;
4
+ exports.fetchVaultRewardsForChain = fetchVaultRewardsForChain;
5
+ const viem_1 = require("viem");
6
+ const tokenlist_1 = require("@factordao/tokenlist");
7
+ const viem_2 = require("./viem");
8
+ // Storage slot constants for Scale rewards calculation
9
+ const REWARD_MANAGER_SLOT = (0, viem_1.keccak256)((0, viem_1.toHex)('factor.base.RewardManager.storage'));
10
+ const FACTOR_GAUGE_SLOT = (0, viem_1.keccak256)((0, viem_1.toHex)('factor.base.gauge.storage'));
11
+ // Minimal ABI for Scale/Boost reward functions
12
+ const REWARDS_ABI = [
13
+ // Scale functions
14
+ {
15
+ name: 'getRewardTokens',
16
+ type: 'function',
17
+ inputs: [],
18
+ outputs: [{ type: 'address[]' }],
19
+ stateMutability: 'view',
20
+ },
21
+ {
22
+ name: 'totalActiveSupply',
23
+ type: 'function',
24
+ inputs: [],
25
+ outputs: [{ type: 'uint256' }],
26
+ stateMutability: 'view',
27
+ },
28
+ {
29
+ name: 'activeBalance',
30
+ type: 'function',
31
+ inputs: [{ name: 'user', type: 'address' }],
32
+ outputs: [{ type: 'uint256' }],
33
+ stateMutability: 'view',
34
+ },
35
+ // Boost functions
36
+ {
37
+ name: 'getAllRewardTokens',
38
+ type: 'function',
39
+ inputs: [],
40
+ outputs: [{ type: 'address[]' }],
41
+ stateMutability: 'view',
42
+ },
43
+ {
44
+ name: 'rewardData',
45
+ type: 'function',
46
+ inputs: [{ name: 'token', type: 'address' }],
47
+ outputs: [
48
+ { name: 'periodFinish', type: 'uint256' },
49
+ { name: 'rewardRate', type: 'uint256' },
50
+ { name: 'lastUpdateTime', type: 'uint256' },
51
+ { name: 'rewardPerTokenStored', type: 'uint256' },
52
+ ],
53
+ stateMutability: 'view',
54
+ },
55
+ {
56
+ name: 'earned',
57
+ type: 'function',
58
+ inputs: [
59
+ { name: 'user', type: 'address' },
60
+ { name: 'token', type: 'address' },
61
+ ],
62
+ outputs: [{ type: 'uint256' }],
63
+ stateMutability: 'view',
64
+ },
65
+ {
66
+ name: 'getRewardForDuration',
67
+ type: 'function',
68
+ inputs: [{ name: 'token', type: 'address' }],
69
+ outputs: [{ type: 'uint256' }],
70
+ stateMutability: 'view',
71
+ },
72
+ ];
73
+ // ERC20 ABI for token metadata
74
+ const ERC20_ABI = [
75
+ {
76
+ name: 'symbol',
77
+ type: 'function',
78
+ inputs: [],
79
+ outputs: [{ type: 'string' }],
80
+ stateMutability: 'view',
81
+ },
82
+ {
83
+ name: 'decimals',
84
+ type: 'function',
85
+ inputs: [],
86
+ outputs: [{ type: 'uint8' }],
87
+ stateMutability: 'view',
88
+ },
89
+ ];
90
+ // Gauge Controller ABI for Scale rewards
91
+ const GAUGE_CONTROLLER_ABI = [
92
+ {
93
+ name: 'rewardData',
94
+ type: 'function',
95
+ inputs: [{ name: 'vault', type: 'address' }],
96
+ outputs: [
97
+ { name: 'fctrPerSec', type: 'uint128' },
98
+ { name: 'accumulatedFctr', type: 'uint128' },
99
+ { name: 'lastUpdated', type: 'uint128' },
100
+ { name: 'incentiveEndsAt', type: 'uint128' },
101
+ ],
102
+ stateMutability: 'view',
103
+ },
104
+ ];
105
+ // Multicall3 ABI for batching getStorageAt calls
106
+ const MULTICALL3_ABI = [
107
+ {
108
+ name: 'aggregate3',
109
+ type: 'function',
110
+ inputs: [
111
+ {
112
+ name: 'calls',
113
+ type: 'tuple[]',
114
+ components: [
115
+ { name: 'target', type: 'address' },
116
+ { name: 'allowFailure', type: 'bool' },
117
+ { name: 'callData', type: 'bytes' },
118
+ ],
119
+ },
120
+ ],
121
+ outputs: [
122
+ {
123
+ name: 'returnData',
124
+ type: 'tuple[]',
125
+ components: [
126
+ { name: 'success', type: 'bool' },
127
+ { name: 'returnData', type: 'bytes' },
128
+ ],
129
+ },
130
+ ],
131
+ stateMutability: 'view',
132
+ },
133
+ ];
134
+ // Multicall3 is deployed at the same address on all chains
135
+ const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
136
+ // Multicall3 getEthBalance can be used to batch reads, but for storage we need a different approach
137
+ // We'll use the standard eth_getStorageAt but batch with Promise.all since viem batches RPC calls
138
+ // Helper to calculate storage slot for mapping(address => value)
139
+ function getMappingSlot(key, baseSlot) {
140
+ return (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)((0, viem_1.parseAbiParameters)('address, bytes32'), [
141
+ key,
142
+ baseSlot,
143
+ ]));
144
+ }
145
+ // Helper to calculate storage slot for nested mapping(address => mapping(address => value))
146
+ function getNestedMappingSlot(outerKey, innerKey, baseSlot) {
147
+ const outerSlot = getMappingSlot(outerKey, baseSlot);
148
+ return getMappingSlot(innerKey, outerSlot);
149
+ }
150
+ // Public RPCs with fallback support
151
+ const PUBLIC_RPCS = {
152
+ [tokenlist_1.ChainId.ARBITRUM_ONE]: [
153
+ 'https://arb1.arbitrum.io/rpc',
154
+ 'https://arbitrum.llamarpc.com',
155
+ 'https://arbitrum-one-rpc.publicnode.com',
156
+ ],
157
+ [tokenlist_1.ChainId.BASE]: [
158
+ 'https://base.llamarpc.com',
159
+ 'https://base-rpc.publicnode.com',
160
+ 'https://base.gateway.tenderly.co',
161
+ ],
162
+ [tokenlist_1.ChainId.OPTIMISM]: [
163
+ 'https://optimism.llamarpc.com',
164
+ 'https://optimism-rpc.publicnode.com',
165
+ ],
166
+ [tokenlist_1.ChainId.SONIC]: [
167
+ 'https://rpc.soniclabs.com',
168
+ 'https://sonic.drpc.org',
169
+ 'https://sonic-rpc.publicnode.com',
170
+ ],
171
+ };
172
+ // Alchemy RPC URLs
173
+ const ALCHEMY_RPCS = {
174
+ [tokenlist_1.ChainId.ARBITRUM_ONE]: 'https://arb-mainnet.g.alchemy.com/v2',
175
+ [tokenlist_1.ChainId.BASE]: 'https://base-mainnet.g.alchemy.com/v2',
176
+ [tokenlist_1.ChainId.OPTIMISM]: 'https://opt-mainnet.g.alchemy.com/v2',
177
+ [tokenlist_1.ChainId.SONIC]: 'https://sonic-mainnet.g.alchemy.com/v2',
178
+ };
179
+ function createClientForChain(chainId, alchemyApiKey) {
180
+ const chain = (0, viem_2.ChainIdToViemChain)(chainId);
181
+ const publicRpcs = PUBLIC_RPCS[chainId] || [];
182
+ // Enable JSON-RPC batching for better performance
183
+ const httpOptions = { batch: true };
184
+ let transports = publicRpcs.map((url) => (0, viem_1.http)(url, httpOptions));
185
+ // If Alchemy key provided, use it as primary
186
+ if (alchemyApiKey && ALCHEMY_RPCS[chainId]) {
187
+ const alchemyUrl = `${ALCHEMY_RPCS[chainId]}/${alchemyApiKey}`;
188
+ transports = [(0, viem_1.http)(alchemyUrl, httpOptions), ...transports];
189
+ }
190
+ return (0, viem_1.createPublicClient)({
191
+ chain,
192
+ transport: transports.length > 1 ? (0, viem_1.fallback)(transports) : transports[0],
193
+ batch: {
194
+ multicall: true,
195
+ },
196
+ });
197
+ }
198
+ // Format bigint with decimals
199
+ function formatWithDecimals(value, decimals) {
200
+ if (value === 0n)
201
+ return '0';
202
+ const divisor = 10n ** BigInt(decimals);
203
+ const whole = value / divisor;
204
+ const remainder = value % divisor;
205
+ if (remainder === 0n) {
206
+ return whole.toString();
207
+ }
208
+ const remainderStr = remainder.toString().padStart(decimals, '0');
209
+ const trimmed = remainderStr.replace(/0+$/, '');
210
+ return `${whole}.${trimmed}`;
211
+ }
212
+ async function fetchVaultRewardsForChain(client, vaults, userAddress) {
213
+ if (vaults.length === 0) {
214
+ return [];
215
+ }
216
+ // Stage 1: Get reward tokens and total active supply for all vaults
217
+ const stage1Contracts = vaults.flatMap((vault) => [
218
+ {
219
+ address: vault.id,
220
+ abi: REWARDS_ABI,
221
+ functionName: 'getRewardTokens',
222
+ },
223
+ {
224
+ address: vault.id,
225
+ abi: REWARDS_ABI,
226
+ functionName: 'getAllRewardTokens',
227
+ },
228
+ {
229
+ address: vault.id,
230
+ abi: REWARDS_ABI,
231
+ functionName: 'totalActiveSupply',
232
+ },
233
+ ]);
234
+ const stage1Results = await client.multicall({
235
+ contracts: stage1Contracts,
236
+ allowFailure: true,
237
+ });
238
+ // Parse Stage 1 results
239
+ const stage1Data = [];
240
+ for (let i = 0; i < vaults.length; i++) {
241
+ const baseIndex = i * 3;
242
+ const scaleTokensResult = stage1Results[baseIndex];
243
+ const boostTokensResult = stage1Results[baseIndex + 1];
244
+ const totalActiveSupplyResult = stage1Results[baseIndex + 2];
245
+ stage1Data.push({
246
+ vaultAddress: vaults[i].id,
247
+ scaleTokens: scaleTokensResult.status === 'success'
248
+ ? scaleTokensResult.result
249
+ : [],
250
+ boostTokens: boostTokensResult.status === 'success'
251
+ ? boostTokensResult.result
252
+ : [],
253
+ totalActiveSupply: totalActiveSupplyResult.status === 'success'
254
+ ? totalActiveSupplyResult.result
255
+ : 0n,
256
+ });
257
+ }
258
+ // Collect all unique tokens for metadata fetching
259
+ const allTokens = new Set();
260
+ for (const data of stage1Data) {
261
+ data.scaleTokens.forEach((t) => allTokens.add(t));
262
+ data.boostTokens.forEach((t) => allTokens.add(t));
263
+ }
264
+ const uniqueTokens = Array.from(allTokens);
265
+ // Stage 1.5: Fetch token metadata (symbol, decimals)
266
+ const tokenMetadataContracts = uniqueTokens.flatMap((token) => [
267
+ { address: token, abi: ERC20_ABI, functionName: 'symbol' },
268
+ { address: token, abi: ERC20_ABI, functionName: 'decimals' },
269
+ ]);
270
+ let tokenMetadataResults = [];
271
+ if (tokenMetadataContracts.length > 0) {
272
+ tokenMetadataResults = await client.multicall({
273
+ contracts: tokenMetadataContracts,
274
+ allowFailure: true,
275
+ });
276
+ }
277
+ // Build token metadata map
278
+ const tokenMetadata = {};
279
+ for (let i = 0; i < uniqueTokens.length; i++) {
280
+ const tokenKey = uniqueTokens[i].toLowerCase();
281
+ const symbolResult = tokenMetadataResults[i * 2];
282
+ const decimalsResult = tokenMetadataResults[i * 2 + 1];
283
+ tokenMetadata[tokenKey] = {
284
+ symbol: symbolResult.status === 'success'
285
+ ? (symbolResult.result ?? null)
286
+ : null,
287
+ decimals: decimalsResult.status === 'success'
288
+ ? (decimalsResult.result ?? 18)
289
+ : 18,
290
+ };
291
+ }
292
+ // Stage 2: Get reward data and getRewardForDuration for all tokens
293
+ const stage2Contracts = [];
294
+ // Track which tokens belong to which vault and type
295
+ const tokenMapping = [];
296
+ for (let i = 0; i < stage1Data.length; i++) {
297
+ const data = stage1Data[i];
298
+ // Add scale tokens - rewardData + getRewardForDuration
299
+ for (const token of data.scaleTokens) {
300
+ stage2Contracts.push({
301
+ address: data.vaultAddress,
302
+ abi: REWARDS_ABI,
303
+ functionName: 'rewardData',
304
+ args: [token],
305
+ });
306
+ tokenMapping.push({
307
+ vaultIndex: i,
308
+ token,
309
+ type: 'scale',
310
+ callType: 'rewardData',
311
+ });
312
+ stage2Contracts.push({
313
+ address: data.vaultAddress,
314
+ abi: REWARDS_ABI,
315
+ functionName: 'getRewardForDuration',
316
+ args: [token],
317
+ });
318
+ tokenMapping.push({
319
+ vaultIndex: i,
320
+ token,
321
+ type: 'scale',
322
+ callType: 'rewardForDuration',
323
+ });
324
+ }
325
+ // Add boost tokens - rewardData + getRewardForDuration
326
+ for (const token of data.boostTokens) {
327
+ stage2Contracts.push({
328
+ address: data.vaultAddress,
329
+ abi: REWARDS_ABI,
330
+ functionName: 'rewardData',
331
+ args: [token],
332
+ });
333
+ tokenMapping.push({
334
+ vaultIndex: i,
335
+ token,
336
+ type: 'boost',
337
+ callType: 'rewardData',
338
+ });
339
+ stage2Contracts.push({
340
+ address: data.vaultAddress,
341
+ abi: REWARDS_ABI,
342
+ functionName: 'getRewardForDuration',
343
+ args: [token],
344
+ });
345
+ tokenMapping.push({
346
+ vaultIndex: i,
347
+ token,
348
+ type: 'boost',
349
+ callType: 'rewardForDuration',
350
+ });
351
+ }
352
+ }
353
+ let stage2Results = [];
354
+ if (stage2Contracts.length > 0) {
355
+ stage2Results = await client.multicall({
356
+ contracts: stage2Contracts,
357
+ allowFailure: true,
358
+ });
359
+ }
360
+ // Parse Stage 2 results and build reward data maps
361
+ const vaultRewardData = stage1Data.map(() => ({
362
+ scaleRewardsData: {},
363
+ boostRewardsData: {},
364
+ }));
365
+ for (let i = 0; i < tokenMapping.length; i++) {
366
+ const mapping = tokenMapping[i];
367
+ const tokenKey = mapping.token.toLowerCase();
368
+ const targetMap = mapping.type === 'scale'
369
+ ? vaultRewardData[mapping.vaultIndex].scaleRewardsData
370
+ : vaultRewardData[mapping.vaultIndex].boostRewardsData;
371
+ // Initialize if not exists
372
+ if (!targetMap[tokenKey]) {
373
+ targetMap[tokenKey] = {
374
+ periodFinish: 0n,
375
+ rewardRate: 0n,
376
+ lastUpdateTime: 0n,
377
+ rewardPerTokenStored: 0n,
378
+ rewardForDuration: 0n,
379
+ };
380
+ }
381
+ const result = stage2Results[i];
382
+ if (result.status === 'success' && result.result !== undefined) {
383
+ if (mapping.callType === 'rewardData') {
384
+ const [periodFinish, rewardRate, lastUpdateTime, rewardPerTokenStored] = result.result;
385
+ targetMap[tokenKey].periodFinish = periodFinish;
386
+ targetMap[tokenKey].rewardRate = rewardRate;
387
+ targetMap[tokenKey].lastUpdateTime = lastUpdateTime;
388
+ targetMap[tokenKey].rewardPerTokenStored = rewardPerTokenStored;
389
+ }
390
+ else {
391
+ targetMap[tokenKey].rewardForDuration = result.result;
392
+ }
393
+ }
394
+ }
395
+ // Stage 3 (Optional): User data including Scale pending rewards
396
+ let userDataMap = {};
397
+ if (userAddress) {
398
+ // Stage 3a: Get basic user data (activeBalance for Scale, earned for Boost)
399
+ const stage3Contracts = [];
400
+ const userDataMapping = [];
401
+ for (let i = 0; i < stage1Data.length; i++) {
402
+ const data = stage1Data[i];
403
+ // Active balance for Scale
404
+ stage3Contracts.push({
405
+ address: data.vaultAddress,
406
+ abi: REWARDS_ABI,
407
+ functionName: 'activeBalance',
408
+ args: [userAddress],
409
+ });
410
+ userDataMapping.push({ vaultIndex: i, type: 'activeBalance' });
411
+ // Earned for each boost token
412
+ for (const token of data.boostTokens) {
413
+ stage3Contracts.push({
414
+ address: data.vaultAddress,
415
+ abi: REWARDS_ABI,
416
+ functionName: 'earned',
417
+ args: [userAddress, token],
418
+ });
419
+ userDataMapping.push({ vaultIndex: i, type: 'earned', token });
420
+ }
421
+ }
422
+ // Initialize user data map
423
+ for (let i = 0; i < stage1Data.length; i++) {
424
+ userDataMap[i] = {
425
+ activeBalance: 0n,
426
+ scalePendingByToken: {},
427
+ earnedByToken: {},
428
+ };
429
+ }
430
+ if (stage3Contracts.length > 0) {
431
+ const stage3Results = await client.multicall({
432
+ contracts: stage3Contracts,
433
+ allowFailure: true,
434
+ });
435
+ // Parse Stage 3 results
436
+ for (let i = 0; i < userDataMapping.length; i++) {
437
+ const mapping = userDataMapping[i];
438
+ const result = stage3Results[i];
439
+ if (result.status === 'success') {
440
+ if (mapping.type === 'activeBalance') {
441
+ userDataMap[mapping.vaultIndex].activeBalance =
442
+ result.result;
443
+ }
444
+ else if (mapping.type === 'earned' && mapping.token) {
445
+ userDataMap[mapping.vaultIndex].earnedByToken[mapping.token.toLowerCase()] = result.result;
446
+ }
447
+ }
448
+ }
449
+ }
450
+ // Stage 3b: Calculate Scale pending rewards via storage slots
451
+ // JSON-RPC batching is enabled in the client, so Promise.all will batch automatically
452
+ // First, read gauge controller addresses from vault storage
453
+ const gaugeControllerSlot = (0, viem_1.toHex)(BigInt(FACTOR_GAUGE_SLOT) + 2n, {
454
+ size: 32,
455
+ });
456
+ const gaugeControllerResults = await Promise.all(stage1Data.map((data) => client.getStorageAt({
457
+ address: data.vaultAddress,
458
+ slot: gaugeControllerSlot,
459
+ })));
460
+ const gaugeControllers = gaugeControllerResults.map((result) => {
461
+ if (!result ||
462
+ result ===
463
+ '0x0000000000000000000000000000000000000000000000000000000000000000') {
464
+ return null;
465
+ }
466
+ return ('0x' + result.slice(-40));
467
+ });
468
+ // Get gauge controller reward data for all vaults
469
+ const gaugeControllerContracts = stage1Data
470
+ .map((data, i) => {
471
+ const gc = gaugeControllers[i];
472
+ if (!gc)
473
+ return null;
474
+ return {
475
+ address: gc,
476
+ abi: GAUGE_CONTROLLER_ABI,
477
+ functionName: 'rewardData',
478
+ args: [data.vaultAddress],
479
+ };
480
+ })
481
+ .filter((c) => c !== null);
482
+ const gcIndexMap = [];
483
+ stage1Data.forEach((_, i) => {
484
+ if (gaugeControllers[i]) {
485
+ gcIndexMap.push(i);
486
+ }
487
+ });
488
+ let gcRewardDataResults = [];
489
+ if (gaugeControllerContracts.length > 0) {
490
+ gcRewardDataResults = await client.multicall({
491
+ contracts: gaugeControllerContracts,
492
+ allowFailure: true,
493
+ });
494
+ }
495
+ const gcRewardData = {};
496
+ for (let i = 0; i < gcRewardDataResults.length; i++) {
497
+ const vaultIndex = gcIndexMap[i];
498
+ const result = gcRewardDataResults[i];
499
+ if (result.status === 'success' && result.result) {
500
+ const [fctrPerSec, accumulatedFctr, lastUpdated, incentiveEndsAt] = result.result;
501
+ gcRewardData[vaultIndex] = {
502
+ fctrPerSec,
503
+ accumulatedFctr,
504
+ lastUpdated,
505
+ incentiveEndsAt,
506
+ };
507
+ }
508
+ }
509
+ // Read storage slots for reward state and user reward
510
+ const storageReadPromises = [];
511
+ for (let i = 0; i < stage1Data.length; i++) {
512
+ const data = stage1Data[i];
513
+ for (const token of data.scaleTokens) {
514
+ const rewardStateBaseSlot = (0, viem_1.toHex)(BigInt(REWARD_MANAGER_SLOT) + 2n, {
515
+ size: 32,
516
+ });
517
+ const rewardStateSlot = getMappingSlot(token, rewardStateBaseSlot);
518
+ const userRewardBaseSlot = (0, viem_1.toHex)(BigInt(REWARD_MANAGER_SLOT) + 1n, {
519
+ size: 32,
520
+ });
521
+ const userRewardSlot = getNestedMappingSlot(token, userAddress, userRewardBaseSlot);
522
+ storageReadPromises.push(Promise.all([
523
+ client.getStorageAt({
524
+ address: data.vaultAddress,
525
+ slot: rewardStateSlot,
526
+ }),
527
+ client.getStorageAt({
528
+ address: data.vaultAddress,
529
+ slot: userRewardSlot,
530
+ }),
531
+ ]).then(([rewardStateData, userRewardData]) => ({
532
+ vaultIndex: i,
533
+ token,
534
+ rewardStateData,
535
+ userRewardData,
536
+ })));
537
+ }
538
+ }
539
+ const storageResults = await Promise.all(storageReadPromises);
540
+ // Calculate Scale pending rewards for each vault/token
541
+ const WAD = 10n ** 18n;
542
+ const INITIAL_REWARD_INDEX = 1n;
543
+ const currentTime = BigInt(Math.floor(Date.now() / 1000));
544
+ for (const storageResult of storageResults) {
545
+ const { vaultIndex, token, rewardStateData, userRewardData } = storageResult;
546
+ const tokenKey = token.toLowerCase();
547
+ const gcData = gcRewardData[vaultIndex];
548
+ const totalActiveSupply = stage1Data[vaultIndex].totalActiveSupply;
549
+ const userActiveBalance = userDataMap[vaultIndex].activeBalance;
550
+ if (!gcData) {
551
+ userDataMap[vaultIndex].scalePendingByToken[tokenKey] = 0n;
552
+ continue;
553
+ }
554
+ // Parse packed structs
555
+ const rewardStateValue = BigInt(rewardStateData || '0x0');
556
+ const globalIndex = rewardStateValue & ((1n << 128n) - 1n);
557
+ const userRewardValue = BigInt(userRewardData || '0x0');
558
+ const userIndex = userRewardValue & ((1n << 128n) - 1n);
559
+ const userAccrued = userRewardValue >> 128n;
560
+ // Calculate new accumulated from gauge controller
561
+ const effectiveTime = currentTime < gcData.incentiveEndsAt
562
+ ? currentTime
563
+ : gcData.incentiveEndsAt;
564
+ let newAccumulatedFromController = gcData.accumulatedFctr;
565
+ if (effectiveTime > gcData.lastUpdated) {
566
+ newAccumulatedFromController +=
567
+ gcData.fctrPerSec * (effectiveTime - gcData.lastUpdated);
568
+ }
569
+ // Calculate new global index
570
+ let index = globalIndex;
571
+ if (index === 0n)
572
+ index = INITIAL_REWARD_INDEX;
573
+ if (totalActiveSupply > 0n) {
574
+ index += (newAccumulatedFromController * WAD) / totalActiveSupply;
575
+ }
576
+ // Calculate user reward
577
+ let effectiveUserIndex = userIndex;
578
+ if (effectiveUserIndex === 0n)
579
+ effectiveUserIndex = INITIAL_REWARD_INDEX;
580
+ const deltaIndex = index > effectiveUserIndex ? index - effectiveUserIndex : 0n;
581
+ const rewardDelta = (userActiveBalance * deltaIndex) / WAD;
582
+ const pendingRewards = userAccrued + rewardDelta;
583
+ userDataMap[vaultIndex].scalePendingByToken[tokenKey] = pendingRewards;
584
+ }
585
+ }
586
+ // Build final results with formatted values
587
+ const results = [];
588
+ for (let i = 0; i < stage1Data.length; i++) {
589
+ const data = stage1Data[i];
590
+ const rawRewardData = vaultRewardData[i];
591
+ // Build Scale reward tokens with metadata
592
+ const scaleTokenInfos = data.scaleTokens.map((t) => {
593
+ const tokenKey = t.toLowerCase();
594
+ const meta = tokenMetadata[tokenKey] || { symbol: null, decimals: 18 };
595
+ return {
596
+ address: tokenKey,
597
+ symbol: meta.symbol,
598
+ decimals: meta.decimals,
599
+ };
600
+ });
601
+ // Build Boost reward tokens with metadata
602
+ const boostTokenInfos = data.boostTokens.map((t) => {
603
+ const tokenKey = t.toLowerCase();
604
+ const meta = tokenMetadata[tokenKey] || { symbol: null, decimals: 18 };
605
+ return {
606
+ address: tokenKey,
607
+ symbol: meta.symbol,
608
+ decimals: meta.decimals,
609
+ };
610
+ });
611
+ // Format Scale rewards data
612
+ const scaleRewardsData = {};
613
+ for (const [tokenKey, raw] of Object.entries(rawRewardData.scaleRewardsData)) {
614
+ const decimals = tokenMetadata[tokenKey]?.decimals ?? 18;
615
+ scaleRewardsData[tokenKey] = {
616
+ periodFinish: raw.periodFinish,
617
+ periodFinishDate: raw.periodFinish > 0n
618
+ ? new Date(Number(raw.periodFinish) * 1000)
619
+ : null,
620
+ rewardRate: raw.rewardRate,
621
+ rewardRateFmt: formatWithDecimals(raw.rewardRate, decimals),
622
+ lastUpdateTime: raw.lastUpdateTime,
623
+ rewardPerTokenStored: raw.rewardPerTokenStored,
624
+ rewardForDuration: raw.rewardForDuration,
625
+ rewardForDurationFmt: formatWithDecimals(raw.rewardForDuration, decimals),
626
+ };
627
+ }
628
+ // Format Boost rewards data
629
+ const boostRewardsData = {};
630
+ for (const [tokenKey, raw] of Object.entries(rawRewardData.boostRewardsData)) {
631
+ const decimals = tokenMetadata[tokenKey]?.decimals ?? 18;
632
+ boostRewardsData[tokenKey] = {
633
+ periodFinish: raw.periodFinish,
634
+ periodFinishDate: raw.periodFinish > 0n
635
+ ? new Date(Number(raw.periodFinish) * 1000)
636
+ : null,
637
+ rewardRate: raw.rewardRate,
638
+ rewardRateFmt: formatWithDecimals(raw.rewardRate, decimals),
639
+ lastUpdateTime: raw.lastUpdateTime,
640
+ rewardPerTokenStored: raw.rewardPerTokenStored,
641
+ rewardForDuration: raw.rewardForDuration,
642
+ rewardForDurationFmt: formatWithDecimals(raw.rewardForDuration, decimals),
643
+ };
644
+ }
645
+ const result = {
646
+ vaultAddress: data.vaultAddress,
647
+ chainId: vaults[i].chainId,
648
+ scale: {
649
+ rewardTokens: scaleTokenInfos,
650
+ totalActiveSupply: data.totalActiveSupply,
651
+ totalActiveSupplyFmt: formatWithDecimals(data.totalActiveSupply, 18), // vault shares are 18 decimals
652
+ rewardsData: scaleRewardsData,
653
+ },
654
+ boost: {
655
+ rewardTokens: boostTokenInfos,
656
+ rewardsData: boostRewardsData,
657
+ },
658
+ };
659
+ if (userAddress && userDataMap[i]) {
660
+ const userData = userDataMap[i];
661
+ // Format Scale pending rewards
662
+ const scalePendingByTokenFmt = {};
663
+ for (const [tokenKey, pending] of Object.entries(userData.scalePendingByToken)) {
664
+ const decimals = tokenMetadata[tokenKey]?.decimals ?? 18;
665
+ scalePendingByTokenFmt[tokenKey] = formatWithDecimals(pending, decimals);
666
+ }
667
+ // Format Boost earned rewards
668
+ const earnedByTokenFmt = {};
669
+ for (const [tokenKey, earned] of Object.entries(userData.earnedByToken)) {
670
+ const decimals = tokenMetadata[tokenKey]?.decimals ?? 18;
671
+ earnedByTokenFmt[tokenKey] = formatWithDecimals(earned, decimals);
672
+ }
673
+ result.user = {
674
+ activeBalance: userData.activeBalance,
675
+ activeBalanceFmt: formatWithDecimals(userData.activeBalance, 18), // vault shares are 18 decimals
676
+ scalePendingByToken: userData.scalePendingByToken,
677
+ scalePendingByTokenFmt,
678
+ earnedByToken: userData.earnedByToken,
679
+ earnedByTokenFmt,
680
+ };
681
+ }
682
+ results.push(result);
683
+ }
684
+ return results;
685
+ }
686
+ //# sourceMappingURL=rewards-multicall.js.map