feat: migrate Trade data to SQLite with TradeLegacy for backward compatibility (#3106)

* feat: migrate Trade data to SQLite with TradeLegacy for backward compatibility

* feat: persist full currency snapshots in sql storage, not raw-only [WIP]

* feat: persist full trade currency snapshots in sql storage

* refactor trade and currency handling for migration

* Minor fixes

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
David Adegoke
2026-05-07 00:02:04 +02:00
committed by GitHub
co-authored by Omar Hatem
parent 813aa348bc
commit 9f6396f5d8
39 changed files with 1011 additions and 833 deletions
+67 -1
View File
@@ -41,7 +41,7 @@ Future<void> initDb({String? pathOverride}) async {
}
}
await db?.close();
db = await openDatabase(dbFile.path, version: 5,
db = await openDatabase(dbFile.path, version: 6,
onUpgrade: (Database db, int oldVersion, int newVersion) async {
printV("migrating: $oldVersion, $newVersion");
if (oldVersion <= 1) {
@@ -93,6 +93,9 @@ CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings (
if (oldVersion <= 4) {
await _createBridgeTransferTable(db);
}
if (oldVersion <= 5) {
await _createTradeTable(db);
}
},
onCreate: (Database db, int version) async {
await db.execute(
@@ -191,10 +194,73 @@ CREATE TABLE BalanceCardStyleSettings (
);
''');
await _createBridgeTransferTable(db);
await _createTradeTable(db);
}
);
}
Future<void> _createTradeTable(Database db) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS Trade (
tradeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
id TEXT NOT NULL,
providerRaw INTEGER NOT NULL DEFAULT 0,
fromTitle TEXT,
fromName TEXT,
fromTag TEXT,
fromFullName TEXT,
fromDecimals INTEGER,
fromRaw INTEGER,
fromIconPath TEXT,
fromFlatIconPath TEXT,
fromChainIconPath TEXT,
toTitle TEXT,
toName TEXT,
toTag TEXT,
toFullName TEXT,
toDecimals INTEGER,
toRaw INTEGER,
toIconPath TEXT,
toFlatIconPath TEXT,
toChainIconPath TEXT,
stateRaw TEXT NOT NULL DEFAULT '',
createdAt INTEGER,
expiredAt INTEGER,
amount TEXT NOT NULL DEFAULT '',
receiveAmount TEXT,
inputAddress TEXT,
extraId TEXT,
outputTransaction TEXT,
refundAddress TEXT,
walletId TEXT,
payoutAddress TEXT,
password TEXT,
providerId TEXT,
providerName TEXT,
fromWalletAddress TEXT,
memo TEXT,
txId TEXT,
isRefund INTEGER DEFAULT 0,
isSendAll INTEGER DEFAULT 0,
router TEXT,
needToRegisterInSwapXyz INTEGER DEFAULT 0,
sourceTokenAddress TEXT,
sourceTokenDecimals INTEGER,
routerData TEXT,
routerValue TEXT,
routerChainId INTEGER,
sourceTokenAmountRaw TEXT,
requiresTokenApproval INTEGER DEFAULT 0,
chainId INTEGER,
fee REAL
);
''');
await db.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS idx_trade_id_unique
ON Trade (id);
''');
}
Future<Map<String, dynamic>> dumpDb() async {
try {
return await _dumpDb();
+33 -29
View File
@@ -246,35 +246,35 @@ class WalletInfo extends HiveObject {
Future<void> migrateToSqlite() async {
final di = newWi.DerivationInfo(
id: 0,
derivationType: derivationInfo?.derivationType ?? derivationType ?? newWi.DerivationType.unknown,
derivationType:
derivationInfo?.derivationType ?? derivationType ?? newWi.DerivationType.unknown,
derivationPath: derivationInfo?.derivationPath ?? derivationPath ?? '',
);
final derivationInfoId = await di.save();
final walletInfo = newWi.WalletInfo(
0,
id,
name,
type,
isRecovery,
restoreHeight,
timestamp,
dirPath,
path,
address,
yatEid,
yatLastUsedAddressRaw,
showIntroCakePayCard,
derivationInfoId,
hardwareWalletType,
parentAddress,
hashedWalletIdentifier,
isNonSeedWallet,
0,
addressPageType,
false,
true,
null
);
0,
id,
name,
type,
isRecovery,
restoreHeight,
timestamp,
dirPath,
path,
address,
yatEid,
yatLastUsedAddressRaw,
showIntroCakePayCard,
derivationInfoId,
hardwareWalletType,
parentAddress,
hashedWalletIdentifier,
isNonSeedWallet,
0,
addressPageType,
false,
true,
null);
final wiId = await walletInfo.save();
for (final address in usedAddresses ?? <String>[]) {
await newWi.WalletInfoAddress.insert(wiId, newWi.WalletInfoAddressType.used, address);
@@ -289,8 +289,8 @@ class WalletInfo extends HiveObject {
for (final address in addressInfos![i] ?? <AddressInfo>[]) {
await newWi.WalletInfoAddressInfo.insert(
walletInfoId: wiId,
mapKey: i,
accountIndex: address.accountIndex??0,
mapKey: i,
accountIndex: address.accountIndex ?? 0,
address: address.address,
label: address.label,
);
@@ -304,8 +304,12 @@ class WalletInfo extends HiveObject {
final sw = Stopwatch()..start();
final list = box.values.toList();
for (final wi in list) {
await wi.migrateToSqlite();
await wi.delete();
try {
await wi.migrateToSqlite();
await wi.delete();
} catch (e) {
printV('Error migrating WalletInfo ${wi.id}: $e');
}
}
printV('Migrating WalletInfo to SQLite: end (${sw.elapsedMilliseconds}ms)');
}
@@ -385,8 +385,8 @@ class TransactionsPageRobot {
Future<void> _verifyTradeListItemDisplay(TradeListItem item) async {
final keyId = 'trade_list_item_${item.trade.id}_key';
final from = item.trade.from?.toString() ?? item.trade.userCurrencyFrom.toString();
final to = item.trade.to?.toString() ?? item.trade.userCurrencyTo.toString();
final from = item.trade.from?.toString() ?? '';
final to = item.trade.to?.toString() ?? '';
//* ==============Confirm it has the right key for this item ========
commonTestCases.hasValueKey(keyId);
+2
View File
@@ -19,6 +19,7 @@ import 'package:cake_wallet/entities/encrypt.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cake_wallet/exchange/trade_legacy.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cake_backup/backup.dart' as cake_backup;
@@ -110,6 +111,7 @@ class $BackupService {
Future<void> verifyWallets() async {
await performHiveMigration(); // for backups made before sqlite migration
await performTradeHiveMigration(_secureStorage);
correctWallets = (await WalletInfo.getAll()).where((info) => availableWalletTypes.contains(info.type)).toList();
if (correctWallets.isEmpty) {
+3 -11
View File
@@ -20,7 +20,6 @@ import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -30,13 +29,11 @@ class TradeMonitor {
TradeMonitor({
required this.tradesStore,
required this.trades,
required this.appStore,
required this.preferences,
});
final TradesStore tradesStore;
final Box<Trade> trades;
final AppStore appStore;
final Map<String, Timer> _tradeTimers = {};
final SharedPreferences preferences;
@@ -54,7 +51,7 @@ class TradeMonitor {
case ExchangeProviderDescription.exolix:
return ExolixExchangeProvider();
case ExchangeProviderDescription.thorChain:
return ThorChainExchangeProvider(tradesStore: trades);
return ThorChainExchangeProvider();
case ExchangeProviderDescription.swapTrade:
return SwapTradeExchangeProvider();
case ExchangeProviderDescription.letsExchange:
@@ -62,7 +59,7 @@ class TradeMonitor {
case ExchangeProviderDescription.stealthEx:
return StealthExExchangeProvider();
case ExchangeProviderDescription.chainflip:
return ChainflipExchangeProvider(tradesStore: trades);
return ChainflipExchangeProvider();
case ExchangeProviderDescription.xoSwap:
return XOSwapExchangeProvider();
case ExchangeProviderDescription.swapsXyz:
@@ -187,12 +184,7 @@ class TradeMonitor {
try {
final updated = await provider.findTradeById(id: trade.id);
trade
..stateRaw = updated.state.raw
..receiveAmount = updated.receiveAmount ?? trade.receiveAmount
..outputTransaction = updated.outputTransaction ?? trade.outputTransaction
..userCurrencyToRaw = updated.userCurrencyToRaw
..userCurrencyFromRaw = updated.userCurrencyFromRaw;
trade.mergeFindTradeByIdResult(updated);
printV('Trade ${trade.id} updated: ${trade.state}');
await trade.save();
+3 -12
View File
@@ -329,7 +329,6 @@ var _isSetupFinished = false;
late Box<Node> _nodeSource;
late Box<Node> _powNodeSource;
late Box<Contact> _contactSource;
late Box<Trade> _tradesSource;
late Box<Template> _templates;
late Box<ExchangeTemplate> _exchangeTemplates;
late Box<TransactionDescription> _transactionDescriptionBox;
@@ -341,7 +340,6 @@ Future<void> setup({
required Box<Node> nodeSource,
required Box<Node> powNodeSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
required Box<Template> templates,
required Box<ExchangeTemplate> exchangeTemplates,
required Box<TransactionDescription> transactionDescriptionBox,
@@ -355,7 +353,6 @@ Future<void> setup({
_nodeSource = nodeSource;
_powNodeSource = powNodeSource;
_contactSource = contactSource;
_tradesSource = tradesSource;
_templates = templates;
_exchangeTemplates = exchangeTemplates;
_transactionDescriptionBox = transactionDescriptionBox;
@@ -402,7 +399,7 @@ Future<void> setup({
nodeListStore: getIt.get<NodeListStore>(),
themeStore: getIt.get<ThemeStore>()));
getIt.registerSingleton<TradesStore>(
TradesStore(tradesSource: _tradesSource, appStore: getIt.get<AppStore>()));
TradesStore(appStore: getIt.get<AppStore>()));
getIt.registerSingleton<OrdersStore>(
OrdersStore(ordersSource: _ordersSource, settingsStore: getIt.get<SettingsStore>()));
getIt.registerSingleton<BridgeTransfersStore>(BridgeTransfersStore());
@@ -569,7 +566,6 @@ Future<void> setup({
getIt.registerFactory(
() => ExchangeViewModel(
getIt.get<AppStore>(),
_tradesSource,
getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>(),
getIt.get<SharedPreferences>(),
@@ -583,7 +579,6 @@ Future<void> setup({
getIt.registerSingleton(
TradeMonitor(
tradesStore: getIt.get<TradesStore>(),
trades: _tradesSource,
appStore: getIt.get<AppStore>(),
preferences: getIt.get<SharedPreferences>(),
),
@@ -1258,7 +1253,6 @@ Future<void> setup({
getIt.registerFactory(
() => ExchangeTradeViewModel(
wallet: getIt.get<AppStore>().wallet!,
trades: _tradesSource,
tradesStore: getIt.get<TradesStore>(),
sendViewModel: getIt.get<SendViewModel>(),
feesViewModel: getIt.get<FeesViewModel>(),
@@ -1471,11 +1465,8 @@ Future<void> setup({
getIt.registerFactoryParam<TransactionSuccessPage, String, void>(
(content, _) => TransactionSuccessPage(content: content));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
TradeDetailsViewModel(
tradeForDetails: trade,
trades: _tradesSource,
appStore: getIt.get<AppStore>()));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>(
(trade, _) => TradeDetailsViewModel(tradeForDetails: trade, appStore: getIt.get<AppStore>()));
getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<CakePayService>()));
+1 -3
View File
@@ -13,7 +13,6 @@ import 'package:cake_wallet/entities/haven_seed_store.dart';
import 'package:cake_wallet/entities/node_list.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:collection/collection.dart';
@@ -64,11 +63,10 @@ Future<void> defaultSettingsMigration(
required SecureStorage secureStorage,
required Box<Node> nodes,
required Box<Node> powNodes,
required Box<Trade> tradeSource,
required Box<Contact> contactSource,
required Box<HavenSeedStore> havenSeedStore}) async {
if (Platform.isIOS) {
await ios_migrate_v1(tradeSource, contactSource);
await ios_migrate_v1(contactSource);
}
// check current nodes for nullability regardless of the version
+12 -9
View File
@@ -29,7 +29,7 @@ Future<void> migrate_android_v1() async {
await android_migrate_wallets(appDocDir: appDocDir);
}
Future<void> ios_migrate_v1(Box<Trade> tradeSource, Box<Contact> contactSource) async {
Future<void> ios_migrate_v1(Box<Contact> contactSource) async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_v1_completed') ?? false) {
@@ -40,7 +40,7 @@ Future<void> ios_migrate_v1(Box<Trade> tradeSource, Box<Contact> contactSource)
await ios_migrate_pin();
await ios_migrate_wallet_passwords();
await ios_migrate_wallet_info();
await ios_migrate_trades_list(tradeSource);
await ios_migrate_trades_list();
await ios_migrate_address_book(contactSource);
await prefs.setBool('ios_migration_v1_completed', true);
@@ -343,7 +343,7 @@ Future<void> ios_migrate_wallet_info() async {
}
}
Future<void> ios_migrate_trades_list(Box<Trade> tradeSource) async {
Future<void> ios_migrate_trades_list() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('ios_migration_trade_list_completed') ?? false) {
@@ -366,7 +366,8 @@ Future<void> ios_migrate_trades_list(Box<Trade> tradeSource) async {
final key = masterPassword!.replaceAll('-', '');
final decoded = await ios_legacy_helper.decrypt(content, key: key, salt: secrets.salt);
final decodedJson = json.decode(decoded) as List<dynamic>;
final trades = decodedJson.map((dynamic el) {
for (final dynamic el in decodedJson) {
final elAsMap = el as Map<String, dynamic>;
final providerAsString = elAsMap['provider'] as String;
final fromAsString = elAsMap['from'] as String;
@@ -393,17 +394,19 @@ Future<void> ios_migrate_trades_list(Box<Trade> tradeSource) async {
break;
}
return Trade(
if (provider == null) continue;
await Trade(
id: tradeId,
provider: provider!,
provider: provider,
from: from,
to: to,
createdAt: date,
amount: '',
receiveAmount: '',
);
});
await tradeSource.addAll(trades);
).save();
}
await prefs.setBool('ios_migration_trade_list_completed', true);
} catch (e) {
printV(e.toString());
@@ -8,15 +8,13 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:hive/hive.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class ChainflipExchangeProvider extends ExchangeProvider {
ChainflipExchangeProvider({required this.tradesStore});
ChainflipExchangeProvider();
static final List<CryptoCurrency> _supported = [
CryptoCurrency.btc,
@@ -41,8 +39,6 @@ class ChainflipExchangeProvider extends ExchangeProvider {
static const _affiliateBps = secrets.chainflipAffiliateFee;
static const _affiliateKey = secrets.chainflipApiKey;
final Box<Trade> tradesStore;
@override
String get title => 'Chainflip';
@@ -229,19 +225,18 @@ class ChainflipExchangeProvider extends ExchangeProvider {
);
return Trade(
id: id,
from: request.fromCurrency,
to: request.toCurrency,
provider: description,
inputAddress: swapResponse['address'].toString(),
createdAt: DateTime.now(),
amount: request.fromAmount,
receiveAmount: request.toAmount,
state: TradeState.waiting,
payoutAddress: request.toAddress,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll);
id: id,
from: request.fromCurrency,
to: request.toCurrency,
provider: description,
inputAddress: swapResponse['address'].toString(),
createdAt: DateTime.now(),
amount: request.fromAmount,
receiveAmount: request.toAmount,
state: TradeState.waiting,
payoutAddress: request.toAddress,
isSendAll: isSendAll,
);
} catch (e, s) {
ExchangeProviderLogger.logError(
provider: description,
@@ -296,29 +291,19 @@ class ChainflipExchangeProvider extends ExchangeProvider {
final to = status['destinationAsset'].toString();
final newTrade = Trade(
id: id,
from: _toCurrency(status['sourceAsset'].toString()),
to: _toCurrency(status['destinationAsset'].toString()),
provider: description,
amount: depositAmount,
receiveAmount: amount,
state: currentState,
payoutAddress: status['destinationAddress'].toString(),
outputTransaction: status['swapEgress']?['transactionReference']?.toString(),
isRefund: isRefund,
userCurrencyFromRaw: '${from.toUpperCase()}' + '_',
userCurrencyToRaw: '${to.toUpperCase()}' + '_',
id: id,
from: _toCurrency(from),
to: _toCurrency(to),
provider: description,
amount: depositAmount,
receiveAmount: amount,
state: currentState,
payoutAddress: status['destinationAddress'].toString(),
outputTransaction:
status['swapEgress']?['transactionReference']?.toString(),
isRefund: isRefund,
);
// Find trade and update receiveAmount with the real value received
final storedTrade = _getStoredTrade(id);
if (storedTrade != null) {
storedTrade.$2.receiveAmount = newTrade.receiveAmount;
storedTrade.$2.outputTransaction = newTrade.outputTransaction;
tradesStore.put(storedTrade.$1, storedTrade.$2);
}
return newTrade;
} catch (e) {
printV(e.toString());
@@ -368,17 +353,6 @@ class ChainflipExchangeProvider extends ExchangeProvider {
return currency;
}
(dynamic, Trade)? _getStoredTrade(String id) {
for (var i = tradesStore.length -1; i >= 0; i--) {
Trade? t = tradesStore.getAt(i);
if (t != null && t.id == id)
return (i, t);
}
return null;
}
String _amountToNative(double amount, CryptoCurrency currency) =>
(amount * pow(10, currency.decimals)).toInt().toString();
@@ -9,7 +9,6 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/distribution_info.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
@@ -248,8 +247,6 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
receiveAmount: toAmount ?? request.toAmount,
state: TradeState.created,
payoutAddress: payoutAddress,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll,
);
}
@@ -315,9 +312,6 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
expiredAt: expiredAt,
outputTransaction: outputTransaction,
payoutAddress: payoutAddress,
userCurrencyFromRaw:
'${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
}
+6 -8
View File
@@ -1,4 +1,3 @@
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
@@ -31,13 +30,12 @@ abstract class ExchangeProvider {
Future<Trade> findTradeById({required String id});
Future<double> fetchRate({
required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount
});
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount});
Future<bool> checkIsAvailable();
}
@@ -314,8 +314,6 @@ class ExolixExchangeProvider extends ExchangeProvider {
receiveAmount: receiveAmount ?? request.toAmount,
state: TradeState.created,
payoutAddress: payoutAddress,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll,
);
}
@@ -363,18 +361,16 @@ class ExolixExchangeProvider extends ExchangeProvider {
final payoutAddress = responseJSON['withdrawalAddress'] as String;
return Trade(
id: id,
from: from,
to: to,
provider: description,
inputAddress: inputAddress,
amount: amount,
state: TradeState.deserialize(raw: _prepareStatus(status)),
extraId: extraId,
outputTransaction: outputTransaction,
payoutAddress: payoutAddress,
userCurrencyFromRaw: '${coinFrom.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyToRaw: '${coinTo.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
id: id,
from: from,
to: to,
provider: description,
inputAddress: inputAddress,
amount: amount,
state: TradeState.deserialize(raw: _prepareStatus(status)),
extraId: extraId,
outputTransaction: outputTransaction,
payoutAddress: payoutAddress,
);
}
@@ -367,8 +367,6 @@ class JupiterExchangeProvider extends ExchangeProvider {
receiveAmount: receiveAmount,
payoutAddress: request.toAddress,
isSendAll: isSendAll,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? 'SOL'}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? 'SOL'}',
routerData: transaction,
routerValue: requestId,
fee: totalFeeInSol,
@@ -9,7 +9,6 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -76,13 +75,12 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
}
@override
Future<double> fetchRate({
required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount
}) async {
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
final networkFrom = _getNetworkType(from);
final networkTo = _getNetworkType(to);
try {
@@ -191,14 +189,13 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
'Authorization': apiKey
};
final uri = Uri.https(_baseUrl,
isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath);
final uri = Uri.https(
_baseUrl, isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath);
final response = await ProxyWrapper().post(
clearnetUri: uri,
headers: headers,
body: json.encode(tradeParams),
);
if (response.statusCode != 200) {
ExchangeProviderLogger.logError(
@@ -304,8 +301,6 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
createdAt: createdAt,
expiredAt: expiredAt,
extraId: extraId,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll,
);
} catch (e, s) {
@@ -342,7 +337,6 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
final url = Uri.https(_baseUrl, '$_getTransactionPath/$id');
final response = await ProxyWrapper().get(clearnetUri: url, headers: headers);
if (response.statusCode != 200) {
throw Exception('LetsExchange fetch trade failed: ${response.body}');
@@ -363,7 +357,6 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
final toTag = toCurrency == normalizedToNetwork ? null : normalizedToNetwork;
final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
final payoutAddress = responseJSON['withdrawal'] as String;
final depositAddress = responseJSON['deposit'] as String;
final refundAddress = responseJSON['return'] as String;
@@ -371,15 +364,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
final receiveAmount = responseJSON['withdrawal_amount'] as String;
final status = responseJSON['status'] as String;
// We ignore the created_at from response and use DateTime.now() instead
final createdAtString = responseJSON['created_at'] as String;
final expiredAtTimestamp = responseJSON['expired_at'] as int;
final extraId = responseJSON['deposit_extra_id'] as String?;
final createdAt = DateTime.parse(createdAtString).toLocal();
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000).toLocal();
return Trade(
id: id,
from: from,
@@ -393,8 +379,6 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
state: TradeState.deserialize(raw: status),
isRefund: status == 'refund',
extraId: extraId,
userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
}
@@ -412,7 +396,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
headers: headers,
body: json.encode(params),
);
if (response.statusCode != 200) {
throw Exception('LetsExchange fetch info failed: ${response.body}');
}
@@ -9,7 +9,6 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -286,10 +285,6 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
receiveAmount: quoteObj['amountOutFormatted']?.toString(),
memo: depositMemo,
isSendAll: isSendAll,
userCurrencyFromRaw:
'${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw:
'${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
);
ExchangeProviderLogger.logSuccess(
@@ -415,8 +410,6 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
txId: originTxHash,
extraId: depositMemo,
isRefund: statusRaw == 'REFUNDED',
userCurrencyFromRaw: '${from?.$1.toUpperCase()}' + '_' + '${from?.$2?.toUpperCase() ?? ''}',
userCurrencyToRaw: '${to?.$1.toUpperCase()}' + '_' + '${to?.$2?.toUpperCase() ?? ''}',
);
}
@@ -9,7 +9,6 @@ import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -337,8 +336,6 @@ class SideShiftExchangeProvider extends ExchangeProvider {
createdAt: DateTime.now(),
isSendAll: isSendAll,
extraId: depositMemo,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
);
}
@@ -377,19 +374,25 @@ class SideShiftExchangeProvider extends ExchangeProvider {
final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal();
final depositMemo = responseJSON['depositMemo'] as String?;
final fromParsed = CryptoCurrency.safeParseCurrencyFromString(
fromCurrency,
tag: fromNetwork,
);
final toParsed = CryptoCurrency.safeParseCurrencyFromString(
toCurrency,
tag: toNetwork,
);
return Trade(
id: id,
from: CryptoCurrency.safeParseCurrencyFromString(fromCurrency),
to: CryptoCurrency.safeParseCurrencyFromString(toCurrency),
provider: description,
inputAddress: inputAddress,
amount: expectedSendAmount ?? '',
state: TradeState.deserialize(raw: status ?? 'created'),
expiredAt: expiredAt,
payoutAddress: settleAddress,
extraId: depositMemo,
userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + _normalizeNetworkType(fromNetwork ?? ''),
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + _normalizeNetworkType(toNetwork ?? ''),
id: id,
from: fromParsed,
to: toParsed,
provider: description,
inputAddress: inputAddress,
amount: expectedSendAmount ?? '',
state: TradeState.deserialize(raw: status ?? 'created'),
expiredAt: expiredAt,
payoutAddress: settleAddress,
extraId: depositMemo,
);
}
@@ -9,7 +9,6 @@ import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_not_found_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -290,8 +289,6 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
payoutAddress: payoutAddress,
createdAt: DateTime.now(),
isSendAll: isSendAll,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
);
}
@@ -326,18 +323,19 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
final status = responseJSON['status'] as String;
final payoutAddress = responseJSON['address_to'] as String;
final fromParsed =
CryptoCurrency.safeParseCurrencyFromString(fromCurrency);
final toParsed = CryptoCurrency.safeParseCurrencyFromString(toCurrency);
return Trade(
id: id,
from: CryptoCurrency.safeParseCurrencyFromString(fromCurrency),
to: CryptoCurrency.safeParseCurrencyFromString(toCurrency),
from: fromParsed,
to: toParsed,
extraId: extraId,
provider: description,
inputAddress: inputAddress,
amount: expectedSendAmount,
state: TradeState.deserialize(raw: status),
payoutAddress: payoutAddress,
userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_',
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_',
);
}
@@ -9,7 +9,6 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
@@ -299,8 +298,6 @@ class StealthExExchangeProvider extends ExchangeProvider {
createdAt: createdAt,
expiredAt: expiredAt,
extraId: extraId,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll,
);
} catch (e, s) {
@@ -377,9 +374,6 @@ class StealthExExchangeProvider extends ExchangeProvider {
createdAt: createdAt,
isRefund: status == 'refunded',
extraId: extraId,
userCurrencyFromRaw:
'${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
}
@@ -407,10 +407,6 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
requiresTokenApproval: requiresTokenApproval,
routerData: routerData,
routerValue: txValue,
userCurrencyFromRaw:
'${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw:
'${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
);
return trade;
@@ -554,8 +550,6 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
state: state,
createdAt: createdAt,
refundAddress: refundAddress,
userCurrencyFromRaw: '${fromSymbol.toUpperCase()}' + '_',
userCurrencyToRaw: '${toSymbol.toUpperCase()}' + '_',
);
}
@@ -297,8 +297,6 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
state: TradeState.created,
payoutAddress: request.toAddress,
isSendAll: isSendAll,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
);
} catch (e, s) {
ExchangeProviderLogger.logError(
@@ -374,8 +372,6 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
receiveAmount: expectedReceiveAmount,
memo: memo,
createdAt: DateTime.tryParse(createdAt ?? ''),
userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_',
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_',
);
} catch (e) {
printV("error getting trade: ${e.toString()}");
@@ -6,15 +6,13 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class ThorChainExchangeProvider extends ExchangeProvider {
ThorChainExchangeProvider({required this.tradesStore});
ThorChainExchangeProvider();
static final isRefundAddressSupported = [CryptoCurrency.eth];
@@ -26,8 +24,6 @@ class ThorChainExchangeProvider extends ExchangeProvider {
static const _affiliateBps = '175';
static const _nameLookUpPath = 'v2/thorname/lookup/';
final Box<Trade> tradesStore;
@override
String get title => 'THORChain';
@@ -195,8 +191,6 @@ class ThorChainExchangeProvider extends ExchangeProvider {
payoutAddress: request.toAddress,
memo: memo,
isSendAll: isSendAll,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
);
}
@@ -254,8 +248,6 @@ class ThorChainExchangeProvider extends ExchangeProvider {
state: currentState,
memo: memo,
isRefund: isRefund,
userCurrencyFromRaw: '${tx['chain'] as String? ?? ''}' + '_',
userCurrencyToRaw: '$toAsset' + '_',
);
}
@@ -392,8 +392,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
payoutAddress: payoutAddress,
isSendAll: isSendAll,
extraId: addressProviderMemo,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
);
}
@@ -456,9 +454,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
providerId: providerId,
providerName: providerName,
extraId: addressProviderMemo,
userCurrencyFromRaw:
'${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
});
}
@@ -423,8 +423,6 @@ class XOSwapExchangeProvider extends ExchangeProvider {
receiveAmount: receiveAmount.toString(),
payoutAddress: payoutAddress,
extraId: extraId,
userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
isSendAll: isSendAll,
);
} catch (e, s) {
@@ -529,14 +527,6 @@ class XOSwapExchangeProvider extends ExchangeProvider {
final createdAt = DateTime.parse(createdAtString).toLocal();
final extraId = responseJSON['payInAddressTag'] as String?;
final userCurrencyFromRaw = fromCurrency != null
? '${fromCurrency.title}' + '_' + '${fromCurrency.tag ?? ''}'
: '${fromAssetBase}' + '_' + '${fromAssetTag ?? ''}';
final userCurrencyToRaw = toCurrency != null
? '${toCurrency.title}' + '_' + '${toCurrency.tag ?? ''}'
: '${toAssetBase}' + '_' + '${toAssetTag ?? ''}';
return Trade(
id: orderId,
from: fromCurrency,
@@ -550,8 +540,6 @@ class XOSwapExchangeProvider extends ExchangeProvider {
receiveAmount: receiveAmount,
payoutAddress: payoutAddress,
extraId: extraId,
userCurrencyFromRaw: userCurrencyFromRaw,
userCurrencyToRaw: userCurrencyToRaw,
);
} catch (e) {
printV(e.toString());
+207 -299
View File
@@ -1,19 +1,22 @@
import 'dart:async';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/db/sqlite.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/generate_name.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart';
import 'package:sqflite/sqflite.dart';
class Trade extends HiveObject {
class Trade {
Trade({
this.internalId = 0,
required this.id,
required this.amount,
ExchangeProviderDescription? provider,
CryptoCurrency? from,
CryptoCurrency? to,
this.from,
this.to,
TradeState? state,
this.receiveAmount,
this.createdAt,
@@ -34,8 +37,6 @@ class Trade extends HiveObject {
this.isRefund,
this.isSendAll,
this.router,
this.userCurrencyFromRaw,
this.userCurrencyToRaw,
// The following fields are used for SwapXyz trades only
this.needToRegisterInSwapXyz,
this.sourceTokenAddress,
@@ -48,364 +49,271 @@ class Trade extends HiveObject {
this.chainId,
}) {
if (provider != null) providerRaw = provider.raw;
fromRaw = from?.raw ?? -1;
toRaw = to?.raw ?? -1;
if (state != null) stateRaw = state.raw;
}
static const typeId = TRADE_TYPE_ID;
static const tableName = 'Trade';
static const selfIdColumn = 'tradeId';
static const boxName = 'Trades';
static const boxKey = 'tradesBoxKey';
@HiveField(0, defaultValue: '')
static final StreamController<void> onChanged = StreamController<void>.broadcast();
int internalId;
String id;
@HiveField(1, defaultValue: 0)
late int providerRaw;
int providerRaw = 0;
ExchangeProviderDescription get provider =>
ExchangeProviderDescription.deserialize(raw: providerRaw);
@HiveField(2, defaultValue: -1)
int fromRaw = -1;
CryptoCurrency? from;
CryptoCurrency? to;
CryptoCurrency? get from => CryptoCurrency.safeDeserialize(raw: fromRaw);
@HiveField(3, defaultValue: -1)
int toRaw = -1;
CryptoCurrency? get to => CryptoCurrency.safeDeserialize(raw: toRaw);
@HiveField(4, defaultValue: '')
late String stateRaw;
String stateRaw = '';
TradeState get state => TradeState.deserialize(raw: stateRaw);
@HiveField(5)
DateTime? createdAt;
@HiveField(6)
DateTime? expiredAt;
@HiveField(7, defaultValue: '')
String amount;
@HiveField(8)
String? receiveAmount;
String? inputAddress;
@HiveField(9)
String? extraId;
@HiveField(10)
String? outputTransaction;
@HiveField(11)
String? refundAddress;
@HiveField(12)
String? walletId;
@HiveField(13)
String? payoutAddress;
@HiveField(14)
String? password;
@HiveField(15)
String? providerId;
@HiveField(16)
String? providerName;
@HiveField(17)
String? fromWalletAddress;
@HiveField(18)
String? memo;
@HiveField(19)
String? txId;
@HiveField(20)
bool? isRefund;
@HiveField(21)
bool? isSendAll;
/// Must be set on createTrade;
@HiveField(22)
String? router;
@HiveField(23, defaultValue: '')
String? receiveAmount;
@HiveField(24, defaultValue: '')
String? userCurrencyFromRaw;
@HiveField(25, defaultValue: '')
String? userCurrencyToRaw;
// The following fields are used for SwapXyz trades only
@HiveField(26)
bool? needToRegisterInSwapXyz;
@HiveField(27)
String? sourceTokenAddress;
@HiveField(28)
int? sourceTokenDecimals;
@HiveField(29)
String? routerData;
@HiveField(30)
String? routerValue;
@HiveField(31)
int? routerChainId;
@HiveField(32)
String? sourceTokenAmountRaw;
@HiveField(33, defaultValue: false)
bool? requiresTokenApproval;
@HiveField(34)
int? chainId;
@HiveField(35)
double? fee;
CryptoCurrency? get userCurrencyFrom {
if (userCurrencyFromRaw == null || userCurrencyFromRaw!.isEmpty) {
return null;
}
final underscoreIndex = userCurrencyFromRaw!.indexOf('_');
final title = userCurrencyFromRaw!.substring(0, underscoreIndex);
String tag = userCurrencyFromRaw!.substring(underscoreIndex + 1);
if (tag.contains('ARB')) tag = 'ARB';
return CryptoCurrency(
title: title,
tag: tag.isNotEmpty ? tag : null,
name: '',
raw: -1,
decimals: 1,
);
}
CryptoCurrency? get userCurrencyTo {
if (userCurrencyToRaw == null || userCurrencyToRaw!.isEmpty) {
return null;
}
final underscoreIndex = userCurrencyToRaw!.indexOf('_');
final title = userCurrencyToRaw!.substring(0, underscoreIndex);
final tag = userCurrencyToRaw!.substring(underscoreIndex + 1);
return CryptoCurrency(
title: title,
tag: tag.isNotEmpty ? tag : null,
name: '',
raw: -1,
decimals: 1,
);
}
String get chainName {
if (chainId == null) return '';
return evm!.getChainNameByChainId(chainId!).capitalized();
}
static Trade fromMap(Map<String, Object?> map) {
return Trade(
id: map['id'] as String,
provider: ExchangeProviderDescription.deserialize(raw: map['provider'] as int),
from: CryptoCurrency.deserialize(raw: map['input'] as int),
to: CryptoCurrency.deserialize(raw: map['output'] as int),
createdAt:
map['date'] != null ? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) : null,
amount: map['amount'] as String,
receiveAmount: map['receive_amount'] as String?,
walletId: map['wallet_id'] as String,
fromWalletAddress: map['from_wallet_address'] as String?,
memo: map['memo'] as String?,
txId: map['tx_id'] as String?,
isRefund: map['isRefund'] as bool?,
isSendAll: map['isSendAll'] as bool?,
router: map['router'] as String?,
extraId: map['extra_id'] as String?,
chainId: map['chain_id'] as int?,
// ── SQLite CRUD ──────────────────────────────────────
Future<int> save() async {
final json = toSqliteMap();
if (json[selfIdColumn] == 0) {
json[selfIdColumn] = null;
}
internalId = await db!.insert(
tableName,
json,
conflictAlgorithm: ConflictAlgorithm.replace,
);
onChanged.add(null);
return internalId;
}
static Future<List<Trade>> getAll({String? orderBy}) async {
final list = await db!.query(
tableName,
orderBy: orderBy ?? 'createdAt DESC',
);
return List.generate(
list.length,
(i) => Trade.fromSqliteRow(list[i]),
);
}
Map<String, dynamic> toMap() {
static Future<Trade?> getByTradeId(String id) async {
final list = await db!.query(
tableName,
where: 'id = ?',
whereArgs: [id],
limit: 1,
);
if (list.isEmpty) return null;
return Trade.fromSqliteRow(list.first);
}
static Future<int> deleteTrade(Trade trade) async {
final rows = await db!.delete(
tableName,
where: '$selfIdColumn = ?',
whereArgs: [trade.internalId],
);
onChanged.add(null);
return rows;
}
// ── SQLite serialization ─────────────────────────────
void mergeFindTradeByIdResult(Trade updated) {
if (updated.stateRaw.isNotEmpty) stateRaw = updated.stateRaw;
if (createdAt == null && updated.createdAt != null) {
createdAt = updated.createdAt;
}
if (updated.expiredAt != null) expiredAt = updated.expiredAt;
if (updated.isRefund != null) isRefund = updated.isRefund;
if (updated.receiveAmount != null) receiveAmount = updated.receiveAmount;
if (updated.inputAddress != null) inputAddress = updated.inputAddress;
if (updated.extraId != null) extraId = updated.extraId;
if (updated.outputTransaction != null) {
outputTransaction = updated.outputTransaction;
}
if (updated.refundAddress != null) refundAddress = updated.refundAddress;
if (updated.payoutAddress != null) payoutAddress = updated.payoutAddress;
if (updated.password != null) password = updated.password;
if (updated.providerId != null) providerId = updated.providerId;
if (updated.providerName != null) providerName = updated.providerName;
if (updated.memo != null) memo = updated.memo;
if (updated.txId != null) txId = updated.txId;
}
Map<String, dynamic> toSqliteMap() {
return <String, dynamic>{
selfIdColumn: internalId,
'id': id,
'provider': provider.serialize(),
'input': fromRaw,
'output': toRaw,
'date': createdAt != null ? createdAt!.millisecondsSinceEpoch : null,
'providerRaw': providerRaw,
'fromTitle': from?.title,
'fromName': from?.name,
'fromTag': from?.tag,
'fromFullName': from?.fullName,
'fromDecimals': from?.decimals,
'fromRaw': from?.raw,
'fromIconPath': from?.iconPath,
'fromFlatIconPath': from?.flatIconPath,
'fromChainIconPath': from?.chainIconPath,
'toTitle': to?.title,
'toName': to?.name,
'toTag': to?.tag,
'toFullName': to?.fullName,
'toDecimals': to?.decimals,
'toRaw': to?.raw,
'toIconPath': to?.iconPath,
'toFlatIconPath': to?.flatIconPath,
'toChainIconPath': to?.chainIconPath,
'stateRaw': stateRaw,
'createdAt': createdAt?.millisecondsSinceEpoch,
'expiredAt': expiredAt?.millisecondsSinceEpoch,
'amount': amount,
'receive_amount': receiveAmount,
'wallet_id': walletId,
'from_wallet_address': fromWalletAddress,
'receiveAmount': receiveAmount,
'inputAddress': inputAddress,
'extraId': extraId,
'outputTransaction': outputTransaction,
'refundAddress': refundAddress,
'walletId': walletId,
'payoutAddress': payoutAddress,
'password': password,
'providerId': providerId,
'providerName': providerName,
'fromWalletAddress': fromWalletAddress,
'memo': memo,
'tx_id': txId,
'isRefund': isRefund,
'isSendAll': isSendAll,
'txId': txId,
'isRefund': isRefund == true ? 1 : 0,
'isSendAll': isSendAll == true ? 1 : 0,
'router': router,
'extra_id': extraId,
'chain_id': chainId,
'needToRegisterInSwapXyz': needToRegisterInSwapXyz == true ? 1 : 0,
'sourceTokenAddress': sourceTokenAddress,
'sourceTokenDecimals': sourceTokenDecimals,
'routerData': routerData,
'routerValue': routerValue,
'routerChainId': routerChainId,
'sourceTokenAmountRaw': sourceTokenAmountRaw,
'requiresTokenApproval': requiresTokenApproval == true ? 1 : 0,
'chainId': chainId,
'fee': fee,
};
}
factory Trade.fromSqliteRow(Map<String, dynamic> row) {
final trade = Trade(
id: row['id'] as String? ?? '',
amount: row['amount'] as String? ?? '',
receiveAmount: row['receiveAmount'] as String?,
createdAt: row['createdAt'] != null
? DateTime.fromMillisecondsSinceEpoch(
row['createdAt'] as int,
)
: null,
expiredAt: row['expiredAt'] != null
? DateTime.fromMillisecondsSinceEpoch(
row['expiredAt'] as int,
)
: null,
inputAddress: row['inputAddress'] as String?,
extraId: row['extraId'] as String?,
outputTransaction: row['outputTransaction'] as String?,
refundAddress: row['refundAddress'] as String?,
walletId: row['walletId'] as String?,
payoutAddress: row['payoutAddress'] as String?,
password: row['password'] as String?,
providerId: row['providerId'] as String?,
providerName: row['providerName'] as String?,
fromWalletAddress: row['fromWalletAddress'] as String?,
memo: row['memo'] as String?,
fee: row['fee'] as double?,
txId: row['txId'] as String?,
isRefund: (row['isRefund'] as int?) == 1,
isSendAll: (row['isSendAll'] as int?) == 1,
router: row['router'] as String?,
from: _currencyFromRow(row, 'from'),
to: _currencyFromRow(row, 'to'),
needToRegisterInSwapXyz: (row['needToRegisterInSwapXyz'] as int?) == 1,
sourceTokenAddress: row['sourceTokenAddress'] as String?,
sourceTokenDecimals: row['sourceTokenDecimals'] as int?,
routerData: row['routerData'] as String?,
routerValue: row['routerValue'] as String?,
routerChainId: row['routerChainId'] as int?,
sourceTokenAmountRaw: row['sourceTokenAmountRaw'] as String?,
requiresTokenApproval: (row['requiresTokenApproval'] as int?) == 1,
chainId: row['chainId'] as int?,
);
trade.internalId = row[selfIdColumn] as int? ?? 0;
trade.providerRaw = row['providerRaw'] as int? ?? 0;
trade.stateRaw = row['stateRaw'] as String? ?? '';
return trade;
}
static CryptoCurrency? _currencyFromRow(Map<String, dynamic> row, String prefix) {
final title = row['${prefix}Title'] as String?;
if (title == null || title.isEmpty) return null;
final tag = row['${prefix}Tag'] as String?;
final live = CryptoCurrency.safeParseCurrencyFromString(title, tag: tag);
if (live != null) return live;
return CryptoCurrency(
title: title,
name: row['${prefix}Name'] as String? ?? '',
tag: tag,
fullName: row['${prefix}FullName'] as String?,
decimals: row['${prefix}Decimals'] as int? ?? 1,
raw: row['${prefix}Raw'] as int? ?? -1,
iconPath: row['${prefix}IconPath'] as String?,
flatIconPath: row['${prefix}FlatIconPath'] as String?,
chainIconPath: row['${prefix}ChainIconPath'] as String?,
);
}
String amountFormatted() => formatAmount(amount);
String receiveAmountFormatted() => formatAmount(receiveAmount ?? '');
}
class TradeAdapter extends TypeAdapter<Trade> {
@override
final int typeId = Trade.typeId;
@override
Trade read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{};
for (int i = 0; i < numOfFields; i++) {
try {
fields[reader.readByte()] = reader.read();
} catch (_) {}
}
return Trade(
id: fields[0] == null ? '' : fields[0] as String,
amount: fields[7] == null ? '' : fields[7] as String,
receiveAmount: fields[23] as String?,
createdAt: fields[5] as DateTime?,
expiredAt: fields[6] as DateTime?,
inputAddress: fields[8] as String?,
extraId: fields[9] as String?,
outputTransaction: fields[10] as String?,
refundAddress: fields[11] as String?,
walletId: fields[12] as String?,
payoutAddress: fields[13] as String?,
password: fields[14] as String?,
providerId: fields[15] as String?,
providerName: fields[16] as String?,
fromWalletAddress: fields[17] as String?,
memo: fields[18] as String?,
txId: fields[19] as String?,
isRefund: fields[20] as bool?,
isSendAll: fields[21] as bool?,
router: fields[22] as String?,
userCurrencyFromRaw: fields[24] as String?,
userCurrencyToRaw: fields[25] as String?,
needToRegisterInSwapXyz: fields[26] as bool?,
sourceTokenAddress: fields[27] as String?,
sourceTokenDecimals: fields[28] as int?,
routerData: fields[29] as String?,
routerValue: fields[30] as String?,
routerChainId: fields[31] as int?,
sourceTokenAmountRaw: fields[32] as String?,
requiresTokenApproval: fields[33] as bool?,
chainId: fields[34] as int?,
fee: fields[35] as double?,
)
..providerRaw = fields[1] == null ? 0 : fields[1] as int
..fromRaw = (fields[2] as int?) ?? -1
..toRaw = (fields[3] as int?) ?? -1
..stateRaw = fields[4] == null ? '' : fields[4] as String;
}
@override
void write(BinaryWriter writer, Trade obj) {
writer
..writeByte(26)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.providerRaw)
..writeByte(2)
..write(obj.fromRaw)
..writeByte(3)
..write(obj.toRaw)
..writeByte(4)
..write(obj.stateRaw)
..writeByte(5)
..write(obj.createdAt)
..writeByte(6)
..write(obj.expiredAt)
..writeByte(7)
..write(obj.amount)
..writeByte(8)
..write(obj.inputAddress)
..writeByte(9)
..write(obj.extraId)
..writeByte(10)
..write(obj.outputTransaction)
..writeByte(11)
..write(obj.refundAddress)
..writeByte(12)
..write(obj.walletId)
..writeByte(13)
..write(obj.payoutAddress)
..writeByte(14)
..write(obj.password)
..writeByte(15)
..write(obj.providerId)
..writeByte(16)
..write(obj.providerName)
..writeByte(17)
..write(obj.fromWalletAddress)
..writeByte(18)
..write(obj.memo)
..writeByte(19)
..write(obj.txId)
..writeByte(20)
..write(obj.isRefund)
..writeByte(21)
..write(obj.isSendAll)
..writeByte(22)
..write(obj.router)
..writeByte(23)
..write(obj.receiveAmount)
..writeByte(24)
..write(obj.userCurrencyFromRaw)
..writeByte(25)
..write(obj.userCurrencyToRaw)
..writeByte(26)
..write(obj.needToRegisterInSwapXyz)
..writeByte(27)
..write(obj.sourceTokenAddress)
..writeByte(28)
..write(obj.sourceTokenDecimals)
..writeByte(29)
..write(obj.routerData)
..writeByte(30)
..write(obj.routerValue)
..writeByte(31)
..write(obj.routerChainId)
..writeByte(32)
..write(obj.sourceTokenAmountRaw)
..writeByte(33)
..write(obj.requiresTokenApproval)
..writeByte(34)
..write(obj.chainId)
..writeByte(35)
..write(obj.fee);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is TradeAdapter && runtimeType == other.runtimeType && typeId == other.typeId;
}
+45
View File
@@ -0,0 +1,45 @@
import 'package:cw_core/crypto_currency.dart';
class TradeCurrencySnapshot {
TradeCurrencySnapshot._();
static CryptoCurrency? fromLegacyHive({
required int raw,
String? displayTitleTag,
}) {
if (raw >= 0) {
final curr = CryptoCurrency.safeDeserialize(raw: raw);
if (curr != null) return curr;
}
return _parseLegacyTitleTag(displayTitleTag);
}
static CryptoCurrency? _parseLegacyTitleTag(String? titleTag) {
if (titleTag == null || titleTag.isEmpty) return null;
final idx = titleTag.indexOf('_');
if (idx < 0) {
return CryptoCurrency(
title: titleTag,
name: '',
raw: -1,
decimals: 1,
);
}
final title = titleTag.substring(0, idx);
var tag = titleTag.substring(idx + 1);
if (tag.contains('ARB')) tag = 'ARB';
return CryptoCurrency(
title: title,
tag: tag.isNotEmpty ? tag : null,
name: '',
raw: -1,
decimals: 1,
);
}
}
+168
View File
@@ -0,0 +1,168 @@
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/get_encryption_key.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_currency_snapshot.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:hive/hive.dart';
part 'trade_legacy.part.dart';
Future<void> performTradeHiveMigration(SecureStorage secureStorage) async {
try {
if (!CakeHive.isAdapterRegistered(TradeLegacy.typeId)) {
CakeHive.registerAdapter(TradeLegacyAdapter());
}
final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey);
final box = await CakeHive.openBox<TradeLegacy>(Trade.boxName, encryptionKey: tradesBoxKey);
await TradeLegacy.migrateAllToSqlite(box);
} catch (e) {
printV('Trade Hive migration error: $e');
}
}
class TradeLegacy extends HiveObject {
TradeLegacy({
required this.id,
required this.amount,
ExchangeProviderDescription? provider,
CryptoCurrency? from,
CryptoCurrency? to,
TradeState? state,
this.receiveAmount,
this.createdAt,
this.expiredAt,
this.inputAddress,
this.extraId,
this.outputTransaction,
this.refundAddress,
this.walletId,
this.payoutAddress,
this.password,
this.providerId,
this.providerName,
this.fromWalletAddress,
this.memo,
this.fee,
this.txId,
this.isRefund,
this.isSendAll,
this.router,
this.userCurrencyFromRaw,
this.userCurrencyToRaw,
this.needToRegisterInSwapXyz,
this.sourceTokenAddress,
this.sourceTokenDecimals,
this.routerData,
this.routerValue,
this.routerChainId,
this.sourceTokenAmountRaw,
this.requiresTokenApproval,
this.chainId,
}) {
if (provider != null) providerRaw = provider.raw;
fromRaw = from?.raw ?? -1;
toRaw = to?.raw ?? -1;
if (state != null) stateRaw = state.raw;
}
static const typeId = TRADE_TYPE_ID;
String id;
late int providerRaw;
int fromRaw = -1;
int toRaw = -1;
late String stateRaw;
DateTime? createdAt;
DateTime? expiredAt;
String amount;
String? receiveAmount;
String? inputAddress;
String? extraId;
String? outputTransaction;
String? refundAddress;
String? walletId;
String? payoutAddress;
String? password;
String? providerId;
String? providerName;
String? fromWalletAddress;
String? memo;
String? txId;
bool? isRefund;
bool? isSendAll;
String? router;
String? userCurrencyFromRaw;
String? userCurrencyToRaw;
bool? needToRegisterInSwapXyz;
String? sourceTokenAddress;
int? sourceTokenDecimals;
String? routerData;
String? routerValue;
int? routerChainId;
String? sourceTokenAmountRaw;
bool? requiresTokenApproval;
int? chainId;
double? fee;
Future<void> migrateToSqlite() async {
final trade = Trade(
id: id,
amount: amount,
receiveAmount: receiveAmount,
createdAt: createdAt,
expiredAt: expiredAt,
inputAddress: inputAddress,
extraId: extraId,
outputTransaction: outputTransaction,
refundAddress: refundAddress,
walletId: walletId,
payoutAddress: payoutAddress,
password: password,
providerId: providerId,
providerName: providerName,
fromWalletAddress: fromWalletAddress,
memo: memo,
fee: fee,
txId: txId,
isRefund: isRefund,
isSendAll: isSendAll,
router: router,
from:
TradeCurrencySnapshot.fromLegacyHive(raw: fromRaw, displayTitleTag: userCurrencyFromRaw),
to: TradeCurrencySnapshot.fromLegacyHive(raw: toRaw, displayTitleTag: userCurrencyToRaw),
needToRegisterInSwapXyz: needToRegisterInSwapXyz,
sourceTokenAddress: sourceTokenAddress,
sourceTokenDecimals: sourceTokenDecimals,
routerData: routerData,
routerValue: routerValue,
routerChainId: routerChainId,
sourceTokenAmountRaw: sourceTokenAmountRaw,
requiresTokenApproval: requiresTokenApproval,
chainId: chainId,
);
trade.providerRaw = providerRaw;
trade.stateRaw = stateRaw;
await trade.save();
}
static Future<void> migrateAllToSqlite(
Box<TradeLegacy> box,
) async {
printV('Migrating Trades to SQLite: start');
final list = box.values.toList();
for (final trade in list) {
try {
await trade.migrateToSqlite();
await trade.delete();
} catch (e) {
printV('Error migrating trade ${trade.id}: $e');
}
}
printV('Migrating Trades to SQLite: end');
}
}
+146
View File
@@ -0,0 +1,146 @@
part of 'trade_legacy.dart';
class TradeLegacyAdapter extends TypeAdapter<TradeLegacy> {
@override
final int typeId = TradeLegacy.typeId;
@override
TradeLegacy read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{};
for (int i = 0; i < numOfFields; i++) {
try {
fields[reader.readByte()] = reader.read();
} catch (_) {}
}
return TradeLegacy(
id: fields[0] == null ? '' : fields[0] as String,
amount: fields[7] == null ? '' : fields[7] as String,
receiveAmount: fields[23] as String?,
createdAt: fields[5] as DateTime?,
expiredAt: fields[6] as DateTime?,
inputAddress: fields[8] as String?,
extraId: fields[9] as String?,
outputTransaction: fields[10] as String?,
refundAddress: fields[11] as String?,
walletId: fields[12] as String?,
payoutAddress: fields[13] as String?,
password: fields[14] as String?,
providerId: fields[15] as String?,
providerName: fields[16] as String?,
fromWalletAddress: fields[17] as String?,
memo: fields[18] as String?,
txId: fields[19] as String?,
isRefund: fields[20] as bool?,
isSendAll: fields[21] as bool?,
router: fields[22] as String?,
userCurrencyFromRaw: fields[24] as String?,
userCurrencyToRaw: fields[25] as String?,
needToRegisterInSwapXyz: fields[26] as bool?,
sourceTokenAddress: fields[27] as String?,
sourceTokenDecimals: fields[28] as int?,
routerData: fields[29] as String?,
routerValue: fields[30] as String?,
routerChainId: fields[31] as int?,
sourceTokenAmountRaw: fields[32] as String?,
requiresTokenApproval: fields[33] as bool?,
chainId: fields[34] as int?,
fee: fields[35] as double?,
)
..providerRaw =
fields[1] == null ? 0 : fields[1] as int
..fromRaw = (fields[2] as int?) ?? -1
..toRaw = (fields[3] as int?) ?? -1
..stateRaw =
fields[4] == null ? '' : fields[4] as String;
}
@override
void write(BinaryWriter writer, TradeLegacy obj) {
writer
..writeByte(36)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.providerRaw)
..writeByte(2)
..write(obj.fromRaw)
..writeByte(3)
..write(obj.toRaw)
..writeByte(4)
..write(obj.stateRaw)
..writeByte(5)
..write(obj.createdAt)
..writeByte(6)
..write(obj.expiredAt)
..writeByte(7)
..write(obj.amount)
..writeByte(8)
..write(obj.inputAddress)
..writeByte(9)
..write(obj.extraId)
..writeByte(10)
..write(obj.outputTransaction)
..writeByte(11)
..write(obj.refundAddress)
..writeByte(12)
..write(obj.walletId)
..writeByte(13)
..write(obj.payoutAddress)
..writeByte(14)
..write(obj.password)
..writeByte(15)
..write(obj.providerId)
..writeByte(16)
..write(obj.providerName)
..writeByte(17)
..write(obj.fromWalletAddress)
..writeByte(18)
..write(obj.memo)
..writeByte(19)
..write(obj.txId)
..writeByte(20)
..write(obj.isRefund)
..writeByte(21)
..write(obj.isSendAll)
..writeByte(22)
..write(obj.router)
..writeByte(23)
..write(obj.receiveAmount)
..writeByte(24)
..write(obj.userCurrencyFromRaw)
..writeByte(25)
..write(obj.userCurrencyToRaw)
..writeByte(26)
..write(obj.needToRegisterInSwapXyz)
..writeByte(27)
..write(obj.sourceTokenAddress)
..writeByte(28)
..write(obj.sourceTokenDecimals)
..writeByte(29)
..write(obj.routerData)
..writeByte(30)
..write(obj.routerValue)
..writeByte(31)
..write(obj.routerChainId)
..writeByte(32)
..write(obj.sourceTokenAmountRaw)
..writeByte(33)
..write(obj.requiresTokenApproval)
..writeByte(34)
..write(obj.chainId)
..writeByte(35)
..write(obj.fee);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is TradeLegacyAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
+11 -20
View File
@@ -19,7 +19,7 @@ import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/exchange/exchange_template.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_legacy.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/locales/locale.dart';
import 'package:cake_wallet/order/order.dart';
@@ -37,7 +37,6 @@ import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/address_info.dart';
import 'package:cw_core/cake_hive.dart';
@@ -65,7 +64,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_daemon/flutter_daemon.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:hive/hive.dart';
import 'package:cw_core/root_dir.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -230,10 +228,6 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
CakeHive.registerAdapter(TransactionDescriptionAdapter());
}
if (!CakeHive.isAdapterRegistered(Trade.typeId)) {
CakeHive.registerAdapter(TradeAdapter());
}
if (!CakeHive.isAdapterRegistered(AddressInfo.typeId)) {
CakeHive.registerAdapter(AddressInfoAdapter());
}
@@ -290,7 +284,6 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
final secureStorage = secureStorageShared;
final transactionDescriptionsBoxKey =
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey);
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
final nodes = await CakeHive.openBox<Node>(Node.boxName);
@@ -299,7 +292,8 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades = await CakeHive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
await performTradeHiveMigration(secureStorage);
final orders = await CakeHive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
final templates = await CakeHive.openBox<Template>(Template.boxName);
final exchangeTemplates = await CakeHive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
@@ -318,7 +312,6 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
nodes: nodes,
powNodes: powNodes,
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
unspentCoinsInfoSource: unspentCoinsInfoSource,
// fiatConvertationService: fiatConvertationService,
@@ -339,7 +332,6 @@ Future<void> initialSetup({
required Box<Node> nodes,
required Box<Node> powNodes,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
required Box<Order> ordersSource,
// required FiatConvertationService fiatConvertationService,
required Box<Template> templates,
@@ -354,19 +346,18 @@ Future<void> initialSetup({
}) async {
LanguageService.loadLocaleList();
await defaultSettingsMigration(
secureStorage: secureStorage,
version: initialMigrationVersion,
sharedPreferences: sharedPreferences,
contactSource: contactSource,
tradeSource: tradesSource,
nodes: nodes,
powNodes: powNodes,
havenSeedStore: havenSeedStore);
secureStorage: secureStorage,
version: initialMigrationVersion,
sharedPreferences: sharedPreferences,
contactSource: contactSource,
nodes: nodes,
powNodes: powNodes,
havenSeedStore: havenSeedStore,
);
await setup(
nodeSource: nodes,
powNodeSource: powNodes,
contactSource: contactSource,
tradesSource: tradesSource,
ordersSource: ordersSource,
templates: templates,
exchangeTemplates: exchangeTemplates,
@@ -35,13 +35,15 @@ class HistorySection extends StatelessWidget {
? SliverPadding(
padding: EdgeInsets.only(top: 24),
sliver: SliverToBoxAdapter(
child: (dashboardViewModel.status is SyncingSyncStatus) ? SizedBox.shrink() : Center(
child: Text(S.of(context).transactions_will_appear_here,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant)),
),
child: (dashboardViewModel.status is SyncingSyncStatus)
? SizedBox.shrink()
: Center(
child: Text(S.of(context).transactions_will_appear_here,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant)),
),
),
)
: SliverList(
@@ -51,136 +53,147 @@ class HistorySection extends StatelessWidget {
final prevItem = index == 0 ? null : dashboardViewModel.items[index - 1];
final topPadding = index == 0 ? 0.0 : 18.0;
final item = dashboardViewModel.items[index];
final nextItem = index == dashboardViewModel.items.length - 1
? null
: dashboardViewModel.items[index + 1];
final nextItem = index == dashboardViewModel.items.length - 1
? null
: dashboardViewModel.items[index + 1];
final roundedBottom = (nextItem == null || nextItem is DateSectionItem);
final roundedTop = (prevItem == null || prevItem is DateSectionItem);
final roundedBottom = (nextItem == null || nextItem is DateSectionItem);
final roundedTop = (prevItem == null || prevItem is DateSectionItem);
if (item is TransactionListItem) {
final transaction = item.transaction;
final transactionType = dashboardViewModel.getTransactionType(transaction);
if (item is TransactionListItem) {
final transaction = item.transaction;
final transactionType = dashboardViewModel.getTransactionType(transaction);
if (item.hasTokens && item.assetOfTransaction == null) {
return Container();
}
if (item.hasTokens && item.assetOfTransaction == null) {
return Container();
}
CryptoCurrency? asset;
if (transaction.additionalInfo["isLightning"] == true)
asset = CryptoCurrency.btcln;
else
asset = item.assetOfTransaction;
CryptoCurrency? asset;
if (transaction.additionalInfo["isLightning"] == true)
asset = CryptoCurrency.btcln;
else
asset = item.assetOfTransaction;
return GestureDetector(
onTap: () {
final page = getIt.get<TransactionDetailsModal>(param1: transaction);
showModalBottomSheet(isScrollControlled:true,context: context, builder: (context) => page);
},
child: HistoryTile(
title: item.formattedTitle + transactionType,
date: DateFormat('HH:mm').format(transaction.date),
amount: item.formattedCryptoAmount,
amountFiat: item.formattedFiatAmount,
hasTokens: item.hasTokens,
chainIconPath: _getChainIconPath(),
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
direction: item.transaction.direction,
pending: item.transaction.isPending,
asset: asset,
),
);
} else if (item is TradeListItem) {
final trade = item.trade;
return GestureDetector(
onTap: () {
final page = getIt.get<TransactionDetailsModal>(param1: transaction);
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) => page);
},
child: HistoryTile(
title: item.formattedTitle + transactionType,
date: DateFormat('HH:mm').format(transaction.date),
amount: item.formattedCryptoAmount,
amountFiat: item.formattedFiatAmount,
hasTokens: item.hasTokens,
chainIconPath: _getChainIconPath(),
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
direction: item.transaction.direction,
pending: item.transaction.isPending,
asset: asset,
),
);
} else if (item is TradeListItem) {
final trade = item.trade;
final tradeFrom = trade.from;
final tradeTo = trade.to;
if (tradeFrom == null || tradeTo == null) {
return const SizedBox.shrink();
}
final tradeFrom = trade.fromRaw >= 0 ? trade.from : trade.userCurrencyFrom;
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
child: HistoryTradeTile(
from: tradeFrom,
to: tradeTo,
provider: trade.provider,
date:
DateFormat('HH:mm').format(item.trade.createdAt ?? DateTime.now()),
amount: trade.amountFormatted(),
receiveAmount: trade.receiveAmountFormatted(),
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
swapState: trade.state,
),
);
} else if (item is DateSectionItem) {
return Padding(
padding: EdgeInsets.only(left: 8.0, bottom: 8.0, top: topPadding),
child: Text(DateFormatter.convertDateTimeToReadableString(item.date),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant)));
} else if (item is OrderListItem) {
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.orderDetails, arguments: item.order),
child: HistoryOrderTile(
date: DateFormat('HH:mm').format(item.order.createdAt),
amount: item.orderFormattedAmount,
amountFiat: "USD 0.00",
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
),
);
} else if (item is PayjoinTransactionListItem) {
final session = item.session;
final tradeTo = trade.toRaw >= 0 ? trade.to : trade.userCurrencyTo;
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(
Routes.payjoinDetails,
arguments: [item.sessionId, item.transaction],
),
child: PayjoinHistoryTile(
createdAt: DateFormat('HH:mm').format(session.inProgressSince!),
amount: dashboardViewModel.appStore.amountParsingProxy
.getDisplayCryptoString(
session.amount.toInt(), CryptoCurrency.btc),
currency: item.transaction?.from ?? "BTC",
state: item.status,
isSending: session.isSenderSession,
roundedTop: roundedTop,
roundedBottom: roundedBottom,
bottomSeparator: !roundedBottom),
);
} else if (item is AnonpayTransactionListItem) {
final transactionInfo = item.transaction;
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
child: HistoryTradeTile(
from: tradeFrom!,
to: tradeTo!,
provider: trade.provider,
date: DateFormat('HH:mm').format(item.trade.createdAt!),
amount: trade.amountFormatted(),
receiveAmount: trade.receiveAmountFormatted(),
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
swapState: trade.state,
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
child: AnonpayHistoryTile(
provider: transactionInfo.provider,
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
amount: transactionInfo.fiatAmount?.toString() ??
(transactionInfo.amountTo?.toString() ?? ''),
currency: transactionInfo.fiatAmount != null
? transactionInfo.fiatEquiv ?? ''
: CryptoCurrency.fromFullName(transactionInfo.coinTo)
.name
.toUpperCase(),
roundedTop: roundedTop,
roundedBottom: roundedBottom,
bottomSeparator: !roundedBottom));
} else
return Text(item.runtimeType.toString());
}),
),
),
);
} else if (item is DateSectionItem) {
return Padding(
padding: EdgeInsets.only(left: 8.0, bottom: 8.0, top: topPadding),
child: Text(DateFormatter.convertDateTimeToReadableString(item.date),
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)));
}else if(item is OrderListItem){
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.orderDetails, arguments: item.order),
child: HistoryOrderTile(
date: DateFormat('HH:mm').format(item.order.createdAt),
amount: item.orderFormattedAmount,
amountFiat: "USD 0.00",
roundedBottom: roundedBottom,
roundedTop: roundedTop,
bottomSeparator: !roundedBottom,
),
);
} else if (item is PayjoinTransactionListItem) {
final session = item.session;
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(
Routes.payjoinDetails,
arguments: [item.sessionId, item.transaction],
),
child: PayjoinHistoryTile(
createdAt: DateFormat('HH:mm').format(session.inProgressSince!),
amount: dashboardViewModel.appStore.amountParsingProxy
.getDisplayCryptoString(session.amount.toInt(), CryptoCurrency.btc),
currency: item.transaction?.from ?? "BTC",
state: item.status,
isSending: session.isSenderSession,
roundedTop: roundedTop,
roundedBottom: roundedBottom,
bottomSeparator: !roundedBottom),
);
} else if (item is AnonpayTransactionListItem) {
final transactionInfo = item.transaction;
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo),
child: AnonpayHistoryTile(
provider: transactionInfo.provider,
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt),
amount: transactionInfo.fiatAmount?.toString() ??
(transactionInfo.amountTo?.toString() ?? ''),
currency: transactionInfo.fiatAmount != null
? transactionInfo.fiatEquiv ?? ''
: CryptoCurrency.fromFullName(transactionInfo.coinTo).name.toUpperCase(),
roundedTop: roundedTop,
roundedBottom: roundedBottom,
bottomSeparator: !roundedBottom));
} else
return Text(item.runtimeType.toString());
}),
),
),
));
));
}
String _getChainIconPath() {
try {
return CryptoCurrency.fromString(dashboardViewModel.wallet.currency.tag ??dashboardViewModel.wallet.currency.title).chainIconPath!;
} catch(e) {
return CryptoCurrency.fromString(
dashboardViewModel.wallet.currency.tag ?? dashboardViewModel.wallet.currency.title)
.chainIconPath!;
} catch (e) {
return dashboardViewModel.wallet.currency.chainIconPath ?? "";
}
}
@@ -39,28 +39,30 @@ class HistoryTradeTile extends StatelessWidget {
width: 50,
child: Stack(
children: [
CakeImageWidget(imageUrl: _getIconPath(from),
width: currencyIconSize, height: currencyIconSize),
CakeImageWidget(
imageUrl: _getIconPath(from), width: currencyIconSize, height: currencyIconSize),
Positioned(
top: currencyIconSize / 2,
left: currencyIconSize / 2,
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 2,
color: Theme.of(context).colorScheme.surfaceContainer),
shape: BoxShape.circle),
child: CakeImageWidget(imageUrl: _getIconPath(to),
width: currencyIconSize, height: currencyIconSize))),
top: currencyIconSize / 2,
left: currencyIconSize / 2,
child: Container(
decoration: BoxDecoration(
border:
Border.all(width: 2, color: Theme.of(context).colorScheme.surfaceContainer),
shape: BoxShape.circle),
child: CakeImageWidget(
imageUrl: _getIconPath(to),
width: currencyIconSize,
height: currencyIconSize,
),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final fromChainIcon = _getChainIcon(from);
final toChainIcon = _getChainIcon(to);
@@ -118,36 +120,32 @@ class HistoryTradeTile extends StatelessWidget {
}
String _getIconPath(CryptoCurrency currency) {
try { // temporarily until we migrate from hive to sqlite and store the full currency object
if (currency.iconPath != null) {
return currency.iconPath!;
try {
if (currency.title.isNotEmpty) {
final live = CryptoCurrency.safeParseCurrencyFromString(currency.title, tag: currency.tag);
if (live?.iconPath != null) return live!.iconPath!;
}
if (currency.iconPath != null) return currency.iconPath!;
if (currency.name.isNotEmpty) {
final currencyFromName = CryptoCurrency.fromString(currency.name);
if (currencyFromName.iconPath != null) {
return currencyFromName.iconPath!;
}
}
if (currency.title.isNotEmpty) {
final currencyFromTitle = CryptoCurrency.fromString(currency.title);
if (currencyFromTitle.iconPath != null) {
return currencyFromTitle.iconPath!;
}
final byName = CryptoCurrency.safeParseCurrencyFromString(currency.name);
if (byName?.iconPath != null) return byName!.iconPath!;
}
} catch (_) {}
//TODO approporiate fallback
return "";
}
String _getChainIcon(CryptoCurrency currency) {
try {
if (currency.chainIconPath != null) {
return currency.chainIconPath!;
if (currency.title.isNotEmpty) {
final parsedCurrency = CryptoCurrency.safeParseCurrencyFromString(currency.title, tag: currency.tag);
if (parsedCurrency?.chainIconPath != null) return parsedCurrency!.chainIconPath!;
}
if (currency.chainIconPath != null) return currency.chainIconPath!;
if ((currency.tag ?? "").isNotEmpty) {
final currencyFromTag = CryptoCurrency.fromString(currency.tag!);
if (currencyFromTag.chainIconPath != null) {
@@ -166,9 +166,9 @@ class TransactionsPage extends StatelessWidget {
final trade = item.trade;
final tradeFrom =
trade.fromRaw >= 0 ? trade.from : trade.userCurrencyFrom;
trade.from;
final tradeTo = trade.toRaw >= 0 ? trade.to : trade.userCurrencyTo;
final tradeTo = trade.to;
return tradeFrom != null && tradeTo != null
? Observer(
@@ -31,7 +31,7 @@ void showInformation(ExchangeTradeViewModel exchangeTradeViewModel, BuildContext
final trade = exchangeTradeViewModel.trade;
final walletName = exchangeTradeViewModel.wallet.name;
final from = trade.from?.toString() ?? trade.userCurrencyFrom.toString();
final from = trade.from?.toString() ?? '';
final information = exchangeTradeViewModel.isSendable
? S.current.exchange_trade_result_confirm(trade.amount, from, walletName) +
@@ -319,14 +319,14 @@ class _SwapDetailsContent extends StatelessWidget {
_SwapDetailsTile(
label: 'You Send',
value:
'${trade.amount} ${trade.from?.title ?? trade.userCurrencyFrom?.title ?? ''}',
'${trade.amount} ${trade.from?.title ?? ''}',
valueFiatFormatted: exchangeTradeViewModel.sendAmountFiatFormatted,
),
const SizedBox(height: 8),
_SwapDetailsTile(
label: 'You Get',
value:
'${trade.receiveAmount ?? '0'} ${trade.to?.title ?? trade.userCurrencyTo?.title ?? ''}',
'${trade.receiveAmount ?? '0'} ${trade.to?.title ?? ''}',
valueFiatFormatted: exchangeTradeViewModel
.getReceiveAmountFiatFormatted(trade.receiveAmount ?? '0.0'),
),
+16 -14
View File
@@ -1,9 +1,7 @@
import 'dart:async';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
part 'trades_store.g.dart';
@@ -11,14 +9,11 @@ part 'trades_store.g.dart';
class TradesStore = TradesStoreBase with _$TradesStore;
abstract class TradesStoreBase with Store {
TradesStoreBase({required this.tradesSource, required this.appStore})
: trades = <TradeListItem>[] {
_onTradesChanged = tradesSource.watch().listen((_) async => await updateTradeList());
TradesStoreBase({required this.appStore}) : trades = <TradeListItem>[] {
Trade.onChanged.stream.listen((_) => updateTradeList());
updateTradeList();
}
Box<Trade> tradesSource;
StreamSubscription<BoxEvent>? _onTradesChanged;
AppStore appStore;
@observable
@@ -31,11 +26,18 @@ abstract class TradesStoreBase with Store {
void setTrade(Trade trade) => this.trade = trade;
@action
Future<void> updateTradeList() async => trades = tradesSource.values
.map((trade) => TradeListItem(
trade: trade,
appStore: appStore,
key: ValueKey('trade_list_item_${trade.id}_key'),
))
.toList();
Future<void> updateTradeList() async {
try {
final allTrades = await Trade.getAll();
runInAction(() {
trades = allTrades
.map((trade) => TradeListItem(
trade: trade,
appStore: appStore,
key: ValueKey('trade_list_item_${trade.id}_key'),
))
.toList();
});
} catch (_) {}
}
}
+17 -8
View File
@@ -15,15 +15,24 @@ class TradeListItem extends ActionListItem {
BalanceDisplayMode get displayMode => appStore.settingsStore.balanceDisplayMode;
String get tradeFormattedAmount => displayMode == BalanceDisplayMode.hiddenBalance
? "---"
: appStore.amountParsingProxy.getDisplayCryptoAmount(trade.amountFormatted(), trade.from!);
String get tradeFormattedAmount {
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
final from = trade.from;
if (from == null) return trade.amountFormatted();
return appStore.amountParsingProxy.getDisplayCryptoAmount(trade.amountFormatted(), from);
}
String get tradeFormattedReceiveAmount => displayMode == BalanceDisplayMode.hiddenBalance
? "---"
: appStore.amountParsingProxy
.getDisplayCryptoAmount(trade.receiveAmountFormatted(), trade.to!);
String get tradeFormattedReceiveAmount {
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
final to = trade.to;
if (to == null) return trade.receiveAmountFormatted();
return appStore.amountParsingProxy.getDisplayCryptoAmount(trade.receiveAmountFormatted(), to);
}
@override
DateTime get date => trade.createdAt!;
DateTime get date => trade.createdAt ?? DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
}
@@ -34,7 +34,6 @@ import 'package:cw_core/payment_uris.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
part 'exchange_trade_view_model.g.dart';
@@ -44,7 +43,6 @@ class ExchangeTradeViewModel = ExchangeTradeViewModelBase with _$ExchangeTradeVi
abstract class ExchangeTradeViewModelBase with Store {
ExchangeTradeViewModelBase({
required this.wallet,
required this.trades,
required this.tradesStore,
required this.sendViewModel,
required this.feesViewModel,
@@ -78,10 +76,10 @@ abstract class ExchangeTradeViewModelBase with Store {
_provider = StealthExExchangeProvider();
break;
case ExchangeProviderDescription.thorChain:
_provider = ThorChainExchangeProvider(tradesStore: trades);
_provider = ThorChainExchangeProvider();
break;
case ExchangeProviderDescription.chainflip:
_provider = ChainflipExchangeProvider(tradesStore: trades);
_provider = ChainflipExchangeProvider();
break;
case ExchangeProviderDescription.xoSwap:
_provider = XOSwapExchangeProvider();
@@ -106,7 +104,6 @@ abstract class ExchangeTradeViewModelBase with Store {
}
final WalletBase wallet;
final Box<Trade> trades;
final TradesStore tradesStore;
final SendViewModel sendViewModel;
final FeesViewModel feesViewModel;
@@ -217,7 +214,7 @@ abstract class ExchangeTradeViewModelBase with Store {
Future<void> confirmSending() async {
if (!isSendable) return;
final selected = trade.from ?? trade.userCurrencyFrom;
final selected = trade.from;
if (selected == null) {
printV('No selectable currency for trade ${trade.id}');
return;
@@ -231,35 +228,23 @@ abstract class ExchangeTradeViewModelBase with Store {
if (_provider is SwapsXyzExchangeProvider) {
final hash = pendingTransaction?.evmTxHashFromRawHex ?? pendingTransaction?.id ?? '';
trade.txId = hash;
if (trade.isInBox) {
await trade.save();
} else {
await trades.add(trade);
}
await trade.save();
}
if (_provider is ThorChainExchangeProvider) {
trade.id = pendingTransaction?.id ?? '';
trades.add(trade);
await trade.save();
}
}
@action
Future<void> _updateTrade() async {
try {
final agreedAmount = tradesStore.trade!.amount;
final isSendAll = tradesStore.trade!.isSendAll;
final updatedTrade = await _provider!.findTradeById(id: trade.id);
if (updatedTrade.createdAt == null && trade.createdAt != null)
updatedTrade.createdAt = trade.createdAt;
if (updatedTrade.amount.isEmpty) updatedTrade.amount = trade.amount;
trade = updatedTrade;
trade.amount = agreedAmount;
trade.isSendAll = isSendAll;
trade.mergeFindTradeByIdResult(updatedTrade);
await trade.save();
tradesStore.setTrade(trade);
_updateItems();
} catch (e) {
@@ -268,10 +253,8 @@ abstract class ExchangeTradeViewModelBase with Store {
}
void _updateItems() {
final trade = tradesStore.trade!;
final tradeFrom = trade.fromRaw >= 0 ? trade.from : trade.userCurrencyFrom;
final tradeTo = trade.toRaw >= 0 ? trade.to : trade.userCurrencyTo;
final tradeFrom = trade.from;
final tradeTo = trade.to;
final tagFrom = tradeFrom?.tag != null ? "${tradeFrom!.tag} " : "";
final tagTo = tradeTo?.tag != null ? "${tradeTo!.tag} " : "";
@@ -289,12 +272,12 @@ abstract class ExchangeTradeViewModelBase with Store {
),
);
if (tradeFrom != null || tradeTo != null) {
if (tradeFrom != null && tradeTo != null) {
items.addAll([
ExchangeTradeItem(
title: S.current.amount,
data:
"${_amountParsingProxy.getDisplayCryptoAmount(trade.amount, tradeFrom!)} ${_amountParsingProxy.getCryptoSymbol(tradeFrom)}",
"${_amountParsingProxy.getDisplayCryptoAmount(trade.amount, tradeFrom)} ${_amountParsingProxy.getCryptoSymbol(tradeFrom)}",
isCopied: false,
isReceiveDetail: false,
isExternalSendDetail: true,
@@ -302,14 +285,14 @@ abstract class ExchangeTradeViewModelBase with Store {
ExchangeTradeItem(
title: "${S.current.you_will_receive_estimated_amount}:",
data:
"${_amountParsingProxy.getDisplayCryptoAmount(tradesStore.trade?.receiveAmount ?? "0", tradeTo!)} ${_amountParsingProxy.getCryptoSymbol(tradeTo)}",
"${_amountParsingProxy.getDisplayCryptoAmount(trade.receiveAmount ?? "0", tradeTo)} ${_amountParsingProxy.getCryptoSymbol(tradeTo)}",
isCopied: true,
isReceiveDetail: true,
isExternalSendDetail: false,
),
ExchangeTradeItem(
title: "${S.current.send_to_this_address("${tradeFrom}", tagFrom)}:",
data: trade.inputAddress ?? "",
title: "${S.current.send_to_this_address("$tradeFrom", tagFrom)}:",
data: trade.inputAddress ?? '',
isCopied: false,
isReceiveDetail: false,
isExternalSendDetail: true,
@@ -367,7 +350,10 @@ abstract class ExchangeTradeViewModelBase with Store {
static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) {
final trade = tradesStore.trade!;
final tradeFrom = trade.fromRaw >= 0 ? trade.from : trade.userCurrencyFrom;
final tradeFrom = trade.from;
bool _sameCurrency(CryptoCurrency? a, CryptoCurrency? b) =>
a != null && b != null && a.titleAndTagEqual(b);
bool _isEthToken() =>
wallet.currency == CryptoCurrency.eth && tradeFrom?.tag == CryptoCurrency.eth.title;
@@ -393,8 +379,9 @@ abstract class ExchangeTradeViewModelBase with Store {
bool _isBscToken() =>
wallet.currency == CryptoCurrency.bnb && tradeFrom?.tag == CryptoCurrency.bnb.tag;
return tradeFrom == wallet.currency ||
tradeFrom == CryptoCurrency.btcln && wallet.currency == CryptoCurrency.btc ||
return _sameCurrency(tradeFrom, wallet.currency) ||
(_sameCurrency(tradeFrom, CryptoCurrency.btcln) &&
wallet.currency == CryptoCurrency.btc) ||
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
_isEthToken() ||
_isPolygonToken() ||
@@ -472,7 +459,7 @@ abstract class ExchangeTradeViewModelBase with Store {
PaymentURI? get paymentUri {
final inputAddress = trade.inputAddress;
final amount = trade.amount;
final fromCurrency = trade.from ?? trade.userCurrencyFrom;
final fromCurrency = trade.from;
if (inputAddress == null || inputAddress.isEmpty || fromCurrency == null) {
return null;
@@ -67,7 +67,6 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -95,7 +94,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
ExchangeViewModelBase(
this._appStore,
this.trades,
this._exchangeTemplateStore,
this.tradesStore,
this.sharedPreferences,
@@ -307,7 +305,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
[WalletType.monero, WalletType.wownero, WalletType.zcash].contains(wallet.type);
bool _useTorOnly;
final Box<Trade> trades;
final ExchangeTemplateStore _exchangeTemplateStore;
final TradesStore tradesStore;
final SharedPreferences sharedPreferences;
@@ -315,7 +312,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
List<ExchangeProvider> get _allProviders => [
ChangeNowExchangeProvider(settingsStore: _settingsStore),
// SideShiftExchangeProvider(),
ChainflipExchangeProvider(tradesStore: trades),
ChainflipExchangeProvider(),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
SwapTradeExchangeProvider(),
LetsExchangeExchangeProvider(),
@@ -1213,7 +1210,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
tradesStore.setTrade(trade);
if (trade.provider != ExchangeProviderDescription.thorChain) await trades.add(trade);
if (trade.provider != ExchangeProviderDescription.thorChain) await trade.save();
tradeState = TradeIsCreatedSuccessfully(trade: trade);
/// return after the first successful trade
@@ -1626,7 +1623,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
if (trade.provider == ExchangeProviderDescription.swapsXyz) {
final tradeFrom = trade.fromRaw >= 0 ? trade.from : trade.userCurrencyFrom;
final tradeFrom = trade.from;
if (tradeFrom == null) {
return CreateTradeResult(
+1 -3
View File
@@ -1091,9 +1091,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
_currentTrade!.stateRaw = isSuccess ? TradeState.completed.raw : TradeState.failed.raw;
if (_currentTrade!.isInBox) {
await _currentTrade!.save();
}
await _currentTrade!.save();
}
@action
+7 -44
View File
@@ -26,11 +26,8 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.d
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:collection/collection.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -41,11 +38,9 @@ class TradeDetailsViewModel = TradeDetailsViewModelBase with _$TradeDetailsViewM
abstract class TradeDetailsViewModelBase with Store {
TradeDetailsViewModelBase({
required Trade tradeForDetails,
required this.trades,
required this.appStore,
}) : items = ObservableList<StandartListItem>(),
trade = trades.values.firstWhereOrNull((element) => element.id == tradeForDetails.id) ??
tradeForDetails {
trade = tradeForDetails {
switch (trade.provider) {
case ExchangeProviderDescription.changeNow:
_provider = ChangeNowExchangeProvider(settingsStore: appStore.settingsStore);
@@ -63,7 +58,7 @@ abstract class TradeDetailsViewModelBase with Store {
_provider = ExolixExchangeProvider();
break;
case ExchangeProviderDescription.thorChain:
_provider = ThorChainExchangeProvider(tradesStore: trades);
_provider = ThorChainExchangeProvider();
break;
case ExchangeProviderDescription.swapTrade:
_provider = SwapTradeExchangeProvider();
@@ -74,7 +69,7 @@ abstract class TradeDetailsViewModelBase with Store {
_provider = StealthExExchangeProvider();
break;
case ExchangeProviderDescription.chainflip:
_provider = ChainflipExchangeProvider(tradesStore: trades);
_provider = ChainflipExchangeProvider();
break;
case ExchangeProviderDescription.xoSwap:
_provider = XOSwapExchangeProvider();
@@ -132,8 +127,6 @@ abstract class TradeDetailsViewModelBase with Store {
return null;
}
final Box<Trade> trades;
@observable
Trade trade;
@@ -151,22 +144,8 @@ abstract class TradeDetailsViewModelBase with Store {
try {
final updatedTrade = await _provider!.findTradeById(id: trade.id);
if (updatedTrade.createdAt == null && trade.createdAt != null) {
updatedTrade.createdAt = trade.createdAt;
}
if (updatedTrade.toRaw == -1 && trade.toRaw != -1) {
updatedTrade.toRaw = trade.toRaw;
}
Trade? foundElement = trades.values.firstWhereOrNull((element) => element.id == trade.id);
if (foundElement != null) {
final editedTrade = trades.get(foundElement.key);
editedTrade?.stateRaw = updatedTrade.stateRaw;
editedTrade?.save();
}
trade = updatedTrade;
trade.mergeFindTradeByIdResult(updatedTrade);
await trade.save();
_updateItems();
} catch (e) {
@@ -186,8 +165,8 @@ abstract class TradeDetailsViewModelBase with Store {
items.add(
DetailsListStatusItem(title: S.current.trade_details_state, value: trade.state.toString()));
final tradeFrom = _safeFrom(trade);
final tradeTo = _safeTo(trade);
final tradeFrom = trade.from;
final tradeTo = trade.to;
if (tradeFrom != null && tradeTo != null) {
items.add(TradeDetailsListCardItem.tradeDetails(
@@ -241,21 +220,5 @@ abstract class TradeDetailsViewModelBase with Store {
} catch (e) {}
}
CryptoCurrency? _safeFrom(Trade trade) {
try {
final raw = trade.fromRaw;
return raw >= 0 ? trade.from : trade.userCurrencyFrom;
} catch (_) {
return trade.userCurrencyFrom;
}
}
CryptoCurrency? _safeTo(Trade trade) {
try {
final raw = trade.toRaw;
return raw >= 0 ? trade.to : trade.userCurrencyTo;
} catch (_) {
return trade.userCurrencyTo;
}
}
}