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.
- package/SeedRoundFundraiser.ts +742 -0
- package/TradingVault.ts +863 -0
- package/abis/SeedRoundFundraiser.json +1519 -0
- package/abis/TradingVault.json +1647 -0
- package/common.ts +131 -0
- package/contracts/SeedRoundFundraiser.ts +1670 -0
- package/contracts/TradingVault.ts +1752 -0
- package/contracts/common.ts +131 -0
- package/contracts/factories/SeedRoundFundraiser__factory.ts +1495 -0
- package/contracts/factories/index.ts +4 -0
- package/contracts/index.ts +6 -0
- package/contracts.json +28 -0
- package/dist/SeedRoundFundraiser.d.ts +130 -0
- package/dist/SeedRoundFundraiser.js +445 -0
- package/dist/TradingVault.d.ts +175 -0
- package/dist/TradingVault.js +521 -0
- package/dist/abis/SeedRoundFundraiser.json +1519 -0
- package/dist/abis/TradingVault.json +1647 -0
- package/dist/common.d.ts +50 -0
- package/dist/common.js +2 -0
- package/dist/contracts/SeedRoundFundraiser.d.ts +936 -0
- package/dist/contracts/SeedRoundFundraiser.js +2 -0
- package/dist/contracts/TradingVault.d.ts +930 -0
- package/dist/contracts/TradingVault.js +2 -0
- package/dist/contracts.json +28 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/types.d.ts +291 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +95 -0
- package/dist/utils.js +370 -0
- package/dist/whitelist-tokens.json +14 -0
- package/index.ts +3 -0
- package/package.json +21 -0
- package/temp/aiia-vault-sdk-1.0.0.tgz +0 -0
- package/tsconfig.json +15 -0
- package/types.ts +301 -0
- package/utils.ts +576 -0
- 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
|
+
}
|