@glowlabs-org/utils 0.2.92 → 0.2.94

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.
@@ -3,6 +3,11 @@ export const FORWARDER_ABI = [
3
3
  inputs: [
4
4
  { internalType: "contract USDG", name: "_usdg", type: "address" },
5
5
  { internalType: "contract IERC20", name: "_usdc", type: "address" },
6
+ {
7
+ internalType: "contract CounterfactualHolderFactory",
8
+ name: "_cfhFactory",
9
+ type: "address",
10
+ },
6
11
  ],
7
12
  stateMutability: "payable",
8
13
  type: "constructor",
@@ -58,6 +63,11 @@ export const FORWARDER_ABI = [
58
63
  { internalType: "address", name: "token", type: "address" },
59
64
  { internalType: "address", name: "to", type: "address" },
60
65
  { internalType: "uint256", name: "amount", type: "uint256" },
66
+ {
67
+ internalType: "bool",
68
+ name: "sendToCounterfactualWallet",
69
+ type: "bool",
70
+ },
61
71
  { internalType: "string", name: "message", type: "string" },
62
72
  ],
63
73
  name: "forward",
@@ -65,6 +75,19 @@ export const FORWARDER_ABI = [
65
75
  stateMutability: "nonpayable",
66
76
  type: "function",
67
77
  },
78
+ {
79
+ inputs: [],
80
+ name: "i_CFHFactory",
81
+ outputs: [
82
+ {
83
+ internalType: "contract CounterfactualHolderFactory",
84
+ name: "",
85
+ type: "address",
86
+ },
87
+ ],
88
+ stateMutability: "view",
89
+ type: "function",
90
+ },
68
91
  {
69
92
  inputs: [],
70
93
  name: "i_USDC",
@@ -90,6 +113,11 @@ export const FORWARDER_ABI = [
90
113
  inputs: [
91
114
  { internalType: "uint256", name: "amount", type: "uint256" },
92
115
  { internalType: "address", name: "to", type: "address" },
116
+ {
117
+ internalType: "bool",
118
+ name: "sendToCounterfactualWallet",
119
+ type: "bool",
120
+ },
93
121
  { internalType: "string", name: "message", type: "string" },
94
122
  ],
95
123
  name: "swapUSDCAndForwardUSDG",
@@ -19,6 +19,22 @@ import type {
19
19
  RestakeRequest,
20
20
  } from "../types";
21
21
 
22
+ export interface PayProtocolDepositUsingStakedControlRequest {
23
+ wallet: string;
24
+ regionId: number;
25
+ applicationId: string;
26
+ amount: string;
27
+ signature: string;
28
+ deadline: string;
29
+ nonce: string;
30
+ }
31
+
32
+ export interface PayProtocolDepositUsingStakedControlResponse {
33
+ success: true;
34
+ farmId: string;
35
+ applicationId: string;
36
+ }
37
+
22
38
  // --------------------------------------------------------------------------
23
39
 
24
40
  /**
@@ -229,6 +245,7 @@ export function ControlRouter(baseUrl: string) {
229
245
  let isUnstaking = false;
230
246
  let isRestaking = false;
231
247
  let isRetryingFailedOperation = false;
248
+ let isPayingProtocolDepositUsingStakedControl = false;
232
249
 
233
250
  const stakeGctl = async (stakeRequest: StakeRequest): Promise<boolean> => {
234
251
  isStaking = true;
@@ -299,6 +316,28 @@ export function ControlRouter(baseUrl: string) {
299
316
  }
300
317
  };
301
318
 
319
+ const payProtocolDepositUsingStakedControl = async (
320
+ paymentRequest: PayProtocolDepositUsingStakedControlRequest
321
+ ): Promise<PayProtocolDepositUsingStakedControlResponse> => {
322
+ isPayingProtocolDepositUsingStakedControl = true;
323
+ try {
324
+ const response =
325
+ await request<PayProtocolDepositUsingStakedControlResponse>(
326
+ `/pay-protocol-deposit-staked`,
327
+ {
328
+ method: "POST",
329
+ headers: { "Content-Type": "application/json" },
330
+ body: JSON.stringify(paymentRequest),
331
+ }
332
+ );
333
+ return response;
334
+ } catch (error) {
335
+ throw new Error(parseApiError(error));
336
+ } finally {
337
+ isPayingProtocolDepositUsingStakedControl = false;
338
+ }
339
+ };
340
+
302
341
  // --------------------------- Public API ----------------------------------
303
342
  return {
304
343
  // Queries
@@ -322,6 +361,7 @@ export function ControlRouter(baseUrl: string) {
322
361
  unstakeGctl,
323
362
  restakeGctl,
324
363
  retryFailedOperation,
364
+ payProtocolDepositUsingStakedControl,
325
365
 
326
366
  // Processing flags
327
367
  get isStaking() {
@@ -336,5 +376,8 @@ export function ControlRouter(baseUrl: string) {
336
376
  get isRetryingFailedOperation() {
337
377
  return isRetryingFailedOperation;
338
378
  },
379
+ get isPayingProtocolDepositUsingStakedControl() {
380
+ return isPayingProtocolDepositUsingStakedControl;
381
+ },
339
382
  } as const;
340
383
  }
@@ -3,7 +3,7 @@ import { FORWARDER_ABI } from "../abis/forwarderABI";
3
3
  import { ERC20_ABI } from "../abis/erc20.abi";
4
4
  import { getAddresses } from "../../constants/addresses";
5
5
  import { formatEther } from "viem";
6
- import { PendingTransferType } from "../types";
6
+ import { PendingTransferType, TRANSFER_TYPES } from "../types";
7
7
 
8
8
  export enum ForwarderError {
9
9
  CONTRACT_NOT_AVAILABLE = "Contract not available",
@@ -96,55 +96,55 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
96
96
  } = params;
97
97
 
98
98
  switch (type) {
99
- case "PayProtocolFeeAndMintGCTLAndStake":
99
+ case TRANSFER_TYPES.PayProtocolFeeAndMintGCTLAndStake:
100
100
  if (!applicationId) {
101
101
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
102
102
  }
103
103
  return `PayProtocolFeeAndMintGCTLAndStake::${applicationId}`;
104
104
 
105
- case "PayProtocolFee":
105
+ case TRANSFER_TYPES.PayProtocolFee:
106
106
  if (!applicationId) {
107
107
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
108
108
  }
109
109
  return `PayProtocolFee::${applicationId}`;
110
110
 
111
- case "SponsorProtocolFee":
111
+ case TRANSFER_TYPES.SponsorProtocolFee:
112
112
  if (!applicationId) {
113
113
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
114
114
  }
115
115
  return `SponsorProtocolFee::${applicationId}`;
116
116
 
117
- case "SponsorProtocolFeeAndMintGCTLAndStake":
117
+ case TRANSFER_TYPES.SponsorProtocolFeeAndMintGCTLAndStake:
118
118
  if (!applicationId) {
119
119
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
120
120
  }
121
121
  return `SponsorProtocolFeeAndMintGCTLAndStake::${applicationId}`;
122
122
 
123
- case "MintGCTLAndStake":
123
+ case TRANSFER_TYPES.MintGCTLAndStake:
124
124
  if (!regionId) {
125
125
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
126
126
  }
127
127
  return `MintGCTLAndStake::${regionId}`;
128
128
 
129
- case "MintGCTL":
129
+ case TRANSFER_TYPES.MintGCTL:
130
130
  if (!userAddress) {
131
131
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
132
132
  }
133
133
  return `MintGCTL::${userAddress}`;
134
134
 
135
- case "BuySolarFarm":
135
+ case TRANSFER_TYPES.BuySolarFarm:
136
136
  if (!farmId) {
137
137
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
138
138
  }
139
139
  return `BuySolarFarm::${farmId}`;
140
140
 
141
- case "PayAuditFees":
141
+ case TRANSFER_TYPES.PayAuditFees:
142
142
  if (!applicationId) {
143
143
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
144
144
  }
145
145
  return `PayAuditFees::${applicationId}`;
146
146
 
147
- case "CommitKickstarter":
147
+ case TRANSFER_TYPES.CommitKickstarter:
148
148
  if (!kickstarterId) {
149
149
  throw new Error(ForwarderError.MISSING_REQUIRED_PARAMS);
150
150
  }
@@ -288,13 +288,14 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
288
288
  const message = constructForwardMessage(params);
289
289
 
290
290
  // Special handling: PayAuditFees can ONLY be USDC, and must call forward()
291
- const isAuditFees = params.type === "PayAuditFees";
291
+ const isAuditFees = params.type === TRANSFER_TYPES.PayAuditFees;
292
292
  if (isAuditFees && currency !== "USDC") {
293
293
  throw new Error("PayAuditFees only supports USDC");
294
294
  }
295
295
 
296
296
  // CommitKickstarter supports only USDC or USDG (GLW not allowed)
297
- const isCommitKickstarter = params.type === "CommitKickstarter";
297
+ const isCommitKickstarter =
298
+ params.type === TRANSFER_TYPES.CommitKickstarter;
298
299
  if (isCommitKickstarter && currency === "GLW") {
299
300
  throw new Error("CommitKickstarter supports only USDC or USDG");
300
301
  }
@@ -340,7 +341,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
340
341
  if (!isAuditFees && currency === "USDC") {
341
342
  await forwarderContract
342
343
  .getFunction("swapUSDCAndForwardUSDG")
343
- .staticCall(amount, ADDRESSES.FOUNDATION_WALLET, message, {
344
+ .staticCall(amount, ADDRESSES.FOUNDATION_WALLET, true, message, {
344
345
  from: owner,
345
346
  });
346
347
  } else {
@@ -352,6 +353,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
352
353
  ? ADDRESSES.AUDIT_FEE_WALLET
353
354
  : ADDRESSES.FOUNDATION_WALLET,
354
355
  amount,
356
+ true,
355
357
  message,
356
358
  { from: owner }
357
359
  );
@@ -366,6 +368,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
366
368
  tx = await forwarderContract.getFunction("swapUSDCAndForwardUSDG")(
367
369
  amount,
368
370
  ADDRESSES.FOUNDATION_WALLET,
371
+ true,
369
372
  message
370
373
  );
371
374
  } else {
@@ -375,6 +378,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
375
378
  ? ADDRESSES.AUDIT_FEE_WALLET
376
379
  : ADDRESSES.FOUNDATION_WALLET,
377
380
  amount,
381
+ true,
378
382
  message
379
383
  );
380
384
  }
@@ -410,7 +414,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
410
414
  return forwardTokens({
411
415
  amount,
412
416
  userAddress,
413
- type: "PayProtocolFeeAndMintGCTLAndStake",
417
+ type: TRANSFER_TYPES.PayProtocolFeeAndMintGCTLAndStake,
414
418
  currency,
415
419
  applicationId,
416
420
  regionId,
@@ -436,7 +440,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
436
440
  return forwardTokens({
437
441
  amount,
438
442
  userAddress,
439
- type: "SponsorProtocolFeeAndMintGCTLAndStake",
443
+ type: TRANSFER_TYPES.SponsorProtocolFeeAndMintGCTLAndStake,
440
444
  currency,
441
445
  applicationId,
442
446
  });
@@ -456,7 +460,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
456
460
  return forwardTokens({
457
461
  amount,
458
462
  userAddress,
459
- type: "PayProtocolFee",
463
+ type: TRANSFER_TYPES.PayProtocolFee,
460
464
  currency,
461
465
  applicationId,
462
466
  });
@@ -476,7 +480,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
476
480
  return forwardTokens({
477
481
  amount,
478
482
  userAddress,
479
- type: "SponsorProtocolFee",
483
+ type: TRANSFER_TYPES.SponsorProtocolFee,
480
484
  currency,
481
485
  applicationId,
482
486
  });
@@ -503,7 +507,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
503
507
  return forwardTokens({
504
508
  amount,
505
509
  userAddress,
506
- type: "MintGCTLAndStake",
510
+ type: TRANSFER_TYPES.MintGCTLAndStake,
507
511
  currency,
508
512
  regionId,
509
513
  });
@@ -529,7 +533,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
529
533
  return forwardTokens({
530
534
  amount,
531
535
  userAddress,
532
- type: "MintGCTL",
536
+ type: TRANSFER_TYPES.MintGCTL,
533
537
  currency,
534
538
  });
535
539
  }
@@ -547,7 +551,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
547
551
  return forwardTokens({
548
552
  amount,
549
553
  userAddress,
550
- type: "PayAuditFees",
554
+ type: TRANSFER_TYPES.PayAuditFees,
551
555
  currency: "USDC",
552
556
  applicationId,
553
557
  });
@@ -567,7 +571,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
567
571
  return forwardTokens({
568
572
  amount,
569
573
  userAddress,
570
- type: "BuySolarFarm",
574
+ type: TRANSFER_TYPES.BuySolarFarm,
571
575
  currency,
572
576
  farmId,
573
577
  });
@@ -591,7 +595,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
591
595
  return forwardTokens({
592
596
  amount,
593
597
  userAddress,
594
- type: "CommitKickstarter",
598
+ type: TRANSFER_TYPES.CommitKickstarter,
595
599
  currency,
596
600
  kickstarterId,
597
601
  });
@@ -614,7 +618,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
614
618
  throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
615
619
 
616
620
  const { amount, currency = "USDC" } = params;
617
- const isAuditFees = params.type === "PayAuditFees";
621
+ const isAuditFees = params.type === TRANSFER_TYPES.PayAuditFees;
618
622
  if (isAuditFees && currency !== "USDC") {
619
623
  throw new Error("PayAuditFees only supports USDC");
620
624
  }
@@ -650,7 +654,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
650
654
  !isAuditFees && currency === "USDC"
651
655
  ? await forwarderContract
652
656
  .getFunction("swapUSDCAndForwardUSDG")
653
- .estimateGas(amount, ADDRESSES.FOUNDATION_WALLET, message)
657
+ .estimateGas(amount, ADDRESSES.FOUNDATION_WALLET, true, message)
654
658
  : await forwarderContract
655
659
  .getFunction("forward")
656
660
  .estimateGas(
@@ -659,6 +663,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
659
663
  ? ADDRESSES.AUDIT_FEE_WALLET
660
664
  : ADDRESSES.FOUNDATION_WALLET,
661
665
  amount,
666
+ true,
662
667
  message
663
668
  );
664
669
  const estimatedCost: bigint = estimatedGas * gasPrice;
@@ -51,20 +51,23 @@ export interface PendingTransfer {
51
51
  kickstarterId?: string;
52
52
  }
53
53
 
54
- export const TRANSFER_TYPES = [
55
- "PayProtocolFeeAndMintGCTLAndStake",
56
- "PayProtocolFee",
57
- "PayAuditFees",
58
- "CommitKickstarter",
59
- "MintGCTLAndStake",
60
- "MintGCTL",
61
- "BuySolarFarm",
62
- "SponsorProtocolFee",
63
- "SponsorProtocolFeeAndMintGCTLAndStake",
64
- ] as const;
54
+ export const TRANSFER_TYPES = {
55
+ PayProtocolFeeAndMintGCTLAndStake: "PayProtocolFeeAndMintGCTLAndStake",
56
+ PayProtocolFee: "PayProtocolFee",
57
+ PayAuditFees: "PayAuditFees",
58
+ CommitKickstarter: "CommitKickstarter",
59
+ MintGCTLAndStake: "MintGCTLAndStake",
60
+ MintGCTL: "MintGCTL",
61
+ BuySolarFarm: "BuySolarFarm",
62
+ SponsorProtocolFee: "SponsorProtocolFee",
63
+ SponsorProtocolFeeAndMintGCTLAndStake:
64
+ "SponsorProtocolFeeAndMintGCTLAndStake",
65
+ PayProtocolDepositUsingStakedControl: "PayProtocolDepositUsingStakedControl",
66
+ } as const;
65
67
 
66
68
  // Pending transfer type filter for listing endpoint
67
- export type PendingTransferType = (typeof TRANSFER_TYPES)[number];
69
+ export type PendingTransferType =
70
+ (typeof TRANSFER_TYPES)[keyof typeof TRANSFER_TYPES];
68
71
 
69
72
  export interface TransferDetails extends PendingTransfer {
70
73
  blockNumber: string;
@@ -1,4 +1,5 @@
1
- import { verifyTypedData, type Hex } from "viem";
1
+ import { TRANSFER_TYPES } from "src/browser";
2
+ import { verifyTypedData, type Hex, getAddress } from "viem";
2
3
  import z from "zod";
3
4
 
4
5
  export const stakeControlEIP712Domain = (chainId: number) => ({
@@ -57,6 +58,15 @@ export const commitKickstarterEIP712Types = {
57
58
  ],
58
59
  } as const;
59
60
 
61
+ export const payProtocolDepositUsingStakedControlEIP712Types = {
62
+ PayProtocolDepositUsingStakedControl: [
63
+ { name: "amount", type: "uint256" },
64
+ { name: "applicationId", type: "string" },
65
+ { name: "nonce", type: "uint256" },
66
+ { name: "deadline", type: "uint256" },
67
+ ],
68
+ } as const;
69
+
60
70
  // Separate request schemas for clarity between stake and unstake
61
71
  export const stakeSignatureRequestSchema = z.object({
62
72
  wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
@@ -105,6 +115,16 @@ export const commitKickstarterSignatureRequestSchema = z.object({
105
115
  deadline: z.string(),
106
116
  });
107
117
 
118
+ export const payProtocolDepositUsingStakedControlSignatureRequestSchema =
119
+ z.object({
120
+ wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
121
+ signature: z.string().regex(/^0x[a-fA-F0-9]{130}$/),
122
+ nonce: z.string(),
123
+ amount: z.string(),
124
+ applicationId: z.string(),
125
+ deadline: z.string(),
126
+ });
127
+
108
128
  export type StakeSignatureRequest = z.infer<typeof stakeSignatureRequestSchema>;
109
129
  export type UnstakeUnlockSignatureRequest = z.infer<
110
130
  typeof unstakeUnlockSignatureRequestSchema
@@ -118,6 +138,9 @@ export type RestakeSignatureRequest = z.infer<
118
138
  export type CommitKickstarterSignatureRequest = z.infer<
119
139
  typeof commitKickstarterSignatureRequestSchema
120
140
  >;
141
+ export type PayProtocolDepositUsingStakedControlSignatureRequest = z.infer<
142
+ typeof payProtocolDepositUsingStakedControlSignatureRequestSchema
143
+ >;
121
144
 
122
145
  export type StakeMessage = {
123
146
  nonce: bigint;
@@ -156,6 +179,13 @@ export type CommitKickstarterMessage = {
156
179
  deadline: bigint;
157
180
  };
158
181
 
182
+ export type PayProtocolDepositUsingStakedControlMessage = {
183
+ amount: bigint;
184
+ applicationId: string;
185
+ nonce: bigint;
186
+ deadline: bigint;
187
+ };
188
+
159
189
  export type SignatureValidationReason =
160
190
  | "deadline_expired"
161
191
  | "signature_failed"
@@ -196,6 +226,11 @@ type CommitKickstarterMessageInput = Pick<
196
226
  "nonce" | "amount" | "kickstarterId" | "deadline"
197
227
  >;
198
228
 
229
+ type PayProtocolDepositUsingStakedControlMessageInput = Pick<
230
+ PayProtocolDepositUsingStakedControlSignatureRequest,
231
+ "amount" | "applicationId" | "nonce" | "deadline"
232
+ >;
233
+
199
234
  export function buildStakeMessage(req: StakeMessageInput): StakeMessage {
200
235
  const nonce = BigInt(req.nonce);
201
236
  const amount = BigInt(req.amount);
@@ -266,6 +301,20 @@ export function buildCommitKickstarterMessage(
266
301
  return { nonce, amount, kickstarterId, deadline };
267
302
  }
268
303
 
304
+ export function buildPayProtocolDepositUsingStakedControlMessage(
305
+ req: PayProtocolDepositUsingStakedControlMessageInput
306
+ ): PayProtocolDepositUsingStakedControlMessage {
307
+ const amount = BigInt(req.amount);
308
+ const nonce = BigInt(req.nonce);
309
+ const deadline = BigInt(req.deadline);
310
+ const applicationId = req.applicationId;
311
+ if (amount < 0n) throw new Error("Amount must be non-negative");
312
+ if (nonce < 0n) throw new Error("Nonce must be non-negative");
313
+ if (deadline < 0n) throw new Error("Deadline must be non-negative");
314
+ if (!applicationId) throw new Error("applicationId must be non-empty");
315
+ return { amount, applicationId, nonce, deadline };
316
+ }
317
+
269
318
  // Helper to validate the signature using viem
270
319
  export async function validateStakeSignature(
271
320
  input: StakeSignatureRequest,
@@ -428,3 +477,35 @@ export async function validateCommitKickstarterSignature(
428
477
  return { valid: false, recovered: null, reason: "signature_failed" };
429
478
  }
430
479
  }
480
+
481
+ export async function validatePayProtocolDepositUsingStakedControlSignature(
482
+ input: PayProtocolDepositUsingStakedControlSignatureRequest,
483
+ domain: ReturnType<
484
+ typeof stakeControlEIP712Domain
485
+ > = stakeControlEIP712Domain(1)
486
+ ): Promise<SignatureValidationResult> {
487
+ const message = buildPayProtocolDepositUsingStakedControlMessage({
488
+ amount: input.amount,
489
+ applicationId: input.applicationId,
490
+ nonce: input.nonce,
491
+ deadline: input.deadline,
492
+ });
493
+ if (isDeadlineExpired(message.deadline)) {
494
+ return { valid: false, recovered: null, reason: "deadline_expired" };
495
+ }
496
+ try {
497
+ const verified = await verifyTypedData({
498
+ address: getAddress(input.wallet as Hex),
499
+ domain,
500
+ types: payProtocolDepositUsingStakedControlEIP712Types,
501
+ primaryType: TRANSFER_TYPES.PayProtocolDepositUsingStakedControl,
502
+ message,
503
+ signature: input.signature as Hex,
504
+ });
505
+ return verified
506
+ ? { valid: true, recovered: input.wallet, reason: null }
507
+ : { valid: false, recovered: null, reason: "signer_mismatch" };
508
+ } catch (_) {
509
+ return { valid: false, recovered: null, reason: "signature_failed" };
510
+ }
511
+ }