@glowlabs-org/utils 0.2.23 → 0.2.26

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.
@@ -6,6 +6,10 @@ import type {
6
6
  RegionWithMetadata,
7
7
  ActivationConfig,
8
8
  CreateRegionPayload,
9
+ CreateKickstarterPayload,
10
+ Kickstarter,
11
+ ActivationEvent,
12
+ KickstarterCreateResponse,
9
13
  } from "../types";
10
14
 
11
15
  // ---------------------------------------------------------------------------
@@ -41,6 +45,7 @@ export function RegionRouter(baseUrl: string) {
41
45
  let cachedRegions: Region[] = [];
42
46
  let isLoading = false;
43
47
  let isCreatingRegion = false;
48
+ let isCreatingKickstarter = false;
44
49
 
45
50
  // -------------------------------------------------------------------------
46
51
  // Queries
@@ -70,6 +75,20 @@ export function RegionRouter(baseUrl: string) {
70
75
  }
71
76
  };
72
77
 
78
+ const fetchActivationEvents = async (
79
+ regionId?: number
80
+ ): Promise<ActivationEvent[]> => {
81
+ try {
82
+ const query = typeof regionId === "number" ? `?regionId=${regionId}` : "";
83
+ const data = await request<{ events: ActivationEvent[] }>(
84
+ `/regions/activation-events${query}`
85
+ );
86
+ return data.events ?? [];
87
+ } catch (error) {
88
+ throw new Error(parseApiError(error));
89
+ }
90
+ };
91
+
73
92
  // -------------------------------------------------------------------------
74
93
  // Mutations
75
94
  // -------------------------------------------------------------------------
@@ -90,6 +109,40 @@ export function RegionRouter(baseUrl: string) {
90
109
  }
91
110
  };
92
111
 
112
+ const createKickstarter = async (
113
+ payload: CreateKickstarterPayload
114
+ ): Promise<KickstarterCreateResponse> => {
115
+ isCreatingKickstarter = true;
116
+ try {
117
+ const data = await request<KickstarterCreateResponse>(
118
+ `/regions/kickstarters`,
119
+ {
120
+ method: "POST",
121
+ headers: { "Content-Type": "application/json" },
122
+ body: JSON.stringify(payload),
123
+ }
124
+ );
125
+ // A new region may have been created; refresh regions cache
126
+ await fetchRegions();
127
+ return data;
128
+ } catch (error) {
129
+ throw new Error(parseApiError(error));
130
+ } finally {
131
+ isCreatingKickstarter = false;
132
+ }
133
+ };
134
+
135
+ const fetchKickstarter = async (idOrSlug: string): Promise<Kickstarter> => {
136
+ try {
137
+ const data = await request<{ kickstarter: Kickstarter }>(
138
+ `/regions/kickstarters/${encodeURIComponent(idOrSlug)}`
139
+ );
140
+ return data.kickstarter;
141
+ } catch (error) {
142
+ throw new Error(parseApiError(error));
143
+ }
144
+ };
145
+
93
146
  // -------------------------------------------------------------------------
94
147
  // Helpers (derived)
95
148
  // -------------------------------------------------------------------------
@@ -136,8 +189,11 @@ export function RegionRouter(baseUrl: string) {
136
189
  // Data access
137
190
  fetchRegions,
138
191
  fetchActivationConfig,
192
+ fetchActivationEvents,
193
+ fetchKickstarter,
139
194
  getRegionByCode,
140
195
  createRegion,
196
+ createKickstarter,
141
197
 
142
198
  // Cached data & flags
143
199
  get regions() {
@@ -149,5 +205,8 @@ export function RegionRouter(baseUrl: string) {
149
205
  get isCreatingRegion() {
150
206
  return isCreatingRegion;
151
207
  },
208
+ get isCreatingKickstarter() {
209
+ return isCreatingKickstarter;
210
+ },
152
211
  } as const;
153
212
  }
@@ -1,7 +1,7 @@
1
1
  import axios from "axios";
2
2
  import { formatUnits, parseUnits } from "viem";
3
3
  import { MerkleTree } from "merkletreejs";
4
- import { BigNumber, ethers } from "ethers";
4
+ import { keccak256 } from "ethers";
5
5
  import { greaterThanMaxDeviation } from "./utils/greater-than-max-deviation";
6
6
  import { customToFixed } from "./utils/custom-to-fixed";
7
7
  import { HUB_URL } from "../../constants/urls";
@@ -202,22 +202,22 @@ export async function createWeeklyReportLegacy(args: CreateWeeklyReportArgs) {
202
202
  })
