fetching thorChain traid info

This commit is contained in:
Serhii 2024-02-04 13:01:01 +02:00
parent a941014fc2
commit 571c9fd82a
8 changed files with 67 additions and 47 deletions

View file

@ -1,7 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:bitbox/bitbox.dart' as bitbox; 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_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
@ -258,7 +257,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
txb.addOutput(changeAddress, changeValue); 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++) { for (var i = 0; i < inputs.length; i++) {
final input = inputs[i]; final input = inputs[i];
@ -333,11 +332,4 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
final HD = index == null ? hd : hd.derive(index); final HD = index == null ? hd : hd.derive(index);
return base64Encode(HD.signMessage(message)); return base64Encode(HD.signMessage(message));
} }
Uint8List createOpReturnScript(String data) {
List<int> script = [];
script.add(bitboxOPCodes.Opcodes.OP_RETURN);
script.addAll(utf8.encode(data));
return Uint8List.fromList(script);
}
} }

View file

@ -31,8 +31,9 @@ class ThorChainExchangeProvider extends ExchangeProvider {
static const _baseURL = 'https://thornode.ninerealms.com'; static const _baseURL = 'https://thornode.ninerealms.com';
static const _quotePath = '/thorchain/quote/swap'; static const _quotePath = '/thorchain/quote/swap';
static const _txInfoPath = '/thorchain/tx/';
static const _affiliateName = 'cakewallet'; static const _affiliateName = 'cakewallet';
static const _affiliateBps = '10'; static const _affiliateBps = '0';
final Box<Trade> tradesStore; final Box<Trade> tradesStore;
@ -68,6 +69,8 @@ class ThorChainExchangeProvider extends ExchangeProvider {
'from_asset': _normalizeCurrency(from), 'from_asset': _normalizeCurrency(from),
'to_asset': _normalizeCurrency(to), 'to_asset': _normalizeCurrency(to),
'amount': _doubleToThorChainString(amount), 'amount': _doubleToThorChainString(amount),
'affiliate': _affiliateName,
'affiliate_bps': _affiliateBps
}; };
final responseJSON = await _getSwapQuote(params); final responseJSON = await _getSwapQuote(params);
@ -95,7 +98,7 @@ class ThorChainExchangeProvider extends ExchangeProvider {
}; };
final responseJSON = await _getSwapQuote(params); 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)); return Limits(min: _thorChainAmountToDouble(minAmountIn));
} }
@ -121,27 +124,58 @@ class ThorChainExchangeProvider extends ExchangeProvider {
final inputAddress = responseJSON['inbound_address'] as String?; final inputAddress = responseJSON['inbound_address'] as String?;
final memo = responseJSON['memo'] as String?; final memo = responseJSON['memo'] as String?;
final tradeId = await getNextTradeCounter();
return Trade( return Trade(
id: tradeId.toString(), id: '',
from: request.fromCurrency, from: request.fromCurrency,
to: request.toCurrency, to: request.toCurrency,
provider: description, provider: description,
inputAddress: inputAddress, inputAddress: inputAddress,
createdAt: DateTime.now(), createdAt: DateTime.now(),
amount: request.fromAmount, amount: request.fromAmount,
state: TradeState.created, state: TradeState.pending,
payoutAddress: request.toAddress, payoutAddress: request.toAddress,
memo: memo); memo: memo);
} }
@override
Future<Trade> findTradeById({required String id}) async { Future<Trade> findTradeById({required String id}) async {
final foundTrade = tradesStore.values.firstWhereOrNull((element) => element.id == id); if (id.isEmpty) throw Exception('Trade id is empty');
if (foundTrade == null) { final uri = Uri.parse('$_baseURL$_txInfoPath$id');
throw Exception('Trade with id $id not found'); 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<dynamic> coins = tx['coins'] as List<dynamic>;
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<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async { Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async {
@ -160,31 +194,9 @@ class ThorChainExchangeProvider extends ExchangeProvider {
return json.decode(response.body) as Map<String, dynamic>; return json.decode(response.body) as Map<String, dynamic>;
} }
String _normalizeCurrency(CryptoCurrency currency) { String _normalizeCurrency(CryptoCurrency currency) => '${currency.title}.${currency.title}';
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 _doubleToThorChainString(double amount) => (amount * 1e8).toInt().toString(); String _doubleToThorChainString(double amount) => (amount * 1e8).toInt().toString();
double _thorChainAmountToDouble(String? amount) => double _thorChainAmountToDouble(String amount) => double.parse(amount) / 1e8;
amount == null ? 0.0 : double.parse(amount) / 1e8;
Future<int> getNextTradeCounter() async {
final prefs = await SharedPreferences.getInstance();
int currentCounter = prefs.getInt(PreferencesKey.thorChainTradeCounter) ?? 0;
currentCounter++;
await prefs.setInt(PreferencesKey.thorChainTradeCounter, currentCounter);
return currentCounter;
}
} }

View file

@ -29,6 +29,7 @@ class Trade extends HiveObject {
this.providerName, this.providerName,
this.fromWalletAddress, this.fromWalletAddress,
this.memo, this.memo,
this.txId
}) { }) {
if (provider != null) providerRaw = provider.raw; if (provider != null) providerRaw = provider.raw;
@ -109,6 +110,9 @@ class Trade extends HiveObject {
@HiveField(18) @HiveField(18)
String? memo; String? memo;
@HiveField(19)
String? txId;
static Trade fromMap(Map<String, Object?> map) { static Trade fromMap(Map<String, Object?> map) {
return Trade( return Trade(
id: map['id'] as String, id: map['id'] as String,
@ -120,7 +124,8 @@ class Trade extends HiveObject {
amount: map['amount'] as String, amount: map['amount'] as String,
walletId: map['wallet_id'] as String, walletId: map['wallet_id'] as String,
fromWalletAddress: map['from_wallet_address'] 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, 'amount': amount,
'wallet_id': walletId, 'wallet_id': walletId,
'from_wallet_address': fromWalletAddress, 'from_wallet_address': fromWalletAddress,
'memo': memo 'memo': memo,
'tx_id': txId
}; };
} }

View file

@ -98,6 +98,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
case 'sending': case 'sending':
return sending; return sending;
case 'success': case 'success':
case 'done':
return success; return success;
default: default:
throw Exception('Unexpected token: $raw in TradeState deserialize'); throw Exception('Unexpected token: $raw in TradeState deserialize');

View file

@ -20,6 +20,7 @@ class SyncIndicatorIcon extends StatelessWidget {
static const String created = 'created'; static const String created = 'created';
static const String fetching = 'fetching'; static const String fetching = 'fetching';
static const String finished = 'finished'; static const String finished = 'finished';
static const String success = 'success';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -45,6 +46,7 @@ class SyncIndicatorIcon extends StatelessWidget {
indicatorColor = Colors.red; indicatorColor = Colors.red;
break; break;
case finished: case finished:
case success:
indicatorColor = PaletteDark.brightGreen; indicatorColor = PaletteDark.brightGreen;
break; break;
default: default:

View file

@ -106,7 +106,11 @@ abstract class ExchangeTradeViewModelBase with Store {
output.setCryptoAmount(trade.amount); output.setCryptoAmount(trade.amount);
if (_provider is ThorChainExchangeProvider) output.memo = trade.memo; if (_provider is ThorChainExchangeProvider) output.memo = trade.memo;
sendViewModel.selectedCryptoCurrency = trade.from; sendViewModel.selectedCryptoCurrency = trade.from;
await sendViewModel.createTransaction(); final pendingTransaction = await sendViewModel.createTransaction();
if (_provider is ThorChainExchangeProvider) {
trade.id = pendingTransaction?.id ?? '';
trades.add(trade);
}
} }
@action @action

View file

@ -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/preferences_key.dart';
import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/ethereum/ethereum.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_template.dart';
import 'package:cake_wallet/exchange/exchange_trade_state.dart'; import 'package:cake_wallet/exchange/exchange_trade_state.dart';
import 'package:cake_wallet/exchange/limits.dart'; import 'package:cake_wallet/exchange/limits.dart';
@ -495,7 +496,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
trade.walletId = wallet.id; trade.walletId = wallet.id;
trade.fromWalletAddress = wallet.walletAddresses.address; trade.fromWalletAddress = wallet.walletAddresses.address;
tradesStore.setTrade(trade); tradesStore.setTrade(trade);
await trades.add(trade); if(trade.provider != ExchangeProviderDescription.thorChain) await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade); tradeState = TradeIsCreatedSuccessfully(trade: trade);
/// return after the first successful trade /// return after the first successful trade

View file

@ -291,13 +291,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
} }
@action @action
Future<void> createTransaction() async { Future<PendingTransaction?> createTransaction() async {
try { try {
state = IsExecutingState(); state = IsExecutingState();
pendingTransaction = await wallet.createTransaction(_credentials()); pendingTransaction = await wallet.createTransaction(_credentials());
state = ExecutedSuccessfullyState(); state = ExecutedSuccessfullyState();
return pendingTransaction;
} catch (e) { } catch (e) {
state = FailureState(e.toString()); state = FailureState(e.toString());
return null;
} }
} }