Merge branch 'main' into CW-529-Modify-2FA-introduction-screens

This commit is contained in:
Serhii 2023-12-13 14:44:38 +02:00
commit 50299e60f0
192 changed files with 4245 additions and 1139 deletions

View file

@ -3,6 +3,12 @@ name: PR Test Build
on:
pull_request:
branches: [main]
workflow_dispatch:
inputs:
branch:
description: 'Branch name to build'
required: true
default: 'main'
jobs:
PR_test_build:
@ -12,6 +18,14 @@ jobs:
KEY_PASS: test@cake_wallet
steps:
- name: is pr
if: github.event_name == 'pull_request'
run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV
- name: is not pr
if: github.event_name != 'pull_request'
run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV
- name: Free Up GitHub Actions Ubuntu Runner Disk Space
run: |
sudo rm -rf /usr/share/dotnet
@ -40,7 +54,7 @@ jobs:
cd /opt/android
-y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install cargo-ndk
git clone https://github.com/cake-tech/cake_wallet.git --branch $GITHUB_HEAD_REF
git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }}
cd cake_wallet/scripts/android/
./install_ndk.sh
source ./app_env.sh cakewallet
@ -97,6 +111,7 @@ jobs:
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && 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 ..
cd cw_polygon && 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
@ -131,6 +146,7 @@ jobs:
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
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 polygonScanApiKey = '${{ secrets.POLYGON_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
@ -139,7 +155,7 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> 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
run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
- name: Build
run: |
@ -154,7 +170,7 @@ jobs:
# appcenter distribute release \
# --group "Testers" \
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
# --release-notes ${GITHUB_HEAD_REF} \
# --release-notes ${{ env.BRANCH_NAME }} \
# --app Cake-Labs/Cake-Wallet \
# --token ${{ secrets.APP_CENTER_TOKEN }} \
# --quiet
@ -163,7 +179,7 @@ jobs:
run: |
cd /opt/android/cake_wallet/build/app/outputs/apk/release
mkdir test-apk
cp app-release.apk test-apk/$GITHUB_HEAD_REF.apk
cp app-release.apk test-apk/${{env.BRANCH_NAME}}.apk
- name: Upload Artifact
uses: kittaakos/upload-artifact-as-is@v0
@ -177,6 +193,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"
filename: ${{github.head_ref}}.apk
title: "${{ env.BRANCH_NAME }}.apk"
filename: ${{ env.BRANCH_NAME }}.apk
initial_comment: ${{ github.event.head_commit.message }}

1
.gitignore vendored
View file

@ -126,6 +126,7 @@ lib/haven/haven.dart
lib/ethereum/ethereum.dart
lib/bitcoin_cash/bitcoin_cash.dart
lib/nano/nano.dart
lib/polygon/polygon.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png

View file

@ -62,6 +62,9 @@
<data android:scheme="bitcoincash" />
<data android:scheme="bitcoincash-wallet" />
<data android:scheme="bitcoincash_wallet" />
<data android:scheme="polygon" />
<data android:scheme="polygon-wallet" />
<data android:scheme="polygon_wallet" />
</intent-filter>
</activity>
<meta-data

BIN
assets/images/dfx_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
assets/images/dfx_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,6 @@
-
uri: polygon-bor.publicnode.com
-
uri: polygon-rpc.com
-
uri: polygon.llamarpc.com

View file

@ -1,7 +1,2 @@
Coin control fixes and enhancements
In-app Tor connection
Accessibility enhancements
Privacy settings enhancements
UI enhancements
Backup flow fixes
Bug fixes
Monero Polyseed support, create and restore from a 16 words phrase and without the need to remember the wallet creation date
Bug fixes and enhancements

View file

@ -1,7 +1,3 @@
Coin control fixes and enhancements
In-app Tor connection
Accessibility enhancements
Privacy settings enhancements
UI enhancements
Backup flow fixes
Bug fixes
Monero Polyseed support, create and restore from a 16 words phrase and without the need to remember the wallet creation date
Add NFTs tab to see all of your purchased NFTs on Ethereum
Bug fixes and enhancements

View file

@ -2,6 +2,7 @@ cd scripts/android
source ./app_env.sh cakewallet
./app_config.sh
cd ../.. && flutter pub get
flutter packages pub run tool/generate_localization.dart
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
@ -9,4 +10,5 @@ cd cw_haven && flutter pub get && flutter packages pub run build_runner build --
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 ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -1,10 +1,11 @@
import 'dart:convert';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_bitcoin/file.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
part 'electrum_transaction_history.g.dart';

View file

@ -18,7 +18,6 @@ import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/file.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/script_hash.dart';
import 'package:cw_bitcoin/utils.dart';
@ -30,6 +29,7 @@ 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/unspent_coins_info.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
@ -480,7 +480,7 @@ abstract class ElectrumWalletBase
unspentCoins = unspent.expand((e) => e).toList();
unspentCoins.forEach((coin) async {
final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
coin.isChange = tx!.direction == TransactionDirection.outgoing;
coin.isChange = tx?.direction == TransactionDirection.outgoing;
});
if (unspentCoinsInfo.isEmpty) {
@ -725,8 +725,7 @@ abstract class ElectrumWalletBase
final index = address != null
? walletAddresses.addresses.firstWhere((element) => element.address == address).index
: null;
return index == null
? base64Encode(hd.sign(message))
: base64Encode(hd.derive(index).sign(message));
final HD = index == null ? hd : hd.derive(index);
return base64Encode(HD.signMessage(message));
}
}

View file

@ -1,8 +1,8 @@
import 'dart:convert';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/file.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_type.dart';
class ElectrumWallletSnapshot {
@ -56,4 +56,4 @@ class ElectrumWallletSnapshot {
regularAddressIndex: regularAddressIndex,
changeAddressIndex: changeAddressIndex);
}
}
}

View file

@ -22,7 +22,7 @@ dependencies:
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
ref: cake-update-v4
bitbox:
git:
url: https://github.com/cake-tech/bitbox-flutter.git
@ -30,7 +30,6 @@ dependencies:
rxdart: ^0.27.5
unorm_dart: ^0.2.0
cryptography: ^2.0.5
encrypt: ^5.0.1
dev_dependencies:
flutter_test:

View file

@ -302,10 +302,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
final index = address != null
? walletAddresses.addresses
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
.index
: null;
return index == null
? base64Encode(hd.sign(message))
: base64Encode(hd.derive(index).sign(message));
.index : null;
final HD = index == null ? hd : hd.derive(index);
return base64Encode(HD.signMessage(message));
}
}

View file

@ -1 +0,0 @@
/Users/blazebrain/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/

View file

@ -1 +0,0 @@
/Users/blazebrain/.pub-cache/git/tor-09ba92cb11d4e3cacf97256e57863b805f79f2e5/

View file

@ -1,11 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void fl_register_plugins(FlPluginRegistry* registry) {
}

View file

@ -1,15 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View file

@ -1,24 +0,0 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
tor
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View file

@ -1,12 +0,0 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}

View file

@ -1,11 +0,0 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=C:\Users\borod\flutter
FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=0.0.1
FLUTTER_BUILD_NUMBER=0.0.1
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View file

@ -1,12 +0,0 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=C:\Users\borod\flutter"
export "FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=0.0.1"
export "FLUTTER_BUILD_NUMBER=0.0.1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

View file

@ -24,7 +24,7 @@ dependencies:
bitcoin_flutter:
git:
url: https://github.com/cake-tech/bitcoin_flutter.git
ref: cake-update-v3
ref: cake-update-v4
bitbox:
git:
url: https://github.com/cake-tech/bitbox-flutter.git

View file

@ -1,11 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void RegisterPlugins(flutter::PluginRegistry* registry) {
}

View file

@ -1,15 +0,0 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View file

@ -1,23 +0,0 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View file

@ -19,6 +19,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
case WalletType.polygon:
return CryptoCurrency.maticpoly;
default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
}

View file

@ -58,6 +58,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
static const typeId = ERC20_TOKEN_TYPE_ID;
static const boxName = 'Erc20Tokens';
static const polygonBoxName = ' PolygonErc20Tokens';
@override
bool operator ==(other) => (other is Erc20Token && other.contractAddress == contractAddress) ||

View file

