Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-394-mac-auth-2

This commit is contained in:
Matthew Fosse 2024-04-25 10:42:57 -07:00
commit 56f88fd675
59 changed files with 330 additions and 154 deletions

View file

@ -10,7 +10,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
this.fullName,
this.iconPath,
this.tag,
this.enabled = false,
this.enabled = true,
})
: super(title: title, raw: raw);

View file

@ -10,7 +10,7 @@ import 'package:http/io_client.dart' as ioc;
part 'node.g.dart';
Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!;
Uri createUriFromElectrumAddress(String address, String path) => Uri.tryParse('tcp://$address$path')!;
@HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable {
@ -83,7 +83,7 @@ class Node extends HiveObject with Keyable {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
return createUriFromElectrumAddress(uriRaw);
return createUriFromElectrumAddress(uriRaw, path ?? '');
case WalletType.nano:
case WalletType.banano:
if (isSSL) {
@ -94,7 +94,7 @@ class Node extends HiveObject with Keyable {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
return Uri.https(uriRaw, '');
return Uri.https(uriRaw, path ?? '');
default:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}

View file

@ -234,14 +234,17 @@ abstract class EVMChainClient {
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
final symbol = (decodedResponse['symbol'] ?? '') as String;
String filteredSymbol = symbol.replaceFirst(RegExp('^\\\$'), '');
final name = decodedResponse['name'] ?? '';
final symbol = decodedResponse['symbol'] ?? '';
final decimal = decodedResponse['decimals'] ?? '0';
final iconPath = decodedResponse['logo'] ?? '';
return Erc20Token(
name: name,
symbol: symbol,
symbol: filteredSymbol,
contractAddress: contractAddress,
decimal: int.tryParse(decimal) ?? 0,
iconPath: iconPath,

View file

@ -468,9 +468,15 @@ abstract class EVMChainWalletBase
await token.delete();
balance.remove(token);
await _removeTokenTransactionsInHistory(token);
_updateBalance();
}
Future<void> _removeTokenTransactionsInHistory(Erc20Token token) async {
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
await transactionHistory.save();
}
Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async =>
await _client.getErc20Token(contractAddress, chainName);

View file

@ -34,10 +34,7 @@ class SolanaTransactionInfo extends TransactionInfo {
@override
String amountFormatted() {
String stringBalance = solAmount.toString();
if (stringBalance.toString().length >= 6) {
stringBalance = stringBalance.substring(0, 6);
}
return '$stringBalance $tokenSymbol';
}

View file

@ -273,32 +273,12 @@ abstract class SolanaWalletBase
final transactions = await _client.fetchTransactions(address);
final Map<String, SolanaTransactionInfo> result = {};
for (var transactionModel in transactions) {
result[transactionModel.id] = SolanaTransactionInfo(
id: transactionModel.id,
to: transactionModel.to,
from: transactionModel.from,
blockTime: transactionModel.blockTime,
direction: transactionModel.isOutgoingTx
? TransactionDirection.outgoing
: TransactionDirection.incoming,
solAmount: transactionModel.amount,
isPending: false,
txFee: transactionModel.fee,
tokenSymbol: transactionModel.tokenSymbol,
);
}
transactionHistory.addMany(result);
await transactionHistory.save();
await _addTransactionsToTransactionHistory(transactions);
}
/// Fetches the SPL Tokens transactions linked to the token account Public Key
Future<void> _updateSPLTokenTransactions() async {
List<SolanaTransactionModel> splTokenTransactions = [];
// List<SolanaTransactionModel> splTokenTransactions = [];
// Make a copy of keys to avoid concurrent modification
var tokenKeys = List<CryptoCurrency>.from(balance.keys);
@ -312,13 +292,20 @@ abstract class SolanaWalletBase
_walletKeyPair!,
);
splTokenTransactions.addAll(tokenTxs);
// splTokenTransactions.addAll(tokenTxs);
await _addTransactionsToTransactionHistory(tokenTxs);
}
}
// await _addTransactionsToTransactionHistory(splTokenTransactions);
}
Future<void> _addTransactionsToTransactionHistory(
List<SolanaTransactionModel> transactions,
) async {
final Map<String, SolanaTransactionInfo> result = {};
for (var transactionModel in splTokenTransactions) {
for (var transactionModel in transactions) {
result[transactionModel.id] = SolanaTransactionInfo(
id: transactionModel.id,
to: transactionModel.to,
@ -460,12 +447,23 @@ abstract class SolanaWalletBase
await token.delete();
balance.remove(token);
await _removeTokenTransactionsInHistory(token);
_updateBalance();
}
Future<void> _removeTokenTransactionsInHistory(SPLToken token) async {
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
await transactionHistory.save();
}
Future<SPLToken?> getSPLToken(String mintAddress) async {
// Convert SPL token mint address to public key
final mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress);
final Ed25519HDPublicKey mintPublicKey;
try {
mintPublicKey = Ed25519HDPublicKey.fromBase58(mintAddress);
} catch (_) {
return null;
}
// Fetch token's metadata account
try {
@ -480,10 +478,12 @@ abstract class SolanaWalletBase
iconPath = await _client.getIconImageFromTokenUri(token.uri);
} catch (_) {}
String filteredTokenSymbol = token.symbol.replaceFirst(RegExp('^\\\$'), '');
return SPLToken.fromMetadata(
name: token.name,
mint: token.mint,
symbol: token.symbol,
symbol: filteredTokenSymbol,
mintAddress: mintAddress,
iconPath: iconPath,
);

View file

@ -19,7 +19,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
@HiveField(3)
final int decimal;
@HiveField(4, defaultValue: false)
@HiveField(4, defaultValue: true)
bool _enabled;
@HiveField(5)
@ -39,7 +39,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
required this.mint,
this.iconPath,
this.tag = 'SOL',
bool enabled = false,
bool enabled = true,
}) : _enabled = enabled,
super(
name: mint.toLowerCase(),

View file

@ -291,4 +291,10 @@ class CWBitcoin extends Bitcoin {
outputsCount,
);
}
@override
int getMaxCustomFeeRate(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return (bitcoinWallet.feeRate(BitcoinTransactionPriority.fast) * 1.1).round();
}
}

View file

@ -17,13 +17,12 @@ class DFXBuyProvider extends BuyProvider {
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
static const _baseUrl = 'api.dfx.swiss';
static const _authPath = '/v1/auth/signMessage';
static const _signUpPath = '/v1/auth/signUp';
static const _signInPath = '/v1/auth/signIn';
// static const _signMessagePath = '/v1/auth/signMessage';
static const _authPath = '/v1/auth';
static const walletName = 'CakeWallet';
@override
String get title => 'DFX Connect';
String get title => 'DFX.swiss';
@override
String get providerDescription => S.current.dfx_option_description;
@ -73,21 +72,25 @@ class DFXBuyProvider extends BuyProvider {
String get walletAddress =>
wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address;
Future<String> getSignMessage() async {
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
Future<String> getSignMessage() async =>
"By_signing_this_message,_you_confirm_that_you_are_the_sole_owner_of_the_provided_Blockchain_address._Your_ID:_$walletAddress";
var response = await http.get(uri, headers: {'accept': 'application/json'});
// // Lets keep this just in case, but we can avoid this API Call
// Future<String> getSignMessage() async {
// final uri = Uri.https(_baseUrl, _signMessagePath, {'address': walletAddress});
//
// final response = await http.get(uri, headers: {'accept': 'application/json'});
//
// if (response.statusCode == 200) {
// final responseBody = jsonDecode(response.body);
// return responseBody['message'] as String;
// } else {
// throw Exception(
// 'Failed to get sign message. Status: ${response.statusCode} ${response.body}');
// }
// }
if (response.statusCode == 200) {
final responseBody = jsonDecode(response.body);
return responseBody['message'] as String;
} else {
throw Exception(
'Failed to get sign message. Status: ${response.statusCode} ${response.body}');
}
}
Future<String> signUp() async {
Future<String> auth() async {
final signMessage = getSignature(await getSignMessage());
final requestBody = jsonEncode({
@ -96,7 +99,7 @@ class DFXBuyProvider extends BuyProvider {
'signature': signMessage,
});
final uri = Uri.https(_baseUrl, _signUpPath);
final uri = Uri.https(_baseUrl, _authPath);
var response = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
@ -115,33 +118,6 @@ class DFXBuyProvider extends BuyProvider {
}
}
Future<String> signIn() async {
final signMessage = getSignature(await getSignMessage());
final requestBody = jsonEncode({
'address': walletAddress,
'signature': signMessage,
});
final uri = Uri.https(_baseUrl, _signInPath);
var response = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
body: requestBody,
);
if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body);
return responseBody['accessToken'] as String;
} else if (response.statusCode == 403) {
final responseBody = jsonDecode(response.body);
final message = responseBody['message'] ?? 'Service unavailable in your country';
throw Exception(message);
} else {
throw Exception('Failed to sign in. Status: ${response.statusCode} ${response.body}');
}
}
String getSignature(String message) {
switch (wallet.type) {
case WalletType.ethereum:
@ -164,17 +140,7 @@ class DFXBuyProvider extends BuyProvider {
final blockchain = this.blockchain;
final actionType = isBuyAction == true ? '/buy' : '/sell';
String accessToken;
try {
accessToken = await signUp();
} on Exception catch (e) {
if (e.toString().contains('409')) {
accessToken = await signIn();
} else {
rethrow;
}
}
final accessToken = await auth();
final uri = Uri.https('services.dfx.swiss', actionType, {
'session': accessToken,
@ -198,7 +164,7 @@ class DFXBuyProvider extends BuyProvider {
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: "DFX Connect",
alertTitle: "DFX.swiss",
alertContent: S.of(context).buy_provider_unavailable + ': $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());

View file

@ -0,0 +1,9 @@
class CreateTradeResult {
bool result;
String? errorMessage;
CreateTradeResult({
required this.result,
this.errorMessage,
});
}

View file

@ -11,5 +11,9 @@ class NodeAddressValidator extends TextValidator {
class NodePathValidator extends TextValidator {
NodePathValidator()
: super(errorMessage: S.current.error_text_node_address, pattern: '^([/0-9a-zA-Z.\-]+)?\$');
: super(
errorMessage: S.current.error_text_node_address,
pattern: '^([/0-9a-zA-Z.\-]+)?\$',
isAutovalidate: true,
);
}

View file

@ -26,4 +26,4 @@ class BiometricAuth {
return canAuthenticate;
}
}
}

View file

@ -3,6 +3,8 @@ String calculateFiatAmount({double? price, String? cryptoAmount}) {
return '0.00';
}
cryptoAmount = cryptoAmount.replaceAll(',', '.');
final _amount = double.parse(cryptoAmount);
final _result = price * _amount;
final result = _result < 0 ? _result * -1 : _result;

View file

@ -22,7 +22,7 @@ extension ProviderTypeName on ProviderType {
case ProviderType.robinhood:
return 'Robinhood Connect';
case ProviderType.dfx:
return 'DFX Connect';
return 'DFX.swiss';
case ProviderType.onramper:
return 'Onramper';
case ProviderType.moonpay:

View file

@ -142,8 +142,10 @@ class CWEthereum extends Ethereum {
}
wallet as EthereumWallet;
return wallet.erc20Currencies
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
return wallet.erc20Currencies.firstWhere(
(element) => transaction.tokenSymbol == element.symbol,
);
}
@override

View file

@ -140,8 +140,10 @@ class CWPolygon extends Polygon {
}
wallet as PolygonWallet;
return wallet.erc20Currencies.firstWhere(
(element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase());
(element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase(),
);
}
@override

View file

@ -110,8 +110,10 @@ class CWSolana extends Solana {
}
wallet as SolanaWallet;
return wallet.splTokenCurrencies
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
return wallet.splTokenCurrencies.firstWhere(
(element) => transaction.tokenSymbol == element.symbol,
);
}
@override

View file

@ -96,18 +96,20 @@ class NodeForm extends StatelessWidget {
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _pathController,
hintText: "/path",
validator: NodePathValidator(),
),
)
],
),
SizedBox(height: 10.0),
if (nodeViewModel.hasPathSupport) ...[
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _pathController,
hintText: "/path",
validator: NodePathValidator(),
),
)
],
),
SizedBox(height: 10.0),
],
Row(
children: <Widget>[
Expanded(

View file

@ -675,6 +675,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null;
await showPopUp<void>(
@ -689,6 +690,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
sendViewModel.displayFeeRate(priority, customFeeRate?.round()),
selectedAtIndex: selectedIdx,
customItemIndex: customItemIndex,
maxValue: maxCustomFeeRate,
title: S.of(context).please_select,
headerEnabled: !isBitcoinWallet,
closeOnItemSelected: !isBitcoinWallet,

View file

@ -37,6 +37,7 @@ class OtherSettingsPage extends BasePage {
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
) :
SettingsPickerCell(
title: S.current.settings_fee_priority,

View file

@ -15,6 +15,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
this.isGridView = false,
this.matchingCriteria,
this.customValue,
this.maxValue,
this.customItemIndex,
this.onItemSelected})
: super(
@ -34,6 +35,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
displayItem: (ItemType item) => displayItem!(item, sliderValue.round()),
selectedAtIndex: selectedAtIndex,
customItemIndex: customItemIndex,
maxValue: maxValue,
headerEnabled: false,
closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center,
@ -61,6 +63,7 @@ class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
final bool isGridView;
final bool Function(ItemType, String)? matchingCriteria;
double? customValue;
double? maxValue;
int? customItemIndex;
@override

View file

@ -10,6 +10,7 @@ class StandardPickerListItem<T> extends TransactionDetailsListItem {
required this.onItemSelected,
required this.selectedIdx,
required this.customItemIndex,
this.maxValue,
required this.customValue})
: super(title: title, value: value);
@ -18,6 +19,7 @@ class StandardPickerListItem<T> extends TransactionDetailsListItem {
final Function(double) onSliderChanged;
final Function(T) onItemSelected;
final int selectedIdx;
final double? maxValue;
final int customItemIndex;
double customValue;
}

View file

@ -74,6 +74,7 @@ class RBFDetailsPage extends BasePage {
selectedIdx: item.selectedIdx,
customItemIndex: item.customItemIndex,
customValue: item.customValue,
maxValue: item.maxValue,
);
}

View file

@ -27,14 +27,21 @@ class Picker<Item> extends StatefulWidget {
this.headerEnabled = true,
this.closeOnItemSelected = true,
this.sliderValue,
this.minValue,
this.maxValue,
this.customItemIndex,
this.isWrapped = true,
this.borderColor,
this.onSliderChanged,
this.matchingCriteria,
}) : assert(hintText == null ||
matchingCriteria !=
null); // make sure that if the search field is enabled then there is a searching criteria provided
}) : assert(hintText == null || matchingCriteria != null) {
// make sure that if the search field is enabled then there is a searching criteria provided
if (sliderValue != null && maxValue != null) {
if (sliderValue! > maxValue!) {
sliderValue = maxValue;
}
}
}
final int selectedAtIndex;
final List<Item> items;
@ -49,12 +56,14 @@ class Picker<Item> extends StatefulWidget {
final String? hintText;
final bool headerEnabled;
final bool closeOnItemSelected;
final double? sliderValue;
double? sliderValue;
final double? minValue;
final int? customItemIndex;
final bool isWrapped;
final Color? borderColor;
final Function(double)? onSliderChanged;
final bool Function(Item, String)? matchingCriteria;
final double? maxValue;
@override
_PickerState<Item> createState() => _PickerState<Item>(items, images, onItemSelected);
@ -138,7 +147,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
containerHeight = height * 0.75;
}
final content = Column (
final content = Column(
children: [
if (widget.title?.isNotEmpty ?? false)
Container(
@ -211,8 +220,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
fontWeight: FontWeight.w500,
fontFamily: 'Lato',
decoration: TextDecoration.none,
color:
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
),
),
)
@ -491,8 +501,8 @@ class _PickerState<Item> extends State<Picker<Item>> {
child: Slider(
value: widget.sliderValue ?? 1,
onChanged: isActivated ? widget.onSliderChanged : null,
min: 1,
max: 100,
min: widget.minValue ?? 1,
max: widget.maxValue ?? 100,
divisions: 100,
),
),

View file

@ -15,6 +15,7 @@ class StandardPickerList<T> extends StatefulWidget {
required this.selectedIdx,
required this.customItemIndex,
required this.customValue,
this.maxValue,
}) : super(key: key);
final String title;
@ -26,6 +27,7 @@ class StandardPickerList<T> extends StatefulWidget {
final String value;
final int selectedIdx;
final double customValue;
final double? maxValue;
@override
_StandardPickerListState<T> createState() => _StandardPickerListState<T>();
@ -59,6 +61,7 @@ class _StandardPickerListState<T> extends State<StandardPickerList<T>> {
displayItem: adaptedDisplayItem,
selectedAtIndex: selectedIdx,
customItemIndex: widget.customItemIndex,
maxValue: widget.maxValue,
headerEnabled: false,
closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center,

View file

@ -173,4 +173,4 @@ class ValidatableAnnotatedEditableTextState extends EditableTextState {
return TextSpan(style: widget.style, text: text);
}
}
}

