diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index eee43a94d..e79d21ba3 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:bitbox/bitbox.dart' as bitbox; -import 'package:bitbox/src/utils/opcodes.dart' as bitboxOPCodes; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cw_bitcoin/bitcoin_address_record.dart'; @@ -258,7 +257,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { txb.addOutput(changeAddress, changeValue); } - if (opReturnMemo != null) txb.addOutput(createOpReturnScript(opReturnMemo), 0); + if (opReturnMemo != null) txb.addOutputData(opReturnMemo); for (var i = 0; i < inputs.length; i++) { final input = inputs[i]; @@ -333,11 +332,4 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { final HD = index == null ? hd : hd.derive(index); return base64Encode(HD.signMessage(message)); } - - Uint8List createOpReturnScript(String data) { - List script = []; - script.add(bitboxOPCodes.Opcodes.OP_RETURN); - script.addAll(utf8.encode(data)); - return Uint8List.fromList(script); - } } diff --git a/lib/exchange/provider/thorchain_exchange.provider.dart b/lib/exchange/provider/thorchain_exchange.provider.dart index c717ee665..e40ad836d 100644 --- a/lib/exchange/provider/thorchain_exchange.provider.dart +++ b/lib/exchange/provider/thorchain_exchange.provider.dart @@ -31,8 +31,9 @@ class ThorChainExchangeProvider extends ExchangeProvider { static const _baseURL = 'https://thornode.ninerealms.com'; static const _quotePath = '/thorchain/quote/swap'; + static const _txInfoPath = '/thorchain/tx/'; static const _affiliateName = 'cakewallet'; - static const _affiliateBps = '10'; + static const _affiliateBps = '0'; final Box tradesStore; @@ -68,6 +69,8 @@ class ThorChainExchangeProvider extends ExchangeProvider { 'from_asset': _normalizeCurrency(from), 'to_asset': _normalizeCurrency(to), 'amount': _doubleToThorChainString(amount), + 'affiliate': _affiliateName, + 'affiliate_bps': _affiliateBps }; final responseJSON = await _getSwapQuote(params); @@ -95,7 +98,7 @@ class ThorChainExchangeProvider extends ExchangeProvider { }; final responseJSON = await _getSwapQuote(params); - final minAmountIn = responseJSON['recommended_min_amount_in'] as String?; + final minAmountIn = responseJSON['recommended_min_amount_in'] as String? ?? '0.0'; return Limits(min: _thorChainAmountToDouble(minAmountIn)); } @@ -121,27 +124,58 @@ class ThorChainExchangeProvider extends ExchangeProvider { final inputAddress = responseJSON['inbound_address'] as String?; final memo = responseJSON['memo'] as String?; - final tradeId = await getNextTradeCounter(); return Trade( - id: tradeId.toString(), + id: '', from: request.fromCurrency, to: request.toCurrency, provider: description, inputAddress: inputAddress, createdAt: DateTime.now(), amount: request.fromAmount, - state: TradeState.created, + state: TradeState.pending, payoutAddress: request.toAddress, memo: memo); } + @override Future findTradeById({required String id}) async { - final foundTrade = tradesStore.values.firstWhereOrNull((element) => element.id == id); - if (foundTrade == null) { - throw Exception('Trade with id $id not found'); + if (id.isEmpty) throw Exception('Trade id is empty'); + final uri = Uri.parse('$_baseURL$_txInfoPath$id'); + final response = await http.get(uri); + + if (response.statusCode == 404) { + throw Exception('Trade not found for id: $id'); + } else if (response.statusCode != 200) { + throw Exception('Unexpected HTTP status: ${response.statusCode}'); } - return foundTrade; + + final responseJSON = json.decode(response.body); + final observedTx = responseJSON['observed_tx']; + if (observedTx == null) { + throw Exception('No observed transaction found for id: $id'); + } + + final tx = observedTx['tx']; + final String fromAddress = tx['from_address'] as String? ?? ''; + final String toAddress = tx['to_address'] as String? ?? ''; + final List coins = tx['coins'] as List; + final String? memo = tx['memo'] as String?; + final String toAsset = memo != null ? (memo.split(':')[1]).split('.')[0] : ''; + final status = observedTx['status'] as String?; + final formattedStatus = status ?? 'pending'; + + return Trade( + id: id, + from: CryptoCurrency.fromString(tx['chain'] as String? ?? ''), + to: CryptoCurrency.fromString(toAsset), + provider: description, + inputAddress: fromAddress, + payoutAddress: toAddress, + amount: coins.first['amount'] as String? ?? '0.0', + state: TradeState.deserialize(raw: formattedStatus), + memo: memo, + ); } Future> _getSwapQuote(Map params) async { @@ -160,31 +194,9 @@ class ThorChainExchangeProvider extends ExchangeProvider { return json.decode(response.body) as Map; } - String _normalizeCurrency(CryptoCurrency currency) { - switch (currency) { - case CryptoCurrency.btc: - return 'BTC.BTC'; - case CryptoCurrency.eth: - return 'ETH.ETH'; - case CryptoCurrency.ltc: - return 'LTC.LTC'; - case CryptoCurrency.bch: - return 'BCH.BCH'; - default: - return currency.title.toLowerCase(); - } - } + String _normalizeCurrency(CryptoCurrency currency) => '${currency.title}.${currency.title}'; String _doubleToThorChainString(double amount) => (amount * 1e8).toInt().toString(); - double _thorChainAmountToDouble(String? amount) => - amount == null ? 0.0 : double.parse(amount) / 1e8; - - Future getNextTradeCounter() async { - final prefs = await SharedPreferences.getInstance(); - int currentCounter = prefs.getInt(PreferencesKey.thorChainTradeCounter) ?? 0; - currentCounter++; - await prefs.setInt(PreferencesKey.thorChainTradeCounter, currentCounter); - return currentCounter; - } + double _thorChainAmountToDouble(String amount) => double.parse(amount) / 1e8; } diff --git a/lib/exchange/trade.dart b/lib/exchange/trade.dart index 4e4420629..8a7a88e07 100644 --- a/lib/exchange/trade.dart +++ b/lib/exchange/trade.dart @@ -29,6 +29,7 @@ class Trade extends HiveObject { this.providerName, this.fromWalletAddress, this.memo, + this.txId }) { if (provider != null) providerRaw = provider.raw; @@ -109,6 +110,9 @@ class Trade extends HiveObject { @HiveField(18) String? memo; + @HiveField(19) + String? txId; + static Trade fromMap(Map map) { return Trade( id: map['id'] as String, @@ -120,7 +124,8 @@ class Trade extends HiveObject { amount: map['amount'] as String, walletId: map['wallet_id'] as String, fromWalletAddress: map['from_wallet_address'] as String?, - memo: map['memo'] as String? + memo: map['memo'] as String?, + txId: map['tx_id'] as String? ); } @@ -134,7 +139,8 @@ class Trade extends HiveObject { 'amount': amount, 'wallet_id': walletId, 'from_wallet_address': fromWalletAddress, - 'memo': memo + 'memo': memo, + 'tx_id': txId }; } diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index ed56d9845..313e62b42 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -98,6 +98,7 @@ class TradeState extends EnumerableItem with Serializable { case 'sending': return sending; case 'success': + case 'done': return success; default: throw Exception('Unexpected token: $raw in TradeState deserialize'); diff --git a/lib/src/screens/dashboard/widgets/sync_indicator_icon.dart b/lib/src/screens/dashboard/widgets/sync_indicator_icon.dart index 11bde6dfa..21133a438 100644 --- a/lib/src/screens/dashboard/widgets/sync_indicator_icon.dart +++ b/lib/src/screens/dashboard/widgets/sync_indicator_icon.dart @@ -20,6 +20,7 @@ class SyncIndicatorIcon extends StatelessWidget { static const String created = 'created'; static const String fetching = 'fetching'; static const String finished = 'finished'; + static const String success = 'success'; @override Widget build(BuildContext context) { @@ -45,6 +46,7 @@ class SyncIndicatorIcon extends StatelessWidget { indicatorColor = Colors.red; break; case finished: + case success: indicatorColor = PaletteDark.brightGreen; break; default: diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 687d87f80..ad95ad44e 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -106,7 +106,11 @@ abstract class ExchangeTradeViewModelBase with Store { output.setCryptoAmount(trade.amount); if (_provider is ThorChainExchangeProvider) output.memo = trade.memo; sendViewModel.selectedCryptoCurrency = trade.from; - await sendViewModel.createTransaction(); + final pendingTransaction = await sendViewModel.createTransaction(); + if (_provider is ThorChainExchangeProvider) { + trade.id = pendingTransaction?.id ?? ''; + trades.add(trade); + } } @action diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 831141987..649f6f821 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/exchange/exchange_trade_state.dart'; import 'package:cake_wallet/exchange/limits.dart'; @@ -495,7 +496,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with trade.walletId = wallet.id; trade.fromWalletAddress = wallet.walletAddresses.address; tradesStore.setTrade(trade); - await trades.add(trade); + if(trade.provider != ExchangeProviderDescription.thorChain) await trades.add(trade); tradeState = TradeIsCreatedSuccessfully(trade: trade); /// return after the first successful trade diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 885e2efe0..ea0ae3e87 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -291,13 +291,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } @action - Future createTransaction() async { + Future createTransaction() async { try { state = IsExecutingState(); pendingTransaction = await wallet.createTransaction(_credentials()); state = ExecutedSuccessfullyState(); + return pendingTransaction; } catch (e) { state = FailureState(e.toString()); + return null; } }