@ -84,7 +84,44 @@ final dates = {
"2020-8": 2153983,
"2020-9": 2176466,
"2020-10": 2198453,
"2020-11": 2220000
"2020-11": 2220000,
"2020-12": 2242240,
"2021-1": 2264584,
"2021-2": 2286892,
"2021-3": 2307079,
"2021-4": 2329385,
"2021-5": 2351004,
"2021-6": 2373306,
"2021-7": 2394882,
"2021-8": 2417162,
"2021-9": 2439490,
"2021-10": 2461020,
"2021-11": 2483377,
"2021-12": 2504932,
"2022-1": 2527316,
"2022-2": 2549605,
"2022-3": 2569711,
"2022-4": 2591995,
"2022-5": 2613603,
"2022-6": 2635840,
"2022-7": 2657395,
"2022-8": 2679705,
"2022-9": 2701991,
"2022-10": 2723607,
"2022-11": 2745899,
"2022-12": 2767427,
"2023-1": 2789763,
"2023-2": 2811996,
"2023-3": 2832118,
"2023-4": 2854365,
"2023-5": 2875972,
"2023-6": 2898234,
"2023-7": 2919771,
"2023-8": 2942045,
"2023-9": 2964280,
"2023-10": 2985937,
"2023-11": 3008178,
"2023-12": 3029759
};
int getMoneroHeigthByDate({required DateTime date}) {

View file

@ -6,7 +6,7 @@ import 'package:hive/hive.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:http/io_client.dart' as ioc;
import 'package:tor/tor.dart';
// import 'package:tor/tor.dart';
part 'node.g.dart';
@ -88,6 +88,8 @@ class Node extends HiveObject with Keyable {
} else {
return Uri.http(uriRaw, '');
}
case WalletType.polygon:
return Uri.https(uriRaw, '');
default:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
@ -146,6 +148,8 @@ class Node extends HiveObject with Keyable {
case WalletType.nano:
case WalletType.banano:
return requestNanoNode();
case WalletType.polygon:
return requestElectrumServer();
default:
return false;
}
@ -210,14 +214,17 @@ class Node extends HiveObject with Keyable {
}
Future<bool> requestNodeWithProxy() async {
if (!isValidProxyAddress && !Tor.instance.enabled) {
if (!isValidProxyAddress/* && !Tor.instance.enabled*/) {
return false;
}
String? proxy = socksProxyAddress;
if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) {
proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}";
// if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) {
// proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}";
// }
if (proxy == null) {
return false;
}
final proxyAddress = proxy!.split(':')[0];
final proxyPort = int.parse(proxy.split(':')[1]);

View file

@ -2,17 +2,8 @@ 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> write({required String path, required String password, required String data}) async =>
writeData(path: path, password: password, data: data);
Future<void> writeData(
{required String path,

View file

@ -44,6 +44,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
String? get privateKey => null;
String? get hexSeed => null;
Object get keys;
WalletAddresses get walletAddresses;

View file

@ -13,6 +13,7 @@ const walletTypes = [
WalletType.bitcoinCash,
WalletType.nano,
WalletType.banano,
WalletType.polygon,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@ -44,6 +45,8 @@ enum WalletType {
@HiveField(8)
bitcoinCash,
@HiveField(9)
polygon
}
int serializeToInt(WalletType type) {
@ -64,6 +67,8 @@ int serializeToInt(WalletType type) {
return 6;
case WalletType.bitcoinCash:
return 7;
case WalletType.polygon:
return 8;
default:
return -1;
}
@ -87,6 +92,8 @@ WalletType deserializeFromInt(int raw) {
return WalletType.banano;
case 7:
return WalletType.bitcoinCash;
case 8:
return WalletType.polygon;
default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
}
@ -110,6 +117,8 @@ String walletTypeToString(WalletType type) {
return 'Nano';
case WalletType.banano:
return 'Banano';
case WalletType.polygon:
return 'Polygon';
default:
return '';
}
@ -133,6 +142,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Nano (XNO)';
case WalletType.banano:
return 'Banano (BAN)';
case WalletType.polygon:
return 'Polygon (MATIC)';
default:
return '';
}
@ -156,7 +167,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
case WalletType.polygon:
return CryptoCurrency.maticpoly;
default:
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
}
}

View file

@ -271,10 +271,10 @@ packages:
dependency: "direct dev"
description:
name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
version: "2.0.1"
http:
dependency: "direct main"
description:

View file

@ -20,10 +20,10 @@ dependencies:
intl: ^0.18.0
encrypt: ^5.0.1
socks5_proxy: ^1.0.4
tor:
git:
url: https://github.com/cake-tech/tor.git
ref: main
# tor:
# git:
# url: https://github.com/cake-tech/tor.git
# ref: main
dev_dependencies:
flutter_test:
@ -31,7 +31,7 @@ dev_dependencies:
build_runner: ^2.1.11
build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
hive_generator: ^2.0.1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View file

@ -15,12 +15,12 @@ import 'package:cw_ethereum/ethereum_transaction_priority.dart';
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
class EthereumClient {
final _httpClient = Client();
final httpClient = Client();
Web3Client? _client;
bool connect(Node node) {
try {
_client = Web3Client(node.uri.toString(), _httpClient);
_client = Web3Client(node.uri.toString(), httpClient);
return true;
} catch (e) {
@ -74,9 +74,11 @@ class EthereumClient {
required int exponent,
String? contractAddress,
}) async {
assert(currency == CryptoCurrency.eth || contractAddress != null);
assert(currency == CryptoCurrency.eth ||
currency == CryptoCurrency.maticpoly ||
contractAddress != null);
bool _isEthereum = currency == CryptoCurrency.eth;
bool _isEVMCompatibleChain = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
final price = _client!.getGasPrice();
@ -84,19 +86,23 @@ class EthereumClient {
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
value: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
);
final signedTransaction = await _client!.signTransaction(privateKey, transaction);
final chainId = _getChainIdForCurrency(currency);
final signedTransaction =
await _client!.signTransaction(privateKey, transaction, chainId: chainId);
final Function _sendTransaction;
if (_isEthereum) {
if (_isEVMCompatibleChain) {
_sendTransaction = () async => await sendTransaction(signedTransaction);
} else {
final erc20 = ERC20(
client: _client!,
address: EthereumAddress.fromHex(contractAddress!),
chainId: chainId,
);
_sendTransaction = () async {
@ -118,6 +124,16 @@ class EthereumClient {
);
}
int _getChainIdForCurrency(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.maticpoly:
return 137;
case CryptoCurrency.eth:
default:
return 1;
}
}
Future<String> sendTransaction(Uint8List signedTransaction) async =>
await _client!.sendRawTransaction(prependTransactionType(0x02, signedTransaction));
@ -198,7 +214,7 @@ I/flutter ( 4474): Gas Used: 53000
Future<List<EthereumTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async {
try {
final response = await _httpClient.get(Uri.https("api.etherscan.io", "/api", {
final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", {
"module": "account",
"action": contractAddress != null ? "tokentx" : "txlist",
if (contractAddress != null) "contractaddress": contractAddress,

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
@ -34,8 +36,10 @@ class EthereumTransactionInfo extends TransactionInfo {
final String? to;
@override
String amountFormatted() =>
'${formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol';
String amountFormatted() {
final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString());
return '${amount.substring(0, min(10, amount.length))} $tokenSymbol';
}
@override
String fiatAmount() => _fiatAmount ?? '';
@ -44,7 +48,10 @@ class EthereumTransactionInfo extends TransactionInfo {
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} ETH';
String feeFormatted() {
final amount = (ethFee / BigInt.from(10).pow(18)).toString();
return '${amount.substring(0, min(10, amount.length))} ETH';
}
factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
return EthereumTransactionInfo(

View file

@ -1,3 +1,4 @@
//! Model used for in parsing transactions fetched using etherscan
class EthereumTransactionModel {
final DateTime date;
final String hash;

View file

@ -21,8 +21,8 @@ class PendingEthereumTransaction with PendingTransaction {
@override
String get amountFormatted {
final _amount = BigInt.parse(amount) / BigInt.from(pow(10, exponent));
return _amount.toStringAsFixed(min(15, _amount.toString().length));
final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
return _amount.substring(0, min(10, _amount.length));
}
@override
@ -30,8 +30,8 @@ class PendingEthereumTransaction with PendingTransaction {
@override
String get feeFormatted {
final _fee = fee / BigInt.from(pow(10, 18));
return _fee.toStringAsFixed(min(15, _fee.toString().length));
final _fee = (fee / BigInt.from(pow(10, 18))).toString();
return _fee.substring(0, min(10, _fee.length));
}
@override

View file

@ -17,7 +17,6 @@ dependencies:
mobx: ^2.0.7+4
bip39: ^1.0.6
bip32: ^2.0.0
ed25519_hd_key: ^2.2.0
hex: ^0.2.0
http: ^1.1.0
shared_preferences: ^2.0.15

View file

@ -301,10 +301,19 @@ packages:
dependency: transitive
description:
name: pointycastle
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.6.2"
version: "3.7.3"
polyseed:
dependency: transitive
description:
path: "."
ref: HEAD
resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7"
url: "https://github.com/cake-tech/polyseed_dart.git"
source: git
version: "0.0.1"
process:
dependency: transitive
description:
@ -416,5 +425,5 @@ packages:
source: hosted
version: "0.2.0+3"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.0.6 <4.0.0"
flutter: ">=3.7.0"

View file

@ -374,6 +374,35 @@ extern "C"
return true;
}
bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error)
{
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey(
std::string(path),
std::string(password),
std::string(language),
_networkType,
(uint64_t)restoreHeight,
std::string(spendKey));
// Cache Raw to support Polyseed
wallet->setCacheAttribute("cakewallet.seed", std::string(seed));
int status;
std::string errorString;
wallet->statusWithErrorString(status, errorString);
if (status != Monero::Wallet::Status_Ok || !errorString.empty())
{
error = strdup(errorString.c_str());
return false;
}
change_current_wallet(wallet);
return true;
}
bool load_wallet(char *path, char *password, int32_t nettype)
{
nice(19);
@ -438,6 +467,11 @@ extern "C"
const char *seed()
{
std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed");
if (!_rawSeed.empty())
{
return strdup(_rawSeed.c_str());
}
return strdup(get_current_wallet()->seed().c_str());
}
@ -892,6 +926,8 @@ extern "C"
return m_wallet->trustedDaemon();
}
// Coin Control //
CoinsInfoRow* coin(int index)
{
if (index >= 0 && index < m_coins_info.size()) {
@ -986,6 +1022,13 @@ extern "C"
m_coins->thaw(index);
}
// Sign Messages //
char *sign_message(char *message, char *address = "")
{
return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str());
}
#ifdef __cplusplus
}
#endif

View file

@ -32,7 +32,8 @@ void store(char *path);
void set_trusted_daemon(bool arg);
bool trusted_daemon();
char *sign_message(char *message, char *address);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -14,6 +14,9 @@ typedef restore_wallet_from_seed = Int8 Function(
typedef restore_wallet_from_keys = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
typedef restore_wallet_from_spend_key = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
typedef is_wallet_exist = Int8 Function(Pointer<Utf8>);
typedef load_wallet = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Int8);
@ -146,3 +149,5 @@ typedef coin = Pointer<CoinsInfoRow> Function(Int32 index);
typedef freeze_coin = Void Function(Int32 index);
typedef thaw_coin = Void Function(Int32 index);
typedef sign_message = Pointer<Utf8> Function(Pointer<Utf8> message, Pointer<Utf8> address);

View file

@ -14,6 +14,9 @@ typedef RestoreWalletFromSeed = int Function(
typedef RestoreWalletFromKeys = int Function(Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
typedef RestoreWalletFromSpendKey = int Function(Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
typedef IsWalletExist = int Function(Pointer<Utf8>);
typedef LoadWallet = int Function(Pointer<Utf8>, Pointer<Utf8>, int);
@ -146,3 +149,5 @@ typedef GetCoin = Pointer<CoinsInfoRow> Function(int);
typedef FreezeCoin = void Function(int);
typedef ThawCoin = void Function(int);
typedef SignMessage = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);

View file

@ -8,7 +8,6 @@ import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
int _boolToInt(bool value) => value ? 1 : 0;
@ -128,6 +127,10 @@ final trustedDaemonNative = moneroApi
.lookup<NativeFunction<trusted_daemon>>('trusted_daemon')
.asFunction<TrustedDaemon>();
final signMessageNative = moneroApi
.lookup<NativeFunction<sign_message>>('sign_message')
.asFunction<SignMessage>();
int getSyncingHeight() => getSyncingHeightNative();
bool isNeededToRefresh() => isNeededToRefreshNative() != 0;
@ -296,7 +299,7 @@ class SyncListener {
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
if (_lastKnownBlockHeight == syncHeight || syncHeight == null) {
if (_lastKnownBlockHeight == syncHeight) {
return;
}
@ -311,7 +314,7 @@ class SyncListener {
}
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
onNewBlock?.call(syncHeight, left, ptc);
onNewBlock.call(syncHeight, left, ptc);
});
}
@ -382,4 +385,15 @@ String getSubaddressLabel(int accountIndex, int addressIndex) {
Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted));
Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
String signMessage(String message, {String address = ""}) {
final messagePointer = message.toNativeUtf8();
final addressPointer = address.toNativeUtf8();
final signature = convertUTF8ToString(pointer: signMessageNative(messagePointer, addressPointer));
calloc.free(messagePointer);
calloc.free(addressPointer);
return signature;
}

View file

@ -1,15 +1,16 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_monero/api/convert_utf8_to_string.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
final createWalletNative = moneroApi
.lookup<NativeFunction<create_wallet>>('create_wallet')
@ -25,6 +26,11 @@ final restoreWalletFromKeysNative = moneroApi
'restore_wallet_from_keys')
.asFunction<RestoreWalletFromKeys>();
final restoreWalletFromSpendKeyNative = moneroApi
.lookup<NativeFunction<restore_wallet_from_spend_key>>(
'restore_wallet_from_spend_key')
.asFunction<RestoreWalletFromSpendKey>();
final isWalletExistNative = moneroApi
.lookup<NativeFunction<is_wallet_exist>>('is_wallet_exist')
.asFunction<IsWalletExist>();
@ -141,6 +147,44 @@ void restoreWalletFromKeysSync(
}
}
void restoreWalletFromSpendKeySync(
{required String path,
required String password,
required String seed,
required String language,
required String spendKey,
int nettype = 0,
int restoreHeight = 0}) {
final pathPointer = path.toNativeUtf8();
final passwordPointer = password.toNativeUtf8();
final seedPointer = seed.toNativeUtf8();
final languagePointer = language.toNativeUtf8();
final spendKeyPointer = spendKey.toNativeUtf8();
final errorMessagePointer = ''.toNativeUtf8();
final isWalletRestored = restoreWalletFromSpendKeyNative(
pathPointer,
passwordPointer,
seedPointer,
languagePointer,
spendKeyPointer,
nettype,
restoreHeight,
errorMessagePointer) !=
0;
calloc.free(pathPointer);
calloc.free(passwordPointer);
calloc.free(languagePointer);
calloc.free(spendKeyPointer);
storeSync();
if (!isWalletRestored) {
throw WalletRestoreFromKeysException(
message: convertUTF8ToString(pointer: errorMessagePointer));
}
}
void loadWallet({
required String path,
required String password,
@ -194,6 +238,23 @@ void _restoreFromKeys(Map<String, dynamic> args) {
spendKey: spendKey);
}
void _restoreFromSpendKey(Map<String, dynamic> args) {
final path = args['path'] as String;
final password = args['password'] as String;
final seed = args['seed'] as String;
final language = args['language'] as String;
final spendKey = args['spendKey'] as String;
final restoreHeight = args['restoreHeight'] as int;
restoreWalletFromSpendKeySync(
path: path,
password: password,
seed: seed,
language: language,
restoreHeight: restoreHeight,
spendKey: spendKey);
}
Future<void> _openWallet(Map<String, String> args) async =>
loadWallet(path: args['path'] as String, password: args['password'] as String);
@ -251,4 +312,22 @@ Future<void> restoreFromKeys(
'restoreHeight': restoreHeight
});
Future<void> restoreFromSpendKey(
{required String path,
required String password,
required String seed,
required String language,
required String spendKey,
int nettype = 0,
int restoreHeight = 0}) async =>
compute<Map<String, Object>, void>(_restoreFromSpendKey, {
'path': path,
'password': password,
'seed': seed,
'language': language,
'spendKey': spendKey,
'nettype': nettype,
'restoreHeight': restoreHeight
});
Future<bool> isWalletExist({required String path}) => compute(_isWalletExist, path);

View file

@ -29,6 +29,7 @@ import 'package:cw_monero/monero_transaction_info.dart';
import 'package:cw_monero/monero_unspent.dart';
import 'package:cw_monero/monero_wallet_addresses.dart';
import 'package:cw_monero/pending_monero_transaction.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@ -78,6 +79,8 @@ abstract class MoneroWalletBase
Box<UnspentCoinsInfo> unspentCoinsInfo;
void Function(FlutterErrorDetails)? _onError;
@override
late MoneroWalletAddresses walletAddresses;
@ -388,46 +391,59 @@ abstract class MoneroWalletBase
}
Future<void> updateUnspent() async {
refreshCoins(walletAddresses.account!.id);
try {
refreshCoins(walletAddresses.account!.id);
unspentCoins.clear();
unspentCoins.clear();
final coinCount = countOfCoins();
for (var i = 0; i < coinCount; i++) {
final coin = getCoin(i);
if (coin.spent == 0) {
final unspent = MoneroUnspent.fromCoinsInfoRow(coin);
unspent.isChange = transaction_history.getTransaction(unspent.hash).direction == 1;
unspentCoins.add(unspent);
}
}
if (unspentCoinsInfo.isEmpty) {
unspentCoins.forEach((coin) => _addCoinInfo(coin));
return;
}
if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values.where((element) =>
element.walletId.contains(id) &&
element.accountIndex == walletAddresses.account!.id &&
element.keyImage!.contains(coin.keyImage!));
if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first;
coin.isFrozen = coinInfo.isFrozen;
coin.isSending = coinInfo.isSending;
coin.note = coinInfo.note;
} else {
_addCoinInfo(coin);
final coinCount = countOfCoins();
for (var i = 0; i < coinCount; i++) {
final coin = getCoin(i);
if (coin.spent == 0) {
final unspent = MoneroUnspent.fromCoinsInfoRow(coin);
if (unspent.hash.isNotEmpty) {
unspent.isChange = transaction_history
.getTransaction(unspent.hash)
.direction == 1;
}
unspentCoins.add(unspent);
}
});
}
}
await _refreshUnspentCoinsInfo();
_askForUpdateBalance();
if (unspentCoinsInfo.isEmpty) {
unspentCoins.forEach((coin) => _addCoinInfo(coin));
return;
}
if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values.where((element) =>
element.walletId.contains(id) &&
element.accountIndex == walletAddresses.account!.id &&
element.keyImage!.contains(coin.keyImage!));
if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first;
coin.isFrozen = coinInfo.isFrozen;
coin.isSending = coinInfo.isSending;
coin.note = coinInfo.note;
} else {
_addCoinInfo(coin);
}
});
}
await _refreshUnspentCoinsInfo();
_askForUpdateBalance();
} catch (e, s) {
print(e.toString());
_onError?.call(FlutterErrorDetails(
exception: e,
stack: s,
library: this.runtimeType.toString(),
));
}
}
Future<void> _addCoinInfo(MoneroUnspent coin) async {
@ -632,4 +648,13 @@ abstract class MoneroWalletBase
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
}
}
@override
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
@override
String signMessage(String message, {String? address}) {
final useAddress = address ?? "";
return monero_wallet.signMessage(message, address: useAddress);
}
}

View file

@ -7,16 +7,19 @@ import 'package:cw_core/wallet_credentials.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_core/get_height_by_date.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/monero_wallet.dart';
import 'package:hive/hive.dart';
import 'package:polyseed/polyseed.dart';
class MoneroNewWalletCredentials extends WalletCredentials {
MoneroNewWalletCredentials({required String name, required this.language, String? password})
MoneroNewWalletCredentials({required String name, required this.language, required this.isPolyseed, String? password})
: super(name: name, password: password);
final String language;
final bool isPolyseed;
}
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
@ -68,10 +71,21 @@ class MoneroWalletService extends WalletService<
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
if (credentials.isPolyseed) {
final polyseed = Polyseed.create();
final lang = PolyseedLang.getByEnglishName(credentials.language);
final heightOverride =
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
overrideHeight: heightOverride);
}
await monero_wallet_manager.createWallet(
path: path,
password: credentials.password!,
language: credentials.language);
path: path, password: credentials.password!, language: credentials.language);
final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init();
@ -215,6 +229,12 @@ class MoneroWalletService extends WalletService<
@override
Future<MoneroWallet> restoreFromSeed(
MoneroRestoreWalletFromSeedCredentials credentials) async {
// Restore from Polyseed
if (Polyseed.isValidSeed(credentials.mnemonic)) {
return restoreFromPolyseed(credentials);
}
try {
final path = await pathForWallet(name: credentials.name, type: getType());
await monero_wallet_manager.restoreFromSeed(
@ -234,6 +254,47 @@ class MoneroWalletService extends WalletService<
}
}
Future<MoneroWallet> restoreFromPolyseed(MoneroRestoreWalletFromSeedCredentials credentials) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
final polyseedCoin = PolyseedCoin.POLYSEED_MONERO;
final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
return _restoreFromPolyseed(path, credentials.password!, polyseed, credentials.walletInfo!, lang);
} catch (e) {
// TODO: Implement Exception for wallet list service.
print('MoneroWalletsManager Error: $e');
rethrow;
}
}
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
final height = overrideHeight ?? getMoneroHeigthByDate(
date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
final spendKey = polyseed.generateKey(coin, 32).toHexString();
final seed = polyseed.encode(lang, coin);
walletInfo.isRecovery = true;
walletInfo.restoreHeight = height;
await monero_wallet_manager.restoreFromSpendKey(
path: path,
password: password,
seed: seed,
language: lang.nameEnglish,
restoreHeight: height,
spendKey: spendKey);
final wallet = MoneroWallet(
walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init();
return wallet;
}
Future<void> repairOldAndroidWallet(String name) async {
try {
if (!Platform.isAndroid) {

View file

@ -234,7 +234,6 @@ extern "C"
}
void setUnlocked(bool unlocked);
};
Monero::Coins *m_coins;
@ -375,6 +374,35 @@ extern "C"
return true;
}
bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error)
{
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey(
std::string(path),
std::string(password),
std::string(language),
_networkType,
(uint64_t)restoreHeight,
std::string(spendKey));
// Cache Raw to support Polyseed
wallet->setCacheAttribute("cakewallet.seed", std::string(seed));
int status;
std::string errorString;
wallet->statusWithErrorString(status, errorString);
if (status != Monero::Wallet::Status_Ok || !errorString.empty())
{
error = strdup(errorString.c_str());
return false;
}
change_current_wallet(wallet);
return true;
}
bool load_wallet(char *path, char *password, int32_t nettype)
{
nice(19);
@ -439,6 +467,11 @@ extern "C"
const char *seed()
{
std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed");
if (!_rawSeed.empty())
{
return strdup(_rawSeed.c_str());
}
return strdup(get_current_wallet()->seed().c_str());
}
@ -842,6 +875,12 @@ extern "C"
return m_transaction_history->count();
}
TransactionInfoRow* get_transaction(char * txId)
{
Monero::TransactionInfo *row = m_transaction_history->transaction(std::string(txId));
return new TransactionInfoRow(row);
}
int LedgerExchange(
unsigned char *command,
unsigned int cmd_len,
@ -971,6 +1010,22 @@ extern "C"
return result;
}
void freeze_coin(int index)
{
m_coins->setFrozen(index);
}
void thaw_coin(int index)
{
m_coins->thaw(index);
}
// Sign Messages //
char *sign_message(char *message, char *address = "")
{
return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str());
}
#ifdef __cplusplus
}

