├── .gitignore ├── .gitmodules ├── doc ├── electrum-banner.png └── electrum-plugin.png ├── src ├── __init__.py ├── cmdline.py ├── run-appimage.sh ├── qt.py └── bwt.py ├── CHANGELOG.md ├── .travis.yml ├── LICENSE ├── SHA256SUMS.asc ├── scripts ├── release-footer.md ├── build.sh └── release.sh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | src/bwt 3 | dist 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "bwt"] 2 | path = bwt 3 | url = https://github.com/bwt-dev/bwt 4 | -------------------------------------------------------------------------------- /doc/electrum-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwt-dev/bwt-electrum-plugin/HEAD/doc/electrum-banner.png -------------------------------------------------------------------------------- /doc/electrum-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwt-dev/bwt-electrum-plugin/HEAD/doc/electrum-plugin.png -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from electrum.i18n import _ 2 | 3 | fullname = _('bwt') 4 | description = _('Connect with Bitcoin Core via bwt') 5 | available_for = ['qt', 'cmdline'] 6 | -------------------------------------------------------------------------------- /src/cmdline.py: -------------------------------------------------------------------------------- 1 | from .bwt import BwtPlugin 2 | 3 | class Plugin(BwtPlugin): 4 | pass 5 | 6 | # TODO: find a way to get the list of loaded wallets when starting up, 7 | # similarly to what the qt mode does using the init_qt hook. 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | 3 | - Support custom CLI arguments with spaces (#4) 4 | 5 | ## 0.2.4 - 2021-03-25 6 | 7 | - Update to [bwt v0.2.4](https://github.com/bwt-dev/bwt/releases/tag/v0.2.4) 8 | 9 | ## 0.2.3 - 2021-03-17 10 | 11 | - Update to [bwt v0.2.3](https://github.com/bwt-dev/bwt/releases/tag/v0.2.3) 12 | 13 | ## 0.2.2 - 2021-01-29 14 | 15 | - Add instructions and utility script for using the Electrum AppImage (#2) 16 | 17 | - Add "Create wallet if missing" option to the GUI (https://github.com/bwt-dev/bwt/issues/76) 18 | 19 | - Fix multi-signature wallet detection 20 | 21 | ## 0.2.1 - 2021-01-14 22 | 23 | Migrated from [`bwt-dev/bwt`](https://github.com/bwt-dev/bwt) into a standalone repo 24 | 25 | (See https://github.com/bwt-dev/bwt/commit/58fbdda64d6287895f48f852d882fff484ef3d4b) 26 | -------------------------------------------------------------------------------- /src/run-appimage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | shopt -s extglob 4 | 5 | # Utility for using the Electrum AppImage with the Bitcoin Wallet Tracker plugin. 6 | # Extracts the AppImage into a directory, adds the bwt plugin, and runs Electrum. 7 | 8 | APPIMAGE_PATH=$(realpath "${1:?"Missing AppImage path, run with: $0 path/to/Electrum-x.y.z.AppImage"}") 9 | BWT_PLUGIN_DIR=$(dirname "$(readlink -e "$0")") 10 | SQUASH_ROOT=$BWT_PLUGIN_DIR/squashfs-root 11 | 12 | if [ ! -d "$SQUASH_ROOT" ]; then 13 | (cd "$BWT_PLUGIN_DIR" && "$APPIMAGE_PATH" --appimage-extract) 14 | 15 | PLUGINS_DIR=$(echo "$SQUASH_ROOT"/usr/lib/python3.*/site-packages/electrum/plugins) 16 | ln -s ../../../../../../.. "$PLUGINS_DIR/bwt" 17 | 18 | echo Electrum AppImage extracted to $SQUASH_ROOT with the Bitcoin Wallet Tracker plugin. 19 | fi 20 | 21 | echo You may also run Electrum directly with: 22 | echo $ $SQUASH_ROOT/AppRun 23 | 24 | exec "$SQUASH_ROOT/AppRun" "${@:2}" 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: [ CARGO_TERM_COLOR=always ] 2 | jobs: 3 | include: 4 | - &build 5 | stage: Reproducible builds 6 | 7 | before_script: | 8 | git submodule update --init 9 | docker build -t bwt-builder - < bwt/scripts/builder.Dockerfile 10 | [ "$IMAGE" == "builder" ] || docker build -t bwt-$IMAGE - < bwt/scripts/$IMAGE.Dockerfile 11 | 12 | script: 13 | - > 14 | echo -e tr''avis_fo''ld:start:build\\nBuilding... && 15 | docker run -u `id -u` -v `pwd`:/usr/src/libbwt -w /usr/src/libbwt \ 16 | --entrypoint scripts/build.sh bwt-$IMAGE && 17 | echo tr''avis_fol''d:end:build 18 | - > 19 | rm -rf dist/*/ && 20 | echo '-----BEGIN SHA256SUM-----' && 21 | (cd dist && sha256sum * | sort) && 22 | echo 23 | 24 | 25 | name: Linux/Windows/ARMv7/ARMv8 26 | env: IMAGE=builder 27 | 28 | - <<: *build 29 | name: Mac OSX 30 | env: IMAGE=builder-osx 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Nadav Ivgi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /SHA256SUMS.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNED MESSAGE----- 2 | Hash: SHA256 3 | 4 | 3de1f0190d18ac76896e6f65f0ea4f150e80b21d1951ac537826cf893c8330ae bwt-electrum-plugin-0.2.4-arm32v7-linux.tar.gz 5 | 7710315527ca7ca2263391fdb11966e60b791a87e5e400337c15777582379dd3 bwt-electrum-plugin-0.2.4-x86_64-linux.tar.gz 6 | 91f370f642d4d54340f5ac6e4824efd6ca48bcb93da9335b742bffb3dea3bf93 bwt-electrum-plugin-0.2.4-x86_64-windows.zip 7 | 96e250ff3385d0d3158a9c192f55b42fe8110079131e4c12fdd8b9001fa57b49 bwt-electrum-plugin-0.2.4-x86_64-osx.zip 8 | d5e950146e94f2301dde4d5bed102b3abadc5010825b5e685cc6b2bbd624c27d bwt-electrum-plugin-0.2.4-arm64v8-linux.tar.gz 9 | -----BEGIN PGP SIGNATURE----- 10 | 11 | iQEzBAEBCAAdFiEE/PGbZ4ZlYvCKQ6rWgfYQTNDxUPwFAmBb35cACgkQgfYQTNDx 12 | UPxjyAf/SuTcM/Akj2dGwcrMuUESeViKhMvb6NeuVSpSLmWkS7hTyR09AAIx1GZW 13 | WcVx6W0lsOVbckeS/UKBpibfCY05pCZM+mumlg2yU1cO3oPUmHyYpguwoIxSDpDK 14 | exW09qZjAeP9d11iS1k+iAauRcK709iCXUrS2AwLdnOA770wxs2MlXnHLBoWXL69 15 | J/lTl+4whj+5H1RG3KFt+zhgOB8H6KJbc8ITBwiPCb+fAyH2z320rHafbDdlMNIE 16 | HNLwWFP/qKsJkVIHYXsHr5doOmRvjT2qkU2+UF5679grjnZ4wh2AkZRwtGWnSEvi 17 | CqL5yXPmQeH7s7fQ5o4wMnEKLOp5sg== 18 | =4VEK 19 | -----END PGP SIGNATURE----- 20 | -------------------------------------------------------------------------------- /scripts/release-footer.md: -------------------------------------------------------------------------------- 1 | 2 | ------------ 3 | 4 | ### Installation 5 | 6 | Installation instructions are [available on the README](https://github.com/bwt-dev/bwt-electrum-plugin#installation). 7 | 8 | ### Verifying signatures 9 | 10 | The releases are signed by Nadav Ivgi (@shesek). The public key can be verified on the [PGP WoT](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x81F6104CD0F150FC), [github](https://api.github.com/users/shesek/gpg_keys), [twitter](https://twitter.com/shesek), [keybase](https://keybase.io/nadav), [hacker news](https://news.ycombinator.com/user?id=nadaviv) and [this video presentation](https://youtu.be/SXJaN2T3M10?t=4). 11 | 12 | ```bash 13 | # Download (change x86_64-linux to your platform) 14 | $ wget https://github.com/bwt-dev/bwt-electrum-plugin/releases/download/vVERSION/bwt-electrum-plugin-VERSION-x86_64-linux.tar.gz 15 | 16 | # Fetch public key 17 | $ gpg --keyserver keyserver.ubuntu.com --recv-keys FCF19B67866562F08A43AAD681F6104CD0F150FC 18 | 19 | # Verify signature 20 | $ wget -qO - https://github.com/bwt-dev/bwt-electrum-plugin/releases/download/vVERSION/SHA256SUMS.asc \ 21 | | gpg --decrypt - | grep x86_64-linux | sha256sum -c - 22 | ``` 23 | 24 | The signature verification should show `Good signature from "Nadav Ivgi " ... Primary key fingerprint: FCF1 9B67 ...` and `bwt-electrum-plugin-VERSION-x86_64-linux.tar.gz: OK`. 25 | 26 | ### Reproducible builds 27 | 28 | The builds are fully reproducible. 29 | 30 | You can verify the checksums against the vVERSION builds on Travis CI: https://travis-ci.org/github/bwt-dev/bwt-electrum-plugin/builds/TRAVIS_JOB 31 | 32 | See [more details here](https://github.com/bwt-dev/bwt-electrum-plugin#reproducible-builds). 33 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xeo pipefail 3 | 4 | # `x86_64-osx` is also available, requires osxcross to be installed (see bwt/builder-osx.Dockerfile) 5 | export TARGETS=${TARGETS:-x86_64-linux,x86_64-win,arm32v7-linux,arm64v8-linux} 6 | 7 | [ -f bwt/Cargo.toml ] || (echo >&2 "Missing bwt submodule, run 'git submodule update --init'" && exit 1) 8 | 9 | version=$(grep -E '^version =' bwt/Cargo.toml | cut -d'"' -f2) 10 | 11 | # Copy the executables from BWT_BIN_DIST if specified, 12 | # or build them if it was not 13 | if [ -z "$BWT_BIN_DIST" ]; then 14 | (cd bwt && ELECTRUM_ONLY_ONLY=1 ./scripts/build.sh) 15 | BWT_BIN_DIST=bwt/dist 16 | elif [ ! -d "$BWT_BIN_DIST" ]; then 17 | echo >&2 BWT_BIN_DIST is configured but missing 18 | exit 1 19 | fi 20 | 21 | build() { 22 | if [[ $TARGETS != *"$1"* ]]; then return; fi 23 | 24 | local platform=$1 25 | local name=bwt-electrum-plugin-$version-$platform 26 | local dest=dist/$name 27 | 28 | mkdir -p $dest 29 | cp $BWT_BIN_DIST/bwt-$version-electrum_only-$platform/* $dest 30 | cp src/*.py src/run-appimage.sh $dest 31 | cp LICENSE README.md $dest 32 | 33 | # needs to be inside a directory with a name that matches the plugin module name for electrum to load it, 34 | # create a temporary link to get tar/zip to pack it properly. (can also be done for tar.gz with --transform) 35 | ln -s $name dist/bwt 36 | pack $name bwt 37 | rm dist/bwt 38 | } 39 | 40 | # pack tar.gz (for linux/mac/arm) or zip (for windows) 41 | pack() { 42 | name=$1; dir=${2:-$1} 43 | pushd dist 44 | touch -t 1711081658 $name $name/* 45 | if [[ $name == *"-linux" || $name == *"-arm"* ]]; then 46 | # use static/removed metadata attrs and deterministic file order for reproducibility 47 | TZ=UTC tar --mtime='2017-11-08 16:58:00' --owner=0 --sort=name -I 'gzip --no-name' -chf $name.tar.gz $dir 48 | else 49 | find -H $dir | sort | xargs zip -X -q $name.zip 50 | fi 51 | popd 52 | } 53 | 54 | build x86_64-linux 55 | build x86_64-osx 56 | build x86_64-windows 57 | build arm32v7-linux 58 | build arm64v8-linux 59 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xeo pipefail 3 | shopt -s expand_aliases 4 | 5 | gh_repo=bwt-dev/bwt-electrum-plugin 6 | 7 | git diff-index --quiet HEAD || (echo >&2 git working directory is dirty && exit 1) 8 | 9 | [ -n "$BWT_BASE" ] || (echo >&2 BWT_BASE is required && exit 1) 10 | [ -n "$BWT_COMMIT" ] || (echo >&2 BWT_COMMIT is required && exit 1) 11 | 12 | export TARGETS=${TARGETS:-x86_64-linux,x86_64-osx,x86_64-windows,arm32v7-linux,arm64v8-linux} 13 | 14 | (cd bwt && git fetch local && git reset --hard $BWT_COMMIT) 15 | 16 | version=$(grep -E '^version =' bwt/Cargo.toml | cut -d'"' -f2) 17 | 18 | echo -e "Releasing bwt-electrum-plugin v$version\n" 19 | 20 | # Prepare unreleased changelog 21 | changelog=$(sed -nr '/^## (Unreleased|'$version' )/{n;:a;n;/^## /q;p;ba}' CHANGELOG.md) 22 | changelog="- Update to [bwt v$version](https://github.com/bwt-dev/bwt/releases/tag/v$version)"$'\n'$changelog 23 | grep '## Unreleased' CHANGELOG.md > /dev/null \ 24 | && sed -i "s/^## Unreleased/## $version - $(date +%Y-%m-%d)/" CHANGELOG.md 25 | 26 | # Update version number in README 27 | sed -i -r "s~bwt-electrum-plugin-[0-9a-z.-]+-x86_64-linux\.~bwt-electrum-plugin-$version-x86_64-linux.~g; s~/(download|tag)/v[0-9a-z.-]+~/\1/v$version~;" README.md 28 | 29 | # Build 30 | if [ -z "$SKIP_BUILD" ]; then 31 | echo Building... 32 | rm -rf dist/* 33 | 34 | BWT_BIN_DIST=$BWT_BASE/bwt/dist \ 35 | ./scripts/build.sh 36 | 37 | rm -rf dist/*/ # remove subdirectories, keep files only 38 | fi 39 | 40 | # Sign 41 | (cd dist && sha256sum *) | sort | gpg --clearsign --digest-algo sha256 > SHA256SUMS.asc 42 | 43 | # Git tag and push 44 | if [ -z "$SKIP_GIT" ]; then 45 | git add {CHANGELOG,README}.md SHA256SUMS.asc bwt 46 | git commit -S -m v$version 47 | git tag --sign -m "$changelog" v$version 48 | git branch -f latest HEAD 49 | git push gh master latest 50 | git push gh --tags 51 | fi 52 | 53 | # Upload distribution files to GitHub releases 54 | if [[ -z "$SKIP_UPLOAD" && -n "$GH_TOKEN" ]]; then 55 | echo Uploading to github... 56 | gh_auth="Authorization: token $GH_TOKEN" 57 | gh_base=https://api.github.com/repos/$gh_repo 58 | 59 | sleep 3 # allow some time for the job to show up on travis 60 | travis_job=$(curl -s "https://api.travis-ci.org/v3/repo/${gh_repo/\//%2F}/branch/v$version" | jq -r '.last_build.id // ""') 61 | 62 | release_text="### Changelog"$'\n'$'\n'$changelog$'\n'$'\n'$(sed "s/VERSION/$version/g; s/TRAVIS_JOB/$travis_job/g;" scripts/release-footer.md) 63 | release_opt=$(jq -n --arg version v$version --arg text "$release_text" \ 64 | '{ tag_name: $version, name: $version, body: $text, draft:true }') 65 | gh_release=$(curl -sf -H "$gh_auth" $gh_base/releases/tags/v$version \ 66 | || curl -sf -H "$gh_auth" -d "$release_opt" $gh_base/releases) 67 | gh_upload=$(echo "$gh_release" | jq -r .upload_url | sed -e 's/{?name,label}//') 68 | 69 | for file in SHA256SUMS.asc dist/*; do 70 | echo ">> Uploading $file" 71 | 72 | curl -f --progress-bar -H "$gh_auth" -H "Content-Type: application/octet-stream" \ 73 | --data-binary @"$file" "$gh_upload?name=$(basename "$file")" | (grep -v browser_download_url || true) 74 | done 75 | 76 | # mark release as public once everything is ready 77 | curl -sf -H "$gh_auth" -X PATCH "$gh_base/releases/$(echo "$gh_release" | jq -r .id)" \ 78 | -d '{"draft":false}' > /dev/null 79 | fi 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Wallet Tracker - Electrum Plugin 2 | 3 | [![Build Status](https://travis-ci.org/bwt-dev/bwt-electrum-plugin.svg?branch=master)](https://travis-ci.org/bwt-dev/bwt-electrum-plugin) 4 | [![Latest release](https://img.shields.io/github/v/release/bwt-dev/bwt-electrum-plugin?color=orange)](https://github.com/bwt-dev/bwt-electrum-plugin/releases/tag/v0.2.4) 5 | [![Downloads](https://img.shields.io/github/downloads/bwt-dev/bwt/total.svg?color=blueviolet)](https://github.com/bwt-dev/bwt-electrum-plugin/releases) 6 | [![MIT license](https://img.shields.io/github/license/bwt-dev/bwt-electrum-plugin.svg?color=yellow)](https://github.com/bwt-dev/bwt-electrum-plugin/blob/master/LICENSE) 7 | [![Pull Requests Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/bwt-dev/bwt#developing) 8 | 9 | 10 | 11 | Electrum plugin for [Bitcoin Wallet Tracker](https://github.com/bwt-dev/bwt), a lightweight personal indexer for bitcoin wallets. 12 | 13 | The plugin allows connecting Electrum to a Bitcoin Core full node backend, by running 14 | an embedded bwt Electrum server within the Electrum wallet itself. 15 | 16 | Support development: [⛓️ on-chain or ⚡ lightning via BTCPay](https://btcpay.shesek.info/) 17 | 18 | ![Screenshot of bwt integrated into Electrum](https://raw.githubusercontent.com/bwt-dev/bwt-electrum-plugin/master/doc/electrum-plugin.png) 19 | 20 | - [Compatibility](#compatibility) 21 | - [Installation](#installation) 22 | - [With the Electrum AppImage](#with-the-electrum-appimage) 23 | - [Verifying the signature](#verifying-the-signature) 24 | - [Welcome banner](#welcome-banner) 25 | - [Building from source](#building-from-source) 26 | - [Reproducible builds](#reproducible-builds) 27 | - [License](#license) 28 | 29 | ## Compatibility 30 | 31 | The plugin supports Electrum v3 and v4. It is available for Linux, Mac, Windows and ARMv7/v8. It works with multi-signature wallets. It *does not* support Lightning. 32 | 33 | Bitcoin Core v0.19+ is recommended, but it can work (not as well) with v0.17+. `txindex` is not required. 34 | Pruning is supported, but you can only scan for transactions in the non-pruned history. 35 | 36 | The plugin can be used with the Electrum AppImage (see special instructions below), Linux package manager installations, 37 | the Windows installer, the `tar.gz` package, or from source. 38 | It cannot be used with the standalone Windows executable. 39 | 40 | The plugin currently *supports watch-only wallets only* and [*cannot be used with hot wallets*](https://twitter.com/shesek/status/1275057901149667329). This is expected to eventually change. 41 | For now, you can use the plugin with hardware wallets or with an offline Electrum setup. 42 | For hot wallets, you will need to [setup a standalone server](https://github.com/bwt-dev/bwt#setting-up-bwt) 43 | instead of using the plugin. 44 | 45 | ## Installation 46 | 47 | 1. Install and sync Bitcoin Core. If you're using QT, set `server=1` in your `bitcoin.conf` file. 48 | 49 | > It is recommended, but not required, to create a separate bitcoind wallet with `createwallet true true`. 50 | 51 | 2. Download the bwt plugin from the [releases page](https://github.com/bwt-dev/bwt-electrum-plugin/releases), 52 | verify the signature (see below) and unpack the `bwt` directory into your `electrum/plugins` directory. 53 | 54 | > You can find the location of your plugins directory by running `electrum.plugins.__path__` in the Electrum console tab. 55 | 56 | 3. Restart Electrum, open `Tools -> Plugins`, enable `bwt`, click `Connect to bitcoind`, configure your Bitcoin Core RPC details, and click `Save & Connect`. That's it! 57 | 58 | On the first run, rescanning for historical transactions from genesis may take up to 2-3 hours. To speed this up, set the rescan date to when 59 | the wallet was created (or disable rescanning entirely for new wallets). If your node is pruned, the rescan date has to be within 60 | the range of non-pruned blocks. 61 | 62 | The plugin automatically configures Electrum with `oneserver` (to avoid connecting to public servers) and `skipmerklecheck` (necessary for [pruning](https://github.com/bwt-dev/bwt#pruning)). 63 | To avoid connecting to public servers while setting up the plugin, make sure the "auto connect" feature is disabled or run Electrum with `--offline` until everything is ready. 64 | 65 | #### With the Electrum AppImage 66 | 67 | If you're using the Electrum AppImage, you will have to extract it to a directory first and copy the bwt plugin directory into it. 68 | This can be done as follows: 69 | 70 | ```bash 71 | # Extract AppImage (to a subdirectory named 'squashfs-root') 72 | $ ./electrum-x.y.z-x86_64.AppImage --appimage-extract 73 | 74 | # Copy the bwt plugin directory 75 | $ cp -r /path/to/bwt squashfs-root/usr/lib/python3.7/site-packages/electrum/plugins/ 76 | 77 | # Start Electrum 78 | $ ./squashfs-root/AppRun 79 | ``` 80 | 81 | Or using the `run-appimage.sh` utility script available within the plugin directory (which does the same): 82 | 83 | ```bash 84 | # Extract the AppImage, copy bwt and start Electrum 85 | $ ./bwt/run-appimage.sh ./electrum-x.y.z-x86_64.AppImage 86 | 87 | # Can also forward arguments 88 | $ ./bwt/run-appimage.sh ./electrum-x.y.z-x86_64.AppImage --offline 89 | ``` 90 | 91 | #### Verifying the signature 92 | 93 | The releases are signed by Nadav Ivgi (@shesek). 94 | The public key can be verified on 95 | the [PGP WoT](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x81F6104CD0F150FC), 96 | [github](https://api.github.com/users/shesek/gpg_keys), 97 | [twitter](https://twitter.com/shesek), 98 | [keybase](https://keybase.io/nadav), 99 | [hacker news](https://news.ycombinator.com/user?id=nadaviv) 100 | and [this video presentation](https://youtu.be/SXJaN2T3M10?t=4). 101 | 102 | 103 | ```bash 104 | # Download plugin (change x86_64-linux to your platform) 105 | $ wget https://github.com/bwt-dev/bwt-electrum-plugin/releases/download/v0.2.4/bwt-electrum-plugin-0.2.4-x86_64-linux.tar.gz 106 | 107 | # Fetch public key 108 | $ gpg --keyserver keyserver.ubuntu.com --recv-keys FCF19B67866562F08A43AAD681F6104CD0F150FC 109 | 110 | # Verify signature 111 | $ wget -qO - https://github.com/bwt-dev/bwt-electrum-plugin/releases/download/v0.2.4/SHA256SUMS.asc \ 112 | | gpg --decrypt - | grep x86_64-linux | sha256sum -c - 113 | ``` 114 | 115 | The signature verification should show `Good signature from "Nadav Ivgi " ... Primary key fingerprint: FCF1 9B67 ...` and `bwt-electrum-plugin-0.2.4-x86_64-linux.tar.gz: OK`. 116 | 117 | ## Welcome banner 118 | 119 | The bwt Electrum server provides a welcome banner with information about your node and the Bitcoin network. 120 | You can view it by opening the Console tab. 121 | 122 | ![Screenshot of the bwt welcome banner](https://raw.githubusercontent.com/bwt-dev/bwt-electrum-plugin/master/doc/electrum-banner.png) 123 | 124 | ## Building from source 125 | 126 | To build the plugin from source, first build the `bwt` binary (as also [described here](https://github.com/bwt-dev/bwt#from-source)), 127 | copy it into the `src` directory in this repo, then copy that directory into `electrum/plugins`, *but renamed to `bwt`* (Electrum won't recognize it otherwise). 128 | 129 | ```bash 130 | $ git clone https://github.com/bwt-dev/bwt-electrum-plugin && cd bwt-electrum-plugin 131 | $ git checkout 132 | $ git verify-commit HEAD 133 | $ git submodule update --init 134 | 135 | $ cd bwt 136 | $ cargo build --release --no-default-features --features cli,electrum 137 | $ cd .. 138 | $ cp bwt/target/release/bwt src/ 139 | $ cp -r src /usr/local/lib/python3.8/site-packages/electrum/plugins/bwt 140 | ``` 141 | 142 | ## Reproducible builds 143 | 144 | The builds for all supported platforms can be reproduced in a Docker container environment as follows: 145 | 146 | ```bash 147 | $ git clone https://github.com/bwt-dev/bwt-electrum-plugin && cd bwt-electrum-plugin 148 | $ git checkout 149 | $ git verify-commit HEAD 150 | $ git submodule update --init 151 | 152 | # Linux, Windows, ARMv7 and ARMv8 153 | $ docker build -t bwt-builder - < bwt/scripts/builder.Dockerfile 154 | $ docker run -it --rm -u `id -u` -v `pwd`:/usr/src/bwt-electrum-plugin -w /usr/src/bwt-electrum-plugin \ 155 | --entrypoint scripts/build.sh bwt-builder 156 | 157 | # Mac OSX (cross-compiled via osxcross) 158 | $ docker build -t bwt-builder-osx - < bwt/scripts/builder-osx.Dockerfile 159 | $ docker run -it --rm -u `id -u` -v `pwd`:/usr/src/bwt-electrum-plugin -w /usr/src/bwt-electrum-plugin \ 160 | --entrypoint scripts/build.sh bwt-builder-osx 161 | 162 | $ sha256sum dist/* 163 | ``` 164 | 165 | The builds are [reproduced on Travis CI](https://travis-ci.org/github/bwt-dev/bwt-electrum-plugin/branches) using the code from GitHub. 166 | The SHA256 checksums are available under the "Reproducible builds" stage. 167 | 168 | ## License 169 | 170 | MIT 171 | -------------------------------------------------------------------------------- /src/qt.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from xml.sax.saxutils import escape 3 | 4 | from PyQt5.QtCore import Qt, QObject, pyqtSignal 5 | from PyQt5.QtGui import QTextOption 6 | from PyQt5.QtWidgets import QSizePolicy, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTextEdit, QComboBox, QPushButton, QFormLayout, QCheckBox 7 | 8 | from electrum.i18n import _ 9 | from electrum.plugin import hook 10 | from electrum.gui.qt.util import EnterButton, Buttons, CloseButton, WindowModalDialog 11 | 12 | from .bwt import BwtPlugin 13 | 14 | from electrum.logging import get_logger 15 | _logger = get_logger('plugins.bwt') 16 | 17 | class Plugin(BwtPlugin, QObject): 18 | 19 | log_signal = pyqtSignal(str, str, str) 20 | 21 | def __init__(self, *args): 22 | BwtPlugin.__init__(self, *args) 23 | QObject.__init__(self) 24 | self.parent.bwt = self 25 | self.closed = False 26 | 27 | def requires_settings(self): 28 | return True 29 | 30 | def settings_widget(self, window): 31 | return EnterButton(_('Connect to bitcoind'), partial(self.settings_dialog, window)) 32 | 33 | def settings_dialog(self, window): 34 | # hack to workaround a bug: https://github.com/spesmilo/electrum/commit/4d8fcded4b42fd673bbb61f85aa99dc329be28a4 35 | if self.closed: 36 | # if this plugin instance is supposed to be closed and we have a different newer instance available, 37 | # forward the call to the newer one. 38 | if self.parent.bwt and self.parent.bwt != self: 39 | self.parent.bwt.settings_dialog(window) 40 | return 41 | 42 | if not self.wallets: 43 | window.show_error(_('No watch-only hd wallets found. Note that bwt cannot currently be used with hot wallets. See the README for more details.')) 44 | return 45 | 46 | d = WindowModalDialog(window, _('Connect to Bitcoin Core with bwt')) 47 | d.setMinimumWidth(570) 48 | vbox = QVBoxLayout(d) 49 | 50 | form = QFormLayout() 51 | form.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter) 52 | vbox.addLayout(form) 53 | 54 | form.addRow(title(_('Bitcoin Core settings'))) 55 | 56 | url_e = input(self.bitcoind_url) 57 | form.addRow(_('RPC URL:'), url_e) 58 | 59 | auth_e = input(self.bitcoind_auth) 60 | auth_e.setPlaceholderText(':') 61 | form.addRow(_('RPC Auth:'), auth_e) 62 | form.addRow('', helptext(_('Leave blank to use the cookie.'), False)) 63 | 64 | dir_e = input(self.bitcoind_dir) 65 | form.addRow(_('Directory:'), dir_e) 66 | form.addRow('', helptext(_('Used for reading the cookie file. Ignored if auth is set.'), False)) 67 | 68 | wallet_e = input(self.bitcoind_wallet, 150) 69 | wallet_ch = checkbox('Create if missing', self.create_wallet_if_missing) 70 | wallet_hbox = QHBoxLayout() 71 | wallet_hbox.addWidget(wallet_e) 72 | wallet_hbox.addWidget(wallet_ch) 73 | form.addRow(_('Wallet:'), wallet_hbox) 74 | form.addRow('', helptext(_('For use with multi-wallet. Leave blank to use the default wallet.'), False)) 75 | 76 | 77 | form.addRow(title(_('Other settings'))) 78 | 79 | rescan_c = QComboBox() 80 | rescan_c.addItems([ _('All history'), _('Since date'), _('None') ]) 81 | rescan_c.setMaximumWidth(150) 82 | rescan_e = input(None, 150) 83 | rescan_e.setPlaceholderText('yyyy-mm-dd') 84 | apply_rescan(self.rescan_since, rescan_c, rescan_e) 85 | rescan_c.currentIndexChanged.connect(lambda i: rescan_e.setVisible(i == 1)) 86 | rescan_l = QHBoxLayout() 87 | rescan_l.addWidget(rescan_c) 88 | rescan_l.addWidget(rescan_e) 89 | form.addRow(_('Scan:'), rescan_l) 90 | form.addRow('', helptext(_('Set to the wallet creation date to reduce scanning time.'), False)) 91 | 92 | custom_opt_e = input(self.custom_opt) 93 | custom_opt_e.setPlaceholderText('e.g. --gap-limit 50 --poll-interval 1') 94 | form.addRow('Options', custom_opt_e) 95 | form.addRow('', helptext(_('Additional custom options. Optional.'), False)) 96 | 97 | verbose_c = QComboBox() 98 | verbose_c.addItems([ _('info'), _('debug'), _('trace') ]) 99 | verbose_c.setCurrentIndex(self.verbose) 100 | verbose_c.setMaximumWidth(150) 101 | form.addRow('Log level:', verbose_c) 102 | 103 | log_t = QTextEdit() 104 | log_t.setReadOnly(True) 105 | log_t.setFixedHeight(80) 106 | log_t.setStyleSheet('QTextEdit { color: #888; font-size: 0.9em }') 107 | log_t.setWordWrapMode(QTextOption.WrapAnywhere) 108 | sp = log_t.sizePolicy() 109 | sp.setRetainSizeWhenHidden(True) 110 | log_t.setSizePolicy(sp) 111 | log_t.hide() 112 | form.addRow(log_t) 113 | 114 | self.log_signal.connect(partial(show_log, log_t)) 115 | 116 | def save_config_and_run(): 117 | self.enabled = True 118 | self.bitcoind_url = str(url_e.text()) 119 | self.bitcoind_dir = str(dir_e.text()) 120 | self.bitcoind_auth = str(auth_e.text()) 121 | self.bitcoind_wallet = str(wallet_e.text()) 122 | self.create_wallet_if_missing = wallet_ch.isChecked() 123 | self.rescan_since = get_rescan_value(rescan_c, rescan_e) 124 | self.custom_opt = str(custom_opt_e.text()) 125 | self.verbose = verbose_c.currentIndex() 126 | 127 | self.config.set_key('bwt_enabled', self.enabled) 128 | self.config.set_key('bwt_bitcoind_url', self.bitcoind_url) 129 | self.config.set_key('bwt_bitcoind_dir', self.bitcoind_dir) 130 | self.config.set_key('bwt_bitcoind_auth', self.bitcoind_auth) 131 | self.config.set_key('bwt_bitcoind_wallet', self.bitcoind_wallet) 132 | self.config.set_key('bwt_create_wallet_if_missing', self.create_wallet_if_missing) 133 | self.config.set_key('bwt_rescan_since', self.rescan_since) 134 | self.config.set_key('bwt_custom_opt', self.custom_opt) 135 | self.config.set_key('bwt_verbose', self.verbose) 136 | 137 | log_t.clear() 138 | log_t.show() 139 | 140 | self.start() 141 | 142 | window.show_message(_('bwt is starting, check the logs for additional information. The bwt server will be available after Bitcoin Core completes rescanning, which may take awhile.')) 143 | log_t.ensureCursorVisible() 144 | 145 | save_b = QPushButton('Save && Connect') 146 | save_b.setDefault(True) 147 | save_b.clicked.connect(save_config_and_run) 148 | 149 | vbox.addLayout(Buttons(CloseButton(d), save_b)) 150 | 151 | d.exec_() 152 | self.log_signal.disconnect() 153 | 154 | @hook 155 | def init_qt(self, gui_object): 156 | daemon = gui_object.daemon 157 | # `get_wallets()` in v4, `wallets` attr in v3 158 | wallets = daemon.get_wallets() if hasattr(daemon, 'get_wallets') else daemon.wallets 159 | for path, wallet in wallets.items(): 160 | self.load_wallet(wallet, None) 161 | 162 | def handle_log(self, *log): 163 | BwtPlugin.handle_log(self, *log) 164 | self.log_signal.emit(*log) 165 | 166 | def close(self): 167 | BwtPlugin.close(self) 168 | self.closed = True 169 | self.parent.bwt = None 170 | 171 | def title(text): 172 | l = QLabel(text) 173 | l.setStyleSheet('QLabel { font-weight: bold }') 174 | return l 175 | 176 | def input(value=None, width=400): 177 | le = QLineEdit() 178 | if value is not None: le.setText(value) 179 | le.setMaximumWidth(width) 180 | return le 181 | 182 | def helptext(text, wrap=True): 183 | l = QLabel(text) 184 | l.setWordWrap(wrap) 185 | l.setStyleSheet('QLabel { color: #aaa; font-size: 0.9em }') 186 | return l 187 | 188 | def checkbox(text, selected=False): 189 | ch = QCheckBox(text) 190 | ch.setChecked(selected) 191 | return ch 192 | 193 | def show_log(log_t, level, pkg, msg): 194 | scrollbar = log_t.verticalScrollBar() 195 | wasOnBottom = scrollbar.value() >= scrollbar.maximum() - 5 196 | 197 | color = { 'ERROR': '#CD0200', 'WARN': '#D47500', 'INFO': '#4BBF73', 'DEBUG': '#2780E3', 'TRACE': '#888'}.get(level, 'auto') 198 | frag = '

