diff --git a/src/integration/blockchain/spark/spark-client.ts b/src/integration/blockchain/spark/spark-client.ts index a836ef3969..3c7ea607f6 100644 --- a/src/integration/blockchain/spark/spark-client.ts +++ b/src/integration/blockchain/spark/spark-client.ts @@ -44,6 +44,8 @@ export interface SparkFeeEstimate { } export class SparkClient extends BlockchainClient { + private static readonly INIT_TIMEOUT_MS = 60_000; + private readonly logger = new DfxLogger(SparkClient); private wallet: AsyncField; @@ -59,6 +61,13 @@ export class SparkClient extends BlockchainClient { this.startTokenOptimization(); } + resetWallet(): void { + this.logger.warn('Spark wallet reset triggered externally'); + this.wallet.reset(); + this.cachedAddress.reset(); + this.reconnectWallet(); + } + private async call(operation: (wallet: SparkWallet) => Promise): Promise { try { const wallet = await this.wallet; @@ -153,14 +162,7 @@ export class SparkClient extends BlockchainClient { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { - const { wallet } = await SparkWallet.initialize({ - mnemonicOrSeed: GetConfig().blockchain.spark.sparkWalletSeed, - accountNumber: 0, - options: { - network: 'MAINNET', - tokenOptimizationOptions: { enabled: false }, - }, - }); + const wallet = await this.initializeWithTimeout(); wallet.on('stream:disconnected', () => this.reconnectWallet()); @@ -180,6 +182,32 @@ export class SparkClient extends BlockchainClient { throw new Error('Spark wallet initialization failed after all retries'); } + private initializeWithTimeout(): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout( + () => reject(new Error(`Spark wallet initialization timed out after ${SparkClient.INIT_TIMEOUT_MS / 1000}s`)), + SparkClient.INIT_TIMEOUT_MS, + ); + + SparkWallet.initialize({ + mnemonicOrSeed: GetConfig().blockchain.spark.sparkWalletSeed, + accountNumber: 0, + options: { + network: 'MAINNET', + tokenOptimizationOptions: { enabled: false }, + }, + }) + .then(({ wallet }) => { + clearTimeout(timer); + resolve(wallet); + }) + .catch((e) => { + clearTimeout(timer); + reject(e); + }); + }); + } + private startTokenOptimization(): void { if (this.tokenOptimizationInterval) clearInterval(this.tokenOptimizationInterval); diff --git a/src/main.ts b/src/main.ts index 191bd9919a..7097c291b4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,7 +25,10 @@ import { PricingService } from './subdomains/supporting/pricing/services/pricing process.on('uncaughtException', (error) => { const logger = new DfxLogger('UncaughtException'); - if (error?.constructor?.name?.includes('Spark') || error?.message?.includes('Channel has been shut down')) { + const isSparkError = + error?.constructor?.name?.includes('Spark') || error?.message?.includes('Channel has been shut down'); + + if (isSparkError) { logger.error('Spark SDK uncaught exception (process kept alive):', error); return; }