Commit Graph

1360 Commits

Author SHA1 Message Date
hailin b5ebf8a615 feat(admin-service): 实现 Debezium CDC 数据同步
- 新增 CdcConsumerService 消费 PostgreSQL WAL 变更事件
- 配置 Debezium Connect 服务和 PostgreSQL 逻辑复制
- 更新 deploy.sh 支持 Debezium 启动和连接器管理
- 新增 identity-postgres-connector 配置同步 user_accounts 表
- 保留原有 Outbox 机制用于业务领域事件

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 18:35:53 -08:00
hailin 84aa8181a9 fix(reporting-service): 兼容 referral-service 的认种事件格式
- planting.order.paid 事件现在由 referral-service 发送
- 消息格式为 { eventName, data: {...} },与原 planting-service 格式不同
- 添加兼容逻辑,同时支持两种格式
- 修复今日认种统计为0的问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 06:58:33 -08:00
hailin c7c793f128 fix(reporting-service): 账户余额改为 累计收入 - 累计转出
- 所有固定账户:账户余额 = 累计收入 - 累计转出
- 总部储蓄(HQ_COMMUNITY):累计收入 = ledger收入 + 过期收益
- 统一计算公式,确保数据一致性

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 06:41:28 -08:00
hailin 842bc42579 fix(reporting-service): 总部储蓄账户余额也需累加过期收益
- 账户余额改为 usdtBalance + expiredRewardsTotal
- 与累计收入的计算方式保持一致
- 过期的分享权益会进入 S0000000001,余额和收入都应包含

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 06:40:20 -08:00
hailin a719294dda fix(blockchain-service): 添加系统账户转出事件主题订阅
- 消费者添加订阅 wallet.system-withdrawals 主题
- 解决系统账户转出订单一直处于 FROZEN 状态的问题
- 事件现在可以被 blockchain-service 消费并处理链上转账

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 06:17:57 -08:00
hailin c22ce4ecc4 fix(reporting-service): 将过期收益累加到总部储蓄账户统计
- 修改 assembleFixedAccounts 方法接受过期收益总额参数
- 将过期分享收益累加到 HQ_COMMUNITY (S0000000001) 的累计收入中
- 添加日志记录累加过程

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:53:51 -08:00
hailin db833fdf45 fix(wallet-service): 修复系统划转请求 DTO 验证错误
- 为 SystemWithdrawalRequestDTO 添加 class-validator 装饰器
- 添加 @ApiProperty 装饰器用于 Swagger 文档
- 使用 @Type(() => Number) 自动转换 amount 类型
- 简化验证逻辑,移除冗余的手动验证

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:52:36 -08:00
hailin 4f2f808484 feat(wallet-service): 实现 Outbox Pattern 保证系统划转事件发布的可靠性
实现内容:
- 添加 OutboxEvent 模型到 schema.prisma
- 创建 OutboxRepository 服务处理事件持久化
- 创建 OutboxPublisherService 后台轮询发布事件到 Kafka
- 修改 SystemWithdrawalApplicationService 将事件写入事务内
- 添加数据库迁移文件创建 outbox_events 表

技术细节:
- 业务数据和事件数据在同一个数据库事务中写入
- 后台任务每秒轮询 outbox_events 表,发布 PENDING 状态事件
- 事件发布后标记为 SENT,等待消费方确认后标记为 CONFIRMED
- 超时未确认的事件自动重试(指数退避策略)
- 保证事件不丢失,即使 Kafka 暂时不可用

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:15:07 -08:00
hailin d16ad81d62 fix(wallet-service): 完善系统账户转出分类账memo信息
- memo 格式更新为包含完整信息:
  - 接收方账户ID
  - 接收方姓名
  - 接收方钱包地址(缩略显示)
  - 操作员姓名/ID
  - 备注(如有)
- payloadJson 新增 toAddress 字段存储完整地址

示例: "转账至 D25010100001 (张三) | 地址: 0x1234...5678 | 操作员: 管理员A | 备注: 奖励发放"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:52:00 -08:00
hailin 6350b36e1a feat(reporting, admin-web): 添加省/市区域账户累计转出统计
- 后端 reporting-service: RegionAccountInfo 和汇总接口添加 totalTransferred 字段
- 后端 wallet-service client: 更新 AllSystemAccountsResponse 类型定义
- 前端 admin-web: SystemAccountsTab 组件显示累计转出列
- 前端类型: RegionAccountsSummary 添加 totalTransferred 字段

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:45:56 -08:00
hailin 27e64819b7 feat(wallet-service): 省/市区域账户增加累计转出字段
- provinceAccounts 和 cityAccounts 返回结构增加 totalTransferred 字段
- 完善分类账统计,与固定系统账户保持一致

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:39:47 -08:00
hailin 78fa354117 fix(wallet-service): 修复系统账户余额统计不一致问题
- 账户余额改为 usdtAvailable + settleableUsdt,与累计收入统计保持一致
- 解决社区权益进入 settleableUsdt 导致的余额与累计收入不匹配问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:15:42 -08:00
hailin 5c7cb616a7 feat(wallet-service): 添加运营1和积分股池到系统划转账户列表
- 添加 S0000000002 (运营1) 和 S0000000004 (积分股池) 到允许转出白名单
- 更新系统账户名称映射与前端保持一致
- 为 S0000000006 手续费归集账户添加兼容逻辑,当余额为0时从提现订单表统计历史手续费
- 优化过期奖励处理,按分配类型分别记录流水便于明细查看

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 03:44:46 -08:00
hailin fa7b45ec2f fix(wallet-service, admin-web): 修复系统账户划转金额类型问题
- wallet-service: 支持 amount 为字符串或数字类型,添加类型转换
- admin-web: 改进错误处理,正确提取 Axios 错误消息

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 03:01:52 -08:00
hailin eb40b658f6 revert(wallet-service): 回滚 fixedAccountTypes 映射修改
撤销错误的账户类型映射修改,恢复原始映射:
- S0000000001 → HQ_COMMUNITY
- S0000000002 → COST_ACCOUNT
- S0000000003 → OPERATION_ACCOUNT

修改后端映射会影响 reward-service、authorization-service 等多个服务。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 23:22:22 -08:00
hailin d28723f141 fix(wallet-service): 修正固定账户类型映射顺序
修复 getAllSystemAccounts 中 fixedAccountTypes 映射错误:
- S0000000001 应为 COST_ACCOUNT (运营1),而非 HQ_COMMUNITY
- S0000000002 应为 OPERATION_ACCOUNT (运营2),而非 COST_ACCOUNT
- S0000000003 应为 HQ_COMMUNITY (总部储蓄),而非 OPERATION_ACCOUNT

此修复确保固定账户的余额、收入和分类账明细正确对应。
同时移除前端调试日志。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 23:20:41 -08:00
hailin 7744abf57d fix(reporting-service): 修复 DashboardController 的依赖注入问题
在 ApiModule 中导入 RedisModule,使 HotWalletBalanceCacheService
可以被 DashboardController 注入使用。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 23:12:24 -08:00
hailin 8dba325499 feat(dashboard): 添加热钱包余额实时显示(公共账户/因子)
- blockchain-service: 扩展调度器同时缓存 dUSDT 和 KAVA 原生代币余额到 Redis
  - Redis Key: hot_wallet:dusdt_balance:KAVA, hot_wallet:native_balance:KAVA
  - 每5秒更新,TTL 30秒

- reporting-service: 添加热钱包余额读取服务和 API
  - 新增 HotWalletBalanceCacheService 从 Redis 读取缓存
  - 新增 GET /v1/dashboard/hot-wallet-balance 接口

- admin-web: 仪表板添加热钱包余额显示
  - 公共账户显示 dUSDT 余额
  - 因子显示 KAVA 原生代币余额
  - 每15秒自动刷新

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 23:04:21 -08:00
hailin 95cb3510aa fix(reward-service): 修复 getExpiredRewardsEntries 的 userId 类型转换
将 bigint 类型的 userId 转换为 number 类型以匹配返回类型定义

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 22:38:50 -08:00
hailin e9c0196d68 feat(admin-web): 添加过期收益明细查询功能
- reward-service: 添加 getExpiredRewardsEntries API 查询过期收益明细
- reporting-service: 添加过期收益明细转发接口和类型定义
- admin-web: 过期收益统计区域新增"查看明细"按钮
  - 支持分页浏览过期收益记录
  - 支持按权益类型筛选
  - 显示过期时间、用户ID、账户、权益类型、金额、订单号

回滚方式:删除各服务中标注 [2026-01-07] 的代码块

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 22:36:31 -08:00
hailin 8c29603f5a fix(reporting-service): use field-level @unique for statsDate in schema
Root cause: @@unique([field], name: "xxx") requires { xxx: { field } } syntax
in findUnique/upsert, but code used { field } directly.

Fix: Change to @unique(map: "uk_realtime_stats_date") on the field itself.
This keeps the same database index name while allowing { statsDate } syntax.

No migration needed - only Prisma client type generation changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:38:13 -08:00
hailin 4e201d3a66 fix(reporting-service): use findFirst + update instead of upsert for realtimeStats
Replace upsert with findFirst + create/update pattern to avoid Prisma
unique constraint syntax issues. The @@unique constraint with a custom
name doesn't allow direct field-based queries in findUnique/upsert.

This approach maintains the same behavior without schema changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:36:26 -08:00
hailin ce1d342269 fix(reporting-service): use named unique constraint for realtimeStats queries
Prisma requires using the named unique constraint (uk_realtime_stats_date)
in where clauses for findUnique and upsert operations. This fixes the
PrismaClientValidationError that was occurring when processing planting
order events.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:25:24 -08:00
hailin d400290652 fix(reporting-service): 修复统计 incrementPlanting 对 undefined 参数的处理
当 planting.order.paid 事件中 treeCount 为 undefined 时:
- GlobalStatsRepository: 使用 ?? 0 提供默认值
- RealtimeStatsRepository: 使用 ?? 0 提供默认值
- 避免 Prisma upsert 因 undefined increment 值而报错

问题原因:planting-service 发送的事件数据中 treeCount 可能为 undefined

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:18:26 -08:00
hailin 272b4ffdbf feat(wallet-service): 添加手续费归集统计的历史数据兼容
当 FEE_COLLECTION 流水为空时,自动从提现订单表查询历史手续费:
- getFeeCollectionSummary: 从 withdrawal_orders 和 fiat_withdrawal_orders 聚合统计
- getFeeCollectionEntries: 从两个订单表查询明细列表,支持分页和类型筛选
- 按月统计使用 UNION ALL 合并两种提现订单数据
- 明细记录添加备注说明区分来源(区块链/法币)

回滚方式:删除 fallback 代码块和两个私有方法

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:11:37 -08:00
hailin 4dcdfb8a3c fix(wallet/reporting): 修复手续费归集统计 API 的数据库表名和响应解包问题
- wallet-service: 修复 getFeeCollectionSummary 中原生 SQL 使用错误表名
  - 将 ledger_entries 改为 wallet_ledger_entries(Prisma 映射表名)
- reporting-service: 修复 getFeeCollectionSummary/Entries 响应解包
  - wallet-service 返回 { success, data, timestamp } 格式需要解包 data

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:04:02 -08:00
hailin 4e5d9685a1 feat(admin-web): 添加面对面结算明细列表功能
- wallet-service: 新增 getOfflineSettlementEntries 方法和 API
- reporting-service: 新增客户端方法和 API 转发
- admin-web: 添加明细列表组件和样式,支持展开/收起

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 20:40:12 -08:00
hailin 9953f0eee5 fix(reporting-service): 修复面对面结算数据解包问题
wallet-service 返回 { success, data, timestamp } 包装格式,
getOfflineSettlementSummary 需要用 response.data.data 解包才能获取真正的数据。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 20:23:54 -08:00
hailin 4df9895863 feat(admin-web): 完善系统账户报表收益统计显示
- 分享引荐收益汇总:显示所有状态(PENDING/SETTLEABLE/SETTLED/EXPIRED)的完整数据
- 面对面结算:改为从 wallet_ledger_entries 表查询 SPECIAL_DEDUCTION 类型
- 新增按状态分组统计表格和详细分类卡片

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 20:11:09 -08:00
hailin 2c8263754f fix(wallet-service): fix system-withdrawal API route prefix to match Kong gateway
Changed controller route from 'system-withdrawal' to 'wallets/system-withdrawal'
to align with Kong's /api/v1/wallets/* routing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:53:54 -08:00
hailin 305514b246 feat(admin-web): 仪表板改用 planting-service 源数据
统计卡片和趋势图不再使用 reporting-service,直接使用 planting-service 的源数据:

- 统计卡片:总认种量、总订单数、今日认种、本月认种
- 趋势图:支持 7天/30天/90天 切换
- 新增 usePlantingTrendForDashboard hook

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:47:44 -08:00
hailin fe8e9a9bb6 fix(planting-service): 修复趋势数据查询表名错误
表名应为 planting_orders(复数),不是 planting_order

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:26:36 -08:00
hailin 64bd82b77b feat(wallet/blockchain/identity): implement system account withdrawal feature
- Add SystemWithdrawalApplicationService to handle system account transfers
- Add SystemWithdrawalController with endpoints for request, query, and account listing
- Add SystemWithdrawalStatusHandler to process blockchain confirmation/failure events
- Add SystemWithdrawalRequestedHandler in blockchain-service to execute ERC20 transfers
- Add getUserByAccountSequence endpoint in identity-service for user lookup
- Support dynamic memo generation based on actual source account name
- Dual-sided ledger entries for system account transfers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:22:15 -08:00
hailin fa1931b3b6 feat(planting-service, admin-web): 实现认种趋势图表功能
后端变更 (planting-service):
- 添加 getTrendData API 接口支持按时间维度(日/周/月/季度/年)查询趋势数据
- 添加 TrendPeriod 类型和 TrendDataPoint 接口
- 实现 repository 层的趋势数据聚合查询

前端变更 (admin-web):
- 添加趋势数据 API 端点和类型定义
- 使用 recharts 实现折线图展示认种棵数和订单数趋势
- 支持日/周/月/季度/年度时间维度切换
- 添加加载状态、错误状态和空数据状态处理

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:21:11 -08:00
hailin 4f3660f05e feat(statistics): 恢复榴莲树认种数量趋势图表
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:01:37 -08:00
hailin 24bcc45d5a refactor(statistics): 删除认种统计页面不相关的mock功能
删除以下mock数据和功能:
- 榴莲树认种数量趋势图表
- 龙虎榜与排名统计
- 区域认种数据统计
- 省/市公司运营统计
- 收益明细与来源

保留核心认种统计:
- 榴莲树认种总量(含积分)
- 今日认种数量(含积分)
- 本月认种数量(含积分)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:00:50 -08:00
hailin 36a83397a8 revert: 撤销对 authorization/identity/reporting 服务的修改
这些服务不需要同步手续费账户的定义,wallet-service 独立处理。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 09:48:42 -08:00
hailin 2be9a2d9c2 feat(statistics): 认种统计改为真实数据并显示积分
后端变更(planting-service):
- 添加 getMonthStats() 方法获取本月认种统计
- 更新 GlobalStatsResult 接口添加 monthStats 字段
- 添加 MonthStatsDto 响应类型

前端变更(admin-web):
- 更新 PlantingGlobalStats 类型定义
- statistics 页面调用真实 API 获取认种统计
- 显示认种总量、今日、本月的棵数和积分

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 09:36:46 -08:00
hailin 898521d236 fix: 同步手续费归集账户到所有相关服务
- identity-service: seed.ts 添加 S0000000005 和 S0000000006
- authorization-service: 枚举添加 SHARE_RIGHT_POOL 和 FEE_COLLECTION
- authorization-service: 初始化固定账户列表添加新账户
- reporting-service: 修复账户类型映射 (PLATFORM_FEE -> FEE_COLLECTION)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 09:30:45 -08:00
hailin 84fb6b8500 fix(reward-service): 修复字段名错误 sourceOrderId → sourceOrderNo
数据库中的字段名是 sourceOrderNo,修复 getRewardEntriesByType 和
getFeeEntriesDetailed 方法中的字段映射错误。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 09:27:25 -08:00
hailin 4b5270f130 feat(admin-web): 添加系统账户收益类型详细明细列表功能
为系统账户报表中的5个收益类型汇总Tab添加详细明细查看功能:
- 手续费账户汇总:点击"查看详细明细"展开手续费记录列表
- 省团队收益汇总:展开省团队权益记录列表
- 市团队收益汇总:展开市团队权益记录列表
- 分享引荐收益汇总:展开分享权益记录列表
- 社区收益汇总:展开社区权益记录列表

后端变更:
- reward-service: 添加 getRewardEntriesByType、getFeeEntriesDetailed 方法
- reward-service: 添加 /statistics/reward-entries-by-type、/statistics/fee-entries-detailed 接口
- reporting-service: 添加对应的聚合接口

前端变更:
- 添加 RewardEntryDTO、RewardEntriesResponse 类型定义
- 添加 getRewardEntriesByType、getFeeEntriesDetailed API 方法
- FeeAccountSection、RewardTypeSummarySection 组件添加详细明细列表展开功能
- 添加分页支持(每页20条)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 09:19:15 -08:00
hailin 283553a474 fix(wallet-service): 统一系统账户 seed migration
- 将 S0000000005 和 S0000000006 添加到初始 seed migration
- 简化 S0000000006 migration 格式与其他账户保持一致
- 新环境初始化时所有系统账户一次性创建

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 09:12:17 -08:00
hailin b9911ab460 feat(wallet-service): 实现手续费归集账户功能
- 新增系统账户 S0000000006 (user_id=-6) 用于归集提现手续费
- 新增 FEE_COLLECTION 流水类型记录手续费归集
- 区块链提现完成时使用 UnitOfWork 事务归集手续费
- 法币提现完成时在事务中归集手续费
- WithdrawalOrderRepository 添加事务支持
- 所有手续费归集操作使用乐观锁保护

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 09:10:41 -08:00
hailin 99b725db0a feat(admin-web): 添加系统账户收益类型汇总统计功能
在数据统计-系统账户中新增5个统计Tab:
- 手续费账户汇总:统计成本费、运营费、总部社区基础费、RWAD底池注入
- 省团队收益汇总:统计省团队权益收益
- 市团队收益汇总:统计市团队权益收益
- 分享引荐收益汇总:统计分享权益收益
- 社区收益汇总:统计社区权益收益

后端变更:
- reward-service: 添加 getRewardsSummaryByType、getAllRewardTypeSummaries 方法
- reporting-service: 聚合收益类型汇总统计接口

