Merge branch 'refs/heads/main' into CW-685-passphrase-support-for-monero-wownero-wallets
# Conflicts: # cw_monero/lib/api/wallet.dart # cw_wownero/lib/api/wallet.dart # tool/configure.dart
4
.github/workflows/cache_dependencies.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: "11.x"
|
java-version: "17.x"
|
||||||
- name: Configure placeholder git details
|
- name: Configure placeholder git details
|
||||||
run: |
|
run: |
|
||||||
git config --global user.email "CI@cakewallet.com"
|
git config --global user.email "CI@cakewallet.com"
|
||||||
|
@ -60,7 +60,7 @@ jobs:
|
||||||
path: |
|
path: |
|
||||||
/opt/android/cake_wallet/cw_haven/android/.cxx
|
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||||
/opt/android/cake_wallet/scripts/monero_c/release
|
/opt/android/cake_wallet/scripts/monero_c/release
|
||||||
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }}
|
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
|
||||||
|
|
||||||
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
||||||
name: Generate Externals
|
name: Generate Externals
|
||||||
|
|
|
@ -41,7 +41,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: "11.x"
|
java-version: "17.x"
|
||||||
- name: Configure placeholder git details
|
- name: Configure placeholder git details
|
||||||
run: |
|
run: |
|
||||||
git config --global user.email "CI@cakewallet.com"
|
git config --global user.email "CI@cakewallet.com"
|
||||||
|
@ -53,7 +53,9 @@ jobs:
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
- name: Install package dependencies
|
- name: Install package dependencies
|
||||||
run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang
|
||||||
|
|
||||||
- name: Execute Build and Setup Commands
|
- name: Execute Build and Setup Commands
|
||||||
run: |
|
run: |
|
||||||
|
@ -76,7 +78,7 @@ jobs:
|
||||||
path: |
|
path: |
|
||||||
/opt/android/cake_wallet/cw_haven/android/.cxx
|
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||||
/opt/android/cake_wallet/scripts/monero_c/release
|
/opt/android/cake_wallet/scripts/monero_c/release
|
||||||
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }}
|
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
|
||||||
|
|
||||||
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
||||||
name: Generate Externals
|
name: Generate Externals
|
187
.github/workflows/pr_test_build_linux.yml
vendored
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
name: PR Test Build linux
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branch:
|
||||||
|
description: "Branch name to build"
|
||||||
|
required: true
|
||||||
|
default: "main"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
PR_test_build:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
env:
|
||||||
|
STORE_PASS: test@cake_wallet
|
||||||
|
KEY_PASS: test@cake_wallet
|
||||||
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
|
|
||||||
|
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_ENVg
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: "17.x"
|
||||||
|
- name: Configure placeholder git details
|
||||||
|
run: |
|
||||||
|
git config --global user.email "CI@cakewallet.com"
|
||||||
|
git config --global user.name "Cake Github Actions"
|
||||||
|
- name: Flutter action
|
||||||
|
uses: subosito/flutter-action@v1
|
||||||
|
with:
|
||||||
|
flutter-version: "3.19.6"
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
- name: Install package dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt-get install -y curl unzip automake build-essential file pkg-config git python-is-python3 libtool libtinfo5 cmake clang
|
||||||
|
|
||||||
|
- name: Install desktop dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y ninja-build libgtk-3-dev gperf
|
||||||
|
- name: Execute Build and Setup Commands
|
||||||
|
run: |
|
||||||
|
sudo mkdir -p /opt/android
|
||||||
|
sudo chown $USER /opt/android
|
||||||
|
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 ${{ env.BRANCH_NAME }}
|
||||||
|
cd scripts && ./gen_android_manifest.sh && cd ..
|
||||||
|
cd cake_wallet/scripts/android/
|
||||||
|
source ./app_env.sh cakewallet
|
||||||
|
./app_config.sh
|
||||||
|
cd ../../..
|
||||||
|
cd cake_wallet/scripts/linux/
|
||||||
|
source ./app_env.sh cakewallet
|
||||||
|
./app_config.sh
|
||||||
|
cd ../../..
|
||||||
|
|
||||||
|
- name: Cache Externals
|
||||||
|
id: cache-externals
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||||
|
/opt/android/cake_wallet/scripts/monero_c/release
|
||||||
|
key: linux_${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
|
||||||
|
|
||||||
|
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
||||||
|
name: Generate Externals
|
||||||
|
run: |
|
||||||
|
cd /opt/android/cake_wallet/scripts/linux/
|
||||||
|
source ./app_env.sh cakewallet
|
||||||
|
./build_monero_all.sh
|
||||||
|
|
||||||
|
- name: Install Flutter dependencies
|
||||||
|
run: |
|
||||||
|
cd /opt/android/cake_wallet
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
- name: Generate localization
|
||||||
|
run: |
|
||||||
|
cd /opt/android/cake_wallet
|
||||||
|
flutter packages pub run tool/generate_localization.dart
|
||||||
|
|
||||||
|
- name: Build generated code
|
||||||
|
run: |
|
||||||
|
cd /opt/android/cake_wallet
|
||||||
|
./model_generator.sh
|
||||||
|
|
||||||
|
- name: Add secrets
|
||||||
|
run: |
|
||||||
|
cd /opt/android/cake_wallet
|
||||||
|
touch lib/.secrets.g.dart
|
||||||
|
touch cw_evm/lib/.secrets.g.dart
|
||||||
|
touch cw_solana/lib/.secrets.g.dart
|
||||||
|
touch cw_core/lib/.secrets.g.dart
|
||||||
|
touch cw_nano/lib/.secrets.g.dart
|
||||||
|
touch cw_tron/lib/.secrets.g.dart
|
||||||
|
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
|
||||||
|
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||||
|
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||||
|
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||||
|
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
|
||||||
|
echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
||||||
|
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
||||||
|
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||||
|
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||||
|
|
||||||
|
- name: Rename app
|
||||||
|
run: |
|
||||||
|
echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cd /opt/android/cake_wallet
|
||||||
|
flutter build linux --release
|
||||||
|
|
||||||
|
- name: Prepare release zip file
|
||||||
|
run: |
|
||||||
|
cd /opt/android/cake_wallet/build/linux/x64/release
|
||||||
|
zip -r ${{env.BRANCH_NAME}}.zip bundle
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
uses: kittaakos/upload-artifact-as-is@v0
|
||||||
|
with:
|
||||||
|
path: /opt/android/cake_wallet/build/linux/x64/release/${{env.BRANCH_NAME}}.zip
|
||||||
|
|
||||||
|
# Just as an artifact would be enough
|
||||||
|
# - name: Send Test APK
|
||||||
|
# continue-on-error: true
|
||||||
|
# uses: adrey/slack-file-upload-action@1.0.5
|
||||||
|
# with:
|
||||||
|
# token: ${{ secrets.SLACK_APP_TOKEN }}
|
||||||
|
# path: /opt/android/cake_wallet/build/linux/x64/release/${{env.BRANCH_NAME}}.zip
|
||||||
|
# channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
||||||
|
# title: "${{ env.BRANCH_NAME }}_linux.zip"
|
||||||
|
# filename: ${{ env.BRANCH_NAME }}_linux.zip
|
||||||
|
# initial_comment: ${{ github.event.head_commit.message }}
|
2
.gitignore
vendored
|
@ -160,6 +160,8 @@ macos/Runner/Release.entitlements
|
||||||
macos/Runner/Runner.entitlements
|
macos/Runner/Runner.entitlements
|
||||||
lib/core/secure_storage.dart
|
lib/core/secure_storage.dart
|
||||||
|
|
||||||
|
lib/core/secure_storage.dart
|
||||||
|
|
||||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
|
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
|
||||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
|
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
|
||||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
|
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
|
||||||
|
|
|
@ -18,6 +18,12 @@ migration:
|
||||||
- platform: windows
|
- platform: windows
|
||||||
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||||
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||||
|
- platform: macos
|
||||||
|
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||||
|
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||||
|
- platform: linux
|
||||||
|
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||||
|
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img height="100" src=".github/assets/Logo_CakeWallet.png">
|
![logo](.github/assets/Logo_CakeWallet.png)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -91,5 +91,4 @@ dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||||
implementation 'com.unstoppabledomains:resolution:5.0.0'
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,10 @@ import android.net.Uri;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import com.unstoppabledomains.resolution.DomainResolution;
|
|
||||||
import com.unstoppabledomains.resolution.Resolution;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public class MainActivity extends FlutterFragmentActivity {
|
public class MainActivity extends FlutterFragmentActivity {
|
||||||
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
||||||
final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24;
|
|
||||||
boolean isAppSecure = false;
|
boolean isAppSecure = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -53,14 +49,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
||||||
random.nextBytes(bytes);
|
random.nextBytes(bytes);
|
||||||
handler.post(() -> result.success(bytes));
|
handler.post(() -> result.success(bytes));
|
||||||
break;
|
break;
|
||||||
case "getUnstoppableDomainAddress":
|
|
||||||
int version = Build.VERSION.SDK_INT;
|
|
||||||
if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) {
|
|
||||||
getUnstoppableDomainAddress(call, result);
|
|
||||||
} else {
|
|
||||||
handler.post(() -> result.success(""));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "setIsAppSecure":
|
case "setIsAppSecure":
|
||||||
isAppSecure = call.argument("isAppSecure");
|
isAppSecure = call.argument("isAppSecure");
|
||||||
if (isAppSecure) {
|
if (isAppSecure) {
|
||||||
|
@ -85,23 +73,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
|
||||||
DomainResolution resolution = new Resolution();
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
String domain = call.argument("domain");
|
|
||||||
String ticker = call.argument("ticker");
|
|
||||||
|
|
||||||
AsyncTask.execute(() -> {
|
|
||||||
try {
|
|
||||||
String address = resolution.getAddress(domain, ticker);
|
|
||||||
handler.post(() -> result.success(address));
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("Expected Address, but got " + e.getMessage());
|
|
||||||
handler.post(() -> result.success(""));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disableBatteryOptimization() {
|
private void disableBatteryOptimization() {
|
||||||
String packageName = getPackageName();
|
String packageName = getPackageName();
|
||||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||||
|
|
|
@ -19,14 +19,10 @@ import android.net.Uri;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import com.unstoppabledomains.resolution.DomainResolution;
|
|
||||||
import com.unstoppabledomains.resolution.Resolution;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public class MainActivity extends FlutterFragmentActivity {
|
public class MainActivity extends FlutterFragmentActivity {
|
||||||
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
||||||
final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||||
|
@ -51,14 +47,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
||||||
random.nextBytes(bytes);
|
random.nextBytes(bytes);
|
||||||
handler.post(() -> result.success(bytes));
|
handler.post(() -> result.success(bytes));
|
||||||
break;
|
break;
|
||||||
case "getUnstoppableDomainAddress":
|
|
||||||
int version = Build.VERSION.SDK_INT;
|
|
||||||
if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) {
|
|
||||||
getUnstoppableDomainAddress(call, result);
|
|
||||||
} else {
|
|
||||||
handler.post(() -> result.success(""));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "disableBatteryOptimization":
|
case "disableBatteryOptimization":
|
||||||
disableBatteryOptimization();
|
disableBatteryOptimization();
|
||||||
handler.post(() -> result.success(null));
|
handler.post(() -> result.success(null));
|
||||||
|
@ -75,23 +63,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
|
||||||
DomainResolution resolution = new Resolution();
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
String domain = call.argument("domain");
|
|
||||||
String ticker = call.argument("ticker");
|
|
||||||
|
|
||||||
AsyncTask.execute(() -> {
|
|
||||||
try {
|
|
||||||
String address = resolution.getAddress(domain, ticker);
|
|
||||||
handler.post(() -> result.success(address));
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("Expected Address, but got " + e.getMessage());
|
|
||||||
handler.post(() -> result.success(""));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disableBatteryOptimization() {
|
private void disableBatteryOptimization() {
|
||||||
String packageName = getPackageName();
|
String packageName = getPackageName();
|
||||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||||
|
|
|
@ -19,14 +19,10 @@ import android.net.Uri;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import com.unstoppabledomains.resolution.DomainResolution;
|
|
||||||
import com.unstoppabledomains.resolution.Resolution;
|
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public class MainActivity extends FlutterFragmentActivity {
|
public class MainActivity extends FlutterFragmentActivity {
|
||||||
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
||||||
final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24;
|
|
||||||
boolean isAppSecure = false;
|
boolean isAppSecure = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,14 +48,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
||||||
random.nextBytes(bytes);
|
random.nextBytes(bytes);
|
||||||
handler.post(() -> result.success(bytes));
|
handler.post(() -> result.success(bytes));
|
||||||
break;
|
break;
|
||||||
case "getUnstoppableDomainAddress":
|
|
||||||
int version = Build.VERSION.SDK_INT;
|
|
||||||
if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) {
|
|
||||||
getUnstoppableDomainAddress(call, result);
|
|
||||||
} else {
|
|
||||||
handler.post(() -> result.success(""));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "setIsAppSecure":
|
case "setIsAppSecure":
|
||||||
isAppSecure = call.argument("isAppSecure");
|
isAppSecure = call.argument("isAppSecure");
|
||||||
if (isAppSecure) {
|
if (isAppSecure) {
|
||||||
|
@ -84,23 +72,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
|
||||||
DomainResolution resolution = new Resolution();
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
String domain = call.argument("domain");
|
|
||||||
String ticker = call.argument("ticker");
|
|
||||||
|
|
||||||
AsyncTask.execute(() -> {
|
|
||||||
try {
|
|
||||||
String address = resolution.getAddress(domain, ticker);
|
|
||||||
handler.post(() -> result.success(address));
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("Expected Address, but got " + e.getMessage());
|
|
||||||
handler.post(() -> result.success(""));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disableBatteryOptimization() {
|
private void disableBatteryOptimization() {
|
||||||
String packageName = getPackageName();
|
String packageName = getPackageName();
|
||||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||||
|
|
|
@ -2,7 +2,7 @@ buildscript {
|
||||||
ext.kotlin_version = '1.8.21'
|
ext.kotlin_version = '1.8.21'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -15,7 +15,7 @@ buildscript {
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 28 KiB |
|
@ -1,4 +1,19 @@
|
||||||
-
|
-
|
||||||
uri: ltc-electrum.cakewallet.com:50002
|
uri: ltc-electrum.cakewallet.com:50002
|
||||||
useSSL: true
|
useSSL: true
|
||||||
isDefault: true
|
isDefault: true
|
||||||
|
-
|
||||||
|
uri: litecoin.stackwallet.com:20063
|
||||||
|
useSSL: true
|
||||||
|
-
|
||||||
|
uri: electrum-ltc.bysh.me:50002
|
||||||
|
useSSL: true
|
||||||
|
-
|
||||||
|
uri: lightweight.fiatfaucet.com:50002
|
||||||
|
useSSL: true
|
||||||
|
-
|
||||||
|
uri: electrum.ltc.xurious.com:50002
|
||||||
|
useSSL: true
|
||||||
|
-
|
||||||
|
uri: backup.electrum-ltc.org:443
|
||||||
|
useSSL: true
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
Monero enhancements
|
Monero synchronization improvements
|
||||||
Synchronization improvements
|
Enhance error handling
|
||||||
|
UI enhancements
|
||||||
Bug fixes
|
Bug fixes
|
|
@ -1,5 +1,6 @@
|
||||||
Monero and Ethereum enhancements
|
Wallets enhancements
|
||||||
Synchronization improvements
|
Monero synchronization improvements
|
||||||
Exchange flow enhancements
|
Improve wallet backups
|
||||||
Ledger improvements
|
Enhance error handling
|
||||||
|
UI enhancements
|
||||||
Bug fixes
|
Bug fixes
|
176
build-guide-linux.md
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
# Building CakeWallet for Linux
|
||||||
|
|
||||||
|
## Requirements and Setup
|
||||||
|
|
||||||
|
The following are the system requirements to build CakeWallet for your Linux device.
|
||||||
|
|
||||||
|
```
|
||||||
|
Ubuntu >= 16.04
|
||||||
|
Flutter 3.10.x
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building CakeWallet on Linux
|
||||||
|
|
||||||
|
These steps will help you configure and execute a build of CakeWallet from its source code.
|
||||||
|
|
||||||
|
### 1. Installing Package Dependencies
|
||||||
|
|
||||||
|
CakeWallet requires some packages to be install on your build system. You may easily install them on your build system with the following command:
|
||||||
|
|
||||||
|
`$ sudo apt install build-essential cmake pkg-config git curl autoconf libtool`
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> ### Check gcc version
|
||||||
|
>
|
||||||
|
> It is needed to use gcc 10 or 9 to successfully link dependencies with flutter.\
|
||||||
|
> To check what gcc version you are using:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> $ gcc --version
|
||||||
|
> $ g++ --version
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> If you are using gcc version newer than 10, then you need to downgrade to version 10.4.0:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> $ sudo apt install gcc-10 g++-10
|
||||||
|
> $ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 10
|
||||||
|
> $ sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 10
|
||||||
|
> ```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> Alternatively, you can use the [nix-shell](https://nixos.org/) with the `gcc10.nix` file\
|
||||||
|
> present on `scripts/linux` like so:
|
||||||
|
> ```bash
|
||||||
|
> $ nix-shell gcc10.nix
|
||||||
|
> ```
|
||||||
|
> This will get you in a nix environment with all the required dependencies that you can use to build the software from,\
|
||||||
|
> and it works in any linux distro.
|
||||||
|
|
||||||
|
### 2. Installing Flutter
|
||||||
|
|
||||||
|
Need to install flutter. For this please check section [How to install flutter on Linux](https://docs.flutter.dev/get-started/install/linux).
|
||||||
|
|
||||||
|
### 3. Verify Installations
|
||||||
|
|
||||||
|
Verify that the Flutter have been correctly installed on your system with the following command:
|
||||||
|
|
||||||
|
`$ flutter doctor`
|
||||||
|
|
||||||
|
The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding.
|
||||||
|
|
||||||
|
```
|
||||||
|
Doctor summary (to see all details, run flutter doctor -v):
|
||||||
|
[✓] Flutter (Channel stable, 3.10.x, on Linux, locale en_US.UTF-8)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Acquiring the CakeWallet Source Code
|
||||||
|
|
||||||
|
Download CakeWallet source code
|
||||||
|
|
||||||
|
`$ git clone https://github.com/cake-tech/cake_wallet.git --branch linux/password-direct-input`
|
||||||
|
|
||||||
|
Proceed into the source code before proceeding with the next steps:
|
||||||
|
|
||||||
|
`$ cd cake_wallet/scripts/linux/`
|
||||||
|
|
||||||
|
To configure some project properties run:
|
||||||
|
|
||||||
|
`$ ./cakewallet.sh`
|
||||||
|
|
||||||
|
Build the Monero libraries and their dependencies:
|
||||||
|
|
||||||
|
`$ ./build_all.sh`
|
||||||
|
|
||||||
|
Now the dependencies need to be copied into the CakeWallet project with this command:
|
||||||
|
|
||||||
|
`$ ./setup.sh`
|
||||||
|
|
||||||
|
It is now time to change back to the base directory of the CakeWallet source code:
|
||||||
|
|
||||||
|
`$ cd ../../`
|
||||||
|
|
||||||
|
Install Flutter package dependencies with this command:
|
||||||
|
|
||||||
|
`$ flutter pub get`
|
||||||
|
|
||||||
|
> #### If you will get an error like:
|
||||||
|
>
|
||||||
|
> ```
|
||||||
|
> The plugin `cw_shared_external` requires your app to be migrated to the Android embedding v2. Follow the steps on the migration doc above and re-run
|
||||||
|
> this command.
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Then need to config Android project settings. For this open `scripts/android` (`$ cd scripts/android`) directory and run followed commands:
|
||||||
|
>
|
||||||
|
> ```
|
||||||
|
> $ source ./app_env.sh cakewallet
|
||||||
|
> $ ./app_config.sh
|
||||||
|
> $ cd ../..
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Then re-configure Linux project again. For this open `scripts/linux` (`$cd scripts/linux`) directory and run:
|
||||||
|
> `$ ./cakewallet.sh`
|
||||||
|
> and back to project root directory:
|
||||||
|
> `$ cd ../..`
|
||||||
|
> and fetch dependecies again
|
||||||
|
> `$ flutter pub get`
|
||||||
|
|
||||||
|
Your CakeWallet binary will be built with some specific keys for iterate with 3rd party services. You may generate these secret keys placeholders with the following command:
|
||||||
|
|
||||||
|
`$ flutter packages pub run tool/generate_new_secrets.dart`
|
||||||
|
|
||||||
|
We will generate mobx models for the project.
|
||||||
|
|
||||||
|
`$ ./model_generator.sh`
|
||||||
|
|
||||||
|
Then we need to generate localization files.
|
||||||
|
|
||||||
|
`$ flutter packages pub run tool/generate_localization.dart`
|
||||||
|
|
||||||
|
### 5. Build!
|
||||||
|
|
||||||
|
`$ flutter build linux --release`
|
||||||
|
|
||||||
|
Path to executable file will be:
|
||||||
|
|
||||||
|
`build/linux/x64/release/bundle/cake_wallet`
|
||||||
|
|
||||||
|
> ### Troubleshooting
|
||||||
|
>
|
||||||
|
> If you got an error while building the application with `$ flutter build linux --release` command, add `-v` argument to the command (`$ flutter build linux -v --release`) to get details.\
|
||||||
|
> If you got in flutter build logs: undefined reference to `hid_free_enumeration`, or another error with undefined reference to `hid_*`, then rebuild monero lib without hidapi lib. Check does exists `libhidapi-dev` in your scope and remove it from your scope for build without it.
|
||||||
|
|
||||||
|
# Flatpak
|
||||||
|
|
||||||
|
For package the built application into flatpak you need fistly to install `flatpak` and `flatpak-builder`:
|
||||||
|
|
||||||
|
`$ sudo apt install flatpak flatpak-builder`
|
||||||
|
|
||||||
|
Then need to [add flathub](https://flatpak.org/setup/Ubuntu) (or just `$ flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo`). Then need to install freedesktop runtime and sdk:
|
||||||
|
|
||||||
|
`$ flatpak install flathub org.freedesktop.Platform//22.08 org.freedesktop.Sdk//22.08`
|
||||||
|
|
||||||
|
To build with using of `flatpak-build` directory run next:
|
||||||
|
|
||||||
|
`$ flatpak-builder --force-clean flatpak-build com.cakewallet.CakeWallet.yml`
|
||||||
|
|
||||||
|
And then export bundle:
|
||||||
|
|
||||||
|
`$ flatpak build-export export flatpak-build`
|
||||||
|
|
||||||
|
`$ flatpak build-bundle export cake_wallet.flatpak com.cakewallet.CakeWallet`
|
||||||
|
|
||||||
|
Result file: `cake_wallet.flatpak` should be generated in current directory.
|
||||||
|
|
||||||
|
For install generated flatpak file use:
|
||||||
|
|
||||||
|
`$ flatpak --user install cake_wallet.flatpak`
|
||||||
|
|
||||||
|
For run the installed application run:
|
||||||
|
|
||||||
|
`$ flatpak run com.cakewallet.CakeWallet`
|
||||||
|
|
||||||
|
Copyright (c) 2023 Cake Technologies LLC.
|
35
com.cakewallet.CakeWallet.yml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
app-id: com.cakewallet.CakeWallet
|
||||||
|
runtime: org.freedesktop.Platform
|
||||||
|
runtime-version: '22.08'
|
||||||
|
sdk: org.freedesktop.Sdk
|
||||||
|
command: cake_wallet
|
||||||
|
separate-locales: false
|
||||||
|
finish-args:
|
||||||
|
- --share=ipc
|
||||||
|
- --socket=fallback-x11
|
||||||
|
- --socket=wayland
|
||||||
|
- --device=dri
|
||||||
|
- --socket=pulseaudio
|
||||||
|
- --share=network
|
||||||
|
- --filesystem=home
|
||||||
|
modules:
|
||||||
|
- name: cake_wallet
|
||||||
|
buildsystem: simple
|
||||||
|
only-arches:
|
||||||
|
- x86_64
|
||||||
|
build-commands:
|
||||||
|
- "cp -R bundle /app/cake_wallet"
|
||||||
|
- "chmod +x /app/cake_wallet/cake_wallet"
|
||||||
|
- "mkdir -p /app/bin"
|
||||||
|
- "ln -s /app/cake_wallet/cake_wallet /app/bin/cake_wallet"
|
||||||
|
- "mkdir -p /app/share/icons/hicolor/scalable/apps"
|
||||||
|
- "cp cakewallet_icon_180.png /app/share/icons/hicolor/scalable/apps/com.cakewallet.CakeWallet.png"
|
||||||
|
- "mkdir -p /app/share/applications"
|
||||||
|
- "cp com.cakewallet.CakeWallet.desktop /app/share/applications"
|
||||||
|
sources:
|
||||||
|
- type: dir
|
||||||
|
path: build/linux/x64/release
|
||||||
|
- type: file
|
||||||
|
path: assets/images/cakewallet_icon_180.png
|
||||||
|
- type: file
|
||||||
|
path: linux/com.cakewallet.CakeWallet.desktop
|
|
@ -3,12 +3,13 @@
|
||||||
IOS="ios"
|
IOS="ios"
|
||||||
ANDROID="android"
|
ANDROID="android"
|
||||||
MACOS="macos"
|
MACOS="macos"
|
||||||
|
LINUX="linux"
|
||||||
|
|
||||||
PLATFORMS=($IOS $ANDROID $MACOS)
|
PLATFORMS=($IOS $ANDROID $MACOS $LINUX)
|
||||||
PLATFORM=$1
|
PLATFORM=$1
|
||||||
|
|
||||||
if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then
|
if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then
|
||||||
echo "specify platform: ./configure_cake_wallet.sh ios|android|macos"
|
echo "specify platform: ./configure_cake_wallet.sh ios|android|macos|linux"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -27,9 +28,14 @@ if [ "$PLATFORM" == "$ANDROID" ]; then
|
||||||
cd scripts/android
|
cd scripts/android
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$PLATFORM" == "$LINUX" ]; then
|
||||||
|
echo "Configuring for linux"
|
||||||
|
cd scripts/linux
|
||||||
|
fi
|
||||||
|
|
||||||
source ./app_env.sh cakewallet
|
source ./app_env.sh cakewallet
|
||||||
./app_config.sh
|
./app_config.sh
|
||||||
cd ../.. && flutter pub get
|
cd ../.. && flutter pub get
|
||||||
#flutter packages pub run tool/generate_localization.dart
|
flutter packages pub run tool/generate_localization.dart
|
||||||
./model_generator.sh
|
./model_generator.sh
|
||||||
#cd macos && pod install
|
#cd macos && pod install
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||||
|
@ -25,7 +25,8 @@ class BitcoinHardwareWalletService {
|
||||||
for (final i in indexRange) {
|
for (final i in indexRange) {
|
||||||
final derivationPath = "m/84'/0'/$i'";
|
final derivationPath = "m/84'/0'/$i'";
|
||||||
final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
|
final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
|
||||||
HDWallet hd = HDWallet.fromBase58(xpub).derive(0);
|
Bip32Slip10Secp256k1 hd =
|
||||||
|
Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
|
||||||
|
|
||||||
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
|
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:convert/convert.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
@ -15,6 +15,7 @@ import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||||
|
@ -30,6 +31,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
Uint8List? seedBytes,
|
Uint8List? seedBytes,
|
||||||
String? mnemonic,
|
String? mnemonic,
|
||||||
String? xpub,
|
String? xpub,
|
||||||
|
@ -50,14 +52,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
networkType: networkParam == null
|
network: networkParam == null
|
||||||
? bitcoin.bitcoin
|
? BitcoinNetwork.mainnet
|
||||||
: networkParam == BitcoinNetwork.mainnet
|
: networkParam == BitcoinNetwork.mainnet
|
||||||
? bitcoin.bitcoin
|
? BitcoinNetwork.mainnet
|
||||||
: bitcoin.testnet,
|
: BitcoinNetwork.testnet,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
currency:
|
currency:
|
||||||
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
|
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
@ -75,10 +78,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialSilentAddresses: initialSilentAddresses,
|
initialSilentAddresses: initialSilentAddresses,
|
||||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||||
mainHd: hd,
|
mainHd: hd,
|
||||||
sideHd: accountHD.derive(1),
|
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||||
network: networkParam ?? network,
|
network: networkParam ?? network,
|
||||||
masterHd:
|
masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
|
||||||
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
|
@ -91,6 +93,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
String? addressPageType,
|
String? addressPageType,
|
||||||
BasedUtxoNetwork? network,
|
BasedUtxoNetwork? network,
|
||||||
|
@ -125,6 +128,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialSilentAddresses: initialSilentAddresses,
|
initialSilentAddresses: initialSilentAddresses,
|
||||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
@ -138,54 +142,87 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
required bool alwaysScan,
|
required bool alwaysScan,
|
||||||
}) async {
|
}) async {
|
||||||
final network = walletInfo.network != null
|
final network = walletInfo.network != null
|
||||||
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
||||||
: BitcoinNetwork.mainnet;
|
: BitcoinNetwork.mainnet;
|
||||||
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
|
|
||||||
|
|
||||||
walletInfo.derivationInfo ??= DerivationInfo(
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
derivationType: snp.derivationType ?? DerivationType.electrum,
|
|
||||||
derivationPath: snp.derivationPath,
|
ElectrumWalletSnapshot? snp = null;
|
||||||
);
|
|
||||||
|
try {
|
||||||
|
snp = await ElectrumWalletSnapshot.load(
|
||||||
|
encryptionFileUtils,
|
||||||
|
name,
|
||||||
|
walletInfo.type,
|
||||||
|
password,
|
||||||
|
network,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
keysData = WalletKeysData(
|
||||||
|
mnemonic: snp!.mnemonic,
|
||||||
|
xPub: snp.xpub,
|
||||||
|
passphrase: snp.passphrase,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(
|
||||||
|
name,
|
||||||
|
walletInfo.type,
|
||||||
|
password,
|
||||||
|
encryptionFileUtils,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
walletInfo.derivationInfo ??= DerivationInfo();
|
||||||
|
|
||||||
// set the default if not present:
|
// set the default if not present:
|
||||||
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path;
|
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
|
||||||
walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
|
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
|
||||||
|
|
||||||
Uint8List? seedBytes = null;
|
Uint8List? seedBytes = null;
|
||||||
|
final mnemonic = keysData.mnemonic;
|
||||||
|
final passphrase = keysData.passphrase;
|
||||||
|
|
||||||
if (snp.mnemonic != null) {
|
if (mnemonic != null) {
|
||||||
switch (walletInfo.derivationInfo!.derivationType) {
|
switch (walletInfo.derivationInfo!.derivationType) {
|
||||||
case DerivationType.electrum:
|
case DerivationType.electrum:
|
||||||
seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
|
seedBytes = await mnemonicToSeedBytes(mnemonic);
|
||||||
break;
|
break;
|
||||||
case DerivationType.bip39:
|
case DerivationType.bip39:
|
||||||
default:
|
default:
|
||||||
seedBytes = await bip39.mnemonicToSeed(
|
seedBytes = await bip39.mnemonicToSeed(
|
||||||
snp.mnemonic!,
|
mnemonic,
|
||||||
passphrase: snp.passphrase ?? '',
|
passphrase: passphrase ?? '',
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
mnemonic: snp.mnemonic,
|
mnemonic: mnemonic,
|
||||||
xpub: snp.xpub,
|
xpub: keysData.xPub,
|
||||||
password: password,
|
password: password,
|
||||||
passphrase: snp.passphrase,
|
passphrase: passphrase,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp?.addresses,
|
||||||
initialSilentAddresses: snp.silentAddresses,
|
initialSilentAddresses: snp?.silentAddresses,
|
||||||
initialSilentAddressIndex: snp.silentAddressIndex,
|
initialSilentAddressIndex: snp?.silentAddressIndex ?? 0,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp?.balance,
|
||||||
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||||
addressPageType: snp.addressPageType,
|
addressPageType: snp?.addressPageType,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
);
|
);
|
||||||
|
@ -235,7 +272,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
|
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
|
||||||
|
|
||||||
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
|
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
|
||||||
return BtcTransaction.fromRaw(hex.encode(rawHex));
|
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -249,8 +286,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
||||||
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
|
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
|
||||||
|
|
||||||
final signature = await _bitcoinLedgerApp!
|
final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!,
|
||||||
.signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath);
|
message: ascii.encode(message), signDerivationPath: derivationPath);
|
||||||
return base64Encode(signature);
|
return base64Encode(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
import 'package:blockchain_utils/bip/bip/bip32/bip32.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -24,7 +24,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
}) : super(walletInfo);
|
}) : super(walletInfo);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
|
String getAddress(
|
||||||
|
{required int index, required Bip32Slip10Secp256k1 hd, BitcoinAddressType? addressType}) {
|
||||||
if (addressType == P2pkhAddressType.p2pkh)
|
if (addressType == P2pkhAddressType.p2pkh)
|
||||||
return generateP2PKHAddress(hd: hd, index: index, network: network);
|
return generateP2PKHAddress(hd: hd, index: index, network: network);
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,13 @@ class BitcoinNewWalletCredentials extends WalletCredentials {
|
||||||
BitcoinNewWalletCredentials(
|
BitcoinNewWalletCredentials(
|
||||||
{required String name,
|
{required String name,
|
||||||
WalletInfo? walletInfo,
|
WalletInfo? walletInfo,
|
||||||
|
String? password,
|
||||||
DerivationType? derivationType,
|
DerivationType? derivationType,
|
||||||
String? derivationPath})
|
String? derivationPath})
|
||||||
: super(
|
: super(
|
||||||
name: name,
|
name: name,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
|
password: password,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_service.dart';
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
@ -19,11 +20,12 @@ class BitcoinWalletService extends WalletService<
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
BitcoinRestoreWalletFromSeedCredentials,
|
||||||
BitcoinRestoreWalletFromWIFCredentials,
|
BitcoinRestoreWalletFromWIFCredentials,
|
||||||
BitcoinRestoreWalletFromHardware> {
|
BitcoinRestoreWalletFromHardware> {
|
||||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan);
|
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
final bool alwaysScan;
|
final bool alwaysScan;
|
||||||
|
final bool isDirect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletType getType() => WalletType.bitcoin;
|
WalletType getType() => WalletType.bitcoin;
|
||||||
|
@ -40,9 +42,12 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
network: network,
|
network: network,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +66,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
saveBackup(name);
|
saveBackup(name);
|
||||||
|
@ -73,6 +79,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -97,6 +104,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: currentWalletInfo,
|
walletInfo: currentWalletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
||||||
await currentWallet.renameWalletFiles(newName);
|
await currentWallet.renameWalletFiles(newName);
|
||||||
|
@ -123,6 +131,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -151,6 +160,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
network: network,
|
network: network,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
|
@ -8,6 +8,8 @@ import 'package:cw_bitcoin/script_hash.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
enum ConnectionStatus { connected, disconnected, connecting, failed }
|
||||||
|
|
||||||
String jsonrpcparams(List<Object> params) {
|
String jsonrpcparams(List<Object> params) {
|
||||||
final _params = params.map((val) => '"${val.toString()}"').join(',');
|
final _params = params.map((val) => '"${val.toString()}"').join(',');
|
||||||
return '[$_params]';
|
return '[$_params]';
|
||||||
|
@ -41,7 +43,7 @@ class ElectrumClient {
|
||||||
|
|
||||||
bool get isConnected => _isConnected;
|
bool get isConnected => _isConnected;
|
||||||
Socket? socket;
|
Socket? socket;
|
||||||
void Function(bool?)? onConnectionStatusChange;
|
void Function(ConnectionStatus)? onConnectionStatusChange;
|
||||||
int _id;
|
int _id;
|
||||||
final Map<String, SocketTask> _tasks;
|
final Map<String, SocketTask> _tasks;
|
||||||
Map<String, SocketTask> get tasks => _tasks;
|
Map<String, SocketTask> get tasks => _tasks;
|
||||||
|
@ -60,17 +62,33 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> connect({required String host, required int port, bool? useSSL}) async {
|
Future<void> connect({required String host, required int port, bool? useSSL}) async {
|
||||||
|
_setConnectionStatus(ConnectionStatus.connecting);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await socket?.close();
|
await socket?.close();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
try {
|
||||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||||
} else {
|
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||||
socket = await SecureSocket.connect(host, port,
|
} else {
|
||||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
socket = await SecureSocket.connect(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
timeout: connectionTimeout,
|
||||||
|
onBadCertificate: (_) => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
_setConnectionStatus(ConnectionStatus.failed);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
_setIsConnected(true);
|
|
||||||
|
if (socket == null) {
|
||||||
|
_setConnectionStatus(ConnectionStatus.failed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_setConnectionStatus(ConnectionStatus.connected);
|
||||||
|
|
||||||
socket!.listen((Uint8List event) {
|
socket!.listen((Uint8List event) {
|
||||||
try {
|
try {
|
||||||
|
@ -86,13 +104,20 @@ class ElectrumClient {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
}, onError: (Object error) {
|
}, onError: (Object error) {
|
||||||
print(error.toString());
|
final errorMsg = error.toString();
|
||||||
|
print(errorMsg);
|
||||||
unterminatedString = '';
|
unterminatedString = '';
|
||||||
_setIsConnected(false);
|
|
||||||
|
final currentHost = socket?.address.host;
|
||||||
|
final isErrorForCurrentHost = errorMsg.contains(" ${currentHost} ");
|
||||||
|
|
||||||
|
if (currentHost != null && isErrorForCurrentHost)
|
||||||
|
_setConnectionStatus(ConnectionStatus.failed);
|
||||||
}, onDone: () {
|
}, onDone: () {
|
||||||
unterminatedString = '';
|
unterminatedString = '';
|
||||||
_setIsConnected(null);
|
if (host == socket?.address.host) _setConnectionStatus(ConnectionStatus.disconnected);
|
||||||
});
|
});
|
||||||
|
|
||||||
keepAlive();
|
keepAlive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,9 +169,9 @@ class ElectrumClient {
|
||||||
Future<void> ping() async {
|
Future<void> ping() async {
|
||||||
try {
|
try {
|
||||||
await callWithTimeout(method: 'server.ping');
|
await callWithTimeout(method: 'server.ping');
|
||||||
_setIsConnected(true);
|
_setConnectionStatus(ConnectionStatus.connected);
|
||||||
} on RequestFailedTimeoutException catch (_) {
|
} on RequestFailedTimeoutException catch (_) {
|
||||||
_setIsConnected(null);
|
_setConnectionStatus(ConnectionStatus.disconnected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,9 +261,24 @@ class ElectrumClient {
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async =>
|
Future<dynamic> getTransaction({required String hash, required bool verbose}) async {
|
||||||
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
|
try {
|
||||||
.then((dynamic result) {
|
final result = await callWithTimeout(
|
||||||
|
method: 'blockchain.transaction.get', params: [hash, verbose], timeout: 10000);
|
||||||
|
if (result is Map<String, dynamic>) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} on RequestFailedTimeoutException catch (_) {
|
||||||
|
return <String, dynamic>{};
|
||||||
|
} catch (e) {
|
||||||
|
print("getTransaction: ${e.toString()}");
|
||||||
|
return <String, dynamic>{};
|
||||||
|
}
|
||||||
|
return <String, dynamic>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getTransactionVerbose({required String hash}) =>
|
||||||
|
getTransaction(hash: hash, verbose: true).then((dynamic result) {
|
||||||
if (result is Map<String, dynamic>) {
|
if (result is Map<String, dynamic>) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -246,9 +286,8 @@ class ElectrumClient {
|
||||||
return <String, dynamic>{};
|
return <String, dynamic>{};
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<String> getTransactionHex({required String hash}) async =>
|
Future<String> getTransactionHex({required String hash}) =>
|
||||||
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
|
getTransaction(hash: hash, verbose: false).then((dynamic result) {
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is String) {
|
if (result is String) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -336,7 +375,7 @@ class ElectrumClient {
|
||||||
try {
|
try {
|
||||||
final topDoubleString = await estimatefee(p: 1);
|
final topDoubleString = await estimatefee(p: 1);
|
||||||
final middleDoubleString = await estimatefee(p: 5);
|
final middleDoubleString = await estimatefee(p: 5);
|
||||||
final bottomDoubleString = await estimatefee(p: 100);
|
final bottomDoubleString = await estimatefee(p: 10);
|
||||||
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
|
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round();
|
||||||
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
||||||
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
||||||
|
@ -353,14 +392,21 @@ class ElectrumClient {
|
||||||
// "height": 520481,
|
// "height": 520481,
|
||||||
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||||
// }
|
// }
|
||||||
Future<int?> getCurrentBlockChainTip() =>
|
|
||||||
callWithTimeout(method: 'blockchain.headers.subscribe').then((result) {
|
|
||||||
if (result is Map<String, dynamic>) {
|
|
||||||
return result["height"] as int;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
Future<int?> getCurrentBlockChainTip() async {
|
||||||
});
|
try {
|
||||||
|
final result = await callWithTimeout(method: 'blockchain.headers.subscribe');
|
||||||
|
if (result is Map<String, dynamic>) {
|
||||||
|
return result["height"] as int;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} on RequestFailedTimeoutException catch (_) {
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
print("getCurrentBlockChainTip: ${e.toString()}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BehaviorSubject<Object>? chainTipSubscribe() {
|
BehaviorSubject<Object>? chainTipSubscribe() {
|
||||||
_id += 1;
|
_id += 1;
|
||||||
|
@ -379,6 +425,10 @@ class ElectrumClient {
|
||||||
BehaviorSubject<T>? subscribe<T>(
|
BehaviorSubject<T>? subscribe<T>(
|
||||||
{required String id, required String method, List<Object> params = const []}) {
|
{required String id, required String method, List<Object> params = const []}) {
|
||||||
try {
|
try {
|
||||||
|
if (socket == null) {
|
||||||
|
_setConnectionStatus(ConnectionStatus.failed);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
final subscription = BehaviorSubject<T>();
|
final subscription = BehaviorSubject<T>();
|
||||||
_regisrySubscription(id, subscription);
|
_regisrySubscription(id, subscription);
|
||||||
socket!.write(jsonrpc(method: method, id: _id, params: params));
|
socket!.write(jsonrpc(method: method, id: _id, params: params));
|
||||||
|
@ -392,6 +442,10 @@ class ElectrumClient {
|
||||||
|
|
||||||
Future<dynamic> call(
|
Future<dynamic> call(
|
||||||
{required String method, List<Object> params = const [], Function(int)? idCallback}) async {
|
{required String method, List<Object> params = const [], Function(int)? idCallback}) async {
|
||||||
|
if (socket == null) {
|
||||||
|
_setConnectionStatus(ConnectionStatus.failed);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
final completer = Completer<dynamic>();
|
final completer = Completer<dynamic>();
|
||||||
_id += 1;
|
_id += 1;
|
||||||
final id = _id;
|
final id = _id;
|
||||||
|
@ -405,6 +459,10 @@ class ElectrumClient {
|
||||||
Future<dynamic> callWithTimeout(
|
Future<dynamic> callWithTimeout(
|
||||||
{required String method, List<Object> params = const [], int timeout = 4000}) async {
|
{required String method, List<Object> params = const [], int timeout = 4000}) async {
|
||||||
try {
|
try {
|
||||||
|
if (socket == null) {
|
||||||
|
_setConnectionStatus(ConnectionStatus.failed);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
final completer = Completer<dynamic>();
|
final completer = Completer<dynamic>();
|
||||||
_id += 1;
|
_id += 1;
|
||||||
final id = _id;
|
final id = _id;
|
||||||
|
@ -426,6 +484,7 @@ class ElectrumClient {
|
||||||
_aliveTimer?.cancel();
|
_aliveTimer?.cancel();
|
||||||
try {
|
try {
|
||||||
await socket?.close();
|
await socket?.close();
|
||||||
|
socket = null;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
onConnectionStatusChange = null;
|
onConnectionStatusChange = null;
|
||||||
}
|
}
|
||||||
|
@ -474,12 +533,9 @@ class ElectrumClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setIsConnected(bool? isConnected) {
|
void _setConnectionStatus(ConnectionStatus status) {
|
||||||
if (_isConnected != isConnected) {
|
onConnectionStatusChange?.call(status);
|
||||||
onConnectionStatusChange?.call(isConnected);
|
_isConnected = status == ConnectionStatus.connected;
|
||||||
}
|
|
||||||
|
|
||||||
_isConnected = isConnected ?? false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleResponse(Map<String, dynamic> response) {
|
void _handleResponse(Map<String, dynamic> response) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
|
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
@ -6,6 +7,8 @@ import 'package:cw_core/transaction_history.dart';
|
||||||
import 'package:cw_core/utils/file.dart';
|
import 'package:cw_core/utils/file.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
|
|
||||||
part 'electrum_transaction_history.g.dart';
|
part 'electrum_transaction_history.g.dart';
|
||||||
|
|
||||||
|
@ -15,13 +18,15 @@ class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$Electru
|
||||||
|
|
||||||
abstract class ElectrumTransactionHistoryBase
|
abstract class ElectrumTransactionHistoryBase
|
||||||
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
||||||
ElectrumTransactionHistoryBase({required this.walletInfo, required String password})
|
ElectrumTransactionHistoryBase(
|
||||||
|
{required this.walletInfo, required String password, required this.encryptionFileUtils})
|
||||||
: _password = password,
|
: _password = password,
|
||||||
_height = 0 {
|
_height = 0 {
|
||||||
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
final WalletInfo walletInfo;
|
final WalletInfo walletInfo;
|
||||||
|
final EncryptionFileUtils encryptionFileUtils;
|
||||||
String _password;
|
String _password;
|
||||||
int _height;
|
int _height;
|
||||||
|
|
||||||
|
@ -44,7 +49,7 @@ abstract class ElectrumTransactionHistoryBase
|
||||||
txjson[tx.key] = tx.value.toJson();
|
txjson[tx.key] = tx.value.toJson();
|
||||||
}
|
}
|
||||||
final data = json.encode({'height': _height, 'transactions': txjson});
|
final data = json.encode({'height': _height, 'transactions': txjson});
|
||||||
await writeData(path: path, password: _password, data: data);
|
await encryptionFileUtils.write(path: path, password: _password, data: data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error while save bitcoin transaction history: ${e.toString()}');
|
print('Error while save bitcoin transaction history: ${e.toString()}');
|
||||||
}
|
}
|
||||||
|
@ -58,7 +63,7 @@ abstract class ElectrumTransactionHistoryBase
|
||||||
Future<Map<String, dynamic>> _read() async {
|
Future<Map<String, dynamic>> _read() async {
|
||||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
final path = '$dirPath/$transactionsHistoryFileName';
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
final content = await read(path: path, password: _password);
|
final content = await encryptionFileUtils.read(path: path, password: _password);
|
||||||
return json.decode(content) as Map<String, dynamic>;
|
return json.decode(content) as Map<String, dynamic>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
|
|
||||||
ElectrumTransactionInfo(this.type,
|
ElectrumTransactionInfo(this.type,
|
||||||
{required String id,
|
{required String id,
|
||||||
required int height,
|
int? height,
|
||||||
required int amount,
|
required int amount,
|
||||||
int? fee,
|
int? fee,
|
||||||
List<String>? inputAddresses,
|
List<String>? inputAddresses,
|
||||||
|
@ -99,7 +99,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromElectrumBundle(
|
factory ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network,
|
ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network,
|
||||||
{required Set<String> addresses, required int height}) {
|
{required Set<String> addresses, int? height}) {
|
||||||
final date = bundle.time != null
|
final date = bundle.time != null
|
||||||
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
|
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
|
||||||
: DateTime.now();
|
: DateTime.now();
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'dart:isolate';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cw_bitcoin/address_from_output.dart';
|
import 'package:cw_bitcoin/address_from_output.dart';
|
||||||
|
@ -22,7 +22,6 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/exceptions.dart';
|
import 'package:cw_bitcoin/exceptions.dart';
|
||||||
import 'package:cw_bitcoin/litecoin_network.dart';
|
|
||||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||||
import 'package:cw_bitcoin/script_hash.dart';
|
import 'package:cw_bitcoin/script_hash.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
|
@ -34,14 +33,13 @@ import 'package:cw_core/sync_status.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.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_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cw_core/get_height_by_date.dart';
|
import 'package:cw_core/get_height_by_date.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:rxdart/subjects.dart';
|
import 'package:rxdart/subjects.dart';
|
||||||
import 'package:sp_scanner/sp_scanner.dart';
|
import 'package:sp_scanner/sp_scanner.dart';
|
||||||
|
@ -54,12 +52,13 @@ const int TWEAKS_COUNT = 25;
|
||||||
|
|
||||||
abstract class ElectrumWalletBase
|
abstract class ElectrumWalletBase
|
||||||
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
|
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
|
||||||
with Store {
|
with Store, WalletKeysFile {
|
||||||
ElectrumWalletBase({
|
ElectrumWalletBase({
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required this.networkType,
|
required this.network,
|
||||||
|
required this.encryptionFileUtils,
|
||||||
String? xpub,
|
String? xpub,
|
||||||
String? mnemonic,
|
String? mnemonic,
|
||||||
Uint8List? seedBytes,
|
Uint8List? seedBytes,
|
||||||
|
@ -70,7 +69,7 @@ abstract class ElectrumWalletBase
|
||||||
CryptoCurrency? currency,
|
CryptoCurrency? currency,
|
||||||
this.alwaysScan,
|
this.alwaysScan,
|
||||||
}) : accountHD =
|
}) : accountHD =
|
||||||
getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo),
|
getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
|
||||||
syncStatus = NotConnectedSyncStatus(),
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
_password = password,
|
_password = password,
|
||||||
_feeRates = <int>[],
|
_feeRates = <int>[],
|
||||||
|
@ -89,23 +88,22 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||||
this.network = _getNetwork(networkType, currency),
|
this.isTestnet = network == BitcoinNetwork.testnet,
|
||||||
this.isTestnet = networkType == bitcoin.testnet,
|
|
||||||
this._mnemonic = mnemonic,
|
this._mnemonic = mnemonic,
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
this.electrumClient = electrumClient ?? ElectrumClient();
|
this.electrumClient = electrumClient ?? ElectrumClient();
|
||||||
this.walletInfo = walletInfo;
|
this.walletInfo = walletInfo;
|
||||||
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
transactionHistory = ElectrumTransactionHistory(
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
password: password,
|
||||||
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
|
);
|
||||||
|
|
||||||
reaction((_) => syncStatus, _syncStatusReaction);
|
reaction((_) => syncStatus, _syncStatusReaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bitcoin.HDWallet getAccountHDWallet(
|
static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
|
||||||
CryptoCurrency? currency,
|
Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
|
||||||
bitcoin.NetworkType networkType,
|
|
||||||
Uint8List? seedBytes,
|
|
||||||
String? xpub,
|
|
||||||
DerivationInfo? derivationInfo) {
|
|
||||||
if (seedBytes == null && xpub == null) {
|
if (seedBytes == null && xpub == null) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"To create a Wallet you need either a seed or an xpub. This should not happen");
|
"To create a Wallet you need either a seed or an xpub. This should not happen");
|
||||||
|
@ -114,25 +112,28 @@ abstract class ElectrumWalletBase
|
||||||
if (seedBytes != null) {
|
if (seedBytes != null) {
|
||||||
return currency == CryptoCurrency.bch
|
return currency == CryptoCurrency.bch
|
||||||
? bitcoinCashHDWallet(seedBytes)
|
? bitcoinCashHDWallet(seedBytes)
|
||||||
: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
|
: Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
|
||||||
.derivePath(_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path));
|
_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
|
||||||
|
as Bip32Slip10Secp256k1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitcoin.HDWallet.fromBase58(xpub!);
|
return Bip32Slip10Secp256k1.fromExtendedKey(xpub!);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bitcoin.HDWallet bitcoinCashHDWallet(Uint8List seedBytes) =>
|
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
|
||||||
bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'");
|
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
|
||||||
|
|
||||||
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
||||||
inputsCount * 68 + outputsCounts * 34 + 10;
|
inputsCount * 68 + outputsCounts * 34 + 10;
|
||||||
|
|
||||||
bool? alwaysScan;
|
bool? alwaysScan;
|
||||||
|
|
||||||
final bitcoin.HDWallet accountHD;
|
final Bip32Slip10Secp256k1 accountHD;
|
||||||
final String? _mnemonic;
|
final String? _mnemonic;
|
||||||
|
|
||||||
bitcoin.HDWallet get hd => accountHD.derive(0);
|
Bip32Slip10Secp256k1 get hd => accountHD.childKey(Bip32KeyIndex(0));
|
||||||
|
|
||||||
|
final EncryptionFileUtils encryptionFileUtils;
|
||||||
final String? passphrase;
|
final String? passphrase;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -164,12 +165,18 @@ abstract class ElectrumWalletBase
|
||||||
.map((addr) => scriptHash(addr.address, network: network))
|
.map((addr) => scriptHash(addr.address, network: network))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
String get xpub => accountHD.base58!;
|
String get xpub => accountHD.publicKey.toExtended;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get seed => _mnemonic;
|
String? get seed => _mnemonic;
|
||||||
|
|
||||||
bitcoin.NetworkType networkType;
|
@override
|
||||||
|
WalletKeysData get walletKeysData =>
|
||||||
|
WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get password => _password;
|
||||||
|
|
||||||
BasedUtxoNetwork network;
|
BasedUtxoNetwork network;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -185,24 +192,21 @@ abstract class ElectrumWalletBase
|
||||||
bool _isTryingToConnect = false;
|
bool _isTryingToConnect = false;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> setSilentPaymentsScanning(bool active, bool usingElectrs) async {
|
Future<void> setSilentPaymentsScanning(bool active) async {
|
||||||
silentPaymentsScanningActive = active;
|
silentPaymentsScanningActive = active;
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
syncStatus = AttemptingSyncStatus();
|
syncStatus = StartingScanSyncStatus();
|
||||||
|
|
||||||
final tip = await getUpdatedChainTip();
|
final tip = await getUpdatedChainTip();
|
||||||
|
|
||||||
if (tip == walletInfo.restoreHeight) {
|
if (tip == walletInfo.restoreHeight) {
|
||||||
syncStatus = SyncedTipSyncStatus(tip);
|
syncStatus = SyncedTipSyncStatus(tip);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tip > walletInfo.restoreHeight) {
|
if (tip > walletInfo.restoreHeight) {
|
||||||
_setListeners(
|
_setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
|
||||||
walletInfo.restoreHeight,
|
|
||||||
chainTipParam: _currentChainTip,
|
|
||||||
usingElectrs: usingElectrs,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alwaysScan = false;
|
alwaysScan = false;
|
||||||
|
@ -240,8 +244,11 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BitcoinWalletKeys get keys =>
|
BitcoinWalletKeys get keys => BitcoinWalletKeys(
|
||||||
BitcoinWalletKeys(wif: hd.wif!, privateKey: hd.privKey!, publicKey: hd.pubKey!);
|
wif: WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer),
|
||||||
|
privateKey: hd.privateKey.toHex(),
|
||||||
|
publicKey: hd.publicKey.toHex(),
|
||||||
|
);
|
||||||
|
|
||||||
String _password;
|
String _password;
|
||||||
List<BitcoinUnspent> unspentCoins;
|
List<BitcoinUnspent> unspentCoins;
|
||||||
|
@ -273,7 +280,7 @@ abstract class ElectrumWalletBase
|
||||||
int height, {
|
int height, {
|
||||||
int? chainTipParam,
|
int? chainTipParam,
|
||||||
bool? doSingleScan,
|
bool? doSingleScan,
|
||||||
bool? usingElectrs,
|
bool? usingSupportedNode,
|
||||||
}) async {
|
}) async {
|
||||||
final chainTip = chainTipParam ?? await getUpdatedChainTip();
|
final chainTip = chainTipParam ?? await getUpdatedChainTip();
|
||||||
|
|
||||||
|
@ -282,7 +289,7 @@ abstract class ElectrumWalletBase
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
syncStatus = AttemptingSyncStatus();
|
syncStatus = StartingScanSyncStatus();
|
||||||
|
|
||||||
if (_isolate != null) {
|
if (_isolate != null) {
|
||||||
final runningIsolate = await _isolate!;
|
final runningIsolate = await _isolate!;
|
||||||
|
@ -300,7 +307,9 @@ abstract class ElectrumWalletBase
|
||||||
chainTip: chainTip,
|
chainTip: chainTip,
|
||||||
electrumClient: ElectrumClient(),
|
electrumClient: ElectrumClient(),
|
||||||
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
|
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
|
||||||
node: usingElectrs == true ? ScanNode(node!.uri, node!.useSSL) : null,
|
node: (await getNodeSupportsSilentPayments()) == true
|
||||||
|
? ScanNode(node!.uri, node!.useSSL)
|
||||||
|
: null,
|
||||||
labels: walletAddresses.labels,
|
labels: walletAddresses.labels,
|
||||||
labelIndexes: walletAddresses.silentAddresses
|
labelIndexes: walletAddresses.silentAddresses
|
||||||
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1)
|
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.index >= 1)
|
||||||
|
@ -388,7 +397,7 @@ abstract class ElectrumWalletBase
|
||||||
BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)),
|
BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)),
|
||||||
)
|
)
|
||||||
: silentAddress.B_spend,
|
: silentAddress.B_spend,
|
||||||
hrp: silentAddress.hrp,
|
network: network,
|
||||||
);
|
);
|
||||||
|
|
||||||
final addressRecord = walletAddresses.silentAddresses
|
final addressRecord = walletAddresses.silentAddresses
|
||||||
|
@ -417,8 +426,6 @@ abstract class ElectrumWalletBase
|
||||||
await updateAllUnspents();
|
await updateAllUnspents();
|
||||||
await updateBalance();
|
await updateBalance();
|
||||||
|
|
||||||
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
|
|
||||||
|
|
||||||
if (alwaysScan == true) {
|
if (alwaysScan == true) {
|
||||||
_setListeners(walletInfo.restoreHeight);
|
_setListeners(walletInfo.restoreHeight);
|
||||||
} else {
|
} else {
|
||||||
|
@ -441,6 +448,57 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
Node? node;
|
Node? node;
|
||||||
|
|
||||||
|
Future<bool> getNodeIsElectrs() async {
|
||||||
|
if (node == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final version = await electrumClient.version();
|
||||||
|
|
||||||
|
if (version.isNotEmpty) {
|
||||||
|
final server = version[0];
|
||||||
|
|
||||||
|
if (server.toLowerCase().contains('electrs')) {
|
||||||
|
node!.isElectrs = true;
|
||||||
|
node!.save();
|
||||||
|
return node!.isElectrs!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node!.isElectrs = false;
|
||||||
|
node!.save();
|
||||||
|
return node!.isElectrs!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> getNodeSupportsSilentPayments() async {
|
||||||
|
// As of today (august 2024), only ElectrumRS supports silent payments
|
||||||
|
if (!(await getNodeIsElectrs())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final tweaksResponse = await electrumClient.getTweaks(height: 0);
|
||||||
|
|
||||||
|
if (tweaksResponse != null) {
|
||||||
|
node!.supportsSilentPayments = true;
|
||||||
|
node!.save();
|
||||||
|
return node!.supportsSilentPayments!;
|
||||||
|
}
|
||||||
|
} on RequestFailedTimeoutException catch (_) {
|
||||||
|
node!.supportsSilentPayments = false;
|
||||||
|
node!.save();
|
||||||
|
return node!.supportsSilentPayments!;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
node!.supportsSilentPayments = false;
|
||||||
|
node!.save();
|
||||||
|
return node!.supportsSilentPayments!;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@override
|
@override
|
||||||
Future<void> connectToNode({required Node node}) async {
|
Future<void> connectToNode({required Node node}) async {
|
||||||
|
@ -502,13 +560,6 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
final hd =
|
final hd =
|
||||||
utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
|
utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
|
||||||
final derivationPath =
|
|
||||||
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
|
|
||||||
"/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
|
|
||||||
"/${utx.bitcoinAddressRecord.index}";
|
|
||||||
final pubKeyHex = hd.derive(utx.bitcoinAddressRecord.index).pubKey!;
|
|
||||||
|
|
||||||
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
|
|
||||||
|
|
||||||
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||||
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
|
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
|
||||||
|
@ -525,6 +576,7 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout));
|
vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout));
|
||||||
|
String pubKeyHex;
|
||||||
|
|
||||||
if (privkey != null) {
|
if (privkey != null) {
|
||||||
inputPrivKeyInfos.add(ECPrivateInfo(
|
inputPrivKeyInfos.add(ECPrivateInfo(
|
||||||
|
@ -532,8 +584,18 @@ abstract class ElectrumWalletBase
|
||||||
address.type == SegwitAddresType.p2tr,
|
address.type == SegwitAddresType.p2tr,
|
||||||
tweak: !isSilentPayment,
|
tweak: !isSilentPayment,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
pubKeyHex = privkey.getPublic().toHex();
|
||||||
|
} else {
|
||||||
|
pubKeyHex = hd.childKey(Bip32KeyIndex(utx.bitcoinAddressRecord.index)).publicKey.toHex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final derivationPath =
|
||||||
|
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
|
||||||
|
"/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
|
||||||
|
"/${utx.bitcoinAddressRecord.index}";
|
||||||
|
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
|
||||||
|
|
||||||
utxos.add(
|
utxos.add(
|
||||||
UtxoWithAddress(
|
UtxoWithAddress(
|
||||||
utxo: BitcoinUtxo(
|
utxo: BitcoinUtxo(
|
||||||
|
@ -931,11 +993,29 @@ abstract class ElectrumWalletBase
|
||||||
bool hasTaprootInputs = false;
|
bool hasTaprootInputs = false;
|
||||||
|
|
||||||
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
|
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
|
||||||
final key = estimatedTx.inputPrivKeyInfos
|
String error = "Cannot find private key.";
|
||||||
.firstWhereOrNull((element) => element.privkey.getPublic().toHex() == publicKey);
|
|
||||||
|
ECPrivateInfo? key;
|
||||||
|
|
||||||
|
if (estimatedTx.inputPrivKeyInfos.isEmpty) {
|
||||||
|
error += "\nNo private keys generated.";
|
||||||
|
} else {
|
||||||
|
error += "\nAddress: ${utxo.ownerDetails.address.toAddress()}";
|
||||||
|
|
||||||
|
key = estimatedTx.inputPrivKeyInfos.firstWhereOrNull((element) {
|
||||||
|
final elemPubkey = element.privkey.getPublic().toHex();
|
||||||
|
if (elemPubkey == publicKey) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
error += "\nExpected: $publicKey";
|
||||||
|
error += "\nPubkey: $elemPubkey";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
throw Exception("Cannot find private key");
|
throw Exception(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (utxo.utxo.isP2tr()) {
|
if (utxo.utxo.isP2tr()) {
|
||||||
|
@ -1076,8 +1156,13 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||||
|
await saveKeysFile(_password, encryptionFileUtils);
|
||||||
|
saveKeysFile(_password, encryptionFileUtils, true);
|
||||||
|
}
|
||||||
|
|
||||||
final path = await makePath();
|
final path = await makePath();
|
||||||
await write(path: path, password: _password, data: toJSON());
|
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
|
||||||
await transactionHistory.save();
|
await transactionHistory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1117,10 +1202,9 @@ abstract class ElectrumWalletBase
|
||||||
int? chainTip,
|
int? chainTip,
|
||||||
ScanData? scanData,
|
ScanData? scanData,
|
||||||
bool? doSingleScan,
|
bool? doSingleScan,
|
||||||
bool? usingElectrs,
|
|
||||||
}) async {
|
}) async {
|
||||||
silentPaymentsScanningActive = true;
|
silentPaymentsScanningActive = true;
|
||||||
_setListeners(height, doSingleScan: doSingleScan, usingElectrs: usingElectrs);
|
_setListeners(height, doSingleScan: doSingleScan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1131,8 +1215,6 @@ abstract class ElectrumWalletBase
|
||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> updateAllUnspents() async {
|
Future<void> updateAllUnspents() async {
|
||||||
List<BitcoinUnspent> updatedUnspentCoins = [];
|
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||||
|
@ -1220,7 +1302,7 @@ abstract class ElectrumWalletBase
|
||||||
await Future.wait(unspents.map((unspent) async {
|
await Future.wait(unspents.map((unspent) async {
|
||||||
try {
|
try {
|
||||||
final coin = BitcoinUnspent.fromJSON(address, unspent);
|
final coin = BitcoinUnspent.fromJSON(address, unspent);
|
||||||
final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
|
final tx = await fetchTransactionInfo(hash: coin.hash);
|
||||||
coin.isChange = address.isHidden;
|
coin.isChange = address.isHidden;
|
||||||
coin.confirmations = tx?.confirmations;
|
coin.confirmations = tx?.confirmations;
|
||||||
|
|
||||||
|
@ -1275,9 +1357,17 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> canReplaceByFee(String hash) async {
|
Future<bool> canReplaceByFee(String hash) async {
|
||||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash);
|
||||||
final confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
|
||||||
final transactionHex = verboseTransaction['hex'] as String?;
|
final String? transactionHex;
|
||||||
|
int confirmations = 0;
|
||||||
|
|
||||||
|
if (verboseTransaction.isEmpty) {
|
||||||
|
transactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||||
|
} else {
|
||||||
|
confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
||||||
|
transactionHex = verboseTransaction['hex'] as String?;
|
||||||
|
}
|
||||||
|
|
||||||
if (confirmations > 0) return false;
|
if (confirmations > 0) return false;
|
||||||
|
|
||||||
|
@ -1285,10 +1375,7 @@ abstract class ElectrumWalletBase
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final original = bitcoin.Transaction.fromHex(transactionHex);
|
return BtcTransaction.fromRaw(transactionHex).canReplaceByFee;
|
||||||
|
|
||||||
return original.ins
|
|
||||||
.any((element) => element.sequence != null && element.sequence! < 4294967293);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isChangeSufficientForFee(String txId, int newFee) async {
|
Future<bool> isChangeSufficientForFee(String txId, int newFee) async {
|
||||||
|
@ -1447,50 +1534,73 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
|
Future<ElectrumTransactionBundle> getTransactionExpanded(
|
||||||
|
{required String hash, int? height}) async {
|
||||||
String transactionHex;
|
String transactionHex;
|
||||||
|
// TODO: time is not always available, and calculating it from height is not always accurate.
|
||||||
|
// Add settings to choose API provider and use and http server instead of electrum for this.
|
||||||
int? time;
|
int? time;
|
||||||
int confirmations = 0;
|
int? confirmations;
|
||||||
if (network == BitcoinNetwork.testnet) {
|
|
||||||
// Testnet public electrum server does not support verbose transaction fetching
|
final verboseTransaction = await electrumClient.getTransactionVerbose(hash: hash);
|
||||||
|
|
||||||
|
if (verboseTransaction.isEmpty) {
|
||||||
transactionHex = await electrumClient.getTransactionHex(hash: hash);
|
transactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||||
|
|
||||||
final status = json.decode(
|
|
||||||
(await http.get(Uri.parse("https://blockstream.info/testnet/api/tx/$hash/status"))).body);
|
|
||||||
|
|
||||||
time = status["block_time"] as int?;
|
|
||||||
final height = status["block_height"] as int? ?? 0;
|
|
||||||
final tip = await getUpdatedChainTip();
|
|
||||||
if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0;
|
|
||||||
} else {
|
} else {
|
||||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
|
||||||
|
|
||||||
transactionHex = verboseTransaction['hex'] as String;
|
transactionHex = verboseTransaction['hex'] as String;
|
||||||
time = verboseTransaction['time'] as int?;
|
time = verboseTransaction['time'] as int?;
|
||||||
confirmations = verboseTransaction['confirmations'] as int? ?? 0;
|
confirmations = verboseTransaction['confirmations'] as int?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height != null) {
|
||||||
|
if (time == null) {
|
||||||
|
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirmations == null) {
|
||||||
|
final tip = await getUpdatedChainTip();
|
||||||
|
if (tip > 0 && height > 0) {
|
||||||
|
// Add one because the block itself is the first confirmation
|
||||||
|
confirmations = tip - height + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final original = BtcTransaction.fromRaw(transactionHex);
|
final original = BtcTransaction.fromRaw(transactionHex);
|
||||||
final ins = <BtcTransaction>[];
|
final ins = <BtcTransaction>[];
|
||||||
|
|
||||||
for (final vin in original.inputs) {
|
for (final vin in original.inputs) {
|
||||||
ins.add(BtcTransaction.fromRaw(await electrumClient.getTransactionHex(hash: vin.txId)));
|
final verboseTransaction = await electrumClient.getTransactionVerbose(hash: vin.txId);
|
||||||
|
|
||||||
|
final String inputTransactionHex;
|
||||||
|
|
||||||
|
if (verboseTransaction.isEmpty) {
|
||||||
|
inputTransactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||||
|
} else {
|
||||||
|
inputTransactionHex = verboseTransaction['hex'] as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ElectrumTransactionBundle(
|
return ElectrumTransactionBundle(
|
||||||
original,
|
original,
|
||||||
ins: ins,
|
ins: ins,
|
||||||
time: time,
|
time: time,
|
||||||
confirmations: confirmations,
|
confirmations: confirmations ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
||||||
{required String hash, required int height, bool? retryOnFailure}) async {
|
{required String hash, int? height, bool? retryOnFailure}) async {
|
||||||
try {
|
try {
|
||||||
return ElectrumTransactionInfo.fromElectrumBundle(
|
return ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
await getTransactionExpanded(hash: hash), walletInfo.type, network,
|
await getTransactionExpanded(hash: hash, height: height),
|
||||||
addresses: addressesSet, height: height);
|
walletInfo.type,
|
||||||
|
network,
|
||||||
|
addresses: addressesSet,
|
||||||
|
height: height,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is FormatException && retryOnFailure == true) {
|
if (e is FormatException && retryOnFailure == true) {
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
@ -1641,8 +1751,8 @@ abstract class ElectrumWalletBase
|
||||||
await getCurrentChainTip();
|
await getCurrentChainTip();
|
||||||
|
|
||||||
transactionHistory.transactions.values.forEach((tx) async {
|
transactionHistory.transactions.values.forEach((tx) async {
|
||||||
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) {
|
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) {
|
||||||
tx.confirmations = await getCurrentChainTip() - tx.height + 1;
|
tx.confirmations = await getCurrentChainTip() - tx.height! + 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1758,8 +1868,12 @@ abstract class ElectrumWalletBase
|
||||||
final index = address != null
|
final index = address != null
|
||||||
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
|
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
|
||||||
: null;
|
: null;
|
||||||
final HD = index == null ? hd : hd.derive(index);
|
final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
|
||||||
return base64Encode(HD.signMessage(message));
|
final priv = ECPrivate.fromWif(
|
||||||
|
WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
|
||||||
|
netVersion: network.wifNetVer,
|
||||||
|
);
|
||||||
|
return priv.signMessage(StringUtils.encode(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setInitialHeight() async {
|
Future<void> _setInitialHeight() async {
|
||||||
|
@ -1785,43 +1899,42 @@ abstract class ElectrumWalletBase
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) {
|
|
||||||
if (networkType == bitcoin.bitcoin && currency == CryptoCurrency.bch) {
|
|
||||||
return BitcoinCashNetwork.mainnet;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkType == litecoinNetwork) {
|
|
||||||
return LitecoinNetwork.mainnet;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkType == bitcoin.testnet) {
|
|
||||||
return BitcoinNetwork.testnet;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BitcoinNetwork.mainnet;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String _hardenedDerivationPath(String derivationPath) =>
|
static String _hardenedDerivationPath(String derivationPath) =>
|
||||||
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
|
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _onConnectionStatusChange(bool? isConnected) {
|
void _onConnectionStatusChange(ConnectionStatus status) {
|
||||||
if (syncStatus is SyncingSyncStatus) return;
|
switch (status) {
|
||||||
|
case ConnectionStatus.connected:
|
||||||
|
if (syncStatus is NotConnectedSyncStatus ||
|
||||||
|
syncStatus is LostConnectionSyncStatus ||
|
||||||
|
syncStatus is ConnectingSyncStatus) {
|
||||||
|
syncStatus = AttemptingSyncStatus();
|
||||||
|
startSync();
|
||||||
|
}
|
||||||
|
|
||||||
if (isConnected == true && syncStatus is! SyncedSyncStatus) {
|
break;
|
||||||
syncStatus = ConnectedSyncStatus();
|
case ConnectionStatus.disconnected:
|
||||||
} else if (isConnected == false) {
|
syncStatus = NotConnectedSyncStatus();
|
||||||
syncStatus = LostConnectionSyncStatus();
|
break;
|
||||||
} else if (isConnected != true && syncStatus is! ConnectingSyncStatus) {
|
case ConnectionStatus.failed:
|
||||||
syncStatus = NotConnectedSyncStatus();
|
syncStatus = LostConnectionSyncStatus();
|
||||||
|
// wait for 5 seconds and then try to reconnect:
|
||||||
|
Future.delayed(Duration(seconds: 5), () {
|
||||||
|
electrumClient.connectToUri(
|
||||||
|
node!.uri,
|
||||||
|
useSSL: node!.useSSL ?? false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case ConnectionStatus.connecting:
|
||||||
|
syncStatus = ConnectingSyncStatus();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _syncStatusReaction(SyncStatus syncStatus) async {
|
void _syncStatusReaction(SyncStatus syncStatus) async {
|
||||||
if (syncStatus is! AttemptingSyncStatus && syncStatus is! SyncedTipSyncStatus) {
|
|
||||||
silentPaymentsScanningActive = syncStatus is SyncingSyncStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (syncStatus is NotConnectedSyncStatus) {
|
if (syncStatus is NotConnectedSyncStatus) {
|
||||||
// Needs to re-subscribe to all scripthashes when reconnected
|
// Needs to re-subscribe to all scripthashes when reconnected
|
||||||
_scripthashesUpdateSubject = {};
|
_scripthashesUpdateSubject = {};
|
||||||
|
@ -1942,8 +2055,8 @@ Future<void> startRefresh(ScanData scanData) async {
|
||||||
final tweaks = t as Map<String, dynamic>;
|
final tweaks = t as Map<String, dynamic>;
|
||||||
|
|
||||||
if (tweaks["message"] != null) {
|
if (tweaks["message"] != null) {
|
||||||
// re-subscribe to continue receiving messages
|
// re-subscribe to continue receiving messages, starting from the next unscanned height
|
||||||
electrumClient.tweaksSubscribe(height: syncHeight, count: count);
|
electrumClient.tweaksSubscribe(height: syncHeight + 1, count: count);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
|
@ -30,7 +29,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
Map<String, int>? initialChangeAddressIndex,
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||||
int initialSilentAddressIndex = 0,
|
int initialSilentAddressIndex = 0,
|
||||||
bitcoin.HDWallet? masterHd,
|
Bip32Slip10Secp256k1? masterHd,
|
||||||
BitcoinAddressType? initialAddressPageType,
|
BitcoinAddressType? initialAddressPageType,
|
||||||
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||||
addressesByReceiveType =
|
addressesByReceiveType =
|
||||||
|
@ -53,9 +52,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
if (masterHd != null) {
|
if (masterHd != null) {
|
||||||
silentAddress = SilentPaymentOwner.fromPrivateKeys(
|
silentAddress = SilentPaymentOwner.fromPrivateKeys(
|
||||||
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!),
|
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()),
|
||||||
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
|
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()),
|
||||||
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
|
network: network,
|
||||||
|
);
|
||||||
|
|
||||||
if (silentAddresses.length == 0) {
|
if (silentAddresses.length == 0) {
|
||||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||||
|
@ -92,8 +92,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||||
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||||
final BasedUtxoNetwork network;
|
final BasedUtxoNetwork network;
|
||||||
final bitcoin.HDWallet mainHd;
|
final Bip32Slip10Secp256k1 mainHd;
|
||||||
final bitcoin.HDWallet sideHd;
|
final Bip32Slip10Secp256k1 sideHd;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
SilentPaymentOwner? silentAddress;
|
SilentPaymentOwner? silentAddress;
|
||||||
|
@ -318,7 +318,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
String getAddress(
|
String getAddress(
|
||||||
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
{required int index,
|
||||||
|
required Bip32Slip10Secp256k1 hd,
|
||||||
|
BitcoinAddressType? addressType}) =>
|
||||||
'';
|
'';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -540,11 +542,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
void _validateAddresses() {
|
void _validateAddresses() {
|
||||||
_addresses.forEach((element) {
|
_addresses.forEach((element) {
|
||||||
if (!element.isHidden && element.address !=
|
if (!element.isHidden &&
|
||||||
getAddress(index: element.index, hd: mainHd, addressType: element.type)) {
|
element.address !=
|
||||||
|
getAddress(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||||
element.isHidden = true;
|
element.isHidden = true;
|
||||||
} else if (element.isHidden && element.address !=
|
} else if (element.isHidden &&
|
||||||
getAddress(index: element.index, hd: sideHd, addressType: element.type)) {
|
element.address !=
|
||||||
|
getAddress(index: element.index, hd: sideHd, addressType: element.type)) {
|
||||||
element.isHidden = false;
|
element.isHidden = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -562,7 +566,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return _isAddressByType(addressRecord, addressPageType);
|
return _isAddressByType(addressRecord, addressPageType);
|
||||||
}
|
}
|
||||||
|
|
||||||
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
||||||
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
||||||
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
|
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
|
||||||
!addr.isHidden && !addr.isUsed && addr.type == type;
|
!addr.isHidden && !addr.isUsed && addr.type == type;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -32,22 +33,28 @@ class ElectrumWalletSnapshot {
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
final String? addressPageType;
|
final String? addressPageType;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
String? mnemonic;
|
String? mnemonic;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
String? xpub;
|
String? xpub;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
String? passphrase;
|
||||||
|
|
||||||
List<BitcoinAddressRecord> addresses;
|
List<BitcoinAddressRecord> addresses;
|
||||||
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||||
ElectrumBalance balance;
|
ElectrumBalance balance;
|
||||||
Map<String, int> regularAddressIndex;
|
Map<String, int> regularAddressIndex;
|
||||||
Map<String, int> changeAddressIndex;
|
Map<String, int> changeAddressIndex;
|
||||||
int silentAddressIndex;
|
int silentAddressIndex;
|
||||||
String? passphrase;
|
|
||||||
DerivationType? derivationType;
|
DerivationType? derivationType;
|
||||||
String? derivationPath;
|
String? derivationPath;
|
||||||
|
|
||||||
static Future<ElectrumWalletSnapshot> load(
|
static Future<ElectrumWalletSnapshot> load(
|
||||||
String name, WalletType type, String password, BasedUtxoNetwork network) async {
|
EncryptionFileUtils encryptionFileUtils, String name, WalletType type, String password, BasedUtxoNetwork network) async {
|
||||||
final path = await pathForWallet(name: name, type: type);
|
final path = await pathForWallet(name: name, type: type);
|
||||||
final jsonSource = await read(path: path, password: password);
|
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
final data = json.decode(jsonSource) as Map;
|
||||||
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
||||||
final mnemonic = data['mnemonic'] as String?;
|
final mnemonic = data['mnemonic'] as String?;
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
|
||||||
|
|
||||||
final litecoinNetwork = NetworkType(
|
|
||||||
messagePrefix: '\x19Litecoin Signed Message:\n',
|
|
||||||
bech32: 'ltc',
|
|
||||||
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
|
|
||||||
pubKeyHash: 0x30,
|
|
||||||
scriptHash: 0x32,
|
|
||||||
wif: 0xb0);
|
|
|
@ -1,20 +1,22 @@
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
|
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
|
||||||
import 'package:cw_bitcoin/litecoin_network.dart';
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
|
||||||
|
|
||||||
part 'litecoin_wallet.g.dart';
|
part 'litecoin_wallet.g.dart';
|
||||||
|
|
||||||
|
@ -27,6 +29,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required Uint8List seedBytes,
|
required Uint8List seedBytes,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
String? addressPageType,
|
String? addressPageType,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
|
@ -37,10 +40,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
networkType: litecoinNetwork,
|
network: LitecoinNetwork.mainnet,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
currency: CryptoCurrency.ltc) {
|
currency: CryptoCurrency.ltc) {
|
||||||
walletAddresses = LitecoinWalletAddresses(
|
walletAddresses = LitecoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
|
@ -48,7 +52,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
mainHd: hd,
|
mainHd: hd,
|
||||||
sideHd: accountHD.derive(1),
|
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||||
network: network,
|
network: network,
|
||||||
);
|
);
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
|
@ -61,6 +65,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
String? addressPageType,
|
String? addressPageType,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
|
@ -88,6 +93,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
@ -95,25 +101,54 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<LitecoinWallet> open({
|
static Future<LitecoinWallet> open(
|
||||||
required String name,
|
{required String name,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
required EncryptionFileUtils encryptionFileUtils}) async {
|
||||||
final snp =
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
|
|
||||||
|
ElectrumWalletSnapshot? snp = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
snp = await ElectrumWalletSnapshot.load(
|
||||||
|
encryptionFileUtils,
|
||||||
|
name,
|
||||||
|
walletInfo.type,
|
||||||
|
password,
|
||||||
|
LitecoinNetwork.mainnet,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
keysData =
|
||||||
|
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(
|
||||||
|
name,
|
||||||
|
walletInfo.type,
|
||||||
|
password,
|
||||||
|
encryptionFileUtils,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return LitecoinWallet(
|
return LitecoinWallet(
|
||||||
mnemonic: snp.mnemonic!,
|
mnemonic: keysData.mnemonic!,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp?.addresses,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp?.balance,
|
||||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic!),
|
seedBytes: await mnemonicToSeedBytes(keysData.mnemonic!),
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||||
addressPageType: snp.addressPageType,
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||||
|
addressPageType: snp?.addressPageType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -22,6 +22,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress(
|
String getAddress(
|
||||||
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
{required int index,
|
||||||
|
required Bip32Slip10Secp256k1 hd,
|
||||||
|
BitcoinAddressType? addressType}) =>
|
||||||
generateP2WPKHAddress(hd: hd, index: index, network: network);
|
generateP2WPKHAddress(hd: hd, index: index, network: network);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
|
@ -16,11 +17,13 @@ import 'package:bip39/bip39.dart' as bip39;
|
||||||
class LitecoinWalletService extends WalletService<
|
class LitecoinWalletService extends WalletService<
|
||||||
BitcoinNewWalletCredentials,
|
BitcoinNewWalletCredentials,
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
BitcoinRestoreWalletFromSeedCredentials,
|
||||||
BitcoinRestoreWalletFromWIFCredentials,BitcoinNewWalletCredentials> {
|
BitcoinRestoreWalletFromWIFCredentials,
|
||||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
BitcoinNewWalletCredentials> {
|
||||||
|
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
|
final bool isDirect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletType getType() => WalletType.litecoin;
|
WalletType getType() => WalletType.litecoin;
|
||||||
|
@ -28,11 +31,13 @@ class LitecoinWalletService extends WalletService<
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
final wallet = await LitecoinWalletBase.create(
|
final wallet = await LitecoinWalletBase.create(
|
||||||
mnemonic: await generateElectrumMnemonic(),
|
mnemonic: await generateElectrumMnemonic(),
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
passphrase: credentials.passphrase,
|
passphrase: credentials.passphrase,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
|
@ -45,21 +50,29 @@ class LitecoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> openWallet(String name, String password) async {
|
Future<LitecoinWallet> openWallet(String name, String password) async {
|
||||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
final walletInfo = walletInfoSource.values
|
||||||
(info) => info.id == WalletBase.idFor(name, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final wallet = await LitecoinWalletBase.open(
|
final wallet = await LitecoinWalletBase.open(
|
||||||
password: password, name: name, walletInfo: walletInfo,
|
password: password,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
name: name,
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
saveBackup(name);
|
saveBackup(name);
|
||||||
return wallet;
|
return wallet;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
await restoreWalletFilesFromBackup(name);
|
await restoreWalletFilesFromBackup(name);
|
||||||
final wallet = await LitecoinWalletBase.open(
|
final wallet = await LitecoinWalletBase.open(
|
||||||
password: password, name: name, walletInfo: walletInfo,
|
password: password,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
name: name,
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
@ -67,22 +80,23 @@ class LitecoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> remove(String wallet) async {
|
Future<void> remove(String wallet) async {
|
||||||
File(await pathForWalletDir(name: wallet, type: getType()))
|
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||||
.delete(recursive: true);
|
final walletInfo = walletInfoSource.values
|
||||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||||
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
|
||||||
await walletInfoSource.delete(walletInfo.key);
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> rename(String currentName, String password, String newName) async {
|
Future<void> rename(String currentName, String password, String newName) async {
|
||||||
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
|
final currentWalletInfo = walletInfoSource.values
|
||||||
(info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||||
final currentWallet = await LitecoinWalletBase.open(
|
final currentWallet = await LitecoinWalletBase.open(
|
||||||
password: password,
|
password: password,
|
||||||
name: currentName,
|
name: currentName,
|
||||||
walletInfo: currentWalletInfo,
|
walletInfo: currentWalletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
|
|
||||||
await currentWallet.renameWalletFiles(newName);
|
await currentWallet.renameWalletFiles(newName);
|
||||||
await saveBackup(newName);
|
await saveBackup(newName);
|
||||||
|
@ -96,27 +110,30 @@ class LitecoinWalletService extends WalletService<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) {
|
Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) {
|
||||||
throw UnimplementedError("Restoring a Litecoin wallet from a hardware wallet is not yet supported!");
|
throw UnimplementedError(
|
||||||
|
"Restoring a Litecoin wallet from a hardware wallet is not yet supported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> restoreFromKeys(
|
Future<LitecoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
|
||||||
BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
|
{bool? isTestnet}) async =>
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LitecoinWallet> restoreFromSeed(
|
Future<LitecoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
||||||
BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
|
{bool? isTestnet}) async {
|
||||||
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
|
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
|
||||||
throw LitecoinMnemonicIsIncorrectException();
|
throw LitecoinMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
||||||
final wallet = await LitecoinWalletBase.create(
|
final wallet = await LitecoinWalletBase.create(
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
passphrase: credentials.passphrase,
|
passphrase: credentials.passphrase,
|
||||||
mnemonic: credentials.mnemonic,
|
mnemonic: credentials.mnemonic,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
|
|
|
@ -1,68 +1,54 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
|
||||||
import 'package:hex/hex.dart';
|
|
||||||
|
|
||||||
bitcoin.PaymentData generatePaymentData({
|
|
||||||
required bitcoin.HDWallet hd,
|
|
||||||
required int index,
|
|
||||||
}) {
|
|
||||||
final pubKey = hd.derive(index).pubKey!;
|
|
||||||
return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ECPrivate generateECPrivate({
|
ECPrivate generateECPrivate({
|
||||||
required bitcoin.HDWallet hd,
|
required Bip32Slip10Secp256k1 hd,
|
||||||
required BasedUtxoNetwork network,
|
required BasedUtxoNetwork network,
|
||||||
required int index,
|
required int index,
|
||||||
}) {
|
}) =>
|
||||||
final wif = hd.derive(index).wif!;
|
ECPrivate(hd.childKey(Bip32KeyIndex(index)).privateKey);
|
||||||
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
|
|
||||||
}
|
|
||||||
|
|
||||||
String generateP2WPKHAddress({
|
String generateP2WPKHAddress({
|
||||||
required bitcoin.HDWallet hd,
|
required Bip32Slip10Secp256k1 hd,
|
||||||
required BasedUtxoNetwork network,
|
required BasedUtxoNetwork network,
|
||||||
required int index,
|
required int index,
|
||||||
}) {
|
}) =>
|
||||||
final pubKey = hd.derive(index).pubKey!;
|
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||||
return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
|
.toP2wpkhAddress()
|
||||||
}
|
.toAddress(network);
|
||||||
|
|
||||||
String generateP2SHAddress({
|
String generateP2SHAddress({
|
||||||
required bitcoin.HDWallet hd,
|
required Bip32Slip10Secp256k1 hd,
|
||||||
required BasedUtxoNetwork network,
|
required BasedUtxoNetwork network,
|
||||||
required int index,
|
required int index,
|
||||||
}) {
|
}) =>
|
||||||
final pubKey = hd.derive(index).pubKey!;
|
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||||
return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
|
.toP2wpkhInP2sh()
|
||||||
}
|
.toAddress(network);
|
||||||
|
|
||||||
String generateP2WSHAddress({
|
String generateP2WSHAddress({
|
||||||
required bitcoin.HDWallet hd,
|
required Bip32Slip10Secp256k1 hd,
|
||||||
required BasedUtxoNetwork network,
|
required BasedUtxoNetwork network,
|
||||||
required int index,
|
required int index,
|
||||||
}) {
|
}) =>
|
||||||
final pubKey = hd.derive(index).pubKey!;
|
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||||
return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
|
.toP2wshAddress()
|
||||||
}
|
.toAddress(network);
|
||||||
|
|
||||||
String generateP2PKHAddress({
|
String generateP2PKHAddress({
|
||||||
required bitcoin.HDWallet hd,
|
required Bip32Slip10Secp256k1 hd,
|
||||||
required BasedUtxoNetwork network,
|
required BasedUtxoNetwork network,
|
||||||
required int index,
|
required int index,
|
||||||
}) {
|
}) =>
|
||||||
final pubKey = hd.derive(index).pubKey!;
|
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||||
return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
|
.toP2pkhAddress()
|
||||||
}
|
.toAddress(network);
|
||||||
|
|
||||||
String generateP2TRAddress({
|
String generateP2TRAddress({
|
||||||
required bitcoin.HDWallet hd,
|
required Bip32Slip10Secp256k1 hd,
|
||||||
required BasedUtxoNetwork network,
|
required BasedUtxoNetwork network,
|
||||||
required int index,
|
required int index,
|
||||||
}) {
|
}) =>
|
||||||
final pubKey = hd.derive(index).pubKey!;
|
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||||
return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
|
.toTaprootAddress()
|
||||||
}
|
.toAddress(network);
|
||||||
|
|
|
@ -41,15 +41,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
bech32:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: "cake-0.2.2"
|
|
||||||
resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192"
|
|
||||||
url: "https://github.com/cake-tech/bech32.git"
|
|
||||||
source: git
|
|
||||||
version: "0.2.2"
|
|
||||||
bip32:
|
bip32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -79,29 +70,20 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: cake-update-v3
|
ref: cake-update-v4
|
||||||
resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149
|
resolved-ref: "574486bfcdbbaf978dcd006b46fc8716f880da29"
|
||||||
url: "https://github.com/cake-tech/bitcoin_base"
|
url: "https://github.com/cake-tech/bitcoin_base"
|
||||||
source: git
|
source: git
|
||||||
version: "4.2.1"
|
version: "4.7.0"
|
||||||
bitcoin_flutter:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: cake-update-v4
|
|
||||||
resolved-ref: e19ffb7e7977278a75b27e0479b3c6f4034223b3
|
|
||||||
url: "https://github.com/cake-tech/bitcoin_flutter.git"
|
|
||||||
source: git
|
|
||||||
version: "2.1.0"
|
|
||||||
blockchain_utils:
|
blockchain_utils:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: cake-update-v1
|
ref: cake-update-v2
|
||||||
resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3
|
resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
|
||||||
url: "https://github.com/cake-tech/blockchain_utils"
|
url: "https://github.com/cake-tech/blockchain_utils"
|
||||||
source: git
|
source: git
|
||||||
version: "2.1.2"
|
version: "3.3.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -182,6 +164,15 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.9.2"
|
version: "8.9.2"
|
||||||
|
cake_backup:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: main
|
||||||
|
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
|
||||||
|
url: "https://github.com/cake-tech/cake_backup.git"
|
||||||
|
source: git
|
||||||
|
version: "1.0.0+1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -254,6 +245,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.0"
|
version: "2.7.0"
|
||||||
|
cupertino_icons:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cupertino_icons
|
||||||
|
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.8"
|
||||||
cw_core:
|
cw_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -411,10 +410,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.2"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -499,11 +498,12 @@ packages:
|
||||||
ledger_flutter:
|
ledger_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: ledger_flutter
|
path: "."
|
||||||
sha256: f1680060ed6ff78f275837e0024ccaf667715a59ba7aa29fa7354bc7752e71c8
|
ref: cake-v3
|
||||||
url: "https://pub.dev"
|
resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4"
|
||||||
source: hosted
|
url: "https://github.com/cake-tech/ledger-flutter.git"
|
||||||
version: "1.0.1"
|
source: git
|
||||||
|
version: "1.0.2"
|
||||||
ledger_usb:
|
ledger_usb:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -596,10 +596,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.4"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -636,18 +636,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.3.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.4"
|
version: "3.1.5"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -700,10 +700,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pubspec_parse
|
name: pubspec_parse
|
||||||
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
|
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.3"
|
version: "1.3.0"
|
||||||
quiver:
|
quiver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -761,10 +761,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: socks5_proxy
|
name: socks5_proxy
|
||||||
sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83"
|
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5+dev.2"
|
version: "1.0.6"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -793,9 +793,9 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "sp_v2.0.0"
|
ref: "sp_v4.0.0"
|
||||||
resolved-ref: "62c152b9086cd968019128845371072f7e1168de"
|
resolved-ref: "3b8ae38592c0584f53560071dc18bc570758fe13"
|
||||||
url: "https://github.com/cake-tech/sp_scanner"
|
url: "https://github.com/rafael-xmr/sp_scanner"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
|
@ -854,6 +854,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
tuple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: tuple
|
||||||
|
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -910,14 +918,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.5"
|
version: "2.4.5"
|
||||||
win32:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: win32
|
|
||||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.5.0"
|
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -19,10 +19,6 @@ dependencies:
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
cw_core:
|
cw_core:
|
||||||
path: ../cw_core
|
path: ../cw_core
|
||||||
bitcoin_flutter:
|
|
||||||
git:
|
|
||||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
|
||||||
ref: cake-update-v4
|
|
||||||
bitbox:
|
bitbox:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||||
|
@ -32,19 +28,19 @@ dependencies:
|
||||||
bitcoin_base:
|
bitcoin_base:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_base
|
url: https://github.com/cake-tech/bitcoin_base
|
||||||
ref: cake-update-v3
|
ref: cake-update-v4
|
||||||
blockchain_utils:
|
blockchain_utils:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/blockchain_utils
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
ref: cake-update-v1
|
ref: cake-update-v2
|
||||||
ledger_flutter: ^1.0.1
|
ledger_flutter: ^1.0.1
|
||||||
ledger_bitcoin:
|
ledger_bitcoin:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/ledger-bitcoin
|
url: https://github.com/cake-tech/ledger-bitcoin
|
||||||
sp_scanner:
|
sp_scanner:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/sp_scanner
|
url: https://github.com/rafael-xmr/sp_scanner
|
||||||
ref: sp_v2.0.0
|
ref: sp_v4.0.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
@ -56,6 +52,10 @@ dev_dependencies:
|
||||||
hive_generator: ^1.1.3
|
hive_generator: ^1.1.3
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
|
ledger_flutter:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/ledger-flutter.git
|
||||||
|
ref: cake-v3
|
||||||
watcher: ^1.1.0
|
watcher: ^1.1.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
@ -12,6 +11,7 @@ import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -29,6 +29,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required Uint8List seedBytes,
|
required Uint8List seedBytes,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
BitcoinAddressType? addressPageType,
|
BitcoinAddressType? addressPageType,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
|
@ -39,18 +40,19 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
networkType: bitcoin.bitcoin,
|
network: BitcoinCashNetwork.mainnet,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
currency: CryptoCurrency.bch) {
|
currency: CryptoCurrency.bch,
|
||||||
|
encryptionFileUtils: encryptionFileUtils) {
|
||||||
walletAddresses = BitcoinCashWalletAddresses(
|
walletAddresses = BitcoinCashWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
mainHd: hd,
|
mainHd: hd,
|
||||||
sideHd: accountHD.derive(1),
|
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||||
network: network,
|
network: network,
|
||||||
initialAddressPageType: addressPageType,
|
initialAddressPageType: addressPageType,
|
||||||
);
|
);
|
||||||
|
@ -64,6 +66,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
String? addressPageType,
|
String? addressPageType,
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
ElectrumBalance? initialBalance,
|
ElectrumBalance? initialBalance,
|
||||||
|
@ -76,7 +79,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialBalance: initialBalance,
|
initialBalance: initialBalance,
|
||||||
seedBytes: await Mnemonic.toSeed(mnemonic),
|
seedBytes: await MnemonicBip39.toSeed(mnemonic),
|
||||||
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
addressPageType: P2pkhAddressType.p2pkh,
|
addressPageType: P2pkhAddressType.p2pkh,
|
||||||
|
@ -88,15 +92,44 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
}) async {
|
}) async {
|
||||||
final snp = await ElectrumWalletSnapshot.load(
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
|
|
||||||
|
ElectrumWalletSnapshot? snp = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
snp = await ElectrumWalletSnapshot.load(
|
||||||
|
encryptionFileUtils,
|
||||||
|
name,
|
||||||
|
walletInfo.type,
|
||||||
|
password,
|
||||||
|
BitcoinCashNetwork.mainnet,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
keysData =
|
||||||
|
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(
|
||||||
|
name,
|
||||||
|
walletInfo.type,
|
||||||
|
password,
|
||||||
|
encryptionFileUtils,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return BitcoinCashWallet(
|
return BitcoinCashWallet(
|
||||||
mnemonic: snp.mnemonic!,
|
mnemonic: keysData.mnemonic!,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses.map((addr) {
|
initialAddresses: snp?.addresses.map((addr) {
|
||||||
try {
|
try {
|
||||||
BitcoinCashAddress(addr.address);
|
BitcoinCashAddress(addr.address);
|
||||||
return BitcoinAddressRecord(
|
return BitcoinAddressRecord(
|
||||||
|
@ -116,16 +149,19 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}).toList(),
|
}).toList(),
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp?.balance,
|
||||||
seedBytes: await Mnemonic.toSeed(snp.mnemonic!),
|
seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!),
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||||
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||||
addressPageType: P2pkhAddressType.p2pkh,
|
addressPageType: P2pkhAddressType.p2pkh,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) =>
|
bitbox.ECPair generateKeyPair({required Bip32Slip10Secp256k1 hd, required int index}) =>
|
||||||
bitbox.ECPair.fromWIF(hd.derive(index).wif!);
|
bitbox.ECPair.fromPrivateKey(
|
||||||
|
Uint8List.fromList(hd.childKey(Bip32KeyIndex(index)).privateKey.raw),
|
||||||
|
);
|
||||||
|
|
||||||
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
|
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
|
||||||
int inputsCount = 0;
|
int inputsCount = 0;
|
||||||
|
@ -171,7 +207,11 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
|
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
|
||||||
.index
|
.index
|
||||||
: null;
|
: null;
|
||||||
final HD = index == null ? hd : hd.derive(index);
|
final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
|
||||||
return base64Encode(HD.signMessage(message));
|
final priv = ECPrivate.fromWif(
|
||||||
|
WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
|
||||||
|
netVersion: network.wifNetVer,
|
||||||
|
);
|
||||||
|
return priv.signMessage(StringUtils.encode(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -23,6 +23,8 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress(
|
String getAddress(
|
||||||
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
{required int index,
|
||||||
|
required Bip32Slip10Secp256k1 hd,
|
||||||
|
BitcoinAddressType? addressType}) =>
|
||||||
generateP2PKHAddress(hd: hd, index: index, network: network);
|
generateP2PKHAddress(hd: hd, index: index, network: network);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ import 'package:cw_core/wallet_credentials.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
class BitcoinCashNewWalletCredentials extends WalletCredentials {
|
class BitcoinCashNewWalletCredentials extends WalletCredentials {
|
||||||
BitcoinCashNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
BitcoinCashNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password})
|
||||||
: super(name: name, walletInfo: walletInfo);
|
: super(name: name, walletInfo: walletInfo, password: password);
|
||||||
}
|
}
|
||||||
|
|
||||||
class BitcoinCashRestoreWalletFromSeedCredentials extends WalletCredentials {
|
class BitcoinCashRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart';
|
import 'package:bip39/bip39.dart';
|
||||||
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
|
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
@ -11,12 +12,16 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
|
class BitcoinCashWalletService extends WalletService<
|
||||||
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials, BitcoinCashNewWalletCredentials> {
|
BitcoinCashNewWalletCredentials,
|
||||||
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
BitcoinCashRestoreWalletFromSeedCredentials,
|
||||||
|
BitcoinCashRestoreWalletFromWIFCredentials,
|
||||||
|
BitcoinCashNewWalletCredentials> {
|
||||||
|
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
|
final bool isDirect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletType getType() => WalletType.bitcoinCash;
|
WalletType getType() => WalletType.bitcoinCash;
|
||||||
|
@ -30,12 +35,15 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||||
|
|
||||||
final wallet = await BitcoinCashWalletBase.create(
|
final wallet = await BitcoinCashWalletBase.create(
|
||||||
mnemonic: await Mnemonic.generate(strength: strength),
|
mnemonic: await MnemonicBip39.generate(strength: strength),
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +57,9 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
password: password,
|
password: password,
|
||||||
name: name,
|
name: name,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
saveBackup(name);
|
saveBackup(name);
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -59,7 +69,9 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
password: password,
|
password: password,
|
||||||
name: name,
|
name: name,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +93,8 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
password: password,
|
password: password,
|
||||||
name: currentName,
|
name: currentName,
|
||||||
walletInfo: currentWalletInfo,
|
walletInfo: currentWalletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
|
||||||
|
|
||||||
await currentWallet.renameWalletFiles(newName);
|
await currentWallet.renameWalletFiles(newName);
|
||||||
await saveBackup(newName);
|
await saveBackup(newName);
|
||||||
|
@ -95,7 +108,8 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinCashWallet> restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) {
|
Future<BitcoinCashWallet> restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) {
|
||||||
throw UnimplementedError("Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!");
|
throw UnimplementedError(
|
||||||
|
"Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -115,7 +129,8 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
mnemonic: credentials.mnemonic,
|
mnemonic: credentials.mnemonic,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
|
||||||
class Mnemonic {
|
class MnemonicBip39 {
|
||||||
/// Generate bip39 mnemonic
|
/// Generate bip39 mnemonic
|
||||||
static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength);
|
static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength);
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,6 @@ dependencies:
|
||||||
path: ../cw_core
|
path: ../cw_core
|
||||||
cw_bitcoin:
|
cw_bitcoin:
|
||||||
path: ../cw_bitcoin
|
path: ../cw_bitcoin
|
||||||
bitcoin_flutter:
|
|
||||||
git:
|
|
||||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
|
||||||
ref: cake-update-v4
|
|
||||||
bitbox:
|
bitbox:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||||
|
@ -32,11 +28,11 @@ dependencies:
|
||||||
bitcoin_base:
|
bitcoin_base:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_base
|
url: https://github.com/cake-tech/bitcoin_base
|
||||||
ref: cake-update-v3
|
ref: cake-update-v4
|
||||||
blockchain_utils:
|
blockchain_utils:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/blockchain_utils
|
url: https://github.com/cake-tech/blockchain_utils
|
||||||
ref: cake-update-v1
|
ref: cake-update-v2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
42
cw_core/lib/encryption_file_utils.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:cw_core/utils/file.dart' as file;
|
||||||
|
import 'package:cake_backup/backup.dart' as cwb;
|
||||||
|
|
||||||
|
EncryptionFileUtils encryptionFileUtilsFor(bool direct)
|
||||||
|
=> direct
|
||||||
|
? XChaCha20EncryptionFileUtils()
|
||||||
|
: Salsa20EncryhptionFileUtils();
|
||||||
|
|
||||||
|
abstract class EncryptionFileUtils {
|
||||||
|
Future<void> write({required String path, required String password, required String data});
|
||||||
|
Future<String> read({required String path, required String password});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Salsa20EncryhptionFileUtils extends EncryptionFileUtils {
|
||||||
|
// Requires legacy complex key + iv as password
|
||||||
|
@override
|
||||||
|
Future<void> write({required String path, required String password, required String data}) async
|
||||||
|
=> await file.write(path: path, password: password, data: data);
|
||||||
|
|
||||||
|
// Requires legacy complex key + iv as password
|
||||||
|
@override
|
||||||
|
Future<String> read({required String path, required String password}) async
|
||||||
|
=> await file.read(path: path, password: password);
|
||||||
|
}
|
||||||
|
|
||||||
|
class XChaCha20EncryptionFileUtils extends EncryptionFileUtils {
|
||||||
|
@override
|
||||||
|
Future<void> write({required String path, required String password, required String data}) async {
|
||||||
|
final encrypted = await cwb.encrypt(password, Uint8List.fromList(data.codeUnits));
|
||||||
|
await File(path).writeAsBytes(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> read({required String path, required String password}) async {
|
||||||
|
final file = File(path);
|
||||||
|
final encrypted = await file.readAsBytes();
|
||||||
|
final bytes = await cwb.decrypt(password, encrypted);
|
||||||
|
return String.fromCharCodes(bytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -245,6 +245,8 @@ Future<int> getHavenCurrentHeight() async {
|
||||||
|
|
||||||
// Data taken from https://timechaincalendar.com/
|
// Data taken from https://timechaincalendar.com/
|
||||||
const bitcoinDates = {
|
const bitcoinDates = {
|
||||||
|
"2024-08": 854889,
|
||||||
|
"2024-07": 850182,
|
||||||
"2024-06": 846005,
|
"2024-06": 846005,
|
||||||
"2024-05": 841590,
|
"2024-05": 841590,
|
||||||
"2024-04": 837182,
|
"2024-04": 837182,
|
||||||
|
@ -371,7 +373,8 @@ const wowDates = {
|
||||||
|
|
||||||
int getWowneroHeightByDate({required DateTime date}) {
|
int getWowneroHeightByDate({required DateTime date}) {
|
||||||
String closestKey =
|
String closestKey =
|
||||||
wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => '');
|
wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => '');
|
||||||
|
|
||||||
return wowDates[closestKey] ?? 0;
|
return wowDates[closestKey] ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ Future<bool> backupWalletFilesExists(String name) async {
|
||||||
backupAddressListFile.existsSync();
|
backupAddressListFile.existsSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WARNING: Transaction keys and your Polyseed CANNOT be recovered if this file is deleted
|
||||||
Future<void> removeCache(String name) async {
|
Future<void> removeCache(String name) async {
|
||||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||||
final cacheFile = File(path);
|
final cacheFile = File(path);
|
||||||
|
@ -92,8 +93,8 @@ Future<void> restoreOrResetWalletFiles(String name) async {
|
||||||
final backupsExists = await backupWalletFilesExists(name);
|
final backupsExists = await backupWalletFilesExists(name);
|
||||||
|
|
||||||
if (backupsExists) {
|
if (backupsExists) {
|
||||||
|
await removeCache(name);
|
||||||
|
|
||||||
await restoreWalletFiles(name);
|
await restoreWalletFiles(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCache(name);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ import 'package:http/io_client.dart' as ioc;
|
||||||
|
|
||||||
part 'node.g.dart';
|
part 'node.g.dart';
|
||||||
|
|
||||||
Uri createUriFromElectrumAddress(String address, String path) => Uri.tryParse('tcp://$address$path')!;
|
Uri createUriFromElectrumAddress(String address, String path) =>
|
||||||
|
Uri.tryParse('tcp://$address$path')!;
|
||||||
|
|
||||||
@HiveType(typeId: Node.typeId)
|
@HiveType(typeId: Node.typeId)
|
||||||
class Node extends HiveObject with Keyable {
|
class Node extends HiveObject with Keyable {
|
||||||
|
@ -72,6 +73,12 @@ class Node extends HiveObject with Keyable {
|
||||||
@HiveField(7, defaultValue: '')
|
@HiveField(7, defaultValue: '')
|
||||||
String? path;
|
String? path;
|
||||||
|
|
||||||
|
@HiveField(8)
|
||||||
|
bool? isElectrs;
|
||||||
|
|
||||||
|
@HiveField(9)
|
||||||
|
bool? supportsSilentPayments;
|
||||||
|
|
||||||
bool get isSSL => useSSL ?? false;
|
bool get isSSL => useSSL ?? false;
|
||||||
|
|
||||||
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
|
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
|
||||||
|
|
|
@ -3,6 +3,11 @@ abstract class SyncStatus {
|
||||||
double progress();
|
double progress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StartingScanSyncStatus extends SyncStatus {
|
||||||
|
@override
|
||||||
|
double progress() => 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
class SyncingSyncStatus extends SyncStatus {
|
class SyncingSyncStatus extends SyncStatus {
|
||||||
SyncingSyncStatus(this.blocksLeft, this.ptc);
|
SyncingSyncStatus(this.blocksLeft, this.ptc);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ abstract class TransactionInfo extends Object with Keyable {
|
||||||
late TransactionDirection direction;
|
late TransactionDirection direction;
|
||||||
late bool isPending;
|
late bool isPending;
|
||||||
late DateTime date;
|
late DateTime date;
|
||||||
late int height;
|
int? height;
|
||||||
late int confirmations;
|
late int confirmations;
|
||||||
String amountFormatted();
|
String amountFormatted();
|
||||||
String fiatAmount();
|
String fiatAmount();
|
||||||
|
@ -25,4 +25,5 @@ abstract class TransactionInfo extends Object with Keyable {
|
||||||
dynamic get keyIndex => id;
|
dynamic get keyIndex => id;
|
||||||
|
|
||||||
late Map<String, dynamic> additionalInfo;
|
late Map<String, dynamic> additionalInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
||||||
|
|
||||||
Future<void> changePassword(String password);
|
Future<void> changePassword(String password);
|
||||||
|
|
||||||
|
String get password;
|
||||||
|
|
||||||
Future<void>? updateBalance();
|
Future<void>? updateBalance();
|
||||||
|
|
||||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => null;
|
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => null;
|
||||||
|
|
119
cw_core/lib/wallet_keys_file.dart
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer' as dev;
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cw_core/balance.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
|
import 'package:cw_core/pathForWallet.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';
|
||||||
|
|
||||||
|
mixin WalletKeysFile<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
|
||||||
|
TransactionType extends TransactionInfo>
|
||||||
|
on WalletBase<BalanceType, HistoryType, TransactionType> {
|
||||||
|
Future<String> makePath() => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
|
||||||
|
// this needs to be overridden
|
||||||
|
WalletKeysData get walletKeysData;
|
||||||
|
|
||||||
|
Future<String> makeKeysFilePath() async => "${await makePath()}.keys";
|
||||||
|
|
||||||
|
Future<void> saveKeysFile(String password, EncryptionFileUtils encryptionFileUtils,
|
||||||
|
[bool isBackup = false]) async {
|
||||||
|
try {
|
||||||
|
final rootPath = await makeKeysFilePath();
|
||||||
|
final path = "$rootPath${isBackup ? ".backup" : ""}";
|
||||||
|
dev.log("Saving .keys file '$path'");
|
||||||
|
await encryptionFileUtils.write(
|
||||||
|
path: path, password: password, data: walletKeysData.toJSON());
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> createKeysFile(String name, WalletType type, String password,
|
||||||
|
WalletKeysData walletKeysData, EncryptionFileUtils encryptionFileUtils,
|
||||||
|
[bool withBackup = true]) async {
|
||||||
|
try {
|
||||||
|
final rootPath = await pathForWallet(name: name, type: type);
|
||||||
|
final path = "$rootPath.keys";
|
||||||
|
|
||||||
|
dev.log("Saving .keys file '$path'");
|
||||||
|
await encryptionFileUtils.write(
|
||||||
|
path: path, password: password, data: walletKeysData.toJSON());
|
||||||
|
|
||||||
|
if (withBackup) {
|
||||||
|
dev.log("Saving .keys.backup file '$path.backup'");
|
||||||
|
await encryptionFileUtils.write(
|
||||||
|
path: "$path.backup", password: password, data: walletKeysData.toJSON());
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> hasKeysFile(String name, WalletType type) async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: name, type: type);
|
||||||
|
return File("$path.keys").existsSync() || File("$path.keys.backup").existsSync();
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<WalletKeysData> readKeysFile(
|
||||||
|
String name,
|
||||||
|
WalletType type,
|
||||||
|
String password,
|
||||||
|
EncryptionFileUtils encryptionFileUtils,
|
||||||
|
) async {
|
||||||
|
final path = await pathForWallet(name: name, type: type);
|
||||||
|
|
||||||
|
var readPath = "$path.keys";
|
||||||
|
try {
|
||||||
|
if (!File(readPath).existsSync()) throw Exception("No .keys file found for $name $type");
|
||||||
|
|
||||||
|
final jsonSource = await encryptionFileUtils.read(path: readPath, password: password);
|
||||||
|
final data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
|
return WalletKeysData.fromJSON(data);
|
||||||
|
} catch (e) {
|
||||||
|
dev.log("Failed to read .keys file. Trying .keys.backup file...");
|
||||||
|
|
||||||
|
readPath = "$readPath.backup";
|
||||||
|
if (!File(readPath).existsSync())
|
||||||
|
throw Exception("No .keys nor a .keys.backup file found for $name $type");
|
||||||
|
|
||||||
|
final jsonSource = await encryptionFileUtils.read(path: readPath, password: password);
|
||||||
|
final data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
|
final keysData = WalletKeysData.fromJSON(data);
|
||||||
|
|
||||||
|
dev.log("Restoring .keys from .keys.backup");
|
||||||
|
createKeysFile(name, type, password, keysData, encryptionFileUtils, false);
|
||||||
|
return keysData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WalletKeysData {
|
||||||
|
final String? privateKey;
|
||||||
|
final String? mnemonic;
|
||||||
|
final String? altMnemonic;
|
||||||
|
final String? passphrase;
|
||||||
|
final String? xPub;
|
||||||
|
|
||||||
|
WalletKeysData({this.privateKey, this.mnemonic, this.altMnemonic, this.passphrase, this.xPub});
|
||||||
|
|
||||||
|
String toJSON() => jsonEncode({
|
||||||
|
"privateKey": privateKey,
|
||||||
|
"mnemonic": mnemonic,
|
||||||
|
if (altMnemonic != null) "altMnemonic": altMnemonic,
|
||||||
|
if (passphrase != null) "passphrase": passphrase,
|
||||||
|
if (xPub != null) "xPub": xPub
|
||||||
|
});
|
||||||
|
|
||||||
|
static WalletKeysData fromJSON(Map<String, dynamic> json) => WalletKeysData(
|
||||||
|
privateKey: json["privateKey"] as String?,
|
||||||
|
mnemonic: json["mnemonic"] as String?,
|
||||||
|
altMnemonic: json["altMnemonic"] as String?,
|
||||||
|
passphrase: json["passphrase"] as String?,
|
||||||
|
xPub: json["xPub"] as String?,
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/utils/file.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_credentials.dart';
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
@ -42,4 +44,21 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
|
||||||
await File(walletDirPath).copy(backupWalletDirPath);
|
await File(walletDirPath).copy(backupWalletDirPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getSeeds(String name, String password, WalletType type) async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: name, type: type);
|
||||||
|
final jsonSource = await read(path: path, password: password);
|
||||||
|
try {
|
||||||
|
final data = json.decode(jsonSource) as Map;
|
||||||
|
return data['mnemonic'] as String? ?? '';
|
||||||
|
} catch (_) {
|
||||||
|
// if not a valid json
|
||||||
|
return jsonSource.substring(0, 200);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// if the file couldn't be opened or read
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,15 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.8.1"
|
version: "8.8.1"
|
||||||
|
cake_backup:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: main
|
||||||
|
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
|
||||||
|
url: "https://github.com/cake-tech/cake_backup.git"
|
||||||
|
source: git
|
||||||
|
version: "1.0.0+1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -169,6 +178,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
cryptography:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cryptography
|
||||||
|
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.0"
|
||||||
|
cupertino_icons:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cupertino_icons
|
||||||
|
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.6"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -648,6 +673,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
tuple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: tuple
|
||||||
|
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -19,6 +19,11 @@ dependencies:
|
||||||
flutter_mobx: ^2.0.6+1
|
flutter_mobx: ^2.0.6+1
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
encrypt: ^5.0.1
|
encrypt: ^5.0.1
|
||||||
|
cake_backup:
|
||||||
|
git:
|
||||||
|
url: https://github.com/cake-tech/cake_backup.git
|
||||||
|
ref: main
|
||||||
|
version: 1.0.0
|
||||||
socks5_proxy: ^1.0.4
|
socks5_proxy: ^1.0.4
|
||||||
unorm_dart: ^0.3.0
|
unorm_dart: ^0.3.0
|
||||||
# tor:
|
# tor:
|
||||||
|
|
|
@ -7,6 +7,7 @@ class EthereumTransactionHistory extends EVMChainTransactionHistory {
|
||||||
EthereumTransactionHistory({
|
EthereumTransactionHistory({
|
||||||
required super.walletInfo,
|
required super.walletInfo,
|
||||||
required super.password,
|
required super.password,
|
||||||
|
required super.encryptionFileUtils,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -2,10 +2,12 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cw_core/cake_hive.dart';
|
import 'package:cw_core/cake_hive.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/erc20_token.dart';
|
import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
|
import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
|
||||||
import 'package:cw_ethereum/ethereum_client.dart';
|
import 'package:cw_ethereum/ethereum_client.dart';
|
||||||
import 'package:cw_ethereum/ethereum_transaction_history.dart';
|
import 'package:cw_ethereum/ethereum_transaction_history.dart';
|
||||||
|
@ -15,7 +17,6 @@ import 'package:cw_evm/evm_chain_transaction_info.dart';
|
||||||
import 'package:cw_evm/evm_chain_transaction_model.dart';
|
import 'package:cw_evm/evm_chain_transaction_model.dart';
|
||||||
import 'package:cw_evm/evm_chain_wallet.dart';
|
import 'package:cw_evm/evm_chain_wallet.dart';
|
||||||
import 'package:cw_evm/evm_erc20_balance.dart';
|
import 'package:cw_evm/evm_erc20_balance.dart';
|
||||||
import 'package:cw_evm/file.dart';
|
|
||||||
|
|
||||||
class EthereumWallet extends EVMChainWallet {
|
class EthereumWallet extends EVMChainWallet {
|
||||||
EthereumWallet({
|
EthereumWallet({
|
||||||
|
@ -25,6 +26,7 @@ class EthereumWallet extends EVMChainWallet {
|
||||||
super.mnemonic,
|
super.mnemonic,
|
||||||
super.initialBalance,
|
super.initialBalance,
|
||||||
super.privateKey,
|
super.privateKey,
|
||||||
|
required super.encryptionFileUtils,
|
||||||
}) : super(nativeCurrency: CryptoCurrency.eth);
|
}) : super(nativeCurrency: CryptoCurrency.eth);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -116,27 +118,57 @@ class EthereumWallet extends EVMChainWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password) {
|
EVMChainTransactionHistory setUpTransactionHistory(
|
||||||
return EthereumTransactionHistory(walletInfo: walletInfo, password: password);
|
WalletInfo walletInfo, String password, EncryptionFileUtils encryptionFileUtils) {
|
||||||
|
return EthereumTransactionHistory(
|
||||||
|
walletInfo: walletInfo, password: password, encryptionFileUtils: encryptionFileUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<EthereumWallet> open(
|
static Future<EthereumWallet> open({
|
||||||
{required String name, required String password, required WalletInfo walletInfo}) async {
|
required String name,
|
||||||
|
required String password,
|
||||||
|
required WalletInfo walletInfo,
|
||||||
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
|
}) async {
|
||||||
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
final jsonSource = await read(path: path, password: password);
|
|
||||||
final data = json.decode(jsonSource) as Map;
|
Map<String, dynamic>? data;
|
||||||
final mnemonic = data['mnemonic'] as String?;
|
try {
|
||||||
final privateKey = data['private_key'] as String?;
|
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||||
final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
|
|
||||||
|
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ??
|
||||||
EVMChainERC20Balance(BigInt.zero);
|
EVMChainERC20Balance(BigInt.zero);
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
final mnemonic = data!['mnemonic'] as String?;
|
||||||
|
final privateKey = data['private_key'] as String?;
|
||||||
|
|
||||||
|
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(
|
||||||
|
name,
|
||||||
|
walletInfo.type,
|
||||||
|
password,
|
||||||
|
encryptionFileUtils,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return EthereumWallet(
|
return EthereumWallet(
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
password: password,
|
password: password,
|
||||||
mnemonic: mnemonic,
|
mnemonic: keysData.mnemonic,
|
||||||
privateKey: privateKey,
|
privateKey: keysData.privateKey,
|
||||||
initialBalance: balance,
|
initialBalance: balance,
|
||||||
client: EthereumClient(),
|
client: EthereumClient(),
|
||||||
|
encryptionFileUtils: encryptionFileUtils,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
@ -9,7 +10,7 @@ import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
|
||||||
import 'package:cw_evm/evm_chain_wallet_service.dart';
|
import 'package:cw_evm/evm_chain_wallet_service.dart';
|
||||||
|
|
||||||
class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
EthereumWalletService(super.walletInfoSource, {required this.client});
|
EthereumWalletService(super.walletInfoSource, super.isDirect, {required this.client});
|
||||||
|
|
||||||
late EthereumClient client;
|
late EthereumClient client;
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
client: client,
|
client: client,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -46,6 +48,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
name: name,
|
name: name,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -59,6 +62,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
name: name,
|
name: name,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
|
@ -71,7 +75,11 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
final currentWalletInfo = walletInfoSource.values
|
final currentWalletInfo = walletInfoSource.values
|
||||||
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||||
final currentWallet = await EthereumWallet.open(
|
final currentWallet = await EthereumWallet.open(
|
||||||
password: password, name: currentName, walletInfo: currentWalletInfo);
|
password: password,
|
||||||
|
name: currentName,
|
||||||
|
walletInfo: currentWalletInfo,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
);
|
||||||
|
|
||||||
await currentWallet.renameWalletFiles(newName);
|
await currentWallet.renameWalletFiles(newName);
|
||||||
await saveBackup(newName);
|
await saveBackup(newName);
|
||||||
|
@ -97,6 +105,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
client: client,
|
client: client,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -114,6 +123,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
privateKey: credentials.privateKey,
|
privateKey: credentials.privateKey,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
client: client,
|
client: client,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -135,6 +145,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
||||||
mnemonic: credentials.mnemonic,
|
mnemonic: credentials.mnemonic,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
client: client,
|
client: client,
|
||||||
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_evm/evm_chain_transaction_info.dart';
|
import 'package:cw_evm/evm_chain_transaction_info.dart';
|
||||||
import 'package:cw_evm/file.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_core/transaction_history.dart';
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ abstract class EVMChainTransactionHistory = EVMChainTransactionHistoryBase
|
||||||
|
|
||||||
abstract class EVMChainTransactionHistoryBase
|
abstract class EVMChainTransactionHistoryBase
|
||||||
extends TransactionHistoryBase<EVMChainTransactionInfo> with Store {
|
extends TransactionHistoryBase<EVMChainTransactionInfo> with Store {
|
||||||
EVMChainTransactionHistoryBase({required this.walletInfo, required String password})
|
EVMChainTransactionHistoryBase(
|
||||||
|
{required this.walletInfo, required String password, required this.encryptionFileUtils})
|
||||||
: _password = password {
|
: _password = password {
|
||||||
transactions = ObservableMap<String, EVMChainTransactionInfo>();
|
transactions = ObservableMap<String, EVMChainTransactionInfo>();
|
||||||
}
|
}
|
||||||
|
@ -23,6 +24,7 @@ abstract class EVMChainTransactionHistoryBase
|
||||||
String _password;
|
String _password;
|
||||||
|
|
||||||
final WalletInfo walletInfo;
|
final WalletInfo walletInfo;
|
||||||
|
final EncryptionFileUtils encryptionFileUtils;
|
||||||
|
|
||||||
//! Method to be overridden by all child classes
|
//! Method to be overridden by all child classes
|
||||||
|
|
||||||
|
@ -41,7 +43,7 @@ abstract class EVMChainTransactionHistoryBase
|
||||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||||
final data = json.encode({'transactions': transactions});
|
final data = json.encode({'transactions': transactions});
|
||||||
await writeData(path: path, password: _password, data: data);
|
await encryptionFileUtils.write(path: path, password: _password, data: data);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
|
log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
|
||||||
log(s.toString());
|
log(s.toString());
|
||||||
|
@ -59,7 +61,7 @@ abstract class EVMChainTransactionHistoryBase
|
||||||
final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName();
|
final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName();
|
||||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||||
final content = await read(path: path, password: _password);
|
final content = await encryptionFileUtils.read(path: path, password: _password);
|
||||||
if (content.isEmpty) {
|
if (content.isEmpty) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:bip32/bip32.dart' as bip32;
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:cw_core/cake_hive.dart';
|
import 'package:cw_core/cake_hive.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/erc20_token.dart';
|
import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:cw_core/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
@ -16,6 +17,7 @@ import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cw_evm/evm_chain_client.dart';
|
import 'package:cw_evm/evm_chain_client.dart';
|
||||||
import 'package:cw_evm/evm_chain_exceptions.dart';
|
import 'package:cw_evm/evm_chain_exceptions.dart';
|
||||||
|
@ -26,7 +28,6 @@ import 'package:cw_evm/evm_chain_transaction_model.dart';
|
||||||
import 'package:cw_evm/evm_chain_transaction_priority.dart';
|
import 'package:cw_evm/evm_chain_transaction_priority.dart';
|
||||||
import 'package:cw_evm/evm_chain_wallet_addresses.dart';
|
import 'package:cw_evm/evm_chain_wallet_addresses.dart';
|
||||||
import 'package:cw_evm/evm_ledger_credentials.dart';
|
import 'package:cw_evm/evm_ledger_credentials.dart';
|
||||||
import 'package:cw_evm/file.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hex/hex.dart';
|
import 'package:hex/hex.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
@ -58,7 +59,7 @@ abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
|
||||||
|
|
||||||
abstract class EVMChainWalletBase
|
abstract class EVMChainWalletBase
|
||||||
extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
|
extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
|
||||||
with Store {
|
with Store, WalletKeysFile {
|
||||||
EVMChainWalletBase({
|
EVMChainWalletBase({
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required EVMChainClient client,
|
required EVMChainClient client,
|
||||||
|
@ -67,6 +68,7 @@ abstract class EVMChainWalletBase
|
||||||
String? privateKey,
|
String? privateKey,
|
||||||
required String password,
|
required String password,
|
||||||
EVMChainERC20Balance? initialBalance,
|
EVMChainERC20Balance? initialBalance,
|
||||||
|
required this.encryptionFileUtils,
|
||||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||||
_password = password,
|
_password = password,
|
||||||
_mnemonic = mnemonic,
|
_mnemonic = mnemonic,
|
||||||
|
@ -82,7 +84,7 @@ abstract class EVMChainWalletBase
|
||||||
),
|
),
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
this.walletInfo = walletInfo;
|
this.walletInfo = walletInfo;
|
||||||
transactionHistory = setUpTransactionHistory(walletInfo, password);
|
transactionHistory = setUpTransactionHistory(walletInfo, password, encryptionFileUtils);
|
||||||
|
|
||||||
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
|
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
|
||||||
CakeHive.registerAdapter(Erc20TokenAdapter());
|
CakeHive.registerAdapter(Erc20TokenAdapter());
|
||||||
|
@ -94,6 +96,7 @@ abstract class EVMChainWalletBase
|
||||||
final String? _mnemonic;
|
final String? _mnemonic;
|
||||||
final String? _hexPrivateKey;
|
final String? _hexPrivateKey;
|
||||||
final String _password;
|
final String _password;
|
||||||
|
final EncryptionFileUtils encryptionFileUtils;
|
||||||
|
|
||||||
late final Box<Erc20Token> erc20TokensBox;
|
late final Box<Erc20Token> erc20TokensBox;
|
||||||
|
|
||||||
|
@ -148,7 +151,11 @@ abstract class EVMChainWalletBase
|
||||||
|
|
||||||
Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath);
|
Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath);
|
||||||
|
|
||||||
EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password);
|
EVMChainTransactionHistory setUpTransactionHistory(
|
||||||
|
WalletInfo walletInfo,
|
||||||
|
String password,
|
||||||
|
EncryptionFileUtils encryptionFileUtils,
|
||||||
|
);
|
||||||
|
|
||||||
//! Common Methods across child classes
|
//! Common Methods across child classes
|
||||||
|
|
||||||
|
@ -508,9 +515,14 @@ abstract class EVMChainWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||||
|
await saveKeysFile(_password, encryptionFileUtils);
|
||||||
|
saveKeysFile(_password, encryptionFileUtils, true);
|
||||||
|
}
|
||||||
|
|
||||||
await walletAddresses.updateAddressesInBox();
|
await walletAddresses.updateAddressesInBox();
|
||||||
final path = await makePath();
|
final path = await makePath();
|
||||||
await write(path: path, password: _password, data: toJSON());
|
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
|
||||||
await transactionHistory.save();
|
await transactionHistory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,7 +534,8 @@ abstract class EVMChainWalletBase
|
||||||
? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey)
|
? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
@override
|
||||||
|
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'mnemonic': _mnemonic,
|
'mnemonic': _mnemonic,
|
||||||
|
@ -683,4 +696,7 @@ abstract class EVMChainWalletBase
|
||||||
bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message)));
|
bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message)));
|
||||||
|
|
||||||
Web3Client? getWeb3Client() => _client.getWeb3Client();
|
Web3Client? getWeb3Client() => _client.getWeb3Client();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get password => _password;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'package:cw_core/wallet_credentials.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
class EVMChainNewWalletCredentials extends WalletCredentials {
|
class EVMChainNewWalletCredentials extends WalletCredentials {
|
||||||
EVMChainNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
EVMChainNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password})
|
||||||
: super(name: name, walletInfo: walletInfo);
|
: super(name: name, walletInfo: walletInfo, password: password);
|
||||||
}
|
}
|
||||||
|
|
||||||
class EVMChainRestoreWalletFromSeedCredentials extends WalletCredentials {
|
class EVMChainRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
|
|
|
@ -15,9 +15,10 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
|
||||||
EVMChainRestoreWalletFromSeedCredentials,
|
EVMChainRestoreWalletFromSeedCredentials,
|
||||||
EVMChainRestoreWalletFromPrivateKey,
|
EVMChainRestoreWalletFromPrivateKey,
|
||||||
EVMChainRestoreWalletFromHardware> {
|
EVMChainRestoreWalletFromHardware> {
|
||||||
EVMChainWalletService(this.walletInfoSource);
|
EVMChainWalletService(this.walletInfoSource, this.isDirect);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
final bool isDirect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletType getType();
|
WalletType getType();
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:cw_core/key.dart';
|
|
||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
|
||||||
|
|
||||||
Future<void> write(
|
|
||||||
{required String path,
|
|
||||||
required String password,
|
|
||||||
required String data}) async {
|
|
||||||
final keys = extractKeys(password);
|
|
||||||
final key = encrypt.Key.fromBase64(keys.first);
|
|
||||||
final iv = encrypt.IV.fromBase64(keys.last);
|
|
||||||
final encrypted = await encode(key: key, iv: iv, data: data);
|
|
||||||
final f = File(path);
|
|
||||||
f.writeAsStringSync(encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> writeData(
|
|
||||||
{required String path,
|
|
||||||
required String password,
|
|
||||||
required String data}) async {
|
|
||||||
final keys = extractKeys(password);
|
|
||||||
final key = encrypt.Key.fromBase64(keys.first);
|
|
||||||
final iv = encrypt.IV.fromBase64(keys.last);
|
|
||||||
final encrypted = await encode(key: key, iv: iv, data: data);
|
|
||||||
final f = File(path);
|
|
||||||
f.writeAsStringSync(encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> read({required String path, required String password}) async {
|
|
||||||
final file = File(path);
|
|
||||||
|
|
||||||
if (!file.existsSync()) {
|
|
||||||
file.createSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
final encrypted = file.readAsStringSync();
|
|
||||||
|
|
||||||
return decode(password: password, data: encrypted);
|
|
||||||
}
|
|
|
@ -20,6 +20,7 @@ dependencies:
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
collection: ^1.17.1
|
collection: ^1.17.1
|
||||||
shared_preferences: ^2.0.15
|
shared_preferences: ^2.0.15
|
||||||
|
mobx: ^2.0.7+4
|
||||||
cw_core:
|
cw_core:
|
||||||
path: ../cw_core
|
path: ../cw_core
|
||||||
ledger_flutter: ^1.0.1
|
ledger_flutter: ^1.0.1
|
||||||
|
@ -35,7 +36,7 @@ dependency_overrides:
|
||||||
ledger_flutter:
|
ledger_flutter:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/ledger-flutter.git
|
url: https://github.com/cake-tech/ledger-flutter.git
|
||||||
ref: cake
|
ref: cake-v3
|
||||||
watcher: ^1.1.0
|
watcher: ^1.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
|
@ -5,7 +5,7 @@ buildscript {
|
||||||
ext.kotlin_version = '1.7.10'
|
ext.kotlin_version = '1.7.10'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -17,7 +17,7 @@ buildscript {
|
||||||
rootProject.allprojects {
|
rootProject.allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,9 +38,10 @@ class HavenWallet = HavenWalletBase with _$HavenWallet;
|
||||||
|
|
||||||
abstract class HavenWalletBase
|
abstract class HavenWalletBase
|
||||||
extends WalletBase<MoneroBalance, HavenTransactionHistory, HavenTransactionInfo> with Store {
|
extends WalletBase<MoneroBalance, HavenTransactionHistory, HavenTransactionInfo> with Store {
|
||||||
HavenWalletBase({required WalletInfo walletInfo})
|
HavenWalletBase({required WalletInfo walletInfo, String? password})
|
||||||
: balance = ObservableMap.of(getHavenBalance(accountIndex: 0)),
|
: balance = ObservableMap.of(getHavenBalance(accountIndex: 0)),
|
||||||
_isTransactionUpdating = false,
|
_isTransactionUpdating = false,
|
||||||
|
_password = password ?? '',
|
||||||
_hasSyncAfterStartup = false,
|
_hasSyncAfterStartup = false,
|
||||||
walletAddresses = HavenWalletAddresses(walletInfo),
|
walletAddresses = HavenWalletAddresses(walletInfo),
|
||||||
syncStatus = NotConnectedSyncStatus(),
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
|
@ -56,6 +57,7 @@ abstract class HavenWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int _autoSaveInterval = 30;
|
static const int _autoSaveInterval = 30;
|
||||||
|
final String _password;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
HavenWalletAddresses walletAddresses;
|
HavenWalletAddresses walletAddresses;
|
||||||
|
@ -111,7 +113,7 @@ abstract class HavenWalletBase
|
||||||
_onAccountChangeReaction?.reaction.dispose();
|
_onAccountChangeReaction?.reaction.dispose();
|
||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> connectToNode({required Node node}) async {
|
Future<void> connectToNode({required Node node}) async {
|
||||||
try {
|
try {
|
||||||
|
@ -414,4 +416,7 @@ abstract class HavenWalletBase
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get password => _password;
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,15 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.4.3"
|
version: "8.4.3"
|
||||||
|
cake_backup:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: main
|
||||||
|
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
|
||||||
|
url: "https://github.com/cake-tech/cake_backup.git"
|
||||||
|
source: git
|
||||||
|
version: "1.0.0+1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -169,6 +178,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
|
cryptography:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cryptography
|
||||||
|
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.0"
|
||||||
|
cupertino_icons:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cupertino_icons
|
||||||
|
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.6"
|
||||||
cw_core:
|
cw_core:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -254,10 +279,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: glob
|
name: glob
|
||||||
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -514,14 +539,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
process:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: process
|
|
||||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.2.4"
|
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -647,6 +664,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
tuple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: tuple
|
||||||
|
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -707,10 +732,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0+3"
|
version: "1.0.4"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -18,6 +18,9 @@ migration:
|
||||||
- platform: macos
|
- platform: macos
|
||||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
|
- platform: linux
|
||||||
|
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
|
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|
1
cw_monero/example/linux/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
flutter/ephemeral
|
138
cw_monero/example/linux/CMakeLists.txt
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
# Project-level configuration.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
# The name of the executable created for the application. Change this to change
|
||||||
|
# the on-disk name of your application.
|
||||||
|
set(BINARY_NAME "cw_monero_example")
|
||||||
|
# The unique GTK application identifier for this application. See:
|
||||||
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
|
set(APPLICATION_ID "com.cakewallet.cw_monero")
|
||||||
|
|
||||||
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
|
# versions of CMake.
|
||||||
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||||
|
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||||
|
|
||||||
|
# Root filesystem for cross-building.
|
||||||
|
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||||
|
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Define build configuration options.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||||
|
STRING "Flutter build mode" FORCE)
|
||||||
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||||
|
"Debug" "Profile" "Release")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Compilation settings that should be applied to most targets.
|
||||||
|
#
|
||||||
|
# Be cautious about adding new options here, as plugins use this function by
|
||||||
|
# default. In most cases, you should add new options to specific targets instead
|
||||||
|
# of modifying this function.
|
||||||
|
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||||
|
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||||
|
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||||
|
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||||
|
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Flutter library and tool build rules.
|
||||||
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
|
||||||
|
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||||
|
|
||||||
|
# Define the application target. To change its name, change BINARY_NAME above,
|
||||||
|
# not the value here, or `flutter run` will no longer work.
|
||||||
|
#
|
||||||
|
# Any new source files that you add to the application should be added here.
|
||||||
|
add_executable(${BINARY_NAME}
|
||||||
|
"main.cc"
|
||||||
|
"my_application.cc"
|
||||||
|
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply the standard set of build settings. This can be removed for applications
|
||||||
|
# that need different build settings.
|
||||||
|
apply_standard_settings(${BINARY_NAME})
|
||||||
|
|
||||||
|
# Add dependency libraries. Add any application-specific dependencies here.
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||||
|
|
||||||
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
|
|
||||||
|
# Only the install-generated bundle's copy of the executable will launch
|
||||||
|
# correctly, since the resources must in the right relative locations. To avoid
|
||||||
|
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||||
|
# the default top-level location.
|
||||||
|
set_target_properties(${BINARY_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generated plugin build rules, which manage building the plugins and adding
|
||||||
|
# them to the application.
|
||||||
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
|
# === Installation ===
|
||||||
|
# By default, "installing" just makes a relocatable bundle in the build
|
||||||
|
# directory.
|
||||||
|
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||||
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Start with a clean build bundle directory every time.
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
|
||||||
|
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||||
|
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||||
|
|
||||||
|
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||||
|
install(FILES "${bundled_library}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endforeach(bundled_library)
|
||||||
|
|
||||||
|
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||||
|
# from a previous install.
|
||||||
|
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||||
|
|
||||||
|
# Install the AOT library on non-Debug builds only.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||||
|
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endif()
|
88
cw_monero/example/linux/flutter/CMakeLists.txt
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# This file controls Flutter-level build steps. It should not be edited.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||||
|
|
||||||
|
# Configuration provided via flutter tool.
|
||||||
|
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||||
|
|
||||||
|
# TODO: Move the rest of this into files in ephemeral. See
|
||||||
|
# https://github.com/flutter/flutter/issues/57146.
|
||||||
|
|
||||||
|
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||||
|
# which isn't available in 3.10.
|
||||||
|
function(list_prepend LIST_NAME PREFIX)
|
||||||
|
set(NEW_LIST "")
|
||||||
|
foreach(element ${${LIST_NAME}})
|
||||||
|
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||||
|
endforeach(element)
|
||||||
|
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# === Flutter Library ===
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||||
|
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||||
|
|
||||||
|
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||||
|
|
||||||
|
# Published to parent scope for install step.
|
||||||
|
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||||
|
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||||
|
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||||
|
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||||
|
"fl_basic_message_channel.h"
|
||||||
|
"fl_binary_codec.h"
|
||||||
|
"fl_binary_messenger.h"
|
||||||
|
"fl_dart_project.h"
|
||||||
|
"fl_engine.h"
|
||||||
|
"fl_json_message_codec.h"
|
||||||
|
"fl_json_method_codec.h"
|
||||||
|
"fl_message_codec.h"
|
||||||
|
"fl_method_call.h"
|
||||||
|
"fl_method_channel.h"
|
||||||
|
"fl_method_codec.h"
|
||||||
|
"fl_method_response.h"
|
||||||
|
"fl_plugin_registrar.h"
|
||||||
|
"fl_plugin_registry.h"
|
||||||
|
"fl_standard_message_codec.h"
|
||||||
|
"fl_standard_method_codec.h"
|
||||||
|
"fl_string_codec.h"
|
||||||
|
"fl_value.h"
|
||||||
|
"fl_view.h"
|
||||||
|
"flutter_linux.h"
|
||||||
|
)
|
||||||
|
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||||
|
add_library(flutter INTERFACE)
|
||||||
|
target_include_directories(flutter INTERFACE
|
||||||
|
"${EPHEMERAL_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||||
|
target_link_libraries(flutter INTERFACE
|
||||||
|
PkgConfig::GTK
|
||||||
|
PkgConfig::GLIB
|
||||||
|
PkgConfig::GIO
|
||||||
|
)
|
||||||
|
add_dependencies(flutter flutter_assemble)
|
||||||
|
|
||||||
|
# === Flutter tool backend ===
|
||||||
|
# _phony_ is a non-existent file to force this command to run every time,
|
||||||
|
# since currently there's no way to get a full input/output list from the
|
||||||
|
# flutter tool.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
${FLUTTER_TOOL_ENVIRONMENT}
|
||||||
|
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||||
|
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(flutter_assemble DEPENDS
|
||||||
|
"${FLUTTER_LIBRARY}"
|
||||||
|
${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <cw_monero/cw_monero_plugin.h>
|
||||||
|
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) cw_monero_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "CwMoneroPlugin");
|
||||||
|
cw_monero_plugin_register_with_registrar(cw_monero_registrar);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
//
|
||||||
|
// 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_
|
24
cw_monero/example/linux/flutter/generated_plugins.cmake
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#
|
||||||
|
# Generated file, do not edit.
|
||||||
|
#
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
cw_monero
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
6
cw_monero/example/linux/main.cc
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
g_autoptr(MyApplication) app = my_application_new();
|
||||||
|
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||||
|
}
|
104
cw_monero/example/linux/my_application.cc
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
struct _MyApplication {
|
||||||
|
GtkApplication parent_instance;
|
||||||
|
char** dart_entrypoint_arguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||||
|
|
||||||
|
// Implements GApplication::activate.
|
||||||
|
static void my_application_activate(GApplication* application) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
GtkWindow* window =
|
||||||
|
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||||
|
|
||||||
|
// Use a header bar when running in GNOME as this is the common style used
|
||||||
|
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||||
|
// desktop).
|
||||||
|
// If running on X and not using GNOME then just use a traditional title bar
|
||||||
|
// in case the window manager does more exotic layout, e.g. tiling.
|
||||||
|
// If running on Wayland assume the header bar will work (may need changing
|
||||||
|
// if future cases occur).
|
||||||
|
gboolean use_header_bar = TRUE;
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
GdkScreen* screen = gtk_window_get_screen(window);
|
||||||
|
if (GDK_IS_X11_SCREEN(screen)) {
|
||||||
|
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||||
|
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||||
|
use_header_bar = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (use_header_bar) {
|
||||||
|
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
|
gtk_header_bar_set_title(header_bar, "cw_monero_example");
|
||||||
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
|
} else {
|
||||||
|
gtk_window_set_title(window, "cw_monero_example");
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
|
||||||
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
|
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||||
|
|
||||||
|
FlView* view = fl_view_new(project);
|
||||||
|
gtk_widget_show(GTK_WIDGET(view));
|
||||||
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||||
|
|
||||||
|
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||||
|
|
||||||
|
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::local_command_line.
|
||||||
|
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
// Strip out the first argument as it is the binary name.
|
||||||
|
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||||
|
|
||||||
|
g_autoptr(GError) error = nullptr;
|
||||||
|
if (!g_application_register(application, nullptr, &error)) {
|
||||||
|
g_warning("Failed to register: %s", error->message);
|
||||||
|
*exit_status = 1;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_application_activate(application);
|
||||||
|
*exit_status = 0;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GObject::dispose.
|
||||||
|
static void my_application_dispose(GObject* object) {
|
||||||
|
MyApplication* self = MY_APPLICATION(object);
|
||||||
|
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||||
|
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_class_init(MyApplicationClass* klass) {
|
||||||
|
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||||
|
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||||
|
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_init(MyApplication* self) {}
|
||||||
|
|
||||||
|
MyApplication* my_application_new() {
|
||||||
|
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||||
|
"application-id", APPLICATION_ID,
|
||||||
|
"flags", G_APPLICATION_NON_UNIQUE,
|
||||||
|
nullptr));
|
||||||
|
}
|
18
cw_monero/example/linux/my_application.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||||
|
#define FLUTTER_MY_APPLICATION_H_
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||||
|
GtkApplication)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* my_application_new:
|
||||||
|
*
|
||||||
|
* Creates a new Flutter-based application.
|
||||||
|
*
|
||||||
|
* Returns: a new #MyApplication.
|
||||||
|
*/
|
||||||
|
MyApplication* my_application_new();
|
||||||
|
|
||||||
|
#endif // FLUTTER_MY_APPLICATION_H_
|
|
@ -34,7 +34,6 @@ List<monero.SubaddressAccountRow> getAllAccount() {
|
||||||
// final size = monero.Wallet_numSubaddressAccounts(wptr!);
|
// final size = monero.Wallet_numSubaddressAccounts(wptr!);
|
||||||
refreshAccounts();
|
refreshAccounts();
|
||||||
int size = monero.SubaddressAccount_getAll_size(subaddressAccount!);
|
int size = monero.SubaddressAccount_getAll_size(subaddressAccount!);
|
||||||
print("size: $size");
|
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
monero.Wallet_addSubaddressAccount(wptr!);
|
monero.Wallet_addSubaddressAccount(wptr!);
|
||||||
return getAllAccount();
|
return getAllAccount();
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
class ConnectionToNodeException implements Exception {
|
|
||||||
ConnectionToNodeException({required this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class AccountRow extends Struct {
|
|
||||||
@Int64()
|
|
||||||
external int id;
|
|
||||||
|
|
||||||
external Pointer<Utf8> label;
|
|
||||||
|
|
||||||
String getLabel() => label.toDartString();
|
|
||||||
int getId() => id;
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class CoinsInfoRow extends Struct {
|
|
||||||
@Int64()
|
|
||||||
external int blockHeight;
|
|
||||||
|
|
||||||
external Pointer<Utf8> hash;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int internalOutputIndex;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int globalOutputIndex;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
external int spent;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
external int frozen;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int spentHeight;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int amount;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
external int rct;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
external int keyImageKnown;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int pkIndex;
|
|
||||||
|
|
||||||
@Uint32()
|
|
||||||
external int subaddrIndex;
|
|
||||||
|
|
||||||
@Uint32()
|
|
||||||
external int subaddrAccount;
|
|
||||||
|
|
||||||
external Pointer<Utf8> address;
|
|
||||||
|
|
||||||
external Pointer<Utf8> addressLabel;
|
|
||||||
|
|
||||||
external Pointer<Utf8> keyImage;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int unlockTime;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
external int unlocked;
|
|
||||||
|
|
||||||
external Pointer<Utf8> pubKey;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
external int coinbase;
|
|
||||||
|
|
||||||
external Pointer<Utf8> description;
|
|
||||||
|
|
||||||
String getHash() => hash.toDartString();
|
|
||||||
|
|
||||||
String getAddress() => address.toDartString();
|
|
||||||
|
|
||||||
String getAddressLabel() => addressLabel.toDartString();
|
|
||||||
|
|
||||||
String getKeyImage() => keyImage.toDartString();
|
|
||||||
|
|
||||||
String getPubKey() => pubKey.toDartString();
|
|
||||||
|
|
||||||
String getDescription() => description.toDartString();
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class SubaddressRow extends Struct {
|
|
||||||
@Int64()
|
|
||||||
external int id;
|
|
||||||
|
|
||||||
external Pointer<Utf8> address;
|
|
||||||
|
|
||||||
external Pointer<Utf8> label;
|
|
||||||
|
|
||||||
String getLabel() => label.toDartString();
|
|
||||||
String getAddress() => address.toDartString();
|
|
||||||
int getId() => id;
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class TransactionInfoRow extends Struct {
|
|
||||||
@Uint64()
|
|
||||||
external int amount;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int fee;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int blockHeight;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
external int confirmations;
|
|
||||||
|
|
||||||
@Uint32()
|
|
||||||
external int subaddrAccount;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
external int direction;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
external int isPending;
|
|
||||||
|
|
||||||
@Uint32()
|
|
||||||
external int subaddrIndex;
|
|
||||||
|
|
||||||
external Pointer<Utf8> hash;
|
|
||||||
|
|
||||||
external Pointer<Utf8> paymentId;
|
|
||||||
|
|
||||||
@Int64()
|
|
||||||
external int datetime;
|
|
||||||
|
|
||||||
int getDatetime() => datetime;
|
|
||||||
int getAmount() => amount >= 0 ? amount : amount * -1;
|
|
||||||
bool getIsPending() => isPending != 0;
|
|
||||||
String getHash() => hash.toDartString();
|
|
||||||
String getPaymentId() => paymentId.toDartString();
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class Utf8Box extends Struct {
|
|
||||||
external Pointer<Utf8> value;
|
|
||||||
|
|
||||||
String getValue() => value.toDartString();
|
|
||||||
}
|
|
|
@ -42,12 +42,16 @@ class Subaddress {
|
||||||
|
|
||||||
List<Subaddress> getAllSubaddresses() {
|
List<Subaddress> getAllSubaddresses() {
|
||||||
final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex);
|
final size = monero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex);
|
||||||
return List.generate(size, (index) {
|
final list = List.generate(size, (index) {
|
||||||
return Subaddress(
|
return Subaddress(
|
||||||
accountIndex: subaddress!.accountIndex,
|
accountIndex: subaddress!.accountIndex,
|
||||||
addressIndex: index,
|
addressIndex: index,
|
||||||
);
|
);
|
||||||
}).reversed.toList();
|
}).reversed.toList();
|
||||||
|
if (list.length == 0) {
|
||||||
|
list.add(Subaddress(addressIndex: subaddress!.accountIndex, accountIndex: 0));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addSubaddressSync({required int accountIndex, required String label}) {
|
void addSubaddressSync({required int accountIndex, required String label}) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
@ -110,7 +109,10 @@ Future<PendingTransactionDescription> createTransactionSync(
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
final message = error;
|
String message = error;
|
||||||
|
if (message.contains("RPC error")) {
|
||||||
|
message = "Invalid node response, please try again or switch node\n\ntrace: $message";
|
||||||
|
}
|
||||||
throw CreationTransactionException(message: message);
|
throw CreationTransactionException(message: message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +287,7 @@ class Transaction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// S finalubAddress? subAddress;
|
// final SubAddress? subAddress;
|
||||||
// List<Transfer> transfers = [];
|
// List<Transfer> transfers = [];
|
||||||
// final int txIndex;
|
// final int txIndex;
|
||||||
final monero.TransactionInfo txInfo;
|
final monero.TransactionInfo txInfo;
|
||||||
|
@ -321,4 +323,4 @@ class Transaction {
|
||||||
required this.key,
|
required this.key,
|
||||||
required this.txInfo
|
required this.txInfo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ String getSeed() {
|
||||||
if (polyseed != "") {
|
if (polyseed != "") {
|
||||||
return polyseed;
|
return polyseed;
|
||||||
}
|
}
|
||||||
final legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
|
final legacy = getSeedLegacy("English");
|
||||||
return legacy;
|
return legacy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,9 @@ String getSeedLegacy(String? language) {
|
||||||
monero.Wallet_setSeedLanguage(wptr!, language: language ?? "English");
|
monero.Wallet_setSeedLanguage(wptr!, language: language ?? "English");
|
||||||
legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
|
legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
|
||||||
}
|
}
|
||||||
|
if (monero.Wallet_status(wptr!) != 0) {
|
||||||
|
return monero.Wallet_errorString(wptr!);
|
||||||
|
}
|
||||||
return legacy;
|
return legacy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,7 +145,7 @@ void storeSync() async {
|
||||||
return monero.Wallet_synchronized(Pointer.fromAddress(addr));
|
return monero.Wallet_synchronized(Pointer.fromAddress(addr));
|
||||||
});
|
});
|
||||||
if (lastStorePointer == wptr!.address &&
|
if (lastStorePointer == wptr!.address &&
|
||||||
lastStoreHeight + 5000 < monero.Wallet_blockChainHeight(wptr!) &&
|
lastStoreHeight + 5000 > monero.Wallet_blockChainHeight(wptr!) &&
|
||||||
!synchronized) {
|
!synchronized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:cw_monero/api/account_list.dart';
|
import 'package:cw_monero/api/account_list.dart';
|
||||||
|
@ -6,10 +7,43 @@ 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_opening_exception.dart';
|
||||||
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_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/exceptions/wallet_restore_from_seed_exception.dart';
|
||||||
import 'package:cw_monero/api/transaction_history.dart';
|
|
||||||
import 'package:cw_monero/api/wallet.dart';
|
import 'package:cw_monero/api/wallet.dart';
|
||||||
|
import 'package:cw_monero/api/transaction_history.dart';
|
||||||
import 'package:monero/monero.dart' as monero;
|
import 'package:monero/monero.dart' as monero;
|
||||||
|
|
||||||
|
class MoneroCException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
MoneroCException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkIfMoneroCIsFine() {
|
||||||
|
final cppCsCpp = monero.MONERO_checksum_wallet2_api_c_cpp();
|
||||||
|
final cppCsH = monero.MONERO_checksum_wallet2_api_c_h();
|
||||||
|
final cppCsExp = monero.MONERO_checksum_wallet2_api_c_exp();
|
||||||
|
|
||||||
|
final dartCsCpp = monero.wallet2_api_c_cpp_sha256;
|
||||||
|
final dartCsH = monero.wallet2_api_c_h_sha256;
|
||||||
|
final dartCsExp = monero.wallet2_api_c_exp_sha256;
|
||||||
|
|
||||||
|
if (cppCsCpp != dartCsCpp) {
|
||||||
|
throw MoneroCException("monero_c and monero.dart cpp wrapper code mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsCpp'\ndart: '$dartCsCpp'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cppCsH != dartCsH) {
|
||||||
|
throw MoneroCException("monero_c and monero.dart cpp wrapper header mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsH'\ndart: '$dartCsH'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cppCsExp != dartCsExp && (Platform.isIOS || Platform.isMacOS)) {
|
||||||
|
throw MoneroCException("monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
monero.WalletManager? _wmPtr;
|
monero.WalletManager? _wmPtr;
|
||||||
final monero.WalletManager wmPtr = Pointer.fromAddress((() {
|
final monero.WalletManager wmPtr = Pointer.fromAddress((() {
|
||||||
try {
|
try {
|
||||||
|
@ -92,7 +126,16 @@ void restoreWalletFromKeysSync(
|
||||||
int nettype = 0,
|
int nettype = 0,
|
||||||
int restoreHeight = 0}) {
|
int restoreHeight = 0}) {
|
||||||
txhistory = null;
|
txhistory = null;
|
||||||
final newWptr = monero.WalletManager_createWalletFromKeys(
|
final newWptr = spendKey != ""
|
||||||
|
? monero.WalletManager_createDeterministicWalletFromSpendKey(
|
||||||
|
wmPtr,
|
||||||
|
path: path,
|
||||||
|
password: password,
|
||||||
|
language: language,
|
||||||
|
spendKeyString: spendKey,
|
||||||
|
newWallet: true, // TODO(mrcyjanek): safe to remove
|
||||||
|
restoreHeight: restoreHeight)
|
||||||
|
: monero.WalletManager_createWalletFromKeys(
|
||||||
wmPtr,
|
wmPtr,
|
||||||
path: path,
|
path: path,
|
||||||
password: password,
|
password: password,
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
import 'cw_monero_platform_interface.dart';
|
|
||||||
|
|
||||||
class CwMonero {
|
|
||||||
Future<String?> getPlatformVersion() {
|
|
||||||
return CwMoneroPlatform.instance.getPlatformVersion();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import 'cw_monero_platform_interface.dart';
|
|
||||||
|
|
||||||
/// An implementation of [CwMoneroPlatform] that uses method channels.
|
|
||||||
class MethodChannelCwMonero extends CwMoneroPlatform {
|
|
||||||
/// The method channel used to interact with the native platform.
|
|
||||||
@visibleForTesting
|
|
||||||
final methodChannel = const MethodChannel('cw_monero');
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String?> getPlatformVersion() async {
|
|
||||||
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
|
||||||
|
|
||||||
import 'cw_monero_method_channel.dart';
|
|
||||||
|
|
||||||
abstract class CwMoneroPlatform extends PlatformInterface {
|
|
||||||
/// Constructs a CwMoneroPlatform.
|
|
||||||
CwMoneroPlatform() : super(token: _token);
|
|
||||||
|
|
||||||
static final Object _token = Object();
|
|
||||||
|
|
||||||
static CwMoneroPlatform _instance = MethodChannelCwMonero();
|
|
||||||
|
|
||||||
/// The default instance of [CwMoneroPlatform] to use.
|
|
||||||
///
|
|
||||||
/// Defaults to [MethodChannelCwMonero].
|
|
||||||
static CwMoneroPlatform get instance => _instance;
|
|
||||||
|
|
||||||
/// Platform-specific implementations should set this with their own
|
|
||||||
/// platform-specific class that extends [CwMoneroPlatform] when
|
|
||||||
/// they register themselves.
|
|
||||||
static set instance(CwMoneroPlatform instance) {
|
|
||||||
PlatformInterface.verifyToken(instance, _token);
|
|
||||||
_instance = instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> getPlatformVersion() {
|
|
||||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,5 @@
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
import 'package:cw_core/monero_amount_format.dart';
|
import 'package:cw_core/monero_amount_format.dart';
|
||||||
import 'package:cw_monero/api/structs/transaction_info_row.dart';
|
|
||||||
import 'package:cw_core/parseBoolFromString.dart';
|
import 'package:cw_core/parseBoolFromString.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/format_amount.dart';
|
import 'package:cw_core/format_amount.dart';
|
||||||
|
@ -37,26 +34,6 @@ class MoneroTransactionInfo extends TransactionInfo {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
MoneroTransactionInfo.fromRow(TransactionInfoRow row)
|
|
||||||
: id = "${row.getHash()}_${row.getAmount()}_${row.subaddrAccount}_${row.subaddrIndex}",
|
|
||||||
txHash = row.getHash(),
|
|
||||||
height = row.blockHeight,
|
|
||||||
direction = parseTransactionDirectionFromInt(row.direction),
|
|
||||||
date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
|
|
||||||
isPending = row.isPending != 0,
|
|
||||||
amount = row.getAmount(),
|
|
||||||
accountIndex = row.subaddrAccount,
|
|
||||||
addressIndex = row.subaddrIndex,
|
|
||||||
confirmations = row.confirmations,
|
|
||||||
key = getTxKey(row.getHash()),
|
|
||||||
fee = row.fee {
|
|
||||||
additionalInfo = <String, dynamic>{
|
|
||||||
'key': key,
|
|
||||||
'accountIndex': accountIndex,
|
|
||||||
'addressIndex': addressIndex
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final String txHash;
|
final String txHash;
|
||||||
final int height;
|
final int height;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:cw_core/unspent_transaction_output.dart';
|
import 'package:cw_core/unspent_transaction_output.dart';
|
||||||
import 'package:cw_monero/api/structs/coins_info_row.dart';
|
|
||||||
|
|
||||||
class MoneroUnspent extends Unspent {
|
class MoneroUnspent extends Unspent {
|
||||||
MoneroUnspent(
|
MoneroUnspent(
|
||||||
|
@ -8,13 +7,5 @@ class MoneroUnspent extends Unspent {
|
||||||
this.isFrozen = isFrozen;
|
this.isFrozen = isFrozen;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory MoneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => MoneroUnspent(
|
|
||||||
coinsInfoRow.getAddress(),
|
|
||||||
coinsInfoRow.getHash(),
|
|
||||||
coinsInfoRow.getKeyImage(),
|
|
||||||
coinsInfoRow.amount,
|
|
||||||
coinsInfoRow.frozen == 1,
|
|
||||||
coinsInfoRow.unlocked == 1);
|
|
||||||
|
|
||||||
final bool isUnlocked;
|
final bool isUnlocked;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import 'dart:ffi';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/account.dart';
|
import 'package:cw_core/account.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/monero_amount_format.dart';
|
import 'package:cw_core/monero_amount_format.dart';
|
||||||
|
@ -19,7 +21,6 @@ import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_monero/api/account_list.dart';
|
|
||||||
import 'package:cw_monero/api/coins_info.dart';
|
import 'package:cw_monero/api/coins_info.dart';
|
||||||
import 'package:cw_monero/api/monero_output.dart';
|
import 'package:cw_monero/api/monero_output.dart';
|
||||||
import 'package:cw_monero/api/structs/pending_transaction.dart';
|
import 'package:cw_monero/api/structs/pending_transaction.dart';
|
||||||
|
@ -51,7 +52,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
MoneroTransactionHistory, MoneroTransactionInfo> with Store {
|
MoneroTransactionHistory, MoneroTransactionInfo> with Store {
|
||||||
MoneroWalletBase(
|
MoneroWalletBase(
|
||||||
{required WalletInfo walletInfo,
|
{required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo})
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
required String password})
|
||||||
: balance = ObservableMap<CryptoCurrency, MoneroBalance>.of({
|
: balance = ObservableMap<CryptoCurrency, MoneroBalance>.of({
|
||||||
CryptoCurrency.xmr: MoneroBalance(
|
CryptoCurrency.xmr: MoneroBalance(
|
||||||
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
||||||
|
@ -60,6 +62,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
_isTransactionUpdating = false,
|
_isTransactionUpdating = false,
|
||||||
_hasSyncAfterStartup = false,
|
_hasSyncAfterStartup = false,
|
||||||
isEnabledAutoGenerateSubaddress = false,
|
isEnabledAutoGenerateSubaddress = false,
|
||||||
|
_password = password,
|
||||||
syncStatus = NotConnectedSyncStatus(),
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
unspentCoins = [],
|
unspentCoins = [],
|
||||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||||
|
@ -110,9 +113,10 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get seed => monero_wallet.getSeed();
|
String get seed => monero_wallet.getSeed();
|
||||||
String seedLegacy(String? language) {
|
String seedLegacy(String? language) => monero_wallet.getSeedLegacy(language);
|
||||||
return monero_wallet.getSeedLegacy(language);
|
|
||||||
}
|
@override
|
||||||
|
String get password => _password;
|
||||||
|
|
||||||
String? passphrase() {
|
String? passphrase() {
|
||||||
return monero_wallet.getPassphrase();
|
return monero_wallet.getPassphrase();
|
||||||
|
@ -134,6 +138,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
bool _hasSyncAfterStartup;
|
bool _hasSyncAfterStartup;
|
||||||
Timer? _autoSaveTimer;
|
Timer? _autoSaveTimer;
|
||||||
List<MoneroUnspent> unspentCoins;
|
List<MoneroUnspent> unspentCoins;
|
||||||
|
String _password;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
await walletAddresses.init();
|
await walletAddresses.init();
|
||||||
|
@ -195,12 +200,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
@override
|
@override
|
||||||
Future<void> startSync() async {
|
Future<void> startSync() async {
|
||||||
try {
|
try {
|
||||||
_setInitialHeight();
|
_assertInitialHeight();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// our restore height wasn't correct, so lets see if using the backup works:
|
// our restore height wasn't correct, so lets see if using the backup works:
|
||||||
try {
|
try {
|
||||||
await resetCache(name);
|
await resetCache(name); // Resetting the cache removes the TX Keys and Polyseed
|
||||||
_setInitialHeight();
|
_assertInitialHeight();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// we still couldn't get a valid height from the backup?!:
|
// we still couldn't get a valid height from the backup?!:
|
||||||
// try to use the date instead:
|
// try to use the date instead:
|
||||||
|
@ -640,18 +645,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
|
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the height is correct:
|
/// Asserts the current height to be above [MIN_RESTORE_HEIGHT]
|
||||||
void _setInitialHeight() {
|
void _assertInitialHeight() {
|
||||||
if (walletInfo.isRecovery) {
|
if (walletInfo.isRecovery) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final height = monero_wallet.getCurrentHeight();
|
final height = monero_wallet.getCurrentHeight();
|
||||||
|
|
||||||
if (height > MIN_RESTORE_HEIGHT) {
|
// the restore height is probably correct, so we do nothing:
|
||||||
// the restore height is probably correct, so we do nothing:
|
if (height > MIN_RESTORE_HEIGHT) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
|
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||||
accountIndex: accountIndex,
|
accountIndex: accountIndex,
|
||||||
defaultLabel: defaultLabel,
|
defaultLabel: defaultLabel,
|
||||||
usedAddresses: usedAddresses.toList());
|
usedAddresses: usedAddresses.toList());
|
||||||
subaddress = subaddressList.subaddresses.last;
|
subaddress = (subaddressList.subaddresses.isEmpty) ? Subaddress(id: 0, address: address, label: defaultLabel) : subaddressList.subaddresses.last;
|
||||||
address = subaddress!.address;
|
address = subaddress!.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,9 @@ class MoneroWalletService extends WalletService<
|
||||||
await monero_wallet_manager.createWallet(
|
await monero_wallet_manager.createWallet(
|
||||||
path: path, password: credentials.password!, language: credentials.language);
|
path: path, password: credentials.password!, language: credentials.language);
|
||||||
final wallet = MoneroWallet(
|
final wallet = MoneroWallet(
|
||||||
walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
|
walletInfo: credentials.walletInfo!,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
password: credentials.password!);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -134,10 +136,14 @@ class MoneroWalletService extends WalletService<
|
||||||
await repairOldAndroidWallet(name);
|
await repairOldAndroidWallet(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
|
await monero_wallet_manager
|
||||||
final walletInfo = walletInfoSource.values
|
.openWalletAsync({'path': path, 'password': password});
|
||||||
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
final walletInfo = walletInfoSource.values.firstWhere(
|
||||||
wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
(info) => info.id == WalletBase.idFor(name, getType()));
|
||||||
|
final wallet = MoneroWallet(
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
password: password);
|
||||||
final isValid = wallet.walletAddresses.validate();
|
final isValid = wallet.walletAddresses.validate();
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
|
@ -170,15 +176,22 @@ class MoneroWalletService extends WalletService<
|
||||||
final bool invalidSignature = e.toString().contains('invalid signature') ||
|
final bool invalidSignature = e.toString().contains('invalid signature') ||
|
||||||
(e is WalletOpeningException && e.message.contains('invalid signature'));
|
(e is WalletOpeningException && e.message.contains('invalid signature'));
|
||||||
|
|
||||||
|
final bool invalidPassword = e.toString().contains('invalid password') ||
|
||||||
|
(e is WalletOpeningException && e.message.contains('invalid password'));
|
||||||
|
|
||||||
if (!isBadAlloc &&
|
if (!isBadAlloc &&
|
||||||
!doesNotCorrespond &&
|
!doesNotCorrespond &&
|
||||||
!isMissingCacheFilesIOS &&
|
!isMissingCacheFilesIOS &&
|
||||||
!isMissingCacheFilesAndroid &&
|
!isMissingCacheFilesAndroid &&
|
||||||
!invalidSignature &&
|
!invalidSignature &&
|
||||||
|
!invalidPassword &&
|
||||||
wallet != null &&
|
wallet != null &&
|
||||||
wallet.onError != null) {
|
wallet.onError != null) {
|
||||||
wallet.onError!(FlutterErrorDetails(exception: e, stack: s));
|
wallet.onError!(FlutterErrorDetails(exception: e, stack: s));
|
||||||
}
|
}
|
||||||
|
if (invalidPassword) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
await restoreOrResetWalletFiles(name);
|
await restoreOrResetWalletFiles(name);
|
||||||
return openWallet(name, password);
|
return openWallet(name, password);
|
||||||
|
@ -214,11 +227,15 @@ class MoneroWalletService extends WalletService<
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> rename(String currentName, String password, String newName) async {
|
Future<void> rename(
|
||||||
final currentWalletInfo = walletInfoSource.values
|
String currentName, String password, String newName) async {
|
||||||
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
final currentWalletInfo = walletInfoSource.values.firstWhere(
|
||||||
final currentWallet =
|
(info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||||
MoneroWallet(walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
final currentWallet = MoneroWallet(
|
||||||
|
walletInfo: currentWalletInfo,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
|
||||||
await currentWallet.renameWalletFiles(newName);
|
await currentWallet.renameWalletFiles(newName);
|
||||||
|
|
||||||
|
@ -243,7 +260,9 @@ class MoneroWalletService extends WalletService<
|
||||||
viewKey: credentials.viewKey,
|
viewKey: credentials.viewKey,
|
||||||
spendKey: credentials.spendKey);
|
spendKey: credentials.spendKey);
|
||||||
final wallet = MoneroWallet(
|
final wallet = MoneroWallet(
|
||||||
walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
|
walletInfo: credentials.walletInfo!,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
password: credentials.password!);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -277,7 +296,9 @@ class MoneroWalletService extends WalletService<
|
||||||
seed: credentials.mnemonic,
|
seed: credentials.mnemonic,
|
||||||
restoreHeight: credentials.height!);
|
restoreHeight: credentials.height!);
|
||||||
final wallet = MoneroWallet(
|
final wallet = MoneroWallet(
|
||||||
walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
|
walletInfo: credentials.walletInfo!,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
password: credentials.password!);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -329,7 +350,11 @@ class MoneroWalletService extends WalletService<
|
||||||
restoreHeight: height,
|
restoreHeight: height,
|
||||||
spendKey: spendKey);
|
spendKey: spendKey);
|
||||||
|
|
||||||
final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
final wallet = MoneroWallet(
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
|
@ -365,4 +390,28 @@ class MoneroWalletService extends WalletService<
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getSeeds(String name, String password, WalletType type) async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: name, type: getType());
|
||||||
|
|
||||||
|
if (walletFilesExist(path)) {
|
||||||
|
await repairOldAndroidWallet(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
|
||||||
|
final walletInfo = walletInfoSource.values
|
||||||
|
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||||
|
final wallet = MoneroWallet(
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
return wallet.seed;
|
||||||
|
} catch (_) {
|
||||||
|
// if the file couldn't be opened or read
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|