203
203
  );
204
204
 
205
- const tree = new MerkleTree(leaves, ethers.utils.keccak256, { sort: true });
205
+ const tree = new MerkleTree(leaves, keccak256, { sort: true });
206
206
  const root = tree.getHexRoot();
207
207
 
208
208
  const totalGlowWeightFinalizedLeavesSum = finalLeaves.reduce(
209
- (acc, { glowWeight }) => acc.add(glowWeight),
210
- ethers.BigNumber.from(0)
209
+ (acc, { glowWeight }) => acc + BigInt(glowWeight),
210
+ 0n
211
211
  );
212
212
  const totalGCCWeightFinalizedLeavesSum = finalLeaves.reduce(
213
- (acc, { usdgWeight }) => acc.add(usdgWeight),
214
- ethers.BigNumber.from(0)
213
+ (acc, { usdgWeight }) => acc + BigInt(usdgWeight),
214
+ 0n
215
215
  );
216
216
 
217
- if (totalGlowWeightFinalizedLeavesSum.toBigInt() > MAX_WEIGHT) {
217
+ if (totalGlowWeightFinalizedLeavesSum > MAX_WEIGHT) {
218
218
  throw new Error("Total glow weight is greater than max weight");
219
219
  }
220
- if (totalGCCWeightFinalizedLeavesSum.toBigInt() > MAX_WEIGHT) {
220
+ if (totalGCCWeightFinalizedLeavesSum > MAX_WEIGHT) {
221
221
  throw new Error("Total gcc weight is greater than max weight");
222
222
  }
223
223
 
@@ -257,14 +257,12 @@ export async function createWeeklyReportLegacy(args: CreateWeeklyReportArgs) {
257
257
  };
258
258
  });
259
259
 
260
- let forLoopedGlowWeightSum: ethers.BigNumber = ethers.BigNumber.from(0);
260
+ let forLoopedGlowWeightSum = 0n;
261
261
  for (const leafProof of farmsWithMerkleProofs) {
262
- forLoopedGlowWeightSum = forLoopedGlowWeightSum.add(
263
- BigNumber.from(leafProof.glowWeight)
264
- );
262
+ forLoopedGlowWeightSum += BigInt(leafProof.glowWeight);
265
263
  }
266
264
 
