Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-447-mobile-scanner

This commit is contained in:
fosse 2023-10-06 09:28:24 -04:00
commit 68405cbd6a
197 changed files with 11727 additions and 1518 deletions

View file

@ -2,11 +2,10 @@ name: PR Test Build
on:
pull_request:
branches: [ main ]
branches: [main]
jobs:
PR_test_build:
runs-on: ubuntu-20.04
env:
STORE_PASS: test@cake_wallet
@ -28,7 +27,7 @@ jobs:
- name: Flutter action
uses: subosito/flutter-action@v1
with:
flutter-version: '3.10.x'
flutter-version: "3.10.x"
channel: stable
- name: Install package dependencies
@ -93,6 +92,7 @@ jobs:
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Add secrets
@ -128,8 +128,10 @@ jobs:
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
- name: Rename app
run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties
@ -139,18 +141,18 @@ jobs:
cd /opt/android/cake_wallet
flutter build apk --release
# - name: Push to App Center
# run: |
# echo 'Installing App Center CLI tools'
# npm install -g appcenter-cli
# echo "Publishing test to App Center"
# appcenter distribute release \
# --group "Testers" \
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
# --release-notes ${GITHUB_HEAD_REF} \
# --app Cake-Labs/Cake-Wallet \
# --token ${{ secrets.APP_CENTER_TOKEN }} \
# --quiet
# - name: Push to App Center
# run: |
# echo 'Installing App Center CLI tools'
# npm install -g appcenter-cli
# echo "Publishing test to App Center"
# appcenter distribute release \
# --group "Testers" \
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
# --release-notes ${GITHUB_HEAD_REF} \
# --app Cake-Labs/Cake-Wallet \
# --token ${{ secrets.APP_CENTER_TOKEN }} \
# --quiet
- name: Rename apk file
run: |
@ -170,6 +172,6 @@ jobs:
token: ${{ secrets.SLACK_APP_TOKEN }}
path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk
channel: ${{ secrets.SLACK_APK_CHANNEL }}
title: '${{github.head_ref}}.apk'
title: "${{github.head_ref}}.apk"
filename: ${{github.head_ref}}.apk
initial_comment: ${{ github.event.head_commit.message }}

1
.gitignore vendored
View file

@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart
lib/monero/monero.dart
lib/haven/haven.dart
lib/ethereum/ethereum.dart
lib/nano/nano.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png

View file

@ -25,10 +25,6 @@
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="#000000"/> <!-- Dark background color -->
</item>
</layer-list>

View file

@ -1,12 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<color android:color="#FFFFFF"/> <!-- Light background color -->
</item>
</layer-list>

BIN
assets/images/exolix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View file

@ -0,0 +1,6 @@
-
uri: rpc.nano.to
useSSL: true
is_default: true
-
uri: node.perish.co:9076

View file

@ -0,0 +1,9 @@
-
uri: rpc.nano.to
useSSL: true
is_default: true
-
uri: workers.perish.co
-
uri: worker.nanoriver.cc
useSSL: true

View file

@ -1,3 +1,3 @@
Enhance Monero coin control
Add Filipino localization
Bug Fixes
Fix 2FA code issue
Bug fixes
Minor enhancements

View file

@ -1,5 +1,4 @@
New Buy Provider Robinhood
Fix sending Ethereum issue
Enhance Monero coin control
Add Filipino localization
Bug Fixes
Ethereum enhancements and bug fixes
Fix 2FA code issue
Bug fixes
Minor enhancements

1
configure_cake_wallet_android.sh Normal file → Executable file
View file

@ -7,4 +7,5 @@ cd cw_monero && flutter pub get && flutter packages pub run build_runner build -
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -2297,4 +2297,4 @@ final englishWordlist = <String>[
'zero',
'zone',
'zoo'
];
];

View file

@ -89,4 +89,4 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex);
}
}
}

View file

@ -20,4 +20,4 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
: super(name: name, password: password, walletInfo: walletInfo);
final String wif;
}
}

View file

@ -100,4 +100,4 @@ class BitcoinWalletService extends WalletService<
await wallet.init();
return wallet;
}
}
}

View file

@ -56,4 +56,4 @@ class ElectrumWallletSnapshot {
regularAddressIndex: regularAddressIndex,
changeAddressIndex: changeAddressIndex);
}
}
}

View file

