diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml
index 84c680dda..47b08c44d 100644
--- a/.github/workflows/automated_integration_test.yml
+++ b/.github/workflows/automated_integration_test.yml
@@ -55,7 +55,7 @@ jobs:
- name: Flutter action
uses: subosito/flutter-action@v1
with:
- flutter-version: "3.27.4"
+ flutter-version: "3.27.0"
channel: stable
- name: Install package dependencies
@@ -153,8 +153,8 @@ jobs:
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 changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> 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
@@ -168,6 +168,7 @@ jobs:
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 trocadorMoneroApiKey = '${{ 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
@@ -178,7 +179,8 @@ jobs:
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 exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const exolixMoneroApiKey = '${{ 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
diff --git a/.github/workflows/no_http_imports.yaml b/.github/workflows/no_http_imports.yaml
new file mode 100644
index 000000000..dad6821ac
--- /dev/null
+++ b/.github/workflows/no_http_imports.yaml
@@ -0,0 +1,21 @@
+name: No http imports
+
+on: [pull_request]
+
+jobs:
+ PR_test_build:
+ runs-on: ubuntu-24.04
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Check for http package usage
+ if: github.event_name == 'pull_request'
+ run: |
+ GIT_GREP_OUT="$(git grep package:http | (grep .dart: || test $? = 1) | (grep -v proxy_wrapper.dart || test $? = 1) | (grep -v very_insecure_http_do_not_use || test $? = 1) || true)"
+ [[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
+ echo "$GIT_GREP_OUT"
+ echo "There are .dart files which use http imports"
+ echo "Using http package breaks proxy integration"
+ echo "Please use ProxyWrapper.getHttpClient() from package:cw_core/utils/proxy_wrapper.dart"
+ exit 1
+
\ No newline at end of file
diff --git a/.github/workflows/no_print_in_dart.yaml b/.github/workflows/no_print_in_dart.yaml
index 9c3d82bc2..507793bd8 100644
--- a/.github/workflows/no_print_in_dart.yaml
+++ b/.github/workflows/no_print_in_dart.yaml
@@ -15,5 +15,5 @@ jobs:
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT"
echo "There are .dart files which use print() statements"
- echo "Please use printV from package: cw_core/utils/print_verbose.dart"
+ echo "Please use printV from package:cw_core/utils/print_verbose.dart"
exit 1
diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml
index 8f6139747..f7c226ce4 100644
--- a/.github/workflows/pr_test_build_android.yml
+++ b/.github/workflows/pr_test_build_android.yml
@@ -9,7 +9,7 @@ jobs:
PR_test_build:
runs-on: linux-amd64
container:
- image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1
+ image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
env:
STORE_PASS: test@cake_wallet
KEY_PASS: test@cake_wallet
@@ -98,8 +98,8 @@ jobs:
else
echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
fi
- 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 changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> 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
@@ -113,6 +113,7 @@ jobs:
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 trocadorMoneroApiKey = '${{ 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
@@ -124,7 +125,8 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_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 exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const exolixMoneroApiKey = '${{ 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
@@ -253,6 +255,11 @@ jobs:
- name: Build generated code
run: |
+ flutter --version
+ flutter clean
+ rm -rf .dart_tool
+ rm pubspec.lock
+ flutter pub get
./model_generator.sh async
- name: Generate key properties
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 476a033a0..f057b19e5 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -9,7 +9,7 @@ jobs:
PR_test_build:
runs-on: linux-amd64
container:
- image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1
+ image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
env:
STORE_PASS: test@cake_wallet
KEY_PASS: test@cake_wallet
@@ -91,8 +91,8 @@ jobs:
else
echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
fi
- 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 changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> 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
@@ -106,6 +106,7 @@ jobs:
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 trocadorMoneroApiKey = '${{ 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
@@ -117,7 +118,8 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_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 exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const exolixMoneroApiKey = '${{ 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
diff --git a/Dockerfile b/Dockerfile
index 84179d645..151b7af20 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1
+# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
# Heavily inspired by cirrusci images
# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile
@@ -15,11 +15,11 @@ LABEL org.opencontainers.image.source=https://github.com/cake-tech/cake_wallet
ENV GOLANG_VERSION=1.24.1
# Pin Flutter version to latest known-working version
-ENV FLUTTER_VERSION=3.27.4
+ENV FLUTTER_VERSION=3.27.0
# Pin Android Studio, platform, and build tools versions to latest known-working version
# Comes from https://developer.android.com/studio/#command-tools
-ENV ANDROID_SDK_TOOLS_VERSION=11076708
+ENV ANDROID_SDK_TOOLS_VERSION=13114758
# Comes from https://developer.android.com/studio/releases/build-tools
ENV ANDROID_PLATFORM_VERSION=35
ENV ANDROID_BUILD_TOOLS_VERSION=34.0.0
@@ -164,9 +164,12 @@ RUN (addgroup kvm || true) && \
ENV PATH=${HOME}/.cargo/bin:${PATH}
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y && \
cargo install cargo-ndk && \
+ for toolchain in stable nightly; \
+ do \
for target in aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu; \
do \
- rustup target add --toolchain stable $target; \
+ rustup target add --toolchain $toolchain $target; \
+ done \
done
# Download and install Flutter
@@ -175,8 +178,11 @@ ENV FLUTTER_HOME=${HOME}/sdks/flutter/${FLUTTER_VERSION}
ENV FLUTTER_ROOT=$FLUTTER_HOME
ENV PATH=${PATH}:${FLUTTER_HOME}/bin:${FLUTTER_HOME}/bin/cache/dart-sdk/bin
-RUN git clone --depth 1 --branch ${FLUTTER_VERSION} https://github.com/flutter/flutter.git ${FLUTTER_HOME} \
- && yes | flutter doctor --android-licenses \
+RUN git clone --branch ${FLUTTER_VERSION} https://github.com/flutter/flutter.git ${FLUTTER_HOME} && \
+ cd ${FLUTTER_HOME} && \
+ git fetch -a
+
+RUN yes | flutter doctor --android-licenses \
&& flutter doctor \
&& chown -R root:root ${FLUTTER_HOME}
diff --git a/assets/images/history.svg b/assets/images/history.svg
new file mode 100644
index 000000000..f308ab7e3
--- /dev/null
+++ b/assets/images/history.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/images/notif.svg b/assets/images/notif.svg
new file mode 100644
index 000000000..b1ff5b4fa
--- /dev/null
+++ b/assets/images/notif.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/qr-cake.png b/assets/images/qr-cake.png
new file mode 100644
index 000000000..7c54dedb0
Binary files /dev/null and b/assets/images/qr-cake.png differ
diff --git a/assets/images/tor_logo.svg b/assets/images/tor_logo.svg
new file mode 100644
index 000000000..ebd00324d
--- /dev/null
+++ b/assets/images/tor_logo.svg
@@ -0,0 +1,76 @@
+
+
+
+
diff --git a/assets/images/usdtbsc_icon.png b/assets/images/usdtbsc_icon.png
new file mode 100644
index 000000000..9f2cda237
Binary files /dev/null and b/assets/images/usdtbsc_icon.png differ
diff --git a/assets/text/Monerocom_Release_Notes.txt b/assets/text/Monerocom_Release_Notes.txt
index 1176d3d8c..faf57258a 100644
--- a/assets/text/Monerocom_Release_Notes.txt
+++ b/assets/text/Monerocom_Release_Notes.txt
@@ -1,3 +1,4 @@
-New themes and UI/UX improvements
-Ledger flow enhancements
+Add built-in Tor support (experimental)
+Ledger improvements
+UI/UX improvements
Bug fixes
\ No newline at end of file
diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt
index b68800bd9..c49b895e3 100644
--- a/assets/text/Release_Notes.txt
+++ b/assets/text/Release_Notes.txt
@@ -1,5 +1,9 @@
-New themes and UI/UX improvements
-Silent Payments scanning fix
-Payjoin enhancements
-Ledger flow enhancements
+Add built-in Tor support (experimental)
+Add dEuro investments
+Solana fixes/enhancements
+Polygon fixes/enhancements
+WalletConnect improvements
+Ledger improvements
+Payjoin improvements
+UI/UX improvements
Bug fixes
\ No newline at end of file
diff --git a/cw_bitcoin/lib/address_from_output.dart b/cw_bitcoin/lib/address_from_output.dart
index d6e931068..0d985b237 100644
--- a/cw_bitcoin/lib/address_from_output.dart
+++ b/cw_bitcoin/lib/address_from_output.dart
@@ -17,16 +17,21 @@ BitcoinBaseAddress addressFromScript(Script script,
switch (addressType) {
case P2pkhAddressType.p2pkh:
- return P2pkhAddress.fromScriptPubkey(script: script);
+ return P2pkhAddress.fromScriptPubkey(
+ script: script, network: BitcoinNetwork.mainnet);
case P2shAddressType.p2pkhInP2sh:
case P2shAddressType.p2pkInP2sh:
- return P2shAddress.fromScriptPubkey(script: script);
- case SegwitAddressType.p2wpkh:
- return P2wpkhAddress.fromScriptPubkey(script: script);
- case SegwitAddressType.p2wsh:
- return P2wshAddress.fromScriptPubkey(script: script);
- case SegwitAddressType.p2tr:
- return P2trAddress.fromScriptPubkey(script: script);
+ return P2shAddress.fromScriptPubkey(
+ script: script, network: BitcoinNetwork.mainnet);
+ case SegwitAddresType.p2wpkh:
+ return P2wpkhAddress.fromScriptPubkey(
+ script: script, network: BitcoinNetwork.mainnet);
+ case SegwitAddresType.p2wsh:
+ return P2wshAddress.fromScriptPubkey(
+ script: script, network: BitcoinNetwork.mainnet);
+ case SegwitAddresType.p2tr:
+ return P2trAddress.fromScriptPubkey(
+ script: script, network: BitcoinNetwork.mainnet);
}
throw ArgumentError("Invalid script");
diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart
index 97b3c08f8..1509f913a 100644
--- a/cw_bitcoin/lib/bitcoin_address_record.dart
+++ b/cw_bitcoin/lib/bitcoin_address_record.dart
@@ -82,7 +82,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
type: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String)
- : SegwitAddressType.p2wpkh,
+ : SegwitAddresType.p2wpkh,
scriptHash: decoded['scriptHash'] as String?,
network: network,
);
diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart
index 8331a182d..07083e111 100644
--- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart
+++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart
@@ -36,9 +36,9 @@ class BitcoinReceivePageOption implements ReceivePageOption {
BitcoinAddressType toType() {
switch (this) {
case BitcoinReceivePageOption.p2tr:
- return SegwitAddressType.p2tr;
+ return SegwitAddresType.p2tr;
case BitcoinReceivePageOption.p2wsh:
- return SegwitAddressType.p2wsh;
+ return SegwitAddresType.p2wsh;
case BitcoinReceivePageOption.p2pkh:
return P2pkhAddressType.p2pkh;
case BitcoinReceivePageOption.p2sh:
@@ -46,20 +46,20 @@ class BitcoinReceivePageOption implements ReceivePageOption {
case BitcoinReceivePageOption.silent_payments:
return SilentPaymentsAddresType.p2sp;
case BitcoinReceivePageOption.mweb:
- return SegwitAddressType.mweb;
+ return SegwitAddresType.mweb;
case BitcoinReceivePageOption.p2wpkh:
default:
- return SegwitAddressType.p2wpkh;
+ return SegwitAddresType.p2wpkh;
}
}
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
switch (type) {
- case SegwitAddressType.p2tr:
+ case SegwitAddresType.p2tr:
return BitcoinReceivePageOption.p2tr;
- case SegwitAddressType.p2wsh:
+ case SegwitAddresType.p2wsh:
return BitcoinReceivePageOption.p2wsh;
- case SegwitAddressType.mweb:
+ case SegwitAddresType.mweb:
return BitcoinReceivePageOption.mweb;
case P2pkhAddressType.p2pkh:
return BitcoinReceivePageOption.p2pkh;
@@ -67,7 +67,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
return BitcoinReceivePageOption.p2sh;
case SilentPaymentsAddresType.p2sp:
return BitcoinReceivePageOption.silent_payments;
- case SegwitAddressType.p2wpkh:
+ case SegwitAddresType.p2wpkh:
default:
return BitcoinReceivePageOption.p2wpkh;
}
diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart
index 73a6ad0ea..9231022f6 100644
--- a/cw_bitcoin/lib/bitcoin_wallet.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet.dart
@@ -73,8 +73,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance,
seedBytes: seedBytes,
encryptionFileUtils: encryptionFileUtils,
- currency:
- networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
+ currency: networkParam == BitcoinNetwork.testnet
+ ? CryptoCurrency.tbtc
+ : CryptoCurrency.btc,
alwaysScan: alwaysScan,
) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
@@ -93,12 +94,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
mainHd: hd,
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
network: networkParam ?? network,
- masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
+ masterHd:
+ seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
isHardwareWallet: walletInfo.isHardwareWallet,
payjoinManager: payjoinManager);
autorun((_) {
- this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
+ this.walletAddresses.isEnabledAutoGenerateSubaddress =
+ this.isEnabledAutoGenerateSubaddress;
});
}
@@ -133,7 +136,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
break;
case DerivationType.electrum:
default:
- seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
+ seedBytes =
+ await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break;
}
@@ -206,8 +210,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
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;
@@ -216,7 +222,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
if (mnemonic != null) {
switch (walletInfo.derivationInfo!.derivationType) {
case DerivationType.electrum:
- seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
+ seedBytes =
+ await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break;
case DerivationType.bip39:
default:
@@ -259,10 +266,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
+ @override
+ Future close({bool shouldCleanup = false}) async {
+ payjoinManager.cleanupSessions();
+ super.close(shouldCleanup: shouldCleanup);
+ }
+
late final PayjoinManager payjoinManager;
bool get isPayjoinAvailable => unspentCoinsInfo.values
- .where((element) => element.walletId == id && element.isSending && !element.isFrozen)
+ .where((element) =>
+ element.walletId == id && element.isSending && !element.isFrozen)
.isNotEmpty;
Future buildPsbt({
@@ -279,8 +293,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}) async {
final psbtReadyInputs = [];
for (final utxo in utxos) {
- final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
- final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
+ final rawTx =
+ await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
+ final publicKeyAndDerivationPath =
+ publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
utxo: utxo.utxo,
@@ -292,7 +308,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
));
}
- return PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF)
+ return PSBTTransactionBuild(
+ inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF)
.psbt;
}
@@ -331,7 +348,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future createTransaction(Object credentials) async {
credentials = credentials as BitcoinTransactionCredentials;
- final tx = (await super.createTransaction(credentials)) as PendingBitcoinTransaction;
+ final tx = (await super.createTransaction(credentials))
+ as PendingBitcoinTransaction;
final payjoinUri = credentials.payjoinUri;
if (payjoinUri == null) return tx;
@@ -354,12 +372,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
publicKeys: tx.publicKeys!,
masterFingerprint: Uint8List(0));
- final originalPsbt =
- await signPsbt(base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
+ final originalPsbt = await signPsbt(
+ base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
tx.commitOverride = () async {
- final sender =
- await payjoinManager.initSender(payjoinUri, originalPsbt, int.parse(tx.feeRate));
+ final sender = await payjoinManager.initSender(
+ payjoinUri, originalPsbt, int.parse(tx.feeRate));
payjoinManager.spawnNewSender(
sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount));
};
@@ -375,7 +393,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future commitPsbt(String finalizedPsbt) {
final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt));
- final btcTx = BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract()));
+ final btcTx =
+ BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract()));
return PendingBitcoinTransaction(
btcTx,
@@ -389,11 +408,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
).commit();
}
- Future signPsbt(String preProcessedPsbt, List utxos) async {
+ Future signPsbt(
+ String preProcessedPsbt, List utxos) async {
final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt));
await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) {
- return utxo.utxo.isP2tr
+ return utxo.utxo.isP2tr()
? key.signTapRoot(
txDigest,
sighash: sighash,
@@ -414,15 +434,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future signMessage(String message, {String? address = null}) async {
if (walletInfo.isHardwareWallet) {
final addressEntry = address != null
- ? walletAddresses.allAddresses.firstWhere((element) => element.address == address)
+ ? walletAddresses.allAddresses
+ .firstWhere((element) => element.address == address)
: null;
final index = addressEntry?.index ?? 0;
final isChange = addressEntry?.isHidden == true ? 1 : 0;
final accountPath = walletInfo.derivationInfo?.derivationPath;
- final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
+ final derivationPath =
+ accountPath != null ? "$accountPath/$isChange/$index" : null;
- final signature = await _bitcoinLedgerApp!
- .signMessage(message: ascii.encode(message), signDerivationPath: derivationPath);
+ final signature = await _bitcoinLedgerApp!.signMessage(
+ message: ascii.encode(message), signDerivationPath: derivationPath);
return base64Encode(signature);
}
diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
index dd6dc5fae..d84d958be 100644
--- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart
@@ -31,12 +31,10 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
final PayjoinManager payjoinManager;
- @observable
payjoin.Receiver? currentPayjoinReceiver;
- @computed
- String? get payjoinEndpoint =>
- currentPayjoinReceiver?.pjUriBuilder().build().pjEndpoint();
+ @observable
+ String? payjoinEndpoint = null;
@override
String getAddress(
@@ -47,10 +45,10 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
if (addressType == P2pkhAddressType.p2pkh)
return generateP2PKHAddress(hd: hd, index: index, network: network);
- if (addressType == SegwitAddressType.p2tr)
+ if (addressType == SegwitAddresType.p2tr)
return generateP2TRAddress(hd: hd, index: index, network: network);
- if (addressType == SegwitAddressType.p2wsh)
+ if (addressType == SegwitAddresType.p2wsh)
return generateP2WSHAddress(hd: hd, index: index, network: network);
if (addressType == P2shAddressType.p2wpkhInP2sh)
@@ -59,16 +57,32 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
return generateP2WPKHAddress(hd: hd, index: index, network: network);
}
+ @action
Future initPayjoin() async {
- currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress);
-
- payjoinManager.resumeSessions();
+ try {
+ await payjoinManager.initPayjoin();
+ currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress);
+ payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
+
+ payjoinManager.resumeSessions();
+ } catch (e) {
+ printV(e);
+ // Ignore Connectivity errors
+ if (!e.toString().contains("error sending request for url")) rethrow;
+ }
}
+ @action
Future newPayjoinReceiver() async {
- currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress);
+ try {
+ currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress);
+ payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
- printV("Initializing new Payjoin Receiver");
- payjoinManager.spawnNewReceiver(receiver: currentPayjoinReceiver!);
+ payjoinManager.spawnReceiver(receiver: currentPayjoinReceiver!);
+ } catch (e) {
+ printV(e);
+ // Ignore Connectivity errors
+ if (!e.toString().contains("error sending request for url")) rethrow;
+ }
}
}
diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart
index 1f5c369e3..2ddd30df6 100644
--- a/cw_bitcoin/lib/electrum.dart
+++ b/cw_bitcoin/lib/electrum.dart
@@ -5,6 +5,8 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/utils/proxy_socket/abstract.dart';
+import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
@@ -42,7 +44,7 @@ class ElectrumClient {
static const aliveTimerDuration = Duration(seconds: 4);
bool get isConnected => _isConnected;
- Socket? socket;
+ ProxySocket? socket;
void Function(ConnectionStatus)? onConnectionStatusChange;
int _id;
final Map _tasks;
@@ -72,18 +74,11 @@ class ElectrumClient {
} catch (_) {}
socket = null;
+ final ssl = !(useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum")));
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,
- );
- }
+ socket = await ProxyWrapper().getSocksSocket(ssl, host, port, connectionTimeout: connectionTimeout);
} catch (e) {
+ printV("connect: $e");
if (e is HandshakeException) {
useSSL = !(useSSL ?? false);
}
@@ -105,7 +100,6 @@ class ElectrumClient {
// use ping to determine actual connection status since we could've just not timed out yet:
// _setConnectionStatus(ConnectionStatus.connected);
-
socket!.listen(
(Uint8List event) {
try {
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index d7449c011..bb9cea1bc 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -4,7 +4,9 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
+import 'package:cw_core/format_amount.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
@@ -17,7 +19,7 @@ import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
-import 'package:cw_bitcoin/electrum.dart' as electrum;
+import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart';
@@ -48,7 +50,6 @@ import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart';
import 'package:hex/hex.dart';
-import 'package:http/http.dart' as http;
part 'electrum_wallet.g.dart';
@@ -68,7 +69,7 @@ abstract class ElectrumWalletBase
Uint8List? seedBytes,
this.passphrase,
List? initialAddresses,
- electrum.ElectrumClient? electrumClient,
+ ElectrumClient? electrumClient,
ElectrumBalance? initialBalance,
CryptoCurrency? currency,
this.alwaysScan,
@@ -95,7 +96,7 @@ abstract class ElectrumWalletBase
this.isTestnet = !network.isMainnet,
this._mnemonic = mnemonic,
super(walletInfo) {
- this.electrumClient = electrumClient ?? electrum.ElectrumClient();
+ this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo;
transactionHistory = ElectrumTransactionHistory(
walletInfo: walletInfo,
@@ -166,7 +167,7 @@ abstract class ElectrumWalletBase
@observable
bool isEnabledAutoGenerateSubaddress;
- late electrum.ElectrumClient electrumClient;
+ late ElectrumClient electrumClient;
Box unspentCoinsInfo;
@override
@@ -181,7 +182,7 @@ abstract class ElectrumWalletBase
SyncStatus syncStatus;
Set get addressesSet => walletAddresses.allAddresses
- .where((element) => element.type != SegwitAddressType.mweb)
+ .where((element) => element.type != SegwitAddresType.mweb)
.map((addr) => addr.address)
.toSet();
@@ -332,14 +333,14 @@ abstract class ElectrumWalletBase
final receivePort = ReceivePort();
_isolate = Isolate.spawn(
- _handleScanSilentPayments,
+ startRefresh,
ScanData(
sendPort: receivePort.sendPort,
silentAddress: walletAddresses.silentAddress!,
network: network,
height: height,
chainTip: chainTip,
- electrumClient: electrum.ElectrumClient(),
+ electrumClient: ElectrumClient(),
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
node: (await getNodeSupportsSilentPayments()) == true
? ScanNode(node!.uri, node!.useSSL)
@@ -438,6 +439,7 @@ abstract class ElectrumWalletBase
BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)),
)
: silentAddress.B_spend,
+ network: network,
);
final addressRecord = walletAddresses.silentAddresses
@@ -491,10 +493,9 @@ abstract class ElectrumWalletBase
Future updateFeeRates() async {
if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) {
try {
- final response = await http
- .get(Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended"))
- .timeout(Duration(seconds: 5));
-
+ final response = await ProxyWrapper()
+ .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended"))
+ .timeout(Duration(seconds: 15));
final result = json.decode(response.body) as Map;
final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
@@ -562,7 +563,7 @@ abstract class ElectrumWalletBase
node!.save();
return node!.supportsSilentPayments!;
}
- } on electrum.RequestFailedTimeoutException catch (_) {
+ } on RequestFailedTimeoutException catch (_) {
node!.supportsSilentPayments = false;
node!.save();
return node!.supportsSilentPayments!;
@@ -623,9 +624,9 @@ abstract class ElectrumWalletBase
switch (coinTypeToSpendFrom) {
case UnspentCoinType.mweb:
- return utx.bitcoinAddressRecord.type == SegwitAddressType.mweb;
+ return utx.bitcoinAddressRecord.type == SegwitAddresType.mweb;
case UnspentCoinType.nonMweb:
- return utx.bitcoinAddressRecord.type != SegwitAddressType.mweb;
+ return utx.bitcoinAddressRecord.type != SegwitAddresType.mweb;
case UnspentCoinType.any:
return true;
}
@@ -633,7 +634,7 @@ abstract class ElectrumWalletBase
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
// sort the unconfirmed coins so that mweb coins are last:
- availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddressType.mweb ? 1 : -1);
+ availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1);
for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i];
@@ -641,7 +642,7 @@ abstract class ElectrumWalletBase
if (paysToSilentPayment) {
// Check inputs for shared secret derivation
- if (utx.bitcoinAddressRecord.type == SegwitAddressType.p2wsh) {
+ if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) {
throw BitcoinTransactionSilentPaymentsNotSupported();
}
}
@@ -676,7 +677,7 @@ abstract class ElectrumWalletBase
if (privkey != null) {
inputPrivKeyInfos.add(ECPrivateInfo(
privkey,
- address.type == SegwitAddressType.p2tr,
+ address.type == SegwitAddresType.p2tr,
tweak: !isSilentPayment,
));
@@ -1162,7 +1163,7 @@ abstract class ElectrumWalletBase
throw Exception(error);
}
- if (utxo.utxo.isP2tr) {
+ if (utxo.utxo.isP2tr()) {
hasTaprootInputs = true;
return key.privkey.signTapRoot(
txDigest,
@@ -1229,7 +1230,7 @@ abstract class ElectrumWalletBase
'change_address_index': walletAddresses.currentChangeAddressIndexByType,
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
'address_page_type': walletInfo.addressPageType == null
- ? SegwitAddressType.p2wpkh.toString()
+ ? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(),
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
@@ -1369,7 +1370,7 @@ abstract class ElectrumWalletBase
List updatedUnspentCoins = [];
final previousUnspentCoins = List.from(unspentCoins.where((utxo) =>
- utxo.bitcoinAddressRecord.type != SegwitAddressType.mweb &&
+ utxo.bitcoinAddressRecord.type != SegwitAddresType.mweb &&
utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord));
if (hasSilentPaymentsScanning) {
@@ -1383,13 +1384,13 @@ abstract class ElectrumWalletBase
// Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
walletAddresses.allAddresses
- .where((element) => element.type != SegwitAddressType.mweb)
+ .where((element) => element.type != SegwitAddresType.mweb)
.forEach((addr) {
if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
});
final addressFutures = walletAddresses.allAddresses
- .where((element) => element.type != SegwitAddressType.mweb)
+ .where((element) => element.type != SegwitAddresType.mweb)
.map((address) => fetchUnspent(address))
.toList();
@@ -1830,7 +1831,7 @@ abstract class ElectrumWalletBase
throw Exception("Cannot find private key");
}
- if (utxo.utxo.isP2tr) {
+ if (utxo.utxo.isP2tr()) {
return key.signTapRoot(txDigest, sighash: sighash);
} else {
return key.signInput(txDigest, sigHash: sighash);
@@ -1876,20 +1877,17 @@ abstract class ElectrumWalletBase
if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) {
try {
- final blockHash = await http.get(
- Uri.parse(
- "https://mempool.cakewallet.com/api/v1/block-height/$height",
- ),
- );
+ final blockHash = await ProxyWrapper()
+ .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block-height/$height"))
+ .timeout(Duration(seconds: 15));
if (blockHash.statusCode == 200 &&
blockHash.body.isNotEmpty &&
jsonDecode(blockHash.body) != null) {
- final blockResponse = await http.get(
- Uri.parse(
- "https://mempool.cakewallet.com/api/v1/block/${blockHash.body}",
- ),
- );
+ final blockResponse = await ProxyWrapper()
+ .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block/${blockHash}"))
+ .timeout(Duration(seconds: 15));
+
if (blockResponse.statusCode == 200 &&
blockResponse.body.isNotEmpty &&
jsonDecode(blockResponse.body)['timestamp'] != null) {
@@ -1980,7 +1978,7 @@ abstract class ElectrumWalletBase
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.litecoin) {
await Future.wait(LITECOIN_ADDRESS_TYPES
- .where((type) => type != SegwitAddressType.mweb)
+ .where((type) => type != SegwitAddresType.mweb)
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
}
@@ -2169,7 +2167,7 @@ abstract class ElectrumWalletBase
final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
(address) =>
!_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)) &&
- address.type != SegwitAddressType.mweb,
+ address.type != SegwitAddresType.mweb,
);
await Future.wait(unsubscribedScriptHashes.map((address) async {
@@ -2392,9 +2390,9 @@ abstract class ElectrumWalletBase
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
@action
- void _onConnectionStatusChange(electrum.ConnectionStatus status) {
+ void _onConnectionStatusChange(ConnectionStatus status) {
switch (status) {
- case electrum.ConnectionStatus.connected:
+ case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus ||
syncStatus is LostConnectionSyncStatus ||
syncStatus is ConnectingSyncStatus) {
@@ -2402,19 +2400,19 @@ abstract class ElectrumWalletBase
}
break;
- case electrum.ConnectionStatus.disconnected:
+ case ConnectionStatus.disconnected:
if (syncStatus is! NotConnectedSyncStatus &&
syncStatus is! ConnectingSyncStatus &&
syncStatus is! SyncronizingSyncStatus) {
syncStatus = NotConnectedSyncStatus();
}
break;
- case electrum.ConnectionStatus.failed:
+ case ConnectionStatus.failed:
if (syncStatus is! LostConnectionSyncStatus) {
syncStatus = LostConnectionSyncStatus();
}
break;
- case electrum.ConnectionStatus.connecting:
+ case ConnectionStatus.connecting:
if (syncStatus is! ConnectingSyncStatus) {
syncStatus = ConnectingSyncStatus();
}
@@ -2526,7 +2524,7 @@ class ScanData {
final ScanNode? node;
final BasedUtxoNetwork network;
final int chainTip;
- final electrum.ElectrumClient electrumClient;
+ final ElectrumClient electrumClient;
final List transactionHistoryIds;
final Map labels;
final List labelIndexes;
@@ -2570,234 +2568,6 @@ class SyncResponse {
SyncResponse(this.height, this.syncStatus);
}
-Future _handleScanSilentPayments(ScanData scanData) async {
- try {
- // if (scanData.shouldSwitchNodes) {
- var scanningClient = await ElectrumProvider.connect(
- ElectrumTCPService.connect(
- Uri.parse("tcp://electrs.cakewallet.com:50001"),
- ),
- );
- // }
-
- int syncHeight = scanData.height;
- int initialSyncHeight = syncHeight;
-
- final receiver = Receiver(
- scanData.silentAddress.b_scan.toHex(),
- scanData.silentAddress.B_spend.toHex(),
- scanData.network == BitcoinNetwork.testnet,
- scanData.labelIndexes,
- );
-
- int getCountToScanPerRequest(int syncHeight) {
- if (scanData.isSingleScan) {
- return 1;
- }
-
- final amountLeft = scanData.chainTip - syncHeight + 1;
- return amountLeft;
- }
-
- // Initial status UI update, send how many blocks in total to scan
- scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight)));
-
- final req = ElectrumTweaksSubscribe(
- height: syncHeight,
- count: getCountToScanPerRequest(syncHeight),
- historicalMode: false,
- );
-
- var _scanningStream = await scanningClient.subscribe(req);
-
- void listenFn(Map event, ElectrumTweaksSubscribe req) {
- final response = req.onResponse(event);
-
- if (response == null || _scanningStream == null) {
- return;
- }
-
- // is success or error msg
- final noData = response.message != null;
-
- if (noData) {
- if (scanData.isSingleScan) {
- return;
- }
-
- // re-subscribe to continue receiving messages, starting from the next unscanned height
- final nextHeight = syncHeight + 1;
-
- if (nextHeight <= scanData.chainTip) {
- final nextStream = scanningClient.subscribe(
- ElectrumTweaksSubscribe(
- height: nextHeight,
- count: getCountToScanPerRequest(nextHeight),
- historicalMode: false,
- ),
- );
-
- if (nextStream != null) {
- nextStream.listen((event) => listenFn(event, req));
- } else {
- scanData.sendPort.send(
- SyncResponse(scanData.height, LostConnectionSyncStatus()),
- );
- }
- }
-
- return;
- }
-
- final tweakHeight = response.block;
-
- if (initialSyncHeight < tweakHeight) initialSyncHeight = tweakHeight;
-
- // Continuous status UI update, send how many blocks left to scan
- final syncingStatus = scanData.isSingleScan
- ? SyncingSyncStatus(1, 0)
- : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, tweakHeight);
-
- scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
-
- try {
- final blockTweaks = response.blockTweaks;
-
- for (final txid in blockTweaks.keys) {
- final tweakData = blockTweaks[txid];
- final outputPubkeys = tweakData!.outputPubkeys;
- final tweak = tweakData.tweak;
-
- try {
- final addToWallet = {};
-
- // receivers.forEach((receiver) {
- // NOTE: scanOutputs, from sp_scanner package, called from rust here
- final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver);
-
- if (scanResult.isEmpty) {
- continue;
- }
-
- if (addToWallet[receiver.BSpend] == null) {
- addToWallet[receiver.BSpend] = scanResult;
- } else {
- addToWallet[receiver.BSpend].addAll(scanResult);
- }
- // });
-
- if (addToWallet.isEmpty) {
- // no results tx, continue to next tx
- continue;
- }
-
- // initial placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) on the following loop
- final txInfo = ElectrumTransactionInfo(
- WalletType.bitcoin,
- id: txid,
- height: tweakHeight,
- amount: 0,
- fee: 0,
- direction: TransactionDirection.incoming,
- isReplaced: false,
- date: DateTime.fromMillisecondsSinceEpoch(
- DateTime.now().millisecondsSinceEpoch * 1000,
- ),
- confirmations: scanData.chainTip - tweakHeight + 1,
- isReceivedSilentPayment: true,
- isPending: false,
- unspents: [],
- );
-
- List unspents = [];
-
- addToWallet.forEach((BSpend, scanResultPerLabel) {
- scanResultPerLabel.forEach((label, scanOutput) {
- final labelValue = label == "None" ? null : label.toString();
-
- (scanOutput as Map).forEach((outputPubkey, tweak) {
- final t_k = tweak as String;
-
- final receivingOutputAddress = ECPublic.fromHex(outputPubkey)
- .toTaprootAddress(tweak: false)
- .toAddress(scanData.network);
-
- final matchingOutput = outputPubkeys[outputPubkey]!;
- final amount = matchingOutput.amount;
- final pos = matchingOutput.vout;
-
- // final matchingSPWallet = scanData.silentPaymentsWallets.firstWhere(
- // (receiver) => receiver.B_spend.toHex() == BSpend.toString(),
- // );
-
- // final labelIndex = labelValue != null ? scanData.labels[label] : 0;
- // final balance = ElectrumBalance();
- // balance.confirmed = amount;
-
- final receivedAddressRecord = BitcoinSilentPaymentAddressRecord(
- receivingOutputAddress,
- index: 0,
- isHidden: false,
- isUsed: true,
- network: scanData.network,
- silentPaymentTweak: t_k,
- type: SegwitAddressType.p2tr,
- txCount: 1,
- balance: amount,
- );
-
- final unspent = BitcoinSilentPaymentsUnspent(
- receivedAddressRecord,
- txid,
- amount,
- pos,
- silentPaymentTweak: t_k,
- silentPaymentLabel: labelValue,
- );
-
- unspents.add(unspent);
- txInfo.unspents!.add(unspent);
- txInfo.amount += unspent.value;
- });
- });
- });
-
- scanData.sendPort.send({txInfo.id: txInfo});
- } catch (e, stacktrace) {
- printV(stacktrace);
- printV(e.toString());
- }
- }
- } catch (e, stacktrace) {
- printV(stacktrace);
- printV(e.toString());
- }
-
- syncHeight = tweakHeight;
-
- if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) {
- if (tweakHeight >= scanData.chainTip)
- scanData.sendPort.send(
- SyncResponse(syncHeight, SyncedTipSyncStatus(scanData.chainTip)),
- );
-
- if (scanData.isSingleScan) {
- scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus()));
- }
-
- _scanningStream?.close();
- _scanningStream = null;
- return;
- }
- }
-
- _scanningStream?.listen((event) => listenFn(event, req));
- } catch (e) {
- printV("Error in _handleScanSilentPayments: $e");
- scanData.sendPort.send(SyncResponse(scanData.height, LostConnectionSyncStatus()));
- }
-}
-
Future startRefresh(ScanData scanData) async {
int syncHeight = scanData.height;
int initialSyncHeight = syncHeight;
@@ -2810,7 +2580,7 @@ Future startRefresh(ScanData scanData) async {
useSSL: scanData.node?.useSSL ?? false,
);
- int getCountToScanPerRequest(int syncHeight) {
+ int getCountPerRequest(int syncHeight) {
if (scanData.isSingleScan) {
return 1;
}
@@ -2825,10 +2595,11 @@ Future startRefresh(ScanData scanData) async {
scanData.silentAddress.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes,
+ scanData.labelIndexes.length,
);
// Initial status UI update, send how many blocks in total to scan
- final initialCount = getCountToScanPerRequest(syncHeight);
+ final initialCount = getCountPerRequest(syncHeight);
scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight)));
tweaksSubscription = await electrumClient.tweaksSubscribe(
@@ -2839,24 +2610,22 @@ Future startRefresh(ScanData scanData) async {
Future listenFn(t) async {
final tweaks = t as Map;
final msg = tweaks["message"];
-
- // is success or error msg
+ // success or error msg
final noData = msg != null;
if (noData) {
- if (scanData.isSingleScan) {
- return;
- }
-
// re-subscribe to continue receiving messages, starting from the next unscanned height
final nextHeight = syncHeight + 1;
+ final nextCount = getCountPerRequest(nextHeight);
- if (nextHeight <= scanData.chainTip) {
- final nextStream = electrumClient.tweaksSubscribe(
+ if (nextCount > 0) {
+ tweaksSubscription?.close();
+
+ final nextTweaksSubscription = electrumClient.tweaksSubscribe(
height: nextHeight,
- count: getCountToScanPerRequest(nextHeight),
+ count: nextCount,
);
- nextStream?.listen(listenFn);
+ nextTweaksSubscription?.listen(listenFn);
}
return;
@@ -2938,7 +2707,7 @@ Future startRefresh(ScanData scanData) async {
isUsed: true,
network: scanData.network,
silentPaymentTweak: t_k,
- type: SegwitAddressType.p2tr,
+ type: SegwitAddresType.p2tr,
txCount: 1,
balance: amount!,
);
@@ -3031,15 +2800,15 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
} else if (type is P2shAddress) {
return P2shAddressType.p2wpkhInP2sh;
} else if (type is P2wshAddress) {
- return SegwitAddressType.p2wsh;
+ return SegwitAddresType.p2wsh;
} else if (type is P2trAddress) {
- return SegwitAddressType.p2tr;
+ return SegwitAddresType.p2tr;
} else if (type is MwebAddress) {
- return SegwitAddressType.mweb;
+ return SegwitAddresType.mweb;
} else if (type is SilentPaymentsAddresType) {
return SilentPaymentsAddresType.p2sp;
} else {
- return SegwitAddressType.p2wpkh;
+ return SegwitAddresType.p2wpkh;
}
}
diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart
index 9d1ae54aa..614a06a3b 100644
--- a/cw_bitcoin/lib/electrum_wallet_addresses.dart
+++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart
@@ -17,16 +17,16 @@ part 'electrum_wallet_addresses.g.dart';
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
const List BITCOIN_ADDRESS_TYPES = [
- SegwitAddressType.p2wpkh,
+ SegwitAddresType.p2wpkh,
P2pkhAddressType.p2pkh,
- SegwitAddressType.p2tr,
- SegwitAddressType.p2wsh,
+ SegwitAddresType.p2tr,
+ SegwitAddresType.p2wsh,
P2shAddressType.p2wpkhInP2sh,
];
const List LITECOIN_ADDRESS_TYPES = [
- SegwitAddressType.p2wpkh,
- SegwitAddressType.mweb,
+ SegwitAddresType.p2wpkh,
+ SegwitAddresType.mweb,
];
const List BITCOIN_CASH_ADDRESS_TYPES = [
@@ -62,7 +62,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
_addressPageType = initialAddressPageType ??
(walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
- : SegwitAddressType.p2wpkh),
+ : SegwitAddresType.p2wpkh),
silentAddresses = ObservableList.of(
(initialSilentAddresses ?? []).toSet()),
currentSilentAddressIndex = initialSilentAddressIndex,
@@ -71,12 +71,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
super(walletInfo) {
if (masterHd != null) {
silentAddress = SilentPaymentOwner.fromPrivateKeys(
- b_scan: ECPrivate.fromHex(
- masterHd.derivePath("m/352'/1'/0'/1'/0").privateKey.toHex(),
- ),
- b_spend: ECPrivate.fromHex(
- masterHd.derivePath("m/352'/1'/0'/0'/0").privateKey.toHex(),
- ),
+ 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) {
@@ -147,13 +144,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return silentAddress.toString();
}
- final typeMatchingAddresses =
- _addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList();
- final typeMatchingReceiveAddresses =
- typeMatchingAddresses.where((addr) => !addr.isUsed).toList();
+ final typeMatchingAddresses = _addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList();
+ final typeMatchingReceiveAddresses = typeMatchingAddresses.where((addr) => !addr.isUsed).toList();
if (!isEnabledAutoGenerateSubaddress) {
- if (previousAddressRecord != null && previousAddressRecord!.type == addressPageType) {
+ if (previousAddressRecord != null &&
+ previousAddressRecord!.type == addressPageType) {
return previousAddressRecord!.address;
}
@@ -253,17 +249,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (walletInfo.type == WalletType.bitcoinCash) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
} else if (walletInfo.type == WalletType.litecoin) {
- await _generateInitialAddresses(type: SegwitAddressType.p2wpkh);
+ await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
- await _generateInitialAddresses(type: SegwitAddressType.mweb);
+ await _generateInitialAddresses(type: SegwitAddresType.mweb);
}
} else if (walletInfo.type == WalletType.bitcoin) {
await _generateInitialAddresses();
if (!isHardwareWallet) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
- await _generateInitialAddresses(type: SegwitAddressType.p2tr);
- await _generateInitialAddresses(type: SegwitAddressType.p2wsh);
+ await _generateInitialAddresses(type: SegwitAddresType.p2tr);
+ await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
}
}
@@ -327,7 +323,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) {
final currentSilentAddressIndex = silentAddresses
- .where((addressRecord) => addressRecord.type != SegwitAddressType.p2tr)
+ .where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr)
.length -
1;
@@ -385,7 +381,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void addBitcoinAddressTypes() {
final lastP2wpkh = _addresses
.where((addressRecord) =>
- _isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.p2wpkh))
+ _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
.toList()
.last;
if (lastP2wpkh.address != address) {
@@ -411,7 +407,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
final lastP2tr = _addresses.firstWhere(
- (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.p2tr));
+ (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
if (lastP2tr.address != address) {
addressesMap[lastP2tr.address] = 'P2TR';
} else {
@@ -419,7 +415,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
final lastP2wsh = _addresses.firstWhere(
- (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.p2wsh));
+ (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
if (lastP2wsh.address != address) {
addressesMap[lastP2wsh.address] = 'P2WSH';
} else {
@@ -444,7 +440,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void addLitecoinAddressTypes() {
final lastP2wpkh = _addresses
.where((addressRecord) =>
- _isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.p2wpkh))
+ _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
.toList()
.last;
if (lastP2wpkh.address != address) {
@@ -454,7 +450,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
final lastMweb = _addresses.firstWhere(
- (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.mweb));
+ (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb));
if (lastMweb.address != address) {
addressesMap[lastMweb.address] = 'MWEB';
} else {
@@ -564,14 +560,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addressRecord.isHidden &&
!addressRecord.isUsed &&
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
- (walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddressType.p2wpkh));
+ (walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh));
changeAddresses.addAll(newAddresses);
}
@action
Future discoverAddresses(List addressList, bool isHidden,
Future Function(BitcoinAddressRecord) getAddressHistory,
- {BitcoinAddressType type = SegwitAddressType.p2wpkh}) async {
+ {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
final newAddresses = await _createNewAddresses(gap,
startIndex: addressList.length, isHidden: isHidden, type: type);
addAddresses(newAddresses);
@@ -585,7 +581,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
Future _generateInitialAddresses(
- {BitcoinAddressType type = SegwitAddressType.p2wpkh}) async {
+ {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
var countOfReceiveAddresses = 0;
var countOfHiddenAddresses = 0;
@@ -662,7 +658,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void _validateAddresses() {
_addresses.forEach((element) async {
- if (element.type == SegwitAddressType.mweb) {
+ if (element.type == SegwitAddresType.mweb) {
// this would add a ton of startup lag for mweb addresses since we have 1000 of them
return;
}
diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart
index 3e5f331df..990719089 100644
--- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart
+++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart
@@ -87,8 +87,8 @@ class ElectrumWalletSnapshot {
final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
- var regularAddressIndexByType = {SegwitAddressType.p2wpkh.toString(): 0};
- var changeAddressIndexByType = {SegwitAddressType.p2wpkh.toString(): 0};
+ var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
+ var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var silentAddressIndex = 0;
final derivationType = DerivationType
@@ -97,10 +97,10 @@ class ElectrumWalletSnapshot {
try {
regularAddressIndexByType = {
- SegwitAddressType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
+ SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
};
changeAddressIndexByType = {
- SegwitAddressType.p2wpkh.toString():
+ SegwitAddresType.p2wpkh.toString():
int.parse(data['change_address_index'] as String? ?? '0')
};
silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 4e6b2536a..08c56c600 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -16,6 +16,7 @@ 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';
@@ -970,9 +971,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
List? inputPrivKeyInfos,
List? vinOutpoints,
}) async {
- bool spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddressType.mweb);
+ bool spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddresType.mweb);
bool paysToMweb = outputs
- .any((output) => output.toOutput.scriptPubKey.getAddressType() == SegwitAddressType.mweb);
+ .any((output) => output.toOutput.scriptPubKey.getAddressType() == SegwitAddresType.mweb);
bool isRegular = !spendsMweb && !paysToMweb;
bool isMweb = spendsMweb || paysToMweb;
@@ -1063,9 +1064,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
tx.isMweb = mwebEnabled;
if (!mwebEnabled) {
- tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
- .getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
- .address;
+ tx.changeAddressOverride =
+ (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
+ .address;
return tx;
}
await waitForMwebAddresses();
@@ -1107,7 +1108,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// check if mweb inputs are used:
for (final utxo in tx.utxos) {
- if (utxo.utxo.scriptType == SegwitAddressType.mweb) {
+ if (utxo.utxo.scriptType == SegwitAddresType.mweb) {
hasMwebInput = true;
}
}
@@ -1118,9 +1119,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool isRegular = !hasMwebInput && !hasMwebOutput;
bool shouldNotUseMwebChange = isPegIn || isRegular || !hasMwebInput;
tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
- .getChangeAddress(
- coinTypeToSpendFrom:
- shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
+ .getChangeAddress(coinTypeToSpendFrom: shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
.address;
if (isRegular) {
tx.isMweb = false;
diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
index 5e5a27fe8..bbb987766 100644
--- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
@@ -106,7 +106,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
.map((e) => BitcoinAddressRecord(
e.value,
index: e.key,
- type: SegwitAddressType.mweb,
+ type: SegwitAddresType.mweb,
network: network,
))
.toList();
@@ -128,7 +128,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required Bip32Slip10Secp256k1 hd,
BitcoinAddressType? addressType,
}) {
- if (addressType == SegwitAddressType.mweb) {
+ if (addressType == SegwitAddresType.mweb) {
return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1];
}
return generateP2WPKHAddress(hd: hd, index: index, network: network);
@@ -140,7 +140,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required Bip32Slip10Secp256k1 hd,
BitcoinAddressType? addressType,
}) async {
- if (addressType == SegwitAddressType.mweb) {
+ if (addressType == SegwitAddresType.mweb) {
await ensureMwebAddressUpToIndexExists(index);
}
return getAddress(index: index, hd: hd, addressType: addressType);
@@ -195,7 +195,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
return BitcoinAddressRecord(
mwebAddrs[0],
index: 0,
- type: SegwitAddressType.mweb,
+ type: SegwitAddresType.mweb,
network: network,
);
}
@@ -207,7 +207,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
String get addressForExchange {
// don't use mweb addresses for exchange refund address:
final addresses = receiveAddresses
- .where((element) => element.type == SegwitAddressType.p2wpkh && !element.isUsed);
+ .where((element) => element.type == SegwitAddresType.p2wpkh && !element.isUsed);
return addresses.first.address;
}
}
diff --git a/cw_bitcoin/lib/payjoin/manager.dart b/cw_bitcoin/lib/payjoin/manager.dart
index 9476ce23e..95a523d89 100644
--- a/cw_bitcoin/lib/payjoin/manager.dart
+++ b/cw_bitcoin/lib/payjoin/manager.dart
@@ -6,6 +6,7 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
+import 'package:cw_bitcoin/payjoin/payjoin_persister.dart';
import 'package:cw_bitcoin/payjoin/payjoin_receive_worker.dart';
import 'package:cw_bitcoin/payjoin/payjoin_send_worker.dart';
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
@@ -16,6 +17,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:payjoin_flutter/common.dart';
import 'package:payjoin_flutter/receive.dart';
import 'package:payjoin_flutter/send.dart';
+import 'package:payjoin_flutter/src/config.dart' as pj_config;
import 'package:payjoin_flutter/uri.dart' as PayjoinUri;
class PayjoinManager {
@@ -31,11 +33,13 @@ class PayjoinManager {
'https://ohttp.cakewallet.com',
];
- static Future randomOhttpRelayUrl() =>
- PayjoinUri.Url.fromStr(ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)]);
+ static String randomOhttpRelayUrl() =>
+ ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)];
static const payjoinDirectoryUrl = 'https://payjo.in';
+ Future initPayjoin() => pj_config.PConfig.initializeApp();
+
Future resumeSessions() async {
final allSessions = _payjoinStorage.readAllOpenSessions(_wallet.id);
@@ -43,13 +47,13 @@ class PayjoinManager {
if (session.isSenderSession) {
printV("Resuming Payjoin Sender Session ${session.pjUri!}");
return _spawnSender(
- sender: Sender.fromJson(session.sender!),
+ sender: Sender.fromJson(json: session.sender!),
pjUri: session.pjUri!,
);
}
- final receiver = Receiver.fromJson(session.receiver!);
+ final receiver = Receiver.fromJson(json: session.receiver!);
printV("Resuming Payjoin Receiver Session ${receiver.id()}");
- return _spawnReceiver(receiver: receiver);
+ return spawnReceiver(receiver: receiver);
});
printV("Resumed ${spawnedSessions.length} Payjoin Sessions");
@@ -59,13 +63,19 @@ class PayjoinManager {
Future initSender(
String pjUriString, String originalPsbt, int networkFeesSatPerVb) async {
try {
- final pjUri = (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported();
+ final pjUri =
+ (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported();
final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250);
final senderBuilder = await SenderBuilder.fromPsbtAndUri(
psbtBase64: originalPsbt,
pjUri: pjUri,
);
- return senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu);
+ final persister = PayjoinSenderPersister.impl();
+ final newSender =
+ await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu);
+ final senderToken = await newSender.persist(persister: persister);
+
+ return Sender.load(token: senderToken, persister: persister);
} catch (e) {
throw Exception('Error initializing Payjoin Sender: $e');
}
@@ -78,7 +88,8 @@ class PayjoinManager {
bool isTestnet = false,
}) async {
final pjUri = Uri.parse(pjUrl).queryParameters['pj']!;
- await _payjoinStorage.insertSenderSession(sender, pjUri, _wallet.id, amount);
+ await _payjoinStorage.insertSenderSession(
+ sender, pjUri, _wallet.id, amount);
return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri);
}
@@ -110,15 +121,13 @@ class PayjoinManager {
}
} catch (e) {
_cleanupSession(pjUri);
- printV(e);
- await _payjoinStorage.markSenderSessionUnrecoverable(pjUri);
- completer.completeError(e);
+ await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, e.toString());
+ completer.complete();
}
} else if (message is PayjoinSessionError) {
_cleanupSession(pjUri);
if (message is UnrecoverableError) {
- printV(message.message);
- await _payjoinStorage.markSenderSessionUnrecoverable(pjUri);
+ await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, message.message);
completer.complete();
} else if (message is RecoverableError) {
completer.complete();
@@ -138,40 +147,41 @@ class PayjoinManager {
return completer.future;
}
- Future initReceiver(String address, [bool isTestnet = false]) async {
- try {
- final payjoinDirectory = await PayjoinUri.Url.fromStr(payjoinDirectoryUrl);
+ Future getUnusedReceiver(String address,
+ [bool isTestnet = false]) async {
+ final session = _payjoinStorage.getUnusedActiveReceiverSession(_wallet.id);
- final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
- ohttpRelay: await randomOhttpRelayUrl(),
- payjoinDirectory: payjoinDirectory,
- );
+ if (session != null) {
+ await PayjoinUri.Url.fromStr(payjoinDirectoryUrl);
- final receiver = await Receiver.create(
- address: address,
- network: isTestnet ? Network.testnet : Network.bitcoin,
- directory: payjoinDirectory,
- ohttpKeys: ohttpKeys,
- ohttpRelay: await randomOhttpRelayUrl(),
- );
-
- await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
-
- return receiver;
- } catch (e) {
- throw Exception('Error initializing Payjoin Receiver: $e');
+ return Receiver.fromJson(json: session.receiver!);
}
+
+ return initReceiver(address);
}
- Future spawnNewReceiver({
- required Receiver receiver,
- bool isTestnet = false,
- }) async {
+ Future initReceiver(String address, [bool isTestnet = false]) async {
+ final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
+ ohttpRelay: await randomOhttpRelayUrl(),
+ payjoinDirectory: payjoinDirectoryUrl,
+ );
+
+ final newReceiver = await NewReceiver.create(
+ address: address,
+ network: isTestnet ? Network.testnet : Network.bitcoin,
+ directory: payjoinDirectoryUrl,
+ ohttpKeys: ohttpKeys,
+ );
+ final persister = PayjoinReceiverPersister.impl();
+ final receiverToken = await newReceiver.persist(persister: persister);
+ final receiver = await Receiver.load(persister: persister, token: receiverToken);
+
await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
- return _spawnReceiver(isTestnet: isTestnet, receiver: receiver);
+
+ return receiver;
}
- Future _spawnReceiver({
+ Future spawnReceiver({
required Receiver receiver,
bool isTestnet = false,
}) async {
@@ -191,11 +201,13 @@ class PayjoinManager {
rawAmount = getOutputAmountFromTx(tx, _wallet);
break;
case PayjoinReceiverRequestTypes.checkIsOwned:
- (_wallet.walletAddresses as BitcoinWalletAddresses).newPayjoinReceiver();
+ (_wallet.walletAddresses as BitcoinWalletAddresses)
+ .newPayjoinReceiver();
_payjoinStorage.markReceiverSessionInProgress(receiver.id());
final inputScript = message['input_script'] as Uint8List;
- final isOwned = _wallet.isMine(Script.fromRaw(bytes: inputScript));
+ final isOwned =
+ _wallet.isMine(Script.fromRaw(byteData: inputScript));
mainToIsolateSendPort?.send({
'requestId': message['requestId'],
'result': isOwned,
@@ -204,7 +216,8 @@ class PayjoinManager {
case PayjoinReceiverRequestTypes.checkIsReceiverOutput:
final outputScript = message['output_script'] as Uint8List;
- final isReceiverOutput = _wallet.isMine(Script.fromRaw(bytes: outputScript));
+ final isReceiverOutput =
+ _wallet.isMine(Script.fromRaw(byteData: outputScript));
mainToIsolateSendPort?.send({
'requestId': message['requestId'],
'result': isReceiverOutput,
@@ -213,6 +226,10 @@ class PayjoinManager {
case PayjoinReceiverRequestTypes.getCandidateInputs:
utxos = _wallet.getUtxoWithPrivateKeys();
+ if (utxos.isEmpty) {
+ await _wallet.updateAllUnspents();
+ utxos = _wallet.getUtxoWithPrivateKeys();
+ }
mainToIsolateSendPort?.send({
'requestId': message['requestId'],
'result': utxos,
@@ -237,13 +254,15 @@ class PayjoinManager {
}
} catch (e) {
_cleanupSession(receiver.id());
- await _payjoinStorage.markReceiverSessionUnrecoverable(receiver.id(), e.toString());
+ await _payjoinStorage.markReceiverSessionUnrecoverable(
+ receiver.id(), e.toString());
completer.completeError(e);
}
} else if (message is PayjoinSessionError) {
_cleanupSession(receiver.id());
if (message is UnrecoverableError) {
- await _payjoinStorage.markReceiverSessionUnrecoverable(receiver.id(), message.message);
+ await _payjoinStorage.markReceiverSessionUnrecoverable(
+ receiver.id(), message.message);
completer.complete();
} else if (message is RecoverableError) {
completer.complete();
diff --git a/cw_bitcoin/lib/payjoin/payjoin_persister.dart b/cw_bitcoin/lib/payjoin/payjoin_persister.dart
new file mode 100644
index 000000000..4e395e36a
--- /dev/null
+++ b/cw_bitcoin/lib/payjoin/payjoin_persister.dart
@@ -0,0 +1,66 @@
+import 'package:payjoin_flutter/src/generated/api/receive.dart';
+import 'package:payjoin_flutter/src/generated/api/send.dart';
+
+class PayjoinSenderPersister implements DartSenderPersister {
+ static DartSenderPersister impl() {
+ final impl = PayjoinSenderPersister();
+ return DartSenderPersister(
+ save: (sender) => impl.save(sender: sender),
+ load: (token) => impl.load(token: token),
+ );
+ }
+
+ final Map _store = {};
+
+ Future save({required FfiSender sender}) async {
+ final token = sender.key();
+ _store[token.toBytes().toString()] = sender;
+ return token;
+ }
+
+ Future load({required SenderToken token}) async {
+ final sender = _store[token.toBytes().toString()];
+ if (sender == null) {
+ throw Exception('Sender not found for the provided token.');
+ }
+ return sender;
+ }
+
+ @override
+ void dispose() => _store.clear();
+
+ @override
+ bool get isDisposed => _store.isEmpty;
+}
+
+class PayjoinReceiverPersister implements DartReceiverPersister {
+ static DartReceiverPersister impl() {
+ final impl = PayjoinReceiverPersister();
+ return DartReceiverPersister(
+ save: (receiver) => impl.save(receiver: receiver),
+ load: (token) => impl.load(token: token),
+ );
+ }
+
+ final Map _store = {};
+
+ Future save({required FfiReceiver receiver}) async {
+ final token = receiver.key();
+ _store[token.toBytes().toString()] = receiver;
+ return token;
+ }
+
+ Future load({required ReceiverToken token}) async {
+ final receiver = _store[token.toBytes().toString()];
+ if (receiver == null) {
+ throw Exception('Receiver not found for the provided token.');
+ }
+ return receiver;
+ }
+
+ @override
+ void dispose() => _store.clear();
+
+ @override
+ bool get isDisposed => _store.isEmpty;
+}
diff --git a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart
index a499660b0..c56148de2 100644
--- a/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart
+++ b/cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart
@@ -4,14 +4,16 @@ import 'dart:isolate';
import 'dart:typed_data';
import 'package:blockchain_utils/blockchain_utils.dart';
+import 'package:cw_bitcoin/payjoin/manager.dart';
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
import 'package:cw_bitcoin/psbt/signer.dart';
import 'package:cw_core/utils/print_verbose.dart';
-import 'package:http/http.dart' as http;
+import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:payjoin_flutter/bitcoin_ffi.dart';
import 'package:payjoin_flutter/common.dart';
import 'package:payjoin_flutter/receive.dart';
import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
+import 'package:http/http.dart' as very_insecure_http_do_not_use; // for errors
enum PayjoinReceiverRequestTypes {
processOriginalTx,
@@ -27,7 +29,7 @@ class PayjoinReceiverWorker {
final pendingRequests = >{};
PayjoinReceiverWorker._(this.sendPort);
-
+ static final client = ProxyWrapper().getHttpIOClient();
static Future run(List