// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ============================================ // 监控地址表 // 存储需要监听充值的地址(用户地址和系统账户地址) // ============================================ model MonitoredAddress { id BigInt @id @default(autoincrement()) @map("address_id") chainType String @map("chain_type") @db.VarChar(20) // KAVA, BSC address String @db.VarChar(42) // 0x地址 // 地址类型: USER (用户钱包) 或 SYSTEM (系统账户) addressType String @default("USER") @map("address_type") @db.VarChar(20) // 用户地址关联 (addressType = USER 时使用) accountSequence String? @map("account_sequence") @db.VarChar(20) // 跨服务关联标识 (格式: D + YYMMDD + 5位序号) userId BigInt? @map("user_id") // 保留兼容 // 系统账户关联 (addressType = SYSTEM 时使用) systemAccountType String? @map("system_account_type") @db.VarChar(50) // COST_ACCOUNT, OPERATION_ACCOUNT, etc. systemAccountId BigInt? @map("system_account_id") regionCode String? @map("region_code") @db.VarChar(10) // 省市代码(省市账户用) isActive Boolean @default(true) @map("is_active") // 是否激活监听 createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") deposits DepositTransaction[] @@unique([chainType, address], name: "uk_chain_address") @@index([accountSequence], name: "idx_account_sequence") @@index([userId], name: "idx_user") @@index([addressType, isActive], name: "idx_type_active") @@index([chainType, isActive], name: "idx_chain_active") @@index([systemAccountType], name: "idx_system_account_type") @@map("monitored_addresses") } // ============================================ // 充值交易表 (Append-Only) // 记录检测到的所有充值交易 // ============================================ model DepositTransaction { id BigInt @id @default(autoincrement()) @map("deposit_id") chainType String @map("chain_type") @db.VarChar(20) txHash String @unique @map("tx_hash") @db.VarChar(66) fromAddress String @map("from_address") @db.VarChar(42) toAddress String @map("to_address") @db.VarChar(42) tokenContract String @map("token_contract") @db.VarChar(42) // USDT合约地址 amount Decimal @db.Decimal(36, 18) // 原始金额 amountFormatted Decimal @map("amount_formatted") @db.Decimal(20, 8) // 格式化金额 blockNumber BigInt @map("block_number") blockTimestamp DateTime @map("block_timestamp") logIndex Int @map("log_index") // 确认状态 confirmations Int @default(0) status String @default("DETECTED") @db.VarChar(20) // DETECTED, CONFIRMING, CONFIRMED, NOTIFIED // 关联 - 使用 accountSequence 作为跨服务主键 addressId BigInt @map("address_id") addressType String @default("USER") @map("address_type") @db.VarChar(20) // USER 或 SYSTEM // 用户地址关联 accountSequence String? @map("account_sequence") @db.VarChar(20) // 跨服务关联标识 (格式: D + YYMMDD + 5位序号) userId BigInt? @map("user_id") // 保留兼容 // 系统账户关联(当 addressType = SYSTEM 时) systemAccountType String? @map("system_account_type") @db.VarChar(50) systemAccountId BigInt? @map("system_account_id") // 通知状态 notifiedAt DateTime? @map("notified_at") notifyAttempts Int @default(0) @map("notify_attempts") lastNotifyError String? @map("last_notify_error") @db.Text createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") monitoredAddress MonitoredAddress @relation(fields: [addressId], references: [id]) @@index([chainType, status], name: "idx_chain_status") @@index([accountSequence], name: "idx_deposit_account") @@index([userId], name: "idx_deposit_user") @@index([blockNumber], name: "idx_block") @@index([status, notifiedAt], name: "idx_pending_notify") @@map("deposit_transactions") } // ============================================ // 区块扫描检查点 (每条链一条记录) // 记录扫描进度,用于断点续扫 // ============================================ model BlockCheckpoint { id BigInt @id @default(autoincrement()) @map("checkpoint_id") chainType String @unique @map("chain_type") @db.VarChar(20) lastScannedBlock BigInt @map("last_scanned_block") lastScannedAt DateTime @map("last_scanned_at") // 健康状态 isHealthy Boolean @default(true) @map("is_healthy") lastError String? @map("last_error") @db.Text createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@map("block_checkpoints") } // ============================================ // 交易广播请求表 // 记录待广播和已广播的交易 // ============================================ model TransactionRequest { id BigInt @id @default(autoincrement()) @map("request_id") chainType String @map("chain_type") @db.VarChar(20) // 请求来源 sourceService String @map("source_service") @db.VarChar(50) sourceOrderId String @map("source_order_id") @db.VarChar(100) // 交易数据 fromAddress String @map("from_address") @db.VarChar(42) toAddress String @map("to_address") @db.VarChar(42) value Decimal @db.Decimal(36, 18) data String? @db.Text // 合约调用数据 // 签名数据 (由 MPC 服务提供) signedTx String? @map("signed_tx") @db.Text // 广播结果 txHash String? @map("tx_hash") @db.VarChar(66) status String @default("PENDING") @db.VarChar(20) // PENDING, SIGNED, BROADCASTED, CONFIRMED, FAILED // Gas 信息 gasLimit BigInt? @map("gas_limit") gasPrice Decimal? @map("gas_price") @db.Decimal(36, 18) nonce Int? // 错误信息 errorMessage String? @map("error_message") @db.Text retryCount Int @default(0) @map("retry_count") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@unique([sourceService, sourceOrderId], name: "uk_source_order") @@index([chainType, status], name: "idx_tx_chain_status") @@index([txHash], name: "idx_tx_hash") @@map("transaction_requests") } // ============================================ // 账户恢复助记词 // 与账户序列号关联,用于账户恢复验证 // ============================================ model RecoveryMnemonic { id BigInt @id @default(autoincrement()) accountSequence String @map("account_sequence") @db.VarChar(20) // 账户序列号 (格式: D + YYMMDD + 5位序号) publicKey String @map("public_key") @db.VarChar(130) // 关联的钱包公钥 // 助记词存储 (加密) encryptedMnemonic String @map("encrypted_mnemonic") @db.Text // AES加密的助记词 mnemonicHash String @map("mnemonic_hash") @db.VarChar(64) // SHA256哈希(用于验证) // 状态管理 status String @default("ACTIVE") @db.VarChar(20) // ACTIVE, REVOKED, REPLACED isBackedUp Boolean @default(false) @map("is_backed_up") // 用户是否已备份 // 挂失/更换相关 revokedAt DateTime? @map("revoked_at") revokedReason String? @map("revoked_reason") @db.VarChar(200) replacedById BigInt? @map("replaced_by_id") // 被哪个新助记词替代 createdAt DateTime @default(now()) @map("created_at") @@unique([accountSequence, status], name: "uk_account_active_mnemonic") // 一个账户只有一个ACTIVE助记词 @@index([accountSequence], name: "idx_recovery_account") @@index([publicKey], name: "idx_recovery_public_key") @@index([status], name: "idx_recovery_status") @@map("recovery_mnemonics") } // ============================================ // Outbox 事件表 (发件箱模式) // 保证事件发布的可靠性 // ============================================ model OutboxEvent { id BigInt @id @default(autoincrement()) @map("event_id") // 事件信息 eventType String @map("event_type") @db.VarChar(100) aggregateId String @map("aggregate_id") @db.VarChar(100) aggregateType String @map("aggregate_type") @db.VarChar(50) payload Json @map("payload") // 发送状态: PENDING -> SENT -> ACKED / FAILED status String @default("PENDING") @db.VarChar(20) // 重试信息 retryCount Int @default(0) @map("retry_count") maxRetries Int @default(10) @map("max_retries") lastError String? @map("last_error") @db.Text nextRetryAt DateTime? @map("next_retry_at") // 时间戳 createdAt DateTime @default(now()) @map("created_at") sentAt DateTime? @map("sent_at") ackedAt DateTime? @map("acked_at") @@index([status, nextRetryAt], name: "idx_outbox_pending") @@index([aggregateType, aggregateId], name: "idx_outbox_aggregate") @@index([eventType], name: "idx_outbox_event_type") @@index([createdAt], name: "idx_outbox_created") @@map("outbox_events") } // ============================================ // 区块链事件日志 (Append-Only 审计) // ============================================ model BlockchainEvent { id BigInt @id @default(autoincrement()) @map("event_id") eventType String @map("event_type") @db.VarChar(50) aggregateId String @map("aggregate_id") @db.VarChar(100) aggregateType String @map("aggregate_type") @db.VarChar(50) eventData Json @map("event_data") chainType String? @map("chain_type") @db.VarChar(20) txHash String? @map("tx_hash") @db.VarChar(66) occurredAt DateTime @default(now()) @map("occurred_at") @db.Timestamp(6) @@index([aggregateType, aggregateId], name: "idx_event_aggregate") @@index([eventType], name: "idx_event_type") @@index([chainType], name: "idx_event_chain") @@index([occurredAt], name: "idx_event_occurred") @@map("blockchain_events") }