@glowlabs-org/utils 0.2.95 → 0.2.96

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,737 @@
1
+ import { Contract, type Signer } from "ethers";
2
+ import { OFFCHAIN_FRACTIONS_ABI } from "../abis/offchainFractions";
3
+ import { ERC20_ABI } from "../abis/erc20.abi";
4
+ import { getAddresses } from "../../constants/addresses";
5
+ import { formatEther } from "viem";
6
+
7
+ export enum OffchainFractionsError {
8
+ CONTRACT_NOT_AVAILABLE = "Contract not available",
9
+ SIGNER_NOT_AVAILABLE = "Signer not available",
10
+ UNKNOWN_ERROR = "Unknown error",
11
+ INVALID_PARAMETERS = "Invalid parameters",
12
+ FRACTION_NOT_FOUND = "Fraction not found",
13
+ INSUFFICIENT_BALANCE = "Insufficient balance",
14
+ INSUFFICIENT_ALLOWANCE = "Insufficient allowance",
15
+ }
16
+
17
+ // Fraction data structure matching the contract
18
+ export interface FractionData {
19
+ token: string;
20
+ expiration: number;
21
+ manuallyClosed: boolean;
22
+ minSharesToRaise: bigint;
23
+ useCounterfactualAddress: boolean;
24
+ claimedFromMinSharesToRaise: boolean;
25
+ owner: string;
26
+ step: bigint;
27
+ to: string;
28
+ soldSteps: bigint;
29
+ totalSteps: bigint;
30
+ }
31
+
32
+ // Parameters for creating a new fraction
33
+ export interface CreateFractionParams {
34
+ id: string; // bytes32 as hex string
35
+ token: string;
36
+ step: bigint; // Price per step in wei
37
+ totalSteps: bigint;
38
+ expiration: number; // Unix timestamp
39
+ to: string; // Recipient address
40
+ useCounterfactualAddress: boolean;
41
+ minSharesToRaise: bigint; // 0 for no minimum
42
+ }
43
+
44
+ // Parameters for buying fractions
45
+ export interface BuyFractionsParams {
46
+ creator: string;
47
+ id: string; // bytes32 as hex string
48
+ stepsToBuy: bigint;
49
+ minStepsToBuy: bigint;
50
+ }
51
+
52
+ // Utility to extract the most useful revert reason from an ethers error object
53
+ function parseEthersError(error: unknown): string {
54
+ if (!error) return "Unknown error";
55
+ const possibleError: any = error;
56
+
57
+ // If the error originates from a callStatic it will often be found at `error?.error?.body`
58
+ if (possibleError?.error?.body) {
59
+ try {
60
+ const body = JSON.parse(possibleError.error.body);
61
+ // Hardhat style errors
62
+ if (body?.error?.message) return body.error.message as string;
63
+ } catch {}
64
+ }
65
+
66
+ // Found on MetaMask/Alchemy shape errors
67
+ if (possibleError?.data?.message) return possibleError.data.message as string;
68
+ if (possibleError?.error?.message)
69
+ return possibleError.error.message as string;
70
+
71
+ // Standard ethers v5 message
72
+ if (possibleError?.reason) return possibleError.reason as string;
73
+ if (possibleError?.message) return possibleError.message as string;
74
+
75
+ return OffchainFractionsError.UNKNOWN_ERROR;
76
+ }
77
+
78
+ // Type-guard style helper to ensure a signer exists throughout the rest of the function.
79
+ function assertSigner(
80
+ maybeSigner: Signer | undefined
81
+ ): asserts maybeSigner is Signer {
82
+ if (!maybeSigner) {
83
+ throw new Error(OffchainFractionsError.SIGNER_NOT_AVAILABLE);
84
+ }
85
+ }
86
+
87
+ export function useOffchainFractions(
88
+ signer: Signer | undefined,
89
+ CHAIN_ID: number
90
+ ) {
91
+ // Use dynamic addresses based on chain configuration
92
+ const ADDRESSES = getAddresses(CHAIN_ID);
93
+
94
+ // Framework-agnostic processing flag
95
+ let isProcessing = false;
96
+ const setIsProcessing = (value: boolean) => {
97
+ isProcessing = value;
98
+ };
99
+
100
+ // Returns a contract instance for OffchainFractions
101
+ function getOffchainFractionsContract() {
102
+ assertSigner(signer);
103
+ return new Contract(
104
+ ADDRESSES.OFFCHAIN_FRACTIONS,
105
+ OFFCHAIN_FRACTIONS_ABI,
106
+ signer
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Get the appropriate token contract
112
+ */
113
+ function getTokenContract(tokenAddress: string) {
114
+ assertSigner(signer);
115
+ return new Contract(tokenAddress, ERC20_ABI, signer);
116
+ }
117
+
118
+ /**
119
+ * Check current token allowance for the offchain fractions contract
120
+ * @param owner The wallet address to check allowance for
121
+ * @param tokenAddress The token contract address
122
+ */
123
+ async function checkTokenAllowance(
124
+ owner: string,
125
+ tokenAddress: string
126
+ ): Promise<bigint> {
127
+ assertSigner(signer);
128
+
129
+ try {
130
+ const tokenContract = getTokenContract(tokenAddress);
131
+ if (!tokenContract)
132
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
133
+
134
+ const allowance: bigint = await tokenContract.allowance(
135
+ owner,
136
+ ADDRESSES.OFFCHAIN_FRACTIONS
137
+ );
138
+ return allowance;
139
+ } catch (error) {
140
+ throw new Error(parseEthersError(error));
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Check user's token balance
146
+ * @param owner The wallet address to check balance for
147
+ * @param tokenAddress The token contract address
148
+ */
149
+ async function checkTokenBalance(
150
+ owner: string,
151
+ tokenAddress: string
152
+ ): Promise<bigint> {
153
+ assertSigner(signer);
154
+
155
+ try {
156
+ const tokenContract = getTokenContract(tokenAddress);
157
+ if (!tokenContract)
158
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
159
+
160
+ const balance: bigint = await tokenContract.balanceOf(owner);
161
+ return balance;
162
+ } catch (error) {
163
+ throw new Error(parseEthersError(error));
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Approve tokens for the offchain fractions contract
169
+ * @param tokenAddress The token contract address
170
+ * @param amount Amount to approve (BigNumber)
171
+ */
172
+ async function approveToken(
173
+ tokenAddress: string,
174
+ amount: bigint
175
+ ): Promise<boolean> {
176
+ assertSigner(signer);
177
+
178
+ try {
179
+ const tokenContract = getTokenContract(tokenAddress);
180
+ if (!tokenContract)
181
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
182
+
183
+ setIsProcessing(true);
184
+
185
+ const approveTx = await tokenContract.approve(
186
+ ADDRESSES.OFFCHAIN_FRACTIONS,
187
+ amount
188
+ );
189
+ await approveTx.wait();
190
+
191
+ return true;
192
+ } catch (error) {
193
+ throw new Error(parseEthersError(error));
194
+ } finally {
195
+ setIsProcessing(false);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Create a new fractional token sale
201
+ * @param params Parameters for creating the fraction
202
+ */
203
+ async function createFraction(params: CreateFractionParams): Promise<string> {
204
+ assertSigner(signer);
205
+
206
+ try {
207
+ const contract = getOffchainFractionsContract();
208
+ if (!contract)
209
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
210
+
211
+ setIsProcessing(true);
212
+
213
+ const {
214
+ id,
215
+ token,
216
+ step,
217
+ totalSteps,
218
+ expiration,
219
+ to,
220
+ useCounterfactualAddress,
221
+ minSharesToRaise,
222
+ } = params;
223
+
224
+ // Validate parameters
225
+ if (!id || !token || !to) {
226
+ throw new Error(OffchainFractionsError.INVALID_PARAMETERS);
227
+ }
228
+
229
+ if (step === 0n || totalSteps === 0n) {
230
+ throw new Error("Step and totalSteps must be greater than zero");
231
+ }
232
+
233
+ if (minSharesToRaise > totalSteps) {
234
+ throw new Error("minSharesToRaise cannot be greater than totalSteps");
235
+ }
236
+
237
+ // Run a static call first to surface any revert reason
238
+ try {
239
+ await contract
240
+ .getFunction("createFraction")
241
+ .staticCall(
242
+ id,
243
+ token,
244
+ step,
245
+ totalSteps,
246
+ expiration,
247
+ to,
248
+ useCounterfactualAddress,
249
+ minSharesToRaise
250
+ );
251
+ } catch (staticError) {
252
+ throw new Error(parseEthersError(staticError));
253
+ }
254
+
255
+ // Execute the transaction
256
+ const tx = await contract.getFunction("createFraction")(
257
+ id,
258
+ token,
259
+ step,
260
+ totalSteps,
261
+ expiration,
262
+ to,
263
+ useCounterfactualAddress,
264
+ minSharesToRaise
265
+ );
266
+ await tx.wait();
267
+
268
+ return tx.hash;
269
+ } catch (error) {
270
+ throw new Error(parseEthersError(error));
271
+ } finally {
272
+ setIsProcessing(false);
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Buy fractions in an existing sale
278
+ * @param params Parameters for buying fractions
279
+ */
280
+ async function buyFractions(params: BuyFractionsParams): Promise<string> {
281
+ assertSigner(signer);
282
+
283
+ try {
284
+ const contract = getOffchainFractionsContract();
285
+ if (!contract)
286
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
287
+
288
+ setIsProcessing(true);
289
+
290
+ const { creator, id, stepsToBuy, minStepsToBuy } = params;
291
+
292
+ // Validate parameters
293
+ if (!creator || !id) {
294
+ throw new Error(OffchainFractionsError.INVALID_PARAMETERS);
295
+ }
296
+
297
+ if (stepsToBuy === 0n) {
298
+ throw new Error("stepsToBuy must be greater than zero");
299
+ }
300
+
301
+ // Get fraction data to calculate required amount
302
+ const fractionData = await getFraction(creator, id);
303
+ const requiredAmount = stepsToBuy * fractionData.step;
304
+
305
+ const owner = await signer.getAddress();
306
+
307
+ // Check token balance
308
+ const balance = await checkTokenBalance(owner, fractionData.token);
309
+ if (balance < requiredAmount) {
310
+ throw new Error(OffchainFractionsError.INSUFFICIENT_BALANCE);
311
+ }
312
+
313
+ // Check and approve tokens if necessary
314
+ const allowance = await checkTokenAllowance(owner, fractionData.token);
315
+ if (allowance < requiredAmount) {
316
+ const tokenContract = getTokenContract(fractionData.token);
317
+ const approveTx = await tokenContract.approve(
318
+ ADDRESSES.OFFCHAIN_FRACTIONS,
319
+ requiredAmount
320
+ );
321
+ await approveTx.wait();
322
+ }
323
+
324
+ // Run a static call first to surface any revert reason
325
+ try {
326
+ await contract
327
+ .getFunction("buyFractions")
328
+ .staticCall(creator, id, stepsToBuy, minStepsToBuy, {
329
+ from: owner,
330
+ });
331
+ } catch (staticError) {
332
+ throw new Error(parseEthersError(staticError));
333
+ }
334
+
335
+ // Execute the transaction
336
+ const tx = await contract.getFunction("buyFractions")(
337
+ creator,
338
+ id,
339
+ stepsToBuy,
340
+ minStepsToBuy
341
+ );
342
+ await tx.wait();
343
+
344
+ return tx.hash;
345
+ } catch (error) {
346
+ throw new Error(parseEthersError(error));
347
+ } finally {
348
+ setIsProcessing(false);
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Claim refund from an unfilled sale
354
+ * @param creator The address that created the fraction sale
355
+ * @param id The unique identifier of the fraction sale
356
+ */
357
+ async function claimRefund(creator: string, id: string): Promise<string> {
358
+ assertSigner(signer);
359
+
360
+ try {
361
+ const contract = getOffchainFractionsContract();
362
+ if (!contract)
363
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
364
+
365
+ setIsProcessing(true);
366
+
367
+ // Validate parameters
368
+ if (!creator || !id) {
369
+ throw new Error(OffchainFractionsError.INVALID_PARAMETERS);
370
+ }
371
+
372
+ const owner = await signer.getAddress();
373
+
374
+ // Check if user has steps purchased
375
+ const userSteps = await getStepsPurchased(owner, creator, id);
376
+ if (userSteps === 0n) {
377
+ throw new Error("No steps purchased for this fraction");
378
+ }
379
+
380
+ // Run a static call first to surface any revert reason
381
+ try {
382
+ await contract.getFunction("claimRefund").staticCall(creator, id, {
383
+ from: owner,
384
+ });
385
+ } catch (staticError) {
386
+ throw new Error(parseEthersError(staticError));
387
+ }
388
+
389
+ // Execute the transaction
390
+ const tx = await contract.getFunction("claimRefund")(creator, id);
391
+ await tx.wait();
392
+
393
+ return tx.hash;
394
+ } catch (error) {
395
+ throw new Error(parseEthersError(error));
396
+ } finally {
397
+ setIsProcessing(false);
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Close a fraction sale manually (only by owner)
403
+ * @param id The unique identifier of the fraction sale to close
404
+ */
405
+ async function closeFraction(id: string): Promise<string> {
406
+ assertSigner(signer);
407
+
408
+ try {
409
+ const contract = getOffchainFractionsContract();
410
+ if (!contract)
411
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
412
+
413
+ setIsProcessing(true);
414
+
415
+ // Validate parameters
416
+ if (!id) {
417
+ throw new Error(OffchainFractionsError.INVALID_PARAMETERS);
418
+ }
419
+
420
+ const owner = await signer.getAddress();
421
+
422
+ // Run a static call first to surface any revert reason
423
+ try {
424
+ await contract.getFunction("closeFraction").staticCall(id, {
425
+ from: owner,
426
+ });
427
+ } catch (staticError) {
428
+ throw new Error(parseEthersError(staticError));
429
+ }
430
+
431
+ // Execute the transaction
432
+ const tx = await contract.getFunction("closeFraction")(id);
433
+ await tx.wait();
434
+
435
+ return tx.hash;
436
+ } catch (error) {
437
+ throw new Error(parseEthersError(error));
438
+ } finally {
439
+ setIsProcessing(false);
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Get fraction data for a specific creator and ID
445
+ * @param creator The address that created the fraction sale
446
+ * @param id The unique identifier of the fraction sale
447
+ */
448
+ async function getFraction(
449
+ creator: string,
450
+ id: string
451
+ ): Promise<FractionData> {
452
+ assertSigner(signer);
453
+
454
+ try {
455
+ const contract = getOffchainFractionsContract();
456
+ if (!contract)
457
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
458
+
459
+ const result = await contract.getFraction(creator, id);
460
+
461
+ return {
462
+ token: result.token,
463
+ expiration: Number(result.expiration),
464
+ manuallyClosed: result.manuallyClosed,
465
+ minSharesToRaise: result.minSharesToRaise,
466
+ useCounterfactualAddress: result.useCounterfactualAddress,
467
+ claimedFromMinSharesToRaise: result.claimedFromMinSharesToRaise,
468
+ owner: result.owner,
469
+ step: result.step,
470
+ to: result.to,
471
+ soldSteps: result.soldSteps,
472
+ totalSteps: result.totalSteps,
473
+ };
474
+ } catch (error) {
475
+ throw new Error(parseEthersError(error));
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Get the number of steps purchased by a user for a specific fraction
481
+ * @param user The user address
482
+ * @param creator The address that created the fraction sale
483
+ * @param id The unique identifier of the fraction sale
484
+ */
485
+ async function getStepsPurchased(
486
+ user: string,
487
+ creator: string,
488
+ id: string
489
+ ): Promise<bigint> {
490
+ assertSigner(signer);
491
+
492
+ try {
493
+ const contract = getOffchainFractionsContract();
494
+ if (!contract)
495
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
496
+
497
+ const result = await contract.stepsPurchased(user, creator, id);
498
+ return result;
499
+ } catch (error) {
500
+ throw new Error(parseEthersError(error));
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Check if a fraction sale is expired
506
+ * @param creator The address that created the fraction sale
507
+ * @param id The unique identifier of the fraction sale
508
+ */
509
+ async function isFractionExpired(
510
+ creator: string,
511
+ id: string
512
+ ): Promise<boolean> {
513
+ try {
514
+ const fraction = await getFraction(creator, id);
515
+ return Date.now() / 1000 > fraction.expiration;
516
+ } catch (error) {
517
+ throw new Error(parseEthersError(error));
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Check if a fraction sale has reached its minimum shares threshold
523
+ * @param creator The address that created the fraction sale
524
+ * @param id The unique identifier of the fraction sale
525
+ */
526
+ async function hasReachedMinimumShares(
527
+ creator: string,
528
+ id: string
529
+ ): Promise<boolean> {
530
+ try {
531
+ const fraction = await getFraction(creator, id);
532
+ return fraction.soldSteps >= fraction.minSharesToRaise;
533
+ } catch (error) {
534
+ throw new Error(parseEthersError(error));
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Check if a fraction sale is completely filled
540
+ * @param creator The address that created the fraction sale
541
+ * @param id The unique identifier of the fraction sale
542
+ */
543
+ async function isFractionCompletelyFilled(
544
+ creator: string,
545
+ id: string
546
+ ): Promise<boolean> {
547
+ try {
548
+ const fraction = await getFraction(creator, id);
549
+ return fraction.soldSteps >= fraction.totalSteps;
550
+ } catch (error) {
551
+ throw new Error(parseEthersError(error));
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Calculate the total amount raised for a fraction
557
+ * @param creator The address that created the fraction sale
558
+ * @param id The unique identifier of the fraction sale
559
+ */
560
+ async function getTotalAmountRaised(
561
+ creator: string,
562
+ id: string
563
+ ): Promise<bigint> {
564
+ try {
565
+ const fraction = await getFraction(creator, id);
566
+ return fraction.soldSteps * fraction.step;
567
+ } catch (error) {
568
+ throw new Error(parseEthersError(error));
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Calculate the remaining steps available for purchase
574
+ * @param creator The address that created the fraction sale
575
+ * @param id The unique identifier of the fraction sale
576
+ */
577
+ async function getRemainingSteps(
578
+ creator: string,
579
+ id: string
580
+ ): Promise<bigint> {
581
+ try {
582
+ const fraction = await getFraction(creator, id);
583
+ return fraction.totalSteps - fraction.soldSteps;
584
+ } catch (error) {
585
+ throw new Error(parseEthersError(error));
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Estimate gas for creating a fraction
591
+ * @param params Parameters for creating the fraction
592
+ * @param ethPriceInUSD Current ETH price in USD (for cost estimation)
593
+ */
594
+ async function estimateGasForCreateFraction(
595
+ params: CreateFractionParams,
596
+ ethPriceInUSD: number | null
597
+ ): Promise<string> {
598
+ assertSigner(signer);
599
+
600
+ try {
601
+ const contract = getOffchainFractionsContract();
602
+ if (!contract)
603
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
604
+
605
+ const {
606
+ id,
607
+ token,
608
+ step,
609
+ totalSteps,
610
+ expiration,
611
+ to,
612
+ useCounterfactualAddress,
613
+ minSharesToRaise,
614
+ } = params;
615
+
616
+ const feeData = await signer.provider?.getFeeData();
617
+ const gasPrice =
618
+ feeData?.gasPrice ?? feeData?.maxFeePerGas ?? (0n as bigint);
619
+ if (gasPrice === 0n) {
620
+ throw new Error("Could not fetch gas price to estimate cost.");
621
+ }
622
+
623
+ const estimatedGas: bigint = await contract
624
+ .getFunction("createFraction")
625
+ .estimateGas(
626
+ id,
627
+ token,
628
+ step,
629
+ totalSteps,
630
+ expiration,
631
+ to,
632
+ useCounterfactualAddress,
633
+ minSharesToRaise
634
+ );
635
+
636
+ const estimatedCost: bigint = estimatedGas * gasPrice;
637
+
638
+ if (ethPriceInUSD) {
639
+ const estimatedCostInEth = formatEther(estimatedCost);
640
+ const estimatedCostInUSD = (
641
+ parseFloat(estimatedCostInEth) * ethPriceInUSD
642
+ ).toFixed(2);
643
+ return estimatedCostInUSD;
644
+ } else {
645
+ throw new Error(
646
+ "Could not fetch the ETH price to calculate cost in USD."
647
+ );
648
+ }
649
+ } catch (error: any) {
650
+ throw new Error(parseEthersError(error));
651
+ }
652
+ }
653
+
654
+ /**
655
+ * Estimate gas for buying fractions
656
+ * @param params Parameters for buying fractions
657
+ * @param ethPriceInUSD Current ETH price in USD (for cost estimation)
658
+ */
659
+ async function estimateGasForBuyFractions(
660
+ params: BuyFractionsParams,
661
+ ethPriceInUSD: number | null
662
+ ): Promise<string> {
663
+ assertSigner(signer);
664
+
665
+ try {
666
+ const contract = getOffchainFractionsContract();
667
+ if (!contract)
668
+ throw new Error(OffchainFractionsError.CONTRACT_NOT_AVAILABLE);
669
+
670
+ const { creator, id, stepsToBuy, minStepsToBuy } = params;
671
+
672
+ const feeData = await signer.provider?.getFeeData();
673
+ const gasPrice =
674
+ feeData?.gasPrice ?? feeData?.maxFeePerGas ?? (0n as bigint);
675
+ if (gasPrice === 0n) {
676
+ throw new Error("Could not fetch gas price to estimate cost.");
677
+ }
678
+
679
+ const estimatedGas: bigint = await contract
680
+ .getFunction("buyFractions")
681
+ .estimateGas(creator, id, stepsToBuy, minStepsToBuy);
682
+
683
+ const estimatedCost: bigint = estimatedGas * gasPrice;
684
+
685
+ if (ethPriceInUSD) {
686
+ const estimatedCostInEth = formatEther(estimatedCost);
687
+ const estimatedCostInUSD = (
688
+ parseFloat(estimatedCostInEth) * ethPriceInUSD
689
+ ).toFixed(2);
690
+ return estimatedCostInUSD;
691
+ } else {
692
+ throw new Error(
693
+ "Could not fetch the ETH price to calculate cost in USD."
694
+ );
695
+ }
696
+ } catch (error: any) {
697
+ throw new Error(parseEthersError(error));
698
+ }
699
+ }
700
+
701
+ return {
702
+ // Core contract functions
703
+ createFraction,
704
+ buyFractions,
705
+ claimRefund,
706
+ closeFraction,
707
+
708
+ // View functions
709
+ getFraction,
710
+ getStepsPurchased,
711
+
712
+ // Token operations
713
+ approveToken,
714
+ checkTokenAllowance,
715
+ checkTokenBalance,
716
+
717
+ // Utility functions
718
+ isFractionExpired,
719
+ hasReachedMinimumShares,
720
+ isFractionCompletelyFilled,
721
+ getTotalAmountRaised,
722
+ getRemainingSteps,
723
+
724
+ // Gas estimation
725
+ estimateGasForCreateFraction,
726
+ estimateGasForBuyFractions,
727
+
728
+ // State
729
+ get isProcessing() {
730
+ return isProcessing;
731
+ },
732
+ addresses: ADDRESSES,
733
+
734
+ // Signer availability
735
+ isSignerAvailable: !!signer,
736
+ };
737
+ }