267
- if (!forLoopedGlowWeightSum.eq(totalGlowWeightFinalizedLeavesSum)) {
265
+ if (forLoopedGlowWeightSum !== totalGlowWeightFinalizedLeavesSum) {
268
266
  console.error("Glow Weight Sum Mismatch:");
269
267
  console.error("Sum from loop:", forLoopedGlowWeightSum.toString());
270
268
  console.error(
@@ -274,14 +272,12 @@ export async function createWeeklyReportLegacy(args: CreateWeeklyReportArgs) {
274
272
  throw new Error("Glow weight sum is not equal");
275
273
  }
276
274
 
277
- let forLoopedGCCWeightSum: ethers.BigNumber = ethers.BigNumber.from(0);
275
+ let forLoopedGCCWeightSum = 0n;
278
276
  for (const leafProof of farmsWithMerkleProofs) {
279
- forLoopedGCCWeightSum = forLoopedGCCWeightSum.add(
280
- BigNumber.from(leafProof.usdgWeight)
281
- );
277
+ forLoopedGCCWeightSum += BigInt(leafProof.usdgWeight);
282
278
  }
283
279
 
284
- if (!forLoopedGCCWeightSum.eq(totalGCCWeightFinalizedLeavesSum)) {
280
+ if (forLoopedGCCWeightSum !== totalGCCWeightFinalizedLeavesSum) {
285
281
  console.error("USDG Weight Sum Mismatch:");
286
282
  console.error("Sum from loop:", forLoopedGCCWeightSum.toString());
287
283
  console.error(
@@ -597,25 +593,25 @@ export async function createWeeklyReport({
597
593
  );
598
594
 
599
595
  // Build the Merkle tree.
600
- const merkleTree = new MerkleTree(hashedLeaves, ethers.utils.keccak256, {
596
+ const merkleTree = new MerkleTree(hashedLeaves, keccak256, {
601
597
  sort: true, // Ensure consistent tree structure.
602
598
  });
603
599
  const merkleRoot = merkleTree.getHexRoot(); // Get the root hash.
604
600
 
605
601
  // Calculate the total weights across all finalized leaves.
606
602
  const totalGlowWeight = finalizedLeaves.reduce(
607
- (acc, { glowWeight }) => acc.add(glowWeight),
608
- ethers.BigNumber.from(0)
603
+ (acc, { glowWeight }) => acc + BigInt(glowWeight),
604
+ 0n
609
605
  );
610
606
  const totalUSDGWeight = finalizedLeaves.reduce(
611
- (acc, { usdgWeight }) => acc.add(usdgWeight),
612
- ethers.BigNumber.from(0)
607
+ (acc, { usdgWeight }) => acc + BigInt(usdgWeight),
608
+ 0n
613
609
  );
614
610
 
615
611
  // Total Weight Overflow Checks: Ensure sums don't exceed maximum.
616
- if (totalGlowWeight.toBigInt() > MAX_WEIGHT)
612
+ if (totalGlowWeight > MAX_WEIGHT)
617
613
  throw new Error("Total glow weight overflow");
618
- if (totalUSDGWeight.toBigInt() > MAX_WEIGHT)
614
+ if (totalUSDGWeight > MAX_WEIGHT)
619
615
  throw new Error("Total USDG weight overflow");
620
616
 
621
617
  // Generate Merkle proofs for each leaf and verify them.
@@ -636,10 +632,10 @@ export async function createWeeklyReport({
636
632
 
637
633
  // Verify that summing weights from leavesWithProofs matches the earlier reduce calculation.
638
634
  const glowSumProofLoop = leavesWithProofs.reduce(
639
- (acc, l) => acc.add(l.glowWeight),
640
- ethers.BigNumber.from(0)
635
+ (acc, l) => acc + BigInt(l.glowWeight),
636
+ 0n
641
637
  );
642
- if (!glowSumProofLoop.eq(totalGlowWeight)) {
638
+ if (glowSumProofLoop !== totalGlowWeight) {
643
639
  console.error("Glow Weight Sum Mismatch (Post-73):");
644
640
  console.error("Sum from loop:", glowSumProofLoop.toString());
645
641
  console.error("Sum from reduce:", totalGlowWeight.toString());
@@ -647,10 +643,10 @@ export async function createWeeklyReport({
647
643
  }
648
644
 
649
645
  const usdgSumProofLoop = leavesWithProofs.reduce(
650
- (acc, l) => acc.add(l.usdgWeight),
651
- ethers.BigNumber.from(0)
646
+ (acc, l) => acc + BigInt(l.usdgWeight),
647
+ 0n
652
648
  );
653
- if (!usdgSumProofLoop.eq(totalUSDGWeight)) {
649
+ if (usdgSumProofLoop !== totalUSDGWeight) {
654
650
  console.error("USDG Weight Sum Mismatch (Post-73):");
655
651
  console.error("Sum from loop:", usdgSumProofLoop.toString());
656
652
  console.error("Sum from reduce:", totalUSDGWeight.toString());
@@ -665,10 +661,7 @@ export async function createWeeklyReport({
665
661
 
666
662
  // Convert total USDG weight back to human-readable decimal for deviation check.
667
663
  const totalUSDGWeightHuman = new Decimal(
668
- formatUnits(
669
- BigInt(totalUSDGWeight.toString()),
670
- USDG_WEIGHT_DECIMAL_PRECISION
671
- )
664
+ formatUnits(totalUSDGWeight, USDG_WEIGHT_DECIMAL_PRECISION)
672
665
  );
673
666
  // Check deviation between total adjusted credits used and the final sum of USDG weights.
674
667
  if (
@@ -689,10 +682,7 @@ export async function createWeeklyReport({
689
682
 
690
683
  // Convert total Glow weight back to human-readable decimal.
691
684
  const totalGlowWeightHuman = new Decimal(
692
- formatUnits(
693
- BigInt(totalGlowWeight.toString()),
694
- GLOW_WEIGHT_DECIMAL_PRECISION
695
- )
685
+ formatUnits(totalGlowWeight, GLOW_WEIGHT_DECIMAL_PRECISION)
696
686
  );
697
687
  // Sum the original weekly protocol fee payments from farm data.
698
688
  const totalProtocolFeePayments = farms.reduce(
@@ -723,7 +713,7 @@ export async function createWeeklyReport({
723
713
  weekNumber: week,
724
714
  totalCreditsProduced: formatUnits(totalCreditsProduced18dp, 18), // Original reported total
725
715
  totalCreditsProducedBN: totalCreditsProduced18dp.toString(),
726
- totalGlowWeightInFinalized: totalGlowWeight.toString(), // Total weight as BigNumber string
716
+ totalGlowWeightInFinalized: totalGlowWeight.toString(), // Total weight as bigint string
727
717
  totalGlowWeightHuman: totalGlowWeightHuman.toString(), // Total weight as human-readable decimal
728
718
  totalUSDGWeightInFinalized: totalUSDGWeight.toString(),
729
719
  totalUSDGWeightHuman: totalUSDGWeightHuman.toString(),
@@ -1,4 +1,4 @@
1
- import { ethers } from "ethers";
1
+ import { solidityPackedKeccak256 } from "ethers";
2
2
  const leafTypes = ["address", "uint256", "uint256"];
3
3
 
4
4
  type HashLeafArgs = {
@@ -11,7 +11,7 @@ export function hashLeaf({
11
11
  glowWeight,
12
12
  usdcWeight,
13
13
  }: HashLeafArgs): string {
14
- const hash = ethers.utils.solidityKeccak256(leafTypes, [
14
+ const hash = solidityPackedKeccak256(leafTypes, [
15
15
  address,
16
16
  glowWeight,
17
17
  usdcWeight,
@@ -1,4 +1,4 @@
1
- import { BigNumber, constants, Contract, providers } from "ethers";
1
+ import { Contract, MaxUint256, type BigNumberish, type Signer } from "ethers";
2
2
  import { FORWARDER_ABI } from "../abis/forwarderABI";
3
3
  import { ERC20_ABI } from "../abis/erc20.abi";
4
4
  import { getAddresses } from "../../constants/addresses";
@@ -26,7 +26,7 @@ export type Currency = "USDC" | "GLW" | "USDG";
26
26
 
27
27
  // Forward parameters interface
28
28
  export interface ForwardParams {
29
- amount: BigNumber;
29
+ amount: bigint;
30
30
  userAddress: string;
31
31
  type: ForwardType;
32
32
  currency?: Currency;
@@ -63,17 +63,14 @@ function parseEthersError(error: unknown): string {
63
63
 
64
64
  // Type-guard style helper to ensure a signer exists throughout the rest of the function.
65
65
  function assertSigner(
66
- maybeSigner: providers.JsonRpcSigner | undefined
67
- ): asserts maybeSigner is providers.JsonRpcSigner {
66
+ maybeSigner: Signer | undefined
67
+ ): asserts maybeSigner is Signer {
68
68
  if (!maybeSigner) {
69
69
  throw new Error(ForwarderError.SIGNER_NOT_AVAILABLE);
70
70
  }
71
71
  }
72
72
 
73
- export function useForwarder(
74
- signer: providers.JsonRpcSigner | undefined,
75
- CHAIN_ID: number
76
- ) {
73
+ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
77
74
  // Use dynamic addresses based on chain configuration
78
75
  const ADDRESSES = getAddresses(CHAIN_ID);
79
76
 
@@ -171,7 +168,7 @@ export function useForwarder(
171
168
  async function checkTokenAllowance(
172
169
  owner: string,
173
170
  currency: Currency = "USDC"
174
- ): Promise<BigNumber> {
171
+ ): Promise<bigint> {
175
172
  assertSigner(signer);
176
173
 
177
174
  try {
@@ -179,7 +176,7 @@ export function useForwarder(
179
176
  if (!tokenContract)
180
177
  throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
181
178
 
182
- const allowance: BigNumber = await tokenContract.allowance(
179
+ const allowance: bigint = await tokenContract.allowance(
183
180
  owner,
184
181
  ADDRESSES.FORWARDER
185
182
  );
@@ -197,7 +194,7 @@ export function useForwarder(
197
194
  async function checkTokenBalance(
198
195
  owner: string,
199
196
  currency: Currency = "USDC"
200
- ): Promise<BigNumber> {
197
+ ): Promise<bigint> {
201
198
  assertSigner(signer);
202
199
 
203
200
  try {
@@ -205,7 +202,7 @@ export function useForwarder(
205
202
  if (!tokenContract)
206
203
  throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
207
204
 
208
- const balance: BigNumber = await tokenContract.balanceOf(owner);
205
+ const balance: bigint = await tokenContract.balanceOf(owner);
209
206
  return balance;
210
207
  } catch (error) {
211
208
  throw new Error(parseEthersError(error));
@@ -218,7 +215,7 @@ export function useForwarder(
218
215
  * @param currency The currency to approve
219
216
  */
220
217
  async function approveToken(
221
- amount: BigNumber,
218
+ amount: BigNumberish,
222
219
  currency: Currency = "USDC"
223
220
  ): Promise<boolean> {
224
221
  assertSigner(signer);
@@ -276,16 +273,16 @@ export function useForwarder(
276
273
  }
277
274
 
278
275
  // Check allowance and approve if necessary
279
- const allowance: BigNumber = await tokenContract.allowance(
276
+ const allowance: bigint = await tokenContract.allowance(
280
277
  owner,
281
278
  ADDRESSES.FORWARDER
282
279
  );
283
280
 
284
- if (allowance.lt(amount)) {
281
+ if (allowance < amount) {
285
282
  try {
286
283
  const approveTx = await tokenContract.approve(
287
284
  ADDRESSES.FORWARDER,
288
- constants.MaxUint256
285
+ MaxUint256
289
286
  );
290
287
  await approveTx.wait();
291
288
  } catch (approveError) {
@@ -311,26 +308,26 @@ export function useForwarder(
311
308
  throw new Error(`Unsupported currency for forwarding: ${currency}`);
312
309
  }
313
310
 
314
- // Run a static call first to surface any revert reason
311
+ // Run a static call first to surface any revert reason (ethers v6)
315
312
  try {
316
- // If PayAuditFees, call forward() even for USDC
317
313
  if (!isAuditFees && currency === "USDC") {
318
- await forwarderContract.callStatic.swapUSDCAndForwardUSDG(
319
- amount,
320
- ADDRESSES.FOUNDATION_WALLET,
321
- message,
322
- { from: owner }
323
- );
314
+ await forwarderContract
315
+ .getFunction("swapUSDCAndForwardUSDG")
316
+ .staticCall(amount, ADDRESSES.FOUNDATION_WALLET, message, {
317
+ from: owner,
318
+ });
324
319
  } else {
325
- await forwarderContract.callStatic.forward(
326
- tokenAddress,
327
- isAuditFees
328
- ? ADDRESSES.AUDIT_FEE_WALLET
329
- : ADDRESSES.FOUNDATION_WALLET,
330
- amount,
331
- message,
332
- { from: owner }
333
- );
320
+ await forwarderContract
321
+ .getFunction("forward")
322
+ .staticCall(
323
+ tokenAddress,
324
+ isAuditFees
325
+ ? ADDRESSES.AUDIT_FEE_WALLET
326
+ : ADDRESSES.FOUNDATION_WALLET,
327
+ amount,
328
+ message,
329
+ { from: owner }
330
+ );
334
331
  }
335
332
  } catch (staticError) {
336
333
  throw new Error(parseEthersError(staticError));
@@ -339,13 +336,13 @@ export function useForwarder(
339
336
  // Execute the forward transaction
340
337
  let tx;
341
338
  if (!isAuditFees && currency === "USDC") {
342
- tx = await forwarderContract.swapUSDCAndForwardUSDG(
339
+ tx = await forwarderContract.getFunction("swapUSDCAndForwardUSDG")(
343
340
  amount,
344
341
  ADDRESSES.FOUNDATION_WALLET,
345
342
  message
346
343
  );
347
344
  } else {
348
- tx = await forwarderContract.forward(
345
+ tx = await forwarderContract.getFunction("forward")(
349
346
  tokenAddress,
350
347
  isAuditFees
351
348
  ? ADDRESSES.AUDIT_FEE_WALLET
@@ -368,7 +365,7 @@ export function useForwarder(
368
365
  * Forward tokens for protocol fee payment and GCTL minting with staking
369
366
  */
370
367
  async function payProtocolFeeAndMintGCTLAndStake(
371
- amount: BigNumber,
368
+ amount: bigint,
372
369
  userAddress: string,
373
370
  applicationId: string,
374
371
  regionId?: number,
@@ -397,7 +394,7 @@ export function useForwarder(
397
394
  * Forward tokens for protocol fee payment only
398
395
  */
399
396
  async function payProtocolFee(
400
- amount: BigNumber,
397
+ amount: bigint,
401
398
  userAddress: string,
402
399
  applicationId: string,
403
400
  currency: Currency = "USDC"
@@ -417,7 +414,7 @@ export function useForwarder(
417
414
  * Forward USDC to mint GCTL and stake to a region
418
415
  */
419
416
  async function mintGCTLAndStake(
420
- amount: BigNumber,
417
+ amount: bigint,
421
418
  userAddress: string,
422
419
  regionId?: number,
423
420
  currency: Currency = "USDC"
@@ -444,7 +441,7 @@ export function useForwarder(
444
441
  * Forward USDC to mint GCTL (existing functionality, keeping for compatibility)
445
442
  */
446
443
  async function mintGCTL(
447
- amount: BigNumber,
444
+ amount: bigint,
448
445
  userAddress: string,
449
446
  currency: Currency = "USDC"
450
447
  ): Promise<string> {
@@ -469,7 +466,7 @@ export function useForwarder(
469
466
  * Forward tokens to pay audit fees (USDC only, calls forward())
470
467
  */
471
468
  async function payAuditFees(
472
- amount: BigNumber,
469
+ amount: bigint,
473
470
  userAddress: string,
474
471
  applicationId: string
475
472
  ): Promise<string> {
@@ -488,7 +485,7 @@ export function useForwarder(
488
485
  * Forward tokens to buy a solar farm
489
486
  */
490
487
  async function buySolarFarm(
491
- amount: BigNumber,
488
+ amount: bigint,
492
489
  userAddress: string,
493
490
  farmId: string,
494
491
  currency: Currency = "USDC"
@@ -547,27 +544,31 @@ export function useForwarder(
547
544
  );
548
545
  }
549
546
 
550
- const gasPrice = await signer.getGasPrice();
551
- const estimatedGas =
552
- // If PayAuditFees, always use forward() even with USDC
547
+ const feeData = await signer.provider?.getFeeData();
548
+ const gasPrice =
549
+ feeData?.gasPrice ?? feeData?.maxFeePerGas ?? (0n as bigint);
550
+ if (gasPrice === 0n) {
551
+ throw new Error("Could not fetch gas price to estimate cost.");
552
+ }
553
+ const estimatedGas: bigint =
553
554
  !isAuditFees && currency === "USDC"
554
- ? await forwarderContract.estimateGas.swapUSDCAndForwardUSDG(
555
- amount,
556
- ADDRESSES.FOUNDATION_WALLET,
557
- message
558
- )
559
- : await forwarderContract.estimateGas.forward(
560
- tokenAddress,
561
- isAuditFees
562
- ? ADDRESSES.AUDIT_FEE_WALLET
563
- : ADDRESSES.FOUNDATION_WALLET,
564
- amount,
565
- message
566
- );
567
- const estimatedCost = estimatedGas.mul(gasPrice);
555
+ ? await forwarderContract
556
+ .getFunction("swapUSDCAndForwardUSDG")
557
+ .estimateGas(amount, ADDRESSES.FOUNDATION_WALLET, message)
558
+ : await forwarderContract
559
+ .getFunction("forward")
560
+ .estimateGas(
561
+ tokenAddress,
562
+ isAuditFees
563
+ ? ADDRESSES.AUDIT_FEE_WALLET
564
+ : ADDRESSES.FOUNDATION_WALLET,
565
+ amount,
566
+ message
567
+ );
568
+ const estimatedCost: bigint = estimatedGas * gasPrice;
568
569
 
569
570
  if (ethPriceInUSD) {
570
- const estimatedCostInEth = formatEther(estimatedCost.toBigInt());
571
+ const estimatedCostInEth = formatEther(estimatedCost);
571
572
  const estimatedCostInUSD = (
572
573
  parseFloat(estimatedCostInEth) * ethPriceInUSD
573
574
  ).toFixed(2);
@@ -588,7 +589,7 @@ export function useForwarder(
588
589
  * @param recipient Address to mint USDC to
589
590
  */
590
591
  async function mintTestUSDC(
591
- amount: BigNumber,
592
+ amount: bigint,
592
593
  recipient: string
593
594
  ): Promise<string> {
594
595
  assertSigner(signer);
@@ -152,6 +152,70 @@ export interface RegionMetadata {
152
152
  flag?: string;
153
153
  }
154
154
 
155
+ // ----------------------------- Kickstarters ---------------------------------
156
+ export type KickstarterStatus =
157
+ | "draft"
158
+ | "awaiting-kickoff"
159
+ | "collecting-support"
160
+ | "ready-to-activate"
161
+ | "completed"
162
+ | "expired"
163
+ | "cancelled"
164
+ | "suspended";
165
+
166
+ export interface CreateKickstarterPayload {
167
+ creatorWallet: string;
168
+ regionName: string;
169
+ isUs?: boolean;
170
+ title: string;
171
+ description: string;
172
+ stakeTargetGctl: string; // decimal string
173
+ requiredFarmCount?: number; // defaults server-side
174
+ requiredInstallerCount?: number; // defaults server-side
175
+ }
176
+
177
+ export interface KickstarterCreateResponse {
178
+ success: true;
179
+ id: string;
180
+ }
181
+
182
+ export interface Kickstarter {
183
+ id: string;
184
+ regionId: number;
185
+ creatorWallet: string;
186
+ title: string;
187
+ description: string;
188
+ slug: string;
189
+ status: KickstarterStatus | string;
190
+ createdAt: string; // ISO 8601
191
+ updatedAt: string; // ISO 8601
192
+ publishedAt?: string; // ISO 8601
193
+ completedAt?: string; // ISO 8601
194
+ cancelledAt?: string; // ISO 8601
195
+ expiredAt?: string; // ISO 8601
196
+ deadline: string; // ISO 8601
197
+ stakeTargetGctl: string; // decimal string
198
+ requiredFarmCount: number;
199
+ requiredInstallerCount: number;
200
+ stakeContributed: boolean;
201
+ farmProvided: boolean;
202
+ installerCertified: boolean;
203
+ kickoffTransferTxId?: string;
204
+ kickoffMintTxId?: string;
205
+ kickoffStakeEventId?: string;
206
+ }
207
+
208
+ export interface ActivationEvent {
209
+ id: string;
210
+ regionId: number;
211
+ epoch: number;
212
+ stakeThresholdMet: boolean;
213
+ solarFarmRequirementMet: boolean;
214
+ installerRequirementMet: boolean;
215
+ activated: boolean;
216
+ ts: string; // ISO 8601
217
+ }
218
+
155
219
  // ---------------------------------------------------------------------------
156
220
  // Barrel exports (convenience)
157
221
  // ---------------------------------------------------------------------------
@@ -1,4 +1,4 @@
1
- import { ethers } from "ethers";
1
+ import { solidityPackedKeccak256 } from "ethers";
2
2
  const leafTypes = ["address", "uint256", "uint256"];
3
3
 
4
4
  type HashLeafArgs = {
@@ -11,7 +11,7 @@ export function hashLeaf({
11
11
  glowWeight,
12
12
  usdcWeight,
13
13
  }: HashLeafArgs): string {
14
- const hash = ethers.utils.solidityKeccak256(leafTypes, [
14
+ const hash = solidityPackedKeccak256(leafTypes, [
15
15
  address,
16
16
  glowWeight,
17
17
  usdcWeight,