View file

@ -122,4 +122,4 @@ abstract class AuthViewModelBase with Store {
_authService.saveLastAuthTime();
}
}
}
}

View file

@ -3,8 +3,20 @@ import 'dart:collection';
import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/create_trade_result.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
@ -33,14 +45,6 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'exchange_view_model.g.dart';
@ -516,10 +520,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
trade.walletId = wallet.id;
trade.fromWalletAddress = wallet.walletAddresses.address;
if (!isCanCreateTrade(trade)) {
final canCreateTrade = await isCanCreateTrade(trade);
if (!canCreateTrade.result) {
tradeState = TradeIsCreatedFailure(
title: S.current.trade_not_created,
error: S.current.thorchain_taproot_address_not_supported);
title: S.current.trade_not_created,
error: canCreateTrade.errorMessage ?? '',
);
return;
}
@ -776,16 +782,100 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
int get receiveMaxDigits => receiveCurrency.decimals;
bool isCanCreateTrade(Trade trade) {
Future<CreateTradeResult> isCanCreateTrade(Trade trade) async {
if (trade.provider == ExchangeProviderDescription.thorChain) {
final payoutAddress = trade.payoutAddress ?? '';
final fromWalletAddress = trade.fromWalletAddress ?? '';
final tapRootPattern = RegExp(P2trAddress.regex.pattern);
if (tapRootPattern.hasMatch(payoutAddress) || tapRootPattern.hasMatch(fromWalletAddress)) {
return false;
return CreateTradeResult(
result: false,
errorMessage: S.current.thorchain_taproot_address_not_supported,
);
}
final currenciesToCheckPattern = RegExp('0x[0-9a-zA-Z]');
// Perform checks for payOutAddress
final isPayOutAddressAccordingToPattern = currenciesToCheckPattern.hasMatch(payoutAddress);
if (isPayOutAddressAccordingToPattern) {
final isPayOutAddressEOA = await _isExternallyOwnedAccountAddress(payoutAddress);
return CreateTradeResult(
result: isPayOutAddressEOA,
errorMessage:
!isPayOutAddressEOA ? S.current.thorchain_contract_address_not_supported : null,
);
}
// Perform checks for fromWalletAddress
final isFromWalletAddressAddressAccordingToPattern =
currenciesToCheckPattern.hasMatch(fromWalletAddress);
if (isFromWalletAddressAddressAccordingToPattern) {
final isFromWalletAddressEOA = await _isExternallyOwnedAccountAddress(fromWalletAddress);
return CreateTradeResult(
result: isFromWalletAddressEOA,
errorMessage:
!isFromWalletAddressEOA ? S.current.thorchain_contract_address_not_supported : null,
);
}
}
return true;
return CreateTradeResult(result: true);
}
String _normalizeReceiveCurrency(CryptoCurrency receiveCurrency) {
switch (receiveCurrency) {
case CryptoCurrency.eth:
return 'eth';
case CryptoCurrency.maticpoly:
return 'polygon';
default:
return receiveCurrency.tag ?? '';
}
}
Future<bool> _isExternallyOwnedAccountAddress(String receivingAddress) async {
final normalizedReceiveCurrency = _normalizeReceiveCurrency(receiveCurrency);
final isEOAAddress = !(await _isContractAddress(normalizedReceiveCurrency, receivingAddress));
return isEOAAddress;
}
Future<bool> _isContractAddress(String chainName, String contractAddress) async {
final httpClient = http.Client();
final uri = Uri.https(
'deep-index.moralis.io',
'/api/v2.2/erc20/metadata',
{
"chain": chainName,
"addresses": contractAddress,
},
);
try {
final response = await httpClient.get(
uri,
headers: {
"Accept": "application/json",
"X-API-Key": secrets.moralisApiKey,
},
);
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
final name = decodedResponse['name'] as String?;
bool isContractAddress = name!.isNotEmpty;
return isContractAddress;
} catch (e) {
print(e);
return false;
}
}
}

