fix: 区块链生态审计修复 — SDK补全 + Enterprise API加固 + 删除无用wallet-service

基于08-区块链生态基础设施开发指南的全面审计,修复以下问题:

## SDK 补全(对齐指南 §7.2-7.4)

- **JS SDK**: 新增 SettlementModule (settlement.ts),实现 executeSwap() 合约交互
  和 onSwapExecuted() 事件监听,补齐指南 §7.2 要求的 settlement 模块
- **Go SDK**: 新增 ExecuteSwap() 函数 (settlement.go),完整实现 ABI 编码 → nonce
  获取 → gas 估算 → 签名 → 广播 → receipt 等链上交易全流程
- **Dart SDK**: 新增统一事件订阅接口 subscribeEvents(EventFilter),匹配指南 §7.4
  规范;新增 EventFilter 模型类,支持 newHeads/logs 两种订阅类型

## Enterprise API 加固(对齐指南 §3.2/§3.4)

- 新增 TierThrottlerGuard 分层限流守卫,按 API tier 区分速率限制:
  public 60/min, institutional 600/min, regulatory/internal unlimited
- WebSocket 网关增加完整认证:API Key 通过 query param 或 header 传递,
  最低要求 institutional 级别,未认证连接自动拒绝

## 删除无用的 wallet-service(架构纠正)

- 删除 blockchain/wallet-service/ 整个目录(13个文件,875行代码)
  该服务架构设计有误:钱包操作(用户钱包、机构操作、治理多签)已由现有
  后端微服务处理(user-service:3001、issuer-service:3002、trading-service:3003、
  clearing-service:3004),无需在 blockchain/ 目录下另建独立服务
- docker-compose.yml: 移除 wallet-service 服务定义和端口 3021 映射
- chain-ci.yml: 从 NestJS 生态服务 CI matrix 中移除 wallet-service
- 08-指南: 删除第4节(钱包体系 §4.1-4.3),移除部署清单中 MPC签名服务:3021,
  更新生态全景图,章节重新编号 (12→11章)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-15 22:05:52 -08:00
parent 3783c5a91b
commit 0ea869ac46
28 changed files with 350 additions and 875 deletions

View File

@ -85,7 +85,6 @@ jobs:
matrix:
service:
- enterprise-api
- wallet-service
- gas-relayer
- faucet-service
steps:

View File

@ -7,7 +7,6 @@
# - genex-regulatory: 1个监管只读节点
# - contract-deployer: 智能合约部署任务
# - enterprise-api: 企业API服务 (:3020)
# - wallet-service: MPC钱包服务 (:3021)
# - gas-relayer: Gas代付中继 (:3022)
# - faucet: 测试网水龙头 (:3023)
# - bridge-monitor: 跨链桥监控 (:3024)
@ -216,28 +215,6 @@ services:
profiles:
- ecosystem
# Wallet Service — MPC多方计算签名服务
wallet-service:
build:
context: ./wallet-service
dockerfile: Dockerfile
container_name: genex-wallet-service
restart: unless-stopped
environment:
- PORT=3021
- GENEX_RPC_URL=http://genex-node-1:8545
- CHAIN_ID=8888
- REDIS_URL=redis://redis:6379
- HSM_PROVIDER=aws-cloudhsm
ports:
- "3021:3021"
networks:
- genex-net
depends_on:
- genex-node-1
profiles:
- ecosystem
# Gas Relayer — Meta-TX 代付中继
gas-relayer:
build:

View File

@ -1,7 +1,9 @@
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { ConfigModule } from '@nestjs/config';
import { ThrottlerModule } from '@nestjs/throttler';
import configuration from './config/configuration';
import { TierThrottlerGuard } from './common/guards/tier-throttler.guard';
import { BlocksController } from './modules/blocks/blocks.controller';
import { BlocksService } from './modules/blocks/blocks.service';
import { TransactionsController } from './modules/transactions/transactions.controller';
@ -24,7 +26,7 @@ import { EventsService } from './modules/events/events.service';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
ThrottlerModule.forRoot([{ ttl: 60000, limit: 60 }]),
ThrottlerModule.forRoot([{ ttl: 60000, limit: 600 }]),
],
controllers: [
BlocksController,
@ -37,6 +39,8 @@ import { EventsService } from './modules/events/events.service';
RegulatoryController,
],
providers: [
// 全局分层限流守卫(按 API tier 区分public 60/min, institutional 600/min, regulatory unlimited
{ provide: APP_GUARD, useClass: TierThrottlerGuard },
BlocksService,
TransactionsService,
AddressService,

View File

@ -0,0 +1,71 @@
import { Injectable, ExecutionContext } from '@nestjs/common';
import { ThrottlerGuard, ThrottlerException } from '@nestjs/throttler';
import { ApiTier } from './api-key.guard';
/**
* API tier
*
* §3.4
* public: 60 req/min, 1,000 req/hour
* institutional: 600 req/min, 50,000 req/hour
* regulatory: unlimited
* internal: unlimited
*/
@Injectable()
export class TierThrottlerGuard extends ThrottlerGuard {
private static readonly TIER_LIMITS: Record<ApiTier, { minuteLimit: number; hourLimit: number }> = {
public: { minuteLimit: 60, hourLimit: 1000 },
institutional: { minuteLimit: 600, hourLimit: 50000 },
regulatory: { minuteLimit: 0, hourLimit: 0 }, // 0 = unlimited
internal: { minuteLimit: 0, hourLimit: 0 },
};
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const tier: ApiTier = request.apiTier || 'public';
const limits = TierThrottlerGuard.TIER_LIMITS[tier];
// regulatory 和 internal 不限流
if (limits.minuteLimit === 0) {
return true;
}
// 使用 API key 作为限流 key比 IP 更精确)
const apiKey = request.headers['x-api-key'] as string || request.ip;
// 分钟级限流检查
const minuteKey = `throttle:min:${apiKey}`;
const minuteCount = this.incrementCounter(minuteKey, 60);
if (await minuteCount > limits.minuteLimit) {
throw new ThrottlerException(`Rate limit exceeded: ${limits.minuteLimit} requests/minute for ${tier} tier`);
}
// 小时级限流检查
const hourKey = `throttle:hour:${apiKey}`;
const hourCount = this.incrementCounter(hourKey, 3600);
if (await hourCount > limits.hourLimit) {
throw new ThrottlerException(`Rate limit exceeded: ${limits.hourLimit} requests/hour for ${tier} tier`);
}
return true;
}
/**
* Redis INCR + TTL
*/
private counters = new Map<string, { count: number; expiresAt: number }>();
private async incrementCounter(key: string, ttlSeconds: number): Promise<number> {
const now = Date.now();
const entry = this.counters.get(key);
if (!entry || entry.expiresAt <= now) {
this.counters.set(key, { count: 1, expiresAt: now + ttlSeconds * 1000 });
return 1;
}
entry.count++;
return entry.count;
}
}