%s %s » %s

' \ 199 | % (color, escape(level), escape(pkg), escape(msg)) 200 | log_t.append(frag) 201 | log_t.show() 202 | # scroll to end 203 | if wasOnBottom: log_t.ensureCursorVisible() 204 | 205 | def apply_rescan(value, rescan_c, rescan_e): 206 | if value == "all": 207 | rescan_c.setCurrentIndex(0) 208 | rescan_e.hide() 209 | elif value == "none": 210 | rescan_c.setCurrentIndex(2) 211 | rescan_e.hide() 212 | else: 213 | rescan_c.setCurrentIndex(1) 214 | rescan_e.setText(value) 215 | rescan_e.show() 216 | 217 | def get_rescan_value(rescan_c, rescan_e): 218 | index = rescan_c.currentIndex() 219 | if index == 0: return 'all' 220 | elif index == 1: return str(rescan_e.text()) 221 | elif index == 2: return 'none' 222 | -------------------------------------------------------------------------------- /src/bwt.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import threading 3 | import platform 4 | import socket 5 | import os 6 | import re 7 | import shlex 8 | 9 | from electrum import constants 10 | from electrum.bip32 import BIP32Node 11 | from electrum.plugin import BasePlugin, hook 12 | from electrum.i18n import _ 13 | from electrum.util import UserFacingException 14 | from electrum.logging import get_logger 15 | from electrum.network import Network 16 | 17 | # Introduced in Electrum v4 18 | try: from electrum.interface import ServerAddr 19 | except: pass 20 | 21 | _logger = get_logger('plugins.bwt') 22 | 23 | plugin_dir = os.path.dirname(__file__) 24 | 25 | bwt_bin = os.path.join(plugin_dir, 'bwt') 26 | if platform.system() == 'Windows': 27 | bwt_bin = '%s.exe' % bwt_bin 28 | 29 | class BwtPlugin(BasePlugin): 30 | wallets = set() 31 | proc = None 32 | prev_settings = None 33 | 34 | def __init__(self, parent, config, name): 35 | BasePlugin.__init__(self, parent, config, name) 36 | 37 | self.enabled = config.get('bwt_enabled') 38 | self.bitcoind_url = config.get('bwt_bitcoind_url', default_bitcoind_url()) 39 | self.bitcoind_dir = config.get('bwt_bitcoind_dir', default_bitcoind_dir()) 40 | self.bitcoind_auth = config.get('bwt_bitcoind_auth', config.get('bwt_bitcoind_cred')) 41 | self.bitcoind_wallet = config.get('bwt_bitcoind_wallet') 42 | self.create_wallet_if_missing = config.get('bwt_create_wallet_if_missing', False) 43 | self.rescan_since = config.get('bwt_rescan_since', 'all') 44 | self.custom_opt = config.get('bwt_custom_opt') 45 | self.socket_path = config.get('bwt_socket_path', default_socket_path()) 46 | self.verbose = config.get('bwt_verbose', 0) 47 | 48 | if self.enabled: 49 | self.set_config() 50 | self.start() 51 | 52 | def start(self): 53 | if not self.enabled or not self.wallets: 54 | return 55 | 56 | self.stop() 57 | 58 | self.rpc_port = free_port() 59 | 60 | args = [ 61 | '--network', get_network_name(), 62 | '--bitcoind-url', self.bitcoind_url, 63 | '--bitcoind-dir', self.bitcoind_dir, 64 | '--rescan-since', self.rescan_since, 65 | '--electrum-addr', '127.0.0.1:%d' % self.rpc_port, 66 | '--electrum-skip-merkle', 67 | '--no-startup-banner', 68 | ] 69 | 70 | if self.bitcoind_auth: 71 | args.extend([ '--bitcoind-auth', self.bitcoind_auth ]) 72 | 73 | if self.bitcoind_wallet: 74 | args.extend([ '--bitcoind-wallet', self.bitcoind_wallet ]) 75 | 76 | if self.create_wallet_if_missing: 77 | args.extend([ '--create-wallet-if-missing' ]) 78 | 79 | if self.socket_path: 80 | args.extend([ '--unix-listener-path', self.socket_path ]) 81 | 82 | for wallet in self.wallets: 83 | if not hasattr(wallet, 'm'): 84 | xpub = wallet.get_master_public_key() 85 | args.extend([ '--xpub', xpub ]) 86 | else: # Multisig wallet 87 | for desc in get_multisig_descriptors(wallet): 88 | args.extend([ '--descriptor', desc ]) 89 | 90 | for i in range(self.verbose): 91 | args.append('-v') 92 | 93 | if self.custom_opt: 94 | args.extend(shlex.split(self.custom_opt)) 95 | 96 | self.set_config() 97 | 98 | _logger.info('Starting the bwt daemon') 99 | _logger.debug('bwt options: %s' % ' '.join(args)) 100 | 101 | if platform.system() == 'Windows': 102 | # hide the console window. can be done with subprocess.CREATE_NO_WINDOW in python 3.7. 103 | suinfo = subprocess.STARTUPINFO() 104 | suinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 105 | else: suinfo = None 106 | 107 | self.proc = subprocess.Popen([ bwt_bin ] + args, startupinfo=suinfo, \ 108 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) 109 | self.thread = threading.Thread(target=proc_logger, args=(self.proc, self.handle_log), daemon=True) 110 | self.thread.start() 111 | 112 | def stop(self): 113 | if self.proc: 114 | _logger.info('Stopping the bwt daemon') 115 | self.proc.terminate() 116 | self.proc = None 117 | self.thread = None 118 | 119 | # enable oneserver/skipmerklecheck and disable manual server selection 120 | def set_config(self): 121 | if self.prev_settings: return # run once 122 | 123 | self.prev_settings = { setting: self.config.cmdline_options.get(setting) 124 | for setting in [ 'oneserver', 'skipmerklecheck', 'server' ] } 125 | 126 | # setting `oneserver`/`skipmerklecheck` directly on `cmdline_options` keeps the settings in-memory only without 127 | # persisting them to the config file, reducing the chance of accidentally leaving them on with public servers. 128 | self.config.cmdline_options['oneserver'] = True 129 | 130 | # for `skipmerklecheck`, this is also the only way to set it an runtime prior to v4 (see https://github.com/spesmilo/electrum/commit/61ccc1ccd3a437d98084089f1d4014ba46c96e3b) 131 | self.config.cmdline_options['skipmerklecheck'] = True 132 | 133 | # set a dummy server so electrum won't attempt connecting to other servers on startup. setting this 134 | # in `cmdline_options` also prevents the user from switching servers using the gui, which further reduces 135 | # the chance of accidentally connecting to public servers with inappropriate settings. 136 | self.config.cmdline_options['server'] = '127.0.0.1:1:t' 137 | 138 | def set_server(self): 139 | _logger.info('Configuring server to 127.0.0.1:%s', self.rpc_port) 140 | 141 | # first, remove the `server` config to allow `set_parameters()` below to update it and trigger the connection mechanism 142 | del self.config.cmdline_options['server'] 143 | 144 | network = Network.get_instance() 145 | net_params = network.get_parameters() 146 | try: 147 | # Electrum v4 148 | server = ServerAddr('127.0.0.1', self.rpc_port, protocol='t') 149 | net_params = net_params._replace(server=server, oneserver=True) 150 | except: 151 | # Electrum v3 152 | net_params = net_params._replace( 153 | host='127.0.0.1', 154 | port=self.rpc_port, 155 | protocol='t', 156 | oneserver=True, 157 | ) 158 | network.run_from_another_thread(network.set_parameters(net_params)) 159 | 160 | # now set the server in `cmdline_options` to lock it in 161 | self.config.cmdline_options['server'] = '127.0.0.1:%s:t' % self.rpc_port 162 | 163 | @hook 164 | def load_wallet(self, wallet, main_window): 165 | if not wallet.get_master_public_keys(): 166 | _logger.warning('skipping unsupported wallet type %s' % wallet.wallet_type) 167 | elif wallet.can_export(): 168 | _logger.warning('skipping hot wallet') 169 | else: 170 | num_wallets = len(self.wallets) 171 | self.wallets |= {wallet} 172 | if len(self.wallets) != num_wallets: 173 | self.start() 174 | 175 | @hook 176 | def close_wallet(self, wallet): 177 | self.wallets -= {wallet} 178 | if not self.wallets: 179 | self.stop() 180 | 181 | def close(self): 182 | BasePlugin.close(self) 183 | self.stop() 184 | 185 | # restore the user's previous settings when the plugin is disabled 186 | if self.prev_settings: 187 | for setting, prev_value in self.prev_settings.items(): 188 | if prev_value is None: self.config.cmdline_options.pop(setting, None) 189 | else: self.config.cmdline_options[setting] = prev_value 190 | self.prev_settings = None 191 | 192 | def handle_log(self, level, pkg, msg): 193 | if msg.startswith('Electrum RPC server running'): 194 | self.set_server() 195 | 196 | def proc_logger(proc, log_handler): 197 | for line in iter(proc.stdout.readline, b''): 198 | line = line.decode('utf-8').strip() 199 | _logger.debug(line) 200 | 201 | m = re.match(r"^(?:\d{4}-[\d-]+T[\d:.]+Z )?(ERROR|WARN|INFO|DEBUG|TRACE) +([^ ]+) +> (.*)", line) 202 | 203 | if m is not None: 204 | log_handler(*m.groups()) 205 | elif line.lower().startswith('error: '): 206 | log_handler('ERROR', 'bwt', line[7:]) 207 | else: 208 | log_handler('INFO', 'bwt', line) 209 | 210 | DESCRIPTOR_MAP_SH = { 211 | 'p2sh': 'sh(%s)', 212 | 'p2wsh': 'wsh(%s)', 213 | 'p2wsh-p2sh': 'sh(wsh(%s))', 214 | } 215 | 216 | def get_multisig_descriptors(wallet): 217 | descriptor_fmt = DESCRIPTOR_MAP_SH[wallet.txin_type] 218 | if not descriptor_fmt: 219 | _logger.warn('missing descriptor type for %s' % wallet.txin_type) 220 | return () 221 | 222 | xpubs = [convert_to_std_xpub(xpub) for xpub in wallet.get_master_public_keys()] 223 | def get_descriptor(child_index): 224 | desc_keys = ['%s/%d/*' % (xpub, child_index) for xpub in xpubs] 225 | return descriptor_fmt % 'sortedmulti(%d,%s)' % (wallet.m, ','.join(desc_keys)) 226 | 227 | # one for receive, one for change 228 | return (get_descriptor(0), get_descriptor(1)) 229 | 230 | # Convert SLIP32 ypubs/zpubs into standard BIP32 xpubs 231 | def convert_to_std_xpub(xpub): 232 | return BIP32Node.from_xkey(xpub) \ 233 | ._replace(xtype='standard') \ 234 | .to_xpub() 235 | 236 | def get_network_name(): 237 | if constants.net == constants.BitcoinMainnet: 238 | return 'bitcoin' 239 | elif constants.net == constants.BitcoinTestnet: 240 | return 'testnet' 241 | elif constants.net == constants.BitcoinRegtest: 242 | return 'regtest' 243 | 244 | raise UserFacingException(_('Unsupported network {}').format(constants.net)) 245 | 246 | def default_bitcoind_url(): 247 | return 'http://localhost:%d/' % \ 248 | { 'bitcoin': 8332, 'testnet': 18332, 'regtest': 18443 }[get_network_name()] 249 | 250 | def default_bitcoind_dir(): 251 | if platform.system() == 'Windows': 252 | return os.path.expandvars('%APPDATA%\\Bitcoin') 253 | elif platform.system() == 'Darwin': 254 | return os.path.expandvars('$HOME/Library/Application Support/Bitcoin') 255 | else: 256 | return os.path.expandvars('$HOME/.bitcoin') 257 | 258 | def default_socket_path(): 259 | if platform.system() in ('Linux', 'Darwin') and os.access(plugin_dir, os.W_OK | os.X_OK): 260 | return os.path.join(plugin_dir, 'bwt-socket') 261 | 262 | def free_port(): 263 | with socket.socket() as s: 264 | s.bind(('',0)) 265 | return s.getsockname()[1] 266 | --------------------------------------------------------------------------------