前端变更:
- 添加 RewardTypeSummary、FeeAccountSummary 类型定义
- 添加 getRewardTypeSummaries API 方法
- 添加 FeeAccountSection、RewardTypeSummarySection 组件

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 08:54:35 -08:00
hailin bbafe58e86 fix(wallet-service): update init migration memo column to TEXT
Ensure new database installations use TEXT type for memo column from the start.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 08:30:07 -08:00
hailin 069c549bc4 feat(wallet-service): add migration for memo column type change to TEXT
Change wallet_ledger_entries.memo from VARCHAR(500) to TEXT to support longer settlement memos.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 08:27:12 -08:00
hailin bf1c8d2228 feat(wallet-service): 实现 Unit of Work 模式保证 settleToBalance 事务原子性
- 新增 UnitOfWork 接口和实现,使用 Prisma Interactive Transaction
- 修改 IWalletAccountRepository 和 ILedgerEntryRepository 接口支持可选事务参数
- 修改仓库实现,支持在事务中执行数据库操作
- 修改 settleToBalance 方法使用 UnitOfWork,确保钱包更新和流水记录原子性
- 注册 UnitOfWorkService 到 InfrastructureModule

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 07:50:02 -08:00
hailin 7dc25b75d2 revert: 回滚 settleToBalance 的直接 Prisma 实现,准备用 Unit of Work 模式重新实现 2026-01-06 07:07:27 -08:00
hailin 4c6e64a604 fix(wallet-service): settleToBalance 添加乐观锁防止并发冲突 2026-01-06 06:56:50 -08:00
hailin 65cb574f59 fix(wallet-service): 添加钱包状态检查,确保只有 ACTIVE 钱包可结算 2026-01-06 06:50:08 -08:00
hailin 5204d24c88 fix(wallet-service): 修复 settleToBalance 方法缺少事务保护的严重 Bug
问题原因:
settleToBalance 方法先执行 wallet.save() 更新账户余额,再执行
ledgerRepo.save() 写入流水记录。两个操作不在同一个事务中。

当流水写入失败时(如 memo 字段超过 VarChar(500) 限制),账户余额
已经被修改,但流水记录未写入,导致数据不一致。

具体案例:
用户 D25122700023 点击结算时,memo 内容超长(66笔奖励详情),
wallet-service 先把 settleable_usdt 转入 usdt_available,然后
写流水失败。账户余额被改但没有对应流水。

修复内容:
1. settleToBalance: 使用 prisma.$transaction 确保原子性
   - 账户余额更新和流水记录在同一事务中
   - 任一操作失败整个事务回滚
2. schema: memo 字段从 VarChar(500) 改为 Text 类型,无长度限制

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 06:40:09 -08:00
hailin 573e58c89b fix(wallet-service): 统一奖励分配到 settleable_usdt,与 reward-service 保持一致
问题原因:
wallet-service 对不同类型奖励的分配方式不一致:
- SHARE_RIGHT: 正确使用 addSettleableReward() → settleable_usdt
- CITY_TEAM_RIGHT/COMMUNITY_RIGHT: 错误使用 addAvailableBalance() → usdt_available

这导致 reward-service 记录的 SETTLEABLE 奖励总额与 wallet-service 的
settleable_usdt 字段不匹配。用户 D25122700024 的案例中:
- reward-service: 3条奖励共 4464 USDT (SHARE_RIGHT 3600 + CITY_TEAM_RIGHT 288 + COMMUNITY_RIGHT 576)
- wallet-service: settleable_usdt = 3600 (仅 SHARE_RIGHT)
差额 864 USDT 被错误地放入了 usdt_available

修复内容:
1. allocateCommunityRight: 改用 addSettleableReward() 替代 addAvailableBalance()
2. allocateToRegionAccount: 改用 addSettleableReward() 替代 addAvailableBalance()
3. 流水类型统一使用 REWARD_TO_SETTLEABLE 替代 SYSTEM_ALLOCATION
4. 日志和备注更新以反映新的分配方式

设计原则:
- reward-service 是奖励的权威来源
- wallet-service 应跟随 reward-service 的设计
- 所有奖励都应进入 settleable_usdt,用户主动结算后才转入 usdt_available

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 03:49:31 -08:00
hailin ec71121907 fix(reward-service): 修复 WalletServiceClient 未正确解析 wallet-service 响应格式的 Bug
问题原因:
wallet-service 使用全局 TransformInterceptor 拦截器,会将所有响应包装成:
{ success: true, data: { success: boolean, ... }, timestamp: "..." }

原代码直接读取外层的 success 字段(始终为 true),导致即使业务失败
(内层 data.success = false)也被误判为成功。

具体案例:
用户 D25122700024 点击结算时,wallet-service 因余额不足返回:
{ success: true, data: { success: false, error: "Insufficient..." }, ... }
reward-service 误读为成功,导致奖励被标记为 SETTLED 但钱包余额未变更。

修复内容:
1. settleToBalance: 解析 response_data.data 获取真实业务结果
2. confirmPlantingDeduction: 同上
3. allocateFunds: 同上

所有方法现在会:
- 使用 response_data.data || response_data 兼容包装和非包装格式
- 严格检查 data.success !== true 来判断业务是否成功
- 失败时记录详细错误日志

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 03:38:00 -08:00
hailin 8b80e45524 fix(authorization): 火柴人排名过滤已撤销授权的考核记录
- findRankingsByMonthAndRegion 和 findRankingsByMonthAndRoleType 增加过滤条件
- 排除 authorization.status = 'REVOKED' 的记录
- 解决同一用户因有多条授权记录(含已撤销)而重复显示的问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 19:55:23 -08:00
hailin 6c78e22000 fix(authorization): 添加火柴人排名调试日志
添加详细日志显示返回的每条记录的区域信息,便于调试过滤问题。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 06:23:57 -08:00
hailin bdc6ba524f fix(authorization): 火柴人排名改为按区域过滤
修改排名逻辑,只显示获得相同省/市公司授权的用户排名。

- 后端 getStickmanRanking 改用 findRankingsByMonthAndRegion
- 简化实时创建评估逻辑,只为当前区域创建
- 更新前端注释说明

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 05:56:30 -08:00
hailin 3b3342de5c feat(wallet-service): 添加内部转账入账修复脚本
新增一次性修复脚本用于补录因接收方钱包未创建导致入账失败的内部转账。

脚本特性:
- DRY_RUN 模式:默认只检查不执行,需手动改为 false 才真正修复
- 完整验证:订单状态、类型、接收方信息、txHash
- 幂等性检查:确认接收方没有 TRANSFER_IN 流水
- 转出方验证:确认转出方有 TRANSFER_OUT 流水(已扣款)
- 乐观锁:使用 version 字段防止并发修改
- 审计追踪:payloadJson.dataFix=true 标记修复操作
- 详细日志:每步操作都有时间戳和日志级别

使用方法:
1. 在 wallet-service 容器内执行 DRY_RUN 检查
2. 确认无误后将 DRY_RUN 改为 false 再次执行

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 05:06:27 -08:00
hailin ac0e73afac feat(wallet/blockchain): 热钱包余额预检查及接收方钱包自动创建
1. blockchain-service: 新增热钱包 dUSDT 余额定时更新调度器
   - 每 5 秒查询热钱包在 KAVA 链上的 dUSDT 余额
   - 更新到 Redis DB 0,key 格式: hot_wallet:dusdt_balance:{chainType}
   - TTL 30 秒,服务故障时缓存自动过期

2. wallet-service: 新增热钱包余额缓存服务
   - 从 Redis DB 0 读取热钱包余额缓存
   - 严格模式:无法获取余额或余额不足时拒绝转账
   - 提示信息:"财务系统审计中,请稍后再试"

3. wallet-service: 转账确认时自动创建接收方钱包
   - 解决接收方钱包不存在导致入账失败的问题
   - 使用 upsert 避免并发创建冲突
   - 在同一事务中完成创建和入账

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 04:31:52 -08:00
hailin 66ace25935 fix(reporting): remove userId dependency in planting.order.paid handler
- Change userId to optional in PlantingOrderPaidEvent interface
- Add accountSequence field for user identification
- Remove relatedUserId from activity creation (was causing BigInt error)
- Store accountSequence in metadata instead

Fixes: TypeError: Cannot convert undefined to a BigInt

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 00:05:56 -08:00
hailin c3c15b7880 fix(wallet-service): remove invalid nested $queryRaw in getOfflineSettlementSummary
删除使用嵌套 $queryRaw 进行条件拼接的错误查询,保留简化版本。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 23:37:57 -08:00
hailin 49cdeb4aef fix(reporting-service): fix planting.order.paid event message format
planting-service 发送的消息是直接数据格式,不包含 payload 包装,
修正 ActivityEventConsumerController 以适配实际消息格式。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 23:32:32 -08:00
hailin 229dff1a9d feat(system-accounts): add ledger detail API for all system accounts
新增所有系统账户的分类账明细查询功能:
- wallet-service: 添加 getSystemAccountLedger 和 getAllSystemAccountsLedger 方法
- wallet-service: 添加 /statistics/system-account-ledger 和 /statistics/all-system-accounts-ledger API
- reporting-service: 添加 /all-ledger 端点透传分类账数据

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 23:30:38 -08:00
hailin 56f2fd206d fix(reporting-service): extract data from wrapped API response
wallet-service API 返回 { success, data } 格式,需要解析 response.data.data

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 23:20:38 -08:00
hailin 6d5c5f7e4c fix(reporting-service): add /api/v1 prefix to wallet and reward service API calls
修复 reporting-service 调用 wallet-service 和 reward-service 时的 404 错误,
所有内部 HTTP 调用路径添加 /api/v1 全局前缀。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 23:18:10 -08:00
hailin 838d5c1d3b feat(reporting): fix system account report to use wallet-service data
The system account balances were showing 0 because data was being fetched
from authorization-service.system_accounts table instead of the actual
wallet-service.wallet_accounts table where funds are stored.

Changes:
- wallet-service: Add getAllSystemAccounts() method to query all system
  accounts (fixed S*, province 9*, city 8*) with actual balances
- wallet-service: Add /wallets/statistics/all-system-accounts API endpoint
- reporting-service: Update SystemAccountReportApplicationService to fetch
  data from wallet-service instead of authorization-service
- reporting-service: Fix default service URLs to use correct container names
  and ports (rwa-wallet-service:3001, rwa-reward-service:3005)
- docker-compose: Add WALLET_SERVICE_URL and REWARD_SERVICE_URL env vars
  for reporting-service

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 23:10:20 -08:00
hailin 1c4def2867 feat(kong): add system-account-reports route to reporting-service
Add Kong route for the new system account reports API endpoint
at /api/v1/system-account-reports, forwarding to reporting-service.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 22:27:55 -08:00
hailin e95316c5f4 fix(authorization-service): register SystemAccountApplicationService in AppModule
Add missing dependency injection for SystemAccountApplicationService
which is required by InternalAuthorizationController for system account
report statistics API.

- Import SystemAccountRepositoryImpl and SYSTEM_ACCOUNT_REPOSITORY
- Register SystemAccountApplicationService as provider
- Register SYSTEM_ACCOUNT_REPOSITORY with SystemAccountRepositoryImpl

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 22:22:02 -08:00
hailin 6e395ce58c feat(reporting): add system account report aggregation feature
## Changes
- Add system account report aggregation APIs in reporting-service
- Add internal statistics APIs in wallet-service, reward-service, authorization-service
- Add system accounts tab in admin-web statistics page
- Enhanced metadata in reward entries for traceability

## Backend Changes
- wallet-service: Add offline settlement summary and system accounts balances APIs
- reward-service: Add expired rewards summary API
- authorization-service: Add fixed accounts list, region accounts summary APIs
- reporting-service: Add HTTP clients and aggregation service for system account reports

## Frontend Changes
- admin-web: Add SystemAccountsTab component with fixed accounts, region summaries,
  offline settlement stats, and expired rewards display

## Rollback Instructions
Each file includes rollback comments with [2026-01-04] tag marking new additions.
To rollback: delete files marked as new, remove code sections marked with date comments.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 22:06:58 -08:00
hailin 8d97ed2720 fix(wallet-service): convert BigInt to string for JSON serialization in getUnprocessedSettlements
The entry.id field is BigInt type from Prisma which cannot be JSON serialized directly.
Convert to string for API response and back to BigInt when storing to database.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 07:46:46 -08:00
hailin 251fee4f1e feat(wallet-service): add offline settlement deduction feature
Add new functionality for admins to automatically deduct all settled
earnings when creating special deductions with amount=0, marking
each record to prevent duplicate deductions.

- Add OfflineSettlementDeduction model to track deducted records
- Add API endpoints for querying unprocessed settlements and executing batch deduction
- Add mode selection UI in admin-web pending-actions
- Add offline settlement card display in mobile-app special deduction page

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 06:56:39 -08:00
hailin 46b68e8652 feat(planting-service): add global stats API for data verification
Add new endpoint GET /api/v1/planting/stats/global to query planting
statistics directly from the database, providing reliable data source
for verifying reporting-service statistics.

New features:
- GlobalPlantingStats: total tree count, order count, amount
- StatusDistribution: breakdown by order status (PAID to MINING_ENABLED)
- TodayStats: daily statistics with tree count, order count, amount

