@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.
- package/dist/cjs/browser.d.ts +1 -0
- package/dist/cjs/browser.js +6 -1
- package/dist/cjs/browser.js.map +1 -1
- package/dist/cjs/constants/addresses.d.ts +1 -1
- package/dist/cjs/{farms-router-DCCy-acA.js → farms-router-DCsg5FYX.js} +888 -28
- package/dist/cjs/farms-router-DCsg5FYX.js.map +1 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/abis/offchainFractions.d.ts +473 -0
- package/dist/cjs/lib/hooks/use-offchain-fractions.d.ts +60 -0
- package/dist/esm/browser.d.ts +1 -0
- package/dist/esm/browser.js +2 -2
- package/dist/esm/constants/addresses.d.ts +1 -1
- package/dist/esm/{farms-router-XDjQOQ-c.js → farms-router-BCnakF0d.js} +888 -29
- package/dist/esm/farms-router-BCnakF0d.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/lib/abis/offchainFractions.d.ts +473 -0
- package/dist/esm/lib/hooks/use-offchain-fractions.d.ts +60 -0
- package/package.json +1 -1
- package/src/browser.ts +1 -0
- package/src/constants/addresses.ts +7 -1
- package/src/index.ts +1 -0
- package/src/lib/abis/offchainFractions.ts +332 -0
- package/src/lib/hooks/use-offchain-fractions.ts +737 -0
- package/dist/cjs/farms-router-DCCy-acA.js.map +0 -1
- package/dist/esm/farms-router-XDjQOQ-c.js.map +0 -1
|
@@ -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
|
+
}
|