View file

@ -69,6 +69,24 @@ abstract class NodeCreateOrEditViewModelBase with Store {
bool get hasTestnetSupport => _walletType == WalletType.bitcoin;
bool get hasPathSupport {
switch (_walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
case WalletType.banano:
case WalletType.nano:
return true;
case WalletType.none:
case WalletType.monero:
case WalletType.haven:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.bitcoin:
return false;
}
}
String get uri {
var uri = address;
@ -217,7 +235,6 @@ abstract class NodeCreateOrEditViewModelBase with Store {
final port = uri.port.toString();
final path = uri.path;
setAddress(ipAddress);
setPath(path);
setPassword(rpcPassword);

View file

@ -166,6 +166,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return null;
}
int? get maxCustomFeeRate {
if (wallet.type == WalletType.bitcoin) {
return bitcoin!.getMaxCustomFeeRate(wallet);
}
return null;
}
@computed
int get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate;
@ -324,14 +331,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider}) async {
try {
state = IsExecutingState();
pendingTransaction = await wallet.createTransaction(_credentials());
if (provider is ThorChainExchangeProvider) {
final outputCount = pendingTransaction?.outputCount ?? 0;
if (outputCount > 10) {
throw Exception("ThorChain does not support more than 10 outputs");
throw Exception("THORChain does not support more than 10 outputs");
}
if (_hasTaprootInput(pendingTransaction)) {
throw Exception("ThorChain does not support Taproot addresses");
throw Exception("THORChain does not support Taproot addresses");
}
}
state = ExecutedSuccessfullyState();

View file

@ -140,6 +140,13 @@ abstract class OtherSettingsViewModelBase with Store {
return customItem != null ? priorities.indexOf(customItem) : null;
}
int? get maxCustomFeeRate {
if (_wallet.type == WalletType.bitcoin) {
return bitcoin!.getMaxCustomFeeRate(_wallet);
}
return null;
}
@action
ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) =>
_settingsStore.defaultBuyProviders[walletType] = buyProviderType;

View file

@ -348,12 +348,14 @@ abstract class TransactionDetailsViewModelBase with Store {
final customItem = priorities.firstWhereOrNull(
(element) => element == sendViewModel.bitcoinTransactionPriorityCustom);
final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
RBFListItems.add(StandardPickerListItem(
title: S.current.estimated_new_fee,
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}',
items: priorityForWalletType(wallet.type),
customValue: settingsStore.customBitcoinFeeRate.toDouble(),
maxValue: maxCustomFeeRate,
selectedIdx: selectedItem,
customItemIndex: customItemIndex ?? 0,
displayItem: (dynamic priority, double sliderValue) =>

View file

@ -660,6 +660,7 @@
"template_name": "اسم القالب",
"third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!",
"third_intro_title": "يتماشي Yat بلطف مع الآخرين",
"thorchain_contract_address_not_supported": "لا يدعم Thorchain الإرسال إلى عنوان العقد",
"thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.",
"time": "${minutes}د ${seconds}س",
"tip": "بقشيش:",

View file

@ -660,6 +660,7 @@
"template_name": "Име на шаблон",
"third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!",
"third_intro_title": "Yat добре се сработва с други",
"thorchain_contract_address_not_supported": "Thorchain не подкрепя изпращането до адрес на договор",
"thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.",
"time": "${minutes} мин ${seconds} сек",
"tip": "Tip:",

View file

@ -660,6 +660,7 @@
"template_name": "Název šablony",
"third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!",
"third_intro_title": "Yat dobře spolupracuje s ostatními",
"thorchain_contract_address_not_supported": "Thorchain nepodporuje odeslání na adresu smlouvy",
"thorchain_taproot_address_not_supported": "Poskytovatel Thorchain nepodporuje adresy Taproot. Změňte adresu nebo vyberte jiného poskytovatele.",
"time": "${minutes}m ${seconds}s",
"tip": "Spropitné:",

View file

@ -50,7 +50,7 @@
"anonpay_description": "Generieren Sie ${type}. Der Empfänger kann ${method} mit jeder unterstützten Kryptowährung verwenden, und Sie erhalten Geld in dieser Wallet.",
"apk_update": "APK-Update",
"approve": "Genehmigen",
"arrive_in_this_address": "${currency} ${tag}wird an dieser Adresse ankommen",
"arrive_in_this_address": "${currency} ${tag} wird an dieser Adresse ankommen",
"ascending": "Aufsteigend",
"ask_each_time": "Jedes Mal fragen",
"auth_store_ban_timeout": "ban_timeout",
@ -87,7 +87,7 @@
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
"buy_with": "Kaufen mit",
"by_cake_pay": "von Cake Pay",
"cake_2fa_preset": "Kuchen 2FA-Voreinstellung",
"cake_2fa_preset": "Cake 2FA-Voreinstellung",
"cake_dark_theme": "Cake Dark Thema",
"cake_pay_account_note": "Melden Sie sich nur mit einer E-Mail-Adresse an, um Karten anzuzeigen und zu kaufen. Einige sind sogar mit Rabatt erhältlich!",
"cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.",
@ -120,7 +120,7 @@
"change_wallet_alert_title": "Aktuelle Wallet ändern",
"choose_account": "Konto auswählen",
"choose_address": "\n\nBitte wählen Sie die Adresse:",
"choose_derivation": "Wählen Sie Brieftaschenableitung",
"choose_derivation": "Wählen Sie Wallet-Ableitung",
"choose_from_available_options": "Wähle aus verfügbaren Optionen:",
"choose_one": "Wähle ein",
"choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",
@ -199,7 +199,7 @@
"disable_fiat": "Fiat deaktivieren",
"disable_sell": "Verkaufsaktion deaktivieren",
"disableBatteryOptimization": "Batterieoptimierung deaktivieren",
"disableBatteryOptimizationDescription": "Möchten Sie die Batterieoptimierung deaktivieren, um die Hintergrundsynchronisierung freier und reibungsloser zu gestalten?",
"disableBatteryOptimizationDescription": "Möchten Sie die Batterieoptimierung deaktivieren, um die Hintergrundsynchronisierung reibungsloser zu gestalten?",
"disabled": "Deaktiviert",
"discount": "${value} % sparen",
"display_settings": "Anzeigeeinstellungen",
@ -459,8 +459,8 @@
"reconnect_alert_text": "Sind Sie sicher, dass Sie sich neu verbinden möchten?",
"reconnection": "Neu verbinden",
"red_dark_theme": "Red Dark Thema",
"red_light_theme": "Rotlichtthema",
"redeemed": "Versilbert",
"red_light_theme": "Red Light Thema",
"redeemed": "Eingelöst",
"refund_address": "Rückerstattungsadresse",
"reject": "Ablehnen",
"remaining": "Rest",
@ -532,7 +532,7 @@
"seed_alert_title": "Achtung",
"seed_alert_yes": "Ja, habe ich",
"seed_choose": "Seed-Sprache auswählen",
"seed_hex_form": "Brieftaschensamen (Sechskantform)",
"seed_hex_form": "Seed (Hexformat)",
"seed_key": "Seed-Schlüssel",
"seed_language": "Seed-Sprache",
"seed_language_chinese": "Chinesisch",
@ -584,7 +584,7 @@
"send_your_wallet": "Ihre Wallet",
"sending": "Senden",
"sent": "Versendet",
"service_health_disabled": "Service Health Bulletin ist behindert",
"service_health_disabled": "Service Health Bulletin ist deaktiviert",
"service_health_disabled_message": "Dies ist die Seite \"Service Health Bulletin\", können Sie diese Seite unter Einstellungen -> Privatsphäre aktivieren",
"settings": "Einstellungen",
"settings_all": "ALLE",
@ -661,6 +661,7 @@
"template_name": "Vorlagenname",
"third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!",
"third_intro_title": "Yat spielt gut mit anderen",
"thorchain_contract_address_not_supported": "Thorchain unterstützt das Senden an eine Vertragsadresse nicht",
"thorchain_taproot_address_not_supported": "Der Thorchain -Anbieter unterstützt keine Taproot -Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.",
"time": "${minutes}m ${seconds}s",
"tip": "Hinweis:",
@ -821,4 +822,4 @@
"you_will_get": "Konvertieren zu",
"you_will_send": "Konvertieren von",
"yy": "YY"
}
}

View file

@ -660,6 +660,7 @@
"template_name": "Template Name",
"third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
"third_intro_title": "Yat plays nicely with others",
"thorchain_contract_address_not_supported": "THORChain does not support sending to a contract address",
"thorchain_taproot_address_not_supported": "The ThorChain provider does not support Taproot addresses. Please change the address or select a different provider.",
"time": "${minutes}m ${seconds}s",
"tip": "Tip:",

View file

@ -661,6 +661,7 @@
"template_name": "Nombre de la plantilla",
"third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!",
"third_intro_title": "Yat juega muy bien con otras",
"thorchain_contract_address_not_supported": "Thorchain no admite enviar a una dirección de contrato",
"thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambie la dirección o seleccione un proveedor diferente.",
"time": "${minutes}m ${seconds}s",
"tip": "Consejo:",

View file

@ -660,6 +660,7 @@
"template_name": "Nom du modèle",
"third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !",
"third_intro_title": "Yat est universel",
"thorchain_contract_address_not_supported": "Thorchain ne prend pas en charge l'envoi à une adresse de contrat",
"thorchain_taproot_address_not_supported": "Le fournisseur de Thorchain ne prend pas en charge les adresses de tapoot. Veuillez modifier l'adresse ou sélectionner un autre fournisseur.",
"time": "${minutes}m ${seconds}s",
"tip": "Pourboire :",

View file

@ -662,6 +662,7 @@
"template_name": "Sunan Samfura",
"third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!",
"third_intro_title": "Yat yana wasa da kyau tare da wasu",
"thorchain_contract_address_not_supported": "Thorchain baya goyon bayan aika zuwa adireshin kwangila",
"thorchain_taproot_address_not_supported": "Mai ba da tallafi na ThorChain baya goyan bayan adreshin taproot. Da fatan za a canza adireshin ko zaɓi mai bayarwa daban.",
"time": "${minutes}m ${seconds}s",
"tip": "Tukwici:",

View file

@ -662,6 +662,7 @@
"template_name": "टेम्पलेट नाम",
"third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!",
"third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है",
"thorchain_contract_address_not_supported": "थोरचेन एक अनुबंध पते पर भेजने का समर्थन नहीं करता है",
"thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।",
"time": "${minutes}m ${seconds}s",
"tip": "टिप:",

View file

@ -660,6 +660,7 @@
"template_name": "Naziv predloška",
"third_intro_content": "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!",
"third_intro_title": "Yat se lijepo igra s drugima",
"thorchain_contract_address_not_supported": "Thorchain ne podržava slanje na adresu ugovora",
"thorchain_taproot_address_not_supported": "Thorchain pružatelj ne podržava Taproot adrese. Promijenite adresu ili odaberite drugog davatelja usluga.",
"time": "${minutes}m ${seconds}s",
"tip": "Savjet:",

View file

@ -663,6 +663,7 @@
"template_name": "Nama Templat",
"third_intro_content": "Yats hidup di luar Cake Wallet juga. Setiap alamat dompet di dunia dapat diganti dengan Yat!",
"third_intro_title": "Yat bermain baik dengan yang lain",
"thorchain_contract_address_not_supported": "Thorchain tidak mendukung pengiriman ke alamat kontrak",
"thorchain_taproot_address_not_supported": "Penyedia Thorchain tidak mendukung alamat Taproot. Harap ubah alamatnya atau pilih penyedia yang berbeda.",
"time": "${minutes}m ${seconds}s",
"tip": "Tip:",

View file

@ -662,6 +662,7 @@
"template_name": "Nome modello",
"third_intro_content": "Yat può funzionare anche fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con uno Yat!",
"third_intro_title": "Yat gioca bene con gli altri",
"thorchain_contract_address_not_supported": "Thorchain non supporta l'invio a un indirizzo contrattuale",
"thorchain_taproot_address_not_supported": "Il provider di Thorchain non supporta gli indirizzi di TapRoot. Si prega di modificare l'indirizzo o selezionare un fornitore diverso.",
"time": "${minutes}m ${seconds}s",
"tip": "Suggerimento:",

View file

@ -661,6 +661,7 @@
"template_name": "テンプレート名",
"third_intro_content": "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます",
"third_intro_title": "Yatは他の人とうまく遊ぶ",
"thorchain_contract_address_not_supported": "Thorchainは、契約アドレスへの送信をサポートしていません",
"thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。",
"time": "${minutes}m ${seconds}s",
"tip": "ヒント: ",

View file

@ -661,6 +661,7 @@
"template_name": "템플릿 이름",
"third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!",
"third_intro_title": "Yat는 다른 사람들과 잘 놉니다.",
"thorchain_contract_address_not_supported": "Thorchain은 계약 주소로 보내는 것을 지원하지 않습니다",
"thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.",
"time": "${minutes}m ${seconds}s",
"tip": "팁:",

View file

@ -660,6 +660,7 @@
"template_name": "နမူနာပုံစံ",
"third_intro_content": "Yats သည် Cake Wallet အပြင်ဘက်တွင် နေထိုင်ပါသည်။ ကမ္ဘာပေါ်ရှိ မည်သည့်ပိုက်ဆံအိတ်လိပ်စာကို Yat ဖြင့် အစားထိုးနိုင်ပါသည်။",
"third_intro_title": "Yat သည် အခြားသူများနှင့် ကောင်းစွာကစားသည်။",
"thorchain_contract_address_not_supported": "Thorchain သည်စာချုပ်လိပ်စာသို့ပို့ခြင်းမပြုပါ",
"thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။",
"time": "${minutes}m ${seconds}s",
"tip": "အကြံပြုချက်-",

View file

@ -660,6 +660,7 @@
"template_name": "Sjabloonnaam",
"third_intro_content": "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!",
"third_intro_title": "Yat speelt leuk met anderen",
"thorchain_contract_address_not_supported": "Thorchain ondersteunt het verzenden niet naar een contractadres",
"thorchain_taproot_address_not_supported": "De Thorchain -provider ondersteunt geen Taprooot -adressen. Wijzig het adres of selecteer een andere provider.",
"time": "${minutes}m ${seconds}s",
"tip": "Tip:",

View file

@ -660,6 +660,7 @@
"template_name": "Nazwa szablonu",
"third_intro_content": "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!",
"third_intro_title": "Yat ładnie bawi się z innymi",
"thorchain_contract_address_not_supported": "Thorchain nie wspiera wysyłania na adres umowy",
"thorchain_taproot_address_not_supported": "Dostawca Thorchain nie obsługuje adresów TAPROOT. Zmień adres lub wybierz innego dostawcę.",
"time": "${minutes}m ${seconds}s",
"tip": "wskazówka:",

View file

@ -662,6 +662,7 @@
"template_name": "Nome do modelo",
"third_intro_content": "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!",
"third_intro_title": "Yat joga bem com os outros",
"thorchain_contract_address_not_supported": "Thorchain não suporta o envio para um endereço de contrato",
"thorchain_taproot_address_not_supported": "O provedor de Thorchain não suporta endereços de raiz de Tap. Altere o endereço ou selecione um provedor diferente.",
"time": "${minutes}m ${seconds}s",
"tip": "Dica:",

View file

@ -661,6 +661,7 @@
"template_name": "Имя Шаблона",
"third_intro_content": "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!",
"third_intro_title": "Yat хорошо взаимодействует с другими",
"thorchain_contract_address_not_supported": "Thorchain не поддерживает отправку на адрес контракта",
"thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.",
"time": "${minutes}мин ${seconds}сек",
"tip": "Совет:",

View file

@ -660,6 +660,7 @@
"template_name": "ชื่อแม่แบบ",
"third_intro_content": "Yat อาศัยอยู่นอก Cake Wallet ด้วย ที่อยู่กระเป๋าใดๆ ทั่วโลกสามารถแทนด้วย Yat ได้อีกด้วย!",
"third_intro_title": "Yat ปฏิบัติตนอย่างดีกับผู้อื่น",
"thorchain_contract_address_not_supported": "Thorchain ไม่สนับสนุนการส่งไปยังที่อยู่สัญญา",
"thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น",
"time": "${minutes}m ${seconds}s",
"tip": "เพิ่มค่าตอบแทน:",

View file

@ -660,6 +660,7 @@
"template_name": "Pangalan ng Template",
"third_intro_content": "Ang mga yats ay nakatira sa labas ng cake wallet, din. Ang anumang address ng pitaka sa mundo ay maaaring mapalitan ng isang yat!",
"third_intro_title": "Si Yat ay mahusay na gumaganap sa iba",
"thorchain_contract_address_not_supported": "Hindi sinusuportahan ng Thorchain ang pagpapadala sa isang address ng kontrata",
"thorchain_taproot_address_not_supported": "Ang Tagabigay ng Thorchain ay hindi sumusuporta sa mga address ng taproot. Mangyaring baguhin ang address o pumili ng ibang provider.",
"time": "${minutes} m ${seconds} s",
"tip": "Tip:",

View file

@ -660,6 +660,7 @@
"template_name": "şablon adı",
"third_intro_content": "Yat'lar Cake Wallet'ın dışında da çalışabilir. Dünya üzerindeki herhangi bir cüzdan adresi Yat ile değiştirilebilir!",
"third_intro_title": "Yat diğerleriyle iyi çalışır",
"thorchain_contract_address_not_supported": "Thorchain bir sözleşme adresine göndermeyi desteklemiyor",
"thorchain_taproot_address_not_supported": "Thorchain sağlayıcısı Taproot adreslerini desteklemiyor. Lütfen adresi değiştirin veya farklı bir sağlayıcı seçin.",
"time": "${minutes}d ${seconds}s",
"tip": "Bahşiş:",

View file

@ -661,6 +661,7 @@
"template_name": "Назва шаблону",
"third_intro_content": "Yat знаходиться за межами Cake Wallet. Будь-яку адресу гаманця на землі можна замінити на Yat!",
"third_intro_title": "Yat добре взаємодіє з іншими",
"thorchain_contract_address_not_supported": "Thorchain не підтримує надсилання на адресу контракту",
"thorchain_taproot_address_not_supported": "Постачальник Thorchain не підтримує адреси Taproot. Будь ласка, змініть адресу або виберіть іншого постачальника.",
"time": "${minutes}хв ${seconds}сек",
"tip": "Порада:",

View file

@ -662,6 +662,7 @@
"template_name": "ٹیمپلیٹ کا نام",
"third_intro_content": "Yats بھی Cake والیٹ سے باہر رہتے ہیں۔ زمین پر کسی بھی بٹوے کے پتے کو Yat سے تبدیل کیا جا سکتا ہے!",
"third_intro_title": "Yat دوسروں کے ساتھ اچھی طرح کھیلتا ہے۔",
"thorchain_contract_address_not_supported": "تھورچین معاہدے کے پتے بھیجنے کی حمایت نہیں کرتا ہے",
"thorchain_taproot_address_not_supported": "تھورچین فراہم کنندہ ٹیپروٹ پتے کی حمایت نہیں کرتا ہے۔ براہ کرم پتہ تبدیل کریں یا ایک مختلف فراہم کنندہ کو منتخب کریں۔",
"time": "${minutes}m ${seconds}s",
"tip": "ٹپ:",

View file

@ -661,6 +661,7 @@
"template_name": "Orukọ Awoṣe",
"third_intro_content": "A sì lè lo Yats níta Cake Wallet. A lè rọ́pò Àdírẹ́sì kankan àpamọ́wọ́ fún Yat!",
"third_intro_title": "Àlàáfíà ni Yat àti àwọn ìmíìn jọ wà",
"thorchain_contract_address_not_supported": "Thorchain ko ṣe atilẹyin fifiranṣẹ si adirẹsi adehun kan",
"thorchain_taproot_address_not_supported": "Olupese Trockchain ko ṣe atilẹyin awọn adirẹsi Taproot. Jọwọ yi adirẹsi pada tabi yan olupese ti o yatọ.",
"time": "${minutes}ìṣj ${seconds}ìṣs",
"tip": "Owó àfikún:",

View file

@ -660,6 +660,7 @@
"template_name": "模板名称",
"third_intro_content": "Yats 也住在 Cake Wallet 之外。 地球上任何一個錢包地址都可以用一個Yat來代替",
"third_intro_title": "Yat 和別人玩得很好",
"thorchain_contract_address_not_supported": "Thorchain不支持发送到合同地址",
"thorchain_taproot_address_not_supported": "Thorchain提供商不支持Taproot地址。请更改地址或选择其他提供商。",
"time": "${minutes}m ${seconds}s",
"tip": "提示:",

View file

@ -159,6 +159,7 @@ abstract class Bitcoin {
Future<bool> isChangeSufficientForFee(Object wallet, String txId, String newFee);
int getFeeAmountForPriority(Object wallet, TransactionPriority priority, int inputsCount, int outputsCount, {int? size});
int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount, {int? size});
int getMaxCustomFeeRate(Object wallet);
}
""";
@ -1057,7 +1058,8 @@ Future<void> generatePubspec(
final inputFile = File(pubspecOutputPath);
final inputText = await inputFile.readAsString();
final inputLines = inputText.split('\n');
final dependenciesIndex = inputLines.indexWhere((line) => line.toLowerCase().contains('dependencies:'));
final dependenciesIndex =
inputLines.indexWhere((line) => line.toLowerCase().contains('dependencies:'));
var output = cwCore;
if (hasMonero) {