Implementation:
- Pure additive changes, no modifications to existing code
- Read-only aggregate queries using Prisma aggregate/groupBy
- No database schema changes required

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 06:55:08 -08:00
hailin 8148f7a52a fix(leaderboard-service): add @IsIn validator to UpdateLeaderboardSwitchDto
The 'type' field was missing validation decorator, causing 400 Bad Request
when ValidationPipe with forbidNonWhitelisted was enabled.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 04:11:05 -08:00
hailin aa58b9e745 fix(leaderboard-service): fix AdminGuard role case sensitivity
The AdminAccount table stores roles in lowercase (admin, super_admin),
but AdminGuard was checking for uppercase (ADMIN, SUPER_ADMIN).
This caused 403 Forbidden errors for authenticated admin users.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 04:07:23 -08:00
hailin cb59a964dd fix(leaderboard-service): change global prefix from 'api' to 'api/v1'
Match the global prefix convention used by all other services.
This fixes Kong routing 404 errors for /api/v1/leaderboard/* endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 04:01:09 -08:00
hailin ea93bafe7e fix(leaderboard): add REFERRAL_SERVICE_URL to docker-compose
The leaderboard-service needs to connect to referral-service for
team statistics data. Without this environment variable, it falls
back to localhost:3004 which fails inside Docker network.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 03:47:00 -08:00
hailin dacefa2b51 feat(leaderboard): add toggle control for mobile-app ranking page
- Add public /leaderboard/status endpoint (no auth required)
- Add LeaderboardService in mobile-app to fetch board status
- Update RankingPage to show "待开启" when board is disabled
- Connect admin-web leaderboard page to real API
- Board toggle now takes effect immediately

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 03:35:57 -08:00
hailin 52afe72f17 fix(authorization): migration should drop both constraint and index
The original migration only used DROP CONSTRAINT which failed silently
because Prisma created an INDEX instead. Added DROP INDEX as well to
handle both cases in future deployments.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 03:14:00 -08:00
hailin 0991d5d484 fix(authorization): allow querying REVOKED records despite deletedAt being set
撤销授权时会同时设置 status=REVOKED 和 deletedAt(软删除),
导致 findByStatus(REVOKED) 因为 deletedAt IS NULL 条件永远返回空。
修改为查询 REVOKED 状态时不过滤 deletedAt。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 02:52:43 -08:00
hailin 5026661fa8 chore(planting): update contract PDF template to release version
Signature field position: x=449.51, y=140.18 (moved further right and up).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 02:45:28 -08:00
hailin bdc3cdd75e chore(planting): update contract PDF template to v1.2
Moved signature button field further right (x=435.60) and down (y=113.51).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 02:13:48 -08:00
hailin bc1d4a62c6 fix(authorization): add Transform decorator to parse includeRevoked query param
查询参数都是字符串类型,需要将 'true' 转换为布尔值 true,
否则后端无法正确处理 includeRevoked 参数,导致已撤销的授权记录不显示。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 02:07:49 -08:00
hailin c8f2d5edff chore(planting): update contract PDF template to v1.1
Updated signature field position to x=427.60 for better alignment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 01:48:34 -08:00
hailin af0b9d38c0 Revert "fix(authorization): exclude revoked records when checking existing authorization"
This reverts commit ec528a7226.
2026-01-04 01:08:28 -08:00
hailin ec528a7226 fix(authorization): exclude revoked records when checking existing authorization
The findByAccountSequenceAndRoleType query now excludes REVOKED status,
allowing users to be re-authorized after their authorization was revoked.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 00:59:37 -08:00
hailin 30cb245301 refactor: rename "总部社区" to "总部" across backend services
Changed display name from "总部社区" to "总部" in:
- authorization-service
- identity-service seed
- leaderboard-service seed and entity

Note: Existing database records need manual update if already seeded.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 00:34:36 -08:00
hailin 67c7d9149c fix(planting): move signature field right to avoid overlapping text
Moved the signature field from x=415 to x=470 in the PDF template
to prevent the signature image from covering the "乙方(签字/盖章):" text.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 00:28:07 -08:00
hailin 16d895d460 debug: add logging to queryAuthorizations 2026-01-04 00:12:43 -08:00
hailin ef6b2ceb22 fix(authorization): show all authorized users in admin list including those in assessment period
Previously used findAllActive() which only returned users with benefitActive=true,
causing users still in assessment period to be hidden. Now uses findByStatus()
to show all AUTHORIZED users regardless of benefit activation status.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 00:04:24 -08:00
hailin f5afb65df8 fix(planting): center signature image on the signature field
Calculate signature position based on field center instead of left-bottom
corner, so the signature image is properly centered within the field area.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 00:00:13 -08:00
hailin ef80a2f23b fix(planting): remove signature button field before flatten to avoid gray background
The signature button field has a gray background that covers the drawn
signature image when the form is flattened. Now we remove the signature
field after drawing the signature image to prevent this.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:45:44 -08:00
hailin 083c0fd540 fix(planting): draw signature directly on page instead of using form field
The PDF signature field is only 92x51 points, which causes signatures to
appear too small or invisible. Changed to use drawImage() directly on
the page at the field's position with a larger size (150x80 max).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:28:18 -08:00
hailin 50f960ecea fix(authorization): allow admin tokens without accountSequence field
Admin JWT tokens from identity-service don't include the accountSequence
field (only userId, email, role, type). This caused a 400 error with
message "管理员账户序列号不能为空" when admins tried to grant authorizations.

Changes:
- Update AdminUserId value object to make accountSequence optional
- Use 'ADMIN' as default value when accountSequence is not provided
- Update all controller methods to handle optional accountSequence

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 22:57:56 -08:00
hailin 4a3658e770 chore(planting): update contract PDF template to v1
更新认种合同PDF模板为v1版本

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 22:43:15 -08:00
hailin 825b80b319 fix(planting): match PDF form field names to template
修改代码中的表单字段名以匹配PDF模板中的实际字段名:
- totalAmount → RmbAmount
- totalAmountChinese → SpellChineseFormatNumber
- greenPointsAmount → GreenAmount

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 21:33:14 -08:00
hailin 9c17140b33 feat(contract): update contract template with amount fields
更新合同模板和 PDF 生成服务,支持动态计算金额字段。

## 合同模板更新
- 替换为新版联合种植协议模板(3页,带公章)
- 新增表单字段:totalAmount、totalAmountChinese、greenPointsAmount

## PDF 生成服务更新
- 新增单价常量:
  - PRICE_PER_TREE_CNY = 17414.1(人民币含税价)
  - PRICE_PER_TREE_GREEN_POINTS = 15831(绿积分价格)
- 新增 numberToChineseAmount() 函数:数字转中文大写金额
- 更新 ContractPdfData 接口:新增可选字段 totalAmount、greenPointsAmount
- 更新 fillFormFields():根据认种棵数自动计算金额
- 移除坐标定位填充方式,仅使用表单字段方式
- 所有表单字段现为必需,缺少时抛出明确错误

## 金额计算逻辑
- 人民币金额 = 棵数 × 17414.1
- 绿积分金额 = 棵数 × 15831
- 大写金额自动生成(如:壹万柒仟肆佰壹拾肆元壹角)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 20:21:37 -08:00
hailin 17b9c09381 feat(ledger): add detailed ledger entry views with source tracking
实现账本流水详情功能,支持点击查看各类型流水的详细信息。

## reward-service 后端

### 数据库
- 新增 `source_account_sequence` 字段到 `reward_ledger_entries` 表
- 添加索引 `idx_source_account_seq` 提升查询性能
- 字段可空,兼容历史数据

### 领域层
- `RewardSource` 值对象新增 `sourceAccountSequence` 属性
- `RewardCalculationService` 传递 `sourceAccountSequence`

### 应用层
- 新增 `getSettlementHistory` 方法查询结算历史
- 新增 `SettlementRecordRepository` 仓储实现

### API层
- 新增 `GET /settlements/history` 接口
- 新增 `SettlementHistoryQueryDTO` 和 `SettlementHistoryDTO`

## mobile-app 前端

### 服务层
- `RewardService` 新增结算历史相关模型和方法:
  - `SettlementHistoryItem` 结算记录模型
  - `SettlementRewardEntry` 关联奖励条目模型
  - `getSettlementHistory()` 获取结算历史

- `WalletService` 新增:
  - `LedgerEntry.payloadJson` 字段及辅助方法
  - `counterpartyAccountSequence` 获取转账对手方ID
  - `counterpartyUserId` 获取转账对手方用户ID
  - `transferFee` 获取转账手续费

### 账本详情页
- 结算流水详情:显示结算金额、币种、涉及奖励明细(含来源用户)
- 提现流水详情:显示提现订单信息、状态、手续费等
- 转账流水详情:显示转入来源/转出目标用户信息

### 交互优化
- REWARD_SETTLED、WITHDRAWAL、TRANSFER_IN、TRANSFER_OUT 类型可点击
- 使用底部弹窗展示详情,支持滚动查看长列表

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 20:09:17 -08:00
hailin 35a812c058 feat(authorization): add admin authorization management API and real data integration
Backend (authorization-service):
- Add QueryAuthorizationsDto for query parameters (roleType, keyword, includeRevoked, page, limit)
- Add queryAuthorizations method to fetch all authorizations with user info
- Add GET /admin/authorizations endpoint for listing authorizations
- Add POST /admin/authorizations/:id/revoke endpoint for revoking authorization

Frontend (admin-web):
- Add authorization.types.ts with RoleType, Authorization, and request types
- Add authorizationService.ts for API calls (list, revoke, grant operations)
- Add useAuthorizations.ts React Query hooks
- Update authorization page to use real API data instead of mock data
- Add loading/error states, pagination, and revoke reason display
- Add new styles for loading, error, pagination, and date columns

The authorization management page now displays all authorized users
from the database with support for filtering by role type, status,
and keyword search.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 18:50:10 -08:00
hailin dcd6f2ce18 fix: 修复特殊扣减API路径和批量创建用户ID解析问题
1. mobile-app: 修复特殊扣减API路径重复问题
   - 将 /api/v1/wallets/special-deduction/execute 改为 /wallets/special-deduction/execute
   - 因为 ApiClient baseURL 已包含 /api/v1 前缀

2. admin-web: 批量创建待办操作支持中文逗号分隔
   - 正则表达式从 /[\n,]/ 改为 /[\n,,]/
   - 同时支持换行、英文逗号、中文逗号作为分隔符

3. identity-service: 添加用户查找调试日志
   - 在 findUserByIdOrSequence 方法中添加日志
   - 便于排查用户ID查找失败的问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 17:54:27 -08:00
hailin dfdd8ed65a feat(pending-actions): add special deduction feature for admin-created user actions
实现特殊扣减功能,允许管理员为用户创建扣减待办操作,由用户在移动端确认执行。

## 后端 (wallet-service)

### 领域层
- 新增 `SPECIAL_DEDUCTION` 到 LedgerEntryType 枚举
  用于记录特殊扣减的账本流水类型

### 应用层
- 新增 `executeSpecialDeduction` 方法
  - 验证用户钱包存在性
  - 检查余额是否充足
  - 乐观锁控制并发
  - 扣减余额并记录账本流水
  - 返回操作结果和新余额

### API层
- 新增内部API: POST /api/v1/wallets/special-deduction/execute
  供移动端调用执行特殊扣减操作

## 前端 (admin-web)

### 类型定义
- 新增 `SPECIAL_DEDUCTION` 到 ACTION_CODES
- 新增 `SpecialDeductionParams` 接口定义扣减参数
  - amount: 扣减金额
  - reason: 扣减原因

### 页面
- 更新待办操作管理页面
  - 当选择 SPECIAL_DEDUCTION 时显示扣减金额和原因输入框
  - 验证扣减金额必须大于0
  - 验证扣减原因不能为空

### 样式
- 新增特殊扣减表单区域样式

## 前端 (mobile-app)

### 服务层
- 新增 `executeSpecialDeduction` 方法到 WalletService
- 新增 `SpecialDeductionResult` 结果类
- 新增 `specialDeduction` 到 PendingActionCode 枚举

### 页面
- 新增 `SpecialDeductionPage` 特殊扣减确认页面
  - 显示扣减金额和管理员备注
  - 显示当前余额和扣减后余额
  - 余额不足时禁用确认按钮
  - 温馨提示说明操作性质

- 更新 `PendingActionsPage`
  - 处理 SPECIAL_DEDUCTION 类型的待办操作
  - 从 actionParams 解析 amount 和 reason
  - 导航到特殊扣减确认页面

## 工作流程

1. 管理员在 admin-web 创建 SPECIAL_DEDUCTION 待办操作
   - 选择目标用户
   - 输入扣减金额
   - 输入扣减原因

2. 用户在 mobile-app 待办操作列表看到该操作

3. 用户点击后进入特殊扣减确认页面
   - 查看扣减详情
   - 确认余额充足
   - 点击确认执行扣减

4. 后端执行扣减并记录账本流水

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 07:04:46 -08:00
hailin a609600cd8 feat(fiat-withdrawal): add complete fiat withdrawal system
实现完整的法币提现功能,支持银行卡、支付宝、微信三种收款方式。
此功能与现有的区块链划转功能完全独立,互不影响。

## 后端 (wallet-service)

### 数据库
- 新增 `fiat_withdrawal_orders` 表存储法币提现订单
- 与现有 `withdrawal_orders` 表(区块链划转)完全分离
- 添加完整索引支持高效查询

### 领域层
- 新增 `FiatWithdrawalStatus` 枚举(与 WithdrawalStatus 独立)
  - 流程: PENDING -> FROZEN -> REVIEWING -> APPROVED -> PAYING -> COMPLETED
  - 或 REJECTED / FAILED / CANCELLED
- 新增 `PaymentMethod` 枚举: BANK_CARD / ALIPAY / WECHAT
- 新增 `FiatWithdrawalOrder` 聚合根
- 新增 `IFiatWithdrawalOrderRepository` 仓储接口
- 新增 `FIAT_WITHDRAWAL` 账本流水类型

### 应用层
- 新增 `FiatWithdrawalApplicationService` 处理业务逻辑
  - 发送短信验证码
  - 申请法币提现(冻结余额)
  - 提交审核
  - 审核通过/驳回
  - 开始打款
  - 完成打款

### API层
- 新增 `FiatWithdrawalController` 提供用户端API
  - POST /wallet/fiat-withdrawal/send-sms - 发送验证码
  - POST /wallet/fiat-withdrawal - 申请提现
  - GET /wallet/fiat-withdrawal - 获取提现记录
- 新增内部API供管理端调用
  - GET /api/v1/wallets/fiat-withdrawals - 查询订单
  - POST /api/v1/wallets/fiat-withdrawals/:orderNo/review - 审核
  - POST /api/v1/wallets/fiat-withdrawals/:orderNo/start-payment - 开始打款
  - POST /api/v1/wallets/fiat-withdrawals/:orderNo/complete-payment - 完成打款

## 前端 (admin-web)

- 新增法币提现审核管理页面 `/withdrawals`
- 支持按状态分 Tab 查看订单
- 支持审核通过/驳回
- 支持打款操作
- 支持查看订单详情

## 前端 (mobile-app)

- 新增 `WithdrawFiatPage` 法币提现页面
  - 支持选择银行卡/支付宝/微信
  - 输入收款账户信息
- 新增 `WithdrawFiatConfirmPage` 确认页面
  - 短信验证码验证
  - 密码验证
- 在 `WalletService` 中添加法币提现相关方法和模型

## 重要说明

此功能与现有的区块链划转功能 (withdraw_usdt_page.dart) 完全独立:
- 独立的数据库表
- 独立的聚合根
- 独立的状态枚举
- 独立的API端点
- 独立的前端页面

原有的区块链划转功能保持不变,不受任何影响。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 06:39:11 -08:00
hailin d614d18e97 Revert "feat(withdrawal): implement fiat withdrawal with bank/alipay/wechat"
This reverts commit 288d894746.
2026-01-03 05:44:43 -08:00
hailin 288d894746 feat(withdrawal): implement fiat withdrawal with bank/alipay/wechat
Add complete fiat withdrawal feature that allows users to withdraw
green credits (绿积分) to their bank card, Alipay, or WeChat account
with 1:1 CNY conversion. Key changes:

Backend (wallet-service):
- Update Prisma schema with fiat withdrawal fields (paymentMethod,
  bankName, bankCardNo, cardHolderName, alipay*, wechat*, review fields)
- Rewrite withdrawal status enum for fiat flow: PENDING → FROZEN →
  REVIEWING → APPROVED → PAYING → COMPLETED (or REJECTED/FAILED)
- Add PaymentMethod enum: BANK_CARD, ALIPAY, WECHAT
- Update WithdrawalOrderAggregate with new fiat withdrawal methods
- Add review/payment workflow methods in WalletApplicationService
- Add internal API endpoints for admin withdrawal management
- Remove blockchain withdrawal event handler (no longer needed)

Frontend (admin-web):
- Add withdrawal review management page at /withdrawals
- Add tabs for reviewing/approved/paying order states
- Add withdrawal service and React Query hooks
- Add types for withdrawal orders and payment methods
- Add sidebar menu item for withdrawal review

Frontend (mobile-app):
- Add withdrawFiat() method to WalletService
- Add PaymentMethod enum with BANK_CARD/ALIPAY/WECHAT
- Create new WithdrawFiatPage for fiat withdrawal input
- Create WithdrawFiatConfirmPage with SMS + password verification
- Add routes for /withdraw/fiat and /withdraw/fiat/confirm
- Keep existing withdraw/usdt (划转) pages unchanged

Note: The existing withdraw_usdt_page.dart is for point-to-point
transfer (划转), which is a different feature from fiat withdrawal.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 05:28:05 -08:00
hailin 036696878f feat(settlement): implement settle-to-balance with detailed source tracking
Add complete settlement-to-balance feature that transfers settleable
earnings directly to wallet USDT balance (no currency swap). Key changes:

Backend (wallet-service):
- Add SettleToBalanceCommand for settlement operations
- Add settleToBalance method to WalletAccountAggregate
- Add settleToBalance application service with ledger recording
- Add internal API endpoint POST /api/v1/wallets/settle-to-balance

Backend (reward-service):
- Add settleToBalance client method for wallet-service communication
- Add settleRewardsToBalance application service method
- Add user-facing API endpoint POST /rewards/settle-to-balance
- Build detailed settlement memo with source user tracking per reward

Frontend (mobile-app):
- Add SettleToBalanceResult model class
- Add settleToBalance() method to RewardService
- Update pending_actions_page to handle SETTLE_REWARDS action
- Add completion detection via settleableUsdt balance check

Settlement memo now includes detailed breakdown by right type with
source user accountSequence for each reward entry, e.g.:
  结算 1000.00 绿积分到钱包余额
  涉及 5 笔奖励
    - SHARE_RIGHT: 500.00 绿积分
        来自 D2512120001: 288.00 绿积分
        来自 D2512120002: 212.00 绿积分

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 04:29:38 -08:00
hailin cbbef170e8 feat(pending-actions): display accountSequence alongside userId
- Add accountSequence field to PendingActionResponseDto
- Add helper methods to fetch accountSequence from UserAccount
- Update queryActions and getAction to include accountSequence
- Update admin-web table and detail view to show both fields
- accountSequence displayed prominently, userId shown as secondary info

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 21:33:03 -08:00
hailin 789d921fd7 fix(pending-actions): support accountSequence for user lookup
Allow admin to create pending actions using accountSequence (e.g.,
D25122700022) instead of requiring numeric userId.

- Add findUserByIdOrSequence helper method
- Update createAction to use helper
- Update batchCreateActions to use helper
- Update queryActions to support accountSequence filter

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 20:39:54 -08:00
hailin 582e80b750 fix(pending-actions): add @Public() decorator to AdminPendingActionController
Skip JWT auth for admin pending-actions endpoints since admin-web
authenticates through a different mechanism (admin-service tokens).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 19:00:24 -08:00
hailin ff038f31f9 fix(pending-actions): fix API response handling and add Kong route
- Fix pending_action_service.dart to access response.data instead of response
- Add Kong route for /api/v1/admin/pending-actions to identity-service

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 18:33:58 -08:00
hailin 28e0396a65 feat(pending-actions): add user pending actions system
Add a fully optional pending actions system that allows admins to configure
specific tasks that users must complete after login.

Backend (identity-service):
- Add UserPendingAction model to Prisma schema
- Add migration for user_pending_actions table
- Add PendingActionService with full CRUD operations
- Add user-facing API (GET list, POST complete)
- Add admin API (CRUD, batch create)

Admin Web:
- Add pending actions management page
- Support single/batch create, edit, cancel, delete
- View action details including completion time
- Filter by userId, actionCode, status

Flutter Mobile App:
- Add PendingActionService and PendingActionCheckService
- Add PendingActionsPage for forced task execution
- Integrate into splash_page login flow
- Users must complete all pending tasks in priority order

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 18:22:51 -08:00
hailin 04a8c56ad6 fix(identity): use correct Aliyun API for ID card verification
Change API from Id2MetaStandardVerify to Id2MetaVerify for two-factor
identity verification (name + ID card number). The previous API was
returning error 440 (no permission).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 08:05:08 -08:00
hailin e2cf3c3d7e fix(admin-service): 修复通知查询时publishedAt为null的问题
问题:当 publishedAt 为 NULL(表示立即发布)时,Prisma 的
`publishedAt: { lte: now }` 条件不匹配,导致通知无法显示

修复:将查询条件改为 OR 逻辑:
- publishedAt 为 null(立即发布)
- publishedAt <= now(定时发布且已到时间)

影响的方法:
- findNotificationsForUser
- countUnreadForUser
- markAllAsRead

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 05:56:43 -08:00
hailin fea0b42223 fix(admin-service): 修复维护拦截器路径检测和错误处理
问题:添加系统维护检测后站内通知功能失效

修复:
1. 使用 request.url 获取完整路径(包含 /api/v1 前缀)
2. 同时支持带前缀和不带前缀的路径检测
3. 添加 try-catch 错误处理,数据库错误时放行请求而非阻断
4. 添加日志记录便于调试

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 05:01:32 -08:00
hailin c392142562 feat(blockchain): 切换到dUSDT(绿积分)合约 - KAVA主网
合约信息:
- 地址: 0xA9F3A35dBa8699c8C681D8db03F0c1A8CEB9D7c3
- 名称: Durian USDT (dUSDT)
- 精度: 6位
- 网络: KAVA EVM Mainnet (Chain ID: 2222)
- 链接: https://kavascan.com/address/0xA9F3A35dBa8699c8C681D8db03F0c1A8CEB9D7c3

修改:
- blockchain.config.ts: 更新默认合约地址
- chain-config.service.ts: 更新默认合约地址
- docker-compose.yml: NETWORK_MODE改为mainnet,配置KAVA主网
- .env.example: 更新合约地址和注释
- KAVA_NETWORK.md: 标注dUSDT为当前使用合约

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 04:31:11 -08:00
hailin 8173e1f973 feat: "同僚"改为"同伴" + KYC从三要素改为二要素
mobile-app:
- profile_page.dart: 将所有"同僚"改为"同伴"

identity-service:
- 层级1实名认证从三要素(姓名+身份证+手机号)改为二要素(姓名+身份证号)
- 使用阿里云 Id2MetaStandardVerify API
- 二要素验证直接调用真实API,不使用mock
- 保留三要素验证方法(verifyIdCardThreeFactor)备用

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 04:20:42 -08:00
hailin 47e4ef2b33 feat(android): add share export and import functionality
Add ability to backup wallet shares to files and restore from backups:

- Add ShareBackup data class in Models.kt for backup format
- Add exportShareBackup() and importShareBackup() in TssRepository
- Add export/import state and methods in MainViewModel
- Add file picker integration in MainActivity using ActivityResultContracts
- Add import FAB button in WalletsScreen
- Export saves as .tss-backup file with address and timestamp in filename
- Import validates backup format and checks for duplicate wallets

The backup file contains all necessary data to restore a wallet share:
sessionId, publicKey, encryptedShare, threshold, partyIndex, address.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 03:35:37 -08:00
hailin 9f33e375d0 fix(android): add @OptIn annotation for experimental FilterChip API
Add @OptIn(ExperimentalMaterial3Api::class) to TransferInputScreen
composable to fix compilation error for FilterChip and FilterChipDefaults.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 03:06:32 -08:00
hailin 9b9612bd5f feat(token): add Green Points (绿积分) ERC-20 token support
Add support for the dUSDT token "绿积分" (Green Points) on both Android
and Electron applications:

Android changes:
- Add TokenType enum and GreenPointsToken config in Models.kt
- Implement ERC-20 balance fetching and transfer encoding in TssRepository
- Update TransactionUtils with ERC-20 transfer support
- Add dual balance display (KAVA + 绿积分) in WalletsScreen
- Add token type selector in TransferScreen

Electron changes:
- Add TokenType and GREEN_POINTS_TOKEN config in transaction.ts
- Implement fetchGreenPointsBalance and ERC-20 transfer encoding
- Update Home.tsx with dual balance display and token selector
- Add token selector styles in Home.module.css

Token contract: 0xA9F3A35dBa8699c8C681D8db03F0c1A8CEB9D7c3 (Kava mainnet)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 03:01:05 -08:00
hailin b3822e48eb fix(android): decode Base64 signature before broadcasting transaction
The TSS native bridge returns signatures in Base64 format, but the
broadcast function expected hex format. Added Base64 decoding in
broadcastTransaction() to properly parse r, s, v components.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 22:44:44 -08:00
hailin 2365a50b1b feat(tss): add real-time round progress from msg.Type() parsing
Extract current round number from tss-lib message type string using
regex pattern `Round(\d+)`. This enables real-time progress updates
(1/4, 2/4... for keygen, 1/9, 2/9... for signing) instead of only
showing completion status.

Changes across all three platforms:
- tss-wasm/main.go: Add extractRoundFromMessageType() and call
  OnProgress with parsed round on each outgoing message
- service-party-android/tsslib/tsslib.go: Same implementation for
  Android gomobile binding
- service-party-app/tss-party/main.go: Same implementation for
  Electron subprocess, with isKeygen parameter to distinguish
  keygen (4 rounds) vs signing (9 rounds)

Safe fallback: Returns 0 if parsing fails, which doesn't affect
protocol execution - only UI display.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 22:41:51 -08:00
hailin f8de55e671 fix(android): reset isLoading after signing completes to enable broadcast button
The broadcast button was disabled because isLoading remained true after
signing completed. Added isLoading = false reset in startSigningProcess
after waitForSignature succeeds or fails.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 22:21:30 -08:00
hailin 001f0ac480 fix(android): remove 0x prefix from messageHash before TSS sign
TSS native library expects pure hex string without 0x prefix.
Fix both startSigning (initiator) and executeSignAsJoiner (joiner) functions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 22:04:18 -08:00
hailin 0bd764e1d1 fix(android): ensure session event subscription active before creating sign session
Add ensureSessionEventSubscriptionActive() call at the start of createSignSession()
to prevent race condition where session_started event arrives before subscription
is ready. Also add debug logging for _signSessionId and pendingSignInitiatorInfo
in event callback to help diagnose sign initiator event matching issues.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 21:45:18 -08:00
hailin ecd7a2a2dc fix(android): clear pendingSessionId in resetSessionStatus to fix stale session matching
The resetSessionStatus() function was not clearing pendingSessionId,
causing events from new sessions to be ignored because pendingSessionId
still held the old session ID.

Added:
- Clear pendingSessionId = null in resetSessionStatus()
- Clear _currentSession.value = null in resetSessionStatus()
- Added debug logging for session state clearing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 21:11:24 -08:00
hailin e865153e8e fix(android): refresh session event subscription when joining sign session
The session event gRPC stream may silently disconnect without triggering
onError or onCompleted callbacks. This causes session_started events to
be lost, preventing the sign process from starting.

Changes:
- Add ensureSessionEventSubscriptionActive() to refresh event subscription
- Call it in joinSignSessionViaGrpc for sign joiner
- Call it in createSignSession for sign initiator after auto-join

This ensures a fresh event stream connection before waiting for events.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 20:47:13 -08:00
hailin da76037d04 fix(tss-wasm): correct signing rounds from 6 to 9
GG20 signing protocol has 9 rounds, not 6. This aligns WASM with
Electron (tss-party/main.go:717) and Android (tsslib/tsslib.go:477).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 20:24:47 -08:00
hailin 16e1e9159c fix(android): sign initiator event handling to match Electron flow
Changes:
- Add sign initiator handling for participant_joined events in MainViewModel
- Add sign initiator handling for session_started events in MainViewModel
- Add sign initiator handling for all_joined events in MainViewModel
- Set pendingSessionId in TssRepository.createSignSession for event matching
- Refactor initiateSignSession to wait for session_started instead of starting immediately
- Add PendingSignInitiatorInfo data class to store pending sign info
- Add sessionAlreadyInProgress flag to SignSessionResult for immediate trigger case

This fixes the issue where sign initiator couldn't detect when other parties
joined the signing session, making Android flow 100% consistent with Electron.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 20:14:32 -08:00
hailin fd56de5c00 fix(android): enable real-time progress updates for keygen/sign rounds
Connect TssNativeBridge.progress Flow to UI through:
- Add progressCallback in TssRepository with startProgressCollection/stopProgressCollection
- Subscribe to native bridge progress in keygen and sign methods
- Add setProgressCallback in MainViewModel to update appropriate round state
- Progress now flows: Go Native → TssNativeBridge → TssRepository → MainViewModel → UI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 19:45:31 -08:00
hailin 3576af0f25 feat(android): add 5-minute countdown timer UI for keygen/sign sessions
Displays remaining time during the 5-minute polling timeout:
- Shows countdown in CreateWalletScreen (SessionScreen)
- Shows countdown in JoinKeygenScreen (JoiningScreen, KeygenProgressScreen)
- Shows countdown in CoSignJoinScreen (JoiningScreen, SigningProgressScreen)
- Format: mm:ss with Timer icon in tertiary container card
- Countdown starts on all_joined event and stops on session start/cancel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 19:33:20 -08:00
hailin b30017f3a7 fix(android): prevent memory leaks from detached coroutine scopes
Critical fixes to prevent app crashes when screens are kept open for extended periods:

- Add repositoryScope with SupervisorJob for structured concurrency in TssRepository
- Replace detached CoroutineScope(Dispatchers.IO).launch with repositoryScope.launch:
  - Session event subscription (line 206)
  - Session status polling (line 291)
  - Message routing (line 1508)
- Add cleanup() method to properly cancel all jobs and repositoryScope
- Update disconnect() to also cancel sessionStatusPollingJob
- Update MainViewModel.onCleared() to call repository.cleanup()

This ensures all background coroutines are properly cancelled when the ViewModel
is cleared, preventing memory accumulation over time.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 19:05:13 -08:00
hailin fc86af918f feat(android): add 5-minute polling timeout mechanism for keygen/sign
Implements Electron's checkAndTriggerKeygen() polling fallback:
- Adds polling every 2 seconds with 5-minute timeout
- Triggers keygen/sign via synthetic session_started event on in_progress status
- Handles gRPC stream disconnection when app goes to background
- Shows timeout error in UI via existing error mechanism

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 18:51:02 -08:00
hailin ad79679ee2 feat(ui): add QR code display for invite code in signing session
Android TransferScreen:
- Add QR code display above invite code text
- Import QRCodeWriter and related components
- Add generateInviteQRCode helper function
- Update hint text to mention scanning

Electron CoSignSession:
- Import QRCodeSVG from qrcode.react
- Add QR code above invite code text with proper styling
- Center QR code and update hint text

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 18:37:18 -08:00
hailin ed55be2b86 fix(android): transfer flow improvements and message_hash format fix
Transfer Screen improvements:
- Add QR code scanning for recipient address (using zxing library)
- Support EIP-681 URI format (ethereum:0x...) and plain address
- Remove password requirement - TSS wallets don't need passwords
- Remove unused onScanQrCode callback parameter

WalletsScreen changes:
- Simplify onTransfer callback to only pass shareId
- Remove TransferDialog - now navigates directly to TransferScreen
- Remove unused state variables (showTransferDialog, transferWallet)

Bug fix:
- Remove 0x prefix from message_hash before sending to API
- Backend expects pure hex, not 0x-prefixed hex string

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 18:18:29 -08:00
hailin 480251b85f fix(android): remove incorrect Participant import
Participant class is already imported via domain.model.* wildcard import,
no need for separate import from data.repository (where it doesn't exist).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 11:03:31 -08:00
hailin ee1cfe082d fix(android): resolve compilation errors for walletName and Participant
- TssRepository: Use address-based wallet name since ShareRecordEntity
  doesn't have wallet_name field (unlike Electron's ShareRecord)
- MainViewModel: Add missing Participant import and simplify type reference

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 11:00:36 -08:00
hailin 04eeadf7a7 fix(android): co-sign flow consistency with Electron + state reset
Changes:
- Fix Android state not resetting after successful keygen/join
  - Add resetSessionStatus() method in TssRepository
  - Call reset on success navigation in MainActivity

- Make Android co-sign flow 100% consistent with Electron:
  - Get keygen session status for participants list
  - Filter out co-managed-party-* (server backup parties)
  - Auto-join via gRPC after creating sign session
  - Start message routing BEFORE signing (prepareForSign)
  - Use gRPC response partyIndex instead of local share
  - Use original keygen thresholdN instead of signingParties.size
  - Pass parties list in join sign flow

- Update SignSessionInfoResponse to include parties array
- Update validateSignInviteCode to parse parties from API response

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 10:52:13 -08:00
hailin 7346b3518a fix(electron): auto-navigate to home after keygen completion
Previously, after keygen completed, the Session page would just update the
status to 'completed' but not navigate away. Users had to manually click
the "Return Home" button. This could result in a white screen if the button
wasn't visible or clickable.

Now the page auto-navigates to home after 2 seconds, giving users time to
see the completion status and public key before redirecting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 10:22:42 -08:00
hailin 549b21f298 fix(message-router): prevent subscription race condition on gRPC reconnect
When a party re-subscribes (e.g., Android reconnects), the old gRPC stream's
defer Unsubscribe() was accidentally removing the NEW subscription from the
subscribers map, causing the party to miss session_started events.

Fix:
- Subscribe() now returns the channel to the caller
- Unsubscribe() now takes the channel and only removes if it matches
- This prevents older streams from removing newer subscriptions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 10:04:11 -08:00
hailin b7fc488dcf feat(android): add persistent partyId storage matching Electron behavior
- Add AppSettingEntity and AppSettingDao to Database.kt for key-value storage
- Add database migration (version 1 → 2) to create app_settings table
- Modify TssRepository.registerParty() to load/create partyId from database
- PartyId is now persisted across app restarts, matching Electron's getOrCreatePartyId()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 09:50:30 -08:00
hailin e2451874ea fix(android): add logging for session event subscription debugging
- Add warning log when parties miss session event broadcast (message-router)
- Add logging for subscribeSessionEvents to detect null asyncStub
- Add sessionStatusPollingJob field for future fallback polling mechanism

This helps diagnose why Android parties are not receiving session_started events.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 09:23:38 -08:00
hailin eb3f71fa2e fix(android): fix session_started event race condition with pendingSessionId
Problem:
- Android initiator/joiner could miss session_started events due to race condition
- Events arriving between joinSession() and _currentSession.value assignment were ignored
- This caused keygen timeout because parties never started the TSS protocol

Solution:
- Add pendingSessionId field set BEFORE joinSession() call
- Modify startSessionEventSubscription() to match events against both activeSession and pendingSessionId
- Clear pendingSessionId on session completion, failure, or cancellation

This ensures session_started events are correctly processed even if they arrive
before _currentSession is fully initialized.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 09:09:52 -08:00
hailin cc56b8fadf fix(android): fetch session status after creation to show all participants
- Add getPartyId() method to TssRepository
- Call getSessionStatus after createKeygenSession to fetch all participants
  including server-party-co-managed that have already auto-joined
- This matches Electron's behavior of calling getSessionStatus on session page

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 08:51:49 -08:00
hailin f305a8cd97 feat(session): broadcast participant_joined event via gRPC for real-time UI updates
Backend changes (session-coordinator):
- Add PublishParticipantJoined method to JoinSessionMessageRouterClient interface
- Implement PublishParticipantJoined in MessageRouterClient to broadcast events
- Call PublishParticipantJoined in join_session.go after participant joins
- Add detailed logging for debugging event broadcast

Android changes (service-party-android):
- Add detailed logging in TssRepository for session event handling
- Add detailed logging in MainViewModel for participant_joined processing
- Log activeSession state, event matching, and participant updates

This enables the initiator's waiting screen to receive real-time updates
when participants join the session, matching the expected behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 08:34:47 -08:00
hailin 13d1e58b84 fix(android): change QR scanner to portrait orientation
- Created PortraitCaptureActivity that extends CaptureActivity
- Registered it in AndroidManifest.xml with screenOrientation="portrait"
- Updated JoinKeygenScreen and CoSignJoinScreen to use the portrait activity
- Also simplified keygen join logic to match Electron exactly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 08:16:41 -08:00
hailin d90c722c7d fix(android): simplify keygen join to match Electron behavior exactly
Removed polling fallback and simplified to match Electron's design:
- If joinSession returns sessionStatus="in_progress", trigger keygen immediately
- Otherwise wait for session_started gRPC event

Added debug log to show sessionStatus value for troubleshooting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 08:14:26 -08:00
hailin 50cb10d6a8 fix(android): improve session_started polling with multiple attempts
Changed from single 2-second delay to 5 attempts at 500ms intervals.
This provides faster detection while covering a longer window (2.5 seconds total).

The polling loop:
- Checks every 500ms for up to 5 times
- Stops immediately if keygen is already triggered
- Stops if session context changes (user cancelled/navigated away)

This handles the case where the last joiner triggers session_started
but cannot receive the event themselves.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 08:10:15 -08:00
hailin c3d5da46f7 fix(android): add polling fallback for session_started race condition
When multiple Android devices join a keygen session nearly simultaneously,
the last joiner may miss the session_started gRPC event because it's sent
before the device has fully set up its event subscription.

This fix adds a 2-second delayed polling check after join to detect if
the session has already started. If the session is in_progress and we
haven't started keygen yet, trigger it via polling instead of relying
solely on the session_started event.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 08:08:18 -08:00
hailin 136a5ba851 fix(android): change address format from Cosmos to EVM and fix balance query
Changes:
- Change address derivation from deriveKavaAddress to deriveEvmAddress
  in TssRepository.kt (3 locations)
- Add AddressUtils.isEvmAddress() and getEvmAddress() helper methods
  to handle both old Cosmos and new EVM address formats
- Fix balance query for old wallets by deriving EVM address from
  public key when needed (MainViewModel.fetchBalanceForShare)
- Add retry logic for optimistic lock conflicts in join_session.go
  to prevent party_index collision during concurrent joins

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 07:48:52 -08:00
hailin 444b720f8d feat(android): strengthen gRPC connection reliability
Major improvements to Android gRPC client:
- Add automatic reconnection with exponential backoff (1s to 30s)
- Add heartbeat mechanism with failure detection (30s interval, 3 failures trigger reconnect)
- Add stream version tracking to filter stale callbacks
- Add channel state monitoring (every 5s)
- Add per-call deadline instead of one-time deadline for stubs
- Add SharedFlow for connection events (Connected, Disconnected, Reconnecting, Reconnected, PendingMessages)
- Add callback exception handling for robustness
- Add stream recovery after reconnection via callback mechanism

TssRepository changes:
- Save message routing params for recovery after reconnect
- Expose grpcConnectionEvents SharedFlow for UI notifications
- Auto-restore event subscriptions after reconnection

Other changes:
- Add QR code to Electron Create page for mobile scanning
- Auto version increment from version.properties
- SettingsScreen shows BuildConfig version info
- CreateWalletScreen tracks hasEnteredSession state

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 06:44:42 -08:00
hailin a3ee831193 fix(android): remove device_info from joinSession to match Electron behavior
The server validates device_type and only accepts specific values.
Electron doesn't send device_info at all, which passes validation.
Match that behavior for consistency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 05:26:45 -08:00
hailin 06e374e747 fix(android): use TLS for gRPC connections on port 443
The app was crashing with FRAME_SIZE_ERROR because the gRPC client
was using plaintext mode when connecting to port 443 (TLS endpoint).
This caused the client to receive encrypted data that it couldn't parse.

Fix: Use useTransportSecurity() for port 443, usePlaintext() for other ports.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 05:07:36 -08:00
hailin d8be40b8b0 feat(android): update theme to dark gray & gold, fix JoinKeygen/CoSign flows
Theme changes:
- Replace green theme with dark gray & gold color scheme
- Primary color: Gold (#D4AF37)
- Background: Dark gray (#1A1A1A)
- Surface: Medium gray (#2D2D2D)
- Disable dynamic colors to enforce custom theme
- Default to dark theme for best visual impact
- Update success indicators from green to gold across screens

JoinKeygen flow fixes (100% Electron compatible):
- Add onResetState callback for proper state reset
- Cancel in confirm/joining/progress resets to input state (stays on page)
- Two-step flow: joinKeygenSessionViaGrpc + executeKeygenAsJoiner
- Wait for session_started event before executing keygen

CoSign flow fixes (100% Electron compatible):
- Add onResetState callback and QR scanner support
- Add three-button layout (Cancel, Back, Join) in select_share step
- Two-step flow: joinSignSessionViaGrpc + executeSignAsJoiner
- If session already in_progress, trigger sign immediately (Solution B)
- Wait for session_started event otherwise

Repository changes:
- Add joinKeygenSessionViaGrpc and executeKeygenAsJoiner methods
- Add joinSignSessionViaGrpc and executeSignAsJoiner methods
- Add JoinKeygenViaGrpcResult and JoinSignViaGrpcResult data classes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 04:35:00 -08:00
hailin 2b0920f9b1 fix(android): add copy feedback and explorer link to wallet detail
Matching Electron app functionality:

1. Copy address button:
   - Shows "✓ 已复制" feedback after copying
   - Auto-resets after 2 seconds

2. Explorer link button (new):
   - Opens address in Kava block explorer
   - Uses correct URL based on network type:
     - Mainnet: kavascan.com
     - Testnet: testnet.kavascan.com

Changes:
- WalletsScreen: Added networkType parameter
- WalletDetailDialog: Added copy feedback state and explorer button
- MainActivity: Pass networkType to WalletsScreen

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 03:32:33 -08:00
hailin f7de1e8d09 fix(electron): fix wallet detail modal buttons
1. Copy address button:
   - Changed from alert() to visual feedback (shows "✓ 已复制")
   - Feedback auto-hides after 2 seconds

2. Explorer link button:
   - Was hardcoded to testnet (true)
   - Now uses getCurrentNetwork() to determine correct explorer URL
   - Links to kavascan.com for mainnet, testnet.kavascan.com for testnet

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 03:29:54 -08:00
hailin 77bbb43eb5 fix(electron): sync network status display with Settings in real-time
Previously, the network badge (testnet/mainnet) in Layout sidebar only
loaded once on mount and didn't update when user changed network in
Settings page.

Changes:
- Layout.tsx: Read network from localStorage first (consistent with
  transaction.ts), then fallback to Electron API
- Layout.tsx: Listen for 'storage' event (cross-tab) and custom
  'kava-network-change' event (same-tab) to update display
- Settings.tsx: Dispatch custom event when switching networks so
  Layout can update immediately

Android app doesn't have this issue - it uses StateFlow which
automatically triggers re-renders when settings change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 03:24:19 -08:00
hailin fd5f4d10ed fix(transfer): MAX button now deducts gas fee from balance
Both Electron and Android apps now calculate the maximum transferable
amount by subtracting estimated gas fees from the balance:

Electron (Home.tsx):
- Added calculateMaxAmount() async function that fetches gas price
- Uses 21000 gas limit for simple transfers
- Shows loading state while calculating

Android (TransferScreen.kt):
- Added calculateMaxTransferAmount() in TransactionUtils
- Uses coroutine to fetch gas price asynchronously
- Shows "..." while calculating, falls back to balance on error

Both implementations:
- Add 10% buffer to gas price for safety
- Round down to 6 decimal places
- Show error if balance insufficient for gas

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 03:08:01 -08:00
hailin 7f66ed0ebe fix(electron): sync network setting to localStorage when switching networks
The network toggle in Settings was saving to database via electron API
but getCurrentNetwork() in transaction.ts reads from localStorage.
This caused the balance display to use wrong RPC endpoint after switching.

Now syncs to localStorage when switching networks to ensure consistency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 02:58:02 -08:00
hailin 5f484f6579 fix(electron): use dynamic network config for balance queries
Previously Home.tsx hardcoded testnet RPC for balance queries.
Now uses getCurrentRpcUrl() to respect user's network setting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 02:46:39 -08:00
hailin c239ac65ee fix(android): simplify build-apk.bat with official gomobile setup
Key changes:
- Add `go get -d golang.org/x/mobile/cmd/gomobile` step (official recommended)
- This adds golang.org/x/mobile dependency to go.mod, fixing "unable to import bind" error
- Remove complex Go 1.22 version detection logic (no longer needed)
- Simplify gomobile installation flow
- Update tsslib/go.mod with proper golang.org/x/mobile dependency

The fix follows the official Go Mobile documentation:
https://go.dev/wiki/Mobile

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 00:53:04 -08:00
hailin 543bee6d26 fix(android): add -androidapi 21 flag to gomobile bind
This ensures compatibility with modern NDK versions that don't
support older Android API levels. API 21 (Android 5.0) is the
minimum supported by current NDK versions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 00:14:52 -08:00
hailin 9a4dd9729c fix(android): correct tsslib path in build-apk.bat
The tsslib source code is located in service-party-android/tsslib/,
not in libs/tsslib/. Updated the path and output location accordingly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 23:59:43 -08:00
hailin d5325efa2a fix(android): properly handle GOPATH/bin for gomobile in build-apk.bat
Changes:
- Get GOPATH using 'go env GOPATH' command
- Add GOPATH/bin to PATH if not already present
- Check for gomobile.exe directly in GOBIN directory
- Use full path to gomobile.exe for init and bind commands
- Add verification that gomobile was installed correctly

This fixes the issue where gomobile is installed but not found
because GOPATH/bin is not in the system PATH.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 23:57:04 -08:00
hailin 131c14742c feat(android): auto-build tsslib.aar if missing in build-apk.bat
When tsslib.aar is not found, the build script now automatically:
1. Checks if Go is installed
2. Installs gomobile if not present (go install golang.org/x/mobile/cmd/gomobile@latest)
3. Initializes gomobile if needed
4. Runs go mod tidy in the tsslib directory
5. Builds tsslib.aar using gomobile bind

This allows building APKs on any machine with Go installed, without
needing to manually compile the TSS library first.

Requirements:
- Go installed and in PATH
- Android NDK (installed via Android SDK)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 23:48:55 -08:00
hailin 8541c83bf5 fix(android): remove quotes from ANDROID_HOME path in build-apk.bat
When ANDROID_HOME environment variable contains quotes (e.g., set with
quotes in system settings), the generated local.properties file would
have an invalid path like 'sdk.dir=C:/Android"'.

This fix strips any surrounding quotes from ANDROID_HOME before using
it to create local.properties, ensuring valid SDK path format.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 23:43:51 -08:00
hailin c5f52190ef feat(android): add Android SDK auto-detection to build-apk.bat
When local.properties is missing, the build script now automatically:
- Checks ANDROID_HOME environment variable first
- Scans common Windows SDK locations:
  - %LOCALAPPDATA%\Android\Sdk
  - %USERPROFILE%\AppData\Local\Android\Sdk
  - C:\Android\Sdk
  - C:\Android
- Creates local.properties with the detected SDK path
- Displays helpful error message if SDK is not found

This allows the build script to work on machines without manual
configuration, making it easier to build APKs on different systems.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 23:42:06 -08:00
hailin 4d62316d17 feat(android): add build-apk.bat script for easy APK building
Add Windows batch script for building Android APKs:
- build-apk.bat debug   - Build debug APK only
- build-apk.bat release - Build release APK only
- build-apk.bat         - Build both debug and release APKs
- build-apk.bat clean   - Clean build files
- build-apk.bat help    - Show usage help

Output locations:
- Debug: app/build/outputs/apk/debug/app-debug.apk
- Release: app/build/outputs/apk/release/app-release.apk

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 23:35:28 -08:00
hailin 7b6d6de801 feat(android): add Android TSS Party app with full API implementation
Major changes:
- Add complete Android app (service-party-android) with Jetpack Compose UI
- Implement real account-service API calls for keygen and sign sessions:
  - POST /api/v1/co-managed/sessions (create keygen session)
  - GET /api/v1/co-managed/sessions/by-invite-code/{code} (validate invite)
  - POST /api/v1/co-managed/sessions/{id}/join (join keygen session)
  - POST /api/v1/co-managed/sign (create sign session)
  - GET /api/v1/co-managed/sign/by-invite-code/{code} (validate sign invite)
  - POST /api/v1/co-managed/sign/{id}/join (join sign session)
- Add QR code generation and scanning for session invites
- Remove password requirement (use empty string)
- Add floating action button for wallet creation
- Add network type aware explorer links (mainnet/testnet)

Network configuration:
- Change default network to Kava mainnet for both Electron and Android apps
- Electron: main.ts, transaction.ts, Settings.tsx, Layout.tsx
- Android: Models.kt (NetworkType.MAINNET default)

Features:
- Full TSS keygen and sign protocol via gomobile bindings
- gRPC message routing for multi-party communication
- Cross-platform compatibility with service-party-app (Electron)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 23:27:29 -08:00
hailin ff995a827b fix(grpc-client): add connection check and better error handling in subscribeMessages
Additional safeguards to prevent "CANCELLED: Cancelled on client" error:

1. Add `this.connected` check at the start of subscribeMessages()
2. Set messageStream to null after canceling old stream
3. Wrap new stream creation in try-catch to handle creation errors
4. Add logging for ignored cancel errors

These changes ensure that:
- subscribeMessages won't proceed if connection is lost
- Old stream is fully cleaned up before creating new one
- Errors during stream creation are properly caught and logged

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 13:09:45 -08:00
hailin 66a718ea72 fix(electron): properly cleanup gRPC message stream after keygen/sign
Root cause: After keygen/sign completion, the gRPC message stream was not
unsubscribed. On the second operation, prepareForSign/prepareForKeygen
would try to cancel the stale stream, causing "CANCELLED: Cancelled on client".

Changes in tss-handler.ts:
- Add grpcClient.unsubscribeMessages() in all cleanup paths:
  - participateKeygen close handler
  - participateKeygen error handler
  - participateSign close handler
  - participateSign error handler
  - cancel() method
- Reset sessionId and partyId in all cleanup paths

Changes in main.ts:
- Add reconnection logic in app 'activate' event for macOS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 13:07:43 -08:00
hailin d051178801 fix(electron): add gRPC connection check before subscribing to messages
The app was crashing with "CANCELLED: Cancelled on client" error when
opening the app a second time. This happened because:

1. When window was reopened, old gRPC streams were in cancelled state
2. prepareForSign/prepareForKeygen tried to subscribe on cancelled streams
3. The error was unhandled and crashed the app

Changes:
- Add isConnected() check in prepareForSign() and prepareForKeygen()
- Throw meaningful error when gRPC client is not connected
- Wrap all prepareFor* calls in try-catch in main.ts
- Return user-friendly error message instead of crashing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 13:04:19 -08:00
hailin c0229a1139 fix(transaction): use eth_gasPrice RPC for Legacy transaction gas estimation
- Changed getGasPrice() to use eth_gasPrice RPC method instead of calculating
  from baseFeePerGas (which is for EIP-1559 transactions)
- Added 10% buffer to gas price to ensure transaction gets included
- Updated Home.tsx to use gasPrice instead of maxFeePerGas for display

KAVA doesn't support EIP-1559, so we must use Legacy (Type 0) transactions
with gasPrice from eth_gasPrice RPC.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 12:48:41 -08:00
hailin 0f8e9cf228 fix(transaction): use Legacy (Type 0) transaction format for KAVA
KAVA EVM does not support EIP-1559 dynamic fee transactions.
Changed from EIP-1559 (Type 2) to Legacy (Type 0) format:

- prepareTransaction: Use [nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]
- finalizeTransaction: Use EIP-155 v calculation (chainId * 2 + 35 + recoveryId)
- Remove type prefix (0x02) as Legacy transactions don't need it
- Update Home.tsx and CoSignSession.tsx to use gasPrice instead of maxFeePerGas

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 12:45:38 -08:00
hailin d18733deb1 fix(tss-party): include recovery ID in signature output for EVM transactions
The signature was 64 bytes (r + s) but EVM transactions need 65 bytes (r + s + v).
Now the recovery ID is appended to the signature so the frontend can correctly
parse and broadcast the transaction.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 12:37:53 -08:00
hailin b5512d421c fix(tss): convert threshold to tss-lib format (threshold-1) in all keygen and signing
TSS-lib convention: threshold=t means (t+1) signers required.
User expectation: "2-of-3" means 2 signers needed.

Before this fix:
- Keygen used thresholdT directly (e.g., 2)
- TSS-lib interpreted as needing 3 signers (2+1)
- 2-of-3 wallet was actually 3-of-3!

After this fix:
- Both keygen and signing use (thresholdT-1)
- For 2-of-3: tss-lib threshold=1, needs 1+1=2 signers ✓

Files changed:
- tss-party/main.go: keygen and signing both use thresholdT-1
- tss-wasm/main.go: keygen and signing both use thresholdT-1
- pkg/tss/keygen.go: uses config.Threshold-1
- pkg/tss/signing.go: uses config.Threshold-1

BREAKING CHANGE: Existing wallets created before this fix used wrong
threshold and need to be regenerated. New wallets will work correctly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 12:19:58 -08:00
hailin 51c0f59924 fix(tss): remove threshold-1 in signing to match keygen exactly
The signing code was using thresholdT-1 while keygen was using thresholdT,
causing Lagrange coefficient mismatch and "U doesn't equal T" error in round 9.

Root cause: commit d0c504dc added -1 to signing threshold to "match user expectation",
but this broke the keygen/sign consistency that TSS-lib requires.

Changes:
- tss-party/main.go: Sign now uses thresholdT (same as keygen)
- pkg/tss/signing.go: Add logging, emphasize threshold must match keygen
- tss-wasm/main.go: Add comment about threshold consistency

NOTE: This fix maintains backward compatibility with existing wallets.
No wallet regeneration is needed.

ROLLBACK: If this causes issues, revert to commit before this one.
Previous signing threshold was thresholdT-1 (commit d0c504dc).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 12:08:40 -08:00
hailin 4a00c8066a fix(tss-party): fix debug logging slice bounds error
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 11:46:46 -08:00
hailin 7a82a56ae5 debug(tss-party): add detailed key matching logs
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 11:41:41 -08:00
hailin 3564f30f27 debug(tss-party): add logging for BuildLocalSaveDataSubset
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 11:35:56 -08:00
hailin 7ab28dced0 fix(tss): use BuildLocalSaveDataSubset for threshold signing with party subsets
When signing with fewer parties than keygen (e.g., 2-of-3 signing with only 2 parties),
the TSS-lib requires filtered save data containing only the participating parties.

Without this fix, signing fails with "U doesn't equal T" error because:
- Keygen creates save data for all N parties (e.g., 3 parties with indices 0, 1, 2)
- Sign uses only T parties (e.g., 2 parties with indices 1, 2)
- TSS-lib internal index validation fails due to mismatch

Changes:
- pkg/tss/signing.go: Use len(sortedPartyIDs) for partyCount and call BuildLocalSaveDataSubset
- tss-party/main.go: Add BuildLocalSaveDataSubset call for Electron app
- tss-wasm/main.go: Add BuildLocalSaveDataSubset call for WASM builds

This fix is backward compatible - when all parties participate, the subset equals the original data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 11:25:22 -08:00
hailin 24ff1409d0 Revert "fix(co-keygen): convert threshold at storage time to match tss-lib convention"
This reverts commit 4dcc7d37ba.
2025-12-31 10:24:25 -08:00
hailin 4dcc7d37ba fix(co-keygen): convert threshold at storage time to match tss-lib convention
User says "3-of-5" meaning 3 signers needed.
tss-lib threshold t means t+1 signers required.
Now we store t-1 at session creation (like persistent-only does).

Changes:
- co_managed_handler.go: tssThresholdT = req.ThresholdT - 1
- tss-party/main.go: remove -1 from sign (now consistent with keygen)

BREAKING: Existing co-managed wallets must be regenerated.
ROLLBACK: Revert this commit if signing still fails.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 10:14:21 -08:00
hailin b876c9dfba fix(co-sign): use actual signer count instead of keygen N in NewParameters
The tss.NewParameters() expects the party count to match the number of
parties in peerCtx. For signing, this should be len(sortedPartyIDs)
(actual signing participants), not thresholdN (original keygen parties).

This fixes the "U doesn't equal T" error in round 9 when doing 3-of-5
co-managed signing with parties at indices 2,3,4.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 09:06:59 -08:00
hailin b231667aba fix(grpc): prevent stream race condition from triggering reconnection
When switching message/event streams, the old stream's 'end' or 'error'
events could fire after the new stream was created. Since activeMessageSubscription
was already updated to the new session, the old stream's events would
incorrectly trigger reconnection, causing TSS message routing to fail.

Fix:
- Remove event listeners from old stream before canceling
- Use closure to capture current stream reference
- Check if event is from current active stream before triggering reconnect

This fixes the "Not connected" error during co-sign TSS message routing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 08:57:44 -08:00
hailin 1708a03aaf fix(session): distinguish keygen vs sign in CanStart() and AllPartiesReady()
- Keygen/co-keygen: must have exactly N participants joined
- Sign (co-sign/persistent): only check all registered participants joined

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 08:34:40 -08:00
hailin d0c504dcf3 fix(co-sign): adjust threshold for tss-lib (t-1) to match user expectation
User says 3-of-5 meaning 3 signers needed, but tss-lib threshold t means t+1 signers.
Pass thresholdT-1 so tss-lib needs (t-1)+1 = t signers, matching user expectation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 08:19:27 -08:00
hailin 54121fa494 revert: undo incorrect threshold conversion that broke keygen
Reverts e81757ad - the threshold conversion was wrong.
Keygen works with original thresholdT/thresholdN parameters.
The signing issue needs a different fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 07:58:36 -08:00
hailin e81757ad83 fix(co-sign): convert user-friendly threshold to tss-lib format
- Rename thresholdT/thresholdN to requiredSigners/totalParties in Create.tsx
- Add parameter conversion in main.ts: threshold_t = requiredSigners - 1
- In tss-lib, threshold t means t+1 parties needed to sign
- For 3-of-5: requiredSigners=3 → threshold_t=2 (t+1=3 signers)
- externalCount = requiredSigners (user parties)
- persistentCount = totalParties - requiredSigners (server parties)
- Backward compatible with legacy thresholdT/thresholdN format

BREAKING: Existing co-managed wallets need re-keygen with new params

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 07:44:17 -08:00
hailin ca69ebc839 fix(co-sign): use keygen N and T for TSS signing parameters
The TSS signing was failing with "U doesn't equal T" error because
tss-party was passing incorrect parameters to tss.NewParameters():
- Was: len(sortedPartyIDs)=3 (signing participants), thresholdT-1=2
- Now: thresholdN=5 (keygen N), thresholdT=3 (keygen T)

This matches how pkg/tss/signing.go creates parameters in server-party,
which uses TotalParties=N and Threshold=T from the original keygen.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 07:01:59 -08:00
hailin 5ebdd4d592 fix(co-sign): add threshold_n to CreateSignSession API response
Add keygenThresholdN to the CreateSignSession response so frontend
can access the original N value from keygen session. This is required
for proper TSS operation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 06:36:38 -08:00
hailin 75b15acda2 docs: add BREAKING CHANGE warnings for co-sign modifications
Add detailed comments to warn about changes that affect persistent sign flow:
- session_coordinator.go: ValidateSessionCreation now allows T <= count <= N for sign
- mpc_session.go: CanStart/AllPartiesReady now check registered participants, not N
- session_coordinator_client.go: ThresholdN now uses keygenThresholdN instead of len(parties)

Each comment includes:
- Original code behavior
- New code behavior
- How to revert if persistent sign breaks
- Related files list

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 06:23:39 -08:00
hailin 94ab63db30 fix(co-sign): allow T to N participants for sign sessions
- Change ValidateSessionCreation to accept T <= participantCount <= N for sign sessions
- Co-managed sign uses exactly T parties
- Persistent sign uses T+1 parties
- Both now pass validation with correct keygenThresholdN

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 06:19:57 -08:00
hailin 99fa003b12 fix(co-sign): fix session start logic to check all registered participants
- CanStart(): Check if all registered participants have joined, not based on T/N
- AddParticipant(): Keep N as max limit (API handles T vs T+1 validation)
- AllPartiesReady(): Check all registered participants, not based on T/N
- This approach works for both co-managed (T parties) and persistent (T+1 parties) signing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 06:09:14 -08:00
hailin a09e163704 fix(co-sign): fix CanStart() to check T parties for sign sessions
- For keygen sessions: require all N parties to join before starting
- For sign sessions: require only T parties to join before starting
- This fixes session_started event not being triggered for signing sessions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 06:01:40 -08:00
hailin 2a95dd107f fix(co-sign): allow signing sessions with t participants instead of n
- Modify ValidateSessionCreation to differentiate between keygen and sign sessions
- For keygen: require participantCount == threshold.N() (all parties must participate)
- For sign: require participantCount == threshold.T() (only t parties needed)
- This fixes "session is full" error when creating signing session with 3 parties but n=5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 05:45:05 -08:00
hailin 042212eae6 fix(co-sign): use keygen session threshold_n for TSS signing
- Query keygen session from mpc_sessions table to get correct threshold_n
- Pass keygenThresholdN to CreateSigningSessionAuto instead of len(parties)
- Return parties list and correct threshold values in GetSignSessionByInviteCode
- This fixes TSS signing failure "U doesn't equal T" caused by mismatched n values

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 05:31:02 -08:00
hailin e284a46e83 fix(co-sign): pass complete parties list to joinSession
Problem: Participants joining early only got incomplete participant list
from other_parties (only those who had joined), causing partyIndex mismatch.

Solution:
- Add parties field to SessionInfo (from validateInviteCode response)
- Pass parties to joinSession call from frontend
- Backend joinSession uses params.parties (complete list) instead of
  result.other_parties (incomplete list)
- Add debug logging to track participant list state

Now all participants have the complete parties list with correct partyIndex.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 04:34:48 -08:00
hailin 8193549aba fix(co-sign): update participants list from session_started event
- Add logic in handleCoSignStart to update participants from event.selectedParties
- Fix initiator immediate trigger to use other_parties + self instead of incomplete participants list
- Add debug logging for participant list updates
- Ensures all parties have correct participant list before TSS signing starts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 04:13:29 -08:00
hailin 742419c0bf fix(layout): change sidebar sign link to new CoSignJoin page
Change /sign to /cosign/join so participants use the correct page
with auto-join functionality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 03:53:51 -08:00
hailin da189ca3d4 feat(co-sign): add debug logs for auto-join flow in CoSignJoin
Add console.log statements to trace the auto-join logic:
- Log loaded shares with sessionId
- Log auto-select share matching check
- Log auto-join conditions and share match status
- Log validateInviteCode results including joinToken
- Log handleJoinSession parameters

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 03:33:10 -08:00
hailin cd63643ba4 fix(account): exclude failed sessions when looking up sign session by invite code
When multiple sign sessions share the same invite code (due to retries),
the query now:
1. Excludes failed sessions (status != 'failed')
2. Orders by created_at DESC to get the most recent session
3. Limits to 1 result

This prevents participants from seeing an old failed session's status
when they look up the invite code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 02:09:18 -08:00
hailin 138650d943 fix(sign): use threshold_n from API response instead of parties.length
The validateSigningSession handler was using parties.length for threshold.n
which returned 0 when parties array was empty. Now correctly uses the
threshold_n value returned from the backend API.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 02:07:05 -08:00
hailin 9f898ccf44 fix(sign): remove password validation check in handleJoinSigning
Password is optional - remove the validation that required password
to be non-empty before joining a sign session.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 01:58:29 -08:00
hailin 227d04bde3 fix(sign): make password optional for joining sign session
Password field was required to enable the join button, but password
is optional when the share was created without encryption.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 01:49:56 -08:00
hailin c1e32a8c04 fix(co-sign): fix threshold_n display and add missing fields in GetSignSessionByInviteCode
- Add threshold_n to GetSignSessionByInviteCodeResponse interface
- Fix main.ts to use result.threshold_n instead of result.parties?.length
- Add message_hash, joined_count, join_token to GetSignSessionByInviteCode response
- Generate join token for sign session lookup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 01:47:08 -08:00
hailin 4d65b8dd83 feat(co-sign): add invite code display in CoSignSession page
- Add invite_code retrieval in GetSignSessionStatus (backend)
- Add inviteCode to cosign:getSessionStatus response (frontend IPC)
- Add inviteCode to SessionState and display UI in CoSignSession

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 01:37:11 -08:00
hailin cfbda7bbc7 fix(co-sign): validate exactly t parties for t-of-n signing
For threshold signing, exactly t parties are required:
- 3-of-5 → 3 parties
- 2-of-3 → 2 parties
- 4-of-7 → 4 parties

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 01:21:12 -08:00
hailin ebbc483b35 fix(co-sign): use keygen session participants with correct party_index for signing
- Fetch keygen session status from backend to get accurate party_index
- Filter out co-managed-party-* (server persistent parties) from signing
- Only temporary/external user parties participate in signing
- For 3-of-5 wallet: 3 user parties sign, 2 co-managed parties are backup only

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 01:12:07 -08:00
hailin 4089b9da6c fix(service-party-app): use API response for co-sign session status display
- Use API's participants field instead of parties
- Use API's threshold_t and threshold_n instead of activeCoSignSession
- Show participant status from API response
- Update GetSignSessionStatusResponse interface

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 20:17:14 -08:00
hailin c1e749e532 fix(co-sign): return join_tokens map for initiator auto-join
- Add join_tokens (map[partyID]token) to CreateSignSession response
- Keep join_token for backward compatibility
- Update frontend to use join_tokens[partyId] for initiator auto-join

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 19:54:20 -08:00
hailin cd1d2cf8d2 feat(account): add GET /sign/:sessionId endpoint for co-sign session status
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 19:41:05 -08:00
hailin b688b0176e fix(service-party-app): serialize BigInt to string for sessionStorage
BigInt cannot be serialized by JSON.stringify. Convert gasLimit,
maxFeePerGas, maxPriorityFeePerGas, and value to strings before
storing in sessionStorage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 19:16:38 -08:00
hailin 879fc3a816 feat(service-party-app): add transfer functionality with co-sign integration
Add complete KAVA transfer feature to the wallet home page:

Frontend (React):
- Home.tsx: Add transfer modal with address/amount input, transaction
  confirmation, and co-sign session initiation
- Home.module.css: Transfer modal styles (form, confirm, error states)
- CoSignSession.tsx: Add transaction broadcast after signing completion,
  with block explorer link

Utils:
- transaction.ts: EIP-1559 transaction building, RLP encoding, Keccak-256
  hashing, nonce/gas fetching, transaction broadcast via JSON-RPC

Flow: Wallet -> Transfer Modal -> Prepare TX -> Confirm -> Co-Sign ->
      Sign Session -> Broadcast -> Block Explorer

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 19:08:03 -08:00
hailin ebea74e57b feat(service-party-app): implement co-sign multi-party signing
Add complete co-sign functionality for multi-party transaction signing:

Frontend (React):
- CoSignCreate.tsx: Create signing session with share selection
- CoSignJoin.tsx: Join signing session via invite code
- CoSignSession.tsx: Monitor signing progress and results
- Add routes in App.tsx for new pages

Backend (Electron):
- main.ts: Add IPC handlers for co-sign operations
- tss-handler.ts: Add participateSign() for TSS signing
- preload.ts: Expose cosign API to renderer
- account-client.ts: Add sign session API types

TSS Party (Go):
- main.go: Implement 'sign' command for GG20 signing protocol
- integration_test.go: Add comprehensive tests for signing flow

Infrastructure:
- docker-compose.windows.yml: Expose gRPC port 50051

This is a pure additive change that does not affect existing
persistent role keygen/sign functionality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 18:36:11 -08:00
hailin 7696f663a5 fix(service-party-app): add 'kava' to LogSource type
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 10:34:57 -08:00
hailin ae936e8a87 feat(service-party-app): add Kava network switch (mainnet/testnet)
- Add KAVA_TESTNET_TX_CONFIG in kava-tx-service.ts
- Add switchNetwork/getNetwork IPC handlers in main.ts
- Add network toggle UI in Settings page
- Show current network (测试网/主网) badge in Layout status bar
- Default to testnet for development

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 10:31:27 -08:00
hailin 9015888b23 fix(service-party-app): fix participants display in Home page
listShares returned `participants` but Home.tsx expected `metadata.participants`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 10:07:11 -08:00
hailin f849a2a9fd fix(tss-party): increase stdin buffer to 1MB for large TSS messages
Default 64KB buffer was truncating large TSS protocol messages in round 3+

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 09:57:04 -08:00
hailin 2a49ab771b fix(message-router): 修复 JoinSession 代理未转发 Status 字段
问题: Message Router 代理 Session Coordinator 的 JoinSession 响应时,
没有转发 session_info.status 字段,导致前端方案B无法工作

修复: 添加 Status 字段的转发

这修复了 co-keygen 中最后一个加入者错过 session_started 事件的问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 09:20:15 -08:00
hailin 57b84bb9fa feat: 恢复EVM地址派生和余额显示功能 + 修复0人参与bug
恢复的功能:
1. ee59d1c0 - 方案B修复最后加入者错过session_started事件的竞态条件
   - 修复了显示"0人参与"的bug
   - 使用事件缓存机制解决时序问题

2. a269e4d1 - 支持压缩公钥派生EVM地址并显示KAVA余额
   - Home页面显示钱包的KAVA EVM地址
   - 显示KAVA测试网余额
   - 支持压缩公钥格式

这些功能已经过验证,与转账功能无关。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 08:53:26 -08:00
hailin 290b5ea766 fix(server-party-co-managed): use session_started event for participants list
session_created event only contains initial co-managed parties,
but session_started event contains ALL participants including
external parties that joined dynamically via invite code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 00:52:28 -08:00
hailin 2164664ca0 feat(server-party): add ExecuteWithSessionInfo for co-managed keygen
Add new ExecuteWithSessionInfo method to ParticipateKeygenUseCase
for server-party-co-managed to skip duplicate JoinSession call.

- server-party-co-managed already calls JoinSession in session_created phase
- ExecuteWithSessionInfo accepts pre-obtained SessionInfo and skips internal JoinSession
- Refactor common execution logic to private executeWithSessionInfo method
- Update server-party-co-managed to use ExecuteWithSessionInfo on session_started

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 00:43:09 -08:00
hailin fd6f84ce82 fix(server-party-co-managed): 修复死锁问题 - session_created 时立即 JoinSession
问题:
- 原来在 session_created 时只存储 token,等待 session_started
- 但 session_started 需要所有 N 方都 JoinSession 后才触发
- 这导致死锁:co-managed-party 永远收不到 session_started

修复:
- Phase 1 (session_created): 立即调用 JoinSession + 存储 session 信息
- Phase 2 (session_started): 执行 TSS 协议(超时从此时开始计算)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 00:23:26 -08:00
hailin e114723ab0 feat(mpc-system): add server-party-co-managed for co_managed_keygen sessions
- Create new server-party-co-managed service with two-phase event handling
  - Phase 1 (session_created): Store join token and wait
  - Phase 2 (session_started): Execute TSS protocol (same timing as service-party-app)
- Add PartyRoleCoManagedPersistent role to isolate from normal keygen/sign
- Update docker-compose.yml with 3 co-managed party instances
- Update deploy.sh service lists
- Modify selectPartiesByCompositionForCoManaged to use new role

This ensures co_managed_keygen sessions use dedicated parties that behave
100% compatible with service-party-app, without affecting existing keygen/sign flows.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 23:54:45 -08:00
hailin 1c66b55ea1 fix(service-party-app): 动态计算 persistent_count 并修复 keygen 触发时机
1. 动态计算 server-party 数量: persistent = n - t
   - 2-of-3 -> persistent=1, external=2
   - 3-of-5 -> persistent=2, external=3
   - 4-of-7 -> persistent=3, external=4

2. 修复 5 分钟超时与 24 小时会话的冲突
   - 之前: joinSession 后立即启动 5 分钟轮询,导致超时失败
   - 现在: 等待 all_joined 事件后才启动 5 分钟倒计时
   - 用户可以在 24 小时内慢慢邀请其他参与者加入

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 21:28:23 -08:00
hailin 66c3cec9a5 Revert "fix(service-party-app): joinSession 添加重试逻辑处理乐观锁冲突"
This reverts commit 8c3a299714.
2025-12-29 13:48:14 -08:00
hailin 8c3a299714 fix(service-party-app): joinSession 添加重试逻辑处理乐观锁冲突
问题:
- 多个参与方同时加入会话时会触发乐观锁冲突
- server-party 有重试逻辑可以成功重试
- service-party-app (Electron) 没有重试逻辑,直接失败
- 导致外部参与方无法成功加入 co_managed_keygen 会话

修复:
- joinSession 方法添加最多 3 次重试
- 支持重试的错误类型:optimistic lock、UNAVAILABLE、DEADLINE_EXCEEDED
- 使用指数退避 + 随机抖动避免重试风暴
- 抽取 doJoinSession 内部方法和 sleep 辅助方法

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 13:47:51 -08:00
hailin 6de545fcb9 fix(session-coordinator): generate wildcard token for co_managed_keygen external participants 2025-12-29 13:35:05 -08:00
hailin 75a2470233 debug(service-party-app): 添加 keygen 触发流程详细日志
添加 [KEYGEN] 前缀的 console.log 来追踪:
- checkAndTriggerKeygen 是否被调用
- activeKeygenSession 的状态
- 轮询条件是否满足
- handleSessionStart 的执行
- participateKeygen 的参数

帮助诊断 external party 为何不启动 TSS 进程

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 13:15:47 -08:00
hailin 576679ae30 fix(server-party): heartbeat during waitForAllParticipants
Problem:
- co_managed_keygen server-party waits for external party after joining
- No heartbeat sent during wait period (up to 5 minutes)
- session-coordinator has 120 second inactivity timeout
- Server-party marked as timed_out/failed while waiting

Fix:
- Send heartbeat in waitForAllParticipants polling loop
- Add Heartbeat method to MessageRouterClient interface
- Heartbeat every 2 seconds with poll interval
- Heartbeat failure only logs warning, does not block

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 13:04:40 -08:00
hailin c0e292535d fix(service-party-app): 修复 handleIncomingMessage 字段名 snake_case 问题
问题:
- gRPC proto-loader 使用 keepCase: true,返回 snake_case 字段名
- tss-handler.ts 的 handleIncomingMessage 期望 camelCase 字段名
- 导致 message_id, from_party, is_broadcast 等字段无法正确读取
- TSS 进程无法收到正确的消息,keygen 无法完成

修复:
- handleIncomingMessage 参数改为 snake_case (message_id, from_party, is_broadcast)
- 内部转换为 camelCase 格式后处理

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 12:53:26 -08:00
hailin 674bc9e5cd fix(mpc-system): GetSessionStatus API 返回 threshold_t 和 threshold_n
问题:
- Account 服务的 GetSessionStatus HTTP API 没有返回 threshold 字段
- 导致 service-party-app 获取到的 threshold 始终是 0
- TSS keygen 无法使用正确的阈值参数

修复:
- Account gRPC client 添加 ThresholdT 和 ThresholdN 字段映射
- Account HTTP handler 返回 threshold_t 和 threshold_n
- service-party-app 优先使用后端返回的 threshold 值
- checkAndTriggerKeygen 使用后端 threshold 更新 activeKeygenSession

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 12:43:59 -08:00
hailin fb1b27e36f fix(service-party-app): 切换 session 时重新订阅消息流
问题:
- prepareForKeygen 只检查 isPrepared 标志
- 当旧 session 失败后 isPrepared 可能仍为 true
- 新 session 调用 prepareForKeygen 时直接跳过,没有重新订阅
- 导致 external party 仍订阅旧 session 的消息流
- server parties 发送的 TSS 消息无法到达 external party

修复:
- 检查 sessionId 是否变化
- 如果是新 session,先取消旧订阅再重新订阅

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 12:27:01 -08:00
hailin 989364969d fix(service-party-app): 修复 gRPC 响应字段名 snake_case 问题
问题:
- proto-loader 使用 keepCase: true,导致 gRPC 响应字段为 snake_case
- TypeScript 接口使用 camelCase,导致字段不匹配
- joinSession 响应的 session_info.threshold_t 和 threshold_n 无法读取
- 导致 activeKeygenSession.threshold 为 {t: 0, n: 0}
- TSS 进程收到错误的 threshold 参数导致 exit code 1

修复:
- grpc-client.ts 接口改为 snake_case 以匹配 proto 定义
- main.ts 更新为使用 snake_case 字段名
- SessionEvent 处理转换为 camelCase 再传递给 handleSessionStart

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 12:14:30 -08:00
hailin 1b48c05aa7 fix(mpc-system): GetSessionStatus 返回实际的 threshold_n 和 threshold_t
问题:
- Message Router 的 GetSessionStatus 把 TotalParties 当作 ThresholdN 返回
- 导致 server-party 收到错误的 threshold_n=2 而不是 3
- TSS 协议无法正确启动(参与者数量验证失败)

修复:
- 在 session_coordinator.proto 添加 threshold_n 和 threshold_t 字段
- Session Coordinator 返回实际的 threshold 值
- Message Router 透传 threshold 值而不是参与者数量

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:59:53 -08:00
hailin 422d7007b1 fix(service-party-app): 补全 getSessionStatus 返回的 threshold 和 participants
问题:
- Session.tsx 期望 session 对象有 threshold 和 participants 字段
- 但 grpc:getSessionStatus 只返回了基础字段
- 导致前端显示 参与方 (0 / 0)

修复:
- 从 activeKeygenSession 获取 threshold 信息
- 从 API 返回的 participants 构建完整的参与者列表
- 添加 walletName, currentRound, totalRounds 字段

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:54:14 -08:00
hailin c94f3e4d83 debug(service-party-app): 添加 TSS 进程详细调试日志
- 输出二进制文件路径和存在性检查
- 输出传递给 TSS 的参与者列表 JSON
- 输出完整的命令行参数
- 收集并输出 stderr 内容
- 帮助诊断 TSS 进程 exit code 1 问题

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:47:37 -08:00
hailin aa9171ce2c fix(service-party-app): 修复 threshold 为 undefined 导致的崩溃
问题:
- Session.tsx 直接访问 session.threshold.n 和 session.threshold.t
- 当后端返回的 session 数据中 threshold 为 undefined 时崩溃

修复:
- 添加空值检查 session.threshold?.n || 0
- 阈值信息部分添加条件渲染

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:37:09 -08:00
hailin 30ec0a1c8e fix(service-party-app): 修复 participants 为 undefined 导致的崩溃
问题:
- Session.tsx 和 Home.tsx 直接访问 participants.length
- 当后端返回的 session 数据中 participants 为 undefined 时崩溃
- 导致 TypeError: Cannot read properties of undefined (reading length)

修复:
- 添加空值检查 (session.participants || []).length
- 使用 Math.max(0, ...) 防止负数长度

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:30:30 -08:00
hailin b0a698250d fix(service-party-app): 在 package.json 的 build 配置中添加 afterPack
问题:
- electron-builder 加载的是 package.json 的 build 字段
- 而不是单独的 electron-builder.json 文件
- 导致 afterPack hook 没有被执行

修复:
- 在 package.json 的 build 配置中添加 afterPack 引用

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:20:35 -08:00
hailin 072fbbad2c fix(service-party-app): 使用 afterPack hook 确保 TSS 二进制文件被正确打包
问题:
- extraResources 的 ${platform}-${arch} 宏在 from 路径中可能不可靠
- 参考: https://github.com/electron-userland/electron-builder/issues/7891

解决:
- 创建 afterPack.js hook 手动复制对应平台/架构的二进制文件
- 移除 extraResources 配置,改用 hook 方式
- 确保 tss-party 二进制文件被正确复制到 resources/bin/ 目录

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:14:39 -08:00
hailin 9b9f6f143e fix(service-party-app): 将 tss-party 二进制文件打包进应用
- 添加 extraResources 配置将 bin/${platform}-${arch} 目录包含到打包资源中
- 修复打包后的应用找不到 tss-party.exe 导致 TSS 协议无法执行的问题
- 二进制文件会被复制到 resources/bin/ 目录

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:10:38 -08:00
hailin b48b59d946 fix(service-party-app): 开发模式默认使用真实 TSS Handler
问题:
- 开发模式自动使用 MockTSSHandler
- MockTSSHandler 不发送真正的 TSS 网络消息
- 导致 co_managed_keygen 无法完成

修复:
- 移除 NODE_ENV === 'development' 的自动 mock 逻辑
- 只有显式设置 USE_MOCK_TSS=true 时才使用 Mock Handler
- 开发模式现在默认使用真实的 TSSHandler

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:07:27 -08:00
hailin b938722ff6 fix(service-party-app): 保留正确的 partyIndex 不覆盖
问题:
- handleSessionStart 中使用 forEach 的 index 作为 partyIndex
- 这会覆盖 checkAndTriggerKeygen 已经从服务器获取的正确 partyIndex
- 导致 TSS 协议使用错误的 partyIndex

修复:
- 优先使用 existing.partyIndex(从服务器获取的正确值)
- 只有找不到已有信息时才使用 fallback
- 按 partyIndex 排序确保顺序正确

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 10:59:33 -08:00
hailin e72f96da10 feat(service-party-app): 验证成功后自动加入会话
- 移除手动输入名称和点击"确认加入"按钮的步骤
- 验证邀请码成功后自动触发 joinSession
- 生成默认参与者名称(参与者-xxxx 格式)
- 保留错误处理和重试功能
- 减少用户操作步骤,提高 co_managed_keygen 可靠性

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 10:48:41 -08:00
hailin bd6537a2cb fix(service-party-app): checkAndTriggerKeygen 改为轮询等待
问题:
- 原来 checkAndTriggerKeygen 只检查一次
- 如果首次检查时会话状态还不是 in_progress,就直接返回
- 导致 external party 永远不触发 keygen

修复:
- 改为与 server-party 的 waitForAllParticipants 一致的轮询逻辑
- 2 秒轮询间隔,最多等待 5 分钟
- 持续检查直到所有参与者加入且状态正确

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 10:44:29 -08:00
hailin dfead071ab fix(service-party-app): 修复 co_managed_keygen 消息丢失问题
问题:
- service-party-app 在 joinSession 后有 1 秒延迟才开始 keygen
- server-party 检测到所有参与者后立即发送 TSS Round 0 消息
- service-party-app 此时还没订阅消息流,导致消息丢失
- TSS 协议无法完成

修复:
- TSSHandler 新增 prepareForKeygen() 方法,在 joinSession 后立即订阅消息
- 新增 isPrepared 状态,在预订阅阶段也能缓冲消息
- handleIncomingMessage 支持 isPrepared || isRunning 时缓冲消息
- participateKeygen 保留预订阅阶段缓冲的消息,不重复订阅
- main.ts 在 joinSession 成功后立即调用 prepareForKeygen()
- 移除 1 秒延迟,改用 setImmediate 立即触发 keygen

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 10:35:39 -08:00
hailin 820a61793c fix(service-party-app): 添加等待所有参与者加入的逻辑
- 在 checkAndTriggerKeygen 中添加参与者数量检查
- 必须等待所有 N 个参与者加入后才能开始 keygen
- 与 server-party 的 waitForAllParticipants 逻辑保持一致
- 修复 co_managed_keygen 场景下 TSS 协议无法完成的问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 10:18:38 -08:00
hailin a22fc16313 fix(session-coordinator): 修复 FindExpired SQL 时区问题
- expires_at 存储为 UTC 时间
- 查询时使用 NOW() AT TIME ZONE 'UTC' 确保时区一致
- 避免因时区差异导致 session 过早被标记为过期

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 10:07:28 -08:00
hailin e222279d77 fix(server-party): co_managed_keygen 等待所有参与者加入后再开始 keygen
- Message Router GetSessionStatus 透传 participants 列表
- Server Party 新增 GetSessionStatusFull 方法获取完整会话状态
- participate_keygen.go 对 co_managed_keygen 类型轮询等待所有 N 个参与者加入
- 不影响原有 keygen/sign 功能(仅 co_managed_keygen 触发等待逻辑)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 09:55:52 -08:00
hailin 48c8c071d5 fix(server-party): 支持 co_managed_keygen 会话类型
server-party 的 ParticipateKeygenUseCase 现在同时接受 "keygen" 和
"co_managed_keygen" 两种会话类型,使 persistent party 能够正确参与
共管钱包的密钥生成流程。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 09:24:44 -08:00
hailin 9bc48d19a9 fix(mpc-system): 修复 co_managed_keygen 参与者 party_index 映射问题
- 在 proto 中添加 ParticipantStatus 消息和 participants 字段
- session-coordinator 返回参与者详细信息(含 party_index)
- account 服务透传 participants 到 HTTP 响应
- service-party-app 使用服务器返回的 party_index 而非数组索引
- 同时返回 join_tokens map 和 join_token 字符串以兼容两种格式

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 09:06:13 -08:00
hailin 0ca37ee76a feat(mpc-system): 增强连接可靠性和消息去重机制
后端改进:
- SessionEventBroadcaster: 重连时自动关闭旧 channel 防止内存泄漏
- MessageBroker: 重连时关闭旧的 party/session channel
- SubscribeMessages: 订阅时自动发送数据库中的 pending 消息

客户端改进:
- GrpcClient: 添加自动重连机制(指数退避,最多10次)
- GrpcClient: 断开/重连/失败事件通知前端
- TSSHandler: 消息缓冲机制,进程启动前缓存收到的消息
- TSSHandler: 客户端本地消息去重,防止重连后重复处理
- Database: 添加 processed_messages 表和相关操作方法
- Main: Keygen 幂等性保护,防止重复触发
- Main: 会话事件缓存,解决前端订阅时序问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 07:37:03 -08:00
hailin df8a14211e debug(mpc-system): 添加 joinToken 调试日志
- service-party-app: validateInviteCode 记录 token 长度
- service-party-app: joinSession 记录 token 信息
- service-party-app: 修复 ValidateInviteCodeResult 类型缺少 joinToken 字段
- session-coordinator: JoinSession 记录 token 解析详情

用于调试 "invalid token" 错误

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 05:55:46 -08:00
hailin 5f4c7c135f feat(mpc-system): 完善 co_managed_keygen 流程并添加调试控制台
主要改动:
- service-party-app: 发起方创建会话后自动加入并设置 activeKeygenSession
- service-party-app: 添加轮询机制确保 100% 可靠触发 keygen
- service-party-app: 添加 DebugConsole 组件 (Ctrl+Shift+D 打开)
- service-party-app: 主进程添加 debugLog 系统,日志可实时显示到前端
- session-coordinator: JoinSession 加入 messageRouterClient 发布事件
- session-coordinator: 添加 PublishSessionStarted 方法

修复:
- 发起方不设置 activeKeygenSession 导致无法触发 keygen 的问题
- 加入方可能错过 session_started 事件的时序问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 05:32:40 -08:00
hailin a5ab2e8350 fix(session-coordinator): 支持 co_managed_keygen 动态参与者加入
问题: 通过邀请码加入共管钱包会话时报 "party not invited" 错误
原因: 外部参与者不在 party pool 中,CreateSession 时无法预先选择

修复:
- join_session.go: 对于 co_managed_keygen + wildcard token,允许动态添加参与者
- create_session.go: 新增 selectPartiesByCompositionForCoManaged,跳过 TemporaryCount 选择
- report_completion.go: 使用 IsKeygen() 方法,co_managed_keygen 完成后也创建账户记录

注意: 所有修改仅对 co_managed_keygen 类型生效,不影响现有 keygen/sign 流程

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 04:25:11 -08:00
hailin af08f0f9c6 fix(mpc-system): 修复通过邀请码加入会话时 invalid token 错误
问题: 通过邀请码查询会话后加入时报 "13 INTERNAL: invalid token"
原因: GetSessionByInviteCode API 没有返回 join_token

修复:
- account-service: GetSessionByInviteCode 在查询时生成新的 wildcard join token
- account-service: CoManagedHTTPHandler 添加 jwtService 依赖注入
- service-party-app: validateInviteCode 返回 join_token
- service-party-app: Join.tsx 保存并使用 joinToken 和 partyId
- service-party-app: preload.ts joinSession 使用正确的参数格式

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 03:40:36 -08:00
hailin 21985abde5 fix(session-coordinator): 保存 WalletName 和 InviteCode 到数据库
- CreateSessionInput 添加 WalletName 和 InviteCode 字段
- gRPC handler 从请求中读取并传递这些字段
- CreateSession use case 在创建会话时设置这些字段

修复: 通过邀请码查询会话时找不到记录的问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 03:07:44 -08:00
hailin 591dc50eb9 fix(service-party-app): 创建会话时添加 initiator_party_id 参数
- CreateKeygenSessionRequest 添加 initiator_party_id 和 initiator_name 字段
- 创建会话前检查是否已连接到消息路由器
- 自动获取已注册的 partyId 作为发起者

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 02:54:43 -08:00
hailin 19e366e0d9 fix(service-party-app): 修复 Account 服务 URL 为 rwaapi.szaiai.com
api.szaiai.com 被 OSS/CDN 拦截,改用 rwaapi.szaiai.com 直接访问 Kong 网关

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 02:45:23 -08:00
hailin 551f3d27d2 feat(api-gateway): 添加 MPC Account Service 路由配置
添加共管钱包 API 路由,将 /api/v1/co-managed/* 请求转发到 account-service (端口 4000)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 02:31:56 -08:00
hailin b1234bc434 feat(admin-web): 添加 TSS WASM 集成,实现与 Service-Party-App 功能对等
## 功能概述
Admin-Web 现在可以作为独立的 TSS 参与方参与共管钱包创建,
与 Service-Party-App 桌面应用功能完全对等。

## 主要变更

### 1. TSS WASM 模块 (backend/mpc-system/services/tss-wasm/)
- main.go: Go WASM 模块,封装 bnb-chain/tss-lib
- 支持 keygen 和 signing 操作
- 通过 syscall/js 与 JavaScript 通信

### 2. Admin-Web TSS 库 (frontend/admin-web/src/lib/tss/)
- tss-wasm-loader.ts: WASM 加载器
- tss-client.ts: 高级 TSS 客户端 API
- grpc-web-client.ts: gRPC-Web 客户端连接 Message Router

### 3. 本地存储模块 (frontend/admin-web/src/lib/storage/)
- share-storage.ts: IndexedDB 加密存储
- 使用 AES-256-GCM 加密,PBKDF2 密钥派生

### 4. React Hooks
- useTSSClient.ts: TSS 客户端状态管理
- useShareStorage.ts: 存储操作封装

### 5. 组件更新
- CreateWalletModal.tsx: 集成 TSS 客户端
  - 添加密码保护对话框
  - 实现真实 keygen 流程
  - 自动保存 share 到 IndexedDB
- CoManagedWalletSection.tsx: 使用真实 API
- coManagedWalletService.ts: API 服务层

### 6. WASM 文件
- frontend/admin-web/public/wasm/tss.wasm (~19MB)
- frontend/admin-web/public/wasm/wasm_exec.js (Go 运行时)

## 技术栈
- Go 1.21+ (WASM 编译)
- bnb-chain/tss-lib v2.0.2 (TSS 协议)
- Web Crypto API (AES-256-GCM)
- IndexedDB (本地存储)
- gRPC-Web (消息路由)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 01:39:43 -08:00
hailin be94a6ab18 fix(server-party): session 事件订阅断开后自动重连
Message Router 重启后,server-party 的 gRPC stream 会断开,
之前的实现会直接退出 goroutine 导致无法收到新的 session 事件。

修改内容:
- 添加自动重连逻辑,stream 断开时会尝试重新订阅
- 使用指数退避策略,从 1 秒到最大 30 秒
- 重连成功后重置退避时间

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 01:25:30 -08:00
hailin 40a257e55c fix(mpc-system): 开发模式添加 message-router gRPC 端口映射
添加 50051:50051 端口映射,使开发模式与生产模式保持一致

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 00:56:04 -08:00
hailin e78b6e6dcb fix(service-party-app): 延迟加载 proto 定义避免启动时崩溃
将 proto 文件加载改为延迟加载模式,在 connect() 时才加载,
避免模块加载时 app.isPackaged 还未准备好导致的路径错误。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 23:38:42 -08:00
hailin 4794cafdaa fix(service-party-app): 改为非阻塞方式连接 Message Router
将 connectAndRegisterToMessageRouter() 改为非阻塞调用,
不再使用 await 阻塞应用启动。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 23:34:10 -08:00
hailin 28da7f6807 feat(service-party-app): 实现启动时自动注册到 Message Router,状态验证真实化
## 主要变更

### 1. 启动时自动连接并注册
- main.ts: 添加 `connectAndRegisterToMessageRouter()` 函数
- 应用启动时自动连接到 Message Router 并注册为 temporary 角色
- 自动生成并持久化 partyId(使用 crypto.randomUUID)
- 自动订阅会话事件

### 2. 状态验证真实化
- appStore.ts: 重写 `checkAllServices()` 消息路由检测逻辑
- 不再只检测连接成功,而是:
  1. 调用 isConnected() 检查连接状态
  2. 调用 getPartyId() 检查是否已注册
  3. 调用 getRegisteredParties() 从 Message Router 获取注册列表
  4. 验证自己的 partyId 是否在列表中
- 状态显示更准确:
  - "未连接到 xxx" - 未连接
  - "已连接但未注册" - 已连接但没注册
  - "已注册 (在线)" - 完全正常
  - "注册验证失败" - 注册了但验证失败

### 3. 新增 IPC API
- grpc:getPartyId - 获取当前 partyId
- grpc:isConnected - 检查连接状态
- grpc:connect - 连接到 Message Router
- grpc:register - 注册为参与方

### 修改的文件
- electron/main.ts
- electron/preload.ts
- src/stores/appStore.ts
- src/types/electron.d.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 23:29:04 -08:00
hailin 73034c072c feat(service-party-app): 添加获取已注册参与方列表API
新增 grpc.getRegisteredParties() API,用于查询 Message Router 中已注册的参与方:
- grpc-client.ts: 添加 getRegisteredParties() 方法
- main.ts: 添加 IPC 处理器
- preload.ts: 暴露 API 到渲染进程
- electron.d.ts: 添加类型定义

此功能用于测试和调试,确认 Service-Party-App 是否成功注册到 Message Router。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 22:50:46 -08:00
hailin de29fa4800 feat(co-managed-wallet): 添加签名会话API和Service-Party-App HTTP客户端
## Account Service 新增 API
- GET /api/v1/co-managed/sessions/by-invite-code/:inviteCode - 通过邀请码查询keygen会话
- POST /api/v1/co-managed/sign - 创建签名会话
- GET /api/v1/co-managed/sign/by-invite-code/:inviteCode - 通过邀请码查询签名会话

## Service-Party-App 变更
- 新增 account-client.ts HTTP客户端模块
- 集成Account服务API到Electron主进程
- 添加account相关IPC处理器
- 更新preload.ts暴露account API到渲染进程
- Settings页面添加Account服务URL配置

## 文档更新
- 更新 docs/service-party-app.md 反映实际实现
- 添加Account Service HTTP API说明
- 添加签名流程文档

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 22:34:35 -08:00
hailin 81c8db9d50 fix(service-party-app): 修复Kava API健康检查逻辑
问题: Kava API检测失败,原因是使用测试地址查询余额的方式不可靠

解决方案:
1. 添加 healthCheck() 方法到 KavaTxService,查询最新区块
2. 添加 kava:healthCheck IPC 处理器
3. 更新 appStore 使用 healthCheck API 而非 getBalance

修改的文件:
- kava-tx-service.ts: 添加 healthCheck() 方法
- main.ts: 添加 kava:healthCheck IPC 处理器
- preload.ts: 暴露 healthCheck API
- appStore.ts: 使用 healthCheck 检测 Kava API
- electron.d.ts: 添加 KavaHealthCheckResult 类型

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 21:01:27 -08:00
hailin a2508ab0fd fix(service-party-app): 修复sql.js在打包后无法加载的问题
问题: Electron打包后sql.js模块报"Cannot find module"错误

解决方案:
1. 使用extraResources将sql-wasm.wasm复制到resources目录
2. 修改database.ts使用wasmBinary方式加载WASM文件
3. 直接读取WASM文件作为ArrayBuffer,避免模块解析问题

修改的文件:
- package.json: 添加extraResources配置复制WASM文件
- database.ts: 使用fs.readFileSync读取WASM并传递给initSqlJs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:50:28 -08:00
hailin 761e03ebb0 fix(service-party-app): 修复sql.js在打包后找不到模块的问题
## 问题
打包后的应用运行时报错 "Cannot find module 'sql.js'"

## 解决方案

### 1. electron-builder 配置
- 添加 `asarUnpack` 配置,将 sql.js 解压到 asar.unpacked 目录
- 将 `node_modules/sql.js/**/*` 添加到 files 列表

### 2. database.ts 修改
- 添加 `getSqlJsWasmPath()` 函数,根据环境返回正确的 WASM 路径
- 开发环境: node_modules/sql.js/dist/sql-wasm.wasm
- 生产环境: app.asar.unpacked/node_modules/sql.js/dist/sql-wasm.wasm
- 使用 locateFile 配置指定 WASM 文件位置

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:41:01 -08:00
hailin f5cbc855f6 feat(service-party-app): 添加应用状态检测和启动检查功能
## 新增功能

### 1. 启动检测页面 (StartupCheck)
- 应用启动时显示环境检测界面
- 检测三个核心服务: 本地数据库、消息路由、Kava API
- 检测完成后自动进入主界面 (1.5秒延迟)
- 支持查看详细错误信息
- 即使部分服务异常也可进入应用

### 2. 应用状态管理 (appStore)
- 使用 Zustand 管理全局应用状态
- 跟踪各服务的连接状态: unknown/checking/connected/disconnected/error
- 支持操作进度跟踪 (keygen/sign)
- 提供状态辅助函数: getStatusColor, getStatusText, getOverallStatus

### 3. 侧边栏状态面板
- 实时显示三个服务的连接状态
- 显示当前操作进度 (keygen/sign 时)
- 支持手动刷新检测
- 显示整体就绪状态

## 新增文件
- src/stores/appStore.ts: 应用状态管理
- src/components/StartupCheck.tsx: 启动检测组件
- src/components/StartupCheck.module.css: 启动检测样式

## 修改文件
- src/App.tsx: 集成启动检测流程
- src/components/Layout.tsx: 添加状态面板
- src/components/Layout.module.css: 状态面板样式
- src/types/electron.d.ts: 添加 metadata 字段兼容

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:33:00 -08:00
hailin 6b2b9e821e fix(service-party-app): 更新build-windows.bat支持clean选项
添加命令行参数支持:
- build-windows.bat          正常构建
- build-windows.bat clean    清理构建产物后重建
- build-windows.bat cleanall 完全清理(含node_modules)后重建

在其他电脑上首次编译时,建议使用:
  build-windows.bat cleanall

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:17:27 -08:00
hailin eb283389c4 fix(service-party-app): 添加bech32类型声明和清理脚本
## 变更

### 1. 添加 bech32 类型声明
- 新增 electron/types/bech32.d.ts 手动声明模块类型
- 解决跨机器编译时找不到 bech32 类型的问题

### 2. 添加清理脚本
- npm run clean: 清理构建产物 (dist, dist-electron, release)
- npm run clean:all: 完全清理 (包括 node_modules)
- npm run rebuild: 清理后重新构建
- npm run rebuild:win: 清理后重新构建 Windows 版本

## 跨机器编译说明

在新电脑上编译时,建议执行:
```bash
npm run clean:all
npm install
npm run build:win
```

或使用一键重建:
```bash
npm run rebuild:win
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:14:17 -08:00
hailin 59b0e2bb22 fix(service-party-app): 移除不兼容的@types/bech32
bech32 v2.0.0 自带TypeScript类型定义,不需要单独的
@types/bech32 包(该包是针对v1.x版本的)。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:10:05 -08:00
hailin 3f25424049 fix(service-party-app): 添加bech32类型定义修复编译错误
添加 @types/bech32 开发依赖以解决 TypeScript 编译时
找不到 bech32 模块类型声明的问题。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:07:13 -08:00
hailin c97cd208ab feat(service-party-app): 添加SQLite存储和Kava区块链集成
## 主要变更

