Merge branch 'main' of https://github.com/cake-tech/cake_wallet into cw_linux_direct_input_password

 Conflicts:
	.gitignore
	cw_haven/pubspec.lock
	cw_nano/lib/nano_wallet.dart
	cw_nano/pubspec.lock
	lib/buy/moonpay/moonpay_provider.dart
	lib/di.dart
	lib/entities/load_current_wallet.dart
This commit is contained in:
OmarHatem 2024-01-16 02:11:28 +02:00
commit 851d22fd33
141 changed files with 1825 additions and 1498 deletions

View file

@ -0,0 +1,33 @@
---
name: "Bug Report \U0001FAB2 "
about: 'Report a bug '
title: ''
labels: Bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Platform:**
- OS: [e.g. iOS 15.1, Android 14]
- Device: [e.g. iPhone 14, Galaxy S21]
- Cake Wallet Version: [e.g. 4.12.1]
**Additional context**
Add any other context about the problem here.

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Not sure where to start?
url: https://guides.cakewallet.com
about: Start by reading checking out the guides!
- name: Need help?
url: https://cakewallet.com/#contact
about: Use our live chat or send a support email!

View file

@ -0,0 +1,20 @@
---
name: Feature or Enhancement Request ✨
about: Suggest an idea for Cake Wallet
title: ''
labels: Enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

9
.gitignore vendored
View file

@ -149,3 +149,12 @@ macos/Runner/DebugProfile.entitlements
macos/Runner/Release.entitlements
lib/core/secure_storage.dart
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
macos/Runner/Configs/AppInfo.xcconfig

View file

@ -37,7 +37,7 @@ if (appPropertiesFile.exists()) {
}
android {
compileSdkVersion 33
compileSdkVersion 34
lintOptions {
disable 'InvalidPackage'

View file

@ -71,8 +71,8 @@
android:name="flutterEmbedding"
android:value="2" />
<provider
android:name="com.pichillilorenzo.flutter_inappwebview.InAppWebViewFileProvider"
android:authorities="${applicationId}.flutter_inappwebview.fileprovider"
android:name="com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider"
android:authorities="${applicationId}.flutter_inappwebview_android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View file

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View file

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -1,4 +1,3 @@
Polyseed enhancements
New on-ramp provider DFX
Usability enhancements
Bug fixes
On-ramp flow fixes and enhancements
UI enhancements
Generic enhancements and bug fixes

View file

@ -1,2 +1,6 @@
Support multiple address types for Bitcoin Cash
Bug fixes
Add new Off-ramp providers (DFX, OnRamper)
On-ramp flow fixes and enhancements
Ethereum and WalletConnect fixes and improvements
Nano enhancements
UI enhancements
Generic enhancements and bug fixes

View file

@ -1,4 +1,24 @@
cd scripts/android
IOS="ios"
ANDROID="android"
PLATFORMS=($IOS $ANDROID)
PLATFORM=$1
if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then
echo "specify platform: ./configure_cake_wallet.sh ios|android"
exit 1
fi
if [ "$PLATFORM" == "$IOS" ]; then
echo "Configuring for iOS"
cd scripts/ios
fi
if [ "$PLATFORM" == "$ANDROID" ]; then
echo "Configuring for Android"
cd scripts/android
fi
source ./app_env.sh cakewallet
./app_config.sh
cd ../.. && flutter pub get

View file

@ -1,13 +1,9 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/balance.dart';
class ElectrumBalance extends Balance {
const ElectrumBalance(
{required this.confirmed,
required this.unconfirmed,
required this.frozen})
const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
: super(confirmed, unconfirmed);
static ElectrumBalance? fromJSON(String? jsonSource) {
@ -28,12 +24,10 @@ class ElectrumBalance extends Balance {
final int frozen;
@override
String get formattedAvailableBalance =>
bitcoinAmountToString(amount: confirmed - unconfirmed.abs() - frozen);
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
@override
String get formattedAdditionalBalance =>
bitcoinAmountToString(amount: unconfirmed);
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
@override
String get formattedUnAvailableBalance {
@ -41,6 +35,6 @@ class ElectrumBalance extends Balance {
return frozenFormatted == '0.0' ? '' : frozenFormatted;
}
String toJSON() => json.encode(
{'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen});
String toJSON() =>
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen});
}

View file

@ -62,6 +62,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
}
@override
String get primaryAddress => getAddress(index: 0, hd: mainHd);
@override
set address(String addr) => null;

View file

@ -95,6 +95,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.banano,
CryptoCurrency.usdtPoly,
CryptoCurrency.usdcEPoly,
CryptoCurrency.kaspa,
];
static const havenCurrencies = [
@ -206,6 +207,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29);
static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POLY', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kaspa', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
static final Map<int, CryptoCurrency> _rawCurrencyMap =

View file

@ -61,7 +61,8 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
static const typeId = ERC20_TOKEN_TYPE_ID;
static const boxName = 'Erc20Tokens';
static const polygonBoxName = ' PolygonErc20Tokens';
static const ethereumBoxName = 'EthereumErc20Tokens';
static const polygonBoxName = 'PolygonErc20Tokens';
@override
bool operator ==(other) =>

View file

@ -15,6 +15,7 @@ abstract class TransactionInfo extends Object with Keyable {
String? feeFormatted();
void changeFiatAmount(String amount);
String? to;
String? from;
@override
dynamic get keyIndex => id;

View file

@ -10,6 +10,8 @@ abstract class WalletAddresses {
String get address;
String? get primaryAddress => null;
set address(String address);
Map<String, String> addressesMap;

View file

@ -83,6 +83,8 @@ abstract class EthereumWalletBase
late final Box<Erc20Token> erc20TokensBox;
late final Box<Erc20Token> ethereumErc20TokensBox;
late final EthPrivateKey _ethPrivateKey;
EthPrivateKey get ethPrivateKey => _ethPrivateKey;
@ -110,7 +112,8 @@ abstract class EthereumWalletBase
Completer<SharedPreferences> _sharedPrefs = Completer();
Future<void> init() async {
erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName);
await movePreviousErc20BoxConfigsToNewBox();
await walletAddresses.init();
await transactionHistory.init();
_ethPrivateKey = await getPrivateKey(
@ -122,6 +125,33 @@ abstract class EthereumWalletBase
await save();
}
/// Majorly for backward compatibility for previous configs that have been set.
Future<void> movePreviousErc20BoxConfigsToNewBox() async {
// Opens a box specific to this wallet
ethereumErc20TokensBox = await CakeHive.openBox<Erc20Token>(
"${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}");
//Open the previous token configs box
erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName);
// Check if it's empty, if it is, we stop the flow and return.
if (erc20TokensBox.isEmpty) {
// If it's empty, but the new wallet specific box is also empty,
// we load the initial tokens to the new box.
if (ethereumErc20TokensBox.isEmpty) addInitialTokens();
return;
}
final allValues = erc20TokensBox.values.toList();
// Clear and delete the old token box
await erc20TokensBox.clear();
await erc20TokensBox.deleteFromDisk();
// Add all the previous tokens with configs to the new box
ethereumErc20TokensBox.addAll(allValues);
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
try {
@ -388,7 +418,7 @@ abstract class EthereumWalletBase
}
Future<void> _fetchErc20Balances() async {
for (var token in erc20TokensBox.values) {
for (var token in ethereumErc20TokensBox.values) {
try {
if (token.enabled) {
balance[token] = await _client.fetchERC20Balances(
@ -423,7 +453,7 @@ abstract class EthereumWalletBase
Future<void>? updateBalance() async => await _updateBalance();
List<Erc20Token> get erc20Currencies => erc20TokensBox.values.toList();
List<Erc20Token> get erc20Currencies => ethereumErc20TokensBox.values.toList();
Future<void> addErc20Token(Erc20Token token) async {
String? iconPath;
@ -443,7 +473,7 @@ abstract class EthereumWalletBase
iconPath: iconPath,
);
await erc20TokensBox.put(_token.contractAddress, _token);
await ethereumErc20TokensBox.put(_token.contractAddress, _token);
if (_token.enabled) {
balance[_token] = await _client.fetchERC20Balances(
@ -473,7 +503,7 @@ abstract class EthereumWalletBase
void addInitialTokens() {
final initialErc20Tokens = DefaultErc20Tokens().initialErc20Tokens;
initialErc20Tokens.forEach((token) => erc20TokensBox.put(token.contractAddress, token));
initialErc20Tokens.forEach((token) => ethereumErc20TokensBox.put(token.contractAddress, token));
}
@override

View file

@ -22,12 +22,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
@override
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
final strength = (credentials.seedPhraseLength == 12)
? 128
: (credentials.seedPhraseLength == 24)
? 256
: 128;
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final mnemonic = bip39.generateMnemonic(strength: strength);
final wallet = EthereumWallet(

View file

@ -113,15 +113,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.4.3"
cake_backup:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
url: "https://github.com/cake-tech/cake_backup.git"
source: git
version: "1.0.0+1"
characters:
dependency: transitive
description:
@ -178,22 +169,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
cryptography:
dependency: transitive
description:
name: cryptography
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
url: "https://pub.dev"
source: hosted
version: "2.5.0"
cupertino_icons:
dependency: transitive
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.6"
cw_core:
dependency: "direct main"
description:
@ -648,14 +623,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
dependency: transitive
description:

View file

@ -1,20 +1,19 @@
import 'package:cw_core/balance.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:nanoutil/nanoutil.dart';
class BananoBalance extends Balance {
final BigInt currentBalance;
final BigInt receivableBalance;
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
}
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
@override
String get formattedAvailableBalance {
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano);
return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano);
}
@override
String get formattedAdditionalBalance {
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano);
return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerBanano);
}
}

View file

@ -1,34 +1,35 @@
import 'package:cw_core/balance.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:nanoutil/nanoutil.dart';
BigInt stringAmountToBigInt(String amount) {
return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano));
return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano));
}
class NanoBalance extends Balance {
final BigInt currentBalance;
final BigInt receivableBalance;
late String formattedCurrentBalance;
late String formattedReceivableBalance;
NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
this.formattedCurrentBalance = "";
this.formattedReceivableBalance = "";
}
NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
NanoBalance.fromString(
{required this.formattedCurrentBalance, required this.formattedReceivableBalance})
NanoBalance.fromFormattedString(
{required String formattedCurrentBalance, required String formattedReceivableBalance})
: currentBalance = stringAmountToBigInt(formattedCurrentBalance),
receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
super(0, 0);
NanoBalance.fromRawString(
{required String currentBalance, required String receivableBalance})
: currentBalance = BigInt.parse(currentBalance),
receivableBalance = BigInt.parse(receivableBalance),
super(0, 0);
@override
String get formattedAvailableBalance {
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano);
return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerNano);
}
@override
String get formattedAdditionalBalance {
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano);
return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerNano);
}
}

View file