@ -90,6 +90,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.zrx,
CryptoCurrency.dydx,
CryptoCurrency.steth,
CryptoCurrency.banano,
];
static const havenCurrencies = [
@ -119,7 +120,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const eos = CryptoCurrency(title: 'EOS', fullName: 'EOS', raw: 7, name: 'eos', iconPath: 'assets/images/eos_icon.png');
static const eth = CryptoCurrency(title: 'ETH', fullName: 'Ethereum', raw: 8, name: 'eth', iconPath: 'assets/images/eth_icon.png');
static const ltc = CryptoCurrency(title: 'LTC', fullName: 'Litecoin', raw: 9, name: 'ltc', iconPath: 'assets/images/litecoin-ltc_icon.png');
static const nano = CryptoCurrency(title: 'NANO', raw: 10, name: 'nano', iconPath: 'assets/images/nano.png');
static const nano = CryptoCurrency(title: 'XNO', raw: 10, fullName: 'Nano', name: 'xno', iconPath: 'assets/images/nano_icon.png');
static const trx = CryptoCurrency(title: 'TRX', fullName: 'TRON', raw: 11, name: 'trx', iconPath: 'assets/images/trx_icon.png');
static const usdt = CryptoCurrency(title: 'USDT', tag: 'OMNI', fullName: 'USDT Tether', raw: 12, name: 'usdt', iconPath: 'assets/images/usdt_icon.png');
static const usdterc20 = CryptoCurrency(title: 'USDT', tag: 'ETH', fullName: 'USDT Tether', raw: 13, name: 'usdterc20', iconPath: 'assets/images/usdterc20_icon.png');
@ -198,6 +199,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const zrx = CryptoCurrency(title: 'ZRX', tag: 'ETH', fullName: '0x Protocol', raw: 83, name: 'zrx', iconPath: 'assets/images/zrx_icon.png');
static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png');
static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png');
static const banano = CryptoCurrency(title: 'BAN', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png');
static final Map<int, CryptoCurrency> _rawCurrencyMap =

View file

@ -13,6 +13,10 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.xhv;
case WalletType.ethereum:
return CryptoCurrency.eth;
case WalletType.nano:
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
}

View file

@ -1,4 +1,4 @@
const CONTACT_TYPE_ID = 0;
const CONTACT_TYPE_ID = 0;
const NODE_TYPE_ID = 1;
const TRANSACTION_TYPE_ID = 2;
const TRADE_TYPE_ID = 3;
@ -11,3 +11,6 @@ const UNSPENT_COINS_INFO_TYPE_ID = 9;
const ANONPAY_INVOICE_INFO_TYPE_ID = 10;
const ADDRESS_INFO_TYPE_ID = 11;
const ERC20_TOKEN_TYPE_ID = 12;
const NANO_ACCOUNT_TYPE_ID = 13;
const POW_NODE_TYPE_ID = 14;
const DERIVATION_TYPE_TYPE_ID = 15;

View file

@ -0,0 +1,23 @@
import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart';
part 'nano_account.g.dart';
@HiveType(typeId: NanoAccount.typeId)
class NanoAccount extends HiveObject {
NanoAccount({required this.label, required this.id, this.balance, this.isSelected = false});
static const typeId = NANO_ACCOUNT_TYPE_ID;
@HiveField(0)
String label;
@HiveField(1)
final int id;
@HiveField(2)
bool isSelected;
@HiveField(3)
String? balance;
}

View file

@ -0,0 +1,23 @@
class AccountInfoResponse {
String frontier;
int confirmationHeight;
String balance;
String representative;
String? address;
AccountInfoResponse({
required this.frontier,
required this.balance,
required this.representative,
required this.confirmationHeight,
});
factory AccountInfoResponse.fromJson(Map<String, dynamic> json) {
return AccountInfoResponse(
frontier: json['frontier'] as String,
representative: json['representative'] as String,
balance: json['balance'] as String,
confirmationHeight: int.parse(json['confirmation_height'] as String),
);
}
}

View file

@ -9,19 +9,19 @@ import 'package:http/io_client.dart' as ioc;
part 'node.g.dart';
Uri createUriFromElectrumAddress(String address) =>
Uri.tryParse('tcp://$address')!;
Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!;
@HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable {
Node(
{this.login,
this.password,
this.useSSL,
this.trusted = false,
this.socksProxyAddress,
String? uri,
WalletType? type,}) {
Node({
this.login,
this.password,
this.useSSL,
this.trusted = false,
this.socksProxyAddress,
String? uri,
WalletType? type,
}) {
if (uri != null) {
uriRaw = uri;
}
@ -78,6 +78,13 @@ class Node extends HiveObject with Keyable {
return Uri.http(uriRaw, '');
case WalletType.ethereum:
return Uri.https(uriRaw, '');
case WalletType.nano:
case WalletType.banano:
if (isSSL) {
return Uri.https(uriRaw, '');
} else {
return Uri.http(uriRaw, '');
}
default:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
@ -86,13 +93,13 @@ class Node extends HiveObject with Keyable {
@override
bool operator ==(other) =>
other is Node &&
(other.uriRaw == uriRaw &&
other.login == login &&
other.password == password &&
other.typeRaw == typeRaw &&
other.useSSL == useSSL &&
other.trusted == trusted &&
other.socksProxyAddress == socksProxyAddress);
(other.uriRaw == uriRaw &&
other.login == login &&
other.password == password &&
other.typeRaw == typeRaw &&
other.useSSL == useSSL &&
other.trusted == trusted &&
other.socksProxyAddress == socksProxyAddress);
@override
int get hashCode =>
@ -120,7 +127,9 @@ class Node extends HiveObject with Keyable {
try {
switch (type) {
case WalletType.monero:
return useSocksProxy ? requestNodeWithProxy(socksProxyAddress ?? '') : requestMoneroNode();
return useSocksProxy
? requestNodeWithProxy(socksProxyAddress ?? '')
: requestMoneroNode();
case WalletType.bitcoin:
return requestElectrumServer();
case WalletType.litecoin:
@ -129,6 +138,9 @@ class Node extends HiveObject with Keyable {
return requestMoneroNode();
case WalletType.ethereum:
return requestElectrumServer();
case WalletType.nano:
case WalletType.banano:
return requestNanoNode();
default:
return false;
}
@ -141,27 +153,23 @@ class Node extends HiveObject with Keyable {
final path = '/json_rpc';
final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
final realm = 'monero-rpc';
final body = {
'jsonrpc': '2.0',
'id': '0',
'method': 'get_info'
};
final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'};
try {
final authenticatingClient = HttpClient();
authenticatingClient.addCredentials(
rpcUri,
realm,
HttpClientDigestCredentials(login ?? '', password ?? ''),
rpcUri,
realm,
HttpClientDigestCredentials(login ?? '', password ?? ''),
);
final http.Client client = ioc.IOClient(authenticatingClient);
final response = await client.post(
rpcUri,
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
rpcUri,
headers: {'Content-Type': 'application/json'},
body: json.encode(body),
);
client.close();
@ -173,8 +181,24 @@ class Node extends HiveObject with Keyable {
}
}
Future<bool> requestNodeWithProxy(String proxy) async {
Future<bool> requestNanoNode() async {
http.Response response = await http.post(
uri,
headers: {'Content-type': 'application/json'},
body: json.encode(
{
"action": "block_count",
},
),
);
if (response.statusCode == 200) {
return true;
} else {
return false;
}
}
Future<bool> requestNodeWithProxy(String proxy) async {
if (proxy.isEmpty || !proxy.contains(':')) {
return false;
}

View file

@ -13,9 +13,7 @@ import 'package:cw_core/sync_status.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletBase<
BalanceType extends Balance,
HistoryType extends TransactionHistoryBase,
abstract class WalletBase<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
TransactionType extends TransactionInfo> {
WalletBase(this.walletInfo);
@ -58,6 +56,9 @@ abstract class WalletBase<
Future<void> connectToNode({required Node node});
// there is a default definition here because only coins with a pow node (nano based) need to override this
Future<void> connectToPowNode({required Node node}) async {}
Future<void> startSync();
Future<PendingTransaction> createTransaction(Object credentials);

View file

@ -5,10 +5,15 @@ abstract class WalletCredentials {
required this.name,
this.height,
this.walletInfo,
this.password});
this.password,
this.derivationType,
this.derivationPath,
});
final String name;
final int? height;
String? password;
DerivationType? derivationType;
String? derivationPath;
WalletInfo? walletInfo;
}

View file

@ -6,29 +6,92 @@ import 'package:hive/hive.dart';
part 'wallet_info.g.dart';
@HiveType(typeId: DERIVATION_TYPE_TYPE_ID)
enum DerivationType {
@HiveField(0)
unknown,
@HiveField(1)
def, // default is a reserved word
@HiveField(2)
nano,
@HiveField(3)
bip39,
@HiveField(4)
electrum1,
@HiveField(5)
electrum2,
}
class DerivationInfo {
DerivationInfo({
required this.derivationType,
this.derivationPath,
this.balance = "",
this.address = "",
this.height = 0,
this.script_type,
this.description,
});
String balance;
String address;
int height;
final DerivationType derivationType;
final String? derivationPath;
final String? script_type;
final String? description;
}
@HiveType(typeId: WalletInfo.typeId)
class WalletInfo extends HiveObject {
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
this.timestamp, this.dirPath, this.path, this.address, this.yatEid,
this.yatLastUsedAddressRaw, this.showIntroCakePayCard)
WalletInfo(
this.id,
this.name,
this.type,
this.isRecovery,
this.restoreHeight,
this.timestamp,
this.dirPath,
this.path,
this.address,
this.yatEid,
this.yatLastUsedAddressRaw,
this.showIntroCakePayCard,
this.derivationType,
this.derivationPath)
: _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external(
{required String id,
required String name,
required WalletType type,
required bool isRecovery,
required int restoreHeight,
required DateTime date,
required String dirPath,
required String path,
required String address,
bool? showIntroCakePayCard,
String yatEid ='',
String yatLastUsedAddressRaw = ''}) {
return WalletInfo(id, name, type, isRecovery, restoreHeight,
date.millisecondsSinceEpoch, dirPath, path, address,
yatEid, yatLastUsedAddressRaw, showIntroCakePayCard);
factory WalletInfo.external({
required String id,
required String name,
required WalletType type,
required bool isRecovery,
required int restoreHeight,
required DateTime date,
required String dirPath,
required String path,
required String address,
bool? showIntroCakePayCard,
String yatEid = '',
String yatLastUsedAddressRaw = '',
DerivationType? derivationType,
String? derivationPath,
}) {
return WalletInfo(
id,
name,
type,
isRecovery,
restoreHeight,
date.millisecondsSinceEpoch,
dirPath,
path,
address,
yatEid,
yatLastUsedAddressRaw,
showIntroCakePayCard,
derivationType,
derivationPath);
}
static const typeId = WALLET_INFO_TYPE_ID;
@ -79,6 +142,12 @@ class WalletInfo extends HiveObject {
@HiveField(15)
List<String>? usedAddresses;
@HiveField(16)
DerivationType? derivationType;
@HiveField(17)
String? derivationPath;
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
set yatLastUsedAddress(String address) {
@ -89,7 +158,7 @@ class WalletInfo extends HiveObject {
String get yatEmojiId => yatEid ?? '';
bool get isShowIntroCakePayCard {
if(showIntroCakePayCard == null) {
if (showIntroCakePayCard == null) {
return type != WalletType.haven;
}
return showIntroCakePayCard!;

View file

@ -1,9 +1,11 @@
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletService<N extends WalletCredentials,
RFS extends WalletCredentials, RFK extends WalletCredentials> {
abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
RFK extends WalletCredentials> {
WalletType getType();
Future<WalletBase> create(N credentials);

View file

@ -10,6 +10,8 @@ const walletTypes = [
WalletType.litecoin,
WalletType.haven,
WalletType.ethereum,
WalletType.nano,
WalletType.banano,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@ -31,6 +33,12 @@ enum WalletType {
@HiveField(5)
ethereum,
@HiveField(6)
nano,
@HiveField(7)
banano,
}
int serializeToInt(WalletType type) {
@ -45,6 +53,10 @@ int serializeToInt(WalletType type) {
return 3;
case WalletType.ethereum:
return 4;
case WalletType.nano:
return 5;
case WalletType.banano:
return 6;
default:
return -1;
}
@ -62,6 +74,10 @@ WalletType deserializeFromInt(int raw) {
return WalletType.haven;
case 4:
return WalletType.ethereum;
case 5:
return WalletType.nano;
case 6:
return WalletType.banano;
default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
}
@ -79,6 +95,10 @@ String walletTypeToString(WalletType type) {
return 'Haven';
case WalletType.ethereum:
return 'Ethereum';
case WalletType.nano:
return 'Nano';
case WalletType.banano:
return 'Banano';
default:
return '';
}
@ -96,6 +116,10 @@ String walletTypeToDisplayName(WalletType type) {
return 'Haven (XHV)';
case WalletType.ethereum:
return 'Ethereum (ETH)';
case WalletType.nano:
return 'Nano (XNO)';
case WalletType.banano:
return 'Banano (BAN)';
default:
return '';
}
@ -113,6 +137,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
return CryptoCurrency.xhv;
case WalletType.ethereum:
return CryptoCurrency.eth;
case WalletType.nano:
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
}

View file

@ -65,13 +65,11 @@ class EthereumClient {
bool _isEthereum = currency == CryptoCurrency.eth;
final price = await _client!.getGasPrice();
final price = _client!.getGasPrice();
final Transaction transaction = Transaction(
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
maxGas: gas,
gasPrice: price,
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
);
@ -101,7 +99,7 @@ class EthereumClient {
return PendingEthereumTransaction(
signedTransaction: signedTransaction,
amount: amount,
fee: BigInt.from(gas) * price.getInWei,
fee: BigInt.from(gas) * (await price).getInWei,
sendTransaction: _sendTransaction,
exponent: exponent,
);
@ -210,6 +208,10 @@ I/flutter ( 4474): Gas Used: 53000
}
}
Web3Client? getWeb3Client() {
return _client;
}
// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
// final contractAbi = ContractAbi.fromJson(abi, "ERC20");

View file

@ -77,6 +77,8 @@ abstract class EthereumWalletBase
late final EthPrivateKey _ethPrivateKey;
EthPrivateKey get ethPrivateKey => _ethPrivateKey;
late EthereumClient _client;
int? _gasPrice;
@ -508,4 +510,6 @@ abstract class EthereumWalletBase
@override
String signMessage(String message, {String? address = null}) =>
bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
Web3Client? getWeb3Client() => _client.getWeb3Client();
}

View file

@ -234,7 +234,6 @@ extern "C"
}
void setUnlocked(bool unlocked);
};
Monero::Coins *m_coins;
@ -568,7 +567,7 @@ extern "C"
_preferred_inputs.insert(std::string(*preferred_inputs));
preferred_inputs++;
}
auto priority = static_cast<Monero::PendingTransaction::Priority>(priority_raw);
std::string _payment_id;
Monero::PendingTransaction *transaction;

View file

@ -37,10 +37,10 @@ const moneroBlockSize = 1000;
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
MoneroTransactionHistory, MoneroTransactionInfo> with Store {
MoneroWalletBase({required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo})
abstract class MoneroWalletBase
extends WalletBase<MoneroBalance, MoneroTransactionHistory, MoneroTransactionInfo> with Store {
MoneroWalletBase(
{required WalletInfo walletInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo})
: balance = ObservableMap<CryptoCurrency, MoneroBalance>.of({
CryptoCurrency.xmr: MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
@ -112,12 +112,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
Future<void> init() async {
await walletAddresses.init();
balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(
<CryptoCurrency, MoneroBalance>{
currency: MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id),
unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id))
});
balance = ObservableMap<CryptoCurrency, MoneroBalance>.of(<CryptoCurrency, MoneroBalance>{
currency: MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id),
unlockedBalance:
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id))
});
_setListeners();
await updateTransactions();
@ -125,15 +125,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
monero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
if (monero_wallet.getCurrentHeight() <= 1) {
monero_wallet.setRefreshFromBlockHeight(
height: walletInfo.restoreHeight);
monero_wallet.setRefreshFromBlockHeight(height: walletInfo.restoreHeight);
}
}
_autoSaveTimer = Timer.periodic(
Duration(seconds: _autoSaveInterval),
(_) async => await save());
_autoSaveTimer =
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
}
@override
Future<void>? updateBalance() => null;
@ -153,7 +152,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
login: node.login,
password: node.password,
useSSL: node.isSSL,
isLightWallet: false, // FIXME: hardcoded value
isLightWallet: false,
// FIXME: hardcoded value
socksProxyAddress: node.socksProxyAddress);
monero_wallet.setTrustedDaemon(node.trusted);
@ -189,7 +189,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
final outputs = _credentials.outputs;
final hasMultiDestination = outputs.length > 1;
final unlockedBalance =
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
var allInputsAmount = 0;
PendingTransactionDescription pendingTransactionDescription;
@ -208,56 +208,42 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
inputs.add(utx.keyImage);
}
}
if (inputs.isEmpty) {
throw MoneroTransactionNoInputsException(0);
}
final spendAllCoins = inputs.length == unspentCoins.length;
if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll
|| (item.formattedCryptoAmount ?? 0) <= 0)) {
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
}
final int totalAmount = outputs.fold(0, (acc, value) =>
acc + (value.formattedCryptoAmount ?? 0));
final int totalAmount =
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount);
if (unlockedBalance < totalAmount) {
throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
}
if (allInputsAmount < totalAmount + estimatedFee) {
if (!spendAllCoins && (allInputsAmount < totalAmount + estimatedFee)) {
throw MoneroTransactionNoInputsException(inputs.length);
}
final moneroOutputs = outputs.map((output) {
final outputAddress = output.isParsedAddress
? output.extractedAddress
: output.address;
final outputAddress = output.isParsedAddress ? output.extractedAddress : output.address;
return MoneroOutput(
address: outputAddress!,
amount: output.cryptoAmount!.replaceAll(',', '.'));
return MoneroOutput(
address: outputAddress!, amount: output.cryptoAmount!.replaceAll(',', '.'));
}).toList();
pendingTransactionDescription =
await transaction_history.createTransactionMultDest(
pendingTransactionDescription = await transaction_history.createTransactionMultDest(
outputs: moneroOutputs,
priorityRaw: _credentials.priority.serialize(),
accountIndex: walletAddresses.account!.id,
preferredInputs: inputs);
} else {
final output = outputs.first;
final address = output.isParsedAddress
? output.extractedAddress
: output.address;
final amount = output.sendAll
? null
: output.cryptoAmount!.replaceAll(',', '.');
final formattedAmount = output.sendAll
? null
: output.formattedCryptoAmount;
final address = output.isParsedAddress ? output.extractedAddress : output.address;
final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.');
final formattedAmount = output.sendAll ? null : output.formattedCryptoAmount;
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
(formattedAmount == null && unlockedBalance <= 0)) {
@ -268,8 +254,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
final estimatedFee = calculateEstimatedFee(_credentials.priority, formattedAmount);
if ((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
(formattedAmount == null && allInputsAmount != unlockedBalance)) {
if (!spendAllCoins &&
((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
formattedAmount == null)) {
throw MoneroTransactionNoInputsException(inputs.length);
}
@ -327,10 +314,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
try {
// -- rename the waller folder --
final currentWalletDir =
Directory(await pathForWalletDir(name: name, type: type));
final newWalletDirPath =
await pathForWalletDir(name: newWalletName, type: type);
final currentWalletDir = Directory(await pathForWalletDir(name: name, type: type));
final newWalletDirPath = await pathForWalletDir(name: newWalletName, type: type);
await currentWalletDir.rename(newWalletDirPath);
// -- use new waller folder to rename files with old names still --
@ -340,8 +325,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
final currentKeysFile = File('$renamedWalletPath.keys');
final currentAddressListFile = File('$renamedWalletPath.address.txt');
final newWalletPath =
await pathForWallet(name: newWalletName, type: type);
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
if (currentCacheFile.existsSync()) {
await currentCacheFile.rename(newWalletPath);
@ -359,8 +343,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
final currentKeysFile = File('$currentWalletPath.keys');
final currentAddressListFile = File('$currentWalletPath.address.txt');
final newWalletPath =
await pathForWallet(name: newWalletName, type: type);
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
// Copies current wallet files into new wallet name's dir and files
if (currentCacheFile.existsSync()) {
@ -426,8 +409,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values.where((element) =>
element.walletId.contains(id) && element.hash.contains(coin.hash));
final coinInfoList = unspentCoinsInfo.values
.where((element) => element.walletId.contains(id) && element.hash.contains(coin.hash));
if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first;
@ -447,16 +430,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
Future<void> _addCoinInfo(MoneroUnspent coin) async {
final newInfo = UnspentCoinsInfo(
walletId: id,
hash: coin.hash,
isFrozen: coin.isFrozen,
isSending: coin.isSending,
noteRaw: coin.note,
address: coin.address,
value: coin.value,
vout: 0,
keyImage: coin.keyImage
);
walletId: id,
hash: coin.hash,
isFrozen: coin.isFrozen,
isSending: coin.isSending,
noteRaw: coin.note,
address: coin.address,
value: coin.value,
vout: 0,
keyImage: coin.keyImage);
await unspentCoinsInfo.add(newInfo);
}
@ -464,8 +446,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
Future<void> _refreshUnspentCoinsInfo() async {
try {
final List<dynamic> keys = <dynamic>[];
final currentWalletUnspentCoins = unspentCoinsInfo.values
.where((element) => element.walletId.contains(id));
final currentWalletUnspentCoins =
unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
if (currentWalletUnspentCoins.isNotEmpty) {
currentWalletUnspentCoins.forEach((element) {
@ -486,16 +468,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
String getTransactionAddress(int accountIndex, int addressIndex) =>
monero_wallet.getAddress(
accountIndex: accountIndex,
addressIndex: addressIndex);
monero_wallet.getAddress(accountIndex: accountIndex, addressIndex: addressIndex);
@override
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
transaction_history.refreshTransactions();
return _getAllTransactionsOfAccount(walletAddresses.account?.id).fold<Map<String, MoneroTransactionInfo>>(
<String, MoneroTransactionInfo>{},
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
return _getAllTransactionsOfAccount(walletAddresses.account?.id)
.fold<Map<String, MoneroTransactionInfo>>(<String, MoneroTransactionInfo>{},
(Map<String, MoneroTransactionInfo> acc, MoneroTransactionInfo tx) {
acc[tx.id] = tx;
return acc;
});
@ -523,12 +503,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
return monero_wallet.getSubaddressLabel(accountIndex, addressIndex);
}
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) =>
transaction_history
.getAllTransactions()
.map((row) => MoneroTransactionInfo.fromRow(row))
.where((element) => element.accountIndex == (accountIndex ?? 0))
.toList();
List<MoneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) => transaction_history
.getAllTransactions()
.map((row) => MoneroTransactionInfo.fromRow(row))
.where((element) => element.accountIndex == (accountIndex ?? 0))
.toList();
void _setListeners() {
_listener?.stop();
@ -550,8 +529,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
int _getHeightDistance(DateTime date) {
final distance =
DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
final distance = DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
final daysTmp = (distance / 86400).round();
final days = daysTmp < 1 ? 1 : daysTmp;
@ -582,11 +560,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
}
Future<void> _askForUpdateTransactionHistory() async =>
await updateTransactions();
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
int _getFullBalance() =>
monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id);
int _getFullBalance() => monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id);
int _getUnlockedBalance() =>
monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
@ -595,8 +571,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
var frozenBalance = 0;
for (var coin in unspentCoinsInfo.values) {
if (coin.isFrozen)
frozenBalance += coin.value;
if (coin.isFrozen) frozenBalance += coin.value;
}
return frozenBalance;
@ -617,9 +592,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
syncStatus = SyncedSyncStatus();
if (!_hasSyncAfterStartup) {
_hasSyncAfterStartup = true;
await save();
}
_hasSyncAfterStartup = true;
await save();
}
if (walletInfo.isRecovery) {
await setAsRecovered();
@ -644,12 +619,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
void _updateSubAddress(bool enableAutoGenerate, {Account? account}) {
if (enableAutoGenerate) {
walletAddresses.updateUnusedSubaddress(
accountIndex: account?.id ?? 0,
defaultLabel: account?.label ?? '',
);
} else {
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
}
walletAddresses.updateUnusedSubaddress(
accountIndex: account?.id ?? 0,
defaultLabel: account?.label ?? '',
);
} else {
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
}
}
}

View file

@ -57,7 +57,7 @@ class MoneroWalletService extends WalletService<
final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
static bool walletFilesExist(String path) =>
!File(path).existsSync() && !File('$path.keys').existsSync();

View file

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

7
cw_nano/lib/cw_nano.dart Normal file
View file

@ -0,0 +1,7 @@
library cw_nano;
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}

39
cw_nano/lib/file.dart Normal file
View file

@ -0,0 +1,39 @@
import 'dart:io';
import 'package:cw_core/key.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
Future<void> write(
{required String path,
required String password,
required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}
Future<void> writeData(
{required String path,
required String password,
required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}
Future<String> read({required String path, required String password}) async {
final file = File(path);
if (!file.existsSync()) {
file.createSync();
}
final encrypted = file.readAsStringSync();
return decode(password: password, data: encrypted);
}

View file

@ -0,0 +1,69 @@
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/nano_account.dart';
import 'package:mobx/mobx.dart';
import 'package:hive/hive.dart';
part 'nano_account_list.g.dart';
class NanoAccountList = NanoAccountListBase with _$NanoAccountList;
abstract class NanoAccountListBase with Store {
NanoAccountListBase(this.address)
: accounts = ObservableList<NanoAccount>(),
_isRefreshing = false,
_isUpdating = false {
refresh();
}
@observable
ObservableList<NanoAccount> accounts;
bool _isRefreshing;
bool _isUpdating;
String address;
Future<void> update(String? address) async {
if (_isUpdating) {
return;
}
try {
_isUpdating = true;
final accounts = await getAll(address: address ?? this.address);
if (accounts.isNotEmpty) {
this.accounts.clear();
this.accounts.addAll(accounts);
}
_isUpdating = false;
} catch (e) {
_isUpdating = false;
rethrow;
}
}
Future<List<NanoAccount>> getAll({String? address}) async {
final box = await CakeHive.openBox<NanoAccount>(address ?? this.address);
// get all accounts in box:
return box.values.toList();
}
Future<void> addAccount({required String label}) async {
final box = await CakeHive.openBox<NanoAccount>(address);
final account = NanoAccount(id: box.length, label: label, balance: "0.00", isSelected: false);
await box.add(account);
await account.save();
}
Future<void> setLabelAccount({required int accountIndex, required String label}) async {
final box = await CakeHive.openBox<NanoAccount>(address);
final account = box.getAt(accountIndex);
account!.label = label;
await account.save();
}
void refresh() {}
}

View file

@ -0,0 +1,34 @@
import 'package:cw_core/balance.dart';
import 'package:cw_nano/nano_util.dart';
BigInt stringAmountToBigInt(String amount) {
return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.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.fromString(
{required this.formattedCurrentBalance, required this.formattedReceivableBalance})
: currentBalance = stringAmountToBigInt(formattedCurrentBalance),
receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
super(0, 0);
@override
String get formattedAvailableBalance {
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano);
}
@override
String get formattedAdditionalBalance {
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano);
}
}

View file

@ -0,0 +1,424 @@
import 'dart:async';
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';
class NanoClient {
static const String DEFAULT_REPRESENTATIVE =
"nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579";
Node? _node;
Node? _powNode;
bool connect(Node node) {
try {
_node = node;
return true;
} catch (e) {
return false;
}
}
bool connectPow(Node node) {
try {
_powNode = node;
return true;
} catch (e) {
return false;
}
}
Future<NanoBalance> getBalance(String address) async {
final response = await http.post(
_node!.uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode(
{
"action": "account_balance",
"account": address,
},
),
);
final data = await jsonDecode(response.body);
final String currentBalance = data["balance"] as String;
final String receivableBalance = data["receivable"] as String;
final BigInt cur = BigInt.parse(currentBalance);
final BigInt rec = BigInt.parse(receivableBalance);
return NanoBalance(currentBalance: cur, receivableBalance: rec);
}
Future<AccountInfoResponse?> getAccountInfo(String address) async {
try {
final response = await http.post(
_node!.uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode(
{
"action": "account_info",
"representative": "true",
"account": address,
},
),
);
final data = await jsonDecode(response.body);
return AccountInfoResponse.fromJson(data as Map<String, dynamic>);
} catch (e) {
print("error while getting account info");
return null;
}
}
Future<String> changeRep({
required String privateKey,
required String repAddress,
required String ourAddress,
}) async {
try {
AccountInfoResponse? accountInfo = await getAccountInfo(ourAddress);
if (accountInfo == null) {
throw Exception("error while getting account info");
}
// construct the change block:
Map<String, String> changeBlock = {
"type": "state",
"account": ourAddress,
"previous": accountInfo.frontier,
"representative": repAddress,
"balance": accountInfo.balance,
"link": "0000000000000000000000000000000000000000000000000000000000000000",
"link_as_account": "nano_1111111111111111111111111111111111111111111111111111hifc8npp",
};
// sign the change block:
final String hash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
changeBlock["account"]!,
changeBlock["previous"]!,
changeBlock["representative"]!,
BigInt.parse(changeBlock["balance"]!),
changeBlock["link"]!,
);
final String signature = NanoSignatures.signBlock(hash, privateKey);
// get PoW for the send block:
final String work = await requestWork(accountInfo.frontier);
changeBlock["signature"] = signature;
changeBlock["work"] = work;
return await processBlock(changeBlock, "change");
} catch (e) {
throw Exception("error while changing representative");
}
}
Future<String> requestWork(String hash) async {
final response = await http.post(
_powNode!.uri,
headers: {'Content-type': 'application/json'},
body: json.encode(
{
"action": "work_generate",
"hash": hash,
},
),
);
if (response.statusCode == 200) {
final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}");
}
return decoded["work"] as String;
} else {
throw Exception("Received work error ${response.body}");
}
}
Future<String> send({
required String privateKey,
required String amountRaw,
required String destinationAddress,
}) async {
final Map<String, String> sendBlock = await constructSendBlock(
privateKey: privateKey,
amountRaw: amountRaw,
destinationAddress: destinationAddress,
);
return await processBlock(sendBlock, "send");
}
Future<String> processBlock(Map<String, String> block, String subtype) async {
final headers = {"Content-Type": "application/json"};
final processBody = jsonEncode({
"action": "process",
"json_block": "true",
"subtype": subtype,
"block": block,
});
final processResponse = await http.post(
_node!.uri,
headers: headers,
body: processBody,
);
final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}");
}
// return the hash of the transaction:
return decoded["hash"].toString();
}
Future<Map<String, String>> constructSendBlock({
required String privateKey,
required String amountRaw,
required String destinationAddress,
BigInt? balanceAfterTx,
String? previousHash,
}) async {
try {
// our address:
final String publicAddress = NanoUtil.privateKeyToAddress(privateKey);
// first get the current account balance:
if (balanceAfterTx == null) {
final BigInt currentBalance = (await getBalance(publicAddress)).currentBalance;
final BigInt txAmount = BigInt.parse(amountRaw);
balanceAfterTx = currentBalance - txAmount;
}
// get the account info (we need the frontier and representative):
AccountInfoResponse? infoResponse = await getAccountInfo(publicAddress);
if (infoResponse == null) {
throw Exception(
"error while getting account info! (we probably don't have an open account yet)");
}
String frontier = infoResponse.frontier;
// override if provided:
if (previousHash != null) {
frontier = previousHash;
}
final String representative = infoResponse.representative;
// link = destination address:
final String link = NanoAccounts.extractPublicKey(destinationAddress);
final String linkAsAccount = destinationAddress;
// construct the send block:
Map<String, String> sendBlock = {
"type": "state",
"account": publicAddress,
"previous": frontier,
"representative": representative,
"balance": balanceAfterTx.toString(),
"link": link,
};
// sign the send block:
final String hash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
sendBlock["account"]!,
sendBlock["previous"]!,
sendBlock["representative"]!,
BigInt.parse(sendBlock["balance"]!),
sendBlock["link"]!,
);
final String signature = NanoSignatures.signBlock(hash, privateKey);
// get PoW for the send block:
final String work = await requestWork(frontier);
sendBlock["link_as_account"] = linkAsAccount;
sendBlock["signature"] = signature;
sendBlock["work"] = work;
// ready to post send block:
return sendBlock;
} catch (e) {
print(e);
rethrow;
}
}
Future<void> receiveBlock({
required String blockHash,
required String source,
required String amountRaw,
required String destinationAddress,
required String privateKey,
}) async {
bool openBlock = false;
final headers = {
"Content-Type": "application/json",
};
// first check if the account is open:
// get the account info (we need the frontier and representative):
AccountInfoResponse? infoData = await getAccountInfo(destinationAddress);
String? frontier;
String? representative;
if (infoData == null) {
// account is not open yet, we need to create an open block:
openBlock = true;
// we don't have a representative set yet:
representative = DEFAULT_REPRESENTATIVE;
// we don't have a frontier yet:
frontier = "0000000000000000000000000000000000000000000000000000000000000000";
} else {
frontier = infoData.frontier;
representative = infoData.representative;
}
// first get the account balance:
final BigInt currentBalance = (await getBalance(destinationAddress)).currentBalance;
final BigInt txAmount = BigInt.parse(amountRaw);
final BigInt balanceAfterTx = currentBalance + txAmount;
// link = send block hash:
final String link = blockHash;
// this "linkAsAccount" is meaningless:
final String linkAsAccount = NanoAccounts.createAccount(NanoAccountType.NANO, blockHash);
// construct the receive block:
Map<String, String> receiveBlock = {
"type": "state",
"account": destinationAddress,
"previous": frontier,
"representative": representative,
"balance": balanceAfterTx.toString(),
"link": link,
"link_as_account": linkAsAccount,
};
// sign the receive block:
final String hash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
receiveBlock["account"]!,
receiveBlock["previous"]!,
receiveBlock["representative"]!,
BigInt.parse(receiveBlock["balance"]!),
receiveBlock["link"]!,
);
final String signature = NanoSignatures.signBlock(hash, privateKey);
// get PoW for the receive block:
String? work;
if (openBlock) {
work = await requestWork(NanoAccounts.extractPublicKey(destinationAddress));
} else {
work = await requestWork(frontier);
}
receiveBlock["link_as_account"] = linkAsAccount;
receiveBlock["signature"] = signature;
receiveBlock["work"] = work;
// process the receive block:
final processBody = jsonEncode({
"action": "process",
"json_block": "true",
"subtype": "receive",
"block": receiveBlock,
});
final processResponse = await http.post(
_node!.uri,
headers: headers,
body: processBody,
);
final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}");
}
}
// returns the number of blocks received:
Future<int> confirmAllReceivable({
required String destinationAddress,
required String privateKey,
}) async {
final receivableResponse = await http.post(_node!.uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"action": "receivable",
"account": destinationAddress,
"count": "-1",
"source": true,
}));
final receivableData = await jsonDecode(receivableResponse.body);
if (receivableData["blocks"] == "" || receivableData["blocks"] == null) {
return 0;
}
dynamic blocks;
if (receivableData["blocks"] is List<dynamic>) {
var listBlocks = receivableData["blocks"] as List<dynamic>;
if (listBlocks.isEmpty) {
return 0;
}
blocks = {for (var block in listBlocks) block['hash']: block};
} else {
blocks = receivableData["blocks"] as Map<String, dynamic>;
}
blocks = blocks as Map<String, dynamic>;
// confirm all receivable blocks:
for (final blockHash in blocks.keys) {
final block = blocks[blockHash];
final String amountRaw = block["amount"] as String;
final String source = block["source"] as String;
await receiveBlock(
blockHash: blockHash,
source: source,
amountRaw: amountRaw,
privateKey: privateKey,
destinationAddress: destinationAddress,
);
// a bit of a hack:
await Future<void>.delayed(const Duration(seconds: 2));
}
return blocks.keys.length;
}
void stop() {}
Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
try {
final response = await http.post(_node!.uri,
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"action": "account_history",
"account": address,
"count": "250", // TODO: pick a number
// "raw": true,
}));
final data = await jsonDecode(response.body);
final transactions = data["history"] is List ? data["history"] as List<dynamic> : [];
// Map the transactions list to NanoTransactionModel using the factory
// reversed so that the DateTime is correct when local_timestamp is absent
return transactions.reversed
.map<NanoTransactionModel>((transaction) => NanoTransactionModel.fromJson(transaction))
.toList();
} catch (e) {
print(e);
return [];
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
import 'package:cw_core/output_info.dart';
class NanoTransactionCredentials {
NanoTransactionCredentials(this.outputs);
final List<OutputInfo> outputs;
}

View file

@ -0,0 +1,72 @@
import 'dart:convert';
import 'dart:core';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_nano/file.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_nano/nano_transaction_info.dart';
part 'nano_transaction_history.g.dart';
const transactionsHistoryFileName = 'transactions.json';
class NanoTransactionHistory = NanoTransactionHistoryBase with _$NanoTransactionHistory;
abstract class NanoTransactionHistoryBase
extends TransactionHistoryBase<NanoTransactionInfo> with Store {
NanoTransactionHistoryBase({required this.walletInfo, required String password})
: _password = password {
transactions = ObservableMap<String, NanoTransactionInfo>();
}
final WalletInfo walletInfo;
String _password;
Future<void> init() async => await _load();
@override
Future<void> save() async {
try {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final data = json.encode({'transactions': transactions});
await writeData(path: path, password: _password, data: data);
} catch (e) {
print('Error while save nano transaction history: ${e.toString()}');
}
}
@override
void addOne(NanoTransactionInfo transaction) => transactions[transaction.id] = transaction;
@override
void addMany(Map<String, NanoTransactionInfo> transactions) =>
this.transactions.addAll(transactions);
Future<Map<String, dynamic>> _read() async {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final content = await read(path: path, password: _password);
return json.decode(content) as Map<String, dynamic>;
}
Future<void> _load() async {
try {
final content = await _read();
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
txs.entries.forEach((entry) {
final val = entry.value;
if (val is Map<String, dynamic>) {
final tx = NanoTransactionInfo.fromJson(val);
_update(tx);
}
});
} catch (e) {
print(e);
}
}
void _update(NanoTransactionInfo transaction) => transactions[transaction.id] = transaction;
}

View file

@ -0,0 +1,70 @@
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';
class NanoTransactionInfo extends TransactionInfo {
NanoTransactionInfo({
required this.id,
required this.height,
required this.amountRaw,
this.tokenSymbol = "XNO",
required this.direction,
required this.confirmed,
required this.date,
required this.confirmations,
}) : this.amount = amountRaw.toInt();
final String id;
final int height;
final int amount;
final BigInt amountRaw;
final TransactionDirection direction;
final DateTime date;
final bool confirmed;
final int confirmations;
final String tokenSymbol;
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);
return "$acc$amt $tokenSymbol";
}
@override
String fiatAmount() => _fiatAmount ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() => "0 XNO";
factory NanoTransactionInfo.fromJson(Map<String, dynamic> data) {
return NanoTransactionInfo(
id: data['id'] as String,
height: data['height'] as int,
amountRaw: BigInt.parse(data['amountRaw'] as String),
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
confirmed: data['confirmed'] as bool,
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'height': height,
'amountRaw': amountRaw.toString(),
'direction': direction.index,
'date': date.millisecondsSinceEpoch,
'confirmed': confirmed,
'confirmations': confirmations,
'tokenSymbol': tokenSymbol,
};
}