View file

@ -32,7 +32,8 @@ void store(char *path);
void set_trusted_daemon(bool arg);
bool trusted_daemon();
char *sign_message(char *message, char *address);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -266,6 +266,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.0"
hashlib:
dependency: transitive
description:
name: hashlib
sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
hashlib_codecs:
dependency: transitive
description:
name: hashlib_codecs
sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
hive:
dependency: transitive
description:
@ -478,10 +494,18 @@ packages:
dependency: transitive
description:
name: pointycastle
sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.6.2"
version: "3.7.3"
polyseed:
dependency: "direct main"
description:
name: polyseed
sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f"
url: "https://pub.dev"
source: hosted
version: "0.0.2"
pool:
dependency: transitive
description:
@ -623,15 +647,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
tor:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5"
url: "https://github.com/cake-tech/tor.git"
source: git
version: "0.0.1"
typed_data:
dependency: transitive
description:
@ -689,5 +704,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.0.6 <4.0.0"
flutter: ">=3.7.0"

View file

@ -19,6 +19,7 @@ dependencies:
flutter_mobx: ^2.0.6+1
intl: ^0.18.0
encrypt: ^5.0.1
polyseed: ^0.0.2
cw_core:
path: ../cw_core

View file

@ -58,13 +58,13 @@ abstract class NanoWalletBase
}
}
final String _mnemonic;
String _mnemonic;
final String _password;
final DerivationType _derivationType;
DerivationType _derivationType;
String? _privateKey;
String? _publicAddress;
String? _seedKey;
String? _hexSeed;
String? _representativeAddress;
Timer? _receiveTimer;
@ -85,22 +85,26 @@ abstract class NanoWalletBase
// initialize the different forms of private / public key we'll need:
Future<void> init() async {
if (_derivationType == DerivationType.unknown) {
_derivationType = DerivationType.nano;
}
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
// our "mnemonic" is actually a seedkey:
// our "mnemonic" is actually a hex form seed:
if (!_mnemonic.contains(' ')) {
_seedKey = _mnemonic;
_hexSeed = _mnemonic;
_mnemonic = "";
}
if (_seedKey == null) {
if (_hexSeed == null) {
if (_derivationType == DerivationType.nano) {
_seedKey = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
_hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
} else {
_seedKey = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' '));
_hexSeed = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' '));
}
}
_privateKey = await NanoUtil.uniSeedToPrivate(_seedKey!, 0, type);
_publicAddress = await NanoUtil.uniSeedToAddress(_seedKey!, 0, type);
_privateKey = await NanoUtil.uniSeedToPrivate(_hexSeed!, 0, type);
_publicAddress = await NanoUtil.uniSeedToAddress(_hexSeed!, 0, type);
this.walletInfo.address = _publicAddress!;
await walletAddresses.init();
@ -182,9 +186,9 @@ abstract class NanoWalletBase
final block = await _client.constructSendBlock(
amountRaw: amt.toString(),
destinationAddress: credentials.outputs.first.isParsedAddress
? credentials.outputs.first.extractedAddress!
: credentials.outputs.first.address,
destinationAddress: txOut.isParsedAddress
? txOut.extractedAddress!
: txOut.address,
privateKey: _privateKey!,
balanceAfterTx: runningBalance,
previousHash: previousHash,
@ -275,11 +279,11 @@ abstract class NanoWalletBase
@override
NanoWalletKeys get keys {
return NanoWalletKeys(seedKey: _seedKey!);
return NanoWalletKeys(seedKey: _hexSeed!);
}
@override
String? get privateKey => _seedKey!;
String? get privateKey => _privateKey!;
@override
Future<void> rescan({required int height}) async {
@ -297,7 +301,9 @@ abstract class NanoWalletBase
}
@override
String get seed => _mnemonic;
String? get seed => _mnemonic.isNotEmpty ? _mnemonic : null;
String get hexSeed => _hexSeed!;
String get representative => _representativeAddress ?? "";
@ -330,7 +336,7 @@ abstract class NanoWalletBase
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'seedKey': _seedKey,
'seedKey': _hexSeed,
'mnemonic': _mnemonic,
'currentBalance': balance[currency]?.currentBalance.toString() ?? "0",
'receivableBalance': balance[currency]?.receivableBalance.toString() ?? "0",
@ -351,9 +357,9 @@ abstract class NanoWalletBase
formattedCurrentBalance: data['currentBalance'] as String? ?? "0",
formattedReceivableBalance: data['receivableBalance'] as String? ?? "0");
DerivationType derivationType = DerivationType.bip39;
if (data['derivationType'] == "DerivationType.nano") {
derivationType = DerivationType.nano;
DerivationType derivationType = DerivationType.nano;
if (data['derivationType'] == "DerivationType.bip39") {
derivationType = DerivationType.bip39;
}
walletInfo.derivationType = derivationType;
@ -390,9 +396,9 @@ abstract class NanoWalletBase
Future<void> regenerateAddress() async {
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
_privateKey =
await NanoUtil.uniSeedToPrivate(_seedKey!, this.walletAddresses.account!.id, type);
await NanoUtil.uniSeedToPrivate(_hexSeed!, this.walletAddresses.account!.id, type);
_publicAddress =
await NanoUtil.uniSeedToAddress(_seedKey!, this.walletAddresses.account!.id, type);
await NanoUtil.uniSeedToAddress(_hexSeed!, this.walletAddresses.account!.id, type);
this.walletInfo.address = _publicAddress!;
this.walletAddresses.address = _publicAddress!;

30
cw_polygon/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

10
cw_polygon/.metadata Normal file
View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
channel: stable
project_type: package

3
cw_polygon/CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
cw_polygon/LICENSE Normal file
View file

@ -0,0 +1 @@
TODO: Add your license here.

39
cw_polygon/README.md Normal file
View file

@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.

View file

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

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

View file

@ -0,0 +1,86 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
class DefaultPolygonErc20Tokens {
final List<Erc20Token> _defaultTokens = [
Erc20Token(
name: "Wrapped Ether",
symbol: "WETH",
contractAddress: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
decimal: 18,
enabled: false,
),
Erc20Token(
name: "Tether USD (PoS)",
symbol: "USDT",
contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
decimal: 6,
enabled: true,
),
Erc20Token(
name: "USD Coin",
symbol: "USDC",
contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
decimal: 6,
enabled: true,
),
Erc20Token(
name: "USD Coin (POS)",
symbol: "USDC.e",
contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
decimal: 6,
enabled: false,
),
Erc20Token(
name: "Avalanche Token",
symbol: "AVAX",
contractAddress: "0x2C89bbc92BD86F8075d1DEcc58C7F4E0107f286b",
decimal: 18,
enabled: false,
),
Erc20Token(
name: "Wrapped BTC (PoS)",
symbol: "WBTC",
contractAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
decimal: 8,
enabled: false,
),
Erc20Token(
name: "Dai (PoS)",
symbol: "DAI",
contractAddress: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
decimal: 18,
enabled: true,
),
Erc20Token(
name: "SHIBA INU (PoS)",
symbol: "SHIB",
contractAddress: "0x6f8a06447Ff6FcF75d803135a7de15CE88C1d4ec",
decimal: 18,
enabled: false,
),
Erc20Token(
name: "Uniswap (PoS)",
symbol: "UNI",
contractAddress: "0xb33EaAd8d922B1083446DC23f610c2567fB5180f",
decimal: 18,
enabled: false,
),
];
List<Erc20Token> get initialPolygonErc20Tokens => _defaultTokens.map((token) {
String? iconPath;
try {
iconPath = CryptoCurrency.all
.firstWhere((element) =>
element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
} catch (_) {}
if (iconPath != null) {
return Erc20Token.copyWith(token, iconPath);
}
return token;
}).toList();
}

View file

@ -0,0 +1,19 @@
import 'dart:typed_data';
import 'package:cw_ethereum/pending_ethereum_transaction.dart';
class PendingPolygonTransaction extends PendingEthereumTransaction {
PendingPolygonTransaction({
required Function sendTransaction,
required Uint8List signedTransaction,
required BigInt fee,
required String amount,
required int exponent,
}) : super(
amount: amount,
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: fee,
exponent: exponent,
);
}

View file

@ -0,0 +1,34 @@
import 'dart:convert';
import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_polygon/polygon_transaction_model.dart';
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
class PolygonClient extends EthereumClient {
@override
Future<List<PolygonTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async {
try {
final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", {
"module": "account",
"action": contractAddress != null ? "tokentx" : "txlist",
if (contractAddress != null) "contractaddress": contractAddress,
"address": address,
"apikey": secrets.polygonScanApiKey,
}));
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
return (jsonResponse['result'] as List)
.map((e) => PolygonTransactionModel.fromJson(e as Map<String, dynamic>))
.toList();
}
return [];
} catch (e) {
print(e);
return [];
}
}
}

