Merge branch 'mweb-bg-sync-2' of https://github.com/cake-tech/cake_wallet into mweb-bg-sync-3

This commit is contained in:
Matthew Fosse 2024-12-30 11:59:58 -05:00
commit 2e13a93f78
69 changed files with 1508 additions and 1410 deletions

View file

@ -171,8 +171,8 @@ jobs:
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
@ -185,6 +185,7 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart

View file

@ -182,8 +182,8 @@ jobs:
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
@ -197,6 +197,7 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart

View file

@ -154,8 +154,8 @@ jobs:
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
@ -167,6 +167,7 @@ jobs:
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart

View file

@ -1,5 +1,7 @@
-
uri: ethereum.publicnode.com
uri: ethereum-rpc.publicnode.com
useSSL: true
isDefault: true
-
uri: eth.llamarpc.com
-

View file

@ -1,7 +1,9 @@
-
uri: polygon-rpc.com
-
uri: polygon-bor.publicnode.com
uri: polygon-bor-rpc.publicnode.com
useSSL: true
isDefault: true
-
uri: polygon.llamarpc.com
-

View file

@ -7,4 +7,7 @@
-
uri: solana-rpc.publicnode.com:443
useSSL: true
-
uri: solana-mainnet.core.chainstack.com
useSSL: true
is_default: true

View file

@ -39,7 +39,7 @@ class ElectrumBalance extends Balance {
int secondUnconfirmed = 0;
@override
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
String get formattedAvailableBalance => bitcoinAmountToString(amount: ((confirmed + unconfirmed) - frozen) );
@override
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
@ -58,7 +58,7 @@ class ElectrumBalance extends Balance {
@override
String get formattedFullAvailableBalance =>
bitcoinAmountToString(amount: confirmed + secondConfirmed - frozen);
bitcoinAmountToString(amount: (confirmed + unconfirmed) + secondConfirmed - frozen);
String toJSON() => json.encode({
'confirmed': confirmed,

View file

@ -4,7 +4,6 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
@ -1014,6 +1013,9 @@ abstract class ElectrumWalletBase
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
try {
// start by updating unspent coins
await updateAllUnspents();
final outputs = <BitcoinOutput>[];
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final hasMultiDestination = transactionCredentials.outputs.length > 1;
@ -2236,18 +2238,6 @@ abstract class ElectrumWalletBase
var totalConfirmed = 0;
var totalUnconfirmed = 0;
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
totalFrozen += element.value;
}
});
});
if (hasSilentPaymentsScanning) {
// Add values from unspent coins that are not fetched by the address list
// i.e. scanned silent payments
@ -2263,6 +2253,20 @@ abstract class ElectrumWalletBase
});
}
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) return;
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
totalFrozen += element.value;
}
});
});
final balances = await Future.wait(balanceFutures);
if (balances.isNotEmpty && balances.first['confirmed'] == null) {

View file

@ -47,7 +47,11 @@ class TransactionInputNotSupported implements Exception {}
class SignNativeTokenTransactionRentException implements Exception {}
class CreateAssociatedTokenAccountException implements Exception {}
class CreateAssociatedTokenAccountException implements Exception {
final String errorMessage;
CreateAssociatedTokenAccountException(this.errorMessage);
}
class SignSPLTokenTransactionRentException implements Exception {}

View file

@ -3,36 +3,25 @@ import 'package:cw_core/monero_amount_format.dart';
class MoneroBalance extends Balance {
MoneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0})
: formattedFullBalance = moneroAmountToString(amount: frozenBalance + fullBalance),
: formattedUnconfirmedBalance = moneroAmountToString(amount: fullBalance - unlockedBalance),
formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance),
formattedLockedBalance =
moneroAmountToString(amount: frozenBalance + fullBalance - unlockedBalance),
formattedFrozenBalance = moneroAmountToString(amount: frozenBalance),
super(unlockedBalance, fullBalance);
MoneroBalance.fromString(
{required this.formattedFullBalance,
required this.formattedUnlockedBalance,
this.formattedLockedBalance = '0.0'})
: fullBalance = moneroParseAmount(amount: formattedFullBalance),
unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance),
frozenBalance = moneroParseAmount(amount: formattedLockedBalance),
super(moneroParseAmount(amount: formattedUnlockedBalance),
moneroParseAmount(amount: formattedFullBalance));
final int fullBalance;
final int unlockedBalance;
final int frozenBalance;
final String formattedFullBalance;
final String formattedUnconfirmedBalance;
final String formattedUnlockedBalance;
final String formattedLockedBalance;
final String formattedFrozenBalance;
@override
String get formattedUnAvailableBalance =>
formattedLockedBalance == '0.0' ? '' : formattedLockedBalance;
formattedFrozenBalance == '0.0' ? '' : formattedFrozenBalance;
@override
String get formattedAvailableBalance => formattedUnlockedBalance;
@override
String get formattedAdditionalBalance => formattedFullBalance;
String get formattedAdditionalBalance => formattedUnconfirmedBalance;
}

View file