View file

@ -0,0 +1,39 @@
class NanoTransactionModel {
final DateTime? date;
final String hash;
final bool confirmed;
final String account;
final BigInt amount;
final int height;
final String type;
NanoTransactionModel({
this.date,
required this.hash,
required this.height,
required this.amount,
required this.confirmed,
required this.type,
required this.account,
});
factory NanoTransactionModel.fromJson(dynamic json) {
DateTime? localTimestamp;
try {
localTimestamp = DateTime.fromMillisecondsSinceEpoch(
int.parse(json["local_timestamp"] as String) * 1000);
} catch (e) {
localTimestamp = DateTime.now();
}
return NanoTransactionModel(
date: localTimestamp,
hash: json["hash"] as String,
height: int.parse(json["height"] as String),
type: json["type"] as String,
amount: BigInt.parse(json["amount"] as String),
account: json["account"] as String,
confirmed: (json["confirmed"] as String) == "true",
);
}
}

193
cw_nano/lib/nano_util.dart Normal file
View file

@ -0,0 +1,193 @@
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

@ -0,0 +1,437 @@
import 'dart:convert';
import 'dart:io';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_nano/file.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_nano/nano_balance.dart';
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';
import 'dart:async';
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;
part 'nano_wallet.g.dart';
class NanoWallet = NanoWalletBase with _$NanoWallet;
abstract class NanoWalletBase
extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> with Store {
NanoWalletBase({
required WalletInfo walletInfo,
required String mnemonic,
required String password,
NanoBalance? initialBalance,
}) : syncStatus = NotConnectedSyncStatus(),
_password = password,
_mnemonic = mnemonic,
_derivationType = walletInfo.derivationType!,
_isTransactionUpdating = false,
_client = NanoClient(),
walletAddresses = NanoWalletAddresses(walletInfo),
balance = ObservableMap<CryptoCurrency, NanoBalance>.of({
CryptoCurrency.nano: initialBalance ??
NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero)
}),
super(walletInfo) {
this.walletInfo = walletInfo;
transactionHistory = NanoTransactionHistory(walletInfo: walletInfo, password: password);
if (!CakeHive.isAdapterRegistered(NanoAccount.typeId)) {
CakeHive.registerAdapter(NanoAccountAdapter());
}
}
final String _mnemonic;
final String _password;
final DerivationType _derivationType;
String? _privateKey;
String? _publicAddress;
String? _seedKey;
String? _representativeAddress;
Timer? _receiveTimer;
late final NanoClient _client;
bool _isTransactionUpdating;
@override
NanoWalletAddresses walletAddresses;
@override
@observable
SyncStatus syncStatus;
@override
@observable
late ObservableMap<CryptoCurrency, NanoBalance> balance;
// initialize the different forms of private / public key we'll need:
Future<void> init() async {
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
// our "mnemonic" is actually a seedkey:
if (!_mnemonic.contains(' ')) {
_seedKey = _mnemonic;
}
if (_seedKey == null) {
if (_derivationType == DerivationType.nano) {
_seedKey = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
} else {
_seedKey = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' '));
}
}
_privateKey = await NanoUtil.uniSeedToPrivate(_seedKey!, 0, type);
_publicAddress = await NanoUtil.uniSeedToAddress(_seedKey!, 0, type);
this.walletInfo.address = _publicAddress!;
await walletAddresses.init();
await transactionHistory.init();
await save();
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
return 0; // always 0 :)
}
@override
Future<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
@override
void close() {
_client.stop();
}
@action
@override
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
final isConnected = _client.connect(node);
if (!isConnected) {
throw Exception("Nano Node connection failed");
}
try {
await _updateBalance();
await _updateRep();
await _receiveAll();
} catch (e) {
print(e);
}
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
}
}
@override
Future<void> connectToPowNode({required Node node}) async {
_client.connectPow(node);
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
credentials = credentials as NanoTransactionCredentials;
BigInt runningAmount = BigInt.zero;
await _updateBalance();
BigInt runningBalance = balance[currency]?.currentBalance ?? BigInt.zero;
final List<Map<String, String>> blocks = [];
String? previousHash;
for (var txOut in credentials.outputs) {
late BigInt amt;
if (txOut.sendAll) {
amt = balance[currency]?.currentBalance ?? BigInt.zero;
} else {
amt = BigInt.tryParse(
NanoUtil.getAmountAsRaw(txOut.cryptoAmount ?? "0", NanoUtil.rawPerNano)) ??
BigInt.zero;
}
if (balance[currency]?.currentBalance != null && amt > balance[currency]!.currentBalance) {
throw Exception("Trying to send more than entire balance!");
}
runningBalance = runningBalance - amt;
final block = await _client.constructSendBlock(
amountRaw: amt.toString(),
destinationAddress: txOut.extractedAddress ?? txOut.address,
privateKey: _privateKey!,
balanceAfterTx: runningBalance,
previousHash: previousHash,
);
previousHash = NanoBlocks.computeStateHash(
NanoAccountType.NANO,
block["account"]!,
block["previous"]!,
block["representative"]!,
BigInt.parse(block["balance"]!),
block["link"]!,
);
blocks.add(block);
runningAmount += amt;
}
try {
if (runningAmount > balance[currency]!.currentBalance || runningBalance < BigInt.zero) {
throw Exception(("Trying to send more than entire balance!"));
}
} catch (e) {
rethrow;
}
return PendingNanoTransaction(
amount: runningAmount,
id: "",
nanoClient: _client,
blocks: blocks,
);
}
Future<void> _receiveAll() async {
await _updateBalance();
int blocksReceived = await this._client.confirmAllReceivable(
destinationAddress: _publicAddress!,
privateKey: _privateKey!,
);
if (blocksReceived > 0) {
await Future<void>.delayed(Duration(seconds: 3));
_updateBalance();
updateTransactions();
}
}
Future<void> updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (_) {
_isTransactionUpdating = false;
}
}
@override
Future<Map<String, NanoTransactionInfo>> fetchTransactions() async {
String address = _publicAddress!;
final transactions = await _client.fetchTransactions(address);
final Map<String, NanoTransactionInfo> result = {};
for (var transactionModel in transactions) {
result[transactionModel.hash] = NanoTransactionInfo(
id: transactionModel.hash,
amountRaw: transactionModel.amount,
height: transactionModel.height,
direction: transactionModel.type == "send"
? TransactionDirection.outgoing
: TransactionDirection.incoming,
confirmed: transactionModel.confirmed,
date: transactionModel.date ?? DateTime.now(),
confirmations: transactionModel.confirmed ? 1 : 0,
);
}
return result;
}
@override
NanoWalletKeys get keys {
return NanoWalletKeys(seedKey: _seedKey!);
}
@override
String? get privateKey => _seedKey!;
@override
Future<void> rescan({required int height}) async {
updateTransactions();
_updateBalance();
return;
}
@override
Future<void> save() async {
await walletAddresses.updateAddressesInBox();
final path = await makePath();
await write(path: path, password: _password, data: toJSON());
await transactionHistory.save();
}
@override
String get seed => _mnemonic;
String get representative => _representativeAddress ?? "";
@action
@override
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
await _updateBalance();
await updateTransactions();
_receiveTimer?.cancel();
_receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async {
// get our balance:
await _updateBalance();
// if we have anything to receive, process it:
if (balance[currency]!.receivableBalance > BigInt.zero) {
await _receiveAll();
}
});
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
rethrow;
}
}
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'seedKey': _seedKey,
'mnemonic': _mnemonic,
'currentBalance': balance[currency]?.currentBalance.toString() ?? "0",
'receivableBalance': balance[currency]?.receivableBalance.toString() ?? "0",
'derivationType': _derivationType.toString()
});
static Future<NanoWallet> open({
required String name,
required String password,
required WalletInfo walletInfo,
}) async {
final path = await pathForWallet(name: name, type: walletInfo.type);
final jsonSource = await read(path: path, password: password);
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");
DerivationType derivationType = DerivationType.bip39;
if (data['derivationType'] == "DerivationType.nano") {
derivationType = DerivationType.nano;
}
walletInfo.derivationType = derivationType;
return NanoWallet(
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
initialBalance: balance,
);
// init() should always be run after this!
}
Future<void> _updateBalance() async {
try {
balance[currency] = await _client.getBalance(_publicAddress!);
} catch (e) {
print("Failed to get balance $e");
}
await save();
}
Future<void> _updateRep() async {
try {
AccountInfoResponse accountInfo = (await _client.getAccountInfo(_publicAddress!))!;
_representativeAddress = accountInfo.representative;
} catch (e) {
// account not found:
_representativeAddress = NanoClient.DEFAULT_REPRESENTATIVE;
throw Exception("Failed to get representative address $e");
}
}
Future<void> regenerateAddress() async {
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
_privateKey =
await NanoUtil.uniSeedToPrivate(_seedKey!, this.walletAddresses.account!.id, type);
_publicAddress =
await NanoUtil.uniSeedToAddress(_seedKey!, this.walletAddresses.account!.id, type);
this.walletInfo.address = _publicAddress!;
this.walletAddresses.address = _publicAddress!;
}
Future<void> changeRep(String address) async {
try {
final String hash = await _client.changeRep(
privateKey: _privateKey!,
repAddress: address,
ourAddress: _publicAddress!,
);
if (hash.isNotEmpty) {
_representativeAddress = address;
}
} catch (e) {
throw Exception("Failed to change representative address $e");
}
}
Future<void>? updateBalance() async => await _updateBalance();
@override
Future<void> renameWalletFiles(String newWalletName) async {
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
final currentWalletFile = File(currentWalletPath);
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
// Copies current wallet files into new wallet name's dir and files
if (currentWalletFile.existsSync()) {
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
await currentWalletFile.copy(newWalletPath);
}
if (currentTransactionsFile.existsSync()) {
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
}
// Delete old name's dir and files
await Directory(currentDirPath).delete(recursive: true);
}
}

View file

@ -0,0 +1,50 @@
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_nano/nano_account_list.dart';
import 'package:mobx/mobx.dart';
part 'nano_wallet_addresses.g.dart';
class NanoWalletAddresses = NanoWalletAddressesBase with _$NanoWalletAddresses;
abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
NanoWalletAddressesBase(WalletInfo walletInfo)
: accountList = NanoAccountList(walletInfo.address),
address = '',
super(walletInfo);
@override
@observable
String address;
@observable
NanoAccount? account;
NanoAccountList accountList;
@override
Future<void> init() async {
var box = await CakeHive.openBox<NanoAccount>(walletInfo.address);
try {
box.getAt(0);
} catch (e) {
box.add(NanoAccount(id: 0, label: "Primary Account", balance: "0.00"));
}
await accountList.update(walletInfo.address);
account = accountList.accounts.first;
address = walletInfo.address;
}
@override
Future<void> updateAddressesInBox() async {
try {
addressesMap.clear();
addressesMap[address] = '';
await saveAddressesInBox();
} catch (e) {
print(e.toString());
}
}
}

View file

@ -0,0 +1,41 @@
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class NanoNewWalletCredentials extends WalletCredentials {
NanoNewWalletCredentials({required String name, String? password})
: super(name: name, password: password);
}
class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
NanoRestoreWalletFromSeedCredentials({
required String name,
required this.mnemonic,
int height = 0,
String? password,
DerivationType? derivationType,
}) : super(
name: name,
password: password,
height: height,
derivationType: derivationType,
);
final String mnemonic;
}
class NanoWalletLoadingException implements Exception {
@override
String toString() => 'Failure to load the wallet.';
}
class NanoRestoreWalletFromKeysCredentials extends WalletCredentials {
NanoRestoreWalletFromKeysCredentials({
required String name,
required String password,
required this.seedKey,
this.derivationType,
}) : super(name: name, password: password);
final String seedKey;
final DerivationType? derivationType;
}

View file

@ -0,0 +1,5 @@
class NanoWalletKeys {
const NanoWalletKeys({required this.seedKey});
final String seedKey;
}

View file

@ -0,0 +1,163 @@
import 'dart:io';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_base.dart';
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';
class NanoWalletService extends WalletService<NanoNewWalletCredentials,
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
NanoWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
static bool walletFilesExist(String path) =>
!File(path).existsSync() && !File('$path.keys').existsSync();
@override
WalletType getType() => WalletType.nano;
@override
Future<WalletBase> create(NanoNewWalletCredentials credentials) async {
// nano standard:
DerivationType derivationType = DerivationType.nano;
String seedKey = NanoSeeds.generateSeed();
String mnemonic = NanoUtil.seedToMnemonic(seedKey);
credentials.walletInfo!.derivationType = derivationType;
final wallet = NanoWallet(
walletInfo: credentials.walletInfo!,
mnemonic: mnemonic,
password: credentials.password!,
);
wallet.init();
return wallet;
}
@override
Future<void> remove(String wallet) async {
final path = await pathForWalletDir(name: wallet, type: getType());
final file = Directory(path);
final isExist = file.existsSync();
if (isExist) {
await file.delete(recursive: true);
}
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(wallet, getType()));
await walletInfoSource.delete(walletInfo.key);
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
String randomWords =
(List<String>.from(nm.NanoMnemomics.WORDLIST)..shuffle()).take(24).join(' ');
final currentWallet =
NanoWallet(walletInfo: currentWalletInfo, password: password, mnemonic: randomWords);
await currentWallet.renameWalletFiles(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName;
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async {
if (credentials.seedKey.contains(' ')) {
throw Exception("Invalid key!");
} else {
if (credentials.seedKey.length != 64 && credentials.seedKey.length != 128) {
throw Exception("Invalid key length!");
}
}
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType;
String? mnemonic;
// 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);
} catch (e) {
throw Exception("Wasn't a valid nano style seed!");
}
}
final wallet = await NanoWallet(
password: credentials.password!,
mnemonic: mnemonic ?? credentials.seedKey,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
await wallet.save();
return wallet;
}
@override
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials) async {
if (credentials.mnemonic.contains(' ')) {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw nm.NanoMnemonicIsIncorrectException();
}
if (!NanoMnemomics.validateMnemonic(credentials.mnemonic.split(' '))) {
throw nm.NanoMnemonicIsIncorrectException();
}
} else {
if (credentials.mnemonic.length != 64 && credentials.mnemonic.length != 128) {
throw Exception("Invalid seed length");
}
}
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
credentials.walletInfo!.derivationType = derivationType;
final wallet = await NanoWallet(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
await wallet.save();
return wallet;
}
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<NanoWallet> openWallet(String name, String password) async {
final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await NanoWalletBase.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init();
await wallet.save();
return wallet;
}
}

View file

@ -0,0 +1,40 @@
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_nano/nano_client.dart';
import 'package:cw_nano/nano_util.dart';
class PendingNanoTransaction with PendingTransaction {
PendingNanoTransaction({
required this.nanoClient,
required this.amount,
required this.id,
required this.blocks,
});
final NanoClient nanoClient;
final BigInt amount;
final String id;
final List<Map<String, String>> blocks;
String hex = "unused";
@override
String get amountFormatted {
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
return amt;
}
String get accurateAmountFormatted {
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano);
return "$acc$amt";
}
@override
String get feeFormatted => "0";
@override
Future<void> commit() async {
for (var block in blocks) {
await nanoClient.processBlock(block, "send");
}
}
}

756
cw_nano/pubspec.lock Normal file
View file

