Files
hash-wallet/lib/view_model/dashboard/nft_send_view_model.dart
T
jwinterm dd449f2594 Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535
upstream commits, including the Monero silent-open fix, Monero on Trezor,
Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin
sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive
adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9.

Merged with both sides normalized first (import rename, 100-column
format), which took the conflict count from 614 paths to 322. The
procedure is now scripts/upstream_sync.sh.

Kept from Hash Bags:
- WalletType enum order and Hive IDs. SQLite stores WalletType.index and
  our order differs from upstream's; taking theirs would reread saved
  wallets as other chains. Our frozen adapter keeps the shipped IDs.
- The 1.0.2 legacy PIN and wallet-password decode and self-heal.
- Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new
  Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out.
- Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me),
  converted to upstream's isDefault node-list key; Wownero is back in the
  node reset list.
- Privacy: address resolvers limited to domain-anchored sources again;
  prices.neroswap.com for fiat; upstream's charts tab, which reads
  prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false.
- Lightning stays behind FeatureFlag.isLightningEnabled, including
  upstream's new post-seed Lightning username step.
- Our icon pipeline, branding and bundle identifiers.

Twelve new upstream widget tests are skipped: test and code are unchanged
from v6.5.0 apart from renames (not rerun on a pristine upstream checkout;
upstream's top_bar_test does not compile against its own TopBar).
flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped;
flutter build bundle compiles. Built with Flutter 3.41.9.
2026-10-24 23:49:29 -04:00

141 lines
4.1 KiB
Dart

import "dart:async";
import "package:hash_wallet/core/execution_state.dart";
import "package:hash_wallet/entities/solana_nft_asset_model.dart";
import "package:hash_wallet/generated/i18n.dart";
import "package:hash_wallet/store/app_store.dart";
import "package:hash_wallet/view_model/contact_list/contact_list_view_model.dart";
import "package:cw_core/exceptions.dart";
import "package:cw_core/pending_transaction.dart";
import "package:cw_core/utils/print_verbose.dart";
import "package:cw_core/wallet_base.dart";
import "package:mobx/mobx.dart";
part "nft_send_view_model.g.dart";
class NFTSendViewModel = NFTSendViewModelBase with _$NFTSendViewModel;
abstract class NFTSendViewModelBase with Store {
NFTSendViewModelBase(this._appStore, this._contactListViewModel);
final AppStore _appStore;
final ContactListViewModel _contactListViewModel;
@observable
ExecutionState state = InitialExecutionState();
@observable
PendingTransaction? pendingTransaction;
bool shouldRequireTOTP2FAFor(String destinationAddress) {
final settingsStore = _appStore.settingsStore;
final isContact = _contactListViewModel.contactsToShow
.any((contact) => contact.address == destinationAddress);
if (isContact) {
return settingsStore.shouldRequireTOTP2FAForSendsToContact;
}
final isInternalWallet = _contactListViewModel.walletContactsToShow
.any((contact) => contact.address == destinationAddress);
if (isInternalWallet) {
return settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets;
}
return settingsStore.shouldRequireTOTP2FAForSendsToNonContact;
}
@action
Future<void> createTransaction(SolanaNFTAssetModel asset, String destinationAddress) async {
state = IsExecutingState();
try {
throw UnsupportedError('Solana NFTs are not supported');
state = ExecutedSuccessfullyState();
} catch (e) {
pendingTransaction = null;
state = FailureState(_errorMessage(e));
}
}
@action
Future<void> commitTransaction() async {
state = IsExecutingState();
try {
final wallet = _appStore.wallet!;
final transaction = pendingTransaction!;
await transaction.commit();
final signature = transaction.id;
pendingTransaction = null;
state = ExecutedSuccessfullyState();
if (signature.isNotEmpty) {
unawaited(_pollForTransaction(wallet, signature));
}
} catch (e) {
state = FailureState(_errorMessage(e));
}
}
/// Re-entrancy latch for the send button, held only while the tap handler is
/// inside [AuthService.authenticateAction].
///
/// Deliberately not [state]: the auth sheet is pushed closable, and closing it
/// pops the route without ever invoking onAuthenticationFinished, so a flag
/// that waits on the auth result would never be cleared. The caller clears
/// this in a finally instead. Once auth succeeds, createTransaction takes over
/// the guard by setting IsExecutingState synchronously.
bool _isStartingSend = false;
bool get isStartingSend => _isStartingSend;
void markSendStarted() => _isStartingSend = true;
void markSendSettled() => _isStartingSend = false;
@action
void reset() {
_isStartingSend = false;
pendingTransaction = null;
state = InitialExecutionState();
}
Future<void> _pollForTransaction(WalletBase wallet, String signature) async {
try {
} catch (e) {
printV("Failed to poll for NFT transfer: ${e.toString()}");
}
}
String _errorMessage(Object error) {
if (error is NotAnNFTException) {
return S.current.nft_send_not_an_nft;
}
if (error is NoAssociatedTokenAccountException) {
return S.current.solana_no_associated_token_account_exception;
}
if (error is SignSPLTokenTransactionRentException) {
return S.current.solana_sign_spl_token_transaction_rent_exception;
}
if (error is CreateAssociatedTokenAccountException) {
return "${S.current.solana_create_associated_token_account_exception} "
"${S.current.added_message_for_ata_error}";
}
printV("NFT send failed: ${error.toString()}");
return error.toString();
}
}