### 1. SQLite 本地存储 (sql.js)
- 使用 sql.js (纯 JavaScript SQLite) 替代 better-sqlite3
- 无需本地数据库服务,跨平台兼容
- 表结构: shares, derived_addresses, signing_history, settings
- AES-256-GCM 加密 share 数据,PBKDF2 密钥派生

### 2. Kava 区块链集成
- 新增 kava-tx-service.ts: REST API 交易服务
  - 余额查询 (ukava/KAVA)
  - 交易构建和广播
  - 交易状态查询
- 支持多个备用端点自动切换

### 3. 地址派生
- 新增 address-derivation.ts: 多链地址派生
- 支持 Kava, Cosmos, Osmosis, Ethereum 等链
- 使用 Node.js crypto 替代 @noble/hashes 以解决模块兼容问题
- 手动实现 secp256k1 公钥解压缩

### 4. IPC 处理器
- main.ts: 添加 Kava 相关 IPC 处理器
- preload.ts: 暴露 kava API 给渲染进程
- electron.d.ts: 完整的 TypeScript 类型定义

## 新增文件
- electron/modules/database.ts
- electron/modules/address-derivation.ts
- electron/modules/kava-client.ts
- electron/modules/kava-tx-service.ts
- electron/types/sql.js.d.ts
- src/utils/address.ts
- .gitignore

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 19:45:45 -08:00
hailin 76ef8b0a8c fix(service-party-app): 修复gRPC测试连接方法
将testConnection从URL解析改为直接使用host:port格式,
与grpc-client.ts的connect方法保持一致。

