staking_service.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import 'package:dio/dio.dart';
  2. import 'package:flutter/foundation.dart';
  3. import '../../core/utils/spot_transfer_asset.dart';
  4. import '../models/finance/airdrop_eligibility.dart';
  5. import '../models/finance/airdrop_record_item.dart';
  6. import '../models/finance/staking_config.dart';
  7. import '../models/finance/staking_wallet_balance.dart';
  8. class StakingService {
  9. const StakingService(this._dio);
  10. final Dio _dio;
  11. void _log(String message) {
  12. if (kDebugMode) {
  13. debugPrint('[Airdrop][Service] $message');
  14. }
  15. }
  16. /// 与 Web `STAKING_COIN_UNIT` / `stakingCoinUnit()` 默认币种一致
  17. static const stakingCoinUnit = 'IBIT';
  18. Future<List<StakingConfig>> getConfigs() async {
  19. final resp =
  20. await _dio.post<Map<String, dynamic>>('uc/staking/config/list');
  21. final raw = resp.data?['data'];
  22. if (raw is List) {
  23. return raw
  24. .whereType<Map<String, dynamic>>()
  25. .map(StakingConfig.fromJson)
  26. .toList();
  27. }
  28. return const [];
  29. }
  30. Future<StakingConfig?> getMinOpenConfig() async {
  31. final resp =
  32. await _dio.post<Map<String, dynamic>>('uc/staking/config/min-open');
  33. final raw = resp.data?['data'];
  34. if (raw is Map<String, dynamic>) {
  35. return StakingConfig.fromJson(raw);
  36. }
  37. return null;
  38. }
  39. Future<void> submitStake({
  40. required int configId,
  41. required String amount,
  42. }) async {
  43. await _dio.post<dynamic>(
  44. 'uc/staking/stake',
  45. data: {'configId': configId, 'amount': amount},
  46. options: Options(contentType: Headers.formUrlEncodedContentType),
  47. );
  48. }
  49. Future<StakingWalletBalance> getStakingWalletBalance(String coinUnit) async {
  50. final resp = await _dio.post<Map<String, dynamic>>(
  51. 'uc/staking/wallet',
  52. data: {'coinUnit': coinUnit},
  53. options: Options(contentType: Headers.formUrlEncodedContentType),
  54. );
  55. final raw = resp.data?['data'];
  56. if (raw is Map<String, dynamic>) {
  57. return StakingWalletBalance.fromJson(raw);
  58. }
  59. return StakingWalletBalance.empty(coinUnit);
  60. }
  61. /// 可提现账户指定币种可用余额(对齐 Web `loadFundCoinBalance`)
  62. ///
  63. /// POST `/uc/asset/wallet`,解析逻辑同 `parseFundWalletBalances`。
  64. Future<String> getFundingWalletAvailable(String coinUnit) async {
  65. try {
  66. final resp = await _dio.post<Map<String, dynamic>>(
  67. 'uc/asset/wallet',
  68. data: <String, dynamic>{},
  69. );
  70. final map = parseFundWalletBalances(resp.data);
  71. return map[coinUnit.toUpperCase()] ?? '0';
  72. } catch (_) {
  73. return '0';
  74. }
  75. }
  76. Future<AirdropEligibility> getAirdropEligibility() async {
  77. final resp =
  78. await _dio.post<Map<String, dynamic>>('uc/airdrop/eligibility');
  79. final raw = resp.data?['data'];
  80. _log(
  81. 'eligibility response dataType=${raw.runtimeType} keys=${raw is Map<String, dynamic> ? raw.keys.toList() : 'n/a'}',
  82. );
  83. if (raw is Map<String, dynamic>) {
  84. return AirdropEligibility.fromJson(raw);
  85. }
  86. _log('eligibility fallback to empty because data is not map');
  87. return AirdropEligibility.empty();
  88. }
  89. Future<void> claimAirdrop() async {
  90. await _dio.post<Map<String, dynamic>>('uc/airdrop/claim');
  91. }
  92. Future<AirdropRecordPage> getAirdropRecords({
  93. required int pageNo,
  94. int pageSize = 10,
  95. }) async {
  96. final resp = await _dio.post<Map<String, dynamic>>(
  97. 'uc/airdrop/records',
  98. data: {'pageNo': pageNo, 'pageSize': pageSize},
  99. options: Options(contentType: Headers.formUrlEncodedContentType),
  100. );
  101. final raw = resp.data?['data'];
  102. _log(
  103. 'records response pageNo=$pageNo pageSize=$pageSize dataType=${raw.runtimeType}');
  104. if (raw is! Map<String, dynamic>) {
  105. _log('records fallback empty: data is not map');
  106. return const AirdropRecordPage(content: [], totalPages: 0, pageNo: 1);
  107. }
  108. final contentRaw = raw['content'];
  109. final content = contentRaw is List
  110. ? contentRaw
  111. .whereType<Map<String, dynamic>>()
  112. .map(AirdropRecordItem.fromJson)
  113. .toList()
  114. : <AirdropRecordItem>[];
  115. final totalPages = int.tryParse('${raw['totalPages'] ?? 0}') ?? 0;
  116. // 后端常见两种分页字段:
  117. // 1) pageNo: 1 基页码
  118. // 2) number: 0 基页码(Spring Pageable)
  119. final rawPageNo = int.tryParse('${raw['pageNo'] ?? ''}');
  120. final rawNumber = int.tryParse('${raw['number'] ?? ''}');
  121. final parsedPageNo = rawPageNo ?? ((rawNumber ?? (pageNo - 1)) + 1);
  122. _log(
  123. 'records parsed pageNo=$parsedPageNo(totalPages=$totalPages, rawPageNo=$rawPageNo, rawNumber=$rawNumber) contentRawType=${contentRaw.runtimeType} contentCount=${content.length}',
  124. );
  125. return AirdropRecordPage(
  126. content: content,
  127. totalPages: totalPages,
  128. pageNo: parsedPageNo,
  129. );
  130. }
  131. }