From ec3350ec97beaa5d88ac8deef38865020aeedc8f Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:14:19 +0100 Subject: [PATCH 1/6] fix: clementine interactive (#3157) --- package-lock.json | 17 +++++ package.json | 1 + src/config/config.ts | 1 + .../clementine/clementine-client.ts | 71 +++++++++---------- 4 files changed, 52 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index e579ebb90e..1d59ad2213 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,6 +90,7 @@ "nestjs-i18n": "^10.5.1", "nestjs-real-ip": "^2.2.0", "node-2fa": "^2.0.3", + "node-pty": "^1.1.0", "node-sql-parser": "^5.3.13", "nodemailer": "^6.10.1", "passport": "^0.6.0", @@ -22942,6 +22943,22 @@ "dev": true, "license": "MIT" }, + "node_modules/node-pty": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", + "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.1.0" + } + }, + "node_modules/node-pty/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", diff --git a/package.json b/package.json index 60bab8cd48..b2adfb4a0d 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "nestjs-i18n": "^10.5.1", "nestjs-real-ip": "^2.2.0", "node-2fa": "^2.0.3", + "node-pty": "^1.1.0", "node-sql-parser": "^5.3.13", "nodemailer": "^6.10.1", "passport": "^0.6.0", diff --git a/src/config/config.ts b/src/config/config.ts index f9a4249612..25bacd921a 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -825,6 +825,7 @@ export class Configuration { timeoutMs: parseInt(process.env.CLEMENTINE_TIMEOUT_MS ?? '60000'), signingTimeoutMs: parseInt(process.env.CLEMENTINE_SIGNING_TIMEOUT_MS ?? '300000'), expectedVersion: process.env.CLEMENTINE_CLI_VERSION ?? '', + passphrase: process.env.CLEMENTINE_PASSPHRASE ?? '', }, bitcoinTestnet4: { btcTestnet4Output: { diff --git a/src/integration/blockchain/clementine/clementine-client.ts b/src/integration/blockchain/clementine/clementine-client.ts index 3a099592de..bdad976a7f 100644 --- a/src/integration/blockchain/clementine/clementine-client.ts +++ b/src/integration/blockchain/clementine/clementine-client.ts @@ -1,4 +1,4 @@ -import { spawn } from 'child_process'; +import * as pty from 'node-pty'; import { DfxLogger } from 'src/shared/services/dfx-logger'; export enum ClementineNetwork { @@ -13,6 +13,7 @@ export interface ClementineConfig { timeoutMs: number; signingTimeoutMs: number; expectedVersion: string; + passphrase: string; } export interface ClementineVersionInfo { @@ -223,6 +224,11 @@ export class ClementineClient { async withdrawScan(signerAddress: string, destinationAddress: string): Promise { const output = await this.executeCommand(['withdraw', 'scan', signerAddress, destinationAddress]); + if (output.toLowerCase().includes('waiting for confirmation') || output.toLowerCase().includes('unconfirmed')) { + this.logger.verbose('withdrawScan: UTXO found but unconfirmed, waiting for confirmation'); + return null; + } + // Parse withdrawal UTXO from output (format: txid:vout) const utxoMatch = output.match(/([a-f0-9]{64}:\d+)/i); if (utxoMatch) { @@ -363,10 +369,9 @@ export class ClementineClient { private async executeCommand(args: string[], timeout?: number, addNetworkFlag = true): Promise { const finalArgs = addNetworkFlag ? this.addNetworkFlag(args) : args; - this.logger.verbose(`Executing: ${this.config.cliPath} ${finalArgs.join(' ')}`); try { - return await this.spawnAsync(finalArgs, timeout ?? this.config.timeoutMs); + return await this.spawnWithPty(finalArgs, timeout ?? this.config.timeoutMs); } catch (error) { const message = error instanceof Error ? error.message : String(error); this.logger.error(`Clementine CLI error: ${message}`); @@ -374,17 +379,23 @@ export class ClementineClient { } } - private spawnAsync(args: string[], timeout: number): Promise { + /** + * Spawn CLI with PTY to handle interactive passphrase prompts. + */ + private spawnWithPty(args: string[], timeout: number): Promise { return new Promise((resolve, reject) => { - let stdout = ''; - let stderr = ''; + let output = ''; let timeoutId: NodeJS.Timeout | null = null; - let killTimeoutId: NodeJS.Timeout | null = null; let isSettled = false; + let passphraseSent = false; + + this.logger.verbose(`Executing (PTY): ${this.config.cliPath} ${args.join(' ')}`); - const proc = spawn(this.config.cliPath, args, { - stdio: ['ignore', 'pipe', 'pipe'], - env: { ...process.env, HOME: this.config.homeDir }, + const proc = pty.spawn(this.config.cliPath, args, { + name: 'xterm', + cols: 200, + rows: 30, + env: { ...process.env, HOME: this.config.homeDir } as Record, }); const cleanup = (): void => { @@ -392,16 +403,13 @@ export class ClementineClient { clearTimeout(timeoutId); timeoutId = null; } - if (killTimeoutId) { - clearTimeout(killTimeoutId); - killTimeoutId = null; - } }; const settle = (error?: Error, result?: string): void => { if (isSettled) return; isSettled = true; cleanup(); + proc.kill(); if (error) { reject(error); @@ -410,38 +418,25 @@ export class ClementineClient { } }; - // Timeout handling timeoutId = setTimeout(() => { - proc.kill('SIGTERM'); - - // Force kill after 5 seconds if process doesn't respond to SIGTERM - killTimeoutId = setTimeout(() => { - if (proc.exitCode === null) { - // Process still running, force kill - proc.kill('SIGKILL'); - } - }, 5000); - settle(new Error(`Command timed out after ${timeout}ms`)); }, timeout); - proc.stdout.on('data', (data: Buffer) => { - stdout += data.toString(); - }); - - proc.stderr.on('data', (data: Buffer) => { - stderr += data.toString(); - }); + proc.onData((data: string) => { + output += data; - proc.on('error', (error: Error) => { - settle(new Error(`Spawn error: ${error.message}`)); + // Send passphrase when prompted + if (!passphraseSent && data.toLowerCase().includes('passphrase')) { + passphraseSent = true; + proc.write(this.config.passphrase + '\r'); + } }); - proc.on('close', (code: number | null) => { - if (code === 0) { - settle(undefined, stdout); + proc.onExit(({ exitCode }) => { + if (exitCode === 0) { + settle(undefined, output); } else { - settle(new Error(`Exit code ${code}: ${stderr || stdout}`)); + settle(new Error(`Exit code ${exitCode}: ${output}`)); } }); }); From 60110bc399b4bf05391d8c9a4cf3e97efddfa413 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:49:40 +0100 Subject: [PATCH 2/6] fix: increase send timeout (#3159) --- .../clementine/clementine-client.ts | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/integration/blockchain/clementine/clementine-client.ts b/src/integration/blockchain/clementine/clementine-client.ts index bdad976a7f..d391c5324c 100644 --- a/src/integration/blockchain/clementine/clementine-client.ts +++ b/src/integration/blockchain/clementine/clementine-client.ts @@ -310,14 +310,10 @@ export class ClementineClient { withdrawalUtxo: string, optimisticSignature: string, ): Promise { - await this.executeCommand([ - 'withdraw', - 'send-safe-withdraw', - signerAddress, - destinationAddress, - withdrawalUtxo, - optimisticSignature, - ]); + await this.executeCommand( + ['withdraw', 'send-safe-withdraw', signerAddress, destinationAddress, withdrawalUtxo, optimisticSignature], + this.config.signingTimeoutMs, + ); } /** @@ -355,14 +351,17 @@ export class ClementineClient { withdrawalUtxo: string, operatorPaidSignature: string, ): Promise { - await this.executeCommand([ - 'withdraw', - 'send-withdrawal-signature-to-operators', - signerAddress, - destinationAddress, - withdrawalUtxo, - operatorPaidSignature, - ]); + await this.executeCommand( + [ + 'withdraw', + 'send-withdrawal-signature-to-operators', + signerAddress, + destinationAddress, + withdrawalUtxo, + operatorPaidSignature, + ], + this.config.signingTimeoutMs, + ); } // --- INTERNAL METHODS --- // @@ -426,7 +425,10 @@ export class ClementineClient { output += data; // Send passphrase when prompted - if (!passphraseSent && data.toLowerCase().includes('passphrase')) { + if ( + !passphraseSent && + (data.toLowerCase().includes('passphrase') || data.toLowerCase().includes('secret key')) + ) { passphraseSent = true; proc.write(this.config.passphrase + '\r'); } From d48de7f0b74dae99582ace14c9b15918eab1cf94 Mon Sep 17 00:00:00 2001 From: Yannick <52333989+Yannick1712@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:11:48 +0100 Subject: [PATCH 3/6] [NOTASK] fix missing relation triggerVideoIdent (#3139) * [NOTASK] fix missing relation triggerVideoIdent * [NOTASK] Refactoring --- src/subdomains/core/aml/services/aml.service.ts | 3 +++ .../core/buy-crypto/process/services/buy-crypto.service.ts | 2 +- .../core/sell-crypto/process/services/buy-fiat.service.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/subdomains/core/aml/services/aml.service.ts b/src/subdomains/core/aml/services/aml.service.ts index 3ad0cb2240..58fec6773a 100644 --- a/src/subdomains/core/aml/services/aml.service.ts +++ b/src/subdomains/core/aml/services/aml.service.ts @@ -5,6 +5,7 @@ import { CountryService } from 'src/shared/models/country/country.service'; import { IpLogService } from 'src/shared/models/ip-log/ip-log.service'; import { DfxLogger } from 'src/shared/services/dfx-logger'; import { Util } from 'src/shared/utils/util'; +import { KycService } from 'src/subdomains/generic/kyc/services/kyc.service'; import { NameCheckService } from 'src/subdomains/generic/kyc/services/name-check.service'; import { AccountMergeService } from 'src/subdomains/generic/user/models/account-merge/account-merge.service'; import { BankData, BankDataType } from 'src/subdomains/generic/user/models/bank-data/bank-data.entity'; @@ -41,6 +42,7 @@ export class AmlService { private readonly userService: UserService, private readonly transactionService: TransactionService, private readonly ipLogService: IpLogService, + private readonly kycService: KycService, ) {} async postProcessing(entity: BuyFiat | BuyCrypto, last30dVolume: number | undefined): Promise { @@ -98,6 +100,7 @@ export class AmlService { const blacklist = await this.specialExternalBankAccountService.getBlacklist(); const multiAccountBankNames = await this.specialExternalBankAccountService.getMultiAccountNames(); + entity.userData.kycSteps = await this.kycService.getStepsByUserData(entity.userData.id); entity.userData.users = await this.userService.getAllUserDataUsers(entity.userData.id); let bankData = await this.getBankData(entity); const refUser = diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts index 8b1a4aa7ab..006c9ea7f4 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts @@ -234,7 +234,7 @@ export class BuyCryptoService { cryptoInput: true, bankTx: true, checkoutTx: true, - transaction: { user: { wallet: true }, userData: { users: true } }, + transaction: { user: { wallet: true }, userData: { users: true, kycSteps: true } }, chargebackOutput: true, bankData: true, }, diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts index dbd75f4cd6..de02dfd4de 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts @@ -136,7 +136,7 @@ export class BuyFiatService { fiatOutput: true, bankTx: true, cryptoInput: { route: { user: true }, transaction: true }, - transaction: { user: { wallet: true }, userData: true }, + transaction: { user: { wallet: true }, userData: { kycSteps: true } }, bankData: true, }, }); From c9c79cae6353c41046675de8409f30e1b1807f9a Mon Sep 17 00:00:00 2001 From: Lam Nguyen <32935491+xlamn@users.noreply.github.com> Date: Fri, 6 Feb 2026 16:13:48 +0100 Subject: [PATCH 4/6] feat: add endpoint to check whether wallet is registered for realunit (#3160) --- .../realunit/controllers/realunit.controller.ts | 13 +++++++++++++ .../supporting/realunit/realunit.service.ts | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/subdomains/supporting/realunit/controllers/realunit.controller.ts b/src/subdomains/supporting/realunit/controllers/realunit.controller.ts index 0461eb6a98..23327e13b6 100644 --- a/src/subdomains/supporting/realunit/controllers/realunit.controller.ts +++ b/src/subdomains/supporting/realunit/controllers/realunit.controller.ts @@ -324,6 +324,19 @@ export class RealUnitController { // --- Registration Endpoints --- + @Get('register/status') + @ApiBearerAuth() + @UseGuards(AuthGuard(), RoleGuard(UserRole.USER), UserActiveGuard()) + @ApiOperation({ + summary: 'Check if wallet is registered for RealUnit', + description: 'Returns true if the connected wallet is registered for RealUnit, false otherwise', + }) + @ApiOkResponse({ type: Boolean }) + async isRegistered(@GetJwt() jwt: JwtPayload): Promise { + const user = await this.userService.getUser(jwt.user, { userData: { kycSteps: true } }); + return this.realunitService.hasRegistrationForWallet(user.userData, jwt.address); + } + @Post('register/email') @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.ACCOUNT), UserActiveGuard()) diff --git a/src/subdomains/supporting/realunit/realunit.service.ts b/src/subdomains/supporting/realunit/realunit.service.ts index 21a29bd545..babdcd6a5b 100644 --- a/src/subdomains/supporting/realunit/realunit.service.ts +++ b/src/subdomains/supporting/realunit/realunit.service.ts @@ -579,7 +579,7 @@ export class RealUnitService { if (!success) throw new BadRequestException('Failed to forward registration to Aktionariat'); } - private hasRegistrationForWallet(userData: UserData, walletAddress: string): boolean { + hasRegistrationForWallet(userData: UserData, walletAddress: string): boolean { return userData .getStepsWith(KycStepName.REALUNIT_REGISTRATION) .filter((s) => !(s.isFailed || s.isCanceled)) From 0f062f9fb4c13697e923b67b2377c8e45fb12434 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:31:08 +0100 Subject: [PATCH 5/6] fix: multiple fixes (#3161) --- .../clementine/clementine-client.ts | 41 ++++++++++++----- .../actions/clementine-bridge.adapter.ts | 44 ++++++++++++++++--- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/integration/blockchain/clementine/clementine-client.ts b/src/integration/blockchain/clementine/clementine-client.ts index d391c5324c..7fd07bcf65 100644 --- a/src/integration/blockchain/clementine/clementine-client.ts +++ b/src/integration/blockchain/clementine/clementine-client.ts @@ -303,16 +303,20 @@ export class ClementineClient { * @param destinationAddress Bitcoin destination address * @param withdrawalUtxo The withdrawal UTXO (format: txid:vout) * @param optimisticSignature The optimistic withdrawal signature + * @param citreaPrivateKey Citrea private key for signing the withdrawal transaction (64 hex chars) */ async withdrawSend( signerAddress: string, destinationAddress: string, withdrawalUtxo: string, optimisticSignature: string, + citreaPrivateKey: string, ): Promise { await this.executeCommand( ['withdraw', 'send-safe-withdraw', signerAddress, destinationAddress, withdrawalUtxo, optimisticSignature], this.config.signingTimeoutMs, + true, + citreaPrivateKey, ); } @@ -366,11 +370,16 @@ export class ClementineClient { // --- INTERNAL METHODS --- // - private async executeCommand(args: string[], timeout?: number, addNetworkFlag = true): Promise { + private async executeCommand( + args: string[], + timeout?: number, + addNetworkFlag = true, + citreaPrivateKey?: string, + ): Promise { const finalArgs = addNetworkFlag ? this.addNetworkFlag(args) : args; try { - return await this.spawnWithPty(finalArgs, timeout ?? this.config.timeoutMs); + return await this.spawnWithPty(finalArgs, timeout ?? this.config.timeoutMs, citreaPrivateKey); } catch (error) { const message = error instanceof Error ? error.message : String(error); this.logger.error(`Clementine CLI error: ${message}`); @@ -379,14 +388,18 @@ export class ClementineClient { } /** - * Spawn CLI with PTY to handle interactive passphrase prompts. + * Spawn CLI with PTY to handle interactive prompts. + * @param args CLI arguments + * @param timeout Timeout in milliseconds + * @param citreaPrivateKey Optional Citrea private key for signing withdrawals (64 hex chars) */ - private spawnWithPty(args: string[], timeout: number): Promise { + private spawnWithPty(args: string[], timeout: number, citreaPrivateKey?: string): Promise { return new Promise((resolve, reject) => { let output = ''; let timeoutId: NodeJS.Timeout | null = null; let isSettled = false; - let passphraseSent = false; + let passphraseHandled = false; + let citreaKeyHandled = false; this.logger.verbose(`Executing (PTY): ${this.config.cliPath} ${args.join(' ')}`); @@ -423,15 +436,21 @@ export class ClementineClient { proc.onData((data: string) => { output += data; + const lowerData = data.toLowerCase(); - // Send passphrase when prompted - if ( - !passphraseSent && - (data.toLowerCase().includes('passphrase') || data.toLowerCase().includes('secret key')) - ) { - passphraseSent = true; + if (!passphraseHandled && lowerData.includes('passphrase')) { + passphraseHandled = true; proc.write(this.config.passphrase + '\r'); } + + if (!citreaKeyHandled && lowerData.includes('secret key')) { + citreaKeyHandled = true; + if (citreaPrivateKey) { + proc.write(citreaPrivateKey + '\r'); + } else { + settle(new Error('CLI prompted for Citrea private key but none was provided')); + } + } }); proc.onExit(({ exitCode }) => { diff --git a/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts b/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts index a5caec0461..249e7198ce 100644 --- a/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts +++ b/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts @@ -66,10 +66,17 @@ const CITREA_CHAIN_IDS: Record = { [ClementineNetwork.TESTNET4]: 5115, // Citrea testnet }; +// Bitcoin confirmations required before block is relayed to Citrea's BitcoinLightClient +const BITCOIN_RELAY_CONFIRMATIONS: Record = { + [ClementineNetwork.BITCOIN]: 6, + [ClementineNetwork.TESTNET4]: 100, +}; + interface WithdrawCorrelationData { step: string; signerAddress: string; destinationAddress: string; + dustTxId?: string; withdrawalUtxo?: string; optimisticSignature?: string; operatorPaidSignature?: string; @@ -264,6 +271,7 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { step: 'dust_sent', signerAddress: this.signerAddress, destinationAddress, + dustTxId, }; return `${CORRELATION_PREFIX.WITHDRAW}${this.encodeWithdrawCorrelation(correlationData)}`; @@ -287,13 +295,12 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { const correlationData = order.correlationId.replace(CORRELATION_PREFIX.DEPOSIT, ''); const [depositAddress, btcTxId] = correlationData.split(':'); - // Step 1: Verify the Bitcoin transaction is confirmed - if (btcTxId) { - const isConfirmed = await this.bitcoinClient.isTxComplete(btcTxId, 6); // Clementine requires 6+ confirmations - if (!isConfirmed) { - this.logger.verbose(`Deposit ${depositAddress}: BTC transaction not yet confirmed (need 6+)`); - return false; - } + // Step 1: Verify the Bitcoin transaction has enough confirmations for block relay + if (btcTxId && !(await this.isBtcTxRelayConfirmed(btcTxId))) { + this.logger.verbose( + `Deposit ${depositAddress}: BTC TX not yet confirmed (need ${BITCOIN_RELAY_CONFIRMATIONS[this.network]}+)`, + ); + return false; } // Step 2: Check Clementine deposit status @@ -403,6 +410,14 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { order: LiquidityManagementOrder, data: WithdrawCorrelationData, ): Promise { + // Check if the dust TX has enough confirmations for the block to be relayed to Citrea + if (data.dustTxId && !(await this.isBtcTxRelayConfirmed(data.dustTxId))) { + this.logger.verbose( + `Withdrawal: waiting for dust TX to reach ${BITCOIN_RELAY_CONFIRMATIONS[this.network]} confirmations`, + ); + return false; + } + // Idempotency check: verify if withdrawal was already sent to bridge // This prevents double cBTC burning if the process crashes after withdrawSend() // but before the correlationId is persisted @@ -430,6 +445,7 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { data.destinationAddress, data.withdrawalUtxo, data.optimisticSignature, + this.citreaPrivateKey, ); data.step = 'sent_to_bridge'; @@ -739,6 +755,12 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { return this.network === ClementineNetwork.TESTNET4; } + private get citreaPrivateKey(): string { + return this.isTestnet + ? GetConfig().blockchain.citreaTestnet.citreaTestnetWalletPrivateKey + : GetConfig().blockchain.citrea.citreaWalletPrivateKey; + } + private get citreaBlockchain(): Blockchain { return this.isTestnet ? Blockchain.CITREA_TESTNET : Blockchain.CITREA; } @@ -752,4 +774,12 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { ? this.bitcoinTestnet4FeeService.getRecommendedFeeRate() : this.bitcoinFeeService.getRecommendedFeeRate(); } + + /** + * Check if a Bitcoin TX has enough confirmations for its block to be relayed to Citrea. + */ + private isBtcTxRelayConfirmed(txId: string): Promise { + const requiredConfirmations = BITCOIN_RELAY_CONFIRMATIONS[this.network]; + return this.bitcoinClient.isTxComplete(txId, requiredConfirmations); + } } From 5fd61b65d22ccd2f8c25c2afc3a3217673ffca96 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:59:43 +0100 Subject: [PATCH 6/6] fix: correct TxAudit filter year from 2025 to 2026 (#3164) * fix: correct TxAudit filter year from 2025 to 2026 The file download export searched for '-TxAudit2025' in filenames, but the 40 existing files are named with '-TxAudit2026'. * fix: keep consistent toLowerCase pattern --- src/config/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.ts b/src/config/config.ts index 25bacd921a..7095825298 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -474,7 +474,7 @@ export class Configuration { { prefixes: (userData: UserData) => [`user/${userData.id}/UserNotes`], fileTypes: [ContentType.PDF], - filter: (file: KycFileBlob) => file.name.toLowerCase().includes('-TxAudit2025'.toLowerCase()), + filter: (file: KycFileBlob) => file.name.toLowerCase().includes('-TxAudit2026'.toLowerCase()), }, ], },