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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
- 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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
- 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>
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>
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>
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>
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>
- 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>
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>
- 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>
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>
添加共管钱包 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>
添加 50051:50051 端口映射,使开发模式与生产模式保持一致
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
将 proto 文件加载改为延迟加载模式,在 connect() 时才加载,
避免模块加载时 app.isPackaged 还未准备好导致的路径错误。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
将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>
添加 ProtoPackage 接口定义 proto 包结构类型,避免类型推断错误
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 新增 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>
- 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>
将 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>
- 在创建新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>
将动态 import('@/domain/value-objects') 改为静态 import
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
之前手动重试时如果状态是 generating/pending/deriving 会直接跳过,
导致卡住的钱包无法重新生成。现在增加超时检查(60秒),超时后允许强制重试。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
二要素 API (Id2MetaStandardVerify) 返回 440 无权限调用错误
暂时恢复使用三要素验证,保留二要素代码待开通权限后使用
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. 合同模板:身份证号和联系方式显示完整信息,不再使用星号掩码
2. 签署页面:checkbox 默认不选中,用户阅读到底部后才可点击确认
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
方便排查 WALLET_SERVICE_URL 环境变量是否正确加载
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
添加 /api/v1 前缀以匹配 identity-service 的全局路由配置
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
迁移脚本添加 IF NOT EXISTS 检查,避免重复执行时失败
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Kafka事件重复消费时,多个消费者同时创建签约任务会导致唯一约束冲突
改用upsert确保幂等性,如果orderNo已存在则返回现有记录
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
打印完整的response和ResultObject字段以便调试
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
修复 ValidationPipe whitelist 模式下属性被过滤的问题
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
验证码页面不再隐藏手机号中间数字,改为完整显示
格式: 138 1234 5678
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
JwtAuthGuard 设置 req.user.id,controller 需要使用相同字段名
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
将用户名格式从"榴莲皇后x号"改为"榴莲皇后xxx",
其中xxx是2-4字的创意组合,包含前缀、核心词和后缀。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
由于系统尚未实现权限管理功能,暂时将 checkPrivateProfileAccess
始终返回 false,禁止所有用户查看其他用户的手机号、邮箱等隐私信息。
后续实现权限系统后可恢复原有逻辑。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 在流水明细查询中排除冻结/解冻等临时记录
- 将"充值 (KAVA)"统一改为"充值"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
当认种用户没有授权时,不再提前返回,确保 checkAllTeamAutoUpgrade()
始终被调用,以正确检查所有市/省团队的自动升级条件。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 处理 TransformInterceptor 包装的响应格式 { success, data }
- 正确提取 data 字段中的用户信息
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
当查询火柴人排名时,如果没有评估记录,则实时调用 assessAndRankRegion
创建评估记录,避免用户需要等待凌晨1点的定时任务。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes:
- Add generic image upload API endpoint (POST /user/upload-image)
- Add uploadImage method in StorageService for backend
- Add uploadImage method in AccountService for frontend
- Add selfApplyStatus and selfApplyAuthorization methods in AuthorizationService
- Replace mock data with real API calls in authorization apply page
- Add API endpoints for self-apply status and self-apply authorization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change misleading "unexpected" log to correctly indicate idempotent behavior
- Add debug log for completed records being skipped in retry task
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
修复构建错误:Property 'registerByPhone' does not exist on type 'UserApplicationService'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- SYSTEM状态会被识别为冻结账户
- 必须是ACTIVE才能作为有效推荐人
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 添加ts-node依赖用于执行seed.ts
- 在start.sh中添加prisma db seed命令
- 与identity-service保持一致
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
修改 phoneNumber 字段类型从 string 改为 string | null,
以支持钱包重试场景中手机号可能为空的情况。
这个修复解决了 Docker 构建时的 TypeScript 编译错误:
- Type 'string | null' is not assignable to type 'string'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Kong 网关:
- 添加 /api/v1/dashboard 路由到 reporting-service
Admin-Web 前端:
- 修复所有 API endpoints 添加 /v1 前缀
- 确保与 Kong 路由配置一致
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- health 检查列表添加 blockchain-service
- migrate 服务列表添加 blockchain-service
- 帮助文档更新服务列表
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
后端:
- blockchain-service: 新增 revokeMnemonic() 方法和 POST /internal/mnemonic/revoke API
- identity-service: 新增 POST /user/mnemonic/revoke 用户端API
- 挂失后助记词状态变为 REVOKED,无法用于账户恢复
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
扫描区块时检测缓存是否为空,如果为空则自动从数据库重新加载地址缓存,
避免因 Redis 数据丢失导致充值漏检。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
HOT_WALLET_USERNAME=rwadurian-system-hot-wallet-01
HOT_WALLET_ADDRESS=0x895aaf83C57f807416E3BbBd093d7aB74a6FDd33
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 后端新增 GET /referral/user/:accountSequence/direct-referrals API
- 前端新增伞下树组件,支持懒加载、缓存、展开/收起
- 使用 CustomPaint 绘制父子节点连接线
- 超出屏幕宽度时显示省略号,点击弹出底部列表
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
将新用户默认昵称从「用户D2512140001」改为「用户1」,
使用 accountSequence.dailySequence 提取当日序号并去除前导零。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
省团队(AUTH_PROVINCE_COMPANY)和市团队(AUTH_CITY_COMPANY)收益分配不再要求
认种者选择的省市与被授权用户的省市一致,只要在推荐链上有授权即可获得收益。
省区域(PROVINCE_COMPANY)和市区域(CITY_COMPANY)收益仍保持省市匹配要求。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Province area (PROVINCE_COMPANY): 50,000 trees initial target
- City area (CITY_COMPANY): 10,000 trees initial target
- Apply consistent assessment logic: pre-target rewards go to system account, post-target rewards go to company
- Auto-activate benefit when target is reached
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add POST /admin/authorizations/auth-province-company for 省团队授权
- Add POST /admin/authorizations/auth-city-company for 市团队授权
- Add team uniqueness validation for all province/city authorization types
- Add domain events: AuthProvinceCompanyGrantedEvent, AuthCityCompanyGrantedEvent
- Add factory methods: createAuthProvinceCompanyByAdmin, createAuthCityCompanyByAdmin
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When tree count + current team count equals initial target exactly,
the benefit was not being activated. Now properly handles three cases:
- afterPlantingCount < target: still in assessment, no activation
- afterPlantingCount === target: reached target, activate benefit
- afterPlantingCount > target: exceeded target, split rewards & activate
Applied to community, province team, and city team reward distribution.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Assessment calculation now uses subordinate team count (excluding self planting)
- Added selfPlantingCount to referral-service API response
- Added subordinateTeamPlantingCount getter to TeamStatistics interface
- Updated all assessment checks to use subordinateTeamPlantingCount
2. Auto-activate benefit when assessment target is reached
- Added tryActivateBenefit helper method
- Community, province team, and city team reward distribution methods
now automatically activate benefit when target is reached
3. Fixed event consumer to support AUTHORIZED status (admin grants)
- Previously only checked PENDING status for activation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When calculating reward distribution for community/province/city benefits,
the totalTeamPlantingCount was being read after referral-service had already
updated it with the current planting. This caused incorrect distribution
where benefits were given to the community holder before they completed
their assessment target.
Fix: Subtract treeCount from rawTeamCount to restore the "before planting"
team count, ensuring correct assessment calculation.
Affected methods:
- getCommunityRewardDistribution
- getProvinceTeamRewardDistribution
- getCityTeamRewardDistribution
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed findActiveCommunityByAccountSequences to not require benefitActive=true.
This allows communities that haven't passed assessment to still be shown as
parent communities in the UI, while reward distribution logic remains unchanged.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Admin grant APIs now default to requiring assessment (benefitActive=false).
Added optional skipAssessment parameter to bypass assessment when needed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive logging to RewardCalculationService for easier debugging:
- Log start/end of calculateRewards with order info
- Log each benefit calculation with input parameters
- Log distribution results from authorization-service calls
- Log share right decisions (settleable vs pending)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add proper assessment/考核 logic for all 5 benefit types:
- Community (80 USDT): 10-tree initial assessment, splits rewards between
current community and parent/headquarters based on progress
- Province Team (20 USDT): 500-tree initial assessment for AUTH_PROVINCE_COMPANY
- Province Area (15 USDT + 1%): 50000-tree target for PROVINCE_COMPANY,
routes to system account until target reached
- City Team (40 USDT): 100-tree initial assessment for AUTH_CITY_COMPANY
- City Area (35 USDT + 2%): 10000-tree target for CITY_COMPANY,
routes to system account until target reached
Changes:
- authorization-service: Add 4 new distribution API endpoints and application
service methods for province/city team/area reward distribution
- authorization-service: Add repository methods for querying authorizations
including benefitActive=false records
- reward-service: Update client to call new distribution APIs
- reward-service: Modify calculation methods to return multiple reward entries
based on assessment distribution plan
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The authorization-service uses TransformInterceptor which wraps all API
responses in a standard format: { success, data, timestamp }
Before this fix, the AuthorizationServiceClient was reading:
data.accountSequence (undefined - wrong level)
After this fix, it correctly reads:
result.data.accountSequence
This ensures that COMMUNITY_RIGHT is allocated to the correct authorized
community user (e.g., accountSequence=6) instead of falling back to
HEADQUARTERS (accountSequence=1).
Changes in reward-service:
- Added AuthorizationServiceResponse<T> interface
- Added NearestAuthorizationResult interface
- Updated findNearestAuthorizedProvince() to parse wrapped response
- Updated findNearestAuthorizedCity() to parse wrapped response
- Updated findNearestCommunity() to parse wrapped response
- Added debug logging for findNearestCommunity
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The authorization-service uses TransformInterceptor which wraps all API
responses in a standard format: { success, data, timestamp }
Before this fix, the AuthorizationServiceClient was reading:
response.data.accountSequence (undefined)
After this fix, it correctly reads:
response.data.data.accountSequence
This ensures that nearestCommunity, nearestProvinceAuth, and nearestCityAuth
are properly extracted from the wrapped response, allowing community benefits
to be correctly allocated to authorized users instead of falling back to
SYSTEM_HEADQUARTERS_COMMUNITY.
Changes:
- Added AuthorizationServiceResponse<T> interface to model wrapped response
- Updated findNearestCommunity() to use wrapped response type
- Updated findNearestProvince() to use wrapped response type
- Updated findNearestCity() to use wrapped response type
- Updated catchError handlers to return properly structured fallback data
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
两个服务互相调用,形成循环依赖。移除 depends_on 约束,使用 fallback 机制处理服务暂时不可用的情况。
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The OutboxPublisherService sends payload fields directly at the message
root level, not nested under a 'payload' property. Update the consumer
to read fields directly from message instead of message.payload.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add publishSummaryUpdatesToOutbox method to write reward.summary.updated
events to outbox table for synchronization to wallet-service.
Events are published after:
- distributeRewards: when rewards are distributed to users
- claimPendingRewardsForUser: when pending rewards are claimed
- settleRewards: when rewards are settled
- expireOverdueRewards: when rewards expire
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Both referral-service and authorization-service use global prefix /api/v1,
so the client calls need to include this prefix.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BigInt cannot be serialized by JSON.stringify, convert to string
before returning from the API endpoint.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Expired referral rewards are transferred to headquarters community (seq=1),
not operation account. Remove the unused method to avoid confusion.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /referral/chain/{userId} API in referral-service for getting referral chain with hasPlanted status
- Add internal authorization APIs in authorization-service:
- GET /authorization/nearest-community: find nearest community in referral chain
- GET /authorization/nearest-province: find nearest province company in referral chain
- GET /authorization/nearest-city: find nearest city company in referral chain
- Add repository methods for finding active authorizations by accountSequence
- Update reward-service client to use accountSequence parameter
These APIs enable reward-service to correctly distribute:
- 分享权益 (share benefit): to referrer with hasPlanted=true
- 社区权益 (community benefit): to nearest community leader
- 省团队权益 (province team benefit): to nearest province company
- 市团队权益 (city team benefit): to nearest city company
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add internal referral-chain API in referral-service for getting ancestor path and team members
- Extend ReferralServiceClient to call referral-chain API
- Add findActiveCommunityByAccountSequences repository method
- Add getCommunityHierarchy application service method
- Add GET /authorizations/my/community-hierarchy endpoint
- Update frontend with CommunityHierarchy model and getMyCommunityHierarchy method
API returns:
- myCommunity: user's own community authorization (if any)
- parentCommunity: nearest parent community (defaults to 总部社区 if none)
- childCommunities: nearest child communities in user's team
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add new endpoint POST /api/v1/admin/authorizations/community
that allows administrators to directly authorize users as community
managers without requiring the user to self-apply first.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
reward-service was trying to connect to localhost instead of Docker
network service names, causing connection refused errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add InternalTeamStatisticsController in referral-service for service-to-service API
- Create ReferralServiceClient in authorization-service to fetch real team statistics
- Replace MockTeamStatisticsRepository with real HTTP client implementation
- Configure docker-compose with REFERRAL_SERVICE_URL for authorization-service
This enables authorization-service to get real team planting counts from
referral-service for authorization assessment and activation logic.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add BACKUP_SERVICE_URL, BACKUP_SERVICE_ENABLED, and SERVICE_JWT_SECRET
to mpc-service configuration to enable backup share storage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- mpc-service: add accountSequence param to StoreBackupShareParams
- mpc-service: pass accountSequence when calling backup-service
- backup-service: add logging for store request success/failure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- reward-service: add accountSequence to aggregates, services, tests
- authorization-service: fix UserId/AdminUserId to accept accountSequence, add findByAccountSequence to repositories
- referral-service: fix test files for accountSequence changes
- Add migration files for reward-service and authorization-service
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- planting-service: extract accountSequence from JWT, pass to referral-service
- referral-service: query by accountSequence instead of userId
- reward-service: add accountSequence field to schema and all layers
- wallet-service: prioritize accountSequence lookup over userId
- authorization-service: change userId from String to BigInt, add accountSequence
This change ensures consistent cross-service user identification using
accountSequence (8-digit unique business ID) instead of internal database IDs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously used amount (raw) with hardcoded 18 decimals, which caused
incorrect formatting for USDT (6 decimals). Now uses amountFormatted
which is already correctly formatted during deposit detection.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The updateConfirmations logic only checked for DETECTED status
when confirming, so deposits that transitioned to CONFIRMING
would never be confirmed even with enough confirmations.
Changed condition to accept both DETECTED and CONFIRMING status.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously decimals were hardcoded to 18, causing incorrect amount parsing
for USDT which uses 6 decimals. Now reads decimals() from ERC20 contract
when processing deposits.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes BigInt conversion error when consuming planting.order.paid events.
The orderId from planting-service is a string format like PLT1765391584505Q0Q6QD,
not a numeric value.
Changes:
- reward-source.vo.ts: sourceOrderId (bigint) → sourceOrderNo (string)
- reward-calculation.service.ts: updated all method signatures
- reward-application.service.ts: updated distributeRewards params
- event-consumer.controller.ts: removed BigInt() conversion
- prisma schema: changed column type to VarChar(50)
- mapper/repository: updated field names accordingly
- Removed incompatible prisma.config.ts (Prisma 7.x format)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Allow same device to create multiple accounts for testing purposes.
Original validation code is commented out and can be restored later.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EventConsumerController was not registered in KafkaModule, causing
@MessagePattern('planting.order.paid') to never be subscribed.
This resulted in reward-service having empty memberAssignment.
Also added forwardRef to resolve circular dependency between
ApplicationModule and InfrastructureModule.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
authorization-service uses @EventPattern('planting-events') to consume
planting events but was missing connectMicroservice() configuration.
Without this, the service could not receive planting events, causing:
- Province team benefits (20 USDT) not distributed
- City team benefits (40 USDT) not distributed
- Community benefits (80 USDT) not distributed
- Monthly assessment data not updated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The @MessagePattern decorator requires connectMicroservice() to work.
Without this configuration, services could not consume Kafka messages
via @MessagePattern, causing events to be lost.
Fixed services:
- planting-service: ACK consumption
- identity-service: event consumption
- reward-service: event consumption
blockchain-service already had this configured.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add processed_events table to track handled events
- Check eventId before processing planting.created events
- Skip duplicate events and still send ACK to stop retries
This prevents data accumulation when Kafka events are redelivered
due to ACK failures or consumer timeouts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added comprehensive logging to track:
- Request parameters (userId, amount, orderId)
- Wallet balance information
- Balance check results
- Freeze operation status
This helps debug the 400 error when planting-service calls wallet-service.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backend:
- Add initialTargetTreeCount, currentTreeCount, monthlyTargetTreeCount
fields to AuthorizationDTO and AuthorizationResponse
- Query TeamStatistics to populate current tree count in getUserAuthorizations
Frontend:
- Update AuthorizationResponse to parse new progress fields
- Replace hardcoded community assessment values with real API data
- Show authorization status: red (unauthorized), orange (pending), green (active)
- Display progress bar and target requirements based on benefit status
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## wallet-service
- Add freeze/confirm/unfreeze API endpoints for planting deduction
- Add deductFrozen method to wallet-account aggregate
- Add PLANT_FREEZE and PLANT_UNFREEZE ledger entry types
- Add idempotency check in deductForPlanting
- Fix test files to include accountSequence parameter
## planting-service
- Add PaymentCompensation model and migration
- Add payment-compensation.repository.ts
- Add payment-compensation.service.ts (background job for retry)
- Add HTTP retry mechanism with exponential backoff
- Refactor payOrder to use freeze → transaction → confirm flow
- Create compensation record on unfreeze failure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Backend: batch query TeamStatistics to get each direct referral's
personalPlantingCount and teamPlantingCount
- Updated DTO and query interfaces with new fields
- Frontend: parse and display "个人/团队" counts in profile page
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Return accountSequence instead of userId for direct referral list display
2. Create missing team statistics record when referrer doesn't have one
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add internal APIs to diagnose and repair historical deposit issues:
- GET /internal/deposit-repair/diagnose - Query unnotified deposits
- POST /internal/deposit-repair/repair/:depositId - Repair single deposit
- POST /internal/deposit-repair/repair-all - Batch repair all pending deposits
- POST /internal/deposit-repair/reset-failed-outbox - Reset failed outbox events
The repair service creates new outbox events for CONFIRMED deposits
that were never notified to wallet-service.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cast ackMessage to Record<string, unknown> for KafkaService compatibility.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Refactor EventAckPublisher to use KafkaService instead of ClientKafka
- Add kafka/index.ts export file
- Register EventAckPublisher in InfrastructureModule providers and exports
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PlantingService for planting order management
- createOrder: Create new planting order
- selectProvinceCity: Select province and city
- confirmProvinceCity: Confirm province/city selection
- payOrder: Pay for order
- getOrder/getMyOrders: Query orders
- cancelOrder: Cancel unpaid orders
- Register PlantingService in DI container
- Update planting_quantity_page to create order before navigation
- Update planting_location_page to call real API endpoints
- Fix referral-service event-ack.publisher to use KafkaService
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- markAsConfirmed now uses aggregateId + eventType for precise matching
- Prevents accidentally confirming multiple events for the same order
- EventAckController passes eventType to markAsConfirmed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Leaderboard functionality has been moved to leaderboard-service.
Remove the dependency and set leaderboardRank to null.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix brokers type narrowing issue in deposit-ack-consumer.service.ts
- Add Prisma.InputJsonValue type cast for payload in outbox-event.repository.impl.ts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove duplicate leaderboard functionality from referral-service.
All leaderboard features should now go through leaderboard-service.
Removed files:
- api/controllers/leaderboard.controller.ts
- api/dto/leaderboard.dto.ts
- application/queries/get-leaderboard.query.ts
- infrastructure/cache/leaderboard-cache.service.ts
- Related test files
Modified:
- TeamStatisticsService: removed getLeaderboard() and getUserRank()
- Module registrations updated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The /api/v1/leaderboard route was duplicated in both referral-service
and leaderboard-service. Since leaderboard-service is the dedicated
ranking service with more features (daily/weekly/monthly rankings,
virtual accounts, snapshots), all leaderboard requests should be
routed to leaderboard-service.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ClientsModule with KAFKA_SERVICE registration
- Add EventAckPublisher to KafkaModule providers/exports
- Move EventConsumerController to AppModule (has access to repositories)
Resolves: Nest can't resolve dependencies of EventConsumerController
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add routes for APIs that were implemented but missing from Kong:
- /api/v1/backup-share (backup-service)
- /api/v1/virtual-accounts (leaderboard-service)
- /api/v1/export (reporting-service)
- /api/v1/analytics (presence-service)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement Outbox Pattern with consumer ACK to ensure 100% reliable event
delivery between blockchain-service and wallet-service:
blockchain-service:
- Add OutboxEvent model to Prisma schema with status tracking
- Create outbox repository interface and implementation
- Modify deposit-detection.service to write events to outbox
- Add outbox-publisher.service with cron jobs for publishing/retry
- Add deposit-ack-consumer.service to receive ACK from wallet-service
- Add publishRaw method to event-publisher.service
wallet-service:
- Modify deposit-confirmed.handler to send ACK after successful processing
- Add wallet.deposit.credited topic mapping for ACK events
Event flow:
1. Deposit detected → written to outbox (status: PENDING)
2. Outbox publisher sends to Kafka → status: SENT
3. wallet-service processes and sends ACK → status: ACKED
4. Events without ACK are retried with exponential backoff
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added migration_lock.toml for planting-service, reward-service,
referral-service, and authorization-service to ensure Prisma
can properly track migration provider.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement reliable event delivery using Outbox Pattern with consumer confirmation:
## planting-service (Producer)
- Add OutboxEvent table with status: PENDING → SENT → CONFIRMED
- Add OutboxRepository with transaction support and timeout handling
- Add OutboxPublisherService with polling, timeout check, and retry
- Add EventAckController to receive consumer confirmations
- Update UnitOfWork to save outbox events atomically with business data
- Update PlantingApplicationService to use outbox pattern
- Update PoolInjectionService to use outbox pattern
## Consumer Services
- Add EventAckPublisher to reward-service, referral-service, authorization-service
- Update event handlers to send acknowledgment after successful processing
## Event Flow
1. Business data + outbox events saved in same transaction
2. OutboxPublisher polls and sends to Kafka, marks as SENT
3. Consumer processes event and sends ack to planting.events.ack
4. EventAckController receives ack and marks as CONFIRMED
5. Timeout check resets SENT→PENDING for retry (max 5 times)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add initial migration for wallet-service (wallet_accounts, ledger_entries, deposit/settlement/withdrawal orders)
- Add initial migration for planting-service (planting_orders, fund_allocations, positions, batches, events)
- Both services now have migrations that will be automatically applied on container startup via `prisma migrate deploy`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add /api/v1/wallet route to Kong gateway to support:
- GET /wallet/my-wallet
- POST /wallet/claim-rewards
- POST /wallet/settle
- POST /wallet/withdraw
- GET /wallet/withdrawals
- GET /wallet/ledger/my-ledger
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change BoxFit.cover to BoxFit.fitWidth for guide page images
- Add screen info logging (resolution, pixel ratio, aspect ratio)
- Add detailed avatar loading logs in frontend and backend
- Log avatarUrl from DB and API response during recovery
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added debug logs in:
- ProfilePage._loadUserData() - local storage data
- ProfilePage._loadMeData() - API response and sync conditions
- ProfilePage._buildAvatarContent() - avatar rendering decision
- UserApplicationService.getMe() - backend avatarUrl value
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When recovering account via mnemonic or phone, check if avatarUrl is null
and regenerate a new random avatar SVG if needed. This fixes the bug where
users see no avatar after recovering their account.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Downloads route was getting /api/v1 prefix, making it inaccessible at /downloads
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Set BASE_URL to public API gateway address so mobile app can download
APK files from correct URL instead of localhost.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mobile app was calling /api/app/version/check but this route was not
configured in Kong gateway. Added admin-mobile-version route to forward
these requests to admin-service.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changelog: allow empty value, return default "No changelog provided"
- MinOsVersion: allow empty value and single number format (e.g., "24" -> "24.0")
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- admin-service: provide default empty string for optional changelog
- profile page: fix map literal syntax for referrals list
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- VersionName value object now accepts x.y.z.w format
- changelog field is now optional in upload version DTO
- profile page: ensure expired section has full width
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Allow version names like 1.0.0.4 in addition to the standard x.y.z format.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The balance display was incorrect because decimals were hardcoded to 18,
but USDT uses 6 decimals. Now reads decimals() from the ERC20 contract.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
sessionTimeout remains at 5 minutes (needed for long MPC operations),
but rebalanceTimeout only affects joining consumer group, not processing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Root cause: When mpc-service restarts, the old Kafka consumer doesn't
properly disconnect, causing the broker to wait for sessionTimeout
(~5 minutes) before completing rebalance. This blocks app startup.
Solution: Enable NestJS shutdown hooks with app.enableShutdownHooks().
This ensures onModuleDestroy() is called on SIGTERM/SIGINT, which calls
consumer.disconnect() and allows immediate rebalance on next startup.
Also reverted the "don't await consumer.run()" workaround since the
proper fix is graceful shutdown.
Sources:
- https://github.com/tulios/kafkajs/issues/807
- https://kafka.js.org/docs/consuming🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>