地址格式: mpc-grpc.szaiai.com:443 (自动检测TLS)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 18:48:04 -08:00
hailin 2a11392ce2 fix(nginx): 配置文件使用.conf后缀以匹配nginx include规则
nginx.conf 使用 include /etc/nginx/sites-enabled/*.conf
因此配置文件必须以 .conf 结尾才能被加载。

变更:
- 添加 DOMAIN_CONF 变量 (mpc-grpc.szaiai.com.conf)
- 更新所有文件路径使用 .conf 后缀
- 卸载时兼容新旧文件名

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 18:39:45 -08:00
hailin e268c33fa9 fix(nginx): 集成certbot目录创建到安装脚本
在安装脚本的configure_http步骤中添加:
- 创建完整的webroot目录结构: /var/www/certbot/.well-known/acme-challenge
- 设置正确的权限: chmod -R 755

这确保Let's Encrypt验证能正常工作,解决404错误问题。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 18:29:59 -08:00
hailin c457d15829 feat(co-managed-wallet): 添加分布式共管钱包 API 和 gRPC 代理
## 功能概述
实现分布式多方共管钱包创建功能的后端 API 和网络基础设施,
支持 Service Party App 通过公网连接参与 TSS 协议。

## 主要变更

### 1. Account Service - 共管钱包 API (新增)
- 新增 co_managed_handler.go - 独立的共管钱包 HTTP handler
- 新增 API 端点:
  - POST /api/v1/co-managed/sessions - 创建共管钱包会话
  - POST /api/v1/co-managed/sessions/:id/join - 加入会话
  - GET /api/v1/co-managed/sessions/:id - 获取会话状态
- 扩展 session_coordinator_client.go:
  - 添加 CreateCoManagedKeygenSession 方法
  - 添加 JoinSession 方法
  - 添加响应类型定义
- 更新 main.go 注册新路由 (SkipPaths 免认证)

### 2. Nginx gRPC 代理 (新增)
- 新增 mpc-grpc.szaiai.com.conf - gRPC over TLS 代理配置
- 新增 install-mpc-grpc.sh - 自动化安装脚本
- 支持 Let's Encrypt SSL 证书
- 代理到后端 Message Router (192.168.1.111:50051)

### 3. Service Party App 更新
- grpc-client.ts: 支持 TLS 连接,自动检测端口 443
- Settings.tsx: 默认地址改为 mpc-grpc.szaiai.com:443
- Home.tsx/Create.tsx: UI 样式优化

## 架构

```
Service Party App (用户电脑)
        │
        │ gRPC over TLS (端口 443)
        ▼
Nginx (mpc-grpc.szaiai.com:443)
        │
        │ grpc_pass
        ▼
Message Router (192.168.1.111:50051)
        │
        ▼
Session Coordinator → Server Parties
```

## 100% 不影响现有功能

- 所有修改均为新增代码,不修改现有逻辑
- 共管钱包 API 完全独立于现有 RWADurian 系统
- Nginx 配置为独立文件,不影响现有 rwaapi.szaiai.com
- 使用现有 proto 定义 (co_managed_keygen, wallet_name, invite_code)

## 部署步骤

1. DNS: 添加 mpc-grpc.szaiai.com A 记录
2. 安装: sudo ./install-mpc-grpc.sh
3. 验证: curl https://mpc-grpc.szaiai.com/health

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 18:11:57 -08:00
hailin a830a88cc3 feat(service-party-app): 添加签名功能并重命名应用
## 新增功能
- 添加"参与签名"页面 (Sign.tsx)
- 支持选择本地 share 参与 TSS 签名
- 支持导入备份文件参与签名
- 签名进度实时显示

## 应用重命名
- 应用名称改为"榴莲皇后绿积分共管账户服务"
- 更新 package.json productName
- 更新 index.html title
- 更新侧边栏 logo 文字

## 代码完善
- 完善 preload.ts API 定义
- 添加 main.ts IPC 处理器
- 更新 electron.d.ts 类型定义
- 添加 storage.ts saveSettings 方法

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 08:00:00 -08:00
hailin 7cfaacc833 fix(service-party-app): 修改默认阈值为 3-of-5
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:50:15 -08:00
hailin 47328c67d7 fix(service-party-app): 修复路由和启动问题
1. 将 BrowserRouter 改为 HashRouter - Electron 使用 file:// 协议
2. 移除生产环境自动打开浏览器的代码
3. HTTP 服务器仅在开发模式下启动

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:45:43 -08:00
hailin 15cbb2401f fix(service-party-app): 修复 proto 文件打包路径问题
- 复制 message_router.proto 到 service-party-app/proto/
- 修改 grpc-client.ts 使用 process.resourcesPath 加载 proto 文件
- 使用 extraResources 将 proto 文件打包到 resources 目录外

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:39:55 -08:00
hailin e43500fc3f fix(service-party-app): 修复 electron-builder files 配置
- 将 electron/**/* 改为 dist-electron/**/* (编译后的文件)
- 添加 proto/**/* (gRPC proto 文件)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:35:26 -08:00
hailin 7cec5b2b4c fix(service-party-app): 修复 gRPC 客户端 TypeScript 类型错误
添加 ProtoPackage 接口定义 proto 包结构类型,避免类型推断错误

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:33:10 -08:00
hailin 1f476e8e5a fix(service-party-app): 修复 Electron 主进程编译配置
- 新增 tsconfig.electron.json 单独编译 Electron 主进程到 dist-electron/
- 更新 package.json main 入口为 dist-electron/main.js
- 更新 build 脚本先编译 electron 再 vite build

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:31:26 -08:00
hailin fcaa57605a fix(service-party-app): fix TypeScript compilation errors
- Fix import/export consistency (use default exports)
- Add CSS module type declarations
- Fix ElectronAPI type definitions (ListSharesResult, ExportShareResult)
- Fix null checks for sessionInfo and session
- Change build script to use npx tsc

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:27:12 -08:00
hailin 88370691d1 fix(service-party-app): fix build script and remove icon requirement
- Rewrite build-windows.bat in English to avoid encoding issues
- Remove icon configuration from electron-builder.json (use default)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:19:19 -08:00
hailin 8733e49735 feat(service-party-app): 添加 Windows 一键编译脚本
添加 build-windows.bat 脚本,支持:
- 检查 Node.js 和 Go 环境
- 编译 TSS 子进程 (tss-party.exe)
- 安装 npm 依赖
- 编译 Electron 应用

