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 '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<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 _quotePath = '/thorchain/quote/swap';
static const _txInfoPath = '/thorchain/tx/';
static const _affiliateName = 'cakewallet';
static const _affiliateBps = '10';
static const _affiliateBps = '0';
final Box<Trade> 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<Trade> 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<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 {
@ -160,31 +194,9 @@ class ThorChainExchangeProvider extends ExchangeProvider {
return json.decode(response.body) as Map<String, dynamic>;
}
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<int> 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;
}

View file

@ -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<String, Object?> 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
};
}

View file

@ -98,6 +98,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
case 'sending':
return sending;
case 'success':
case 'done':
return success;
default:
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 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:

View file

@ -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

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/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

View file

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