@ -0,0 +1,756 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
url: "https://pub.dev"
source: hosted
version: "47.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
url: "https://pub.dev"
source: hosted
version: "4.7.0"
args:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.4.2"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: b74e3842a52c61f8819a1ec8444b4de5419b41a7465e69d4aa681445377398b0
url: "https://pub.dev"
source: hosted
version: "1.4.1"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
bip32:
dependency: "direct main"
description:
name: bip32
sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
bip39:
dependency: "direct main"
description:
name: bip39
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
url: "https://pub.dev"
source: hosted
version: "1.0.6"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bs58check:
dependency: transitive
description:
name: bs58check
sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9
url: "https://pub.dev"
source: hosted
version: "1.0.2"
build:
dependency: transitive
description:
name: build
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
build_config:
dependency: transitive
description:
name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
url: "https://pub.dev"
source: hosted
version: "1.1.1"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
url: "https://pub.dev"
source: hosted
version: "2.0.10"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
url: "https://pub.dev"
source: hosted
version: "2.3.3"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e"
url: "https://pub.dev"
source: hosted
version: "7.2.7+1"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
url: "https://pub.dev"
source: hosted
version: "8.6.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
url: "https://pub.dev"
source: hosted
version: "4.5.0"
collection:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev"
source: hosted
version: "1.17.1"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cw_core:
dependency: "direct main"
description:
path: "../cw_core"
relative: true
source: path
version: "0.0.1"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
decimal:
dependency: "direct main"
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
ed25519_hd_key:
dependency: "direct main"
description:
name: ed25519_hd_key
sha256: "326608234e986ea826a5db4cf4cd6826058d860875a3fff7926c0725fe1a604d"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
encrypt:
dependency: transitive
description:
name: encrypt
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
url: "https://pub.dev"
source: hosted
version: "5.0.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
url: "https://pub.dev"
source: hosted
version: "2.0.2"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
fixnum_nanodart:
dependency: transitive
description:
name: fixnum_nanodart
sha256: "4b0132d11ecddc0d2ca64b6d7dee6726db432ed02cac1349d7532a08be5c54fc"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_mobx:
dependency: transitive
description:
name: flutter_mobx
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
url: "https://pub.dev"
source: hosted
version: "2.0.6+5"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
glob:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
graphs:
dependency: transitive
description:
name: graphs
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev"
source: hosted
version: "2.3.1"
hex:
dependency: "direct main"
description:
name: hex
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
hive:
dependency: transitive
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
http:
dependency: "direct main"
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
intl:
dependency: transitive
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev"
source: hosted
version: "4.8.1"
libcrypto:
dependency: "direct main"
description:
name: libcrypto
sha256: "18a97db8d88147b0b60d2755f29b5e4944181c4c1a9f52bd1ecbea1b0a5aab03"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
logging:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted
version: "0.2.0"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mobx:
dependency: "direct main"
description:
name: mobx
sha256: "0afcf88b3ee9d6819890bf16c11a727fc8c62cf736fda8e5d3b9b4eace4e62ea"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
mobx_codegen:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
url: "https://pub.dev"
source: hosted
version: "2.3.0"
nanodart:
dependency: "direct main"
description:
name: nanodart
sha256: "4b2f42d60307b54e8cf384d6193a567d07f8efd773858c0d5948246153c13282"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
url: "https://pub.dev"
source: hosted
version: "2.0.15"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
url: "https://pub.dev"
source: hosted
version: "2.0.27"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
url: "https://pub.dev"
source: hosted
version: "2.1.11"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
url: "https://pub.dev"
source: hosted
version: "2.0.6"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev"
source: hosted
version: "2.1.7"
pinenacl:
dependency: transitive
description:
name: pinenacl
sha256: e5fb0bce1717b7f136f35ee98b5c02b3e6383211f8a77ca882fa7812232a07b9
url: "https://pub.dev"
source: hosted
version: "0.3.4"
platform:
dependency: transitive
description:
name: platform
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev"
source: hosted
version: "1.2.3"
rational:
dependency: transitive
description:
name: rational
sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shelf:
dependency: transitive
description:
name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
version: "1.4.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
url: "https://pub.dev"
source: hosted
version: "1.2.6"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
source_span:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted
version: "1.9.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
url: "https://pub.dev"
source: hosted
version: "0.5.1"
timing:
dependency: transitive
description:
name: timing
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
watcher:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.4.0"
win32:
dependency: transitive
description:
name: win32
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
url: "https://pub.dev"
source: hosted
version: "4.1.4"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
url: "https://pub.dev"
source: hosted
version: "1.0.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.0.0 <4.0.0"
flutter: ">=3.3.0"

69
cw_nano/pubspec.yaml Normal file
View file

@ -0,0 +1,69 @@
name: cw_nano
description: A new Flutter package project.
version: 0.0.1
publish_to: none
author: Cake Wallet
homepage: https://cakewallet.com
environment:
sdk: '>=2.18.2 <3.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
mobx: ^2.0.7+4
bip39: ^1.0.6
bip32: ^2.0.0
nanodart: ^2.0.0
decimal: ^2.3.3
libcrypto: ^0.2.2
ed25519_hd_key: ^2.2.0
hex: ^0.2.0
http: ^1.1.0
cw_core:
path: ../cw_core
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.1.11
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

View file

@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:cw_nano/cw_nano.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}

View file

@ -161,4 +161,4 @@ class CWBitcoin extends Bitcoin {
@override
TransactionPriority getLitecoinTransactionPrioritySlow()
=> LitecoinTransactionPriority.slow;
}
}

View file

@ -27,6 +27,8 @@ class OnRamperBuyProvider {
return "LTC_LITECOIN";
case CryptoCurrency.xmr:
return "XMR_MONERO";
case CryptoCurrency.nano:
return "XNO_NANO";
default:
return _wallet.currency.title;
}

View file

@ -28,6 +28,8 @@ class AddressValidator extends TextValidator {
return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.banano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.usdc:
case CryptoCurrency.usdcpoly:
case CryptoCurrency.ape:
@ -177,6 +179,8 @@ class AddressValidator extends TextValidator {
return [34, 43, 63];
case CryptoCurrency.nano:
return [64, 65];
case CryptoCurrency.banano:
return [64, 65];
case CryptoCurrency.sc:
return [76];
case CryptoCurrency.sol:
@ -267,4 +271,4 @@ class AddressValidator extends TextValidator {
return null;
}
}
}
}

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/utils/language_list.dart';
class SeedValidator extends Validator<MnemonicItem> {
@ -28,6 +29,9 @@ class SeedValidator extends Validator<MnemonicItem> {
return haven!.getMoneroWordList(language);
case WalletType.ethereum:
return ethereum!.getEthereumWordList(language);
case WalletType.nano:
case WalletType.banano:
return nano!.getNanoWordList(language);
default:
return [];
}

View file

@ -0,0 +1,5 @@
abstract class ChainService {
String getNamespace();
String getChainId();
List<String> getEvents();
}

View file

@ -0,0 +1,60 @@
class WCEthereumTransactionModel {
final String from;
final String to;
final String value;
final String? nonce;
final String? gasPrice;
final String? maxFeePerGas;
final String? maxPriorityFeePerGas;
final String? gas;
final String? gasLimit;
final String? data;
WCEthereumTransactionModel({
required this.from,
required this.to,
required this.value,
this.nonce,
this.gasPrice,
this.maxFeePerGas,
this.maxPriorityFeePerGas,
this.gas,
this.gasLimit,
this.data,
});
factory WCEthereumTransactionModel.fromJson(Map<String, dynamic> json) {
return WCEthereumTransactionModel(
from: json['from'] as String,
to: json['to'] as String,
value: json['value'] as String,
nonce: json['nonce'] as String?,
gasPrice: json['gasPrice'] as String?,
maxFeePerGas: json['maxFeePerGas'] as String?,
maxPriorityFeePerGas: json['maxPriorityFeePerGas'] as String?,
gas: json['gas'] as String?,
gasLimit: json['gasLimit'] as String?,
data: json['data'] as String?,
);
}
Map<String, dynamic> toJson() {
return {
'from': from,
'to': to,
'value': value,
'nonce': nonce,
'gasPrice': gasPrice,
'maxFeePerGas': maxFeePerGas,
'maxPriorityFeePerGas': maxPriorityFeePerGas,
'gas': gas,
'gasLimit': gasLimit,
'data': data,
};
}
@override
String toString() {
return 'EthereumTransactionModel(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)';
}
}

View file

@ -0,0 +1,35 @@
import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart';
enum EVMChainId {
ethereum,
polygon,
goerli,
mumbai,
arbitrum,
}
extension EVMChainIdX on EVMChainId {
String chain() {
String name = '';
switch (this) {
case EVMChainId.ethereum:
name = '1';
break;
case EVMChainId.polygon:
name = '137';
break;
case EVMChainId.goerli:
name = '5';
break;
case EVMChainId.arbitrum:
name = '42161';
break;
case EVMChainId.mumbai:
name = '80001';
break;
}
return '${EvmChainServiceImpl.namespace}:$name';
}
}

View file

@ -0,0 +1,294 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart';
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/error_display_widget.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
import 'package:cake_wallet/core/wallet_connect/models/connection_model.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
import 'package:convert/convert.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:eth_sig_util/util/utils.dart';
import 'package:http/http.dart' as http;
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
import 'package:web3dart/web3dart.dart';
import 'chain_service.dart';
import 'wallet_connect_key_service.dart';
class EvmChainServiceImpl implements ChainService {
final AppStore appStore;
final BottomSheetService bottomSheetService;
final Web3Wallet wallet;
final WalletConnectKeyService wcKeyService;
static const namespace = 'eip155';
static const pSign = 'personal_sign';
static const eSign = 'eth_sign';
static const eSignTransaction = 'eth_signTransaction';
static const eSignTypedData = 'eth_signTypedData_v4';
static const eSendTransaction = 'eth_sendTransaction';
final EVMChainId reference;
final Web3Client ethClient;
EvmChainServiceImpl({
required this.reference,
required this.appStore,
required this.wcKeyService,
required this.bottomSheetService,
required this.wallet,
Web3Client? ethClient,
}) : ethClient = ethClient ??
Web3Client(
appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(),
http.Client(),
) {
for (final String event in getEvents()) {
wallet.registerEventEmitter(chainId: getChainId(), event: event);
}
wallet.registerRequestHandler(
chainId: getChainId(),
method: pSign,
handler: personalSign,
);
wallet.registerRequestHandler(
chainId: getChainId(),
method: eSign,
handler: ethSign,
);
wallet.registerRequestHandler(
chainId: getChainId(),
method: eSignTransaction,
handler: ethSignTransaction,
);
wallet.registerRequestHandler(
chainId: getChainId(),
method: eSendTransaction,
handler: ethSignTransaction,
);
wallet.registerRequestHandler(
chainId: getChainId(),
method: eSignTypedData,
handler: ethSignTypedData,
);
}
@override
String getNamespace() {
return namespace;
}
@override
String getChainId() {
return reference.chain();
}
@override
List<String> getEvents() {
return ['chainChanged', 'accountsChanged'];
}
Future<String?> requestAuthorization(String? text) async {
// Show the bottom sheet
final bool? isApproved = await bottomSheetService.queueBottomSheet(
widget: Web3RequestModal(
child: ConnectionWidget(
title: S.current.signTransaction,
info: [
ConnectionModel(
text: text,
),
],
),
),
) as bool?;
if (isApproved != null && isApproved == false) {
return 'User rejected signature';
}
return null;
}
Future<String> personalSign(String topic, dynamic parameters) async {
log('received personal sign request: $parameters');
final String message;
if (parameters[0] == null) {
message = '';
} else {
message = parameters[0].toString().utf8Message;
}
final String? authError = await requestAuthorization(message);
if (authError != null) {
return authError;
}
try {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
final String signature = hex.encode(
credentials.signPersonalMessageToUint8List(Uint8List.fromList(utf8.encode(message))),
);
return '0x$signature';
} catch (e) {
log(e.toString());
bottomSheetService.queueBottomSheet(
isModalDismissible: true,
widget: BottomSheetMessageDisplayWidget(
message: '${S.current.errorGettingCredentials} ${e.toString()}',
),
);
return 'Failed: Error while getting credentials';
}
}
Future<String> ethSign(String topic, dynamic parameters) async {
log('received eth sign request: $parameters');
final String message;
if (parameters[1] == null) {
message = '';
} else {
message = parameters[1].toString().utf8Message;
}
final String? authError = await requestAuthorization(message);
if (authError != null) {
return authError;
}
try {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
final String signature = hex.encode(
credentials.signPersonalMessageToUint8List(
Uint8List.fromList(utf8.encode(message)),
),
);
log(signature);
return '0x$signature';
} catch (e) {
log('error: ${e.toString()}');
bottomSheetService.queueBottomSheet(
isModalDismissible: true,
widget: BottomSheetMessageDisplayWidget(message: '${S.current.error}: ${e.toString()}'),
);
return 'Failed';
}
}
Future<String> ethSignTransaction(String topic, dynamic parameters) async {
log('received eth sign transaction request: $parameters');
final paramsData = parameters[0] as Map<String, dynamic>;
final message = _convertToReadable(paramsData);
final String? authError = await requestAuthorization(message);
if (authError != null) {
return authError;
}
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
WCEthereumTransactionModel ethTransaction =
WCEthereumTransactionModel.fromJson(parameters[0] as Map<String, dynamic>);
final transaction = Transaction(
from: EthereumAddress.fromHex(ethTransaction.from),
to: EthereumAddress.fromHex(ethTransaction.to),
maxGas: ethTransaction.gasLimit != null ? int.tryParse(ethTransaction.gasLimit ?? "") : null,
gasPrice: ethTransaction.gasPrice != null
? EtherAmount.inWei(BigInt.parse(ethTransaction.gasPrice ?? ""))
: null,
value: EtherAmount.inWei(BigInt.parse(ethTransaction.value)),
data: hexToBytes(ethTransaction.data ?? ""),
nonce: ethTransaction.nonce != null ? int.tryParse(ethTransaction.nonce ?? "") : null,
);
try {
final result = await ethClient.sendTransaction(credentials, transaction);
log('Result: $result');
bottomSheetService.queueBottomSheet(
isModalDismissible: true,
widget: BottomSheetMessageDisplayWidget(
message: S.current.awaitDAppProcessing,
isError: false,
),
);
return result;
} catch (e) {
log('An error has occured while signing transaction: ${e.toString()}');
bottomSheetService.queueBottomSheet(
isModalDismissible: true,
widget: BottomSheetMessageDisplayWidget(
message: '${S.current.errorSigningTransaction}: ${e.toString()}',
),
);
return 'Failed';
}
}
Future<String> ethSignTypedData(String topic, dynamic parameters) async {
log('received eth sign typed data request: $parameters');
final String? data = parameters[1] as String?;
final String? authError = await requestAuthorization(data);
if (authError != null) {
return authError;
}
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
return EthSigUtil.signTypedData(
privateKey: keys[0].privateKey,
jsonData: data ?? '',
version: TypedDataVersion.V4,
);
}
String _convertToReadable(Map<String, dynamic> data) {
String gas = int.parse((data['gas'] as String).substring(2), radix: 16).toString();
String value = data['value'] != null
? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() + ' ETH'
: '0 ETH';
String from = data['from'] as String;
String to = data['to'] as String;
return '''
Gas: $gas\n
Value: $value\n
From: $from\n
To: $to
''';
}
}

View file

@ -0,0 +1,16 @@
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
class AuthRequestModel {
final String iss;
final AuthRequest request;
AuthRequestModel({
required this.iss,
required this.request,
});
@override
String toString() {
return 'AuthRequestModel(iss: $iss, request: $request)';
}
}

View file

@ -0,0 +1,20 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
class BottomSheetQueueItemModel {
final Widget widget;
final bool isModalDismissible;
final Completer<dynamic> completer;
BottomSheetQueueItemModel({
required this.widget,
required this.completer,
this.isModalDismissible = false,
});
@override
String toString() {
return 'BottomSheetQueueItemModel(widget: $widget, completer: $completer)';
}
}

View file

@ -0,0 +1,16 @@
class ChainKeyModel {
final List<String> chains;
final String privateKey;
final String publicKey;
ChainKeyModel({
required this.chains,
required this.privateKey,
required this.publicKey,
});
@override
String toString() {
return 'ChainKeyModel(chains: $chains, privateKey: $privateKey, publicKey: $publicKey)';
}
}

View file

@ -0,0 +1,18 @@
class ConnectionModel {
final String? title;
final String? text;
final List<String>? elements;
final Map<String, void Function()>? elementActions;
ConnectionModel({
this.title,
this.text,
this.elements,
this.elementActions,
});
@override
String toString() {
return 'WalletConnectRequestModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)';
}
}

View file

@ -0,0 +1,14 @@
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
class SessionRequestModel {
final ProposalData request;
SessionRequestModel({
required this.request,
});
@override
String toString() {
return 'SessionRequestModel(request: $request)';
}
}

View file

@ -0,0 +1,72 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_base.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();
/// 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);
/// 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: ethereum!.getPrivateKey(wallet),
publicKey: ethereum!.getPublicKey(wallet),
),
];
late final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
late final List<ChainKeyModel> _keys;
@override
List<String> getChains() {
final List<String> chainIds = [];
for (final ChainKeyModel key in _keys) {
chainIds.addAll(key.chains);
}
return chainIds;
}
@override
List<ChainKeyModel> getKeys() => _keys;
@override
List<ChainKeyModel> getKeysForChain(String chain) {
return _keys.where((e) => e.chains.contains(chain)).toList();
}
@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;
}
}

View file

@ -0,0 +1,43 @@
import 'dart:async';
import 'package:cake_wallet/core/wallet_connect/models/bottom_sheet_queue_item_model.dart';
import 'package:flutter/material.dart';
abstract class BottomSheetService {
abstract final ValueNotifier<BottomSheetQueueItemModel?> currentSheet;
Future<dynamic> queueBottomSheet({
required Widget widget,
bool isModalDismissible = false,
});
void resetCurrentSheet();
}
class BottomSheetServiceImpl implements BottomSheetService {
@override
final ValueNotifier<BottomSheetQueueItemModel?> currentSheet = ValueNotifier(null);
@override
Future<dynamic> queueBottomSheet({
required Widget widget,
bool isModalDismissible = false,
}) async {
// Create the bottom sheet queue item
final completer = Completer<dynamic>();
final queueItem = BottomSheetQueueItemModel(
widget: widget,
completer: completer,
isModalDismissible: isModalDismissible,
);
currentSheet.value = queueItem;
return await completer.future;
}
@override
void resetCurrentSheet() {
currentSheet.value = null;
}
}

View file

