Merge branch 'mweb' of https://github.com/cake-tech/cake_wallet into mweb-bg-sync
7
.github/workflows/cache_dependencies.yml
vendored
|
@ -23,9 +23,10 @@ jobs:
|
|||
docker-images: true
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: "11.x"
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
- name: Configure placeholder git details
|
||||
run: |
|
||||
git config --global user.email "CI@cakewallet.com"
|
||||
|
@ -60,7 +61,7 @@ jobs:
|
|||
path: |
|
||||
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||
/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' }}
|
||||
name: Generate Externals
|
||||
|
|
|
@ -39,9 +39,10 @@ jobs:
|
|||
docker-images: true
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: "11.x"
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
- name: Configure placeholder git details
|
||||
run: |
|
||||
git config --global user.email "CI@cakewallet.com"
|
||||
|
@ -53,7 +54,9 @@ jobs:
|
|||
channel: stable
|
||||
|
||||
- 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
|
||||
run: |
|
||||
|
@ -76,7 +79,7 @@ jobs:
|
|||
path: |
|
||||
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||
/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' }}
|
||||
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
|
||||
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_32.png
|
||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
|
||||
|
|
|
@ -18,6 +18,12 @@ migration:
|
|||
- platform: windows
|
||||
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
- platform: macos
|
||||
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
- platform: linux
|
||||
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
|
||||
# User provided section
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div align="center">
|
||||
|
||||
<img height="100" src=".github/assets/Logo_CakeWallet.png">
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
@ -161,7 +161,9 @@ The only parts to be translated, if needed, are the values m and s after the var
|
|||
|
||||
4. Add the language to `lib/entities/language_service.dart` under both `supportedLocales` and `localeCountryCode`. Use the name of the language in the local language and in English in parentheses after for `supportedLocales`. Use the [ISO 3166-1 alpha-3 code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) for `localeCountryCode`. You must choose one country, so choose the country with the most native speakers of this language or is otherwise best associated with this language.
|
||||
|
||||
5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 digit localeCountryCode. The image must be 42x26 pixels with a 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make transparent. Or you can use another program like Photoshop.
|
||||
5. Add a relevant flag to `assets/images/flags/XXXX.png`, replacing XXXX with the 3 letters localeCountryCode. The image must be 42x26 pixels with a 3 pixels of transparent margin on all 4 sides. You can resize the flag with [paint.net](https://www.getpaint.net/) to 36x20 pixels, expand the canvas to 42x26 pixels with the flag anchored in the middle, and then manually delete the 3 pixels on each side to make transparent. Or you can use another program like Photoshop.
|
||||
|
||||
6. Add the new language code to `tool/utils/translation/translation_constants.dart`
|
||||
|
||||
## Add a new fiat currency
|
||||
|
||||
|
|
|
@ -10,7 +10,18 @@ analyzer:
|
|||
lib/generated/*.dart,
|
||||
cw_monero/ios/External/**,
|
||||
cw_shared_external/**,
|
||||
shared_external/**]
|
||||
shared_external/**,
|
||||
lib/bitcoin/cw_bitcoin.dart,
|
||||
lib/bitcoin_cash/cw_bitcoin_cash.dart,
|
||||
lib/ethereum/cw_ethereum.dart,
|
||||
lib/haven/cw_haven.dart,
|
||||
lib/monero/cw_monero.dart,
|
||||
lib/nano/cw_nano.dart,
|
||||
lib/polygon/cw_polygon.dart,
|
||||
lib/solana/cw_solana.dart,
|
||||
lib/tron/cw_tron.dart,
|
||||
lib/wownero/cw_wownero.dart,
|
||||
]
|
||||
language:
|
||||
strict-casts: true
|
||||
strict-raw-types: true
|
||||
|
|
|
@ -91,5 +91,4 @@ dependencies {
|
|||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.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.provider.Settings;
|
||||
|
||||
import com.unstoppabledomains.resolution.DomainResolution;
|
||||
import com.unstoppabledomains.resolution.Resolution;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class MainActivity extends FlutterFragmentActivity {
|
||||
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
||||
final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24;
|
||||
boolean isAppSecure = false;
|
||||
|
||||
@Override
|
||||
|
@ -53,14 +49,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
|||
random.nextBytes(bytes);
|
||||
handler.post(() -> result.success(bytes));
|
||||
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":
|
||||
isAppSecure = call.argument("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() {
|
||||
String packageName = getPackageName();
|
||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
|
|
|
@ -19,14 +19,10 @@ import android.net.Uri;
|
|||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.unstoppabledomains.resolution.DomainResolution;
|
||||
import com.unstoppabledomains.resolution.Resolution;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class MainActivity extends FlutterFragmentActivity {
|
||||
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
||||
final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24;
|
||||
|
||||
@Override
|
||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||
|
@ -51,14 +47,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
|||
random.nextBytes(bytes);
|
||||
handler.post(() -> result.success(bytes));
|
||||
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":
|
||||
disableBatteryOptimization();
|
||||
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() {
|
||||
String packageName = getPackageName();
|
||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
|
|
|
@ -19,14 +19,10 @@ import android.net.Uri;
|
|||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.unstoppabledomains.resolution.DomainResolution;
|
||||
import com.unstoppabledomains.resolution.Resolution;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class MainActivity extends FlutterFragmentActivity {
|
||||
final String UTILS_CHANNEL = "com.cake_wallet/native_utils";
|
||||
final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24;
|
||||
boolean isAppSecure = false;
|
||||
|
||||
@Override
|
||||
|
@ -52,14 +48,6 @@ public class MainActivity extends FlutterFragmentActivity {
|
|||
random.nextBytes(bytes);
|
||||
handler.post(() -> result.success(bytes));
|
||||
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":
|
||||
isAppSecure = call.argument("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() {
|
||||
String packageName = getPackageName();
|
||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
|
|
|
@ -2,7 +2,7 @@ buildscript {
|
|||
ext.kotlin_version = '1.8.21'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -15,7 +15,7 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Before ![]() (image error) Size: 3.4 KiB After ![]() (image error) Size: 9.9 KiB ![]() ![]() |
Before ![]() (image error) Size: 3.4 KiB After ![]() (image error) Size: 12 KiB ![]() ![]() |
Before ![]() (image error) Size: 287 B After ![]() (image error) Size: 376 B ![]() ![]() |
Before ![]() (image error) Size: 340 B After ![]() (image error) Size: 1.2 KiB ![]() ![]() |
BIN
assets/images/flags/arm.png
Normal file
After ![]() (image error) Size: 4.7 KiB |
Before ![]() (image error) Size: 1.4 KiB After ![]() (image error) Size: 1.8 KiB ![]() ![]() |
Before ![]() (image error) Size: 446 B After ![]() (image error) Size: 913 B ![]() ![]() |
Before ![]() (image error) Size: 735 B After ![]() (image error) Size: 372 B ![]() ![]() |
Before ![]() (image error) Size: 1.2 KiB After ![]() (image error) Size: 692 B ![]() ![]() |
Before ![]() (image error) Size: 1,005 B After ![]() (image error) Size: 788 B ![]() ![]() |
Before ![]() (image error) Size: 855 B After ![]() (image error) Size: 371 B ![]() ![]() |
Before ![]() (image error) Size: 351 B After ![]() (image error) Size: 753 B ![]() ![]() |
Before ![]() (image error) Size: 860 B After ![]() (image error) Size: 840 B ![]() ![]() |
Before ![]() (image error) Size: 217 B After ![]() (image error) Size: 381 B ![]() ![]() |
Before ![]() (image error) Size: 915 B After ![]() (image error) Size: 830 B ![]() ![]() |
Before ![]() (image error) Size: 703 B After ![]() (image error) Size: 370 B ![]() ![]() |
Before ![]() (image error) Size: 801 B After ![]() (image error) Size: 387 B ![]() ![]() |
Before ![]() (image error) Size: 431 B After ![]() (image error) Size: 553 B ![]() ![]() |
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 1.8 KiB ![]() ![]() |
Before ![]() (image error) Size: 1,005 B After ![]() (image error) Size: 1.4 KiB ![]() ![]() |
Before ![]() (image error) Size: 700 B After ![]() (image error) Size: 351 B ![]() ![]() |
Before ![]() (image error) Size: 1.3 KiB After ![]() (image error) Size: 1.9 KiB ![]() ![]() |
Before ![]() (image error) Size: 432 B After ![]() (image error) Size: 762 B ![]() ![]() |
Before ![]() (image error) Size: 477 B After ![]() (image error) Size: 1.7 KiB ![]() ![]() |
Before ![]() (image error) Size: 1,023 B After ![]() (image error) Size: 2 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 1.4 KiB ![]() ![]() |
Before ![]() (image error) Size: 728 B After ![]() (image error) Size: 373 B ![]() ![]() |
Before ![]() (image error) Size: 684 B After ![]() (image error) Size: 360 B ![]() ![]() |
Before ![]() (image error) Size: 615 B After ![]() (image error) Size: 1.1 KiB ![]() ![]() |
Before ![]() (image error) Size: 867 B After ![]() (image error) Size: 424 B ![]() ![]() |
Before ![]() (image error) Size: 937 B After ![]() (image error) Size: 994 B ![]() ![]() |
Before ![]() (image error) Size: 705 B After ![]() (image error) Size: 351 B ![]() ![]() |
Before ![]() (image error) Size: 896 B After ![]() (image error) Size: 851 B ![]() ![]() |
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 2.5 KiB ![]() ![]() |
Before ![]() (image error) Size: 429 B After ![]() (image error) Size: 849 B ![]() ![]() |
Before ![]() (image error) Size: 1,013 B After ![]() (image error) Size: 1.6 KiB ![]() ![]() |
Before ![]() (image error) Size: 902 B After ![]() (image error) Size: 1 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 1.4 KiB ![]() ![]() |
Before ![]() (image error) Size: 193 B After ![]() (image error) Size: 351 B ![]() ![]() |
Before ![]() (image error) Size: 762 B After ![]() (image error) Size: 372 B ![]() ![]() |
Before ![]() (image error) Size: 898 B After ![]() (image error) Size: 424 B ![]() ![]() |
Before ![]() (image error) Size: 1.3 KiB After ![]() (image error) Size: 2 KiB ![]() ![]() |
Before ![]() (image error) Size: 899 B After ![]() (image error) Size: 1.1 KiB ![]() ![]() |
Before ![]() (image error) Size: 1.2 KiB After ![]() (image error) Size: 1.5 KiB ![]() ![]() |
Before ![]() (image error) Size: 707 B After ![]() (image error) Size: 366 B ![]() ![]() |
Before ![]() (image error) Size: 1.1 KiB After ![]() (image error) Size: 2.5 KiB ![]() ![]() |
Before ![]() (image error) Size: 705 B After ![]() (image error) Size: 351 B ![]() ![]() |
Before ![]() (image error) Size: 702 B After ![]() (image error) Size: 371 B ![]() ![]() |
Before ![]() (image error) Size: 1.3 KiB After ![]() (image error) Size: 750 B ![]() ![]() |
Before ![]() (image error) Size: 476 B After ![]() (image error) Size: 2.1 KiB ![]() ![]() |
Before ![]() (image error) Size: 902 B After ![]() (image error) Size: 1.1 KiB ![]() ![]() |
Before ![]() (image error) Size: 752 B After ![]() (image error) Size: 390 B ![]() ![]() |
Before ![]() (image error) Size: 774 B After ![]() (image error) Size: 384 B ![]() ![]() |
Before ![]() (image error) Size: 1 KiB After ![]() (image error) Size: 1.1 KiB ![]() ![]() |
Before ![]() (image error) Size: 373 B After ![]() (image error) Size: 1,005 B ![]() ![]() |
Before ![]() (image error) Size: 1.2 KiB After ![]() (image error) Size: 597 B ![]() ![]() |
Before ![]() (image error) Size: 1,009 B After ![]() (image error) Size: 887 B ![]() ![]() |
Before ![]() (image error) Size: 448 B After ![]() (image error) Size: 882 B ![]() ![]() |
Before ![]() (image error) Size: 1.4 KiB After ![]() (image error) Size: 16 KiB ![]() ![]() |
Before ![]() (image error) Size: 23 KiB After ![]() (image error) Size: 14 KiB ![]() ![]() |
Before ![]() (image error) Size: 20 KiB After ![]() (image error) Size: 14 KiB ![]() ![]() |
Before ![]() (image error) Size: 10 KiB After ![]() (image error) Size: 28 KiB ![]() ![]() |
|
@ -1,4 +1,19 @@
|
|||
-
|
||||
uri: ltc-electrum.cakewallet.com:50002
|
||||
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,4 +1,7 @@
|
|||
-
|
||||
uri: rpc.ankr.com
|
||||
is_default: true
|
||||
useSSL: true
|
||||
-
|
||||
uri: api.mainnet-beta.solana.com:443
|
||||
useSSL: true
|
|
@ -1,3 +1,3 @@
|
|||
Monero enhancements
|
||||
Synchronization improvements
|
||||
Scan and verify messages
|
||||
Synchronization enhancements
|
||||
Bug fixes
|
|
@ -1,5 +1,3 @@
|
|||
Monero and Ethereum enhancements
|
||||
Synchronization improvements
|
||||
Exchange flow enhancements
|
||||
Ledger improvements
|
||||
Scan and verify messages
|
||||
Synchronization enhancements
|
||||
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"
|
||||
ANDROID="android"
|
||||
MACOS="macos"
|
||||
LINUX="linux"
|
||||
|
||||
PLATFORMS=($IOS $ANDROID $MACOS)
|
||||
PLATFORMS=($IOS $ANDROID $MACOS $LINUX)
|
||||
PLATFORM=$1
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
|
@ -27,9 +28,14 @@ if [ "$PLATFORM" == "$ANDROID" ]; then
|
|||
cd scripts/android
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" == "$LINUX" ]; then
|
||||
echo "Configuring for linux"
|
||||
cd scripts/linux
|
||||
fi
|
||||
|
||||
source ./app_env.sh cakewallet
|
||||
./app_config.sh
|
||||
cd ../.. && flutter pub get
|
||||
#flutter packages pub run tool/generate_localization.dart
|
||||
flutter packages pub run tool/generate_localization.dart
|
||||
./model_generator.sh
|
||||
#cd macos && pod install
|
||||
#cd macos && pod install
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
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_core/hardware/hardware_account_data.dart';
|
||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||
|
@ -25,7 +25,8 @@ class BitcoinHardwareWalletService {
|
|||
for (final i in indexRange) {
|
||||
final derivationPath = "m/84'/0'/$i'";
|
||||
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);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cryptography/cryptography.dart' as cryptography;
|
||||
import 'package:cw_core/sec_random_native.dart';
|
||||
|
@ -59,11 +60,7 @@ void maskBytes(Uint8List bytes, int bits) {
|
|||
}
|
||||
}
|
||||
|
||||
String bufferToBin(Uint8List data) {
|
||||
final q1 = data.map((e) => e.toRadixString(2).padLeft(8, '0'));
|
||||
final q2 = q1.join('');
|
||||
return q2;
|
||||
}
|
||||
String bufferToBin(Uint8List data) => data.map((e) => e.toRadixString(2).padLeft(8, '0')).join('');
|
||||
|
||||
String encode(Uint8List data) {
|
||||
final dataBitLen = data.length * 8;
|
||||
|
@ -112,17 +109,18 @@ Future<bool> checkIfMnemonicIsElectrum2(String mnemonic) async {
|
|||
Future<String> getMnemonicHash(String mnemonic) async {
|
||||
final hmacSha512 = Hmac(sha512, utf8.encode('Seed version'));
|
||||
final digest = hmacSha512.convert(utf8.encode(normalizeText(mnemonic)));
|
||||
final hx = digest.toString();
|
||||
return hx;
|
||||
return digest.toString();
|
||||
}
|
||||
|
||||
Future<Uint8List> mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) async {
|
||||
Future<Uint8List> mnemonicToSeedBytes(String mnemonic,
|
||||
{String prefix = segwit, String passphrase = ''}) async {
|
||||
final pbkdf2 =
|
||||
cryptography.Pbkdf2(macAlgorithm: cryptography.Hmac.sha512(), iterations: 2048, bits: 512);
|
||||
final text = normalizeText(mnemonic);
|
||||
// pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce)
|
||||
final passphraseBytes = utf8.encode(normalizeText(passphrase));
|
||||
final key = await pbkdf2.deriveKey(
|
||||
secretKey: cryptography.SecretKey(text.codeUnits), nonce: 'electrum'.codeUnits);
|
||||
secretKey: cryptography.SecretKey(text.codeUnits),
|
||||
nonce: [...'electrum'.codeUnits, ...passphraseBytes]);
|
||||
final bytes = await key.extractBytes();
|
||||
return Uint8List.fromList(bytes);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
class Mnemonic {
|
||||
class MnemonicBip39 {
|
||||
/// Generate bip39 mnemonic
|
||||
static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength);
|
||||
|
||||
/// Create root seed from mnemonic
|
||||
static Uint8List toSeed(String mnemonic) => bip39.mnemonicToSeed(mnemonic);
|
||||
static Uint8List toSeed(String mnemonic, {String? passphrase}) =>
|
||||
bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
|
||||
}
|
|
@ -2,10 +2,10 @@ import 'dart:convert';
|
|||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:convert/convert.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_core/encryption_file_utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.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/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||
|
@ -30,6 +31,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
Uint8List? seedBytes,
|
||||
String? mnemonic,
|
||||
String? xpub,
|
||||
|
@ -50,14 +52,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: networkParam == null
|
||||
? bitcoin.bitcoin
|
||||
network: networkParam == null
|
||||
? BitcoinNetwork.mainnet
|
||||
: networkParam == BitcoinNetwork.mainnet
|
||||
? bitcoin.bitcoin
|
||||
: bitcoin.testnet,
|
||||
? BitcoinNetwork.mainnet
|
||||
: BitcoinNetwork.testnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
currency:
|
||||
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
|
||||
alwaysScan: alwaysScan,
|
||||
|
@ -75,10 +78,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
initialSilentAddresses: initialSilentAddresses,
|
||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.derive(1),
|
||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||
network: networkParam ?? network,
|
||||
masterHd:
|
||||
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
|
||||
masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
|
||||
);
|
||||
|
||||
autorun((_) {
|
||||
|
@ -91,6 +93,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
String? passphrase,
|
||||
String? addressPageType,
|
||||
BasedUtxoNetwork? network,
|
||||
|
@ -112,7 +115,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
break;
|
||||
case DerivationType.electrum:
|
||||
default:
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic);
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
||||
break;
|
||||
}
|
||||
return BitcoinWallet(
|
||||
|
@ -125,6 +128,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
initialSilentAddresses: initialSilentAddresses,
|
||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||
initialBalance: initialBalance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
|
@ -138,54 +142,87 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required String password,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
required bool alwaysScan,
|
||||
}) async {
|
||||
final network = walletInfo.network != null
|
||||
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
||||
: BitcoinNetwork.mainnet;
|
||||
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
|
||||
|
||||
walletInfo.derivationInfo ??= DerivationInfo(
|
||||
derivationType: snp.derivationType ?? DerivationType.electrum,
|
||||
derivationPath: snp.derivationPath,
|
||||
);
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
|
||||
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:
|
||||
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path;
|
||||
walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
|
||||
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
|
||||
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
|
||||
|
||||
Uint8List? seedBytes = null;
|
||||
final mnemonic = keysData.mnemonic;
|
||||
final passphrase = keysData.passphrase;
|
||||
|
||||
if (snp.mnemonic != null) {
|
||||
if (mnemonic != null) {
|
||||
switch (walletInfo.derivationInfo!.derivationType) {
|
||||
case DerivationType.electrum:
|
||||
seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
||||
break;
|
||||
case DerivationType.bip39:
|
||||
default:
|
||||
seedBytes = await bip39.mnemonicToSeed(
|
||||
snp.mnemonic!,
|
||||
passphrase: snp.passphrase ?? '',
|
||||
mnemonic,
|
||||
passphrase: passphrase ?? '',
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return BitcoinWallet(
|
||||
mnemonic: snp.mnemonic,
|
||||
xpub: snp.xpub,
|
||||
mnemonic: mnemonic,
|
||||
xpub: keysData.xPub,
|
||||
password: password,
|
||||
passphrase: snp.passphrase,
|
||||
passphrase: passphrase,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialSilentAddresses: snp.silentAddresses,
|
||||
initialSilentAddressIndex: snp.silentAddressIndex,
|
||||
initialBalance: snp.balance,
|
||||
initialAddresses: snp?.addresses,
|
||||
initialSilentAddresses: snp?.silentAddresses,
|
||||
initialSilentAddressIndex: snp?.silentAddressIndex ?? 0,
|
||||
initialBalance: snp?.balance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||
addressPageType: snp?.addressPageType,
|
||||
networkParam: network,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
|
@ -235,7 +272,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
|
||||
|
||||
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
|
||||
return BtcTransaction.fromRaw(hex.encode(rawHex));
|
||||
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -249,8 +286,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
||||
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
|
||||
|
||||
final signature = await _bitcoinLedgerApp!
|
||||
.signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath);
|
||||
final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!,
|
||||
message: ascii.encode(message), signDerivationPath: derivationPath);
|
||||
return base64Encode(signature);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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/utils.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -24,7 +24,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
|||
}) : super(walletInfo);
|
||||
|
||||
@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)
|
||||
return generateP2PKHAddress(hd: hd, index: index, network: network);
|
||||
|
||||
|
|
|
@ -3,14 +3,18 @@ import 'package:cw_core/wallet_credentials.dart';
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class BitcoinNewWalletCredentials extends WalletCredentials {
|
||||
BitcoinNewWalletCredentials(
|
||||
{required String name,
|
||||
WalletInfo? walletInfo,
|
||||
DerivationType? derivationType,
|
||||
String? derivationPath})
|
||||
: super(
|
||||
BitcoinNewWalletCredentials({
|
||||
required String name,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
DerivationType? derivationType,
|
||||
String? derivationPath,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'dart:io';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
||||
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.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/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
|
@ -19,11 +21,12 @@ class BitcoinWalletService extends WalletService<
|
|||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials,
|
||||
BitcoinRestoreWalletFromHardware> {
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan);
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
final bool alwaysScan;
|
||||
final bool isDirect;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.bitcoin;
|
||||
|
@ -33,16 +36,32 @@ class BitcoinWalletService extends WalletService<
|
|||
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
||||
credentials.walletInfo?.network = network.value;
|
||||
|
||||
final String mnemonic;
|
||||
switch ( credentials.walletInfo?.derivationInfo?.derivationType) {
|
||||
case DerivationType.bip39:
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
mnemonic = await MnemonicBip39.generate(strength: strength);
|
||||
break;
|
||||
case DerivationType.electrum:
|
||||
default:
|
||||
mnemonic = await generateElectrumMnemonic();
|
||||
break;
|
||||
}
|
||||
|
||||
final wallet = await BitcoinWalletBase.create(
|
||||
mnemonic: await generateElectrumMnemonic(),
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
network: network,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
|
@ -61,6 +80,7 @@ class BitcoinWalletService extends WalletService<
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.init();
|
||||
saveBackup(name);
|
||||
|
@ -73,6 +93,7 @@ class BitcoinWalletService extends WalletService<
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
@ -97,6 +118,7 @@ class BitcoinWalletService extends WalletService<
|
|||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
|
@ -123,6 +145,7 @@ class BitcoinWalletService extends WalletService<
|
|||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
networkParam: network,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
@ -151,6 +174,7 @@ class BitcoinWalletService extends WalletService<
|
|||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
network: network,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
|
|
@ -8,6 +8,8 @@ import 'package:cw_bitcoin/script_hash.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
enum ConnectionStatus { connected, disconnected, connecting, failed }
|
||||
|
||||
String jsonrpcparams(List<Object> params) {
|
||||
final _params = params.map((val) => '"${val.toString()}"').join(',');
|
||||
return '[$_params]';
|
||||
|
@ -41,7 +43,7 @@ class ElectrumClient {
|
|||
|
||||
bool get isConnected => _isConnected;
|
||||
Socket? socket;
|
||||
void Function(bool?)? onConnectionStatusChange;
|
||||
void Function(ConnectionStatus)? onConnectionStatusChange;
|
||||
int _id;
|
||||
final Map<String, SocketTask> _tasks;
|
||||
Map<String, SocketTask> get tasks => _tasks;
|
||||
|
@ -60,39 +62,72 @@ class ElectrumClient {
|
|||
}
|
||||
|
||||
Future<void> connect({required String host, required int port, bool? useSSL}) async {
|
||||
_setConnectionStatus(ConnectionStatus.connecting);
|
||||
|
||||
try {
|
||||
await socket?.close();
|
||||
socket = null;
|
||||
} catch (_) {}
|
||||
|
||||
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||
} else {
|
||||
socket = await SecureSocket.connect(host, port,
|
||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
||||
}
|
||||
_setIsConnected(true);
|
||||
|
||||
socket!.listen((Uint8List event) {
|
||||
try {
|
||||
final msg = utf8.decode(event.toList());
|
||||
final messagesList = msg.split("\n");
|
||||
for (var message in messagesList) {
|
||||
if (message.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
_parseResponse(message);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
try {
|
||||
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||
} else {
|
||||
socket = await SecureSocket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: connectionTimeout,
|
||||
onBadCertificate: (_) => true,
|
||||
);
|
||||
}
|
||||
}, onError: (Object error) {
|
||||
print(error.toString());
|
||||
unterminatedString = '';
|
||||
_setIsConnected(false);
|
||||
}, onDone: () {
|
||||
unterminatedString = '';
|
||||
_setIsConnected(null);
|
||||
});
|
||||
} catch (_) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return;
|
||||
}
|
||||
_setConnectionStatus(ConnectionStatus.connected);
|
||||
|
||||
socket!.listen(
|
||||
(Uint8List event) {
|
||||
try {
|
||||
final msg = utf8.decode(event.toList());
|
||||
final messagesList = msg.split("\n");
|
||||
for (var message in messagesList) {
|
||||
if (message.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
_parseResponse(message);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
},
|
||||
onError: (Object error) {
|
||||
socket = null;
|
||||
final errorMsg = error.toString();
|
||||
print(errorMsg);
|
||||
unterminatedString = '';
|
||||
|
||||
final currentHost = socket?.address.host;
|
||||
final isErrorForCurrentHost = errorMsg.contains(" ${currentHost} ");
|
||||
|
||||
if (currentHost != null && isErrorForCurrentHost)
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
},
|
||||
onDone: () {
|
||||
unterminatedString = '';
|
||||
if (host == socket?.address.host) {
|
||||
socket = null;
|
||||
_setConnectionStatus(ConnectionStatus.disconnected);
|
||||
}
|
||||
},
|
||||
cancelOnError: true,
|
||||
);
|
||||
|
||||
keepAlive();
|
||||
}
|
||||
|
||||
|
@ -144,9 +179,9 @@ class ElectrumClient {
|
|||
Future<void> ping() async {
|
||||
try {
|
||||
await callWithTimeout(method: 'server.ping');
|
||||
_setIsConnected(true);
|
||||
_setConnectionStatus(ConnectionStatus.connected);
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
_setIsConnected(null);
|
||||
_setConnectionStatus(ConnectionStatus.disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,9 +271,24 @@ class ElectrumClient {
|
|||
return [];
|
||||
});
|
||||
|
||||
Future<Map<String, dynamic>> getTransactionRaw({required String hash}) async =>
|
||||
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, true], timeout: 10000)
|
||||
.then((dynamic result) {
|
||||
Future<dynamic> getTransaction({required String hash, required bool verbose}) async {
|
||||
try {
|
||||
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>) {
|
||||
return result;
|
||||
}
|
||||
|
@ -246,9 +296,8 @@ class ElectrumClient {
|
|||
return <String, dynamic>{};
|
||||
});
|
||||
|
||||
Future<String> getTransactionHex({required String hash}) async =>
|
||||
callWithTimeout(method: 'blockchain.transaction.get', params: [hash, false], timeout: 10000)
|
||||
.then((dynamic result) {
|
||||
Future<String> getTransactionHex({required String hash}) =>
|
||||
getTransaction(hash: hash, verbose: false).then((dynamic result) {
|
||||
if (result is String) {
|
||||
return result;
|
||||
}
|
||||
|
@ -336,7 +385,7 @@ class ElectrumClient {
|
|||
try {
|
||||
final topDoubleString = await estimatefee(p: 1);
|
||||
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 middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round();
|
||||
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round();
|
||||
|
@ -353,19 +402,18 @@ class ElectrumClient {
|
|||
// "height": 520481,
|
||||
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||
// }
|
||||
BehaviorSubject<Map<String, dynamic>>? tipListener;
|
||||
int? currentTip;
|
||||
|
||||
Future<int?> getCurrentBlockChainTip() async {
|
||||
try {
|
||||
final method = 'blockchain.headers.subscribe';
|
||||
final cb = (result) => currentTip = result['height'] as int;
|
||||
if (tipListener == null) {
|
||||
tipListener = subscribe(id: method, method: method);
|
||||
tipListener?.listen(cb);
|
||||
callWithTimeout(method: method).then(cb);
|
||||
final result = await callWithTimeout(method: 'blockchain.headers.subscribe');
|
||||
if (result is Map<String, dynamic>) {
|
||||
return result["height"] as int;
|
||||
}
|
||||
return currentTip;
|
||||
return null;
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
return null;
|
||||
} catch (e) {
|
||||
print("getCurrentBlockChainTip: ${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -387,6 +435,10 @@ class ElectrumClient {
|
|||
BehaviorSubject<T>? subscribe<T>(
|
||||
{required String id, required String method, List<Object> params = const []}) {
|
||||
try {
|
||||
if (socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return null;
|
||||
}
|
||||
final subscription = BehaviorSubject<T>();
|
||||
_regisrySubscription(id, subscription);
|
||||
socket!.write(jsonrpc(method: method, id: _id, params: params));
|
||||
|
@ -400,6 +452,10 @@ class ElectrumClient {
|
|||
|
||||
Future<dynamic> call(
|
||||
{required String method, List<Object> params = const [], Function(int)? idCallback}) async {
|
||||
if (socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return null;
|
||||
}
|
||||
final completer = Completer<dynamic>();
|
||||
_id += 1;
|
||||
final id = _id;
|
||||
|
@ -413,6 +469,10 @@ class ElectrumClient {
|
|||
Future<dynamic> callWithTimeout(
|
||||
{required String method, List<Object> params = const [], int timeout = 4000}) async {
|
||||
try {
|
||||
if (socket == null) {
|
||||
_setConnectionStatus(ConnectionStatus.failed);
|
||||
return null;
|
||||
}
|
||||
final completer = Completer<dynamic>();
|
||||
_id += 1;
|
||||
final id = _id;
|
||||
|
@ -434,6 +494,7 @@ class ElectrumClient {
|
|||
_aliveTimer?.cancel();
|
||||
try {
|
||||
await socket?.close();
|
||||
socket = null;
|
||||
} catch (_) {}
|
||||
onConnectionStatusChange = null;
|
||||
}
|
||||
|
@ -488,12 +549,9 @@ class ElectrumClient {
|
|||
}
|
||||
}
|
||||
|
||||
void _setIsConnected(bool? isConnected) {
|
||||
if (_isConnected != isConnected) {
|
||||
onConnectionStatusChange?.call(isConnected);
|
||||
}
|
||||
|
||||
_isConnected = isConnected ?? false;
|
||||
void _setConnectionStatus(ConnectionStatus status) {
|
||||
onConnectionStatusChange?.call(status);
|
||||
_isConnected = status == ConnectionStatus.connected;
|
||||
}
|
||||
|
||||
void _handleResponse(Map<String, dynamic> response) {
|
||||
|
|
|
@ -109,5 +109,4 @@ Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
|
|||
],
|
||||
};
|
||||
|
||||
|
||||
String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!;
|
||||
String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
|
||||
import 'package:cw_bitcoin/electrum_transaction_info.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/wallet_info.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';
|
||||
|
||||
|
@ -15,13 +18,15 @@ class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$Electru
|
|||
|
||||
abstract class ElectrumTransactionHistoryBase
|
||||
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
||||
ElectrumTransactionHistoryBase({required this.walletInfo, required String password})
|
||||
ElectrumTransactionHistoryBase(
|
||||
{required this.walletInfo, required String password, required this.encryptionFileUtils})
|
||||
: _password = password,
|
||||
_height = 0 {
|
||||
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
||||
}
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
String _password;
|
||||
int _height;
|
||||
|
||||
|
@ -44,7 +49,7 @@ abstract class ElectrumTransactionHistoryBase
|
|||
txjson[tx.key] = tx.value.toJson();
|
||||
}
|
||||
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) {
|
||||
print('Error while save bitcoin transaction history: ${e.toString()}');
|
||||
}
|
||||
|
@ -58,7 +63,7 @@ abstract class ElectrumTransactionHistoryBase
|
|||
Future<Map<String, dynamic>> _read() async {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final content = await read(path: path, password: _password);
|
||||
final content = await encryptionFileUtils.read(path: path, password: _password);
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/address_from_output.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
|
@ -7,10 +9,12 @@ import 'package:cw_core/transaction_direction.dart';
|
|||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
|
||||
class ElectrumTransactionBundle {
|
||||
ElectrumTransactionBundle(this.originalTransaction,
|
||||
{required this.ins, required this.confirmations, this.time});
|
||||
|
||||
final BtcTransaction originalTransaction;
|
||||
final List<BtcTransaction> ins;
|
||||
final int? time;
|
||||
|
@ -22,7 +26,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
|
||||
ElectrumTransactionInfo(this.type,
|
||||
{required String id,
|
||||
required int height,
|
||||
int? height,
|
||||
required int amount,
|
||||
int? fee,
|
||||
List<String>? inputAddresses,
|
||||
|
@ -99,7 +103,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
|
||||
factory ElectrumTransactionInfo.fromElectrumBundle(
|
||||
ElectrumTransactionBundle bundle, WalletType type, BasedUtxoNetwork network,
|
||||
{required Set<String> addresses, required int height}) {
|
||||
{required Set<String> addresses, int? height}) {
|
||||
final date = bundle.time != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(bundle.time! * 1000)
|
||||
: DateTime.now();
|
||||
|
@ -125,7 +129,24 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
for (final out in bundle.originalTransaction.outputs) {
|
||||
totalOutAmount += out.amount.toInt();
|
||||
final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
|
||||
outputAddresses.add(addressFromOutputScript(out.scriptPubKey, network));
|
||||
final address = addressFromOutputScript(out.scriptPubKey, network);
|
||||
|
||||
if (address.isNotEmpty) outputAddresses.add(address);
|
||||
|
||||
// Check if the script contains OP_RETURN
|
||||
final script = out.scriptPubKey.script;
|
||||
if (script.contains('OP_RETURN')) {
|
||||
final index = script.indexOf('OP_RETURN');
|
||||
if (index + 1 <= script.length) {
|
||||
try {
|
||||
final opReturnData = script[index + 1].toString();
|
||||
final decodedString = utf8.decode(HEX.decode(opReturnData));
|
||||
outputAddresses.add('OP_RETURN:$decodedString');
|
||||
} catch (_) {
|
||||
outputAddresses.add('OP_RETURN:');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addressExists) {
|
||||
receivedAmounts.add(out.amount.toInt());
|
||||
|
@ -235,6 +256,6 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
}
|
||||
|
||||
String toString() {
|
||||
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, confirmations: $confirmations, to: $to, unspent: $unspents)';
|
||||
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
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/bitcoin_address_record.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
|
@ -31,7 +30,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
Map<String, int>? initialChangeAddressIndex,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
int initialSilentAddressIndex = 0,
|
||||
bitcoin.HDWallet? masterHd,
|
||||
Bip32Slip10Secp256k1? masterHd,
|
||||
BitcoinAddressType? initialAddressPageType,
|
||||
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||
addressesByReceiveType =
|
||||
|
@ -54,9 +53,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
super(walletInfo) {
|
||||
if (masterHd != null) {
|
||||
silentAddress = SilentPaymentOwner.fromPrivateKeys(
|
||||
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!),
|
||||
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
|
||||
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
|
||||
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()),
|
||||
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()),
|
||||
network: network,
|
||||
);
|
||||
|
||||
if (silentAddresses.length == 0) {
|
||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||
|
@ -93,8 +93,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
final BasedUtxoNetwork network;
|
||||
final bitcoin.HDWallet mainHd;
|
||||
final bitcoin.HDWallet sideHd;
|
||||
final Bip32Slip10Secp256k1 mainHd;
|
||||
final Bip32Slip10Secp256k1 sideHd;
|
||||
|
||||
@observable
|
||||
SilentPaymentOwner? silentAddress;
|
||||
|
@ -319,14 +319,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
return address;
|
||||
}
|
||||
|
||||
String getAddress(
|
||||
{required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
|
||||
String getAddress({
|
||||
required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType,
|
||||
}) =>
|
||||
'';
|
||||
|
||||
Future<String> getAddressAsync(
|
||||
{required int index,
|
||||
required bitcoin.HDWallet hd,
|
||||
BitcoinAddressType? addressType}) async =>
|
||||
Future<String> getAddressAsync({
|
||||
required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType,
|
||||
}) async =>
|
||||
getAddress(index: index, hd: hd, addressType: addressType);
|
||||
|
||||
void addBitcoinAddressTypes() {
|
||||
|
@ -414,7 +418,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
} else {
|
||||
addressesMap[address] = 'Active - P2WPKH';
|
||||
}
|
||||
|
||||
|
||||
final lastMweb = _addresses.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb));
|
||||
if (lastMweb.address != address) {
|
||||
|
@ -575,8 +579,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
void _validateAddresses() {
|
||||
_addresses.forEach((element) {
|
||||
if (!element.isHidden && element.address !=
|
||||
getAddress(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
if (!element.isHidden &&
|
||||
element.address !=
|
||||
getAddress(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
element.isHidden = true;
|
||||
} else if (element.isHidden &&
|
||||
element.address !=
|
||||
|
@ -598,7 +603,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
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 _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
|
||||
!addr.isHidden && !addr.isUsed && addr.type == type;
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.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_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -32,22 +33,28 @@ class ElectrumWalletSnapshot {
|
|||
final WalletType type;
|
||||
final String? addressPageType;
|
||||
|
||||
@deprecated
|
||||
String? mnemonic;
|
||||
|
||||
@deprecated
|
||||
String? xpub;
|
||||
|
||||
@deprecated
|
||||
String? passphrase;
|
||||
|
||||
List<BitcoinAddressRecord> addresses;
|
||||
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
ElectrumBalance balance;
|
||||
Map<String, int> regularAddressIndex;
|
||||
Map<String, int> changeAddressIndex;
|
||||
int silentAddressIndex;
|
||||
String? passphrase;
|
||||
DerivationType? derivationType;
|
||||
String? derivationPath;
|
||||
|
||||
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 jsonSource = await read(path: path, password: password);
|
||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
||||
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,39 +1,46 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:convert/convert.dart' as convert;
|
||||
import 'dart:math';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/mweb_utxo.dart';
|
||||
import 'package:cw_mweb/mwebd.pbgrpc.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:blockchain_utils/signer/ecdsa_signing_key.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/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_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:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.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:cw_mweb/cw_mweb.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
|
||||
import 'package:pointycastle/ecc/api.dart';
|
||||
import 'package:pointycastle/ecc/curves/secp256k1.dart';
|
||||
|
||||
part 'litecoin_wallet.g.dart';
|
||||
|
||||
|
@ -46,6 +53,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required Uint8List seedBytes,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
String? passphrase,
|
||||
String? addressPageType,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
|
@ -53,19 +62,19 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
Map<String, int>? initialChangeAddressIndex,
|
||||
int? initialMwebHeight,
|
||||
bool? alwaysScan,
|
||||
}) : mwebHd =
|
||||
bitcoin.HDWallet.fromSeed(seedBytes, network: litecoinNetwork).derivePath("m/1000'"),
|
||||
super(
|
||||
}) : super(
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: litecoinNetwork,
|
||||
network: LitecoinNetwork.mainnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
currency: CryptoCurrency.ltc,
|
||||
) {
|
||||
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
|
||||
mwebEnabled = alwaysScan ?? false;
|
||||
walletAddresses = LitecoinWalletAddresses(
|
||||
walletInfo,
|
||||
|
@ -73,31 +82,32 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.derive(1),
|
||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||
network: network,
|
||||
mwebHd: mwebHd,
|
||||
mwebEnabled: mwebEnabled,
|
||||
);
|
||||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
});
|
||||
CwMweb.stub().then((value) {
|
||||
_stub = value;
|
||||
});
|
||||
}
|
||||
|
||||
final bitcoin.HDWallet mwebHd;
|
||||
late final Bip32Slip10Secp256k1 mwebHd;
|
||||
late final Box<MwebUtxo> mwebUtxosBox;
|
||||
Timer? _syncTimer;
|
||||
Timer? _feeRatesTimer;
|
||||
StreamSubscription<Utxo>? _utxoStream;
|
||||
int mwebUtxosHeight = 0;
|
||||
late RpcClient _stub;
|
||||
late bool mwebEnabled;
|
||||
|
||||
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||
List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
|
||||
|
||||
static Future<LitecoinWallet> create(
|
||||
{required String mnemonic,
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
String? passphrase,
|
||||
String? addressPageType,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
|
@ -115,7 +125,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
break;
|
||||
case DerivationType.electrum:
|
||||
default:
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic);
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
||||
break;
|
||||
}
|
||||
return LitecoinWallet(
|
||||
|
@ -125,6 +135,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
passphrase: passphrase,
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
|
@ -138,20 +150,76 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required String password,
|
||||
required bool alwaysScan,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
}) async {
|
||||
final snp =
|
||||
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
walletInfo.derivationInfo ??= DerivationInfo();
|
||||
|
||||
// set the default if not present:
|
||||
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
|
||||
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
|
||||
|
||||
Uint8List? seedBytes = null;
|
||||
final mnemonic = keysData.mnemonic;
|
||||
final passphrase = keysData.passphrase;
|
||||
|
||||
if (mnemonic != null) {
|
||||
switch (walletInfo.derivationInfo?.derivationType) {
|
||||
case DerivationType.bip39:
|
||||
seedBytes = await bip39.mnemonicToSeed(
|
||||
mnemonic,
|
||||
passphrase: passphrase ?? "",
|
||||
);
|
||||
break;
|
||||
case DerivationType.electrum:
|
||||
default:
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return LitecoinWallet(
|
||||
mnemonic: snp.mnemonic!,
|
||||
mnemonic: keysData.mnemonic!,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialBalance: snp.balance,
|
||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic!),
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
initialAddresses: snp?.addresses,
|
||||
initialBalance: snp?.balance,
|
||||
seedBytes: seedBytes!,
|
||||
passphrase: passphrase,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||
addressPageType: snp?.addressPageType,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
}
|
||||
|
@ -159,57 +227,72 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
|
||||
syncStatus = SyncronizingSyncStatus();
|
||||
await subscribeForUpdates();
|
||||
await updateTransactions();
|
||||
await updateFeeRates();
|
||||
|
||||
_feeRatesTimer?.cancel();
|
||||
_feeRatesTimer =
|
||||
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
|
||||
|
||||
if (!mwebEnabled) {
|
||||
syncStatus = SyncronizingSyncStatus();
|
||||
await subscribeForUpdates();
|
||||
await updateTransactions();
|
||||
syncStatus = SyncedSyncStatus();
|
||||
try {
|
||||
await updateAllUnspents();
|
||||
await updateBalance();
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e, s) {
|
||||
print(e);
|
||||
print(s);
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// await subscribeForUpdates();
|
||||
// await updateTransactions();
|
||||
// await updateFeeRates();
|
||||
await getStub();
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
|
||||
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
|
||||
|
||||
_stub = await CwMweb.stub();
|
||||
_syncTimer?.cancel();
|
||||
_syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) async {
|
||||
if (syncStatus is FailedSyncStatus) return;
|
||||
final height = await electrumClient.getCurrentBlockChainTip() ?? 0;
|
||||
final resp = await _stub.status(StatusRequest());
|
||||
if (resp.blockHeaderHeight < height) {
|
||||
int h = resp.blockHeaderHeight;
|
||||
syncStatus = SyncingSyncStatus(height - h, h / height);
|
||||
} else if (resp.mwebHeaderHeight < height) {
|
||||
int h = resp.mwebHeaderHeight;
|
||||
syncStatus = SyncingSyncStatus(height - h, h / height);
|
||||
} else if (resp.mwebUtxosHeight < height) {
|
||||
syncStatus = SyncingSyncStatus(1, 0.999);
|
||||
} else {
|
||||
// prevent unnecessary reaction triggers:
|
||||
if (syncStatus is! SyncedSyncStatus) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
}
|
||||
// delay the timer by a second so we don't overrride the restoreheight if one is set
|
||||
Timer(const Duration(seconds: 1), () async {
|
||||
_syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) async {
|
||||
if (syncStatus is FailedSyncStatus) return;
|
||||
final nodeHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
|
||||
final resp = await _stub.status(StatusRequest());
|
||||
|
||||
if (resp.mwebUtxosHeight > mwebUtxosHeight) {
|
||||
mwebUtxosHeight = resp.mwebUtxosHeight;
|
||||
await checkMwebUtxosSpent();
|
||||
// update the confirmations for each transaction:
|
||||
for (final transaction in transactionHistory.transactions.values) {
|
||||
if (transaction.isPending) continue;
|
||||
final confirmations = mwebUtxosHeight - transaction.height + 1;
|
||||
if (transaction.confirmations == confirmations) continue;
|
||||
transaction.confirmations = confirmations;
|
||||
transactionHistory.addOne(transaction);
|
||||
if (resp.blockHeaderHeight < nodeHeight) {
|
||||
int h = resp.blockHeaderHeight;
|
||||
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
|
||||
} else if (resp.mwebHeaderHeight < nodeHeight) {
|
||||
int h = resp.mwebHeaderHeight;
|
||||
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
|
||||
} else if (resp.mwebUtxosHeight < nodeHeight) {
|
||||
syncStatus = SyncingSyncStatus(1, 0.999);
|
||||
} else {
|
||||
// prevent unnecessary reaction triggers:
|
||||
if (syncStatus is! SyncedSyncStatus) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
}
|
||||
|
||||
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
|
||||
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
|
||||
await checkMwebUtxosSpent();
|
||||
// update the confirmations for each transaction:
|
||||
for (final transaction in transactionHistory.transactions.values) {
|
||||
if (transaction.isPending) continue;
|
||||
int txHeight = transaction.height ?? resp.mwebUtxosHeight;
|
||||
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
|
||||
if (transaction.confirmations == confirmations) continue;
|
||||
transaction.confirmations = confirmations;
|
||||
transactionHistory.addOne(transaction);
|
||||
}
|
||||
await transactionHistory.save();
|
||||
}
|
||||
await transactionHistory.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// updateUnspent();
|
||||
// fetchBalances();
|
||||
// this runs in the background and processes new utxos as they come in:
|
||||
processMwebUtxos();
|
||||
}
|
||||
|
@ -252,11 +335,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
bool? doSingleScan,
|
||||
bool? usingElectrs,
|
||||
}) async {
|
||||
await mwebUtxosBox.clear();
|
||||
transactionHistory.clear();
|
||||
mwebUtxosHeight = height;
|
||||
_syncTimer?.cancel();
|
||||
int oldHeight = walletInfo.restoreHeight;
|
||||
await walletInfo.updateRestoreHeight(height);
|
||||
|
||||
// go through mwebUtxos and clear any that are above the new restore height:
|
||||
if (height == 0) {
|
||||
await mwebUtxosBox.clear();
|
||||
transactionHistory.clear();
|
||||
} else {
|
||||
for (final utxo in mwebUtxosBox.values) {
|
||||
if (utxo.height > height) {
|
||||
await mwebUtxosBox.delete(utxo.outputId);
|
||||
}
|
||||
}
|
||||
// TODO: remove transactions that are above the new restore height!
|
||||
}
|
||||
|
||||
// reset coin balances and txCount to 0:
|
||||
unspentCoins.forEach((coin) {
|
||||
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
||||
|
@ -269,7 +364,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
addressRecord.txCount = 0;
|
||||
}
|
||||
|
||||
print("STARTING SYNC");
|
||||
await startSync();
|
||||
}
|
||||
|
||||
|
@ -324,6 +418,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address);
|
||||
if (addressRecord == null) {
|
||||
print("we don't have this address in the wallet! ${utxo.address}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -348,27 +443,25 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
|
||||
Future<void> processMwebUtxos() async {
|
||||
final scanSecret = mwebHd.derive(0x80000000).privKey!;
|
||||
int restoreHeight = walletInfo.restoreHeight;
|
||||
print("SCANNING FROM HEIGHT: $restoreHeight");
|
||||
final req = UtxosRequest(scanSecret: hex.decode(scanSecret), fromHeight: restoreHeight);
|
||||
final req = UtxosRequest(scanSecret: scanSecret, fromHeight: restoreHeight);
|
||||
|
||||
// process old utxos:
|
||||
for (final utxo in mwebUtxosBox.values) {
|
||||
if (utxo.address.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
// for (final utxo in mwebUtxosBox.values) {
|
||||
// if (utxo.address.isEmpty) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (walletInfo.restoreHeight > utxo.height) {
|
||||
// continue;
|
||||
// }
|
||||
// // if (walletInfo.restoreHeight > utxo.height) {
|
||||
// // continue;
|
||||
// // }
|
||||
// // await handleIncoming(utxo, _stub);
|
||||
|
||||
await handleIncoming(utxo, _stub);
|
||||
|
||||
if (utxo.height > walletInfo.restoreHeight) {
|
||||
await walletInfo.updateRestoreHeight(utxo.height);
|
||||
}
|
||||
}
|
||||
// if (utxo.height > walletInfo.restoreHeight) {
|
||||
// await walletInfo.updateRestoreHeight(utxo.height);
|
||||
// }
|
||||
// }
|
||||
|
||||
// process new utxos as they come in:
|
||||
_utxoStream?.cancel();
|
||||
|
@ -381,10 +474,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
value: sUtxo.value.toInt(),
|
||||
);
|
||||
|
||||
if (mwebUtxosBox.containsKey(utxo.outputId)) {
|
||||
// we've already stored this utxo, skip it:
|
||||
return;
|
||||
}
|
||||
// if (mwebUtxosBox.containsKey(utxo.outputId)) {
|
||||
// // we've already stored this utxo, skip it:
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (utxo.address.isEmpty) {
|
||||
// await updateUnspent();
|
||||
|
@ -425,7 +518,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
if (status.mwebUtxosHeight != height) return;
|
||||
int amount = 0;
|
||||
Set<String> inputAddresses = {};
|
||||
var output = AccumulatorSink<Digest>();
|
||||
var output = convert.AccumulatorSink<Digest>();
|
||||
var input = sha256.startChunkedConversion(output);
|
||||
for (final outputId in spent) {
|
||||
final utxo = mwebUtxosBox.get(outputId);
|
||||
|
@ -435,7 +528,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
|
||||
if (!inputAddresses.contains(utxo.address)) {
|
||||
addressRecord.txCount++;
|
||||
// print("COUNT UPDATED HERE 3!!!!! ${addressRecord.address} ${addressRecord.txCount} !!!!!!");
|
||||
}
|
||||
addressRecord.balance -= utxo.value.toInt();
|
||||
amount += utxo.value.toInt();
|
||||
|
@ -494,69 +586,76 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateUnspent() async {
|
||||
await super.updateUnspent();
|
||||
await checkMwebUtxosSpent();
|
||||
await updateAllUnspents();
|
||||
}
|
||||
|
||||
@override
|
||||
@action
|
||||
Future<void> updateAllUnspents() async {
|
||||
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||
// get ltc unspents:
|
||||
await super.updateAllUnspents();
|
||||
|
||||
await Future.wait(walletAddresses.allAddresses.map((address) async {
|
||||
updatedUnspentCoins.addAll(await fetchUnspent(address));
|
||||
}));
|
||||
|
||||
if (mwebEnabled) {
|
||||
// update mweb unspents:
|
||||
final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
|
||||
mwebUtxosBox.keys.forEach((dynamic oId) {
|
||||
final String outputId = oId as String;
|
||||
final utxo = mwebUtxosBox.get(outputId);
|
||||
if (utxo == null) {
|
||||
return;
|
||||
}
|
||||
if (utxo.address.isEmpty) {
|
||||
// not sure if a bug or a special case but we definitely ignore these
|
||||
return;
|
||||
}
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address);
|
||||
|
||||
if (addressRecord == null) {
|
||||
print("utxo contains an address that is not in the wallet: ${utxo.address}");
|
||||
return;
|
||||
}
|
||||
final unspent = BitcoinUnspent(
|
||||
addressRecord,
|
||||
outputId,
|
||||
utxo.value.toInt(),
|
||||
mwebAddrs.indexOf(utxo.address),
|
||||
);
|
||||
if (unspent.vout == 0) {
|
||||
unspent.isChange = true;
|
||||
}
|
||||
updatedUnspentCoins.add(unspent);
|
||||
});
|
||||
if (!mwebEnabled) {
|
||||
return;
|
||||
}
|
||||
await getStub();
|
||||
|
||||
unspentCoins = updatedUnspentCoins;
|
||||
// add the mweb unspents to the list:
|
||||
List<BitcoinUnspent> mwebUnspentCoins = [];
|
||||
// update mweb unspents:
|
||||
final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
|
||||
mwebUtxosBox.keys.forEach((dynamic oId) {
|
||||
final String outputId = oId as String;
|
||||
final utxo = mwebUtxosBox.get(outputId);
|
||||
if (utxo == null) {
|
||||
return;
|
||||
}
|
||||
if (utxo.address.isEmpty) {
|
||||
// not sure if a bug or a special case but we definitely ignore these
|
||||
return;
|
||||
}
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address);
|
||||
|
||||
if (addressRecord == null) {
|
||||
print("utxo contains an address that is not in the wallet: ${utxo.address}");
|
||||
return;
|
||||
}
|
||||
final unspent = BitcoinUnspent(
|
||||
addressRecord,
|
||||
outputId,
|
||||
utxo.value.toInt(),
|
||||
mwebAddrs.indexOf(utxo.address),
|
||||
);
|
||||
if (unspent.vout == 0) {
|
||||
unspent.isChange = true;
|
||||
}
|
||||
mwebUnspentCoins.add(unspent);
|
||||
});
|
||||
unspentCoins.addAll(mwebUnspentCoins);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ElectrumBalance> fetchBalances() async {
|
||||
final balance = await super.fetchBalances();
|
||||
var confirmed = balance.confirmed;
|
||||
var unconfirmed = balance.unconfirmed;
|
||||
mwebUtxosBox.values.forEach((utxo) {
|
||||
if (utxo.height > 0) {
|
||||
confirmed += utxo.value.toInt();
|
||||
} else {
|
||||
unconfirmed += utxo.value.toInt();
|
||||
}
|
||||
});
|
||||
if (!mwebEnabled) {
|
||||
return balance;
|
||||
}
|
||||
await getStub();
|
||||
|
||||
int confirmed = balance.confirmed;
|
||||
int unconfirmed = balance.unconfirmed;
|
||||
try {
|
||||
mwebUtxosBox.values.forEach((utxo) {
|
||||
if (utxo.height > 0) {
|
||||
confirmed += utxo.value.toInt();
|
||||
} else {
|
||||
unconfirmed += utxo.value.toInt();
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
|
||||
// update unspent balances:
|
||||
|
||||
|
@ -566,6 +665,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
// coin.bitcoinAddressRecord.balance = 0;
|
||||
// coin.bitcoinAddressRecord.txCount = 0;
|
||||
// });
|
||||
|
||||
await updateUnspent();
|
||||
|
||||
for (var addressRecord in walletAddresses.allAddresses) {
|
||||
addressRecord.balance = 0;
|
||||
addressRecord.txCount = 0;
|
||||
|
@ -594,10 +696,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
|
||||
// update the txCount for each address using the tx history, since we can't rely on mwebd
|
||||
// to have an accurate count, we should just keep it in sync with what we know from the tx history:
|
||||
for (var tx in transactionHistory.transactions.values) {
|
||||
if (tx.isPending) continue;
|
||||
for (final tx in transactionHistory.transactions.values) {
|
||||
// if (tx.isPending) continue;
|
||||
if (tx.inputAddresses == null || tx.outputAddresses == null) {
|
||||
continue;
|
||||
}
|
||||
final txAddresses = tx.inputAddresses! + tx.outputAddresses!;
|
||||
for (var address in txAddresses) {
|
||||
for (final address in txAddresses) {
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhereOrNull((addressRecord) => addressRecord.address == address);
|
||||
if (addressRecord == null) {
|
||||
|
@ -607,8 +712,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
}
|
||||
|
||||
await updateUnspent();
|
||||
|
||||
return ElectrumBalance(confirmed: confirmed, unconfirmed: unconfirmed, frozen: balance.frozen);
|
||||
}
|
||||
|
||||
|
@ -665,8 +768,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network);
|
||||
final resp = await _stub.create(CreateRequest(
|
||||
rawTx: txb.buildTransaction((a, b, c, d) => '').toBytes(),
|
||||
scanSecret: hex.decode(mwebHd.derive(0x80000000).privKey!),
|
||||
spendSecret: hex.decode(mwebHd.derive(0x80000001).privKey!),
|
||||
scanSecret: scanSecret,
|
||||
spendSecret: spendSecret,
|
||||
feeRatePerKb: Int64(feeRate * 1000),
|
||||
dryRun: true));
|
||||
final tx = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
|
||||
|
@ -702,11 +805,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
if (!mwebEnabled) {
|
||||
return tx;
|
||||
}
|
||||
await getStub();
|
||||
|
||||
final resp = await _stub.create(CreateRequest(
|
||||
rawTx: hex.decode(tx.hex),
|
||||
scanSecret: hex.decode(mwebHd.derive(0x80000000).privKey!),
|
||||
spendSecret: hex.decode(mwebHd.derive(0x80000001).privKey!),
|
||||
scanSecret: scanSecret,
|
||||
spendSecret: spendSecret,
|
||||
feeRatePerKb: Int64.parseInt(tx.feeRate) * 1000,
|
||||
));
|
||||
final tx2 = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
|
||||
|
@ -768,7 +872,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
@override
|
||||
Future<void> close() async {
|
||||
await super.close();
|
||||
await mwebUtxosBox.close();
|
||||
_syncTimer?.cancel();
|
||||
_utxoStream?.cancel();
|
||||
}
|
||||
|
@ -779,6 +882,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
|
||||
mwebEnabled = enabled;
|
||||
(walletAddresses as LitecoinWalletAddresses).mwebEnabled = enabled;
|
||||
if (enabled) {
|
||||
// generate inital mweb addresses:
|
||||
(walletAddresses as LitecoinWalletAddresses).topUpMweb(0);
|
||||
}
|
||||
stopSync();
|
||||
startSync();
|
||||
}
|
||||
|
@ -793,4 +901,127 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
final resp = await _stub.status(StatusRequest());
|
||||
return resp;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address = null}) async {
|
||||
final index = address != null
|
||||
? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
|
||||
: null;
|
||||
final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
|
||||
final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex());
|
||||
|
||||
final privateKey = ECDSAPrivateKey.fromBytes(
|
||||
priv.toBytes(),
|
||||
Curves.generatorSecp256k1,
|
||||
);
|
||||
|
||||
final signature =
|
||||
signLitecoinMessage(utf8.encode(message), privateKey: privateKey, bipPrive: priv.prive);
|
||||
|
||||
return base64Encode(signature);
|
||||
}
|
||||
|
||||
List<int> _magicPrefix(List<int> message, List<int> messagePrefix) {
|
||||
final encodeLength = IntUtils.encodeVarint(message.length);
|
||||
|
||||
return [...messagePrefix, ...encodeLength, ...message];
|
||||
}
|
||||
|
||||
List<int> signLitecoinMessage(List<int> message,
|
||||
{required ECDSAPrivateKey privateKey, required Bip32PrivateKey bipPrive}) {
|
||||
String messagePrefix = '\x19Litecoin Signed Message:\n';
|
||||
final messageHash = QuickCrypto.sha256Hash(magicMessage(message, messagePrefix));
|
||||
final signingKey = EcdsaSigningKey(privateKey);
|
||||
ECDSASignature ecdsaSign =
|
||||
signingKey.signDigestDeterminstic(digest: messageHash, hashFunc: () => SHA256());
|
||||
final n = Curves.generatorSecp256k1.order! >> 1;
|
||||
BigInt newS;
|
||||
if (ecdsaSign.s.compareTo(n) > 0) {
|
||||
newS = Curves.generatorSecp256k1.order! - ecdsaSign.s;
|
||||
} else {
|
||||
newS = ecdsaSign.s;
|
||||
}
|
||||
final rawSig = ECDSASignature(ecdsaSign.r, newS);
|
||||
final rawSigBytes = rawSig.toBytes(BitcoinSignerUtils.baselen);
|
||||
|
||||
final pub = bipPrive.publicKey;
|
||||
final ECDomainParameters curve = ECCurve_secp256k1();
|
||||
final point = curve.curve.decodePoint(pub.point.toBytes());
|
||||
|
||||
final rawSigEc = ECSignature(rawSig.r, rawSig.s);
|
||||
|
||||
final recId = SignUtils.findRecoveryId(
|
||||
SignUtils.getHexString(messageHash, offset: 0, length: messageHash.length),
|
||||
rawSigEc,
|
||||
Uint8List.fromList(pub.uncompressed),
|
||||
);
|
||||
|
||||
final v = recId + 27 + (point!.isCompressed ? 4 : 0);
|
||||
|
||||
final combined = Uint8List.fromList([v, ...rawSigBytes]);
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
List<int> magicMessage(List<int> message, String messagePrefix) {
|
||||
final prefixBytes = StringUtils.encode(messagePrefix);
|
||||
final magic = _magicPrefix(message, prefixBytes);
|
||||
return QuickCrypto.sha256Hash(magic);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
|
||||
if (address == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<int> sigDecodedBytes = [];
|
||||
|
||||
if (signature.endsWith('=')) {
|
||||
sigDecodedBytes = base64.decode(signature);
|
||||
} else {
|
||||
sigDecodedBytes = hex.decode(signature);
|
||||
}
|
||||
|
||||
if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) {
|
||||
throw ArgumentException(
|
||||
"litecoin signature must be 64 bytes without recover-id or 65 bytes with recover-id");
|
||||
}
|
||||
|
||||
String messagePrefix = '\x19Litecoin Signed Message:\n';
|
||||
final messageHash = QuickCrypto.sha256Hash(magicMessage(utf8.encode(message), messagePrefix));
|
||||
|
||||
List<int> correctSignature =
|
||||
sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes);
|
||||
List<int> rBytes = correctSignature.sublist(0, 32);
|
||||
List<int> sBytes = correctSignature.sublist(32);
|
||||
final sig = ECDSASignature(BigintUtils.fromBytes(rBytes), BigintUtils.fromBytes(sBytes));
|
||||
|
||||
List<int> possibleRecoverIds = [0, 1];
|
||||
|
||||
final baseAddress = addressTypeFromStr(address, network);
|
||||
|
||||
for (int recoveryId in possibleRecoverIds) {
|
||||
final pubKey = sig.recoverPublicKey(messageHash, Curves.generatorSecp256k1, recoveryId);
|
||||
final recoveredPub = ECPublic.fromBytes(pubKey!.toBytes());
|
||||
|
||||
String? recoveredAddress;
|
||||
|
||||
if (baseAddress is P2pkAddress) {
|
||||
recoveredAddress = recoveredPub.toP2pkAddress().toAddress(network);
|
||||
} else if (baseAddress is P2pkhAddress) {
|
||||
recoveredAddress = recoveredPub.toP2pkhAddress().toAddress(network);
|
||||
} else if (baseAddress is P2wshAddress) {
|
||||
recoveredAddress = recoveredPub.toP2wshAddress().toAddress(network);
|
||||
} else if (baseAddress is P2wpkhAddress) {
|
||||
recoveredAddress = recoveredPub.toP2wpkhAddress().toAddress(network);
|
||||
}
|
||||
|
||||
if (recoveredAddress == address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:convert/convert.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/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -17,29 +17,36 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
WalletInfo walletInfo, {
|
||||
required super.mainHd,
|
||||
required super.sideHd,
|
||||
required this.mwebHd,
|
||||
required super.network,
|
||||
required this.mwebHd,
|
||||
required this.mwebEnabled,
|
||||
super.initialAddresses,
|
||||
super.initialRegularAddressIndex,
|
||||
super.initialChangeAddressIndex,
|
||||
}) : super(walletInfo) {
|
||||
topUpMweb(0);
|
||||
if (mwebEnabled) {
|
||||
topUpMweb(0);
|
||||
}
|
||||
}
|
||||
|
||||
final HDWallet mwebHd;
|
||||
final Bip32Slip10Secp256k1 mwebHd;
|
||||
bool mwebEnabled;
|
||||
|
||||
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||
List<int> get spendPubkey =>
|
||||
mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
|
||||
|
||||
List<String> mwebAddrs = [];
|
||||
|
||||
Future<void> topUpMweb(int index) async {
|
||||
while (mwebAddrs.length - index < 1000) {
|
||||
final length = mwebAddrs.length;
|
||||
final scanSecret = mwebHd.derive(0x80000000).privKey!;
|
||||
final spendPubkey = mwebHd.derive(0x80000001).pubKey!;
|
||||
final stub = await CwMweb.stub();
|
||||
final resp = await stub.addresses(AddressRequest(
|
||||
fromIndex: length,
|
||||
toIndex: index + 1000,
|
||||
scanSecret: hex.decode(scanSecret),
|
||||
spendPubkey: hex.decode(spendPubkey),
|
||||
scanSecret: scanSecret,
|
||||
spendPubkey: spendPubkey,
|
||||
));
|
||||
if (mwebAddrs.length == length) {
|
||||
mwebAddrs.addAll(resp.address);
|
||||
|
@ -48,8 +55,12 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
}
|
||||
|
||||
@override
|
||||
String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
|
||||
if (addressType == SegwitAddresType.mweb) {
|
||||
String getAddress({
|
||||
required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType,
|
||||
}) {
|
||||
if (addressType == SegwitAddresType.mweb && mwebEnabled) {
|
||||
topUpMweb(index);
|
||||
return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1];
|
||||
}
|
||||
|
@ -57,9 +68,16 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
}
|
||||
|
||||
@override
|
||||
Future<String> getAddressAsync(
|
||||
{required int index, required HDWallet hd, BitcoinAddressType? addressType}) async {
|
||||
if (addressType == SegwitAddresType.mweb) {
|
||||
Future<String> getAddressAsync({
|
||||
required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType,
|
||||
}) async {
|
||||
// if mweb isn't enabled we'll just return the regular address type which does effectively nothing
|
||||
// sort of a hack but easier than trying to pull the mweb setting into the electrum_wallet_addresses initialization code
|
||||
// (we want to avoid initializing the mweb.stub() if it's not enabled or we'd be starting the whole server for no reason and it's slow)
|
||||
// TODO: find a way to do address generation without starting the whole mweb server
|
||||
if (addressType == SegwitAddresType.mweb && mwebEnabled) {
|
||||
await topUpMweb(index);
|
||||
}
|
||||
return getAddress(index: index, hd: hd, addressType: addressType);
|
||||
|
@ -68,11 +86,10 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
@action
|
||||
@override
|
||||
Future<String> getChangeAddress() async {
|
||||
// super.getChangeAddress();
|
||||
// updateChangeAddresses();
|
||||
// print("getChangeAddress @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
||||
// this means all change addresses used will be mweb addresses!:
|
||||
await topUpMweb(0);
|
||||
return mwebAddrs[0];
|
||||
if (mwebEnabled) {
|
||||
await topUpMweb(0);
|
||||
return mwebAddrs[0];
|
||||
}
|
||||
return super.getChangeAddress();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'dart:io';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||
|
@ -19,10 +21,11 @@ class LitecoinWalletService extends WalletService<
|
|||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials,
|
||||
BitcoinNewWalletCredentials> {
|
||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan);
|
||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect, this.alwaysScan);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
final bool isDirect;
|
||||
final bool alwaysScan;
|
||||
|
||||
@override
|
||||
|
@ -30,12 +33,26 @@ class LitecoinWalletService extends WalletService<
|
|||
|
||||
@override
|
||||
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
final String mnemonic;
|
||||
switch ( credentials.walletInfo?.derivationInfo?.derivationType) {
|
||||
case DerivationType.bip39:
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
mnemonic = await MnemonicBip39.generate(strength: strength);
|
||||
break;
|
||||
case DerivationType.electrum:
|
||||
default:
|
||||
mnemonic = await generateElectrumMnemonic();
|
||||
break;
|
||||
}
|
||||
|
||||
final wallet = await LitecoinWalletBase.create(
|
||||
mnemonic: await generateElectrumMnemonic(),
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
|
@ -59,6 +76,7 @@ class LitecoinWalletService extends WalletService<
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.init();
|
||||
saveBackup(name);
|
||||
|
@ -71,6 +89,7 @@ class LitecoinWalletService extends WalletService<
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
@ -104,6 +123,7 @@ class LitecoinWalletService extends WalletService<
|
|||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
|
@ -135,11 +155,13 @@ class LitecoinWalletService extends WalletService<
|
|||
}
|
||||
|
||||
final wallet = await LitecoinWalletBase.create(
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
|
|
@ -1,68 +1,54 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:flutter/foundation.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)));
|
||||
}
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
|
||||
ECPrivate generateECPrivate({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final wif = hd.derive(index).wif!;
|
||||
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
|
||||
}
|
||||
}) =>
|
||||
ECPrivate(hd.childKey(Bip32KeyIndex(index)).privateKey);
|
||||
|
||||
String generateP2WPKHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toP2wpkhAddress()
|
||||
.toAddress(network);
|
||||
|
||||
String generateP2SHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toP2wpkhInP2sh()
|
||||
.toAddress(network);
|
||||
|
||||
String generateP2WSHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toP2wshAddress()
|
||||
.toAddress(network);
|
||||
|
||||
String generateP2PKHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toP2pkhAddress()
|
||||
.toAddress(network);
|
||||
|
||||
String generateP2TRAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
|
||||
}
|
||||
}) =>
|
||||
ECPublic.fromBip32(hd.childKey(Bip32KeyIndex(index)).publicKey)
|
||||
.toTaprootAddress()
|
||||
.toAddress(network);
|
||||
|
|