View File

@ -1,34 +1,86 @@
import { Logger } from '@nestjs/common';
import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, WebSocket } from 'ws';
import { IncomingMessage } from 'http';
import { EventsService } from './events.service';
import { ApiTier } from '../../common/guards/api-key.guard';
/**
* WebSocket
*
* query param header API Key
* - ws://host:3020/v1/ws/events?apiKey=gx_inst_xxx
* - header: X-API-Key: gx_inst_xxx
*
* institutional §3.2
*/
@WebSocketGateway({ path: '/v1/ws/events' })
export class EventsGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer() server: Server;
private readonly logger = new Logger(EventsGateway.name);
private readonly authenticatedClients = new WeakSet<WebSocket>();
constructor(private readonly eventsService: EventsService) {}
afterInit() {
this.eventsService.startListening((event) => {
this.server.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
if (client.readyState === WebSocket.OPEN && this.authenticatedClients.has(client)) {
client.send(JSON.stringify(event));
}
});
});
}
handleConnection(client: WebSocket) {
client.send(JSON.stringify({ type: 'connected', chainId: 8888 }));
handleConnection(client: WebSocket, req: IncomingMessage) {
// 从 query param 或 header 获取 API Key
const url = new URL(req.url || '', `http://${req.headers.host}`);
const apiKey = url.searchParams.get('apiKey') || req.headers['x-api-key'] as string;
if (!apiKey) {
client.send(JSON.stringify({ type: 'error', message: 'Missing API key. Pass via ?apiKey= or X-API-Key header' }));
client.close(4001, 'Unauthorized: missing API key');
return;
}
const tier = this.resolveApiKeyTier(apiKey);
if (!tier) {
client.send(JSON.stringify({ type: 'error', message: 'Invalid API key' }));
client.close(4001, 'Unauthorized: invalid API key');
return;
}
// WebSocket 需要 institutional 或更高权限
const tierHierarchy: ApiTier[] = ['public', 'institutional', 'regulatory', 'internal'];
if (tierHierarchy.indexOf(tier) < tierHierarchy.indexOf('institutional')) {
client.send(JSON.stringify({ type: 'error', message: 'WebSocket requires institutional tier or above' }));
client.close(4003, 'Forbidden: insufficient API tier');
return;
}
this.authenticatedClients.add(client);
this.logger.log(`WebSocket client connected (tier: ${tier})`);
client.send(JSON.stringify({ type: 'connected', chainId: 8888, tier }));
}
handleDisconnect() {
// cleanup
handleDisconnect(client: WebSocket) {
this.authenticatedClients.delete(client);
}
@SubscribeMessage('subscribe')
handleSubscribe(client: WebSocket, data: { eventType: string }) {
if (!this.authenticatedClients.has(client)) {
return { event: 'error', data: { message: 'Not authenticated' } };
}
// 支持订阅特定事件类型: newBlock, largeTx, compliance, couponMint
return { event: 'subscribed', data: { eventType: data.eventType } };
}
private resolveApiKeyTier(apiKey: string): ApiTier | null {
if (apiKey.startsWith('gx_internal_')) return 'internal';
if (apiKey.startsWith('gx_reg_')) return 'regulatory';
if (apiKey.startsWith('gx_inst_')) return 'institutional';
if (apiKey.startsWith('gx_pub_')) return 'public';
return null;
}
}

View File

@ -14,6 +14,7 @@ export 'src/models/block_info.dart';
export 'src/models/transaction_info.dart';
export 'src/models/chain_stats.dart';
export 'src/models/chain_event.dart';
export 'src/models/event_filter.dart';
export 'src/models/address_balance.dart';
// RPC

View File

@ -6,6 +6,7 @@ import 'models/block_info.dart';
import 'models/transaction_info.dart';
import 'models/chain_stats.dart';
import 'models/chain_event.dart';
import 'models/event_filter.dart';
import 'models/address_balance.dart';
import 'rpc/json_rpc_client.dart';
import 'rpc/websocket_client.dart';
@ -133,12 +134,23 @@ class GenexClient {
//
GenexWebSocketClient _ensureWs() {
_ws ??= GenexWebSocketClient(rpcUrl.replaceFirst('http', 'ws'));
return _ws!;
}
/// §8.4 subscribeEvents
///
/// [filter]
/// - type='newHeads'
/// - type='logs' address / topics
Stream<ChainEvent> subscribeEvents(EventFilter filter) {
return _ensureWs().subscribe(filter.type, filter.params);
}
/// WebSocket
Stream<ChainEvent> subscribeNewHeads() {
_ws ??= GenexWebSocketClient(
rpcUrl.replaceFirst('http', 'ws'),
);
return _ws!.subscribe('newHeads', {});
return _ensureWs().subscribe('newHeads', {});
}
///
@ -146,13 +158,10 @@ class GenexClient {
String? address,
List<String>? topics,
}) {
_ws ??= GenexWebSocketClient(
rpcUrl.replaceFirst('http', 'ws'),
);
final params = <String, dynamic>{};
if (address != null) params['address'] = address;
if (topics != null) params['topics'] = topics;
return _ws!.subscribe('logs', params);
return _ensureWs().subscribe('logs', params);
}
//

View File

@ -0,0 +1,34 @@
/// §8.4 subscribeEvents
class EventFilter {
/// : 'newHeads' | 'logs'
final String type;
/// logs 使
final String? address;
/// logs 使
final List<String>? topics;
const EventFilter({
required this.type,
this.address,
this.topics,
});
///
const EventFilter.newHeads()
: type = 'newHeads',
address = null,
topics = null;
///
const EventFilter.logs({this.address, this.topics}) : type = 'logs';
/// eth_subscribe params
Map<String, dynamic> get params {
final p = <String, dynamic>{};
if (address != null) p['address'] = address;
if (topics != null) p['topics'] = topics;
return p;
}
}

View File

@ -0,0 +1,96 @@
package genex
import (
"context"
"fmt"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// Settlement 合约 ABIexecuteSwap 函数签名)
const settlementABIJSON = `[{"name":"executeSwap","type":"function","inputs":[{"name":"tokenId","type":"uint256"},{"name":"buyer","type":"address"},{"name":"seller","type":"address"},{"name":"price","type":"uint256"},{"name":"stablecoin","type":"address"}],"outputs":[]}]`
// SettlementContractAddress 默认 Settlement 合约地址(可通过配置覆盖)
var SettlementContractAddress = common.HexToAddress("0x0000000000000000000000000000000000000004")
// TxReceipt 交易回执
type TxReceipt struct {
TxHash string `json:"txHash"`
BlockNumber int64 `json:"blockNumber"`
GasUsed uint64 `json:"gasUsed"`
Status uint64 `json:"status"` // 1=success, 0=failed
}
// ExecuteSwap 构造+签名+广播券原子交换交易
func (c *Client) ExecuteSwap(params SwapParams, signer Signer) (*TxReceipt, error) {
parsedABI, err := abi.JSON(strings.NewReader(settlementABIJSON))
if err != nil {
return nil, fmt.Errorf("parse settlement ABI: %w", err)
}
calldata, err := parsedABI.Pack("executeSwap",
params.TokenID,
params.Buyer,
params.Seller,
params.Price,
params.Stablecoin,
)
if err != nil {
return nil, fmt.Errorf("pack executeSwap: %w", err)
}
// 获取 nonce
nonce, err := c.ethClient.PendingNonceAt(context.Background(), signer.GetAddress())
if err != nil {
return nil, fmt.Errorf("get nonce: %w", err)
}
// 获取 gas price
gasPrice, err := c.ethClient.SuggestGasPrice(context.Background())
if err != nil {
return nil, fmt.Errorf("suggest gas price: %w", err)
}
// 构造交易
tx := types.NewTransaction(
nonce,
SettlementContractAddress,
big.NewInt(0),
uint64(300000), // gas limit
gasPrice,
calldata,
)
// 签名
signedBytes, err := signer.SignTransaction(tx)
if err != nil {
return nil, fmt.Errorf("sign transaction: %w", err)
}
// 广播
var signedTx types.Transaction
if err := signedTx.UnmarshalBinary(signedBytes); err != nil {
return nil, fmt.Errorf("unmarshal signed tx: %w", err)
}
if err := c.ethClient.SendTransaction(context.Background(), &signedTx); err != nil {
return nil, fmt.Errorf("send transaction: %w", err)
}
// 等待回执
receipt, err := c.ethClient.TransactionReceipt(context.Background(), signedTx.Hash())
if err != nil {
return nil, fmt.Errorf("get receipt: %w", err)
}
return &TxReceipt{
TxHash: signedTx.Hash().Hex(),
BlockNumber: receipt.BlockNumber.Int64(),
GasUsed: receipt.GasUsed,
Status: receipt.Status,
}, nil
}

View File

@ -3,6 +3,7 @@ import { GenexConfig } from './types';
import { CouponModule } from './modules/coupon';
import { BlockModule } from './modules/blocks';
import { EventModule } from './modules/events';
import { SettlementModule } from './modules/settlement';
export class GenexClient {
private provider: JsonRpcProvider;
@ -11,6 +12,7 @@ export class GenexClient {
readonly coupon: CouponModule;
readonly blocks: BlockModule;
readonly events: EventModule;
readonly settlement: SettlementModule;
constructor(config: GenexConfig) {
this.config = {
@ -23,6 +25,7 @@ export class GenexClient {
this.coupon = new CouponModule(this.provider);
this.blocks = new BlockModule(this.provider);
this.events = new EventModule(this.config.wsUrl);
this.settlement = new SettlementModule(this.provider);
}
getProvider(): JsonRpcProvider {

View File

@ -13,4 +13,5 @@ export type {
export { CouponModule } from './modules/coupon';
export { BlockModule } from './modules/blocks';
export { EventModule } from './modules/events';
export { SettlementModule } from './modules/settlement';
export { formatGNX, parseGNX, isValidAddress } from './utils';

View File

@ -0,0 +1,38 @@
import { JsonRpcProvider, Contract, Signer } from 'ethers';
import { SwapParams } from '../types';
import { SETTLEMENT_ABI, SETTLEMENT_ADDRESS } from '../contracts/abis';
export class SettlementModule {
private settlement: Contract;
constructor(private provider: JsonRpcProvider) {
this.settlement = new Contract(SETTLEMENT_ADDRESS, SETTLEMENT_ABI, provider);
}
/**
*
* @param params tokenId, buyer, seller, price, stablecoin
* @param signer MPC签名器或钱包
*/
async executeSwap(params: SwapParams, signer: Signer): Promise<string> {
const contract = this.settlement.connect(signer) as Contract;
const tx = await contract.executeSwap(
params.tokenId,
params.buyer,
params.seller,
params.price,
params.stablecoin,
);
const receipt = await tx.wait();
return receipt.hash;
}
/**
* SwapExecuted
*/
onSwapExecuted(callback: (tokenId: string, buyer: string, seller: string, price: string) => void): void {
this.settlement.on('SwapExecuted', (tokenId, buyer, seller, price) => {
callback(tokenId.toString(), buyer, seller, price.toString());
});
}
}

View File

@ -1,17 +0,0 @@
# Genex Wallet Service — Environment Variables
PORT=3021
RPC_URL=http://localhost:8545
REDIS_URL=redis://localhost:6379/1
CHAIN_ID=8888
# MPC Key Shard Locations
HSM_US_EAST_ENDPOINT=hsm://us-east-1.genex.internal:3300
HSM_SG_ENDPOINT=hsm://sg.genex.internal:3300
COLD_STORAGE_ENDPOINT=hsm://cold.genex.internal:3300
# MPC Threshold
MPC_THRESHOLD=2
MPC_PARTIES=3
# Encryption key for address mapping storage
ENCRYPTION_KEY=your-32-byte-encryption-key-here

View File

@ -1,14 +0,0 @@
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3021
CMD ["node", "dist/main"]

View File

@ -1,36 +0,0 @@
{
"name": "@genex/wallet-service",
"version": "1.0.0",
"description": "Genex Chain MPC Wallet Service — threshold signing & wallet management",
"private": true,
"scripts": {
"build": "nest build",
"start": "nest start",
"start:dev": "nest start --watch",
"start:prod": "node dist/main",
"test": "jest"
},
"dependencies": {
"@nestjs/common": "^10.4.0",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.0",
"@nestjs/platform-express": "^10.4.0",
"@nestjs/swagger": "^7.4.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"ethers": "^6.13.0",
"ioredis": "^5.4.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"uuid": "^9.0.1"
},
"devDependencies": {
"@nestjs/cli": "^10.4.0",
"@nestjs/testing": "^10.4.0",
"@types/node": "^20.14.0",
"@types/uuid": "^9.0.8",
"jest": "^29.7.0",
"ts-jest": "^29.2.0",
"typescript": "^5.5.0"
}
}

View File

@ -1,27 +0,0 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MpcSignerService } from './modules/mpc/mpc-signer.service';
import { MpcSignerController } from './modules/mpc/mpc-signer.controller';
import { UserWalletService } from './modules/user-wallet/user-wallet.service';
import { UserWalletController } from './modules/user-wallet/user-wallet.controller';
import { InstitutionalWalletService } from './modules/institutional/institutional-wallet.service';
import { InstitutionalWalletController } from './modules/institutional/institutional-wallet.controller';
import { GovernanceWalletService } from './modules/governance/governance-wallet.service';
import { GovernanceWalletController } from './modules/governance/governance-wallet.controller';
@Module({
imports: [ConfigModule.forRoot({ isGlobal: true })],
controllers: [
MpcSignerController,
UserWalletController,
InstitutionalWalletController,
GovernanceWalletController,
],
providers: [
MpcSignerService,
UserWalletService,
InstitutionalWalletService,
GovernanceWalletService,
],
})
export class AppModule {}

View File

@ -1,63 +0,0 @@
export interface MPCConfig {
threshold: number; // 2-of-3
parties: number; // 3
keyShardLocations: string[]; // ['hsm-us-east', 'hsm-sg', 'cold-storage']
}
export interface TransactionRequest {
to: string;
data?: string;
value?: string;
gasLimit?: string;
}
export interface TxReceipt {
txHash: string;
blockNumber: number;
gasUsed: string;
status: 'success' | 'failed';
}
export interface MintRequest {
couponType: 'utility' | 'security';
faceValue: string;
quantity: number;
expiryDate: number;
transferable: boolean;
maxResaleCount: number;
}
export interface OrderRequest {
tokenId: string;
side: 'buy' | 'sell';
price: string;
stablecoin: string;
}
export interface TradeRequest {
tokenId: string;
buyer: string;
seller: string;
price: string;
stablecoin: string;
}
export interface ApprovalStatus {
proposalId: string;
requiredApprovals: number;
currentApprovals: number;
approvers: string[];
status: 'pending' | 'approved' | 'rejected' | 'executed';
}
export interface InstitutionalWallet {
mintCoupons(batch: MintRequest): Promise<TxReceipt>;
depositGuarantee(amount: bigint): Promise<TxReceipt>;
withdrawRevenue(amount: bigint): Promise<TxReceipt>;
placeOrder(order: OrderRequest): Promise<TxReceipt>;
cancelOrder(orderId: string): Promise<TxReceipt>;
batchSettle(trades: TradeRequest[]): Promise<TxReceipt[]>;
proposeTransaction(tx: TransactionRequest): Promise<string>;
approveTransaction(proposalId: string): Promise<void>;
getApprovalStatus(proposalId: string): Promise<ApprovalStatus>;
}

View File

@ -1,27 +0,0 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
const config = new DocumentBuilder()
.setTitle('Genex Wallet Service')
.setDescription('MPC Wallet — User / Institutional / Governance wallet management')
.setVersion('1.0')
.addTag('mpc', 'MPC signing operations')
.addTag('user-wallet', 'User abstract wallet')
.addTag('institutional', 'Institutional wallet (issuers/market makers)')
.addTag('governance', 'Governance multi-sig wallet')
.build();
SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, config));
const port = process.env.PORT || 3021;
await app.listen(port);
console.log(`Wallet Service running on :${port} | Swagger: /docs`);
}
bootstrap();

View File

@ -1,33 +0,0 @@
import { Controller, Post, Get, Body, Param } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { GovernanceWalletService, GovernanceAction } from './governance-wallet.service';
@ApiTags('governance')
@Controller('v1/governance')
export class GovernanceWalletController {
constructor(private readonly governance: GovernanceWalletService) {}
@Post('propose')
@ApiOperation({ summary: '创建治理提案3/5 常规 | 4/5 紧急)' })
createProposal(@Body() body: { action: GovernanceAction; data: Record<string, any>; proposer: string }) {
return this.governance.createProposal(body.action, body.data, body.proposer);
}
@Post('approve')
@ApiOperation({ summary: '审批治理提案' })
approveProposal(@Body() body: { proposalId: string; signer: string }) {
return this.governance.approveProposal(body.proposalId, body.signer);
}
@Get('proposal/:id')
@ApiOperation({ summary: '查询治理提案详情' })
getProposal(@Param('id') id: string) {
return this.governance.getProposal(id);
}
@Get('proposals')
@ApiOperation({ summary: '列出所有治理提案' })
listProposals() {
return this.governance.listProposals();
}
}

View File

@ -1,103 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
export type GovernanceAction =
| 'contract_upgrade'
| 'emergency_freeze'
| 'gas_parameter_adjustment'
| 'stablecoin_whitelist'
| 'validator_admission'
| 'guarantee_payout';
interface GovernanceProposal {
id: string;
action: GovernanceAction;
data: Record<string, any>;
proposer: string;
approvers: string[];
requiredApprovals: number; // 3/5 常规, 4/5 紧急
status: 'pending' | 'approved' | 'rejected' | 'executed';
createdAt: Date;
executedAt?: Date;
}
/** 5 个平台高管/董事签名人 */
const GOVERNANCE_SIGNERS = [
'0x1111111111111111111111111111111111111111',
'0x2222222222222222222222222222222222222222',
'0x3333333333333333333333333333333333333333',
'0x4444444444444444444444444444444444444444',
'0x5555555555555555555555555555555555555555',
];
const EMERGENCY_ACTIONS: GovernanceAction[] = ['emergency_freeze', 'guarantee_payout'];
@Injectable()
export class GovernanceWalletService {
private readonly logger = new Logger(GovernanceWalletService.name);
private proposals = new Map<string, GovernanceProposal>();
/** 创建治理提案 */
async createProposal(
action: GovernanceAction,
data: Record<string, any>,
proposer: string,
): Promise<GovernanceProposal> {
if (!GOVERNANCE_SIGNERS.includes(proposer)) {
throw new Error('Only governance signers can create proposals');
}
const isEmergency = EMERGENCY_ACTIONS.includes(action);
const proposal: GovernanceProposal = {
id: uuidv4(),
action,
data,
proposer,
approvers: [proposer],
requiredApprovals: isEmergency ? 4 : 3, // 紧急 4/5, 常规 3/5
status: 'pending',
createdAt: new Date(),
};
this.proposals.set(proposal.id, proposal);
this.logger.log(`Governance proposal created: ${action} by ${proposer} (${isEmergency ? 'emergency 4/5' : 'normal 3/5'})`);
return proposal;
}
/** 审批提案 */
async approveProposal(proposalId: string, signer: string): Promise<GovernanceProposal> {
const proposal = this.proposals.get(proposalId);
if (!proposal) throw new Error('Proposal not found');
if (!GOVERNANCE_SIGNERS.includes(signer)) throw new Error('Not a governance signer');
if (proposal.status !== 'pending') throw new Error(`Proposal is ${proposal.status}`);
if (!proposal.approvers.includes(signer)) {
proposal.approvers.push(signer);
}
if (proposal.approvers.length >= proposal.requiredApprovals) {
proposal.status = 'approved';
await this.executeProposal(proposal);
}
return proposal;
}
/** 查询提案 */
getProposal(proposalId: string): GovernanceProposal | undefined {
return this.proposals.get(proposalId);
}
/** 列出所有提案 */
listProposals(): GovernanceProposal[] {
return Array.from(this.proposals.values());
}
/** 执行已审批的提案 */
private async executeProposal(proposal: GovernanceProposal): Promise<void> {
this.logger.log(`Executing governance proposal: ${proposal.action}`);
// 实际实现:通过 Gnosis Safe 多签合约执行链上交易
proposal.status = 'executed';
proposal.executedAt = new Date();
}
}

View File

@ -1,50 +0,0 @@
import { Controller, Post, Get, Body, Param } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { InstitutionalWalletService } from './institutional-wallet.service';
import { MintRequest, OrderRequest, TradeRequest, TransactionRequest } from '../../common/interfaces/wallet.interfaces';
@ApiTags('institutional')
@Controller('v1/institutional')
export class InstitutionalWalletController {
constructor(private readonly wallet: InstitutionalWalletService) {}
@Post('mint')
@ApiOperation({ summary: '铸造券(发行方)' })
mintCoupons(@Body() batch: MintRequest) { return this.wallet.mintCoupons(batch); }
@Post('guarantee/deposit')
@ApiOperation({ summary: '缴纳保障资金' })
depositGuarantee(@Body() body: { amount: string }) { return this.wallet.depositGuarantee(BigInt(body.amount)); }
@Post('revenue/withdraw')
@ApiOperation({ summary: '提取销售收入' })
withdrawRevenue(@Body() body: { amount: string }) { return this.wallet.withdrawRevenue(BigInt(body.amount)); }
@Post('order/place')
@ApiOperation({ summary: '挂单(做市商)' })
placeOrder(@Body() order: OrderRequest) { return this.wallet.placeOrder(order); }
@Post('order/cancel')
@ApiOperation({ summary: '撤单' })
cancelOrder(@Body() body: { orderId: string }) { return this.wallet.cancelOrder(body.orderId); }
@Post('settle/batch')
@ApiOperation({ summary: '批量结算' })
batchSettle(@Body() body: { trades: TradeRequest[] }) { return this.wallet.batchSettle(body.trades); }
@Post('multisig/propose')
@ApiOperation({ summary: '提案(多签)' })
propose(@Body() tx: TransactionRequest) { return this.wallet.proposeTransaction(tx); }
@Post('multisig/approve')
@ApiOperation({ summary: '审批提案(多签)' })
approve(@Body() body: { proposalId: string; approver: string }) {
return this.wallet.approveTransaction(body.proposalId, body.approver);
}
@Get('multisig/status/:proposalId')
@ApiOperation({ summary: '查询审批状态' })
getApprovalStatus(@Param('proposalId') proposalId: string) {
return this.wallet.getApprovalStatus(proposalId);
}
}

View File

@ -1,84 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
import {
MintRequest, OrderRequest, TradeRequest,
TxReceipt, ApprovalStatus, TransactionRequest,
} from '../../common/interfaces/wallet.interfaces';
@Injectable()
export class InstitutionalWalletService {
private readonly logger = new Logger(InstitutionalWalletService.name);
private proposals = new Map<string, { tx: TransactionRequest; approvers: string[]; status: string }>();
/** 铸造券 */
async mintCoupons(batch: MintRequest): Promise<TxReceipt> {
this.logger.log(`Minting ${batch.quantity} ${batch.couponType} coupons, face value: ${batch.faceValue}`);
// 实际实现:调用 CouponFactory.batchMint() via 机构 HSM 签名
return { txHash: `0x${uuidv4().replace(/-/g, '')}`, blockNumber: 0, gasUsed: '0', status: 'success' };
}
/** 缴纳保障资金 */
async depositGuarantee(amount: bigint): Promise<TxReceipt> {
this.logger.log(`Depositing guarantee: ${amount}`);
return { txHash: `0x${uuidv4().replace(/-/g, '')}`, blockNumber: 0, gasUsed: '0', status: 'success' };
}
/** 提取销售收入 */
async withdrawRevenue(amount: bigint): Promise<TxReceipt> {
this.logger.log(`Withdrawing revenue: ${amount}`);
return { txHash: `0x${uuidv4().replace(/-/g, '')}`, blockNumber: 0, gasUsed: '0', status: 'success' };
}
/** 挂单 */
async placeOrder(order: OrderRequest): Promise<TxReceipt> {
this.logger.log(`Placing ${order.side} order for token ${order.tokenId} at ${order.price}`);
return { txHash: `0x${uuidv4().replace(/-/g, '')}`, blockNumber: 0, gasUsed: '0', status: 'success' };
}
/** 撤单 */
async cancelOrder(orderId: string): Promise<TxReceipt> {
this.logger.log(`Cancelling order ${orderId}`);
return { txHash: `0x${uuidv4().replace(/-/g, '')}`, blockNumber: 0, gasUsed: '0', status: 'success' };
}
/** 批量结算 */
async batchSettle(trades: TradeRequest[]): Promise<TxReceipt[]> {
this.logger.log(`Batch settling ${trades.length} trades`);
return trades.map(() => ({
txHash: `0x${uuidv4().replace(/-/g, '')}`, blockNumber: 0, gasUsed: '0', status: 'success' as const,
}));
}
/** 多签 — 提案 */
async proposeTransaction(tx: TransactionRequest): Promise<string> {
const proposalId = uuidv4();
this.proposals.set(proposalId, { tx, approvers: [], status: 'pending' });
return proposalId;
}
/** 多签 — 审批 */
async approveTransaction(proposalId: string, approver: string): Promise<void> {
const proposal = this.proposals.get(proposalId);
if (!proposal) throw new Error('Proposal not found');
if (!proposal.approvers.includes(approver)) {
proposal.approvers.push(approver);
}
// 2-of-3 或 3-of-5 达标后自动执行
if (proposal.approvers.length >= 2) {
proposal.status = 'approved';
}
}
/** 查询审批状态 */
async getApprovalStatus(proposalId: string): Promise<ApprovalStatus> {
const proposal = this.proposals.get(proposalId);
if (!proposal) throw new Error('Proposal not found');
return {
proposalId,
requiredApprovals: 2,
currentApprovals: proposal.approvers.length,
approvers: proposal.approvers,
status: proposal.status as any,
};
}
}

View File

@ -1,34 +0,0 @@
import { Controller, Post, Get, Body, Param } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { MpcSignerService } from './mpc-signer.service';
import { TransactionRequest } from '../../common/interfaces/wallet.interfaces';
@ApiTags('mpc')
@Controller('v1/mpc')
export class MpcSignerController {
constructor(private readonly mpcSigner: MpcSignerService) {}
@Post('sign')
@ApiOperation({ summary: 'MPC 门限签名交易' })
signTransaction(@Body() body: { userId: string; txData: TransactionRequest }) {
return this.mpcSigner.signTransaction(body.userId, body.txData);
}
@Post('generate-key')
@ApiOperation({ summary: '生成新的 MPC 密钥对' })
generateKey(@Body() body: { userId: string }) {
return this.mpcSigner.generateKey(body.userId);
}
@Get('address/:userId')
@ApiOperation({ summary: '查询用户 MPC 钱包地址' })
getAddress(@Param('userId') userId: string) {
return { userId, address: this.mpcSigner.getAddress(userId) };
}
@Get('config')
@ApiOperation({ summary: '获取 MPC 配置信息' })
getConfig() {
return this.mpcSigner.getMpcConfig();
}
}

View File

@ -1,98 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JsonRpcProvider, Wallet, parseEther } from 'ethers';
import { MPCConfig, TransactionRequest } from '../../common/interfaces/wallet.interfaces';
@Injectable()
export class MpcSignerService {
private readonly logger = new Logger(MpcSignerService.name);
private provider: JsonRpcProvider;
private mpcConfig: MPCConfig;
// 简化使用内存Map存储用户地址映射生产使用加密数据库
private keyMapping = new Map<string, { address: string; keyId: string }>();
constructor(private config: ConfigService) {
this.provider = new JsonRpcProvider(this.config.get('RPC_URL') || 'http://localhost:8545');
this.mpcConfig = {
threshold: parseInt(this.config.get('MPC_THRESHOLD') || '2'),
parties: parseInt(this.config.get('MPC_PARTIES') || '3'),
keyShardLocations: [
this.config.get('HSM_US_EAST_ENDPOINT') || 'hsm-us-east',
this.config.get('HSM_SG_ENDPOINT') || 'hsm-sg',
this.config.get('COLD_STORAGE_ENDPOINT') || 'cold-storage',
],
};
}
/** 用户无感签名:平台代签,用户只需确认操作 */
async signTransaction(userId: string, txData: TransactionRequest): Promise<string> {
const mapping = this.keyMapping.get(userId);
if (!mapping) throw new Error(`No wallet found for user ${userId}`);
const tx = await this.buildTransaction(mapping.address, txData);
// MPC 门限签名2-of-3 分片协作,无完整私钥出现)
const signature = await this.mpcSign(tx, mapping.keyId);
this.logger.log(`MPC signed tx for user ${userId}, address ${mapping.address}`);
return signature;
}
/** 构造 EVM 交易 */
async buildTransaction(fromAddress: string, txData: TransactionRequest) {
const nonce = await this.provider.getTransactionCount(fromAddress);
const feeData = await this.provider.getFeeData();
return {
from: fromAddress,
to: txData.to,
data: txData.data || '0x',
value: txData.value ? parseEther(txData.value) : 0n,
gasLimit: BigInt(txData.gasLimit || '200000'),
nonce,
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
chainId: parseInt(this.config.get('CHAIN_ID') || '8888'),
};
}
/** 生成新的 MPC 密钥对并返回地址 */
async generateKey(userId: string): Promise<string> {
// 实际实现:通过 MPC 协议在 3 个分片间生成密钥
// 此处简化:使用 ethers 生成随机钱包
const wallet = Wallet.createRandom();
const keyId = `mpc-${userId}-${Date.now()}`;
this.keyMapping.set(userId, {
address: wallet.address,
keyId,
});
this.logger.log(`Generated MPC key for user ${userId}: ${wallet.address}`);
return wallet.address;
}
/** 获取用户地址 */
getAddress(userId: string): string | null {
return this.keyMapping.get(userId)?.address || null;
}
getMpcConfig(): MPCConfig {
return this.mpcConfig;
}
/** MPC 门限签名(模拟) */
private async mpcSign(tx: any, keyId: string): Promise<string> {
// 实际实现:
// 1. 选择 threshold 个分片(任选 2-of-3
// 2. 各分片使用本地密钥分片计算部分签名
// 3. 合成完整签名
// 全程无完整私钥出现
const shards = this.mpcConfig.keyShardLocations.slice(0, this.mpcConfig.threshold);
this.logger.log(`MPC signing with shards: ${shards.join(', ')} for key ${keyId}`);
// 模拟签名结果
return `0x${'0'.repeat(130)}`;
}
}

View File

@ -1,33 +0,0 @@
import { Controller, Post, Get, Body, Param } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { UserWalletService } from './user-wallet.service';
@ApiTags('user-wallet')
@Controller('v1/user-wallet')
export class UserWalletController {
constructor(private readonly userWallet: UserWalletService) {}
@Post('create')
@ApiOperation({ summary: '创建用户钱包(注册时自动调用)' })
createWallet(@Body() body: { userId: string }) {
return this.userWallet.createWallet(body.userId);
}
@Get(':userId/address')
@ApiOperation({ summary: '查询用户链上地址' })
getAddress(@Param('userId') userId: string) {
return { userId, address: this.userWallet.getAddress(userId) };
}
@Get(':userId/balance')
@ApiOperation({ summary: '查询用户余额GNX + 稳定币)' })
getBalance(@Param('userId') userId: string) {
return this.userWallet.getBalance(userId);
}
@Get(':userId/nfts')
@ApiOperation({ summary: '查询用户持有的券 NFT' })
getNFTHoldings(@Param('userId') userId: string) {
return this.userWallet.getNFTHoldings(userId);
}
}

View File

@ -1,54 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JsonRpcProvider, formatEther } from 'ethers';
import { MpcSignerService } from '../mpc/mpc-signer.service';
@Injectable()
export class UserWalletService {
private readonly logger = new Logger(UserWalletService.name);
private provider: JsonRpcProvider;
constructor(
private config: ConfigService,
private mpcSigner: MpcSignerService,
) {
this.provider = new JsonRpcProvider(this.config.get('RPC_URL') || 'http://localhost:8545');
}
/** 用户注册时自动创建链上地址(用户无感知) */
async createWallet(userId: string): Promise<{ userId: string; address: string }> {
const existing = this.mpcSigner.getAddress(userId);
if (existing) return { userId, address: existing };
const address = await this.mpcSigner.generateKey(userId);
this.logger.log(`Created wallet for user ${userId}: ${address}`);
return { userId, address };
}
/** 获取用户链上地址 */
getAddress(userId: string): string | null {
return this.mpcSigner.getAddress(userId);
}
/** 获取用户余额GNX + 稳定币) */
async getBalance(userId: string) {
const address = this.mpcSigner.getAddress(userId);
if (!address) return null;
const gnxBalance = await this.provider.getBalance(address);
return {
userId,
address,
gnx: formatEther(gnxBalance),
};
}
/** 获取用户持有的券 NFT */
async getNFTHoldings(userId: string) {
const address = this.mpcSigner.getAddress(userId);
if (!address) return { userId, holdings: [] };
// 实际实现:查询 Coupon 合约的 balanceOf + tokenOfOwnerByIndex
return { userId, address, holdings: [] };
}
}

View File

@ -1,19 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"skipLibCheck": true,
"strictNullChecks": true,
"noImplicitAny": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@ -1,6 +1,6 @@
# Genex Chain 生态基础设施开发指南
> 区块浏览器 + 企业API + 钱包体系 + Gas Relayer + 链监控 + 开发者SDK + 跨链桥
> 区块浏览器 + 企业API + Gas Relayer + 链监控 + 开发者SDK + 跨链桥
---
@ -14,19 +14,19 @@
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ 区块浏览器 │ │ 企业API网关 │ │ 钱包体系 │ │ 水龙头 │ │
│ │ Blockscout │ │ RPC + REST │ │ 托管+用户 │ │ 测试网 │ │
│ │ 区块浏览器 │ │ 企业API网关 │ │ Gas Relayer │ │ 水龙头 │ │
│ │ Blockscout │ │ RPC + REST │ │ Meta-TX │ │ 测试网 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ Gas Relayer │ │ 链监控平台 │ │ 托管/密钥 │ │ 开发者SDK │ │
│ │ Meta-TX │ │ Prometheus │ │ MPC/HSM │ │ JS/Go/Dart │ │
│ │ 链监控平台 │ │ 开发者SDK │ │ 归档节点 │ │ 合约验证 │ │
│ │ Prometheus │ │ JS/Go/Dart │ │ Archive Node│ │ 源码公示 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐
│ │ 跨链桥监控 │ │ 交易监控AML │ │ 归档节点 │ │ 合约验证 │
│ │ Axelar/IBC │ │ Chainalysis │ │ Archive Node│ │ 源码公示 │
│ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ │ 跨链桥监控 │ │ 交易监控AML │ │ CI安全检查 │
│ │ Axelar/IBC │ │ Chainalysis │ │ Slither等 │
│ └─────────────┘ └─────────────┘ └─────────────┘
└──────────────────────────────────────────────────────────────────────┘
```
@ -201,123 +201,7 @@ plugins:
---
## 4. 钱包体系
Genex Chain 的钱包分为三层,服务不同角色。
### 4.1 用户抽象钱包面向C端用户
> 用户不直接接触私钥和链地址。手机号 = 身份,平台托管密钥。
```
用户视角:
手机号注册 → 自动创建链上地址 → 用户完全不知道
购买券 → 法币支付 → 链上原子交换自动完成
查看"我的券" → 实际是查询链上NFT余额
技术实现:
┌──────────────┐
│ 用户手机App │ ← 用户只看到券和余额
└──────┬───────┘
│ API
┌──────┴───────┐
│ user-service │ ← 手机号→地址映射(加密存储)
└──────┬───────┘
│ 签名
┌──────┴───────┐
│ MPC签名服务 │ ← 门限签名,无单点私钥
└──────┬───────┘
│ TX
┌──────┴───────┐
│ Genex Chain │ ← 链上执行
└──────────────┘
```
**MPC 钱包架构:**
```typescript
// wallet-service/src/mpc-signer.ts
interface MPCConfig {
threshold: 2; // 2-of-3 门限
parties: 3; // 3个签名分片
keyShardLocations: [
'hsm-us-east', // AWS CloudHSM 美国
'hsm-sg', // AWS CloudHSM 新加坡
'cold-storage' // 离线冷存储(灾备)
];
}
class MPCSigner {
/// 用户无感签名:平台代签,用户只需确认操作
async signTransaction(userId: string, txData: TransactionRequest): Promise<string> {
// 1. 从加密存储获取用户地址
const address = await this.keyMapping.getAddress(userId);
// 2. 构造EVM交易
const tx = await this.buildTransaction(address, txData);
// 3. MPC门限签名2-of-3分片协作无完整私钥出现
const signature = await this.mpcProtocol.sign(tx.hash(), {
keyId: address,
parties: ['hsm-us-east', 'hsm-sg'], // 任选2个分片
});
// 4. 广播
return this.provider.sendTransaction(tx.serialize(signature));
}
}
```
### 4.2 机构托管钱包(面向发行方/做市商)
| 特性 | 说明 |
|------|------|
| 密钥管理 | 机构自持HSM或第三方托管Fireblocks/Copper |
| 多签 | 机构内部多签审批流2-of-3或3-of-5 |
| 白名单 | 只能向预审批地址转账 |
| 限额 | 单笔/日累计限额,超额需额外审批 |
| 审计 | 所有操作记录,导出给机构合规部门 |
```typescript
// 机构钱包接口
interface InstitutionalWallet {
// 发行方操作
mintCoupons(batch: MintRequest): Promise<TxReceipt>; // 铸造券
depositGuarantee(amount: bigint): Promise<TxReceipt>; // 缴纳保障资金
withdrawRevenue(amount: bigint): Promise<TxReceipt>; // 提取销售收入
// 做市商操作
placeOrder(order: OrderRequest): Promise<TxReceipt>; // 挂单
cancelOrder(orderId: string): Promise<TxReceipt>; // 撤单
batchSettle(trades: TradeRequest[]): Promise<TxReceipt[]>; // 批量结算
// 多签管理
proposeTransaction(tx: TransactionRequest): Promise<string>; // 提案
approveTransaction(proposalId: string): Promise<void>; // 审批
getApprovalStatus(proposalId: string): Promise<ApprovalStatus>; // 查询审批状态
}
```
### 4.3 治理多签钱包(面向平台管理层)
```
Governance多签钱包Gnosis Safe部署在Genex Chain上
签名人5个平台高管/董事
门限3/5常规操作或 4/5紧急操作
职责:
├── 合约升级审批
├── 紧急冻结执行
├── Gas参数调整
├── 新稳定币白名单
├── 验证节点准入审批
└── 保障资金赔付执行
```
---
## 5. Gas RelayerMeta-Transaction 中继器)
## 4. Gas RelayerMeta-Transaction 中继器)
> 用户零Gas体验的技术实现。用户签名操作意图Relayer代付Gas广播上链。
@ -372,9 +256,9 @@ class GasRelayer {
---
## 6. 链监控与运维平台
## 5. 链监控与运维平台
### 6.1 监控栈
### 5.1 监控栈
```
Genex Chain 节点 → Prometheus Exporter → Prometheus → Grafana Dashboard
@ -386,7 +270,7 @@ Application Metrics ────────────────────
AlertManager → PagerDuty / Slack
```
### 6.2 关键监控指标
### 5.2 关键监控指标
| 类别 | 指标 | 告警阈值 |
|------|------|---------|
@ -405,7 +289,7 @@ Application Metrics ────────────────────
| **Relayer** | 热钱包余额 | < 10,000 GNX 告警 |
| **跨链桥** | 桥锁定资产偏差 | > 0.01% 严重告警 |
### 6.3 Grafana Dashboard
### 5.3 Grafana Dashboard
```json
{
@ -425,7 +309,7 @@ Application Metrics ────────────────────
---
## 7. 测试网水龙头Faucet
## 6. 测试网水龙头Faucet
为开发者和测试用户分发测试网 GNX 和测试稳定币。
@ -458,11 +342,11 @@ class Faucet {
---
## 8. 开发者SDK
## 7. 开发者SDK
为外部开发者和内部微服务提供类型安全的链交互工具。
### 8.1 SDK 矩阵
### 7.1 SDK 矩阵
| SDK | 语言 | 用途 |
|-----|------|------|
@ -470,7 +354,7 @@ class Faucet {
| **genex-sdk-go** | Go | trading-service、chain-indexer |
| **genex-sdk-dart** | Dart | genex-mobile、admin-app |
### 8.2 JS SDK 核心接口
### 7.2 JS SDK 核心接口
```typescript
// @genex/sdk
@ -500,7 +384,7 @@ const tx = await client.settlement.executeSwap(tokenId, buyer, seller, price, {
});
```
### 8.3 Go SDK 核心接口
### 7.3 Go SDK 核心接口
```go
// github.com/gogenex/genex-sdk-go
@ -522,7 +406,7 @@ func (c *Client) SubscribeEvents(ctx context.Context, filter EventFilter) (<-cha
func (c *Client) ExecuteSwap(params SwapParams, signer Signer) (*TxReceipt, error)
```
### 8.4 Dart SDK 核心接口
### 7.4 Dart SDK 核心接口
```dart
// package:genex_sdk
@ -544,7 +428,7 @@ class GenexClient {
---
## 9. 归档节点Archive Node
## 8. 归档节点Archive Node
| 项目 | 说明 |
|------|------|
@ -568,9 +452,9 @@ class GenexClient {
---
## 10. 跨链桥监控
## 9. 跨链桥监控
### 10.1 监控架构
### 9.1 监控架构
```
Ethereum ←── Axelar Bridge ──→ Genex Chain
@ -583,7 +467,7 @@ Ethereum ←── Axelar Bridge ──→ Genex Chain
└── 紧急暂停:异常时自动暂停桥,需多签恢复
```
### 10.2 桥对账服务
### 9.2 桥对账服务
```typescript
// bridge-monitor/src/monitor.ts
@ -611,7 +495,7 @@ class BridgeMonitor {
---
## 11. 合约源码验证与安全
## 10. 合约源码验证与安全
| 工具 | 用途 |
|------|------|
@ -642,13 +526,12 @@ jobs:
---
## 12. 生态基础设施部署清单
## 11. 生态基础设施部署清单
| 组件 | 技术栈 | 部署位置 | 端口 | 依赖 |
|------|--------|---------|------|------|
| **Blockscout** | Elixir + PostgreSQL | US-East + SG | 4000 | genexd RPC |
| **企业API** | NestJS | US-East + SG | 3020 | Kong, genexd, PostgreSQL |
| **MPC签名服务** | Go + AWS CloudHSM | US-East + SG + Cold | 3021 | CloudHSM |
| **Gas Relayer** | NestJS | US-East + SG | 3022 | genexd, Redis |
| **Faucet** | NestJS | US-East | 3023 | genexd(testnet) |
| **Bridge Monitor** | Go | US-East + SG | 3024 | Axelar, genexd, Ethereum |
@ -670,7 +553,6 @@ jobs:
新增生态服务(本文档):
3020 — 企业API服务
3021 — MPC签名服务
3022 — Gas Relayer
3023 — 测试网Faucet
3024 — 跨链桥监控
@ -683,4 +565,4 @@ jobs:
*文档版本: v1.0*
*基于: 06-区块链开发指南 v3.0(量产版)*
*覆盖: 区块浏览器(Blockscout)、企业API网关(4层认证)、三层钱包体系(用户MPC/机构HSM/治理多签)、Gas Relayer(Meta-TX)、链监控(Prometheus+Grafana)、测试网水龙头、开发者SDK(JS/Go/Dart)、归档节点、跨链桥监控(Axelar)、合约安全CI(Slither+Mythril)*
*覆盖: 区块浏览器(Blockscout)、企业API网关(4层认证)、Gas Relayer(Meta-TX)、链监控(Prometheus+Grafana)、测试网水龙头、开发者SDK(JS/Go/Dart)、归档节点、跨链桥监控(Axelar)、合约安全CI(Slither+Mythril)*