fix(mining-app): 统一贡献值页与兑换页的剩余积分股计算方式

贡献值页原先使用 sharePoolBalance API (Pool A + Pool B 余额) 显示剩余积分股,
兑换页使用公式 totalShares - totalMined - blackHoleAmount 计算。
两者显示结果不一致 (差异约 228,343 积分股)。

根本原因调查:
- mining-wallet-service 的 Kafka 消费者在服务重启期间丢失了部分事件
- Pool B 仅处理了 15.7% 的挖矿分配事件 (28,261/180,497)
- Pool A 遗漏了 66,591 笔销毁扣减事件
- 池账户余额是通过 Kafka 事件维护的记账台账,存在消费遗漏
- totalShares - totalMined - blackHoleAmount 公式基于实际挖矿和销毁数据计算,是数学上的 ground truth
- 实际用户分配和销毁均 100% 准确,仅池账户记账有偏差

修复方案:
- 贡献值页改用 marketOverviewProvider (同兑换页)
- 使用公式 totalShares - totalMined - blackHoleAmount 计算剩余积分股
- 两个页面现在显示完全一致的数据

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-02 22:28:55 -08:00
parent a7dd926877
commit 0576733579
1 changed files with 21 additions and 10 deletions

View File

@ -1,3 +1,4 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
@ -5,10 +6,12 @@ import '../../../core/constants/app_colors.dart';
import '../../../core/router/routes.dart';
import '../../../core/utils/format_utils.dart';
import '../../../domain/entities/contribution.dart';
import '../../../domain/entities/market_overview.dart';
import '../../../domain/entities/share_account.dart';
import '../../providers/user_providers.dart';
import '../../providers/contribution_providers.dart';
import '../../providers/mining_providers.dart';
import '../../providers/trading_providers.dart';
import '../../widgets/shimmer_loading.dart';
class ContributionPage extends ConsumerWidget {
@ -25,8 +28,8 @@ class ContributionPage extends ConsumerWidget {
final contributionAsync = ref.watch(contributionProvider(accountSequence));
//
final estimatedEarnings = ref.watch(estimatedEarningsProvider(accountSequence));
//
final sharePoolAsync = ref.watch(sharePoolBalanceProvider);
// = totalShares - totalMined - blackHoleAmount
final marketAsync = ref.watch(marketOverviewProvider);
// 730
final shareAccountAsync = ref.watch(shareAccountProvider(accountSequence));
@ -35,8 +38,8 @@ class ContributionPage extends ConsumerWidget {
final contribution = contributionAsync.valueOrNull;
final hasError = contributionAsync.hasError;
final error = contributionAsync.error;
final isSharePoolLoading = sharePoolAsync.isLoading;
final sharePoolBalance = sharePoolAsync.valueOrNull;
final isMarketLoading = marketAsync.isLoading;
final market = marketAsync.valueOrNull;
return Scaffold(
backgroundColor: AppColors.backgroundOf(context),
@ -45,7 +48,7 @@ class ContributionPage extends ConsumerWidget {
child: RefreshIndicator(
onRefresh: () async {
ref.invalidate(contributionProvider(accountSequence));
ref.invalidate(sharePoolBalanceProvider);
ref.invalidate(marketOverviewProvider);
ref.invalidate(shareAccountProvider(accountSequence));
},
child: hasError && contribution == null
@ -74,7 +77,7 @@ class ContributionPage extends ConsumerWidget {
sliver: SliverList(
delegate: SliverChildListDelegate([
//
_buildTotalContributionCard(context, ref, contribution, isLoading, sharePoolBalance, isSharePoolLoading),
_buildTotalContributionCard(context, ref, contribution, isLoading, market, isMarketLoading),
const SizedBox(height: 16),
//
_buildThreeColumnStats(context, ref, contribution, isLoading),
@ -165,8 +168,8 @@ class ContributionPage extends ConsumerWidget {
WidgetRef ref,
Contribution? contribution,
bool isLoading,
SharePoolBalance? sharePoolBalance,
bool isSharePoolLoading,
MarketOverview? market,
bool isMarketLoading,
) {
final isDark = AppColors.isDark(context);
final total = contribution?.totalContribution ?? '0';
@ -219,13 +222,21 @@ class ContributionPage extends ConsumerWidget {
'100亿销毁剩余量: ',
style: TextStyle(fontSize: 12, color: AppColors.textSecondaryOf(context)),
),
isSharePoolLoading
isMarketLoading
? const ShimmerText(
placeholder: '----',
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: _orange),
)
: Text(
hideAmounts ? '******' : formatAmount(sharePoolBalance?.total ?? '0'),
hideAmounts
? '******'
: market != null
? formatAmount(
(Decimal.parse(market.totalShares) -
Decimal.parse(market.totalMined) -
Decimal.parse(market.blackHoleAmount))
.toString())
: '0',
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: _orange),
),
],