@ -0,0 +1,277 @@
import 'dart:async';
import 'dart:developer';
import 'dart:typed_data';
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/error_display_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
import 'wc_bottom_sheet_service.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
part 'web3wallet_service.g.dart';
class Web3WalletService = Web3WalletServiceBase with _$Web3WalletService;
abstract class Web3WalletServiceBase with Store {
final AppStore appStore;
final BottomSheetService _bottomSheetHandler;
final WalletConnectKeyService walletKeyService;
late Web3Wallet _web3Wallet;
@observable
bool isInitialized;
/// The list of requests from the dapp
/// Potential types include, but aren't limited to:
/// [SessionProposalEvent], [AuthRequest]
@observable
ObservableList<PairingInfo> pairings;
@observable
ObservableList<SessionData> sessions;
@observable
ObservableList<StoredCacao> auth;
Web3WalletServiceBase(this._bottomSheetHandler, this.walletKeyService, this.appStore)
: pairings = ObservableList<PairingInfo>(),
sessions = ObservableList<SessionData>(),
auth = ObservableList<StoredCacao>(),
isInitialized = false;
@action
void create() {
// Create the web3wallet client
_web3Wallet = Web3Wallet(
core: Core(projectId: secrets.walletConnectProjectId),
metadata: const PairingMetadata(
name: 'Cake Wallet',
description: 'Cake Wallet',
url: 'https://cakewallet.com',
icons: ['https://cakewallet.com/assets/image/cake_logo.png'],
),
);
// Setup our accounts
List<ChainKeyModel> chainKeys = walletKeyService.getKeys();
for (final chainKey in chainKeys) {
for (final chainId in chainKey.chains) {
_web3Wallet.registerAccount(
chainId: chainId,
accountAddress: chainKey.publicKey,
);
}
}
// Setup our listeners
log('Created instance of web3wallet');
_web3Wallet.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid);
_web3Wallet.core.pairing.onPairingCreate.subscribe(_onPairingCreate);
_web3Wallet.core.pairing.onPairingDelete.subscribe(_onPairingDelete);
_web3Wallet.core.pairing.onPairingExpire.subscribe(_onPairingDelete);
_web3Wallet.pairings.onSync.subscribe(_onPairingsSync);
_web3Wallet.onSessionProposal.subscribe(_onSessionProposal);
_web3Wallet.onSessionProposalError.subscribe(_onSessionProposalError);
_web3Wallet.onSessionConnect.subscribe(_onSessionConnect);
_web3Wallet.onAuthRequest.subscribe(_onAuthRequest);
}
@action
Future<void> init() async {
// Await the initialization of the web3wallet
log('Intializing web3wallet');
if (!isInitialized) {
try {
await _web3Wallet.init();
log('Initialized');
isInitialized = true;
} catch (e) {
log('Experimentallllll: $e');
isInitialized = false;
}
}
_refreshPairings();
final newSessions = _web3Wallet.sessions.getAll();
sessions.addAll(newSessions);
final newAuthRequests = _web3Wallet.completeRequests.getAll();
auth.addAll(newAuthRequests);
for (final cId in EVMChainId.values) {
EvmChainServiceImpl(
reference: cId,
appStore: appStore,
wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler,
wallet: _web3Wallet,
);
}
}
@action
FutureOr<void> onDispose() {
log('web3wallet dispose');
_web3Wallet.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid);
_web3Wallet.pairings.onSync.unsubscribe(_onPairingsSync);
_web3Wallet.onSessionProposal.unsubscribe(_onSessionProposal);
_web3Wallet.onSessionProposalError.unsubscribe(_onSessionProposalError);
_web3Wallet.onSessionConnect.unsubscribe(_onSessionConnect);
_web3Wallet.onAuthRequest.unsubscribe(_onAuthRequest);
_web3Wallet.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete);
_web3Wallet.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete);
}
Web3Wallet getWeb3Wallet() {
return _web3Wallet;
}
void _onPairingsSync(StoreSyncEvent? args) {
if (args != null) {
_refreshPairings();
}
}
void _onPairingDelete(PairingEvent? event) {
_refreshPairings();
}
@action
void _refreshPairings() {
pairings.clear();
final allPairings = _web3Wallet.pairings.getAll();
pairings.addAll(allPairings);
}
Future<void> _onSessionProposalError(SessionProposalErrorEvent? args) async {
log(args.toString());
}
void _onSessionProposal(SessionProposalEvent? args) async {
if (args != null) {
final Widget modalWidget = Web3RequestModal(
child: ConnectionRequestWidget(
wallet: _web3Wallet,
sessionProposal: SessionRequestModel(request: args.params),
),
);
// show the bottom sheet
final bool? isApproved = await _bottomSheetHandler.queueBottomSheet(
widget: modalWidget,
) as bool?;
if (isApproved != null && isApproved) {
_web3Wallet.approveSession(
id: args.id,
namespaces: args.params.generatedNamespaces!,
);
} else {
_web3Wallet.rejectSession(
id: args.id,
reason: Errors.getSdkError(
Errors.USER_REJECTED,
),
);
}
}
}
@action
void _onPairingInvalid(PairingInvalidEvent? args) {
log('Pairing Invalid Event: $args');
_bottomSheetHandler.queueBottomSheet(
isModalDismissible: true,
widget: BottomSheetMessageDisplayWidget(message: '${S.current.pairingInvalidEvent}: $args'),
);
}
void _onPairingCreate(PairingEvent? args) {
log('Pairing Create Event: $args');
}
@action
void _onSessionConnect(SessionConnect? args) {
if (args != null) {
sessions.add(args.session);
}
}
@action
Future<void> _onAuthRequest(AuthRequest? args) async {
if (args != null) {
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain('eip155:1');
// Create the message to be signed
final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}';
final Widget modalWidget = Web3RequestModal(
child: ConnectionRequestWidget(
wallet: _web3Wallet,
authRequest: AuthRequestModel(iss: iss, request: args),
),
);
final bool? isAuthenticated = await _bottomSheetHandler.queueBottomSheet(
widget: modalWidget,
) as bool?;
if (isAuthenticated != null && isAuthenticated) {
final String message = _web3Wallet.formatAuthMessage(
iss: iss,
cacaoPayload: CacaoRequestPayload.fromPayloadParams(
args.payloadParams,
),
);
final String sig = EthSigUtil.signPersonalMessage(
message: Uint8List.fromList(message.codeUnits),
privateKey: chainKeys.first.privateKey,
);
await _web3Wallet.respondAuthRequest(
id: args.id,
iss: iss,
signature: CacaoSignature(
t: CacaoSignature.EIP191,
s: sig,
),
);
} else {
await _web3Wallet.respondAuthRequest(
id: args.id,
iss: iss,
error: Errors.getSdkError(
Errors.USER_REJECTED_AUTH,
),
);
}
}
}
@action
Future<void> disconnectSession(String topic) async {
final session = sessions.firstWhere((element) => element.pairingTopic == topic);
await _web3Wallet.core.pairing.disconnect(topic: topic);
await _web3Wallet.disconnectSession(
topic: session.topic, reason: Errors.getSdkError(Errors.USER_DISCONNECTED));
}
@action
List<SessionData> getSessionsForPairingInfo(PairingInfo pairing) {
return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
}
}

View file