@ -4,10 +4,10 @@ import 'dart:convert';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_transaction_model.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:http/http.dart' as http;
import 'package:nanodart/nanodart.dart';
import 'package:cw_core/node.dart';
import 'package:nanoutil/nanoutil.dart';
import 'package:shared_preferences/shared_preferences.dart';
class NanoClient {
@ -61,6 +61,13 @@ class NanoClient {
),
);
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"] : ""}");
}
final String currentBalance = data["balance"] as String;
final String receivableBalance = data["receivable"] as String;
final BigInt cur = BigInt.parse(currentBalance);
@ -203,7 +210,7 @@ class NanoClient {
String? previousHash,
}) async {
// our address:
final String publicAddress = NanoUtil.privateKeyToAddress(privateKey);
final String publicAddress = NanoDerivations.privateKeyToAddress(privateKey);
// first get the current account balance:
if (balanceAfterTx == null) {

View file

@ -1,7 +1,7 @@
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:nanoutil/nanoutil.dart';
class NanoTransactionInfo extends TransactionInfo {
NanoTransactionInfo({
@ -13,6 +13,8 @@ class NanoTransactionInfo extends TransactionInfo {
required this.confirmed,
required this.date,
required this.confirmations,
required this.to,
required this.from,
}) : this.amount = amountRaw.toInt();
final String id;
@ -24,14 +26,17 @@ class NanoTransactionInfo extends TransactionInfo {
final bool confirmed;
final int confirmations;
final String tokenSymbol;
final String? to;
final String? from;
String? _fiatAmount;
bool get isPending => !this.confirmed;
@override
String amountFormatted() {
final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano);
final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano);
final String amt =
NanoAmounts.getRawAsUsableString(amountRaw.toString(), NanoAmounts.rawPerNano);
final String acc = NanoAmounts.getRawAccuracy(amountRaw.toString(), NanoAmounts.rawPerNano);
return "$acc$amt $tokenSymbol";
}
@ -54,6 +59,8 @@ class NanoTransactionInfo extends TransactionInfo {
confirmed: data['confirmed'] as bool,
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
to: data['to'] as String,
from: data['from'] as String,
);
}
@ -66,5 +73,7 @@ class NanoTransactionInfo extends TransactionInfo {
'confirmed': confirmed,
'confirmations': confirmations,
'tokenSymbol': tokenSymbol,
'to': to,
'from': from,
};
}

View file

@ -1,193 +0,0 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import "package:ed25519_hd_key/ed25519_hd_key.dart";
import 'package:libcrypto/libcrypto.dart';
import 'package:nanodart/nanodart.dart';
import 'package:decimal/decimal.dart';
class NanoUtil {
// standard:
static String seedToPrivate(String seed, int index) {
return NanoKeys.seedToPrivate(seed, index);
}
static String seedToAddress(String seed, int index) {
return NanoAccounts.createAccount(
NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
}
static String seedToMnemonic(String seed) {
return NanoMnemomics.seedToMnemonic(seed).join(" ");
}
static Future<String> mnemonicToSeed(String mnemonic) async {
return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
}
static String privateKeyToPublic(String privateKey) {
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
return NanoKeys.createPublicKey(privateKey);
}
static String addressToPublicKey(String publicAddress) {
return NanoAccounts.extractPublicKey(publicAddress);
}
// universal:
static String privateKeyToAddress(String privateKey) {
return NanoAccounts.createAccount(NanoAccountType.NANO, privateKeyToPublic(privateKey));
}
static String publicKeyToAddress(String publicKey) {
return NanoAccounts.createAccount(NanoAccountType.NANO, publicKey);
}
// standard + hd:
static bool isValidSeed(String seed) {
// Ensure seed is 64 or 128 characters long
if (seed == null || (seed.length != 64 && seed.length != 128)) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return NanoHelpers.isHexString(seed);
}
// // hd:
static Future<String> hdMnemonicListToSeed(List<String> words) async {
// if (words.length != 24) {
// throw Exception('Expected a 24-word list, got a ${words.length} list');
// }
final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
final String seed = await hasher.sha512(words.join(' '), salt);
return seed;
}
static Future<String> hdSeedToPrivate(String seed, int index) async {
List<int> seedBytes = hex.decode(seed);
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
return hex.encode(data.key);
}
static Future<String> hdSeedToAddress(String seed, int index) async {
return NanoAccounts.createAccount(
NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
}
static Future<String> uniSeedToAddress(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToAddress(seed, index));
} else if (type == "hd") {
return hdSeedToAddress(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
static Future<String> uniSeedToPrivate(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToPrivate(seed, index));
} else if (type == "hd") {
return hdSeedToPrivate(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
static bool isValidBip39Seed(String seed) {
// Ensure seed is 128 characters long
if (seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return NanoHelpers.isHexString(seed);
}
// number util:
static const int maxDecimalDigits = 6; // Max digits after decimal
static BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
static BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
static BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
static BigInt rawPerXMR = BigInt.parse("1000000000000");
static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
// static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
/// Convert raw to ban and return as BigDecimal
///
/// @param raw 100000000000000000000000000000
/// @return Decimal value 1.000000000000000000000000000000
///
static Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) {
rawPerCur ??= rawPerNano;
final Decimal amount = Decimal.parse(raw.toString());
final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
return result;
}
static String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
Decimal bigger = input.shift(digits);
bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
bigger = bigger.shift(-digits);
return bigger.toString();
}
/// Return raw as a NANO amount.
///
/// @param raw 100000000000000000000000000000
/// @returns 1
///
static String getRawAsUsableString(String? raw, BigInt rawPerCur) {
final String res =
truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
return "0";
}
if (!res.contains(".")) {
return res;
}
final String numAmount = res.split(".")[0];
String decAmount = res.split(".")[1];
// truncate:
if (decAmount.length > maxDecimalDigits) {
decAmount = decAmount.substring(0, maxDecimalDigits);
// remove trailing zeros:
decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
if (decAmount.isEmpty) {
return numAmount;
}
}
return "$numAmount.$decAmount";
}
static String getRawAccuracy(String? raw, BigInt rawPerCur) {
final String rawString = getRawAsUsableString(raw, rawPerCur);
final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString();
if (raw == null || raw.isEmpty || raw == "0") {
return "";
}
if (rawString != rawDecimalString) {
return "~";
}
return "";
}
/// Return readable string amount as raw string
/// @param amount 1.01
/// @returns 101000000000000000000000000000
///
static String getAmountAsRaw(String amount, BigInt rawPerCur) {
final Decimal asDecimal = Decimal.parse(amount);
final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
return (asDecimal * rawDecimal).toString();
}
}

View file

@ -18,7 +18,6 @@ import 'package:cw_nano/nano_client.dart';
import 'package:cw_nano/nano_transaction_credentials.dart';
import 'package:cw_nano/nano_transaction_history.dart';
import 'package:cw_nano/nano_transaction_info.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:cw_nano/nano_wallet_keys.dart';
import 'package:cw_nano/pending_nano_transaction.dart';
import 'package:mobx/mobx.dart';
@ -27,6 +26,7 @@ import 'package:cw_nano/nano_wallet_addresses.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:nanodart/nanodart.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:nanoutil/nanoutil.dart';
part 'nano_wallet.g.dart';
@ -94,6 +94,8 @@ abstract class NanoWalletBase
@override
String get password => _password;
static const int POLL_INTERVAL_SECONDS = 10;
// initialize the different forms of private / public key we'll need:
Future<void> init() async {
if (_derivationType == DerivationType.unknown) {
@ -111,11 +113,21 @@ abstract class NanoWalletBase
if (_derivationType == DerivationType.nano) {
_hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
} else {
_hexSeed = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' '));
_hexSeed = await NanoDerivations.hdMnemonicListToSeed(_mnemonic.split(' '));
}
}
_privateKey = await NanoUtil.uniSeedToPrivate(_hexSeed!, 0, type);
_publicAddress = await NanoUtil.uniSeedToAddress(_hexSeed!, 0, type);
NanoDerivationType derivationType =
type == "standard" ? NanoDerivationType.STANDARD : NanoDerivationType.HD;
_privateKey = await NanoDerivations.universalSeedToPrivate(
_hexSeed!,
index: 0,
type: derivationType,
);
_publicAddress = await NanoDerivations.universalSeedToAddress(
_hexSeed!,
index: 0,
type: derivationType,
);
this.walletInfo.address = _publicAddress!;
await walletAddresses.init();
@ -136,6 +148,7 @@ abstract class NanoWalletBase
@override
void close() {
_client.stop();
_receiveTimer?.cancel();
}
@action
@ -150,6 +163,7 @@ abstract class NanoWalletBase
try {
await _updateBalance();
await updateTransactions();
await _updateRep();
await _receiveAll();
} catch (e) {
@ -184,8 +198,8 @@ abstract class NanoWalletBase
if (txOut.sendAll) {
amt = balance[currency]?.currentBalance ?? BigInt.zero;
} else {
amt = BigInt.tryParse(NanoUtil.getAmountAsRaw(
txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ??
amt = BigInt.tryParse(NanoAmounts.getAmountAsRaw(
txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoAmounts.rawPerNano)) ??
BigInt.zero;
}
@ -197,9 +211,7 @@ abstract class NanoWalletBase
final block = await _client.constructSendBlock(
amountRaw: amt.toString(),
destinationAddress: txOut.isParsedAddress
? txOut.extractedAddress!
: txOut.address,
destinationAddress: txOut.isParsedAddress ? txOut.extractedAddress! : txOut.address,
privateKey: _privateKey!,
balanceAfterTx: runningBalance,
previousHash: previousHash,
@ -247,10 +259,10 @@ abstract class NanoWalletBase
}
}
Future<void> updateTransactions() async {
Future<bool> updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
return false;
}
_isTransactionUpdating = true;
@ -258,8 +270,10 @@ abstract class NanoWalletBase
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
return true;
} catch (_) {
_isTransactionUpdating = false;
return false;
}
}
@ -272,16 +286,17 @@ abstract class NanoWalletBase
final Map<String, NanoTransactionInfo> result = {};
for (var transactionModel in transactions) {
final bool isSend = transactionModel.type == "send";
result[transactionModel.hash] = NanoTransactionInfo(
id: transactionModel.hash,
amountRaw: transactionModel.amount,
height: transactionModel.height,
direction: transactionModel.type == "send"
? TransactionDirection.outgoing
: TransactionDirection.incoming,
direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
confirmed: transactionModel.confirmed,
date: transactionModel.date ?? DateTime.now(),
confirmations: transactionModel.confirmed ? 1 : 0,
to: isSend ? transactionModel.account : address,
from: isSend ? address : transactionModel.account,
);
}
@ -323,11 +338,10 @@ abstract class NanoWalletBase
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
await _updateBalance();
await updateTransactions();
// setup a timer to receive transactions periodically:
_receiveTimer?.cancel();
_receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async {
_receiveTimer = Timer.periodic(const Duration(seconds: POLL_INTERVAL_SECONDS), (timer) async {
// get our balance:
await _updateBalance();
// if we have anything to receive, process it:
@ -336,6 +350,14 @@ abstract class NanoWalletBase
}
});
// also run once, immediately:
await _updateBalance();
bool updateSuccess = await updateTransactions();
if (!updateSuccess) {
syncStatus = FailedSyncStatus();
return;
}
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e);
@ -365,9 +387,11 @@ abstract class NanoWalletBase
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
final balance = NanoBalance.fromString(
formattedCurrentBalance: data['currentBalance'] as String? ?? "0",
formattedReceivableBalance: data['receivableBalance'] as String? ?? "0");
final balance = NanoBalance.fromRawString(
currentBalance: data['currentBalance'] as String? ?? "0",
receivableBalance: data['receivableBalance'] as String? ?? "0",
);
DerivationType derivationType = DerivationType.nano;
if (data['derivationType'] == "DerivationType.bip39") {
@ -387,12 +411,26 @@ abstract class NanoWalletBase
}
Future<void> _updateBalance() async {
var oldBalance = balance[currency];
try {
balance[currency] = await _client.getBalance(_publicAddress!);
} catch (e) {
print("Failed to get balance $e");
// if we don't have a balance, we should at least create one, since it's a late binding
// otherwise, it's better to just leave it as whatever it was before:
if (balance[currency] == null) {
balance[currency] =
NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero);
}
}
// don't save unnecessarily:
// trying to save too frequently can cause problems with the file system
// since nano is updated frequently this can be a problem, so we only save if there is a change:
if (oldBalance == null ||
balance[currency]!.currentBalance != oldBalance.currentBalance ||
balance[currency]!.receivableBalance != oldBalance.receivableBalance) {
await save();
}
await save();
}
Future<void> _updateRep() async {
@ -407,11 +445,19 @@ abstract class NanoWalletBase
}
Future<void> regenerateAddress() async {
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
_privateKey =
await NanoUtil.uniSeedToPrivate(_hexSeed!, this.walletAddresses.account!.id, type);
_publicAddress =
await NanoUtil.uniSeedToAddress(_hexSeed!, this.walletAddresses.account!.id, type);
final NanoDerivationType type = (_derivationType == DerivationType.nano)
? NanoDerivationType.STANDARD
: NanoDerivationType.HD;
_privateKey = await NanoDerivations.universalSeedToPrivate(
_hexSeed!,
index: this.walletAddresses.account!.id,
type: type,
);
_publicAddress = await NanoDerivations.universalSeedToAddress(
_hexSeed!,
index: this.walletAddresses.account!.id,
type: type,
);
this.walletInfo.address = _publicAddress!;
this.walletAddresses.address = _publicAddress!;

View file

@ -7,12 +7,12 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_nano/nano_mnemonic.dart' as nm;
import 'package:cw_nano/nano_util.dart';
import 'package:cw_nano/nano_wallet.dart';
import 'package:cw_nano/nano_wallet_creation_credentials.dart';
import 'package:hive/hive.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:nanodart/nanodart.dart';
import 'package:nanoutil/nanoutil.dart';
class NanoWalletService extends WalletService<NanoNewWalletCredentials,
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
@ -32,7 +32,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
// nano standard:
DerivationType derivationType = DerivationType.nano;
String seedKey = NanoSeeds.generateSeed();
String mnemonic = NanoUtil.seedToMnemonic(seedKey);
String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
credentials.walletInfo!.derivationType = derivationType;
@ -102,7 +102,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
// we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
if (credentials.seedKey.length == 64) {
try {
mnemonic = NanoUtil.seedToMnemonic(credentials.seedKey);
mnemonic = NanoDerivations.standardSeedToMnemonic(credentials.seedKey);
} catch (e) {
throw Exception("Wasn't a valid nano style seed!");
}

View file

@ -1,6 +1,6 @@
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_nano/nano_client.dart';
import 'package:cw_nano/nano_util.dart';
import 'package:nanoutil/nanoutil.dart';
class PendingNanoTransaction with PendingTransaction {
PendingNanoTransaction({
@ -18,13 +18,13 @@ class PendingNanoTransaction with PendingTransaction {
@override
String get amountFormatted {
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano);
return amt;
}
String get accurateAmountFormatted {
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano);
final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano);
final String acc = NanoAmounts.getRawAccuracy(amount.toString(), NanoAmounts.rawPerNano);
return "$acc$amt";
}

View file

@ -137,15 +137,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.6.1"
cake_backup:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
url: "https://github.com/cake-tech/cake_backup.git"
source: git
version: "1.0.0+1"
characters:
dependency: transitive
description:
@ -202,22 +193,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cryptography:
dependency: transitive
description:
name: cryptography
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
url: "https://pub.dev"
source: hosted
version: "2.5.0"
cupertino_icons:
dependency: transitive
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.6"
cw_core:
dependency: "direct main"
description:
@ -496,6 +471,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
nanoutil:
dependency: "direct main"
description:
path: "."
ref: c37e72817cf0a28162f43124f79661d6c8e0098f
resolved-ref: c37e72817cf0a28162f43124f79661d6c8e0098f
url: "https://github.com/perishllc/nanoutil.git"
source: git
version: "1.0.0"
package_config:
dependency: transitive
description:
@ -789,14 +773,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
dependency: transitive
description:

View file

@ -22,6 +22,10 @@ dependencies:
hex: ^0.2.0
http: ^1.1.0
shared_preferences: ^2.0.15
nanoutil:
git:
url: https://github.com/perishllc/nanoutil.git
ref: c37e72817cf0a28162f43124f79661d6c8e0098f
cw_core:
path: ../cw_core

View file

@ -102,8 +102,12 @@ abstract class PolygonWalletBase
final Completer<SharedPreferences> _sharedPrefs = Completer();
Future<void> init() async {
polygonErc20TokensBox = await CakeHive.openBox<Erc20Token>(
"${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.polygonBoxName}");
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.polygonBoxName}";
if (await CakeHive.boxExists(boxName)) {
polygonErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
} else {
polygonErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName.replaceAll(" ", ""));
}
await walletAddresses.init();
await transactionHistory.init();
_polygonPrivateKey = await getPrivateKey(

View file

@ -20,11 +20,7 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
@override
Future<PolygonWallet> create(PolygonNewWalletCredentials credentials) async {
final strength = (credentials.seedPhraseLength == 12)
? 128
: (credentials.seedPhraseLength == 24)
? 256
: 128;
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final mnemonic = bip39.generateMnemonic(strength: strength);
final wallet = PolygonWallet(

View file

@ -102,11 +102,11 @@ PODS:
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview (0.0.1):
- flutter_inappwebview_ios (0.0.1):
- Flutter
- flutter_inappwebview/Core (= 0.0.1)
- flutter_inappwebview_ios/Core (= 0.0.1)
- OrderedSet (~> 5.0)
- flutter_inappwebview/Core (0.0.1):
- flutter_inappwebview_ios/Core (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
- flutter_mailer (0.0.1):
@ -169,7 +169,7 @@ DEPENDENCIES:
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
@ -224,8 +224,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_inappwebview:
:path: ".symlinks/plugins/flutter_inappwebview/ios"
flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_mailer:
:path: ".symlinks/plugins/flutter_mailer/ios"
flutter_secure_storage:
@ -274,29 +274,29 @@ SPEC CHECKSUMS:
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_inappwebview: 3d32228f1304635e7c028b0d4252937730bbc6cf
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
local_auth_ios: 1ba1475238daa33a6ffa2a29242558437be435ac
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 2aea163b50bfcb569a2726b6a754c54a4506fcf6
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
SwiftProtobuf: 40bd808372cb8706108f22d28f8ab4a6b9bc6989
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6

View file

@ -2,11 +2,11 @@ import 'package:flutter/foundation.dart';
import 'package:cake_wallet/buy/buy_provider_description.dart';
class BuyException implements Exception {
BuyException({required this.description, required this.text});
BuyException({required this.title, required this.content});
final BuyProviderDescription description;
final String text;
final String title;
final String content;
@override
String toString() => '${description.title}: $text';
String toString() => '$title: $content';
}

View file

@ -1,27 +1,33 @@
import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider_description.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
abstract class BuyProvider {
BuyProvider({required this.wallet, required this.isTestEnvironment});
BuyProvider({
required this.wallet,
required this.isTestEnvironment,
});
final WalletBase wallet;
final bool isTestEnvironment;
String get title;
BuyProviderDescription get description;
String get trackUrl;
WalletType get walletType => wallet.type;
String get walletAddress => wallet.walletAddresses.address;
String get walletId => wallet.id;
String get providerDescription;
String get lightIcon;
String get darkIcon;
@override
String toString() => title;
Future<String> requestUrl(String amount, String sourceCurrency);
Future<Order> findOrderById(String id);
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency);
}
Future<void> launchProvider(BuildContext context, bool? isBuyAction);
Future<String> requestUrl(String amount, String sourceCurrency) => throw UnimplementedError();
Future<Order> findOrderById(String id) => throw UnimplementedError();
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError();
}

View file

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
@ -11,10 +12,9 @@ import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
class DFXBuyProvider {
DFXBuyProvider({required WalletBase wallet}) : this._wallet = wallet;
final WalletBase _wallet;
class DFXBuyProvider extends BuyProvider {
DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
static const _baseUrl = 'api.dfx.swiss';
static const _authPath = '/v1/auth/signMessage';
@ -22,8 +22,20 @@ class DFXBuyProvider {
static const _signInPath = '/v1/auth/signIn';
static const walletName = 'CakeWallet';
@override
String get title => 'DFX Connect';
@override
String get providerDescription => S.current.dfx_option_description;
@override
String get lightIcon => 'assets/images/dfx_light.png';
@override
String get darkIcon => 'assets/images/dfx_dark.png';
String get assetOut {
switch (_wallet.type) {
switch (wallet.type) {
case WalletType.bitcoin:
return 'BTC';
case WalletType.bitcoinCash:
@ -35,12 +47,12 @@ class DFXBuyProvider {
case WalletType.ethereum:
return 'ETH';
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
throw Exception("WalletType is not available for DFX ${wallet.type}");
}
}
String get blockchain {
switch (_wallet.type) {
switch (wallet.type) {
case WalletType.bitcoin:
case WalletType.bitcoinCash:
case WalletType.litecoin:
@ -50,12 +62,14 @@ class DFXBuyProvider {
case WalletType.ethereum:
return 'Ethereum';
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
throw Exception("WalletType is not available for DFX ${wallet.type}");
}
}
String get walletAddress =>
wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address;
Future<String> getSignMessage() async {
final walletAddress = _wallet.walletAddresses.address;
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
var response = await http.get(uri, headers: {'accept': 'application/json'});
@ -71,7 +85,6 @@ class DFXBuyProvider {
Future<String> signUp() async {
final signMessage = getSignature(await getSignMessage());
final walletAddress = _wallet.walletAddresses.address;
final requestBody = jsonEncode({
'wallet': walletName,
@ -80,21 +93,26 @@ class DFXBuyProvider {
});
final uri = Uri.https(_baseUrl, _signUpPath);
var response = await http.post(uri,
headers: {'Content-Type': 'application/json'}, body: requestBody);
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 up. Status: ${response.statusCode} ${response.body}');
throw Exception('Failed to sign up. Status: ${response.statusCode} ${response.body}');
}
}
Future<String> signIn() async {
final signMessage = getSignature(await getSignMessage());
final walletAddress = _wallet.walletAddresses.address;
final requestBody = jsonEncode({
'address': walletAddress,
@ -102,37 +120,44 @@ class DFXBuyProvider {
});
final uri = Uri.https(_baseUrl, _signInPath);
var response = await http.post(uri,
headers: {'Content-Type': 'application/json'}, body: requestBody);
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}');
throw Exception('Failed to sign in. Status: ${response.statusCode} ${response.body}');
}
}
String getSignature(String message) {
switch (_wallet.type) {
switch (wallet.type) {
case WalletType.ethereum:
return _wallet.signMessage(message);
return wallet.signMessage(message);
case WalletType.monero:
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
return _wallet.signMessage(message,
address: _wallet.walletAddresses.address);
return wallet.signMessage(message, address: walletAddress);
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
throw Exception("WalletType is not available for DFX ${wallet.type}");
}
}
Future<void> launchProvider(BuildContext context) async {
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
try {
final assetOut = this.assetOut;
final blockchain = this.blockchain;
final actionType = isBuyAction == true ? '/buy' : '/sell';
String accessToken;
@ -146,7 +171,7 @@ class DFXBuyProvider {
}
}
final uri = Uri.https('services.dfx.swiss', '/buy', {
final uri = Uri.https('services.dfx.swiss', actionType, {
'session': accessToken,
'lang': 'en',
'asset-out': assetOut,
@ -156,8 +181,7 @@ class DFXBuyProvider {
if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage,
arguments: [S.of(context).buy, uri]);
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [title, uri]);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}

View file

@ -1,9 +1,14 @@
import 'dart:convert';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:crypto/crypto.dart';
import 'package:cake_wallet/buy/buy_exception.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
@ -14,17 +19,38 @@ import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart';
import 'package:url_launcher/url_launcher.dart';
class MoonPaySellProvider {
MoonPaySellProvider({this.isTest = false}) : baseUrl = isTest ? _baseTestUrl : _baseProductUrl;
class MoonPaySellProvider extends BuyProvider {
MoonPaySellProvider({
required SettingsStore settingsStore,
required WalletBase wallet,
bool isTestEnvironment = false,
}) : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl,
this._settingsStore = settingsStore,
super(wallet: wallet, isTestEnvironment: isTestEnvironment);
final SettingsStore _settingsStore;
static const _baseTestUrl = 'sell-sandbox.moonpay.com';
static const _baseProductUrl = 'sell.moonpay.com';
@override
String get providerDescription =>
'MoonPay offers a fast and simple way to buy and sell cryptocurrencies';
@override
String get title => 'MoonPay';
@override
String get lightIcon => 'assets/images/moonpay_light.png';
@override
String get darkIcon => 'assets/images/moonpay_dark.png';
static String themeToMoonPayTheme(ThemeBase theme) {
switch (theme.type) {
case ThemeType.bright:
return 'light';
case ThemeType.light:
return 'light';
case ThemeType.dark:
@ -35,13 +61,13 @@ class MoonPaySellProvider {
static String get _apiKey => secrets.moonPayApiKey;
static String get _secretKey => secrets.moonPaySecretKey;
final bool isTest;
final String baseUrl;
Future<Uri> requestUrl(
{required CryptoCurrency currency,
required String refundWalletAddress,
required SettingsStore settingsStore}) async {
Future<Uri> requestMoonPayUrl({
required CryptoCurrency currency,
required String refundWalletAddress,
required SettingsStore settingsStore,
}) async {
final customParams = {
'theme': themeToMoonPayTheme(settingsStore.currentTheme),
'language': settingsStore.languageCode,
@ -51,20 +77,22 @@ class MoonPaySellProvider {
};
final originalUri = Uri.https(
baseUrl,
'',
<String, dynamic>{
'apiKey': _apiKey,
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
'refundWalletAddress': refundWalletAddress
}..addAll(customParams));
baseUrl,
'',
<String, dynamic>{
'apiKey': _apiKey,
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
'refundWalletAddress': refundWalletAddress,
}..addAll(customParams),
);
final messageBytes = utf8.encode('?${originalUri.query}');
final key = utf8.encode(_secretKey);
final hmac = Hmac(sha256, key);
final digest = hmac.convert(messageBytes);
final signature = base64.encode(digest.bytes);
if (isTest) {
if (isTestEnvironment) {
return originalUri;
}
@ -73,6 +101,39 @@ class MoonPaySellProvider {
final signedUri = originalUri.replace(queryParameters: query);
return signedUri;
}
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
try {
final uri = await requestMoonPayUrl(
currency: wallet.currency,
refundWalletAddress: wallet.walletAddresses.address,
settingsStore: _settingsStore,
);
if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else {
throw Exception('Could not launch URL');
}
} catch (e) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: 'MoonPay',
alertContent: 'The MoonPay service is currently unavailable: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
}
}
class MoonPayBuyProvider extends BuyProvider {
@ -94,16 +155,21 @@ class MoonPayBuyProvider extends BuyProvider {
String get title => 'MoonPay';
@override
BuyProviderDescription get description => BuyProviderDescription.moonPay;
String get currencyCode => walletTypeToCryptoCurrency(walletType).title.toLowerCase();
String get providerDescription =>
'MoonPay offers a fast and simple way to buy and sell cryptocurrencies';
@override
String get lightIcon => 'assets/images/moonpay_light.png';
@override
String get darkIcon => 'assets/images/moonpay_dark.png';
String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
String get trackUrl => baseUrl + '/transaction_receipt?transactionId=';
String baseUrl;
@override
Future<String> requestUrl(String amount, String sourceCurrency) async {
final enabledPaymentMethods = 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay'
'%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment';
@ -115,7 +181,7 @@ class MoonPayBuyProvider extends BuyProvider {
'&enabledPaymentMethods=' +
enabledPaymentMethods +
'&walletAddress=' +
walletAddress +
wallet.walletAddresses.address +
'&baseCurrencyCode=' +
sourceCurrency.toLowerCase() +
'&baseCurrencyAmount=' +
@ -136,7 +202,6 @@ class MoonPayBuyProvider extends BuyProvider {
return isTestEnvironment ? originalUrl : urlWithSignature;
}
@override
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
final url = _apiUrl +
_currenciesSuffix +
@ -152,7 +217,7 @@ class MoonPayBuyProvider extends BuyProvider {
final response = await get(uri);
if (response.statusCode != 200) {
throw BuyException(description: description, text: 'Quote is not found!');
throw BuyException(title: providerDescription, content: 'Quote is not found!');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -164,14 +229,13 @@ class MoonPayBuyProvider extends BuyProvider {
sourceAmount: sourceAmount, destAmount: destAmount, minAmount: minSourceAmount);
}
@override
Future<Order> findOrderById(String id) async {
final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey;
final uri = Uri.parse(url);
final response = await get(uri);
if (response.statusCode != 200) {
throw BuyException(description: description, text: 'Transaction $id is not found!');
throw BuyException(title: providerDescription, content: 'Transaction $id is not found!');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -183,13 +247,13 @@ class MoonPayBuyProvider extends BuyProvider {
return Order(
id: id,
provider: description,
provider: BuyProviderDescription.moonPay,
transferId: id,
state: state,
createdAt: createdAt,
amount: amount.toString(),
receiveAddress: walletAddress,
walletId: walletId);
receiveAddress: wallet.walletAddresses.address,
walletId: wallet.id);
}
static Future<bool> onEnabled() async {
@ -208,4 +272,8 @@ class MoonPayBuyProvider extends BuyProvider {
return isBuyEnable;
}
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) =>
throw UnimplementedError();
}

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/settings_store.dart';
@ -9,20 +10,31 @@ import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class OnRamperBuyProvider {
OnRamperBuyProvider({required SettingsStore settingsStore, required WalletBase wallet})
: this._settingsStore = settingsStore,
this._wallet = wallet;
final SettingsStore _settingsStore;
final WalletBase _wallet;
class OnRamperBuyProvider extends BuyProvider {
OnRamperBuyProvider(this._settingsStore,
{required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
static const _baseUrl = 'buy.onramper.com';
final SettingsStore _settingsStore;
@override
String get title => 'Onramper';
@override
String get providerDescription => S.current.onramper_option_description;
@override
String get lightIcon => 'assets/images/onramper_light.png';
@override
String get darkIcon => 'assets/images/onramper_dark.png';
String get _apiKey => secrets.onramperApiKey;
String get _normalizeCryptoCurrency {
switch (_wallet.currency) {
switch (wallet.currency) {
case CryptoCurrency.ltc:
return "LTC_LITECOIN";
case CryptoCurrency.xmr:
@ -32,7 +44,7 @@ class OnRamperBuyProvider {
case CryptoCurrency.nano:
return "XNO_NANO";
default:
return _wallet.currency.title;
return wallet.currency.title;
}
}
@ -40,7 +52,7 @@ class OnRamperBuyProvider {
return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
}
Uri requestUrl(BuildContext context) {
Uri requestOnramperUrl(BuildContext context, bool? isBuyAction) {
String primaryColor,
secondaryColor,
primaryTextColor,
@ -50,9 +62,10 @@ class OnRamperBuyProvider {
primaryColor = getColorStr(Theme.of(context).primaryColor);
secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
primaryTextColor = getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
secondaryTextColor =
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
primaryTextColor =
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
secondaryTextColor = getColorStr(
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
containerColor = getColorStr(Theme.of(context).colorScheme.background);
cardColor = getColorStr(Theme.of(context).cardColor);
@ -60,27 +73,30 @@ class OnRamperBuyProvider {
cardColor = getColorStr(Colors.white);
}
final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
final networkName =
wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
return Uri.https(_baseUrl, '', <String, dynamic>{
'apiKey': _apiKey,
'defaultCrypto': _normalizeCryptoCurrency,
'networkWallets': '${networkName}:${_wallet.walletAddresses.address}',
'supportSell': "false",
'sell_defaultCrypto': _normalizeCryptoCurrency,
'networkWallets': '${networkName}:${wallet.walletAddresses.address}',
'supportSwap': "false",
'primaryColor': primaryColor,
'secondaryColor': secondaryColor,
'primaryTextColor': primaryTextColor,
'secondaryTextColor': secondaryTextColor,
'containerColor': containerColor,
'cardColor': cardColor
'cardColor': cardColor,
'mode': isBuyAction == true ? 'buy' : 'sell',
});
}
Future<void> launchProvider(BuildContext context) async {
final uri = requestUrl(context);
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
final uri = requestOnramperUrl(context, isBuyAction);
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]);
Navigator.of(context)
.pushNamed(Routes.webViewPage, arguments: [title, uri]);
} else {
await launchUrl(uri);
}

View file

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@ -10,40 +11,44 @@ import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
class RobinhoodBuyProvider {
RobinhoodBuyProvider({required WalletBase wallet}) : this._wallet = wallet;
final WalletBase _wallet;
class RobinhoodBuyProvider extends BuyProvider {
RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
static const _baseUrl = 'applink.robinhood.com';
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
@override
String get title => 'Robinhood Connect';
@override
String get providerDescription => S.current.robinhood_option_description;
@override
String get lightIcon => 'assets/images/robinhood_light.png';
@override
String get darkIcon => 'assets/images/robinhood_dark.png';
String get _applicationId => secrets.robinhoodApplicationId;
String get _apiSecret => secrets.robinhoodCIdApiSecret;
bool get isAvailable => [
WalletType.bitcoin,
WalletType.bitcoinCash,
WalletType.litecoin,
WalletType.ethereum
].contains(_wallet.type);
String getSignature(String message) {
switch (_wallet.type) {
switch (wallet.type) {
case WalletType.ethereum:
return _wallet.signMessage(message);
return wallet.signMessage(message);
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
return _wallet.signMessage(message, address: _wallet.walletAddresses.address);
return wallet.signMessage(message, address: wallet.walletAddresses.address);
default:
throw Exception("WalletType is not available for Robinhood ${_wallet.type}");
throw Exception("WalletType is not available for Robinhood ${wallet.type}");
}
}
Future<String> getConnectId() async {
final walletAddress = _wallet.walletAddresses.address;
final walletAddress = wallet.walletAddresses.address;
final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10;
final message = "$_apiSecret:${valid_until}";
@ -64,22 +69,22 @@ class RobinhoodBuyProvider {
}
}
Future<Uri> requestUrl() async {
Future<Uri> requestProviderUrl() async {
final connectId = await getConnectId();
final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_");
final networkName = wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_");
return Uri.https(_baseUrl, '/u/connect', <String, dynamic>{
'applicationId': _applicationId,
'connectId': connectId,
'walletAddress': _wallet.walletAddresses.address,
'userIdentifier': _wallet.walletAddresses.address,
'walletAddress': wallet.walletAddresses.address,
'userIdentifier': wallet.walletAddresses.address,
'supportedNetworks': networkName
});
}
Future<void> launchProvider(BuildContext context) async {
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
try {
final uri = await requestUrl();
final uri = await requestProviderUrl();
await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (_) {
await showPopUp<void>(

View file

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:cake_wallet/buy/buy_exception.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:http/http.dart';
import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
@ -12,10 +13,8 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
class WyreBuyProvider extends BuyProvider {
WyreBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
: baseApiUrl = isTestEnvironment
? _baseTestApiUrl
: _baseProductApiUrl,
super(wallet: wallet, isTestEnvironment: isTestEnvironment);
: baseApiUrl = isTestEnvironment ? _baseTestApiUrl : _baseProductApiUrl,
super(wallet: wallet, isTestEnvironment: isTestEnvironment);
static const _baseTestApiUrl = 'https://api.testwyre.com';
static const _baseProductApiUrl = 'https://api.sendwyre.com';
@ -35,26 +34,27 @@ class WyreBuyProvider extends BuyProvider {
String get title => 'Wyre';
@override
BuyProviderDescription get description => BuyProviderDescription.wyre;
String get providerDescription => '';
@override
String get trackUrl => isTestEnvironment
? _trackTestUrl
: _trackProductUrl;
String get lightIcon => 'assets/images/robinhood_light.png';
@override
String get darkIcon => 'assets/images/robinhood_dark.png';
String get trackUrl => isTestEnvironment ? _trackTestUrl : _trackProductUrl;
String baseApiUrl;
@override
Future<String> requestUrl(String amount, String sourceCurrency) async {
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
final url = baseApiUrl + _ordersSuffix + _reserveSuffix +
_timeStampSuffix + timestamp;
final url = baseApiUrl + _ordersSuffix + _reserveSuffix + _timeStampSuffix + timestamp;
final uri = Uri.parse(url);
final body = {
'amount': amount,
'sourceCurrency': sourceCurrency,
'destCurrency': walletTypeToCryptoCurrency(walletType).title,
'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress,
'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
'referrerAccountId': _accountId,
'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest']
};
@ -67,9 +67,7 @@ class WyreBuyProvider extends BuyProvider {
body: json.encode(body));
if (response.statusCode != 200) {
throw BuyException(
description: description,
text: 'Url $url is not found!');
throw BuyException(title: providerDescription, content: 'Url $url is not found!');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -77,14 +75,13 @@ class WyreBuyProvider extends BuyProvider {
return urlFromResponse;
}
@override
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix;
final body = {
'amount': amount,
'sourceCurrency': sourceCurrency,
'destCurrency': walletTypeToCryptoCurrency(walletType).title,
'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress,
'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
'accountId': _accountId,
'country': _countryCode
};
@ -98,9 +95,7 @@ class WyreBuyProvider extends BuyProvider {
body: json.encode(body));
if (response.statusCode != 200) {
throw BuyException(
description: description,
text: 'Quote is not found!');
throw BuyException(title: providerDescription, content: 'Quote is not found!');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
@ -108,58 +103,55 @@ class WyreBuyProvider extends BuyProvider {
final destAmount = responseJSON['destAmount'] as double;
final achAmount = responseJSON['sourceAmountWithoutFees'] as double;
return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount);
return BuyAmount(
sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount);
}
@override
Future<Order> findOrderById(String id) async {
final orderUrl = baseApiUrl + _ordersSuffix + '/$id';
final orderUri = Uri.parse(orderUrl);
final orderResponse = await get(orderUri);
if (orderResponse.statusCode != 200) {
throw BuyException(
description: description,
text: 'Order $id is not found!');
throw BuyException(title: providerDescription, content: 'Order $id is not found!');
}
final orderResponseJSON =
json.decode(orderResponse.body) as Map<String, dynamic>;
final orderResponseJSON = json.decode(orderResponse.body) as Map<String, dynamic>;
final transferId = orderResponseJSON['transferId'] as String;
final from = orderResponseJSON['sourceCurrency'] as String;
final to = orderResponseJSON['destCurrency'] as String;
final status = orderResponseJSON['status'] as String;
final state = TradeState.deserialize(raw: status.toLowerCase());
final createdAtRaw = orderResponseJSON['createdAt'] as int;
final createdAt =
DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal();
final createdAt = DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal();
final transferUrl =
baseApiUrl + _transferSuffix + transferId + _trackSuffix;
final transferUrl = baseApiUrl + _transferSuffix + transferId + _trackSuffix;
final transferUri = Uri.parse(transferUrl);
final transferResponse = await get(transferUri);
if (transferResponse.statusCode != 200) {
throw BuyException(
description: description,
text: 'Transfer $transferId is not found!');
throw BuyException(title: providerDescription, content: 'Transfer $transferId is not found!');
}
final transferResponseJSON =
json.decode(transferResponse.body) as Map<String, dynamic>;
final transferResponseJSON = json.decode(transferResponse.body) as Map<String, dynamic>;
final amount = transferResponseJSON['destAmount'] as double;
return Order(
id: id,
provider: description,
provider: BuyProviderDescription.wyre,
transferId: transferId,
from: from,
to: to,
state: state,
createdAt: createdAt,
amount: amount.toString(),
receiveAddress: walletAddress,
walletId: walletId
);
receiveAddress: wallet.walletAddresses.address,
walletId: wallet.id);
}
}
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) {
// TODO: implement launchProvider
throw UnimplementedError();
}
}

View file

@ -138,7 +138,7 @@ class EvmChainServiceImpl implements ChainService {
try {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
.getKeysForChain(appStore.wallet!);
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@ -177,7 +177,7 @@ class EvmChainServiceImpl implements ChainService {
try {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
.getKeysForChain(appStore.wallet!);
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@ -215,7 +215,7 @@ class EvmChainServiceImpl implements ChainService {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
.getKeysForChain(appStore.wallet!);
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@ -275,7 +275,7 @@ class EvmChainServiceImpl implements ChainService {
}
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
.getKeysForChain(appStore.wallet!);
return EthSigUtil.signTypedData(
privateKey: keys[0].privateKey,

View file

@ -1,48 +1,22 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletConnectKeyService {
/// Returns a list of all the keys.
List<ChainKeyModel> getKeys();
/// Returns a list of all the chain ids.
List<String> getChains();
List<ChainKeyModel> getKeys(WalletBase wallet);
/// Returns a list of all the keys for a given chain id.
/// If the chain is not found, returns an empty list.
/// - [chain]: The chain to get the keys for.
List<ChainKeyModel> getKeysForChain(String chain);
List<ChainKeyModel> getKeysForChain(WalletBase wallet);
/// Returns a list of all the accounts in namespace:chainId:address format.
List<String> getAllAccounts();
}
class KeyServiceImpl implements WalletConnectKeyService {
KeyServiceImpl(this.wallet)
: _keys = [
ChainKeyModel(
chains: [
'eip155:1',
'eip155:5',
'eip155:137',
'eip155:42161',
'eip155:80001',
],
privateKey: _getPrivateKeyForWallet(wallet),
publicKey: _getPublicKeyForWallet(wallet),
),
];
late final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
late final List<ChainKeyModel> _keys;
static String _getPrivateKeyForWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.ethereum:
@ -64,31 +38,31 @@ class KeyServiceImpl implements WalletConnectKeyService {
return '';
}
}
@override
List<String> getChains() {
final List<String> chainIds = [];
for (final ChainKeyModel key in _keys) {
chainIds.addAll(key.chains);
}
return chainIds;
List<ChainKeyModel> getKeys(WalletBase wallet) {
final keys = [
ChainKeyModel(
chains: [
'eip155:1',
'eip155:5',
'eip155:137',
'eip155:42161',
'eip155:80001',
],
privateKey: _getPrivateKeyForWallet(wallet),
publicKey: _getPublicKeyForWallet(wallet),
),
];
return keys;
}
@override
List<ChainKeyModel> getKeys() => _keys;
List<ChainKeyModel> getKeysForChain(WalletBase wallet) {
final chain = getChainNameSpaceAndIdBasedOnWalletType(wallet.type);
@override
List<ChainKeyModel> getKeysForChain(String chain) {
return _keys.where((e) => e.chains.contains(chain)).toList();
}
final keys = getKeys(wallet);
@override
List<String> getAllAccounts() {
final List<String> accounts = [];
for (final ChainKeyModel key in _keys) {
for (final String chain in key.chains) {
accounts.add('$chain:${key.publicKey}');
}
}
return accounts;
return keys.where((e) => e.chains.contains(chain)).toList();
}
}

View file

@ -68,7 +68,7 @@ abstract class Web3WalletServiceBase with Store {
);
// Setup our accounts
List<ChainKeyModel> chainKeys = walletKeyService.getKeys();
List<ChainKeyModel> chainKeys = walletKeyService.getKeys(appStore.wallet!);
for (final chainKey in chainKeys) {
for (final chainId in chainKey.chains) {
_web3Wallet.registerAccount(
@ -136,6 +136,7 @@ abstract class Web3WalletServiceBase with Store {
_web3Wallet.onAuthRequest.unsubscribe(_onAuthRequest);
_web3Wallet.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete);
_web3Wallet.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete);
isInitialized = false;
}
Web3Wallet getWeb3Wallet() {
@ -236,7 +237,7 @@ abstract class Web3WalletServiceBase with Store {
Future<void> _onAuthRequest(AuthRequest? args) async {
if (args != null) {
final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(chaindIdNamespace);
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
// Create the message to be signed
final String iss = 'did:pkh:$chaindIdNamespace:${chainKeys.first.publicKey}';
final Widget modalWidget = Web3RequestModal(

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
@ -58,6 +59,7 @@ import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
@ -354,14 +356,18 @@ Future<void> setup({
settingsStore: getIt.get<SettingsStore>(),
walletInfoSource: _walletInfoSource));
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
getIt.registerFactory<WalletLoadingService>(() => WalletLoadingService(
getIt.get<SharedPreferences>(),
getIt.get<KeyService>(),
(WalletType type) => getIt.get<WalletService>(param1: type)));
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) => WalletNewVM(
getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type));
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) =>
WalletNewVM(getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),type: type));
getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) {
return WalletRestorationFromQRVM(getIt.get<AppStore>(),
@ -487,7 +493,7 @@ Future<void> setup({
final appStore = getIt.get<AppStore>();
getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl(appStore.wallet!));
getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl());
getIt.registerLazySingleton<Web3WalletService>(() {
final Web3WalletService web3WalletService = Web3WalletService(
@ -532,6 +538,8 @@ Future<void> setup({
getIt.registerFactory<TransactionsPage>(
() => TransactionsPage(dashboardViewModel: getIt.get<DashboardViewModel>()));
getIt.registerFactory<Setup2FAInfoPage>(() => Setup2FAInfoPage());
getIt.registerFactory<Setup2FAPage>(
() => Setup2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
@ -809,8 +817,12 @@ Future<void> setup({
getIt
.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory<MoonPaySellProvider>(() => MoonPaySellProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!,
));
@ -924,9 +936,9 @@ Future<void> setup({
getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), bool?>(
(param1, isCreate) => NewWalletTypePage(onTypeSelected: param1, isCreate: isCreate ?? true));
getIt.registerFactoryParam<PreSeedPage, WalletType, AdvancedPrivacySettingsViewModel>(
(WalletType type, AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel) =>
PreSeedPage(type, advancedPrivacySettingsViewModel, getIt.get<SeedTypeViewModel>()));
getIt.registerFactoryParam<PreSeedPage, int, void>(
(seedPhraseLength, _)
=> PreSeedPage(seedPhraseLength));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
TradeDetailsViewModel(
@ -959,7 +971,8 @@ Future<void> setup({
getIt.registerFactory(() => BuyAmountViewModel());
getIt.registerFactory(() => BuyOptionsPage(getIt.get<DashboardViewModel>()));
getIt.registerFactoryParam<BuySellOptionsPage, bool, void>(
(isBuyOption, _) => BuySellOptionsPage(getIt.get<DashboardViewModel>(), isBuyOption));
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
@ -1172,9 +1185,6 @@ Future<void> setup({
IoniaPaymentStatusPage(
getIt.get<IoniaPaymentStatusViewModel>(param1: paymentInfo, param2: committedInfo)));
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>((args, _) {
final currentWalletName =
getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName) ?? '';

View file

@ -1,57 +0,0 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/wallet_type.dart';
enum BuyProviderType {
AskEachTime,
Robinhood,
Onramper,
DFX;
@override
String toString() {
switch (this) {
case BuyProviderType.AskEachTime:
return S.current.ask_each_time;
case BuyProviderType.Robinhood:
return "Robinhood";
case BuyProviderType.Onramper:
return "Onramper";
case BuyProviderType.DFX:
return "DFX";
}
}
static List<BuyProviderType> getAvailableProviders(WalletType walletType) {
switch (walletType) {
case WalletType.nano:
case WalletType.banano:
return [
BuyProviderType.AskEachTime,
BuyProviderType.Onramper
];
case WalletType.monero:
return [
BuyProviderType.AskEachTime,
BuyProviderType.Onramper,
BuyProviderType.DFX
];
case WalletType.bitcoin:
case WalletType.ethereum:
return [
BuyProviderType.AskEachTime,
BuyProviderType.Onramper,
BuyProviderType.DFX,
BuyProviderType.Robinhood
];
case WalletType.litecoin:
case WalletType.bitcoinCash:
return [
BuyProviderType.AskEachTime,
BuyProviderType.Onramper,
BuyProviderType.Robinhood
];
default:
return [];
}
}
}

View file

@ -10,7 +10,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
static List<FiatCurrency> get all => _all.values.toList();
static List<FiatCurrency> get currenciesAvailableToBuyWith =>
[aud, bgn, brl, cad, chf, clp, cop, czk, dkk, egp, eur, gbp, gtq, hkd, hrk, huf, idr, ils, inr, isk, jpy, krw, mad, mxn, myr, ngn, nok, nzd, php, pkr, pln, ron, sek, sgd, thb, twd, usd, vnd, zar];
[aud, bgn, brl, cad, chf, clp, cop, czk, dkk, egp, eur, gbp, gtq, hkd, hrk, huf, idr, ils, inr, isk, jpy, krw, mad, mxn, myr, ngn, nok, nzd, php, pkr, pln, ron, sek, sgd, thb, twd, usd, vnd, zar, tur];
static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso");
static const aud = FiatCurrency(symbol: 'AUD', countryCode: "aus", fullName: "Australian Dollar");
@ -60,6 +60,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
static const vef = FiatCurrency(symbol: 'VEF', countryCode: "ven", fullName: "Venezuelan Bolivar Bolívar");
static const vnd = FiatCurrency(symbol: 'VND', countryCode: "vnm", fullName: "Vietnamese Dong đồng");
static const zar = FiatCurrency(symbol: 'ZAR', countryCode: "saf", fullName: "South African Rand");
static const tur = FiatCurrency(symbol: 'TRY', countryCode: "tur", fullName: "Turkish Lira");
static final _all = {
FiatCurrency.ars.raw: FiatCurrency.ars,
@ -109,7 +110,8 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
FiatCurrency.usd.raw: FiatCurrency.usd,
FiatCurrency.vef.raw: FiatCurrency.vef,
FiatCurrency.vnd.raw: FiatCurrency.vnd,
FiatCurrency.zar.raw: FiatCurrency.zar
FiatCurrency.zar.raw: FiatCurrency.zar,
FiatCurrency.tur.raw: FiatCurrency.tur,
};
static FiatCurrency deserialize({required String raw}) => _all[raw]!;

View file

@ -25,7 +25,7 @@ Future<void> loadCurrentWallet({String? password}) async {
type,
name,
password: password);
appStore.changeCurrentWallet(wallet);
await appStore.changeCurrentWallet(wallet);
getIt.get<BackgroundTasks>().registerSyncTask();
}

View file

@ -1,18 +1,9 @@
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/device_info.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/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class MainActions {
final String Function(BuildContext context) name;
@ -46,53 +37,20 @@ class MainActions {
canShow: (viewModel) => viewModel.hasBuyAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async {
if (!viewModel.isEnabledBuyAction) {
await _showErrorDialog(context, S.of(context).unsupported_asset);
return;
}
final defaultBuyProvider = viewModel.defaultBuyProvider;
try {
await _launchProviderByType(context, defaultBuyProvider);
defaultBuyProvider != null
? await defaultBuyProvider.launchProvider(context, true)
: await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: true);
} catch (e) {
await _showErrorDialog(context, e.toString());
await _showErrorDialog(context, defaultBuyProvider.toString(), e.toString());
}
},
);
static Future<void> _launchProviderByType(BuildContext context, BuyProviderType providerType) async {
switch (providerType) {
case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy);
break;
case BuyProviderType.Onramper:
await getIt.get<OnRamperBuyProvider>().launchProvider(context);
break;
case BuyProviderType.Robinhood:
await getIt.get<RobinhoodBuyProvider>().launchProvider(context);
break;
case BuyProviderType.DFX:
await getIt.get<DFXBuyProvider>().launchProvider(context);
break;
default:
throw UnsupportedError('Unsupported buy provider type');
}
}
static Future<void> _showErrorDialog(BuildContext context, String errorMessage) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).buy,
alertContent: errorMessage,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
static MainActions receiveAction = MainActions._(
name: (context) => S.of(context).receive,
image: 'assets/images/received.png',
@ -127,42 +85,33 @@ class MainActions {
isEnabled: (viewModel) => viewModel.isEnabledSellAction,
canShow: (viewModel) => viewModel.hasSellAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async {
final walletType = viewModel.type;
if (!viewModel.isEnabledSellAction) {
return;
}
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.bitcoinCash:
if (viewModel.isEnabledSellAction) {
final moonPaySellProvider = MoonPaySellProvider();
final uri = await moonPaySellProvider.requestUrl(
currency: viewModel.wallet.currency,
refundWalletAddress: viewModel.wallet.walletAddresses.address,
settingsStore: viewModel.settingsStore,
);
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage,
arguments: [S.of(context).sell, uri]);
} else {
await launchUrl(uri);
}
}
break;
default:
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).sell,
alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
},
);
final defaultSellProvider = viewModel.defaultSellProvider;
try {
defaultSellProvider != null
? await defaultSellProvider.launchProvider(context, false)
: await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false);
} catch (e) {
await _showErrorDialog(context, defaultSellProvider.toString(), e.toString());
}
},
);
}
static Future<void> _showErrorDialog(
BuildContext context, String title, String errorMessage) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: title,
alertContent: errorMessage,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
}

View file

@ -0,0 +1,108 @@
import 'package:cake_wallet/buy/buy_provider.dart';
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/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cw_core/wallet_type.dart';
enum ProviderType {
askEachTime,
robinhood,
dfx,
onramper,
moonpaySell,
}
extension ProviderTypeName on ProviderType {
String get title {
switch (this) {
case ProviderType.askEachTime:
return 'Ask each time';
case ProviderType.robinhood:
return 'Robinhood Connect';
case ProviderType.dfx:
return 'DFX Connect';
case ProviderType.onramper:
return 'Onramper';
case ProviderType.moonpaySell:
return 'MoonPay';
}
}
String get id {
switch (this) {
case ProviderType.askEachTime:
return 'ask_each_time_provider';
case ProviderType.robinhood:
return 'robinhood_connect_provider';
case ProviderType.dfx:
return 'dfx_connect_provider';
case ProviderType.onramper:
return 'onramper_provider';
case ProviderType.moonpaySell:
return 'moonpay_provider';
}
}
}
class ProvidersHelper {
static List<ProviderType> getAvailableBuyProviderTypes(WalletType walletType) {
switch (walletType) {
case WalletType.nano:
case WalletType.banano:
return [ProviderType.askEachTime, ProviderType.onramper];
case WalletType.monero:
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.dfx];
case WalletType.bitcoin:
case WalletType.ethereum:
return [
ProviderType.askEachTime,
ProviderType.onramper,
ProviderType.dfx,
ProviderType.robinhood,
];
case WalletType.litecoin:
case WalletType.bitcoinCash:
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
case WalletType.none:
case WalletType.haven:
case WalletType.polygon:
return [];
}
}
static List<ProviderType> getAvailableSellProviderTypes(WalletType walletType) {
switch (walletType) {
case WalletType.bitcoin:
case WalletType.ethereum:
return [ProviderType.askEachTime, ProviderType.onramper,
ProviderType.moonpaySell, ProviderType.dfx];
case WalletType.litecoin:
case WalletType.bitcoinCash:
return [ProviderType.askEachTime, ProviderType.moonpaySell];
case WalletType.monero:
case WalletType.nano:
case WalletType.banano:
case WalletType.none:
case WalletType.haven:
case WalletType.polygon:
return [];
}
}
static BuyProvider? getProviderByType(ProviderType type) {
switch (type) {
case ProviderType.robinhood:
return getIt.get<RobinhoodBuyProvider>();
case ProviderType.dfx:
return getIt.get<DFXBuyProvider>();
case ProviderType.onramper:
return getIt.get<OnRamperBuyProvider>();
case ProviderType.askEachTime:
return null;
case ProviderType.moonpaySell:
return getIt.get<MoonPaySellProvider>();
}
}
}

View file

@ -265,6 +265,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
}
String _normalizeCurrency(CryptoCurrency currency) {
if (currency.title == "USDC" && currency.tag == "POLY") {
throw "Only Bridged USDC (USDC.e) is allowed in ChangeNow";
}
switch (currency) {
case CryptoCurrency.zec:
return 'zec';

View file

@ -173,7 +173,7 @@ class CWNano extends Nano {
}
@override
Future<void> updateTransactions(Object wallet) async {
Future<bool> updateTransactions(Object wallet) async {
return (wallet as NanoWallet).updateTransactions();
}
@ -189,116 +189,10 @@ class CWNano extends Nano {
}
class CWNanoUtil extends NanoUtil {
// standard:
@override
String seedToPrivate(String seed, int index) {
return ND.NanoKeys.seedToPrivate(seed, index);
}
@override
String seedToAddress(String seed, int index) {
return ND.NanoAccounts.createAccount(
ND.NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
}
@override
String seedToMnemonic(String seed) {
return NanoMnemomics.seedToMnemonic(seed).join(" ");
}
@override
Future<String> mnemonicToSeed(String mnemonic) async {
return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
}
@override
String privateKeyToPublic(String privateKey) {
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
return ND.NanoKeys.createPublicKey(privateKey);
}
@override
String addressToPublicKey(String publicAddress) {
return ND.NanoAccounts.extractPublicKey(publicAddress);
}
// universal:
@override
String privateKeyToAddress(String privateKey) {
return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, privateKeyToPublic(privateKey));
}
@override
String publicKeyToAddress(String publicKey) {
return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, publicKey);
}
// standard + hd:
@override
bool isValidSeed(String seed) {
// Ensure seed is 64 or 128 characters long
if (seed.length != 64 && seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return ND.NanoHelpers.isHexString(seed);
}
// hd:
@override
Future<String> hdMnemonicListToSeed(List<String> words) async {
// if (words.length != 24) {
// throw Exception('Expected a 24-word list, got a ${words.length} list');
// }
final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
final String seed = await hasher.sha512(words.join(' '), salt);
return seed;
}
@override
Future<String> hdSeedToPrivate(String seed, int index) async {
List<int> seedBytes = hex.decode(seed);
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
return hex.encode(data.key);
}
@override
Future<String> hdSeedToAddress(String seed, int index) async {
return ND.NanoAccounts.createAccount(
ND.NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
}
@override
Future<String> uniSeedToAddress(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToAddress(seed, index));
} else if (type == "hd") {
return hdSeedToAddress(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
@override
Future<String> uniSeedToPrivate(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToPrivate(seed, index));
} else if (type == "hd") {
return hdSeedToPrivate(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
@override
bool isValidBip39Seed(String seed) {
// Ensure seed is 128 characters long
if (seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return ND.NanoHelpers.isHexString(seed);
return NanoDerivations.isValidBip39Seed(seed);
}
// number util:
@ -309,92 +203,20 @@ class CWNanoUtil extends NanoUtil {
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
BigInt rawPerXMR = BigInt.parse("1000000000000");
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
// static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
/// Convert raw to ban and return as BigDecimal
///
/// @param raw 100000000000000000000000000000
/// @return Decimal value 1.000000000000000000000000000000
///
Decimal _getRawAsDecimal(String? raw, BigInt? rawPerCur) {
rawPerCur ??= rawPerNano;
final Decimal amount = Decimal.parse(raw.toString());
final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
return result;
}
@override
String getRawAsDecimalString(String? raw, BigInt? rawPerCur) {
final Decimal result = _getRawAsDecimal(raw, rawPerCur);
return result.toString();
}
@override
String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
Decimal bigger = input.shift(digits);
bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
bigger = bigger.shift(-digits);
return bigger.toString();
}
/// Return raw as a NANO amount.
///
/// @param raw 100000000000000000000000000000
/// @returns 1
///
@override
String getRawAsUsableString(String? raw, BigInt rawPerCur) {
final String res =
truncateDecimal(_getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
return "0";
}
if (!res.contains(".")) {
return res;
}
final String numAmount = res.split(".")[0];
String decAmount = res.split(".")[1];
// truncate:
if (decAmount.length > maxDecimalDigits) {
decAmount = decAmount.substring(0, maxDecimalDigits);
// remove trailing zeros:
decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
if (decAmount.isEmpty) {
return numAmount;
}
}
return "$numAmount.$decAmount";
return NanoAmounts.getRawAsUsableString(raw, rawPerCur);
}
@override
String getRawAccuracy(String? raw, BigInt rawPerCur) {
final String rawString = getRawAsUsableString(raw, rawPerCur);
final String rawDecimalString = _getRawAsDecimal(raw, rawPerCur).toString();
if (raw == null || raw.isEmpty || raw == "0") {
return "";
}
if (rawString != rawDecimalString) {
return "~";
}
return "";
return NanoAmounts.getRawAccuracy(raw, rawPerCur);
}
/// Return readable string amount as raw string
/// @param amount 1.01
/// @returns 101000000000000000000000000000
///
@override
String getAmountAsRaw(String amount, BigInt rawPerCur) {
final Decimal asDecimal = Decimal.parse(amount);
final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
return (asDecimal * rawDecimal).toString();
return NanoAmounts.getAmountAsRaw(amount, rawPerCur);
}
@override
@ -411,29 +233,29 @@ class CWNanoUtil extends NanoUtil {
if (seedKey != null) {
if (seedKey.length == 64) {
try {
mnemonic = nanoUtil!.seedToMnemonic(seedKey);
mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
} catch (e) {
print("not a valid 'nano' seed key");
}
}
if (derivationType == DerivationType.bip39) {
publicAddress = await hdSeedToAddress(seedKey, 0);
publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
} else if (derivationType == DerivationType.nano) {
publicAddress = await seedToAddress(seedKey, 0);
publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
}
}
if (derivationType == DerivationType.bip39) {
if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
publicAddress = await hdSeedToAddress(seedKey, 0);
seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
}
}
if (derivationType == DerivationType.nano) {
if (mnemonic != null) {
seedKey = await mnemonicToSeed(mnemonic);
publicAddress = await seedToAddress(seedKey, 0);
seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
}
}
@ -461,7 +283,7 @@ class CWNanoUtil extends NanoUtil {
return [DerivationType.bip39];
} else if (seedKey?.length == 64) {
try {
mnemonic = nanoUtil!.seedToMnemonic(seedKey!);
mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey!);
} catch (e) {
print("not a valid 'nano' seed key");
}
@ -475,19 +297,19 @@ class CWNanoUtil extends NanoUtil {
nanoClient.connect(node);
if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
seedKey = await mnemonicToSeed(mnemonic);
publicAddressStandard = await seedToAddress(seedKey, 0);
seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
} else if (seedKey != null) {
try {
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
} catch (e) {
return [DerivationType.nano];
}
try {
publicAddressStandard = await seedToAddress(seedKey, 0);
publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
} catch (e) {
return [DerivationType.bip39];
}

View file

@ -47,6 +47,7 @@ import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
@ -433,8 +434,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
return MaterialPageRoute<void>(
builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
case Routes.buy:
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>());
case Routes.buySellPage:
final args = settings.arguments as bool;
return MaterialPageRoute<void>(
builder: (_) => getIt.get<BuySellOptionsPage>(param1: args));
case Routes.buyWebView:
final args = settings.arguments as List;
@ -455,12 +458,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.faq:
return MaterialPageRoute<void>(builder: (_) => getIt.get<FaqPage>());
case Routes.preSeed:
case Routes.preSeedPage:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<PreSeedPage>(
param1: settings.arguments as WalletType,
param2: getIt.get<AdvancedPrivacySettingsViewModel>(
param1: settings.arguments as WalletType)));
param1: settings.arguments as int));
case Routes.backup:
return CupertinoPageRoute<void>(
@ -625,6 +626,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.modify2FAPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<Modify2FAPage>());
case Routes.setup2faInfoPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAInfoPage>());
case Routes.homeSettings:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<HomeSettingsPage>(param1: settings.arguments),

View file

@ -47,7 +47,7 @@ class Routes {
static const exchangeTemplate = '/exchange_template';
static const restoreWalletType = '/restore_wallet_type';
static const restoreWallet = '/restore_wallet';
static const preSeed = '/pre_seed';
static const preSeedPage = '/pre_seed_page';
static const backup = '/backup';
static const editBackupPassword = '/edit_backup_passowrd';
static const restoreFromBackup = '/restore_from_backup';
@ -55,7 +55,7 @@ class Routes {
static const supportLiveChat = '/support/live_chat';
static const supportOtherLinks = '/support/other';
static const orderDetails = '/order_details';
static const buy = '/buy';
static const buySellPage = '/buy_sell_page';
static const buyWebView = '/buy_web_view';
static const unspentCoinsList = '/unspent_coins_list';
static const unspentCoinsDetails = '/unspent_coins_details';
@ -98,6 +98,7 @@ class Routes {
static const setup_2faQRPage = '/setup_2fa_qr_page';
static const totpAuthCodePage = '/totp_auth_code_page';
static const modify2FAPage = '/modify_2fa_page';
static const setup2faInfoPage = '/setup_2fa_info_page';
static const homeSettings = '/home_settings';
static const editToken = '/edit_token';
static const manageNodes = '/manage_nodes';

View file

@ -0,0 +1,83 @@
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart';
abstract class InfoPage extends BasePage {
InfoPage({
this.imageLightPath = 'assets/images/pre_seed_light.png',
this.imageDarkPath = 'assets/images/pre_seed_dark.png',
});
final String imageLightPath;
final String imageDarkPath;
Image get imageLight => Image.asset(imageLightPath);
Image get imageDark => Image.asset(imageDarkPath);
bool get onWillPop => true;
String get pageTitle;
String get pageDescription;
String get buttonText;
void Function(BuildContext) get onPressed;
@override
Widget? leading(BuildContext context) => null;
@override
String get title => pageTitle;
@override
Widget body(BuildContext context) {
final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight;
return WillPopScope(
onWillPop: () async => onWillPop,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(24),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.3),
child: AspectRatio(aspectRatio: 1, child: image),
),
),
Expanded(
child: Padding(
padding: EdgeInsets.all(10),
child: Text(
pageDescription,
textAlign: TextAlign.center,
style: TextStyle(
height: 1.7,
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
),
),
),
),
PrimaryButton(
onPressed: () => onPressed(context),
text: buttonText,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
],
),
),
),
);
}
}

View file

@ -1,8 +1,3 @@
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/option_tile.dart';
@ -11,38 +6,24 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
class BuyOptionsPage extends BasePage {
BuyOptionsPage(this.dashboardViewModel);
class BuySellOptionsPage extends BasePage {
BuySellOptionsPage(this.dashboardViewModel, this.isBuyAction);
final DashboardViewModel dashboardViewModel;
final iconDarkRobinhood = 'assets/images/robinhood_dark.png';
final iconLightRobinhood = 'assets/images/robinhood_light.png';
final iconDarkOnramper = 'assets/images/onramper_dark.png';
final iconLightOnramper = 'assets/images/onramper_light.png';
final iconDarkDFX = 'assets/images/dfx_dark.png';
final iconLightDFX = 'assets/images/dfx_light.png';
final bool isBuyAction;
@override
String get title => S.current.buy;
String get title => isBuyAction ? S.current.buy : S.current.sell;
@override
AppBarStyle get appBarStyle => AppBarStyle.regular;
@override
Widget body(BuildContext context) {
final isLightMode =
Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
final iconRobinhood = Image.asset(
isLightMode ? iconLightRobinhood : iconDarkRobinhood,
height: 40,
width: 40);
final iconOnramper = Image.asset(
isLightMode ? iconLightOnramper : iconDarkOnramper,
height: 40,
width: 40);
final iconDFX = Image.asset(isLightMode ? iconLightDFX : iconDarkDFX,
height: 40, width: 40);
final availableProviders = dashboardViewModel.availableProviders;
final isLightMode = Theme.of(context).extension<OptionTileTheme>()?.useDarkImage ?? false;
final availableProviders = isBuyAction
? dashboardViewModel.availableBuyProviders
: dashboardViewModel.availableSellProviders;
return Container(
child: Center(
@ -50,54 +31,35 @@ class BuyOptionsPage extends BasePage {
constraints: BoxConstraints(maxWidth: 330),
child: Column(
children: [
if (availableProviders.contains(BuyProviderType.Onramper))
Padding(
...availableProviders.map((provider) {
final icon = Image.asset(
isLightMode ? provider.lightIcon : provider.darkIcon,
height: 40,
width: 40,
);
return Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconOnramper,
title: "Onramper",
description: S.of(context).onramper_option_description,
onPressed: () async => await getIt
.get<OnRamperBuyProvider>()
.launchProvider(context),
image: icon,
title: provider.toString(),
description: provider.providerDescription,
onPressed: () => provider.launchProvider(context, isBuyAction),
),
),
if (availableProviders.contains(BuyProviderType.Robinhood))
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconRobinhood,
title: "Robinhood Connect",
description: S.of(context).robinhood_option_description,
onPressed: () async => await getIt
.get<RobinhoodBuyProvider>()
.launchProvider(context),
),
),
if (availableProviders.contains(BuyProviderType.DFX))
Padding(
padding: EdgeInsets.only(top: 24),
child: OptionTile(
image: iconDFX,
title: "DFX Connect",
description: S.of(context).dfx_option_description,
onPressed: () async => await getIt
.get<DFXBuyProvider>()
.launchProvider(context),
),
),
);
}).toList(),
Spacer(),
Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text(
S.of(context).select_buy_provider_notice,
isBuyAction
? S.of(context).select_buy_provider_notice
: S.of(context).select_sell_provider_notice,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context)
.extension<TransactionTradeTheme>()!
.detailsTitlesColor,
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
),
),
),

View file

@ -1,10 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/store/dashboard/orders_store.dart';
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
@ -72,10 +69,10 @@ class BuyWebViewPageBodyState extends State<BuyWebViewPageBody> {
Widget build(BuildContext context) {
return InAppWebView(
key: _webViewkey,
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(transparentBackground: true),
initialSettings: InAppWebViewSettings(
transparentBackground: true,
),
initialUrlRequest: URLRequest(url: Uri.tryParse(widget.url ?? '')),
initialUrlRequest: URLRequest(url: WebUri(widget.url ?? '')),
onWebViewCreated: (InAppWebViewController controller) =>
setState(() => _webViewController = controller));
}

View file

@ -1,5 +1,3 @@
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
@ -35,21 +33,21 @@ class WebViewPageBodyState extends State<WebViewPageBody> {
@override
Widget build(BuildContext context) {
return InAppWebView(
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(transparentBackground: true),
initialSettings: InAppWebViewSettings(
transparentBackground: true,
),
initialUrlRequest: URLRequest(url: widget.uri),
androidOnPermissionRequest: (_, __, resources) async {
initialUrlRequest: URLRequest(url: WebUri.uri(widget.uri)),
onPermissionRequest: (controller, request) async {
bool permissionGranted = await Permission.camera.status == PermissionStatus.granted;
if (!permissionGranted) {
permissionGranted = await Permission.camera.request().isGranted;
}
return PermissionRequestResponse(
resources: resources,
return PermissionResponse(
resources: request.resources,
action: permissionGranted
? PermissionRequestResponseAction.GRANT
: PermissionRequestResponseAction.DENY,
? PermissionResponseAction.GRANT
: PermissionResponseAction.DENY,
);
},
);

View file

@ -29,11 +29,10 @@ class BuyListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isSelected = selectedProvider?.description == provider.description;
final isSelected = selectedProvider?.providerDescription == provider.providerDescription;
final iconColor = isSelected ? Colors.white : Colors.black;
final providerIcon = getBuyProviderIcon(provider.description,
iconColor: iconColor)!;
final providerIcon = Image.asset('assets/images/wyre-icon.png', width: 36, height: 36);
final backgroundColor = isSelected
? Palette.greyBlueCraiola
@ -76,7 +75,7 @@ class BuyListItem extends StatelessWidget {
padding: EdgeInsets.only(right: 10),
child: providerIcon),
Text(
provider.description.title,
provider.title,
style: TextStyle(
color: secondaryTextColor,
fontSize: 20,

View file

@ -126,8 +126,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(false),
actionRightButton: () => Navigator.of(context).pop(true));
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
actionRightButton: () => Navigator.of(dialogContext).pop(true));
}) ??
false;

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
@ -14,6 +16,7 @@ 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:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -200,7 +203,7 @@ class CryptoBalanceWidget extends StatelessWidget {
additionalFiatBalance: balance.fiatAdditionalBalance,
frozenBalance: balance.frozenBalance,
frozenFiatBalance: balance.fiatFrozenBalance,
currency: balance.formattedAssetTitle,
currency: balance.asset,
hasAdditionalBalance:
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
);
@ -216,7 +219,7 @@ class CryptoBalanceWidget extends StatelessWidget {
}
class BalanceRowWidget extends StatelessWidget {
const BalanceRowWidget({
BalanceRowWidget({
required this.availableBalanceLabel,
required this.availableBalance,
required this.availableFiatBalance,
@ -238,7 +241,7 @@ class BalanceRowWidget extends StatelessWidget {
final String additionalFiatBalance;
final String frozenBalance;
final String frozenFiatBalance;
final String currency;
final CryptoCurrency currency;
final bool hasAdditionalBalance;
// void _showBalanceDescription(BuildContext context) {
@ -262,13 +265,13 @@ class BalanceRowWidget extends StatelessWidget {
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
child: Container(
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
@ -325,13 +328,48 @@ class BalanceRowWidget extends StatelessWidget {
],
),
),
Text(currency,
style: TextStyle(
fontSize: 28,
fontFamily: 'Lato',
fontWeight: FontWeight.w800,
color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
height: 1)),
SizedBox(
width: min(MediaQuery.of(context).size.width * 0.2, 100),
child: Center(
child: Column(
children: [
currency.iconPath != null
? Container(
child: Image.asset(
currency.iconPath!,
height: 40.0,
width: 40.0,
),
)
: 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)
@ -374,7 +412,9 @@ class BalanceRowWidget extends StatelessWidget {
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
height: 1,
),
maxLines: 1,
@ -388,7 +428,7 @@ class BalanceRowWidget extends StatelessWidget {
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1,
),
),

View file

@ -49,7 +49,7 @@ class TransactionsPage extends StatelessWidget {
onTap: () => Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [
'',
Uri.parse(
'https://guides.cakewallet.com/docs/bugs-service-status/why_are_my_funds_not_appearing/')
'https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/')
]),
title: S.of(context).syncing_wallet_alert_title,
subTitle: S.of(context).syncing_wallet_alert_content,

View file

@ -261,7 +261,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
fee: S.of(popupContext).send_fee,
feeValue: widget.exchangeTradeViewModel.sendViewModel
.pendingTransaction!.feeFormatted,
rightButtonText: S.of(popupContext).ok,
rightButtonText: S.of(popupContext).send,
leftButtonText: S.of(popupContext).cancel,
actionRightButton: () async {
Navigator.of(popupContext).pop();
@ -299,7 +299,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
}
void transactionStatePopup() {
showPopUp<void>(
if (this.mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return Observer(builder: (_) {
@ -344,7 +345,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
onPressed: () {
Navigator.of(popupContext).pop();
RequestReviewHandler.requestReview();
},
},
text: S.of(popupContext).got_it,
color: Theme.of(popupContext).primaryColor,
textColor: Colors.white))
@ -391,5 +392,6 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
);
});
});
}
}
}

View file

@ -78,7 +78,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
_stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async {
if (state is ExecutedSuccessfullyState) {
Navigator.of(navigatorKey.currentContext!)
.pushNamed(Routes.preSeed, arguments: _walletNewVM.type);
.pushNamed(Routes.preSeedPage, arguments: _walletNewVM.seedPhraseWordsLength);
}
if (state is FailureState) {

View file

@ -4,12 +4,16 @@ import 'package:qr_flutter/qr_flutter.dart' as qr;
class QrImage extends StatelessWidget {
QrImage({
required this.data,
this.foregroundColor = Colors.black,
this.backgroundColor = Colors.white,
this.size = 100.0,
this.version,
this.errorCorrectionLevel = qr.QrErrorCorrectLevel.L,
});
final double size;
final Color foregroundColor;
final Color backgroundColor;
final String data;
final int? version;
final int errorCorrectionLevel;
@ -21,8 +25,8 @@ class QrImage extends StatelessWidget {
errorCorrectionLevel: errorCorrectionLevel,
version: version ?? 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ???
size: size,
foregroundColor: Colors.black,
backgroundColor: Colors.white,
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
padding: const EdgeInsets.all(8.0),
);
}

View file

@ -1,97 +1,27 @@
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/screens/InfoPage.dart';
import 'package:flutter/cupertino.dart';
class PreSeedPage extends BasePage {
PreSeedPage(this.type, this.advancedPrivacySettingsViewModel, this.seedTypeViewModel)
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
seedPhraseLength = advancedPrivacySettingsViewModel.seedPhraseLength.value,
moneroSeedType = seedTypeViewModel.moneroSeedType {
wordsCount = _wordsCount(type, seedPhraseLength, moneroSeedType);
}
class PreSeedPage extends InfoPage {
PreSeedPage(this.seedPhraseLength);
final Image imageDark;
final Image imageLight;
final WalletType type;
final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel;
final SeedTypeViewModel seedTypeViewModel;
final int seedPhraseLength;
final SeedType moneroSeedType;
late final int wordsCount;
@override
Widget? leading(BuildContext context) => null;
bool get onWillPop => false;
@override
String? get title => S.current.pre_seed_title;
String get pageTitle => S.current.pre_seed_title;
@override
Widget body(BuildContext context) {
final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight;
String get pageDescription =>
S.current.pre_seed_description(seedPhraseLength.toString());
return WillPopScope(
onWillPop: () async => false,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(24),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.3),
child: AspectRatio(aspectRatio: 1, child: image),
),
Padding(
padding: EdgeInsets.all(10),
child: Text(
S.of(context).pre_seed_description(wordsCount.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor),
),
),
PrimaryButton(
onPressed: () => Navigator.of(context)
.popAndPushNamed(Routes.seed, arguments: true),
text: S.of(context).pre_seed_button_text,
color: Theme.of(context).primaryColor,
textColor: Colors.white)
],
),
),
));
}
@override
String get buttonText => S.current.pre_seed_button_text;
static int _wordsCount(WalletType type, int seedPhraseLength, SeedType moneroSeedType) {
switch (type) {
case WalletType.monero:
if (moneroSeedType == SeedType.polyseed)
return 16;
return 25;
case WalletType.ethereum:
case WalletType.bitcoinCash:
case WalletType.polygon:
return seedPhraseLength;
default:
return 24;
}
}
@override
void Function(BuildContext) get onPressed => (BuildContext context) =>
Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true);
}

View file

@ -423,7 +423,7 @@ class SendPage extends BasePage {
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: sendViewModel.outputs,
rightButtonText: S.of(_dialogContext).ok,
rightButtonText: S.of(_dialogContext).send,
leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () {
Navigator.of(_dialogContext).pop();

View file

@ -43,10 +43,18 @@ class OtherSettingsPage extends BasePage {
if(_otherSettingsViewModel.isEnabledBuyAction)
SettingsPickerCell(
title: S.current.default_buy_provider,
items: _otherSettingsViewModel.availableBuyProviders,
items: _otherSettingsViewModel.availableBuyProvidersTypes,
displayItem: _otherSettingsViewModel.getBuyProviderType,
selectedItem: _otherSettingsViewModel.buyProviderType,
onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected,
onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected
),
if(_otherSettingsViewModel.isEnabledSellAction)
SettingsPickerCell(
title: S.current.default_sell_provider,
items: _otherSettingsViewModel.availableSellProvidersTypes,
displayItem: _otherSettingsViewModel.getSellProviderType,
selectedItem: _otherSettingsViewModel.sellProviderType,
onItemSelected: _otherSettingsViewModel.onSellProviderTypeSelected,
),
SettingsCellWithArrow(
title: S.current.settings_terms_and_conditions,

View file

@ -111,7 +111,7 @@ class SecurityBackupPage extends BasePage {
context,
route: _securitySettingsViewModel.useTotp2FA
? Routes.modify2FAPage
: Routes.setup_2faPage,
: Routes.setup2faInfoPage,
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),

View file

@ -1,13 +1,12 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import '../../widgets/standard_list.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class Setup2FAPage extends BasePage {
Setup2FAPage({required this.setup2FAViewModel});
@ -15,52 +14,64 @@ class Setup2FAPage extends BasePage {
final Setup2FAViewModel setup2FAViewModel;
@override
String get title => S.current.setup_2fa;
String get title => 'Cake 2FA';
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
final cake2FAGuideTitle = 'Cake 2FA Guide';
final cake2FAGuideUri =
Uri.parse('https://guides.cakewallet.com/docs/advanced-features/authentication');
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 2,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
child:
AspectRatio(aspectRatio: 0.6, child: Image.asset('assets/images/setup_2fa_img.png')),
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.current.important_note,
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 14,
height: 1.571,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
SizedBox(height: 16),
Text(
S.current.setup_2fa_text,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 1.571,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
],
child: Text(
S.current.setup_2fa_text,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14,
height: 1.571,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
),
SizedBox(height: 86),
SettingsCellWithArrow(
title: S.current.setup_totp_recommended,
handler: (_) {
setup2FAViewModel.generateSecretKey();
return Navigator.of(context).pushReplacementNamed(Routes.setup_2faQRPage);
},
),
Expanded(
child: Column(
children: [
SettingsCellWithArrow(
title: S.current.setup_totp_recommended,
handler: (_) {
setup2FAViewModel.generateSecretKey();
return Navigator.of(context).pushReplacementNamed(Routes.setup_2faQRPage);
},
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow(
title: cake2FAGuideTitle, handler: (_) => _launchUrl(cake2FAGuideUri)),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
],
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
],
),
),
],
);
}
static void _launchUrl(Uri url) async {
try {
await launchUrl(url, mode: LaunchMode.externalApplication);
} catch (e) {}
}
}

View file

@ -0,0 +1,20 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/InfoPage.dart';
import 'package:flutter/cupertino.dart';
class Setup2FAInfoPage extends InfoPage {
@override
String get pageTitle => S.current.pre_seed_title;
@override
String get pageDescription => S.current.setup_warning_2fa_text;
@override
String get buttonText => S.current.understand;
@override
void Function(BuildContext) get onPressed => (BuildContext context) =>
Navigator.of(context).popAndPushNamed(Routes.setup_2faPage);
}

View file

@ -4,8 +4,8 @@ import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/utils/clipboard_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
@ -13,6 +13,7 @@ import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qr_flutter/qr_flutter.dart' as qr;
import 'package:url_launcher/url_launcher.dart';
class Setup2FAQRPage extends BasePage {
Setup2FAQRPage({required this.setup2FAViewModel});
@ -24,51 +25,63 @@ class Setup2FAQRPage extends BasePage {
@override
Widget body(BuildContext context) {
final copyImage = Image.asset(
'assets/images/copy_content.png',
height: 12,
width: 12,
color: Color(0xFF355688),
);
final copyImage = Image.asset('assets/images/copy_content.png',
height: 16,
width: 16,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
final cake2FAHowToUseUrl = Uri.parse(
'https://guides.cakewallet.com/docs/advanced-features/authentication/#enabling-cake-2fa');
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
children: [
SizedBox(height: 58),
Spacer(),
Text(
S.current.scan_qr_on_device,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
height: 1.5714,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
SizedBox(height: 10),
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.4),
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
),
),
child: Container(
child: QrImage(
data: setup2FAViewModel.totpVersionOneLink,
version: qr.QrVersions.auto,
foregroundColor:
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
backgroundColor: Colors.transparent,
)),
),
),
),
SizedBox(height: 26),
Text(
S.current.add_secret_code,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
height: 1.5714,
color: Palette.darkBlueCraiola,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
SizedBox(height: 10),
AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
),
),
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Colors.white,
),
),
child: QrImage(
data: setup2FAViewModel.totpVersionOneLink,
version: qr.QrVersions.auto,
)),
),
),
SizedBox(height: 13),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
@ -83,7 +96,9 @@ class Setup2FAQRPage extends BasePage {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Palette.darkGray,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
height: 1.8333,
),
),
@ -91,10 +106,10 @@ class Setup2FAQRPage extends BasePage {
Text(
'${setup2FAViewModel.totpSecretKey}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
height: 1.375,
),
fontSize: 18,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@ -107,15 +122,11 @@ class Setup2FAQRPage extends BasePage {
height: 32,
child: InkWell(
onTap: () {
ClipboardUtil.setSensitiveDataToClipboard(
ClipboardData(text: '${setup2FAViewModel.totpSecretKey}'));
ClipboardUtil.setSensitiveDataToClipboard(ClipboardData(
text: '${setup2FAViewModel.totpSecretKey}'));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Color(0xFFF2F0FA),
),
child: copyImage,
),
),
@ -139,7 +150,9 @@ class Setup2FAQRPage extends BasePage {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Palette.darkGray,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
height: 1.8333,
),
),
@ -147,10 +160,10 @@ class Setup2FAQRPage extends BasePage {
Text(
'${setup2FAViewModel.totpVersionOneLink}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
height: 1.375,
),
fontSize: 18,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@ -163,15 +176,11 @@ class Setup2FAQRPage extends BasePage {
height: 32,
child: InkWell(
onTap: () {
ClipboardUtil.setSensitiveDataToClipboard(
ClipboardData(text: '${setup2FAViewModel.totpVersionOneLink}'));
ClipboardUtil.setSensitiveDataToClipboard(ClipboardData(
text: '${setup2FAViewModel.totpVersionOneLink}'));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Color(0xFFF2F0FA),
),
child: copyImage,
),
),
@ -180,13 +189,36 @@ class Setup2FAQRPage extends BasePage {
),
SizedBox(height: 8),
StandardListSeparator(),
Spacer(),
SizedBox(height: 16),
GestureDetector(
onTap: () => _launchUrl(cake2FAHowToUseUrl),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(S.current.how_to_use,
style: TextStyle(
decoration: TextDecoration.underline,
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor)),
Icon(Icons.info_outline,
size: 20,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor)
],
)),
Spacer(flex: 5),
PrimaryButton(
onPressed: () {
Navigator.of(context).pushReplacementNamed(Routes.totpAuthCodePage,
arguments: TotpAuthArgumentsModel(
isForSetup: true,
));
Navigator.of(context)
.pushReplacementNamed(Routes.totpAuthCodePage,
arguments: TotpAuthArgumentsModel(
isForSetup: true,
));
},
text: S.current.continue_text,
color: Theme.of(context).primaryColor,
@ -197,4 +229,10 @@ class Setup2FAQRPage extends BasePage {
),
);
}
static void _launchUrl(Uri url) async {
try {
await launchUrl(url, mode: LaunchMode.externalApplication);
} catch (e) {}
}
}

View file

@ -22,17 +22,17 @@ class ChatwootWidgetState extends State<ChatwootWidget> {
@override
Widget build(BuildContext context) => InAppWebView(
key: _webViewkey,
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(transparentBackground: true),
),
initialUrlRequest: URLRequest(url: Uri.tryParse(widget.supportUrl)),
initialSettings: InAppWebViewSettings(
transparentBackground: true,
),
initialUrlRequest: URLRequest(url: WebUri(widget.supportUrl)),
onWebViewCreated: (InAppWebViewController controller) {
controller.addWebMessageListener(
WebMessageListener(
jsObjectName: 'ReactNativeWebView',
onPostMessage: (String? message, Uri? sourceOrigin, bool isMainFrame,
JavaScriptReplyProxy replyProxy) {
final shortenedMessage = message?.substring(16);
onPostMessage: (WebMessage? message, WebUri? sourceOrigin, bool isMainFrame,
PlatformJavaScriptReplyProxy replyProxy) {
final shortenedMessage = message?.data.toString().substring(16);
if (shortenedMessage != null && isJsonString(shortenedMessage)) {
final parsedMessage = jsonDecode(shortenedMessage);
final eventType = parsedMessage["event"];

View file

@ -351,7 +351,9 @@ class WalletListBodyState extends State<WalletListBody> {
// in desktop platforms the navigation tree is different
if (responsiveLayoutUtil.shouldRenderMobileUI) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
if (this.mounted) {
Navigator.of(context).pop();
}
});
}
} catch (e) {

View file

@ -26,8 +26,7 @@ abstract class AppStoreBase with Store {
AuthenticationStore authenticationStore;
@observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
wallet;
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>? wallet;
WalletListStore walletList;
@ -36,16 +35,16 @@ abstract class AppStoreBase with Store {
NodeListStore nodeListStore;
@action
void changeCurrentWallet(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
TransactionInfo>
wallet) {
Future<void> changeCurrentWallet(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet) async {
this.wallet?.close();
this.wallet = wallet;
this.wallet!.setExceptionHandler(ExceptionHandler.onError);
if (isEVMCompatibleChain(wallet.type)) {
getIt.get<Web3WalletService>().init();
await getIt.get<Web3WalletService>().onDispose();
getIt.get<Web3WalletService>().create();
await getIt.get<Web3WalletService>().init();
}
}
}

View file

@ -2,8 +2,9 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
@ -147,7 +148,9 @@ abstract class SettingsStoreBase with Store {
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
currentSyncMode = initialSyncMode,
currentSyncAll = initialSyncAll,
priority = ObservableMap<WalletType, TransactionPriority>() {
priority = ObservableMap<WalletType, TransactionPriority>(),
defaultBuyProviders = ObservableMap<WalletType, ProviderType>(),
defaultSellProviders = ObservableMap<WalletType, ProviderType>() {
//this.nodes = ObservableMap<WalletType, Node>.of(nodes);
if (initialMoneroTransactionPriority != null) {
@ -181,9 +184,25 @@ abstract class SettingsStoreBase with Store {
initializeTrocadorProviderStates();
WalletType.values.forEach((walletType) {
final key = 'defaultBuyProvider_${walletType.toString()}';
final providerIndex = sharedPreferences.getInt(key);
defaultBuyProviders[walletType] = providerIndex != null ? BuyProviderType.values[providerIndex] : BuyProviderType.AskEachTime;
final key = 'buyProvider_${walletType.toString()}';
final providerId = sharedPreferences.getString(key);
if (providerId != null) {
defaultBuyProviders[walletType] = ProviderType.values
.firstWhere((provider) => provider.id == providerId, orElse: () => ProviderType.askEachTime);
} else {
defaultBuyProviders[walletType] = ProviderType.askEachTime;
}
});
WalletType.values.forEach((walletType) {
final key = 'sellProvider_${walletType.toString()}';
final providerId = sharedPreferences.getString(key);
if (providerId != null) {
defaultSellProviders[walletType] = ProviderType.values
.firstWhere((provider) => provider.id == providerId, orElse: () => ProviderType.askEachTime);
} else {
defaultSellProviders[walletType] = ProviderType.askEachTime;
}
});
reaction(
@ -196,6 +215,20 @@ abstract class SettingsStoreBase with Store {
(bool shouldShowYatPopup) =>
sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
defaultBuyProviders.observe((change) {
final String key = 'buyProvider_${change.key.toString()}';
if (change.newValue != null) {
sharedPreferences.setString(key, change.newValue!.id);
}
});
defaultSellProviders.observe((change) {
final String key = 'sellProvider_${change.key.toString()}';
if (change.newValue != null) {
sharedPreferences.setString(key, change.newValue!.id);
}
});
priority.observe((change) {
final String? key;
switch (change.key) {
@ -251,16 +284,6 @@ abstract class SettingsStoreBase with Store {
(bool disableSell) =>
sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell));
reaction(
(_) => defaultBuyProviders.asObservable(),
(ObservableMap<WalletType, BuyProviderType> providers) {
providers.forEach((walletType, provider) {
final key = 'defaultBuyProvider_${walletType.toString()}';
sharedPreferences.setInt(key, provider.index);
});
}
);
reaction(
(_) => walletListOrder,
(WalletListOrderType walletListOrder) =>
@ -595,7 +618,10 @@ abstract class SettingsStoreBase with Store {
ObservableMap<String, bool> trocadorProviderStates = ObservableMap<String, bool>();
@observable
ObservableMap<WalletType, BuyProviderType> defaultBuyProviders = ObservableMap<WalletType, BuyProviderType>();
ObservableMap<WalletType, ProviderType> defaultBuyProviders;
@observable
ObservableMap<WalletType, ProviderType> defaultSellProviders;
@observable
SortBalanceBy sortBalanceBy;

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/seed_phrase_length.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
@ -37,6 +38,9 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
@computed
SeedPhraseLength get seedPhraseLength => _settingsStore.seedPhraseLength;
@computed
bool get isPolySeed => _settingsStore.moneroSeedType == SeedType.polyseed;
@action
void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode;

View file

@ -1,5 +1,5 @@
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
@ -61,8 +61,7 @@ abstract class BuyViewModelBase with Store {
String _url = '';
try {
_url = await selectedProvider
!.requestUrl(doubleAmount.toString(), fiatCurrency.title);
_url = await selectedProvider!.requestUrl(doubleAmount.toString(), fiatCurrency.title);
} catch (e) {
print(e.toString());
}

View file

@ -125,6 +125,8 @@ abstract class BalanceViewModelBase with Store {
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.nano:
case WalletType.banano:
return S.current.xmr_available_balance;
default:
return S.current.confirmed;
@ -139,6 +141,9 @@ abstract class BalanceViewModelBase with Store {
case WalletType.ethereum:
case WalletType.polygon:
return S.current.xmr_full_balance;
case WalletType.nano:
case WalletType.banano:
return S.current.receivable_balance;
default:
return S.current.unconfirmed;
}

View file

@ -1,9 +1,10 @@
import 'dart:convert';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -41,6 +42,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/util/utils.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/provider_types.dart';
part 'dashboard_view_model.g.dart';
@ -295,13 +297,31 @@ abstract class DashboardViewModelBase with Store {
Map<String, List<FilterItem>> filterItems;
BuyProviderType get defaultBuyProvider =>
settingsStore.defaultBuyProviders[wallet.type] ?? BuyProviderType.AskEachTime;
BuyProvider? get defaultBuyProvider => ProvidersHelper.getProviderByType(
settingsStore.defaultBuyProviders[wallet.type] ?? ProviderType.askEachTime);
BuyProvider? get defaultSellProvider => ProvidersHelper.getProviderByType(
settingsStore.defaultSellProviders[wallet.type] ?? ProviderType.askEachTime);
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
List<BuyProviderType> get availableProviders =>
BuyProviderType.getAvailableProviders(wallet.type);
List<BuyProvider> get availableBuyProviders {
final providerTypes = ProvidersHelper.getAvailableBuyProviderTypes(wallet.type);
return providerTypes
.map((type) => ProvidersHelper.getProviderByType(type))
.where((provider) => provider != null)
.cast<BuyProvider>()
.toList();
}
List<BuyProvider> get availableSellProviders {
final providerTypes = ProvidersHelper.getAvailableSellProviderTypes(wallet.type);
return providerTypes
.map((type) => ProvidersHelper.getProviderByType(type))
.where((provider) => provider != null)
.cast<BuyProvider>()
.toList();
}
bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup;
@ -315,16 +335,15 @@ abstract class DashboardViewModelBase with Store {
bool hasExchangeAction;
@computed
bool get isEnabledBuyAction => !settingsStore.disableBuy && wallet.type != WalletType.haven;
bool get isEnabledBuyAction =>
!settingsStore.disableBuy && availableBuyProviders.isNotEmpty;
@observable
bool hasBuyAction;
@computed
bool get isEnabledSellAction =>
!settingsStore.disableSell &&
wallet.type != WalletType.haven &&
wallet.type != WalletType.monero;
!settingsStore.disableSell && availableSellProviders.isNotEmpty;
@observable
bool hasSellAction;

View file

@ -101,7 +101,7 @@ class TransactionListItem extends ActionListItem with Keyable {
break;
case WalletType.nano:
amount = calculateFiatAmountRaw(
cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString(
cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString(
nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)),
price: price);
break;

View file

@ -9,7 +9,7 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.d
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
part 'order_details_view_model.g.dart';
@ -50,8 +50,10 @@ abstract class OrderDetailsViewModelBase with Store {
@action
Future<void> _updateOrder() async {
try {
if (_provider != null) {
final updatedOrder = await _provider!.findOrderById(order.id);
if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) {
final updatedOrder = _provider is MoonPayBuyProvider
? await (_provider as MoonPayBuyProvider).findOrderById(order.id)
: await (_provider as WyreBuyProvider).findOrderById(order.id);
updatedOrder.from = order.from;
updatedOrder.to = order.to;
updatedOrder.receiveAddress = order.receiveAddress;
@ -87,19 +89,26 @@ abstract class OrderDetailsViewModelBase with Store {
value: order.provider.title)
);
if (_provider?.trackUrl.isNotEmpty ?? false) {
final buildURL = _provider!.trackUrl + '${order.transferId}';
items.add(
TrackTradeListItem(
title: 'Track',
value: buildURL,
onTap: () {
try {
launch(buildURL);
} catch (e) {}
}
)
);
if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) {
final trackUrl = _provider is MoonPayBuyProvider
? (_provider as MoonPayBuyProvider).trackUrl
: (_provider as WyreBuyProvider).trackUrl;
if (trackUrl.isNotEmpty ?? false) {
final buildURL = trackUrl + '${order.transferId}';
items.add(
TrackTradeListItem(
title: 'Track',
value: buildURL,
onTap: () {
try {
launch(buildURL);
} catch (e) {}
}
)
);
}
}
items.add(

View file

@ -1,5 +1,7 @@
import 'dart:io';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/main.dart';
@ -60,11 +62,17 @@ abstract class RestoreFromBackupViewModelBase with Store {
});
state = ExecutedSuccessfullyState();
} catch (e) {
} catch (e, s) {
var msg = e.toString();
if (msg.toLowerCase().contains("message authentication code (mac)")) {
msg = 'Incorrect backup password';
} else {
ExceptionHandler.onError(FlutterErrorDetails(
exception: e,
stack: s,
library: this.toString(),
));
}
state = FailureState(msg);

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/buy_provider_types.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
@ -59,12 +60,24 @@ abstract class OtherSettingsViewModelBase with Store {
bool get isEnabledBuyAction =>
!_settingsStore.disableBuy && _wallet.type != WalletType.haven;
List<BuyProviderType> get availableBuyProviders =>
BuyProviderType.getAvailableProviders(walletType);
@computed
bool get isEnabledSellAction =>
!_settingsStore.disableSell && _wallet.type != WalletType.haven;
BuyProviderType get buyProviderType =>
List<ProviderType> get availableBuyProvidersTypes {
return ProvidersHelper.getAvailableBuyProviderTypes(walletType);
}
List<ProviderType> get availableSellProvidersTypes =>
ProvidersHelper.getAvailableSellProviderTypes(walletType);
ProviderType get buyProviderType =>
_settingsStore.defaultBuyProviders[walletType] ??
BuyProviderType.AskEachTime;
ProviderType.askEachTime;
ProviderType get sellProviderType =>
_settingsStore.defaultSellProviders[walletType] ??
ProviderType.askEachTime;
String getDisplayPriority(dynamic priority) {
final _priority = priority as TransactionPriority;
@ -80,14 +93,28 @@ abstract class OtherSettingsViewModelBase with Store {
}
String getBuyProviderType(dynamic buyProviderType) {
final _buyProviderType = buyProviderType as BuyProviderType;
final _buyProviderType = buyProviderType as ProviderType;
return _buyProviderType == ProviderType.askEachTime
? S.current.ask_each_time
: _buyProviderType.title;
}
return _buyProviderType.toString();
String getSellProviderType(dynamic sellProviderType) {
final _sellProviderType = sellProviderType as ProviderType;
return _sellProviderType == ProviderType.askEachTime
? S.current.ask_each_time
: _sellProviderType.title;
}
void onDisplayPrioritySelected(TransactionPriority priority) =>
_settingsStore.priority[_wallet.type] = priority;
void onBuyProviderTypeSelected(BuyProviderType buyProviderType) =>
@action
ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) =>
_settingsStore.defaultBuyProviders[walletType] = buyProviderType;
@action
ProviderType onSellProviderTypeSelected(
ProviderType sellProviderType) =>
_settingsStore.defaultSellProviders[walletType] = sellProviderType;
}

Some files were not shown because too many files have changed in this diff Show more