使用方法: 双击运行 build-windows.bat

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 07:07:43 -08:00
hailin 9e21d8c8cd feat(api-gateway): 添加移动端系统维护接口的 Kong 路由
添加 /api/v1/mobile/system 路由到 admin-service,
使移动端可以访问 /mobile/system/maintenance-status 接口
检查系统维护状态。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 06:36:02 -08:00
hailin 0781c53101 fix(admin-service): 修复 getCurrentStatus 方法使用旧响应格式的问题
AdminMaintenanceController.getCurrentStatus() 方法仍使用旧的 inMaintenance 字段,
导致构建失败。更新为与 MobileMaintenanceController 一致的新格式:
- 使用 isUnderMaintenance 代替 inMaintenance
- 使用嵌套的 maintenance 对象包含详情
- 添加 remainingMinutes 字段计算

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 06:11:54 -08:00
hailin 6c4a40c42d fix(migration): 使数据库迁移脚本幂等化,支持重复执行
将 008_add_co_managed_wallet_fields.up.sql 改为幂等脚本:
- 使用 DO $$ ... IF NOT EXISTS 检查列是否存在再添加
- 使用 CREATE INDEX IF NOT EXISTS 创建索引
- 使用 DROP CONSTRAINT IF EXISTS 删除约束

这确保迁移脚本可以安全地多次执行,不会因列/索引已存在而失败。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 05:26:38 -08:00
hailin 75a9ffadef fix(admin-service): 修复移动端维护状态API响应格式不匹配问题
移动端期望的格式:
{
  "isUnderMaintenance": true,
  "maintenance": { "title", "message", "startTime", "endTime", "remainingMinutes" }
}

