diff --git a/apps/snfoundry/scripts-ts/helpers/error-handler.ts b/apps/snfoundry/scripts-ts/helpers/error-handler.ts new file mode 100644 index 0000000..e4b20cb --- /dev/null +++ b/apps/snfoundry/scripts-ts/helpers/error-handler.ts @@ -0,0 +1,108 @@ +import { Provider } from "starknet"; +import { DeploymentError, DeploymentErrorType, RetryConfig, Networks } from "../types"; + +class Logger { + private formatMessage(level: string, message: any): string { + const timestamp = new Date().toISOString(); + return `[${timestamp}] ${level}: ${typeof message === 'string' ? message : JSON.stringify(message)}`; + } + + info(message: any): void { + console.log(this.formatMessage('INFO', message)); + } + + warn(message: any): void { + console.warn(this.formatMessage('WARN', message)); + } + + error(message: any): void { + console.error(this.formatMessage('ERROR', message)); + } +} + +export const logger = new Logger(); + +const defaultRetryConfig: RetryConfig = { + maxAttempts: 3, + initialDelay: 1000, + maxDelay: 10000, + factor: 2 +}; + +export async function withRetry( + operation: () => Promise, + config: RetryConfig = defaultRetryConfig, + context: string +): Promise { + let delay = config.initialDelay; + let attempt = 0; + + while (attempt < config.maxAttempts) { + try { + return await operation(); + } catch (error: any) { + attempt++; + + const errorType = classifyStarknetError(error); + logger.warn({ + message: `Retry attempt ${attempt}/${config.maxAttempts} for ${context}`, + error: error.message, + type: errorType + }); + + if (attempt === config.maxAttempts || !isRetryableError(errorType)) { + throw new DeploymentError(errorType, error.message); + } + + await sleep(delay); + delay = Math.min(delay * config.factor, config.maxDelay); + } + } + + throw new DeploymentError( + DeploymentErrorType.UNKNOWN_ERROR, + `Max retry attempts (${config.maxAttempts}) reached for ${context}` + ); +} + +function classifyStarknetError(error: any): DeploymentErrorType { + const errorMsg = error.message.toLowerCase(); + + if (errorMsg.includes("insufficient max fee")) { + return DeploymentErrorType.GAS_ERROR; + } + if (errorMsg.includes("invalid transaction nonce")) { + return DeploymentErrorType.NONCE_ERROR; + } + if (errorMsg.includes("network") || errorMsg.includes("timeout")) { + return DeploymentErrorType.NETWORK_ERROR; + } + if (errorMsg.includes("contract") || errorMsg.includes("class hash")) { + return DeploymentErrorType.CONTRACT_ERROR; + } + if (errorMsg.includes("invalid") || errorMsg.includes("validation")) { + return DeploymentErrorType.VALIDATION_ERROR; + } + return DeploymentErrorType.UNKNOWN_ERROR; +} + +function isRetryableError(errorType: DeploymentErrorType): boolean { + return [ + DeploymentErrorType.NETWORK_ERROR, + DeploymentErrorType.GAS_ERROR, + DeploymentErrorType.NONCE_ERROR + ].includes(errorType); +} + +export async function validateNetwork(provider: Provider): Promise { + try { + await provider.getChainId(); + } catch (error) { + throw new DeploymentError( + DeploymentErrorType.NETWORK_ERROR, + "Failed to validate network connection" + ); + } +} + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); \ No newline at end of file diff --git a/apps/snfoundry/scripts-ts/types.ts b/apps/snfoundry/scripts-ts/types.ts index 0d43f9c..f77e080 100644 --- a/apps/snfoundry/scripts-ts/types.ts +++ b/apps/snfoundry/scripts-ts/types.ts @@ -14,3 +14,39 @@ export type DeployContractParams = { constructorArgs?: RawArgs; options?: UniversalDetails; }; + +export enum DeploymentErrorType { + VALIDATION_ERROR = "VALIDATION_ERROR", + NETWORK_ERROR = "NETWORK_ERROR", + GAS_ERROR = "GAS_ERROR", + NONCE_ERROR = "NONCE_ERROR", + CONTRACT_ERROR = "CONTRACT_ERROR", + UNKNOWN_ERROR = "UNKNOWN_ERROR", +} + +export class DeploymentError extends Error { + constructor( + public type: DeploymentErrorType, + message: string, + public txHash?: string, + public contractAddress?: string + ) { + super(message); + this.name = "DeploymentError"; + } +} + +export interface RetryConfig { + maxAttempts: number; + initialDelay: number; + maxDelay: number; + factor: number; +} + +export interface TransactionQueueItem { + id: string; + execute: () => Promise; + priority: number; + network: keyof Networks; + dependencies?: string[]; +}