aiia-vault-sdk 1.0.1

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.
Files changed (39) hide show
  1. package/SeedRoundFundraiser.ts +742 -0
  2. package/TradingVault.ts +863 -0
  3. package/abis/SeedRoundFundraiser.json +1519 -0
  4. package/abis/TradingVault.json +1647 -0
  5. package/common.ts +131 -0
  6. package/contracts/SeedRoundFundraiser.ts +1670 -0
  7. package/contracts/TradingVault.ts +1752 -0
  8. package/contracts/common.ts +131 -0
  9. package/contracts/factories/SeedRoundFundraiser__factory.ts +1495 -0
  10. package/contracts/factories/index.ts +4 -0
  11. package/contracts/index.ts +6 -0
  12. package/contracts.json +28 -0
  13. package/dist/SeedRoundFundraiser.d.ts +130 -0
  14. package/dist/SeedRoundFundraiser.js +445 -0
  15. package/dist/TradingVault.d.ts +175 -0
  16. package/dist/TradingVault.js +521 -0
  17. package/dist/abis/SeedRoundFundraiser.json +1519 -0
  18. package/dist/abis/TradingVault.json +1647 -0
  19. package/dist/common.d.ts +50 -0
  20. package/dist/common.js +2 -0
  21. package/dist/contracts/SeedRoundFundraiser.d.ts +936 -0
  22. package/dist/contracts/SeedRoundFundraiser.js +2 -0
  23. package/dist/contracts/TradingVault.d.ts +930 -0
  24. package/dist/contracts/TradingVault.js +2 -0
  25. package/dist/contracts.json +28 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.js +19 -0
  28. package/dist/types.d.ts +291 -0
  29. package/dist/types.js +2 -0
  30. package/dist/utils.d.ts +95 -0
  31. package/dist/utils.js +370 -0
  32. package/dist/whitelist-tokens.json +14 -0
  33. package/index.ts +3 -0
  34. package/package.json +21 -0
  35. package/temp/aiia-vault-sdk-1.0.0.tgz +0 -0
  36. package/tsconfig.json +15 -0
  37. package/types.ts +301 -0
  38. package/utils.ts +576 -0
  39. package/whitelist-tokens.json +14 -0
