mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-07 03:19:31 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-447-mobile-scanner
This commit is contained in:
commit
68405cbd6a
197 changed files with 11727 additions and 1518 deletions
34
.github/workflows/pr_test_build.yml
vendored
34
.github/workflows/pr_test_build.yml
vendored
|
@ -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
1
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
|
@ -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
BIN
assets/images/exolix.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/images/walletconnect_logo.png
Normal file
BIN
assets/images/walletconnect_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
6
assets/nano_node_list.yml
Normal file
6
assets/nano_node_list.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
-
|
||||
uri: rpc.nano.to
|
||||
useSSL: true
|
||||
is_default: true
|
||||
-
|
||||
uri: node.perish.co:9076
|
9
assets/nano_pow_node_list.yml
Normal file
9
assets/nano_pow_node_list.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
-
|
||||
uri: rpc.nano.to
|
||||
useSSL: true
|
||||
is_default: true
|
||||
-
|
||||
uri: workers.perish.co
|
||||
-
|
||||
uri: worker.nanoriver.cc
|
||||
useSSL: true
|
|
@ -1,3 +1,3 @@
|
|||
Enhance Monero coin control
|
||||
Add Filipino localization
|
||||
Bug Fixes
|
||||
Fix 2FA code issue
|
||||
Bug fixes
|
||||
Minor enhancements
|
|
@ -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
1
configure_cake_wallet_android.sh
Normal file → Executable 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
|
||||
|
|
|
@ -2297,4 +2297,4 @@ final englishWordlist = <String>[
|
|||
'zero',
|
||||
'zone',
|
||||
'zoo'
|
||||
];
|
||||
];
|
|
@ -89,4 +89,4 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,4 +20,4 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
|||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String wif;
|
||||
}
|
||||
}
|
|
@ -100,4 +100,4 @@ class BitcoinWalletService extends WalletService<
|
|||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,4 +56,4 @@ class ElectrumWallletSnapshot {
|
|||
regularAddressIndex: regularAddressIndex,
|
||||
changeAddressIndex: changeAddressIndex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 =
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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;
|
23
cw_core/lib/nano_account.dart
Normal file
23
cw_core/lib/nano_account.dart
Normal 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;
|
||||
}
|
23
cw_core/lib/nano_account_info_response.dart
Normal file
23
cw_core/lib/nano_account_info_response.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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!;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
20
cw_nano/lib/banano_balance.dart
Normal file
20
cw_nano/lib/banano_balance.dart
Normal 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
7
cw_nano/lib/cw_nano.dart
Normal 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
39
cw_nano/lib/file.dart
Normal 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);
|
||||
}
|
69
cw_nano/lib/nano_account_list.dart
Normal file
69
cw_nano/lib/nano_account_list.dart
Normal 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() {}
|
||||
}
|
34
cw_nano/lib/nano_balance.dart
Normal file
34
cw_nano/lib/nano_balance.dart
Normal 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);
|
||||
}
|
||||
}
|
424
cw_nano/lib/nano_client.dart
Normal file
424
cw_nano/lib/nano_client.dart
Normal 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 [];
|
||||
}
|
||||
}
|
||||
}
|
2088
cw_nano/lib/nano_mnemonic.dart
Normal file
2088
cw_nano/lib/nano_mnemonic.dart
Normal file
File diff suppressed because it is too large
Load diff
7
cw_nano/lib/nano_transaction_credentials.dart
Normal file
7
cw_nano/lib/nano_transaction_credentials.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class NanoTransactionCredentials {
|
||||
NanoTransactionCredentials(this.outputs);
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
}
|
72
cw_nano/lib/nano_transaction_history.dart
Normal file
72
cw_nano/lib/nano_transaction_history.dart
Normal 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;
|
||||
}
|
70
cw_nano/lib/nano_transaction_info.dart
Normal file
70
cw_nano/lib/nano_transaction_info.dart
Normal 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,
|
||||
};
|
||||
}
|
39
cw_nano/lib/nano_transaction_model.dart
Normal file
39
cw_nano/lib/nano_transaction_model.dart
Normal 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
193
cw_nano/lib/nano_util.dart
Normal 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();
|
||||
}
|
||||
}
|
437
cw_nano/lib/nano_wallet.dart
Normal file
437
cw_nano/lib/nano_wallet.dart
Normal 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);
|
||||
}
|
||||
}
|
50
cw_nano/lib/nano_wallet_addresses.dart
Normal file
50
cw_nano/lib/nano_wallet_addresses.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
41
cw_nano/lib/nano_wallet_creation_credentials.dart
Normal file
41
cw_nano/lib/nano_wallet_creation_credentials.dart
Normal 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;
|
||||
}
|
5
cw_nano/lib/nano_wallet_keys.dart
Normal file
5
cw_nano/lib/nano_wallet_keys.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
class NanoWalletKeys {
|
||||
const NanoWalletKeys({required this.seedKey});
|
||||
|
||||
final String seedKey;
|
||||
}
|
163
cw_nano/lib/nano_wallet_service.dart
Normal file
163
cw_nano/lib/nano_wallet_service.dart
Normal 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;
|
||||
}
|
||||
}
|
40
cw_nano/lib/pending_nano_transaction.dart
Normal file
40
cw_nano/lib/pending_nano_transaction.dart
Normal 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
756
cw_nano/pubspec.lock
Normal 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
69
cw_nano/pubspec.yaml
Normal 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
|
12
cw_nano/test/cw_nano_test.dart
Normal file
12
cw_nano/test/cw_nano_test.dart
Normal 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);
|
||||
});
|
||||
}
|
|
@ -161,4 +161,4 @@ class CWBitcoin extends Bitcoin {
|
|||
@override
|
||||
TransactionPriority getLitecoinTransactionPrioritySlow()
|
||||
=> LitecoinTransactionPriority.slow;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 [];
|
||||
}
|
||||
|
|
5
lib/core/wallet_connect/chain_service.dart
Normal file
5
lib/core/wallet_connect/chain_service.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
abstract class ChainService {
|
||||
String getNamespace();
|
||||
String getChainId();
|
||||
List<String> getEvents();
|
||||
}
|
60
lib/core/wallet_connect/eth_transaction_model.dart
Normal file
60
lib/core/wallet_connect/eth_transaction_model.dart
Normal 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)';
|
||||
}
|
||||
}
|
35
lib/core/wallet_connect/evm_chain_id.dart
Normal file
35
lib/core/wallet_connect/evm_chain_id.dart
Normal 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';
|
||||
}
|
||||
}
|
294
lib/core/wallet_connect/evm_chain_service.dart
Normal file
294
lib/core/wallet_connect/evm_chain_service.dart
Normal 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
|
||||
''';
|
||||
}
|
||||
}
|
16
lib/core/wallet_connect/models/auth_request_model.dart
Normal file
16
lib/core/wallet_connect/models/auth_request_model.dart
Normal 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)';
|
||||
}
|
||||
}
|
|
@ -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)';
|
||||
}
|
||||
}
|
16
lib/core/wallet_connect/models/chain_key_model.dart
Normal file
16
lib/core/wallet_connect/models/chain_key_model.dart
Normal 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)';
|
||||
}
|
||||
}
|
18
lib/core/wallet_connect/models/connection_model.dart
Normal file
18
lib/core/wallet_connect/models/connection_model.dart
Normal 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)';
|
||||
}
|
||||
}
|
14
lib/core/wallet_connect/models/session_request_model.dart
Normal file
14
lib/core/wallet_connect/models/session_request_model.dart
Normal 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)';
|
||||
}
|
||||
}
|
72
lib/core/wallet_connect/wallet_connect_key_service.dart
Normal file
72
lib/core/wallet_connect/wallet_connect_key_service.dart
Normal 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;
|
||||
}
|
||||
}
|
43
lib/core/wallet_connect/wc_bottom_sheet_service.dart
Normal file
43
lib/core/wallet_connect/wc_bottom_sheet_service.dart
Normal 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;
|
||||
}
|
||||
}
|
277
lib/core/wallet_connect/web3wallet_service.dart
Normal file
277
lib/core/wallet_connect/web3wallet_service.dart
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
155
lib/di.dart
155
lib/di.dart
|
@ -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());
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
46
lib/entities/ens_record.dart
Normal file
46
lib/entities/ens_record.dart
Normal 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 "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
294
lib/exchange/exolix/exolix_exchange_provider.dart
Normal file
294
lib/exchange/exolix/exolix_exchange_provider.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
20
lib/exchange/exolix/exolix_request.dart
Normal file
20
lib/exchange/exolix/exolix_request.dart
Normal 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;
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
63
lib/mastodon/mastodon_api.dart
Normal file
63
lib/mastodon/mastodon_api.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
36
lib/mastodon/mastodon_user.dart
Normal file
36
lib/mastodon/mastodon_user.dart
Normal 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
499
lib/nano/cw_nano.dart
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
266
lib/router.dart
266
lib/router.dart
|
@ -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')))));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()}');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
103
lib/src/screens/nano/nano_change_rep_page.dart
Normal file
103
lib/src/screens/nano/nano_change_rep_page.dart
Normal 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
Loading…
Reference in a new issue