@ -39,15 +39,11 @@ class WalletCreationService {
bool exists(String name) {
final walletName = name.toLowerCase();
return walletInfoSource
.values
.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
return walletInfoSource.values.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
}
bool typeExists(WalletType type) {
return walletInfoSource
.values
.any((walletInfo) => walletInfo.type == type);
return walletInfoSource.values.any((walletInfo) => walletInfo.type == type);
}
void checkIfExists(String name) {
@ -60,15 +56,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
final wallet = await _service!.create(credentials);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.create(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
return wallet;
@ -78,15 +71,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromKeys(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
return wallet;
@ -96,15 +86,12 @@ class WalletCreationService {
checkIfExists(credentials.name);
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
await keyService.saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service!.restoreFromSeed(credentials);
if (wallet.type == WalletType.monero) {
await sharedPreferences
.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
_isNewMoneroWalletPasswordUpdated);
await sharedPreferences.setBool(
PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
}
return wallet;

View file

@ -3,14 +3,17 @@ import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/receive_page_option.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart';
@ -25,8 +28,13 @@ import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/qr/scan_screen.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
@ -72,6 +80,9 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
@ -82,7 +93,9 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cw_core/wallet_service.dart';
@ -213,6 +226,7 @@ final getIt = GetIt.instance;
var _isSetupFinished = false;
late Box<WalletInfo> _walletInfoSource;
late Box<Node> _nodeSource;
late Box<Node> _powNodeSource;
late Box<Contact> _contactSource;
late Box<Trade> _tradesSource;
late Box<Template> _templates;
@ -225,6 +239,7 @@ late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
Future<void> setup({
required Box<WalletInfo> walletInfoSource,
required Box<Node> nodeSource,
required Box<Node> powNodeSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
required Box<Template> templates,
@ -236,6 +251,7 @@ Future<void> setup({
}) async {
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
_powNodeSource = powNodeSource;
_contactSource = contactSource;
_tradesSource = tradesSource;
_templates = templates;
@ -258,6 +274,7 @@ Future<void> setup({
final settingsStore = await SettingsStoreBase.load(
nodeSource: _nodeSource,
powNodeSource: _powNodeSource,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
// Enforce darkTheme on platforms other than mobile till the design for other themes is completed
initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile
@ -270,6 +287,7 @@ Future<void> setup({
}
getIt.registerFactory<Box<Node>>(() => _nodeSource);
getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
getIt.registerSingleton(AuthenticationStore());
@ -361,7 +379,7 @@ Future<void> setup({
(onAuthFinished, closable) => AuthPage(getIt.get<AuthViewModel>(),
onAuthenticationFinished: onAuthFinished, closable: closable));
getIt.registerFactory<Setup2FAViewModel>(
getIt.registerLazySingleton<Setup2FAViewModel>(
() => Setup2FAViewModel(
getIt.get<SettingsStore>(),
getIt.get<SharedPreferences>(),
@ -400,6 +418,10 @@ Future<void> setup({
}
if (appStore.wallet != null) {
authStore.allowed();
if (appStore.wallet!.type == WalletType.ethereum) {
getIt.get<Web3WalletService>().init();
}
return;
}
@ -420,6 +442,10 @@ Future<void> setup({
} else {
if (appStore.wallet != null) {
authStore.allowed();
if (appStore.wallet!.type == WalletType.ethereum) {
getIt.get<Web3WalletService>().init();
}
return;
}
@ -439,11 +465,28 @@ Future<void> setup({
}, closable: false);
}, instanceName: 'login');
getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
final appStore = getIt.get<AppStore>();
getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl(appStore.wallet!));
getIt.registerLazySingleton<Web3WalletService>(() {
final Web3WalletService web3WalletService = Web3WalletService(
getIt.get<BottomSheetService>(),
getIt.get<WalletConnectKeyService>(),
appStore,
);
web3WalletService.create();
return web3WalletService;
});
getIt.registerFactory(() => BalancePage(
dashboardViewModel: getIt.get<DashboardViewModel>(),
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactory<DashboardPage>(() => DashboardPage(
bottomSheetService: getIt.get<BottomSheetService>(),
balancePage: getIt.get<BalancePage>(),
dashboardViewModel: getIt.get<DashboardViewModel>(),
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
@ -460,6 +503,7 @@ Future<void> setup({
});
getIt.registerFactoryParam<DesktopDashboardPage, GlobalKey<NavigatorState>, void>(
(desktopKey, _) => DesktopDashboardPage(
bottomSheetService: getIt.get<BottomSheetService>(),
balancePage: getIt.get<BalancePage>(),
dashboardViewModel: getIt.get<DashboardViewModel>(),
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
@ -585,20 +629,30 @@ Future<void> setup({
editingWallet: editingWallet);
});
getIt.registerFactory(() {
getIt.registerFactory<NanoAccountListViewModel>(() {
final wallet = getIt.get<AppStore>().wallet!;
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
return NanoAccountListViewModel(wallet);
}
throw Exception(
'Unexpected wallet type: ${wallet.type} for generate Nano/Banano AccountListViewModel');
});
getIt.registerFactory<MoneroAccountListViewModel>(() {
final wallet = getIt.get<AppStore>().wallet!;
if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
return MoneroAccountListViewModel(wallet);
}
throw Exception(
'Unexpected wallet type: ${wallet.type} for generate MoneroAccountListViewModel');
'Unexpected wallet type: ${wallet.type} for generate Monero AccountListViewModel');
});
getIt.registerFactory(
() => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
getIt.registerFactory(
() => NanoAccountListPage(accountListViewModel: getIt.get<NanoAccountListViewModel>()));
/*getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
@ -626,6 +680,18 @@ Future<void> setup({
moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactoryParam<NanoAccountEditOrCreateViewModel, NanoAccount?, void>(
(NanoAccount? account, _) =>
NanoAccountEditOrCreateViewModel(nano!.getAccountList(getIt.get<AppStore>().wallet!),
// banano?.getAccountList(getIt.get<AppStore>().wallet!),
wallet: getIt.get<AppStore>().wallet!,
accountListItem: account));
getIt.registerFactoryParam<NanoAccountEditOrCreatePage, NanoAccount?, void>(
(NanoAccount? account, _) => NanoAccountEditOrCreatePage(
nanoAccountCreationViewModel:
getIt.get<NanoAccountEditOrCreateViewModel>(param1: account)));
getIt.registerFactory(() {
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
});
@ -669,7 +735,18 @@ Future<void> setup({
return NodeListViewModel(_nodeSource, appStore);
});
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
getIt.registerFactory(() {
final appStore = getIt.get<AppStore>();
return PowNodeListViewModel(_powNodeSource, appStore);
});
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
return ConnectionSyncPage(
getIt.get<DashboardViewModel>(),
wallet?.type == WalletType.ethereum ? getIt.get<Web3WalletService>() : null,
);
});
getIt.registerFactory(
() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
@ -680,13 +757,23 @@ Future<void> setup({
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>((WalletType? type, _) =>
NodeCreateOrEditViewModel(
_nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
getIt.registerFactory(() => NanoChangeRepPage(getIt.get<AppStore>().wallet!));
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, bool?>(
(WalletType? type, bool? isPow) => NodeCreateOrEditViewModel(
(isPow ?? false) ? _powNodeSource : _nodeSource,
type ?? getIt.get<AppStore>().wallet!.type,
getIt.get<SettingsStore>()));
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(),
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: false),
editingNode: editingNode,
isSelected: isSelected));
getIt.registerFactoryParam<PowNodeCreateOrEditPage, Node?, bool?>(
(Node? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: true),
editingNode: editingNode,
isSelected: isSelected));
@ -742,6 +829,8 @@ Future<void> setup({
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.ethereum:
return ethereum!.createEthereumWalletService(_walletInfoSource);
case WalletType.nano:
return nano!.createNanoWalletService(_walletInfoSource);
default:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
}
@ -769,6 +858,15 @@ Future<void> setup({
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>(
(type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, List<DerivationInfo>, void>(
(credentials, _) =>
WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>(
param1: credentials,
)));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) {
final wallet = getIt.get<AppStore>().wallet!;
@ -831,7 +929,7 @@ Future<void> setup({
wallet: wallet!);
});
getIt.registerFactoryParam<BuyWebViewPage, List, void>((List args, _) {
getIt.registerFactoryParam<BuyWebViewPage, List<dynamic>, void>((List<dynamic> args, _) {
final url = args.first as String;
final buyViewModel = args[1] as BuyViewModel;
@ -852,16 +950,15 @@ Future<void> setup({
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
getIt.registerFactory(() =>
SupportChatPage(
getIt.get<SupportViewModel>(), secureStorage: getIt.get<FlutterSecureStorage>()));
getIt.registerFactory(() => SupportChatPage(getIt.get<SupportViewModel>(),
secureStorage: getIt.get<FlutterSecureStorage>()));
getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>()));
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource!);
return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource);
});
getIt.registerFactory(() =>
@ -872,7 +969,7 @@ Future<void> setup({
(item, model) =>
UnspentCoinsDetailsViewModel(unspentCoinsItem: item, unspentCoinsListViewModel: model));
getIt.registerFactoryParam<UnspentCoinsDetailsPage, List, void>((List args, _) {
getIt.registerFactoryParam<UnspentCoinsDetailsPage, List<dynamic>, void>((List<dynamic> args, _) {
final item = args.first as UnspentCoinsItem;
final unspentCoinsListViewModel = args[1] as UnspentCoinsListViewModel;
@ -883,8 +980,8 @@ Future<void> setup({
getIt.registerFactory(() => YatService());
getIt.registerFactory(() => AddressResolver(
yatService: getIt.get<YatService>(), walletType: getIt.get<AppStore>().wallet!.type));
getIt.registerFactory(() =>
AddressResolver(yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
@ -925,7 +1022,7 @@ Future<void> setup({
getIt.registerFactory(() => IoniaLoginPage(getIt.get<IoniaAuthViewModel>()));
getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List, void>((List args, _) {
getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List<dynamic>, void>((List<dynamic> args, _) {
final email = args.first as String;
final isSignIn = args[1] as bool;
@ -934,13 +1031,14 @@ Future<void> setup({
getIt.registerFactory(() => IoniaWelcomePage());
getIt.registerFactoryParam<IoniaBuyGiftCardPage, List, void>((List args, _) {
getIt.registerFactoryParam<IoniaBuyGiftCardPage, List<dynamic>, void>((List<dynamic> args, _) {
final merchant = args.first as IoniaMerchant;
return IoniaBuyGiftCardPage(getIt.get<IoniaBuyCardViewModel>(param1: merchant));
});
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List, void>((List args, _) {
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List<dynamic>, void>(
(List<dynamic> args, _) {
final amount = args.first as double;
final merchant = args.last as IoniaMerchant;
return IoniaBuyGiftCardDetailPage(
@ -953,7 +1051,7 @@ Future<void> setup({
ioniaService: getIt.get<IoniaService>(), giftCard: giftCard);
});
getIt.registerFactoryParam<IoniaCustomTipViewModel, List, void>((List args, _) {
getIt.registerFactoryParam<IoniaCustomTipViewModel, List<dynamic>, void>((List<dynamic> args, _) {
final amount = args[0] as double;
final merchant = args[1] as IoniaMerchant;
final tip = args[2] as IoniaTip;
@ -966,7 +1064,7 @@ Future<void> setup({
return IoniaGiftCardDetailPage(getIt.get<IoniaGiftCardDetailsViewModel>(param1: giftCard));
});
getIt.registerFactoryParam<IoniaMoreOptionsPage, List, void>((List args, _) {
getIt.registerFactoryParam<IoniaMoreOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
final giftCard = args.first as IoniaGiftCard;
return IoniaMoreOptionsPage(giftCard);
@ -976,13 +1074,13 @@ Future<void> setup({
(IoniaGiftCard giftCard, _) =>
IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get<IoniaService>()));
getIt.registerFactoryParam<IoniaCustomRedeemPage, List, void>((List args, _) {
getIt.registerFactoryParam<IoniaCustomRedeemPage, List<dynamic>, void>((List<dynamic> args, _) {
final giftCard = args.first as IoniaGiftCard;
return IoniaCustomRedeemPage(getIt.get<IoniaCustomRedeemViewModel>(param1: giftCard));
});
getIt.registerFactoryParam<IoniaCustomTipPage, List, void>((List args, _) {
getIt.registerFactoryParam<IoniaCustomTipPage, List<dynamic>, void>((List<dynamic> args, _) {
return IoniaCustomTipPage(getIt.get<IoniaCustomTipViewModel>(param1: args));
});
@ -1049,7 +1147,12 @@ Future<void> setup({
),
);
getIt.registerFactory<ManageNodesPage>(() => ManageNodesPage(getIt.get<NodeListViewModel>()));
getIt.registerFactoryParam<ManageNodesPage, bool, void>((bool isPow, _) {
if (isPow) {
return ManageNodesPage(isPow, powNodeListViewModel: getIt.get<PowNodeListViewModel>());
}
return ManageNodesPage(isPow, nodeListViewModel: getIt.get<NodeListViewModel>());
});
getIt.registerFactory<ScanScreen>(() => ScanScreen());

View file

@ -26,12 +26,15 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
const nanoDefaultNodeUri = 'rpc.nano.to';
const nanoDefaultPowNodeUri = 'rpc.nano.to';
Future<void> defaultSettingsMigration(
{required int version,
required SharedPreferences sharedPreferences,
required FlutterSecureStorage secureStorage,
required Box<Node> nodes,
required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource,
required Box<Trade> tradeSource,
required Box<Contact> contactSource}) async {
@ -40,41 +43,36 @@ Future<void> defaultSettingsMigration(
}
// check current nodes for nullability regardless of the version
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
final isNewInstall =
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
await _validateWalletInfoBoxData(walletInfoSource);
final isNewInstall = sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall);
await sharedPreferences.setBool(
PreferencesKey.isNewInstall, isNewInstall);
final currentVersion =
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ?? 0;
final currentVersion = sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ??
0;
if (currentVersion >= version) {
return;
}
final migrationVersionsLength = version - currentVersion;
final migrationVersions = List<int>.generate(
migrationVersionsLength, (i) => currentVersion + (i + 1));
final migrationVersions =
List<int>.generate(migrationVersionsLength, (i) => currentVersion + (i + 1));
await Future.forEach(migrationVersions, (int version) async {
try {
switch (version) {
case 1:
await sharedPreferences.setString(
PreferencesKey.currentFiatCurrencyKey,
FiatCurrency.usd.toString());
await sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKeyLegacy,
PreferencesKey.currentFiatCurrencyKey, FiatCurrency.usd.toString());
await sharedPreferences.setInt(PreferencesKey.currentTransactionPriorityKeyLegacy,
monero!.getDefaultTransactionPriority().raw);
await sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey,
BalanceDisplayMode.availableBalance.raw);
PreferencesKey.currentBalanceDisplayModeKey, BalanceDisplayMode.availableBalance.raw);
await sharedPreferences.setBool('save_recipient_address', true);
await resetToDefault(nodes);
await changeMoneroCurrentNodeToDefault(
@ -83,14 +81,12 @@ Future<void> defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeHavenCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 2:
await replaceNodesMigration(nodes: nodes);
await replaceDefaultNode(
sharedPreferences: sharedPreferences, nodes: nodes);
await replaceDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 3:
@ -124,7 +120,7 @@ Future<void> defaultSettingsMigration(
break;
case 12:
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 13:
@ -135,14 +131,13 @@ Future<void> defaultSettingsMigration(
await addLitecoinElectrumServerList(nodes: nodes);
await changeLitecoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, sharedPreferences);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 16:
await addHavenNodeList(nodes: nodes);
await changeHavenCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, sharedPreferences);
await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
break;
case 17:
@ -164,6 +159,13 @@ Future<void> defaultSettingsMigration(
await changeEthereumCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 22:
await addNanoNodeList(nodes: nodes);
await addNanoPowNodeList(nodes: powNodes);
await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
await changeNanoCurrentPowNodeToDefault(
sharedPreferences: sharedPreferences, nodes: powNodes);
break;
default:
break;
@ -176,8 +178,7 @@ Future<void> defaultSettingsMigration(
}
});
await sharedPreferences.setInt(
PreferencesKey.currentDefaultSettingsMigrationVersion, version);
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
}
Future<void> _validateWalletInfoBoxData(Box<WalletInfo> walletInfoSource) async {
@ -247,8 +248,8 @@ Future<void> validateBitcoinSavedTransactionPriority(SharedPreferences sharedPre
final int? savedBitcoinPriority =
sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority);
if (!bitcoin!.getTransactionPriorities().any((element) => element.raw == savedBitcoinPriority)) {
await sharedPreferences.setInt(
PreferencesKey.bitcoinTransactionPriority, bitcoin!.getMediumTransactionPriority().serialize());
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
bitcoin!.getMediumTransactionPriority().serialize());
}
}
@ -265,10 +266,9 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
final replaceNodes = <String, Node>{
'eu-node.cakewallet.io:18081':
Node(uri: 'xmr-node-eu.cakewallet.com:18081', type: WalletType.monero),
'node.cakewallet.io:18081': Node(
uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero),
'node.xmr.ru:13666':
Node(uri: 'node.monero.net:18081', type: WalletType.monero)
'node.cakewallet.io:18081':
Node(uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero),
'node.xmr.ru:13666': Node(uri: 'node.monero.net:18081', type: WalletType.monero)
};
nodes.values.forEach((Node node) async {
@ -284,8 +284,7 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
}
Future<void> changeMoneroCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getMoneroDefaultNode(nodes: nodes);
final nodeId = node.key as int? ?? 0; // 0 - England
@ -293,27 +292,35 @@ Future<void> changeMoneroCurrentNodeToDefault(
}
Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
}
Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin);
return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin);
}
Node? getHavenDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == havenDefaultNodeUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == havenDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
}
Node? getEthereumDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == ethereumDefaultNodeUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == ethereumDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
}
Node? getNanoDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
}
Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
}
Node getMoneroDefaultNode({required Box<Node> nodes}) {
@ -329,16 +336,14 @@ Node getMoneroDefaultNode({required Box<Node> nodes}) {
}
try {
return nodes.values
.firstWhere((Node node) => node.uriRaw == nodeUri);
} catch(_) {
return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri);
} catch (_) {
return nodes.values.first;
}
}
Future<void> changeBitcoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final server = getBitcoinDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int? ?? 0;
@ -346,8 +351,7 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
}
Future<void> changeLitecoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final server = getLitecoinDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int? ?? 0;
@ -355,8 +359,7 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
}
Future<void> changeHavenCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getHavenDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
@ -364,25 +367,21 @@ Future<void> changeHavenCurrentNodeToDefault(
}
Future<void> replaceDefaultNode(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
const nodesForReplace = <String>[
'xmr-node-uk.cakewallet.com:18081',
'eu-node.cakewallet.io:18081',
'node.cakewallet.io:18081'
];
final currentNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentNode =
nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId);
final needToReplace =
currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
final currentNode = nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId);
final needToReplace = currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
if (!needToReplace) {
return;
}
await changeMoneroCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
}
Future<void> updateNodeTypes({required Box<Node> nodes}) async {
@ -421,14 +420,11 @@ Future<void> addHavenNodeList({required Box<Node> nodes}) async {
}
}
Future<void> addAddressesForMoneroWallets(
Box<WalletInfo> walletInfoSource) async {
final moneroWalletsInfo =
walletInfoSource.values.where((info) => info.type == WalletType.monero);
Future<void> addAddressesForMoneroWallets(Box<WalletInfo> walletInfoSource) async {
final moneroWalletsInfo = walletInfoSource.values.where((info) => info.type == WalletType.monero);
moneroWalletsInfo.forEach((info) async {
try {
final walletPath =
await pathForWallet(name: info.name, type: WalletType.monero);
final walletPath = await pathForWallet(name: info.name, type: WalletType.monero);
final addressFilePath = '$walletPath.address.txt';
final addressFile = File(addressFilePath);
@ -449,8 +445,7 @@ Future<void> updateDisplayModes(SharedPreferences sharedPreferences) async {
final currentBalanceDisplayMode =
sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey) ?? -1;
final balanceDisplayMode = currentBalanceDisplayMode < 2 ? 3 : 2;
await sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
await sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
}
Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
@ -464,10 +459,9 @@ Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
await secureStorage.write(key: key, value: password);
}
Future<void> changeTransactionPriorityAndFeeRateKeys(
SharedPreferences sharedPreferences) async {
final legacyTransactionPriority = sharedPreferences
.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
Future<void> changeTransactionPriorityAndFeeRateKeys(SharedPreferences sharedPreferences) async {
final legacyTransactionPriority =
sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
await sharedPreferences.setInt(
PreferencesKey.moneroTransactionPriority, legacyTransactionPriority);
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
@ -477,10 +471,8 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
Future<void> changeDefaultMoneroNode(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
final currentMoneroNodeId =
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentMoneroNode =
nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
final needToReplaceCurrentMoneroNode =
currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
@ -491,78 +483,87 @@ Future<void> changeDefaultMoneroNode(
}
});
final newCakeWalletNode =
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode);
if (needToReplaceCurrentMoneroNode) {
await sharedPreferences.setInt(
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
}
}
Future<void> checkCurrentNodes(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId =
sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentBitcoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentLitecoinElectrumSeverId = sharedPreferences
.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences
.getInt(PreferencesKey.currentHavenNodeIdKey);
final currentEthereumNodeId = sharedPreferences
.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentMoneroNode = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentBitcoinElectrumSeverId);
final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentEthereumNodeId);
final currentLitecoinElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentMoneroNode =
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId);
final currentLitecoinElectrumServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId);
final currentHavenNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId);
final currentEthereumNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentNanoPowNodeServer =
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
if (currentMoneroNode == null) {
final newCakeWalletNode =
Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode);
await sharedPreferences.setInt(
PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
}
if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
cakeWalletElectrum.key as int);
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
}
if (currentLitecoinElectrumServer == null) {
final cakeWalletElectrum =
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt(
PreferencesKey.currentLitecoinElectrumSererIdKey,
cakeWalletElectrum.key as int);
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
}
if (currentHavenNodeServer == null) {
final node = Node(uri: havenDefaultNodeUri, type: WalletType.haven);
await nodeSource.add(node);
await sharedPreferences.setInt(
PreferencesKey.currentHavenNodeIdKey, node.key as int);
await sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
}
if (currentEthereumNodeServer == null) {
final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
await nodeSource.add(node);
await sharedPreferences.setInt(
PreferencesKey.currentEthereumNodeIdKey, node.key as int);
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
}
if (currentNanoNodeServer == null) {
final node = Node(uri: nanoDefaultNodeUri, useSSL: true, type: WalletType.nano);
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
}
if (currentNanoPowNodeServer == null) {
Node? node = powNodeSource.values
.firstWhereOrNull((node) => node.uri.toString() == nanoDefaultPowNodeUri);
if (node == null) {
node = Node(uri: nanoDefaultPowNodeUri, useSSL: true, type: WalletType.nano);
await powNodeSource.add(node);
}
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
}
}
@ -570,31 +571,27 @@ Future<void> resetBitcoinElectrumServer(
Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
final currentElectrumSeverId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final oldElectrumServer = nodeSource.values.firstWhereOrNull(
(node) => node.uri.toString().contains('electrumx.cakewallet.com'));
var cakeWalletNode = nodeSource.values.firstWhereOrNull(
(node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
final oldElectrumServer = nodeSource.values
.firstWhereOrNull((node) => node.uri.toString().contains('electrumx.cakewallet.com'));
var cakeWalletNode = nodeSource.values
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
if (cakeWalletNode == null) {
cakeWalletNode =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
await nodeSource.add(cakeWalletNode);
}
if (currentElectrumSeverId == oldElectrumServer?.key) {
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey,
cakeWalletNode.key as int);
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int);
}
await oldElectrumServer?.delete();
}
Future<void> changeDefaultHavenNode(
Box<Node> nodeSource) async {
Future<void> changeDefaultHavenNode(Box<Node> nodeSource) async {
const previousHavenDefaultNodeUri = 'vault.havenprotocol.org:443';
final havenNodes = nodeSource.values.where(
(node) => node.uriRaw == previousHavenDefaultNodeUri);
final havenNodes = nodeSource.values.where((node) => node.uriRaw == previousHavenDefaultNodeUri);
havenNodes.forEach((node) async {
node.uriRaw = havenDefaultNodeUri;
await node.save();
@ -607,8 +604,8 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
return;
}
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey,
isExchangeDisabled ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
}
@ -623,10 +620,42 @@ Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
}
Future<void> changeEthereumCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getEthereumDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
}
Future<void> addNanoNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> addNanoPowNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoPowNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> changeNanoCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getNanoDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, nodeId);
}
Future<void> changeNanoCurrentPowNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getNanoDefaultPowNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
}

View file

@ -0,0 +1,46 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:ens_dart/ens_dart.dart';
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';
class EnsRecord {
static Future<String> fetchEnsAddress(String name, {WalletBase? wallet}) async {
Web3Client? _client;
if (wallet != null && wallet.type == WalletType.ethereum) {
_client = ethereum!.getWeb3Client(wallet);
}
if (_client == null) {
_client = Web3Client("https://ethereum.publicnode.com", Client());
}
try {
final ens = Ens(client: _client);
if (wallet != null) {
switch (wallet.type) {
case WalletType.monero:
return await ens.withName(name).getCoinAddress(CoinType.XMR);
case WalletType.bitcoin:
return await ens.withName(name).getCoinAddress(CoinType.BTC);
case WalletType.litecoin:
return await ens.withName(name).getCoinAddress(CoinType.LTC);
case WalletType.haven:
return await ens.withName(name).getCoinAddress(CoinType.XHV);
case WalletType.ethereum:
default:
return (await ens.withName(name).getAddress()).hex;
}
}
final addr = await ens.withName(name).getAddress();
return addr.hex;
} catch (e) {
print(e);
return "";
}
}
}

View file

@ -52,6 +52,8 @@ class MainActions {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.nano:
case WalletType.banano:
switch (defaultBuyProvider) {
case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy);
@ -73,7 +75,7 @@ class MainActions {
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).buy,
alertContent: S.of(context).buy_alert_content,
alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
@ -143,7 +145,7 @@ class MainActions {
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).sell,
alertContent: S.of(context).sell_alert_content,
alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
},

View file

@ -21,13 +21,12 @@ Future<List<Node>> loadDefaultNodes() async {
}
Future<List<Node>> loadBitcoinElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
final serverListRaw = await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[];
for (final raw in loadedServerList) {
if (raw is Map) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.bitcoin;
serverList.add(node);
@ -38,8 +37,7 @@ Future<List<Node>> loadBitcoinElectrumServerList() async {
}
Future<List<Node>> loadLitecoinElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
final serverListRaw = await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
final loadedServerList = loadYaml(serverListRaw) as YamlList;
final serverList = <Node>[];
@ -66,7 +64,7 @@ Future<List<Node>> loadDefaultHavenNodes() async {
nodes.add(node);
}
}
return nodes;
}
@ -86,17 +84,60 @@ Future<List<Node>> loadDefaultEthereumNodes() async {
return nodes;
}
Future<List<Node>> loadDefaultNanoNodes() async {
final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml');
final loadedNodes = loadYaml(nodesRaw) as YamlList;
final nodes = <Node>[];
for (final raw in loadedNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.nano;
nodes.add(node);
}
}
return nodes;
}
Future<List<Node>> loadDefaultNanoPowNodes() async {
final powNodesRaw = await rootBundle.loadString('assets/nano_pow_node_list.yml');
final loadedPowNodes = loadYaml(powNodesRaw) as YamlList;
final nodes = <Node>[];
for (final raw in loadedPowNodes) {
if (raw is Map) {
final node = Node.fromMap(Map<String, Object>.from(raw));
node.type = WalletType.nano;
nodes.add(node);
}
}
return nodes;
}
Future resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
final havenNodes = await loadDefaultHavenNodes();
final nodes =
moneroNodes +
final ethereumNodes = await loadDefaultEthereumNodes();
final nanoNodes = await loadDefaultNanoNodes();
final nodes = moneroNodes +
bitcoinElectrumServerList +
litecoinElectrumServerList +
havenNodes;
havenNodes +
ethereumNodes +
nanoNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);
}
Future resetPowToDefault(Box<Node> powNodeSource) async {
final nanoPowNodes = await loadDefaultNanoPowNodes();
final nodes = nanoPowNodes;
await powNodeSource.clear();
await powNodeSource.addAll(nodes);
}

View file

@ -1,19 +1,23 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/entities/ens_record.dart';
import 'package:cake_wallet/entities/openalias_record.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
import 'package:cake_wallet/entities/emoji_string_extension.dart';
import 'package:cake_wallet/mastodon/mastodon_api.dart';
import 'package:cake_wallet/twitter/twitter_api.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/entities/fio_address_provider.dart';
class AddressResolver {
AddressResolver({required this.yatService, required this.walletType});
AddressResolver({required this.yatService, required this.wallet}) : walletType = wallet.type;
final YatService yatService;
final WalletType walletType;
final WalletBase wallet;
static const unstoppableDomains = [
'crypto',
@ -63,13 +67,47 @@ class AddressResolver {
});
final userTweetsText = subString.toString();
final addressFromPinnedTweet =
extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker));
extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker));
if (addressFromPinnedTweet != null) {
return ParsedAddress.fetchTwitterAddress(address: addressFromPinnedTweet, name: text);
}
}
}
if (text.startsWith('@') && text.contains('@', 1) && text.contains('.', 1)) {
final subText = text.substring(1);
final hostNameIndex = subText.indexOf('@');
final hostName = subText.substring(hostNameIndex + 1);
final userName = subText.substring(0, hostNameIndex);
final mastodonUser =
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
if (mastodonUser != null) {
String? addressFromBio =
extractAddressByType(raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker));
if (addressFromBio != null) {
return ParsedAddress.fetchMastodonAddress(address: addressFromBio, name: text);
} else {
final pinnedPosts =
await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName);
if (pinnedPosts.isNotEmpty) {
final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
String? addressFromPinnedPost = extractAddressByType(
raw: userPinnedPostsText, type: CryptoCurrency.fromString(ticker));
if (addressFromPinnedPost != null) {
return ParsedAddress.fetchMastodonAddress(
address: addressFromPinnedPost, name: text);
}
}
}
}
}
if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) {
final bool isFioRegistered = await FioAddressProvider.checkAvail(text);
if (isFioRegistered) {
@ -96,6 +134,13 @@ class AddressResolver {
return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text);
}
if (text.endsWith(".eth")) {
final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet);
if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") {
return ParsedAddress.fetchEnsAddress(name: text, address: address);
}
}
if (formattedName.contains(".")) {
final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName);
if (txtRecord != null) {

View file

@ -1,7 +1,8 @@
import 'package:cake_wallet/entities/openalias_record.dart';
import 'package:cake_wallet/entities/yat_record.dart';
enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, contact }
enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, ens, contact, mastodon }
class ParsedAddress {
ParsedAddress({
@ -69,6 +70,14 @@ class ParsedAddress {
);
}
factory ParsedAddress.fetchMastodonAddress({required String address, required String name}){
return ParsedAddress(
addresses: [address],
name: name,
parseFrom: ParseFrom.mastodon
);
}
factory ParsedAddress.fetchContactAddress({required String address, required String name}){
return ParsedAddress(
addresses: [address],
@ -77,8 +86,17 @@ class ParsedAddress {
);
}
factory ParsedAddress.fetchEnsAddress({required String address, required String name}) {
return ParsedAddress(
addresses: [address],
name: name,
parseFrom: ParseFrom.ens,
);
}
final List<String> addresses;
final String name;
final String description;
final ParseFrom parseFrom;
}

View file

@ -6,6 +6,10 @@ class PreferencesKey {
static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
static const currentHavenNodeIdKey = 'current_node_id_xhv';
static const currentEthereumNodeIdKey = 'current_node_id_eth';
static const currentNanoNodeIdKey = 'current_node_id_nano';
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
static const currentBananoNodeIdKey = 'current_node_id_banano';
static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
static const currentFiatCurrencyKey = 'current_fiat_currency';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
@ -15,11 +19,9 @@ class PreferencesKey {
static const disableSellKey = 'disable_sell';
static const defaultBuyProvider = 'default_buy_provider';
static const currentFiatApiModeKey = 'current_fiat_api_mode';
static const allowBiometricalAuthenticationKey =
'allow_biometrical_authentication';
static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication';
static const useTOTP2FA = 'use_totp_2fa';
static const failedTotpTokenTrials = 'failed_token_trials';
static const totpSecretKey = 'totp_qr_secret_key';
static const disableExchangeKey = 'disable_exchange';
static const exchangeStatusKey = 'exchange_status';
static const currentTheme = 'current_theme';
@ -55,8 +57,7 @@ class PreferencesKey {
static const clearnetDonationLink = 'clearnet_donation_link';
static const onionDonationLink = 'onion_donation_link';
static const lastSeenAppVersion = 'last_seen_app_version';
static const shouldShowMarketPlaceInDashboard =
'should_show_marketplace_in_dashboard';
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
static const isNewInstall = 'is_new_install';
static const shouldRequireTOTP2FAForAccessingWallet =
'should_require_totp_2fa_for_accessing_wallets';
@ -75,4 +76,5 @@ class PreferencesKey {
static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
'should_require_totp_2fa_for_all_security_and_backup_settings';
static const selectedCake2FAPreset = 'selected_cake_2fa_preset';
static const totpSecretKey = 'totp_secret_key';
}

View file

@ -17,8 +17,11 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return haven!.getTransactionPriorities();
case WalletType.ethereum:
return ethereum!.getTransactionPriorities();
// no such thing for nano/banano:
case WalletType.nano:
case WalletType.banano:
return [];
default:
return [];
}
}

View file

@ -33,6 +33,20 @@ class CWEthereum extends Ethereum {
@override
String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as EthereumWallet).ethPrivateKey;
String stringKey = bytesToHex(privateKeyHolder.privateKey);
return stringKey;
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as EthereumWallet).ethPrivateKey;
final publicKey = privateKeyInUnitInt.address.hex;
return publicKey;
}
@override
TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
@ -131,4 +145,9 @@ class CWEthereum extends Ethereum {
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
(wallet as EthereumWallet).updateEtherscanUsageState(isEnabled);
}
@override
Web3Client? getWeb3Client(WalletBase wallet) {
return (wallet as EthereumWallet).getWeb3Client();
}
}

View file

@ -24,7 +24,10 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
static const trocador =
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 6, image: '');
static const exolix =
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) {
@ -41,6 +44,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
case 5:
return trocador;
case 6:
return exolix;
case 7:
return all;
default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');

View file

@ -0,0 +1,294 @@
import 'dart:convert';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:http/http.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_pair.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/exolix/exolix_request.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
class ExolixExchangeProvider extends ExchangeProvider {
ExolixExchangeProvider() : super(pairList: _supportedPairs());
static final apiKey = secrets.exolixApiKey;
static const apiBaseUrl = 'exolix.com';
static const transactionsPath = '/api/v2/transactions';
static const ratePath = '/api/v2/rate';
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.usdt,
CryptoCurrency.xhv,
CryptoCurrency.btt,
CryptoCurrency.firo,
CryptoCurrency.zaddr,
CryptoCurrency.xvg,
CryptoCurrency.kmd,
CryptoCurrency.paxg,
CryptoCurrency.rune,
CryptoCurrency.scrt,
CryptoCurrency.btcln,
CryptoCurrency.cro,
CryptoCurrency.ftm,
CryptoCurrency.frax,
CryptoCurrency.gusd,
CryptoCurrency.gtc,
CryptoCurrency.weth,
];
static List<ExchangePair> _supportedPairs() {
final supportedCurrencies =
CryptoCurrency.all.where((element) => !_notSupported.contains(element)).toList();
return supportedCurrencies
.map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
.expand((i) => i)
.toList();
}
@override
String get title => 'Exolix';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.exolix;
@override
Future<bool> checkIsAvailable() async => true;
static String getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float';
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final params = <String, String>{
'rateType': getRateType(isFixedRateMode),
'amount': '1',
};
if (isFixedRateMode) {
params['coinFrom'] = _normalizeCurrency(to);
params['coinTo'] = _normalizeCurrency(from);
params['networkFrom'] = _networkFor(to);
params['networkTo'] = _networkFor(from);
} else {
params['coinFrom'] = _normalizeCurrency(from);
params['coinTo'] = _normalizeCurrency(to);
params['networkFrom'] = _networkFor(from);
params['networkTo'] = _networkFor(to);
}
final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri);
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
return Limits(min: responseJSON['minAmount'] as double?);
}
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final _request = request as ExolixRequest;
final headers = {'Content-Type': 'application/json'};
final body = <String, dynamic>{
'coinFrom': _normalizeCurrency(_request.from),
'coinTo': _normalizeCurrency(_request.to),
'networkFrom': _networkFor(_request.from),
'networkTo': _networkFor(_request.to),
'withdrawalAddress': _request.address,
'refundAddress': _request.refundAddress,
'rateType': getRateType(isFixedRateMode),
'apiToken': apiKey,
};
if (isFixedRateMode) {
body['withdrawalAmount'] = _request.toAmount;
} else {
body['amount'] = _request.fromAmount;
}
final uri = Uri.https(apiBaseUrl, transactionsPath);
final response = await post(uri, headers: headers, body: json.encode(body));
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final errors = responseJSON['errors'] as Map<String, String>;
final errorMessage = errors.values.join(', ');
throw Exception(errorMessage);
}
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['id'] as String;
final inputAddress = responseJSON['depositAddress'] as String;
final refundAddress = responseJSON['refundAddress'] as String?;
final extraId = responseJSON['depositExtraId'] as String?;
final payoutAddress = responseJSON['withdrawalAddress'] as String;
final amount = responseJSON['amount'].toString();
return Trade(
id: id,
from: _request.from,
to: _request.to,
provider: description,
inputAddress: inputAddress,
refundAddress: refundAddress,
extraId: extraId,
createdAt: DateTime.now(),
amount: amount,
state: TradeState.created,
payoutAddress: payoutAddress);
}
@override
Future<Trade> findTradeById({required String id}) async {
final findTradeByIdPath = transactionsPath + '/$id';
final uri = Uri.https(apiBaseUrl, findTradeByIdPath);
final response = await get(uri);
if (response.statusCode == 404) {
throw TradeNotFoundException(id, provider: description);
}
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final errors = responseJSON['errors'] as Map<String, String>;
final errorMessage = errors.values.join(', ');
throw TradeNotFoundException(id, provider: description, description: errorMessage);
}
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final coinFrom = responseJSON['coinFrom']['coinCode'] as String;
final from = CryptoCurrency.fromString(coinFrom);
final coinTo = responseJSON['coinTo']['coinCode'] as String;
final to = CryptoCurrency.fromString(coinTo);
final inputAddress = responseJSON['depositAddress'] as String;
final amount = responseJSON['amount'].toString();
final status = responseJSON['status'] as String;
final state = TradeState.deserialize(raw: _prepareStatus(status));
final extraId = responseJSON['depositExtraId'] as String?;
final outputTransaction = responseJSON['hashOut']['hash'] as String?;
final payoutAddress = responseJSON['withdrawalAddress'] as String;
return Trade(
id: id,
from: from,
to: to,
provider: description,
inputAddress: inputAddress,
amount: amount,
state: state,
extraId: extraId,
outputTransaction: outputTransaction,
payoutAddress: payoutAddress);
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) {
return 0.0;
}
final params = <String, String>{
'coinFrom': _normalizeCurrency(from),
'coinTo': _normalizeCurrency(to),
'networkFrom': _networkFor(from),
'networkTo': _networkFor(to),
'rateType': getRateType(isFixedRateMode),
};
if (isReceiveAmount) {
params['withdrawalAmount'] = amount.toString();
} else {
params['amount'] = amount.toString();
}
final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode != 200) {
final message = responseJSON['message'] as String?;
throw Exception(message);
}
final rate = responseJSON['rate'] as double;
return rate;
} catch (e) {
print(e.toString());
return 0.0;
}
}
String _prepareStatus(String status) {
switch (status) {
case 'deleted':
case 'error':
return 'overdue';
default:
return status;
}
}
String _networkFor(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.arb:
return 'ARBITRUM';
default:
return currency.tag != null ? _normalizeTag(currency.tag!) : currency.title;
}
}
String _normalizeCurrency(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.nano:
return 'XNO';
case CryptoCurrency.bttc:
return 'BTT';
case CryptoCurrency.zec:
return 'ZEC';
default:
return currency.title;
}
}
String _normalizeTag(String tag) {
switch (tag) {
case 'POLY':
return 'Polygon';
default:
return tag;
}
}
}

View file

@ -0,0 +1,20 @@
import 'package:flutter/foundation.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
class ExolixRequest extends TradeRequest {
ExolixRequest(
{required this.from,
required this.to,
required this.address,
required this.fromAmount,
required this.toAmount,
required this.refundAddress});
CryptoCurrency from;
CryptoCurrency to;
String address;
String fromAmount;
String toAmount;
String refundAddress;
}

View file

@ -35,6 +35,15 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
static const completed = TradeState(raw: 'completed', title: 'Completed');
static const settling = TradeState(raw: 'settling', title: 'Settlement in progress');
static const settled = TradeState(raw: 'settled', title: 'Settlement completed');
static const wait = TradeState(raw: 'wait', title: 'Waiting');
static const overdue = TradeState(raw: 'overdue', title: 'Overdue');
static const refund = TradeState(raw: 'refund', title: 'Refund');
static const refunded = TradeState(raw: 'refunded', title: 'Refunded');
static const confirmation = TradeState(raw: 'confirmation', title: 'Confirmation');
static const confirmed = TradeState(raw: 'confirmed', title: 'Confirmed');
static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging');
static const sending = TradeState(raw: 'sending', title: 'Sending');
static const success = TradeState(raw: 'success', title: 'Success');
static TradeState deserialize({required String raw}) {
switch (raw) {
case 'pending':
@ -77,6 +86,24 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
return failed;
case 'completed':
return completed;
case 'wait':
return wait;
case 'overdue':
return overdue;
case 'refund':
return refund;
case 'refunded':
return refunded;
case 'confirmation':
return confirmation;
case 'confirmed':
return confirmed;
case 'exchanging':
return exchanging;
case 'sending':
return sending;
case 'success':
return success;
default:
throw Exception('Unexpected token: $raw in TradeState deserialize');
}

View file

@ -39,7 +39,6 @@ import 'package:cake_wallet/src/screens/root/root.dart';
import 'package:uni_links/uni_links.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/cake_hive.dart';
final navigatorKey = GlobalKey<NavigatorState>();
@ -98,6 +97,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(WalletInfoAdapter());
}
if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(DerivationTypeAdapter());
}
if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
CakeHive.registerAdapter(WalletTypeAdapter());
}
@ -129,6 +132,7 @@ Future<void> initializeAppConfigs() async {
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
final nodes = await CakeHive.openBox<Node>(Node.boxName);
final powNodes = await CakeHive.openBox<Node>(Node.boxName + "pow");// must be different from Node.boxName
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
@ -143,6 +147,7 @@ Future<void> initializeAppConfigs() async {
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
powNodes: powNodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
@ -154,12 +159,13 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 21);
initialMigrationVersion: 22);
}
Future<void> initialSetup(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes,
required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource,
required Box<Contact> contactSource,
required Box<Trade> tradesSource,
@ -180,10 +186,12 @@ Future<void> initialSetup(
walletInfoSource: walletInfoSource,
contactSource: contactSource,
tradeSource: tradesSource,
nodes: nodes);
nodes: nodes,
powNodes: powNodes);
await setup(
walletInfoSource: walletInfoSource,
nodeSource: nodes,
powNodeSource: powNodes,
contactSource: contactSource,
tradesSource: tradesSource,
templates: templates,
@ -308,26 +316,26 @@ class _Home extends StatefulWidget {
}
class _HomeState extends State<_Home> {
@override
@override
void didChangeDependencies() {
if(!ResponsiveLayoutUtil.instance.isMobile){
_setOrientation(context);
if (!ResponsiveLayoutUtil.instance.isMobile) {
_setOrientation(context);
}
super.didChangeDependencies();
}
void _setOrientation(BuildContext context){
void _setOrientation(BuildContext context) {
final orientation = MediaQuery.of(context).orientation;
final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
if (orientation == Orientation.portrait && width < height) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
} else if (orientation == Orientation.landscape && width > height) {
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
SystemChrome.setPreferredOrientations(
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
}
}
}
@override
Widget build(BuildContext context) {

View file

@ -0,0 +1,63 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:cake_wallet/mastodon/mastodon_user.dart';
class MastodonAPI {
static const httpsScheme = 'https';
static const userPath = '/api/v1/accounts/lookup';
static const statusesPath = '/api/v1/accounts/:id/statuses';
static Future<MastodonUser?> lookupUserByUserName(
{required String userName, required String apiHost}) async {
try {
final queryParams = {'acct': userName};
final uri = Uri(
scheme: httpsScheme,
host: apiHost,
path: userPath,
queryParameters: queryParams,
);
final response = await http.get(uri);
if (response.statusCode != 200) return null;
final Map<String, dynamic> responseJSON = json.decode(response.body) as Map<String, dynamic>;
return MastodonUser.fromJson(responseJSON);
} catch (e) {
print('Error in lookupUserByUserName: $e');
return null;
}
}
static Future<List<PinnedPost>> getPinnedPosts({
required String userId,
required String apiHost,
}) async {
try {
final queryParams = {'pinned': 'true'};
final uri = Uri(
scheme: httpsScheme,
host: apiHost,
path: statusesPath.replaceAll(':id', userId),
queryParameters: queryParams,
);
final response = await http.get(uri);
if (response.statusCode != 200) {
throw Exception('Unexpected HTTP status: ${response.statusCode}');
}
final List<dynamic> responseJSON = json.decode(response.body) as List<dynamic>;
return responseJSON.map((json) => PinnedPost.fromJson(json as Map<String, dynamic>)).toList();
} catch (e) {
print('Error in getPinnedPosts: $e');
throw e;
}
}
}

View file

@ -0,0 +1,36 @@
class MastodonUser {
String id;
String username;
String acct;
String note;
MastodonUser({
required this.id,
required this.username,
required this.acct,
required this.note,
});
factory MastodonUser.fromJson(Map<String, dynamic> json) {
return MastodonUser(
id: json['id'] as String,
username: json['username'] as String,
acct: json['acct'] as String,
note: json['note'] as String,
);
}
}
class PinnedPost {
final String id;
final String content;
PinnedPost({required this.id, required this.content});
factory PinnedPost.fromJson(Map<String, dynamic> json) {
return PinnedPost(
id: json['id'] as String,
content: json['content'] as String,
);
}
}

499
lib/nano/cw_nano.dart Normal file
View file

@ -0,0 +1,499 @@
part of 'nano.dart';
class CWNanoAccountList extends NanoAccountList {
CWNanoAccountList(this._wallet);
final Object _wallet;
@override
@computed
ObservableList<NanoAccount> get accounts {
final nanoWallet = _wallet as NanoWallet;
final accounts = nanoWallet.walletAddresses.accountList.accounts
.map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
.toList();
return ObservableList<NanoAccount>.of(accounts);
}
@override
void update(Object wallet) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.accountList.update(null);
}
@override
void refresh(Object wallet) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.accountList.refresh();
}
@override
Future<List<NanoAccount>> getAll(Object wallet) async {
final nanoWallet = wallet as NanoWallet;
return (await nanoWallet.walletAddresses.accountList.getAll())
.map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
.toList();
}
@override
Future<void> addAccount(Object wallet, {required String label}) async {
final nanoWallet = wallet as NanoWallet;
await nanoWallet.walletAddresses.accountList.addAccount(label: label);
}
@override
Future<void> setLabelAccount(Object wallet,
{required int accountIndex, required String label}) async {
final nanoWallet = wallet as NanoWallet;
await nanoWallet.walletAddresses.accountList
.setLabelAccount(accountIndex: accountIndex, label: label);
}
}
class CWNano extends Nano {
@override
NanoAccountList getAccountList(Object wallet) {
return CWNanoAccountList(wallet);
}
@override
Account getCurrentAccount(Object wallet) {
final nanoWallet = wallet as NanoWallet;
final acc = nanoWallet.walletAddresses.account;
return Account(id: acc!.id, label: acc.label, balance: acc.balance);
}
@override
void setCurrentAccount(Object wallet, int id, String label, String? balance) {
final nanoWallet = wallet as NanoWallet;
nanoWallet.walletAddresses.account = NanoAccount(id: id, label: label, balance: balance);
nanoWallet.regenerateAddress();
}
@override
List<String> getNanoWordList(String language) {
return NanoMnemomics.WORDLIST;
}
@override
WalletService createNanoWalletService(Box<WalletInfo> walletInfoSource) {
return NanoWalletService(walletInfoSource);
}
@override
Map<String, String> getKeys(Object wallet) {
final nanoWallet = wallet as NanoWallet;
final keys = nanoWallet.keys;
return <String, String>{
"seedKey": keys.seedKey,
};
}
@override
WalletCredentials createNanoNewWalletCredentials({
required String name,
String? password,
}) =>
NanoNewWalletCredentials(
name: name,
password: password,
);
@override
WalletCredentials createNanoRestoreWalletFromSeedCredentials({
required String name,
required String password,
required String mnemonic,
DerivationType? derivationType,
}) {
if (derivationType == null) {
// figure out the derivation type as best we can, otherwise set it to "unknown"
if (mnemonic.split(" ").length == 12) {
derivationType = DerivationType.bip39;
} else {
derivationType = DerivationType.unknown;
}
}
return NanoRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
derivationType: derivationType,
);
}
@override
WalletCredentials createNanoRestoreWalletFromKeysCredentials({
required String name,
required String password,
required String seedKey,
DerivationType? derivationType,
}) {
if (derivationType == null) {
// figure out the derivation type as best we can, otherwise set it to "unknown"
if (seedKey.length == 64) {
derivationType = DerivationType.nano;
} else {
derivationType = DerivationType.unknown;
}
}
return NanoRestoreWalletFromKeysCredentials(
name: name,
password: password,
seedKey: seedKey,
derivationType: derivationType,
);
}
@override
Object createNanoTransactionCredentials(List<Output> outputs) {
return NanoTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
))
.toList(),
);
}
@override
Future<void> changeRep(Object wallet, String address) async {
return (wallet as NanoWallet).changeRep(address);
}
@override
Future<void> updateTransactions(Object wallet) async {
return (wallet as NanoWallet).updateTransactions();
}
@override
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) {
return (transactionInfo as NanoTransactionInfo).amountRaw;
}
}
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);
}
// number util:
static const int maxDecimalDigits = 6; // Max digits after decimal
BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
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
///
@override
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 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";
}
@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 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();
}
@override
Future<AccountInfoResponse?> getInfoFromSeedOrMnemonic(
DerivationType derivationType, {
String? seedKey,
String? mnemonic,
required Node node,
}) async {
NanoClient nanoClient = NanoClient();
nanoClient.connect(node);
late String publicAddress;
if (seedKey != null) {
if (derivationType == DerivationType.bip39) {
publicAddress = await hdSeedToAddress(seedKey, 0);
} else if (derivationType == DerivationType.nano) {
publicAddress = await seedToAddress(seedKey, 0);
}
}
if (derivationType == DerivationType.bip39) {
if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
publicAddress = await hdSeedToAddress(seedKey, 0);
}
}
if (derivationType == DerivationType.nano) {
if (mnemonic != null) {
seedKey = await mnemonicToSeed(mnemonic);
publicAddress = await seedToAddress(seedKey, 0);
}
}
AccountInfoResponse? accountInfo = await nanoClient.getAccountInfo(publicAddress);
if (accountInfo == null) {
accountInfo = AccountInfoResponse(frontier: "", balance: "0", representative: "", confirmationHeight: 0);
}
accountInfo.address = publicAddress;
return accountInfo;
}
@override
Future<List<DerivationType>> compareDerivationMethods({
String? mnemonic,
String? privateKey,
required Node node,
}) async {
String? seedKey = privateKey;
if (mnemonic?.split(' ').length == 12) {
return [DerivationType.bip39];
}
if (seedKey?.length == 128) {
return [DerivationType.bip39];
} else if (seedKey?.length == 64) {
return [DerivationType.nano];
}
late String publicAddressStandard;
late String publicAddressBip39;
try {
NanoClient nanoClient = NanoClient();
nanoClient.connect(node);
if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
seedKey = await mnemonicToSeed(mnemonic);
publicAddressStandard = await seedToAddress(seedKey, 0);
} else if (seedKey != null) {
try {
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
} catch (e) {
return [DerivationType.nano];
}
try {
publicAddressStandard = await seedToAddress(seedKey, 0);
} catch (e) {
return [DerivationType.bip39];
}
}
// check if account has a history:
AccountInfoResponse? bip39Info;
AccountInfoResponse? standardInfo;
try {
bip39Info = await nanoClient.getAccountInfo(publicAddressBip39);
} catch (e) {
bip39Info = null;
}
try {
standardInfo = await nanoClient.getAccountInfo(publicAddressStandard);
} catch (e) {
standardInfo = null;
}
// one of these is *probably* null:
if (bip39Info == null && standardInfo != null) {
return [DerivationType.nano];
} else if (standardInfo == null && bip39Info != null) {
return [DerivationType.bip39];
}
// we don't know for sure:
return [DerivationType.nano, DerivationType.bip39];
} catch (e) {
return [DerivationType.unknown];
}
}
}

View file

@ -13,4 +13,11 @@ void startOnCurrentNodeChangeReaction(AppStore appStore) {
print(e.toString());
}
});
appStore.settingsStore.powNodes.observe((change) async {
try {
await appStore.wallet!.connectToPowNode(node: change.newValue!);
} catch (e) {
print(e.toString());
}
});
}

View file

@ -58,16 +58,23 @@ void startCurrentWalletChangeReaction(
}
final node = settingsStore.getCurrentNode(wallet.type);
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
startCheckConnectionReaction(wallet, settingsStore);
await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
await getIt
.get<SharedPreferences>()
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
if (wallet.type == WalletType.monero) {
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
}
await wallet.connectToNode(node: node);
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
final powNode = settingsStore.getCurrentPowNode(wallet.type);
await wallet.connectToPowNode(node: powNode);
}
if (wallet.type == WalletType.haven) {
await updateHavenRate(fiatConversionStore);
@ -101,7 +108,7 @@ void startCurrentWalletChangeReaction(
if (wallet.type == WalletType.ethereum) {
final currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
for (final currency in currencies) {
() async {

View file

@ -13,11 +13,15 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/qr/scan_screen.dart';
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
@ -52,6 +56,8 @@ import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dar
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
@ -112,36 +118,34 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.newWalletFromWelcome:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (availableWalletTypes.length == 1) {
Navigator.of(context.context).pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
} else {
Navigator.of(context.context).pushNamed(Routes.newWalletType);
}
}),
builder: (_) =>
getIt.get<SetupPinCodePage>(param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (availableWalletTypes.length == 1) {
Navigator.of(context.context)
.pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
} else {
Navigator.of(context.context).pushNamed(Routes.newWalletType);
}
}),
fullscreenDialog: true);
case Routes.newWalletType:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.newWallet, arguments: type)));
Navigator.of(context).pushNamed(Routes.newWallet, arguments: type)));
case Routes.newWallet:
final type = settings.arguments as WalletType;
final walletNewVM = getIt.get<WalletNewVM>(param1: type);
return CupertinoPageRoute<void>(
builder: (_) => NewWalletPage(walletNewVM));
return CupertinoPageRoute<void>(builder: (_) => NewWalletPage(walletNewVM));
case Routes.setupPin:
Function(PinCodeState<PinCodeWidget>, String)? callback;
if (settings.arguments is Function(PinCodeState<PinCodeWidget>, String)) {
callback =
settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
callback = settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
}
return CupertinoPageRoute<void>(
@ -151,8 +155,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.restoreWallet, arguments: type),
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
param2: false));
case Routes.restoreOptions:
@ -167,66 +170,62 @@ Route<dynamic> createRoute(RouteSettings settings) {
if (isNewInstall) {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
if (isSingleCoin) {
return Navigator.of(context.context)
.pushNamed(Routes.restoreWallet, arguments: availableWalletTypes.first);
}
return Navigator.pushNamed(
context.context, Routes.restoreWalletType);
return Navigator.pushNamed(context.context, Routes.restoreWalletType);
}),
fullscreenDialog: true);
} else if (isSingleCoin) {
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>(
param1: availableWalletTypes.first
));
builder: (_) => getIt.get<WalletRestorePage>(param1: availableWalletTypes.first));
} else {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.restoreWallet, arguments: type),
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
param2: false));
}
case Routes.seed:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
builder: (_) => getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
case Routes.restoreWallet:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestorePage>(
param1: settings.arguments as WalletType));
builder: (_) => getIt.get<WalletRestorePage>(param1: settings.arguments as WalletType));
case Routes.restoreWalletChooseDerivation:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletRestoreChooseDerivationPage>(
param1: settings.arguments as List<DerivationInfo>));
case Routes.sweepingWalletPage:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SweepingWalletPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SweepingWalletPage>());
case Routes.dashboard:
return CupertinoPageRoute<void>(
settings: settings,
builder: (_) => getIt.get<DashboardPage>());
settings: settings, builder: (_) => getIt.get<DashboardPage>());
case Routes.send:
final initialPaymentRequest = settings.arguments as PaymentRequest?;
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
));
fullscreenDialog: true,
builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest,
));
case Routes.sendTemplate:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SendTemplatePage>());
fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());
case Routes.receive:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ReceivePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ReceivePage>());
case Routes.addressPage:
return CupertinoPageRoute<void>(
@ -235,20 +234,21 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.transactionDetails:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<TransactionDetailsPage>(
param1: settings.arguments as TransactionInfo));
builder: (_) =>
getIt.get<TransactionDetailsPage>(param1: settings.arguments as TransactionInfo));
case Routes.newSubaddress:
return CupertinoPageRoute<void>(
builder: (_) =>
getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
builder: (_) => getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
case Routes.disclaimer:
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
case Routes.readDisclaimer:
return CupertinoPageRoute<void>(
builder: (_) => DisclaimerPage(isReadOnly: true));
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage(isReadOnly: true));
case Routes.changeRep:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
case Routes.seedLanguage:
final args = settings.arguments as List<dynamic>;
@ -257,8 +257,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(builder: (_) {
return SeedLanguage(
onConfirm: (context, lang) => Navigator.of(context)
.popAndPushNamed(redirectRoute, arguments: [type, lang]));
onConfirm: (context, lang) =>
Navigator.of(context).popAndPushNamed(redirectRoute, arguments: [type, lang]));
});
case Routes.walletList:
@ -268,15 +268,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.walletEdit:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<WalletEditPage>(
param1: settings.arguments as List<dynamic>));
builder: (_) => getIt.get<WalletEditPage>(param1: settings.arguments as List<dynamic>));
case Routes.auth:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished,
param2: true));
param1: settings.arguments as OnAuthenticationFinished, param2: true));
case Routes.totpAuthCodePage:
final args = settings.arguments as TotpAuthArgumentsModel;
@ -291,9 +289,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (context) => WillPopScope(
child: getIt.get<AuthPage>(instanceName: 'login'),
onWillPop: () async =>
// FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
onWillPop: () async =>
// FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
false),
),
fullscreenDialog: true);
@ -303,53 +301,54 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true,
builder: (_) => WillPopScope(
child: getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished,
param2: false),
param1: settings.arguments as OnAuthenticationFinished, param2: false),
onWillPop: () async => false));
case Routes.connectionSync:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ConnectionSyncPage>());
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
case Routes.securityBackupPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SecurityBackupPage>());
fullscreenDialog: true, builder: (_) => getIt.get<SecurityBackupPage>());
case Routes.privacyPage:
case Routes.privacyPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<PrivacyPage>());
fullscreenDialog: true, builder: (_) => getIt.get<PrivacyPage>());
case Routes.displaySettingsPage:
case Routes.displaySettingsPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<DisplaySettingsPage>());
fullscreenDialog: true, builder: (_) => getIt.get<DisplaySettingsPage>());
case Routes.otherSettingsPage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<OtherSettingsPage>());
fullscreenDialog: true, builder: (_) => getIt.get<OtherSettingsPage>());
case Routes.newNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?,
param2: args?['isSelected'] as bool?));
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
case Routes.newPowNode:
final args = settings.arguments as Map<String, dynamic>?;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<PowNodeCreateOrEditPage>(
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
case Routes.accountCreation:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
param1: settings.arguments as AccountListItem?));
case Routes.nanoAccountCreation:
return CupertinoPageRoute<String>(
builder: (_) =>
getIt.get<NanoAccountEditOrCreatePage>(param1: settings.arguments as NanoAccount?));
case Routes.addressBook:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ContactListPage>());
fullscreenDialog: true, builder: (_) => getIt.get<ContactListPage>());
case Routes.pickerAddressBook:
final selectedCurrency = settings.arguments as CryptoCurrency?;
@ -358,31 +357,26 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.addressBookAddContact:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ContactPage>(
param1: settings.arguments as ContactRecord?));
builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
case Routes.showKeys:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<WalletKeysPage>(), fullscreenDialog: true);
case Routes.exchangeTrade:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ExchangeTradePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTradePage>());
case Routes.exchangeConfirm:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<ExchangeConfirmPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<ExchangeConfirmPage>());
case Routes.tradeDetails:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
builder: (_) => getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
case Routes.orderDetails:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
case Routes.buy:
return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>());
@ -391,18 +385,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
final args = settings.arguments as List;
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<BuyWebViewPage>(param1: args));
fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
case Routes.exchange:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<ExchangePage>());
fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
case Routes.exchangeTemplate:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ExchangeTemplatePage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
case Routes.rescan:
return MaterialPageRoute<void>(builder: (_) => getIt.get<RescanPage>());
@ -412,51 +402,41 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.preSeed:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
builder: (_) => getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
case Routes.backup:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
case Routes.editBackupPassword:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<EditBackupPasswordPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<EditBackupPasswordPage>());
case Routes.restoreFromBackup:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<RestoreFromBackupPage>());
fullscreenDialog: true, builder: (_) => getIt.get<RestoreFromBackupPage>());
case Routes.support:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SupportPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportPage>());
case Routes.supportLiveChat:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SupportChatPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportChatPage>());
case Routes.supportOtherLinks:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SupportOtherLinksPage>());
fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>());
case Routes.unspentCoinsList:
return MaterialPageRoute<void>(
builder: (_) => getIt.get<UnspentCoinsListPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<UnspentCoinsListPage>());
case Routes.unspentCoinsDetails:
final args = settings.arguments as List;
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<UnspentCoinsDetailsPage>(
param1: args));
builder: (_) => getIt.get<UnspentCoinsDetailsPage>(param1: args));
case Routes.fullscreenQR:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<FullscreenQRPage>(
builder: (_) => getIt.get<FullscreenQRPage>(
param1: settings.arguments as QrViewData,
));
@ -467,26 +447,27 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
case Routes.ioniaLoginPage:
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaLoginPage>());
case Routes.ioniaCreateAccountPage:
return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCreateAccountPage>());
case Routes.ioniaManageCardsPage:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaManageCardsPage>());
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
case Routes.ioniaBuyGiftCardPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
case Routes.ioniaBuyGiftCardDetailPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
case Routes.ioniaVerifyIoniaOtpPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaVerifyIoniaOtp>(param1: args));
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaVerifyIoniaOtp>(param1: args));
case Routes.ioniaDebitCardPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
@ -502,57 +483,60 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.ioniaCustomTipPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaCustomTipPage>(param1: args));
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomTipPage>(param1: args));
case Routes.ioniaGiftCardDetailPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
case Routes.ioniaCustomRedeemPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
case Routes.ioniaMoreOptionsPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
case Routes.ioniaPaymentStatusPage:
final args = settings.arguments as List;
final paymentInfo = args.first as IoniaAnyPayPaymentInfo;
final commitedInfo = args[1] as AnyPayPaymentCommittedInfo;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaPaymentStatusPage>(
param1: paymentInfo,
param2: commitedInfo));
return CupertinoPageRoute<void>(
builder: (_) =>
getIt.get<IoniaPaymentStatusPage>(param1: paymentInfo, param2: commitedInfo));
case Routes.webViewPage:
final args = settings.arguments as List;
final title = args.first as String;
final url = args[1] as Uri;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<WebViewPage>(
param1: title,
param2: url));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url));
case Routes.advancedPrivacySettings:
final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>(
builder: (_) => AdvancedPrivacySettingsPage(
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type),
));
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
));
case Routes.anonPayInvoicePage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
case Routes.anonPayReceivePage:
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
case Routes.anonPayDetailsPage:
final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
case Routes.desktop_actions:
return PageRouteBuilder(
@ -561,12 +545,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
case Routes.desktop_settings_page:
return CupertinoPageRoute<void>(
builder: (_) => DesktopSettingsPage());
return CupertinoPageRoute<void>(builder: (_) => DesktopSettingsPage());
case Routes.empty_no_route:
return MaterialPageRoute<void>(
builder: (_) => SizedBox.shrink());
return MaterialPageRoute<void>(builder: (_) => SizedBox.shrink());
case Routes.transactionsPage:
return CupertinoPageRoute<void>(
@ -603,7 +585,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
);
case Routes.manageNodes:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>());
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: false));
case Routes.managePowNodes:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: true));
case Routes.scanQr:
return MaterialPageRoute<void>(builder: (_) => getIt.get<ScanScreen>());
@ -611,7 +596,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(
body: Center(
child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
body: Center(child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
}
}

View file

@ -6,6 +6,7 @@ class Routes {
static const seed = '/seed';
static const restoreOptions = '/restore_options';
static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys';
static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
static const dashboard = '/dashboard';
static const send = '/send';
static const transactionDetails = '/transaction_info';
@ -14,13 +15,16 @@ class Routes {
static const walletEdit = '/walletEdit';
static const disclaimer = '/disclaimer';
static const readDisclaimer = '/read_disclaimer';
static const changeRep = '/change_representative';
static const seedLanguage = '/seed_language';
static const walletList = '/view_model.wallet_list';
static const auth = '/auth';
static const newNode = '/new_node_list';
static const newPowNode = '/new_pow_node_list';
static const login = '/login';
static const splash = '/splash';
static const accountCreation = '/account_new';
static const nanoAccountCreation = '/nano_account_new';
static const addressBook = '/address_book';
static const pickerAddressBook = '/picker_address_book';
static const addressBookAddContact = '/address_book_add_contact';
@ -93,4 +97,5 @@ class Routes {
static const editToken = '/edit_token';
static const manageNodes = '/manage_nodes';
static const scanQr = '/scan_qr';
static const managePowNodes = '/manage_pow_nodes';
}

View file

@ -1,9 +1,12 @@
import 'dart:async';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/utils/device_info.dart';
@ -35,12 +38,14 @@ import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
class DashboardPage extends StatelessWidget {
DashboardPage({
required this.bottomSheetService,
required this.balancePage,
required this.dashboardViewModel,
required this.addressListViewModel,
});
final BalancePage balancePage;
final BottomSheetService bottomSheetService;
final DashboardViewModel dashboardViewModel;
final WalletAddressListViewModel addressListViewModel;
@ -55,12 +60,14 @@ class DashboardPage extends StatelessWidget {
} else {
return _DashboardPageView(
balancePage: balancePage,
bottomSheetService: bottomSheetService,
dashboardViewModel: dashboardViewModel,
addressListViewModel: addressListViewModel,
);
}
} else if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) {
return _DashboardPageView(
bottomSheetService: bottomSheetService,
balancePage: balancePage,
dashboardViewModel: dashboardViewModel,
addressListViewModel: addressListViewModel,
@ -76,6 +83,7 @@ class DashboardPage extends StatelessWidget {
class _DashboardPageView extends BasePage {
_DashboardPageView({
required this.bottomSheetService,
required this.balancePage,
required this.dashboardViewModel,
required this.addressListViewModel,
@ -126,6 +134,7 @@ class _DashboardPageView extends BasePage {
}
final DashboardViewModel dashboardViewModel;
final BottomSheetService bottomSheetService;
final WalletAddressListViewModel addressListViewModel;
int get initialPage => dashboardViewModel.shouldShowMarketPlaceInDashboard ? 1 : 0;
@ -158,102 +167,106 @@ class _DashboardPageView extends BasePage {
return SafeArea(
minimum: EdgeInsets.only(bottom: 24),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Observer(
builder: (context) {
return PageView.builder(
controller: controller,
itemCount: pages.length,
itemBuilder: (context, index) => pages[index],
);
},
),
),
Padding(
padding: EdgeInsets.only(bottom: 24, top: 10),
child: Observer(
builder: (context) {
return ExcludeSemantics(
child: SmoothPageIndicator(
child: BottomSheetListener(
bottomSheetService: bottomSheetService,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Observer(
builder: (context) {
return PageView.builder(
controller: controller,
count: pages.length,
effect: ColorTransitionEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).indicatorColor,
activeDotColor: Theme.of(context)
.extension<DashboardPageTheme>()!
.indicatorDotTheme
.activeIndicatorColor,
),
),
);
},
itemCount: pages.length,
itemBuilder: (context, index) => pages[index],
);
},
),
),
),
Observer(
builder: (_) {
return ClipRect(
child: Container(
margin: const EdgeInsets.only(left: 16, right: 16),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
width: 1,
Padding(
padding: EdgeInsets.only(bottom: 24, top: 10),
child: Observer(
builder: (context) {
return ExcludeSemantics(
child: SmoothPageIndicator(
controller: controller,
count: pages.length,
effect: ColorTransitionEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).indicatorColor,
activeDotColor: Theme.of(context)
.extension<DashboardPageTheme>()!
.indicatorDotTheme
.activeIndicatorColor,
),
color:
Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
);
},
),
),
Observer(
builder: (_) {
return ClipRect(
child: Container(
margin: const EdgeInsets.only(left: 16, right: 16),
child: Container(
padding: EdgeInsets.only(left: 32, right: 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: MainActions.all
.where((element) => element.canShow?.call(dashboardViewModel) ?? true)
.map(
(action) => Semantics(
button: true,
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
child: ActionButton(
image: Image.asset(
action.image,
height: 24,
width: 24,
color: action.isEnabled?.call(dashboardViewModel) ?? true
? Theme.of(context)
.extension<DashboardPageTheme>()!
.mainActionsIconColor
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
width: 1,
),
color: Theme.of(context)
.extension<SyncIndicatorTheme>()!
.syncedBackgroundColor,
),
child: Container(
padding: EdgeInsets.only(left: 32, right: 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: MainActions.all
.where((element) => element.canShow?.call(dashboardViewModel) ?? true)
.map(
(action) => Semantics(
button: true,
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
child: ActionButton(
image: Image.asset(
action.image,
height: 24,
width: 24,
color: action.isEnabled?.call(dashboardViewModel) ?? true
? Theme.of(context)
.extension<DashboardPageTheme>()!
.mainActionsIconColor
: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
),
title: action.name(context),
onClick: () async =>
await action.onTap(context, dashboardViewModel),
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
? null
: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
),
title: action.name(context),
onClick: () async =>
await action.onTap(context, dashboardViewModel),
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
? null
: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
),
),
)
.toList(),
)
.toList(),
),
),
),
),
),
);
},
),
],
);
},
),
],
),
),
);
}
@ -282,29 +295,6 @@ class _DashboardPageView extends BasePage {
);
_isEffectsInstalled = true;
autorun(
(_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_description,
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
},
);
_showReleaseNotesPopup(context);
var needToPresentYat = false;

View file

@ -1,8 +1,10 @@
import 'dart:async';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@ -19,12 +21,14 @@ import 'package:shared_preferences/shared_preferences.dart';
class DesktopDashboardPage extends StatelessWidget {
DesktopDashboardPage({
required this.balancePage,
required this.bottomSheetService,
required this.dashboardViewModel,
required this.addressListViewModel,
required this.desktopKey,
});
final BalancePage balancePage;
final BottomSheetService bottomSheetService;
final DashboardViewModel dashboardViewModel;
final WalletAddressListViewModel addressListViewModel;
final GlobalKey<NavigatorState> desktopKey;
@ -36,31 +40,34 @@ class DesktopDashboardPage extends StatelessWidget {
Widget build(BuildContext context) {
_setEffects(context);
return Container(
color: Theme.of(context).colorScheme.background,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 400,
child: balancePage,
),
Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Navigator(
key: desktopKey,
initialRoute: Routes.desktop_actions,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
return [
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
return BottomSheetListener(
bottomSheetService: bottomSheetService,
child: Container(
color: Theme.of(context).colorScheme.background,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 400,
child: balancePage,
),
Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Navigator(
key: desktopKey,
initialRoute: Routes.desktop_actions,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
return [
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
),
),
),
),
],
],
),
),
);
}
@ -71,23 +78,6 @@ class DesktopDashboardPage extends StatelessWidget {
}
_isEffectsInstalled = true;
autorun((_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_description,
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop());
});
});
var needToPresentYat = false;
var isInactive = false;

View file

@ -33,6 +33,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
Image _newWalletImage(BuildContext context) => Image.asset(
@ -141,6 +143,10 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
default:
return nonWalletTypeIcon;
}

View file

@ -120,31 +120,6 @@ class AddressPage extends BasePage {
Widget body(BuildContext context) {
_setEffects(context);
autorun((_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet ||
!dashboardViewModel.settingsStore.shouldShowReceiveWarning) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
leftButtonText: S.of(context).understand,
actionLeftButton: () => Navigator.of(context).pop(),
rightButtonText: S.of(context).do_not_show_me,
actionRightButton: () {
dashboardViewModel.settingsStore.setShouldShowReceiveWarning(false);
Navigator.of(context).pop();
});
});
}
});
return KeyboardActions(
autoScroll: false,
disableScroll: true,

View file

@ -18,18 +18,21 @@ class MenuWidget extends StatefulWidget {
class MenuWidgetState extends State<MenuWidget> {
MenuWidgetState()
: this.menuWidth = 0,
this.screenWidth = 0,
this.screenHeight = 0,
this.headerHeight = 120,
this.tileHeight = 60,
this.fromTopEdge = 50,
this.fromBottomEdge = 25,
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
this.ethereumIcon = Image.asset('assets/images/eth_icon.png');
: this.menuWidth = 0,
this.screenWidth = 0,
this.screenHeight = 0,
this.headerHeight = 120,
this.tileHeight = 60,
this.fromTopEdge = 50,
this.fromBottomEdge = 25,
this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
this.havenIcon = Image.asset('assets/images/haven_menu.png'),
this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
this.bananoIcon = Image.asset('assets/images/nano_icon.png');
final largeScreen = 731;
@ -47,6 +50,9 @@ class MenuWidgetState extends State<MenuWidget> {
Image litecoinIcon;
Image havenIcon;
Image ethereumIcon;
Image nanoIcon;
Image bananoIcon;
@override
void initState() {
@ -206,6 +212,10 @@ class MenuWidgetState extends State<MenuWidget> {
return havenIcon;
case WalletType.ethereum:
return ethereumIcon;
case WalletType.nano:
return nanoIcon;
case WalletType.banano:
return bananoIcon;
default:
throw Exception('No icon for ${type.toString()}');
}

View file

@ -94,6 +94,9 @@ class TradeRow extends StatelessWidget {
borderRadius: BorderRadius.circular(50),
child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
break;
case ExchangeProviderDescription.exolix:
image = Image.asset('assets/images/exolix.png', width: 36, height: 36);
break;
default:
image = null;
}

View file

@ -0,0 +1,103 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_nano/nano_wallet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class NanoChangeRepPage extends BasePage {
NanoChangeRepPage(WalletBase wallet)
: _wallet = wallet,
_addressController = TextEditingController() {
_addressController.text = (wallet as NanoWallet).representative;
}
final TextEditingController _addressController;
final WalletBase _wallet;
@override
String get title => S.current.change_rep;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _addressController,
hintText: S.of(context).node_address,
validator: AddressValidator(type: CryptoCurrency.nano),
),
)
],
),
],
),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_rep,
alertContent: S.of(context).change_rep_message,
rightButtonText: S.of(context).change,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
try {
await nano!.changeRep(_wallet, _addressController.text);
Navigator.of(context).pop();
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
throw e;
}
}
},
text: S.of(context).change,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
)),
],
)),
));
}
}

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