2200 lines
72 KiB
Markdown
2200 lines
72 KiB
Markdown
# Genex 后端开发指南
|
||
|
||
> DDD + Clean Architecture + 微服务 | NestJS + Go + Kong + PostgreSQL
|
||
|
||
---
|
||
|
||
## 1. 技术栈总览
|
||
|
||
| 层级 | 技术 | 用途 |
|
||
|------|------|------|
|
||
| **API网关** | Kong | 限流、认证、路由、负载均衡 |
|
||
| **主要业务服务** | NestJS (TypeScript) | 业务逻辑微服务 |
|
||
| **高性能服务** | Go | 撮合引擎、链上事件监听、高频任务 |
|
||
| **翻译层** | Go + Redis | 地址映射、Gas代付、术语翻译 |
|
||
| **数据库** | PostgreSQL 15+ | 核心业务数据 |
|
||
| **时序数据库** | TimescaleDB | 行情数据、监控指标 |
|
||
| **缓存** | Redis Cluster | 热点数据、会话、订单簿缓存 |
|
||
| **消息队列** | Kafka | 事件驱动、链上事件监听 |
|
||
| **搜索** | Elasticsearch | 全文检索、日志分析 |
|
||
| **AI/ML** | FastAPI (Python) | 信用评分、价格预测、异常检测 |
|
||
|
||
---
|
||
|
||
## 2. 架构设计:三层五域
|
||
|
||
### 2.1 五大业务域
|
||
|
||
| 域 | 服务名 | 语言 | 职责 |
|
||
|----|--------|------|------|
|
||
| D1 发行域 | `issuer-service` | NestJS | 发行方入驻、券管理、发行审批 |
|
||
| D2 交易域 | `trading-service` | Go | 撮合引擎、订单簿、做市商 |
|
||
| D3 清算域 | `clearing-service` | NestJS + Go | 链上结算、兑付清算、退款 |
|
||
| D4 合规域 | `compliance-service` | NestJS | KYC/AML、OFAC、Travel Rule |
|
||
| D5 AI域 | `ai-service` | FastAPI | 信用评分、定价、异常检测 |
|
||
| 通用 | `user-service` | NestJS | 用户注册、认证、Profile |
|
||
| 通用 | `translate-service` | Go | UX翻译层(地址映射、Gas代付) |
|
||
| 通用 | `notification-service` | NestJS | 消息推送、邮件、短信 |
|
||
| 通用 | `chain-indexer` | Go | 链上事件监听、数据索引 |
|
||
|
||
### 2.2 服务间通信
|
||
|
||
```
|
||
┌──────────────────────────────────────────────┐
|
||
│ Kong API Gateway │
|
||
│ 限流 | OAuth 2.1 + JWT | 路由 | 负载均衡 │
|
||
├──────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
|
||
│ │ user │ │ issuer │ │ trading │ │
|
||
│ │ service │ │ service │ │ service(Go)│ │
|
||
│ └────┬────┘ └────┬─────┘ └─────┬──────┘ │
|
||
│ │ │ │ │
|
||
│ └────────────┼──────────────┘ │
|
||
│ │ │
|
||
│ ┌─────┴─────┐ │
|
||
│ │ Kafka │ │
|
||
│ └─────┬─────┘ │
|
||
│ ┌────────────┼───────────────┐ │
|
||
│ ┌────┴────┐ ┌─────┴──────┐ ┌─────┴─────┐ │
|
||
│ │clearing │ │compliance │ │ai-service │ │
|
||
│ │service │ │service │ │(FastAPI) │ │
|
||
│ └─────────┘ └────────────┘ └───────────┘ │
|
||
└──────────────────────────────────────────────┘
|
||
```
|
||
|
||
- **同步通信**:gRPC(服务间直接调用,如撮合→结算)
|
||
- **异步通信**:Kafka事件驱动(链上事件、通知、审计日志)
|
||
|
||
---
|
||
|
||
## 3. DDD + Clean Architecture(NestJS服务)
|
||
|
||
### 3.1 模块结构(以issuer-service为例)
|
||
|
||
```
|
||
issuer-service/
|
||
├── src/
|
||
│ ├── main.ts
|
||
│ ├── app.module.ts
|
||
│ ├── domain/ # 领域层(纯业务逻辑,零外部依赖)
|
||
│ │ ├── entities/
|
||
│ │ │ ├── issuer.entity.ts # 发行方聚合根
|
||
│ │ │ ├── coupon-template.entity.ts
|
||
│ │ │ └── credit-rating.vo.ts # 值对象
|
||
│ │ ├── repositories/
|
||
│ │ │ └── issuer.repository.ts # Repository接口
|
||
│ │ ├── services/
|
||
│ │ │ ├── issuer-domain.service.ts
|
||
│ │ │ └── credit-scoring.service.ts
|
||
│ │ └── events/
|
||
│ │ ├── issuer-approved.event.ts
|
||
│ │ └── coupon-issued.event.ts
|
||
│ ├── application/ # 应用层(用例编排)
|
||
│ │ ├── commands/
|
||
│ │ │ ├── register-issuer.command.ts
|
||
│ │ │ ├── create-coupon.command.ts
|
||
│ │ │ └── handlers/
|
||
│ │ ├── queries/
|
||
│ │ │ ├── get-issuer.query.ts
|
||
│ │ │ └── handlers/
|
||
│ │ └── dto/
|
||
│ │ ├── create-issuer.dto.ts
|
||
│ │ └── create-coupon.dto.ts
|
||
│ ├── infrastructure/ # 基础设施层(外部实现)
|
||
│ │ ├── persistence/
|
||
│ │ │ ├── issuer.repository.impl.ts
|
||
│ │ │ ├── issuer.orm-entity.ts
|
||
│ │ │ └── typeorm.config.ts
|
||
│ │ ├── messaging/
|
||
│ │ │ └── kafka-publisher.ts
|
||
│ │ └── external/
|
||
│ │ └── chain-client.ts # 链上交互
|
||
│ └── interfaces/ # 接口层(HTTP/gRPC)
|
||
│ ├── http/
|
||
│ │ ├── issuer.controller.ts
|
||
│ │ └── coupon.controller.ts
|
||
│ └── grpc/
|
||
│ └── issuer.grpc-controller.ts
|
||
├── test/
|
||
└── package.json
|
||
```
|
||
|
||
### 3.2 领域实体
|
||
|
||
```typescript
|
||
// src/domain/entities/issuer.entity.ts
|
||
export class Issuer {
|
||
readonly id: string;
|
||
readonly companyName: string;
|
||
private _creditRating: CreditRating;
|
||
private _issuanceQuota: number;
|
||
private _tier: IssuerTier;
|
||
private _status: IssuerStatus;
|
||
|
||
// 信用评分四因子
|
||
updateCreditScore(metrics: CreditMetrics): void {
|
||
const score =
|
||
0.35 * metrics.redemptionRate +
|
||
0.25 * (1 - metrics.breakageRatio) +
|
||
0.20 * Math.log(metrics.marketTenure + 1) / Math.log(37) +
|
||
0.20 * metrics.userSatisfaction;
|
||
|
||
this._creditRating = CreditRating.fromScore(score);
|
||
this._issuanceQuota = this._creditRating.calculateQuota();
|
||
}
|
||
|
||
// 发券校验
|
||
canIssueCoupon(params: CreateCouponParams): Result<void> {
|
||
if (this._status !== IssuerStatus.ACTIVE)
|
||
return Result.fail('发行方状态异常');
|
||
if (params.totalValue > this.remainingQuota)
|
||
return Result.fail('超出发行额度');
|
||
if (params.expiryDays > 365)
|
||
return Result.fail('Utility Track有效期不得超过12个月');
|
||
return Result.ok();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.3 应用层Command
|
||
|
||
```typescript
|
||
// src/application/commands/handlers/create-coupon.handler.ts
|
||
@CommandHandler(CreateCouponCommand)
|
||
export class CreateCouponHandler {
|
||
constructor(
|
||
private issuerRepo: IssuerRepository,
|
||
private eventBus: EventBus,
|
||
) {}
|
||
|
||
async execute(command: CreateCouponCommand): Promise<CouponDraft> {
|
||
const issuer = await this.issuerRepo.findById(command.issuerId);
|
||
if (!issuer) throw new NotFoundException('发行方不存在');
|
||
|
||
// 领域校验
|
||
const validation = issuer.canIssueCoupon(command.params);
|
||
if (validation.isFailure) throw new BadRequestException(validation.error);
|
||
|
||
// 创建券草稿(待审核)
|
||
const draft = CouponDraft.create(issuer, command.params);
|
||
await this.issuerRepo.saveCouponDraft(draft);
|
||
|
||
// 发布领域事件
|
||
this.eventBus.publish(new CouponDraftCreatedEvent(draft));
|
||
|
||
return draft;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Go 高性能服务
|
||
|
||
### 4.1 撮合引擎
|
||
|
||
```go
|
||
// trading-service/internal/matching/engine.go
|
||
type MatchingEngine struct {
|
||
orderBooks map[string]*OrderBook // couponId → OrderBook
|
||
mu sync.RWMutex
|
||
}
|
||
|
||
type OrderBook struct {
|
||
Bids *redblacktree.Tree // 买单:价格降序
|
||
Asks *redblacktree.Tree // 卖单:价格升序
|
||
}
|
||
|
||
func (e *MatchingEngine) SubmitOrder(order *Order) []*Trade {
|
||
e.mu.Lock()
|
||
defer e.mu.Unlock()
|
||
|
||
book := e.getOrCreateBook(order.CouponID)
|
||
|
||
// Utility Track价格校验
|
||
if order.CouponType == CouponTypeUtility && order.Price > order.FaceValue {
|
||
return nil // 拒绝溢价单
|
||
}
|
||
|
||
// 撮合:价格优先 → 时间优先
|
||
trades := book.Match(order)
|
||
|
||
// 成交后发布事件到Kafka
|
||
for _, trade := range trades {
|
||
e.publisher.Publish("trade.matched", trade)
|
||
}
|
||
|
||
return trades
|
||
}
|
||
```
|
||
|
||
### 4.2 链上事件监听
|
||
|
||
```go
|
||
// chain-indexer/internal/indexer/indexer.go
|
||
type ChainIndexer struct {
|
||
ethClient *ethclient.Client
|
||
kafka sarama.SyncProducer
|
||
contracts map[string]common.Address
|
||
}
|
||
|
||
func (i *ChainIndexer) Start(ctx context.Context) {
|
||
// 监听Genex Chain事件
|
||
query := ethereum.FilterQuery{
|
||
Addresses: []common.Address{
|
||
i.contracts["CouponFactory"],
|
||
i.contracts["Settlement"],
|
||
i.contracts["Redemption"],
|
||
},
|
||
}
|
||
|
||
logs := make(chan types.Log)
|
||
sub, _ := i.ethClient.SubscribeFilterLogs(ctx, query, logs)
|
||
|
||
for {
|
||
select {
|
||
case log := <-logs:
|
||
event := i.parseEvent(log)
|
||
i.kafka.SendMessage(&sarama.ProducerMessage{
|
||
Topic: "chain.events",
|
||
Value: sarama.ByteEncoder(event.Marshal()),
|
||
})
|
||
case err := <-sub.Err():
|
||
log.Error("subscription error", "err", err)
|
||
// 重连逻辑
|
||
case <-ctx.Done():
|
||
return
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. UX翻译层
|
||
|
||
> 核心创新:将所有Web3操作翻译为Web2用户可理解的操作。
|
||
|
||
```go
|
||
// translate-service/internal/service/translate.go
|
||
type TranslateService struct {
|
||
mappingDB *redis.Client // 手机号→链上地址映射
|
||
mpcClient MPCClient // MPC钱包服务
|
||
paymaster PaymasterClient // Gas代付服务
|
||
}
|
||
|
||
// 手机号→链上地址
|
||
func (s *TranslateService) ResolveAddress(phone string) (common.Address, error) {
|
||
addr, err := s.mappingDB.Get(ctx, "phone:"+phone).Result()
|
||
if err == redis.Nil {
|
||
return common.Address{}, ErrUserNotFound
|
||
}
|
||
return common.HexToAddress(addr), nil
|
||
}
|
||
|
||
// 购买券:法币→链上原子交换
|
||
func (s *TranslateService) ExecutePurchase(req PurchaseRequest) (*PurchaseResult, error) {
|
||
// 1. 法币支付确认
|
||
// 2. 法币→稳定币转换
|
||
// 3. 调用Settlement合约原子交换(Gas由Paymaster代付)
|
||
// 4. 返回"订单号"(映射TX Hash)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. API设计
|
||
|
||
### 6.1 RESTful规范
|
||
|
||
```yaml
|
||
API规范:
|
||
风格: RESTful
|
||
版本控制: URL路径 /api/v1/
|
||
认证: OAuth 2.1 + JWT
|
||
限流:
|
||
普通用户: 100 req/min
|
||
发行方: 1,000 req/min
|
||
做市商: 10,000 req/min
|
||
响应格式: JSON
|
||
错误处理: RFC 7807 Problem Details
|
||
```
|
||
|
||
### 6.2 核心API端点
|
||
|
||
| 域 | 路径前缀 | 主要接口 |
|
||
|-----|---------|---------|
|
||
| 用户 | `/api/v1/users` | 注册、登录、KYC、资产查询 |
|
||
| 券 | `/api/v1/coupons` | 发行、查询、转让、核销 |
|
||
| 交易 | `/api/v1/trades` | 挂单、撤单、成交、历史 |
|
||
| 支付 | `/api/v1/payments` | 充值、提现、法币转换 |
|
||
| 发行方 | `/api/v1/issuers` | 入驻、审核、数据统计 |
|
||
| 合规 | `/api/v1/compliance` | KYC、AML、审计日志 |
|
||
| 翻译层 | `/api/v1/translate` | 地址映射、Gas代付 |
|
||
|
||
---
|
||
|
||
## 7. 数据库设计
|
||
|
||
### 7.1 PostgreSQL核心表
|
||
|
||
```sql
|
||
-- 用户表
|
||
CREATE TABLE users (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
phone VARCHAR(20) UNIQUE,
|
||
email VARCHAR(100) UNIQUE,
|
||
kyc_level SMALLINT NOT NULL DEFAULT 0,
|
||
wallet_mode VARCHAR(10) NOT NULL DEFAULT 'standard',
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
-- 发行方表
|
||
CREATE TABLE issuers (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
company_name VARCHAR(200) NOT NULL,
|
||
business_license VARCHAR(100),
|
||
credit_rating VARCHAR(5) NOT NULL DEFAULT 'BBB',
|
||
credit_score NUMERIC(5,2) NOT NULL DEFAULT 60.00,
|
||
issuance_quota NUMERIC(15,2) NOT NULL DEFAULT 100000,
|
||
tier VARCHAR(10) NOT NULL DEFAULT 'silver',
|
||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
-- 券表(链下缓存,权威数据在链上)
|
||
CREATE TABLE coupons (
|
||
id UUID PRIMARY KEY,
|
||
chain_token_id BIGINT UNIQUE, -- 链上Token ID
|
||
issuer_id UUID REFERENCES issuers(id),
|
||
face_value NUMERIC(12,2) NOT NULL,
|
||
current_price NUMERIC(12,2),
|
||
expiry_date DATE NOT NULL,
|
||
coupon_type VARCHAR(10) NOT NULL DEFAULT 'utility',
|
||
status VARCHAR(20) NOT NULL DEFAULT 'available',
|
||
resale_count SMALLINT NOT NULL DEFAULT 0,
|
||
max_resale_count SMALLINT NOT NULL DEFAULT 3,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
-- 地址映射表(翻译层核心)
|
||
CREATE TABLE address_mappings (
|
||
user_id UUID REFERENCES users(id),
|
||
chain_address VARCHAR(42) NOT NULL UNIQUE,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
PRIMARY KEY (user_id)
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 安全规范
|
||
|
||
| 领域 | 规范 |
|
||
|------|------|
|
||
| 传输 | TLS 1.3 + 服务间mTLS |
|
||
| 认证 | OAuth 2.1 + JWT(短有效期 + Refresh Token) |
|
||
| 授权 | RBAC |
|
||
| 数据加密 | AES-256(敏感字段),HSM管理密钥 |
|
||
| API安全 | Rate Limiting + CORS + CSRF防护 |
|
||
| SQL注入 | ORM参数化查询(TypeORM / sqlx) |
|
||
| 审计 | 全链路操作日志,不可篡改 |
|
||
|
||
---
|
||
|
||
## 9. 部署架构
|
||
|
||
```yaml
|
||
# Kubernetes部署
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: issuer-service
|
||
spec:
|
||
replicas: 3
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: issuer-service
|
||
image: genex/issuer-service:latest
|
||
resources:
|
||
requests: { cpu: "250m", memory: "512Mi" }
|
||
limits: { cpu: "1000m", memory: "1Gi" }
|
||
env:
|
||
- name: DB_HOST
|
||
valueFrom:
|
||
configMapKeyRef: { name: db-config, key: host }
|
||
- name: DB_PASSWORD
|
||
valueFrom:
|
||
secretKeyRef: { name: db-secrets, key: password }
|
||
```
|
||
|
||
### 多区域部署
|
||
|
||
| 区域 | 部署 | 角色 |
|
||
|------|------|------|
|
||
| AWS美国(主) | 全量服务 + 主数据库 + Genex Chain主验证节点 |
|
||
| AWS新加坡 | 亚太服务 + 灾备数据库 + 区域验证节点 |
|
||
| 香港 | 监管节点 + 审计接口 |
|
||
|
||
---
|
||
|
||
## 10. 监控与可观测性
|
||
|
||
| 组件 | 工具 | 用途 |
|
||
|------|------|------|
|
||
| 指标 | Prometheus + Grafana | 服务指标、业务指标 |
|
||
| 日志 | ELK Stack | 日志收集分析 |
|
||
| 链路追踪 | Jaeger | 分布式调用追踪 |
|
||
| 告警 | AlertManager + PagerDuty | 异常告警 |
|
||
|
||
---
|
||
|
||
## 11. 手续费与收入计算
|
||
|
||
### 11.1 交易手续费(核心收入)
|
||
|
||
```typescript
|
||
// src/domain/services/fee-calculation.service.ts
|
||
export class FeeCalculationService {
|
||
// Maker-Taker模型
|
||
private readonly FEE_RATES = {
|
||
taker: { buy: 0.005, sell: 0.005 }, // Taker 0.5%
|
||
maker: { buy: 0.001, sell: 0.001 }, // Maker 0.1%(做市商激励)
|
||
};
|
||
|
||
// 发行方分层手续费率
|
||
private readonly ISSUER_FEE_RATES: Record<IssuerTier, number> = {
|
||
silver: 0.015, // 白银 1.5%
|
||
gold: 0.012, // 黄金 1.2%
|
||
platinum: 0.010, // 铂金 1.0%
|
||
diamond: 0.008, // 钻石 0.8%
|
||
};
|
||
|
||
calculateTradeFee(trade: Trade): TradeFees {
|
||
const isMaker = (side: 'buyer' | 'seller') =>
|
||
trade[side].isMaker;
|
||
return {
|
||
buyerFee: trade.price * (isMaker('buyer') ? this.FEE_RATES.maker.buy : this.FEE_RATES.taker.buy),
|
||
sellerFee: trade.price * (isMaker('seller') ? this.FEE_RATES.maker.sell : this.FEE_RATES.taker.sell),
|
||
};
|
||
}
|
||
|
||
calculateIssuanceFee(issuer: Issuer, totalValue: number): number {
|
||
// 新入驻首月享黄金层级
|
||
const tier = issuer.isFirstMonth ? 'gold' : issuer.tier;
|
||
return totalValue * this.ISSUER_FEE_RATES[tier];
|
||
}
|
||
}
|
||
```
|
||
|
||
### 11.2 Breakage收益计算与分配
|
||
|
||
```typescript
|
||
// src/domain/services/breakage.service.ts
|
||
export class BreakageService {
|
||
/**
|
||
* 券过期后Breakage计算:
|
||
* 未使用的券面值 → 发行方Breakage收入
|
||
* 平台按比例分润(发行服务协议约定)
|
||
*/
|
||
async processExpiredCoupons(): Promise<void> {
|
||
const expired = await this.couponRepo.findExpiredUnprocessed();
|
||
|
||
for (const coupon of expired) {
|
||
const breakageAmount = coupon.faceValue;
|
||
const platformShare = breakageAmount * 0.10; // 平台分润10%(可配置)
|
||
const issuerShare = breakageAmount - platformShare;
|
||
|
||
await this.financeService.recordBreakage({
|
||
couponId: coupon.id,
|
||
issuerId: coupon.issuerId,
|
||
totalAmount: breakageAmount,
|
||
platformShare,
|
||
issuerShare,
|
||
expiredAt: coupon.expiryDate,
|
||
});
|
||
|
||
// 更新券状态
|
||
await this.couponRepo.markBreakageProcessed(coupon.id);
|
||
|
||
// 更新发行方信用评分(Breakage率因子)
|
||
this.eventBus.publish(new CouponExpiredEvent(coupon));
|
||
}
|
||
}
|
||
}
|
||
|
||
// 定时任务:每日批量处理过期券
|
||
@Cron('0 2 * * *') // 每天凌晨2点
|
||
async handleExpiredCoupons() {
|
||
await this.breakageService.processExpiredCoupons();
|
||
}
|
||
```
|
||
|
||
### 11.3 退款机制
|
||
|
||
```typescript
|
||
// src/domain/services/refund.service.ts
|
||
interface RefundPolicy {
|
||
primaryMarket: {
|
||
windowDays: number; // 发行方可配退款窗口(默认7天)
|
||
fullRefund: boolean; // 一级市场全额退(含手续费)
|
||
};
|
||
secondaryMarket: {
|
||
requireArbitration: true; // 二级市场需仲裁
|
||
feeRefund: false; // 手续费不退
|
||
};
|
||
}
|
||
|
||
export class RefundService {
|
||
async processRefund(request: RefundRequest): Promise<RefundResult> {
|
||
// 已核销/已过期券不可退
|
||
if (['redeemed', 'expired'].includes(request.coupon.status)) {
|
||
throw new BadRequestException('已核销或已过期券不可退款');
|
||
}
|
||
|
||
// 一级市场退款:发行方退款窗口内
|
||
if (request.type === 'primary') {
|
||
const withinWindow = this.isWithinRefundWindow(request);
|
||
if (!withinWindow) throw new BadRequestException('超出退款期限');
|
||
|
||
// 调用Settlement合约反向原子交换
|
||
return this.chainClient.executeRefund(request);
|
||
}
|
||
|
||
// 二级市场退款:需仲裁裁决
|
||
if (request.type === 'secondary') {
|
||
return this.disputeService.createRefundCase(request);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 12. 做市商系统
|
||
|
||
### 12.1 做市商准入与管理
|
||
|
||
```typescript
|
||
// src/domain/entities/market-maker.entity.ts
|
||
export class MarketMaker {
|
||
id: string;
|
||
userId: string;
|
||
kycLevel: KycLevel; // 必须KYC L3
|
||
depositAmount: number; // 最低保证金
|
||
status: 'active' | 'suspended' | 'terminated';
|
||
obligations: MarketMakerObligations;
|
||
}
|
||
|
||
interface MarketMakerObligations {
|
||
minSpreadBps: number; // 最大价差(基点)
|
||
minDepthPerSide: number; // 单边最小挂单深度
|
||
uptimePercent: number; // 最低在线时间 (95%)
|
||
maxResponseMs: number; // 报价响应时间上限
|
||
}
|
||
```
|
||
|
||
### 12.2 做市商专用API
|
||
|
||
```typescript
|
||
// 低延迟专用接口(10,000 req/min)
|
||
@Controller('api/v1/mm')
|
||
export class MarketMakerController {
|
||
@Post('batch-orders')
|
||
async batchOrders(@Body() dto: BatchOrderDto) {
|
||
// 批量挂单/撤单(单次最多100单)
|
||
}
|
||
|
||
@Get('orderbook/:couponId')
|
||
async getOrderBook(@Param('couponId') id: string) {
|
||
// 实时订单簿快照
|
||
}
|
||
|
||
@Ws('mm/stream')
|
||
async streamMarketData() {
|
||
// WebSocket低延迟行情推送
|
||
}
|
||
}
|
||
```
|
||
|
||
### 12.3 Spoofing/Layering检测
|
||
|
||
```typescript
|
||
// 做市商行为监控
|
||
export class MarketManipulationDetector {
|
||
/**
|
||
* Spoofing检测:大量挂单后短时间内撤单
|
||
* Layering检测:多层挂单制造虚假深度
|
||
*/
|
||
detect(orders: OrderActivity[]): ManipulationAlert[] {
|
||
const alerts: ManipulationAlert[] = [];
|
||
|
||
// 挂单→撤单比率异常(>80%撤单率)
|
||
const cancelRate = orders.filter(o => o.cancelled).length / orders.length;
|
||
if (cancelRate > 0.8) {
|
||
alerts.push({ type: 'spoofing', severity: 'high', cancelRate });
|
||
}
|
||
|
||
// 短时间多层挂单检测
|
||
const layeringPattern = this.detectLayeringPattern(orders);
|
||
if (layeringPattern) alerts.push(layeringPattern);
|
||
|
||
return alerts;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 13. 三因子定价引擎
|
||
|
||
```typescript
|
||
// src/domain/services/pricing.service.ts
|
||
export class PricingService {
|
||
/**
|
||
* P = F × (1 - dt - rc - lp)
|
||
* dt = Time Discount = f(剩余有效期)
|
||
* rc = Credit Risk = f(发行方信用评分)
|
||
* lp = Liquidity Premium = f(市场供需)
|
||
*/
|
||
calculateSuggestedPrice(coupon: Coupon, issuer: Issuer, market: MarketData): PriceSuggestion {
|
||
const F = coupon.faceValue;
|
||
const totalDays = differenceInDays(coupon.expiryDate, coupon.issuedDate);
|
||
const remainDays = differenceInDays(coupon.expiryDate, new Date());
|
||
|
||
// 时间折扣 (0% - 15%)
|
||
const dt = ((totalDays - remainDays) / totalDays) * 0.15;
|
||
|
||
// 信用风险溢价 (0% - 20%)
|
||
const CREDIT_MAP: Record<CreditRating, number> = {
|
||
AAA: 0, AA: 0.03, A: 0.06, BBB: 0.10, BB: 0.15,
|
||
};
|
||
const rc = CREDIT_MAP[issuer.creditRating] || 0.20;
|
||
|
||
// 流动性溢价 (-5% - 10%)
|
||
const supplyDemandRatio = (market.sellOrders - market.buyOrders) / (market.totalOrders || 1);
|
||
const lp = Math.max(-0.05, Math.min(0.10, supplyDemandRatio * 0.1));
|
||
|
||
const price = F * (1 - dt - rc - lp);
|
||
|
||
// Utility Track: 价格上限 = 面值
|
||
const cappedPrice = coupon.type === 'utility' ? Math.min(price, F) : price;
|
||
|
||
return { suggestedPrice: cappedPrice, factors: { dt, rc, lp } };
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 14. AI/ML 模型服务
|
||
|
||
### 14.1 信用评分引擎
|
||
|
||
```python
|
||
# ai-service/models/credit_scoring.py
|
||
import lightgbm as lgb
|
||
from sklearn.model_selection import cross_val_score
|
||
|
||
class CreditScoringModel:
|
||
"""
|
||
发行方信用评分:四因子基础 + ML增强
|
||
特征:核销率、Breakage率、市场存续月数、用户满意度、历史违约、行业
|
||
"""
|
||
def __init__(self):
|
||
self.model = lgb.LGBMRegressor(
|
||
n_estimators=500, learning_rate=0.05,
|
||
max_depth=6, num_leaves=31,
|
||
)
|
||
|
||
def predict_credit_score(self, issuer_features: dict) -> float:
|
||
# 四因子基础分
|
||
base = (0.35 * issuer_features['redemption_rate'] +
|
||
0.25 * (1 - issuer_features['breakage_ratio']) +
|
||
0.20 * math.log(issuer_features['tenure_months'] + 1) / math.log(37) +
|
||
0.20 * issuer_features['user_satisfaction'])
|
||
# ML调整(基于更多特征)
|
||
ml_adjustment = self.model.predict([self._extract_features(issuer_features)])[0]
|
||
return min(100, max(0, base * 100 * 0.7 + ml_adjustment * 0.3))
|
||
```
|
||
|
||
### 14.2 价格预测模型
|
||
|
||
```python
|
||
# ai-service/models/price_prediction.py
|
||
from prophet import Prophet
|
||
import torch.nn as nn
|
||
|
||
class LSTMPricePredictor(nn.Module):
|
||
"""LSTM序列预测:历史价格→未来价格走势"""
|
||
def __init__(self, input_size=5, hidden_size=64, num_layers=2):
|
||
super().__init__()
|
||
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
|
||
self.fc = nn.Linear(hidden_size, 1)
|
||
|
||
def forward(self, x):
|
||
out, _ = self.lstm(x)
|
||
return self.fc(out[:, -1, :])
|
||
|
||
class ProphetSeasonalPredictor:
|
||
"""Prophet周期性预测:节假日/季节性价格模式"""
|
||
def predict(self, history: pd.DataFrame, periods: int = 30):
|
||
model = Prophet(yearly_seasonality=True, weekly_seasonality=True)
|
||
model.fit(history)
|
||
future = model.make_future_dataframe(periods=periods)
|
||
return model.predict(future)
|
||
```
|
||
|
||
### 14.3 异常检测系统
|
||
|
||
```python
|
||
# ai-service/models/anomaly_detection.py
|
||
from sklearn.ensemble import IsolationForest
|
||
|
||
class TransactionAnomalyDetector:
|
||
"""
|
||
异常交易检测:Isolation Forest + 规则引擎
|
||
实时消费Kafka trade.events,标记可疑交易
|
||
"""
|
||
def __init__(self):
|
||
self.isolation_forest = IsolationForest(
|
||
contamination=0.01, n_estimators=200, random_state=42
|
||
)
|
||
|
||
def detect(self, transaction: dict) -> AnomalyResult:
|
||
features = self._extract_features(transaction)
|
||
|
||
# ML检测
|
||
score = self.isolation_forest.decision_function([features])[0]
|
||
|
||
# 规则引擎检测
|
||
rule_alerts = self._apply_rules(transaction)
|
||
|
||
return AnomalyResult(
|
||
ml_score=score,
|
||
is_anomaly=score < -0.5 or len(rule_alerts) > 0,
|
||
alerts=rule_alerts,
|
||
)
|
||
|
||
def _apply_rules(self, tx: dict) -> list:
|
||
alerts = []
|
||
if tx['amount'] > 10000: alerts.append('large_amount')
|
||
if tx['frequency_1h'] > 20: alerts.append('high_frequency')
|
||
if tx['geolocation_change']: alerts.append('geo_anomaly')
|
||
return alerts
|
||
```
|
||
|
||
### 14.4 智能推荐引擎(P2优先级)
|
||
|
||
```python
|
||
# 协同过滤 + 内容推荐 混合推荐
|
||
class CouponRecommender:
|
||
def recommend(self, user_id: str, top_k: int = 10):
|
||
# 协同过滤:相似用户购买历史
|
||
cf_scores = self.collaborative_filter(user_id)
|
||
# 内容推荐:基于券属性(品牌、类别、折扣率)
|
||
content_scores = self.content_based(user_id)
|
||
# 混合加权
|
||
return self.blend(cf_scores, content_scores, top_k)
|
||
```
|
||
|
||
---
|
||
|
||
## 15. AML反洗钱系统
|
||
|
||
### 15.1 AML检测规则引擎
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/aml-detection.service.ts
|
||
export class AmlDetectionService {
|
||
// 已识别洗钱路径检测
|
||
private readonly RULES: AmlRule[] = [
|
||
// 1. 买券洗钱:脏钱买券→P2P转给另一账户→卖出提现
|
||
{ name: 'buy_transfer_withdraw', detect: this.detectBuyTransferWithdraw },
|
||
// 2. 分散洗钱:一个账户→P2P分散转给大量小账户
|
||
{ name: 'fan_out', detect: this.detectFanOut },
|
||
// 3. 发行方自洗:关联账户自买自卖
|
||
{ name: 'self_dealing', detect: this.detectSelfDealing },
|
||
// 4. 跨境洗钱:A国买→P2P转B国→B国提现
|
||
{ name: 'cross_border', detect: this.detectCrossBorder },
|
||
// 5. Structuring:拆分交易规避阈值
|
||
{ name: 'structuring', detect: this.detectStructuring },
|
||
];
|
||
|
||
async detectFanOut(userId: string, window: TimeWindow): Promise<Alert | null> {
|
||
const transfers = await this.getP2PTransfers(userId, window);
|
||
const uniqueRecipients = new Set(transfers.map(t => t.recipientId));
|
||
// 24h内向>10个不同账户P2P转移
|
||
if (uniqueRecipients.size > 10) {
|
||
return { type: 'fan_out', severity: 'high', details: { recipients: uniqueRecipients.size } };
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async detectStructuring(userId: string, window: TimeWindow): Promise<Alert | null> {
|
||
const txs = await this.getTransactions(userId, window);
|
||
// 检测是否有多笔接近$3,000阈值的交易($2,500-$2,999)
|
||
const nearThreshold = txs.filter(t => t.amount >= 2500 && t.amount < 3000);
|
||
if (nearThreshold.length >= 3) {
|
||
return { type: 'structuring', severity: 'critical', details: { count: nearThreshold.length } };
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 15.2 交易图谱分析
|
||
|
||
```typescript
|
||
// 构建用户间交易关系图谱
|
||
export class TransactionGraphService {
|
||
async buildGraph(timeWindow: TimeWindow): Promise<TransactionGraph> {
|
||
const edges = await this.tradeRepo.getTransferEdges(timeWindow);
|
||
const graph = new Graph();
|
||
|
||
for (const edge of edges) {
|
||
graph.addEdge(edge.sender, edge.receiver, {
|
||
amount: edge.amount, timestamp: edge.timestamp,
|
||
});
|
||
}
|
||
|
||
// 检测环形转移
|
||
const cycles = graph.detectCycles();
|
||
// 检测扇入/扇出模式
|
||
const fanPatterns = graph.detectFanPatterns(threshold: 10);
|
||
// 关联账户聚类
|
||
const clusters = graph.communityDetection();
|
||
|
||
return { graph, cycles, fanPatterns, clusters };
|
||
}
|
||
}
|
||
```
|
||
|
||
### 15.3 SAR自动生成
|
||
|
||
```typescript
|
||
// 可疑交易报告(Suspicious Activity Report)
|
||
export class SarService {
|
||
async generateSar(alert: AmlAlert): Promise<SarReport> {
|
||
return {
|
||
filingType: 'initial',
|
||
subjectInfo: await this.getUserInfo(alert.userId),
|
||
suspiciousActivity: {
|
||
dateRange: alert.timeWindow,
|
||
amount: alert.totalAmount,
|
||
instruments: 'Digital Coupon Assets',
|
||
description: this.generateNarrative(alert),
|
||
},
|
||
// FinCEN BSA E-Filing格式
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 16. OFAC合规服务
|
||
|
||
```typescript
|
||
// compliance-service/src/infrastructure/external/ofac.service.ts
|
||
export class OfacService {
|
||
private sdnList: Map<string, SdnEntry> = new Map();
|
||
|
||
// 名单同步(24h内更新)
|
||
@Cron('0 */6 * * *') // 每6小时同步
|
||
async syncSdnList(): Promise<void> {
|
||
// 主选:Chainalysis,备选:Elliptic/TRM Labs
|
||
const entries = await this.chainalysis.getLatestSdnList();
|
||
this.sdnList = new Map(entries.map(e => [e.id, e]));
|
||
this.logger.log(`OFAC SDN list synced: ${entries.length} entries`);
|
||
}
|
||
|
||
// 注册时筛查
|
||
async screenOnRegistration(user: UserRegistrationDto): Promise<OfacScreenResult> {
|
||
return this.screenPerson({
|
||
name: user.fullName,
|
||
nationality: user.nationality,
|
||
address: user.address,
|
||
dateOfBirth: user.dob,
|
||
});
|
||
}
|
||
|
||
// 每笔交易实时筛查
|
||
async screenTransaction(buyer: string, seller: string): Promise<OfacScreenResult> {
|
||
const buyerResult = await this.screenAddress(buyer);
|
||
const sellerResult = await this.screenAddress(seller);
|
||
if (buyerResult.isMatch || sellerResult.isMatch) {
|
||
// 命中:立即冻结 + 上报
|
||
await this.freezeAndReport(buyerResult, sellerResult);
|
||
}
|
||
return { buyerResult, sellerResult };
|
||
}
|
||
|
||
// P2P转移链上地址筛查
|
||
async screenChainAddress(address: string): Promise<boolean> {
|
||
return this.chainalysis.checkAddress(address);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 17. Travel Rule合规(TRISA/TRP)
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/travel-rule.service.ts
|
||
export class TravelRuleService {
|
||
/**
|
||
* FATF Travel Rule: ≥$3,000转移需传递身份信息
|
||
* 接入TRISA协议实现跨平台信息传递
|
||
*/
|
||
async processP2PTransfer(transfer: P2PTransferRequest): Promise<void> {
|
||
if (transfer.amount >= 3000) {
|
||
// 验证双方KYC≥L2
|
||
await this.verifyKycLevel(transfer.senderId, KycLevel.L2);
|
||
await this.verifyKycLevel(transfer.receiverId, KycLevel.L2);
|
||
|
||
// 身份信息哈希写入链上
|
||
const senderHash = this.hashIdentity(await this.getKycInfo(transfer.senderId));
|
||
const receiverHash = this.hashIdentity(await this.getKycInfo(transfer.receiverId));
|
||
await this.chainClient.recordTravelRule(
|
||
transfer.senderAddress, transfer.receiverAddress,
|
||
senderHash, receiverHash,
|
||
);
|
||
|
||
// TRISA跨平台传递(如接收方在其他平台)
|
||
if (transfer.isExternalReceiver) {
|
||
await this.trisaClient.sendTravelRuleInfo({
|
||
originator: await this.getOriginatorInfo(transfer.senderId),
|
||
beneficiary: await this.getBeneficiaryInfo(transfer.receiverId),
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 18. 税务合规
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/tax.service.ts
|
||
export class TaxComplianceService {
|
||
// IRS Form 1099-DA / 1099-B 生成
|
||
async generate1099(userId: string, taxYear: number): Promise<Tax1099Report> {
|
||
const trades = await this.tradeRepo.getUserTrades(userId, taxYear);
|
||
const proceeds = trades.reduce((sum, t) => sum + t.sellPrice, 0);
|
||
const costBasis = trades.reduce((sum, t) => sum + t.buyPrice, 0);
|
||
|
||
return {
|
||
form: proceeds > 600 ? '1099-DA' : null, // >$600 threshold
|
||
recipientTIN: await this.getUserTIN(userId),
|
||
grossProceeds: proceeds,
|
||
costBasis,
|
||
gainOrLoss: proceeds - costBasis,
|
||
transactions: trades.map(t => this.formatTaxTransaction(t)),
|
||
};
|
||
}
|
||
|
||
// FATCA跨境税务(外国账户持有人)
|
||
async checkFatcaObligation(userId: string): Promise<boolean> {
|
||
const user = await this.userRepo.findById(userId);
|
||
return user.nationality !== 'US' && user.hasUsSourceIncome;
|
||
}
|
||
|
||
// 发行方Breakage收入税务处理
|
||
async generateIssuerTaxReport(issuerId: string, taxYear: number): Promise<IssuerTaxReport> {
|
||
const breakageIncome = await this.financeRepo.getBreakageIncome(issuerId, taxYear);
|
||
const salesRevenue = await this.financeRepo.getSalesRevenue(issuerId, taxYear);
|
||
return { breakageIncome, salesRevenue, totalTaxableIncome: breakageIncome + salesRevenue };
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 19. 数据隐私合规(CCPA/GDPR)
|
||
|
||
```typescript
|
||
// user-service/src/domain/services/privacy.service.ts
|
||
export class PrivacyService {
|
||
/**
|
||
* 用户数据删除流程:
|
||
* 链上仅地址和哈希(不可删除,但不构成PII)
|
||
* 链下PII全部可删除
|
||
*
|
||
* 例外:AML/BSA法规要求交易记录保留≥5年
|
||
*/
|
||
async processDeleteRequest(userId: string): Promise<DeleteResult> {
|
||
// 检查数据保留义务
|
||
const retentionCheck = await this.checkRetentionObligation(userId);
|
||
if (retentionCheck.hasActiveObligation) {
|
||
return { status: 'deferred', reason: 'AML/BSA 5年保留期未满', deferUntil: retentionCheck.expiresAt };
|
||
}
|
||
|
||
// 1. 删除链下所有PII
|
||
await this.userRepo.deletePersonalData(userId); // 姓名、手机号、邮箱、身份证
|
||
await this.kycRepo.deleteKycMaterials(userId); // KYC资料(人脸、证件照)
|
||
// 2. 删除映射表记录
|
||
await this.mappingRepo.deleteMapping(userId); // 手机号→地址映射
|
||
// 3. 链上地址变为匿名(不可逆删除映射后地址无法关联到人)
|
||
// 4. Travel Rule链下明文删除,链上仅哈希(不可逆)
|
||
|
||
return { status: 'completed', deletedAt: new Date() };
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 20. 数据留存与审计
|
||
|
||
```typescript
|
||
// 数据保留策略
|
||
const DATA_RETENTION_POLICIES = {
|
||
transactionRecords: { minYears: 5, source: 'BSA/AML' },
|
||
kycDocuments: { minYears: 5, source: 'BSA/CDD' },
|
||
sarReports: { minYears: 5, source: 'FinCEN' },
|
||
auditLogs: { minYears: 7, source: 'SOX' },
|
||
chainData: { minYears: Infinity, source: 'blockchain' },
|
||
};
|
||
|
||
// 审计日志(append-only,不可篡改)
|
||
export class AuditLogService {
|
||
async log(entry: AuditEntry): Promise<void> {
|
||
await this.appendOnlyStore.write({
|
||
timestamp: new Date(),
|
||
actor: entry.actorId,
|
||
action: entry.action,
|
||
resource: entry.resource,
|
||
details: entry.details,
|
||
ipAddress: entry.ip,
|
||
hash: this.computeChainHash(entry), // 链式哈希防篡改
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 21. 券生命周期管理
|
||
|
||
```typescript
|
||
// 券状态流转
|
||
enum CouponStatus {
|
||
MINTED = 'minted', // 已铸造(链上)
|
||
LISTED = 'listed', // 已上架
|
||
SOLD = 'sold', // 已售出
|
||
IN_CIRCULATION = 'in_circulation', // 流通中(二级市场)
|
||
REDEEMED = 'redeemed', // 已兑付
|
||
EXPIRED = 'expired', // 已过期
|
||
RECALLED = 'recalled', // 已召回
|
||
}
|
||
|
||
// 过期自动处理
|
||
@Cron('0 0 * * *') // 每日凌晨
|
||
async processExpiredCoupons(): Promise<void> {
|
||
const expiring = await this.couponRepo.findExpiringToday();
|
||
for (const coupon of expiring) {
|
||
await this.couponRepo.updateStatus(coupon.id, CouponStatus.EXPIRED);
|
||
// Breakage收益计算
|
||
await this.breakageService.calculateAndDistribute(coupon);
|
||
// 通知持有人
|
||
await this.notificationService.send(coupon.holderId, {
|
||
type: 'coupon_expired',
|
||
couponName: coupon.name,
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 22. 争议与纠纷处理
|
||
|
||
```typescript
|
||
// 争议处理流程
|
||
interface DisputeCase {
|
||
id: string;
|
||
type: 'buyer_complaint' | 'seller_complaint' | 'refund_request';
|
||
status: 'submitted' | 'evidence_collection' | 'arbitration' | 'resolved' | 'escalated';
|
||
buyerId: string;
|
||
sellerId: string;
|
||
orderId: string;
|
||
chainEvidence: ChainEvidence[]; // 链上不可篡改证据
|
||
slaDeadline: Date; // 24h响应,72h处理
|
||
}
|
||
|
||
export class DisputeService {
|
||
async createDispute(dto: CreateDisputeDto): Promise<DisputeCase> {
|
||
const chainEvidence = await this.chainClient.getTransactionProof(dto.orderId);
|
||
const dispute = DisputeCase.create({ ...dto, chainEvidence });
|
||
|
||
// SLA:24h内响应
|
||
await this.slaService.scheduleReminder(dispute.id, '24h');
|
||
return dispute;
|
||
}
|
||
|
||
async arbitrate(disputeId: string, decision: ArbitrationDecision): Promise<void> {
|
||
if (decision.refundApproved) {
|
||
await this.refundService.processRefund(decision.refundDetails);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 23. 客服系统
|
||
|
||
```typescript
|
||
// 工单系统
|
||
interface Ticket {
|
||
id: string;
|
||
userId: string;
|
||
category: 'transaction' | 'account' | 'coupon' | 'compliance' | 'other';
|
||
priority: 'low' | 'medium' | 'high' | 'urgent';
|
||
status: 'open' | 'in_progress' | 'waiting_user' | 'resolved' | 'closed';
|
||
sla: { responseTime: '24h', resolutionTime: '72h' };
|
||
}
|
||
|
||
// 发行方专属客服通道(铂金/钻石层级)
|
||
export class IssuerSupportService {
|
||
getDedicatedChannel(issuer: Issuer): SupportChannel {
|
||
if (['platinum', 'diamond'].includes(issuer.tier)) {
|
||
return { type: 'dedicated', responseTime: '1h', manager: this.getAccountManager(issuer.id) };
|
||
}
|
||
return { type: 'standard', responseTime: '24h' };
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 24. 安全事件响应计划
|
||
|
||
### 24.1 事件分级
|
||
|
||
| 级别 | 定义 | 响应时限 | 示例 |
|
||
|------|------|---------|------|
|
||
| **P0** | 资产被盗/合约漏洞被利用 | 15分钟内启动 | MPC密钥泄露、合约攻击 |
|
||
| **P1** | 数据泄露/系统被入侵 | 1小时内响应 | 用户数据泄露、映射表篡改 |
|
||
| **P2** | 局部服务异常/可疑活动 | 4小时内响应 | API攻击、异常登录 |
|
||
| **P3** | 潜在风险/安全隐患 | 24小时内评估 | 依赖库漏洞 |
|
||
|
||
### 24.2 P0应急流程
|
||
|
||
```typescript
|
||
// 自动化应急响应
|
||
export class IncidentResponseService {
|
||
async handleP0Incident(incident: SecurityIncident): Promise<void> {
|
||
// 1. 自动触发紧急冻结(Governance合约)
|
||
await this.chainClient.triggerEmergencyFreeze(incident.affectedAddresses);
|
||
|
||
// 2. 通知安全团队(PagerDuty)
|
||
await this.pagerDuty.trigger({
|
||
severity: 'critical',
|
||
description: incident.description,
|
||
});
|
||
|
||
// 3. 保全证据
|
||
await this.forensics.preserveEvidence({
|
||
logs: await this.collectLogs(incident.timeRange),
|
||
chainState: await this.chainClient.getStateSnapshot(),
|
||
systemSnapshot: await this.infrastructure.getSnapshot(),
|
||
});
|
||
|
||
// 4. 用户通知(≤24h,CCPA要求72h内通知受影响用户)
|
||
await this.notificationService.scheduleBreachNotification(incident);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 24.3 Bug Bounty
|
||
|
||
| 严重程度 | 奖励范围 | 示例 |
|
||
|---------|---------|------|
|
||
| Critical | $50K-$100K | 合约资金盗取、MPC密钥泄露 |
|
||
| High | $10K-$50K | 权限绕过、数据泄露 |
|
||
| Medium | $2K-$10K | 信息泄露、DoS |
|
||
| Low | $500-$2K | 低影响漏洞 |
|
||
|
||
---
|
||
|
||
## 25. 灾难恢复与业务连续性
|
||
|
||
### 25.1 DR指标
|
||
|
||
| 指标 | 目标 |
|
||
|------|------|
|
||
| RPO(恢复点目标) | < 1分钟 |
|
||
| RTO(恢复时间目标) | < 15分钟 |
|
||
|
||
### 25.2 备份与故障转移
|
||
|
||
```yaml
|
||
# 数据库备份策略
|
||
postgresql:
|
||
replication: streaming_replication # 实时同步至备用区域
|
||
backup:
|
||
full: weekly # 每周全量备份
|
||
incremental: every_15_minutes # 每15分钟增量备份
|
||
retention: 90_days # 备份保留90天
|
||
failover:
|
||
automatic: true # 自动故障转移
|
||
health_check_interval: 5s
|
||
|
||
# 链下数据快照
|
||
orderbook:
|
||
snapshot: every_5_minutes # 订单簿快照
|
||
wal: continuous # 预写日志持续记录
|
||
|
||
# 链上数据:区块链本身分布式存储,天然灾备
|
||
genex_chain:
|
||
validators: 3 # ≥3自有验证节点,不同地理位置
|
||
third_party_rpc: [alchemy, quicknode] # 备用RPC节点
|
||
```
|
||
|
||
### 25.3 业务连续性
|
||
|
||
| 故障场景 | 应对策略 |
|
||
|---------|---------|
|
||
| 交易系统故障 | 自动切换备用撮合引擎,未完成订单状态保护 |
|
||
| 链节点故障 | 多节点冗余(≥3个自有 + 第三方RPC备用) |
|
||
| 法币通道故障 | ≥2家支付服务商,自动热备切换 |
|
||
| MPC密钥服务故障 | 密钥分片跨地理位置存储,Fireblocks/Fordefi热备 |
|
||
| 年度DR演练 | 每年≥1次全量灾难恢复演练并记录 |
|
||
|
||
---
|
||
|
||
## 26. 映射表安全方案
|
||
|
||
> 手机号→地址映射表是最高价值安全资产,被篡改将导致资产错误转移。
|
||
|
||
```typescript
|
||
// translate-service/src/security/mapping-security.service.ts
|
||
export class MappingSecurityService {
|
||
/**
|
||
* 映射记录创建/修改需多方签名:
|
||
* 平台服务器 + HSM + 第三方审计节点
|
||
*/
|
||
async createMapping(userId: string, chainAddress: string): Promise<void> {
|
||
// 1. MPC多方签名
|
||
const signature = await this.mpcSigner.multiSign({
|
||
parties: ['platform_server', 'hsm_module', 'audit_node'],
|
||
threshold: 2, // 至少2方签名
|
||
data: { userId, chainAddress },
|
||
});
|
||
|
||
// 2. 写入加密数据库(AES-256,HSM管理密钥)
|
||
await this.encryptedStore.write(userId, chainAddress, signature);
|
||
|
||
// 3. 记录到append-only审计日志
|
||
await this.auditLog.append({
|
||
action: 'mapping_created',
|
||
userId, chainAddress, signature,
|
||
});
|
||
}
|
||
|
||
// 定期链上锚定(Merkle Root)
|
||
@Cron('0 * * * *') // 每小时
|
||
async anchorToChain(): Promise<void> {
|
||
const merkleRoot = await this.computeMappingMerkleRoot();
|
||
await this.chainClient.anchorMerkleRoot(merkleRoot);
|
||
}
|
||
|
||
// 完整性校验
|
||
async verifyIntegrity(): Promise<boolean> {
|
||
const currentRoot = await this.computeMappingMerkleRoot();
|
||
const chainRoot = await this.chainClient.getLatestMerkleRoot();
|
||
return currentRoot === chainRoot;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 27. 发行方违约处理
|
||
|
||
```typescript
|
||
// issuer-service/src/domain/services/default-handling.service.ts
|
||
export class IssuerDefaultService {
|
||
async handleDefault(issuerId: string, severity: 'minor' | 'major' | 'critical'): Promise<void> {
|
||
switch (severity) {
|
||
case 'minor':
|
||
// 降级 + 额度缩减
|
||
await this.issuerRepo.downgradeCreditRating(issuerId, 1);
|
||
break;
|
||
case 'major':
|
||
// 冻结发行 + 启用保障资金
|
||
await this.issuerRepo.freezeIssuance(issuerId);
|
||
if (await this.hasGuaranteeFund(issuerId)) {
|
||
await this.activateGuaranteeFund(issuerId);
|
||
}
|
||
break;
|
||
case 'critical':
|
||
// 跑路:冻结账户 + 链上标记风险券 + 通知持有人
|
||
await this.issuerRepo.freezeAccount(issuerId);
|
||
await this.chainClient.markRiskCoupons(issuerId);
|
||
await this.notifyAllHolders(issuerId, '发行方异常,请关注券状态');
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 28. 多币种与法币通道
|
||
|
||
```typescript
|
||
// 多稳定币支持
|
||
interface StablecoinConfig {
|
||
primary: 'USDC';
|
||
secondary: 'USDT';
|
||
oracle: 'chainlink'; // 汇率预言机
|
||
}
|
||
|
||
// 法币通道热备
|
||
export class FiatGatewayService {
|
||
private providers: FiatProvider[] = [
|
||
{ name: 'primary_bank', priority: 1, status: 'active' },
|
||
{ name: 'backup_processor', priority: 2, status: 'standby' },
|
||
];
|
||
|
||
async processPayment(request: PaymentRequest): Promise<PaymentResult> {
|
||
for (const provider of this.providers.sort((a, b) => a.priority - b.priority)) {
|
||
try {
|
||
if (provider.status === 'active' || provider.status === 'standby') {
|
||
return await provider.process(request);
|
||
}
|
||
} catch (error) {
|
||
this.logger.error(`Fiat provider ${provider.name} failed, trying next`);
|
||
provider.status = 'failed';
|
||
continue; // 自动切换
|
||
}
|
||
}
|
||
throw new ServiceUnavailableException('All fiat providers unavailable');
|
||
}
|
||
}
|
||
|
||
// 汇率管理
|
||
export class ExchangeRateService {
|
||
async getRate(from: string, to: string): Promise<ExchangeRate> {
|
||
// Oracle获取实时汇率
|
||
const rate = await this.oracleClient.getRate(from, to);
|
||
return { rate: rate.value, lockedUntil: addMinutes(new Date(), 15) }; // 锁定15分钟
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 29. 链上对账
|
||
|
||
```typescript
|
||
// 链上数据 vs 链下账本对账
|
||
export class ReconciliationService {
|
||
// 实时对账(链上数据即账本)
|
||
async reconcile(): Promise<ReconciliationResult> {
|
||
const chainBalances = await this.chainClient.getAllBalances();
|
||
const dbBalances = await this.financeRepo.getAllBalances();
|
||
|
||
const discrepancies: Discrepancy[] = [];
|
||
for (const [userId, chainBal] of chainBalances) {
|
||
const dbBal = dbBalances.get(userId);
|
||
if (Math.abs(chainBal - dbBal) > 0.01) {
|
||
discrepancies.push({ userId, chainBalance: chainBal, dbBalance: dbBal });
|
||
}
|
||
}
|
||
|
||
if (discrepancies.length > 0) {
|
||
await this.alertService.sendAlert('reconciliation_mismatch', discrepancies);
|
||
}
|
||
|
||
return { totalChecked: chainBalances.size, discrepancies };
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 30. 容量规划
|
||
|
||
| 阶段 | 用户规模 | 日交易量 | 基础设施 |
|
||
|------|---------|---------|---------|
|
||
| Phase 1 (基础平台) | 10万 | 50万笔 | 单区域K8s(3节点) |
|
||
| Phase 2 (商业化) | 100万 | 500万笔 | 双区域K8s + 热备(5+节点) |
|
||
| Phase 3 (金融化) | 1,000万 | 5,000万笔 | 多区域集群 + GCFN节点 |
|
||
|
||
### 关键性能指标
|
||
|
||
| 指标 | 目标 |
|
||
|------|------|
|
||
| API响应时间(P99) | < 200ms |
|
||
| 撮合引擎延迟 | < 10ms |
|
||
| 翻译层额外耗时 | < 50ms |
|
||
| 系统可用性 | > 99.9% SLA |
|
||
|
||
---
|
||
|
||
## 31. SDK开发计划
|
||
|
||
| SDK | 语言/平台 | 功能范围 | 优先级 |
|
||
|-----|----------|---------|--------|
|
||
| genex-js | JavaScript/Node.js | 全功能(发行、交易、核销、数据查询) | P0 |
|
||
| genex-java | Java/Android | 全功能 + Android原生集成 | P0 |
|
||
| genex-python | Python | 数据分析、信用查询、批量操作 | P1 |
|
||
| genex-swift | Swift/iOS | 移动端核销、用户端功能 | P1 |
|
||
| genex-go | Go | 高性能服务端集成、做市商接入 | P2 |
|
||
|
||
### 开发者门户
|
||
|
||
- 沙箱环境(Testnet API + 测试券 + 测试USDC)
|
||
- API文档(OpenAPI 3.0 + Swagger UI)
|
||
- SDK快速入门指南
|
||
- Webhook事件订阅
|
||
|
||
---
|
||
|
||
## 32. 会计处理(ASC 606 / GAAP)
|
||
|
||
### 32.1 收入确认框架(ASC 606五步法)
|
||
|
||
```typescript
|
||
// clearing-service/src/domain/services/accounting.service.ts
|
||
|
||
/**
|
||
* ASC 606 五步法收入确认:
|
||
* 1. 识别合同 → 券发行/交易成交
|
||
* 2. 识别履约义务 → 券兑付义务(发行方)/ 平台服务义务
|
||
* 3. 确定交易价格 → 发行价/成交价
|
||
* 4. 分配交易价格 → 各履约义务
|
||
* 5. 确认收入 → 履约义务满足时
|
||
*/
|
||
export class AccountingService {
|
||
/**
|
||
* 券发行时:发行方收到资金 → 记入递延负债(Deferred Revenue)
|
||
* 券核销时:履约义务满足 → 递延负债转为已确认收入
|
||
* 券过期时:Breakage收入确认(ASC 606-10-55-48~51)
|
||
*/
|
||
async recordIssuance(event: CouponIssuedEvent): Promise<JournalEntry[]> {
|
||
const entries: JournalEntry[] = [];
|
||
|
||
// 发行方视角:收到现金,形成递延负债
|
||
entries.push({
|
||
date: event.issuedAt,
|
||
debit: { account: 'cash', amount: event.totalSalesAmount },
|
||
credit: { account: 'deferred_revenue', amount: event.totalSalesAmount },
|
||
memo: `券发行 Batch#${event.batchId},发行方${event.issuerId}`,
|
||
reference: event.txHash,
|
||
});
|
||
|
||
// 平台视角:发行服务费收入(立即确认,平台履约义务已完成)
|
||
const issuanceFee = event.totalSalesAmount * event.feeRate;
|
||
entries.push({
|
||
date: event.issuedAt,
|
||
debit: { account: 'accounts_receivable_issuer', amount: issuanceFee },
|
||
credit: { account: 'revenue_issuance_fee', amount: issuanceFee },
|
||
memo: `发行服务费 ${event.feeRate * 100}%`,
|
||
reference: event.txHash,
|
||
});
|
||
|
||
await this.journalRepo.batchSave(entries);
|
||
return entries;
|
||
}
|
||
|
||
async recordRedemption(event: CouponRedeemedEvent): Promise<JournalEntry[]> {
|
||
const entries: JournalEntry[] = [];
|
||
|
||
// 发行方视角:券核销 → 递延负债转为已确认收入
|
||
entries.push({
|
||
date: event.redeemedAt,
|
||
debit: { account: 'deferred_revenue', amount: event.faceValue },
|
||
credit: { account: 'revenue_earned', amount: event.faceValue },
|
||
memo: `券核销 Token#${event.tokenId}`,
|
||
reference: event.txHash,
|
||
});
|
||
|
||
await this.journalRepo.batchSave(entries);
|
||
return entries;
|
||
}
|
||
|
||
async recordTrade(event: TradeSettledEvent): Promise<JournalEntry[]> {
|
||
const entries: JournalEntry[] = [];
|
||
|
||
// 平台视角:交易手续费收入(成交即确认)
|
||
entries.push({
|
||
date: event.settledAt,
|
||
debit: { account: 'cash_stablecoin', amount: event.buyerFee + event.sellerFee },
|
||
credit: { account: 'revenue_trading_fee', amount: event.buyerFee + event.sellerFee },
|
||
memo: `交易手续费 Order#${event.orderId}`,
|
||
reference: event.txHash,
|
||
});
|
||
|
||
await this.journalRepo.batchSave(entries);
|
||
return entries;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 32.2 Breakage收入确认(ASC 606-10-55-48~51)
|
||
|
||
```typescript
|
||
// clearing-service/src/domain/services/breakage-accounting.service.ts
|
||
export class BreakageAccountingService {
|
||
/**
|
||
* ASC 606 Breakage收入确认规则:
|
||
* - 如果发行方预期部分券不会被兑付(Breakage),且Breakage金额可合理估计
|
||
* → 在券生命周期内按比例确认Breakage收入(proportional method)
|
||
* - 如果Breakage不可合理估计
|
||
* → 在券到期/权利失效时一次性确认(remote method)
|
||
*
|
||
* 平台策略:初期采用remote method(到期时确认),积累足够历史数据后切换proportional method
|
||
*/
|
||
private readonly RECOGNITION_METHOD: 'proportional' | 'remote' = 'remote';
|
||
|
||
async processBreakageAccounting(coupon: ExpiredCoupon): Promise<JournalEntry[]> {
|
||
const entries: JournalEntry[] = [];
|
||
|
||
if (this.RECOGNITION_METHOD === 'remote') {
|
||
// Remote method: 到期时一次性确认
|
||
// 发行方侧:递延负债 → Breakage收入
|
||
entries.push({
|
||
date: coupon.expiryDate,
|
||
debit: { account: 'deferred_revenue', amount: coupon.faceValue },
|
||
credit: { account: 'revenue_breakage', amount: coupon.faceValue },
|
||
memo: `Breakage收入确认(remote method)Token#${coupon.tokenId}`,
|
||
});
|
||
|
||
// 平台侧:Breakage分润(按协议比例)
|
||
const platformShare = coupon.faceValue * this.PLATFORM_BREAKAGE_SHARE;
|
||
entries.push({
|
||
date: coupon.expiryDate,
|
||
debit: { account: 'accounts_receivable_breakage', amount: platformShare },
|
||
credit: { account: 'revenue_breakage_share', amount: platformShare },
|
||
memo: `平台Breakage分润 ${this.PLATFORM_BREAKAGE_SHARE * 100}%`,
|
||
});
|
||
}
|
||
|
||
await this.journalRepo.batchSave(entries);
|
||
return entries;
|
||
}
|
||
|
||
/**
|
||
* 递延负债余额报表(监管/审计用)
|
||
* 展示平台上所有未兑付券的递延负债总额
|
||
*/
|
||
async getDeferredRevenueReport(asOfDate: Date): Promise<DeferredRevenueReport> {
|
||
const outstandingCoupons = await this.couponRepo.findOutstanding(asOfDate);
|
||
const totalDeferred = outstandingCoupons.reduce((sum, c) => sum + c.faceValue, 0);
|
||
const byIssuer = this.groupByIssuer(outstandingCoupons);
|
||
|
||
return {
|
||
asOfDate,
|
||
totalDeferredRevenue: totalDeferred,
|
||
byIssuer,
|
||
byMaturity: this.groupByMaturityBucket(outstandingCoupons),
|
||
recognitionMethod: this.RECOGNITION_METHOD,
|
||
};
|
||
}
|
||
}
|
||
|
||
// 会计科目表(Chart of Accounts)
|
||
const CHART_OF_ACCOUNTS = {
|
||
// 资产类
|
||
cash: '1001 现金及等价物',
|
||
cash_stablecoin: '1002 稳定币资产(USDC/USDT)',
|
||
accounts_receivable_issuer: '1101 应收账款-发行方',
|
||
accounts_receivable_breakage: '1102 应收账款-Breakage分润',
|
||
// 负债类
|
||
deferred_revenue: '2001 递延收入(券未兑付负债)',
|
||
user_deposits: '2002 用户托管资金',
|
||
guarantee_funds_held: '2003 发行方保障资金(代管)',
|
||
// 收入类
|
||
revenue_trading_fee: '4001 交易手续费收入',
|
||
revenue_issuance_fee: '4002 发行服务费收入',
|
||
revenue_breakage_share: '4003 Breakage分润收入',
|
||
revenue_vas: '4004 增值服务收入',
|
||
revenue_earned: '4005 已确认收入(发行方侧)',
|
||
revenue_breakage: '4006 Breakage收入(发行方侧)',
|
||
};
|
||
```
|
||
|
||
### 32.3 链上数据与GAAP对账
|
||
|
||
```typescript
|
||
// 链上资产余额 vs GAAP账面价值对账
|
||
export class ChainGaapReconciliationService {
|
||
async reconcile(period: AccountingPeriod): Promise<ReconciliationReport> {
|
||
// 1. 链上数据汇总
|
||
const chainData = {
|
||
totalCouponsOutstanding: await this.chainClient.getTotalOutstandingCoupons(),
|
||
totalStablecoinHeld: await this.chainClient.getTotalStablecoinBalance(),
|
||
totalGuaranteeFunds: await this.chainClient.getTotalGuaranteeFunds(),
|
||
};
|
||
|
||
// 2. GAAP账面数据
|
||
const gaapData = {
|
||
deferredRevenue: await this.accountingRepo.getDeferredRevenue(period.endDate),
|
||
userDeposits: await this.accountingRepo.getUserDeposits(period.endDate),
|
||
guaranteeFunds: await this.accountingRepo.getGuaranteeFunds(period.endDate),
|
||
};
|
||
|
||
// 3. 差异分析
|
||
const discrepancies = this.computeDiscrepancies(chainData, gaapData);
|
||
|
||
return {
|
||
period,
|
||
chainData,
|
||
gaapData,
|
||
discrepancies,
|
||
isClean: discrepancies.length === 0,
|
||
auditorNotes: discrepancies.map(d => this.generateAuditNote(d)),
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 33. 消费者保护法合规(FTC / UDAAP / CARD Act)
|
||
|
||
### 33.1 FTC Act Section 5 / Dodd-Frank UDAAP 规则引擎
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/consumer-protection.service.ts
|
||
export class ConsumerProtectionService {
|
||
/**
|
||
* FTC Act Section 5: 禁止不公平或欺骗性商业行为
|
||
* Dodd-Frank UDAAP: 禁止Unfair, Deceptive, or Abusive Acts or Practices
|
||
*
|
||
* 平台作为券交易市场,对发行方的券描述负审核责任
|
||
*/
|
||
private readonly RULES: ConsumerProtectionRule[] = [
|
||
// 券信息披露完整性检查
|
||
{
|
||
name: 'disclosure_completeness',
|
||
check: (coupon: CouponListing) => {
|
||
const required = ['faceValue', 'expiryDate', 'useConditions', 'issuerCreditRating'];
|
||
const missing = required.filter(f => !coupon[f]);
|
||
return missing.length === 0
|
||
? { pass: true }
|
||
: { pass: false, violation: `缺少必要披露: ${missing.join(',')}` };
|
||
},
|
||
},
|
||
// 定价不得误导(折扣率标注准确性)
|
||
{
|
||
name: 'pricing_accuracy',
|
||
check: (coupon: CouponListing) => {
|
||
const actualDiscount = 1 - coupon.currentPrice / coupon.faceValue;
|
||
const labeledDiscount = coupon.displayedDiscount;
|
||
return Math.abs(actualDiscount - labeledDiscount) <= 0.01
|
||
? { pass: true }
|
||
: { pass: false, violation: `标注折扣${labeledDiscount}与实际折扣${actualDiscount}不符` };
|
||
},
|
||
},
|
||
// 发行方信用等级标注准确性
|
||
{
|
||
name: 'credit_rating_accuracy',
|
||
check: (coupon: CouponListing) => {
|
||
const currentRating = coupon.issuer.creditRating;
|
||
const displayedRating = coupon.displayedCreditRating;
|
||
return currentRating === displayedRating
|
||
? { pass: true }
|
||
: { pass: false, violation: `展示信用等级${displayedRating}与实际${currentRating}不符` };
|
||
},
|
||
},
|
||
// 退款政策透明化
|
||
{
|
||
name: 'refund_policy_disclosure',
|
||
check: (coupon: CouponListing) => {
|
||
return coupon.refundPolicy && coupon.refundPolicy.isVisible
|
||
? { pass: true }
|
||
: { pass: false, violation: '退款政策未明确展示' };
|
||
},
|
||
},
|
||
];
|
||
|
||
async auditListing(coupon: CouponListing): Promise<ConsumerProtectionAuditResult> {
|
||
const violations = this.RULES
|
||
.map(rule => ({ rule: rule.name, result: rule.check(coupon) }))
|
||
.filter(r => !r.result.pass);
|
||
|
||
if (violations.length > 0) {
|
||
await this.alertService.sendViolationAlert(coupon.id, violations);
|
||
}
|
||
|
||
return { couponId: coupon.id, pass: violations.length === 0, violations };
|
||
}
|
||
|
||
// AI辅助虚假宣传检测(批量扫描)
|
||
@Cron('0 6 * * *') // 每日凌晨6点
|
||
async batchScanListings(): Promise<void> {
|
||
const activeListings = await this.couponRepo.findActiveListings();
|
||
for (const listing of activeListings) {
|
||
const result = await this.auditListing(listing);
|
||
if (!result.pass) {
|
||
await this.createEnforcementCase(listing, result.violations);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 33.2 CARD Act 有效期冲突检测
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/card-act.service.ts
|
||
export class CardActComplianceService {
|
||
/**
|
||
* CARD Act (Credit CARD Act of 2009) 礼品卡条款:
|
||
* - 有效期不得少于5年
|
||
* - 不得收取休眠费(dormancy/inactivity fees)
|
||
*
|
||
* 冲突:Utility Track强制有效期≤12个月(SEC合规)
|
||
* 解决:论证"消费型券≠Gift Card"(券是发行方预付债务工具,非零售商礼品卡)
|
||
*
|
||
* 本服务实现分类检测:如果券可能被认定为Gift Card,则触发合规审查
|
||
*/
|
||
async checkCardActApplicability(coupon: CouponTemplate): Promise<CardActCheckResult> {
|
||
// Gift Card特征匹配
|
||
const giftCardIndicators = {
|
||
hasFixedFaceValue: coupon.faceValue > 0 && !coupon.isPercentageDiscount,
|
||
isOpenLoop: !coupon.allowedStores || coupon.allowedStores.length === 0,
|
||
isStoreBranded: coupon.allowedStores && coupon.allowedStores.length > 0,
|
||
labeledAsGiftCard: /gift\s*card|礼品卡|礼券/i.test(coupon.name + coupon.description),
|
||
isPreloaded: coupon.couponCategory === 'stored_value',
|
||
};
|
||
|
||
const isLikelyGiftCard = giftCardIndicators.labeledAsGiftCard ||
|
||
(giftCardIndicators.hasFixedFaceValue && giftCardIndicators.isPreloaded);
|
||
|
||
if (isLikelyGiftCard && coupon.couponType === 'utility') {
|
||
// 冲突检测:Utility Track ≤12个月 vs CARD Act ≥5年
|
||
const expiryDays = differenceInDays(coupon.expiryDate, coupon.issuedDate);
|
||
if (expiryDays < 365 * 5) {
|
||
return {
|
||
isConflict: true,
|
||
couponId: coupon.id,
|
||
conflictType: 'CARD_ACT_VS_UTILITY_TRACK',
|
||
detail: `券"${coupon.name}"可能被认定为Gift Card(CARD Act要求≥5年有效期),但Utility Track限制≤12个月`,
|
||
recommendation: [
|
||
'1. 修改券名称/描述,避免使用"礼品卡/Gift Card"字样',
|
||
'2. 或在法律意见书中论证该券为"预付债务工具"而非"零售礼品卡"',
|
||
'3. 或将该券移出Utility Track,按CARD Act规则设置≥5年有效期',
|
||
],
|
||
requiresLegalReview: true,
|
||
};
|
||
}
|
||
}
|
||
|
||
// 休眠费检查(平台不收取,但检查发行方设置)
|
||
if (coupon.inactivityFee && coupon.inactivityFee > 0) {
|
||
return {
|
||
isConflict: true,
|
||
couponId: coupon.id,
|
||
conflictType: 'CARD_ACT_DORMANCY_FEE',
|
||
detail: 'CARD Act禁止对礼品卡/预付卡收取休眠费',
|
||
recommendation: ['移除休眠费设置'],
|
||
requiresLegalReview: false,
|
||
};
|
||
}
|
||
|
||
return { isConflict: false, couponId: coupon.id };
|
||
}
|
||
|
||
// 发行审核时自动触发
|
||
async preIssuanceCardActCheck(template: CouponTemplate): Promise<void> {
|
||
const result = await this.checkCardActApplicability(template);
|
||
if (result.isConflict) {
|
||
// 标记为需要法律审查,暂停发行审批
|
||
await this.issuerRepo.flagForLegalReview(template.id, result);
|
||
throw new ComplianceException(`CARD Act合规冲突: ${result.detail}`);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 33.3 GENIUS Act 稳定币合规
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/genius-act.service.ts
|
||
export class GeniusActComplianceService {
|
||
/**
|
||
* GENIUS Act (2025年签署) — 稳定币监管法:
|
||
* 平台策略:使用第三方合规稳定币(USDC/USDT),不自行发行
|
||
* 合规责任:确保出入金通道对接的稳定币发行方持有合规牌照
|
||
*/
|
||
private readonly APPROVED_STABLECOINS: StablecoinInfo[] = [
|
||
{
|
||
symbol: 'USDC', issuer: 'Circle', license: 'NY BitLicense + State MTLs',
|
||
reserveAudit: 'Deloitte (monthly attestation)', compliant: true,
|
||
},
|
||
{
|
||
symbol: 'USDT', issuer: 'Tether', license: 'Various',
|
||
reserveAudit: 'BDO Italia', compliant: true,
|
||
},
|
||
{
|
||
symbol: 'PYUSD', issuer: 'PayPal/Paxos', license: 'NYDFS regulated',
|
||
reserveAudit: 'Withum', compliant: true,
|
||
},
|
||
];
|
||
|
||
async validateStablecoinCompliance(stablecoinAddress: string): Promise<boolean> {
|
||
const info = this.APPROVED_STABLECOINS.find(s =>
|
||
s.contractAddress === stablecoinAddress
|
||
);
|
||
if (!info || !info.compliant) {
|
||
throw new ComplianceException('不支持的或不合规的稳定币');
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 季度审查:更新稳定币合规状态
|
||
@Cron('0 0 1 */3 *') // 每季度1号
|
||
async quarterlyStablecoinReview(): Promise<void> {
|
||
for (const coin of this.APPROVED_STABLECOINS) {
|
||
const latestAttestation = await this.fetchLatestReserveAttestation(coin.issuer);
|
||
coin.compliant = latestAttestation.isClean;
|
||
if (!coin.compliant) {
|
||
await this.alertService.sendCriticalAlert(
|
||
`稳定币${coin.symbol}储备审计异常,考虑暂停出入金通道`
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 34. 州级合规路由(MTL / BitLicense / DFAL)
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/state-compliance.service.ts
|
||
export class StateComplianceService {
|
||
/**
|
||
* 美国49州+DC各自有独立的Money Transmitter License (MTL)要求
|
||
* 不同州的交易限额、KYC要求、报告义务不同
|
||
* 本服务根据用户所在州动态应用对应合规规则
|
||
*/
|
||
private readonly STATE_RULES: Record<string, StateComplianceRule> = {
|
||
NY: {
|
||
state: 'New York',
|
||
license: 'BitLicense (NYDFS)',
|
||
status: 'required',
|
||
additionalKyc: true, // NY要求增强KYC
|
||
maxSingleTx: 10000, // 单笔上限(USD)
|
||
reportingThreshold: 3000, // 报告阈值
|
||
custodyRequirements: 'NYDFS托管规则:客户资产必须隔离托管',
|
||
restrictions: ['不允许匿名交易', '需额外的NY居民声明'],
|
||
},
|
||
CA: {
|
||
state: 'California',
|
||
license: 'DFAL (2026年7月生效)',
|
||
status: 'pending_regulation',
|
||
additionalKyc: false,
|
||
maxSingleTx: null, // 无额外限制
|
||
reportingThreshold: 3000,
|
||
custodyRequirements: 'DFAL框架下的托管要求(待细则)',
|
||
restrictions: ['CCPA增强隐私要求'],
|
||
},
|
||
TX: {
|
||
state: 'Texas',
|
||
license: 'MTL (Texas Dept of Banking)',
|
||
status: 'required',
|
||
additionalKyc: false,
|
||
maxSingleTx: null,
|
||
reportingThreshold: 3000,
|
||
custodyRequirements: '标准MTL托管要求',
|
||
restrictions: [],
|
||
},
|
||
FL: {
|
||
state: 'Florida',
|
||
license: 'MTL (OFR)',
|
||
status: 'required',
|
||
additionalKyc: false,
|
||
maxSingleTx: null,
|
||
reportingThreshold: 3000,
|
||
custodyRequirements: '标准MTL托管要求',
|
||
restrictions: [],
|
||
},
|
||
// ... 其他州配置(配置化管理,通过Admin后台维护)
|
||
};
|
||
|
||
// 默认规则(适用于未单独配置的州)
|
||
private readonly DEFAULT_RULE: StateComplianceRule = {
|
||
state: 'Default',
|
||
license: 'MTL (standard)',
|
||
status: 'required',
|
||
additionalKyc: false,
|
||
maxSingleTx: null,
|
||
reportingThreshold: 3000,
|
||
custodyRequirements: '标准MTL托管要求',
|
||
restrictions: [],
|
||
};
|
||
|
||
// 受限州(暂不服务,直到获得相应牌照)
|
||
private readonly RESTRICTED_STATES = ['NY', 'HI']; // NY需BitLicense,HI暂不服务
|
||
|
||
async checkStateCompliance(userId: string, transaction: TransactionRequest): Promise<StateCheckResult> {
|
||
const user = await this.userRepo.findById(userId);
|
||
const state = user.residenceState;
|
||
|
||
// 1. 检查是否为受限州
|
||
if (this.RESTRICTED_STATES.includes(state)) {
|
||
const hasLicense = await this.licenseRepo.hasActiveLicense(state);
|
||
if (!hasLicense) {
|
||
return {
|
||
allowed: false,
|
||
reason: `暂不支持${state}州用户交易,待取得${this.STATE_RULES[state]?.license || 'MTL'}牌照`,
|
||
};
|
||
}
|
||
}
|
||
|
||
// 2. 获取州规则
|
||
const rule = this.STATE_RULES[state] || this.DEFAULT_RULE;
|
||
|
||
// 3. 单笔限额检查
|
||
if (rule.maxSingleTx && transaction.amount > rule.maxSingleTx) {
|
||
return {
|
||
allowed: false,
|
||
reason: `${state}州单笔交易上限$${rule.maxSingleTx}`,
|
||
};
|
||
}
|
||
|
||
// 4. 增强KYC检查(如NY)
|
||
if (rule.additionalKyc && user.kycLevel < KycLevel.L2) {
|
||
return {
|
||
allowed: false,
|
||
reason: `${state}州要求KYC L2+`,
|
||
action: 'upgrade_kyc',
|
||
};
|
||
}
|
||
|
||
// 5. 州级报告义务
|
||
if (transaction.amount >= rule.reportingThreshold) {
|
||
await this.stateReportingService.queueReport(state, transaction);
|
||
}
|
||
|
||
return { allowed: true, appliedRule: rule };
|
||
}
|
||
|
||
// 牌照状态管理
|
||
async getLicenseStatus(): Promise<LicenseStatusReport> {
|
||
const allStates = Object.keys(this.STATE_RULES);
|
||
const statuses = await Promise.all(
|
||
allStates.map(async (state) => ({
|
||
state,
|
||
required: this.STATE_RULES[state].license,
|
||
obtained: await this.licenseRepo.hasActiveLicense(state),
|
||
expiryDate: await this.licenseRepo.getLicenseExpiry(state),
|
||
renewalDue: await this.licenseRepo.isRenewalDue(state),
|
||
}))
|
||
);
|
||
return { statuses, restrictedStates: this.RESTRICTED_STATES };
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 35. SEC证券合规(Phase 4预留)
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/securities-compliance.service.ts
|
||
|
||
/**
|
||
* Securities Track合规服务(Phase 4启用)
|
||
*
|
||
* 前提条件:
|
||
* 1. 券的法律属性意见书已出具
|
||
* 2. 如需Broker-Dealer注册,已完成SEC注册
|
||
* 3. 如需ATS注册,已提交Form ATS
|
||
*
|
||
* 豁免路径(SRS 1.5-1.6):
|
||
* - Reg D (Rule 506(b/c)): 面向合格投资者,无公开募集,无发行上限
|
||
* - Reg A+ (Tier 2): 面向所有投资者,年发行上限$75M,需SEC审查
|
||
* - Reg CF: 众筹豁免,年上限$5M,需通过注册融资门户
|
||
*/
|
||
export class SecuritiesComplianceService {
|
||
private readonly PHASE4_ENABLED = false; // Phase 4前保持关闭
|
||
|
||
// Reg D 506(c) 合格投资者验证
|
||
async verifyAccreditedInvestor(userId: string): Promise<AccreditationResult> {
|
||
if (!this.PHASE4_ENABLED) throw new Error('Securities Track not enabled');
|
||
|
||
const user = await this.userRepo.findById(userId);
|
||
|
||
// SEC Rule 501(a) 合格投资者标准
|
||
const criteria = {
|
||
// 个人:年收入>$200K(或与配偶合计>$300K)连续2年
|
||
incomeTest: user.annualIncome >= 200000 || user.jointIncome >= 300000,
|
||
// 个人:净资产>$1M(不含主要住所)
|
||
netWorthTest: user.netWorthExcludingPrimaryResidence >= 1000000,
|
||
// 专业认证:Series 7/65/82等
|
||
professionalTest: user.hasFinancialLicense,
|
||
// 实体:资产>$5M
|
||
entityTest: user.isEntity && user.entityAssets >= 5000000,
|
||
};
|
||
|
||
const isAccredited = criteria.incomeTest || criteria.netWorthTest ||
|
||
criteria.professionalTest || criteria.entityTest;
|
||
|
||
return {
|
||
userId,
|
||
isAccredited,
|
||
verificationMethod: 'self_certification', // 初期自认证,后期第三方验证
|
||
verifiedAt: new Date(),
|
||
validUntil: addMonths(new Date(), 12), // 12个月有效
|
||
criteria,
|
||
};
|
||
}
|
||
|
||
// Reg A+ 投资限额检查
|
||
async checkRegAInvestmentLimit(userId: string, amount: number): Promise<boolean> {
|
||
if (!this.PHASE4_ENABLED) throw new Error('Securities Track not enabled');
|
||
|
||
const user = await this.userRepo.findById(userId);
|
||
|
||
// Reg A+ Tier 2: 非合格投资者年投资上限 = max(年收入, 净资产) × 10%
|
||
if (!user.isAccredited) {
|
||
const limit = Math.max(user.annualIncome, user.netWorth) * 0.10;
|
||
const ytdInvestment = await this.investmentRepo.getYtdTotal(userId);
|
||
return (ytdInvestment + amount) <= limit;
|
||
}
|
||
|
||
return true; // 合格投资者无限额
|
||
}
|
||
|
||
// Form ATS报告义务
|
||
async generateAtsReport(quarter: string): Promise<AtsQuarterlyReport> {
|
||
if (!this.PHASE4_ENABLED) throw new Error('Securities Track not enabled');
|
||
|
||
return {
|
||
quarter,
|
||
totalSecuritiesTraded: await this.tradeRepo.getSecuritiesVolume(quarter),
|
||
uniqueParticipants: await this.tradeRepo.getUniqueSecuritiesTraders(quarter),
|
||
topSecurities: await this.tradeRepo.getTopSecuritiesByVolume(quarter, 10),
|
||
// SEC Form ATS-N 披露要求
|
||
atsOperations: {
|
||
orderTypes: ['limit', 'market'],
|
||
matchingLogic: 'price-time priority',
|
||
feeSchedule: this.getSecuritiesFeeSchedule(),
|
||
},
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 36. 商业保险需求跟踪
|
||
|
||
```typescript
|
||
// compliance-service/src/domain/services/insurance-tracking.service.ts
|
||
|
||
/**
|
||
* 商业保险管理(Nasdaq上市审计要求)
|
||
* IPO前12个月完成所有保险采购
|
||
*/
|
||
export class InsuranceTrackingService {
|
||
private readonly REQUIRED_POLICIES: InsurancePolicyRequirement[] = [
|
||
{
|
||
type: 'D&O',
|
||
name: '董事及高管责任险 (Directors & Officers)',
|
||
description: '董事/高管因管理决策被诉的法律费用与赔偿',
|
||
minCoverage: 'match_market_cap', // 覆盖额度匹配公司市值
|
||
requiredBy: 'IPO前12个月',
|
||
status: 'not_purchased',
|
||
carriers: ['AIG', 'Chubb', 'Berkshire Hathaway'],
|
||
},
|
||
{
|
||
type: 'E&O',
|
||
name: '专业责任险 (Errors & Omissions)',
|
||
description: '平台服务失误导致用户损失(信用评级错误、交易撮合问题)',
|
||
minCoverage: 10_000_000, // $10M
|
||
requiredBy: 'IPO前12个月',
|
||
status: 'not_purchased',
|
||
carriers: ['Lloyd\'s', 'AIG', 'Travelers'],
|
||
},
|
||
{
|
||
type: 'CYBER',
|
||
name: '网络安全险 (Cyber Liability)',
|
||
description: '数据泄露、黑客攻击、勒索软件损失与响应成本',
|
||
minCoverage: 50_000_000, // $50M(覆盖MPC托管的全部用户资产)
|
||
requiredBy: 'IPO前12个月',
|
||
status: 'not_purchased',
|
||
carriers: ['Coalition', 'Beazley', 'AIG'],
|
||
notes: '需覆盖数字资产托管风险,保费较高',
|
||
},
|
||
{
|
||
type: 'FIDELITY',
|
||
name: '忠诚保证金 (Fidelity Bond)',
|
||
description: '内部员工欺诈、盗窃数字资产',
|
||
minCoverage: 5_000_000, // $5M
|
||
requiredBy: 'IPO前12个月',
|
||
status: 'not_purchased',
|
||
carriers: ['Travelers', 'Hartford', 'CNA'],
|
||
},
|
||
{
|
||
type: 'GL',
|
||
name: '商业综合险 (General Liability)',
|
||
description: '一般商业责任',
|
||
minCoverage: 2_000_000, // $2M
|
||
requiredBy: 'IPO前12个月',
|
||
status: 'not_purchased',
|
||
carriers: ['Progressive', 'Hartford'],
|
||
},
|
||
];
|
||
|
||
async getInsuranceStatus(): Promise<InsuranceStatusReport> {
|
||
const policies = await this.policyRepo.findAll();
|
||
return {
|
||
requiredPolicies: this.REQUIRED_POLICIES.map(req => {
|
||
const purchased = policies.find(p => p.type === req.type);
|
||
return {
|
||
...req,
|
||
status: purchased ? 'active' : 'not_purchased',
|
||
policyNumber: purchased?.policyNumber,
|
||
effectiveDate: purchased?.effectiveDate,
|
||
expiryDate: purchased?.expiryDate,
|
||
coverageAmount: purchased?.coverageAmount,
|
||
annualPremium: purchased?.annualPremium,
|
||
isAdequate: purchased
|
||
? this.isCoverageAdequate(req, purchased)
|
||
: false,
|
||
};
|
||
}),
|
||
allRequirementsMet: this.REQUIRED_POLICIES.every(
|
||
req => policies.some(p => p.type === req.type && p.status === 'active')
|
||
),
|
||
};
|
||
}
|
||
|
||
// 保险到期提醒
|
||
@Cron('0 9 * * 1') // 每周一上午9点
|
||
async checkExpiringPolicies(): Promise<void> {
|
||
const policies = await this.policyRepo.findExpiringWithin(90); // 90天内到期
|
||
for (const policy of policies) {
|
||
await this.alertService.sendAlert({
|
||
type: 'insurance_expiring',
|
||
severity: 'high',
|
||
message: `${policy.name}将于${policy.expiryDate}到期,请及时续期`,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
*文档版本: v2.1*
|
||
*基于: Genex 券交易平台 - 软件需求规格说明书 v4.1*
|
||
*技术栈: NestJS + Go + Kong + PostgreSQL + Kafka + Redis*
|
||
*更新: v2.1补充会计处理(ASC 606/递延负债/GAAP对账)/消费者保护法(FTC/UDAAP/CARD Act冲突检测/GENIUS Act)/州级合规路由(MTL/BitLicense/DFAL)/SEC证券合规(Reg D/A+/CF预留)/商业保险跟踪*
|