1414 * Environment Variables:
1515 * - API_BASE_URL: Base URL for the API (default: http://localhost:3000)
1616 * - SNAPSHOT_AUTH_TOKEN: Authentication token for API requests
17- * - BATCH_SIZE: Number of wallets per batch (default: 10 )
17+ * - BATCH_SIZE: Number of wallets per batch (default: 5 )
1818 * - DELAY_BETWEEN_BATCHES: Delay between batches in seconds (default: 10)
1919 * - MAX_RETRIES: Maximum retries for failed batches (default: 3)
20+ * - REQUEST_TIMEOUT: Request timeout in seconds (default: 45)
2021 */
2122
2223interface BatchProgress {
@@ -35,6 +36,26 @@ interface BatchProgress {
3536 walletId : string ;
3637 errorType: string ;
3738 errorMessage: string ;
39+ walletStructure ?: {
40+ name : string ;
41+ type: string ;
42+ numRequiredSigners: number ;
43+ signersCount: number ;
44+ hasStakeCredential: boolean ;
45+ hasScriptCbor: boolean ;
46+ isArchived: boolean ;
47+ verified: number ;
48+ hasDRepKeys: boolean ;
49+ hasClarityApiKey: boolean ;
50+ // Character counts for key fields
51+ scriptCborLength: number ;
52+ stakeCredentialLength: number ;
53+ signersAddressesLength: number ;
54+ signersStakeKeysLength: number ;
55+ signersDRepKeysLength: number ;
56+ signersDescriptionsLength: number ;
57+ clarityApiKeyLength: number ;
58+ } ;
3859 } > ;
3960}
4061
@@ -63,6 +84,26 @@ interface BatchResults {
6384 errorType: string ;
6485 errorMessage: string ;
6586 batchNumber: number ;
87+ walletStructure ?: {
88+ name : string ;
89+ type: string ;
90+ numRequiredSigners: number ;
91+ signersCount: number ;
92+ hasStakeCredential: boolean ;
93+ hasScriptCbor: boolean ;
94+ isArchived: boolean ;
95+ verified: number ;
96+ hasDRepKeys: boolean ;
97+ hasClarityApiKey: boolean ;
98+ // Character counts for key fields
99+ scriptCborLength: number ;
100+ stakeCredentialLength: number ;
101+ signersAddressesLength: number ;
102+ signersStakeKeysLength: number ;
103+ signersDRepKeysLength: number ;
104+ signersDescriptionsLength: number ;
105+ clarityApiKeyLength: number ;
106+ } ;
66107 } > ;
67108 failureSummary: Record < string , number > ;
68109}
@@ -73,6 +114,7 @@ interface BatchConfig {
73114 batchSize: number ;
74115 delayBetweenBatches: number ;
75116 maxRetries: number ;
117+ requestTimeout: number ; // in seconds
76118}
77119
78120interface ApiResponse < T > {
@@ -113,20 +155,52 @@ class BatchSnapshotOrchestrator {
113155 throw new Error ( 'SNAPSHOT_AUTH_TOKEN environment variable is required' ) ;
114156 }
115157
158+ if ( authToken . trim ( ) . length === 0 ) {
159+ throw new Error ( 'SNAPSHOT_AUTH_TOKEN environment variable cannot be empty' ) ;
160+ }
161+
162+ // Validate API base URL format
163+ try {
164+ new URL ( apiBaseUrl ) ;
165+ } catch ( error ) {
166+ throw new Error ( `Invalid API_BASE_URL format: ${ apiBaseUrl } ` ) ;
167+ }
168+
169+ // Parse and validate numeric environment variables
170+ const batchSize = this . parseAndValidateNumber ( process . env . BATCH_SIZE || '5' , 'BATCH_SIZE' , 1 , 10 ) ;
171+ const delayBetweenBatches = this . parseAndValidateNumber ( process . env . DELAY_BETWEEN_BATCHES || '10' , 'DELAY_BETWEEN_BATCHES' , 1 , 300 ) ;
172+ const maxRetries = this . parseAndValidateNumber ( process . env . MAX_RETRIES || '3' , 'MAX_RETRIES' , 1 , 10 ) ;
173+ const requestTimeout = this . parseAndValidateNumber ( process . env . REQUEST_TIMEOUT || '45' , 'REQUEST_TIMEOUT' , 10 , 300 ) ;
174+
116175 return {
117176 apiBaseUrl,
118177 authToken,
119- batchSize : parseInt ( process . env . BATCH_SIZE || '10' ) ,
120- delayBetweenBatches : parseInt ( process . env . DELAY_BETWEEN_BATCHES || '10' ) ,
121- maxRetries : parseInt ( process . env . MAX_RETRIES || '3' ) ,
178+ batchSize,
179+ delayBetweenBatches,
180+ maxRetries,
181+ requestTimeout,
122182 } ;
123183 }
124184
185+ private parseAndValidateNumber ( value : string , name : string , min : number , max : number ) : number {
186+ const parsed = parseInt ( value , 10 ) ;
187+
188+ if ( isNaN ( parsed ) ) {
189+ throw new Error ( `${ name } must be a valid integer, got: ${ value } ` ) ;
190+ }
191+
192+ if ( parsed < min || parsed > max ) {
193+ throw new Error ( `${ name } must be between ${ min } and ${ max } , got: ${ parsed } ` ) ;
194+ }
195+
196+ return parsed ;
197+ }
198+
125199 private async makeRequest < T > ( url : string , options : RequestInit = { } ) : Promise < ApiResponse < T >> {
126200 try {
127- // Add timeout to prevent hanging requests
201+ // Add configurable timeout to prevent hanging requests
128202 const controller = new AbortController ( ) ;
129- const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 30000 ) ; // 30 second timeout
203+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , this . config . requestTimeout * 1000 ) ;
130204
131205 const response = await fetch ( url , {
132206 ...options ,
@@ -148,7 +222,7 @@ class BatchSnapshotOrchestrator {
148222 return { data, status : response . status } ;
149223 } catch ( error ) {
150224 if ( error instanceof Error && error . name === 'AbortError' ) {
151- throw new Error ( ' Request timeout after 30 seconds' ) ;
225+ throw new Error ( ` Request timeout after ${ this . config . requestTimeout } seconds` ) ;
152226 }
153227 throw error ;
154228 }
@@ -172,6 +246,15 @@ class BatchSnapshotOrchestrator {
172246 private async processBatch ( batchNumber : number , batchId : string ) : Promise < BatchProgress | null > {
173247 console . log ( `📦 Processing batch ${ batchNumber } ...` ) ;
174248
249+ // Validate inputs
250+ if ( ! Number . isInteger ( batchNumber ) || batchNumber < 1 ) {
251+ throw new Error ( `Invalid batchNumber: ${ batchNumber } . Must be a positive integer.` ) ;
252+ }
253+
254+ if ( ! batchId || typeof batchId !== 'string' || batchId . trim ( ) . length === 0 ) {
255+ throw new Error ( `Invalid batchId: ${ batchId } . Must be a non-empty string.` ) ;
256+ }
257+
175258 for ( let attempt = 1 ; attempt <= this . config . maxRetries ; attempt ++ ) {
176259 try {
177260 const url = new URL ( `${ this . config . apiBaseUrl } /api/v1/stats/run-snapshots-batch` ) ;
@@ -196,6 +279,22 @@ class BatchSnapshotOrchestrator {
196279 console . log ( ` ❌ Failures in this batch:` ) ;
197280 data . progress . failures . forEach ( ( failure , index ) => {
198281 console . log ( ` ${ index + 1 } . ${ failure . walletId } ... - ${ failure . errorMessage } ` ) ;
282+ if ( failure . walletStructure ) {
283+ const structure = failure . walletStructure ;
284+ console . log ( ` 📋 Wallet Structure:` ) ;
285+ console . log ( ` • Name: ${ structure . name } (${ structure . name . length } chars)` ) ;
286+ console . log ( ` • Type: ${ structure . type } (${ structure . type . length } chars)` ) ;
287+ console . log ( ` • Required Signers: ${ structure . numRequiredSigners } /${ structure . signersCount } ` ) ;
288+ console . log ( ` • Has Stake Credential: ${ structure . hasStakeCredential } (${ structure . stakeCredentialLength } chars)` ) ;
289+ console . log ( ` • Has Script CBOR: ${ structure . hasScriptCbor } (${ structure . scriptCborLength } chars)` ) ;
290+ console . log ( ` • Is Archived: ${ structure . isArchived } ` ) ;
291+ console . log ( ` • Verified Count: ${ structure . verified } ` ) ;
292+ console . log ( ` • Has DRep Keys: ${ structure . hasDRepKeys } (${ structure . signersDRepKeysLength } items)` ) ;
293+ console . log ( ` • Has Clarity API Key: ${ structure . hasClarityApiKey } (${ structure . clarityApiKeyLength } chars)` ) ;
294+ console . log ( ` • Signers Addresses: ${ structure . signersAddressesLength } items` ) ;
295+ console . log ( ` • Signers Stake Keys: ${ structure . signersStakeKeysLength } items` ) ;
296+ console . log ( ` • Signers Descriptions: ${ structure . signersDescriptionsLength } items` ) ;
297+ }
199298 } ) ;
200299 }
201300
@@ -214,8 +313,10 @@ class BatchSnapshotOrchestrator {
214313 return null ;
215314 }
216315
217- // Wait before retry
218- await this . delay ( this . config . delayBetweenBatches ) ;
316+ // For 405 errors (Method Not Allowed), wait longer as it might be a server-side issue
317+ const waitTime = errorMessage . includes ( '405' ) ? this . config . delayBetweenBatches * 2 : this . config . delayBetweenBatches ;
318+ console . log ( ` ⏳ Waiting ${ waitTime } s before retry...` ) ;
319+ await this . delay ( waitTime ) ;
219320 }
220321 }
221322
@@ -253,8 +354,11 @@ class BatchSnapshotOrchestrator {
253354 // Accumulate failures
254355 firstBatch . failures . forEach ( failure => {
255356 this . results . allFailures . push ( {
256- ...failure ,
257- batchNumber : 1
357+ walletId : failure . walletId ,
358+ errorType : failure . errorType ,
359+ errorMessage : failure . errorMessage ,
360+ batchNumber : 1 ,
361+ walletStructure : failure . walletStructure
258362 } ) ;
259363 this . results . failureSummary [ failure . errorType ] = ( this . results . failureSummary [ failure . errorType ] || 0 ) + 1 ;
260364 } ) ;
@@ -284,8 +388,11 @@ class BatchSnapshotOrchestrator {
284388 // Accumulate failures
285389 batchProgress . failures . forEach ( failure => {
286390 this . results . allFailures . push ( {
287- ...failure ,
288- batchNumber
391+ walletId : failure . walletId ,
392+ errorType : failure . errorType ,
393+ errorMessage : failure . errorMessage ,
394+ batchNumber,
395+ walletStructure : failure . walletStructure
289396 } ) ;
290397 this . results . failureSummary [ failure . errorType ] = ( this . results . failureSummary [ failure . errorType ] || 0 ) + 1 ;
291398 } ) ;
0 commit comments