From 16c6087ed758d0097aa93a2e8cfc0ca3b930776d Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 9 Jan 2026 11:43:24 +0100 Subject: [PATCH 1/6] Disable credit card payment option (#2582) * Disable credit card payment option - Block credit card payments in PaymentInfoService with clear error message - Add PAYMENT_METHOD_NOT_ALLOWED to QuoteError enum - Return error early in TransactionHelper for card payment quotes - Remove obsolete card-specific validation checks * fix: removed duplicated check --------- Co-authored-by: David May --- .../transaction-helper/quote-error.enum.ts | 1 + .../payment/services/transaction-helper.ts | 20 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/subdomains/supporting/payment/dto/transaction-helper/quote-error.enum.ts b/src/subdomains/supporting/payment/dto/transaction-helper/quote-error.enum.ts index 310cd236ce..f947c87230 100644 --- a/src/subdomains/supporting/payment/dto/transaction-helper/quote-error.enum.ts +++ b/src/subdomains/supporting/payment/dto/transaction-helper/quote-error.enum.ts @@ -9,6 +9,7 @@ export enum QuoteError { LIMIT_EXCEEDED = 'LimitExceeded', NATIONALITY_NOT_ALLOWED = 'NationalityNotAllowed', NAME_REQUIRED = 'NameRequired', + PAYMENT_METHOD_NOT_ALLOWED = 'PaymentMethodNotAllowed', IBAN_CURRENCY_MISMATCH = 'IbanCurrencyMismatch', RECOMMENDATION_REQUIRED = 'RecommendationRequired', EMAIL_REQUIRED = 'EmailRequired', diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index 3d0a4d2f6e..cd00bdf80b 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -890,14 +890,12 @@ export class TransactionHelper implements OnModuleInit { : QuoteError.EMAIL_REQUIRED; } + // Credit card payments disabled + if (paymentMethodIn === FiatPaymentMethod.CARD) return QuoteError.PAYMENT_METHOD_NOT_ALLOWED; + if (isSell && ibanCountry && !to.isIbanCountryAllowed(ibanCountry)) return QuoteError.IBAN_CURRENCY_MISMATCH; - if ( - nationality && - ((isBuy && !nationality.bankEnable) || - (paymentMethodIn === FiatPaymentMethod.CARD && !nationality.checkoutEnable) || - ((isSell || isSwap) && !nationality.cryptoEnable)) - ) + if (nationality && ((isBuy && !nationality.bankEnable) || ((isSell || isSwap) && !nationality.cryptoEnable))) return QuoteError.NATIONALITY_NOT_ALLOWED; // KYC checks @@ -935,15 +933,7 @@ export class TransactionHelper implements OnModuleInit { // verification checks if ( - paymentMethodIn === FiatPaymentMethod.CARD && - user && - !user.userData.completeName && - !user.userData.verifiedName - ) - return QuoteError.NAME_REQUIRED; - - if ( - ((isSell && to.name !== 'CHF') || paymentMethodIn === FiatPaymentMethod.CARD || isSwap) && + ((isSell && to.name !== 'CHF') || isSwap) && user && !user.userData.hasBankTxVerification && txAmountChf > Config.tradingLimits.monthlyDefaultWoKyc From 54f45cc0e6c54751866b22771aa3d3ac353e4217 Mon Sep 17 00:00:00 2001 From: Yannick1712 <52333989+Yannick1712@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:53:05 +0100 Subject: [PATCH 2/6] [NOTASK] improve empty string lists --- src/subdomains/supporting/payment/entities/fee.entity.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subdomains/supporting/payment/entities/fee.entity.ts b/src/subdomains/supporting/payment/entities/fee.entity.ts index d0fb27e106..fdf3b5e677 100644 --- a/src/subdomains/supporting/payment/entities/fee.entity.ts +++ b/src/subdomains/supporting/payment/entities/fee.entity.ts @@ -220,7 +220,7 @@ export class Fee extends IEntity { //*** GETTER METHODS ***// get assetList(): number[] { - return this.assets?.split(';')?.map(Number); + return this.assets ? this.assets.split(';')?.map(Number) : undefined; } get excludedAssetList(): number[] { @@ -228,7 +228,7 @@ export class Fee extends IEntity { } get fiatList(): number[] { - return this.fiats?.split(';')?.map(Number); + return this.fiats ? this.fiats.split(';')?.map(Number) : undefined; } get excludedUserDataList(): number[] { From c63cf0bfff678b775bf9257bd1a5182aeb9f7b49 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:03:33 +0100 Subject: [PATCH 3/6] Add CodeQL analysis workflow configuration (#2894) --- .github/workflows/codeql.yml | 103 +++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..5e697a51a7 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,103 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "develop", "master" ] + pull_request: + branches: [ "develop", "master" ] + schedule: + - cron: '38 8 * * 2' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: javascript-typescript + build-mode: none + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" From 1d021542388d3a2e667d6d6162e264183390d94d Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:33:34 +0100 Subject: [PATCH 4/6] fix: restore MAERKI_BAUMANN mapping in blockchainToBankName (#2896) PR #2747 accidentally removed the MAERKI_BAUMANN case from blockchainToBankName(), causing isBankMatching() to always return false for Maerki Baumann assets. This broke the FinancialDataLog balance calculation - pending transactions (bankTxPending, bankTxRepeat, bankTxReturn, buyCrypto) for Maerki Baumann assets were no longer counted in minusBalance, inflating the reported total balance by ~208k CHF. The mapping is still needed as long as there are pending Maerki Baumann transactions in the system. --- src/subdomains/supporting/bank/bank/bank.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/subdomains/supporting/bank/bank/bank.service.ts b/src/subdomains/supporting/bank/bank/bank.service.ts index 02b43d3075..d81c9ec7fc 100644 --- a/src/subdomains/supporting/bank/bank/bank.service.ts +++ b/src/subdomains/supporting/bank/bank/bank.service.ts @@ -107,6 +107,8 @@ export class BankService implements OnModuleInit { private static blockchainToBankName(blockchain: Blockchain): IbanBankName | undefined { switch (blockchain) { + case Blockchain.MAERKI_BAUMANN: + return IbanBankName.MAERKI; case Blockchain.OLKYPAY: return IbanBankName.OLKY; case Blockchain.YAPEAL: From 15d55269e7ebcd948d732cb89b5fd56699c2742f Mon Sep 17 00:00:00 2001 From: bernd2022 <104787072+bernd2022@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:34:02 +0100 Subject: [PATCH 5/6] fix: cleanup CodeQL workflow configuration (#2895) * fix: cleanup CodeQL workflow configuration - Remove scheduled cron job - Add config-file reference to use existing codeql-config.yml - Remove boilerplate comments and unused manual build step - Simplify runs-on to ubuntu-latest * fix: remove Python from CodeQL matrix (no analyzable Python code) --------- Co-authored-by: Bernd --- .github/workflows/codeql.yml | 67 ++---------------------------------- 1 file changed, 3 insertions(+), 64 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5e697a51a7..5ae46ca11d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,14 +1,3 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# name: "CodeQL Advanced" on: @@ -16,26 +5,14 @@ on: branches: [ "develop", "master" ] pull_request: branches: [ "develop", "master" ] - schedule: - - cron: '38 8 * * 2' jobs: analyze: name: Analyze (${{ matrix.language }}) - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners (GitHub.com only) - # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + runs-on: ubuntu-latest permissions: - # required for all workflows security-events: write - - # required to fetch internal or private CodeQL packs packages: read - - # only required for workflows in private repositories actions: read contents: read @@ -47,55 +24,17 @@ jobs: build-mode: none - language: javascript-typescript build-mode: none - - language: python - build-mode: none - # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' - # Use `c-cpp` to analyze code written in C, C++ or both - # Use 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, - # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. - # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how - # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: - name: Checkout repository uses: actions/checkout@v4 - # Add any setup steps before running the `github/codeql-action/init` action. - # This includes steps like installing compilers or runtimes (`actions/setup-node` - # or others). This is typically only required for manual builds. - # - name: Setup runtime (example) - # uses: actions/setup-example@v1 - - # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - name: Run manual build steps - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 + config-file: ./.github/codeql/codeql-config.yml - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 From aebea9d7dc18d1a15f1a764b4558c5e8f51ce24a Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:34:54 +0100 Subject: [PATCH 6/6] feat: add on-demand Kraken trading fee check before trades (#2890) * feat: add Kraken trading fee schedule sync - Add ExchangeTradingFeeDto for storing fee data - Add getTradingFee() method to KrakenService using CCXT fetchTradingFee - Add daily cronjob (6:00 AM) to sync Kraken trading fees - Store fees in settings under 'krakenTradingFee' key - Log when maker/taker fee rates change - Add getKrakenTradingFee() getter to SettingService * fix: add process ID and optimize DB writes for trading fee sync - Add Process.EXCHANGE_TRADING_FEE_SYNC to enable job monitoring/disabling - Only save to DB when fee actually changed (avoid daily unnecessary writes) * refactor: change Kraken trading fee sync to on-demand before trades - Remove cronjob-based sync (was EVERY_DAY_AT_6AM) - Add on-demand fee check in KrakenService.buy() and sell() - Check if fee data is older than 60 minutes before each trade - If stale: fetch from Kraken API and update settings - Support multiple symbols: BTC/CHF and USDT/CHF - Separate setting keys: krakenTradingFee:BTC/CHF, krakenTradingFee:USDT/CHF - Each entry stores timestamp in 'updated' field This ensures fee data is always fresh when executing trades, rather than potentially being up to 24 hours old with cronjob approach. * fix: add error handling for trading fee API calls API errors no longer block trades. If fee refresh fails, use cached/stale value and log warning instead. * refactor: improve symbol matching and use parallel execution - Use exact symbol matching (tradePair/reversePair) instead of includes() - Use Promise.all() for parallel fee refresh instead of sequential loop * fix: check ALL tracked trading fees before every Kraken trade Before ANY trade on Kraken (regardless of which pair), ensure both BTC/CHF and USDT/CHF fees are up-to-date (not older than 60 minutes). - Rename ensureTradingFeeUpToDate() to ensureAllTradingFeesUpToDate() - Remove from/to parameters - always check all TRACKED_SYMBOLS - Keep parallel execution with Promise.all() * fix: handle invalid date in isStale() defensively Treat corrupted/invalid updated timestamps as stale to ensure fee refresh rather than silently using potentially outdated data. * feat: refactoring --------- Co-authored-by: David May --- .../exchange/services/exchange.service.ts | 2 +- .../exchange/services/kraken.service.ts | 76 ++++++++++++++++++- .../setting/dto/exchange-trading-fee.dto.ts | 9 +++ 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 src/shared/models/setting/dto/exchange-trading-fee.dto.ts diff --git a/src/integration/exchange/services/exchange.service.ts b/src/integration/exchange/services/exchange.service.ts index eb49df34fe..c2229c711b 100644 --- a/src/integration/exchange/services/exchange.service.ts +++ b/src/integration/exchange/services/exchange.service.ts @@ -296,7 +296,7 @@ export abstract class ExchangeService extends PricingProvider implements OnModul // orders - private async trade(from: string, to: string, amount: number): Promise { + protected async trade(from: string, to: string, amount: number): Promise { // place the order const { pair, direction } = await this.getTradePair(from, to); const { amount: amountPrecision } = await this.getPrecision(pair); diff --git a/src/integration/exchange/services/kraken.service.ts b/src/integration/exchange/services/kraken.service.ts index 02408bed4a..dc846f3c2a 100644 --- a/src/integration/exchange/services/kraken.service.ts +++ b/src/integration/exchange/services/kraken.service.ts @@ -1,14 +1,20 @@ -import { Injectable } from '@nestjs/common'; -import { kraken, Order } from 'ccxt'; +import { Inject, Injectable } from '@nestjs/common'; +import { kraken, Order, TradingFeeInterface } from 'ccxt'; import { GetConfig } from 'src/config/config'; import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum'; +import { ExchangeTradingFeeDto } from 'src/shared/models/setting/dto/exchange-trading-fee.dto'; +import { SettingService } from 'src/shared/models/setting/setting.service'; import { DfxLogger } from 'src/shared/services/dfx-logger'; +import { ExchangeName } from '../enums/exchange.enum'; import { ExchangeService } from './exchange.service'; @Injectable() export class KrakenService extends ExchangeService { protected readonly logger = new DfxLogger(KrakenService); + private static readonly FEE_MAX_AGE_MINUTES = 60; + private static readonly TRACKED_SYMBOLS = ['BTC/CHF', 'USDT/CHF']; + // use auto-detect for kraken protected networks: { [b in Blockchain]: string | false } = { Arbitrum: false, @@ -46,14 +52,80 @@ export class KrakenService extends ExchangeService { Yapeal: undefined, }; + @Inject() private readonly settingService: SettingService; + constructor() { super(kraken, GetConfig().kraken); } + // Override trade to ensure ALL trading fees are up-to-date before each trade + protected async trade(from: string, to: string, amount: number): Promise { + await this.ensureAllTradingFeesUpToDate(); + return super.trade(from, to, amount); + } + protected async updateOrderPrice(order: Order, amount: number, price: number): Promise { // order ID does not change for Kraken return this.callApi((e) => e.editOrder(order.id, order.symbol, order.type, order.side, amount, price)).then( () => order.id, ); } + + async getTradingFee(symbol: string): Promise { + return this.callApi((e) => e.fetchTradingFee(symbol)); + } + + // Public getter for other services + async getKrakenTradingFee(symbol: string): Promise { + return this.settingService.getObjCached(this.getTradingFeeKey(symbol)); + } + + private async ensureAllTradingFeesUpToDate(): Promise { + await Promise.all(KrakenService.TRACKED_SYMBOLS.map((symbol) => this.refreshTradingFeeIfStale(symbol))); + } + + private async refreshTradingFeeIfStale(symbol: string): Promise { + const key = this.getTradingFeeKey(symbol); + const current = await this.settingService.getObj(key); + + if (current && !this.isStale(current.updated)) { + return current; + } + + try { + const fee = await this.getTradingFee(symbol); + const newFee: ExchangeTradingFeeDto = { + exchange: ExchangeName.KRAKEN, + symbol: fee.symbol, + maker: fee.maker, + taker: fee.taker, + percentage: fee.percentage, + tierBased: fee.tierBased, + updated: new Date().toISOString(), + }; + + if (current?.maker !== newFee.maker || current?.taker !== newFee.taker) { + this.logger.info( + `Kraken trading fee for ${symbol} changed: maker ${current?.maker ?? 'N/A'} -> ${newFee.maker}, taker ${current?.taker ?? 'N/A'} -> ${newFee.taker}`, + ); + } + + await this.settingService.setObj(key, newFee); + return newFee; + } catch (e) { + this.logger.warn(`Failed to refresh trading fee for ${symbol}, using cached value:`, e); + return current; + } + } + + private getTradingFeeKey(symbol: string): string { + return `krakenTradingFee:${symbol}`; + } + + private isStale(updated: string): boolean { + const updatedDate = new Date(updated); + if (isNaN(updatedDate.getTime())) return true; // Treat invalid date as stale + const ageMinutes = (Date.now() - updatedDate.getTime()) / 1000 / 60; + return ageMinutes > KrakenService.FEE_MAX_AGE_MINUTES; + } } diff --git a/src/shared/models/setting/dto/exchange-trading-fee.dto.ts b/src/shared/models/setting/dto/exchange-trading-fee.dto.ts new file mode 100644 index 0000000000..1fd949cae9 --- /dev/null +++ b/src/shared/models/setting/dto/exchange-trading-fee.dto.ts @@ -0,0 +1,9 @@ +export class ExchangeTradingFeeDto { + exchange: string; + symbol: string; + maker: number; + taker: number; + percentage: boolean; + tierBased: boolean; + updated: string; +}