@glowlabs-org/utils 0.1.4 → 0.2.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.
@@ -149,11 +149,6 @@ export async function createWeeklyReportLegacy(args: CreateWeeklyReportArgs) {
149
149
  usdgWeight <= 0 &&
150
150
  rewardSplit.usdgSplitPercent > 0
151
151
  ) {
152
- console.log(
153
- farm.carbonCreditsProduced,
154
- usdgWeight,
155
- rewardSplit.usdgSplitPercent
156
- );
157
152
  throw new Error(
158
153
  "USDG weight is less than 0 and carbon credits produced is greater than 0"
159
154
  );
@@ -163,11 +158,6 @@ export async function createWeeklyReportLegacy(args: CreateWeeklyReportArgs) {
163
158
  glowWeight <= 0 &&
164
159
  rewardSplit.glowSplitPercent > 0
165
160
  ) {
166
- console.log(
167
- farm.weeklyPayment,
168
- glowWeight,
169
- rewardSplit.glowSplitPercent
170
- );
171
161
  throw new Error(
172
162
  "Glow weight is less than 0 and weekly payment is greater than 0"
173
163
  );
@@ -178,11 +168,6 @@ export async function createWeeklyReportLegacy(args: CreateWeeklyReportArgs) {
178
168
  usdgWeight <= 0 &&
179
169
  rewardSplit.usdgSplitPercent > 0
180
170
  ) {
181
- console.log(
182
- farm.carbonCreditsProduced,
183
- usdgWeight.toString(),
184
- rewardSplit.usdgSplitPercent
185
- );
186
171
  throw new Error(
187
172
  `USDG weight is less than or equal to 0 and carbon credits produced is greater than 0 for farm ${farm.shortId}`
188
173
  );
@@ -192,11 +177,6 @@ export async function createWeeklyReportLegacy(args: CreateWeeklyReportArgs) {
192
177
  glowWeight <= 0 &&
193
178
  rewardSplit.glowSplitPercent > 0
194
179
  ) {
195
- console.log(
196
- farm.weeklyPayment,
197
- glowWeight.toString(),
198
- rewardSplit.glowSplitPercent
199
- );
200
180
  throw new Error(
201
181
  `Glow weight is less than or equal to 0 and weekly payment is greater than 0 for farm ${farm.shortId}`
202
182
  );
@@ -374,6 +354,7 @@ export async function createWeeklyReport({
374
354
  }: CreateWeeklyReportArgs) {
375
355
  const map = new Map<string, MerkleLeaf>();
376
356
 
357
+ // Fetch active farms for the week (source of truth) and audits for the week.
377
358
  const [apiResponse, auditsRes] = await Promise.all([
378
359
  fetchFarmsForWeek(week, gcaUrls, apiUrl),
379
360
  axios.get<Audit[]>(
@@ -381,38 +362,119 @@ export async function createWeeklyReport({
381
362
  ),
382
363
  ]);
383
364
 
384
- const farms = apiResponse.filteredFarms;
385
- const audits = auditsRes.data;
365
+ const farms = apiResponse.filteredFarms; // List of farms active this week.
366
+ const audits = auditsRes.data; // Audits potentially containing adjusted credits.
386
367
  const rawData = apiResponse.rawData;
387
368
 
369
+ // Map to store the final adjusted credit (as bigint) for each active farm shortId.
388
370
  const shortIdToAdjustedCredit = new Map<string, bigint>();
371
+ // Set to track shortIds that have been assigned a credit from a valid audit to prevent duplicates.
372
+ const processedAuditShortIds = new Set<string>();
373
+ // Set of shortIds from the active farms list for quick lookup.
374
+ const farmShortIds = new Set(farms.map((f) => String(f.shortId)));
389
375
 
376
+ // Process each audit to extract adjusted credit values for active farms.
390
377
  for (const audit of audits) {
391
378
  const activeShortIds = audit.activeShortIds ?? [];
392
379
  const adjustedCredit =
393
380
  audit.summary?.carbonFootprintAndProduction?.adjustedWeeklyCarbonCredit?.trim();
394
381
 
382
+ // Basic validation: Ensure the audit has a valid numeric credit and lists associated shortIds.
395
383
  if (!adjustedCredit || !NUMERIC_REGEX.test(adjustedCredit))
396
384
  throw new Error(
397
385
  `Invalid adjustedWeeklyCarbonCredit for audit ${audit.id}`
398
386
  );
399
387
  if (activeShortIds.length === 0)
400
- throw new Error(`audit ${audit.id} has empty activeShortIds`);
388
+ throw new Error(`Audit ${audit.id} has empty activeShortIds`);
389
+
390
+ // Separate the shortIds listed in the audit into those that are active this week and those that are not.
391
+ const auditActiveFarmSids: string[] = [];
392
+ const auditInactiveFarmSids: string[] = [];
393
+ for (const sid of activeShortIds) {
394
+ const sidString = String(sid);
395
+ if (farmShortIds.has(sidString)) {
396
+ auditActiveFarmSids.push(sidString);
397
+ } else {
398
+ auditInactiveFarmSids.push(sidString);
399
+ }
400
+ }
401
+
402
+ // --- Audit Validity Checks ---
403
+
404
+ // Data Integrity Check: An audit for credit distribution should not mix active and inactive farms for the current week.
405
+ if (auditActiveFarmSids.length > 0 && auditInactiveFarmSids.length > 0) {
406
+ throw new Error(
407
+ `Audit ${
408
+ audit.id
409
+ } for week ${week} contains a mix of active farms (${auditActiveFarmSids.join(
410
+ ", "
411
+ )}) and inactive farms (${auditInactiveFarmSids.join(
412
+ ", "
413
+ )}). Audits should only contain active farms when distributing credits.`
414
+ );
415
+ }
401
416
 
402
- const splitValue = new Decimal(adjustedCredit).div(activeShortIds.length);
417
+ // Skip Audit: If the audit only contains shortIds that are not active this week, it's irrelevant for this report.
418
+ if (auditActiveFarmSids.length === 0) {
419
+ continue; // Move to the next audit.
420
+ }
421
+
422
+ // --- Process Valid Audit for Active Farms ---
423
+
424
+ // Calculate the credit share per active farm within this audit.
425
+ const splitValue = new Decimal(adjustedCredit).div(
426
+ auditActiveFarmSids.length // Divide only by the count of *active* farms found in this audit.
427
+ );
428
+
429
+ // Sanity Check: Ensure the calculated split is not zero.
403
430
  if (splitValue.isZero())
404
431
  throw new Error(
405
- `Zero adjustedWeeklyCarbonCredit split in audit ${audit.id}`
432
+ `Zero adjustedWeeklyCarbonCredit split for active farms in audit ${audit.id}`
406
433
  );
407
434
 
435
+ // Convert the decimal split value to a bigint using the defined precision.
408
436
  const splitBigInt = toBigInt(splitValue, USDG_WEIGHT_DECIMAL_PRECISION);
409
- for (const sid of activeShortIds)
410
- shortIdToAdjustedCredit.set(String(sid), splitBigInt);
437
+
438
+ // Dust Check: Ensure that the conversion to bigint didn't truncate the value to zero.
439
+ if (splitBigInt === BigInt(0)) {
440
+ throw new Error(
441
+ `Adjusted credit split for audit ${
442
+ audit.id
443
+ } resulted in zero BigInt after conversion for precision ${USDG_WEIGHT_DECIMAL_PRECISION}. Original split value: ${splitValue.toString()}. This might indicate dust loss.`
444
+ );
445
+ }
446
+
447
+ // Distribute the calculated split value to each active farm associated with this audit.
448
+ for (const sidString of auditActiveFarmSids) {
449
+ // Duplicate Check: Ensure this shortId hasn't already received credit from another valid audit this week.
450
+ if (processedAuditShortIds.has(sidString)) {
451
+ throw new Error(
452
+ `ShortId ${sidString} found in multiple valid audits for week ${week}`
453
+ );
454
+ }
455
+ processedAuditShortIds.add(sidString); // Mark this shortId as processed.
456
+
457
+ // Store the adjusted credit for this active farm.
458
+ shortIdToAdjustedCredit.set(sidString, splitBigInt);
459
+ }
460
+ } // End of loop through audits.
461
+
462
+ // Final Validation: Ensure every farm considered active for this week received an adjusted credit value from a valid audit.
463
+ for (const farmId of farmShortIds) {
464
+ if (!shortIdToAdjustedCredit.has(farmId)) {
465
+ throw new Error(
466
+ `Farm ${farmId} is active for week ${week} but has no corresponding adjusted credit found in any valid audit.`
467
+ );
468
+ }
411
469
  }
412
470
 
413
- let totalCreditsProduced18dp = BigInt(0);
471
+ // --- Start Calculation of Merkle Tree Weights ---
472
+
473
+ let totalCreditsProduced18dp = BigInt(0); // Raw total from farm data, for reporting/comparison.
414
474
 
475
+ // Iterate through each active farm to calculate its contribution to the Merkle tree leaves.
415
476
  for (const farm of farms) {
477
+ // Basic checks for farm data consistency.
416
478
  if (farm.status === "Unassigned") {
417
479
  throw new Error(`farm ${farm.shortId} is unassigned`);
418
480
  }
@@ -429,11 +491,13 @@ export async function createWeeklyReport({
429
491
  );
430
492
  }
431
493
 
494
+ // Accumulate the reported carbon credits (18 decimal places).
432
495
  totalCreditsProduced18dp += parseUnits(
433
496
  customToFixed(farm.carbonCreditsProduced, 18),
434
497
  18
435
498
  );
436
499
 
500
+ // Validate reward split percentages add up to 1 (within tolerance).
437
501
  const sumGlow = farm.rewardSplits.reduce(
438
502
  (acc: number, r: any) => acc + r.glowSplitPercent,
439
503
  0
@@ -448,49 +512,62 @@ export async function createWeeklyReport({
448
512
  if (Math.abs(sumUSDG - 1) > percentTolerance)
449
513
  throw new Error(`USDG splits ≠1 for farm ${farm.shortId}`);
450
514
 
515
+ // Get the base GLOW payment for the farm, converted to bigint with appropriate precision.
451
516
  const glowBase = parseUnits(
452
517
  customToFixed(farm.weeklyPayment, GLOW_WEIGHT_DECIMAL_PRECISION),
453
518
  GLOW_WEIGHT_DECIMAL_PRECISION
454
519
  );
455
520
 
521
+ // Retrieve the pre-calculated adjusted credit for this farm. Should always exist due to prior checks.
456
522
  const adjustedCreditBigInt =
457
523
  shortIdToAdjustedCredit.get(String(farm.shortId)) ?? BigInt(0);
458
524
 
525
+ // Sanity check (likely redundant due to earlier checks, but safe).
459
526
  if (adjustedCreditBigInt <= BigInt(0))
460
527
  throw new Error(
461
528
  `Adjusted credit is less than or equal to 0 for farm ${farm.shortId}`
462
529
  );
463
530
 
531
+ // Process each reward split recipient for this farm.
464
532
  for (const split of farm.rewardSplits) {
533
+ // Calculate the GLOW weight for this recipient based on the farm's weekly payment and split percentage.
465
534
  const glowWeight = multiplyBigIntByDecimalPercentage(
466
535
  glowBase,
467
536
  GLOW_WEIGHT_DECIMAL_PRECISION,
468
537
  split.glowSplitPercent
469
538
  );
539
+ // Calculate the USDG weight for this recipient based on the farm's *adjusted* credit and split percentage.
470
540
  const usdgWeight = multiplyBigIntByDecimalPercentage(
471
541
  adjustedCreditBigInt,
472
542
  USDG_WEIGHT_DECIMAL_PRECISION,
473
543
  split.usdgSplitPercent
474
544
  );
475
545
 
546
+ // Dust Loss Checks: Ensure non-zero percentages didn't result in zero weight due to precision limits.
476
547
  if (
477
548
  split.usdgSplitPercent > 0 &&
478
549
  adjustedCreditBigInt > BigInt(0) &&
479
550
  usdgWeight === BigInt(0)
480
551
  )
481
- throw new Error(`USDG dust lost for farm ${farm.shortId}`);
552
+ throw new Error(
553
+ `USDG dust lost for farm ${farm.shortId} wallet ${split.walletAddress}`
554
+ );
482
555
  if (
483
556
  split.glowSplitPercent > 0 &&
484
557
  glowBase > BigInt(0) &&
485
558
  glowWeight === BigInt(0)
486
559
  )
487
- throw new Error(`Glow dust lost for farm ${farm.shortId}`);
560
+ throw new Error(
561
+ `Glow dust lost for farm ${farm.shortId} wallet ${split.walletAddress}`
562
+ );
488
563
 
564
+ // Overflow Check: Ensure individual leaf weights don't exceed the maximum allowed.
489
565
  if (glowWeight > MAX_WEIGHT || usdgWeight > MAX_WEIGHT)
490
566
  throw new Error(
491
567
  `Leaf weight overflow on wallet ${split.walletAddress}`
492
568
  );
493
569
 
570
+ // Accumulate the calculated weights into the map for the recipient wallet.
494
571
  accumulateLeafWeights(
495
572
  split.walletAddress,
496
573
  { wallet: split.walletAddress, glowWeight, usdgWeight },
@@ -499,27 +576,33 @@ export async function createWeeklyReport({
499
576
  }
500
577
  }
501
578
 
579
+ // --- Finalize Merkle Tree and Generate Report Data ---
580
+
581
+ // Convert the accumulated weights map into the final list of leaves for the tree.
502
582
  const finalizedLeaves: FinalizedLeaf[] = Array.from(map.values()).map(
503
583
  ({ wallet, glowWeight, usdgWeight }) => ({
504
584
  wallet,
505
- glowWeight: glowWeight.toString(),
585
+ glowWeight: glowWeight.toString(), // Convert BigInts to strings for hashing/reporting.
506
586
  usdgWeight: usdgWeight.toString(),
507
587
  })
508
588
  );
509
589
 
590
+ // Hash each leaf according to the defined structure.
510
591
  const hashedLeaves = finalizedLeaves.map((leaf) =>
511
592
  hashLeaf({
512
593
  address: leaf.wallet,
513
594
  glowWeight: leaf.glowWeight,
514
- usdcWeight: leaf.usdgWeight,
595
+ usdcWeight: leaf.usdgWeight, // Note: Parameter name inconsistency (usdc vs usdg)
515
596
  })
516
597
  );
517
598
 
599
+ // Build the Merkle tree.
518
600
  const merkleTree = new MerkleTree(hashedLeaves, ethers.utils.keccak256, {
519
- sort: true,
601
+ sort: true, // Ensure consistent tree structure.
520
602
  });
521
- const merkleRoot = merkleTree.getHexRoot();
603
+ const merkleRoot = merkleTree.getHexRoot(); // Get the root hash.
522
604
 
605
+ // Calculate the total weights across all finalized leaves.
523
606
  const totalGlowWeight = finalizedLeaves.reduce(
524
607
  (acc, { glowWeight }) => acc.add(glowWeight),
525
608
  ethers.BigNumber.from(0)
@@ -529,23 +612,29 @@ export async function createWeeklyReport({
529
612
  ethers.BigNumber.from(0)
530
613
  );
531
614
 
615
+ // Total Weight Overflow Checks: Ensure sums don't exceed maximum.
532
616
  if (totalGlowWeight.toBigInt() > MAX_WEIGHT)
533
617
  throw new Error("Total glow weight overflow");
534
618
  if (totalUSDGWeight.toBigInt() > MAX_WEIGHT)
535
619
  throw new Error("Total USDG weight overflow");
536
620
 
621
+ // Generate Merkle proofs for each leaf and verify them.
537
622
  const leavesWithProofs = finalizedLeaves.map((leaf) => {
538
623
  const hashed = hashLeaf({
539
624
  address: leaf.wallet,
540
625
  glowWeight: leaf.glowWeight,
541
- usdcWeight: leaf.usdgWeight,
626
+ usdcWeight: leaf.usdgWeight, // Note: Parameter name inconsistency
542
627
  });
543
628
  const proof = merkleTree.getHexProof(hashed);
544
629
  if (!merkleTree.verify(proof, hashed, merkleRoot))
630
+ // Verify proof against the calculated root.
545
631
  throw new Error("Invalid proof for " + leaf.wallet);
546
632
  return { ...leaf, proof };
547
633
  });
548
634
 
635
+ // --- Sanity Checks on Final Weights ---
636
+
637
+ // Verify that summing weights from leavesWithProofs matches the earlier reduce calculation.
549
638
  const glowSumProofLoop = leavesWithProofs.reduce(
550
639
  (acc, l) => acc.add(l.glowWeight),
551
640
  ethers.BigNumber.from(0)
@@ -568,22 +657,25 @@ export async function createWeeklyReport({
568
657
  throw new Error("USDG sum mismatch");
569
658
  }
570
659
 
660
+ // Calculate the total expected credits based on the adjusted values used.
571
661
  const totalExpectedCredits = farms.reduce((acc: Decimal, f: any) => {
572
662
  const adj = shortIdToAdjustedCredit.get(String(f.shortId)) ?? BigInt(0);
573
663
  return acc.plus(fromBigInt(adj, USDG_WEIGHT_DECIMAL_PRECISION));
574
664
  }, new Decimal(0));
575
665
 
666
+ // Convert total USDG weight back to human-readable decimal for deviation check.
576
667
  const totalUSDGWeightHuman = new Decimal(
577
668
  formatUnits(
578
669
  BigInt(totalUSDGWeight.toString()),
579
670
  USDG_WEIGHT_DECIMAL_PRECISION
580
671
  )
581
672
  );
673
+ // Check deviation between total adjusted credits used and the final sum of USDG weights.
582
674
  if (
583
675
  greaterThanMaxDeviation(
584
676
  totalExpectedCredits.toNumber(),
585
677
  totalUSDGWeightHuman.toNumber(),
586
- 0.001
678
+ 0.001 // 0.1% tolerance
587
679
  )
588
680
  ) {
589
681
  console.error(
@@ -595,21 +687,24 @@ export async function createWeeklyReport({
595
687
  throw new Error("totalExpectedCredits vs USDG weight deviation >0.1% ");
596
688
  }
597
689
 
690
+ // Convert total Glow weight back to human-readable decimal.
598
691
  const totalGlowWeightHuman = new Decimal(
599
692
  formatUnits(
600
693
  BigInt(totalGlowWeight.toString()),
601
694
  GLOW_WEIGHT_DECIMAL_PRECISION
602
695
  )
603
696
  );
697
+ // Sum the original weekly protocol fee payments from farm data.
604
698
  const totalProtocolFeePayments = farms.reduce(
605
699
  (acc: number, f: any) => acc + f.weeklyPayment,
606
700
  0
607
701
  );
702
+ // Check deviation between total glow weight and total protocol fees paid.
608
703
  if (
609
704
  greaterThanMaxDeviation(
610
705
  totalGlowWeightHuman.toNumber(),
611
706
  totalProtocolFeePayments,
612
- 0.001
707
+ 0.001 // 0.1% tolerance
613
708
  )
614
709
  ) {
615
710
  console.error(
@@ -621,12 +716,15 @@ export async function createWeeklyReport({
621
716
  throw new Error("totalGlowWeight vs protocol fees deviation >0.1% ");
622
717
  }
623
718
 
719
+ // --- Prepare Output Data ---
720
+
721
+ // Key statistics for the report.
624
722
  const headlineStats = {
625
723
  weekNumber: week,
626
- totalCreditsProduced: formatUnits(totalCreditsProduced18dp, 18),
724
+ totalCreditsProduced: formatUnits(totalCreditsProduced18dp, 18), // Original reported total
627
725
  totalCreditsProducedBN: totalCreditsProduced18dp.toString(),
628
- totalGlowWeightInFinalized: totalGlowWeight.toString(),
629
- totalGlowWeightHuman: totalGlowWeightHuman.toString(),
726
+ totalGlowWeightInFinalized: totalGlowWeight.toString(), // Total weight as BigNumber string
727
+ totalGlowWeightHuman: totalGlowWeightHuman.toString(), // Total weight as human-readable decimal
630
728
  totalUSDGWeightInFinalized: totalUSDGWeight.toString(),
631
729
  totalUSDGWeightHuman: totalUSDGWeightHuman.toString(),
632
730
  root: merkleRoot,
@@ -634,6 +732,7 @@ export async function createWeeklyReport({
634
732
  usdgWeightDecimals: USDG_WEIGHT_DECIMAL_PRECISION,
635
733
  };
636
734
 
735
+ // List of shortIds and their final adjusted credit value (human-readable).
637
736
  const shortIdAdjustedList = Array.from(shortIdToAdjustedCredit.entries()).map(
638
737
  ([shortId, creditBigInt]) => ({
639
738
  shortId,
@@ -644,6 +743,7 @@ export async function createWeeklyReport({
644
743
  })
645
744
  );
646
745
 
746
+ // Detailed comparison between originally reported credits and adjusted credits for each farm.
647
747
  const creditsDeviationList = farms.map((farm: any) => {
648
748
  const adj = shortIdToAdjustedCredit.get(String(farm.shortId)) ?? BigInt(0);
649
749
  const adjusted = fromBigInt(adj, USDG_WEIGHT_DECIMAL_PRECISION);
@@ -651,6 +751,7 @@ export async function createWeeklyReport({
651
751
  let deviation: Decimal;
652
752
  let absDeviation: Decimal;
653
753
  let deviationPercent: Decimal;
754
+ // Handle division by zero if produced is zero.
654
755
  if (produced.isZero() && adjusted.isZero()) {
655
756
  deviation = new Decimal(0);
656
757
  absDeviation = new Decimal(0);
@@ -659,7 +760,7 @@ export async function createWeeklyReport({
659
760
  deviation = produced.minus(adjusted);
660
761
  absDeviation = deviation.abs();
661
762
  deviationPercent = deviation
662
- .div(produced.isZero() ? 1 : produced)
763
+ .div(produced.isZero() ? 1 : produced) // Avoid division by zero
663
764
  .mul(100);
664
765
  }
665
766
  return {
@@ -672,13 +773,14 @@ export async function createWeeklyReport({
672
773
  };
673
774
  });
674
775
 
776
+ // Return all calculated data.
675
777
  return {
676
778
  headlineStats,
677
- finalizedLeaves,
678
- leavesWithProofs,
679
- farms,
680
- rawData,
681
- shortIdAdjustedList,
682
- creditsDeviationList,
779
+ finalizedLeaves, // Leaves without proofs (for potential use cases)
780
+ leavesWithProofs, // Leaves with proofs (primary data for distribution)
781
+ farms, // Original filtered farm data
782
+ rawData, // Raw GCA responses
783
+ shortIdAdjustedList, // List of adjusted credits per farm
784
+ creditsDeviationList, // Detailed deviation report per farm
683
785
  };
684
786
  }