View file

@ -0,0 +1,6 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_ethereum/ethereum_exceptions.dart';
class PolygonTransactionCreationException extends EthereumTransactionCreationException {
PolygonTransactionCreationException(CryptoCurrency currency) : super(currency);
}

View file

@ -0,0 +1,25 @@
import 'package:intl/intl.dart';
const polygonAmountLength = 12;
const polygonAmountDivider = 1000000000000;
final polygonAmountFormat = NumberFormat()
..maximumFractionDigits = polygonAmountLength
..minimumFractionDigits = 1;
class PolygonFormatter {
static int parsePolygonAmount(String amount) {
try {
return (double.parse(amount) * polygonAmountDivider).round();
} catch (_) {
return 0;
}
}
static double parsePolygonAmountToDouble(int amount) {
try {
return amount / polygonAmountDivider;
} catch (_) {
return 0;
}
}
}

View file

@ -0,0 +1,5 @@
class PolygonMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'Polygon mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
}

View file

@ -0,0 +1,18 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
import 'package:cw_polygon/polygon_transaction_priority.dart';
class PolygonTransactionCredentials extends EthereumTransactionCredentials {
PolygonTransactionCredentials(
List<OutputInfo> outputs, {
required PolygonTransactionPriority? priority,
required CryptoCurrency currency,
final int? feeRate,
}) : super(
outputs,
currency: currency,
priority: priority,
feeRate: feeRate,
);
}

View file

