mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Merge branch 'main' into CW-529-Modify-2FA-introduction-screens
This commit is contained in:
commit
50299e60f0
192 changed files with 4245 additions and 1139 deletions
28
.github/workflows/pr_test_build.yml
vendored
28
.github/workflows/pr_test_build.yml
vendored
|
@ -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
1
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
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
BIN
assets/images/dfx_light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
6
assets/polygon_node_list.yml
Normal file
6
assets/polygon_node_list.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
-
|
||||
uri: polygon-bor.publicnode.com
|
||||
-
|
||||
uri: polygon-rpc.com
|
||||
-
|
||||
uri: polygon.llamarpc.com
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
/Users/blazebrain/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/
|
|
@ -1 +0,0 @@
|
|||
/Users/blazebrain/.pub-cache/git/tor-09ba92cb11d4e3cacf97256e57863b805f79f2e5/
|
|
@ -1,11 +0,0 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
}
|
|
@ -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_
|
|
@ -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)
|
|
@ -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"))
|
||||
}
|
|
@ -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
|
|
@ -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"
|
|
@ -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
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
}
|
|
@ -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_
|
|
@ -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)
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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) ||
|
||||
|
|
|
@ -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}) {
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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,
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//! Model used for in parsing transactions fetched using etherscan
|
||||
class EthereumTransactionModel {
|
||||
final DateTime date;
|
||||
final String hash;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,6 +32,7 @@ void store(char *path);
|
|||
|
||||
void set_trusted_daemon(bool arg);
|
||||
bool trusted_daemon();
|
||||
char *sign_message(char *message, char *address);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -383,3 +386,14 @@ String getSubaddressLabel(int accountIndex, int addressIndex) {
|
|||
Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted));
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,6 +391,7 @@ abstract class MoneroWalletBase
|
|||
}
|
||||
|
||||
Future<void> updateUnspent() async {
|
||||
try {
|
||||
refreshCoins(walletAddresses.account!.id);
|
||||
|
||||
unspentCoins.clear();
|
||||
|
@ -397,7 +401,11 @@ abstract class MoneroWalletBase
|
|||
final coin = getCoin(i);
|
||||
if (coin.spent == 0) {
|
||||
final unspent = MoneroUnspent.fromCoinsInfoRow(coin);
|
||||
unspent.isChange = transaction_history.getTransaction(unspent.hash).direction == 1;
|
||||
if (unspent.hash.isNotEmpty) {
|
||||
unspent.isChange = transaction_history
|
||||
.getTransaction(unspent.hash)
|
||||
.direction == 1;
|
||||
}
|
||||
unspentCoins.add(unspent);
|
||||
}
|
||||
}
|
||||
|
@ -428,6 +436,14 @@ abstract class MoneroWalletBase
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ void store(char *path);
|
|||
|
||||
void set_trusted_daemon(bool arg);
|
||||
bool trusted_daemon();
|
||||
char *sign_message(char *message, char *address);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
30
cw_polygon/.gitignore
vendored
Normal 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
10
cw_polygon/.metadata
Normal 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
3
cw_polygon/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
1
cw_polygon/LICENSE
Normal file
1
cw_polygon/LICENSE
Normal file
|
@ -0,0 +1 @@
|
|||
TODO: Add your license here.
|
39
cw_polygon/README.md
Normal file
39
cw_polygon/README.md
Normal 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.
|
4
cw_polygon/analysis_options.yaml
Normal file
4
cw_polygon/analysis_options.yaml
Normal 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
|
7
cw_polygon/lib/cw_polygon.dart
Normal file
7
cw_polygon/lib/cw_polygon.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
library cw_polygon;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
86
cw_polygon/lib/default_erc20_tokens.dart
Normal file
86
cw_polygon/lib/default_erc20_tokens.dart
Normal 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();
|
||||
}
|
19
cw_polygon/lib/pending_polygon_transaction.dart
Normal file
19
cw_polygon/lib/pending_polygon_transaction.dart
Normal 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,
|
||||
);
|
||||
}
|
34
cw_polygon/lib/polygon_client.dart
Normal file
34
cw_polygon/lib/polygon_client.dart
Normal 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 [];
|
||||
}
|
||||
}
|
||||
}
|
6
cw_polygon/lib/polygon_exceptions.dart
Normal file
6
cw_polygon/lib/polygon_exceptions.dart
Normal 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);
|
||||
}
|
25
cw_polygon/lib/polygon_formatter.dart
Normal file
25
cw_polygon/lib/polygon_formatter.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
5
cw_polygon/lib/polygon_mnemonics_exception.dart
Normal file
5
cw_polygon/lib/polygon_mnemonics_exception.dart
Normal 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.';
|
||||
}
|
18
cw_polygon/lib/polygon_transaction_credentials.dart
Normal file
18
cw_polygon/lib/polygon_transaction_credentials.dart
Normal 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,
|
||||
);
|
||||
}
|
77
cw_polygon/lib/polygon_transaction_history.dart
Normal file
77
cw_polygon/lib/polygon_transaction_history.dart
Normal 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;
|
||||
}
|
49
cw_polygon/lib/polygon_transaction_info.dart
Normal file
49
cw_polygon/lib/polygon_transaction_info.dart
Normal 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';
|
||||
}
|
49
cw_polygon/lib/polygon_transaction_model.dart
Normal file
49
cw_polygon/lib/polygon_transaction_model.dart
Normal 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",
|
||||
);
|
||||
}
|
51
cw_polygon/lib/polygon_transaction_priority.dart
Normal file
51
cw_polygon/lib/polygon_transaction_priority.dart
Normal 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;
|
||||
}
|
||||
}
|
540
cw_polygon/lib/polygon_wallet.dart
Normal file
540
cw_polygon/lib/polygon_wallet.dart
Normal 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();
|
||||
}
|
5
cw_polygon/lib/polygon_wallet_addresses.dart
Normal file
5
cw_polygon/lib/polygon_wallet_addresses.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
|
||||
|
||||
class PolygonWalletAddresses extends EthereumWalletAddresses {
|
||||
PolygonWalletAddresses(super.walletInfo);
|
||||
}
|
28
cw_polygon/lib/polygon_wallet_creation_credentials.dart
Normal file
28
cw_polygon/lib/polygon_wallet_creation_credentials.dart
Normal 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;
|
||||
}
|
123
cw_polygon/lib/polygon_wallet_service.dart
Normal file
123
cw_polygon/lib/polygon_wallet_service.dart
Normal 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
73
cw_polygon/pubspec.yaml
Normal 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
|
12
cw_polygon/test/cw_polygon_test.dart
Normal file
12
cw_polygon/test/cw_polygon_test.dart
Normal 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);
|
||||
});
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
179
lib/buy/dfx/dfx_buy_provider.dart
Normal file
179
lib/buy/dfx/dfx_buy_provider.dart
Normal 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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
|
|
21
lib/di.dart
21
lib/di.dart
|
@ -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;
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
final currentNanoPowNodeServer =
|
||||
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
|
||||
final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull(
|
||||
(node) => node.key == currentBitcoinCashNodeId);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -13,6 +14,10 @@ class EnsRecord {
|
|||
_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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,17 +45,22 @@ 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;
|
||||
try {
|
||||
await _launchProviderByType(context, defaultBuyProvider);
|
||||
} catch (e) {
|
||||
await _showErrorDialog(context, e.toString());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!viewModel.isEnabledBuyAction) return;
|
||||
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.ethereum:
|
||||
case WalletType.bitcoinCash:
|
||||
switch (defaultBuyProvider) {
|
||||
static Future<void> _launchProviderByType(BuildContext context, BuyProviderType providerType) async {
|
||||
switch (providerType) {
|
||||
case BuyProviderType.AskEachTime:
|
||||
Navigator.pushNamed(context, Routes.buy);
|
||||
break;
|
||||
|
@ -63,26 +70,28 @@ class MainActions {
|
|||
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);
|
||||
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: S.of(context).unsupported_asset,
|
||||
alertContent: errorMessage,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static MainActions receiveAction = MainActions._(
|
||||
name: (context) => S.of(context).receive,
|
||||
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
|
|
36
lib/entities/seed_type.dart
Normal file
36
lib/entities/seed_type.dart
Normal 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
Loading…
Reference in a new issue