From 100cc54bfe80fc31b76d0788b31aa08a7b486448 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 5 Jun 2026 08:49:12 +0200 Subject: [PATCH 1/2] fix(support): match compliance search address case-insensitively --- .../generic/support/support.service.ts | 2 +- .../user/models/user/tests/user.service.spec.ts | 17 +++++++++++++++++ .../generic/user/models/user/user.service.ts | 10 ++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/subdomains/generic/support/support.service.ts b/src/subdomains/generic/support/support.service.ts index 63c1d2c125..735f37df74 100644 --- a/src/subdomains/generic/support/support.service.ts +++ b/src/subdomains/generic/support/support.service.ts @@ -1074,7 +1074,7 @@ export class SupportService { }; if (Config.formats.address.test(key)) { - const user = await this.userService.getUserByKey('address', key, true); + const user = await this.userService.getUserByAddressIgnoreCase(key); if (user) return { type: ComplianceSearchType.USER_ADDRESS, userData: user.userData }; return Promise.all([ diff --git a/src/subdomains/generic/user/models/user/tests/user.service.spec.ts b/src/subdomains/generic/user/models/user/tests/user.service.spec.ts index f9025101c4..8b6c713cc9 100644 --- a/src/subdomains/generic/user/models/user/tests/user.service.spec.ts +++ b/src/subdomains/generic/user/models/user/tests/user.service.spec.ts @@ -15,6 +15,7 @@ import { FeeService } from 'src/subdomains/supporting/payment/services/fee.servi import { UserDataRepository } from '../../user-data/user-data.repository'; import { UserDataService } from '../../user-data/user-data.service'; import { WalletService } from '../../wallet/wallet.service'; +import { User } from '../user.entity'; import { UserRepository } from '../user.repository'; import { UserService } from '../user.service'; @@ -82,4 +83,20 @@ describe('UserService', () => { it('should be defined', () => { expect(service).toBeDefined(); }); + + it('should match the address case-insensitively in getUserByAddressIgnoreCase', async () => { + const expected = Object.assign(new User(), { id: 1, address: '0xAbCdEf' }); + const queryBuilder = { + select: jest.fn().mockReturnThis(), + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + getOne: jest.fn().mockResolvedValue(expected), + }; + jest.spyOn(userRepo, 'createQueryBuilder').mockReturnValue(queryBuilder as any); + + const result = await service.getUserByAddressIgnoreCase('0xabcdef'); + + expect(queryBuilder.where).toHaveBeenCalledWith('LOWER(user.address) = LOWER(:address)', { address: '0xabcdef' }); + expect(result).toBe(expected); + }); }); diff --git a/src/subdomains/generic/user/models/user/user.service.ts b/src/subdomains/generic/user/models/user/user.service.ts index 59f313cfe5..37e335caec 100644 --- a/src/subdomains/generic/user/models/user/user.service.ts +++ b/src/subdomains/generic/user/models/user/user.service.ts @@ -92,6 +92,16 @@ export class UserService { return this.userRepo.findOne({ where: { address }, relations }); } + // case-insensitive variant for the compliance search only: admins may paste a non-checksummed (lower-case) EVM address, while addresses are stored EIP-55 checksummed + async getUserByAddressIgnoreCase(address: string): Promise { + return this.userRepo + .createQueryBuilder('user') + .select('user') + .leftJoinAndSelect('user.userData', 'userData') + .where('LOWER(user.address) = LOWER(:address)', { address }) + .getOne(); + } + async getUserByKey(key: string, value: any, onlyDefaultRelation = false): Promise { const query = this.userRepo .createQueryBuilder('user') From 7c73104d4680ef128cbdd3742b34a437a765d9de Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:07:15 +0200 Subject: [PATCH 2/2] fix(support): extend compliance search case-insensitive match to deposit addresses --- .../swap/__tests__/swap.service.spec.ts | 19 +++++++++++++++++++ .../buy-crypto/routes/swap/swap.service.ts | 12 ++++++++++++ .../route/__tests__/sell.service.spec.ts | 19 +++++++++++++++++++ .../core/sell-crypto/route/sell.service.ts | 12 ++++++++++++ .../generic/support/support.service.ts | 4 ++-- 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/subdomains/core/buy-crypto/routes/swap/__tests__/swap.service.spec.ts b/src/subdomains/core/buy-crypto/routes/swap/__tests__/swap.service.spec.ts index 9105719367..ca534df0f8 100644 --- a/src/subdomains/core/buy-crypto/routes/swap/__tests__/swap.service.spec.ts +++ b/src/subdomains/core/buy-crypto/routes/swap/__tests__/swap.service.spec.ts @@ -17,6 +17,7 @@ import { TransactionHelper } from 'src/subdomains/supporting/payment/services/tr import { TransactionRequestService } from 'src/subdomains/supporting/payment/services/transaction-request.service'; import { BuyCryptoWebhookService } from '../../../process/services/buy-crypto-webhook.service'; import { BuyCryptoService } from '../../../process/services/buy-crypto.service'; +import { Swap } from '../swap.entity'; import { SwapRepository } from '../swap.repository'; import { SwapService } from '../swap.service'; @@ -89,6 +90,24 @@ describe('SwapService', () => { expect(service).toBeDefined(); }); + it('should match the deposit address case-insensitively in getSwapByDepositAddressIgnoreCase', async () => { + const expected = Object.assign(new Swap(), { id: 1 }); + const queryBuilder = { + select: jest.fn().mockReturnThis(), + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + getOne: jest.fn().mockResolvedValue(expected), + }; + jest.spyOn(swapRepo, 'createQueryBuilder').mockReturnValue(queryBuilder as any); + + const result = await service.getSwapByDepositAddressIgnoreCase('0xabcdef'); + + expect(queryBuilder.where).toHaveBeenCalledWith('LOWER(deposit.address) = LOWER(:address)', { + address: '0xabcdef', + }); + expect(result).toBe(expected); + }); + describe('createDepositTx', () => { const mockRequest = { id: 1, diff --git a/src/subdomains/core/buy-crypto/routes/swap/swap.service.ts b/src/subdomains/core/buy-crypto/routes/swap/swap.service.ts index 3648623820..dd4a3ebacf 100644 --- a/src/subdomains/core/buy-crypto/routes/swap/swap.service.ts +++ b/src/subdomains/core/buy-crypto/routes/swap/swap.service.ts @@ -147,6 +147,18 @@ export class SwapService { return swap; } + // case-insensitive deposit-address variant for the compliance search only: admins may paste a non-checksummed (lower-case) EVM address, while addresses are stored EIP-55 checksummed + async getSwapByDepositAddressIgnoreCase(address: string): Promise { + return this.swapRepo + .createQueryBuilder('swap') + .select('swap') + .leftJoinAndSelect('swap.deposit', 'deposit') + .leftJoinAndSelect('swap.user', 'user') + .leftJoinAndSelect('user.userData', 'userData') + .where('LOWER(deposit.address) = LOWER(:address)', { address }) + .getOne(); + } + async getSwapByKey(key: string, value: any, onlyDefaultRelation = false): Promise { const query = this.swapRepo .createQueryBuilder('swap') diff --git a/src/subdomains/core/sell-crypto/route/__tests__/sell.service.spec.ts b/src/subdomains/core/sell-crypto/route/__tests__/sell.service.spec.ts index ffa78ca11c..a469aea084 100644 --- a/src/subdomains/core/sell-crypto/route/__tests__/sell.service.spec.ts +++ b/src/subdomains/core/sell-crypto/route/__tests__/sell.service.spec.ts @@ -17,6 +17,7 @@ import { PayInService } from 'src/subdomains/supporting/payin/services/payin.ser import { TransactionHelper } from 'src/subdomains/supporting/payment/services/transaction-helper'; import { TransactionRequestService } from 'src/subdomains/supporting/payment/services/transaction-request.service'; import { BuyFiatService } from '../../process/services/buy-fiat.service'; +import { Sell } from '../sell.entity'; import { SellRepository } from '../sell.repository'; import { SellService } from '../sell.service'; @@ -89,6 +90,24 @@ describe('SellService', () => { expect(service).toBeDefined(); }); + it('should match the deposit address case-insensitively in getSellByDepositAddressIgnoreCase', async () => { + const expected = Object.assign(new Sell(), { id: 1 }); + const queryBuilder = { + select: jest.fn().mockReturnThis(), + leftJoinAndSelect: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + getOne: jest.fn().mockResolvedValue(expected), + }; + jest.spyOn(sellRepo, 'createQueryBuilder').mockReturnValue(queryBuilder as any); + + const result = await service.getSellByDepositAddressIgnoreCase('0xabcdef'); + + expect(queryBuilder.where).toHaveBeenCalledWith('LOWER(deposit.address) = LOWER(:address)', { + address: '0xabcdef', + }); + expect(result).toBe(expected); + }); + describe('createDepositTx', () => { const mockRequest = { id: 1, diff --git a/src/subdomains/core/sell-crypto/route/sell.service.ts b/src/subdomains/core/sell-crypto/route/sell.service.ts index 039c03cc3f..5b300694db 100644 --- a/src/subdomains/core/sell-crypto/route/sell.service.ts +++ b/src/subdomains/core/sell-crypto/route/sell.service.ts @@ -90,6 +90,18 @@ export class SellService { return this.sellRepo.findOne(merge(defaultOptions, options)); } + // case-insensitive deposit-address variant for the compliance search only: admins may paste a non-checksummed (lower-case) EVM address, while addresses are stored EIP-55 checksummed + async getSellByDepositAddressIgnoreCase(address: string): Promise { + return this.sellRepo + .createQueryBuilder('sell') + .select('sell') + .leftJoinAndSelect('sell.deposit', 'deposit') + .leftJoinAndSelect('sell.user', 'user') + .leftJoinAndSelect('user.userData', 'userData') + .where('LOWER(deposit.address) = LOWER(:address)', { address }) + .getOne(); + } + async getSellByKey(key: string, value: any, onlyDefaultRelation = false): Promise { const query = this.sellRepo .createQueryBuilder('sell') diff --git a/src/subdomains/generic/support/support.service.ts b/src/subdomains/generic/support/support.service.ts index 735f37df74..3c31ee79b2 100644 --- a/src/subdomains/generic/support/support.service.ts +++ b/src/subdomains/generic/support/support.service.ts @@ -1078,8 +1078,8 @@ export class SupportService { if (user) return { type: ComplianceSearchType.USER_ADDRESS, userData: user.userData }; return Promise.all([ - this.sellService.getSellByKey('deposit.address', key, true), - this.swapService.getSwapByKey('deposit.address', key, true), + this.sellService.getSellByDepositAddressIgnoreCase(key), + this.swapService.getSwapByDepositAddressIgnoreCase(key), ]).then((s) => { return { type: ComplianceSearchType.DEPOSIT_ADDRESS, userData: s.find((s) => s)?.userData }; });