@ -0,0 +1,77 @@
import 'dart:convert';
import 'dart:core';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_ethereum/file.dart';
import 'package:cw_polygon/polygon_transaction_info.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
part 'polygon_transaction_history.g.dart';
const transactionsHistoryFileName = 'polygon_transactions.json';
class PolygonTransactionHistory = PolygonTransactionHistoryBase with _$PolygonTransactionHistory;
abstract class PolygonTransactionHistoryBase extends TransactionHistoryBase<PolygonTransactionInfo>
with Store {
PolygonTransactionHistoryBase({required this.walletInfo, required String password})
: _password = password {
transactions = ObservableMap<String, PolygonTransactionInfo>();
}
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, s) {
print('Error while saving polygon transaction history: ${e.toString()}');
print(s);
}
}
@override
void addOne(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
@override
void addMany(Map<String, PolygonTransactionInfo> 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);
if (content.isEmpty) {
return {};
}
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 = PolygonTransactionInfo.fromJson(val);
_update(tx);
}
});
} catch (e) {
print(e);
}
}
void _update(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
}

View file

@ -0,0 +1,49 @@
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_ethereum/ethereum_transaction_info.dart';
class PolygonTransactionInfo extends EthereumTransactionInfo {
PolygonTransactionInfo({
required String id,
required int height,
required BigInt ethAmount,
int exponent = 18,
required TransactionDirection direction,
required DateTime date,
required bool isPending,
required BigInt ethFee,
required int confirmations,
String tokenSymbol = "MATIC",
required String? to,
}) : super(
confirmations: confirmations,
id: id,
height: height,
ethAmount: ethAmount,
exponent: exponent,
direction: direction,
date: date,
isPending: isPending,
ethFee: ethFee,
to: to,
tokenSymbol: tokenSymbol,
);
factory PolygonTransactionInfo.fromJson(Map<String, dynamic> data) {
return PolygonTransactionInfo(
id: data['id'] as String,
height: data['height'] as int,
ethAmount: BigInt.parse(data['amount']),
exponent: data['exponent'] as int,
ethFee: BigInt.parse(data['fee']),
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
to: data['to'],
);
}
@override
String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC';
}

View file

@ -0,0 +1,49 @@
import 'package:cw_ethereum/ethereum_transaction_model.dart';
class PolygonTransactionModel extends EthereumTransactionModel {
PolygonTransactionModel({
required DateTime date,
required String hash,
required String from,
required String to,
required BigInt amount,
required int gasUsed,
required BigInt gasPrice,
required String contractAddress,
required int confirmations,
required int blockNumber,
required String? tokenSymbol,
required int? tokenDecimal,
required bool isError,
}) : super(
amount: amount,
date: date,
hash: hash,
from: from,
to: to,
gasPrice: gasPrice,
gasUsed: gasUsed,
confirmations: confirmations,
contractAddress: contractAddress,
blockNumber: blockNumber,
tokenDecimal: tokenDecimal,
tokenSymbol: tokenSymbol,
isError: isError,
);
factory PolygonTransactionModel.fromJson(Map<String, dynamic> json) => PolygonTransactionModel(
date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
hash: json["hash"],
from: json["from"],
to: json["to"],
amount: BigInt.parse(json["value"]),
gasUsed: int.parse(json["gasUsed"]),
gasPrice: BigInt.parse(json["gasPrice"]),
contractAddress: json["contractAddress"],
confirmations: int.parse(json["confirmations"]),
blockNumber: int.parse(json["blockNumber"]),
tokenSymbol: json["tokenSymbol"] ?? "MATIC",
tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
isError: json["isError"] == "1",
);
}

View file

@ -0,0 +1,51 @@
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
class PolygonTransactionPriority extends EthereumTransactionPriority {
const PolygonTransactionPriority({required String title, required int raw, required int tip})
: super(title: title, raw: raw, tip: tip);
static const List<PolygonTransactionPriority> all = [fast, medium, slow];
static const PolygonTransactionPriority slow =
PolygonTransactionPriority(title: 'slow', raw: 0, tip: 1);
static const PolygonTransactionPriority medium =
PolygonTransactionPriority(title: 'Medium', raw: 1, tip: 2);
static const PolygonTransactionPriority fast =
PolygonTransactionPriority(title: 'Fast', raw: 2, tip: 4);
static PolygonTransactionPriority deserialize({required int raw}) {
switch (raw) {
case 0:
return slow;
case 1:
return medium;
case 2:
return fast;
default:
throw Exception('Unexpected token: $raw for PolygonTransactionPriority deserialize');
}
}
@override
String get units => 'gas';
@override
String toString() {
var label = '';
switch (this) {
case PolygonTransactionPriority.slow:
label = 'Slow';
break;
case PolygonTransactionPriority.medium:
label = 'Medium';
break;
case PolygonTransactionPriority.fast:
label = 'Fast';
break;
default:
break;
}
return label;
}
}

View file

@ -0,0 +1,540 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/cake_hive.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_addresses.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_ethereum/erc20_balance.dart';
import 'package:cw_ethereum/ethereum_formatter.dart';
import 'package:cw_ethereum/ethereum_transaction_model.dart';
import 'package:cw_ethereum/file.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_polygon/default_erc20_tokens.dart';
import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_exceptions.dart';
import 'package:cw_polygon/polygon_formatter.dart';
import 'package:cw_polygon/polygon_transaction_credentials.dart';
import 'package:cw_polygon/polygon_transaction_history.dart';
import 'package:cw_polygon/polygon_transaction_info.dart';
import 'package:cw_polygon/polygon_transaction_model.dart';
import 'package:cw_polygon/polygon_transaction_priority.dart';
import 'package:cw_polygon/polygon_wallet_addresses.dart';
import 'package:hive/hive.dart';
import 'package:hex/hex.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:bip32/bip32.dart' as bip32;
part 'polygon_wallet.g.dart';
class PolygonWallet = PolygonWalletBase with _$PolygonWallet;
abstract class PolygonWalletBase extends WalletBase<ERC20Balance,
PolygonTransactionHistory, PolygonTransactionInfo> with Store {
PolygonWalletBase({
required WalletInfo walletInfo,
String? mnemonic,
String? privateKey,
required String password,
ERC20Balance? initialBalance,
}) : syncStatus = NotConnectedSyncStatus(),
_password = password,
_mnemonic = mnemonic,
_hexPrivateKey = privateKey,
_isTransactionUpdating = false,
_client = PolygonClient(),
walletAddresses = PolygonWalletAddresses(walletInfo),
balance = ObservableMap<CryptoCurrency, ERC20Balance>.of({
CryptoCurrency.maticpoly: initialBalance ?? ERC20Balance(BigInt.zero)
}),
super(walletInfo) {
this.walletInfo = walletInfo;
transactionHistory =
PolygonTransactionHistory(walletInfo: walletInfo, password: password);
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
CakeHive.registerAdapter(Erc20TokenAdapter());
}
_sharedPrefs.complete(SharedPreferences.getInstance());
}
final String? _mnemonic;
final String? _hexPrivateKey;
final String _password;
late final Box<Erc20Token> polygonErc20TokensBox;
late final EthPrivateKey _polygonPrivateKey;
EthPrivateKey get polygonPrivateKey => _polygonPrivateKey;
late PolygonClient _client;
int? _gasPrice;
int? _estimatedGas;
bool _isTransactionUpdating;
// TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
Timer? _transactionsUpdateTimer;
@override
WalletAddresses walletAddresses;
@override
@observable
SyncStatus syncStatus;
@override
@observable
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
Completer<SharedPreferences> _sharedPrefs = Completer();
Future<void> init() async {
polygonErc20TokensBox =
await CakeHive.openBox<Erc20Token>(Erc20Token.polygonBoxName);
await walletAddresses.init();
await transactionHistory.init();
_polygonPrivateKey = await getPrivateKey(
mnemonic: _mnemonic,
privateKey: _hexPrivateKey,
password: _password,
);
walletAddresses.address = _polygonPrivateKey.address.toString();
await save();
}
@override
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
try {
if (priority is PolygonTransactionPriority) {
final priorityFee =
EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
}
return 0;
} catch (e) {
return 0;
}
}
@override
Future<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
@override
void close() {
_client.stop();
_transactionsUpdateTimer?.cancel();
}
@action
@override
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
final isConnected = _client.connect(node);
if (!isConnected) {
throw Exception("Polygon Node connection failed");
}
_client.setListeners(_polygonPrivateKey.address, _onNewTransaction);
_setTransactionUpdateTimer();
syncStatus = ConnectedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as PolygonTransactionCredentials;
final outputs = _credentials.outputs;
final hasMultiDestination = outputs.length > 1;
final CryptoCurrency transactionCurrency = balance.keys
.firstWhere((element) => element.title == _credentials.currency.title);
final _erc20Balance = balance[transactionCurrency]!;
BigInt totalAmount = BigInt.zero;
int exponent =
transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
num amountToPolygonMultiplier = pow(10, exponent);
// so far this can not be made with Polygon as Polygon does not support multiple recipients
if (hasMultiDestination) {
if (outputs.any(
(item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
throw PolygonTransactionCreationException(transactionCurrency);
}
final totalOriginalAmount = PolygonFormatter.parsePolygonAmountToDouble(
outputs.fold(
0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
totalAmount =
BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw PolygonTransactionCreationException(transactionCurrency);
}
} else {
final output = outputs.first;
// since the fees are taken from Ethereum
// then no need to subtract the fees from the amount if send all
final BigInt allAmount;
if (transactionCurrency is Erc20Token) {
allAmount = _erc20Balance.balance;
} else {
allAmount = _erc20Balance.balance -
BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
}
final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
output.formattedCryptoAmount ?? 0);
totalAmount = output.sendAll
? allAmount
: BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw PolygonTransactionCreationException(transactionCurrency);
}
}
final pendingPolygonTransaction = await _client.signTransaction(
privateKey: _polygonPrivateKey,
toAddress: _credentials.outputs.first.isParsedAddress
? _credentials.outputs.first.extractedAddress!
: _credentials.outputs.first.address,
amount: totalAmount.toString(),
gas: _estimatedGas!,
priority: _credentials.priority!,
currency: transactionCurrency,
exponent: exponent,
contractAddress: transactionCurrency is Erc20Token
? transactionCurrency.contractAddress
: null,
);
return pendingPolygonTransaction;
}
Future<void> _updateTransactions() async {
try {
if (_isTransactionUpdating) {
return;
}
bool isPolygonScanEnabled = (await _sharedPrefs.future).getBool("use_polygonscan") ?? true;
if (!isPolygonScanEnabled) {
return;
}
_isTransactionUpdating = true;
final transactions = await fetchTransactions();
transactionHistory.addMany(transactions);
await transactionHistory.save();
_isTransactionUpdating = false;
} catch (_) {
_isTransactionUpdating = false;
}
}
@override
Future<Map<String, PolygonTransactionInfo>> fetchTransactions() async {
final address = _polygonPrivateKey.address.hex;
final transactions = await _client.fetchTransactions(address);
final List<Future<List<PolygonTransactionModel>>> polygonErc20TokensTransactions =
[];
for (var token in balance.keys) {
if (token is Erc20Token) {
polygonErc20TokensTransactions.add(_client.fetchTransactions(
address,
contractAddress: token.contractAddress,
) as Future<List<PolygonTransactionModel>>);
}
}
final tokensTransaction = await Future.wait(polygonErc20TokensTransactions);
transactions.addAll(tokensTransaction.expand((element) => element));
final Map<String, PolygonTransactionInfo> result = {};
for (var transactionModel in transactions) {
if (transactionModel.isError) {
continue;
}
result[transactionModel.hash] = PolygonTransactionInfo(
id: transactionModel.hash,
height: transactionModel.blockNumber,
ethAmount: transactionModel.amount,
direction: transactionModel.from == address
? TransactionDirection.outgoing
: TransactionDirection.incoming,
isPending: false,
date: transactionModel.date,
confirmations: transactionModel.confirmations,
ethFee:
BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
exponent: transactionModel.tokenDecimal ?? 18,
tokenSymbol: transactionModel.tokenSymbol ?? "MATIC",
to: transactionModel.to,
);
}
return result;
}
@override
Object get keys => throw UnimplementedError("keys");
@override
Future<void> rescan({required int height}) {
throw UnimplementedError("rescan");
}
@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;
@override
String get privateKey => HEX.encode(_polygonPrivateKey.privateKey);
@action
@override
Future<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
await _updateBalance();
await _updateTransactions();
_gasPrice = await _client.getGasUnitPrice();
_estimatedGas = await _client.getEstimatedGas();
Timer.periodic(const Duration(minutes: 1),
(timer) async => _gasPrice = await _client.getGasUnitPrice());
Timer.periodic(const Duration(seconds: 10),
(timer) async => _estimatedGas = await _client.getEstimatedGas());
syncStatus = SyncedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
}
}
Future<String> makePath() async =>
pathForWallet(name: walletInfo.name, type: walletInfo.type);
String toJSON() => json.encode({
'mnemonic': _mnemonic,
'private_key': privateKey,
'balance': balance[currency]!.toJSON(),
});
static Future<PolygonWallet> 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 privateKey = data['private_key'] as String?;
final balance = ERC20Balance.fromJSON(data['balance'] as String) ??
ERC20Balance(BigInt.zero);
return PolygonWallet(
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
privateKey: privateKey,
initialBalance: balance,
);
}
Future<void> _updateBalance() async {
balance[currency] = await _fetchMaticBalance();
await _fetchErc20Balances();
await save();
}
Future<ERC20Balance> _fetchMaticBalance() async {
final balance = await _client.getBalance(_polygonPrivateKey.address);
return ERC20Balance(balance.getInWei);
}
Future<void> _fetchErc20Balances() async {
for (var token in polygonErc20TokensBox.values) {
try {
if (token.enabled) {
balance[token] = await _client.fetchERC20Balances(
_polygonPrivateKey.address,
token.contractAddress,
);
} else {
balance.remove(token);
}
} catch (_) {}
}
}
Future<EthPrivateKey> getPrivateKey(
{String? mnemonic, String? privateKey, required String password}) async {
assert(mnemonic != null || privateKey != null);
if (privateKey != null) {
return EthPrivateKey.fromHex(privateKey);
}
final seed = bip39.mnemonicToSeed(mnemonic!);
final root = bip32.BIP32.fromSeed(seed);
const _hdPathPolygon = "m/44'/60'/0'/0";
const index = 0;
final addressAtIndex = root.derivePath("$_hdPathPolygon/$index");
return EthPrivateKey.fromHex(
HEX.encode(addressAtIndex.privateKey as List<int>));
}
Future<void>? updateBalance() async => await _updateBalance();
List<Erc20Token> get erc20Currencies => polygonErc20TokensBox.values.toList();
Future<void> addErc20Token(Erc20Token token) async {
String? iconPath;
try {
iconPath = CryptoCurrency.all
.firstWhere((element) =>
element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
} catch (_) {}
final _token = Erc20Token(
name: token.name,
symbol: token.symbol,
contractAddress: token.contractAddress,
decimal: token.decimal,
enabled: token.enabled,
iconPath: iconPath,
);
await polygonErc20TokensBox.put(_token.contractAddress, _token);
if (_token.enabled) {
balance[_token] = await _client.fetchERC20Balances(
_polygonPrivateKey.address,
_token.contractAddress,
);
} else {
balance.remove(_token);
}
}
Future<void> deleteErc20Token(Erc20Token token) async {
await token.delete();
balance.remove(token);
_updateBalance();
}
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
await _client.getErc20Token(contractAddress);
void _onNewTransaction() {
_updateBalance();
_updateTransactions();
}
void addInitialTokens() {
final initialErc20Tokens =
DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
for (var token in initialErc20Tokens) {
polygonErc20TokensBox.put(token.contractAddress, token);
}
}
@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);
}
void _setTransactionUpdateTimer() {
if (_transactionsUpdateTimer?.isActive ?? false) {
_transactionsUpdateTimer!.cancel();
}
_transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) {
_updateTransactions();
_updateBalance();
});
}
void updatePolygonScanUsageState(bool isEnabled) {
if (isEnabled) {
_updateTransactions();
_setTransactionUpdateTimer();
} else {
_transactionsUpdateTimer?.cancel();
}
}
@override
String signMessage(String message, {String? address = null}) => bytesToHex(
_polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
Web3Client? getWeb3Client() => _client.getWeb3Client();
}

