From 2c8d9bd69828c6aa8fe9c1362ba19707dde9907d Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Wed, 4 Feb 2026 02:26:42 +0100 Subject: [PATCH 01/14] feat(liquidity): Clementine Bridge Integration for BTC-cBTC (#3106) --- src/config/config.ts | 9 + .../blockchain/blockchain.module.ts | 3 + .../clementine/clementine-client.ts | 465 +++++++++++++++ .../clementine/clementine.module.ts | 10 + .../clementine/clementine.service.ts | 17 + .../actions/clementine-bridge.adapter.ts | 543 ++++++++++++++++++ .../core/liquidity-management/enums/index.ts | 2 + .../liquidity-action-integration.factory.ts | 3 + .../liquidity-management.module.ts | 2 + 9 files changed, 1054 insertions(+) create mode 100644 src/integration/blockchain/clementine/clementine-client.ts create mode 100644 src/integration/blockchain/clementine/clementine.module.ts create mode 100644 src/integration/blockchain/clementine/clementine.service.ts create mode 100644 src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts diff --git a/src/config/config.ts b/src/config/config.ts index a066380874..78dcf138b7 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -5,6 +5,7 @@ import { ConstructorArgs } from 'ccxt'; import JSZip from 'jszip'; import { I18nOptions } from 'nestjs-i18n'; import { join } from 'path'; +import { ClementineNetwork } from 'src/integration/blockchain/clementine/clementine-client'; import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum'; import { WalletAccount } from 'src/integration/blockchain/shared/evm/domain/wallet-account'; import { Asset } from 'src/shared/models/asset/asset.entity'; @@ -815,6 +816,14 @@ export class Configuration { citreaTestnetApiKey: process.env.CITREA_TESTNET_API_KEY, blockscoutApiUrl: process.env.CITREA_TESTNET_BLOCKSCOUT_API_URL, }, + clementine: { + network: (process.env.CLEMENTINE_NETWORK as ClementineNetwork) ?? ClementineNetwork.BITCOIN, + cliPath: process.env.CLEMENTINE_CLI_PATH ?? 'clementine-cli', + recoveryTaprootAddress: process.env.CLEMENTINE_RECOVERY_TAPROOT_ADDRESS, + signerAddress: process.env.CLEMENTINE_SIGNER_ADDRESS, + timeoutMs: parseInt(process.env.CLEMENTINE_TIMEOUT_MS ?? '60000'), + signingTimeoutMs: parseInt(process.env.CLEMENTINE_SIGNING_TIMEOUT_MS ?? '300000'), + }, bitcoinTestnet4: { btcTestnet4Output: { active: process.env.NODE_BTC_TESTNET4_OUT_URL_ACTIVE, diff --git a/src/integration/blockchain/blockchain.module.ts b/src/integration/blockchain/blockchain.module.ts index 23c96b721e..084994d5b9 100644 --- a/src/integration/blockchain/blockchain.module.ts +++ b/src/integration/blockchain/blockchain.module.ts @@ -11,6 +11,7 @@ import { BaseModule } from './base/base.module'; import { BscModule } from './bsc/bsc.module'; import { CitreaModule } from './citrea/citrea.module'; import { CitreaTestnetModule } from './citrea-testnet/citrea-testnet.module'; +import { ClementineModule } from './clementine/clementine.module'; import { DEuroModule } from './deuro/deuro.module'; import { Ebel2xModule } from './ebel2x/ebel2x.module'; import { JuiceModule } from './juice/juice.module'; @@ -63,6 +64,7 @@ import { ZanoModule } from './zano/zano.module'; CardanoModule, CitreaModule, CitreaTestnetModule, + ClementineModule, RealUnitBlockchainModule, Eip7702DelegationModule, PimlicoPaymasterModule, @@ -93,6 +95,7 @@ import { ZanoModule } from './zano/zano.module'; CardanoModule, CitreaModule, CitreaTestnetModule, + ClementineModule, CryptoService, BlockchainRegistryService, TxValidationService, diff --git a/src/integration/blockchain/clementine/clementine-client.ts b/src/integration/blockchain/clementine/clementine-client.ts new file mode 100644 index 0000000000..81ca02a3c6 --- /dev/null +++ b/src/integration/blockchain/clementine/clementine-client.ts @@ -0,0 +1,465 @@ +import { spawn } from 'child_process'; +import { DfxLogger } from 'src/shared/services/dfx-logger'; + +export enum ClementineNetwork { + BITCOIN = 'bitcoin', + TESTNET4 = 'testnet4', +} + +export interface ClementineConfig { + network: ClementineNetwork; + cliPath: string; + timeoutMs: number; + signingTimeoutMs: number; +} + +export enum DepositStatus { + PENDING = 'pending', + CONFIRMING = 'confirming', + COMPLETED = 'completed', + FAILED = 'failed', +} + +export enum WithdrawStatus { + PENDING = 'pending', + SCANNING = 'scanning', + SIGNING = 'signing', + BROADCASTING = 'broadcasting', + COMPLETED = 'completed', + FAILED = 'failed', +} + +export interface DepositStartResult { + depositAddress: string; +} + +export interface DepositStatusResult { + status: DepositStatus; + depositAddress: string; + btcTxId?: string; + errorMessage?: string; +} + +export interface WithdrawStartResult { + signerAddress: string; +} + +export interface WithdrawScanResult { + withdrawalUtxo: string; +} + +export interface WithdrawSignaturesResult { + optimisticSignature: string; + operatorPaidSignature: string; +} + +export interface WithdrawStatusResult { + status: WithdrawStatus; + withdrawalUtxo: string; + btcTxId?: string; + errorMessage?: string; +} + +// Fixed bridge amount as per Clementine/BitVM design +export const CLEMENTINE_BRIDGE_AMOUNT_BTC = 10; + +// Dust amount required to create withdrawal UTXO (330 satoshis) +export const CLEMENTINE_WITHDRAWAL_DUST_BTC = 0.0000033; + +export class ClementineClient { + private readonly logger = new DfxLogger(ClementineClient); + + constructor(private readonly config: ClementineConfig) {} + + // --- INITIALIZATION --- // + + /** + * Initialize the CLI configuration + * Creates ~/.clementine/bridge_cli_config.toml + */ + async init(): Promise { + await this.executeCommand(['init']); + } + + /** + * Get the current CLI configuration + */ + async showConfig(): Promise { + return this.executeCommand(['show-config']); + } + + // --- WALLET OPERATIONS --- // + + /** + * Create a new wallet + * @param name Wallet name + * @param type Wallet type: 'deposit' or 'withdrawal' + */ + async walletCreate(name: string, type: 'deposit' | 'withdrawal'): Promise { + await this.executeCommand(['wallet', 'create', name, type]); + } + + // --- DEPOSIT OPERATIONS --- // + + /** + * Start a deposit operation (BTC -> cBTC) + * Command: clementine-cli deposit start + * @param recoveryTaprootAddress Recovery address for failed deposits (dep-prefixed) + * @param citreaAddress Destination address on Citrea for cBTC + * @returns The deposit address to send 10 BTC to + */ + async depositStart(recoveryTaprootAddress: string, citreaAddress: string): Promise { + const output = await this.executeCommand(['deposit', 'start', recoveryTaprootAddress, citreaAddress]); + + // Parse the deposit address from CLI output + const addressMatch = + output.match(/(?:deposit\s+)?address[:\s]+([a-zA-Z0-9]+)/i) || + output.match(/bc1[a-zA-Z0-9]{59,}/i) || + output.match(/tb1[a-zA-Z0-9]{59,}/i); + if (!addressMatch) { + throw new Error(`Failed to parse deposit address from CLI output: ${output}`); + } + + return { depositAddress: addressMatch[1] || addressMatch[0] }; + } + + /** + * Get the status of a deposit operation + * Command: clementine-cli deposit status + * @param depositAddress The deposit address to check + */ + async depositStatus(depositAddress: string): Promise { + const output = await this.executeCommand(['deposit', 'status', depositAddress]); + return this.parseDepositStatus(output, depositAddress); + } + + /** + * Create a signed recovery transaction (for airgapped device usage) + * Command: clementine-cli deposit create-signed-recovery-tx + * @param recoveryTaprootAddress Recovery address (dep-prefixed) + * @param citreaAddress Citrea address used in deposit + * @param depositUtxoOutpoint Deposit UTXO (format: txid:vout) + * @param destinationAddress Bitcoin destination for recovered funds + * @param feeRate Fee rate in sat/vB + * @param amount Amount in BTC + * @param clementineAggregatedKey Aggregated public key from bridge + */ + async depositCreateSignedRecoveryTx( + recoveryTaprootAddress: string, + citreaAddress: string, + depositUtxoOutpoint: string, + destinationAddress: string, + feeRate: number, + amount: number, + clementineAggregatedKey: string, + ): Promise { + return this.executeCommand( + [ + 'deposit', + 'create-signed-recovery-tx', + recoveryTaprootAddress, + citreaAddress, + depositUtxoOutpoint, + destinationAddress, + feeRate.toString(), + amount.toString(), + clementineAggregatedKey, + ], + this.config.signingTimeoutMs, + ); + } + + // --- WITHDRAWAL OPERATIONS --- // + + /** + * Start a withdrawal operation (cBTC -> BTC) + * Command: clementine-cli withdraw start + * Note: After this, user must send 330 satoshis to the signer address + * @param signerAddress Signer address (wit-prefixed) for the withdrawal + * @param destinationAddress Bitcoin destination address for the withdrawal + * @returns The signer address (user must send 330 sats to this) + */ + async withdrawStart(signerAddress: string, destinationAddress: string): Promise { + await this.executeCommand(['withdraw', 'start', signerAddress, destinationAddress]); + return { signerAddress }; + } + + /** + * Scan for available withdrawal UTXOs + * Command: clementine-cli withdraw scan + * @param signerAddress Signer address (wit-prefixed) + * @param destinationAddress Bitcoin destination address + * @returns The withdrawal UTXO if found + */ + async withdrawScan(signerAddress: string, destinationAddress: string): Promise { + const output = await this.executeCommand(['withdraw', 'scan', signerAddress, destinationAddress]); + + // Parse withdrawal UTXO from output (format: txid:vout) + const utxoMatch = output.match(/([a-f0-9]{64}:\d+)/i); + if (utxoMatch) { + return { withdrawalUtxo: utxoMatch[1] }; + } + + // Check if no UTXO found + if (output.toLowerCase().includes('no') || output.toLowerCase().includes('not found')) { + return null; + } + + return null; + } + + /** + * Generate withdrawal signatures + * Command: clementine-cli withdraw generate-withdrawal-signatures + * @param signerAddress Signer address (wit-prefixed) + * @param destinationAddress Bitcoin destination address + * @param withdrawalUtxo The withdrawal UTXO (format: txid:vout) + * @returns Two signatures: optimistic (9.9999976 BTC) and operator-paid (9.97 BTC) + */ + async withdrawGenerateSignatures( + signerAddress: string, + destinationAddress: string, + withdrawalUtxo: string, + ): Promise { + const output = await this.executeCommand( + ['withdraw', 'generate-withdrawal-signatures', signerAddress, destinationAddress, withdrawalUtxo], + this.config.signingTimeoutMs, + ); + + // Parse both signatures from output + const sigMatches = output.match(/signature[:\s]+([a-f0-9]+)/gi); + if (!sigMatches || sigMatches.length < 2) { + throw new Error(`Failed to parse withdrawal signatures from CLI output: ${output}`); + } + + const extractSig = (match: string) => match.replace(/signature[:\s]+/i, ''); + + return { + optimisticSignature: extractSig(sigMatches[0]), + operatorPaidSignature: extractSig(sigMatches[1]), + }; + } + + /** + * Send withdrawal request to bridge contract (burns cBTC) + * Command: clementine-cli withdraw send-safe-withdraw + * Note: Uses send-safe-withdraw (non-interactive) instead of send (opens browser) + * @param signerAddress Signer address (wit-prefixed) + * @param destinationAddress Bitcoin destination address + * @param withdrawalUtxo The withdrawal UTXO (format: txid:vout) + * @param optimisticSignature The optimistic withdrawal signature + */ + async withdrawSend( + signerAddress: string, + destinationAddress: string, + withdrawalUtxo: string, + optimisticSignature: string, + ): Promise { + await this.executeCommand([ + 'withdraw', + 'send-safe-withdraw', + signerAddress, + destinationAddress, + withdrawalUtxo, + optimisticSignature, + ]); + } + + /** + * Get the status of a withdrawal operation + * Command: clementine-cli withdraw status + * @param withdrawalUtxo The withdrawal UTXO to check (format: txid:vout) + */ + async withdrawStatus(withdrawalUtxo: string): Promise { + const output = await this.executeCommand(['withdraw', 'status', withdrawalUtxo]); + return this.parseWithdrawStatus(output, withdrawalUtxo); + } + + /** + * Send operator-paid withdrawal signature (fallback after 12 hours) + * Command: clementine-cli withdraw send-withdrawal-signature-to-operators + * @param signerAddress Signer address (wit-prefixed) + * @param destinationAddress Bitcoin destination address + * @param withdrawalUtxo The withdrawal UTXO (format: txid:vout) + * @param operatorPaidSignature The operator-paid withdrawal signature + */ + async withdrawSendToOperators( + signerAddress: string, + destinationAddress: string, + withdrawalUtxo: string, + operatorPaidSignature: string, + ): Promise { + await this.executeCommand([ + 'withdraw', + 'send-withdrawal-signature-to-operators', + signerAddress, + destinationAddress, + withdrawalUtxo, + operatorPaidSignature, + ]); + } + + // --- INTERNAL METHODS --- // + + private async executeCommand(args: string[], timeout?: number): Promise { + const fullArgs = this.buildArgs(args); + this.logger.verbose(`Executing: ${this.config.cliPath} ${fullArgs.join(' ')}`); + + try { + const output = await this.spawnAsync(fullArgs, timeout ?? this.config.timeoutMs); + return output; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + this.logger.error(`Clementine CLI error: ${message}`); + throw new Error(`Clementine CLI failed: ${message}`); + } + } + + private spawnAsync(args: string[], timeout: number): Promise { + return new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + let timeoutId: NodeJS.Timeout | null = null; + let killTimeoutId: NodeJS.Timeout | null = null; + let isSettled = false; + + const proc = spawn(this.config.cliPath, args, { + stdio: ['ignore', 'pipe', 'pipe'], + }); + + const cleanup = (): void => { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + if (killTimeoutId) { + clearTimeout(killTimeoutId); + killTimeoutId = null; + } + }; + + const settle = (error?: Error, result?: string): void => { + if (isSettled) return; + isSettled = true; + cleanup(); + + if (error) { + reject(error); + } else { + resolve(result ?? ''); + } + }; + + // Timeout handling + timeoutId = setTimeout(() => { + proc.kill('SIGTERM'); + + // Force kill after 5 seconds if process doesn't respond to SIGTERM + killTimeoutId = setTimeout(() => { + if (proc.exitCode === null) { + // Process still running, force kill + proc.kill('SIGKILL'); + } + }, 5000); + + settle(new Error(`Command timed out after ${timeout}ms`)); + }, timeout); + + proc.stdout.on('data', (data: Buffer) => { + stdout += data.toString(); + }); + + proc.stderr.on('data', (data: Buffer) => { + stderr += data.toString(); + }); + + proc.on('error', (error: Error) => { + settle(new Error(`Spawn error: ${error.message}`)); + }); + + proc.on('close', (code: number | null) => { + if (code === 0) { + settle(undefined, stdout); + } else { + settle(new Error(`Exit code ${code}: ${stderr || stdout}`)); + } + }); + }); + } + + private buildArgs(baseArgs: string[]): string[] { + const args = [...baseArgs]; + + // Insert --network flag after the subcommand, before positional arguments + // For commands with subcommand (e.g., 'deposit start'): insert at position 2 + // For single commands (e.g., 'show-config'): insert at position 1 + const insertPosition = baseArgs.length >= 2 ? 2 : 1; + args.splice(insertPosition, 0, '--network', this.config.network); + + return args; + } + + private parseDepositStatus(output: string, depositAddress: string): DepositStatusResult { + const lowerOutput = output.toLowerCase(); + + let status: DepositStatus = DepositStatus.PENDING; + if (lowerOutput.includes('completed') || lowerOutput.includes('success') || lowerOutput.includes('minted')) { + status = DepositStatus.COMPLETED; + } else if ( + lowerOutput.includes('confirming') || + lowerOutput.includes('waiting') || + lowerOutput.includes('confirmation') + ) { + status = DepositStatus.CONFIRMING; + } else if (lowerOutput.includes('failed') || lowerOutput.includes('error')) { + status = DepositStatus.FAILED; + } + + // Try to parse BTC transaction ID + const txMatch = output.match(/txid[:\s]+([a-f0-9]{64})/i) || output.match(/transaction[:\s]+([a-f0-9]{64})/i); + const btcTxId = txMatch ? txMatch[1] : undefined; + + // Try to parse error message + const errorMatch = output.match(/error[:\s]+(.+)/i); + const errorMessage = status === DepositStatus.FAILED ? errorMatch?.[1]?.trim() : undefined; + + return { + depositAddress, + status, + btcTxId, + errorMessage, + }; + } + + private parseWithdrawStatus(output: string, withdrawalUtxo: string): WithdrawStatusResult { + const lowerOutput = output.toLowerCase(); + + let status: WithdrawStatus = WithdrawStatus.PENDING; + if (lowerOutput.includes('completed') || lowerOutput.includes('success')) { + status = WithdrawStatus.COMPLETED; + } else if (lowerOutput.includes('broadcasting') || lowerOutput.includes('broadcast')) { + status = WithdrawStatus.BROADCASTING; + } else if (lowerOutput.includes('signing') || lowerOutput.includes('signature')) { + status = WithdrawStatus.SIGNING; + } else if (lowerOutput.includes('scanning') || lowerOutput.includes('scan')) { + status = WithdrawStatus.SCANNING; + } else if (lowerOutput.includes('failed') || lowerOutput.includes('error')) { + status = WithdrawStatus.FAILED; + } + + // Try to parse BTC transaction ID + const txMatch = output.match(/txid[:\s]+([a-f0-9]{64})/i) || output.match(/transaction[:\s]+([a-f0-9]{64})/i); + const btcTxId = txMatch ? txMatch[1] : undefined; + + // Try to parse error message + const errorMatch = output.match(/error[:\s]+(.+)/i); + const errorMessage = status === WithdrawStatus.FAILED ? errorMatch?.[1]?.trim() : undefined; + + return { + withdrawalUtxo, + status, + btcTxId, + errorMessage, + }; + } +} diff --git a/src/integration/blockchain/clementine/clementine.module.ts b/src/integration/blockchain/clementine/clementine.module.ts new file mode 100644 index 0000000000..84a189e2a4 --- /dev/null +++ b/src/integration/blockchain/clementine/clementine.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { SharedModule } from 'src/shared/shared.module'; +import { ClementineService } from './clementine.service'; + +@Module({ + imports: [SharedModule], + providers: [ClementineService], + exports: [ClementineService], +}) +export class ClementineModule {} diff --git a/src/integration/blockchain/clementine/clementine.service.ts b/src/integration/blockchain/clementine/clementine.service.ts new file mode 100644 index 0000000000..32854fc242 --- /dev/null +++ b/src/integration/blockchain/clementine/clementine.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { GetConfig } from 'src/config/config'; +import { ClementineClient } from './clementine-client'; + +@Injectable() +export class ClementineService { + private readonly client: ClementineClient; + + constructor() { + const config = GetConfig().blockchain.clementine; + this.client = new ClementineClient(config); + } + + getDefaultClient(): ClementineClient { + return this.client; + } +} diff --git a/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts b/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts new file mode 100644 index 0000000000..6c2ea0af8d --- /dev/null +++ b/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts @@ -0,0 +1,543 @@ +import { Injectable } from '@nestjs/common'; +import { GetConfig } from 'src/config/config'; +import { BitcoinClient } from 'src/integration/blockchain/bitcoin/node/bitcoin-client'; +import { BitcoinNodeType, BitcoinService } from 'src/integration/blockchain/bitcoin/node/bitcoin.service'; +import { BitcoinFeeService } from 'src/integration/blockchain/bitcoin/services/bitcoin-fee.service'; +import { CitreaClient } from 'src/integration/blockchain/citrea/citrea-client'; +import { CitreaService } from 'src/integration/blockchain/citrea/citrea.service'; +import { + CLEMENTINE_BRIDGE_AMOUNT_BTC, + CLEMENTINE_WITHDRAWAL_DUST_BTC, + ClementineClient, + ClementineNetwork, +} from 'src/integration/blockchain/clementine/clementine-client'; +import { ClementineService } from 'src/integration/blockchain/clementine/clementine.service'; +import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum'; +import { isAsset } from 'src/shared/models/active'; +import { AssetType } from 'src/shared/models/asset/asset.entity'; +import { AssetService } from 'src/shared/models/asset/asset.service'; +import { DfxLogger } from 'src/shared/services/dfx-logger'; +import { LiquidityManagementOrder } from '../../entities/liquidity-management-order.entity'; +import { LiquidityManagementSystem } from '../../enums'; +import { OrderFailedException } from '../../exceptions/order-failed.exception'; +import { OrderNotProcessableException } from '../../exceptions/order-not-processable.exception'; +import { Command, CorrelationId } from '../../interfaces'; +import { LiquidityActionAdapter } from './base/liquidity-action.adapter'; + +export enum ClementineBridgeCommands { + DEPOSIT = 'deposit', // BTC -> cBTC (10 BTC fixed) + WITHDRAW = 'withdraw', // cBTC -> BTC (10 cBTC fixed) +} + +/** + * Correlation ID format for tracking operations: + * - Deposit: clementine:deposit:{depositAddress}:{btcTxId} + * - Withdraw: clementine:withdraw:{step}:{signerAddress}:{destinationAddress}:{withdrawalUtxo}:{optimisticSig}:{operatorSig} + * + * Withdrawal steps: dust_sent, scanning, signatures_generated, sent_to_bridge, waiting_optimistic, sent_to_operators + */ +const CORRELATION_PREFIX = { + DEPOSIT: 'clementine:deposit:', + WITHDRAW: 'clementine:withdraw:', +}; + +// 12 hours in milliseconds for optimistic withdrawal timeout +const OPTIMISTIC_TIMEOUT_MS = 12 * 60 * 60 * 1000; + +// Bitcoin address prefixes by network +const BTC_ADDRESS_PREFIXES: Record = { + [ClementineNetwork.BITCOIN]: ['bc1', '1', '3'], + [ClementineNetwork.TESTNET4]: ['tb1', 'bcrt1', 'm', 'n', '2'], +}; + +interface WithdrawCorrelationData { + step: string; + signerAddress: string; + destinationAddress: string; + withdrawalUtxo?: string; + optimisticSignature?: string; + operatorPaidSignature?: string; + sentToBridgeAt?: number; +} + +@Injectable() +export class ClementineBridgeAdapter extends LiquidityActionAdapter { + private readonly logger = new DfxLogger(ClementineBridgeAdapter); + + protected commands = new Map(); + + private readonly clementineClient: ClementineClient; + private readonly bitcoinClient: BitcoinClient; + private readonly citreaClient: CitreaClient; + private readonly recoveryTaprootAddress: string; + private readonly signerAddress: string; + private readonly network: ClementineNetwork; + private networkValidated = false; + + constructor( + clementineService: ClementineService, + bitcoinService: BitcoinService, + citreaService: CitreaService, + private readonly assetService: AssetService, + private readonly bitcoinFeeService: BitcoinFeeService, + ) { + super(LiquidityManagementSystem.CLEMENTINE_BRIDGE); + + const config = GetConfig().blockchain.clementine; + this.network = config.network; + this.recoveryTaprootAddress = config.recoveryTaprootAddress; + this.signerAddress = config.signerAddress; + + this.clementineClient = clementineService.getDefaultClient(); + this.bitcoinClient = bitcoinService.getDefaultClient(BitcoinNodeType.BTC_OUTPUT); + this.citreaClient = citreaService.getDefaultClient(); + + this.commands.set(ClementineBridgeCommands.DEPOSIT, this.deposit.bind(this)); + this.commands.set(ClementineBridgeCommands.WITHDRAW, this.withdraw.bind(this)); + } + + async checkCompletion(order: LiquidityManagementOrder): Promise { + const { + action: { command }, + } = order; + + if (command === ClementineBridgeCommands.DEPOSIT) { + return this.checkDepositCompletion(order); + } else if (command === ClementineBridgeCommands.WITHDRAW) { + return this.checkWithdrawCompletion(order); + } + + throw new OrderFailedException(`Unknown command: ${command}`); + } + + validateParams(_command: string, _params: Record): boolean { + // Clementine bridge doesn't require additional params + return true; + } + + //*** COMMANDS IMPLEMENTATIONS ***// + + /** + * Deposit BTC to receive cBTC on Citrea + * Note: Clementine uses a fixed bridge amount of 10 BTC + */ + private async deposit(order: LiquidityManagementOrder): Promise { + const { + pipeline: { + rule: { targetAsset: citreaAsset }, + }, + } = order; + + // Validate asset is cBTC on Citrea + if (citreaAsset.type !== AssetType.COIN || citreaAsset.blockchain !== Blockchain.CITREA) { + throw new OrderNotProcessableException('Clementine deposit only supports cBTC (native coin) on Citrea'); + } + + // Validate configuration + if (!this.recoveryTaprootAddress) { + throw new OrderNotProcessableException('Clementine recovery taproot address not configured'); + } + + // Validate network consistency on first use + this.validateNetworkConsistency(); + + // Get the corresponding Bitcoin asset + const bitcoinAsset = await this.assetService.getBtcCoin(); + + // Check BTC balance on Bitcoin node - must have at least 10 BTC (fixed bridge amount) + const btcBalance = await this.bitcoinClient.getNativeCoinBalance(); + if (btcBalance < CLEMENTINE_BRIDGE_AMOUNT_BTC) { + throw new OrderNotProcessableException( + `Not enough BTC for Clementine bridge (balance: ${btcBalance}, required: ${CLEMENTINE_BRIDGE_AMOUNT_BTC} BTC)`, + ); + } + + // Start deposit with Clementine CLI + const citreaAddress = this.citreaClient.walletAddress; + const { depositAddress } = await this.clementineClient.depositStart(this.recoveryTaprootAddress, citreaAddress); + + this.logger.verbose(`Deposit started: sending ${CLEMENTINE_BRIDGE_AMOUNT_BTC} BTC to ${depositAddress}`); + + // Update order with fixed amount + order.inputAmount = CLEMENTINE_BRIDGE_AMOUNT_BTC; + order.inputAsset = bitcoinAsset.name; + order.outputAsset = citreaAsset.name; + + // Send BTC to the deposit address + const btcTxId = await this.sendBtcToAddress(depositAddress, CLEMENTINE_BRIDGE_AMOUNT_BTC); + + // Store deposit address and txId in correlation ID for status checks + return `${CORRELATION_PREFIX.DEPOSIT}${depositAddress}:${btcTxId}`; + } + + /** + * Withdraw cBTC to receive BTC on Bitcoin + * Note: Clementine uses a fixed bridge amount of 10 BTC + * + * Withdrawal is a multi-step process: + * 1. Call withdraw start + * 2. Send 330 satoshis to signer address + * 3. Scan for UTXO + * 4. Generate signatures + * 5. Send to bridge contract (burns cBTC) + * 6. Wait for optimistic withdrawal (12 hours) + * 7. If not complete, send to operators + */ + private async withdraw(order: LiquidityManagementOrder): Promise { + const { + pipeline: { + rule: { targetAsset: bitcoinAsset }, + }, + } = order; + + // Validate asset is BTC on Bitcoin + if (bitcoinAsset.type !== AssetType.COIN || bitcoinAsset.blockchain !== Blockchain.BITCOIN) { + throw new OrderNotProcessableException('Clementine withdraw only supports BTC (native coin) on Bitcoin'); + } + + // Validate configuration + if (!this.signerAddress) { + throw new OrderNotProcessableException('Clementine signer address not configured'); + } + + // Validate network consistency on first use + this.validateNetworkConsistency(); + + // Get the corresponding Citrea cBTC asset + const citreaAsset = await this.assetService.getCitreaCoin(); + + // Check cBTC balance on Citrea - must have at least 10 cBTC (fixed bridge amount) + const cbtcBalance = await this.citreaClient.getNativeCoinBalance(); + if (cbtcBalance < CLEMENTINE_BRIDGE_AMOUNT_BTC) { + throw new OrderNotProcessableException( + `Not enough cBTC for Clementine bridge (balance: ${cbtcBalance}, required: ${CLEMENTINE_BRIDGE_AMOUNT_BTC} cBTC)`, + ); + } + + // Get destination address (our Bitcoin wallet) + const destinationAddress = this.bitcoinClient.walletAddress; + + // Step 1: Start withdrawal + await this.clementineClient.withdrawStart(this.signerAddress, destinationAddress); + this.logger.verbose(`Withdrawal started for ${this.signerAddress} -> ${destinationAddress}`); + + // Step 2: Send 330 satoshis to signer address to create withdrawal UTXO + const dustTxId = await this.sendBtcToAddress(this.signerAddress, CLEMENTINE_WITHDRAWAL_DUST_BTC); + this.logger.verbose(`Sent ${CLEMENTINE_WITHDRAWAL_DUST_BTC} BTC (330 sats) to signer: ${dustTxId}`); + + // Update order with fixed amount + order.inputAmount = CLEMENTINE_BRIDGE_AMOUNT_BTC; + order.inputAsset = citreaAsset.name; + order.outputAsset = bitcoinAsset.name; + + // Store withdrawal state in correlation ID + const correlationData: WithdrawCorrelationData = { + step: 'dust_sent', + signerAddress: this.signerAddress, + destinationAddress, + }; + + return `${CORRELATION_PREFIX.WITHDRAW}${this.encodeWithdrawCorrelation(correlationData)}`; + } + + //*** COMPLETION CHECKS ***// + + private async checkDepositCompletion(order: LiquidityManagementOrder): Promise { + const { + pipeline: { + rule: { target: asset }, + }, + } = order; + + if (!isAsset(asset)) { + throw new Error('ClementineBridgeAdapter.checkDepositCompletion(...) supports only Asset instances as an input.'); + } + + try { + // Extract deposit address and BTC txId from correlation ID + const correlationData = order.correlationId.replace(CORRELATION_PREFIX.DEPOSIT, ''); + const [depositAddress, btcTxId] = correlationData.split(':'); + + // Step 1: Verify the Bitcoin transaction is confirmed + if (btcTxId) { + const isConfirmed = await this.bitcoinClient.isTxComplete(btcTxId, 6); // Clementine requires 6+ confirmations + if (!isConfirmed) { + this.logger.verbose(`Deposit ${depositAddress}: BTC transaction not yet confirmed (need 6+)`); + return false; + } + } + + // Step 2: Check Clementine deposit status + const depositStatus = await this.clementineClient.depositStatus(depositAddress); + this.logger.verbose(`Deposit ${depositAddress}: Clementine status = ${depositStatus.status}`); + + if (depositStatus.status === 'failed') { + throw new OrderFailedException(`Clementine deposit failed: ${depositStatus.errorMessage ?? 'Unknown error'}`); + } + + if (depositStatus.status === 'completed') { + order.outputAmount = CLEMENTINE_BRIDGE_AMOUNT_BTC; + return true; + } + + return false; + } catch (e) { + throw e instanceof OrderFailedException ? e : new OrderFailedException(e.message); + } + } + + private async checkWithdrawCompletion(order: LiquidityManagementOrder): Promise { + const { + pipeline: { + rule: { target: asset }, + }, + } = order; + + if (!isAsset(asset)) { + throw new Error( + 'ClementineBridgeAdapter.checkWithdrawCompletion(...) supports only Asset instances as an input.', + ); + } + + try { + const correlationData = this.decodeWithdrawCorrelation( + order.correlationId.replace(CORRELATION_PREFIX.WITHDRAW, ''), + ); + + this.logger.verbose(`Withdrawal check: step=${correlationData.step}`); + + // Process based on current step + switch (correlationData.step) { + case 'dust_sent': + return await this.processWithdrawScanStep(order, correlationData); + + case 'scanning': + return await this.processWithdrawScanStep(order, correlationData); + + case 'signatures_generated': + return await this.processWithdrawSendStep(order, correlationData); + + case 'sent_to_bridge': + return await this.processWithdrawWaitStep(order, correlationData); + + case 'waiting_optimistic': + return await this.processWithdrawOptimisticStep(order, correlationData); + + case 'sent_to_operators': + return await this.processWithdrawFinalStep(order, correlationData); + + default: + throw new OrderFailedException(`Unknown withdrawal step: ${correlationData.step}`); + } + } catch (e) { + throw e instanceof OrderFailedException ? e : new OrderFailedException(e.message); + } + } + + private async processWithdrawScanStep( + order: LiquidityManagementOrder, + data: WithdrawCorrelationData, + ): Promise { + // Scan for withdrawal UTXO + const scanResult = await this.clementineClient.withdrawScan(data.signerAddress, data.destinationAddress); + + if (!scanResult) { + this.logger.verbose(`Withdrawal: no UTXO found yet, still scanning`); + data.step = 'scanning'; + order.correlationId = `${CORRELATION_PREFIX.WITHDRAW}${this.encodeWithdrawCorrelation(data)}`; + return false; + } + + this.logger.verbose(`Withdrawal: found UTXO ${scanResult.withdrawalUtxo}`); + data.withdrawalUtxo = scanResult.withdrawalUtxo; + + // Generate signatures + const signatures = await this.clementineClient.withdrawGenerateSignatures( + data.signerAddress, + data.destinationAddress, + data.withdrawalUtxo, + ); + + data.optimisticSignature = signatures.optimisticSignature; + data.operatorPaidSignature = signatures.operatorPaidSignature; + data.step = 'signatures_generated'; + + this.logger.verbose(`Withdrawal: signatures generated`); + order.correlationId = `${CORRELATION_PREFIX.WITHDRAW}${this.encodeWithdrawCorrelation(data)}`; + return false; + } + + private async processWithdrawSendStep( + order: LiquidityManagementOrder, + data: WithdrawCorrelationData, + ): Promise { + // Send to bridge contract (burns cBTC) + await this.clementineClient.withdrawSend( + data.signerAddress, + data.destinationAddress, + data.withdrawalUtxo, + data.optimisticSignature, + ); + + data.step = 'sent_to_bridge'; + data.sentToBridgeAt = Date.now(); + + this.logger.verbose(`Withdrawal: sent to bridge contract`); + order.correlationId = `${CORRELATION_PREFIX.WITHDRAW}${this.encodeWithdrawCorrelation(data)}`; + return false; + } + + private async processWithdrawWaitStep( + order: LiquidityManagementOrder, + data: WithdrawCorrelationData, + ): Promise { + // Check status + const status = await this.clementineClient.withdrawStatus(data.withdrawalUtxo); + this.logger.verbose(`Withdrawal: status = ${status.status}`); + + if (status.status === 'failed') { + throw new OrderFailedException(`Clementine withdrawal failed: ${status.errorMessage ?? 'Unknown error'}`); + } + + if (status.status === 'completed') { + order.outputAmount = CLEMENTINE_BRIDGE_AMOUNT_BTC; + return true; + } + + // Move to waiting_optimistic state + data.step = 'waiting_optimistic'; + order.correlationId = `${CORRELATION_PREFIX.WITHDRAW}${this.encodeWithdrawCorrelation(data)}`; + return false; + } + + private async processWithdrawOptimisticStep( + order: LiquidityManagementOrder, + data: WithdrawCorrelationData, + ): Promise { + // Check status + const status = await this.clementineClient.withdrawStatus(data.withdrawalUtxo); + this.logger.verbose(`Withdrawal: optimistic status = ${status.status}`); + + if (status.status === 'failed') { + throw new OrderFailedException(`Clementine withdrawal failed: ${status.errorMessage ?? 'Unknown error'}`); + } + + if (status.status === 'completed') { + order.outputAmount = CLEMENTINE_BRIDGE_AMOUNT_BTC; + return true; + } + + // Check if 12 hours have passed - if so, send to operators + const elapsed = Date.now() - (data.sentToBridgeAt ?? 0); + if (elapsed > OPTIMISTIC_TIMEOUT_MS) { + this.logger.verbose(`Withdrawal: 12 hours elapsed, sending to operators`); + + await this.clementineClient.withdrawSendToOperators( + data.signerAddress, + data.destinationAddress, + data.withdrawalUtxo, + data.operatorPaidSignature, + ); + + data.step = 'sent_to_operators'; + order.correlationId = `${CORRELATION_PREFIX.WITHDRAW}${this.encodeWithdrawCorrelation(data)}`; + } + + return false; + } + + private async processWithdrawFinalStep( + order: LiquidityManagementOrder, + data: WithdrawCorrelationData, + ): Promise { + // Check status after sending to operators + const status = await this.clementineClient.withdrawStatus(data.withdrawalUtxo); + this.logger.verbose(`Withdrawal: final status = ${status.status}`); + + if (status.status === 'failed') { + throw new OrderFailedException(`Clementine withdrawal failed: ${status.errorMessage ?? 'Unknown error'}`); + } + + if (status.status === 'completed') { + order.outputAmount = CLEMENTINE_BRIDGE_AMOUNT_BTC; + return true; + } + + return false; + } + + //*** HELPER METHODS ***// + + private async sendBtcToAddress(address: string, amount: number): Promise { + const feeRate = await this.bitcoinFeeService.getRecommendedFeeRate(); + const txId = await this.bitcoinClient.sendMany([{ addressTo: address, amount }], feeRate); + + if (!txId) { + throw new OrderFailedException(`Failed to send BTC to address ${address}`); + } + + return txId; + } + + private encodeWithdrawCorrelation(data: WithdrawCorrelationData): string { + return Buffer.from(JSON.stringify(data)).toString('base64'); + } + + private decodeWithdrawCorrelation(encoded: string): WithdrawCorrelationData { + return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8')); + } + + //*** NETWORK VALIDATION ***// + + /** + * Validates that all configured addresses match the expected network (mainnet/testnet). + * Throws an error on first use if there's a network mismatch to prevent fund loss. + * Only validates once, subsequent calls are skipped. + */ + private validateNetworkConsistency(): void { + if (this.networkValidated) return; + + const errors: string[] = []; + + // Validate Bitcoin wallet address + const btcWalletAddress = this.bitcoinClient.walletAddress; + if (btcWalletAddress && !this.isAddressForNetwork(btcWalletAddress, this.network)) { + errors.push(`Bitcoin wallet address '${btcWalletAddress}' does not match Clementine network '${this.network}'`); + } + + // Validate recovery taproot address (dep-prefixed, but underlying address should match network) + if (this.recoveryTaprootAddress) { + const underlyingRecoveryAddress = this.recoveryTaprootAddress.replace(/^dep/, ''); + if (!this.isAddressForNetwork(underlyingRecoveryAddress, this.network)) { + errors.push( + `Recovery taproot address '${this.recoveryTaprootAddress}' does not match Clementine network '${this.network}'`, + ); + } + } + + // Validate signer address (wit-prefixed, but underlying address should match network) + if (this.signerAddress) { + const underlyingSignerAddress = this.signerAddress.replace(/^wit/, ''); + if (!this.isAddressForNetwork(underlyingSignerAddress, this.network)) { + errors.push(`Signer address '${this.signerAddress}' does not match Clementine network '${this.network}'`); + } + } + + if (errors.length > 0) { + const errorMsg = `Clementine network configuration mismatch:\n${errors.join('\n')}`; + this.logger.error(errorMsg); + throw new OrderNotProcessableException(errorMsg); + } + + this.networkValidated = true; + this.logger.info(`Clementine network validation passed for '${this.network}'`); + } + + /** + * Checks if a Bitcoin address matches the expected network based on its prefix. + */ + private isAddressForNetwork(address: string, network: ClementineNetwork): boolean { + const prefixes = BTC_ADDRESS_PREFIXES[network]; + return prefixes.some((prefix) => address.startsWith(prefix)); + } +} diff --git a/src/subdomains/core/liquidity-management/enums/index.ts b/src/subdomains/core/liquidity-management/enums/index.ts index 82befa642f..5bd5c9cf74 100644 --- a/src/subdomains/core/liquidity-management/enums/index.ts +++ b/src/subdomains/core/liquidity-management/enums/index.ts @@ -16,6 +16,7 @@ export enum LiquidityManagementSystem { POLYGON_L2_BRIDGE = 'PolygonL2Bridge', BASE_L2_BRIDGE = 'BaseL2Bridge', LAYERZERO_BRIDGE = 'LayerZeroBridge', + CLEMENTINE_BRIDGE = 'ClementineBridge', LIQUIDITY_PIPELINE = 'LiquidityPipeline', FRANKENCOIN = 'Frankencoin', DEURO = 'dEURO', @@ -68,4 +69,5 @@ export const LiquidityManagementBridges = [ LiquidityManagementSystem.ARBITRUM_L2_BRIDGE, LiquidityManagementSystem.OPTIMISM_L2_BRIDGE, LiquidityManagementSystem.LAYERZERO_BRIDGE, + LiquidityManagementSystem.CLEMENTINE_BRIDGE, ]; diff --git a/src/subdomains/core/liquidity-management/factories/liquidity-action-integration.factory.ts b/src/subdomains/core/liquidity-management/factories/liquidity-action-integration.factory.ts index 403d43b1ba..bab3922616 100644 --- a/src/subdomains/core/liquidity-management/factories/liquidity-action-integration.factory.ts +++ b/src/subdomains/core/liquidity-management/factories/liquidity-action-integration.factory.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { ArbitrumL2BridgeAdapter } from '../adapters/actions/arbitrum-l2-bridge.adapter'; import { BaseL2BridgeAdapter } from '../adapters/actions/base-l2-bridge.adapter'; import { BinanceAdapter } from '../adapters/actions/binance.adapter'; +import { ClementineBridgeAdapter } from '../adapters/actions/clementine-bridge.adapter'; import { DEuroAdapter } from '../adapters/actions/deuro.adapter'; import { DfxDexAdapter } from '../adapters/actions/dfx-dex.adapter'; import { FrankencoinAdapter } from '../adapters/actions/frankencoin.adapter'; @@ -29,6 +30,7 @@ export class LiquidityActionIntegrationFactory { readonly polygonL2BridgeAdapter: PolygonL2BridgeAdapter, readonly baseL2BridgeAdapter: BaseL2BridgeAdapter, readonly layerZeroBridgeAdapter: LayerZeroBridgeAdapter, + readonly clementineBridgeAdapter: ClementineBridgeAdapter, readonly krakenAdapter: KrakenAdapter, readonly binanceAdapter: BinanceAdapter, readonly mexcAdapter: MexcAdapter, @@ -45,6 +47,7 @@ export class LiquidityActionIntegrationFactory { this.adapters.set(LiquidityManagementSystem.POLYGON_L2_BRIDGE, polygonL2BridgeAdapter); this.adapters.set(LiquidityManagementSystem.BASE_L2_BRIDGE, baseL2BridgeAdapter); this.adapters.set(LiquidityManagementSystem.LAYERZERO_BRIDGE, layerZeroBridgeAdapter); + this.adapters.set(LiquidityManagementSystem.CLEMENTINE_BRIDGE, clementineBridgeAdapter); this.adapters.set(LiquidityManagementSystem.KRAKEN, krakenAdapter); this.adapters.set(LiquidityManagementSystem.BINANCE, binanceAdapter); this.adapters.set(LiquidityManagementSystem.MEXC, mexcAdapter); diff --git a/src/subdomains/core/liquidity-management/liquidity-management.module.ts b/src/subdomains/core/liquidity-management/liquidity-management.module.ts index eddec14c15..3773d7e876 100644 --- a/src/subdomains/core/liquidity-management/liquidity-management.module.ts +++ b/src/subdomains/core/liquidity-management/liquidity-management.module.ts @@ -13,6 +13,7 @@ import { PricingModule } from 'src/subdomains/supporting/pricing/pricing.module' import { ArbitrumL2BridgeAdapter } from './adapters/actions/arbitrum-l2-bridge.adapter'; import { BaseL2BridgeAdapter } from './adapters/actions/base-l2-bridge.adapter'; import { BinanceAdapter } from './adapters/actions/binance.adapter'; +import { ClementineBridgeAdapter } from './adapters/actions/clementine-bridge.adapter'; import { LayerZeroBridgeAdapter } from './adapters/actions/layerzero-bridge.adapter'; import { DEuroAdapter } from './adapters/actions/deuro.adapter'; import { DfxDexAdapter } from './adapters/actions/dfx-dex.adapter'; @@ -98,6 +99,7 @@ import { LiquidityManagementService } from './services/liquidity-management.serv PolygonL2BridgeAdapter, BaseL2BridgeAdapter, LayerZeroBridgeAdapter, + ClementineBridgeAdapter, BinanceAdapter, MexcAdapter, ScryptAdapter, From ba8bfac7333b749bc9d42473aae9c4a54bfdfe82 Mon Sep 17 00:00:00 2001 From: bernd2022 <104787072+bernd2022@users.noreply.github.com> Date: Wed, 4 Feb 2026 07:36:26 +0100 Subject: [PATCH 02/14] Config Citrea Mainnet (#3025) * Config Citrea Mainnet * Config for JuiceDollar mainnet * feat: custom domain creation script * feat: jd mainnet monitoring container app setup * fix app.juicedollar.com -> bapp.juicedollar.com * fix: fixed JUSD ponder config * feat: added frontdoor dev container with mainnet * changed cmdef to cmef * feat: adapted script for Azure DNS --------- Co-authored-by: David May --- .../bicep/container-apps/apps/deploy.sh | 6 +- .../apps/parameters/dev-jdma.json | 111 ++++++++ .../apps/parameters/dev-jdmd.json | 82 ++++++ .../apps/parameters/dev-jdmm.json | 98 +++++++ .../apps/parameters/dev-jdmp.json | 71 +++++ .../apps/parameters/dev-jdta.json | 40 ++- .../apps/parameters/dev-jdtd.json | 20 +- .../apps/parameters/dev-jdtp.json | 18 +- .../apps/parameters/prd-jdma.json | 111 ++++++++ .../apps/parameters/prd-jdmd.json | 82 ++++++ .../apps/parameters/prd-jdmm.json | 98 +++++++ .../apps/parameters/prd-jdmp.json | 71 +++++ .../apps/parameters/prd-jdta.json | 40 ++- .../apps/parameters/prd-jdtd.json | 20 +- .../apps/parameters/prd-jdtp.json | 18 +- .../container-apps/manualFrontdoorSetup.sh | 6 +- .../container-apps/setup-custom-domains.sh | 249 ++++++++++++++++++ .../virtual-machines/front-door/deploy.sh | 5 +- .../{prd-cteb.json => dev-cmef.json} | 2 +- .../{prd-ctef.json => prd-cmeb.json} | 2 +- .../front-door/parameters/prd-cmef.json | 12 + .../parameters/{prd-ctn.json => prd-cmn.json} | 2 +- .../virtual-machines/load-balancer/deploy.sh | 3 +- .../config/bitcoind/bitcoin-mainnet.conf | 14 + ...cker-compose-blockscout-citrea-mainnet.yml | 88 +++++++ .../docker/docker-compose-citrea-mainnet.yml | 73 +++++ .../docker/docker-compose-nginx-mainnet.yml | 18 ++ ...x.yml => docker-compose-nginx-testnet.yml} | 0 .../config/nginx/default-mainnet.conf | 37 +++ .../{default.conf => default-testnet.conf} | 0 30 files changed, 1344 insertions(+), 53 deletions(-) create mode 100644 infrastructure/bicep/container-apps/apps/parameters/dev-jdma.json create mode 100644 infrastructure/bicep/container-apps/apps/parameters/dev-jdmd.json create mode 100644 infrastructure/bicep/container-apps/apps/parameters/dev-jdmm.json create mode 100644 infrastructure/bicep/container-apps/apps/parameters/dev-jdmp.json create mode 100644 infrastructure/bicep/container-apps/apps/parameters/prd-jdma.json create mode 100644 infrastructure/bicep/container-apps/apps/parameters/prd-jdmd.json create mode 100644 infrastructure/bicep/container-apps/apps/parameters/prd-jdmm.json create mode 100644 infrastructure/bicep/container-apps/apps/parameters/prd-jdmp.json create mode 100755 infrastructure/bicep/container-apps/setup-custom-domains.sh mode change 100644 => 100755 infrastructure/bicep/virtual-machines/front-door/deploy.sh rename infrastructure/bicep/virtual-machines/front-door/parameters/{prd-cteb.json => dev-cmef.json} (90%) rename infrastructure/bicep/virtual-machines/front-door/parameters/{prd-ctef.json => prd-cmeb.json} (90%) create mode 100644 infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmef.json rename infrastructure/bicep/virtual-machines/front-door/parameters/{prd-ctn.json => prd-cmn.json} (91%) create mode 100644 infrastructure/citrea/citreascan/config/bitcoind/bitcoin-mainnet.conf create mode 100644 infrastructure/citrea/citreascan/config/docker/docker-compose-blockscout-citrea-mainnet.yml create mode 100644 infrastructure/citrea/citreascan/config/docker/docker-compose-citrea-mainnet.yml create mode 100644 infrastructure/citrea/citreascan/config/docker/docker-compose-nginx-mainnet.yml rename infrastructure/citrea/citreascan/config/docker/{docker-compose-nginx.yml => docker-compose-nginx-testnet.yml} (100%) create mode 100644 infrastructure/citrea/citreascan/config/nginx/default-mainnet.conf rename infrastructure/citrea/citreascan/config/nginx/{default.conf => default-testnet.conf} (100%) diff --git a/infrastructure/bicep/container-apps/apps/deploy.sh b/infrastructure/bicep/container-apps/apps/deploy.sh index 6bd2787833..464d0e61e7 100755 --- a/infrastructure/bicep/container-apps/apps/deploy.sh +++ b/infrastructure/bicep/container-apps/apps/deploy.sh @@ -20,8 +20,12 @@ environmentOptions=("loc" "dev" "prd") # "jdta": JuiceDollar Testnet API # "jdtd": JuiceDollar Testnet dApp # "jdtm": JuiceDollar Testnet Monitoring +# "jdmp": JuiceDollar Mainnet Ponder +# "jdma": JuiceDollar Mainnet API +# "jdmd": JuiceDollar Mainnet dApp +# "jdmm": JuiceDollar Mainnet Monitoring # "rup": realUnit Ponder -appNameOptions=("fcp" "dep" "dea" "ded" "dem" "jsp" "jsw" "n8n" "jdtp" "jdta" "jdtd" "jdtm" "rup") +appNameOptions=("fcp" "dep" "dea" "ded" "dem" "jsp" "jsw" "n8n" "jdtp" "jdta" "jdtd" "jdtm" "jdmp" "jdma" "jdmd" "jdmm" "rup") # --- FUNCTIONS --- # selectOption() { diff --git a/infrastructure/bicep/container-apps/apps/parameters/dev-jdma.json b/infrastructure/bicep/container-apps/apps/parameters/dev-jdma.json new file mode 100644 index 0000000000..5ab1a2f67a --- /dev/null +++ b/infrastructure/bicep/container-apps/apps/parameters/dev-jdma.json @@ -0,0 +1,111 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "fileShareQuota": { + "value": 1 + }, + "containerImage": { + "value": "juicedollar-api:beta" + }, + "containerVolumeMounts": { + "value": [ + { + "volumeName": "volume", + "mountPath": "/app/.api" + } + ] + }, + "containerCPU": { + "value": "0.5" + }, + "containerMemory": { + "value": "1Gi" + }, + "containerMinReplicas": { + "value": 1 + }, + "containerMaxReplicas": { + "value": 1 + }, + "containerIngressTargetPort": { + "value": 3000 + }, + "containerIngressAdditionalPorts": { + "value": [] + }, + "containerProbes": { + "value": [] + }, + "containerEnv": { + "value": [ + { + "name": "API_ENVIRONMENT", + "value": "dev" + }, + { + "name": "LOG_LEVEL", + "value": "info" + }, + { + "name": "PORT", + "value": "3000" + }, + { + "name": "CONFIG_CHAIN", + "value": "mainnet" + }, + { + "name": "CONFIG_APP_URL", + "value": "https://dev.bapp.juicedollar.com" + }, + { + "name": "CONFIG_INDEXER_URL", + "value": "https://dev.ponder.juicedollar.com" + }, + { + "name": "COINGECKO_API_KEY", + "value": "[API-KEY]" + }, + { + "name": "RPC_URL_MAINNET", + "value": "https://rpc.juiceswap.com" + }, + { + "name": "TELEGRAM_BOT_TOKEN", + "value": "[API-KEY]" + }, + { + "name": "TELEGRAM_GROUPS_JSON", + "value": "/app/.api/telegram.groups.json" + }, + { + "name": "TWITTER_CLIENT_APP_KEY", + "value": "[TWITTER_CLIENT_APP_KEY]" + }, + { + "name": "TWITTER_CLIENT_APP_SECRET", + "value": "[TWITTER_CLIENT_APP_SECRET]" + }, + { + "name": "TWITTER_ACCESS_TOKEN", + "value": "[TWITTER_ACCESS_TOKEN]" + }, + { + "name": "TWITTER_ACCESS_SECRET", + "value": "[TWITTER_ACCESS_SECRET]" + }, + { + "name": "TWITTER_IMAGES_DIR", + "value": "/app/.api/images/twitter" + } + ] + }, + "containerCommand": { + "value": [] + }, + "containerArgs": { + "value": [] + } + } +} diff --git a/infrastructure/bicep/container-apps/apps/parameters/dev-jdmd.json b/infrastructure/bicep/container-apps/apps/parameters/dev-jdmd.json new file mode 100644 index 0000000000..451982393f --- /dev/null +++ b/infrastructure/bicep/container-apps/apps/parameters/dev-jdmd.json @@ -0,0 +1,82 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "fileShareQuota": { + "value": 1 + }, + "containerImage": { + "value": "juicedollar-dapp:beta" + }, + "containerVolumeMounts": { + "value": [] + }, + "containerCPU": { + "value": "0.5" + }, + "containerMemory": { + "value": "1Gi" + }, + "containerMinReplicas": { + "value": 1 + }, + "containerMaxReplicas": { + "value": 1 + }, + "containerIngressTargetPort": { + "value": 3000 + }, + "containerIngressAdditionalPorts": { + "value": [] + }, + "containerProbes": { + "value": [] + }, + "containerEnv": { + "value": [ + { + "name": "NEXT_PUBLIC_LANDINGPAGE_URL", + "value": "https://juicedollar.com" + }, + { + "name": "NEXT_PUBLIC_APP_URL", + "value": "https://dev.bapp.juicedollar.com" + }, + { + "name": "NEXT_PUBLIC_API_URL", + "value": "https://dev.api.juicedollar.com" + }, + { + "name": "NEXT_PUBLIC_PONDER_URL", + "value": "https://dev.ponder.juicedollar.com" + }, + { + "name": "NEXT_PUBLIC_WAGMI_ID", + "value": "[API-KEY]" + }, + { + "name": "NEXT_PUBLIC_CHAIN_NAME", + "value": "mainnet" + }, + { + "name": "NEXT_PUBLIC_RPC_URL_MAINNET", + "value": "https://rpc.juiceswap.com" + }, + { + "name": "NEXT_PUBLIC_RPC_URL_TESTNET", + "value": "https://rpc.testnet.juiceswap.com" + }, + { + "name": "NEXT_PUBLIC_PONDER_FALLBACK_URL", + "value": "https://dev.ponder.juicedollar.com" + } + ] + }, + "containerCommand": { + "value": [] + }, + "containerArgs": { + "value": [] + } + } +} diff --git a/infrastructure/bicep/container-apps/apps/parameters/dev-jdmm.json b/infrastructure/bicep/container-apps/apps/parameters/dev-jdmm.json new file mode 100644 index 0000000000..f257c57906 --- /dev/null +++ b/infrastructure/bicep/container-apps/apps/parameters/dev-jdmm.json @@ -0,0 +1,98 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "fileShareQuota": { + "value": 100 + }, + "containerImage": { + "value": "dfxswiss/deuro-monitoring:beta" + }, + "containerVolumeMounts": { + "value": [] + }, + "containerCPU": { + "value": "0.25" + }, + "containerMemory": { + "value": "0.5Gi" + }, + "containerMinReplicas": { + "value": 1 + }, + "containerMaxReplicas": { + "value": 1 + }, + "containerIngressTargetPort": { + "value": 3000 + }, + "containerIngressAdditionalPorts": { + "value": [] + }, + "containerProbes": { + "value": [] + }, + "containerEnv": { + "value": [ + { + "name": "PORT", + "value": "3000" + }, + { + "name": "ALLOWED_ORIGINS", + "value": "https://dev.monitoring.testnet.juicedollar.com,https://monitoring.testnet.juicedollar.com" + }, + { + "name": "DATABASE_URL", + "value": "[DATABASE_URL]" + }, + { + "name": "RPC_URL", + "value": "https://eth-mainnet.g.alchemy.com/v2/[API-KEY]" + }, + { + "name": "DEPLOYMENT_BLOCK", + "value": "22088283" + }, + { + "name": "BLOCKCHAIN_ID", + "value": "1" + }, + { + "name": "PRICE_CACHE_TTL_MS", + "value": "120000" + }, + { + "name": "PG_MAX_CLIENTS", + "value": "10" + }, + { + "name": "MAX_BLOCKS_PER_BATCH", + "value": "1000" + }, + { + "name": "TELEGRAM_BOT_TOKEN", + "value": "[TELEGRAM_BOT_TOKEN]" + }, + { + "name": "TELEGRAM_CHAT_ID", + "value": "[TELEGRAM_CHAT_ID]" + }, + { + "name": "TELEGRAM_ALERTS_ENABLED", + "value": "true" + }, + { + "name": "ALERT_TIMEFRAME_HOURS", + "value": "12" + } + ] + }, + "containerCommand": { + "value": [] + }, + "containerArgs": { + "value": [] + } + } +} diff --git a/infrastructure/bicep/container-apps/apps/parameters/dev-jdmp.json b/infrastructure/bicep/container-apps/apps/parameters/dev-jdmp.json new file mode 100644 index 0000000000..2238594272 --- /dev/null +++ b/infrastructure/bicep/container-apps/apps/parameters/dev-jdmp.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "fileShareQuota": { + "value": 1 + }, + "containerImage": { + "value": "juicedollar-ponder:beta" + }, + "containerVolumeMounts": { + "value": [ + { + "volumeName": "volume", + "mountPath": "/app/.ponder" + } + ] + }, + "containerCPU": { + "value": "0.5" + }, + "containerMemory": { + "value": "1Gi" + }, + "containerMinReplicas": { + "value": 1 + }, + "containerMaxReplicas": { + "value": 1 + }, + "containerIngressTargetPort": { + "value": 3000 + }, + "containerIngressAdditionalPorts": { + "value": [] + }, + "containerProbes": { + "value": [] + }, + "containerEnv": { + "value": [ + { + "name": "PORT", + "value": "3000" + }, + { + "name": "PONDER_PROFILE", + "value": "mainnet" + }, + { + "name": "PONDER_SCHEMA_VERSION", + "value": "6" + }, + { + "name": "DATABASE_URL", + "value": "[DATABASE_URL]" + }, + { + "name": "RPC_URL_MAINNET", + "value": "https://rpc.juiceswap.com" + } + ] + }, + "containerCommand": { + "value": [] + }, + "containerArgs": { + "value": [] + } + } +} diff --git a/infrastructure/bicep/container-apps/apps/parameters/dev-jdta.json b/infrastructure/bicep/container-apps/apps/parameters/dev-jdta.json index 7cd76ffdf7..de41e7f068 100644 --- a/infrastructure/bicep/container-apps/apps/parameters/dev-jdta.json +++ b/infrastructure/bicep/container-apps/apps/parameters/dev-jdta.json @@ -6,7 +6,7 @@ "value": 1 }, "containerImage": { - "value": "dfxswiss/deuro-api:beta" + "value": "juicedollar-api:beta" }, "containerVolumeMounts": { "value": [ @@ -39,13 +39,21 @@ }, "containerEnv": { "value": [ + { + "name": "API_ENVIRONMENT", + "value": "dev" + }, + { + "name": "LOG_LEVEL", + "value": "info" + }, { "name": "PORT", "value": "3000" }, { "name": "CONFIG_CHAIN", - "value": "ethereum" + "value": "testnet" }, { "name": "CONFIG_APP_URL", @@ -61,11 +69,7 @@ }, { "name": "RPC_URL_MAINNET", - "value": "https://eth-mainnet.g.alchemy.com/v2/[API-KEY]" - }, - { - "name": "RPC_URL_POLYGON", - "value": "https://polygon-mainnet.g.alchemy.com/v2/[API-KEY]" + "value": "https://rpc.testnet.juiceswap.com/" }, { "name": "TELEGRAM_BOT_TOKEN", @@ -74,6 +78,26 @@ { "name": "TELEGRAM_GROUPS_JSON", "value": "/app/.api/telegram.groups.json" + }, + { + "name": "TWITTER_CLIENT_APP_KEY", + "value": "[TWITTER_CLIENT_APP_KEY]" + }, + { + "name": "TWITTER_CLIENT_APP_SECRET", + "value": "[TWITTER_CLIENT_APP_SECRET]" + }, + { + "name": "TWITTER_ACCESS_TOKEN", + "value": "[TWITTER_ACCESS_TOKEN]" + }, + { + "name": "TWITTER_ACCESS_SECRET", + "value": "[TWITTER_ACCESS_SECRET]" + }, + { + "name": "TWITTER_IMAGES_DIR", + "value": "/app/.api/images/twitter" } ] }, @@ -84,4 +108,4 @@ "value": [] } } -} +} \ No newline at end of file diff --git a/infrastructure/bicep/container-apps/apps/parameters/dev-jdtd.json b/infrastructure/bicep/container-apps/apps/parameters/dev-jdtd.json index 1dce360eba..a3e863c813 100644 --- a/infrastructure/bicep/container-apps/apps/parameters/dev-jdtd.json +++ b/infrastructure/bicep/container-apps/apps/parameters/dev-jdtd.json @@ -6,7 +6,7 @@ "value": 1 }, "containerImage": { - "value": "dfxswiss/deuro-dapp:beta" + "value": "juicedollar-dapp:beta" }, "containerVolumeMounts": { "value": [] @@ -54,21 +54,21 @@ "name": "NEXT_PUBLIC_WAGMI_ID", "value": "[API-KEY]" }, - { - "name": "NEXT_PUBLIC_ALCHEMY_API_KEY", - "value": "[API-KEY]" - }, { "name": "NEXT_PUBLIC_CHAIN_NAME", - "value": "mainnet" + "value": "testnet" }, { "name": "NEXT_PUBLIC_RPC_URL_MAINNET", - "value": "https://eth-mainnet.g.alchemy.com/v2" + "value": "https://rpc.testnet.juiceswap.com" }, { - "name": "NEXT_PUBLIC_RPC_URL_POLYGON", - "value": "https://polygon-mainnet.g.alchemy.com/v2" + "name": "NEXT_PUBLIC_RPC_URL_TESTNET", + "value": "https://rpc.testnet.juiceswap.com" + }, + { + "name": "NEXT_PUBLIC_PONDER_FALLBACK_URL", + "value": "https://dev.ponder.testnet.juicedollar.com" } ] }, @@ -79,4 +79,4 @@ "value": [] } } -} +} \ No newline at end of file diff --git a/infrastructure/bicep/container-apps/apps/parameters/dev-jdtp.json b/infrastructure/bicep/container-apps/apps/parameters/dev-jdtp.json index 970c078fba..0cbe9a39e2 100644 --- a/infrastructure/bicep/container-apps/apps/parameters/dev-jdtp.json +++ b/infrastructure/bicep/container-apps/apps/parameters/dev-jdtp.json @@ -6,7 +6,7 @@ "value": 1 }, "containerImage": { - "value": "dfxswiss/deuro-ponder:beta" + "value": "juicedollar-ponder:beta" }, "containerVolumeMounts": { "value": [ @@ -45,11 +45,19 @@ }, { "name": "PONDER_PROFILE", - "value": "mainnet" + "value": "testnet" }, { - "name": "RPC_URL_MAINNET", - "value": "https://eth-mainnet.g.alchemy.com/v2/[API-KEY]" + "name": "PONDER_SCHEMA_VERSION", + "value": "6" + }, + { + "name": "DATABASE_URL", + "value": "[DATABASE_URL]" + }, + { + "name": "RPC_URL_TESTNET", + "value": "http://10.0.1.6:8085" } ] }, @@ -60,4 +68,4 @@ "value": [] } } -} \ No newline at end of file +} diff --git a/infrastructure/bicep/container-apps/apps/parameters/prd-jdma.json b/infrastructure/bicep/container-apps/apps/parameters/prd-jdma.json new file mode 100644 index 0000000000..452a40f01a --- /dev/null +++ b/infrastructure/bicep/container-apps/apps/parameters/prd-jdma.json @@ -0,0 +1,111 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "fileShareQuota": { + "value": 1 + }, + "containerImage": { + "value": "juicedollar-api:latest" + }, + "containerVolumeMounts": { + "value": [ + { + "volumeName": "volume", + "mountPath": "/app/.api" + } + ] + }, + "containerCPU": { + "value": "0.5" + }, + "containerMemory": { + "value": "1Gi" + }, + "containerMinReplicas": { + "value": 1 + }, + "containerMaxReplicas": { + "value": 1 + }, + "containerIngressTargetPort": { + "value": 3000 + }, + "containerIngressAdditionalPorts": { + "value": [] + }, + "containerProbes": { + "value": [] + }, + "containerEnv": { + "value": [ + { + "name": "API_ENVIRONMENT", + "value": "prd" + }, + { + "name": "LOG_LEVEL", + "value": "info" + }, + { + "name": "PORT", + "value": "3000" + }, + { + "name": "CONFIG_CHAIN", + "value": "mainnet" + }, + { + "name": "CONFIG_APP_URL", + "value": "https://bapp.juicedollar.com" + }, + { + "name": "CONFIG_INDEXER_URL", + "value": "https://ponder.juicedollar.com" + }, + { + "name": "COINGECKO_API_KEY", + "value": "[API-KEY]" + }, + { + "name": "RPC_URL_MAINNET", + "value": "https://rpc.juiceswap.com" + }, + { + "name": "TELEGRAM_BOT_TOKEN", + "value": "[API-KEY]" + }, + { + "name": "TELEGRAM_GROUPS_JSON", + "value": "/app/.api/telegram.groups.json" + }, + { + "name": "TWITTER_CLIENT_APP_KEY", + "value": "[TWITTER_CLIENT_APP_KEY]" + }, + { + "name": "TWITTER_CLIENT_APP_SECRET", + "value": "[TWITTER_CLIENT_APP_SECRET]" + }, + { + "name": "TWITTER_ACCESS_TOKEN", + "value": "[TWITTER_ACCESS_TOKEN]" + }, + { + "name": "TWITTER_ACCESS_SECRET", + "value": "[TWITTER_ACCESS_SECRET]" + }, + { + "name": "TWITTER_IMAGES_DIR", + "value": "/app/.api/images/twitter" + } + ] + }, + "containerCommand": { + "value": [] + }, + "containerArgs": { + "value": [] + } + } +} diff --git a/infrastructure/bicep/container-apps/apps/parameters/prd-jdmd.json b/infrastructure/bicep/container-apps/apps/parameters/prd-jdmd.json new file mode 100644 index 0000000000..83bac31342 --- /dev/null +++ b/infrastructure/bicep/container-apps/apps/parameters/prd-jdmd.json @@ -0,0 +1,82 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "fileShareQuota": { + "value": 1 + }, + "containerImage": { + "value": "juicedollar-dapp:latest" + }, + "containerVolumeMounts": { + "value": [] + }, + "containerCPU": { + "value": "0.5" + }, + "containerMemory": { + "value": "1Gi" + }, + "containerMinReplicas": { + "value": 1 + }, + "containerMaxReplicas": { + "value": 1 + }, + "containerIngressTargetPort": { + "value": 3000 + }, + "containerIngressAdditionalPorts": { + "value": [] + }, + "containerProbes": { + "value": [] + }, + "containerEnv": { + "value": [ + { + "name": "NEXT_PUBLIC_LANDINGPAGE_URL", + "value": "https://juicedollar.com" + }, + { + "name": "NEXT_PUBLIC_APP_URL", + "value": "https://bapp.juicedollar.com" + }, + { + "name": "NEXT_PUBLIC_API_URL", + "value": "https://api.juicedollar.com" + }, + { + "name": "NEXT_PUBLIC_PONDER_URL", + "value": "https://ponder.juicedollar.com" + }, + { + "name": "NEXT_PUBLIC_WAGMI_ID", + "value": "[API-KEY]" + }, + { + "name": "NEXT_PUBLIC_CHAIN_NAME", + "value": "mainnet" + }, + { + "name": "NEXT_PUBLIC_RPC_URL_MAINNET", + "value": "https://rpc.juiceswap.com" + }, + { + "name": "NEXT_PUBLIC_RPC_URL_TESTNET", + "value": "https://rpc.testnet.juiceswap.com" + }, + { + "name": "NEXT_PUBLIC_PONDER_FALLBACK_URL", + "value": "https://dev.ponder.juicedollar.com/" + } + ] + }, + "containerCommand": { + "value": [] + }, + "containerArgs": { + "value": [] + } + } +} diff --git a/infrastructure/bicep/container-apps/apps/parameters/prd-jdmm.json b/infrastructure/bicep/container-apps/apps/parameters/prd-jdmm.json new file mode 100644 index 0000000000..f257c57906 --- /dev/null +++ b/infrastructure/bicep/container-apps/apps/parameters/prd-jdmm.json @@ -0,0 +1,98 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "fileShareQuota": { + "value": 100 + }, + "containerImage": { + "value": "dfxswiss/deuro-monitoring:beta" + }, + "containerVolumeMounts": { + "value": [] + }, + "containerCPU": { + "value": "0.25" + }, + "containerMemory": { + "value": "0.5Gi" + }, + "containerMinReplicas": { + "value": 1 + }, + "containerMaxReplicas": { + "value": 1 + }, + "containerIngressTargetPort": { + "value": 3000 + }, + "containerIngressAdditionalPorts": { + "value": [] + }, + "containerProbes": { + "value": [] + }, + "containerEnv": { + "value": [ + { + "name": "PORT", + "value": "3000" + }, + { + "name": "ALLOWED_ORIGINS", + "value": "https://dev.monitoring.testnet.juicedollar.com,https://monitoring.testnet.juicedollar.com" + }, + { + "name": "DATABASE_URL", + "value": "[DATABASE_URL]" + }, + { + "name": "RPC_URL", + "value": "https://eth-mainnet.g.alchemy.com/v2/[API-KEY]" + }, + { + "name": "DEPLOYMENT_BLOCK", + "value": "22088283" + }, + { + "name": "BLOCKCHAIN_ID", + "value": "1" + }, + { + "name": "PRICE_CACHE_TTL_MS", + "value": "120000" + }, + { + "name": "PG_MAX_CLIENTS", + "value": "10" + }, + { + "name": "MAX_BLOCKS_PER_BATCH", + "value": "1000" + }, + { + "name": "TELEGRAM_BOT_TOKEN", + "value": "[TELEGRAM_BOT_TOKEN]" + }, + { + "name": "TELEGRAM_CHAT_ID", + "value": "[TELEGRAM_CHAT_ID]" + }, + { + "name": "TELEGRAM_ALERTS_ENABLED", + "value": "true" + }, + { + "name": "ALERT_TIMEFRAME_HOURS", + "value": "12" + } + ] + }, + "containerCommand": { + "value": [] + }, + "containerArgs": { + "value": [] + } + } +} diff --git a/infrastructure/bicep/container-apps/apps/parameters/prd-jdmp.json b/infrastructure/bicep/container-apps/apps/parameters/prd-jdmp.json new file mode 100644 index 0000000000..f7ce0d2015 --- /dev/null +++ b/infrastructure/bicep/container-apps/apps/parameters/prd-jdmp.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "fileShareQuota": { + "value": 1 + }, + "containerImage": { + "value": "juicedollar-ponder:latest" + }, + "containerVolumeMounts": { + "value": [ + { + "volumeName": "volume", + "mountPath": "/app/.ponder" + } + ] + }, + "containerCPU": { + "value": "0.5" + }, + "containerMemory": { + "value": "1Gi" + }, + "containerMinReplicas": { + "value": 1 + }, + "containerMaxReplicas": { + "value": 1 + }, + "containerIngressTargetPort": { + "value": 3000 + }, + "containerIngressAdditionalPorts": { + "value": [] + }, + "containerProbes": { + "value": [] + }, + "containerEnv": { + "value": [ + { + "name": "PORT", + "value": "3000" + }, + { + "name": "PONDER_PROFILE", + "value": "mainnet" + }, + { + "name": "PONDER_SCHEMA_VERSION", + "value": "6" + }, + { + "name": "DATABASE_URL", + "value": "[DATABASE_URL]" + }, + { + "name": "RPC_URL_MAINNET", + "value": "http://10.0.1.6:8085" + } + ] + }, + "containerCommand": { + "value": [] + }, + "containerArgs": { + "value": [] + } + } +} \ No newline at end of file diff --git a/infrastructure/bicep/container-apps/apps/parameters/prd-jdta.json b/infrastructure/bicep/container-apps/apps/parameters/prd-jdta.json index 769e768e48..ae5ccb5495 100644 --- a/infrastructure/bicep/container-apps/apps/parameters/prd-jdta.json +++ b/infrastructure/bicep/container-apps/apps/parameters/prd-jdta.json @@ -6,7 +6,7 @@ "value": 1 }, "containerImage": { - "value": "dfxswiss/deuro-api:beta" + "value": "juicedollar-api:latest" }, "containerVolumeMounts": { "value": [ @@ -39,13 +39,21 @@ }, "containerEnv": { "value": [ + { + "name": "API_ENVIRONMENT", + "value": "dev" + }, + { + "name": "LOG_LEVEL", + "value": "info" + }, { "name": "PORT", "value": "3000" }, { "name": "CONFIG_CHAIN", - "value": "ethereum" + "value": "testnet" }, { "name": "CONFIG_APP_URL", @@ -61,11 +69,7 @@ }, { "name": "RPC_URL_MAINNET", - "value": "https://eth-mainnet.g.alchemy.com/v2/[API-KEY]" - }, - { - "name": "RPC_URL_POLYGON", - "value": "https://polygon-mainnet.g.alchemy.com/v2/[API-KEY]" + "value": "https://rpc.testnet.juiceswap.com" }, { "name": "TELEGRAM_BOT_TOKEN", @@ -74,6 +78,26 @@ { "name": "TELEGRAM_GROUPS_JSON", "value": "/app/.api/telegram.groups.json" + }, + { + "name": "TWITTER_CLIENT_APP_KEY", + "value": "[TWITTER_CLIENT_APP_KEY]" + }, + { + "name": "TWITTER_CLIENT_APP_SECRET", + "value": "[TWITTER_CLIENT_APP_SECRET]" + }, + { + "name": "TWITTER_ACCESS_TOKEN", + "value": "[TWITTER_ACCESS_TOKEN]" + }, + { + "name": "TWITTER_ACCESS_SECRET", + "value": "[TWITTER_ACCESS_SECRET]" + }, + { + "name": "TWITTER_IMAGES_DIR", + "value": "/app/.api/images/twitter" } ] }, @@ -84,4 +108,4 @@ "value": [] } } -} +} \ No newline at end of file diff --git a/infrastructure/bicep/container-apps/apps/parameters/prd-jdtd.json b/infrastructure/bicep/container-apps/apps/parameters/prd-jdtd.json index bfc5dbbc5d..2712914bc5 100644 --- a/infrastructure/bicep/container-apps/apps/parameters/prd-jdtd.json +++ b/infrastructure/bicep/container-apps/apps/parameters/prd-jdtd.json @@ -6,7 +6,7 @@ "value": 1 }, "containerImage": { - "value": "dfxswiss/deuro-dapp:beta" + "value": "juicedollar-dapp:latest" }, "containerVolumeMounts": { "value": [] @@ -54,21 +54,21 @@ "name": "NEXT_PUBLIC_WAGMI_ID", "value": "[API-KEY]" }, - { - "name": "NEXT_PUBLIC_ALCHEMY_API_KEY", - "value": "[API-KEY]" - }, { "name": "NEXT_PUBLIC_CHAIN_NAME", - "value": "mainnet" + "value": "testnet" }, { "name": "NEXT_PUBLIC_RPC_URL_MAINNET", - "value": "https://eth-mainnet.g.alchemy.com/v2" + "value": "https://rpc.testnet.juiceswap.com" + }, + { + "name": "NEXT_PUBLIC_RPC_URL_TESTNET", + "value": "https://rpc.testnet.juiceswap.com" }, { - "name": "NEXT_PUBLIC_RPC_URL_POLYGON", - "value": "https://polygon-mainnet.g.alchemy.com/v2" + "name": "NEXT_PUBLIC_PONDER_FALLBACK_URL", + "value": "https://dev.ponder.testnet.juicedollar.com/" } ] }, @@ -79,4 +79,4 @@ "value": [] } } -} +} \ No newline at end of file diff --git a/infrastructure/bicep/container-apps/apps/parameters/prd-jdtp.json b/infrastructure/bicep/container-apps/apps/parameters/prd-jdtp.json index 970c078fba..9eb5ae1141 100644 --- a/infrastructure/bicep/container-apps/apps/parameters/prd-jdtp.json +++ b/infrastructure/bicep/container-apps/apps/parameters/prd-jdtp.json @@ -6,7 +6,7 @@ "value": 1 }, "containerImage": { - "value": "dfxswiss/deuro-ponder:beta" + "value": "juicedollar-ponder:latest" }, "containerVolumeMounts": { "value": [ @@ -45,11 +45,19 @@ }, { "name": "PONDER_PROFILE", - "value": "mainnet" + "value": "testnet" }, { - "name": "RPC_URL_MAINNET", - "value": "https://eth-mainnet.g.alchemy.com/v2/[API-KEY]" + "name": "PONDER_SCHEMA_VERSION", + "value": "6" + }, + { + "name": "DATABASE_URL", + "value": "[DATABASE_URL]" + }, + { + "name": "RPC_URL_TESTNET", + "value": "https://rpc.testnet.juiceswap.com" } ] }, @@ -60,4 +68,4 @@ "value": [] } } -} \ No newline at end of file +} diff --git a/infrastructure/bicep/container-apps/manualFrontdoorSetup.sh b/infrastructure/bicep/container-apps/manualFrontdoorSetup.sh index 657613297b..3073ad692b 100755 --- a/infrastructure/bicep/container-apps/manualFrontdoorSetup.sh +++ b/infrastructure/bicep/container-apps/manualFrontdoorSetup.sh @@ -19,7 +19,11 @@ environmentOptions=("loc" "dev" "prd") # "jdta": JuiceDollar Testnet API # "jdtd": JuiceDollar Testnet dApp # "jdtm": JuiceDollar Testnet Monitoring -appNameOptions=("fcp" "dep" "dea" "ded" "dem" "jsp" "jsw" "n8n" "ctn" "jdtp" "jdta" "jdtd" "jdtm") +# "jdmp": JuiceDollar Mainnet Ponder +# "jdma": JuiceDollar Mainnet API +# "jdmd": JuiceDollar Mainnet dApp +# "jdmm": JuiceDollar Mainnet Monitoring +appNameOptions=("fcp" "dep" "dea" "ded" "dem" "jsp" "jsw" "n8n" "jdtp" "jdta" "jdtd" "jdtm" "jdmp" "jdma" "jdmd" "jdmm") # --- FUNCTIONS --- # selectOption() { diff --git a/infrastructure/bicep/container-apps/setup-custom-domains.sh b/infrastructure/bicep/container-apps/setup-custom-domains.sh new file mode 100755 index 0000000000..2da5ee573c --- /dev/null +++ b/infrastructure/bicep/container-apps/setup-custom-domains.sh @@ -0,0 +1,249 @@ +#!/bin/sh +set -e + +# Documentation: +# Creates custom domains on Azure Front Door and optionally creates DNS records in Azure DNS + +# --- OPTIONS --- # +environmentOptions=("loc" "dev" "prd") + +# --- DOMAINS --- # +# Format: "custom-domain:app-code" (for external DNS) +# Format: "custom-domain:app-code:dns-resource-group:dns-zone" (for Azure DNS) +# App codes must match appNameOptions in manualFrontdoorSetup.sh +# Example: "example.com:dea" -> creates custom domain for dEuro API +# Example: "api.example.com:dea:rg-dns:example.com" -> also creates Azure DNS records +CUSTOM_DOMAINS=( + # "api.example.com:app" # External DNS (outputs records for manual creation) + # "api.example.com:app:rg-dns:example.com" # Azure DNS (creates records automatically) +) + +# --- FUNCTIONS --- # +selectOption() { + PS3="${1}: " + shift + options=("$@") + + select opt in "${options[@]}" "quit"; do + case "$REPLY" in + *) selection="${opt}"; break ;; + esac + done + + if [[ ! $selection || $selection == "quit" ]]; then exit -1; fi + echo "${selection}" +} + +extractSubdomain() { + local domain="$1" + local zone="$2" + echo "$domain" | sed "s/\.$zone$//" +} + +createAzureDnsRecord() { + local rg="$1" + local zone="$2" + local name="$3" + local type="$4" + local value="$5" + + echo "Creating DNS record: ${name}.${zone} ${type} ${value}" + + if [ "$type" == "CNAME" ]; then + # Check if record exists + EXISTING=$(az network dns record-set cname show \ + --resource-group "$rg" \ + --zone-name "$zone" \ + --name "$name" \ + --query "cnameRecord.cname" \ + --output tsv 2>/dev/null || echo "") + + if [ -n "$EXISTING" ]; then + echo " CNAME record already exists, updating..." + az network dns record-set cname set-record \ + --resource-group "$rg" \ + --zone-name "$zone" \ + --record-set-name "$name" \ + --cname "$value" \ + --output none + else + az network dns record-set cname create \ + --resource-group "$rg" \ + --zone-name "$zone" \ + --name "$name" \ + --output none + + az network dns record-set cname set-record \ + --resource-group "$rg" \ + --zone-name "$zone" \ + --record-set-name "$name" \ + --cname "$value" \ + --output none + fi + elif [ "$type" == "TXT" ]; then + # Check if record exists + EXISTING=$(az network dns record-set txt show \ + --resource-group "$rg" \ + --zone-name "$zone" \ + --name "$name" \ + --query "txtRecords[0].value[0]" \ + --output tsv 2>/dev/null || echo "") + + if [ -n "$EXISTING" ]; then + echo " TXT record already exists, updating..." + # Remove old records and add new one + az network dns record-set txt remove-record \ + --resource-group "$rg" \ + --zone-name "$zone" \ + --record-set-name "$name" \ + --value "$EXISTING" \ + --output none 2>/dev/null || true + else + az network dns record-set txt create \ + --resource-group "$rg" \ + --zone-name "$zone" \ + --name "$name" \ + --output none 2>/dev/null || true + fi + + az network dns record-set txt add-record \ + --resource-group "$rg" \ + --zone-name "$zone" \ + --record-set-name "$name" \ + --value "$value" \ + --output none + fi + + echo " Done." +} + +# --- MAIN --- # +ENV=$(selectOption "Select Environment" "${environmentOptions[@]}") + +# Global variables +COMP_NAME="dfx" +API_NAME="api" + +RESOURCE_GROUP="rg-${COMP_NAME}-${API_NAME}-${ENV}" +AFD_PROFILE="afd-${COMP_NAME}-${API_NAME}-${ENV}" + +echo "" +echo "Resource Group: ${RESOURCE_GROUP}" +echo "Frontdoor Profile: ${AFD_PROFILE}" + +DNS_RECORDS="" + +for entry in "${CUSTOM_DOMAINS[@]}"; do + # Parse entry (domain:app or domain:app:dns-rg:dns-zone) + IFS=':' read -r DOMAIN APP DNS_RG DNS_ZONE <<< "$entry" + + # Generate resource names + AFD_ENDPOINT="fde-${COMP_NAME}-${APP}-${ENV}" + DOMAIN_NAME="${DOMAIN//./-}" + + echo "" + echo "Custom Domain: ${DOMAIN}" + echo "Frontdoor Endpoint: ${AFD_ENDPOINT}" + echo "Domain Resource Name: ${DOMAIN_NAME}" + + # Get endpoint hostname + ENDPOINT_HOSTNAME=$(az afd endpoint show \ + --resource-group "$RESOURCE_GROUP" \ + --profile-name "$AFD_PROFILE" \ + --endpoint-name "$AFD_ENDPOINT" \ + --query "hostName" \ + --output tsv) + + echo "Endpoint Hostname: ${ENDPOINT_HOSTNAME}" + + # Check if custom domain already exists + EXISTING=$(az afd custom-domain show \ + --resource-group "$RESOURCE_GROUP" \ + --profile-name "$AFD_PROFILE" \ + --custom-domain-name "$DOMAIN_NAME" \ + --query "hostName" \ + --output tsv 2>/dev/null || echo "") + + if [ -n "$EXISTING" ]; then + echo "Custom domain already exists, skipping creation..." + else + # Create the custom domain + az afd custom-domain create \ + --resource-group "$RESOURCE_GROUP" \ + --profile-name "$AFD_PROFILE" \ + --custom-domain-name "$DOMAIN_NAME" \ + --host-name "$DOMAIN" \ + --certificate-type ManagedCertificate \ + --minimum-tls-version TLS12 \ + --output none + fi + + # Get validation token + VALIDATION_TOKEN=$(az afd custom-domain show \ + --resource-group "$RESOURCE_GROUP" \ + --profile-name "$AFD_PROFILE" \ + --custom-domain-name "$DOMAIN_NAME" \ + --query "validationProperties.validationToken" \ + --output tsv) + + echo "Validation Token: ${VALIDATION_TOKEN}" + + # Handle DNS records - Azure DNS if info provided, otherwise collect for manual creation + if [ -n "$DNS_RG" ] && [ -n "$DNS_ZONE" ]; then + echo "" + echo "Creating Azure DNS records in zone ${DNS_ZONE} (${DNS_RG})..." + + # Extract subdomain from full domain + SUBDOMAIN=$(extractSubdomain "$DOMAIN" "$DNS_ZONE") + + # Create CNAME record + createAzureDnsRecord "$DNS_RG" "$DNS_ZONE" "$SUBDOMAIN" "CNAME" "$ENDPOINT_HOSTNAME" + + # Create TXT record for validation + createAzureDnsRecord "$DNS_RG" "$DNS_ZONE" "_dnsauth.${SUBDOMAIN}" "TXT" "$VALIDATION_TOKEN" + else + # Collect DNS records for summary (external DNS) + DNS_RECORDS="${DNS_RECORDS}${DOMAIN}|CNAME|${ENDPOINT_HOSTNAME}\n" + DNS_RECORDS="${DNS_RECORDS}_dnsauth.${DOMAIN}|TXT|${VALIDATION_TOKEN}\n\n" + fi +done + +echo "" +echo "--- Associating domains with routes ---" + +for entry in "${CUSTOM_DOMAINS[@]}"; do + IFS=':' read -r DOMAIN APP DNS_RG DNS_ZONE <<< "$entry" + AFD_ENDPOINT="fde-${COMP_NAME}-${APP}-${ENV}" + AFD_ROUTE="fdor-${COMP_NAME}-${APP}-${ENV}" + DOMAIN_NAME="${DOMAIN//./-}" + + echo "" + echo "Associating ${DOMAIN} with route ${AFD_ROUTE}..." + + az afd route update \ + --resource-group $RESOURCE_GROUP \ + --profile-name $AFD_PROFILE \ + --endpoint-name $AFD_ENDPOINT \ + --route-name $AFD_ROUTE \ + --custom-domains $DOMAIN_NAME +done + +# Show DNS records summary for external DNS entries +if [ -n "$DNS_RECORDS" ]; then + echo "" + echo "--- DNS Records to Create (External DNS) ---" + echo "" + printf "%-40s %-8s %s\n" "NAME" "TYPE" "VALUE" + printf "%-40s %-8s %s\n" "---" "----" "-----" + printf "%b" "$DNS_RECORDS" | while IFS='|' read -r name type value; do + if [ -n "$name" ]; then + # Extract subdomain and root domain (assumes 2-part TLD like .com, .ch) + root_domain=$(echo "$name" | awk -F. '{print $(NF-1)"."$NF}') + subdomain=$(echo "$name" | sed "s/\.$root_domain$//") + printf "%-40s %-8s %s\n" "$subdomain (.$root_domain)" "$type" "$value" + fi + done +fi + +echo "" +echo "--- Done ---" diff --git a/infrastructure/bicep/virtual-machines/front-door/deploy.sh b/infrastructure/bicep/virtual-machines/front-door/deploy.sh old mode 100644 new mode 100755 index 51a913bdf7..9ebc18a4d3 --- a/infrastructure/bicep/virtual-machines/front-door/deploy.sh +++ b/infrastructure/bicep/virtual-machines/front-door/deploy.sh @@ -11,7 +11,10 @@ environmentOptions=("loc" "dev" "prd") # "ctn": Citrea Testnet Node # "cteb": Citrea Testnet Explorer Backend # "ctef": Citrea Testnet Explorer Frontend -nodeNameOptions=("ctn" "cteb" "ctef") +# "cmn": Citrea Mainnet Node +# "cmeb": Citrea Mainnet Explorer Backend +# "cmef": Citrea Mainnet Explorer Frontend +nodeNameOptions=("ctn" "cteb" "ctef" "cmn" "cmeb" "cmef") # --- FUNCTIONS --- # selectOption() { diff --git a/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cteb.json b/infrastructure/bicep/virtual-machines/front-door/parameters/dev-cmef.json similarity index 90% rename from infrastructure/bicep/virtual-machines/front-door/parameters/prd-cteb.json rename to infrastructure/bicep/virtual-machines/front-door/parameters/dev-cmef.json index 8879bbed7c..d5eb6b37a6 100644 --- a/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cteb.json +++ b/infrastructure/bicep/virtual-machines/front-door/parameters/dev-cmef.json @@ -6,7 +6,7 @@ "value": "node" }, "originPath": { - "value": "/cteb" + "value": "/cmef" } } } \ No newline at end of file diff --git a/infrastructure/bicep/virtual-machines/front-door/parameters/prd-ctef.json b/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmeb.json similarity index 90% rename from infrastructure/bicep/virtual-machines/front-door/parameters/prd-ctef.json rename to infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmeb.json index efe125a718..7bf6cee972 100644 --- a/infrastructure/bicep/virtual-machines/front-door/parameters/prd-ctef.json +++ b/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmeb.json @@ -6,7 +6,7 @@ "value": "node" }, "originPath": { - "value": "/ctef" + "value": "/cmeb" } } } \ No newline at end of file diff --git a/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmef.json b/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmef.json new file mode 100644 index 0000000000..d5eb6b37a6 --- /dev/null +++ b/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmef.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vmName": { + "value": "node" + }, + "originPath": { + "value": "/cmef" + } + } +} \ No newline at end of file diff --git a/infrastructure/bicep/virtual-machines/front-door/parameters/prd-ctn.json b/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmn.json similarity index 91% rename from infrastructure/bicep/virtual-machines/front-door/parameters/prd-ctn.json rename to infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmn.json index 1c16ab65bf..cd720942ed 100644 --- a/infrastructure/bicep/virtual-machines/front-door/parameters/prd-ctn.json +++ b/infrastructure/bicep/virtual-machines/front-door/parameters/prd-cmn.json @@ -6,7 +6,7 @@ "value": "node" }, "originPath": { - "value": "/ctn" + "value": "/cmn" } } } \ No newline at end of file diff --git a/infrastructure/bicep/virtual-machines/load-balancer/deploy.sh b/infrastructure/bicep/virtual-machines/load-balancer/deploy.sh index a4d99de649..c0263837d4 100644 --- a/infrastructure/bicep/virtual-machines/load-balancer/deploy.sh +++ b/infrastructure/bicep/virtual-machines/load-balancer/deploy.sh @@ -11,8 +11,9 @@ environmentOptions=("loc" "dev" "prd") resourceGroupOptions=("api" "core") # "ctn": Citrea Testnet Node +# "cmn": Citrea Mainnet Node # "sln": Swiss Ledger Node -nodeNameOptions=("ctn" "sln") +nodeNameOptions=("ctn" "cmn" "sln") # --- FUNCTIONS --- # selectOption() { diff --git a/infrastructure/citrea/citreascan/config/bitcoind/bitcoin-mainnet.conf b/infrastructure/citrea/citreascan/config/bitcoind/bitcoin-mainnet.conf new file mode 100644 index 0000000000..db8040a472 --- /dev/null +++ b/infrastructure/citrea/citreascan/config/bitcoind/bitcoin-mainnet.conf @@ -0,0 +1,14 @@ +server=1 +rest=1 +txindex=1 + +rpcallowip=0.0.0.0/0 +rpcbind=0.0.0.0 +rpcport=8332 + +rpcauth=[rpcauth] +rpcuser=[rpcuser] +rpcpassword=[rpcpassword] + +zmqpubrawblock=tcp://0.0.0.0:28332 +zmqpubrawtx=tcp://0.0.0.0:28333 diff --git a/infrastructure/citrea/citreascan/config/docker/docker-compose-blockscout-citrea-mainnet.yml b/infrastructure/citrea/citreascan/config/docker/docker-compose-blockscout-citrea-mainnet.yml new file mode 100644 index 0000000000..57e2418cc6 --- /dev/null +++ b/infrastructure/citrea/citreascan/config/docker/docker-compose-blockscout-citrea-mainnet.yml @@ -0,0 +1,88 @@ +name: blockscout-citrea-mainnet + +services: + postgres: + image: postgres:17 + cpus: 2.0 + shm_size: '4gb' + restart: unless-stopped + networks: + - shared + volumes: + - ./volumes/blockscout/citrea/mainnet/postgres-data:/var/lib/postgresql/data + ports: + - '5432:5432' + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U blockscout'] + interval: 30s + timeout: 5s + retries: 5 + logging: + driver: 'json-file' + options: + max-size: '100m' + max-file: '3' + environment: + - POSTGRES_PASSWORD=[POSTGRES_PASSWORD] + - POSTGRES_USER=[POSTGRES_USER] + - POSTGRES_DB=[POSTGRES_DB] + command: postgres -c max_connections=200 + + backend: + image: dfxswiss/citrea-backend:beta + restart: unless-stopped + networks: + - shared + depends_on: + postgres: + condition: service_healthy + ports: + - '4000:4000' + logging: + driver: 'json-file' + options: + max-size: '100m' + max-file: '3' + env_file: docker-compose-blockscout-citrea-mainnet.backend.env + environment: + - DATABASE_URL=[DATABASE_URL] + - MARKET_COINGECKO_API_KEY=[MARKET_COINGECKO_API_KEY] + command: sh -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start" + + frontend: + image: dfxswiss/citrea-frontend:beta + restart: unless-stopped + networks: + - shared + depends_on: + - backend + ports: + - '3000:3000' + logging: + driver: 'json-file' + options: + max-size: '100m' + max-file: '3' + env_file: docker-compose-blockscout-citrea-mainnet.frontend.env + #environment: + + redis-db: + image: redis:alpine + restart: unless-stopped + networks: + - shared + volumes: + - ./volumes/redis/data:/data + ports: + - '6379:6379' + logging: + driver: 'json-file' + options: + max-size: '100m' + max-file: '3' + command: redis-server + +networks: + shared: + external: true + name: citrea-mainnet-network diff --git a/infrastructure/citrea/citreascan/config/docker/docker-compose-citrea-mainnet.yml b/infrastructure/citrea/citreascan/config/docker/docker-compose-citrea-mainnet.yml new file mode 100644 index 0000000000..e81972d860 --- /dev/null +++ b/infrastructure/citrea/citreascan/config/docker/docker-compose-citrea-mainnet.yml @@ -0,0 +1,73 @@ +name: 'citrea-mainnet' + +services: + bitcoind: + image: lightninglabs/bitcoin-core + restart: unless-stopped + networks: + - shared + volumes: + - ./volumes/bitcoin:/home/bitcoin/.bitcoin + - ./volumes/bitcoin/bitcoin-mainnet.conf:/home/bitcoin/.bitcoin/bitcoin.conf + ports: + - '18443:18443' + healthcheck: + test: curl --fail http://localhost:18443/rest/chaininfo.json || exit 1 + start_period: 120s + interval: 30s + timeout: 60s + retries: 10 + logging: + driver: 'json-file' + options: + max-size: '100m' + max-file: '3' + command: > + bitcoind -conf=/home/bitcoin/.bitcoin/bitcoin.conf + + citread: + depends_on: + - bitcoind + image: chainwayxyz/citrea-full-node:mainnet + restart: unless-stopped + networks: + - shared + volumes: + - ./volumes/citrea/mainnet:/mnt/task/citrea-db + ports: + - '8085:8085' + logging: + driver: 'json-file' + options: + max-size: '100m' + max-file: '3' + environment: + - SEQUENCER_PUBLIC_KEY=0201edff3b3ee593dbef54e2fbdd421070db55e2de2aebe75f398bd85ac97ed364 + - SEQUENCER_DA_PUB_KEY=03015a7c4d2cc1c771198686e2ebef6fe7004f4136d61f6225b061d1bb9b821b9b + - PROVER_DA_PUB_KEY=0357d255ab93638a2d880787ebaadfefdfc9bb51a26b4a37e5d588e04e54c60a42 + - NODE_URL=http://bitcoind:18443/ + - NODE_USERNAME=[NODE_USERNAME] + - NODE_PASSWORD=[NODE_PASSWORD] + - NETWORK=mainnet + - TX_BACKUP_DIR= + - STORAGE_PATH=/mnt/task/citrea-db + - DB_MAX_OPEN_FILES=5000 + - RPC_BIND_HOST=0.0.0.0 + - RPC_BIND_PORT=8085 + - RPC_MAX_CONNECTIONS=100 + - RPC_MAX_REQUEST_BODY_SIZE=10485760 + - RPC_MAX_RESPONSE_BODY_SIZE=10485760 + - RPC_BATCH_REQUESTS_LIMIT=100 + - RPC_ENABLE_SUBSCRIPTIONS=true + - RPC_MAX_SUBSCRIPTIONS_PER_CONNECTION=10 + - SEQUENCER_CLIENT_URL=https://rpc.mainnet.citrea.xyz + - INCLUDE_TX_BODY=false + - SCAN_L1_START_HEIGHT=45496 + - SYNC_BLOCKS_COUNT=10 + - RUST_LOG=info + - JSON_LOGS=1 + +networks: + shared: + external: true + name: citrea-mainnet-network diff --git a/infrastructure/citrea/citreascan/config/docker/docker-compose-nginx-mainnet.yml b/infrastructure/citrea/citreascan/config/docker/docker-compose-nginx-mainnet.yml new file mode 100644 index 0000000000..43034e25e1 --- /dev/null +++ b/infrastructure/citrea/citreascan/config/docker/docker-compose-nginx-mainnet.yml @@ -0,0 +1,18 @@ +name: 'nginx-citrea-mainnet' + +services: + nginx: + image: nginx:1.29.2-perl + restart: unless-stopped + networks: + - shared + volumes: + - ./volumes/nginx/default.conf:/etc/nginx/conf.d/default.conf + - ./volumes/nginx/www:/var/www + ports: + - '80:80' + +networks: + shared: + external: true + name: citrea-mainnet-network diff --git a/infrastructure/citrea/citreascan/config/docker/docker-compose-nginx.yml b/infrastructure/citrea/citreascan/config/docker/docker-compose-nginx-testnet.yml similarity index 100% rename from infrastructure/citrea/citreascan/config/docker/docker-compose-nginx.yml rename to infrastructure/citrea/citreascan/config/docker/docker-compose-nginx-testnet.yml diff --git a/infrastructure/citrea/citreascan/config/nginx/default-mainnet.conf b/infrastructure/citrea/citreascan/config/nginx/default-mainnet.conf new file mode 100644 index 0000000000..d9eb2271b6 --- /dev/null +++ b/infrastructure/citrea/citreascan/config/nginx/default-mainnet.conf @@ -0,0 +1,37 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + location /cmn/ { + proxy_pass http://citread:8085/; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /cmeb/ { + proxy_pass http://backend:4000/; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /cmef/ { + proxy_pass http://frontend:3000/; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/infrastructure/citrea/citreascan/config/nginx/default.conf b/infrastructure/citrea/citreascan/config/nginx/default-testnet.conf similarity index 100% rename from infrastructure/citrea/citreascan/config/nginx/default.conf rename to infrastructure/citrea/citreascan/config/nginx/default-testnet.conf From acda15480192a14c79ebe4898af4a3f2bdefc512 Mon Sep 17 00:00:00 2001 From: Lam Nguyen <32935491+xlamn@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:52:44 +0100 Subject: [PATCH 03/14] fix: enable registration with different email when registration not completed (#3081) * fix: enable email registration when wallet has no complete registration. * chore: check if registration comes from RealUnit Wallet. * chore: get address from jwt. --- .../controllers/realunit.controller.ts | 2 +- .../supporting/realunit/realunit.service.ts | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/subdomains/supporting/realunit/controllers/realunit.controller.ts b/src/subdomains/supporting/realunit/controllers/realunit.controller.ts index 597e5224b0..0461eb6a98 100644 --- a/src/subdomains/supporting/realunit/controllers/realunit.controller.ts +++ b/src/subdomains/supporting/realunit/controllers/realunit.controller.ts @@ -339,7 +339,7 @@ export class RealUnitController { @GetJwt() jwt: JwtPayload, @Body() dto: RealUnitEmailRegistrationDto, ): Promise { - const status = await this.realunitService.registerEmail(jwt.account, dto); + const status = await this.realunitService.registerEmail(jwt.account, jwt.address, dto); return { status }; } diff --git a/src/subdomains/supporting/realunit/realunit.service.ts b/src/subdomains/supporting/realunit/realunit.service.ts index 8f612174e1..886bce1bbd 100644 --- a/src/subdomains/supporting/realunit/realunit.service.ts +++ b/src/subdomains/supporting/realunit/realunit.service.ts @@ -349,11 +349,25 @@ export class RealUnitService { return !success; } - async registerEmail(userDataId: number, dto: RealUnitEmailRegistrationDto): Promise { - const userData = await this.userDataService.getUserData(userDataId, { users: true }); + async registerEmail( + userDataId: number, + walletAddress: string, + dto: RealUnitEmailRegistrationDto, + ): Promise { + const userData = await this.userDataService.getUserData(userDataId, { users: true, kycSteps: true, wallet: true }); if (!userData) throw new NotFoundException('User not found'); - if (!userData.mail) { + if (userData.wallet?.name !== 'RealUnit') { + throw new BadRequestException('Registration is only allowed from RealUnit wallet'); + } + + const isNewEmail = !userData.mail || !Util.equalsIgnoreCase(dto.email, userData.mail); + + if (isNewEmail) { + if (userData.mail && this.hasRegistrationForWallet(userData, walletAddress)) { + throw new BadRequestException('Not allowed to register a new email for this address'); + } + try { await this.userDataService.trySetUserMail(userData, dto.email); } catch (e) { @@ -364,8 +378,6 @@ export class RealUnitService { } throw e; } - } else if (!Util.equalsIgnoreCase(dto.email, userData.mail)) { - throw new BadRequestException('Email does not match verified email'); } if (userData.kycLevel < KycLevel.LEVEL_10) { From 481367db6d6c1fb77fd209d59eedb474960c151b Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:02:35 +0100 Subject: [PATCH 04/14] fix: vulnerabilities + invoice PDF generation (#3120) * fix: invoice PDF generation * fix: vulnerabilities * fix: address line fix * fix: fiat-output logging --- package-lock.json | 1068 +++++++++-------- src/shared/i18n/de/invoice.json | 7 +- src/shared/i18n/en/invoice.json | 7 +- src/shared/i18n/fr/invoice.json | 7 +- src/shared/i18n/it/invoice.json | 7 +- src/shared/utils/pdf.util.ts | 36 + .../core/buy-crypto/routes/buy/buy.service.ts | 22 +- .../fiat-output/fiat-output.service.ts | 6 +- .../payment/services/swiss-qr.service.ts | 71 +- .../payment/services/transaction-helper.ts | 2 + 10 files changed, 666 insertions(+), 567 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7f894de9d..e579ebb90e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -420,502 +420,523 @@ } }, "node_modules/@aws-sdk/client-ses": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.920.0.tgz", - "integrity": "sha512-lA1R69ebw3bGjhekGeGoCU/U1OBKfhO7r6msTNhZvYqtWlJA8VuNQAau1kC80PS8n338Sp47iHPPUbBjCbzCnA==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.982.0.tgz", + "integrity": "sha512-FMfZsrdevWomqEwLWaW5Jfq+8jRbROQe8sbEANVTNPYBfXvnd8TxPs/09h7TgFjtSB7hsjUv8Ja6IjeMV5HHPA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.920.0", - "@aws-sdk/credential-provider-node": "3.920.0", - "@aws-sdk/middleware-host-header": "3.920.0", - "@aws-sdk/middleware-logger": "3.920.0", - "@aws-sdk/middleware-recursion-detection": "3.920.0", - "@aws-sdk/middleware-user-agent": "3.920.0", - "@aws-sdk/region-config-resolver": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@aws-sdk/util-endpoints": "3.920.0", - "@aws-sdk/util-user-agent-browser": "3.920.0", - "@aws-sdk/util-user-agent-node": "3.920.0", - "@smithy/config-resolver": "^4.4.0", - "@smithy/core": "^3.17.1", - "@smithy/fetch-http-handler": "^5.3.4", - "@smithy/hash-node": "^4.2.3", - "@smithy/invalid-dependency": "^4.2.3", - "@smithy/middleware-content-length": "^4.2.3", - "@smithy/middleware-endpoint": "^4.3.5", - "@smithy/middleware-retry": "^4.4.5", - "@smithy/middleware-serde": "^4.2.3", - "@smithy/middleware-stack": "^4.2.3", - "@smithy/node-config-provider": "^4.3.3", - "@smithy/node-http-handler": "^4.4.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/smithy-client": "^4.9.1", - "@smithy/types": "^4.8.0", - "@smithy/url-parser": "^4.2.3", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-node": "^3.972.5", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.4", - "@smithy/util-defaults-mode-node": "^4.2.6", - "@smithy/util-endpoints": "^3.2.3", - "@smithy/util-middleware": "^4.2.3", - "@smithy/util-retry": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.3", + "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.920.0.tgz", - "integrity": "sha512-m/Gb/ojGX4uqJAcvFWCbutVBnRXAKnlU+rrHUy3ugmg4lmMl1RjP4mwqlj+p+thCq2OmoEJtqZIuO2a/5N/NPA==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz", + "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.920.0", - "@aws-sdk/middleware-host-header": "3.920.0", - "@aws-sdk/middleware-logger": "3.920.0", - "@aws-sdk/middleware-recursion-detection": "3.920.0", - "@aws-sdk/middleware-user-agent": "3.920.0", - "@aws-sdk/region-config-resolver": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@aws-sdk/util-endpoints": "3.920.0", - "@aws-sdk/util-user-agent-browser": "3.920.0", - "@aws-sdk/util-user-agent-node": "3.920.0", - "@smithy/config-resolver": "^4.4.0", - "@smithy/core": "^3.17.1", - "@smithy/fetch-http-handler": "^5.3.4", - "@smithy/hash-node": "^4.2.3", - "@smithy/invalid-dependency": "^4.2.3", - "@smithy/middleware-content-length": "^4.2.3", - "@smithy/middleware-endpoint": "^4.3.5", - "@smithy/middleware-retry": "^4.4.5", - "@smithy/middleware-serde": "^4.2.3", - "@smithy/middleware-stack": "^4.2.3", - "@smithy/node-config-provider": "^4.3.3", - "@smithy/node-http-handler": "^4.4.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/smithy-client": "^4.9.1", - "@smithy/types": "^4.8.0", - "@smithy/url-parser": "^4.2.3", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.4", - "@smithy/util-defaults-mode-node": "^4.2.6", - "@smithy/util-endpoints": "^3.2.3", - "@smithy/util-middleware": "^4.2.3", - "@smithy/util-retry": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.920.0.tgz", - "integrity": "sha512-vETnyaBJgIK6dh0hXzxw8e6v9SEFs/NgP6fJOn87QC+0M8U/omaB298kJ+i7P3KJafW6Pv/CWTsciMP/NNrg6A==", + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz", + "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.920.0", - "@aws-sdk/xml-builder": "3.914.0", - "@smithy/core": "^3.17.1", - "@smithy/node-config-provider": "^4.3.3", - "@smithy/property-provider": "^4.2.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/signature-v4": "^5.3.3", - "@smithy/smithy-client": "^4.9.1", - "@smithy/types": "^4.8.0", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.22.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.3", + "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.920.0.tgz", - "integrity": "sha512-f8AcW9swaoJnJIj43TNyUVCR7ToEbUftD9y5Ht6IwNhRq2iPwZ7uTvgrkjfdxOayj1uD7Gw5MkeC3Ki5lcsasA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz", + "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/property-provider": "^4.2.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.920.0.tgz", - "integrity": "sha512-C75OGAnyHuILiIFfwbSUyV1YIJvcQt2U63IqlZ25eufV1NA+vP3Y60nvaxrzSxvditxXL95+YU3iLa4n2M0Omw==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz", + "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/fetch-http-handler": "^5.3.4", - "@smithy/node-http-handler": "^4.4.3", - "@smithy/property-provider": "^4.2.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/smithy-client": "^4.9.1", - "@smithy/types": "^4.8.0", - "@smithy/util-stream": "^4.5.4", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.920.0.tgz", - "integrity": "sha512-rwTWfPhE2cs1kQ5dBpOEedhlzNcXf9LRzd9K4rn577pLJiWUc/n/Ibh4Hvw8Px1cp9krIk1q6wo+iK+kLQD8YA==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz", + "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.920.0", - "@aws-sdk/credential-provider-env": "3.920.0", - "@aws-sdk/credential-provider-http": "3.920.0", - "@aws-sdk/credential-provider-process": "3.920.0", - "@aws-sdk/credential-provider-sso": "3.920.0", - "@aws-sdk/credential-provider-web-identity": "3.920.0", - "@aws-sdk/nested-clients": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/credential-provider-imds": "^4.2.3", - "@smithy/property-provider": "^4.2.3", - "@smithy/shared-ini-file-loader": "^4.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/credential-provider-env": "^3.972.4", + "@aws-sdk/credential-provider-http": "^3.972.6", + "@aws-sdk/credential-provider-login": "^3.972.4", + "@aws-sdk/credential-provider-process": "^3.972.4", + "@aws-sdk/credential-provider-sso": "^3.972.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz", + "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.920.0.tgz", - "integrity": "sha512-PGlmTe22KOLzk79urV7ILRF2ka3RXkiS6B5dgJC+OUjf209plcI+fs/p/sGdKCGCrPCYWgTHgqpyY2c8nO9B2A==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz", + "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.920.0", - "@aws-sdk/credential-provider-http": "3.920.0", - "@aws-sdk/credential-provider-ini": "3.920.0", - "@aws-sdk/credential-provider-process": "3.920.0", - "@aws-sdk/credential-provider-sso": "3.920.0", - "@aws-sdk/credential-provider-web-identity": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/credential-provider-imds": "^4.2.3", - "@smithy/property-provider": "^4.2.3", - "@smithy/shared-ini-file-loader": "^4.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/credential-provider-env": "^3.972.4", + "@aws-sdk/credential-provider-http": "^3.972.6", + "@aws-sdk/credential-provider-ini": "^3.972.4", + "@aws-sdk/credential-provider-process": "^3.972.4", + "@aws-sdk/credential-provider-sso": "^3.972.4", + "@aws-sdk/credential-provider-web-identity": "^3.972.4", + "@aws-sdk/types": "^3.973.1", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.920.0.tgz", - "integrity": "sha512-7dc0L0BCme4P17BgK/RtWLmwnM/R+si4Xd1cZe1oBLWRV+s++AXU/nDwfy1ErOLVpE9+lGG3Iw5zEPA/pJc7gQ==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz", + "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/property-provider": "^4.2.3", - "@smithy/shared-ini-file-loader": "^4.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.920.0.tgz", - "integrity": "sha512-+g1ajAa7nZGyLjKvQTzbasFvBwVWqMYSJl/3GbM61rpgjyHjeTPDZy2WXpQcpVGeCp6fWJG3J36Qjj7f9pfNeQ==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz", + "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.920.0", - "@aws-sdk/core": "3.920.0", - "@aws-sdk/token-providers": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/property-provider": "^4.2.3", - "@smithy/shared-ini-file-loader": "^4.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/client-sso": "3.982.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/token-providers": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.920.0.tgz", - "integrity": "sha512-B/YX/5A9LcYBLMjb9Fjn88KEJXdl22dSGwLfW/iHr/ET7XrZgc2Vh+f0KtsH+0GOa/uq7m1G6rIuvQ6FojpJ1A==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz", + "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.920.0", - "@aws-sdk/nested-clients": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/property-provider": "^4.2.3", - "@smithy/shared-ini-file-loader": "^4.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.920.0.tgz", - "integrity": "sha512-XQv9GRKGhXuWv797l/GnE9pt4UhlbzY39f2G3prcsLJCLyeIMeZ00QACIyshlArQ3ZhJp5FCRGGBcoSPQ2nk0Q==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.920.0", - "@smithy/protocol-http": "^5.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.920.0.tgz", - "integrity": "sha512-96v4hvJ9Cg/+XTYtM2aVTwZPzDOwyUiBh+FLioMng32mR64ofO1lvet4Zi1Uer9j7s086th3DJWkvqpi3K83dQ==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.920.0", - "@smithy/types": "^4.8.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.920.0.tgz", - "integrity": "sha512-5OfZ4RDYAW08kxMaGxIebJoUhzH7/MpGOoPzVMfxxfGbf+e4p0DNHJ9EL6msUAsbGBhGccDl1b4aytnYW+IkgA==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.920.0", - "@aws/lambda-invoke-store": "^0.1.1", - "@smithy/protocol-http": "^5.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/types": "^3.973.1", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.920.0.tgz", - "integrity": "sha512-7kvJyz7a1v0C243DJUZTu4C++4U5gyFYKN35Ng7rBR03kQC8oE10qHfWNNc39Lj3urabjRvQ80e06pA/vCZ8xA==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz", + "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@aws-sdk/util-endpoints": "3.920.0", - "@smithy/core": "^3.17.1", - "@smithy/protocol-http": "^5.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@smithy/core": "^3.22.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.920.0.tgz", - "integrity": "sha512-1JlzZJ0qp68zr6wPoLFwZ0+EH6HQvKMJjF8e2y9yO82LC3CsetaMxLUC2em7uY+3Gp0TMSA/Yxy4rTShf0vmng==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz", + "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.920.0", - "@aws-sdk/middleware-host-header": "3.920.0", - "@aws-sdk/middleware-logger": "3.920.0", - "@aws-sdk/middleware-recursion-detection": "3.920.0", - "@aws-sdk/middleware-user-agent": "3.920.0", - "@aws-sdk/region-config-resolver": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@aws-sdk/util-endpoints": "3.920.0", - "@aws-sdk/util-user-agent-browser": "3.920.0", - "@aws-sdk/util-user-agent-node": "3.920.0", - "@smithy/config-resolver": "^4.4.0", - "@smithy/core": "^3.17.1", - "@smithy/fetch-http-handler": "^5.3.4", - "@smithy/hash-node": "^4.2.3", - "@smithy/invalid-dependency": "^4.2.3", - "@smithy/middleware-content-length": "^4.2.3", - "@smithy/middleware-endpoint": "^4.3.5", - "@smithy/middleware-retry": "^4.4.5", - "@smithy/middleware-serde": "^4.2.3", - "@smithy/middleware-stack": "^4.2.3", - "@smithy/node-config-provider": "^4.3.3", - "@smithy/node-http-handler": "^4.4.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/smithy-client": "^4.9.1", - "@smithy/types": "^4.8.0", - "@smithy/url-parser": "^4.2.3", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.982.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.4", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.4", - "@smithy/util-defaults-mode-node": "^4.2.6", - "@smithy/util-endpoints": "^3.2.3", - "@smithy/util-middleware": "^4.2.3", - "@smithy/util-retry": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.920.0.tgz", - "integrity": "sha512-4g88FyRN+O4iFe8azt/9IEGeyktQcJPgjwpCCFwGL9QmOIOJja+F+Og05ydjnMBcUxH4CrWXJm0a54MXS2C9Fg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.920.0", - "@smithy/config-resolver": "^4.4.0", - "@smithy/node-config-provider": "^4.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/config-resolver": "^4.4.6", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.920.0.tgz", - "integrity": "sha512-ESDgN6oTq9ypqxK2qVAs5+LJMJCjky41B52k38LDfgyjgrZwqHcRgCQhH2L9/gC4MVOaE4fI24TgZsJlfyJ5dA==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz", + "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.920.0", - "@aws-sdk/nested-clients": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/property-provider": "^4.2.3", - "@smithy/shared-ini-file-loader": "^4.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/core": "^3.973.6", + "@aws-sdk/nested-clients": "3.982.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.920.0.tgz", - "integrity": "sha512-W8FI6HteaMwACb49IQzNABjbaGM/fP0t4lLBHeL6KXBmXung2S9FMIBHGxoZvBCRt5omFF31yDCbFaDN/1BPYQ==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.920.0.tgz", - "integrity": "sha512-PuoK3xl27LPLkm6VaeajBBTEtIF24aY+EfBWRKr/zqUJ6lTqicBLbxY0MqhsQ9KXALg/Ju0Aq7O4G0jpLu5S8w==", + "version": "3.982.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", + "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.920.0", - "@smithy/types": "^4.8.0", - "@smithy/url-parser": "^4.2.3", - "@smithy/util-endpoints": "^3.2.3", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", - "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "version": "3.965.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz", + "integrity": "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==", "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.920.0.tgz", - "integrity": "sha512-7nMoQjTa1SwULoUXBHm1hx24cb969e98AwPbrSmGwEZl2ZYXULOX3EZuDaX9QTzHutw8AMOgoI6JxCXhRQfmAg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.920.0", - "@smithy/types": "^4.8.0", + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.920.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.920.0.tgz", - "integrity": "sha512-JJykxGXilkeUeU5x3g8bXvkyedtmZ/gXZVwCnWfe/DHxoUDHgYhF0VAz+QJoh2lSN/lRnUV08K0ILmEzGQzY4A==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz", + "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.920.0", - "@aws-sdk/types": "3.920.0", - "@smithy/node-config-provider": "^4.3.3", - "@smithy/types": "^4.8.0", + "@aws-sdk/middleware-user-agent": "^3.972.6", + "@aws-sdk/types": "^3.973.1", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -927,24 +948,24 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.914.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.914.0.tgz", - "integrity": "sha512-k75evsBD5TcIjedycYS7QXQ98AmOtbnxRJOPtCo0IwYRmy7UvqgS/gBL5SmrIqeV6FDSYRQMgdBxSMp6MLmdew==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", - "fast-xml-parser": "5.2.5", + "@smithy/types": "^4.12.0", + "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", "dev": true, "funding": [ { @@ -961,9 +982,9 @@ } }, "node_modules/@aws-sdk/xml-builder/node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "dev": true, "funding": [ { @@ -974,9 +995,9 @@ "license": "MIT" }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.1.1.tgz", - "integrity": "sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1143,9 +1164,9 @@ } }, "node_modules/@azure/core-xml/node_modules/fast-xml-parser": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.0.tgz", - "integrity": "sha512-gkWGshjYcQCF+6qtlrqBqELqNqnt4CxruY6UVAWWnqb3DQ6qaNFEIKqzYep1XzHLM/QtrHVCxyPOtTk4LTQ7Aw==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", "funding": [ { "type": "github", @@ -4863,9 +4884,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "license": "MIT", "dependencies": { "@isaacs/balanced-match": "^4.0.1" @@ -7883,13 +7904,13 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.3.tgz", - "integrity": "sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", + "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -7897,17 +7918,17 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.0.tgz", - "integrity": "sha512-Kkmz3Mup2PGp/HNJxhCWkLNdlajJORLSjwkcfrj0E7nu6STAEdcMR1ir5P9/xOmncx8xXfru0fbUYLlZog/cFg==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", + "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.3", - "@smithy/types": "^4.8.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.3", - "@smithy/util-middleware": "^4.2.3", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -7915,19 +7936,19 @@ } }, "node_modules/@smithy/core": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.17.1.tgz", - "integrity": "sha512-V4Qc2CIb5McABYfaGiIYLTmo/vwNIK7WXI5aGveBd9UcdhbOMwcvIMxIw/DJj1S9QgOMa/7FBkarMdIC0EOTEQ==", + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", + "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/types": "^4.8.0", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.3", - "@smithy/util-stream": "^4.5.4", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -7937,16 +7958,16 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.3.tgz", - "integrity": "sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", + "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.3", - "@smithy/property-provider": "^4.2.3", - "@smithy/types": "^4.8.0", - "@smithy/url-parser": "^4.2.3", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -7954,15 +7975,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.4.tgz", - "integrity": "sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", + "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.3", - "@smithy/querystring-builder": "^4.2.3", - "@smithy/types": "^4.8.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, @@ -7971,13 +7992,13 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.3.tgz", - "integrity": "sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", + "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -7987,13 +8008,13 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.3.tgz", - "integrity": "sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", + "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8014,14 +8035,14 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.3.tgz", - "integrity": "sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", + "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.3", - "@smithy/types": "^4.8.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8029,19 +8050,19 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.5.tgz", - "integrity": "sha512-SIzKVTvEudFWJbxAaq7f2GvP3jh2FHDpIFI6/VAf4FOWGFZy0vnYMPSRj8PGYI8Hjt29mvmwSRgKuO3bK4ixDw==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", + "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.17.1", - "@smithy/middleware-serde": "^4.2.3", - "@smithy/node-config-provider": "^4.3.3", - "@smithy/shared-ini-file-loader": "^4.3.3", - "@smithy/types": "^4.8.0", - "@smithy/url-parser": "^4.2.3", - "@smithy/util-middleware": "^4.2.3", + "@smithy/core": "^3.22.1", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" }, "engines": { @@ -8049,19 +8070,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.5.tgz", - "integrity": "sha512-DCaXbQqcZ4tONMvvdz+zccDE21sLcbwWoNqzPLFlZaxt1lDtOE2tlVpRSwcTOJrjJSUThdgEYn7HrX5oLGlK9A==", + "version": "4.4.30", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", + "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/service-error-classification": "^4.2.3", - "@smithy/smithy-client": "^4.9.1", - "@smithy/types": "^4.8.0", - "@smithy/util-middleware": "^4.2.3", - "@smithy/util-retry": "^4.2.3", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, @@ -8070,14 +8091,14 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.3.tgz", - "integrity": "sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", + "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.3", - "@smithy/types": "^4.8.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8085,13 +8106,13 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.3.tgz", - "integrity": "sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", + "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8099,15 +8120,15 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.3.tgz", - "integrity": "sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", + "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.3", - "@smithy/shared-ini-file-loader": "^4.3.3", - "@smithy/types": "^4.8.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/shared-ini-file-loader": "^4.4.3", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8115,16 +8136,16 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.3.tgz", - "integrity": "sha512-MAwltrDB0lZB/H6/2M5PIsISSwdI5yIh6DaBB9r0Flo9nx3y0dzl/qTMJPd7tJvPdsx6Ks/cwVzheGNYzXyNbQ==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", + "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/querystring-builder": "^4.2.3", - "@smithy/types": "^4.8.0", + "@smithy/abort-controller": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/querystring-builder": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8132,13 +8153,13 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.3.tgz", - "integrity": "sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", + "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8146,13 +8167,13 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.3.tgz", - "integrity": "sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", + "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8160,13 +8181,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.3.tgz", - "integrity": "sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", + "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, @@ -8175,13 +8196,13 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.3.tgz", - "integrity": "sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", + "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8189,26 +8210,26 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.3.tgz", - "integrity": "sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", + "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0" + "@smithy/types": "^4.12.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.3.tgz", - "integrity": "sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", + "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8216,17 +8237,17 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.3.tgz", - "integrity": "sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", + "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.3", - "@smithy/types": "^4.8.0", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.3", + "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -8236,18 +8257,18 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.1.tgz", - "integrity": "sha512-Ngb95ryR5A9xqvQFT5mAmYkCwbXvoLavLFwmi7zVg/IowFPCfiqRfkOKnbc/ZRL8ZKJ4f+Tp6kSu6wjDQb8L/g==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", + "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.17.1", - "@smithy/middleware-endpoint": "^4.3.5", - "@smithy/middleware-stack": "^4.2.3", - "@smithy/protocol-http": "^5.3.3", - "@smithy/types": "^4.8.0", - "@smithy/util-stream": "^4.5.4", + "@smithy/core": "^3.22.1", + "@smithy/middleware-endpoint": "^4.4.13", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/types": "^4.12.0", + "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" }, "engines": { @@ -8255,9 +8276,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.8.0.tgz", - "integrity": "sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", + "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -8268,14 +8289,14 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.3.tgz", - "integrity": "sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", + "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.3", - "@smithy/types": "^4.8.0", + "@smithy/querystring-parser": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8351,15 +8372,15 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.4.tgz", - "integrity": "sha512-qI5PJSW52rnutos8Bln8nwQZRpyoSRN6k2ajyoUHNMUzmWqHnOJCnDELJuV6m5PML0VkHI+XcXzdB+6awiqYUw==", + "version": "4.3.29", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", + "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.3", - "@smithy/smithy-client": "^4.9.1", - "@smithy/types": "^4.8.0", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8367,18 +8388,18 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.6.tgz", - "integrity": "sha512-c6M/ceBTm31YdcFpgfgQAJaw3KbaLuRKnAz91iMWFLSrgxRpYm03c3bu5cpYojNMfkV9arCUelelKA7XQT36SQ==", + "version": "4.2.32", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", + "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.0", - "@smithy/credential-provider-imds": "^4.2.3", - "@smithy/node-config-provider": "^4.3.3", - "@smithy/property-provider": "^4.2.3", - "@smithy/smithy-client": "^4.9.1", - "@smithy/types": "^4.8.0", + "@smithy/config-resolver": "^4.4.6", + "@smithy/credential-provider-imds": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/property-provider": "^4.2.8", + "@smithy/smithy-client": "^4.11.2", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8386,14 +8407,14 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.3.tgz", - "integrity": "sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", + "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.3", - "@smithy/types": "^4.8.0", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8414,13 +8435,13 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.3.tgz", - "integrity": "sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", + "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.8.0", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8428,14 +8449,14 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.3.tgz", - "integrity": "sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", + "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.3", - "@smithy/types": "^4.8.0", + "@smithy/service-error-classification": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -8443,15 +8464,15 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.4.tgz", - "integrity": "sha512-+qDxSkiErejw1BAIXUFBSfM5xh3arbz1MmxlbMCKanDDZtVEQ7PSKW9FQS0Vud1eI/kYn0oCTVKyNzRlq+9MUw==", + "version": "4.5.11", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", + "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.4", - "@smithy/node-http-handler": "^4.4.3", - "@smithy/types": "^4.8.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", @@ -8490,14 +8511,14 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.3.tgz", - "integrity": "sha512-5+nU///E5sAdD7t3hs4uwvCTWQtTR8JwKwOCSJtBRx0bY1isDo1QwH87vRK86vlFLBTISqoDA2V6xvP6nF1isQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", + "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.3", - "@smithy/types": "^4.8.0", + "@smithy/abort-controller": "^4.2.8", + "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "engines": { @@ -9972,17 +9993,17 @@ } }, "node_modules/@uniswap/router-sdk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@uniswap/router-sdk/-/router-sdk-2.3.2.tgz", - "integrity": "sha512-KyKiYpdZ4tA2vY2139lusS+8sHDzqqX47+XXCkdo1puJYW+SvGByQe2HyVrQwQo79ASJUetB+bYiyXu26opyjA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@uniswap/router-sdk/-/router-sdk-2.4.0.tgz", + "integrity": "sha512-dfMJ/zRnZ+RqanKTUrKR129Z8It8MPFM2Fhtaop6+Vk7BURY8kiyPi/GIUnPmk7H0WSWhdaB+R7zJZR8wuhqlg==", "license": "MIT", "dependencies": { "@ethersproject/abi": "^5.5.0", - "@uniswap/sdk-core": "^7.8.0", + "@uniswap/sdk-core": "^7.10.1", "@uniswap/swap-router-contracts": "^1.3.0", "@uniswap/v2-sdk": "^4.17.0", "@uniswap/v3-sdk": "^3.27.0", - "@uniswap/v4-sdk": "^1.25.1" + "@uniswap/v4-sdk": "^1.27.0" } }, "node_modules/@uniswap/sdk-core": { @@ -10047,24 +10068,24 @@ } }, "node_modules/@uniswap/smart-order-router": { - "version": "4.31.2", - "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-4.31.2.tgz", - "integrity": "sha512-cozZJqH6LY3ndAy2DF/mUXPlr7hUPbEN4cXdStJ4pEh+kWa7iEWFz7tyjWClgxoLQckKd/bSIq9rJwgBgpeQxQ==", + "version": "4.31.10", + "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-4.31.10.tgz", + "integrity": "sha512-Bmg46KXDSfE1AAf1tPg+vDzMngVoGfzdohekhjJ1hIQG13tXcjEykqapQy+CrJUqXLL3lQvZj2uVEKfzCNH74Q==", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", "@types/brotli": "^1.3.4", "@uniswap/default-token-list": "^11.13.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^2.3.2", + "@uniswap/router-sdk": "^2.3.5", "@uniswap/sdk-core": "^7.10.0", "@uniswap/swap-router-contracts": "^1.3.1", "@uniswap/token-lists": "^1.0.0-beta.31", "@uniswap/universal-router": "^1.6.0", - "@uniswap/universal-router-sdk": "^4.29.2", + "@uniswap/universal-router-sdk": "^4.29.5", "@uniswap/v2-sdk": "^4.17.0", "@uniswap/v3-sdk": "^3.27.0", - "@uniswap/v4-sdk": "^1.25.1", + "@uniswap/v4-sdk": "^1.25.6", "async-retry": "^1.3.1", "await-timeout": "^1.1.1", "axios": "^0.21.1", @@ -10206,21 +10227,21 @@ } }, "node_modules/@uniswap/universal-router-sdk": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@uniswap/universal-router-sdk/-/universal-router-sdk-4.29.2.tgz", - "integrity": "sha512-yrTJn4wVPOP/1aB+n8TCpJ5KG+O8eFkv1NXuyQbtbMKPFRyeX2gifPoH/wDxMS5Up9zaFnTDJcJ8Zvq1M+qCdw==", + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@uniswap/universal-router-sdk/-/universal-router-sdk-4.30.0.tgz", + "integrity": "sha512-yO34+VMtqGScCxym0x9C3nTCk5sLef/dkOj5dvnLEs21fXp7k3Y54ih6zCWh2BqJnWcD8/tQdME1jTTy9RsZYA==", "license": "MIT", "dependencies": { "@openzeppelin/contracts": "4.7.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^2.3.2", + "@uniswap/router-sdk": "^2.4.0", "@uniswap/sdk-core": "^7.10.0", - "@uniswap/universal-router": "2.0.0-beta.2", + "@uniswap/universal-router": "2.1.0", "@uniswap/v2-core": "^1.0.1", "@uniswap/v2-sdk": "^4.17.0", "@uniswap/v3-core": "1.0.0", "@uniswap/v3-sdk": "^3.27.0", - "@uniswap/v4-sdk": "^1.25.1", + "@uniswap/v4-sdk": "^1.27.0", "bignumber.js": "^9.0.2", "ethers": "^5.7.0" }, @@ -10235,9 +10256,9 @@ "license": "MIT" }, "node_modules/@uniswap/universal-router-sdk/node_modules/@uniswap/universal-router": { - "version": "2.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@uniswap/universal-router/-/universal-router-2.0.0-beta.2.tgz", - "integrity": "sha512-/USVkWZrOCjLeZluR7Yk8SpfWDUKG/MLcOyuxuwnqM1xCJj5ekguSYhct+Yfo/3t9fsZcnL8vSYgz0MKqAomGg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@uniswap/universal-router/-/universal-router-2.1.0.tgz", + "integrity": "sha512-rt18RUsZd9xDfyVfIONJo+TEQ8w+olOYxu9+A1g4Thil1R7IMa+8mnyVQjdLPK2REhejScDwjYbOGpeaAce0hg==", "license": "GPL-2.0-or-later", "dependencies": { "@openzeppelin/contracts": "5.0.2", @@ -10378,14 +10399,14 @@ } }, "node_modules/@uniswap/v4-sdk": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.25.1.tgz", - "integrity": "sha512-eVif+esfKtkU5rLF5PWN4Yb875I2vSL/Oqs8g3zZCdfizK8IX1RyEwQturDF08OxRMQqA9fRceN/SH275wBnjg==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.27.0.tgz", + "integrity": "sha512-htQFiON12RR4BipyVdzr4XklYjy756bqruBLA8b4n9Wn7QAWemYrDGbnCvmk1xvzvtc4t9WggDlbRjZ5ON34+g==", "license": "MIT", "dependencies": { "@ethersproject/solidity": "^5.0.9", - "@uniswap/sdk-core": "^7.10.0", - "@uniswap/v3-sdk": "3.26.0", + "@uniswap/sdk-core": "^7.10.1", + "@uniswap/v3-sdk": "3.27.0", "tiny-invariant": "^1.1.0", "tiny-warning": "^1.0.3" }, @@ -10393,25 +10414,6 @@ "node": ">=14" } }, - "node_modules/@uniswap/v4-sdk/node_modules/@uniswap/v3-sdk": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/@uniswap/v3-sdk/-/v3-sdk-3.26.0.tgz", - "integrity": "sha512-bcoWNE7ntNNTHMOnDPscIqtIN67fUyrbBKr6eswI2gD2wm5b0YYFBDeh+Qc5Q3117o9i8S7QdftqrU8YSMQUfQ==", - "license": "MIT", - "dependencies": { - "@ethersproject/abi": "^5.5.0", - "@ethersproject/solidity": "^5.0.9", - "@uniswap/sdk-core": "^7.8.0", - "@uniswap/swap-router-contracts": "^1.3.0", - "@uniswap/v3-periphery": "^1.1.1", - "@uniswap/v3-staker": "1.0.0", - "tiny-invariant": "^1.1.0", - "tiny-warning": "^1.0.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -12408,9 +12410,9 @@ "license": "MIT" }, "node_modules/bowser": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", - "integrity": "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", "dev": true, "license": "MIT" }, @@ -14299,9 +14301,9 @@ } }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", "license": "BSD-3-Clause", "peer": true, "engines": { @@ -17321,9 +17323,9 @@ "license": "MIT" }, "node_modules/hardhat": { - "version": "2.28.3", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.28.3.tgz", - "integrity": "sha512-f1WxpCJCXzxDc12MgIIxxkvB2QK40g/atsW4Az5WQFhUXpZx4VFoSfvwYBIRsRbq6xIrgxef+tXuWda5wTLlgA==", + "version": "2.28.4", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.28.4.tgz", + "integrity": "sha512-iQC4WNWjWMz7cVVFqzEBNisUQ/EEEJrWysJ2hRAMTnfXJx6Y11UXdmtz4dHIzvGL0z27XCCaJrcApDPH0KaZEg==", "license": "MIT", "peer": true, "dependencies": { @@ -28586,9 +28588,9 @@ } }, "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" diff --git a/src/shared/i18n/de/invoice.json b/src/shared/i18n/de/invoice.json index 4757c72137..e1bbef8637 100644 --- a/src/shared/i18n/de/invoice.json +++ b/src/shared/i18n/de/invoice.json @@ -35,5 +35,10 @@ } }, "info": "* Info: Der effektive Wechselkurs und damit die effektiv gelieferte Menge wird bestimmt, wenn das Geld bei DFX eingeht und verarbeitet wird.", - "terms": "Es gelten unsere Allgemeinen Geschäftsbedingungen: https://docs.dfx.swiss/de/tnc.html." + "terms": "Es gelten unsere Allgemeinen Geschäftsbedingungen: https://docs.dfx.swiss/de/tnc.html.", + "payment_info": "Zahlungsinformationen", + "payment_info_iban": "IBAN", + "payment_info_bic": "BIC", + "payment_info_recipient": "Empfänger", + "payment_info_reference": "Referenz" } diff --git a/src/shared/i18n/en/invoice.json b/src/shared/i18n/en/invoice.json index 3604db25e2..e015a27f8d 100644 --- a/src/shared/i18n/en/invoice.json +++ b/src/shared/i18n/en/invoice.json @@ -35,5 +35,10 @@ } }, "info": "* Info: The effective exchange rate and thus the effectively delivered quantity is determined when the money is received and processed by DFX.", - "terms": "Our General Terms and Conditions apply: https://docs.dfx.swiss/en/tnc.html." + "terms": "Our General Terms and Conditions apply: https://docs.dfx.swiss/en/tnc.html.", + "payment_info": "Payment Information", + "payment_info_iban": "IBAN", + "payment_info_bic": "BIC", + "payment_info_recipient": "Recipient", + "payment_info_reference": "Reference" } diff --git a/src/shared/i18n/fr/invoice.json b/src/shared/i18n/fr/invoice.json index 52401ed3a4..9a9ec5396c 100644 --- a/src/shared/i18n/fr/invoice.json +++ b/src/shared/i18n/fr/invoice.json @@ -35,5 +35,10 @@ } }, "info": "* Info: Le taux de change effectif et donc la quantité effectivement livrée sont déterminés lorsque l'argent est reçu et traité par DFX.", - "terms": "Nos conditions générales s'appliquent: https://docs.dfx.swiss/fr/tnc.html." + "terms": "Nos conditions générales s'appliquent: https://docs.dfx.swiss/fr/tnc.html.", + "payment_info": "Informations de paiement", + "payment_info_iban": "IBAN", + "payment_info_bic": "BIC", + "payment_info_recipient": "Bénéficiaire", + "payment_info_reference": "Référence" } diff --git a/src/shared/i18n/it/invoice.json b/src/shared/i18n/it/invoice.json index 6e46660b46..e2986dfd2d 100644 --- a/src/shared/i18n/it/invoice.json +++ b/src/shared/i18n/it/invoice.json @@ -35,5 +35,10 @@ } }, "info": "* Info: Il tasso di cambio effettivo e quindi la quantità effettivamente consegnata vengono determinati quando il denaro viene ricevuto e elaborato da DFX.", - "terms": "Si applicano i nostri Termini e Condizioni Generali: https://docs.dfx.swiss/it/tnc.html." + "terms": "Si applicano i nostri Termini e Condizioni Generali: https://docs.dfx.swiss/it/tnc.html.", + "payment_info": "Informazioni di pagamento", + "payment_info_iban": "IBAN", + "payment_info_bic": "BIC", + "payment_info_recipient": "Beneficiario", + "payment_info_reference": "Riferimento" } diff --git a/src/shared/utils/pdf.util.ts b/src/shared/utils/pdf.util.ts index 3a5aab288d..2ede4468ff 100644 --- a/src/shared/utils/pdf.util.ts +++ b/src/shared/utils/pdf.util.ts @@ -1,5 +1,6 @@ import { I18nService } from 'nestjs-i18n'; import PDFDocument from 'pdfkit'; +import { Config } from 'src/config/config'; import { Asset } from 'src/shared/models/asset/asset.entity'; import { PdfLanguage } from 'src/subdomains/supporting/balance/dto/input/get-balance-pdf.dto'; import { PriceCurrency } from 'src/subdomains/supporting/pricing/services/pricing.service'; @@ -7,6 +8,20 @@ import { mm2pt } from 'swissqrbill/utils'; import { dfxLogoBall1, dfxLogoBall2, dfxLogoText } from './logos/dfx-logo'; import { realunitLogoColor, realunitLogoPath } from './logos/realunit-logo'; +export interface GiroCodeData { + name: string; + street?: string; + number?: string; + zip?: string; + city?: string; + country?: string; + iban: string; + bic?: string; + currency?: string; + amount?: number; + reference?: string; +} + export enum PdfBrand { DFX = 'DFX', REALUNIT = 'REALUNIT', @@ -210,4 +225,25 @@ export class PdfUtil { return b.value - a.value; }); } + + static generateGiroCode(data: GiroCodeData): string { + const streetNumber = [data.street, data.number].filter(Boolean).join(' '); + const zipCity = [data.zip, data.city].filter(Boolean).join(' '); + const addressLine = [data.name, streetNumber, zipCity, data.country].filter(Boolean).join(', '); + const amountStr = data.amount && data.currency ? `${data.currency}${data.amount}` : ''; + + return ` +${Config.giroCode.service} +${Config.giroCode.version} +${Config.giroCode.encoding} +${Config.giroCode.transfer} +${data.bic ?? ''} +${addressLine} +${data.iban} +${amountStr} +${Config.giroCode.char} +${Config.giroCode.ref} +${data.reference ?? ''} +`.trim(); + } } diff --git a/src/subdomains/core/buy-crypto/routes/buy/buy.service.ts b/src/subdomains/core/buy-crypto/routes/buy/buy.service.ts index ee856ebae7..480195cf29 100644 --- a/src/subdomains/core/buy-crypto/routes/buy/buy.service.ts +++ b/src/subdomains/core/buy-crypto/routes/buy/buy.service.ts @@ -15,6 +15,7 @@ import { AssetDtoMapper } from 'src/shared/models/asset/dto/asset-dto.mapper'; import { FiatDtoMapper } from 'src/shared/models/fiat/dto/fiat-dto.mapper'; import { PaymentInfoService } from 'src/shared/services/payment-info.service'; import { DfxCron } from 'src/shared/utils/cron'; +import { PdfUtil } from 'src/shared/utils/pdf.util'; import { Util } from 'src/shared/utils/util'; import { RouteService } from 'src/subdomains/core/route/route.service'; import { UserData } from 'src/subdomains/generic/user/models/user-data/user-data.entity'; @@ -443,20 +444,11 @@ export class BuyService { } private generateGiroCode(bankInfo: BankInfoDto & { reference?: string }, dto: GetBuyPaymentInfoDto): string { - const reference = bankInfo.reference ?? ''; - - return ` -${Config.giroCode.service} -${Config.giroCode.version} -${Config.giroCode.encoding} -${Config.giroCode.transfer} -${bankInfo.bic} -${bankInfo.name}, ${bankInfo.street} ${bankInfo.number}, ${bankInfo.zip} ${bankInfo.city}, ${bankInfo.country} -${bankInfo.iban} -${dto.currency.name}${dto.amount} -${Config.giroCode.char} -${Config.giroCode.ref} -${reference} -`.trim(); + return PdfUtil.generateGiroCode({ + ...bankInfo, + currency: dto.currency.name, + amount: dto.amount, + reference: bankInfo.reference, + }); } } diff --git a/src/subdomains/supporting/fiat-output/fiat-output.service.ts b/src/subdomains/supporting/fiat-output/fiat-output.service.ts index f5fafc5070..aec84e4183 100644 --- a/src/subdomains/supporting/fiat-output/fiat-output.service.ts +++ b/src/subdomains/supporting/fiat-output/fiat-output.service.ts @@ -132,7 +132,11 @@ export class FiatOutputService { }); // Validate creditor fields for all types - data comes from frontend or admin DTO - this.validateRequiredCreditorFields(entity); + try { + this.validateRequiredCreditorFields(entity); + } catch (e) { + throw new Error(`Failed to create fiat output for ${type} ${originEntityId}: ${e.message}`); + } if (createReport) entity.reportCreated = false; diff --git a/src/subdomains/supporting/payment/services/swiss-qr.service.ts b/src/subdomains/supporting/payment/services/swiss-qr.service.ts index 9d166429d1..5eaf723351 100644 --- a/src/subdomains/supporting/payment/services/swiss-qr.service.ts +++ b/src/subdomains/supporting/payment/services/swiss-qr.service.ts @@ -1,6 +1,7 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { I18nService } from 'nestjs-i18n'; import PDFDocument from 'pdfkit'; +import * as QRCode from 'qrcode'; import { Config } from 'src/config/config'; import { AssetService } from 'src/shared/models/asset/asset.service'; import { LogoSize, PdfBrand, PdfUtil } from 'src/shared/utils/pdf.util'; @@ -80,7 +81,7 @@ export class SwissQRService { tableData, language, data, - true, + bankInfo, TransactionType.BUY, PdfBrand.DFX, request.userData.completeName, @@ -109,7 +110,7 @@ export class SwissQRService { tableData, language, billData, - !!bankInfo, + bankInfo, transactionType, brand, transaction.userData.completeName, @@ -146,11 +147,11 @@ export class SwissQRService { return this.generateMultiPdfInvoice(tableDataWithType, language, billData, brand); } - private generatePdfInvoice( + private async generatePdfInvoice( tableData: SwissQRBillTableData, language: string, billData: QrBillData, - includeQrBill: boolean, + bankInfo: BankInfoDto | undefined, transactionType: TransactionType, brand: PdfBrand = PdfBrand.DFX, debtorName?: string, @@ -176,7 +177,7 @@ export class SwissQRService { backgroundColor: '#4A4D51', columns: [ { - text: this.translate('invoice.table.headers.quantity', language) + (includeQrBill ? ' *' : ''), + text: this.translate('invoice.table.headers.quantity', language) + (bankInfo ? ' *' : ''), width: mm2pt(40), }, { @@ -292,9 +293,7 @@ export class SwissQRService { const termsAndConditions = this.getTermsAndConditions(language); - // QR-Bill - let qrBill: SwissQRBill = null; - if (includeQrBill) { + if (bankInfo) { rows.push({ columns: [ { @@ -305,16 +304,60 @@ export class SwissQRService { }, ], }); - - rows.push({ columns: [termsAndConditions] }); - qrBill = new SwissQRBill(billData, { language: language as SupportedInvoiceLanguage }); - } else { - rows.push({ columns: [termsAndConditions] }); } + rows.push({ columns: [termsAndConditions] }); const table = new Table({ rows, width: mm2pt(170) }); table.attachTo(pdf); - qrBill?.attachTo(pdf); + + // QR-Bill (Swiss/LI IBAN) or GiroCode (other IBANs) + const isDomesticTransfer = bankInfo && Config.isDomesticIban(bankInfo.iban); + if (isDomesticTransfer) { + const qrBill = new SwissQRBill(billData, { language: language as SupportedInvoiceLanguage }); + qrBill.attachTo(pdf); + } else if (bankInfo) { + const qrSize = 25; // mm + const qrX = mm2pt(20); + const textX = mm2pt(20 + qrSize + 5); + const startY = pdf.y + 15; + + // GiroCode QR + const giroCode = PdfUtil.generateGiroCode({ + ...bankInfo, + currency: billData.currency, + amount: billData.amount, + reference: billData.message, + }); + const qrDataUrl = await QRCode.toDataURL(giroCode, { width: 150, margin: 1 }); + const qrImage = qrDataUrl.replace(/^data:image\/png;base64,/, ''); + + pdf.image(Buffer.from(qrImage, 'base64'), qrX, startY, { width: mm2pt(qrSize) }); + + // Payment info text + const recipientAddress = [bankInfo.street, bankInfo.number].filter(Boolean).join(' '); + const recipientCity = [bankInfo.zip, bankInfo.city].filter(Boolean).join(' '); + const recipientFull = [bankInfo.name, recipientAddress, recipientCity, bankInfo.country] + .filter(Boolean) + .join(', '); + + pdf.font('Helvetica-Bold').fontSize(11); + pdf.text(this.translate('invoice.payment_info', language), textX, startY); + + const paymentInfoData = [ + { label: this.translate('invoice.payment_info_recipient', language), value: recipientFull }, + { label: this.translate('invoice.payment_info_iban', language), value: bankInfo.iban }, + { label: this.translate('invoice.payment_info_bic', language), value: bankInfo.bic ?? '' }, + { label: this.translate('invoice.payment_info_reference', language), value: billData.message ?? '' }, + ]; + + pdf.fontSize(10); + let currentY = startY + 18; + for (const { label, value } of paymentInfoData) { + pdf.font('Helvetica-Bold').text(label, textX, currentY, { continued: true }); + pdf.font('Helvetica').text(` ${value}`); + currentY += 14; + } + } pdf.end(); diff --git a/src/subdomains/supporting/payment/services/transaction-helper.ts b/src/subdomains/supporting/payment/services/transaction-helper.ts index 0dd6b932ab..d6972a33c0 100644 --- a/src/subdomains/supporting/payment/services/transaction-helper.ts +++ b/src/subdomains/supporting/payment/services/transaction-helper.ts @@ -528,8 +528,10 @@ export class TransactionHelper implements OnModuleInit { if (transaction.buyCrypto && !transaction.buyCrypto.isCryptoCryptoTransaction) { const fiat = await this.fiatService.getFiatByName(transaction.buyCrypto.inputAsset); const buy = transaction.buyCrypto.buy; + const isCardPayment = transaction.buyCrypto.paymentMethodIn === FiatPaymentMethod.CARD; const bankInfo = statementType === TxStatementType.INVOICE && + !isCardPayment && (await this.buyService.getBankInfo( { amount: transaction.buyCrypto.outputAmount, From 430102c668b399f9f59680c8ed9b7662ee8af8cb Mon Sep 17 00:00:00 2001 From: Yannick <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:00:07 +0100 Subject: [PATCH 05/14] [NOTASK] amlRule 16 only for buyCryptos (#3122) --- src/subdomains/core/aml/enums/aml-rule.enum.ts | 2 +- src/subdomains/core/aml/services/aml-helper.service.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/subdomains/core/aml/enums/aml-rule.enum.ts b/src/subdomains/core/aml/enums/aml-rule.enum.ts index 3050b4467c..e12f0b4122 100644 --- a/src/subdomains/core/aml/enums/aml-rule.enum.ts +++ b/src/subdomains/core/aml/enums/aml-rule.enum.ts @@ -16,7 +16,7 @@ export enum AmlRule { RULE_13 = 13, // Checkout BankTransactionVerificationDate & KycLevel 50 RULE_14 = 14, // No phoneCallCheck RULE_15 = 15, // Force Manual Check - RULE_16 = 16, // Force phoneCallCheck for personal accounts + RULE_16 = 16, // Force phoneCallCheck for buyCrypto with personal accounts } export const SpecialIpCountries = ['CH']; diff --git a/src/subdomains/core/aml/services/aml-helper.service.ts b/src/subdomains/core/aml/services/aml-helper.service.ts index bb338e680b..124b9e9a68 100644 --- a/src/subdomains/core/aml/services/aml-helper.service.ts +++ b/src/subdomains/core/aml/services/aml-helper.service.ts @@ -437,7 +437,11 @@ export class AmlHelperService { break; case AmlRule.RULE_16: - if (entity.userData.accountType === AccountType.PERSONAL && !entity.userData.phoneCallCheckDate) + if ( + entity instanceof BuyCrypto && + entity.userData.accountType === AccountType.PERSONAL && + !entity.userData.phoneCallCheckDate + ) errors.push(AmlError.PHONE_VERIFICATION_NEEDED); break; } From f22c47d1e017eb3feb14a393271cfab744103dc3 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:03:19 +0100 Subject: [PATCH 06/14] fix: set home directory (#3123) --- src/integration/blockchain/clementine/clementine-client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/integration/blockchain/clementine/clementine-client.ts b/src/integration/blockchain/clementine/clementine-client.ts index 81ca02a3c6..66d8cbc557 100644 --- a/src/integration/blockchain/clementine/clementine-client.ts +++ b/src/integration/blockchain/clementine/clementine-client.ts @@ -325,6 +325,7 @@ export class ClementineClient { const proc = spawn(this.config.cliPath, args, { stdio: ['ignore', 'pipe', 'pipe'], + env: { ...process.env, HOME: '/home' }, }); const cleanup = (): void => { From 0b8dbdad7d0c68ef457da586062c7a1439b46df3 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:44:53 +0100 Subject: [PATCH 07/14] fix: Clementine mainnet/testnet (#3124) * fix: mainnet/testnet * fix: improved safety checks --- .../actions/clementine-bridge.adapter.ts | 117 +++++++++++++++--- 1 file changed, 99 insertions(+), 18 deletions(-) diff --git a/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts b/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts index 6c2ea0af8d..c37cd20b3e 100644 --- a/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts +++ b/src/subdomains/core/liquidity-management/adapters/actions/clementine-bridge.adapter.ts @@ -1,8 +1,11 @@ import { Injectable } from '@nestjs/common'; import { GetConfig } from 'src/config/config'; -import { BitcoinClient } from 'src/integration/blockchain/bitcoin/node/bitcoin-client'; +import { BitcoinTestnet4Service } from 'src/integration/blockchain/bitcoin-testnet4/bitcoin-testnet4.service'; +import { BitcoinTestnet4FeeService } from 'src/integration/blockchain/bitcoin-testnet4/services/bitcoin-testnet4-fee.service'; +import { BitcoinBasedClient } from 'src/integration/blockchain/bitcoin/node/bitcoin-based-client'; import { BitcoinNodeType, BitcoinService } from 'src/integration/blockchain/bitcoin/node/bitcoin.service'; import { BitcoinFeeService } from 'src/integration/blockchain/bitcoin/services/bitcoin-fee.service'; +import { CitreaTestnetService } from 'src/integration/blockchain/citrea-testnet/citrea-testnet.service'; import { CitreaClient } from 'src/integration/blockchain/citrea/citrea-client'; import { CitreaService } from 'src/integration/blockchain/citrea/citrea.service'; import { @@ -14,7 +17,7 @@ import { import { ClementineService } from 'src/integration/blockchain/clementine/clementine.service'; import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum'; import { isAsset } from 'src/shared/models/active'; -import { AssetType } from 'src/shared/models/asset/asset.entity'; +import { Asset, AssetType } from 'src/shared/models/asset/asset.entity'; import { AssetService } from 'src/shared/models/asset/asset.service'; import { DfxLogger } from 'src/shared/services/dfx-logger'; import { LiquidityManagementOrder } from '../../entities/liquidity-management-order.entity'; @@ -50,6 +53,18 @@ const BTC_ADDRESS_PREFIXES: Record = { [ClementineNetwork.TESTNET4]: ['tb1', 'bcrt1', 'm', 'n', '2'], }; +// Expected Bitcoin chain names by network +const BTC_CHAIN_NAMES: Record = { + [ClementineNetwork.BITCOIN]: 'main', + [ClementineNetwork.TESTNET4]: 'testnet4', +}; + +// Expected Citrea chain IDs by network +const CITREA_CHAIN_IDS: Record = { + [ClementineNetwork.BITCOIN]: 4114, // Citrea mainnet + [ClementineNetwork.TESTNET4]: 5115, // Citrea testnet +}; + interface WithdrawCorrelationData { step: string; signerAddress: string; @@ -67,7 +82,7 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { protected commands = new Map(); private readonly clementineClient: ClementineClient; - private readonly bitcoinClient: BitcoinClient; + private readonly bitcoinClient: BitcoinBasedClient; private readonly citreaClient: CitreaClient; private readonly recoveryTaprootAddress: string; private readonly signerAddress: string; @@ -77,9 +92,12 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { constructor( clementineService: ClementineService, bitcoinService: BitcoinService, + bitcoinTestnet4Service: BitcoinTestnet4Service, citreaService: CitreaService, + citreaTestnetService: CitreaTestnetService, private readonly assetService: AssetService, private readonly bitcoinFeeService: BitcoinFeeService, + private readonly bitcoinTestnet4FeeService: BitcoinTestnet4FeeService, ) { super(LiquidityManagementSystem.CLEMENTINE_BRIDGE); @@ -89,8 +107,12 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { this.signerAddress = config.signerAddress; this.clementineClient = clementineService.getDefaultClient(); - this.bitcoinClient = bitcoinService.getDefaultClient(BitcoinNodeType.BTC_OUTPUT); - this.citreaClient = citreaService.getDefaultClient(); + this.bitcoinClient = this.isTestnet + ? bitcoinTestnet4Service.getDefaultClient() + : bitcoinService.getDefaultClient(BitcoinNodeType.BTC_OUTPUT); + this.citreaClient = this.isTestnet + ? citreaTestnetService.getDefaultClient() + : citreaService.getDefaultClient(); this.commands.set(ClementineBridgeCommands.DEPOSIT, this.deposit.bind(this)); this.commands.set(ClementineBridgeCommands.WITHDRAW, this.withdraw.bind(this)); @@ -129,8 +151,10 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { } = order; // Validate asset is cBTC on Citrea - if (citreaAsset.type !== AssetType.COIN || citreaAsset.blockchain !== Blockchain.CITREA) { - throw new OrderNotProcessableException('Clementine deposit only supports cBTC (native coin) on Citrea'); + if (citreaAsset.type !== AssetType.COIN || citreaAsset.blockchain !== this.citreaBlockchain) { + throw new OrderNotProcessableException( + `Clementine deposit only supports cBTC (native coin) on ${this.citreaBlockchain}`, + ); } // Validate configuration @@ -139,10 +163,10 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { } // Validate network consistency on first use - this.validateNetworkConsistency(); + await this.validateNetworkConsistency(); // Get the corresponding Bitcoin asset - const bitcoinAsset = await this.assetService.getBtcCoin(); + const bitcoinAsset = await this.getBtcAsset(); // Check BTC balance on Bitcoin node - must have at least 10 BTC (fixed bridge amount) const btcBalance = await this.bitcoinClient.getNativeCoinBalance(); @@ -191,8 +215,10 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { } = order; // Validate asset is BTC on Bitcoin - if (bitcoinAsset.type !== AssetType.COIN || bitcoinAsset.blockchain !== Blockchain.BITCOIN) { - throw new OrderNotProcessableException('Clementine withdraw only supports BTC (native coin) on Bitcoin'); + if (bitcoinAsset.type !== AssetType.COIN || bitcoinAsset.blockchain !== this.btcBlockchain) { + throw new OrderNotProcessableException( + `Clementine withdraw only supports BTC (native coin) on ${this.btcBlockchain}`, + ); } // Validate configuration @@ -201,10 +227,10 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { } // Validate network consistency on first use - this.validateNetworkConsistency(); + await this.validateNetworkConsistency(); // Get the corresponding Citrea cBTC asset - const citreaAsset = await this.assetService.getCitreaCoin(); + const citreaAsset = await this.getCitreaAsset(); // Check cBTC balance on Citrea - must have at least 10 cBTC (fixed bridge amount) const cbtcBalance = await this.citreaClient.getNativeCoinBalance(); @@ -222,7 +248,9 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { this.logger.verbose(`Withdrawal started for ${this.signerAddress} -> ${destinationAddress}`); // Step 2: Send 330 satoshis to signer address to create withdrawal UTXO - const dustTxId = await this.sendBtcToAddress(this.signerAddress, CLEMENTINE_WITHDRAWAL_DUST_BTC); + // Strip 'wit' prefix as Bitcoin nodes only accept standard addresses + const signerBtcAddress = this.signerAddress.replace(/^wit/, ''); + const dustTxId = await this.sendBtcToAddress(signerBtcAddress, CLEMENTINE_WITHDRAWAL_DUST_BTC); this.logger.verbose(`Sent ${CLEMENTINE_WITHDRAWAL_DUST_BTC} BTC (330 sats) to signer: ${dustTxId}`); // Update order with fixed amount @@ -428,7 +456,11 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { } // Check if 12 hours have passed - if so, send to operators - const elapsed = Date.now() - (data.sentToBridgeAt ?? 0); + if (!data.sentToBridgeAt) { + this.logger.warn('Withdrawal: sentToBridgeAt missing, skipping operator escalation check'); + return false; + } + const elapsed = Date.now() - data.sentToBridgeAt; if (elapsed > OPTIMISTIC_TIMEOUT_MS) { this.logger.verbose(`Withdrawal: 12 hours elapsed, sending to operators`); @@ -469,7 +501,7 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { //*** HELPER METHODS ***// private async sendBtcToAddress(address: string, amount: number): Promise { - const feeRate = await this.bitcoinFeeService.getRecommendedFeeRate(); + const feeRate = await this.getFeeRate(); const txId = await this.bitcoinClient.sendMany([{ addressTo: address, amount }], feeRate); if (!txId) { @@ -490,15 +522,36 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { //*** NETWORK VALIDATION ***// /** - * Validates that all configured addresses match the expected network (mainnet/testnet). + * Validates that all configured addresses and connected nodes match the expected network (mainnet/testnet). * Throws an error on first use if there's a network mismatch to prevent fund loss. * Only validates once, subsequent calls are skipped. */ - private validateNetworkConsistency(): void { + private async validateNetworkConsistency(): Promise { if (this.networkValidated) return; const errors: string[] = []; + // Validate Bitcoin node is on correct network + try { + const btcInfo = await this.bitcoinClient.getInfo(); + const expectedChain = BTC_CHAIN_NAMES[this.network]; + if (btcInfo.chain !== expectedChain) { + errors.push( + `Bitcoin node is on '${btcInfo.chain}' but Clementine network is '${this.network}' (expected '${expectedChain}')`, + ); + } + } catch (e) { + errors.push(`Failed to verify Bitcoin node network: ${e.message}`); + } + + // Validate Citrea client is on correct network + const expectedCitreaChainId = CITREA_CHAIN_IDS[this.network]; + if (this.citreaClient.chainId !== expectedCitreaChainId) { + errors.push( + `Citrea client chainId is ${this.citreaClient.chainId} but expected ${expectedCitreaChainId} for Clementine network '${this.network}'`, + ); + } + // Validate Bitcoin wallet address const btcWalletAddress = this.bitcoinClient.walletAddress; if (btcWalletAddress && !this.isAddressForNetwork(btcWalletAddress, this.network)) { @@ -540,4 +593,32 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter { const prefixes = BTC_ADDRESS_PREFIXES[network]; return prefixes.some((prefix) => address.startsWith(prefix)); } + + //*** NETWORK HELPERS ***// + + private get isTestnet(): boolean { + return this.network === ClementineNetwork.TESTNET4; + } + + private get btcBlockchain(): Blockchain { + return this.isTestnet ? Blockchain.BITCOIN_TESTNET4 : Blockchain.BITCOIN; + } + + private get citreaBlockchain(): Blockchain { + return this.isTestnet ? Blockchain.CITREA_TESTNET : Blockchain.CITREA; + } + + private getBtcAsset(): Promise { + return this.isTestnet ? this.assetService.getBitcoinTestnet4Coin() : this.assetService.getBtcCoin(); + } + + private getCitreaAsset(): Promise { + return this.isTestnet ? this.assetService.getCitreaTestnetCoin() : this.assetService.getCitreaCoin(); + } + + private getFeeRate(): Promise { + return this.isTestnet + ? this.bitcoinTestnet4FeeService.getRecommendedFeeRate() + : this.bitcoinFeeService.getRecommendedFeeRate(); + } } From 68e8cb912e87aef14984a4008f1aedda939dc164 Mon Sep 17 00:00:00 2001 From: Yannick <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:27:28 +0100 Subject: [PATCH 08/14] [DEV-4538] expand support data limit request (#3107) * [DEV-4538] expand supportData limitRequest * [DEV-4538] Refactoring * [DEV-4538] Refactoring 2 --- .../dto/support-issue-dto.mapper.ts | 19 ++++- .../support-issue/dto/support-issue.dto.ts | 84 ++++++++++++------- .../services/support-issue.service.ts | 6 +- .../support-issue/support-issue.controller.ts | 4 +- 4 files changed, 78 insertions(+), 35 deletions(-) diff --git a/src/subdomains/supporting/support-issue/dto/support-issue-dto.mapper.ts b/src/subdomains/supporting/support-issue/dto/support-issue-dto.mapper.ts index 43c3934a8b..f44256a578 100644 --- a/src/subdomains/supporting/support-issue/dto/support-issue-dto.mapper.ts +++ b/src/subdomains/supporting/support-issue/dto/support-issue-dto.mapper.ts @@ -1,3 +1,4 @@ +import { UserRole } from 'src/shared/auth/user-role.enum'; import { CountryDtoMapper } from 'src/shared/models/country/dto/country-dto.mapper'; import { UserData } from 'src/subdomains/generic/user/models/user-data/user-data.entity'; import { Transaction } from '../../payment/entities/transaction.entity'; @@ -8,6 +9,7 @@ import { SupportIssueDto, SupportIssueInternalAccountDataDto, SupportIssueInternalDataDto, + SupportIssueInternalLimitRequestDataDto, SupportIssueInternalTransactionDataDto, SupportIssueLimitRequestDto, SupportIssueStateMapper, @@ -32,7 +34,7 @@ export class SupportIssueDtoMapper { return Object.assign(new SupportIssueDto(), dto); } - static mapSupportIssueData(supportIssue: SupportIssue): SupportIssueInternalDataDto { + static mapSupportIssueData(supportIssue: SupportIssue, role: UserRole): SupportIssueInternalDataDto { const dto: SupportIssueInternalDataDto = { id: supportIssue.id, created: supportIssue.created, @@ -44,6 +46,8 @@ export class SupportIssueDtoMapper { name: supportIssue.name, account: SupportIssueDtoMapper.mapUserData(supportIssue.userData), transaction: SupportIssueDtoMapper.mapTransactionData(supportIssue.transaction), + limitRequest: + role === UserRole.SUPPORT ? undefined : SupportIssueDtoMapper.mapLimitRequestData(supportIssue.limitRequest), }; return Object.assign(new SupportIssueInternalDataDto(), dto); @@ -61,6 +65,19 @@ export class SupportIssueDtoMapper { return Object.assign(new SupportMessageDto(), dto); } + static mapLimitRequestData(limitRequest: LimitRequest): SupportIssueInternalLimitRequestDataDto { + if (!limitRequest?.id) return undefined; + + return { + id: limitRequest.id, + fundOrigin: limitRequest.fundOrigin, + investmentDate: limitRequest.investmentDate, + limit: limitRequest.limit, + acceptedLimit: limitRequest.acceptedLimit, + decision: limitRequest.decision, + }; + } + static mapUserData(userData: UserData): SupportIssueInternalAccountDataDto { return { id: userData.id, diff --git a/src/subdomains/supporting/support-issue/dto/support-issue.dto.ts b/src/subdomains/supporting/support-issue/dto/support-issue.dto.ts index 4de910dd01..74a2bd1491 100644 --- a/src/subdomains/supporting/support-issue/dto/support-issue.dto.ts +++ b/src/subdomains/supporting/support-issue/dto/support-issue.dto.ts @@ -6,6 +6,7 @@ import { CheckStatus } from 'src/subdomains/core/aml/enums/check-status.enum'; import { AccountType } from 'src/subdomains/generic/user/models/user-data/account-type.enum'; import { KycLevel, UserDataStatus } from 'src/subdomains/generic/user/models/user-data/user-data.enum'; import { TransactionSourceType, TransactionTypeInternal } from '../../payment/entities/transaction.entity'; +import { FundOrigin, InvestmentDate, LimitRequestDecision } from '../entities/limit-request.entity'; import { Department } from '../enums/department.enum'; import { SupportIssueInternalState, @@ -88,20 +89,20 @@ export class SupportIssueInternalAccountDataDto { @ApiProperty({ enum: UserDataStatus }) status: UserDataStatus; - @ApiProperty() - verifiedName: string; + @ApiPropertyOptional() + verifiedName?: string; - @ApiProperty() - completeName: string; + @ApiPropertyOptional() + completeName?: string; - @ApiProperty({ enum: AccountType }) - accountType: AccountType; + @ApiPropertyOptional({ enum: AccountType }) + accountType?: AccountType; @ApiProperty({ enum: KycLevel }) kycLevel: KycLevel; - @ApiProperty() - depositLimit: number; + @ApiPropertyOptional() + depositLimit?: number; @ApiProperty() annualVolume: number; @@ -109,8 +110,8 @@ export class SupportIssueInternalAccountDataDto { @ApiProperty() kycHash: string; - @ApiProperty({ type: CountryDto }) - country: CountryDto; + @ApiPropertyOptional({ type: CountryDto }) + country?: CountryDto; } export class SupportIssueInternalWalletDto { @@ -134,38 +135,58 @@ export class SupportIssueInternalTransactionDataDto { @ApiProperty({ enum: TransactionTypeInternal }) type: TransactionTypeInternal; - @ApiProperty({ enum: CheckStatus }) - amlCheck: CheckStatus; + @ApiPropertyOptional({ enum: CheckStatus }) + amlCheck?: CheckStatus; - @ApiProperty({ enum: AmlReason }) - amlReason: AmlReason; + @ApiPropertyOptional({ enum: AmlReason }) + amlReason?: AmlReason; - @ApiProperty() - comment: string; + @ApiPropertyOptional() + comment?: string; - @ApiProperty() - inputAmount: number; + @ApiPropertyOptional() + inputAmount?: number; - @ApiProperty() - inputAsset: string; + @ApiPropertyOptional() + inputAsset?: string; @ApiPropertyOptional({ enum: Blockchain }) inputBlockchain?: Blockchain; - @ApiProperty() - outputAmount: number; + @ApiPropertyOptional() + outputAmount?: number; - @ApiProperty() - outputAsset: string; + @ApiPropertyOptional() + outputAsset?: string; @ApiPropertyOptional({ enum: Blockchain }) outputBlockchain?: Blockchain; - @ApiProperty({ type: SupportIssueInternalWalletDto }) - wallet: SupportIssueInternalWalletDto; + @ApiPropertyOptional({ type: SupportIssueInternalWalletDto }) + wallet?: SupportIssueInternalWalletDto; + + @ApiPropertyOptional() + isComplete?: boolean; +} + +export class SupportIssueInternalLimitRequestDataDto { + @ApiProperty() + id: number; + + @ApiProperty() + limit: number; + + @ApiPropertyOptional() + acceptedLimit?: number; + + @ApiProperty() + investmentDate: InvestmentDate; @ApiProperty() - isComplete: boolean; + fundOrigin: FundOrigin; + + @ApiPropertyOptional() + decision?: LimitRequestDecision; } export class SupportIssueInternalDataDto { @@ -181,7 +202,7 @@ export class SupportIssueInternalDataDto { @ApiProperty({ enum: SupportIssueType }) type: SupportIssueType; - @ApiProperty({ enum: Department }) + @ApiPropertyOptional({ enum: Department }) department?: Department; @ApiProperty({ enum: SupportIssueReason }) @@ -196,8 +217,11 @@ export class SupportIssueInternalDataDto { @ApiProperty({ type: SupportIssueInternalAccountDataDto }) account: SupportIssueInternalAccountDataDto; - @ApiProperty({ type: SupportIssueInternalTransactionDataDto }) - transaction: SupportIssueInternalTransactionDataDto; + @ApiPropertyOptional({ type: SupportIssueInternalTransactionDataDto }) + transaction?: SupportIssueInternalTransactionDataDto; + + @ApiPropertyOptional({ type: SupportIssueInternalLimitRequestDataDto }) + limitRequest?: SupportIssueInternalLimitRequestDataDto; } export const SupportIssueStateMapper: { diff --git a/src/subdomains/supporting/support-issue/services/support-issue.service.ts b/src/subdomains/supporting/support-issue/services/support-issue.service.ts index 390e4db426..27c6620dbc 100644 --- a/src/subdomains/supporting/support-issue/services/support-issue.service.ts +++ b/src/subdomains/supporting/support-issue/services/support-issue.service.ts @@ -7,6 +7,7 @@ import { } from '@nestjs/common'; import { Config } from 'src/config/config'; import { BlobContent } from 'src/integration/infrastructure/azure-storage.service'; +import { UserRole } from 'src/shared/auth/user-role.enum'; import { FiatService } from 'src/shared/models/fiat/fiat.service'; import { Util } from 'src/shared/utils/util'; import { ContentType } from 'src/subdomains/generic/kyc/enums/content-type.enum'; @@ -222,7 +223,7 @@ export class SupportIssueService { return SupportIssueDtoMapper.mapSupportIssue(issue); } - async getIssueData(id: number): Promise { + async getIssueData(id: number, role: UserRole): Promise { const issue = await this.supportIssueRepo.findOne({ where: { id }, relations: { @@ -231,11 +232,12 @@ export class SupportIssueService { buyCrypto: { transaction: true, cryptoInput: true }, buyFiat: { transaction: true, cryptoInput: true }, }, + limitRequest: true, }, }); if (!issue) throw new NotFoundException('Support issue not found'); - return SupportIssueDtoMapper.mapSupportIssueData(issue); + return SupportIssueDtoMapper.mapSupportIssueData(issue, role); } async getIssueFile(id: string, messageId: number, userDataId?: number): Promise { diff --git a/src/subdomains/supporting/support-issue/support-issue.controller.ts b/src/subdomains/supporting/support-issue/support-issue.controller.ts index 27865086fd..16edfa1c00 100644 --- a/src/subdomains/supporting/support-issue/support-issue.controller.ts +++ b/src/subdomains/supporting/support-issue/support-issue.controller.ts @@ -69,8 +69,8 @@ export class SupportIssueController { @ApiBearerAuth() @ApiExcludeEndpoint() @UseGuards(AuthGuard(), RoleGuard(UserRole.SUPPORT), UserActiveGuard()) - async getIssueData(@Param('id') id: string): Promise { - return this.supportIssueService.getIssueData(+id); + async getIssueData(@GetJwt() jwt: JwtPayload, @Param('id') id: string): Promise { + return this.supportIssueService.getIssueData(+id, jwt.role); } @Post(':id/message') From 359b9c53b92e3a5bc67f81a363bd775837df33bd Mon Sep 17 00:00:00 2001 From: Yannick <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:32:10 +0100 Subject: [PATCH 09/14] [DEV-4543] SupportIssue sepa template (#3076) * [DEV-4543] SupportIssue sepa template * [DEV-4543] fix linter * [DEV-4543] Refactoring * [DEV-4543] Refactoring 2 * [DEV-4543] fix undefined bug * [DEV-4543] renaming setting --- src/shared/i18n/de/support-issue.json | 4 +- src/shared/i18n/en/support-issue.json | 4 +- src/shared/i18n/es/support-issue.json | 4 +- src/shared/i18n/fr/support-issue.json | 4 +- src/shared/i18n/it/support-issue.json | 4 +- src/shared/i18n/pt/support-issue.json | 4 +- src/shared/utils/util.ts | 11 ++++ .../support-issue/dto/support-issue.dto.ts | 2 + .../services/support-issue-job.service.ts | 51 ++++++++++++++++--- 9 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/shared/i18n/de/support-issue.json b/src/shared/i18n/de/support-issue.json index 572355bf99..159c3b8787 100644 --- a/src/shared/i18n/de/support-issue.json +++ b/src/shared/i18n/de/support-issue.json @@ -1,4 +1,6 @@ { "bot_hint": "Wenn dir diese Nachricht nicht weiterhilft, kannst du einfach nochmal eine Nachricht hier schreiben und wirst automatisch an einen Support Mitarbeiter übergeben, der sich den Fall anschaut.", - "monero_not_displayed": "❓ Die Kryptolieferung wird in der Cake Wallet nicht angezeigt:\n\n⓵ Verbindung prüfen:\n▪️ Gehe zum Guthaben-Bildschirm in der Cake Wallet\n▪️ überprüfe, ob die Leiste oben eines der folgenden anzeigt\n🟢 „Synchronisiert“ („Synchronised“)\n🟠 „Connecting“ („Verbindung wird hergestellt“)\n🔴 „Blöcke verbleibend“ („blocks remaining“)\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Wallet synchronisieren, falls Blöcke verbleiben:\n▪️ Lass die App geöffnet\n▪️ bleibe auf dem Guthaben-Bildschirm\n▫️ bis die verbleibenden Blöcke auf 0 reduziert sind.\n\n⓷ Blockchain scannen (Neuscan ab Datum):\n▪️ Falls die Synchronisation das Problem nicht löst\n▪️ starte einen Scan der Blockchain-Blöcke\n👉 1–2 Tage vor der Kryptolieferung.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Überprüfen:\nDeine Kryptolieferung sollte nach diesen Schritten\nin der Wallet angezeigt werden.\n\n⓹ Cake Wallet Support kontaktieren\n(falls ⓵ – ⓸ nicht helfen)\nhttps://docs.cakewallet.com/support\n\n🫵 Du kannst auch die „Cake Wallet In-App Support“-Funktion nutzen,\num direkt über die App mit dem Support-Team zu chatten:\n📲 Öffne dazu das Menü und wähle:\nSupport ➔ Live-Support, um dort Kontakt aufzunehmen." + "monero_not_displayed": "❓ Die Kryptolieferung wird in der Cake Wallet nicht angezeigt:\n\n⓵ Verbindung prüfen:\n▪️ Gehe zum Guthaben-Bildschirm in der Cake Wallet\n▪️ überprüfe, ob die Leiste oben eines der folgenden anzeigt\n🟢 „Synchronisiert“ („Synchronised“)\n🟠 „Connecting“ („Verbindung wird hergestellt“)\n🔴 „Blöcke verbleibend“ („blocks remaining“)\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Wallet synchronisieren, falls Blöcke verbleiben:\n▪️ Lass die App geöffnet\n▪️ bleibe auf dem Guthaben-Bildschirm\n▫️ bis die verbleibenden Blöcke auf 0 reduziert sind.\n\n⓷ Blockchain scannen (Neuscan ab Datum):\n▪️ Falls die Synchronisation das Problem nicht löst\n▪️ starte einen Scan der Blockchain-Blöcke\n👉 1–2 Tage vor der Kryptolieferung.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Überprüfen:\nDeine Kryptolieferung sollte nach diesen Schritten\nin der Wallet angezeigt werden.\n\n⓹ Cake Wallet Support kontaktieren\n(falls ⓵ – ⓸ nicht helfen)\nhttps://docs.cakewallet.com/support\n\n🫵 Du kannst auch die „Cake Wallet In-App Support“-Funktion nutzen,\num direkt über die App mit dem Support-Team zu chatten:\n📲 Öffne dazu das Menü und wähle:\nSupport ➔ Live-Support, um dort Kontakt aufzunehmen.", + "sepa_standard": "SEPA-Standard = 🐌 SEPA.\nBanküberweisungen haben unterschiedliche Laufzeiten, bis sie bei DFX eintreffen.\n\n🟢 An Arbeitstagen (tagsüber): im Durchschnitt 3–5 Stunden.\n🟡 An Arbeitstagen (ab nachmittags): meist am nächsten Arbeitstag.\n🟠 An Feiertagen: meist am nächsten Arbeitstag.\n🔴 Am Wochenende: Anfang der Folge-Woche, meist am Montag.\n\n☝️ Dementsprechend lange wartet man,\n▫️ bis die Überweisung schließlich bei DFX angekommen ist,\n▫️ verarbeitet (und geprüft) werden kann,\n▫️ man dann endlich ein Statusupdate bekommt.\n\n❗️ Sobald die Zahlung bei DFX eingegangen ist,\nkannst du den DFX-Verarbeitungsstatus prüfen (einsehen).\nBis dahin – solange die Zahlung noch im Bankensystem kreist – 💸\nbist du von den Bearbeitungszeiten der Banken abhängig. 🙁", + "sepa_weekend": "SEPA-Standard = 🐌 SEPA.\nBanküberweisungen haben unterschiedliche Laufzeiten, bis sie bei DFX eintreffen.\n\n🟢 An Arbeitstagen (tagsüber): im Durchschnitt 3–5 Stunden.\n🟡 An Arbeitstagen (ab nachmittags): meist am nächsten Arbeitstag.\n🟠 An Feiertagen: meist am nächsten Arbeitstag.\n🔴 Am Wochenende: Anfang der Folge-Woche,\nmeist am Montag. ⬅️\n\n⚠️ An Montagen haben Banken die höchste Arbeitslast.\nAlles, was seit Freitag am Nachmittag eingegangen ist,\nkommt zu den Überweisungen vom Montag noch hinzu. ☝️\n\nDaher werden oft nicht alle Überweisungen am Montag verarbeitet,\nwodurch einige erst am Dienstag zugestellt werden. 😳\n\nIm Extremfall kann eine am Freitagnachmittag getätigte Überweisung\nalso erst am Dienstag der folgenden Woche eintreffen.\n‼️ Dies ist kein Einzelfall bei Wochenend-Überweisungen,\nsondern ein häufiges Phänomen.\n\n☝️ Dementsprechend lange wartet man,\n▫️ bis die Überweisung schließlich bei DFX angekommen ist,\n▫️ verarbeitet (und geprüft) werden kann,\n▫️ man dann endlich ein Statusupdate bekommt.\n\n❗️ Sobald die Zahlung bei DFX eingegangen ist,\nkannst du den DFX-Verarbeitungsstatus prüfen (einsehen).\nBis dahin – solange die Zahlung noch im Bankensystem kreist – 💸\nbist du von den Bearbeitungszeiten der Banken abhängig. 🙁" } diff --git a/src/shared/i18n/en/support-issue.json b/src/shared/i18n/en/support-issue.json index 2276ac2a9f..6635cb3fa0 100644 --- a/src/shared/i18n/en/support-issue.json +++ b/src/shared/i18n/en/support-issue.json @@ -1,4 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/i18n/es/support-issue.json b/src/shared/i18n/es/support-issue.json index 2276ac2a9f..6635cb3fa0 100644 --- a/src/shared/i18n/es/support-issue.json +++ b/src/shared/i18n/es/support-issue.json @@ -1,4 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/i18n/fr/support-issue.json b/src/shared/i18n/fr/support-issue.json index 2276ac2a9f..6635cb3fa0 100644 --- a/src/shared/i18n/fr/support-issue.json +++ b/src/shared/i18n/fr/support-issue.json @@ -1,4 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/i18n/it/support-issue.json b/src/shared/i18n/it/support-issue.json index 2276ac2a9f..6635cb3fa0 100644 --- a/src/shared/i18n/it/support-issue.json +++ b/src/shared/i18n/it/support-issue.json @@ -1,4 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/i18n/pt/support-issue.json b/src/shared/i18n/pt/support-issue.json index 2276ac2a9f..6635cb3fa0 100644 --- a/src/shared/i18n/pt/support-issue.json +++ b/src/shared/i18n/pt/support-issue.json @@ -1,4 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/utils/util.ts b/src/shared/utils/util.ts index e6016ff3d5..a65426a8a3 100644 --- a/src/shared/utils/util.ts +++ b/src/shared/utils/util.ts @@ -577,6 +577,17 @@ export class Util { return Promise.allSettled(tasks).then((results) => results.filter(this.filterFulfilledCalls).map((r) => r.value)); } + static partition(list: T[], accessor: (item: T) => boolean): [T[], T[]] { + const truthy: T[] = []; + const falsy: T[] = []; + + for (const item of list) { + (accessor(item) ? truthy : falsy).push(item); + } + + return [truthy, falsy]; + } + private static filterFulfilledCalls(result: PromiseSettledResult): result is PromiseFulfilledResult { return result.status === 'fulfilled'; } diff --git a/src/subdomains/supporting/support-issue/dto/support-issue.dto.ts b/src/subdomains/supporting/support-issue/dto/support-issue.dto.ts index 74a2bd1491..f1726595a4 100644 --- a/src/subdomains/supporting/support-issue/dto/support-issue.dto.ts +++ b/src/subdomains/supporting/support-issue/dto/support-issue.dto.ts @@ -18,6 +18,8 @@ import { export enum SupportMessageTranslationKey { BOT_HINT = 'support-issue.bot_hint', MONERO_NOT_DISPLAYED = 'support-issue.monero_not_displayed', + SEPA_STANDARD = 'support-issue.sepa_standard', + SEPA_WEEKEND = 'support-issue.sepa_weekend', } export class SupportMessageDto { diff --git a/src/subdomains/supporting/support-issue/services/support-issue-job.service.ts b/src/subdomains/supporting/support-issue/services/support-issue-job.service.ts index 80a79dfbec..0940ad9b5a 100644 --- a/src/subdomains/supporting/support-issue/services/support-issue-job.service.ts +++ b/src/subdomains/supporting/support-issue/services/support-issue-job.service.ts @@ -1,11 +1,13 @@ import { Injectable } from '@nestjs/common'; import { CronExpression } from '@nestjs/schedule'; +import { SettingService } from 'src/shared/models/setting/setting.service'; import { Process } from 'src/shared/services/process.service'; import { DfxCron } from 'src/shared/utils/cron'; import { Util } from 'src/shared/utils/util'; import { CheckStatus } from 'src/subdomains/core/aml/enums/check-status.enum'; import { FindOptionsWhere, In, IsNull, MoreThan, Not } from 'typeorm'; import { MailFactory } from '../../notification/factories/mail.factory'; +import { TransactionRequestType } from '../../payment/entities/transaction-request.entity'; import { SupportMessageTranslationKey } from '../dto/support-issue.dto'; import { SupportIssue } from '../entities/support-issue.entity'; import { AutoResponder } from '../entities/support-message.entity'; @@ -13,21 +15,52 @@ import { SupportIssueInternalState, SupportIssueReason, SupportIssueType } from import { SupportIssueRepository } from '../repositories/support-issue.repository'; import { SupportIssueService } from './support-issue.service'; +enum AutoResponse { + MONERO_COMPLETE = 'MoneroComplete', + SEPA = 'Sepa', +} + @Injectable() export class SupportIssueJobService { constructor( private readonly supportIssueRepo: SupportIssueRepository, private readonly supportIssueService: SupportIssueService, private readonly mailFactory: MailFactory, + private readonly settingsService: SettingService, ) {} @DfxCron(CronExpression.EVERY_MINUTE, { process: Process.SUPPORT_BOT, timeout: 1800 }) async sendAutoResponses() { - await this.moneroComplete(); + const disabledTemplates = await this.settingsService + .get('supportBot') + .then((s) => (s?.split(',') ?? []) as AutoResponse[]); + + if (!disabledTemplates.includes(AutoResponse.MONERO_COMPLETE)) await this.moneroComplete(); + if (!disabledTemplates.includes(AutoResponse.SEPA)) await this.sepa(); + } + + async sepa(): Promise { + const issues = await this.getAutoResponseIssues({ + type: SupportIssueType.TRANSACTION_ISSUE, + reason: In([SupportIssueReason.FUNDS_NOT_RECEIVED, SupportIssueReason.TRANSACTION_MISSING]), + transactionRequest: { type: TransactionRequestType.BUY }, + transaction: { id: IsNull() }, + }); + if (!issues.length) return; + + const [standard, weekend] = Util.partition(issues, (i) => { + const day = i.created.getDay(); + const hour = i.created.getHours(); + + return (day === 2 && hour >= 14) || (day > 2 && day < 5) || (day === 5 && hour < 14); + }); + + await this.sendAutoResponse(SupportMessageTranslationKey.SEPA_STANDARD, standard); + await this.sendAutoResponse(SupportMessageTranslationKey.SEPA_WEEKEND, weekend); } async moneroComplete(): Promise { - await this.sendAutoResponse(SupportMessageTranslationKey.MONERO_NOT_DISPLAYED, { + const issues = await this.getAutoResponseIssues({ type: SupportIssueType.TRANSACTION_ISSUE, reason: In([SupportIssueReason.FUNDS_NOT_RECEIVED, SupportIssueReason.TRANSACTION_MISSING]), transaction: { @@ -35,20 +68,24 @@ export class SupportIssueJobService { }, created: MoreThan(Util.daysBefore(2)), }); + await this.sendAutoResponse(SupportMessageTranslationKey.MONERO_NOT_DISPLAYED, issues); } - private async sendAutoResponse( - translationKey: SupportMessageTranslationKey, - where: FindOptionsWhere, - ): Promise { - const entities = await this.supportIssueRepo.find({ + // --- HELPER METHODS --- // + private async getAutoResponseIssues(where: FindOptionsWhere): Promise { + return this.supportIssueRepo.find({ where: { state: SupportIssueInternalState.CREATED, messages: { author: Not(AutoResponder) }, ...where, }, }); + } + private async sendAutoResponse( + translationKey: SupportMessageTranslationKey, + entities: SupportIssue[], + ): Promise { for (const entity of entities) { const lang = entity.userData.language.symbol.toLowerCase(); const message = this.mailFactory.translate(translationKey, lang); From 5ab5fc0f992dcf3019f41ce373490a67787f8a01 Mon Sep 17 00:00:00 2001 From: David May <85513542+davidleomay@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:28:34 +0100 Subject: [PATCH 10/14] Fix: trading improvements (#3126) * fix: improved MEXC error loggin * fix: no-dust trades * feat: Clementine home dir --- src/config/config.ts | 1 + .../clementine/clementine-client.ts | 3 +- .../exchange/services/exchange.service.ts | 34 +++++++++++++------ .../exchange/services/mexc.service.ts | 19 ++++++++--- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 78dcf138b7..8a9924eaef 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -819,6 +819,7 @@ export class Configuration { clementine: { network: (process.env.CLEMENTINE_NETWORK as ClementineNetwork) ?? ClementineNetwork.BITCOIN, cliPath: process.env.CLEMENTINE_CLI_PATH ?? 'clementine-cli', + homeDir: process.env.CLEMENTINE_HOME_DIR ?? '/home', recoveryTaprootAddress: process.env.CLEMENTINE_RECOVERY_TAPROOT_ADDRESS, signerAddress: process.env.CLEMENTINE_SIGNER_ADDRESS, timeoutMs: parseInt(process.env.CLEMENTINE_TIMEOUT_MS ?? '60000'), diff --git a/src/integration/blockchain/clementine/clementine-client.ts b/src/integration/blockchain/clementine/clementine-client.ts index 66d8cbc557..2e82a65fd6 100644 --- a/src/integration/blockchain/clementine/clementine-client.ts +++ b/src/integration/blockchain/clementine/clementine-client.ts @@ -9,6 +9,7 @@ export enum ClementineNetwork { export interface ClementineConfig { network: ClementineNetwork; cliPath: string; + homeDir: string; timeoutMs: number; signingTimeoutMs: number; } @@ -325,7 +326,7 @@ export class ClementineClient { const proc = spawn(this.config.cliPath, args, { stdio: ['ignore', 'pipe', 'pipe'], - env: { ...process.env, HOME: '/home' }, + env: { ...process.env, HOME: this.config.homeDir }, }); const cleanup = (): void => { diff --git a/src/integration/exchange/services/exchange.service.ts b/src/integration/exchange/services/exchange.service.ts index 01523d71f3..702c29b247 100644 --- a/src/integration/exchange/services/exchange.service.ts +++ b/src/integration/exchange/services/exchange.service.ts @@ -178,8 +178,8 @@ export abstract class ExchangeService extends PricingProvider implements OnModul case OrderStatus.CANCELED: { // check for min. amount - const minAmount = await this.getMinTradeAmount(order.symbol); - if (order.remaining < minAmount) { + const { amount: minAmount, cost: minCost } = await this.getMinTradeAmount(order.symbol); + if (order.remaining < minAmount || order.remaining * order.price < minCost) { return true; } @@ -238,8 +238,12 @@ export abstract class ExchangeService extends PricingProvider implements OnModul return this.callApi((e) => e.fetchMarkets()); } - async getMinTradeAmount(pair: string): Promise { - return this.getMarket(pair).then((m) => m.limits.amount.min); + async getMinTradeAmount(pair: string): Promise<{ amount: number; cost: number }> { + const market = await this.getMarket(pair); + return { + amount: market.limits.amount.min ?? 0, + cost: market.limits.cost?.min ?? 0, + }; } private async getPrecision(pair: string): Promise<{ price: number; amount: number }> { @@ -296,8 +300,8 @@ export abstract class ExchangeService extends PricingProvider implements OnModul return this.callApi((e) => e.fetchOrderBook(pair)); } - private async fetchCurrentOrderPrice(pair: string, direction: string): Promise { - const orderBook = await this.fetchOrderBook(pair); + private async fetchCurrentOrderPrice(pair: string, direction: string, orderBook?: OrderBook): Promise { + orderBook ??= await this.fetchOrderBook(pair); const { price: pricePrecision } = await this.getPrecision(pair); @@ -310,15 +314,14 @@ export abstract class ExchangeService extends PricingProvider implements OnModul async getBestBidLiquidity(from: string, to: string): Promise<{ price: number; amount: number } | undefined> { const { pair, direction } = await this.getTradePair(from, to); - const minAmount = await this.getMinTradeAmount(pair); + const { amount: minAmount, cost: minCost } = await this.getMinTradeAmount(pair); const orderBook = await this.fetchOrderBook(pair); const { price: pricePrecision } = await this.getPrecision(pair); const orders = direction === OrderSide.SELL ? orderBook.bids : orderBook.asks; // Find first order that meets minimum amount requirement - const validOrder = orders.find(([, amount]) => amount >= minAmount); - + const validOrder = orders.find(([price, amount]) => amount >= minAmount && price * amount >= minCost); if (!validOrder) return undefined; const [price, amount] = validOrder; @@ -335,9 +338,18 @@ export abstract class ExchangeService extends PricingProvider implements OnModul // place the order const { pair, direction } = await this.getTradePair(from, to); const { amount: amountPrecision } = await this.getPrecision(pair); - const price = await this.fetchCurrentOrderPrice(pair, direction); + const orderBook = await this.fetchOrderBook(pair); + const price = await this.fetchCurrentOrderPrice(pair, direction, orderBook); + + const orders = direction === OrderSide.BUY ? orderBook.asks : orderBook.bids; - const orderAmount = Util.floorToValue(direction === OrderSide.BUY ? amount / price : amount, amountPrecision); + let orderAmount = Util.floorToValue(direction === OrderSide.BUY ? amount / price : amount, amountPrecision); + + // Snap to nearby order book amount to avoid leaving dust + const matchingOrder = orders.find(([, amt]) => Math.abs(amt - orderAmount) <= 2 * amountPrecision); + if (matchingOrder) { + orderAmount = matchingOrder[1]; + } const id = await this.placeOrder(pair, direction, orderAmount, price); diff --git a/src/integration/exchange/services/mexc.service.ts b/src/integration/exchange/services/mexc.service.ts index 2ff8c49e57..4f203cbe4f 100644 --- a/src/integration/exchange/services/mexc.service.ts +++ b/src/integration/exchange/services/mexc.service.ts @@ -166,11 +166,20 @@ export class MexcService extends ExchangeService { const url = `${this.baseUrl}/${path}?${searchParams}`; - return this.http.request({ - url, - method, - headers: { 'X-MEXC-APIKEY': Config.mexc.apiKey, 'Content-Type': 'application/json' }, - }); + try { + return await this.http.request({ + url, + method, + headers: { 'X-MEXC-APIKEY': Config.mexc.apiKey, 'Content-Type': 'application/json' }, + }); + } catch (e) { + // Extract MEXC error details from response body + const mexcError = e.response?.data; + if (mexcError) { + throw new Error(`MEXC API error: ${mexcError.msg ?? JSON.stringify(mexcError)} (code: ${mexcError.code})`); + } + throw e; + } } // --- ZCHF Assessment Period - can be removed once assessment ends --- // From 576b642702a137e0096e4fe006452dc77739c30f Mon Sep 17 00:00:00 2001 From: Yannick <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:52:35 +0100 Subject: [PATCH 11/14] [NOTASK] fix missing videoIdent bug (#3125) * [NOTASK] fix missing videoIdent bug * [NOTASK] Renaming * [NOTASK] Renaming 2 --- src/subdomains/core/aml/services/aml.service.ts | 13 ++++++------- .../services/buy-crypto-preparation.service.ts | 2 +- .../process/services/buy-crypto.service.ts | 4 ++-- .../services/buy-fiat-preparation.service.ts | 2 +- .../process/services/buy-fiat.service.ts | 2 +- .../generic/kyc/services/kyc-admin.service.ts | 2 +- .../user/models/user-data/user-data.service.ts | 4 ++-- 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/subdomains/core/aml/services/aml.service.ts b/src/subdomains/core/aml/services/aml.service.ts index b67c1f5f64..3ad0cb2240 100644 --- a/src/subdomains/core/aml/services/aml.service.ts +++ b/src/subdomains/core/aml/services/aml.service.ts @@ -43,15 +43,14 @@ export class AmlService { private readonly ipLogService: IpLogService, ) {} - async postProcessing( - entity: BuyFiat | BuyCrypto, - amlCheckBefore: CheckStatus, - last30dVolume: number | undefined, - ): Promise { + async postProcessing(entity: BuyFiat | BuyCrypto, last30dVolume: number | undefined): Promise { if (entity.cryptoInput) await this.payInService.updatePayInAction(entity.cryptoInput.id, entity.amlCheck); - if (amlCheckBefore !== entity.amlCheck && entity.amlReason === AmlReason.VIDEO_IDENT_NEEDED) - await this.userDataService.triggerVideoIdent(entity.userData); + if ( + [CheckStatus.PENDING, CheckStatus.GSHEET].includes(entity.amlCheck) && + entity.amlReason === AmlReason.VIDEO_IDENT_NEEDED + ) + await this.userDataService.checkOrTriggerVideoIdent(entity.userData); if (entity.amlCheck === CheckStatus.PASS) { if (entity.user.status === UserStatus.NA) await this.userService.activateUser(entity.user, entity.userData); diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts index e1996d3079..51aae45b2f 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto-preparation.service.ts @@ -189,7 +189,7 @@ export class BuyCryptoPreparationService { ), ); - await this.amlService.postProcessing(entity, amlCheckBefore, last30dVolume); + await this.amlService.postProcessing(entity, last30dVolume); if (amlCheckBefore !== entity.amlCheck) await this.buyCryptoWebhookService.triggerWebhook(entity); diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts index b33aff8a80..8b1a4aa7ab 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts @@ -347,9 +347,9 @@ export class BuyCryptoService { inputReferenceCurrency, ); - await this.amlService.postProcessing(entity, amlCheckBefore, last30dVolume); + await this.amlService.postProcessing(entity, last30dVolume); } else { - await this.amlService.postProcessing(entity, amlCheckBefore, undefined); + await this.amlService.postProcessing(entity, undefined); } } diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat-preparation.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat-preparation.service.ts index 96bbb75209..db48b42aa7 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat-preparation.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat-preparation.service.ts @@ -151,7 +151,7 @@ export class BuyFiatPreparationService { ), ); - await this.amlService.postProcessing(entity, amlCheckBefore, last30dVolume); + await this.amlService.postProcessing(entity, last30dVolume); if (amlCheckBefore !== entity.amlCheck) await this.buyFiatService.triggerWebhook(entity); diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts index 58c0c900cf..dbd75f4cd6 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts @@ -199,7 +199,7 @@ export class BuyFiatService { if (forceUpdate.amlCheck || (!amlCheckBefore && update.amlCheck)) { if (update.amlCheck === CheckStatus.PASS) await this.buyFiatNotificationService.paymentProcessing(entity); - await this.amlService.postProcessing(entity, amlCheckBefore, undefined); + await this.amlService.postProcessing(entity, undefined); } // payment webhook diff --git a/src/subdomains/generic/kyc/services/kyc-admin.service.ts b/src/subdomains/generic/kyc/services/kyc-admin.service.ts index 0d5ff1efc3..59749a0499 100644 --- a/src/subdomains/generic/kyc/services/kyc-admin.service.ts +++ b/src/subdomains/generic/kyc/services/kyc-admin.service.ts @@ -112,7 +112,7 @@ export class KycAdminService { } } - async triggerVideoIdentInternal(userData: UserData): Promise { + async checkOrTriggerVideoIdentInternal(userData: UserData): Promise { try { await this.kycService.getOrCreateStepInternal(KycStepName.IDENT, userData, undefined, KycStepType.SUMSUB_VIDEO); } catch (e) { 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 5b15ec7c29..87e3d9e97a 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 @@ -728,8 +728,8 @@ export class UserDataService { return userWithCustomMethod?.wallet.identMethod; } - async triggerVideoIdent(userData: UserData): Promise { - await this.kycAdminService.triggerVideoIdentInternal(userData); + async checkOrTriggerVideoIdent(userData: UserData): Promise { + await this.kycAdminService.checkOrTriggerVideoIdentInternal(userData); } async setCheckIpRisk(ip: string): Promise { From 0f48ee497bef7e7919df43301d2677b09305e06b Mon Sep 17 00:00:00 2001 From: Yannick <52333989+Yannick1712@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:53:01 +0100 Subject: [PATCH 12/14] [NOTASK] Auto responder template refactoring (#3127) --- src/shared/i18n/de/support-issue.json | 6 +++--- src/shared/i18n/en/support-issue.json | 6 +++--- src/shared/i18n/es/support-issue.json | 6 +++--- src/shared/i18n/fr/support-issue.json | 6 +++--- src/shared/i18n/it/support-issue.json | 6 +++--- src/shared/i18n/pt/support-issue.json | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/shared/i18n/de/support-issue.json b/src/shared/i18n/de/support-issue.json index 159c3b8787..b3d06312d0 100644 --- a/src/shared/i18n/de/support-issue.json +++ b/src/shared/i18n/de/support-issue.json @@ -1,6 +1,6 @@ { "bot_hint": "Wenn dir diese Nachricht nicht weiterhilft, kannst du einfach nochmal eine Nachricht hier schreiben und wirst automatisch an einen Support Mitarbeiter übergeben, der sich den Fall anschaut.", - "monero_not_displayed": "❓ Die Kryptolieferung wird in der Cake Wallet nicht angezeigt:\n\n⓵ Verbindung prüfen:\n▪️ Gehe zum Guthaben-Bildschirm in der Cake Wallet\n▪️ überprüfe, ob die Leiste oben eines der folgenden anzeigt\n🟢 „Synchronisiert“ („Synchronised“)\n🟠 „Connecting“ („Verbindung wird hergestellt“)\n🔴 „Blöcke verbleibend“ („blocks remaining“)\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Wallet synchronisieren, falls Blöcke verbleiben:\n▪️ Lass die App geöffnet\n▪️ bleibe auf dem Guthaben-Bildschirm\n▫️ bis die verbleibenden Blöcke auf 0 reduziert sind.\n\n⓷ Blockchain scannen (Neuscan ab Datum):\n▪️ Falls die Synchronisation das Problem nicht löst\n▪️ starte einen Scan der Blockchain-Blöcke\n👉 1–2 Tage vor der Kryptolieferung.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Überprüfen:\nDeine Kryptolieferung sollte nach diesen Schritten\nin der Wallet angezeigt werden.\n\n⓹ Cake Wallet Support kontaktieren\n(falls ⓵ – ⓸ nicht helfen)\nhttps://docs.cakewallet.com/support\n\n🫵 Du kannst auch die „Cake Wallet In-App Support“-Funktion nutzen,\num direkt über die App mit dem Support-Team zu chatten:\n📲 Öffne dazu das Menü und wähle:\nSupport ➔ Live-Support, um dort Kontakt aufzunehmen.", - "sepa_standard": "SEPA-Standard = 🐌 SEPA.\nBanküberweisungen haben unterschiedliche Laufzeiten, bis sie bei DFX eintreffen.\n\n🟢 An Arbeitstagen (tagsüber): im Durchschnitt 3–5 Stunden.\n🟡 An Arbeitstagen (ab nachmittags): meist am nächsten Arbeitstag.\n🟠 An Feiertagen: meist am nächsten Arbeitstag.\n🔴 Am Wochenende: Anfang der Folge-Woche, meist am Montag.\n\n☝️ Dementsprechend lange wartet man,\n▫️ bis die Überweisung schließlich bei DFX angekommen ist,\n▫️ verarbeitet (und geprüft) werden kann,\n▫️ man dann endlich ein Statusupdate bekommt.\n\n❗️ Sobald die Zahlung bei DFX eingegangen ist,\nkannst du den DFX-Verarbeitungsstatus prüfen (einsehen).\nBis dahin – solange die Zahlung noch im Bankensystem kreist – 💸\nbist du von den Bearbeitungszeiten der Banken abhängig. 🙁", - "sepa_weekend": "SEPA-Standard = 🐌 SEPA.\nBanküberweisungen haben unterschiedliche Laufzeiten, bis sie bei DFX eintreffen.\n\n🟢 An Arbeitstagen (tagsüber): im Durchschnitt 3–5 Stunden.\n🟡 An Arbeitstagen (ab nachmittags): meist am nächsten Arbeitstag.\n🟠 An Feiertagen: meist am nächsten Arbeitstag.\n🔴 Am Wochenende: Anfang der Folge-Woche,\nmeist am Montag. ⬅️\n\n⚠️ An Montagen haben Banken die höchste Arbeitslast.\nAlles, was seit Freitag am Nachmittag eingegangen ist,\nkommt zu den Überweisungen vom Montag noch hinzu. ☝️\n\nDaher werden oft nicht alle Überweisungen am Montag verarbeitet,\nwodurch einige erst am Dienstag zugestellt werden. 😳\n\nIm Extremfall kann eine am Freitagnachmittag getätigte Überweisung\nalso erst am Dienstag der folgenden Woche eintreffen.\n‼️ Dies ist kein Einzelfall bei Wochenend-Überweisungen,\nsondern ein häufiges Phänomen.\n\n☝️ Dementsprechend lange wartet man,\n▫️ bis die Überweisung schließlich bei DFX angekommen ist,\n▫️ verarbeitet (und geprüft) werden kann,\n▫️ man dann endlich ein Statusupdate bekommt.\n\n❗️ Sobald die Zahlung bei DFX eingegangen ist,\nkannst du den DFX-Verarbeitungsstatus prüfen (einsehen).\nBis dahin – solange die Zahlung noch im Bankensystem kreist – 💸\nbist du von den Bearbeitungszeiten der Banken abhängig. 🙁" + "monero_not_displayed": "❓ Die Kryptolieferung wird in der Cake Wallet nicht angezeigt:\n\n⓵ Verbindung prüfen:\n▪️ Gehe zum Guthaben-Bildschirm in der Cake Wallet\n▪️ überprüfe, ob die Leiste oben eines der folgenden anzeigt\n🟢 „Synchronisiert“ („Synchronised“)\n🟠 „Connecting“ („Verbindung wird hergestellt“)\n🔴 „Blöcke verbleibend“ („blocks remaining“)\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Wallet synchronisieren, falls Blöcke verbleiben:\n▪️ Lass die App geöffnet\n▪️ bleibe auf dem Guthaben-Bildschirm\n▫️ bis die verbleibenden Blöcke auf 0 reduziert sind.\n\n⓷ Blockchain scannen (Neuscan ab Datum):\n▪️ Falls die Synchronisation das Problem nicht löst\n▪️ starte einen Scan der Blockchain-Blöcke\n👉 1–2 Tage vor der Kryptolieferung.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Überprüfen:\nDeine Kryptolieferung sollte nach diesen Schritten in der Wallet angezeigt werden.\n\n⓹ Cake Wallet Support kontaktieren\n(falls ⓵ – ⓸ nicht helfen)\nhttps://docs.cakewallet.com/support\n\n🫵 Du kannst auch die „Cake Wallet In-App Support“-Funktion nutzen, um direkt über die App mit dem Support-Team zu chatten:\n📲 Öffne dazu das Menü und wähle:\nSupport ➔ Live-Support, um dort Kontakt aufzunehmen.", + "sepa_standard": "SEPA-Standard = 🐌 SEPA.\nBanküberweisungen haben unterschiedliche Laufzeiten, bis sie bei DFX eintreffen.\n\n🟢 An Arbeitstagen (tagsüber): im Durchschnitt 3–5 Stunden.\n🟡 An Arbeitstagen (ab nachmittags): meist am nächsten Arbeitstag.\n🟠 An Feiertagen: meist am nächsten Arbeitstag.\n🔴 Am Wochenende: Anfang der Folge-Woche, meist am Montag.\n\n☝️ Dementsprechend lange wartet man,\n▫️ bis die Überweisung schließlich bei DFX angekommen ist,\n▫️ verarbeitet (und geprüft) werden kann,\n▫️ man dann endlich ein Statusupdate bekommt.\n\n❗️ Sobald die Zahlung bei DFX eingegangen ist, kannst du den DFX-Verarbeitungsstatus prüfen (einsehen).\nBis dahin – solange die Zahlung noch im Bankensystem kreist – 💸\nbist du von den Bearbeitungszeiten der Banken abhängig. 🙁", + "sepa_weekend": "📬 Deine letzte (aktuelle) Einzahlung\n\nDeine Einzahlung ist bei DFX noch nicht eingetroffen?\nWenn du deine aktuelle Überweisung am Freitag oder am Wochenende abgeschickt hast, ist sie noch unterwegs - und sie muss auch nicht zwingend schon am Montag bei DFX ankommen.\n\nDas ist völlig normal bei SEPA-Überweisungen, die am Ende der Woche getätigt werden.\n\n---\n\n🐌 SEPA-Standard-Bearbeitungszeiten\n\nBanküberweisungen haben unterschiedliche Laufzeiten, bis sie bei DFX ankommen:\n\n🟢 Werktage (tagsüber): durchschnittlich 3–5 Stunden\n🟡 Werktage (nachmittags): meist am nächsten Werktag\n🟠 Feiertage: meist am nächsten Werktag\n🔴 Wochenenden: zu Beginn der nächsten Woche, normalerweise am Montag ⬅️\n\n---\n\n⚠️ Wichtige Hinweise zu Montagen\n\nMontags haben Banken die höchste Auslastung.\nAlles, was seit Freitagnachmittag eingeht, wird der Montags-Verarbeitung hinzugefügt. ☝️\n\nDadurch werden nicht alle Transaktionen bereits montags verarbeitet — einige verschieben sich auf Dienstag. 😳\n\nIn Extremfällen kann eine Überweisung, die Freitagnachmittag getätigt wurde, sogar erst am Dienstag der folgenden Woche ankommen.\n‼️ Das ist bei Wochenend-Überweisungen keine Ausnahme, sondern kommt regelmäßig vor.\n\n---\n\n⏳ Was das für dich bedeutet\n\nDu musst warten, bis deine Überweisung\n▫️ bei DFX eingetroffen ist\n▫️ verarbeitet (und geprüft) werden kann\n▫️ und du ein Status-Update erhältst\n\n❗️ Sobald die Zahlung bei DFX eingegangen ist, kannst du den Bearbeitungsstatus in deiner DFX-Transaktionshistorie einsehen.\n\nBis dahin — solange das Geld noch im Bankensystem kreist — 💸\nbist du von den Bearbeitungszeiten der Banken abhängig. 🙁" } diff --git a/src/shared/i18n/en/support-issue.json b/src/shared/i18n/en/support-issue.json index 6635cb3fa0..f329fe9d17 100644 --- a/src/shared/i18n/en/support-issue.json +++ b/src/shared/i18n/en/support-issue.json @@ -1,6 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", - "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", - "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function to chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX), you’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "📬 Your Latest (current) Deposit\n\nYour deposit has not yet arrived at DFX?\nIf your current transfer was sent on Friday or during the weekend, it has not arrived at DFX yet — and it also does not necessarily have to arrive on Monday.\n\nThis is totally normal for SEPA transfers sent at the end of the week.\n\n---\n\n🐌 SEPA Standard Processing Times\n\nBank transfers have different processing times until they arrive at DFX:\n\n🟢 Working days (during the day): on average 3–5 hours\n🟡 Working days (afternoon): usually the next working day\n🟠 Holidays: usually the next working day\n🔴 Weekends: at the beginning of the next week, usually on Monday ⬅️\n\n---\n\n⚠️ Important Information About Mondays\n\nBanks have their highest workload on Mondays.\nEverything that arrives from Friday afternoon onward is added to Monday’s queue. ☝️\n\nBecause of this, not all transfers are processed on Monday — some are delayed until Tuesday. 😳\n\nIn extreme cases, a transfer sent on Friday afternoon may not arrive until Tuesday of the following week.\n‼️ This is not unusual for weekend transfers — it happens regularly.\n\n---\n\n⏳ What This Means for You\n\nYou will need to wait until your transfer\n▫️ arrives at DFX\n▫️ can be processed (and checked)\n▫️ and you receive a status update\n\n❗️ Once the payment has arrived at DFX, you will be able to see the processing status in your DFX transaction history.\n\nUntil then — as long as the money is still in the banking system — 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/i18n/es/support-issue.json b/src/shared/i18n/es/support-issue.json index 6635cb3fa0..f329fe9d17 100644 --- a/src/shared/i18n/es/support-issue.json +++ b/src/shared/i18n/es/support-issue.json @@ -1,6 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", - "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", - "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function to chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX), you’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "📬 Your Latest (current) Deposit\n\nYour deposit has not yet arrived at DFX?\nIf your current transfer was sent on Friday or during the weekend, it has not arrived at DFX yet — and it also does not necessarily have to arrive on Monday.\n\nThis is totally normal for SEPA transfers sent at the end of the week.\n\n---\n\n🐌 SEPA Standard Processing Times\n\nBank transfers have different processing times until they arrive at DFX:\n\n🟢 Working days (during the day): on average 3–5 hours\n🟡 Working days (afternoon): usually the next working day\n🟠 Holidays: usually the next working day\n🔴 Weekends: at the beginning of the next week, usually on Monday ⬅️\n\n---\n\n⚠️ Important Information About Mondays\n\nBanks have their highest workload on Mondays.\nEverything that arrives from Friday afternoon onward is added to Monday’s queue. ☝️\n\nBecause of this, not all transfers are processed on Monday — some are delayed until Tuesday. 😳\n\nIn extreme cases, a transfer sent on Friday afternoon may not arrive until Tuesday of the following week.\n‼️ This is not unusual for weekend transfers — it happens regularly.\n\n---\n\n⏳ What This Means for You\n\nYou will need to wait until your transfer\n▫️ arrives at DFX\n▫️ can be processed (and checked)\n▫️ and you receive a status update\n\n❗️ Once the payment has arrived at DFX, you will be able to see the processing status in your DFX transaction history.\n\nUntil then — as long as the money is still in the banking system — 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/i18n/fr/support-issue.json b/src/shared/i18n/fr/support-issue.json index 6635cb3fa0..f329fe9d17 100644 --- a/src/shared/i18n/fr/support-issue.json +++ b/src/shared/i18n/fr/support-issue.json @@ -1,6 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", - "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", - "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function to chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX), you’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "📬 Your Latest (current) Deposit\n\nYour deposit has not yet arrived at DFX?\nIf your current transfer was sent on Friday or during the weekend, it has not arrived at DFX yet — and it also does not necessarily have to arrive on Monday.\n\nThis is totally normal for SEPA transfers sent at the end of the week.\n\n---\n\n🐌 SEPA Standard Processing Times\n\nBank transfers have different processing times until they arrive at DFX:\n\n🟢 Working days (during the day): on average 3–5 hours\n🟡 Working days (afternoon): usually the next working day\n🟠 Holidays: usually the next working day\n🔴 Weekends: at the beginning of the next week, usually on Monday ⬅️\n\n---\n\n⚠️ Important Information About Mondays\n\nBanks have their highest workload on Mondays.\nEverything that arrives from Friday afternoon onward is added to Monday’s queue. ☝️\n\nBecause of this, not all transfers are processed on Monday — some are delayed until Tuesday. 😳\n\nIn extreme cases, a transfer sent on Friday afternoon may not arrive until Tuesday of the following week.\n‼️ This is not unusual for weekend transfers — it happens regularly.\n\n---\n\n⏳ What This Means for You\n\nYou will need to wait until your transfer\n▫️ arrives at DFX\n▫️ can be processed (and checked)\n▫️ and you receive a status update\n\n❗️ Once the payment has arrived at DFX, you will be able to see the processing status in your DFX transaction history.\n\nUntil then — as long as the money is still in the banking system — 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/i18n/it/support-issue.json b/src/shared/i18n/it/support-issue.json index 6635cb3fa0..f329fe9d17 100644 --- a/src/shared/i18n/it/support-issue.json +++ b/src/shared/i18n/it/support-issue.json @@ -1,6 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", - "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", - "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function to chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX), you’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "📬 Your Latest (current) Deposit\n\nYour deposit has not yet arrived at DFX?\nIf your current transfer was sent on Friday or during the weekend, it has not arrived at DFX yet — and it also does not necessarily have to arrive on Monday.\n\nThis is totally normal for SEPA transfers sent at the end of the week.\n\n---\n\n🐌 SEPA Standard Processing Times\n\nBank transfers have different processing times until they arrive at DFX:\n\n🟢 Working days (during the day): on average 3–5 hours\n🟡 Working days (afternoon): usually the next working day\n🟠 Holidays: usually the next working day\n🔴 Weekends: at the beginning of the next week, usually on Monday ⬅️\n\n---\n\n⚠️ Important Information About Mondays\n\nBanks have their highest workload on Mondays.\nEverything that arrives from Friday afternoon onward is added to Monday’s queue. ☝️\n\nBecause of this, not all transfers are processed on Monday — some are delayed until Tuesday. 😳\n\nIn extreme cases, a transfer sent on Friday afternoon may not arrive until Tuesday of the following week.\n‼️ This is not unusual for weekend transfers — it happens regularly.\n\n---\n\n⏳ What This Means for You\n\nYou will need to wait until your transfer\n▫️ arrives at DFX\n▫️ can be processed (and checked)\n▫️ and you receive a status update\n\n❗️ Once the payment has arrived at DFX, you will be able to see the processing status in your DFX transaction history.\n\nUntil then — as long as the money is still in the banking system — 💸\nyou are dependent on the banks’ processing times. 🙁" } diff --git a/src/shared/i18n/pt/support-issue.json b/src/shared/i18n/pt/support-issue.json index 6635cb3fa0..f329fe9d17 100644 --- a/src/shared/i18n/pt/support-issue.json +++ b/src/shared/i18n/pt/support-issue.json @@ -1,6 +1,6 @@ { "bot_hint": "If this message does not help you, simply write another message here and you will be automatically transferred to a support employee who will look into the issue.", - "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function\nto chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", - "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", - "sepa_weekend": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week,\nusually on Monday. ⬅️\n\n⚠️ On Mondays, banks have the highest workload.\nEverything that has arrived since Friday afternoon\nis added to the Monday transfers. ☝️\n\nTherefore, not all transfers are often processed on Monday,\ncausing some to be delayed until Tuesday. 😳\n\nIn extreme cases, a transfer made on Friday afternoon\nmay not arrive until Tuesday of the following week.\n‼️ This is not a single case for weekend transfers,\nbut rather a common phenomenon.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX),\nyou’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁" + "monero_not_displayed": "❓ The crypto delivery is not displayed in the Cake Wallet:\n\n⓵ Check Connection:\n▪️ Go to the balance screen in the Cake Wallet\n▪️ Check if the bar at the top shows one of the following\n🟢 „Synchronised“\n🟠 „Connecting“\n🔴 „blocks remaining“\nhttps://docs.cakewallet.com/faq/funds-not-appearing\n\n⓶ Synchronise the wallet if the bar shows blocks remaining:\nTo do this,\n▪️ Leave the app open\n▪️ and stay on the balance screen\n▫️ until the remaining blocks decrease to 0.\n\n⓷ Scan the blockchain (Rescan from date):\n▪️ If the synchronisation does not solve the problem\n▪️ start the scan of the blockchain blocks\n👉 1–2 days before the crypto delivery.\nhttps://docs.cakewallet.com/features/advanced/rescan-wallet\n\n⓸ Check:\nYour crypto delivery should appear in the wallet after these steps.\n\n⓹ Contact Cake Wallet Support\n(if ⓵ – ⓸ does not work)\nhttps://docs.cakewallet.com/support\n\n🫵 You may also use the „Cake Wallet In-App Support“ function to chat with the support team directly through the app:\n📲 Open the menu and select:\nSupport ➔ Live Support to get in touch there. ☝️", + "sepa_standard": "SEPA standard = 🐌 SEPA.\nBank transfers have different processing times until they arrive at DFX.\n\n🟢 On working days (during the day): on average 3–5 hours.\n🟡 On working days (in the afternoon): usually the next working day.\n🟠 On holidays: usually on the next working day.\n🔴 On weekends: at the beginning of the next week, usually on Monday.\n\n☝️ Accordingly, you will wait,\n▫️ until the transfer has finally arrived at DFX,\n▫️ can be processed (and checked),\n▫️ and you finally receive a status update.\n\n❗️ Once the payment has arrived (been received by DFX), you’ll be able to check the DFX processing status.\nUntil then – as long as the payment is in the banking system – 💸\nyou are dependent on the banks’ processing times. 🙁", + "sepa_weekend": "📬 Your Latest (current) Deposit\n\nYour deposit has not yet arrived at DFX?\nIf your current transfer was sent on Friday or during the weekend, it has not arrived at DFX yet — and it also does not necessarily have to arrive on Monday.\n\nThis is totally normal for SEPA transfers sent at the end of the week.\n\n---\n\n🐌 SEPA Standard Processing Times\n\nBank transfers have different processing times until they arrive at DFX:\n\n🟢 Working days (during the day): on average 3–5 hours\n🟡 Working days (afternoon): usually the next working day\n🟠 Holidays: usually the next working day\n🔴 Weekends: at the beginning of the next week, usually on Monday ⬅️\n\n---\n\n⚠️ Important Information About Mondays\n\nBanks have their highest workload on Mondays.\nEverything that arrives from Friday afternoon onward is added to Monday’s queue. ☝️\n\nBecause of this, not all transfers are processed on Monday — some are delayed until Tuesday. 😳\n\nIn extreme cases, a transfer sent on Friday afternoon may not arrive until Tuesday of the following week.\n‼️ This is not unusual for weekend transfers — it happens regularly.\n\n---\n\n⏳ What This Means for You\n\nYou will need to wait until your transfer\n▫️ arrives at DFX\n▫️ can be processed (and checked)\n▫️ and you receive a status update\n\n❗️ Once the payment has arrived at DFX, you will be able to see the processing status in your DFX transaction history.\n\nUntil then — as long as the money is still in the banking system — 💸\nyou are dependent on the banks’ processing times. 🙁" } From e2d5642a988933c104204bb62060ccaf71db115e Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:06:12 +0100 Subject: [PATCH 13/14] Add --referral-chain option to db-debug.sh (#3128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add --referral-chain option to db-debug.sh Add new parameter to trace complete referral chain for a user. Walks up the recommendation table and displays the full chain from root user to target user with method and date info. Usage: ./scripts/db-debug.sh --referral-chain * Add --referral-tree option to show complete referral network New parameter that finds the root user and displays the entire referral tree with all branches, including: - User status and KYC status for each node - Tree structure with proper formatting (├──, └──, │) - Target user marker - Total user count in the network --- scripts/db-debug.sh | 232 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) diff --git a/scripts/db-debug.sh b/scripts/db-debug.sh index 33fb41a7d3..89c3e6ab5f 100755 --- a/scripts/db-debug.sh +++ b/scripts/db-debug.sh @@ -35,6 +35,10 @@ if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then echo " -s, --stats Show log statistics by system/subsystem" echo " -A, --asset-history [N]" echo " Show balance history for asset (default: 10)" + echo " -R, --referral-chain " + echo " Show complete referral chain for user" + echo " -T, --referral-tree " + echo " Show complete referral tree (all branches)" echo "" echo "Examples:" echo " ./scripts/db-debug.sh --anomalies 50" @@ -42,6 +46,8 @@ if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then echo " ./scripts/db-debug.sh --asset-history 405 20" echo " ./scripts/db-debug.sh --asset-history Yapeal/EUR 20" echo " ./scripts/db-debug.sh --asset-history MaerkiBaumann/CHF 10" + echo " ./scripts/db-debug.sh --referral-chain 370625" + echo " ./scripts/db-debug.sh --referral-tree 370625" echo " ./scripts/db-debug.sh \"SELECT TOP 10 * FROM asset\"" exit 0 fi @@ -72,6 +78,9 @@ ASSET_HISTORY_MODE="" ASSET_ID="" ASSET_INPUT="" ASSET_LIMIT="10" +REFERRAL_CHAIN_MODE="" +REFERRAL_TREE_MODE="" +TARGET_USER_ID="" case "${1:-}" in -a|--anomalies) @@ -98,6 +107,24 @@ case "${1:-}" in ASSET_INPUT="$2" ASSET_LIMIT="${3:-10}" ;; + -R|--referral-chain) + if [ -z "${2:-}" ]; then + echo "Error: --referral-chain requires a userDataId" + echo "Usage: ./scripts/db-debug.sh --referral-chain " + exit 1 + fi + REFERRAL_CHAIN_MODE="1" + TARGET_USER_ID="$2" + ;; + -T|--referral-tree) + if [ -z "${2:-}" ]; then + echo "Error: --referral-tree requires a userDataId" + echo "Usage: ./scripts/db-debug.sh --referral-tree " + exit 1 + fi + REFERRAL_TREE_MODE="1" + TARGET_USER_ID="$2" + ;; *) SQL="${1:-SELECT TOP 5 id, name, blockchain FROM asset ORDER BY id DESC}" ;; @@ -143,6 +170,211 @@ ROLE=$(echo "$TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq -r '.role' 2>/ echo "Authenticated with role: $ROLE" echo "" +# --- Referral chain mode --- +if [ -n "$REFERRAL_CHAIN_MODE" ]; then + if ! command -v jq &> /dev/null; then + echo "Error: jq is required for --referral-chain" + exit 1 + fi + + echo "=== Referral Chain for UserDataId $TARGET_USER_ID ===" + echo "" + + # Collect chain by walking up (store as space-separated string for POSIX compatibility) + CHAIN="" + METHODS="" + CURRENT_ID="$TARGET_USER_ID" + + while [ -n "$CURRENT_ID" ]; do + if [ -z "$CHAIN" ]; then + CHAIN="$CURRENT_ID" + else + CHAIN="$CHAIN $CURRENT_ID" + fi + + # Query recommendation for current user + RESULT=$(curl -s -X POST "$API_URL/gs/debug" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"sql\":\"SELECT recommenderId, method, created FROM recommendation WHERE recommendedId = $CURRENT_ID\"}") + + REFERRER_ID=$(echo "$RESULT" | jq -r '.[0].recommenderId // empty') + METHOD=$(echo "$RESULT" | jq -r '.[0].method // empty') + CREATED=$(echo "$RESULT" | jq -r '.[0].created // empty' | cut -d'T' -f1) + + if [ -n "$REFERRER_ID" ] && [ "$REFERRER_ID" != "null" ]; then + # Store method/date for display (format: userId:method:date) + if [ -z "$METHODS" ]; then + METHODS="$CURRENT_ID:$METHOD:$CREATED" + else + METHODS="$METHODS $CURRENT_ID:$METHOD:$CREATED" + fi + CURRENT_ID="$REFERRER_ID" + else + CURRENT_ID="" + fi + done + + # Convert to array and print chain (reversed, root first) + CHAIN_ARRAY=($CHAIN) + CHAIN_LEN=${#CHAIN_ARRAY[@]} + + for ((i=CHAIN_LEN-1; i>=0; i--)); do + USER_ID="${CHAIN_ARRAY[$i]}" + + if [ $i -eq $((CHAIN_LEN-1)) ]; then + # Root user (no referrer) + echo "$USER_ID (Root - no referrer)" + else + # Find method for this user + METHOD_INFO="" + for entry in $METHODS; do + entry_id=$(echo "$entry" | cut -d':' -f1) + if [ "$entry_id" == "$USER_ID" ]; then + entry_method=$(echo "$entry" | cut -d':' -f2) + entry_date=$(echo "$entry" | cut -d':' -f3) + METHOD_INFO="$entry_method ($entry_date)" + break + fi + done + + echo " ↓ $METHOD_INFO" + if [ "$USER_ID" == "$TARGET_USER_ID" ]; then + echo "$USER_ID ← (target)" + else + echo "$USER_ID" + fi + fi + done + + exit 0 +fi + +# --- Referral tree mode --- +if [ -n "$REFERRAL_TREE_MODE" ]; then + if ! command -v jq &> /dev/null; then + echo "Error: jq is required for --referral-tree" + exit 1 + fi + + # First, find the root by walking up + CURRENT_ID="$TARGET_USER_ID" + ROOT_ID="" + + while [ -n "$CURRENT_ID" ]; do + ROOT_ID="$CURRENT_ID" + RESULT=$(curl -s -X POST "$API_URL/gs/debug" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"sql\":\"SELECT recommenderId FROM recommendation WHERE recommendedId = $CURRENT_ID\"}") + REFERRER_ID=$(echo "$RESULT" | jq -r '.[0].recommenderId // empty') + if [ -n "$REFERRER_ID" ] && [ "$REFERRER_ID" != "null" ]; then + CURRENT_ID="$REFERRER_ID" + else + CURRENT_ID="" + fi + done + + echo "=== Referral Tree for UserDataId $TARGET_USER_ID (Root: $ROOT_ID) ===" + echo "" + + # Recursive function to print tree + print_tree() { + local user_id="$1" + local prefix="$2" + local is_last="$3" + local is_root="$4" + + # Get user status + local status_result=$(curl -s -X POST "$API_URL/gs/debug" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"sql\":\"SELECT status, kycStatus FROM user_data WHERE id = $user_id\"}") + local status=$(echo "$status_result" | jq -r '.[0].status // "?"') + local kyc_status=$(echo "$status_result" | jq -r '.[0].kycStatus // "?"') + + # Build display line + local marker="" + if [ "$user_id" == "$TARGET_USER_ID" ]; then + marker=" ← (target)" + fi + local status_info="($status, $kyc_status)" + + if [ "$is_root" == "1" ]; then + echo "$user_id $status_info$marker" + else + if [ "$is_last" == "1" ]; then + echo "${prefix}└── $user_id $status_info$marker" + else + echo "${prefix}├── $user_id $status_info$marker" + fi + fi + + # Get children + local children_result=$(curl -s -X POST "$API_URL/gs/debug" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"sql\":\"SELECT recommendedId FROM recommendation WHERE recommenderId = $user_id ORDER BY created\"}") + local children=$(echo "$children_result" | jq -r '.[].recommendedId // empty' 2>/dev/null) + + if [ -n "$children" ]; then + local children_array=($children) + local num_children=${#children_array[@]} + local child_index=0 + + for child_id in "${children_array[@]}"; do + child_index=$((child_index + 1)) + local child_is_last="0" + if [ $child_index -eq $num_children ]; then + child_is_last="1" + fi + + local new_prefix="" + if [ "$is_root" == "1" ]; then + new_prefix="" + elif [ "$is_last" == "1" ]; then + new_prefix="${prefix} " + else + new_prefix="${prefix}│ " + fi + + print_tree "$child_id" "$new_prefix" "$child_is_last" "0" + done + fi + } + + # Start printing from root + print_tree "$ROOT_ID" "" "" "1" + + # Count total users + echo "" + KNOWN_IDS="$ROOT_ID" + TO_CHECK="$ROOT_ID" + + while [ -n "$TO_CHECK" ]; do + NEW_TO_CHECK="" + for check_id in $TO_CHECK; do + count_children_result=$(curl -s -X POST "$API_URL/gs/debug" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"sql\":\"SELECT recommendedId FROM recommendation WHERE recommenderId = $check_id\"}") + count_children=$(echo "$count_children_result" | jq -r '.[].recommendedId // empty' 2>/dev/null) + for child in $count_children; do + if [[ ! " $KNOWN_IDS " =~ " $child " ]]; then + KNOWN_IDS="$KNOWN_IDS $child" + NEW_TO_CHECK="$NEW_TO_CHECK $child" + fi + done + done + TO_CHECK="$NEW_TO_CHECK" + done + + TOTAL_COUNT=$(echo $KNOWN_IDS | wc -w | tr -d ' ') + echo "Total users in tree: $TOTAL_COUNT" + + exit 0 +fi + # --- Resolve asset ID if needed --- if [ -n "$ASSET_HISTORY_MODE" ]; then if [[ "$ASSET_INPUT" =~ ^[0-9]+$ ]]; then From 568d65261f3abbdbec329ecdcf5bed4974b6a010 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:27:29 +0100 Subject: [PATCH 14/14] Add migration to set BuyCrypto 112734 to Fail status (#3129) Set BuyCrypto 112734 to Fail retroactively due to high risk. The blockchain transaction was already executed (3.23 BNB), this migration only corrects the database records. Changes: - buy_crypto: amlCheck=Fail, amlReason=HighRiskBlocked, nullify fee/output fields - transaction: nullify amlCheck, amountInChf, assets, eventDate, amlType, highRisk - user_data: subtract 1945.74 CHF from buy volumes --- .../1770235383000-FailBuyCrypto112734.js | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 migration/1770235383000-FailBuyCrypto112734.js diff --git a/migration/1770235383000-FailBuyCrypto112734.js b/migration/1770235383000-FailBuyCrypto112734.js new file mode 100644 index 0000000000..7836d29a31 --- /dev/null +++ b/migration/1770235383000-FailBuyCrypto112734.js @@ -0,0 +1,173 @@ +/** + * @typedef {import('typeorm').MigrationInterface} MigrationInterface + * @typedef {import('typeorm').QueryRunner} QueryRunner + */ + +/** + * Set BuyCrypto 112734 to Fail status retroactively. + * + * This transaction was incorrectly processed and needs to be marked as failed + * as if it never passed AML check. + * + * BuyCrypto: 112734 + * Transaction: 298950 + * UserData: 370625 + * Amount: 1945.74 CHF (2120 EUR) + * + * Note: The blockchain transaction (3.23 BNB) was already executed. + * This migration only corrects the database records. + * + * @class + * @implements {MigrationInterface} + */ +module.exports = class FailBuyCrypto1127341770235383000 { + name = 'FailBuyCrypto1127341770235383000'; + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + const buyCryptoId = 112734; + const transactionId = 298950; + const userDataId = 370625; + const amountInChf = 1945.74; + + console.log('=== Fail BuyCrypto 112734 Retroactively ===\n'); + + // Verify current state + const currentBuyCrypto = await queryRunner.query(` + SELECT id, amlCheck, amlReason, status, outputAmount, txId, transactionId + FROM dbo.buy_crypto + WHERE id = ${buyCryptoId} + `); + + if (currentBuyCrypto.length === 0) { + console.log('ERROR: BuyCrypto not found. Aborting.'); + return; + } + + const bc = currentBuyCrypto[0]; + console.log('Current BuyCrypto state:'); + console.log(` ID: ${bc.id}`); + console.log(` amlCheck: ${bc.amlCheck}`); + console.log(` amlReason: ${bc.amlReason}`); + console.log(` status: ${bc.status}`); + console.log(` outputAmount: ${bc.outputAmount}`); + console.log(` txId: ${bc.txId}`); + console.log(''); + + if (bc.amlCheck === 'Fail') { + console.log('BuyCrypto already set to Fail. Skipping.'); + return; + } + + // 1. Update buy_crypto + console.log('1. Updating buy_crypto...'); + await queryRunner.query(` + UPDATE dbo.buy_crypto + SET + amlCheck = 'Fail', + amlReason = 'HighRiskBlocked', + percentFee = NULL, + inputReferenceAmountMinusFee = NULL, + outputReferenceAmount = NULL, + outputAmount = NULL, + txId = NULL, + outputDate = NULL, + usedRef = NULL, + refProvision = NULL, + refFactor = NULL, + percentFeeAmount = NULL, + absoluteFeeAmount = NULL, + batchId = NULL, + minFeeAmount = NULL, + minFeeAmountFiat = NULL, + totalFeeAmount = NULL, + totalFeeAmountChf = NULL, + highRisk = NULL, + usedFees = NULL, + blockchainFee = NULL, + priceSteps = NULL, + priceDefinitionAllowedDate = NULL, + networkStartFeeAmount = NULL, + bankFeeAmount = NULL, + liquidityPipelineId = NULL, + updated = GETDATE() + WHERE id = ${buyCryptoId} + `); + console.log(' buy_crypto updated.'); + + // 2. Update transaction + console.log('2. Updating transaction...'); + await queryRunner.query(` + UPDATE dbo.[transaction] + SET + amlCheck = NULL, + amountInChf = NULL, + highRisk = NULL, + assets = NULL, + eventDate = NULL, + amlType = NULL, + updated = GETDATE() + WHERE id = ${transactionId} + `); + console.log(' transaction updated.'); + + // 3. Update user_data volumes + console.log('3. Updating user_data volumes...'); + const currentUserData = await queryRunner.query(` + SELECT id, buyVolume, annualBuyVolume, monthlyBuyVolume + FROM dbo.user_data + WHERE id = ${userDataId} + `); + + if (currentUserData.length > 0) { + const ud = currentUserData[0]; + console.log( + ` Current volumes: buy=${ud.buyVolume}, annual=${ud.annualBuyVolume}, monthly=${ud.monthlyBuyVolume}`, + ); + + await queryRunner.query(` + UPDATE dbo.user_data + SET + buyVolume = buyVolume - ${amountInChf}, + annualBuyVolume = annualBuyVolume - ${amountInChf}, + monthlyBuyVolume = monthlyBuyVolume - ${amountInChf}, + updated = GETDATE() + WHERE id = ${userDataId} + `); + console.log(` Subtracted ${amountInChf} CHF from all volume fields.`); + } + + // Verify final state + console.log('\n=== Verification ==='); + const finalBuyCrypto = await queryRunner.query(` + SELECT id, amlCheck, amlReason, outputAmount, txId, chargebackDate + FROM dbo.buy_crypto + WHERE id = ${buyCryptoId} + `); + const finalTx = await queryRunner.query(` + SELECT id, amlCheck, amountInChf + FROM dbo.[transaction] + WHERE id = ${transactionId} + `); + const finalUserData = await queryRunner.query(` + SELECT id, buyVolume, annualBuyVolume, monthlyBuyVolume + FROM dbo.user_data + WHERE id = ${userDataId} + `); + + console.log('BuyCrypto:', JSON.stringify(finalBuyCrypto[0], null, 2)); + console.log('Transaction:', JSON.stringify(finalTx[0], null, 2)); + console.log('UserData volumes:', JSON.stringify(finalUserData[0], null, 2)); + + console.log('\n=== Migration Complete ==='); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + console.log('Down migration not implemented. Manual intervention required to restore data.'); + } +};