diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml
index 88dd2c1eb..ddc8869f0 100644
--- a/.github/workflows/pr_test_build.yml
+++ b/.github/workflows/pr_test_build.yml
@@ -6,9 +6,9 @@ on:
workflow_dispatch:
inputs:
branch:
- description: 'Branch name to build'
+ description: "Branch name to build"
required: true
- default: 'main'
+ default: "main"
jobs:
PR_test_build:
@@ -16,6 +16,7 @@ jobs:
env:
STORE_PASS: test@cake_wallet
KEY_PASS: test@cake_wallet
+ PR_NUMBER: ${{ github.event.number }}
steps:
- name: is pr
@@ -104,22 +105,14 @@ jobs:
- name: Build generated code
run: |
cd /opt/android/cake_wallet
- cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
- cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
- cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
- cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
- cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
- cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
- cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
- cd cw_ethereum && flutter pub get && cd ..
- cd cw_polygon && flutter pub get && cd ..
- flutter packages pub run build_runner build --delete-conflicting-outputs
+ ./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
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
@@ -150,49 +143,51 @@ jobs:
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
- echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
+ echo "const 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
- name: Rename app
- run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
+ 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 apk --release
+ flutter build apk --release --split-per-abi
-# - name: Push to App Center
-# run: |
-# echo 'Installing App Center CLI tools'
-# npm install -g appcenter-cli
-# echo "Publishing test to App Center"
-# appcenter distribute release \
-# --group "Testers" \
-# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
-# --release-notes ${{ env.BRANCH_NAME }} \
-# --app Cake-Labs/Cake-Wallet \
-# --token ${{ secrets.APP_CENTER_TOKEN }} \
-# --quiet
+ # - name: Push to App Center
+ # run: |
+ # echo 'Installing App Center CLI tools'
+ # npm install -g appcenter-cli
+ # echo "Publishing test to App Center"
+ # appcenter distribute release \
+ # --group "Testers" \
+ # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
+ # --release-notes ${{ env.BRANCH_NAME }} \
+ # --app Cake-Labs/Cake-Wallet \
+ # --token ${{ secrets.APP_CENTER_TOKEN }} \
+ # --quiet
- name: Rename apk file
run: |
- cd /opt/android/cake_wallet/build/app/outputs/apk/release
+ cd /opt/android/cake_wallet/build/app/outputs/flutter-apk
mkdir test-apk
- cp app-release.apk test-apk/${{env.BRANCH_NAME}}.apk
+ cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk
- name: Upload Artifact
uses: kittaakos/upload-artifact-as-is@v0
with:
- path: /opt/android/cake_wallet/build/app/outputs/apk/release/test-apk/
+ path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/
- 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/app/outputs/apk/release/app-release.apk
+ path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/${{env.BRANCH_NAME}}.apk
channel: ${{ secrets.SLACK_APK_CHANNEL }}
title: "${{ env.BRANCH_NAME }}.apk"
filename: ${{ env.BRANCH_NAME }}.apk
diff --git a/.gitignore b/.gitignore
index f084f8d0d..6f2d0a182 100644
--- a/.gitignore
+++ b/.gitignore
@@ -86,14 +86,17 @@ cw_monero/cw_monero/android/.cxx/
**/*.g.dart
android/key.properties
+android/app/key.jks
**/tool/.secrets-prod.json
**/tool/.secrets-test.json
**/tool/.secrets-config.json
**/tool/.evm-secrets-config.json
**/tool/.ethereum-secrets-config.json
+**/tool/.solana-secrets-config.json
**/lib/.secrets.g.dart
**/cw_evm/lib/.secrets.g.dart
+**/cw_solana/lib/.secrets.g.dart
vendor/
@@ -128,6 +131,7 @@ lib/ethereum/ethereum.dart
lib/bitcoin_cash/bitcoin_cash.dart
lib/nano/nano.dart
lib/polygon/polygon.dart
+lib/solana/solana.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index 2dceca577..eea9b5521 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -8,6 +8,7 @@
+
+
result.success(null));
+ break;
+ case "isBatteryOptimizationDisabled":
+ boolean isDisabled = isBatteryOptimizationDisabled();
+ handler.post(() -> result.success(isDisabled));
+ break;
default:
handler.post(() -> result.notImplemented());
}
@@ -89,4 +101,22 @@ public class MainActivity extends FlutterFragmentActivity {
}
});
}
+
+ private void disableBatteryOptimization() {
+ String packageName = getPackageName();
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ if (!pm.isIgnoringBatteryOptimizations(packageName)) {
+ Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ intent.setData(Uri.parse("package:" + packageName));
+ startActivity(intent);
+ }
+ }
+
+ private boolean isBatteryOptimizationDisabled() {
+ String packageName = getPackageName();
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ return pm.isIgnoringBatteryOptimizations(packageName);
+ }
+
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java
index 8c13d1f8d..d0a465d22 100644
--- a/android/app/src/main/java/com/cakewallet/haven/MainActivity.java
+++ b/android/app/src/main/java/com/cakewallet/haven/MainActivity.java
@@ -14,6 +14,10 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.provider.Settings;
import com.unstoppabledomains.resolution.DomainResolution;
import com.unstoppabledomains.resolution.Resolution;
@@ -55,6 +59,14 @@ public class MainActivity extends FlutterFragmentActivity {
handler.post(() -> result.success(""));
}
break;
+ case "disableBatteryOptimization":
+ disableBatteryOptimization();
+ handler.post(() -> result.success(null));
+ break;
+ case "isBatteryOptimizationDisabled":
+ boolean isDisabled = isBatteryOptimizationDisabled();
+ handler.post(() -> result.success(isDisabled));
+ break;
default:
handler.post(() -> result.notImplemented());
}
@@ -79,4 +91,22 @@ public class MainActivity extends FlutterFragmentActivity {
}
});
}
+
+ private void disableBatteryOptimization() {
+ String packageName = getPackageName();
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ if (!pm.isIgnoringBatteryOptimizations(packageName)) {
+ Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ intent.setData(Uri.parse("package:" + packageName));
+ startActivity(intent);
+ }
+ }
+
+ private boolean isBatteryOptimizationDisabled() {
+ String packageName = getPackageName();
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ return pm.isIgnoringBatteryOptimizations(packageName);
+ }
+
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/monero/app/MainActivity.java b/android/app/src/main/java/com/monero/app/MainActivity.java
index 73914c43c..49c368ec7 100644
--- a/android/app/src/main/java/com/monero/app/MainActivity.java
+++ b/android/app/src/main/java/com/monero/app/MainActivity.java
@@ -14,6 +14,10 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.provider.Settings;
import com.unstoppabledomains.resolution.DomainResolution;
import com.unstoppabledomains.resolution.Resolution;
@@ -64,6 +68,14 @@ public class MainActivity extends FlutterFragmentActivity {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
break;
+ case "disableBatteryOptimization":
+ disableBatteryOptimization();
+ handler.post(() -> result.success(null));
+ break;
+ case "isBatteryOptimizationDisabled":
+ boolean isDisabled = isBatteryOptimizationDisabled();
+ handler.post(() -> result.success(isDisabled));
+ break;
default:
handler.post(() -> result.notImplemented());
}
@@ -88,4 +100,22 @@ public class MainActivity extends FlutterFragmentActivity {
}
});
}
+
+ private void disableBatteryOptimization() {
+ String packageName = getPackageName();
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ if (!pm.isIgnoringBatteryOptimizations(packageName)) {
+ Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ intent.setData(Uri.parse("package:" + packageName));
+ startActivity(intent);
+ }
+ }
+
+ private boolean isBatteryOptimizationDisabled() {
+ String packageName = getPackageName();
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ return pm.isIgnoringBatteryOptimizations(packageName);
+ }
+
}
\ No newline at end of file
diff --git a/assets/images/avdo_icon.png b/assets/images/avdo_icon.png
new file mode 100644
index 000000000..c02e2760d
Binary files /dev/null and b/assets/images/avdo_icon.png differ
diff --git a/assets/images/bonk_icon.png b/assets/images/bonk_icon.png
new file mode 100644
index 000000000..c59537eab
Binary files /dev/null and b/assets/images/bonk_icon.png differ
diff --git a/assets/images/digibyte.png b/assets/images/digibyte.png
new file mode 100644
index 000000000..0045c6852
Binary files /dev/null and b/assets/images/digibyte.png differ
diff --git a/assets/images/gmt_icon.png b/assets/images/gmt_icon.png
new file mode 100644
index 000000000..25c8a00b6
Binary files /dev/null and b/assets/images/gmt_icon.png differ
diff --git a/assets/images/hnt_icon.png b/assets/images/hnt_icon.png
new file mode 100644
index 000000000..8b64b76dd
Binary files /dev/null and b/assets/images/hnt_icon.png differ
diff --git a/assets/images/notification_icon.svg b/assets/images/notification_icon.svg
new file mode 100644
index 000000000..099039e67
--- /dev/null
+++ b/assets/images/notification_icon.svg
@@ -0,0 +1,69 @@
+
+
+
+
\ No newline at end of file
diff --git a/assets/images/ray_icon.png b/assets/images/ray_icon.png
new file mode 100644
index 000000000..0d48e54a6
Binary files /dev/null and b/assets/images/ray_icon.png differ
diff --git a/assets/images/status_website_image.png b/assets/images/status_website_image.png
new file mode 100644
index 000000000..017bb64e1
Binary files /dev/null and b/assets/images/status_website_image.png differ
diff --git a/assets/solana_node_list.yml b/assets/solana_node_list.yml
new file mode 100644
index 000000000..4a2e12161
--- /dev/null
+++ b/assets/solana_node_list.yml
@@ -0,0 +1,4 @@
+-
+ uri: rpc.ankr.com
+ is_default: true
+ useSSL: true
\ No newline at end of file
diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt
index 2b783ee1e..90fcd2a75 100644
--- a/assets/text/Monerocom_Release_Notes.txt
+++ b/assets/text/Monerocom_Release_Notes.txt
@@ -1,3 +1,4 @@
-Security and Privacy enhancements
-Usability enhancements
-Bug fixes
\ No newline at end of file
+Monero enhancements
+In-App live status page for the app services
+Add Exolix exchange provider
+Bug fixes and enhancements
\ No newline at end of file
diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt
index 411ed609b..83e18c18e 100644
--- a/assets/text/Release_Notes.txt
+++ b/assets/text/Release_Notes.txt
@@ -1,4 +1 @@
-List previously used Bitcoin addresses
-Security and Privacy enhancements
-Usability enhancements
-Bug fixes
\ No newline at end of file
+Bug fixes and enhancements
\ No newline at end of file
diff --git a/configure_cake_wallet.sh b/configure_cake_wallet.sh
index 5009cd9e3..cc55e8fcc 100755
--- a/configure_cake_wallet.sh
+++ b/configure_cake_wallet.sh
@@ -30,6 +30,7 @@ cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
+cd cw_solana && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
cd cw_ethereum && flutter pub get && cd ..
cd cw_polygon && flutter pub get && cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs
diff --git a/cw_bitcoin/lib/address_from_output.dart b/cw_bitcoin/lib/address_from_output.dart
index d06ffe402..73bc101c4 100644
--- a/cw_bitcoin/lib/address_from_output.dart
+++ b/cw_bitcoin/lib/address_from_output.dart
@@ -1,23 +1,23 @@
-import 'dart:typed_data';
-import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
-import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
+import 'package:bitcoin_base/bitcoin_base.dart';
-String addressFromOutput(Uint8List script, bitcoin.NetworkType networkType) {
+String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
try {
- return bitcoin.P2PKH(
- data: PaymentData(output: script),
- network: networkType)
- .data
- .address!;
+ switch (script.getAddressType()) {
+ case P2pkhAddressType.p2pkh:
+ return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network);
+ case P2shAddressType.p2pkInP2sh:
+ return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
+ case SegwitAddresType.p2wpkh:
+ return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network);
+ case P2shAddressType.p2pkhInP2sh:
+ return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
+ case SegwitAddresType.p2wsh:
+ return P2wshAddress.fromScriptPubkey(script: script).toAddress(network);
+ case SegwitAddresType.p2tr:
+ return P2trAddress.fromScriptPubkey(script: script).toAddress(network);
+ default:
+ }
} catch (_) {}
- try {
- return bitcoin.P2WPKH(
- data: PaymentData(output: script),
- network: networkType)
- .data
- .address!;
- } catch(_) {}
-
return '';
-}
\ No newline at end of file
+}
diff --git a/cw_bitcoin/lib/address_to_output_script.dart b/cw_bitcoin/lib/address_to_output_script.dart
index 01c7b67a5..892f7a0d6 100644
--- a/cw_bitcoin/lib/address_to_output_script.dart
+++ b/cw_bitcoin/lib/address_to_output_script.dart
@@ -1,27 +1,12 @@
import 'dart:typed_data';
-import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
-import 'package:bs58check/bs58check.dart' as bs58check;
-import 'package:bitcoin_flutter/src/utils/constants/op.dart';
-import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
-import 'package:bitcoin_flutter/src/address.dart';
+import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
-Uint8List p2shAddressToOutputScript(String address) {
- final decodeBase58 = bs58check.decode(address);
- final hash = decodeBase58.sublist(1);
- return bscript.compile([OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
-}
-
-Uint8List addressToOutputScript(
- String address, bitcoin.NetworkType networkType) {
+List addressToOutputScript(String address, bitcoin.BasedUtxoNetwork network) {
try {
- // FIXME: improve validation for p2sh addresses
- // 3 for bitcoin
- // m for litecoin
- if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
- return p2shAddressToOutputScript(address);
+ if (network == bitcoin.BitcoinCashNetwork.mainnet) {
+ return bitcoin.BitcoinCashAddress(address).baseAddress.toScriptPubKey().toBytes();
}
-
- return Address.addressToOutputScript(address, networkType);
+ return bitcoin.addressToOutputScript(address: address, network: network);
} catch (err) {
print(err);
return Uint8List(0);
diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart
index f2cd63904..d1c3b6a61 100644
--- a/cw_bitcoin/lib/bitcoin_address_record.dart
+++ b/cw_bitcoin/lib/bitcoin_address_record.dart
@@ -1,5 +1,7 @@
import 'dart:convert';
-import 'package:bitbox/bitbox.dart' as bitbox;
+
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:cw_bitcoin/script_hash.dart' as sh;
class BitcoinAddressRecord {
BitcoinAddressRecord(
@@ -10,30 +12,47 @@ class BitcoinAddressRecord {
int balance = 0,
String name = '',
bool isUsed = false,
+ required this.type,
+ String? scriptHash,
+ required this.network,
}) : _txCount = txCount,
_balance = balance,
_name = name,
- _isUsed = isUsed;
+ _isUsed = isUsed,
+ scriptHash = scriptHash ?? sh.scriptHash(address, network: network);
- factory BitcoinAddressRecord.fromJSON(String jsonSource) {
+ factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork network) {
final decoded = json.decode(jsonSource) as Map;
- return BitcoinAddressRecord(decoded['address'] as String,
- index: decoded['index'] as int,
- isHidden: decoded['isHidden'] as bool? ?? false,
- isUsed: decoded['isUsed'] as bool? ?? false,
- txCount: decoded['txCount'] as int? ?? 0,
- name: decoded['name'] as String? ?? '',
- balance: decoded['balance'] as int? ?? 0);
+ return BitcoinAddressRecord(
+ decoded['address'] as String,
+ index: decoded['index'] as int,
+ isHidden: decoded['isHidden'] as bool? ?? false,
+ isUsed: decoded['isUsed'] as bool? ?? false,
+ txCount: decoded['txCount'] as int? ?? 0,
+ name: decoded['name'] as String? ?? '',
+ balance: decoded['balance'] as int? ?? 0,
+ type: decoded['type'] != null && decoded['type'] != ''
+ ? BitcoinAddressType.values
+ .firstWhere((type) => type.toString() == decoded['type'] as String)
+ : SegwitAddresType.p2wpkh,
+ scriptHash: decoded['scriptHash'] as String?,
+ network: network,
+ );
}
+ @override
+ bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
+
final String address;
- final bool isHidden;
+ bool isHidden;
final int index;
int _txCount;
int _balance;
String _name;
bool _isUsed;
+ String? scriptHash;
+ BasedUtxoNetwork network;
int get txCount => _txCount;
@@ -50,21 +69,25 @@ class BitcoinAddressRecord {
void setAsUsed() => _isUsed = true;
void setNewName(String label) => _name = label;
- @override
- bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
-
@override
int get hashCode => address.hashCode;
- String get cashAddr => bitbox.Address.toCashAddress(address);
+ BitcoinAddressType type;
+
+ String updateScriptHash(BasedUtxoNetwork network) {
+ scriptHash = sh.scriptHash(address, network: network);
+ return scriptHash!;
+ }
String toJSON() => json.encode({
'address': address,
'index': index,
'isHidden': isHidden,
+ 'isUsed': isUsed,
'txCount': txCount,
'name': name,
- 'isUsed': isUsed,
'balance': balance,
+ 'type': type.toString(),
+ 'scriptHash': scriptHash,
});
}
diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart
new file mode 100644
index 000000000..2d2339a41
--- /dev/null
+++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart
@@ -0,0 +1,42 @@
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:cw_core/receive_page_option.dart';
+
+class BitcoinReceivePageOption implements ReceivePageOption {
+ static const p2wpkh = BitcoinReceivePageOption._('Segwit (P2WPKH) (Default)');
+ static const p2sh = BitcoinReceivePageOption._('Segwit-Compatible (P2SH)');
+ static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)');
+ static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
+ static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
+
+ const BitcoinReceivePageOption._(this.value);
+
+ final String value;
+
+ String toString() {
+ return value;
+ }
+
+ static const all = [
+ BitcoinReceivePageOption.p2wpkh,
+ BitcoinReceivePageOption.p2tr,
+ BitcoinReceivePageOption.p2wsh,
+ BitcoinReceivePageOption.p2sh,
+ BitcoinReceivePageOption.p2pkh
+ ];
+
+ factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
+ switch (type) {
+ case SegwitAddresType.p2tr:
+ return BitcoinReceivePageOption.p2tr;
+ case SegwitAddresType.p2wsh:
+ return BitcoinReceivePageOption.p2wsh;
+ case P2pkhAddressType.p2pkh:
+ return BitcoinReceivePageOption.p2pkh;
+ case P2shAddressType.p2wpkhInP2sh:
+ return BitcoinReceivePageOption.p2sh;
+ case SegwitAddresType.p2wpkh:
+ default:
+ return BitcoinReceivePageOption.p2wpkh;
+ }
+ }
+}
diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart
index 9c198c27c..52edea091 100644
--- a/cw_bitcoin/lib/bitcoin_unspent.dart
+++ b/cw_bitcoin/lib/bitcoin_unspent.dart
@@ -6,10 +6,9 @@ class BitcoinUnspent extends Unspent {
: bitcoinAddressRecord = addressRecord,
super(addressRecord.address, hash, value, vout, null);
- factory BitcoinUnspent.fromJSON(
- BitcoinAddressRecord address, Map json) =>
- BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
- json['tx_pos'] as int);
+ factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map json) =>
+ BitcoinUnspent(
+ address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int);
final BitcoinAddressRecord bitcoinAddressRecord;
}
diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart
index 9cdb78f2d..bf59e8637 100644
--- a/cw_bitcoin/lib/bitcoin_wallet.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet.dart
@@ -1,3 +1,4 @@
+import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coins_info.dart';
@@ -17,36 +18,42 @@ part 'bitcoin_wallet.g.dart';
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
- BitcoinWalletBase(
- {required String mnemonic,
- required String password,
- required WalletInfo walletInfo,
- required Box unspentCoinsInfo,
- required Uint8List seedBytes,
- List? initialAddresses,
- ElectrumBalance? initialBalance,
- int initialRegularAddressIndex = 0,
- int initialChangeAddressIndex = 0})
- : super(
+ BitcoinWalletBase({
+ required String mnemonic,
+ required String password,
+ required WalletInfo walletInfo,
+ required Box unspentCoinsInfo,
+ required Uint8List seedBytes,
+ String? addressPageType,
+ BasedUtxoNetwork? networkParam,
+ List? initialAddresses,
+ ElectrumBalance? initialBalance,
+ Map? initialRegularAddressIndex,
+ Map? initialChangeAddressIndex,
+ }) : super(
mnemonic: mnemonic,
password: password,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo,
- networkType: bitcoin.bitcoin,
+ networkType: networkParam == null
+ ? bitcoin.bitcoin
+ : networkParam == BitcoinNetwork.mainnet
+ ? bitcoin.bitcoin
+ : bitcoin.testnet,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.btc) {
walletAddresses = BitcoinWalletAddresses(
- walletInfo,
- electrumClient: electrumClient,
- initialAddresses: initialAddresses,
- initialRegularAddressIndex: initialRegularAddressIndex,
- initialChangeAddressIndex: initialChangeAddressIndex,
- mainHd: hd,
- sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType)
- .derivePath("m/0'/1"),
- networkType: networkType);
+ walletInfo,
+ electrumClient: electrumClient,
+ initialAddresses: initialAddresses,
+ initialRegularAddressIndex: initialRegularAddressIndex,
+ initialChangeAddressIndex: initialChangeAddressIndex,
+ mainHd: hd,
+ sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
+ network: networkParam ?? network,
+ );
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
@@ -57,21 +64,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required String password,
required WalletInfo walletInfo,
required Box unspentCoinsInfo,
+ String? addressPageType,
+ BasedUtxoNetwork? network,
List? initialAddresses,
ElectrumBalance? initialBalance,
- int initialRegularAddressIndex = 0,
- int initialChangeAddressIndex = 0
+ Map? initialRegularAddressIndex,
+ Map? initialChangeAddressIndex,
}) async {
return BitcoinWallet(
- mnemonic: mnemonic,
- password: password,
- walletInfo: walletInfo,
- unspentCoinsInfo: unspentCoinsInfo,
- initialAddresses: initialAddresses,
- initialBalance: initialBalance,
- seedBytes: await mnemonicToSeedBytes(mnemonic),
- initialRegularAddressIndex: initialRegularAddressIndex,
- initialChangeAddressIndex: initialChangeAddressIndex);
+ mnemonic: mnemonic,
+ password: password,
+ walletInfo: walletInfo,
+ unspentCoinsInfo: unspentCoinsInfo,
+ initialAddresses: initialAddresses,
+ initialBalance: initialBalance,
+ seedBytes: await mnemonicToSeedBytes(mnemonic),
+ initialRegularAddressIndex: initialRegularAddressIndex,
+ initialChangeAddressIndex: initialChangeAddressIndex,
+ addressPageType: addressPageType,
+ networkParam: network,
+ );
}
static Future open({
@@ -80,16 +92,23 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required Box unspentCoinsInfo,
required String password,
}) async {
- final snp = await ElectrumWallletSnapshot.load(name, walletInfo.type, password);
+ final network = walletInfo.network != null
+ ? BasedUtxoNetwork.fromName(walletInfo.network!)
+ : BitcoinNetwork.mainnet;
+ final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
+
return BitcoinWallet(
- mnemonic: snp.mnemonic,
- password: password,
- walletInfo: walletInfo,
- unspentCoinsInfo: unspentCoinsInfo,
- initialAddresses: snp.addresses,
- initialBalance: snp.balance,
- seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
- initialRegularAddressIndex: snp.regularAddressIndex,
- initialChangeAddressIndex: snp.changeAddressIndex);
+ mnemonic: snp.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,
+ networkParam: network,
+ );
}
-}
\ No newline at end of file
+}
diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
index 36d37127d..f12577492 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
@@ -1,6 +1,5 @@
-import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
-import 'package:cw_bitcoin/bitcoin_address_record.dart';
-import 'package:cw_bitcoin/electrum.dart';
+import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:bitcoin_flutter/bitcoin_flutter.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_info.dart';
@@ -11,24 +10,31 @@ part 'bitcoin_wallet_addresses.g.dart';
class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAddresses;
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
- BitcoinWalletAddressesBase(WalletInfo walletInfo,
- {required bitcoin.HDWallet mainHd,
- required bitcoin.HDWallet sideHd,
- required bitcoin.NetworkType networkType,
- required ElectrumClient electrumClient,
- List? initialAddresses,
- int initialRegularAddressIndex = 0,
- int initialChangeAddressIndex = 0})
- : super(walletInfo,
- initialAddresses: initialAddresses,
- initialRegularAddressIndex: initialRegularAddressIndex,
- initialChangeAddressIndex: initialChangeAddressIndex,
- mainHd: mainHd,
- sideHd: sideHd,
- electrumClient: electrumClient,
- networkType: networkType);
+ BitcoinWalletAddressesBase(
+ WalletInfo walletInfo, {
+ required super.mainHd,
+ required super.sideHd,
+ required super.network,
+ required super.electrumClient,
+ super.initialAddresses,
+ super.initialRegularAddressIndex,
+ super.initialChangeAddressIndex,
+ }) : super(walletInfo);
@override
- String getAddress({required int index, required bitcoin.HDWallet hd}) =>
- generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
+ String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
+ if (addressType == P2pkhAddressType.p2pkh)
+ return generateP2PKHAddress(hd: hd, index: index, network: network);
+
+ if (addressType == SegwitAddresType.p2tr)
+ return generateP2TRAddress(hd: hd, index: index, network: network);
+
+ if (addressType == SegwitAddresType.p2wsh)
+ return generateP2WSHAddress(hd: hd, index: index, network: network);
+
+ if (addressType == P2shAddressType.p2wpkhInP2sh)
+ return generateP2SHAddress(hd: hd, index: index, network: network);
+
+ return generateP2WPKHAddress(hd: hd, index: index, network: network);
+ }
}
diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart
index 3a97e0682..38e769d15 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_service.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart
@@ -1,6 +1,7 @@
import 'dart:io';
+import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
-import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
+import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
@@ -12,10 +13,8 @@ import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:collection/collection.dart';
-class BitcoinWalletService extends WalletService<
- BitcoinNewWalletCredentials,
- BitcoinRestoreWalletFromSeedCredentials,
- BitcoinRestoreWalletFromWIFCredentials> {
+class BitcoinWalletService extends WalletService {
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box walletInfoSource;
@@ -25,12 +24,17 @@ class BitcoinWalletService extends WalletService<
WalletType getType() => WalletType.bitcoin;
@override
- Future create(BitcoinNewWalletCredentials credentials) async {
+ Future create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
+ final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
+ credentials.walletInfo?.network = network.value;
+
final wallet = await BitcoinWalletBase.create(
- mnemonic: await generateMnemonic(),
- password: credentials.password!,
- walletInfo: credentials.walletInfo!,
- unspentCoinsInfo: unspentCoinsInfoSource);
+ mnemonic: await generateMnemonic(),
+ password: credentials.password!,
+ walletInfo: credentials.walletInfo!,
+ unspentCoinsInfo: unspentCoinsInfoSource,
+ network: network,
+ );
await wallet.save();
await wallet.init();
return wallet;
@@ -42,28 +46,41 @@ class BitcoinWalletService extends WalletService<
@override
Future openWallet(String name, String password) async {
- final walletInfo = walletInfoSource.values.firstWhereOrNull(
- (info) => info.id == WalletBase.idFor(name, getType()))!;
- final wallet = await BitcoinWalletBase.open(
- password: password, name: name, walletInfo: walletInfo,
- unspentCoinsInfo: unspentCoinsInfoSource);
- await wallet.init();
- return wallet;
+ final walletInfo = walletInfoSource.values
+ .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
+ try {
+ final wallet = await BitcoinWalletBase.open(
+ password: password,
+ name: name,
+ walletInfo: walletInfo,
+ unspentCoinsInfo: unspentCoinsInfoSource);
+ await wallet.init();
+ saveBackup(name);
+ return wallet;
+ } catch (_) {
+ await restoreWalletFilesFromBackup(name);
+ final wallet = await BitcoinWalletBase.open(
+ password: password,
+ name: name,
+ walletInfo: walletInfo,
+ unspentCoinsInfo: unspentCoinsInfoSource);
+ await wallet.init();
+ return wallet;
+ }
}
@override
Future remove(String wallet) async {
- File(await pathForWalletDir(name: wallet, type: getType()))
- .delete(recursive: true);
- final walletInfo = walletInfoSource.values.firstWhereOrNull(
- (info) => info.id == WalletBase.idFor(wallet, getType()))!;
+ File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
+ final walletInfo = walletInfoSource.values
+ .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
await walletInfoSource.delete(walletInfo.key);
}
@override
Future rename(String currentName, String password, String newName) async {
- final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
- (info) => info.id == WalletBase.idFor(currentName, getType()))!;
+ final currentWalletInfo = walletInfoSource.values
+ .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWallet = await BitcoinWalletBase.open(
password: password,
name: currentName,
@@ -71,6 +88,7 @@ class BitcoinWalletService extends WalletService<
unspentCoinsInfo: unspentCoinsInfoSource);
await currentWallet.renameWalletFiles(newName);
+ await saveBackup(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
@@ -80,24 +98,29 @@ class BitcoinWalletService extends WalletService<
}
@override
- Future restoreFromKeys(
- BitcoinRestoreWalletFromWIFCredentials credentials) async =>
+ Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
+ {bool? isTestnet}) async =>
throw UnimplementedError();
@override
- Future restoreFromSeed(
- BitcoinRestoreWalletFromSeedCredentials credentials) async {
+ Future restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
+ {bool? isTestnet}) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}
+ final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
+ credentials.walletInfo?.network = network.value;
+
final wallet = await BitcoinWalletBase.create(
- password: credentials.password!,
- mnemonic: credentials.mnemonic,
- walletInfo: credentials.walletInfo!,
- unspentCoinsInfo: unspentCoinsInfoSource);
+ password: credentials.password!,
+ mnemonic: credentials.mnemonic,
+ walletInfo: credentials.walletInfo!,
+ unspentCoinsInfo: unspentCoinsInfoSource,
+ network: network,
+ );
await wallet.save();
await wallet.init();
return wallet;
}
-}
\ No newline at end of file
+}
diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart
index a05c251fe..51a53e285 100644
--- a/cw_bitcoin/lib/electrum.dart
+++ b/cw_bitcoin/lib/electrum.dart
@@ -2,12 +2,12 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
-import 'package:bitcoin_flutter/bitcoin_flutter.dart';
+import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/script_hash.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
-import 'package:collection/collection.dart';
+import 'package:http/http.dart' as http;
String jsonrpcparams(List