View file

@ -0,0 +1,5 @@
import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
class PolygonWalletAddresses extends EthereumWalletAddresses {
PolygonWalletAddresses(super.walletInfo);
}

View file

@ -0,0 +1,28 @@
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class PolygonNewWalletCredentials extends WalletCredentials {
PolygonNewWalletCredentials({required String name, WalletInfo? walletInfo})
: super(name: name, walletInfo: walletInfo);
}
class PolygonRestoreWalletFromSeedCredentials extends WalletCredentials {
PolygonRestoreWalletFromSeedCredentials(
{required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
final String mnemonic;
}
class PolygonRestoreWalletFromPrivateKey extends WalletCredentials {
PolygonRestoreWalletFromPrivateKey(
{required String name,
required String password,
required this.privateKey,
WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
final String privateKey;
}

View file

@ -0,0 +1,123 @@
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_ethereum/ethereum_mnemonics.dart';
import 'package:cw_polygon/polygon_wallet.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:hive/hive.dart';
import 'polygon_wallet_creation_credentials.dart';
import 'package:collection/collection.dart';
class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
PolygonRestoreWalletFromSeedCredentials, PolygonRestoreWalletFromPrivateKey> {
PolygonWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@override
Future<PolygonWallet> create(PolygonNewWalletCredentials credentials) async {
final strength = (credentials.seedPhraseLength == 12)
? 128
: (credentials.seedPhraseLength == 24)
? 256
: 128;
final mnemonic = bip39.generateMnemonic(strength: strength);
final wallet = PolygonWallet(
walletInfo: credentials.walletInfo!,
mnemonic: mnemonic,
password: credentials.password!,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
WalletType getType() => WalletType.polygon;
@override
Future<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<PolygonWallet> openWallet(String name, String password) async {
final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await PolygonWalletBase.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init();
await wallet.save();
return wallet;
}
@override
Future<void> remove(String wallet) async {
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
final walletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
await walletInfoSource.delete(walletInfo.key);
}
@override
Future<PolygonWallet> restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async {
final wallet = PolygonWallet(
password: credentials.password!,
privateKey: credentials.privateKey,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<PolygonWallet> restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EthereumMnemonicIsIncorrectException();
}
final wallet = PolygonWallet(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
final currentWallet = await PolygonWalletBase.open(
password: password, name: currentName, walletInfo: currentWalletInfo);
await currentWallet.renameWalletFiles(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName;
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
}

73
cw_polygon/pubspec.yaml Normal file
View file

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

View file

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

View file

@ -122,8 +122,6 @@ PODS:
- Flutter
- MTBBarcodeScanner (5.0.11)
- OrderedSet (5.0.0)
- package_info (0.0.1):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
@ -177,7 +175,6 @@ DEPENDENCIES:
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
@ -239,8 +236,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/in_app_review/ios"
local_auth_ios:
:path: ".symlinks/plugins/local_auth_ios/ios"
package_info:
:path: ".symlinks/plugins/package_info/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
@ -287,7 +282,6 @@ SPEC CHECKSUMS:
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6

View file

@ -160,6 +160,26 @@
<string>bitcoincash-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>polygon</string>
<key>CFBundleURLSchemes</key>
<array>
<string>polygon</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>polygon-wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>polygon-wallet</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>

View file

@ -0,0 +1,179 @@
import 'dart:convert';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
class DFXBuyProvider {
DFXBuyProvider({required WalletBase wallet}) : this._wallet = wallet;
final WalletBase _wallet;
static const _baseUrl = 'api.dfx.swiss';
static const _authPath = '/v1/auth/signMessage';
static const _signUpPath = '/v1/auth/signUp';
static const _signInPath = '/v1/auth/signIn';
static const walletName = 'CakeWallet';
String get assetOut {
switch (_wallet.type) {
case WalletType.bitcoin:
return 'BTC';
case WalletType.bitcoinCash:
return 'BCH';
case WalletType.litecoin:
return 'LTC';
case WalletType.monero:
return 'XMR';
case WalletType.ethereum:
return 'ETH';
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
}
}
String get blockchain {
switch (_wallet.type) {
case WalletType.bitcoin:
case WalletType.bitcoinCash:
case WalletType.litecoin:
return 'Bitcoin';
case WalletType.monero:
return 'Monero';
case WalletType.ethereum:
return 'Ethereum';
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
}
}
Future<String> getSignMessage() async {
final walletAddress = _wallet.walletAddresses.address;
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
var response = await http.get(uri, headers: {'accept': 'application/json'});
if (response.statusCode == 200) {
final responseBody = jsonDecode(response.body);
return responseBody['message'] as String;
} else {
throw Exception(
'Failed to get sign message. Status: ${response.statusCode} ${response.body}');
}
}
Future<String> signUp() async {
final signMessage = getSignature(await getSignMessage());
final walletAddress = _wallet.walletAddresses.address;
final requestBody = jsonEncode({
'wallet': walletName,
'address': walletAddress,
'signature': signMessage,
});
final uri = Uri.https(_baseUrl, _signUpPath);
var response = await http.post(uri,
headers: {'Content-Type': 'application/json'}, body: requestBody);
if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body);
return responseBody['accessToken'] as String;
} else {
throw Exception(
'Failed to sign up. Status: ${response.statusCode} ${response.body}');
}
}
Future<String> signIn() async {
final signMessage = getSignature(await getSignMessage());
final walletAddress = _wallet.walletAddresses.address;
final requestBody = jsonEncode({
'address': walletAddress,
'signature': signMessage,
});
final uri = Uri.https(_baseUrl, _signInPath);
var response = await http.post(uri,
headers: {'Content-Type': 'application/json'}, body: requestBody);
if (response.statusCode == 201) {
final responseBody = jsonDecode(response.body);
return responseBody['accessToken'] as String;
} else {
throw Exception(
'Failed to sign in. Status: ${response.statusCode} ${response.body}');
}
}
String getSignature(String message) {
switch (_wallet.type) {
case WalletType.ethereum:
return _wallet.signMessage(message);
case WalletType.monero:
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
return _wallet.signMessage(message,
address: _wallet.walletAddresses.address);
default:
throw Exception("WalletType is not available for DFX ${_wallet.type}");
}
}
Future<void> launchProvider(BuildContext context) async {
try {
final assetOut = this.assetOut;
final blockchain = this.blockchain;
String accessToken;
try {
accessToken = await signUp();
} on Exception catch (e) {
if (e.toString().contains('409')) {
accessToken = await signIn();
} else {
rethrow;
}
}
final uri = Uri.https('services.dfx.swiss', '/buy', {
'session': accessToken,
'lang': 'en',
'asset-out': assetOut,
'blockchain': blockchain,
'asset-in': 'EUR',
});
if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage,
arguments: [S.of(context).buy, uri]);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else {
throw Exception('Could not launch URL');
}
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: "DFX Connect",
alertContent: S.of(context).buy_provider_unavailable + ': $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
}
}

View file

@ -271,6 +271,8 @@ class AddressValidator extends TextValidator {
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
case CryptoCurrency.eth:
return '0x[0-9a-zA-Z]{42}';
case CryptoCurrency.maticpoly:
return '0x[0-9a-zA-Z]{42}';
case CryptoCurrency.nano:
return 'nano_[0-9a-zA-Z]{60}';
case CryptoCurrency.banano:

View file

@ -436,6 +436,7 @@ class BackupService {
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = keychainJSON[backupPasswordKey] as String;
await _flutterSecureStorage.delete(key: backupPasswordKey);
await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async {
@ -443,6 +444,7 @@ class BackupService {
await importWalletKeychainInfo(info);
});
await _flutterSecureStorage.delete(key: pinCodeKey);
await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync();
@ -462,6 +464,7 @@ class BackupService {
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = keychainJSON[backupPasswordKey] as String;
await _flutterSecureStorage.delete(key: backupPasswordKey);
await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async {
@ -469,6 +472,7 @@ class BackupService {
await importWalletKeychainInfo(info);
});
await _flutterSecureStorage.delete(key: pinCodeKey);
await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync();

View file

@ -16,7 +16,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
final Map<String, String> queryParams = {
'interval_count': '1',
'base': crypto,
'base': crypto.split(".").first,
'quote': fiat,
'key': secrets.fiatApiKey,
};

View file

@ -19,6 +19,7 @@ class KeyService {
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = encodeWalletPassword(password: password);
await _secureStorage.delete(key: key);
await _secureStorage.write(key: key, value: encodedPassword);
}

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
@ -34,6 +35,8 @@ class SeedValidator extends Validator<MnemonicItem> {
case WalletType.nano:
case WalletType.banano:
return nano!.getNanoWordList(language);
case WalletType.polygon:
return polygon!.getPolygonWordList(language);
default:
return [];
}

View file

@ -6,6 +6,7 @@ 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/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
@ -14,7 +15,6 @@ import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget
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;
@ -46,13 +46,12 @@ class EvmChainServiceImpl implements ChainService {
required this.wcKeyService,
required this.bottomSheetService,
required this.wallet,
Web3Client? ethClient,
}) : ethClient = ethClient ??
Web3Client? web3Client,
}) : ethClient = web3Client ??
Web3Client(
appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(),
appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(),
http.Client(),
) {
for (final String event in getEvents()) {
wallet.registerEventEmitter(chainId: getChainId(), event: event);
}
@ -138,7 +137,8 @@ class EvmChainServiceImpl implements ChainService {
try {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@ -176,13 +176,15 @@ class EvmChainServiceImpl implements ChainService {
try {
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
final String signature = hex.encode(
credentials.signPersonalMessageToUint8List(
Uint8List.fromList(utf8.encode(message)),
chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
),
);
log(signature);
@ -212,7 +214,8 @@ class EvmChainServiceImpl implements ChainService {
}
// Load the private key
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
@ -232,7 +235,11 @@ class EvmChainServiceImpl implements ChainService {
);
try {
final result = await ethClient.sendTransaction(credentials, transaction);
final result = await ethClient.sendTransaction(
credentials,
transaction,
chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
);
log('Result: $result');
@ -267,7 +274,8 @@ class EvmChainServiceImpl implements ChainService {
return authError;
}
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
final List<ChainKeyModel> keys = wcKeyService
.getKeysForChain(getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type));
return EthSigUtil.signTypedData(
privateKey: keys[0].privateKey,

View file

@ -1,9 +1,11 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
abstract class WalletConnectKeyService {
/// Returns a list of all the keys.
@ -32,16 +34,36 @@ class KeyServiceImpl implements WalletConnectKeyService {
'eip155:42161',
'eip155:80001',
],
privateKey: ethereum!.getPrivateKey(wallet),
publicKey: ethereum!.getPublicKey(wallet),
privateKey: _getPrivateKeyForWallet(wallet),
publicKey: _getPublicKeyForWallet(wallet),
),
];
late final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
late final List<ChainKeyModel> _keys;
static String _getPrivateKeyForWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.ethereum:
return ethereum!.getPrivateKey(wallet);
case WalletType.polygon:
return polygon!.getPrivateKey(wallet);
default:
return '';
}
}
static String _getPublicKeyForWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.ethereum:
return ethereum!.getPublicKey(wallet);
case WalletType.polygon:
return polygon!.getPublicKey(wallet);
default:
return '';
}
}
@override
List<String> getChains() {
final List<String> chainIds = [];

View file

@ -9,6 +9,7 @@ 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/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
@ -164,8 +165,10 @@ abstract class Web3WalletServiceBase with Store {
void _onSessionProposal(SessionProposalEvent? args) async {
if (args != null) {
final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
final Widget modalWidget = Web3RequestModal(
child: ConnectionRequestWidget(
chaindIdNamespace: chaindIdNamespace,
wallet: _web3Wallet,
sessionProposal: SessionRequestModel(request: args.params),
),
@ -232,12 +235,13 @@ abstract class Web3WalletServiceBase with Store {
@action
Future<void> _onAuthRequest(AuthRequest? args) async {
if (args != null) {
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain('eip155:1');
final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(chaindIdNamespace);
// Create the message to be signed
final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}';
final String iss = 'did:pkh:$chaindIdNamespace:${chainKeys.first.publicKey}';
final Widget modalWidget = Web3RequestModal(
child: ConnectionRequestWidget(
chaindIdNamespace: chaindIdNamespace,
wallet: _web3Wallet,
authRequest: AuthRequestModel(iss: iss, request: args),
),

View file

@ -18,6 +18,8 @@ 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';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
@ -88,6 +90,7 @@ import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dar
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/seed_type_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';
@ -225,6 +228,7 @@ import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'buy/dfx/dfx_buy_provider.dart';
import 'core/totp_request_details.dart';
import 'src/screens/settings/desktop_settings/desktop_settings_page.dart';
@ -720,6 +724,8 @@ Future<void> setup({
getIt.registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet!));
getIt.registerFactory<SeedTypeViewModel>(() => SeedTypeViewModel(getIt.get<AppStore>()));
getIt.registerFactoryParam<WalletSeedPage, bool, void>((bool isWalletCreated, _) =>
WalletSeedPage(getIt.get<WalletSeedViewModel>(), isNewWalletCreated: isWalletCreated));
@ -754,7 +760,7 @@ Future<void> setup({
final wallet = getIt.get<AppStore>().wallet;
return ConnectionSyncPage(
getIt.get<DashboardViewModel>(),
wallet?.type == WalletType.ethereum ? getIt.get<Web3WalletService>() : null,
isEVMCompatibleChain(wallet!.type) ? getIt.get<Web3WalletService>() : null,
);
});
@ -797,6 +803,9 @@ Future<void> setup({
getIt.registerFactory<RobinhoodBuyProvider>(
() => RobinhoodBuyProvider(wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory<DFXBuyProvider>(
() => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!,
@ -848,9 +857,11 @@ Future<void> setup({
return ethereum!.createEthereumWalletService(_walletInfoSource);
case WalletType.bitcoinCash:
return bitcoinCash!
.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.nano:
return nano!.createNanoWalletService(_walletInfoSource);
case WalletType.polygon:
return polygon!.createPolygonWalletService(_walletInfoSource);
default:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
}
@ -875,8 +886,8 @@ Future<void> setup({
getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
type: type));
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>(
(type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) => WalletRestorePage(
getIt.get<WalletRestoreViewModel>(param1: type), getIt.get<SeedTypeViewModel>()));
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
@ -940,7 +951,7 @@ Future<void> setup({
getIt.registerFactory(() => BuyAmountViewModel());
getIt.registerFactory(() => BuyOptionsPage());
getIt.registerFactory(() => BuyOptionsPage(getIt.get<DashboardViewModel>()));
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;

View file

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

View file

@ -1,5 +1,6 @@
import 'dart:io' show Directory, File, Platform;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/encrypt.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
@ -26,6 +27,7 @@ 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 polygonDefaultNodeUri = 'polygon-bor.publicnode.com';
const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
const nanoDefaultNodeUri = 'rpc.nano.to';
const nanoDefaultPowNodeUri = 'rpc.nano.to';
@ -64,6 +66,8 @@ Future<void> defaultSettingsMigration(
final migrationVersions =
List<int>.generate(migrationVersionsLength, (i) => currentVersion + (i + 1));
/// When you add a new case, increase the initialMigrationVersion parameter in the main.dart file.
/// This ensures that this switch case runs the newly added case.
await Future.forEach(migrationVersions, (int version) async {
try {
switch (version) {
@ -82,8 +86,7 @@ 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);
await changeBitcoinCashCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
@ -175,6 +178,14 @@ Future<void> defaultSettingsMigration(
await changeBitcoinCurrentElectrumServerToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 24:
await addPolygonNodeList(nodes: nodes);
await changePolygonCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 25:
await rewriteSecureStoragePin(secureStorage: secureStorage);
break;
default:
break;
@ -329,6 +340,11 @@ Node? getEthereumDefaultNode({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
}
Node? getPolygonDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == polygonDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.polygon);
}
Node? getNanoDefaultNode({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
@ -340,9 +356,9 @@ Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
}
Node? getBitcoinCashDefaultElectrumServer({required Box<Node> nodes}) {
return nodes.values.firstWhereOrNull(
(Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri)
?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash);
return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash);
}
Node getMoneroDefaultNode({required Box<Node> nodes}) {
@ -364,6 +380,37 @@ Node getMoneroDefaultNode({required Box<Node> nodes}) {
}
}
Future<void> rewriteSecureStoragePin({required FlutterSecureStorage secureStorage}) async {
// the bug only affects ios/mac:
if (!Platform.isIOS && !Platform.isMacOS) {
return;
}
// first, get the encoded pin:
final keyForPinCode = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
String? encodedPin;
try {
encodedPin = await secureStorage.read(key: keyForPinCode);
} catch (e) {
// either we don't have a pin, or we can't read it (maybe even because of the bug!)
// the only option here is to abort the migration or we risk losing the pin and locking the user out
return;
}
if (encodedPin == null) {
return;
}
// ensure we overwrite by deleting the old key first:
await secureStorage.delete(key: keyForPinCode);
await secureStorage.write(
key: keyForPinCode,
value: encodedPin,
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
mOptions: MacOsOptions(accessibility: KeychainAccessibility.first_unlock),
);
}
Future<void> changeBitcoinCurrentElectrumServerToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final server = getBitcoinDefaultElectrumServer(nodes: nodes);
@ -381,8 +428,7 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
}
Future<void> changeBitcoinCashCurrentNodeToDefault(
{required SharedPreferences sharedPreferences,
required Box<Node> nodes}) async {
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final server = getBitcoinCashDefaultElectrumServer(nodes: nodes);
final serverId = server?.key as int ?? 0;
@ -496,6 +542,7 @@ Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
}
final password = encrypt.Key.fromSecureRandom(32).base16;
await secureStorage.delete(key: key);
await secureStorage.write(key: key, value: password);
}
@ -537,34 +584,33 @@ Future<void> checkCurrentNodes(
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 currentNanoNodeId = sharedPreferences
.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences
.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentBitcoinCashNodeId = sharedPreferences
.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
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 currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentBitcoinCashNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
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 currentPolygonNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentNanoPowNodeServer =
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull(
(node) => node.key == currentBitcoinCashNodeId);
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
final currentBitcoinCashNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinCashNodeId);
if (currentMoneroNode == null) {
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
await nodeSource.add(newCakeWalletNode);
@ -616,8 +662,13 @@ Future<void> checkCurrentNodes(
if (currentBitcoinCashNodeServer == null) {
final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash);
await nodeSource.add(node);
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
}
if (currentPolygonNodeServer == null) {
final node = Node(uri: polygonDefaultNodeUri, type: WalletType.polygon);
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
}
}
@ -713,3 +764,20 @@ Future<void> changeNanoCurrentPowNodeToDefault(
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
}
Future<void> addPolygonNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultPolygonNodes();
for (var node in nodeList) {
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
await nodes.add(node);
}
}
}
Future<void> changePolygonCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
final node = getPolygonDefaultNode(nodes: nodes);
final nodeId = node?.key as int? ?? 0;
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, nodeId);
}

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:ens_dart/ens_dart.dart';
@ -12,6 +13,10 @@ class EnsRecord {
if (wallet != null && wallet.type == WalletType.ethereum) {
_client = ethereum!.getWeb3Client(wallet);
}
if (wallet != null && wallet.type == WalletType.polygon) {
_client = polygon!.getWeb3Client(wallet);
}
if (_client == null) {
_client = Web3Client("https://ethereum.publicnode.com", Client());
@ -31,6 +36,7 @@ class EnsRecord {
case WalletType.haven:
return await ens.withName(name).getCoinAddress(CoinType.XHV);
case WalletType.ethereum:
case WalletType.polygon:
default:
return (await ens.withName(name).getAddress()).hex;
}

View file

@ -147,6 +147,7 @@ Future<void> ios_migrate_pin() async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPassword = encodedPinCode(pin: pinPassword);
await flutterSecureStorage.delete(key: key);
await flutterSecureStorage.write(key: key, value: encodedPassword);
await prefs.setBool('ios_migration_pin_completed', true);
}

View file

@ -9,7 +9,9 @@ Future<List<int>> getEncryptionKey(
if (stringifiedKey == null) {
key = CakeHive.generateSecureKey();
final keyStringified = key.join(',');
await secureStorage.write(key: 'transactionDescriptionsBoxKey', value: keyStringified);
String storageKey = 'transactionDescriptionsBoxKey';
await secureStorage.delete(key: storageKey);
await secureStorage.write(key: storageKey, value: keyStringified);
} else {
key = stringifiedKey.split(',').map((i) => int.parse(i)).toList();
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
@ -19,7 +20,8 @@ class MainActions {
final bool Function(DashboardViewModel viewModel)? isEnabled;
final bool Function(DashboardViewModel viewModel)? canShow;
final Future<void> Function(BuildContext context, DashboardViewModel viewModel) onTap;
final Future<void> Function(
BuildContext context, DashboardViewModel viewModel) onTap;
MainActions._({
required this.name,
@ -43,47 +45,54 @@ class MainActions {
isEnabled: (viewModel) => viewModel.isEnabledBuyAction,
canShow: (viewModel) => viewModel.hasBuyAction,
onTap: (BuildContext context, DashboardViewModel viewModel) async {
if (!viewModel.isEnabledBuyAction) {
await _showErrorDialog(context, S.of(context).unsupported_asset);
return;
}
final defaultBuyProvider = viewModel.defaultBuyProvider;
final walletType = viewModel.type;
if (!viewModel.isEnabledBuyAction) return;
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.bitcoinCash:
switch (defaultBuyProvider) {
case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy);
break;
case BuyProviderType.Onramper:
await getIt.get<OnRamperBuyProvider>().launchProvider(context);
break;
case BuyProviderType.Robinhood:
await getIt.get<RobinhoodBuyProvider>().launchProvider(context);
break;
}
break;
case WalletType.nano:
case WalletType.banano:
case WalletType.monero:
await getIt.get<OnRamperBuyProvider>().launchProvider(context);
break;
default:
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).buy,
alertContent: S.of(context).unsupported_asset,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
try {
await _launchProviderByType(context, defaultBuyProvider);
} catch (e) {
await _showErrorDialog(context, e.toString());
}
},
);
static Future<void> _launchProviderByType(BuildContext context, BuyProviderType providerType) async {
switch (providerType) {
case BuyProviderType.AskEachTime:
Navigator.pushNamed(context, Routes.buy);
break;
case BuyProviderType.Onramper:
await getIt.get<OnRamperBuyProvider>().launchProvider(context);
break;
case BuyProviderType.Robinhood:
await getIt.get<RobinhoodBuyProvider>().launchProvider(context);
break;
case BuyProviderType.DFX:
await getIt.get<DFXBuyProvider>().launchProvider(context);
break;
default:
throw UnsupportedError('Unsupported buy provider type');
}
}
static Future<void> _showErrorDialog(BuildContext context, String errorMessage) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).buy,
alertContent: errorMessage,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
static MainActions receiveAction = MainActions._(
name: (context) => S.of(context).receive,
image: 'assets/images/received.png',
@ -124,6 +133,7 @@ class MainActions {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.bitcoinCash:
if (viewModel.isEnabledSellAction) {
final moonPaySellProvider = MoonPaySellProvider();

View file

@ -133,6 +133,22 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
return nodes;
}
Future<List<Node>> loadDefaultPolygonNodes() async {
final nodesRaw = await rootBundle.loadString('assets/polygon_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.polygon;
nodes.add(node);
}
}
return nodes;
}
Future<void> resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
@ -141,6 +157,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final havenNodes = await loadDefaultHavenNodes();
final ethereumNodes = await loadDefaultEthereumNodes();
final nanoNodes = await loadDefaultNanoNodes();
final polygonNodes = await loadDefaultPolygonNodes();
final nodes = moneroNodes +
bitcoinElectrumServerList +
@ -148,7 +166,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
havenNodes +
ethereumNodes +
bitcoinCashElectrumServerList +
nanoNodes;
nanoNodes +
polygonNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);

View file

@ -6,6 +6,7 @@ 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 currentPolygonNodeIdKey = 'current_node_id_matic';
static const currentNanoNodeIdKey = 'current_node_id_nano';
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
static const currentBananoNodeIdKey = 'current_node_id_banano';
@ -37,6 +38,7 @@ class PreferencesKey {
static const havenTransactionPriority = 'current_fee_priority_haven';
static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
static const polygonTransactionPriority = 'current_fee_priority_polygon';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
@ -50,6 +52,7 @@ class PreferencesKey {
static const sortBalanceBy = 'sort_balance_by';
static const pinNativeTokenAtTop = 'pin_native_token_at_top';
static const useEtherscan = 'use_etherscan';
static const usePolygonScan = 'use_polygonscan';
static const defaultNanoRep = 'default_nano_representative';
static const defaultBananoRep = 'default_banano_representative';
static const lookupsTwitter = 'looks_up_twitter';
@ -64,6 +67,7 @@ class PreferencesKey {
static const exchangeProvidersSelection = 'exchange-providers-selection';
static const autoGenerateSubaddressStatusKey = 'auto_generate_subaddress_status';
static const moneroSeedType = 'monero_seed_type';
static const clearnetDonationLink = 'clearnet_donation_link';
static const onionDonationLink = 'onion_donation_link';
static const lastSeenAppVersion = 'last_seen_app_version';

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
@ -24,6 +25,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
case WalletType.nano:
case WalletType.banano:
return [];
case WalletType.polygon:
return polygon!.getTransactionPriorities();
default:
return [];
}

View file

@ -0,0 +1,36 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/enumerable_item.dart';
class SeedType extends EnumerableItem<int> with Serializable<int> {
const SeedType({required String title, required int raw}) : super(title: title, raw: raw);
static const all = [SeedType.legacy, SeedType.polyseed];
static const defaultSeedType = polyseed;
static const legacy = SeedType(raw: 0, title: 'Legacy (25 words)');
static const polyseed = SeedType(raw: 1, title: 'Polyseed (16 words)');
static SeedType deserialize({required int raw}) {
switch (raw) {
case 0:
return legacy;
case 1:
return polyseed;
default:
throw Exception('Unexpected token: $raw for SeedType deserialize');
}
}
@override
String toString() {
switch (this) {
case SeedType.legacy:
return S.current.seedtype_legacy;
case SeedType.polyseed:
return S.current.seedtype_polyseed;
default:
return '';
}
}
}

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