-
Notifications
You must be signed in to change notification settings - Fork 18
Fix case-sensitive email comparison issues #2695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9f59c90 to
5550ad1
Compare
- Add Util.toLowerCaseTrim for email input normalization - Apply @Transform(Util.toLowerCaseTrim) to all email input DTOs: - KycContactData, KycInputDataDto - AuthMailDto - UpdateUserDataDto, UpdateUserMailDto - CreateRecommendationDto - AktionariatRegistrationDto Fixes issue where users registering with mixed-case emails (e.g. Samuel.kullmann@...) had merge conflicts when later using lowercase variant (samuel.kullmann@...)
5550ad1 to
f8e0d05
Compare
davidleomay
approved these changes
Dec 23, 2025
TaprootFreak
added a commit
that referenced
this pull request
Dec 24, 2025
- Add Util.toLowerCaseTrim for email input normalization - Apply @Transform(Util.toLowerCaseTrim) to all email input DTOs: - KycContactData, KycInputDataDto - AuthMailDto - UpdateUserDataDto, UpdateUserMailDto - CreateRecommendationDto - AktionariatRegistrationDto Fixes issue where users registering with mixed-case emails (e.g. Samuel.kullmann@...) had merge conflicts when later using lowercase variant (samuel.kullmann@...)
xlamn
pushed a commit
that referenced
this pull request
Dec 24, 2025
* Normalize email inputs to lowercase (#2695) - Add Util.toLowerCaseTrim for email input normalization - Apply @Transform(Util.toLowerCaseTrim) to all email input DTOs: - KycContactData, KycInputDataDto - AuthMailDto - UpdateUserDataDto, UpdateUserMailDto - CreateRecommendationDto - AktionariatRegistrationDto Fixes issue where users registering with mixed-case emails (e.g. Samuel.kullmann@...) had merge conflicts when later using lowercase variant (samuel.kullmann@...) * Allow RealUnit registration with unverified email - If userData.mail is NULL, try to set the submitted email - Email is rejected if already used by another user - Email is saved to userData.mail if available * Validate lowercase email instead of transforming - Replace @Transform(Util.toLowerCaseTrim) with @IsLowercase validation - Preserves original email for EIP-712 signature verification - Returns clear error message if email contains uppercase
TaprootFreak
added a commit
that referenced
this pull request
Dec 29, 2025
* Add dedicated RealUnit buy endpoint with personal IBAN - Add PUT /realunit/paymentInfo endpoint for REALU purchases - Auto-create buy-specific vIBAN for each purchase request - Require KYC Level 50, RealUnit registration, and allowlist status - Return personal IBAN, estimated shares, and QR code for payment - No remittanceInfo needed (IBAN is unique per asset) * Restrict RealUnit buy currency to CHF and EUR only * Remove bank name from RealUnit payment info response * Use RealUnit company address as payment recipient * Use RealUnit config for recipient address * Return separate address fields in RealUnit payment info * Remove sepaInstant from RealUnit payment info response * Remove allowlist check from RealUnit payment info * Add fee/rate calculation to RealUnit payment info - Integrate TransactionHelper.getTxDetails() for fee calculation - Return fees, minVolume, maxVolume, exchangeRate, rate, priceSteps - Use estimatedAmount from transaction helper instead of brokerbot - Only generate QR code if isValid is true * Add TransactionRequest creation to RealUnit payment info - Import TransactionRequestService and create transaction request after building response - Add id and routeId fields to RealUnitPaymentInfoDto - Fix bankInfo type for Swiss QR code generation * Add minVolumeTarget and maxVolumeTarget to RealUnit payment info * Add timestamp to RealUnit payment info response * fix: verifyRealUnitRegistrationSignature used wrong value for verifyTypedData. * Allow RealUnit registration with unverified email (#2699) * Normalize email inputs to lowercase (#2695) - Add Util.toLowerCaseTrim for email input normalization - Apply @Transform(Util.toLowerCaseTrim) to all email input DTOs: - KycContactData, KycInputDataDto - AuthMailDto - UpdateUserDataDto, UpdateUserMailDto - CreateRecommendationDto - AktionariatRegistrationDto Fixes issue where users registering with mixed-case emails (e.g. Samuel.kullmann@...) had merge conflicts when later using lowercase variant (samuel.kullmann@...) * Allow RealUnit registration with unverified email - If userData.mail is NULL, try to set the submitted email - Email is rejected if already used by another user - Email is saved to userData.mail if available * Validate lowercase email instead of transforming - Replace @Transform(Util.toLowerCaseTrim) with @IsLowercase validation - Preserves original email for EIP-712 signature verification - Returns clear error message if email contains uppercase --------- Co-authored-by: TuanLamNguyen <xnguyen.lam@gmail.com>
TaprootFreak
added a commit
that referenced
this pull request
Dec 29, 2025
* Add dedicated RealUnit buy endpoint with personal IBAN - Add PUT /realunit/paymentInfo endpoint for REALU purchases - Auto-create buy-specific vIBAN for each purchase request - Require KYC Level 50, RealUnit registration, and allowlist status - Return personal IBAN, estimated shares, and QR code for payment - No remittanceInfo needed (IBAN is unique per asset) * Restrict RealUnit buy currency to CHF and EUR only * Remove bank name from RealUnit payment info response * Use RealUnit company address as payment recipient * Use RealUnit config for recipient address * Return separate address fields in RealUnit payment info * Remove sepaInstant from RealUnit payment info response * Remove allowlist check from RealUnit payment info * Add fee/rate calculation to RealUnit payment info - Integrate TransactionHelper.getTxDetails() for fee calculation - Return fees, minVolume, maxVolume, exchangeRate, rate, priceSteps - Use estimatedAmount from transaction helper instead of brokerbot - Only generate QR code if isValid is true * Add TransactionRequest creation to RealUnit payment info - Import TransactionRequestService and create transaction request after building response - Add id and routeId fields to RealUnitPaymentInfoDto - Fix bankInfo type for Swiss QR code generation * Add minVolumeTarget and maxVolumeTarget to RealUnit payment info * Add timestamp to RealUnit payment info response * fix: verifyRealUnitRegistrationSignature used wrong value for verifyTypedData. * Allow RealUnit registration with unverified email (#2699) * Normalize email inputs to lowercase (#2695) - Add Util.toLowerCaseTrim for email input normalization - Apply @Transform(Util.toLowerCaseTrim) to all email input DTOs: - KycContactData, KycInputDataDto - AuthMailDto - UpdateUserDataDto, UpdateUserMailDto - CreateRecommendationDto - AktionariatRegistrationDto Fixes issue where users registering with mixed-case emails (e.g. Samuel.kullmann@...) had merge conflicts when later using lowercase variant (samuel.kullmann@...) * Allow RealUnit registration with unverified email - If userData.mail is NULL, try to set the submitted email - Email is rejected if already used by another user - Email is saved to userData.mail if available * Validate lowercase email instead of transforming - Replace @Transform(Util.toLowerCaseTrim) with @IsLowercase validation - Preserves original email for EIP-712 signature verification - Returns clear error message if email contains uppercase --------- Co-authored-by: TuanLamNguyen <xnguyen.lam@gmail.com>
TaprootFreak
added a commit
that referenced
this pull request
Dec 29, 2025
* Add buy-specific virtual IBAN support - Add buyId relation to VirtualIban entity for asset-specific vIBANs - Add personalIbanEnabled flag to Asset entity - Add buySpecificIbanEnabled flag to Wallet entity - Auto-create vIBAN when requesting payment info for eligible assets - Assign incoming payments via vIBAN→Buy link (no remittanceInfo needed) - Update QR codes, GiroCodes and invoices to omit reference for buy-specific IBANs - Add unique index on (buyId, currencyId) for race condition protection - Limit to max 10 vIBANs per UserData (falls back to normal bank silently) - Fix cache invalidation consistency in VirtualIbanService * Add dedicated RealUnit buy endpoint with personal IBAN (#2686) * Add dedicated RealUnit buy endpoint with personal IBAN - Add PUT /realunit/paymentInfo endpoint for REALU purchases - Auto-create buy-specific vIBAN for each purchase request - Require KYC Level 50, RealUnit registration, and allowlist status - Return personal IBAN, estimated shares, and QR code for payment - No remittanceInfo needed (IBAN is unique per asset) * Restrict RealUnit buy currency to CHF and EUR only * Remove bank name from RealUnit payment info response * Use RealUnit company address as payment recipient * Use RealUnit config for recipient address * Return separate address fields in RealUnit payment info * Remove sepaInstant from RealUnit payment info response * Remove allowlist check from RealUnit payment info * Add fee/rate calculation to RealUnit payment info - Integrate TransactionHelper.getTxDetails() for fee calculation - Return fees, minVolume, maxVolume, exchangeRate, rate, priceSteps - Use estimatedAmount from transaction helper instead of brokerbot - Only generate QR code if isValid is true * Add TransactionRequest creation to RealUnit payment info - Import TransactionRequestService and create transaction request after building response - Add id and routeId fields to RealUnitPaymentInfoDto - Fix bankInfo type for Swiss QR code generation * Add minVolumeTarget and maxVolumeTarget to RealUnit payment info * Add timestamp to RealUnit payment info response * fix: verifyRealUnitRegistrationSignature used wrong value for verifyTypedData. * Allow RealUnit registration with unverified email (#2699) * Normalize email inputs to lowercase (#2695) - Add Util.toLowerCaseTrim for email input normalization - Apply @Transform(Util.toLowerCaseTrim) to all email input DTOs: - KycContactData, KycInputDataDto - AuthMailDto - UpdateUserDataDto, UpdateUserMailDto - CreateRecommendationDto - AktionariatRegistrationDto Fixes issue where users registering with mixed-case emails (e.g. Samuel.kullmann@...) had merge conflicts when later using lowercase variant (samuel.kullmann@...) * Allow RealUnit registration with unverified email - If userData.mail is NULL, try to set the submitted email - Email is rejected if already used by another user - Email is saved to userData.mail if available * Validate lowercase email instead of transforming - Replace @Transform(Util.toLowerCaseTrim) with @IsLowercase validation - Preserves original email for EIP-712 signature verification - Returns clear error message if email contains uppercase --------- Co-authored-by: TuanLamNguyen <xnguyen.lam@gmail.com> --------- Co-authored-by: TuanLamNguyen <xnguyen.lam@gmail.com>
TaprootFreak
added a commit
that referenced
this pull request
Dec 30, 2025
* [DEV-4468] Poll only deposit inputs * Add transaction.uid and Yapeal accountServiceRef to compliance search (#2707) * Add transaction.uid and Yapeal accountServiceRef to compliance search - Add transactionUid pattern (T + 16 alphanumeric chars) to Config.formats - Add TRANSACTION_UID to ComplianceSearchType enum - Extend accountServiceRef pattern to support Yapeal format (32 hex chars) - Add TransactionService dependency to SupportService * [NOTASK] Small refactoring --------- Co-authored-by: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> * Add local development setup with automatic mocking and fixes Infrastructure: - Add docker-compose.yml with MSSQL Server for local database - Add .env.local.example with minimal config (no secrets) - Add scripts/setup.js for one-command setup (npm run setup) Setup Script Features: - Generate 19 wallet seeds/keys securely on first run - Start API in background (logs to api.log, PID to .api.pid) - Wait for database tables and seed data - Register admin user via API and grant admin role - Seed deposit addresses directly in database Seed Data (migration/seed/): - Add CSV files for language, fiat, country, asset, bank, fee, price_rule - Run seed.js after app start to populate database - Add safety checks to prevent accidental production seeding Mock Mode (ENVIRONMENT=loc): - Mock external HTTP calls (Alchemy, Tatum, Sift, CoinGecko, SumSub) - Mock Azure Storage with in-memory storage - Skip Alchemy webhook creation in local mode - Add sepatools mock for IBAN validation Code Changes: - Extract static chain configs to src/config/chains.config.ts - Clean up .env.example (remove hardcoded URLs/chain IDs) - Add SQL_ENCRYPT=false support for Docker self-signed certs - Fix Gnosis config: remove undefined swap/quote contract addresses - Fix Gnosis service: don't destructure non-existent properties - Fix package.json scripts for Linux/Azure compatibility: * Remove Windows-specific "SET NODE_ENV=master&" command * Fix path from "dist/main" to "dist/src/main" * Makes scripts cross-platform compatible Documentation: - Update README with Local Development section - Document Quick Start, NPM scripts, Docker commands - Explain mock mode and what is/isn't mocked Bug Fixes: - Fixed Gnosis chain initialization crash (undefined swap addresses) - Fixed Azure deployment startup crash (Linux incompatible scripts) - Fixed PricingService fiatMap race condition in local development Testing: - All tests passing (396 tests) - Build successful - API starts without errors on both local and Azure - Cross-platform compatibility verified * Add buy-specific virtual IBAN support (#2685) * Add buy-specific virtual IBAN support - Add buyId relation to VirtualIban entity for asset-specific vIBANs - Add personalIbanEnabled flag to Asset entity - Add buySpecificIbanEnabled flag to Wallet entity - Auto-create vIBAN when requesting payment info for eligible assets - Assign incoming payments via vIBAN→Buy link (no remittanceInfo needed) - Update QR codes, GiroCodes and invoices to omit reference for buy-specific IBANs - Add unique index on (buyId, currencyId) for race condition protection - Limit to max 10 vIBANs per UserData (falls back to normal bank silently) - Fix cache invalidation consistency in VirtualIbanService * Add dedicated RealUnit buy endpoint with personal IBAN (#2686) * Add dedicated RealUnit buy endpoint with personal IBAN - Add PUT /realunit/paymentInfo endpoint for REALU purchases - Auto-create buy-specific vIBAN for each purchase request - Require KYC Level 50, RealUnit registration, and allowlist status - Return personal IBAN, estimated shares, and QR code for payment - No remittanceInfo needed (IBAN is unique per asset) * Restrict RealUnit buy currency to CHF and EUR only * Remove bank name from RealUnit payment info response * Use RealUnit company address as payment recipient * Use RealUnit config for recipient address * Return separate address fields in RealUnit payment info * Remove sepaInstant from RealUnit payment info response * Remove allowlist check from RealUnit payment info * Add fee/rate calculation to RealUnit payment info - Integrate TransactionHelper.getTxDetails() for fee calculation - Return fees, minVolume, maxVolume, exchangeRate, rate, priceSteps - Use estimatedAmount from transaction helper instead of brokerbot - Only generate QR code if isValid is true * Add TransactionRequest creation to RealUnit payment info - Import TransactionRequestService and create transaction request after building response - Add id and routeId fields to RealUnitPaymentInfoDto - Fix bankInfo type for Swiss QR code generation * Add minVolumeTarget and maxVolumeTarget to RealUnit payment info * Add timestamp to RealUnit payment info response * fix: verifyRealUnitRegistrationSignature used wrong value for verifyTypedData. * Allow RealUnit registration with unverified email (#2699) * Normalize email inputs to lowercase (#2695) - Add Util.toLowerCaseTrim for email input normalization - Apply @Transform(Util.toLowerCaseTrim) to all email input DTOs: - KycContactData, KycInputDataDto - AuthMailDto - UpdateUserDataDto, UpdateUserMailDto - CreateRecommendationDto - AktionariatRegistrationDto Fixes issue where users registering with mixed-case emails (e.g. Samuel.kullmann@...) had merge conflicts when later using lowercase variant (samuel.kullmann@...) * Allow RealUnit registration with unverified email - If userData.mail is NULL, try to set the submitted email - Email is rejected if already used by another user - Email is saved to userData.mail if available * Validate lowercase email instead of transforming - Replace @Transform(Util.toLowerCaseTrim) with @IsLowercase validation - Preserves original email for EIP-712 signature verification - Returns clear error message if email contains uppercase --------- Co-authored-by: TuanLamNguyen <xnguyen.lam@gmail.com> --------- Co-authored-by: TuanLamNguyen <xnguyen.lam@gmail.com> * Fix: Skip IP country mismatch check for crypto input buy crypto transactions (#2712) The IP_COUNTRY_MISMATCH check should only apply to bank and card transactions, not to crypto-to-crypto swaps where the user's location is less relevant for AML purposes. * Change reset() to set type PENDING instead of GSHEET (#2714) When a BankTx is reset, it should have type PENDING to properly reflect its state as an unprocessed transaction. * fix(mock): remove unused import and suppress console warning (#2713) - Remove unused HttpService import - Add eslint-disable comment for intentional console.log in local dev * [NOTASK] fix chargebackBankFee for buyCrypto * [NOTASK] fix multiAccountBankName merge bug * chore: combined dependency updates and security fixes (#2720) - Upgrade supertest from v6 to v7.1.3 - Add multer v2.0.0 override (security fix for CVE-2022-24434) - Add Jest forceExit config (fixes worker exit warning) - Upgrade ESLint from v8 to v9 with flat config - Upgrade prettier from v2 to v3 - Add uuid and semver overrides (security fixes) Backward compatibility maintained for ESLint by disabling new strict rules. * Fix: Only show failure reason when AML check is not passed (#2722) The transaction reason should only be derived from amlReason when amlCheck is in FAIL, PENDING, or GSHEET status. This prevents showing misleading failure reasons for transactions with passed AML checks. * [NO-TASK] Auto-generated migration * Improve account merge confirmation email (#2708) * Improve account merge confirmation email - Only show greeting when name is available - Remove unnecessary support warning text - Add button for consistent UX with login email - Remove closing translations from all languages * [NOTASK] Refactoring --------- Co-authored-by: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> * Add bank account details to refund endpoint (#2681) * Add bank account details to refund endpoint - Extend RefundDataDto with name, address, houseNumber, zip, city, country, iban, bic - Extract bank details from BankTx entity in getRefundData - Add getBankTxFromRefundEntity helper method * Fix refund bank fee calculation Use flat 2x bankFixed fee (1 EUR/CHF per transaction) instead of variable chargebackBankFee calculation * Revert "Fix refund bank fee calculation" This reverts commit 734707b. * [NOTASK] Refactoring --------- Co-authored-by: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> * [NO-TASK] Fixed BSC gateway URL * fix: replace DeFiChain with Bitcoin as default blockchain (#2726) DeFiChain should never be the default or fallback blockchain. Changed all occurrences where DeFiChain was used as default to Bitcoin: - Asset entity column default - CryptoService address fallback - Mock entities for tests * fix: use Bitcoin instead of DeFiChain in test for non-supported AssetCategory (#2727) * fix: remove DeFiChain signature verification, add address detection (#2728) - Removes DeFiChain signature verification from CryptoService - Adds isDefichainAddress() to correctly identify existing DeFiChain users - Adds DeFiChain signature comparison in AuthService for existing users - Removes fallback to Bitcoin for unknown addresses - Throws 'Unsupported blockchain address' for unrecognized address formats - Existing users verified via stored signature in database - New DeFiChain registrations not possible * fix: use Bitcoin instead of DeFiChain in liquidity-order factory test (#2729) * fix: replace DFI with active assets in mocks and tests (#2730) * Fix: Auto-generate EVM wallet addresses in setup script (#2731) Problem: Setup script generated WALLET_PRIVATE_KEY variables but not the corresponding WALLET_ADDRESS variables, causing runtime errors: TypeError: Cannot read properties of undefined (reading 'toHexString') Solution: Derive wallet address from private key using ethers.Wallet and automatically set all *_WALLET_ADDRESS environment variables. Missing variables that are now auto-generated: - ETH_WALLET_ADDRESS - SEPOLIA_WALLET_ADDRESS - BSC_WALLET_ADDRESS - OPTIMISM_WALLET_ADDRESS - BASE_WALLET_ADDRESS - ARBITRUM_WALLET_ADDRESS - POLYGON_WALLET_ADDRESS - GNOSIS_WALLET_ADDRESS - CITREA_TESTNET_WALLET_ADDRESS * refactor(bitcoin): replace DeFiChain packages with @btc-vision/bitcoin-rpc (#2732) - Remove @defichain/jellyfish-api-core, @defichain/jellyfish-api-jsonrpc, @defichain/jellyfish-network, @defichain/whale-api-client - Add @btc-vision/bitcoin-rpc for native Bitcoin Core RPC support - Define UTXO and NetworkName types locally - Use wallet gettransaction RPC to get tx fee (not getrawtransaction) - Pass address_type parameter correctly in createAddress - Add safeguard for undefined fee in payout completion - Replace BigNumber with number for balance handling - Add Jest mock for @btc-vision/bitcoin-rpc * feat(bitcoin): Add support for unconfirmed UTXOs with next-block filtering (#2673) * Add EIP-7702 delegation for token forwarding (#2702) * Add EIP-7702 delegation for token forwarding - Add Eip7702DelegationService using MetaMask's EIP7702StatelessDeleGator - Single TX instead of 2-TX flow (gas top-up + token transfer) - Support for ETH, Arbitrum, Optimism, Polygon, Base, BSC, Gnosis, Sepolia - Toggleable via EVM_DELEGATION_ENABLED config flag - Automatic fallback to legacy flow when disabled or chain not supported - Input validation for amount, recipient, and token contract address * Update Citrea Testnet explorer URL to citreascan.com * Use DelegationManager for EIP-7702 token transfers - Replace direct execute() call with redeemDelegations() via DelegationManager - Add EIP-712 delegation signing (Deposit -> Relayer) - Use ERC-7579 encodePacked format for execution data - Remove unused eip7702-stateless-delegator.abi.json - Add delegation-manager.abi.json with redeemDelegations ABI The MetaMask Delegator's execute() function has onlyEntryPointOrSelf access control. This change uses the DelegationManager contract instead, which allows any caller to redeem delegations with valid signatures. Flow: Relayer -> DelegationManager.redeemDelegations() -> Account.executeFromExecutor() Tested successfully on Ethereum mainnet: TX: 0x641da73a82691185612ff79d7e0d1b2d9bdc66d84e32455b58ddba7cec3d5a9a * Remove empty constructor to fix linter * Add comprehensive tests for EIP-7702 delegation service - Test isDelegationSupported for all 8 supported chains (ETH, ARB, OP, MATIC, BASE, BSC, GNO, SEP) - Test isDelegationSupported returns false for unsupported chains (BTC, LN, XMR, SOL, TRX, ADA) - Test input validation: zero/negative amounts, invalid addresses, missing token contract - Test chain support validation with proper error messages - Test successful transfers on multiple chains - Test edge cases: mixed case addresses, 0/18 decimals, different account indices - Test viem integration: client creation, ERC20 encoding, EIP-712 signing - 32 tests covering all critical paths * Fix lint errors in delegation tests * Add EIP-7702 delegation test script Local test script for verifying delegation encoding and signing: - EIP-712 delegation signing - ERC20 transfer encoding - ERC-7579 execution data encoding - Permission context encoding for redeemDelegations Run with: npx ts-node scripts/test-delegation.ts * Add integration tests for EvmTokenStrategy delegation flow - Test pay-in status splitting logic (ACKNOWLEDGED/TO_RETURN vs PREPARING/PREPARED) - Test amount calculation for FORWARD and RETURN types - Test delegation service contract and method calls - Test error handling and legacy fallback scenarios - 13 tests covering the integration between EvmTokenStrategy and Eip7702DelegationService * Add comprehensive smart contract validation tests for EIP-7702 delegation Add 37 new tests to verify correct interaction with DelegationManager contract: - Delegation struct validation (ROOT_AUTHORITY, delegate/delegator mapping, caveats, salt) - Permission context ABI encoding verification - redeemDelegations arguments (CALLTYPE_SINGLE, array structure) - EIP-7702 authorization (DELEGATOR_ADDRESS, deposit account) - ChainId verification for all supported blockchains - Error handling for contract reverts, RPC failures, nonce/gas issues - ERC-7579 execution data format validation * Add Sepolia integration tests for EIP-7702 delegation Add integration tests that verify actual contract interaction on Sepolia: - Contract existence verification (DelegationManager, Delegator) - EIP-712 delegation signature creation and hash computation - ABI encoding verification with round-trip decoding - ERC-7579 execution data format validation - Gas estimation and block limit checks - Full delegation flow dry run Tests are skipped by default unless SEPOLIA_GATEWAY_URL is set. Run with: SEPOLIA_GATEWAY_URL=https://... npm test -- --testPathPattern="integration" * Update tests for dynamic gas estimation in EIP-7702 delegation - Add estimateGas mock to all test describe blocks - Add new Dynamic Gas Estimation test section - Fix mock isolation between test sections - Service now uses publicClient.estimateGas instead of hardcoded 300k gas * Fix DelegationManager ABI to match deployed contract - Change DOMAIN_HASH to getDomainHash (correct function name) - Update integration tests to use getDomainHash - Replace getDelegationHash calls with struct validation * Add live EIP-7702 delegation test script for Sepolia Tested successfully on Sepolia with real USDC transfer: - TX: 0x292dab686f5ba0aa9b22c35ef722289e1458905a6bbb1b4a54a148834c03e44d - Gas used: 127,073 - Transfer: 1 USDC via DelegationManager * Add local development setup with automatic mocking (#2703) * Add local development setup with Docker and automatic mocking Infrastructure: - Add docker-compose.yml with MSSQL Server for local database - Add .env.local.example with minimal config (no secrets) - Add scripts/setup.js for one-command setup Setup Script (npm run setup): - Generate 19 wallet seeds/keys securely on first run - Start API in background (logs to api.log, PID to .api.pid) - Wait for database tables and seed data - Register admin user via API and grant admin role - Seed deposit addresses directly in database Seed Data (migration/seed/): - Add CSV files for language, fiat, country, asset, bank, fee, price_rule - Run seed.js after app start to populate database - Add safety checks to prevent accidental production seeding Mock Mode (ENVIRONMENT=loc): - Mock external HTTP calls (Alchemy, Tatum, Sift, CoinGecko, SumSub) - Mock Azure Storage with in-memory storage - Skip Alchemy webhook creation in local mode - Add sepatools mock for IBAN validation Code Changes: - Extract static chain configs to src/config/chains.config.ts - Clean up .env.example (remove hardcoded URLs/chain IDs) - Add SQL_ENCRYPT=false support for Docker self-signed certs Documentation: - Update README with Local Development section - Document Quick Start, NPM scripts, Docker commands - Explain mock mode and what is/isn't mocked * Fix fiatMap initialization race condition in local development (#2706) - Make PricingService.onModuleInit async and await fiat loading - Run database seeding synchronously before app.listen() in LOC env - Re-initialize PricingService after seeding to load fiat data Previously, onModuleInit used fire-and-forget promises (void) which could complete after the API started accepting requests. Combined with async seeding that ran after app.listen(), this caused "Cannot read properties of undefined (reading 'id')" errors when the fiatMap was empty during price lookups. * Fix: Critical EIP-7702 delegation bugs preventing execution Problem 1: Invalid private key signature error - createSignedDelegation() was using depositViemAccount.source - This is not a valid hex string for signTypedData() Solution: Pass depositPrivateKey: Hex directly Problem 2: Gas parameter conflict - Used legacy gasPrice parameter - Conflicts with EIP-1559 maxFeePerGas/maxPriorityFeePerGas - Error: "Cannot specify both gasPrice and maxFeePerGas" Solution: Use EIP-1559 parameters exclusively Problem 3: Gas estimation failures during testing - Alchemy rate limiting caused estimateGas() to fail Solution: Added try-catch with conservative fallback (150000n) Note: Fallback is for testing resilience - review for production These fixes enable successful EIP-7702 token forwarding in one atomic transaction instead of legacy 2-transaction flow. * Remove gas estimation fallback for EIP-7702 delegation - Remove try-catch with hardcoded 150000n fallback - Let estimateGas() errors propagate to caller - Caller can decide whether to retry or fall back to legacy flow The 150000n fallback was added for testing (Alchemy rate limiting) but is not appropriate for production. Gas estimation failures should be handled at a higher level with proper retry logic. * Fix: Update tests for EIP-1559 gas parameter changes - Add getBlock() and estimateMaxPriorityFeePerGas() mocks to all test blocks - Update error tests to mock estimateGas/getBlock instead of getGasPrice - All 67 tests now passing This fixes test failures introduced by switching from legacy gasPrice to EIP-1559 gas parameters (maxFeePerGas/maxPriorityFeePerGas). * [NO-TASK] Fix: Use pipeline price for all assets, not just FPS/nDEPS (#2670) * [NO-TASK] Fix: Use pipeline price for all assets, not just FPS/nDEPS Previously, the pipeline price (actual exchange rate from liquidity purchase) was only used for FPS and nDEPS assets. For other assets like BTC, the market price was used instead, which could differ from the actual purchase price. This caused price risk during BTC transfers from Binance to HotWallet: - If BTC price drops during transfer, customers expect more BTC than purchased - If BTC price rises during transfer, DFX loses potential margin Changes: - Remove `exchangeRateFromLiquidityOrder` config (was ['FPS', 'nDEPS']) - Remove asset check in `calculateOutputReferenceAmount()` - Link liquidityPipeline to BuyCrypto for ALL assets - Add comprehensive tests (27 test cases) Now all assets benefit from the accurate pipeline price, protecting both customers and DFX from price fluctuations during liquidity transfers. * Fix: Find exchange order from sub-pipelines for BTC purchases The previous implementation used `orders[0].exchangePrice` which doesn't work for BTC purchases because: - Pipeline 41235 (Bitcoin/BTC HotWallet) orders are: NotProcessable, sub-pipeline trigger, withdraw (BTC→BTC) - None have the USDT→BTC exchange rate - The actual exchange rate is in sub-pipeline 41236's order Changes: - Add `exchangeOrder` getter to LiquidityManagementPipeline that finds the order with actual exchange rate (system is exchange, different input/output assets, valid amounts) - Add `subPipelineOrder` getter to find orders that trigger sub-pipelines - Add `getPipelineWithOrders()` to LiquidityManagementService - Add `findExchangeOrder()` to BuyCryptoBatchService that recursively traverses sub-pipelines (max depth 3) to find the exchange order - Modify `calculateOutputReferenceAmount()` to accept exchange order as parameter instead of finding it from `orders[0]` * Fix: Use market price when no trade happened (enough liquidity) When pipeline completes but no exchange order exists (e.g., enough BTC was available at Binance for withdrawal without trading), use market price instead of throwing an error. Scenarios: - Trade happened → use pipeline price (protects against price changes) - No trade needed → use market price (no exchange rate to use) * Fix: Find all exchange orders in pipeline chain The previous implementation only found the first exchange order. Now it recursively traverses all sub-pipelines and collects ALL exchange orders, then aggregates them by asset pair. For Pipeline 41235 (EUR→BTC): - Finds EUR→USDT trades from sub-pipelines 41238/41239 - Finds USDT→BTC trade from sub-pipeline 41236 - Aggregates multiple orders with same asset pair - Replaces CoinGecko steps with actual pipeline prices Example result for Pipeline 41235: Old: CoinGecko EUR→USDT (0.85158), CoinGecko USDT→BTC (86805) New: Kraken EUR→USDT (aggregated), Binance USDT→BTC (86863.47) * Refactor: Move buildAggregatedPriceSteps to HELPER METHODS section * Fix: Include FRANKENCOIN and DEURO in exchange orders filter Without this fix, FPS and nDEPS pipeline prices would not be used because their orders were filtered out by the exchangeOrders getter. * Add tests for FPS/nDEPS and multi-step price matching * [DEV-4473] OCP crypto payout (#2550) * [DEV-4473] Allow crypto routes for OCP * [DEV-4473] Implemented OCP crypto payout pricing * [DEV-4473] Payout aggregation * [DEV-4473] Payout aggregation release * [DEV-4473] PR feedback * [DEV-4473] Merge fixes * Cleanup * [DEV-4473] Fixed price steps * [DEV-4473] Fixed acknowledgement * [NO-TASK] Refactoring * Remove Bank Frick implementation (#2733) * Remove Bank Frick implementation - Remove bank.frick configuration from config.ts - Remove FRICK_* environment variables from .env.example - Remove IbanBankName.FRICK enum from bank.dto.ts - Remove frick parameters from Azure Bicep infrastructure files - Remove frick test mocks and update bank service tests - Keep historical 'Bank Frick' strings in log service for Kraken transaction matching * Fix bank service tests - use olkyEUR instead of maerkiEUR - Tests expected maerkiEUR but service returns first matching bank (olkyEUR) - olkyEUR is first EUR bank in createDefaultBanks() array - Update test expectations to match actual bank selection logic * Remove Kaleido bank implementation (#2737) Kaleido is no longer used in the DFX system. This commit removes: - Kaleido from IbanBankName enum - Kaleido entries from bank.csv seed file (CHF, EUR, USD accounts) Historical data in the database remains intact. * Complete Kaleido removal from codebase (#2738) Removes all remaining Kaleido references: - Removed Blockchain.KALEIDO from blockchain enum - Removed Kaleido from BlockchainExplorerUrls map (blockchain.util.ts) - Removed Kaleido from TxPaths map (blockchain.util.ts) - Removed Kaleido from PayoutLimits map (ref-reward.service.ts) - Removed Kaleido from networks map in 8 exchange services - Removed 3 Kaleido assets from asset.csv seed file (IDs 301-303) Historical database records remain intact. This completes the removal started in PR #2737 which removed IbanBankName.KALEIDO and bank seeds. * feat: Blockchain Balance und Transaction API Endpoints (#2704) * feat: add blockchain balance and transaction API endpoints - Add /blockchain/balances endpoint for querying wallet balances - Add /blockchain/transaction endpoint for creating unsigned transactions - Add /blockchain/broadcast endpoint for broadcasting signed transactions - Support Solana, Tron, and EVM chains (via Alchemy) - New BlockchainBalanceModule with DTOs, services, and controller * fix: use correct base64 encoding for Solana transaction broadcast - Fix broadcastSolanaTransaction to send base64 directly to RPC - Use proper TronWeb import instead of require() - Remove unused TronClient dependency * chore: remove unused TronService from BlockchainTransactionService * fix: align with existing codebase patterns - Use 'x-api-key' header for Tron API (consistent with TronClient) - Use AlchemyNetworkMapper.availableNetworks for EVM blockchain list * Add RateLimitGuard to blockchain endpoints * Add address validation and JSON parse error handling * Fix critical bugs: asset validation and Tron API error handling * Remove IDnow integration (#2739) * Remove IDnow integration - Delete IdentService and remove from module - Remove IDnow config keys (gatewayHost, auto, video) - Disable IDnow webhook and sync endpoints - Block new IDnow ident initiation - Keep historical data parsing (IdentType.ID_NOW, IdNowResult DTOs) Only Sumsub and Manual ident are now supported for new identifications. Historical IDnow ident data remains readable. * Fix: Remove remaining IDnow webhook endpoints and dead code Critical fixes for PR #2739 (Remove IDnow integration): 1. Build Errors Fixed: - Removed Config.kyc.allowedWebhookIps references causing TypeScript errors - All IDnow-related build errors now resolved 2. Endpoints Removed: - identWebhook() - POST /kyc/ident/:type - identRedirect() - GET /kyc/ident/:type/:channel/:status - checkWebhookIp() helper method 3. Functions Removed: - updateIdentStatus() from KycService (only called by removed endpoint) - syncIdentStep() from KycAdminService (dead code, always throws) 4. Imports Cleaned: - Removed unused IdNowResult and IdentStatus imports Impact: - IDnow webhooks and redirects no longer accepted - Build now succeeds (remaining 9 errors are unrelated Bitcoin client issues) - No runtime errors for IDnow webhook calls - Old IDnow ident data remains fully readable Related to: #2739 * Fix TypeScript errors in bitcoin client (#2740) Fixes all 9 TypeScript compilation errors in bitcoin-client.ts and node-client.ts: ## Module Declaration Errors (2 fixed) - Added @ts-ignore comments for @btc-vision/bitcoin-rpc imports - Package has no type declarations, suppression is necessary ## Type Inference Errors (7 fixed) - Added explicit type parameters to callNode<T>() calls - Defined SendResult and WalletInfo interfaces for RPC responses - Fixed bitcoin-client.ts: - send() → callNode<{ txid: string }>() - sendMany() → callNode<{ txid: string }>() - sendSignedTransaction() → callNode<string>() - Fixed node-client.ts: - getBlockCount() → callNode<number>() - getBlockHash() → callNode<string>() - getBalance() → callNode<WalletInfo>() - sendUtxoToMany() → callNode<SendResult>() All changes are type-only annotations - no functional changes. TypeScript compilation now passes with zero errors. * Fix local development password to avoid shell special characters (#2742) Changed SQL_PASSWORD from LocalDev123! to LocalDev2026@SQL to prevent issues with special characters (!) in shell environments. The exclamation mark can cause problems with Bash history expansion and command execution. Updated in: - docker-compose.yml (MSSQL_SA_PASSWORD, healthcheck, db-init) - scripts/setup.js (database config defaults) - migration/seed/seed.js (database config default) - .env.local.example (SQL_PASSWORD) * chore: reformat code with Prettier v3 (#2743) * fix: enable ESLint rules and fix violations (#2744) * Remove Revolut bank integration (#2745) * Remove Revolut bank integration - Delete revolut.service.ts - Remove RevolutService from bank module - Remove REVOLUT enum from IbanBankName - Remove REVOLUT_CARD_PAYMENT transaction type - Remove Revolut transaction sync from bank-tx service - Remove Revolut balance monitoring from bank observer - Remove Revolut config - Remove Revolut seed data * Remove Revolut environment variables from infrastructure * fix: wrap case blocks with lexical declarations in braces (#2746) * fix(eslint): fix remaining ESLint rule violations (#2748) * fix(eslint): fix no-async-promise-executor violations - Refactor Util.poll() to use async/await directly without Promise wrapper - Pre-generate QR codes in generateClassicStickersPdf before Promise - Remove async from generateBitcoinFocusStickersPdf executor (no await inside) - Re-enable no-async-promise-executor rule * fix(eslint): fix no-unused-expressions violations (58 fixes) Convert short-circuit expressions to proper if statements: - `condition && action()` → `if (condition) action();` - `value && (obj.prop = value)` → `if (value) obj.prop = value;` Re-enable @typescript-eslint/no-unused-expressions rule * fix(eslint): fix simple warnings (#2749) - Remove 5 unused eslint-disable directives - Remove 9 unused catch error variables (use empty catch) - Remove 3 unused imports in kyc files - Prefix 2 unused function params with underscore * fix(eslint): fix all remaining console warnings (#2752) - Add ESLint override to disable no-console for test files (*.spec.ts, *.test.ts, __tests__) - Add DfxLogger to EvmClient and replace console.log with logger.verbose - Change logger from private to protected in EvmClient and subclasses for proper inheritance * Fix remaining ESLint warnings for scripts folder (#2753) - Add scripts/**/*.ts to no-console override (73 warnings) - Add migration/**/*.js and scripts/*.js to ignores (3 parsing errors) - Fix unused walletClient variable with underscore prefix - Fix unused error in catch block with empty catch * Remove deprecated interface-name-prefix ESLint rule (#2754) * [NOTASK] Small refactoring * feat: add feature flag for pipeline price mechanism (#2756) Add USE_PIPELINE_PRICE_FOR_ALL_ASSETS environment variable to control whether the pipeline price (actual exchange rate from liquidity purchase) is used for all assets. When disabled (default): Market price is used for all assets When enabled: Pipeline price is used when available This allows gradual rollout of the pipeline price feature introduced in commit 7b0b6e6. Changes: - Add usePipelinePriceForAllAssets config option - Guard pipeline price logic with feature flag in buy-crypto.entity.ts - Guard exchange order lookup with feature flag in buy-crypto-batch.service.ts * [NO-TASK] Payment step timestamp fix * [NO-TASK] Cleanup * fix: enable pipeline price feature flag in tests (#2757) The pipeline price tests require the usePipelinePriceForAllAssets config option to be enabled. Add beforeEach block to configure the feature flag for the liquidityPipeline test suite. * [DEV-4516] change financeLog to yapeal (#2700) * [DEV-4516] change financeLog to yapeal * [DEV-4516] Refactoring * [DEV-4516] Refactoring 2 * [NO-TASK] Cleanup 2 * refactor(bitcoin): replace @btc-vision/bitcoin-rpc with custom RPC client (#2758) * fix(bitcoin): use positional parameters for RPC calls Switch from named parameters (objects) to positional parameters (arrays) for all Bitcoin Core RPC calls to ensure compatibility with servers that don't fully support named parameters. Changes: - Add callRpcWithPositionalParams helper method for direct HTTP RPC calls - Update all RPC methods to use positional parameters - Remove unused getInternalRpcClient method - Add WalletInfo interface locally This fixes "Params must be an array or object" errors that occurred when the Bitcoin Core server doesn't support named parameter format. * refactor(bitcoin): replace @btc-vision/bitcoin-rpc with custom RPC client Removes the @btc-vision/bitcoin-rpc dependency and replaces it with our own minimal, fully-typed Bitcoin Core RPC client that uses positional parameters. New files in src/integration/blockchain/bitcoin/node/rpc/: - bitcoin-rpc-client.ts: Minimal RPC client (~230 lines) - bitcoin-rpc-types.ts: Full TypeScript type definitions (~190 lines) - index.ts: Barrel exports Key improvements: - Uses positional parameters (arrays) for maximum Bitcoin Core compatibility - Full TypeScript type definitions for all RPC methods - Only implements the ~15 methods needed by DFX - No external dependencies beyond existing HTTP service - Cleaner, more maintainable code in node-client.ts and bitcoin-client.ts This fixes the "Params must be an array or object" error caused by @btc-vision/bitcoin-rpc using named parameters (objects) which some Bitcoin Core servers don't support. * refactor(bitcoin): replace @btc-vision/bitcoin-rpc with custom RPC client Removes the @btc-vision/bitcoin-rpc dependency and replaces it with our own minimal, fully-typed Bitcoin Core RPC client that uses positional parameters. New files in src/integration/blockchain/bitcoin/node/rpc/: - bitcoin-rpc-client.ts: Minimal RPC client (~230 lines) - bitcoin-rpc-types.ts: Full TypeScript type definitions (~190 lines) - index.ts: Barrel exports Key improvements: - Uses positional parameters (arrays) for maximum Bitcoin Core compatibility - Full TypeScript type definitions for all RPC methods - Only implements the ~15 methods needed by DFX - No external dependencies beyond existing HTTP service - Cleaner, more maintainable code in node-client.ts and bitcoin-client.ts - Preserves full URL including protocol (http/https) and path (/wallet/...) This fixes the 'Params must be an array or object' error caused by @btc-vision/bitcoin-rpc using named parameters (objects) which some Bitcoin Core servers don't support. * [NO-TASK] Renaming * [DEV-4473] Network fee fix * test(bitcoin): add comprehensive tests for custom RPC client (#2759) * fix(bitcoin): use positional parameters for RPC calls Switch from named parameters (objects) to positional parameters (arrays) for all Bitcoin Core RPC calls to ensure compatibility with servers that don't fully support named parameters. Changes: - Add callRpcWithPositionalParams helper method for direct HTTP RPC calls - Update all RPC methods to use positional parameters - Remove unused getInternalRpcClient method - Add WalletInfo interface locally This fixes "Params must be an array or object" errors that occurred when the Bitcoin Core server doesn't support named parameter format. * refactor(bitcoin): replace @btc-vision/bitcoin-rpc with custom RPC client Removes the @btc-vision/bitcoin-rpc dependency and replaces it with our own minimal, fully-typed Bitcoin Core RPC client that uses positional parameters. New files in src/integration/blockchain/bitcoin/node/rpc/: - bitcoin-rpc-client.ts: Minimal RPC client (~230 lines) - bitcoin-rpc-types.ts: Full TypeScript type definitions (~190 lines) - index.ts: Barrel exports Key improvements: - Uses positional parameters (arrays) for maximum Bitcoin Core compatibility - Full TypeScript type definitions for all RPC methods - Only implements the ~15 methods needed by DFX - No external dependencies beyond existing HTTP service - Cleaner, more maintainable code in node-client.ts and bitcoin-client.ts This fixes the "Params must be an array or object" error caused by @btc-vision/bitcoin-rpc using named parameters (objects) which some Bitcoin Core servers don't support. * refactor(bitcoin): replace @btc-vision/bitcoin-rpc with custom RPC client Removes the @btc-vision/bitcoin-rpc dependency and replaces it with our own minimal, fully-typed Bitcoin Core RPC client that uses positional parameters. New files in src/integration/blockchain/bitcoin/node/rpc/: - bitcoin-rpc-client.ts: Minimal RPC client (~230 lines) - bitcoin-rpc-types.ts: Full TypeScript type definitions (~190 lines) - index.ts: Barrel exports Key improvements: - Uses positional parameters (arrays) for maximum Bitcoin Core compatibility - Full TypeScript type definitions for all RPC methods - Only implements the ~15 methods needed by DFX - No external dependencies beyond existing HTTP service - Cleaner, more maintainable code in node-client.ts and bitcoin-client.ts - Preserves full URL including protocol (http/https) and path (/wallet/...) This fixes the 'Params must be an array or object' error caused by @btc-vision/bitcoin-rpc using named parameters (objects) which some Bitcoin Core servers don't support. * test(bitcoin): add comprehensive tests for custom RPC client Add unit tests and integration tests for the new Bitcoin RPC implementation: - bitcoin-rpc-client.spec.ts: 46 tests for RPC parameter ordering, auth, error handling - node-client.spec.ts: 39 tests for URL handling, fee conversion, retry logic - bitcoin-client.spec.ts: 46 tests for send, sendMany, testMempoolAccept - bitcoin-fee.service.spec.ts: 23 tests for caching and status handling - bitcoin-rpc.integration.spec.ts: Extended with fee estimation, mempool, CLI tests Total: 235 tests covering all critical paths of the Bitcoin RPC migration. * fix(test): remove unused imports and variables - Remove unused imports: AddressType, InWalletTransaction, Block - Remove unused imports: TxFeeRateResult, TxFeeRateStatus - Prefix unused parameter with underscore: _address - Remove unused variable assignment: result * feat(bitcoin): add getRawTransaction and RPC client improvements (#2761) - Add getRawTransaction method to BitcoinRpcClient with verbosity support - Add RawTransaction type with comprehensive transaction data fields - Add tests for getRawTransaction including edge cases - Improve error handling for transaction not found (-5) errors - Add additional integration test coverage for RPC client * fix(buy-crypto): reset batch when setting missing liquidity status (#2762) When a BuyCrypto transaction is set to MISSING_LIQUIDITY status, the batch reference was not being cleared. This caused transactions to become stuck because the batch query requires batch to be null for transactions to be re-processed. This fix ensures that when liquidity is missing, the transaction is properly detached from its batch so it can be picked up again when liquidity becomes available. * [NO-TASK] Changed path * fix: translate German comments to English in Bitcoin RPC tests (#2760) Translate JSDoc header comments from German to English for consistency. * fix: remove unused mock services (#2763) Remove orphaned mock services from /integration/mock/ that were never imported or used. The MockModule.forRoot() was never called anywhere in the application. Actual mocking is done inline in real services (HttpService, AzureStorageService, etc.) via Environment.LOC checks. Removed files: - mock.module.ts - mock-http.service.ts - mock-storage.service.ts - mock-mail.service.ts - mock-alchemy.service.ts - index.ts * revert: remove batch reset in setMissingLiquidityStatus (#2764) Reverts the change from PR #2762 that added `batch: null` when setting MISSING_LIQUIDITY status. * [NO-TASK] BTC balance fix * fix(tests): update getBalance tests to use getbalances RPC Update test mocks to use getbalances RPC method with correct response structure { mine: { trusted: ... } } instead of deprecated getwalletinfo. * fix(lint): remove empty else block in evm.strategy.ts --------- Co-authored-by: bernd2022 <bernd.nospam.2018@t-online.de> Co-authored-by: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Co-authored-by: TuanLamNguyen <xnguyen.lam@gmail.com> Co-authored-by: David May <david.leo.may@gmail.com> Co-authored-by: David May <85513542+davidleomay@users.noreply.github.com>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
@Transform(Util.toLowerCaseTrim)Problem
Users who registered with mixed-case emails (e.g.
Samuel.kullmann@startmail.com) had merge conflicts when later using lowercase variant (samuel.kullmann@startmail.com), because different casing was stored in the database.Solution
Normalize email inputs at the DTO level to ensure consistent lowercase storage.
Changes
toLowerCaseTrimtransform function@Transform(Util.toLowerCaseTrim)to all email input fieldsTest plan