@ -99,8 +99,8 @@ class Node extends HiveObject with Keyable {
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
return Uri.parse(
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
return Uri.parse(
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
case WalletType.none:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
@ -152,6 +152,7 @@ class Node extends HiveObject with Keyable {
return requestMoneroNode();
case WalletType.nano:
case WalletType.banano:
return requestNanoNode();
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
@ -198,14 +199,16 @@ class Node extends HiveObject with Keyable {
);
client.close();
if ((
response.body.contains("400 Bad Request") // Some other generic error
|| response.body.contains("plain HTTP request was sent to HTTPS port") // Cloudflare
|| response.headers["location"] != null // Generic reverse proxy
|| response.body.contains("301 Moved Permanently") // Poorly configured generic reverse proxy
) && !(useSSL??false)
) {
if ((response.body.contains("400 Bad Request") // Some other generic error
||
response.body.contains("plain HTTP request was sent to HTTPS port") // Cloudflare
||
response.headers["location"] != null // Generic reverse proxy
||
response.body
.contains("301 Moved Permanently") // Poorly configured generic reverse proxy
) &&
!(useSSL ?? false)) {
final oldUseSSL = useSSL;
useSSL = true;
try {
@ -271,6 +274,35 @@ class Node extends HiveObject with Keyable {
}
}
Future<bool> requestNanoNode() async {
try {
final response = await http.post(
uri,
headers: {
"Content-Type": "application/json",
"nano-app": "cake-wallet"
},
body: jsonEncode(
{
"action": "account_balance",
"account": "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579",
},
),
);
final data = await jsonDecode(response.body);
if (response.statusCode != 200 ||
data["error"] != null ||
data["balance"] == null ||
data["receivable"] == null) {
throw Exception(
"Error while trying to get balance! ${data["error"] != null ? data["error"] : ""}");
}
return true;
} catch (_) {
return false;
}
}
Future<bool> requestEthereumServer() async {
try {
final response = await http.get(

View file

@ -3,36 +3,26 @@ import 'package:cw_core/wownero_amount_format.dart';
class WowneroBalance extends Balance {
WowneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0})
: formattedFullBalance = wowneroAmountToString(amount: fullBalance),
formattedUnlockedBalance = wowneroAmountToString(amount: unlockedBalance - frozenBalance),
formattedLockedBalance =
wowneroAmountToString(amount: frozenBalance + fullBalance - unlockedBalance),
: formattedUnconfirmedBalance = wowneroAmountToString(amount: fullBalance - unlockedBalance),
formattedUnlockedBalance = wowneroAmountToString(amount: unlockedBalance),
formattedFrozenBalance =
wowneroAmountToString(amount: frozenBalance),
super(unlockedBalance, fullBalance);
WowneroBalance.fromString(
{required this.formattedFullBalance,
required this.formattedUnlockedBalance,
this.formattedLockedBalance = '0.0'})
: fullBalance = wowneroParseAmount(amount: formattedFullBalance),
unlockedBalance = wowneroParseAmount(amount: formattedUnlockedBalance),
frozenBalance = wowneroParseAmount(amount: formattedLockedBalance),
super(wowneroParseAmount(amount: formattedUnlockedBalance),
wowneroParseAmount(amount: formattedFullBalance));
final int fullBalance;
final int unlockedBalance;
final int frozenBalance;
final String formattedFullBalance;
final String formattedUnconfirmedBalance;
final String formattedUnlockedBalance;
final String formattedLockedBalance;
final String formattedFrozenBalance;
@override
String get formattedUnAvailableBalance =>
formattedLockedBalance == '0.0' ? '' : formattedLockedBalance;
formattedFrozenBalance == '0.0' ? '' : formattedFrozenBalance;
@override
String get formattedAvailableBalance => formattedUnlockedBalance;
@override
String get formattedAdditionalBalance => formattedFullBalance;
String get formattedAdditionalBalance => formattedUnconfirmedBalance;
}

View file

@ -11,13 +11,12 @@ class EVMChainERC20Balance extends Balance {
final int exponent;
@override
String get formattedAdditionalBalance {
final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
return formattedBalance.substring(0, min(12, formattedBalance.length));
}
String get formattedAdditionalBalance => _balance();
@override
String get formattedAvailableBalance {
String get formattedAvailableBalance => _balance();
String _balance() {
final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
return formattedBalance.substring(0, min(12, formattedBalance.length));
}

View file

@ -742,10 +742,19 @@ abstract class MoneroWalletBase
int _getFrozenBalance() {
var frozenBalance = 0;
for (var coin in unspentCoinsInfo.values.where((element) =>
element.walletId == id && element.accountIndex == walletAddresses.account!.id)) {
if (coin.isFrozen && !coin.isSending) frozenBalance += coin.value;
}
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.value == info.value &&
info.walletId == id &&
info.accountIndex == walletAddresses.account!.id) {
if (element.isFrozen && !element.isSending) frozenBalance += element.value;
}
});
});
return frozenBalance;
}

View file

@ -54,12 +54,12 @@ class NanoClient {
}
}
Map<String, String> getHeaders() {
Map<String, String> getHeaders(String host) {
final headers = Map<String, String>.from(CAKE_HEADERS);
if (_node!.uri.host == "rpc.nano.to") {
if (host == "rpc.nano.to") {
headers["key"] = nano_secrets.nano2ApiKey;
}
if (_node!.uri.host == "nano.nownodes.io") {
if (host == "nano.nownodes.io") {
headers["api-key"] = nano_secrets.nanoNowNodesApiKey;
}
return headers;
@ -68,7 +68,7 @@ class NanoClient {
Future<NanoBalance> getBalance(String address) async {
final response = await http.post(
_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: jsonEncode(
{
"action": "account_balance",
@ -95,7 +95,7 @@ class NanoClient {
try {
final response = await http.post(
_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: jsonEncode(
{
"action": "account_info",
@ -116,7 +116,7 @@ class NanoClient {
try {
final response = await http.post(
_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(_node!.uri.host),
body: jsonEncode(
{
"action": "block_info",
@ -183,7 +183,7 @@ class NanoClient {
Future<String> requestWork(String hash) async {
final response = await http.post(
_powNode!.uri,
headers: getHeaders(),
headers: getHeaders(_powNode!.uri.host),
body: json.encode(
{
"action": "work_generate",
@ -226,7 +226,7 @@ class NanoClient {
final processResponse = await http.post(
_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: processBody,
);
@ -425,7 +425,7 @@ class NanoClient {
});
final processResponse = await http.post(
_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: processBody,
);
@ -441,7 +441,7 @@ class NanoClient {
required String privateKey,
}) async {
final receivableResponse = await http.post(_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: jsonEncode({
"action": "receivable",
"account": destinationAddress,
@ -493,7 +493,7 @@ class NanoClient {
Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
try {
final response = await http.post(_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: jsonEncode({
"action": "account_history",
"account": address,
@ -509,15 +509,16 @@ class NanoClient {
.map<NanoTransactionModel>((transaction) => NanoTransactionModel.fromJson(transaction))
.toList();
} catch (e) {
printV(e);
return [];
printV("error fetching transactions: $e");
rethrow;
}
}
Future<List<N2Node>> getN2Reps() async {
final uri = Uri.parse(N2_REPS_ENDPOINT);
final response = await http.post(
Uri.parse(N2_REPS_ENDPOINT),
headers: CAKE_HEADERS,
uri,
headers: getHeaders(uri.host),
body: jsonEncode({"action": "reps"}),
);
try {
@ -531,9 +532,10 @@ class NanoClient {
}
Future<int> getRepScore(String rep) async {
final uri = Uri.parse(N2_REPS_ENDPOINT);
final response = await http.post(
Uri.parse(N2_REPS_ENDPOINT),
headers: CAKE_HEADERS,
uri,
headers: getHeaders(uri.host),
body: jsonEncode({
"action": "rep_info",
"account": rep,

View file

@ -21,22 +21,23 @@ class SolanaWalletClient {
bool connect(Node node) {
try {
Uri? rpcUri;
String webSocketUrl;
bool isModifiedNodeUri = false;
Uri rpcUri = node.uri;
String webSocketUrl = 'wss://${node.uriRaw}';
if (node.uriRaw == 'rpc.ankr.com') {
isModifiedNodeUri = true;
String ankrApiKey = secrets.ankrApiKey;
rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
} else {
webSocketUrl = 'wss://${node.uriRaw}';
} else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') {
String chainStackApiKey = secrets.chainStackApiKey;
rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey');
webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey';
}
_client = SolanaClient(
rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
rpcUrl: rpcUri,
websocketUrl: Uri.parse(webSocketUrl),
timeout: const Duration(minutes: 2),
);
@ -115,10 +116,14 @@ class SolanaWalletClient {
final message =
_getMessageForNativeTransaction(ownerKeypair, ownerKeypair.address, lamportsPerSol);
final recentBlockhash = await _getRecentBlockhash(commitment);
final latestBlockhash = await _getLatestBlockhash(commitment);
final estimatedFee =
_getFeeFromCompiledMessage(message, ownerKeypair.publicKey, recentBlockhash, commitment);
final estimatedFee = _getFeeFromCompiledMessage(
message,
ownerKeypair.publicKey,
latestBlockhash,
commitment,
);
return estimatedFee;
}
@ -131,13 +136,25 @@ class SolanaWalletClient {
List<SolanaTransactionModel> transactions = [];
try {
final response = await _client!.rpcClient.getTransactionsList(
publicKey,
final signatures = await _client!.rpcClient.getSignaturesForAddress(
publicKey.toBase58(),
commitment: Commitment.confirmed,
limit: 1000,
);
for (final tx in response) {
final List<TransactionDetails> transactionDetails = [];
for (int i = 0; i < signatures.length; i += 20) {
final response = await _client!.rpcClient.getMultipleTransactions(
signatures.sublist(i, math.min(i + 20, signatures.length)),
commitment: Commitment.confirmed,
encoding: Encoding.jsonParsed,
);
transactionDetails.addAll(response);
// to avoid reaching the node RPS limit
await Future.delayed(Duration(milliseconds: 500));
}
for (final tx in transactionDetails) {
if (tx.transaction is ParsedTransaction) {
final parsedTx = (tx.transaction as ParsedTransaction);
final message = parsedTx.message;
@ -310,16 +327,16 @@ class SolanaWalletClient {
}
}
Future<RecentBlockhash> _getRecentBlockhash(Commitment commitment) async {
final latestBlockhash =
Future<LatestBlockhash> _getLatestBlockhash(Commitment commitment) async {
final latestBlockHashResult =
await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value;
final recentBlockhash = RecentBlockhash(
blockhash: latestBlockhash.blockhash,
feeCalculator: const FeeCalculator(lamportsPerSignature: 500),
final latestBlockhash = LatestBlockhash(
blockhash: latestBlockHashResult.blockhash,
lastValidBlockHeight: latestBlockHashResult.lastValidBlockHeight,
);
return recentBlockhash;
return latestBlockhash;
}
Message _getMessageForNativeTransaction(
@ -342,11 +359,11 @@ class SolanaWalletClient {
Future<double> _getFeeFromCompiledMessage(
Message message,
Ed25519HDPublicKey feePayer,
RecentBlockhash recentBlockhash,
LatestBlockhash latestBlockhash,
Commitment commitment,
) async {
final compile = message.compile(
recentBlockhash: recentBlockhash.blockhash,
recentBlockhash: latestBlockhash.blockhash,
feePayer: feePayer,
);
@ -362,16 +379,18 @@ class SolanaWalletClient {
required double solBalance,
required double fee,
}) async {
final rent =
await _client!.getMinimumBalanceForMintRentExemption(commitment: Commitment.confirmed);
final rentInSol = (rent / lamportsPerSol).toDouble();
final remnant = solBalance - (inputAmount + fee);
if (remnant > rentInSol) return true;
return false;
return true;
// TODO: this is not doing what the name inclines
// final rent =
// await _client!.getMinimumBalanceForMintRentExemption(commitment: Commitment.confirmed);
//
// final rentInSol = (rent / lamportsPerSol).toDouble();
//
// final remnant = solBalance - (inputAmount + fee);
//
// if (remnant > rentInSol) return true;
//
// return false;
}
Future<PendingSolanaTransaction> _signNativeTokenTransaction({
@ -391,12 +410,12 @@ class SolanaWalletClient {
final signers = [ownerKeypair];
RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
LatestBlockhash latestBlockhash = await _getLatestBlockhash(commitment);
final fee = await _getFeeFromCompiledMessage(
message,
signers.first.publicKey,
recentBlockhash,
latestBlockhash,
commitment,
);
@ -422,14 +441,14 @@ class SolanaWalletClient {
message: updatedMessage,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
latestBlockhash: latestBlockhash,
);
} else {
signedTx = await _signTransactionInternal(
message: message,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
latestBlockhash: latestBlockhash,
);
}
@ -507,12 +526,12 @@ class SolanaWalletClient {
final signers = [ownerKeypair];
RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
LatestBlockhash latestBlockhash = await _getLatestBlockhash(commitment);
final fee = await _getFeeFromCompiledMessage(
message,
signers.first.publicKey,
recentBlockhash,
latestBlockhash,
commitment,
);
@ -530,7 +549,7 @@ class SolanaWalletClient {
message: message,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
latestBlockhash: latestBlockhash,
);
sendTx() async => await sendTransaction(
@ -552,9 +571,9 @@ class SolanaWalletClient {
required Message message,
required List<Ed25519HDKeyPair> signers,
required Commitment commitment,
required RecentBlockhash recentBlockhash,
required LatestBlockhash latestBlockhash,
}) async {
final signedTx = await signTransaction(recentBlockhash, message, signers);
final signedTx = await signTransaction(latestBlockhash, message, signers);
return signedTx;
}

View file

@ -25,9 +25,7 @@ class SolanaSignNativeTokenTransactionRentException
extends SignNativeTokenTransactionRentException {}
class SolanaCreateAssociatedTokenAccountException extends CreateAssociatedTokenAccountException {
SolanaCreateAssociatedTokenAccountException(this.exceptionMessage);
final String exceptionMessage;
SolanaCreateAssociatedTokenAccountException(super.errorMessage);
}
class SolanaSignSPLTokenTransactionRentException extends SignSPLTokenTransactionRentException {}

View file

@ -11,7 +11,7 @@ environment:
dependencies:
flutter:
sdk: flutter
solana: ^0.30.4
solana: ^0.31.0+1
cw_core:
path: ../cw_core
http: ^1.1.0

View file

@ -1,6 +1,6 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/crypto_balance_widget.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter_test/flutter_test.dart';

View file

@ -1,24 +0,0 @@
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_base.dart';
class PayfuraBuyProvider {
PayfuraBuyProvider({required SettingsStore settingsStore, required WalletBase wallet})
: this._settingsStore = settingsStore,
this._wallet = wallet;
final SettingsStore _settingsStore;
final WalletBase _wallet;
static const _baseUrl = 'exchange.payfura.com';
Uri requestUrl() {
return Uri.https(_baseUrl, '', <String, dynamic>{
'apiKey': secrets.payfuraApiKey,
'to': _wallet.currency.title,
'from': _settingsStore.fiatCurrency.title,
'walletAddress': '${_wallet.currency.title}:${_wallet.walletAddresses.address}',
'mode': 'buy'
});
}
}

View file

@ -204,8 +204,8 @@ class CakePayApi {
/// Get Vendors
Future<List<CakePayVendor>> getVendors({
required String apiKey,
required String country,
int? page,
String? country,
String? countryCode,
String? search,
List<String>? vendorIds,
@ -230,6 +230,7 @@ class CakePayApi {
var headers = {
'accept': 'application/json; charset=UTF-8',
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Api-Key $apiKey',
};
@ -240,14 +241,14 @@ class CakePayApi {
'Failed to fetch vendors: statusCode - ${response.statusCode}, queryParams -$queryParams, response - ${response.body}');
}
final bodyJson = json.decode(response.body);
final bodyJson = json.decode(utf8.decode(response.bodyBytes));
if (bodyJson is List<dynamic> && bodyJson.isEmpty) {
return [];
}
return (bodyJson['results'] as List)
.map((e) => CakePayVendor.fromJson(e as Map<String, dynamic>))
.map((e) => CakePayVendor.fromJson(e as Map<String, dynamic>, country))
.toList();
}
}

View file

@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:cake_wallet/entities/fiat_currency.dart';
class CakePayCard {
@ -38,17 +36,11 @@ class CakePayCard {
});
factory CakePayCard.fromJson(Map<String, dynamic> json) {
final name = stripHtmlIfNeeded(json['name'] as String? ?? '');
final decodedName = fixEncoding(name);
final description = stripHtmlIfNeeded(json['description'] as String? ?? '');
final decodedDescription = fixEncoding(description);
final termsAndConditions = stripHtmlIfNeeded(json['terms_and_conditions'] as String? ?? '');
final decodedTermsAndConditions = fixEncoding(termsAndConditions);
final howToUse = stripHtmlIfNeeded(json['how_to_use'] as String? ?? '');
final decodedHowToUse = fixEncoding(howToUse);
final fiatCurrency = FiatCurrency.deserialize(raw: json['currency_code'] as String? ?? '');
@ -59,10 +51,10 @@ class CakePayCard {
return CakePayCard(
id: json['id'] as int? ?? 0,
name: decodedName,
description: decodedDescription,
termsAndConditions: decodedTermsAndConditions,
howToUse: decodedHowToUse,
name: name,
description: description,
termsAndConditions: termsAndConditions,
howToUse: howToUse,
expiryAndValidity: json['expiry_and_validity'] as String?,
cardImageUrl: json['card_image_url'] as String?,
country: json['country'] as String?,
@ -79,9 +71,4 @@ class CakePayCard {
static String stripHtmlIfNeeded(String text) {
return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ');
}
static String fixEncoding(String text) {
final bytes = latin1.encode(text);
return utf8.decode(bytes, allowMalformed: true);
}
}

View file

@ -29,8 +29,8 @@ class CakePayService {
/// Get Vendors
Future<List<CakePayVendor>> getVendors({
required String country,
int? page,
String? country,
String? countryCode,
String? search,
List<String>? vendorIds,

View file

@ -1,5 +1,3 @@
import 'dart:convert';
import 'cake_pay_card.dart';
class CakePayVendor {
@ -7,7 +5,7 @@ class CakePayVendor {
final String name;
final bool unavailable;
final String? cakeWarnings;
final List<String> countries;
final String country;
final CakePayCard? card;
CakePayVendor({
@ -15,37 +13,35 @@ class CakePayVendor {
required this.name,
required this.unavailable,
this.cakeWarnings,
required this.countries,
required this.country,
this.card,
});
factory CakePayVendor.fromJson(Map<String, dynamic> json) {
factory CakePayVendor.fromJson(Map<String, dynamic> json, String country) {
final name = stripHtmlIfNeeded(json['name'] as String);
final decodedName = fixEncoding(name);
var cardsJson = json['cards'] as List?;
CakePayCard? firstCard;
CakePayCard? cardForVendor;
if (cardsJson != null && cardsJson.isNotEmpty) {
firstCard = CakePayCard.fromJson(cardsJson.first as Map<String, dynamic>);
try {
cardForVendor = CakePayCard.fromJson(cardsJson
.where((element) => element['country'] == country)
.first as Map<String, dynamic>);
} catch (_) {}
}
return CakePayVendor(
id: json['id'] as int,
name: decodedName,
name: name,
unavailable: json['unavailable'] as bool? ?? false,
cakeWarnings: json['cake_warnings'] as String?,
countries: List<String>.from(json['countries'] as List? ?? []),
card: firstCard,
country: country,
card: cardForVendor,
);
}
static String stripHtmlIfNeeded(String text) {
return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ');
}
static String fixEncoding(String text) {
final bytes = latin1.encode(text);
return utf8.decode(bytes, allowMalformed: true);
}
}

View file

@ -140,25 +140,24 @@ abstract class Web3WalletServiceBase with Store {
for (final cId in SolanaChainId.values) {
final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
Uri? rpcUri;
String webSocketUrl;
bool isModifiedNodeUri = false;
Uri rpcUri = node.uri;
String webSocketUrl = 'wss://${node.uriRaw}';
if (node.uriRaw == 'rpc.ankr.com') {
isModifiedNodeUri = true;
//A better way to handle this instead of adding this to the general secrets?
String ankrApiKey = secrets.ankrApiKey;
rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
} else {
webSocketUrl = 'wss://${node.uriRaw}';
} else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') {
String chainStackApiKey = secrets.chainStackApiKey;
rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey');
webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey';
}
SolanaChainServiceImpl(
reference: cId,
rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
rpcUrl: rpcUri,
webSocketUrl: webSocketUrl,
wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler,

View file

@ -11,7 +11,6 @@ import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/core/auth_service.dart';
@ -81,7 +80,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
@ -1021,11 +1020,6 @@ Future<void> setup({
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
getIt.registerFactory<PayfuraBuyProvider>(() => PayfuraBuyProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!,
));
getIt.registerFactory(() => ExchangeViewModel(
getIt.get<AppStore>(),
_tradesSource,

View file

@ -13,6 +13,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
@ -236,26 +237,26 @@ Future<void> onStart(ServiceInstance service) async {
// get all bitcoin wallets and add them:
final List<WalletListItem> bitcoinWallets =
walletListViewModel.wallets.where((element) => element.type == WalletType.bitcoin).toList();
bool spSupported = true;
for (int i = 0; i < bitcoinWallets.length; i++) {
try {
if (!spSupported) continue;
final wallet =
await walletLoadingService.load(bitcoinWallets[i].type, bitcoinWallets[i].name);
final node = settingsStore.getCurrentNode(WalletType.bitcoin);
var node = settingsStore.getCurrentNode(WalletType.bitcoin);
await wallet.connectToNode(node: node);
bool nodeSupportsSP = await (wallet as ElectrumWallet).getNodeSupportsSilentPayments();
if (!nodeSupportsSP) {
printV("Configured node does not support silent payments, skipping wallet");
setWalletNotification(
flutterLocalNotificationsPlugin,
title: initialNotificationTitle,
content: spNodeNotificationMessage,
walletNum: syncingWallets.length + 1,
);
spSupported = false;
continue;
// printV("Configured node does not support silent payments, skipping wallet");
// setWalletNotification(
// flutterLocalNotificationsPlugin,
// title: initialNotificationTitle,
// content: spNodeNotificationMessage,
// walletNum: syncingWallets.length + 1,
// );
// spSupported = false;
// continue;
node = Node(uri: "electrs.cakewallet.com:50001");
await wallet.connectToNode(node: node);
}
await wallet.stopSync();

View file

@ -35,12 +35,12 @@ const publicBitcoinTestnetElectrumUri =
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
const polygonDefaultNodeUri = 'polygon-bor.publicnode.com';
const ethereumDefaultNodeUri = 'ethereum-rpc.publicnode.com';
const polygonDefaultNodeUri = 'polygon-bor-rpc.publicnode.com';
const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
const nanoDefaultNodeUri = 'nano.nownodes.io';
const nanoDefaultPowNodeUri = 'rpc.nano.to';
const solanaDefaultNodeUri = 'solana-rpc.publicnode.com:443';
const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com';
const tronDefaultNodeUri = 'api.trongrid.io';
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
@ -347,6 +347,31 @@ Future<void> defaultSettingsMigration(
type: WalletType.litecoin,
useSSL: true,
);
_changeDefaultNode(
nodes: nodes,
sharedPreferences: sharedPreferences,
type: WalletType.solana,
newDefaultUri: solanaDefaultNodeUri,
currentNodePreferenceKey: PreferencesKey.currentSolanaNodeIdKey,
useSSL: true,
oldUri: [
'rpc.ankr.com',
'api.mainnet-beta.solana.com:443',
'solana-rpc.publicnode.com:443',
],
);
_updateNode(
nodes: nodes,
currentUri: "ethereum.publicnode.com",
newUri: "ethereum-rpc.publicnode.com",
useSSL: true,
);
_updateNode(
nodes: nodes,
currentUri: "polygon-bor.publicnode.com",
newUri: "polygon-bor-rpc.publicnode.com",
useSSL: true,
);
break;
default:
break;
@ -362,6 +387,24 @@ Future<void> defaultSettingsMigration(
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
}
void _updateNode({
required Box<Node> nodes,
required String currentUri,
String? newUri,
bool? useSSL,
}) {
for (Node node in nodes.values) {
if (node.uriRaw == currentUri) {
if (newUri != null) {
node.uriRaw = newUri;
}
if (useSSL != null) {
node.useSSL = useSSL;
}
}
}
}
Future<void> _backupHavenSeeds(Box<HavenSeedStore> havenSeedStore) async {
final future = haven?.backupHavenSeeds(havenSeedStore);
if (future != null) {
@ -462,7 +505,7 @@ Future<void> updateNanoNodeList({required Box<Node> nodes}) async {
];
// add new nodes:
for (final node in nodeList) {
if (listOfNewEndpoints.contains(node.uriRaw)) {
if (listOfNewEndpoints.contains(node.uriRaw) && !nodes.values.contains(node)) {
await nodes.add(node);
}
}

View file

@ -24,7 +24,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';

View file

@ -8,7 +8,7 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/version_comparator.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/router.dart' as Router;

View file

@ -0,0 +1,88 @@
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/crypto_balance_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class BalancePage extends StatelessWidget {
BalancePage({
required this.dashboardViewModel,
required this.settingsStore,
required this.nftViewModel,
});
final DashboardViewModel dashboardViewModel;
final NFTViewModel nftViewModel;
final SettingsStore settingsStore;
@override
Widget build(BuildContext context) {
return Observer(
builder: (context) {
final isEVMCompatible = isEVMCompatibleChain(dashboardViewModel.type);
return DefaultTabController(
length: isEVMCompatible ? 2 : 1,
child: Column(
children: [
if (isEVMCompatible)
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 8),
child: TabBar(
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
physics: NeverScrollableScrollPhysics(),
labelStyle: TextStyle(
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
height: 1,
),
unselectedLabelStyle: TextStyle(
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
height: 1,
),
labelColor:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
dividerColor: Colors.transparent,
indicatorColor:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
unselectedLabelColor: Theme.of(context)
.extension<DashboardPageTheme>()!
.pageTitleTextColor
.withOpacity(0.5),
tabAlignment: TabAlignment.start,
tabs: [
Tab(text: 'My Crypto'),
Tab(text: 'My NFTs'),
],
),
),
),
Expanded(
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
CryptoBalanceWidget(dashboardViewModel: dashboardViewModel),
if (isEVMCompatible) NFTListingPage(nftViewModel: nftViewModel)
],
),
),
],
),
);
},
);
}
}

View file

@ -0,0 +1,654 @@
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class BalanceRowWidget extends StatelessWidget {
BalanceRowWidget({
required this.availableBalanceLabel,
required this.availableBalance,
required this.availableFiatBalance,
required this.additionalBalanceLabel,
required this.additionalBalance,
required this.additionalFiatBalance,
required this.secondAvailableBalanceLabel,
required this.secondAvailableBalance,
required this.secondAvailableFiatBalance,
required this.secondAdditionalBalanceLabel,
required this.secondAdditionalBalance,
required this.secondAdditionalFiatBalance,
required this.frozenBalance,
required this.frozenFiatBalance,
required this.currency,
required this.hasAdditionalBalance,
required this.hasSecondAvailableBalance,
required this.hasSecondAdditionalBalance,
required this.isTestnet,
required this.dashboardViewModel,
super.key,
});
final String availableBalanceLabel;
final String availableBalance;
final String availableFiatBalance;
final String additionalBalanceLabel;
final String additionalBalance;
final String additionalFiatBalance;
final String secondAvailableBalanceLabel;
final String secondAvailableBalance;
final String secondAvailableFiatBalance;
final String secondAdditionalBalanceLabel;
final String secondAdditionalBalance;
final String secondAdditionalFiatBalance;
final String frozenBalance;
final String frozenFiatBalance;
final CryptoCurrency currency;
final bool hasAdditionalBalance;
final bool hasSecondAvailableBalance;
final bool hasSecondAdditionalBalance;
final bool isTestnet;
final DashboardViewModel dashboardViewModel;
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
width: 1,
),
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
child: Container(
margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: hasAdditionalBalance
? () => _showBalanceDescription(
context, S.of(context).available_balance_description)
: null,
child: Row(
children: [
Semantics(
hint: 'Double tap to see more information',
container: true,
child: Text('${availableBalanceLabel}',
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1)),
),
if (hasAdditionalBalance)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
),
],
),
),
SizedBox(height: 6),
AutoSizeText(availableBalance,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
height: 1),
maxLines: 1,
textAlign: TextAlign.start),
SizedBox(height: 6),
if (isTestnet)
Text(S.of(context).testnet_coins_no_value,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
if (!isTestnet)
Text('${availableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
color:
Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
],
),
SizedBox(
width: min(MediaQuery.of(context).size.width * 0.2, 100),
child: Center(
child: Column(
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
displayOnError: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: TextStyle(fontSize: 11),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
),
),
),
const SizedBox(height: 10),
Text(
currency.title,
style: TextStyle(
fontSize: 15,
fontFamily: 'Lato',
fontWeight: FontWeight.w800,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
),
],
),
),
),
],
),
),
if (frozenBalance.isNotEmpty)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: hasAdditionalBalance
? () => _showBalanceDescription(
context, S.of(context).unavailable_balance_description)
: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 26),
Row(
children: [
Text(
S.of(context).unavailable_balance,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
),
],
),
SizedBox(height: 8),
AutoSizeText(
frozenBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.balanceAmountColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
frozenFiatBalance,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1,
),
),
],
),
),
if (hasAdditionalBalance)
GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${additionalBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
additionalBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
'${additionalFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1,
),
),
],
),
),
],
),
),
),
if (hasSecondAdditionalBalance || hasSecondAvailableBalance) ...[
SizedBox(height: 10),
Container(
margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
width: 1,
),
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16),
child: Stack(
children: [
if (currency == CryptoCurrency.ltc)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding: EdgeInsets.only(right: 16, top: 0),
child: Column(
children: [
Container(
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
size: 40,
),
),
const SizedBox(height: 10),
Text(
'MWEB',
style: TextStyle(
fontSize: 15,
fontFamily: 'Lato',
fontWeight: FontWeight.w800,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
),
],
),
),
],
),
if (hasSecondAvailableBalance)
GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://docs.cakewallet.com/cryptos/litecoin.html#mweb"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
'${secondAvailableBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
)
],
),
),
SizedBox(height: 8),
AutoSizeText(
secondAvailableBalance,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 6),
if (!isTestnet)
Text(
'${secondAvailableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
height: 1,
),
),
],
),
],
),
),
],
),
),
Container(
margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16),
child: Stack(
children: [
if (hasSecondAdditionalBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${secondAdditionalBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
secondAdditionalBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
'${secondAdditionalFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
height: 1,
),
),
],
),
],
),
],
),
),
IntrinsicHeight(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegin,
child: OutlinedButton(
onPressed: () {
final mwebAddress =
bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((mwebAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${mwebAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.nonMweb,
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400.withAlpha(50),
side: BorderSide(
color: Colors.grey.shade400.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/received.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegin,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
SizedBox(width: 24),
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegout,
child: OutlinedButton(
onPressed: () {
final litecoinAddress =
bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((litecoinAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${litecoinAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.mweb,
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400.withAlpha(50),
side: BorderSide(
color: Colors.grey.shade400.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/upload.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegout,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
],
),
),
),
SizedBox(height: 16),
],
),
),
),
],
],
);
}
void _showBalanceDescription(BuildContext context, String content) {
showPopUp<void>(context: context, builder: (_) => InformationPage(information: content));
}
}

View file

@ -0,0 +1,424 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_row_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/src/widgets/introducing_card.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart';
class CryptoBalanceWidget extends StatelessWidget {
const CryptoBalanceWidget({
super.key,
required this.dashboardViewModel,
});
final DashboardViewModel dashboardViewModel;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Observer(
builder: (_) {
if (dashboardViewModel.getMoneroError != null) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: DashBoardRoundedCardWidget(
title: "Invalid monero bindings",
subTitle: dashboardViewModel.getMoneroError.toString(),
onTap: () {},
),
);
}
return Container();
},
),
Observer(
builder: (_) {
if (dashboardViewModel.getWowneroError != null) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: DashBoardRoundedCardWidget(
title: "Invalid wownero bindings",
subTitle: dashboardViewModel.getWowneroError.toString(),
onTap: () {},
));
}
return Container();
},
),
Observer(
builder: (_) => dashboardViewModel.balanceViewModel.hasAccounts
? HomeScreenAccountWidget(
walletName: dashboardViewModel.name, accountName: dashboardViewModel.subname)
: Column(
children: [
SizedBox(height: 16),
Container(
margin: const EdgeInsets.only(left: 24, bottom: 16),
child: Observer(
builder: (_) {
return Row(
children: [
Text(
dashboardViewModel.balanceViewModel.asset,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.pageTitleTextColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
if (dashboardViewModel.wallet.isHardwareWallet)
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'assets/images/hardware_wallet/ledger_nano_x.png',
width: 24,
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.pageTitleTextColor,
),
),
if (dashboardViewModel
.balanceViewModel.isHomeScreenSettingsEnabled)
InkWell(
onTap: () => Navigator.pushNamed(context, Routes.homeSettings,
arguments: dashboardViewModel.balanceViewModel),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'assets/images/home_screen_settings_icon.png',
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.pageTitleTextColor,
),
),
),
],
);
},
),
),
],
)),
Observer(
builder: (_) {
if (dashboardViewModel.balanceViewModel.isShowCard && FeatureFlag.isCakePayEnabled) {
return IntroducingCard(
title: S.of(context).introducing_cake_pay,
subTitle: S.of(context).cake_pay_learn_more,
borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
}
return Container();
},
),
Observer(builder: (_) {
if (!dashboardViewModel.showRepWarning) {
return const SizedBox();
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
title: S.of(context).rep_warning,
subTitle: S.of(context).rep_warning_sub,
onTap: () => Navigator.of(context).pushNamed(Routes.changeRep),
onClose: () {
dashboardViewModel.settingsStore.shouldShowRepWarning = false;
},
),
);
}),
Observer(
builder: (_) {
return ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
itemBuilder: (__, index) {
final balance =
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
return Observer(builder: (_) {
return BalanceRowWidget(
dashboardViewModel: dashboardViewModel,
availableBalanceLabel:
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
availableBalance: balance.availableBalance,
availableFiatBalance: balance.fiatAvailableBalance,
additionalBalanceLabel:
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
additionalBalance: balance.additionalBalance,
additionalFiatBalance: balance.fiatAdditionalBalance,
frozenBalance: balance.frozenBalance,
frozenFiatBalance: balance.fiatFrozenBalance,
currency: balance.asset,
hasAdditionalBalance:
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
hasSecondAdditionalBalance:
dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance,
hasSecondAvailableBalance:
dashboardViewModel.balanceViewModel.hasSecondAvailableBalance,
secondAdditionalBalance: balance.secondAdditionalBalance,
secondAdditionalFiatBalance: balance.fiatSecondAdditionalBalance,
secondAvailableBalance: balance.secondAvailableBalance,
secondAvailableFiatBalance: balance.fiatSecondAvailableBalance,
secondAdditionalBalanceLabel:
'${dashboardViewModel.balanceViewModel.secondAdditionalBalanceLabel}',
secondAvailableBalanceLabel:
'${dashboardViewModel.balanceViewModel.secondAvailableBalanceLabel}',
isTestnet: dashboardViewModel.isTestnet,
);
});
},
);
},
),
Observer(builder: (context) {
return Column(
children: [
if (dashboardViewModel.isMoneroWalletBrokenReasons.isNotEmpty) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: "This wallet has encountered an issue",
subTitle: "Here are the things that you should note:\n - " +
dashboardViewModel.isMoneroWalletBrokenReasons.join("\n - ") +
"\n\nPlease restart your wallet and if it doesn't help contact our support.",
onTap: () {},
))
],
if (dashboardViewModel.showSilentPaymentsCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).silent_payments,
subTitle: S.of(context).enable_silent_payments_scanning,
hint: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://docs.cakewallet.com/cryptos/bitcoin#silent-payments"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
S.of(context).what_is_silent_payments,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
softWrap: true,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
)
],
),
),
Observer(
builder: (_) => StandardSwitch(
value: dashboardViewModel.silentPaymentsScanningActive,
onTaped: () => _toggleSilentPaymentsScanning(context),
),
)
],
),
],
),
onTap: () => _toggleSilentPaymentsScanning(context),
icon: Icon(
Icons.lock,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
size: 50,
),
),
),
],
if (dashboardViewModel.showMwebCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).litecoin_mweb,
subTitle: S.of(context).litecoin_mweb_description,
hint: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
mode: LaunchMode.externalApplication,
),
child: Text(
S.of(context).learn_more,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
softWrap: true,
),
),
SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _dismissMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
),
child: Text(
S.of(context).litecoin_mweb_dismiss,
style: TextStyle(color: Colors.white),
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: () => _enableMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
child: Text(
S.of(context).enable,
maxLines: 1,
),
),
),
],
),
],
),
onTap: () => {},
icon: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Color.fromARGB(255, 11, 70, 129),
size: 40,
),
),
),
),
],
],
);
}),
],
),
);
}
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
final newValue = !isSilentPaymentsScanningActive;
dashboardViewModel.silentPaymentsScanningActive = newValue;
final needsToSwitch = !isSilentPaymentsScanningActive &&
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue);
Navigator.of(context).pop();
},
actionLeftButton: () {
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
Navigator.of(context).pop();
},
));
}
return dashboardViewModel.setSilentPaymentsScanning(newValue);
}
Future<void> _enableMweb(BuildContext context) async {
if (!dashboardViewModel.hasEnabledMwebBefore) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).litecoin_mweb_warning,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
));
}
dashboardViewModel.setMwebEnabled();
}
Future<void> _dismissMweb(BuildContext context) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).litecoin_mweb_enable_later,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
));
dashboardViewModel.dismissMweb();
}
}

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,8 @@ class SeedVerificationPage extends BasePage {
builder: (context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: walletSeedViewModel.isVerificationComplete
child: walletSeedViewModel.isVerificationComplete ||
walletSeedViewModel.verificationIndices.isEmpty
? SeedVerificationSuccessView(
imageColor: titleColor(context),
)

View file

@ -1,7 +1,9 @@
import 'package:flutter/foundation.dart';
class FeatureFlag {
static const bool isCakePayEnabled = false;
static const bool isExolixEnabled = true;
static const bool isInAppTorEnabled = false;
static const bool isBackgroundSyncEnabled = true;
static const int verificationWordsCount = 2;
static const int verificationWordsCount = kDebugMode ? 0 : 2;
}

View file

@ -158,17 +158,17 @@ abstract class BalanceViewModelBase with Store {
case WalletType.banano:
case WalletType.solana:
case WalletType.tron:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.none:
return S.current.xmr_available_balance;
default:
return S.current.confirmed;
}
}
@computed
String get additionalBalanceLabel {
switch (wallet.type) {
case WalletType.monero:
case WalletType.wownero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
@ -357,7 +357,12 @@ abstract class BalanceViewModelBase with Store {
bool mwebEnabled = false;
@computed
bool get hasAdditionalBalance => _hasAdditionalBalanceForWalletType(wallet.type);
bool get hasAdditionalBalance {
bool isWalletTypeActivated = _hasAdditionalBalanceForWalletType(wallet.type);
bool isNotZeroAmount = additionalBalance != "0.0";
return isWalletTypeActivated && isNotZeroAmount;
}
@computed
bool get hasSecondAdditionalBalance =>
@ -373,6 +378,9 @@ abstract class BalanceViewModelBase with Store {
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
case WalletType.litecoin:
return false;
default:
return true;

View file

@ -680,7 +680,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
if (error is CreateAssociatedTokenAccountException) {
return S.current.solana_create_associated_token_account_exception;
return "${S.current.solana_create_associated_token_account_exception}\n\n${error.errorMessage}";
}
if (error is SignSPLTokenTransactionRentException) {

View file

@ -29,6 +29,7 @@ abstract class WalletSeedViewModelBase with Store {
List<String> get seedSplit => seed.split(RegExp(r'\s+'));
int get columnCount => seedSplit.length <= 16 ? 2 : 3;
double get columnAspectRatio => seedSplit.length <= 16 ? 1.8 : 2.8;
/// The indices of the seed to be verified.
@ -60,8 +61,10 @@ abstract class WalletSeedViewModelBase with Store {
bool isVerificationComplete = false;
void setupSeedVerification() {
generateRandomIndices();
generateOptions();
if (verificationWordCount != 0) {
generateRandomIndices();
generateOptions();
}
}
/// Generate the indices of the seeds to be verified.

View file

@ -109,7 +109,7 @@ dependencies:
flutter_svg: ^2.0.9
polyseed: ^0.0.6
nostr_tools: ^1.0.9
solana: ^0.30.1
solana: ^0.31.0+1
ledger_flutter_plus: ^1.4.1
hashlib: ^1.19.2

View file

@ -333,6 +333,7 @@
"freeze": "تجميد",
"frequently_asked_questions": "الأسئلة الشائعة",
"frozen": "مجمدة",
"frozen_balance": "التوازن المجمد",
"full_balance": "الرصيد الكامل",
"gas_exceeds_allowance": "الغاز المطلوب بالمعاملة يتجاوز البدل.",
"generate_name": "توليد الاسم",

View file

@ -333,6 +333,7 @@
"freeze": "Замразяване",
"frequently_asked_questions": "Често задавани въпроси",
"frozen": "Замразени",
"frozen_balance": "Замразен баланс",
"full_balance": "Пълен баланс",
"gas_exceeds_allowance": "Газът, изискван от транзакцията, надвишава надбавката.",
"generate_name": "Генериране на име",

View file

@ -333,6 +333,7 @@
"freeze": "Zmrazit",
"frequently_asked_questions": "Často kladené otázky",
"frozen": "Zmraženo",
"frozen_balance": "Zmrazená rovnováha",
"full_balance": "Celkový zůstatek",
"gas_exceeds_allowance": "Plyn vyžadovaný transakcí přesahuje příspěvek.",
"generate_name": "Generovat jméno",

View file

@ -333,6 +333,7 @@
"freeze": "Einfrieren",
"frequently_asked_questions": "Häufig gestellte Fragen",
"frozen": "Gefroren",
"frozen_balance": "Gefrorenes Gleichgewicht",
"full_balance": "Gesamtguthaben",
"gas_exceeds_allowance": "Die durch Transaktion erforderliche Gas übertrifft die Zulage.",
"generate_name": "Namen generieren",

View file

@ -333,6 +333,7 @@
"freeze": "Freeze",
"frequently_asked_questions": "Frequently asked questions",
"frozen": "Frozen",
"frozen_balance": "Frozen Balance",
"full_balance": "Full Balance",
"gas_exceeds_allowance": "Gas required by transaction exceeds allowance.",
"generate_name": "Generate Name",

View file

@ -333,6 +333,7 @@
"freeze": "Congelar",
"frequently_asked_questions": "Preguntas frecuentes",
"frozen": "Congelada",
"frozen_balance": "Equilibrio congelado",
"full_balance": "Balance completo",
"gas_exceeds_allowance": "El gas requerido por la transacción excede la asignación.",
"generate_name": "Generar nombre",

View file

@ -333,6 +333,7 @@
"freeze": "Geler",
"frequently_asked_questions": "Foire aux questions",
"frozen": "Gelées",
"frozen_balance": "Équilibre gelé",
"full_balance": "Solde Complet",
"gas_exceeds_allowance": "Le gaz requis par la transaction dépasse l'allocation.",
"generate_name": "Générer un nom",

View file

@ -333,6 +333,7 @@
"freeze": "Daskare",
"frequently_asked_questions": "Tambayoyin da ake yawan yi",
"frozen": "Daskararre",
"frozen_balance": "Daidaituwa mai sanyi",
"full_balance": "DUKAN KUDI",
"gas_exceeds_allowance": "Gas da ake buƙata ta hanyar ma'amala ya wuce izini.",
"generate_name": "Ƙirƙirar Suna",

View file

@ -333,6 +333,7 @@
"freeze": "फ्रीज",
"frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न",
"frozen": "जमा हुआ",
"frozen_balance": "जमे हुए संतुलन",
"full_balance": "पूर्ण संतुलन",
"gas_exceeds_allowance": "लेनदेन द्वारा आवश्यक गैस भत्ता से अधिक है।",
"generate_name": "नाम जनरेट करें",

View file

@ -333,6 +333,7 @@
"freeze": "Zamrznuti",
"frequently_asked_questions": "Često postavljana pitanja",
"frozen": "Smrznuto",
"frozen_balance": "Smrznuta ravnoteža",
"full_balance": "Pun iznos",
"gas_exceeds_allowance": "Plin potreban transakcijom premašuje dodatak.",
"generate_name": "Generiraj ime",

View file

@ -333,6 +333,7 @@
"freeze": "Կասեցնել",
"frequently_asked_questions": "Հաճախ տրվող հարցեր",
"frozen": "Կասեցված",
"frozen_balance": "Սառեցված հավասարակշռություն",
"full_balance": "Լրիվ մնացորդ",
"gas_exceeds_allowance": "Գործարքով պահանջվող գազը գերազանցում է նպաստը:",
"generate_name": "Գեներացնել անուն",

View file

@ -333,6 +333,7 @@
"freeze": "Freeze",
"frequently_asked_questions": "Pertanyaan yang sering diajukan",
"frozen": "Dibekukan",
"frozen_balance": "Keseimbangan beku",
"full_balance": "Saldo Penuh",
"gas_exceeds_allowance": "Gas yang dibutuhkan oleh transaksi melebihi tunjangan.",
"generate_name": "Hasilkan Nama",

View file

@ -334,6 +334,7 @@
"freeze": "Congelare",
"frequently_asked_questions": "Domande frequenti",
"frozen": "Congelato",
"frozen_balance": "Equilibrio congelato",
"full_balance": "Saldo Completo",
"gas_exceeds_allowance": "Il gas richiesto dalla transazione supera l'indennità.",
"generate_name": "Genera nome",

View file

@ -333,6 +333,7 @@
"freeze": "氷結",
"frequently_asked_questions": "よくある質問",
"frozen": "凍った",
"frozen_balance": "凍結バランス",
"full_balance": "フルバランス",
"gas_exceeds_allowance": "取引に必要なガスは、手当を超えています。",
"generate_name": "名前の生成",

View file

@ -333,6 +333,7 @@
"freeze": "얼다",
"frequently_asked_questions": "자주 묻는 질문",
"frozen": "겨울 왕국",
"frozen_balance": "냉동 균형",
"full_balance": "풀 밸런스",
"gas_exceeds_allowance": "거래에 필요한 가스는 수당을 초과합니다.",
"generate_name": "이름 생성",

View file

@ -333,6 +333,7 @@
"freeze": "အေးခဲ",
"frequently_asked_questions": "မေးလေ့ရှိသောမေးခွန်းများ",
"frozen": "ဖြူဖြူ",
"frozen_balance": "လက်ကျန်ငွေ",
"full_balance": "Balance အပြည့်",
"gas_exceeds_allowance": "ငွေပေးငွေယူမှလိုအပ်သောဓာတ်ငွေ့ထောက်ပံ့ကြေးကျော်လွန်။",
"generate_name": "အမည်ဖန်တီးပါ။",

View file

@ -333,6 +333,7 @@
"freeze": "Bevriezen",
"frequently_asked_questions": "Veelgestelde vragen",
"frozen": "Bevroren",
"frozen_balance": "Bevroren balans",
"full_balance": "Volledig saldo",
"gas_exceeds_allowance": "Gas vereist door transactie overschrijdt de vergoeding.",
"generate_name": "Naam genereren",

View file

@ -333,6 +333,7 @@
"freeze": "Zamróź",
"frequently_asked_questions": "Często zadawane pytania",
"frozen": "Zamrożone",
"frozen_balance": "Mrożona równowaga",
"full_balance": "Pełne saldo",
"gas_exceeds_allowance": "Gaz wymagany przez transakcję przekracza dodatek.",
"generate_name": "Wygeneruj nazwę",

View file

@ -333,6 +333,7 @@
"freeze": "Congelar",
"frequently_asked_questions": "Perguntas frequentes",
"frozen": "Congeladas",
"frozen_balance": "Equilíbrio congelado",
"full_balance": "Saldo total",
"gas_exceeds_allowance": "O gás exigido pela transação excede o subsídio.",
"generate_name": "Gerar nome",

View file

@ -333,6 +333,7 @@
"freeze": "Заморозить",
"frequently_asked_questions": "Часто задаваемые вопросы",
"frozen": "Заморожено",
"frozen_balance": "Замороженный баланс",
"full_balance": "Весь баланс",
"gas_exceeds_allowance": "Газ, требуемый в результате транзакции, превышает пособие.",
"generate_name": "Создать имя",

View file

@ -333,6 +333,7 @@
"freeze": "ดักจับ",
"frequently_asked_questions": "คำถามที่พบบ่อย",
"frozen": "ถูกดักจับ",
"frozen_balance": "สมดุลแช่แข็ง",
"full_balance": "ยอดคงเหลือทั้งหมด",
"gas_exceeds_allowance": "ก๊าซที่ต้องการโดยการทำธุรกรรมเกินค่าเผื่อ",
"generate_name": "สร้างชื่อ",

View file

@ -333,6 +333,7 @@
"freeze": "I-freeze",
"frequently_asked_questions": "Mga madalas itanong",
"frozen": "Frozen",
"frozen_balance": "Frozen na balanse",
"full_balance": "Buong Balanse",
"gas_exceeds_allowance": "Ang gas na kinakailangan ng transaksyon ay lumampas sa allowance.",
"generate_name": "Bumuo ng pangalan",

View file

@ -333,6 +333,7 @@
"freeze": "Dondur",
"frequently_asked_questions": "Sıkça sorulan sorular",
"frozen": "Dondurulmuş",
"frozen_balance": "Dondurulmuş denge",
"full_balance": "Tüm bakiye",
"gas_exceeds_allowance": "İşlemin gerektirdiği gaz ödeneği aşar.",
"generate_name": "İsim Oluştur",

View file

@ -333,6 +333,7 @@
"freeze": "Заморозити",
"frequently_asked_questions": "Часті запитання",
"frozen": "Заморожено",
"frozen_balance": "Заморожений баланс",
"full_balance": "Весь баланс",
"gas_exceeds_allowance": "Газ, необхідний транзакціям, перевищує надбавку.",
"generate_name": "Згенерувати назву",

View file

@ -333,6 +333,7 @@
"freeze": "منجمد",
"frequently_asked_questions": "اکثر پوچھے گئے سوالات",
"frozen": "منجمد",
"frozen_balance": "منجمد توازن",
"full_balance": "مکمل بیلنس",
"gas_exceeds_allowance": "لین دین کے ذریعہ درکار گیس الاؤنس سے زیادہ ہے۔",
"generate_name": "نام پیدا کریں۔",

View file

@ -332,6 +332,7 @@
"freeze": "Đóng băng",
"frequently_asked_questions": "Các câu hỏi thường gặp",
"frozen": "Đã đóng băng",
"frozen_balance": "Cân bằng đông lạnh",
"full_balance": "Số dư đầy đủ",
"gas_exceeds_allowance": "Gas theo yêu cầu của giao dịch vượt quá trợ cấp.",
"generate_name": "Tạo tên",

View file

@ -334,6 +334,7 @@
"freeze": "Tì pa",
"frequently_asked_questions": "Àwọn ìbéèrè la máa ń béèrè",
"frozen": "Ó l'a tì pa",
"frozen_balance": "Iwontunwonsi ti o tutu",
"full_balance": "Ìyókù owó kíkún",
"gas_exceeds_allowance": "Gaasi ti a beere nipasẹ idunadura ju lọ.",
"generate_name": "Ṣẹda Orukọ",

View file

@ -333,6 +333,7 @@
"freeze": "凍結",
"frequently_asked_questions": "常见问题",
"frozen": "凍結的",
"frozen_balance": "冷冻平衡",
"full_balance": "全部余额",
"gas_exceeds_allowance": "交易要求的气体超出了津贴。",
"generate_name": "生成名称",

View file

@ -30,7 +30,6 @@ class SecretKey {
SecretKey('twitterBearerToken', () => ''),
SecretKey('anonPayReferralCode', () => ''),
SecretKey('fiatApiKey', () => ''),
SecretKey('payfuraApiKey', () => ''),
SecretKey('chatwootWebsiteToken', () => ''),
SecretKey('exolixApiKey', () => ''),
SecretKey('robinhoodApplicationId', () => ''),
@ -38,6 +37,7 @@ class SecretKey {
SecretKey('walletConnectProjectId', () => ''),
SecretKey('moralisApiKey', () => ''),
SecretKey('ankrApiKey', () => ''),
SecretKey('chainStackApiKey', () => ''),
SecretKey('quantexExchangeMarkup', () => ''),
SecretKey('seeds', () => ''),
SecretKey('testCakePayApiKey', () => ''),
@ -86,6 +86,7 @@ class SecretKey {
static final solanaSecrets = [
SecretKey('ankrApiKey', () => ''),
SecretKey('chainStackApiKey', () => ''),
];
static final nanoSecrets = [