package/utils.ts ADDED
@@ -0,0 +1,576 @@
1
+ import { ethers } from "ethers";
2
+
3
+ // Debug log prefixes with colors
4
+ export const LOG_PREFIXES = {
5
+ INFO: "\x1b[36m[STREAM-INFO]\x1b[0m", // Cyan
6
+ BLOCK: "\x1b[33m[STREAM-BLOCK]\x1b[0m", // Yellow
7
+ EVENT: "\x1b[32m[STREAM-EVENT]\x1b[0m", // Green
8
+ ERROR: "\x1b[31m[STREAM-ERROR]\x1b[0m", // Red
9
+ SAVE: "\x1b[35m[STREAM-SAVE]\x1b[0m", // Magenta
10
+ DEBUG: "\x1b[34m[STREAM-DEBUG]\x1b[0m", // Blue
11
+ };
12
+
13
+ export const sleep = async (ms: number): Promise<void> => {
14
+ return new Promise((resolve) => setTimeout(resolve, ms));
15
+ };
16
+
17
+ export const getRandomProvider = (
18
+ providers: ethers.Provider[]
19
+ ): ethers.Provider => {
20
+ return providers[Math.floor(Math.random() * providers.length)];
21
+ };
22
+
23
+ export const checkRpcHealth = async (
24
+ provider: ethers.Provider,
25
+ index = 0
26
+ ): Promise<boolean> => {
27
+ try {
28
+ // Try to get the latest block number with a timeout of 5 seconds
29
+ const timeoutPromise = new Promise((_, reject) => {
30
+ setTimeout(() => reject(new Error("RPC request timeout")), 5000);
31
+ });
32
+
33
+ await Promise.race([provider.getBlockNumber(), timeoutPromise]);
34
+ return true;
35
+ } catch (error) {
36
+ console.error(
37
+ `${LOG_PREFIXES.ERROR} RPC health check failed:`,
38
+ error,
39
+ index
40
+ );
41
+ return false;
42
+ }
43
+ };
44
+
45
+ export const getTransactionStatus = async (
46
+ provider: ethers.Provider,
47
+ txHash: string,
48
+ maxRetries: number = 10
49
+ ): Promise<{
50
+ hash: string;
51
+ status: boolean | null;
52
+ confirmations: number;
53
+ isCompleted: boolean;
54
+ attempts: number;
55
+ }> => {
56
+ let attempts = 0;
57
+ let waitTime = 1000; // Start with 1 second
58
+
59
+ while (attempts < maxRetries) {
60
+ try {
61
+ const receipt = await provider.getTransactionReceipt(txHash);
62
+
63
+ if (!receipt) {
64
+ attempts++;
65
+ if (attempts === maxRetries) {
66
+ return {
67
+ hash: txHash,
68
+ status: null,
69
+ confirmations: 0,
70
+ isCompleted: false,
71
+ attempts,
72
+ };
73
+ }
74
+ // Exponential backoff: 1s, 2s, 4s, 8s, 16s
75
+ await sleep(waitTime);
76
+ waitTime *= 2;
77
+ continue;
78
+ }
79
+
80
+ const confirmations = Number((await receipt.confirmations()) || 0);
81
+
82
+ return {
83
+ hash: receipt.hash,
84
+ status: receipt.status === 1,
85
+ confirmations,
86
+ isCompleted: true,
87
+ attempts: attempts + 1,
88
+ };
89
+ } catch (error: any) {
90
+ throw new Error(`Failed to get transaction status: ${error.message}`);
91
+ }
92
+ }
93
+
94
+ // This should never be reached due to the return in the if(!receipt) block
95
+ throw new Error("Failed to get transaction status after maximum retries");
96
+ };
97
+
98
+ export const getTokenDecimals = async (
99
+ tokenAddress: string,
100
+ provider: ethers.Provider
101
+ ): Promise<number> => {
102
+ const tokenContract = new ethers.Contract(
103
+ tokenAddress,
104
+ ["function decimals() view returns (uint8)"],
105
+ provider
106
+ );
107
+ return Number(await tokenContract.decimals());
108
+ };
109
+
110
+ export const getTokenSymbol = async (
111
+ tokenAddress: string,
112
+ provider: ethers.Provider
113
+ ): Promise<string> => {
114
+ const tokenContract = new ethers.Contract(
115
+ tokenAddress,
116
+ ["function symbol() view returns (string)"],
117
+ provider
118
+ );
119
+ return await tokenContract.symbol();
120
+ };
121
+
122
+ export const getTokenAllowance = async (
123
+ tokenAddress: string,
124
+ owner: string,
125
+ spender: string,
126
+ provider: ethers.Provider
127
+ ): Promise<bigint> => {
128
+ // If the token is ETH (zero address), return max bigint value
129
+ if (
130
+ tokenAddress === ethers.ZeroAddress ||
131
+ tokenAddress.toLowerCase() === ethers.ZeroAddress
132
+ ) {
133
+ // Max bigint value (2^256 - 1)
134
+ return BigInt(
135
+ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
136
+ );
137
+ }
138
+
139
+ const tokenContract = new ethers.Contract(
140
+ tokenAddress,
141
+ [
142
+ "function allowance(address owner, address spender) view returns (uint256)",
143
+ ],
144
+ provider
145
+ );
146
+ return await tokenContract.allowance(owner, spender);
147
+ };
148
+
149
+ export const buildApproveERC20Tx = async (
150
+ tokenAddress: string,
151
+ spender: string,
152
+ amount: bigint,
153
+ provider: ethers.Provider
154
+ ) => {
155
+ const tokenContract = new ethers.Contract(
156
+ tokenAddress,
157
+ ["function approve(address spender, uint256 amount) returns (bool)"],
158
+ provider
159
+ );
160
+ return await tokenContract.approve.populateTransaction(spender, amount);
161
+ };
162
+
163
+ export const getRewardWeight = (
164
+ duration: number,
165
+ configs: Array<{ weight: number; duration: number }>
166
+ ): number => {
167
+ let maxWeight = 0;
168
+ // Sort configs by duration to ensure correct weight calculation
169
+ const sortedConfigs = [...configs].sort((a, b) => a.duration - b.duration);
170
+
171
+ for (const config of sortedConfigs) {
172
+ if (duration >= config.duration) {
173
+ maxWeight = config.weight;
174
+ }
175
+ }
176
+ return maxWeight;
177
+ };
178
+
179
+ export const resolveContractAddress = (
180
+ rpcUrl: string,
181
+ contractName: string,
182
+ contracts: any,
183
+ providedAddress?: string
184
+ ): string => {
185
+ if (providedAddress) {
186
+ return providedAddress;
187
+ }
188
+
189
+ const rpcLower = rpcUrl.toLowerCase();
190
+ if (
191
+ ["testnet", "sepolia", "goerli"].some((network) =>
192
+ rpcLower.includes(network)
193
+ )
194
+ ) {
195
+ if (rpcLower.includes("sepolia.base")) {
196
+ return contracts?.["base-sepolia"]?.[contractName];
197
+ } else if (rpcLower.includes("sepolia")) {
198
+ return contracts?.sepolia?.[contractName];
199
+ } else if (rpcLower.includes("goerli")) {
200
+ return contracts?.goerli?.[contractName];
201
+ }
202
+ throw new Error("Unsupported testnet network");
203
+ }
204
+ return contracts?.mainnet?.[contractName];
205
+ };
206
+
207
+ // Define a base interface for blockchain events
208
+ export interface BaseEventRaw {
209
+ eventName: string;
210
+ blockNumber: number;
211
+ transactionHash: string;
212
+ args: Record<string, any>;
213
+ timestamp: number | null;
214
+ }
215
+
216
+ /**
217
+ * Generic function to get all events from a contract within a block range
218
+ * @param contract The ethers contract instance
219
+ * @param getRandomProvider Function to get a random provider
220
+ * @param fromBlock Starting block number
221
+ * @param toBlock Ending block number
222
+ * @returns Array of parsed events with timestamps
223
+ */
224
+ export const getAllEvents = async <TEventRaw extends BaseEventRaw, TContract extends ethers.BaseContract>(
225
+ contract: TContract,
226
+ getRandomProvider: () => ethers.Provider,
227
+ getContractWithRandomProvider: () => TContract,
228
+ fromBlock: number,
229
+ toBlock: number
230
+ ): Promise<TEventRaw[]> => {
231
+ // Get all event names from the contract interface
232
+ const eventNames: string[] = [];
233
+
234
+ contract.interface.forEachEvent((event) => {
235
+ eventNames.push(event.name);
236
+ });
237
+
238
+ // Create filter for each event type
239
+ const eventPromises = eventNames.map(async (eventName) => {
240
+ // Create a new contract instance with a random provider for each query
241
+ const randomContract = getContractWithRandomProvider();
242
+ const filter = (randomContract.filters as any)[eventName]();
243
+ const events = await randomContract.queryFilter(
244
+ filter,
245
+ fromBlock,
246
+ toBlock
247
+ );
248
+
249
+ // Parse each event into a more readable format
250
+ return events.map((event) => {
251
+ const parsedLog = contract.interface.parseLog({
252
+ topics: event.topics,
253
+ data: event.data,
254
+ });
255
+
256
+ return {
257
+ eventName,
258
+ blockNumber: event.blockNumber,
259
+ transactionHash: event.transactionHash,
260
+ args: parsedLog?.args.toObject(),
261
+ timestamp: null, // Will be populated below
262
+ } as TEventRaw;
263
+ });
264
+ });
265
+
266
+ // Wait for all event queries to complete
267
+ const allEvents = (await Promise.all(eventPromises)).flat();
268
+
269
+ // Sort events by block number
270
+ allEvents.sort((a, b) => a.blockNumber - b.blockNumber);
271
+
272
+ // Get block timestamps in batches to improve performance
273
+ const uniqueBlocks = [...new Set(allEvents.map((e) => e.blockNumber))];
274
+ const blockPromises = uniqueBlocks.map((blockNumber) =>
275
+ getRandomProvider().getBlock(blockNumber)
276
+ );
277
+ const blocks = await Promise.all(blockPromises);
278
+
279
+ // Create a map of block numbers to timestamps
280
+ const blockTimestamps = new Map(
281
+ blocks.map((block) => [block!.number, block!.timestamp])
282
+ );
283
+
284
+ // Add timestamps to events
285
+ return allEvents.map(
286
+ (event) =>
287
+ ({
288
+ eventName: event.eventName,
289
+ blockNumber: event.blockNumber,
290
+ transactionHash: event.transactionHash,
291
+ args: Object.fromEntries(
292
+ Object.entries(event.args || {}).filter(([key]) =>
293
+ isNaN(Number(key))
294
+ )
295
+ ),
296
+ timestamp: blockTimestamps.get(event.blockNumber) ?? null,
297
+ } as TEventRaw)
298
+ );
299
+ };
300
+
301
+ /**
302
+ * Generic function to stream blockchain events
303
+ * @param getProvider Function to get a provider
304
+ * @param getAllEvents Function to get events for a block range
305
+ * @param formatEvent Function to format raw events
306
+ * @param onEvent Callback for each processed event
307
+ * @param saveLatestBlock Function to save the latest processed block
308
+ * @param fromBlock Starting block number
309
+ * @param batchSize Number of blocks to process in each batch
310
+ * @param sleepTime Time to wait between iterations when caught up
311
+ */
312
+ export const streamEvents = async <TRawEvent, TFormattedEvent>({
313
+ getProvider,
314
+ getAllEvents,
315
+ formatEvent,
316
+ onEvent,
317
+ saveLatestBlock,
318
+ fromBlock,
319
+ batchSize = 1000,
320
+ sleepTime = 5000,
321
+ }: {
322
+ getProvider: () => ethers.Provider;
323
+ getAllEvents: (fromBlock: number, toBlock: number) => Promise<TRawEvent[]>;
324
+ formatEvent: (event: TRawEvent) => TFormattedEvent;
325
+ onEvent: (event: TFormattedEvent) => Promise<void>;
326
+ saveLatestBlock: (blockNumber: number) => Promise<void>;
327
+ fromBlock: number;
328
+ batchSize?: number;
329
+ sleepTime?: number;
330
+ }): Promise<void> => {
331
+ const debugEnabled = process.env.DEBUG_STREAM_EVENTS === "1";
332
+ let currentBlock = fromBlock;
333
+
334
+ if (debugEnabled) {
335
+ console.log(`${LOG_PREFIXES.INFO} StreamEvents Debug Mode Enabled`);
336
+ console.log(`${LOG_PREFIXES.INFO} Initial fromBlock: ${currentBlock}`);
337
+ console.log(`${LOG_PREFIXES.INFO} Batch size: ${batchSize}`);
338
+ console.log(`${LOG_PREFIXES.INFO} Sleep time: ${sleepTime}ms`);
339
+ }
340
+
341
+ while (true) {
342
+ try {
343
+ // Get the latest block number
344
+ const latestBlock = await getProvider().getBlockNumber();
345
+ if (debugEnabled) {
346
+ console.log(`${LOG_PREFIXES.BLOCK} Latest block: ${latestBlock}`);
347
+ }
348
+
349
+ // Calculate toBlock as min(fromBlock + batchSize, latestBlock)
350
+ const toBlock = Math.min(currentBlock + batchSize, latestBlock);
351
+ if (debugEnabled) {
352
+ console.log(
353
+ `${LOG_PREFIXES.BLOCK} Processing blocks from ${currentBlock} to ${toBlock}`
354
+ );
355
+ }
356
+
357
+ // If we've caught up to the latest block, wait before next iteration
358
+ if (currentBlock >= latestBlock) {
359
+ if (debugEnabled) {
360
+ console.log(
361
+ `${LOG_PREFIXES.DEBUG} Caught up to latest block, waiting ${sleepTime}ms...`
362
+ );
363
+ }
364
+ await sleep(sleepTime);
365
+ continue;
366
+ }
367
+
368
+ // Get events for the current batch
369
+ const events = await getAllEvents(currentBlock, toBlock);
370
+ if (debugEnabled && events.length > 0) {
371
+ const eventTypes = events.reduce((acc, event) => {
372
+ const eventName = (event as any).eventName || "Unknown";
373
+ acc[eventName] = (acc[eventName] || 0) + 1;
374
+ return acc;
375
+ }, {} as Record<string, number>);
376
+
377
+ console.log(`${LOG_PREFIXES.EVENT} Found ${events.length} events:`);
378
+ Object.entries(eventTypes).forEach(([eventName, count]) => {
379
+ console.log(`${LOG_PREFIXES.EVENT} - ${eventName}: ${count}`);
380
+ });
381
+ }
382
+
383
+ // Process each event
384
+ for (const event of events) {
385
+ if (debugEnabled) {
386
+ console.log(
387
+ `${LOG_PREFIXES.EVENT} Processing: ${
388
+ (event as any).eventName || "Event"
389
+ }`
390
+ );
391
+ }
392
+ await onEvent(formatEvent(event));
393
+ }
394
+
395
+ // Save the latest processed block
396
+ await saveLatestBlock(toBlock);
397
+ if (debugEnabled) {
398
+ console.log(`${LOG_PREFIXES.SAVE} Saved latest block: ${toBlock}`);
399
+ }
400
+
401
+ // Update fromBlock for next iteration
402
+ currentBlock = toBlock + 1;
403
+ } catch (error) {
404
+ if (debugEnabled) {
405
+ console.error(`${LOG_PREFIXES.ERROR} Error in streamEvents:`, error);
406
+ console.error(
407
+ `${LOG_PREFIXES.ERROR} Stack trace:`,
408
+ error instanceof Error ? error.stack : ""
409
+ );
410
+ } else {
411
+ console.error(`${LOG_PREFIXES.ERROR} Error in streamEvents:`, error);
412
+ }
413
+ await sleep(1000); // Wait before retrying on error
414
+ }
415
+ }
416
+ };
417
+
418
+ /**
419
+ * Generic function to sign and send a transaction
420
+ * @param tx The transaction to send
421
+ * @param wallet The wallet or sendTransaction function to use
422
+ * @param getRandomProvider Function to get a random provider
423
+ * @param contract Optional contract instance
424
+ * @param callbacks Optional callbacks for transaction events
425
+ * @returns Transaction result with status
426
+ */
427
+ export const signAndSendTransaction = async (
428
+ tx: ethers.ContractTransaction,
429
+ wallet: ethers.Wallet | any,
430
+ getRandomProvider: () => ethers.Provider,
431
+ {
432
+ onSubmit,
433
+ onFinally,
434
+ onError,
435
+ }: {
436
+ onSubmit?: (tx: string) => void | Promise<void>;
437
+ onFinally?: (status: {
438
+ status: boolean | null;
439
+ confirmations: number;
440
+ txHash: string;
441
+ isCompleted: boolean;
442
+ attempts: number;
443
+ }) => void | Promise<void>;
444
+ onError?: (error: Error) => void | Promise<void>;
445
+ } = {},
446
+ contract?: ethers.BaseContract
447
+ ): Promise<{
448
+ transaction: {
449
+ hash: string;
450
+ };
451
+ status: {
452
+ status: boolean | null;
453
+ confirmations: number;
454
+ isCompleted: boolean;
455
+ attempts: number;
456
+ };
457
+ }> => {
458
+ try {
459
+ // Prepare the transaction
460
+ const transaction = {
461
+ ...tx,
462
+ };
463
+
464
+ // Sign and send the transaction
465
+ let txHash: string;
466
+
467
+ // Check if wallet is an object with the expected Wallet methods
468
+ if (
469
+ typeof wallet === "object" &&
470
+ wallet !== null &&
471
+ "sendTransaction" in wallet
472
+ ) {
473
+ const connectedWallet = wallet.connect(getRandomProvider());
474
+ const signedTx = await connectedWallet.sendTransaction(transaction);
475
+ txHash = signedTx.hash;
476
+ } else {
477
+ // Assume it's a SendTransactionMutateAsync function
478
+ txHash = await wallet(transaction);
479
+ }
480
+
481
+ // Call onSubmit callback if provided
482
+ if (onSubmit) {
483
+ await onSubmit(txHash);
484
+ }
485
+
486
+ // Check transaction status
487
+ const status = await getTransactionStatus(getRandomProvider(), txHash);
488
+
489
+ // Add new error handling
490
+ if (status.status === null) {
491
+ throw new Error(
492
+ "Transaction may not be minted on-chain yet or has failed. Please check the blockchain explorer."
493
+ );
494
+ }
495
+
496
+ if (status.status === false) {
497
+ throw new Error(
498
+ "Transaction failed. Please check the blockchain explorer for details."
499
+ );
500
+ }
501
+
502
+ // Call onFinally callback if provided
503
+ if (onFinally) {
504
+ await onFinally({ ...status, txHash });
505
+ }
506
+
507
+ return {
508
+ transaction: {
509
+ hash: txHash,
510
+ },
511
+ status,
512
+ };
513
+ } catch (error: any) {
514
+ // Try to decode the error if contract is provided
515
+ if (contract && error) {
516
+ try {
517
+ // Handle custom errors from estimateGas or other pre-transaction errors
518
+ if (error.code === "CALL_EXCEPTION" && error.data) {
519
+ // The error.data contains the custom error selector (first 4 bytes of the error signature hash)
520
+ const errorSelector = error.data;
521
+
522
+ // Try to find matching error in the contract interface
523
+ for (const errorFragment of Object.values(
524
+ contract.interface.fragments
525
+ ).filter((fragment) => fragment.type === "error")) {
526
+ if ("name" in errorFragment) {
527
+ // Calculate the selector for this error
528
+ const errorDef = contract.interface.getError(
529
+ errorFragment.name as string
530
+ );
531
+ if (errorDef && errorDef.selector) {
532
+ const calculatedSelector = errorDef.selector;
533
+
534
+ if (calculatedSelector === errorSelector) {
535
+ // Found matching error!
536
+ const errorName = errorFragment.name;
537
+ const errorArgs = error.errorArgs || [];
538
+
539
+ const enhancedError = new Error(
540
+ `Transaction failed with custom error: ${errorName}(${errorArgs.join(
541
+ ", "
542
+ )})`
543
+ );
544
+
545
+ // Preserve original error properties
546
+ Object.assign(enhancedError, error);
547
+
548
+ if (onError) {
549
+ await onError(enhancedError);
550
+ }
551
+ throw enhancedError;
552
+ }
553
+ }
554
+ }
555
+ }
556
+
557
+ // If we couldn't match the selector to a known error
558
+ console.log(
559
+ `${LOG_PREFIXES.DEBUG} Unknown custom error with selector: ${errorSelector}`
560
+ );
561
+ }
562
+ } catch (decodeError) {
563
+ console.error(
564
+ `${LOG_PREFIXES.ERROR} Error decoding transaction error:`,
565
+ decodeError
566
+ );
567
+ }
568
+ }
569
+
570
+ // Call onError callback if provided
571
+ if (onError) {
572
+ await onError(error instanceof Error ? error : new Error(String(error)));
573
+ }
574
+ throw error; // Re-throw the error after handling
575
+ }
576
+ };
@@ -0,0 +1,14 @@
1
+ {
2
+ "sepolia": [
3
+ "0x1234567890123456789012345678901234567890",
4
+ "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
5
+ "0x0000000000000000000000000000000000000000",
6
+ "0x09F827Fa3307cF467F51782722A5e3f43a2598dc",
7
+ "0xC82AE9c8b25F5b48ac200efd3e86Ca372AE1AD91"
8
+ ],
9
+ "base-sepolia": [
10
+ "0x0000000000000000000000000000000000000000",
11
+ "0xa5Ef2328aa2F6F231D15cdcF48503974D0938eD4",
12
+ "0x08a2d38807B1D345b6c1642f8030a29DC2Cc7223"
13
+ ]
14
+ }