From e577c6c36303a2d125f36a00d9c043458be00dd2 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:08:00 +0100 Subject: [PATCH 1/4] chore: rename master branch references to main (#2900) * chore: rename master branch references to main Update all workflow files to use 'main' instead of 'master': - api-prd.yaml: trigger on main branch - api-pr.yaml: run PR checks for main branch - codeql.yml: scan main branch - auto-release-pr.yaml: create release PRs to main * docs: fix CitreaScan branch reference (master -> main) --- .github/workflows/api-pr.yaml | 2 +- .github/workflows/api-prd.yaml | 2 +- .github/workflows/auto-release-pr.yaml | 16 ++++++++-------- .github/workflows/codeql.yml | 4 ++-- infrastructure/citrea/citreascan/README.md | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/api-pr.yaml b/.github/workflows/api-pr.yaml index e560e4c469..fedaa8712e 100644 --- a/.github/workflows/api-pr.yaml +++ b/.github/workflows/api-pr.yaml @@ -3,7 +3,7 @@ name: API PR CI on: pull_request: branches: - - master + - main - develop workflow_dispatch: diff --git a/.github/workflows/api-prd.yaml b/.github/workflows/api-prd.yaml index 336945acdd..b65407a89e 100644 --- a/.github/workflows/api-prd.yaml +++ b/.github/workflows/api-prd.yaml @@ -2,7 +2,7 @@ name: API PRD CI/CD on: push: - branches: [master] + branches: [main] workflow_dispatch: permissions: diff --git a/.github/workflows/auto-release-pr.yaml b/.github/workflows/auto-release-pr.yaml index 710cb56df6..cbb705b6d7 100644 --- a/.github/workflows/auto-release-pr.yaml +++ b/.github/workflows/auto-release-pr.yaml @@ -23,15 +23,15 @@ jobs: with: fetch-depth: 0 - - name: Fetch master branch - run: git fetch origin master + - name: Fetch main branch + run: git fetch origin main - name: Check for existing PR id: check-pr run: | - PR_COUNT=$(gh pr list --base master --head develop --state open --json number --jq 'length') + PR_COUNT=$(gh pr list --base main --head develop --state open --json number --jq 'length') echo "pr_exists=$([[ $PR_COUNT -gt 0 ]] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT - echo "::notice::Open PRs from develop to master: $PR_COUNT" + echo "::notice::Open PRs from develop to main: $PR_COUNT" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -39,10 +39,10 @@ jobs: id: check-diff if: steps.check-pr.outputs.pr_exists == 'false' run: | - DIFF_COUNT=$(git rev-list --count origin/master..origin/develop) + DIFF_COUNT=$(git rev-list --count origin/main..origin/develop) echo "has_changes=$([[ $DIFF_COUNT -gt 0 ]] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT echo "commit_count=$DIFF_COUNT" >> $GITHUB_OUTPUT - echo "::notice::Commits ahead of master: $DIFF_COUNT" + echo "::notice::Commits ahead of main: $DIFF_COUNT" - name: Create Release PR if: steps.check-pr.outputs.pr_exists == 'false' && steps.check-diff.outputs.has_changes == 'true' @@ -64,7 +64,7 @@ jobs: > /tmp/pr-body.md gh pr create \ - --base master \ + --base main \ --head develop \ - --title "Release: develop -> master" \ + --title "Release: develop -> main" \ --body-file /tmp/pr-body.md diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5ae46ca11d..921384d6b7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,9 +2,9 @@ name: "CodeQL Advanced" on: push: - branches: [ "develop", "master" ] + branches: [ "develop", "main" ] pull_request: - branches: [ "develop", "master" ] + branches: [ "develop", "main" ] jobs: analyze: diff --git a/infrastructure/citrea/citreascan/README.md b/infrastructure/citrea/citreascan/README.md index 1eca472986..caecbbbd13 100644 --- a/infrastructure/citrea/citreascan/README.md +++ b/infrastructure/citrea/citreascan/README.md @@ -26,7 +26,7 @@ 1. Create the file `docker-compose-blockscout-citrea-testnet.frontend.env` in `/home/{user}` 1. Copy the content of the github repo file `https://github.com/CitreaScan/frontend/blob/develop/.env.dev` in `docker-compose-blockscout-citrea-testnet.frontend.env` -The `docker-compose-blockscout-citrea-testnet.backend.env` and `docker-compose-blockscout-citrea-testnet.frontend.env` files are also located in the corresponding github repos. They will be overwritten by github workflows in case of changes in the `develop` or in the `master` branch. +The `docker-compose-blockscout-citrea-testnet.backend.env` and `docker-compose-blockscout-citrea-testnet.frontend.env` files are also located in the corresponding github repos. They will be overwritten by github workflows in case of changes in the `develop` or in the `main` branch. # Start Docker Containers From 6f0520e3913b413a64bf233ddddc923a3e07bda7 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:12:54 +0100 Subject: [PATCH 2/4] fix: load wallet relation for autoTradeApproval check in mail login (#2904) In completeSignInByMail(), the wallet relation was not loaded when fetching userData, causing the autoTradeApproval check in checkPendingRecommendation() to always fail. Changes: - Add wallet to relations in getUserData() call - Pass account.wallet to checkPendingRecommendation() This aligns mail-login with wallet-login behavior where the wallet is properly passed to checkPendingRecommendation(). --- src/subdomains/generic/user/models/auth/auth.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subdomains/generic/user/models/auth/auth.service.ts b/src/subdomains/generic/user/models/auth/auth.service.ts index 7360e1b1dd..36af0e9e08 100644 --- a/src/subdomains/generic/user/models/auth/auth.service.ts +++ b/src/subdomains/generic/user/models/auth/auth.service.ts @@ -299,7 +299,7 @@ export class AuthService { const entry = this.mailKeyList.get(code); if (!this.isMailKeyValid(entry)) throw new Error('Login link expired'); - const account = await this.userDataService.getUserData(entry.userDataId, { users: true }); + const account = await this.userDataService.getUserData(entry.userDataId, { users: true, wallet: true }); const ipLog = await this.ipLogService.create(ip, entry.loginUrl, entry.mail, undefined, account); if (!ipLog.result) throw new Error('The country of IP address is not allowed'); @@ -311,7 +311,7 @@ export class AuthService { if (account.isDeactivated) await this.userDataService.updateUserDataInternal(account, account.reactivateUserData()); - if (!account.tradeApprovalDate) await this.checkPendingRecommendation(account); + if (!account.tradeApprovalDate) await this.checkPendingRecommendation(account, account.wallet); const url = new URL(entry.redirectUri ?? `${Config.frontend.services}/account`); url.searchParams.set('session', token); From ff71ad9edb7b48b80cbb79046672d1aac9f77993 Mon Sep 17 00:00:00 2001 From: bernd2022 <104787072+bernd2022@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:17:53 +0100 Subject: [PATCH 3/4] feat: improve local development experience for mail handling (#2905) - Skip mail sending in local environment and log mail details instead - Log mail login URL in local environment for easy testing - Add SERVICES_URL to .env.local.example for complete login URLs --- .env.local.example | 3 +++ src/subdomains/generic/user/models/auth/auth.service.ts | 7 ++++++- .../supporting/notification/services/mail.service.ts | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.env.local.example b/.env.local.example index 2e8253cc2f..a572ed9126 100644 --- a/.env.local.example +++ b/.env.local.example @@ -44,6 +44,9 @@ SQL_ENCRYPT=false JWT_SECRET=local-dev-secret-change-in-production JWT_EXPIRES_IN=14d +# Frontend URLs (for mail login redirect) +SERVICES_URL=http://localhost:3000 + # Blockchain Gateway URLs (seeds are generated by 'npm run setup') SOLANA_GATEWAY_URL=https://api.mainnet-beta.solana.com TRON_GATEWAY_URL=https://api.trongrid.io diff --git a/src/subdomains/generic/user/models/auth/auth.service.ts b/src/subdomains/generic/user/models/auth/auth.service.ts index 36af0e9e08..723adc1728 100644 --- a/src/subdomains/generic/user/models/auth/auth.service.ts +++ b/src/subdomains/generic/user/models/auth/auth.service.ts @@ -8,7 +8,7 @@ import { import { JwtService } from '@nestjs/jwt'; import { CronExpression } from '@nestjs/schedule'; import { randomUUID } from 'crypto'; -import { Config } from 'src/config/config'; +import { Config, Environment } from 'src/config/config'; import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum'; import { CryptoService } from 'src/integration/blockchain/shared/services/crypto.service'; import { GeoLocationService } from 'src/integration/geolocation/geo-location.service'; @@ -255,6 +255,11 @@ export class AuthService { const key = randomUUID(); const loginUrl = `${Config.frontend.services}/mail-login?otp=${key}`; + // Log login URL in local environment for testing + if (Config.environment === Environment.LOC) { + this.logger.info(`[LOCAL DEV] Mail login URL for ${dto.mail}: ${loginUrl}`); + } + this.mailKeyList.set(key, { created: new Date(), key, diff --git a/src/subdomains/supporting/notification/services/mail.service.ts b/src/subdomains/supporting/notification/services/mail.service.ts index 67cf10ab1f..46c2818bf2 100644 --- a/src/subdomains/supporting/notification/services/mail.service.ts +++ b/src/subdomains/supporting/notification/services/mail.service.ts @@ -1,5 +1,6 @@ import { MailerOptions, MailerService } from '@nestjs-modules/mailer'; import { Injectable } from '@nestjs/common'; +import { Config, Environment } from 'src/config/config'; import { DfxLogger } from 'src/shared/services/dfx-logger'; import { Mail } from '../entities/mail/base/mail'; @@ -21,6 +22,12 @@ export class MailService { constructor(private readonly mailerService: MailerService) {} async send(mail: Mail): Promise { + // Skip mail sending in local environment + if (Config.environment === Environment.LOC) { + this.logger.info(`[LOCAL DEV] Mail skipped - to: ${mail.to}, subject: ${mail.subject}`); + return; + } + try { await this.mailerService.sendMail({ from: mail.from, From d31c779d7544ad6057128668312489804b87e859 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:01:11 +0100 Subject: [PATCH 4/4] fix: initialize KYC progress on mail login to set kycLevel 10 (#2903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: initialize KYC progress on mail login to set kycLevel 10 Mail login users had kycLevel 0 even though their email was verified via OTP. This happened because the KYC flow was never triggered after mail login, leaving CONTACT_DATA step uncompleted. Changes: - Add initializeProgress() method to KycService that triggers updateProgress() for a given user - Call initializeProgress() in completeSignInByMail() after successful authentication Now when a user completes mail login: 1. initializeProgress() triggers updateProgress() 2. CONTACT_DATA step is auto-completed (user.mail exists) 3. PERSONAL_DATA becomes next step → kycLevel set to 10 This makes mail login behavior consistent with wallet login where adding an email triggers the same KYC flow. * fix: improve initializeProgress with retry logic and error handling - Set shouldContinue=false to only set kycLevel without initiating next KYC steps (PERSONAL_DATA) - Add Util.retry() with duplicate key check for race conditions (e.g., user double-clicks OTP link) - Make KYC initialization non-blocking in completeSignInByMail() so login succeeds even if KYC init fails * fix: correct initializeProgress to use autoStep=false The previous fix with shouldContinue=false was incorrect - it prevented any KYC progress from happening because CONTACT_DATA doesn't return a nextLevel value. The correct solution is shouldContinue=true, autoStep=false: - shouldContinue=true: allows CONTACT_DATA to be initiated and auto-completed - autoStep=false: prevents PERSONAL_DATA from being initiated (depth > 0) Flow: 1. depth=0: (autoStep || depth===0) = true → CONTACT_DATA initiated/completed 2. depth=1: (autoStep || depth===0) = false → Level 10 set, no further steps * fix: skip initializeProgress for users with CONTACT_DATA completed Prevents unintentionally initiating PERSONAL_DATA step for returning users who already have CONTACT_DATA completed. The level should already be set for these users. * chore: add migration script to fix kycLevel for edge case users 4 active users have CONTACT_DATA completed but kycLevel = 0. This SQL script updates their level to 10. Affected user IDs: 257036, 229330, 1158, 1058 * fix: make migration script safer - Comment out UPDATE statement (must be uncommented manually) - Add transaction wrapper (BEGIN/COMMIT/ROLLBACK) - Use JOIN-based UPDATE syntax for SQL Server - Add clear step-by-step instructions - Add row count verification check * fix: Start KYC process on mail add * fix: script executed --------- Co-authored-by: David May --- src/subdomains/generic/kyc/services/kyc.service.ts | 7 +++++++ .../generic/user/models/auth/auth.service.ts | 10 ++++++++++ .../generic/user/models/user-data/user-data.service.ts | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/src/subdomains/generic/kyc/services/kyc.service.ts b/src/subdomains/generic/kyc/services/kyc.service.ts index a2e1435f9b..878c1e2160 100644 --- a/src/subdomains/generic/kyc/services/kyc.service.ts +++ b/src/subdomains/generic/kyc/services/kyc.service.ts @@ -430,6 +430,13 @@ export class KycService { ); } + async initializeProcess(userData: UserData): Promise { + const user = await this.getUser(userData.kycHash); + if (user.hasDoneStep(KycStepName.CONTACT_DATA)) return user; + + return this.updateProgress(user, true, false); + } + public getMailFailedReason(comment: string, language: string): string { return `
    ${comment ?.split(';') diff --git a/src/subdomains/generic/user/models/auth/auth.service.ts b/src/subdomains/generic/user/models/auth/auth.service.ts index 723adc1728..cbc95ff3c1 100644 --- a/src/subdomains/generic/user/models/auth/auth.service.ts +++ b/src/subdomains/generic/user/models/auth/auth.service.ts @@ -1,9 +1,11 @@ import { BadRequestException, ConflictException, + Inject, Injectable, NotFoundException, UnauthorizedException, + forwardRef, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { CronExpression } from '@nestjs/schedule'; @@ -25,6 +27,7 @@ import { Util } from 'src/shared/utils/util'; import { RefService } from 'src/subdomains/core/referral/process/ref.service'; import { KycStepName } from 'src/subdomains/generic/kyc/enums/kyc-step-name.enum'; import { KycAdminService } from 'src/subdomains/generic/kyc/services/kyc-admin.service'; +import { KycService } from 'src/subdomains/generic/kyc/services/kyc.service'; import { MailContext, MailType } from 'src/subdomains/supporting/notification/enums'; import { MailKey, MailTranslationKey } from 'src/subdomains/supporting/notification/factories/mail.factory'; import { NotificationService } from 'src/subdomains/supporting/notification/services/notification.service'; @@ -87,6 +90,7 @@ export class AuthService { private readonly settingService: SettingService, private readonly recommendationService: RecommendationService, private readonly kycAdminService: KycAdminService, + @Inject(forwardRef(() => KycService)) private readonly kycService: KycService, ) {} @DfxCron(CronExpression.EVERY_MINUTE) @@ -318,6 +322,12 @@ export class AuthService { if (!account.tradeApprovalDate) await this.checkPendingRecommendation(account, account.wallet); + try { + await this.kycService.initializeProcess(account); + } catch (e) { + this.logger.error(`Failed to initialize KYC process for account ${account.id}:`, e); + } + const url = new URL(entry.redirectUri ?? `${Config.frontend.services}/account`); url.searchParams.set('session', token); return url.toString(); diff --git a/src/subdomains/generic/user/models/user-data/user-data.service.ts b/src/subdomains/generic/user/models/user-data/user-data.service.ts index 37bc9fa885..c1f735f9d6 100644 --- a/src/subdomains/generic/user/models/user-data/user-data.service.ts +++ b/src/subdomains/generic/user/models/user-data/user-data.service.ts @@ -647,6 +647,12 @@ export class UserDataService { await this.kycLogService.createMailChangeLog(userData, userData.mail, mail); + try { + await this.kycService.initializeProcess(userData); + } catch (e) { + this.logger.error(`Failed to initialize KYC process for account ${userData.id}:`, e); + } + return userData; }