后端之前返回的格式:
{
  "inMaintenance": true,
  "title", "message", "endTime"
}

修改内容:
- 字段名 inMaintenance → isUnderMaintenance
- 嵌套维护详情到 maintenance 对象
- 添加 startTime 和 remainingMinutes 字段

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 05:10:18 -08:00
hailin cc3644de9d feat(mpc-system): 添加单服务管理命令到deploy.sh
新增命令:
- start-svc: 启动单个服务
- stop-svc: 停止单个服务
- restart-svc: 重启单个服务
- rebuild-svc: 重建并重启服务 (支持--no-cache)

支持开发模式和生产模式

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 04:23:22 -08:00
hailin 1af5780b19 fix(admin-service): 修复共管钱包 status 类型不匹配问题
使用 Prisma 生成的类型替代手动定义的接口:
- PrismaCoManagedWalletSession -> @prisma/client
- PrismaCoManagedWallet -> @prisma/client
- status 字段使用 PrismaWalletSessionStatus 枚举类型

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 00:02:18 -08:00
hailin e51450d9f9 chore(admin-service): 添加系统维护和共管钱包的数据库迁移
添加缺失的 migration 文件,包含:
- system_maintenances 表 (系统维护公告)
- WalletSessionStatus 枚举
- co_managed_wallet_sessions 表 (共管钱包会话)
- co_managed_wallets 表 (共管钱包记录)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 23:36:42 -08:00
hailin 1b5bcf3fda fix(co-managed-wallet): 修复向后兼容性问题并完善protobuf定义
## 变更概述
根据用户反馈,将 Session Coordinator 的函数签名改为可选参数模式,
确保新功能 100% 不影响现有的 keygen/sign 功能。

## 主要变更

### 1. Session Coordinator 向后兼容修复
- 保留原有 `ReconstructSession` 函数签名不变
- 新增 `ReconstructSessionOptions` 结构体存放可选参数
- 新增 `ReconstructSessionWithOptions` 函数支持新字段
- 原函数内部调用新函数,传入 nil options

### 2. Protobuf 定义更新
- CreateSessionRequest 新增字段:
  - wallet_name (field 10): 钱包名称
  - invite_code (field 11): 邀请码
- SessionInfo 新增字段:
  - wallet_name (field 8): 钱包名称
  - invite_code (field 9): 邀请码
- session_type 支持 "co_managed_keygen"

### 3. TSS Party 子进程修复
- 修复 tss.NewPartyID 参数类型错误 (big.Int)
- 修复 go.mod 依赖问题 (ed25519 replace)
- 删除未使用的变量

### 4. 清理错误生成的文件
- 删除 api/proto/*.pb.go (错误位置)
- 保留 api/grpc/coordinator/v1/*.pb.go (正确位置)

## 修改的文件

| 文件 | 变更类型 | 说明 |
|------|---------|------|
| mpc_session.go | 修改 | 添加 ReconstructSessionWithOptions |
| session_postgres_repo.go | 修改 | 使用新函数传入 options |
| session_cache_adapter.go | 修改 | 使用新函数传入 options |
| session_coordinator.proto | 修改 | 添加 wallet_name, invite_code 字段 |
| session_coordinator.pb.go | 重新生成 | 包含新 protobuf 字段 |
| tss-party/main.go | 修复 | NewPartyID 参数和未使用变量 |
| tss-party/go.mod | 修复 | ed25519 依赖替换 |

## 向后兼容性保证

- 所有现有代码调用 ReconstructSession 无需任何修改
- 数据库使用 COALESCE 处理 NULL 值
- Protobuf 新字段使用高序号,不影响现有消息解析
- **影响现有功能的风险: 0%**

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 23:33:40 -08:00
hailin dc16a616a5 fix(identity-service): 修复并发auto-login请求导致的唯一约束冲突
- 在创建新token前先撤销该设备的旧token
- 使用upsert替代create避免并发时refresh_token_hash唯一约束冲突

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 23:27:21 -08:00
hailin c328d8b59b feat(mobile-app,admin): 添加系统维护功能和通知徽章功能
系统维护功能:
- 后端: 添加系统维护配置实体、仓库和控制器
- 后端: 添加维护模式拦截器,返回503状态码
- admin-web: 添加系统维护管理页面,支持创建/编辑/开关维护配置
- mobile-app: 添加维护状态检查服务和阻断弹窗
- mobile-app: 在启动页、向导页集成维护检查
- mobile-app: 支持App从后台恢复时自动检查维护状态

通知徽章功能:
- 添加通知徽章Provider,监听登录状态自动刷新
- 底部导航栏"我的"标签显示未读通知红点
- 进入通知页面自动刷新徽章状态
- 切换账号、退出登录自动清除徽章

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 23:26:01 -08:00
hailin fea01642e7 feat(co-managed-wallet): 添加分布式多方共管钱包创建功能
## 功能概述
实现分布式多方共管钱包创建功能,包括 Admin-Web 扩展和 Service-Party 桌面应用。

## 主要变更

### 1. Admin-Web 扩展 (前端)
- 新增 CoManagedWalletSection 组件 (frontend/admin-web/src/components/features/co-managed-wallet/)
- 在授权管理页面添加共管钱包入口卡片
- 实现创建钱包向导: 配置 → 邀请 → 生成 → 完成
- 包含组件: ThresholdConfig, InviteQRCode, ParticipantList, SessionProgress, WalletResult

### 2. Admin-Service 后端 API
- 新增共管钱包领域实体和枚举 (domain/entities/co-managed-wallet.entity.ts)
- 新增 REST 控制器 (api/controllers/co-managed-wallet.controller.ts)
- 新增服务层 (application/services/co-managed-wallet.service.ts)
- 新增 Prisma 模型: CoManagedWalletSession, CoManagedWallet
- 更新 app.module.ts 注册新模块

### 3. Session Coordinator 扩展 (Go)
- 新增会话类型: SessionTypeCoManagedKeygen ("co_managed_keygen")
- 扩展 MPCSession 实体添加 WalletName 和 InviteCode 字段
- 更新 PostgreSQL 和 Redis 适配器支持新字段
- 新增数据库迁移: 008_add_co_managed_wallet_fields

### 4. Service-Party 桌面应用 (新项目)
- 位置: backend/mpc-system/services/service-party-app/
- 技术栈: Electron + React + TypeScript + Vite
- 包含模块:
  - gRPC 客户端 (连接 Message Router)
  - TSS 处理器 (子进程方式运行 Go TSS 协议)
  - 本地加密存储 (AES-256-GCM)
- 页面: Home, Join, Create, Session, Settings

## 修改的现有文件 (便于回滚)

1. backend/mpc-system/services/session-coordinator/domain/entities/mpc_session.go
   - 添加 SessionTypeCoManagedKeygen 常量
   - 添加 IsKeygen() 方法
   - 添加 WalletName, InviteCode 字段
   - 更新 ReconstructSession, ToDTO, SessionDTO

2. backend/mpc-system/services/session-coordinator/adapters/output/postgres/session_postgres_repo.go
   - 更新 SQL 查询包含 wallet_name, invite_code
   - 更新 Save, FindByUUID, FindByStatus 等方法
   - 更新 scanSessions, sessionRow

3. backend/mpc-system/services/session-coordinator/adapters/output/redis/session_cache_adapter.go
   - 更新 sessionCacheEntry 结构
   - 更新 sessionToCacheEntry, cacheEntryToSession

4. backend/services/admin-service/prisma/schema.prisma
   - 新增 WalletSessionStatus 枚举
   - 新增 CoManagedWalletSession, CoManagedWallet 模型

5. backend/services/admin-service/src/app.module.ts
   - 导入并注册共管钱包相关组件

6. frontend/admin-web/src/app/(dashboard)/authorization/page.tsx
   - 导入并添加 CoManagedWalletSection

7. frontend/admin-web/src/infrastructure/api/endpoints.ts
   - 添加 CO_MANAGED_WALLETS API 端点

## 回滚说明

如需回滚此功能:
1. 回滚数据库迁移: 运行 008_add_co_managed_wallet_fields.down.sql
2. 删除新增文件夹:
   - backend/mpc-system/services/service-party-app/
   - frontend/admin-web/src/components/features/co-managed-wallet/
   - backend/services/admin-service/src/**/co-managed-wallet*
3. 恢复修改的文件到前一个版本
4. 运行 prisma generate 重新生成 Prisma 客户端

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 21:39:07 -08:00