From 1ab6ecba6f509b7b76865d65c77ecebc51efd2d3 Mon Sep 17 00:00:00 2001 From: sommerfeld Date: Sun, 21 Apr 2024 16:04:38 +0100 Subject: Initial commit --- .editorconfig | 9 + .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 17 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .../PULL_REQUEST_TEMPLATE/pull_request_template.md | 29 + .github/workflows/build.yml | 37 + .github/workflows/ci.yml | 22 + .github/workflows/deps.yml | 11 + .gitignore | 1 + Cargo.lock | 4242 ++++++++++++++++++++ Cargo.toml | 43 + LICENSE.txt | 7 + README.md | 361 ++ contrib/sentrum.service | 16 + docs/CHANGELOG.md | 3 + docs/CODE_OF_CONDUCT.md | 3 + docs/CONTRIBUTING.md | 27 + docs/SECURITY.md | 13 + docs/SUPPORT.md | 10 + sentrum.sample.toml | 46 + src/actions/command.rs | 61 + src/actions/desktop_notification.rs | 28 + src/actions/email.rs | 142 + src/actions/mod.rs | 120 + src/actions/nostr.rs | 192 + src/actions/ntfy.rs | 119 + src/actions/telegram.rs | 56 + src/actions/terminal_print.rs | 28 + src/blockchain.rs | 96 + src/config.rs | 143 + src/main.rs | 179 + src/message.rs | 203 + src/wallets.rs | 230 ++ 33 files changed, 6515 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deps.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 contrib/sentrum.service create mode 100644 docs/CHANGELOG.md create mode 100644 docs/CODE_OF_CONDUCT.md create mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/SECURITY.md create mode 100644 docs/SUPPORT.md create mode 100644 sentrum.sample.toml create mode 100644 src/actions/command.rs create mode 100644 src/actions/desktop_notification.rs create mode 100644 src/actions/email.rs create mode 100644 src/actions/mod.rs create mode 100644 src/actions/nostr.rs create mode 100644 src/actions/ntfy.rs create mode 100644 src/actions/telegram.rs create mode 100644 src/actions/terminal_print.rs create mode 100644 src/blockchain.rs create mode 100644 src/config.rs create mode 100644 src/main.rs create mode 100644 src/message.rs create mode 100644 src/wallets.rs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..99580d0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..033cde3 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: pay.sommerfeld.dev diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..1848673 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,17 @@ +--- +name: Bug report +about: Report a bug +title: '' +labels: bug +assignees: sommerfelddev + +--- + +**Bug Description** + +**To Reproduce** + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Environment** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..85ccdfe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea +title: '' +labels: enhancement +assignees: sommerfelddev + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..dc6d5e4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,29 @@ +--- +name: Pull Request +about: Submit a pull request +title: '' +--- + + + +# Description + + + + +# Notes for reviewers + + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..02c9567 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,37 @@ +name: build every tag +on: + push: + tags: + - '*' +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - run: cargo build --release + - if: matrix.os == 'windows-latest' + uses: actions/upload-artifact@v3 + with: + path: target/release/sentrum.exe + name: sentrum.exe + - if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v3 + with: + path: target/release/sentrum* + name: sentrum_linux + - if: matrix.os == 'macos-latest' + uses: actions/upload-artifact@v3 + with: + path: target/release/sentrum* + name: sentrum_macos diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d51f1af --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ $default-branch ] + pull_request: + branches: [ $default-branch ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml new file mode 100644 index 0000000..828fffa --- /dev/null +++ b/.github/workflows/deps.yml @@ -0,0 +1,11 @@ +jobs: + latest_deps: + name: Latest Dependencies + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v3 + - run: rustup update stable && rustup default stable + - run: cargo update --verbose + - run: cargo build --verbose + - run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8cf2401 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4242 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "aquamarine" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a941c39708478e8eea39243b5983f1c42d2717b3620ee91f4a52115fd02ac43f" +dependencies = [ + "itertools", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-broadcast" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258b52a1aa741b9f09783b2d86cf0aeeb617bbf847f6933340a39644227acbdb" +dependencies = [ + "event-listener 5.3.0", + "event-listener-strategy 0.5.1", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener 5.3.0", + "event-listener-strategy 0.5.1", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc19683171f287921f2405677dd2ed2549c3b3bda697a563ebc3a121ace2aba1" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53fc6301894e04a92cb2584fedde80cb25ba8e02d9dc39d4a87d036e22f397d" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.0", + "futures-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "async-scoped" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4042078ea593edffc452eef14e99fdb2b120caa4ad9618bcdeabc4a023b98740" +dependencies = [ + "futures", + "pin-project", + "tokio", +] + +[[package]] +name = "async-signal" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afe66191c335039c7bb78f99dc7520b0cbb166b3a1cb33a03f53d8a1c6f2afda" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-task" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "async-utility" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a349201d80b4aa18d17a34a182bdd7f8ddf845e9e57d2ea130a12e10ef1e3a47" +dependencies = [ + "futures-util", + "gloo-timers", + "tokio", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-wsocket" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c38341e6ee670913fb9dc3aba40c22d616261da4dc0928326d3168ebf576fb0" +dependencies = [ + "async-utility", + "futures-util", + "thiserror", + "tokio", + "tokio-rustls 0.25.0", + "tokio-socks", + "tokio-tungstenite", + "url", + "wasm-ws", + "webpki-roots 0.26.1", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", +] + +[[package]] +name = "atomic-destructor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4653a42bf04120a1d4e92452e006b4e3af4ab4afff8fb4af0f1bbb98418adf3e" +dependencies = [ + "tracing", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bdk" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc1fc1a92e0943bfbcd6eb7d32c1b2a79f2f1357eb1e2eee9d7f36d6d7ca44a" +dependencies = [ + "async-trait", + "bdk-macros", + "bitcoin 0.30.2", + "electrum-client", + "getrandom", + "js-sys", + "log", + "miniscript", + "rand", + "serde", + "serde_json", + "sled", + "tokio", +] + +[[package]] +name = "bdk-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81c1980e50ae23bb6efa9283ae8679d6ea2c6fa6a99fe62533f65f4a25a1a56c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bech32" +version = "0.10.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" + +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes 0.11.0", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" +dependencies = [ + "base64 0.13.1", + "bech32 0.9.1", + "bitcoin-private", + "bitcoin_hashes 0.12.0", + "hex_lit", + "secp256k1 0.27.0", + "serde", +] + +[[package]] +name = "bitcoin" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" +dependencies = [ + "bech32 0.10.0-beta", + "bitcoin-internals", + "bitcoin_hashes 0.13.0", + "hex-conservative", + "hex_lit", + "secp256k1 0.28.2", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "fastrand", + "futures-io", + "futures-lite", + "piper", + "tracing", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.5", +] + +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown", + "stacker", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dptree" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81175dab5ec79c30e0576df2ed2c244e1721720c302000bb321b107e82e265c" +dependencies = [ + "futures", +] + +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "electrum-client" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc133f1c8d829d254f013f946653cbeb2b08674b960146361d1e9b67733ad19" +dependencies = [ + "bitcoin 0.30.2", + "bitcoin-private", + "byteorder", + "libc", + "log", + "rustls 0.21.11", + "serde", + "serde_json", + "webpki", + "webpki-roots 0.22.6", + "winapi", +] + +[[package]] +name = "email-encoding" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" +dependencies = [ + "base64 0.22.0", + "memchr", +] + +[[package]] +name = "email_address" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erasable" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f11890ce181d47a64e5d1eb4b6caba0e7bae911a356723740d058a5d0340b7d" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +dependencies = [ + "event-listener 5.3.0", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "human-panic" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f016c89920bbb30951a8405ecacbb4540db5524313b9445736e7e1855cf370" +dependencies = [ + "anstream", + "anstyle", + "backtrace", + "os_info", + "serde", + "serde_derive", + "toml", + "uuid", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.28", + "rustls 0.21.11", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.28", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lettre" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47460276655930189e0919e4fbf46e46476b14f934f18a63dd726a5fb7b60e2e" +dependencies = [ + "async-trait", + "base64 0.22.0", + "chumsky", + "email-encoding", + "email_address", + "fastrand", + "futures-io", + "futures-util", + "hostname", + "httpdate", + "idna", + "mime", + "native-tls", + "nom", + "percent-encoding", + "quoted_printable", + "serde", + "socket2", + "tokio", + "tokio-native-tls", + "url", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "mac-notification-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51fca4d74ff9dbaac16a01b924bc3693fa2bba0862c2c633abc73f9a8ea21f64" +dependencies = [ + "cc", + "dirs-next", + "objc-foundation", + "objc_id", + "time", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markdown" +version = "1.0.0-alpha.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0f0025e8c0d89b84d6dc63e859475e40e8e82ab1a08be0a93ad5731513a508" +dependencies = [ + "unicode-id", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniscript" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eb102b66b2127a872dbcc73095b7b47aeb9d92f7b03c2b2298253ffc82c7594" +dependencies = [ + "bitcoin 0.30.2", + "bitcoin-private", + "serde", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "negentropy" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe" + +[[package]] +name = "never" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nostr" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27223888faca0c4ba9b97c2b7dc776e9a33d5f54e3558887471cf17798b5fbf" +dependencies = [ + "aes", + "base64 0.21.7", + "bip39", + "bitcoin 0.31.2", + "cbc", + "chacha20", + "chacha20poly1305", + "getrandom", + "instant", + "negentropy", + "once_cell", + "reqwest 0.12.4", + "scrypt", + "serde", + "serde_json", + "tracing", + "unicode-normalization", + "url", + "wasm-bindgen", +] + +[[package]] +name = "nostr-database" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f726b8c0904a838f64b51a931a1bf39e341f5584a5e04f06310fbfb847e2e924" +dependencies = [ + "async-trait", + "lru", + "nostr", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "nostr-relay-pool" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f0ccf9e81aa747abdfa130007651248b37c3699d37029bad701e68902257ce" +dependencies = [ + "async-utility", + "async-wsocket", + "atomic-destructor", + "nostr", + "nostr-database", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "nostr-sdk" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ffedac7ab488e0dfea52804d0c43fafc7e3eefc62d97726d3927a1390db05b" +dependencies = [ + "async-utility", + "nostr", + "nostr-database", + "nostr-relay-pool", + "nostr-signer", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "nostr-signer" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e568670664cf5cc14a794ae32dfc04bde385d63ff0f5b1c3745dd3ea69f73a" +dependencies = [ + "async-utility", + "nostr", + "thiserror", + "tokio", +] + +[[package]] +name = "notify-rust" +version = "4.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5312f837191c317644f313f7b2b39f9cb1496570c74f7c17152dd3961219551f" +dependencies = [ + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + +[[package]] +name = "ntfy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862d7410910ec279335692789a23b057a5046479242d3f9cb1dfd4c4b07f3a72" +dependencies = [ + "base64 0.21.7", + "chrono", + "reqwest 0.11.27", + "serde", + "serde_json", + "thiserror", + "tracing", + "url", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "polling" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quoted_printable" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79ec282e887b434b68c18fe5c121d38e72a5cf35119b59e54ec5b992ea9c8eb0" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rc-box" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0690759eabf094030c2cdabc25ade1395bac02210d920d655053c1d49583fd8" +dependencies = [ + "erasable", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-rustls 0.24.2", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.11", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.24.1", + "tokio-socks", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 0.25.4", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.0", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.26.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.22.4", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.25.0", + "tokio-socks", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.1", + "winreg 0.52.0", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.0", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "bitcoin_hashes 0.12.0", + "rand", + "secp256k1-sys 0.8.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "bitcoin_hashes 0.13.0", + "rand", + "secp256k1-sys 0.9.2", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "sentrum" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-scoped", + "async-trait", + "bdk", + "chrono", + "clap", + "const_format", + "dirs", + "human-panic", + "lettre", + "log", + "markdown", + "nostr-relay-pool", + "nostr-sdk", + "notify-rust", + "ntfy", + "pretty_env_logger", + "serde", + "strfmt", + "systemd-directories", + "teloxide", + "tokio", + "toml", +] + +[[package]] +name = "serde" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_json" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strfmt" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "systemd-directories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5469e2e28839738d6549f9eed6b48af8c11eb8d720b308ec01fd3c105c9f394" + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "takecell" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e" + +[[package]] +name = "tauri-winrt-notification" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89f5fb70d6f62381f5d9b2ba9008196150b40b75f3068eb24faeddf1c686871" +dependencies = [ + "quick-xml", + "windows", + "windows-version", +] + +[[package]] +name = "teloxide" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c63345cf32a8850ebddcdd769dc2d5193d5e231262d5dada264b79da01a664da" +dependencies = [ + "aquamarine", + "bytes", + "derive_more", + "dptree", + "futures", + "log", + "mime", + "pin-project", + "serde", + "serde_json", + "serde_with_macros", + "teloxide-core", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "url", +] + +[[package]] +name = "teloxide-core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "303db260110c238e3af77bb9dff18bf7a5b5196f783059b0852aab75f91d5a16" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "chrono", + "derive_more", + "either", + "futures", + "log", + "mime", + "never", + "once_cell", + "pin-project", + "rc-box", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_with_macros", + "take_mut", + "takecell", + "thiserror", + "tokio", + "tokio-util", + "url", + "uuid", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.11", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tungstenite", + "webpki-roots 0.26.1", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.12", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.6", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "rustls 0.22.4", + "rustls-pki-types", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-id" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-ws" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5b3a482e27ff54809c0848629d9033179705c5ea2f58e26cf45dc77c34c4984" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "pharos", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +dependencies = [ + "windows-core 0.56.0", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "windows-result" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows-version" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "xdg-home" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e5a325c3cb8398ad6cf859c1135b25dd29e186679cf2da7581d9679f63b38e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "zbus" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9ff46f2a25abd690ed072054733e0bc3157e3d4c45f41bd183dce09c2ff8ab9" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "derivative", + "enumflags2", + "event-listener 5.3.0", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0e3852c93dcdb49c9462afe67a2a468f7bd464150d866e861eaf06208633e0" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zvariant" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1b3ca6db667bfada0f1ebfc94b2b1759ba25472ee5373d4551bb892616389a" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a4b236063316163b69039f77ce3117accb41a09567fd24c168e43491e521bc" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bedb16a193cc12451873fee2a1bc6550225acece0e36f333e68326c73c8172" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ff4d23c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "sentrum" +version = "0.1.0" +edition = "2021" +authors = ["sommerfeld "] +description = "Daemon that monitors watch-only bitcoin wallets" +repository = "https://github.com/sommerfelddev/sentrum" +license = "MIT" +keywords = ["bitcoin", "notification", "daemon"] +categories = ["command-line-utilities", "cryptography::cryptocurrencies"] + +[features] +default = ["ntfy", "email", "telegram", "nostr", "desktop"] +ntfy = ["dep:ntfy"] +email = ["dep:lettre", "dep:markdown"] +telegram = ["dep:teloxide"] +nostr = ["dep:nostr-sdk", "dep:nostr-relay-pool"] +desktop = ["dep:notify-rust"] + +[dependencies] +anyhow = "1.0.81" +bdk = "0.29.0" +chrono = "0.4.37" +clap = { version = "4.5.4", features = ["derive"] } +const_format = "0.2.32" +dirs = "5.0.1" +human-panic = "1.2.3" +lettre = { version = "0.11.6", features = ["serde", "tokio1", "tokio1-native-tls"], optional = true } +log = "0.4.21" +notify-rust = { version = "4.11.0", optional = true } +ntfy = { version = "0.4.0", optional = true} +pretty_env_logger = "0.5.0" +serde = {version = "1.0.197", features = ["derive"] } +strfmt = "0.2.4" +systemd-directories = "0.1.1" +toml = "0.8.12" +markdown = { version = "1.0.0-alpha.16", optional = true } +tokio = { version = "1.37.0", features = ["rt-multi-thread", "signal", "time"] } +async-scoped = { version = "0.9.0", features = ["use-tokio"] } +async-trait = "0.1.80" +teloxide = { version = "0.12.2", optional = true } +nostr-sdk = { version = "0.30.0", features = ["nip04", "nip05", "nip44", "nip59"], default-features = false, optional = true } +nostr-relay-pool = { version = "0.30.0", optional = true } diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..47c682a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (c) 2024 sommerfeld + +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b017306 --- /dev/null +++ b/README.md @@ -0,0 +1,361 @@ +# sentrum + +Daemon that monitors the Bitcoin blockchain for transactions involving your +wallets and sends you notifications in many different channels (ntfy push +notifications, email, telegram, nostr, arbitrary command, etc). + +## Installation + +Either: + +* Compile from source using `cargo install sentrum` +* Download the binary from the [releases page](releases) +* If using archlinux, install it from the [AUR](https://aur.archlinux.org/packages/sentrum) + +## Configuration + +### Config file path + +It will look for a `sentrum.toml` configuration file located in any of these +directories (with this priority): + +1. Current working directory +2. `$XDG_CONFIG_HOME/sentrum` +3. `~/.config/sentrum` +4. `/etc/sentrum` (more appropriate if running as a systemd service) + +Alternatively, you can pass the configuration file path as an argument in the +invocation and that will override any of the above. + +**Start by copying the sample configuration to where you want it.** E.g. + +```bash +cp sentrum.sample.toml sentrum.toml +sudo cp sentrum.sample.toml /etc/sentrum/sentrum.toml +``` + +or + +```bash +sudo cp sentrum.sample.toml /etc/sentrum/sentrum.toml +``` + +### What to configure + +You can use the [sentrum.sample.toml](sentrum.sample.toml) file as an +example. +Most options have very good defaults so you don't need to change them unless you +want to. **In the examples below, commented options showcase their defaults, +unless explicitly said otherwise.** + +#### Required + +* `wallets`: what wallets you want to monitor +* `actions`: what actions you want to take once a relevant transaction is found + +#### Optional + +* `electrum`: by default, public electrum servers are used. You can configure it + to connect to your own +* `message`: this allows you to configure the subject and body templates of the + notification message and choose the relevant data from the transaction that +you want to include + +## Wallets + +For each wallet you want to track, add the following configuration: + +```toml +[[wallets]] +# Identifier for naming purposes (required) +name = "alice" +# Wallet xpub (required) +xpub = "xpub6CkXHzuU1NyHUFNiQZLq2bgt6QPqjZbwpJ1MDgDeo4bWZ8ZP7HZr7v9WTLCQFhxVhqiJNcw5wSKE77rkAK1SzcuHjt36ZUibBHezGzGL9h9" +# Script kind ("legacy","nested_segwit","segwit","taproot") (optional) +#kind = "segwit" +``` + +It assumes a BIP84 (native segwit, `bc1` style addresses) wallet. If your wallet +has a different script kind add the field `kind = "legacy"` (or `nested_segwit`, +or `taproot`). + +More complex wallet types are supported by providing `primary = ""` and +`change = ""` wallet descriptors instead of `xpub =` and `kind = `. + +## Actions + +For each new relevant transaction, you can take multiple actions. For each +action you desire to take, you need to add the configuration: + +```toml +[[actions]] +# Action type (required) +type = "" +<.... INSERT ACTION SPECIFIC CONFIGURATION HERE...> +``` + +Below we explain the configuration for each action kind. You can have multiple +actions of the same kind (e.g. you want to send multiple emails from different +accounts for some reason). + +### ntfy + +This is the best straightforward way to get push notifications on a smartphone. + +1. Install the android/iOS app following the relevant links in https://ntfy.sh +2. If you don't run your own ntfy self-hosted server, create an account at + ntfy.sh +3. Open the app, give it the needed permissions and configure your account + credentials +4. Click on the `+` button and create a "topic", preferably named `sentrum` + since that's what will be used by default. + +Then you just need to add the relevant configuration: + +```toml +[[actions]] +type = "ntfy" +# Credentials (required if you use a public server like the default one) +credentials.username = "" +credentials.password = "" +# ntfy server (optional) +#url = "https://ntfy.sh" +# notification channel name (optional) +#topic = "sentrum" +# Proxy used to connect (optional, defaults to None) +#proxy = "socks5://127.0.0.1:9050" +# Priority ("max", "high", "default", "low", "min") (optional) +#priority = "default" +``` + +### nostr + +Get notified by a nostr [NIP04 encrypted +DM](https://github.com/nostr-protocol/nips/blob/master/04.md) (leaks metadata +but widely supported) or a +[NIP59 GiftWrap sealed sender DM](https://github.com/nostr-protocol/nips/blob/master/59.md) +(more private but not supported by many clients). Add: + +```toml +[[actions]] +type = "nostr" +# Which npub to send the DM (required) +recipient = "" +# If NIP59 giftwrap DMs should be used instead of NIP04 (optional) +#sealed_dm = false +# Which relays to use to send DMs +#relays = ["wss://nostr.bitcoiner.social", "wss://nostr.oxtr.dev", "wss://nostr.orangepill.dev", "wss://relay.damus.io"] +``` + +### email + +You need to add the configuration below and essentially configure an +authenticated connection to your email provider's SMTP server. I cannot help you +out with every provider's weird rules (maybe you need to allow 3rd party apps +for gmail, who knows). + +```toml +[[actions]] +type = "email" +# SMTP server (required) +server = "`. + +You can pass the `--test` flag to send a single test notification to all +configured actions. + +By default, only new transactions can trigger actions. If you pass +`--notify-past-txs`, it will send notifications of past transactions +in the initial wallet sync. If you have a long transaction history, this will +spam your notification channels for every transaction. + +## systemd service + +The ideal use-case is as a long running daemon, so it makes sense to configure +it as a systemd service. + +If you are installing `sentrum` manually (e.g. from the releases page), you +should: + +1. Create a new `sentrum` user: + +```bash +sudo useradd -d /var/lib/sentrum -m sentrum +``` + +2. Place the `sentrum.toml` configuration file in `/etc/sentrum`: + +```bash +sudo mkdir -p /etc/sentrum +sudo cp sentrum.toml /etc/sentrum +sudo chown -R sentrum:sentrum /etc/sentrum +``` + +3. Copy the [contrib/sentrum.service](contrib/sentrum.service) into the + `/etc/systemd/system` + +3. Reload systemd so that the service file can be found: + +```bash +sudo systemclt daemon-reload +``` + +4. Enable and start the service: + +```bash +sudo systemclt enable --now sentrum.service +``` + +5. Check if everything is fine with `systemctl status sentrum` + +6. Check the logs with `journalctl -fu sentrum` + +# Future Work + +* More action types: + - Matrix DM + - SimpleX chat DM + - IRC + - XMPP + - Whatsapp/Signal using linked devices (harder) + - HTTP request +* More wallet types: + - Single Address (blocked by + https://github.com/bitcoindevkit/bdk/issues/759) + - Collections of wallets as a single entity +* Notifications for the first tx confirmation and after N confirmations +* Filtering notifications by the transaction amounts (e.g. no action for +transactions smaller than 1M sats) +* Debian package (using `cargo-deb`) +* Allow per wallet actions +* Support other blockchain backends (bitcoind-rpc, explora, block filters, dojo) +* Maybe create a little web UI that helps with writing the configuration +* Incentivize node distributions to package sentrum diff --git a/contrib/sentrum.service b/contrib/sentrum.service new file mode 100644 index 0000000..2d27524 --- /dev/null +++ b/contrib/sentrum.service @@ -0,0 +1,16 @@ +[Unit] +Description=sentrum daemon + +[Service] +ExecStart=/usr/bin/sentrum +User=sentrum + +# Hardening +PrivateTmp=true +ProtectSystem=full +NoNewPrivileges=true +PrivateDevices=true +MemoryDenyWriteExecute=true + +[Install] +WantedBy=multi-user.target diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..009131d --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,3 @@ +0.1.0 (2024-04-21) +------------------ +Initial release diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..466b93b --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +Behave and say however you want, if your code is good, you will not be canceled. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..e2d58ea --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing + +## How to contribute + +1. Fork repo and create a new topic branch +2. Make changes +3. Ensure it compiles and passes tests using + +```bash +cargo build +cargo test +``` + +4. Auto format the code using `rustfmt` or a tool that integrates it (such as + `rust-analyzer` or some IDE). +5. Make small atomic compilable working commits. Do NOT use "Conventional + Commits" for the commit title. Instead just directly write what was changed +without any prefixes. Write it in the imperative tense and use the +["50/72" rule](https://stackoverflow.com/a/11993051) +6. Push commits to the created topic branch in your repo. +7. Open a PR, wait for review. + +## How to create a new Action + +Use the [telegram action](../src/actions/telegram.rs) as a template. You need to +implement the `Action` trait for your action and add the necessary hooks in +[actions/mod.rs](../src/actions/mod.rs). diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000..5586ce2 --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| master | :white_check_mark: | +| < master | :x: | + +## Reporting a Vulnerability + +If you discover a serious vulnerability, +do not open an issue, instead contact the repository maintainer directly. diff --git a/docs/SUPPORT.md b/docs/SUPPORT.md new file mode 100644 index 0000000..a497321 --- /dev/null +++ b/docs/SUPPORT.md @@ -0,0 +1,10 @@ +# Bitcoin + +## On-chain: + +* paynym: [+mistymud0Bf](https://paynym.is/+mistymud0Bf) +* [satsale sever](https://pay.sommerfeld.dev) + +## LN + +* Self-custodial lightning address: [sommerfeld@sommerfeld.dev](lightning:sommerfeld@sommerfeld.dev) diff --git a/sentrum.sample.toml b/sentrum.sample.toml new file mode 100644 index 0000000..63f4f17 --- /dev/null +++ b/sentrum.sample.toml @@ -0,0 +1,46 @@ +[[wallets]] +# Identifier for naming purposes (required) +name = "alice" +# Wallet xpub (required) +xpub = "xpub6CkXHzuU1NyHUFNiQZLq2bgt6QPqjZbwpJ1MDgDeo4bWZ8ZP7HZr7v9WTLCQFhxVhqiJNcw5wSKE77rkAK1SzcuHjt36ZUibBHezGzGL9h9" +# Script kind ("legacy","nested_segwit","segwit","taproot") (optional) +#kind = "segwit" + +# Another wallet +#[[wallets]] +#name = "bob" +#xpub = "xpubblablabla" + +[[actions]] +type = "terminal_print" + +# Add more actions here (ntfy, nostr, email, telegram, etc) +#[[actions]] +#type = "" +#<.... INSERT ACTION SPECIFIC CONFIGURATION HERE...> + + +[message] +subject = "[{wallet}] new transaction" +body = "net: {tx_net} sats, balance: {total_balance} sats, txid: {txid_short}" +# Can be "plain", "markdown" or "html" +format = "plain" +# Configure blockexplorer urls. This is used to create the {tx_url} parameter +block_explorers.mainnet = "https://mempool.space/tx/{txid}" +block_explorers.testnet = "https://mempool.space/testnet/tx/{txid}" +block_explorers.signet = "https://mempool.space/signet/tx/{txid}" + +[electrum] +# Defaults: +# - mainnet: ssl://fulcrum.sethforprivacy.com:50002 +# - testnet: ssl://electrum.blockstream.info:60002 +# - signet: ssl://mempool.space:60602 +# Use "tcp://" if you are connecting without SSL (e.g. "tcp://localhost:50001" +# or "tcp://fwafiuesngirdghrdhgiurdhgirdgirdhgrd.onion:50001" +url = "ssl://fulcrum.sethforprivacy.com:50002" +# blockchain network ("bitcoin", "testnet", "signet", "regtest") +network = "bitcoin" +# Optional socks5 proxy (defaults to None) +#socks5 = 127.0.0.1:9050 +# If using ssl with a trusted certificate, set this to true +certificate_validation = false diff --git a/src/actions/command.rs b/src/actions/command.rs new file mode 100644 index 0000000..28d951a --- /dev/null +++ b/src/actions/command.rs @@ -0,0 +1,61 @@ +use std::collections::HashMap; +use std::process::Command; + +use super::Action; +use crate::message::MessageConfig; +use crate::message::MessageParams; +use anyhow::Result; +use async_trait::async_trait; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct CommandConfig { + cmd: String, + #[serde(default)] + args: Vec, + #[serde(default)] + clear_parent_env: bool, + #[serde(default)] + envs: HashMap, + working_dir: Option, +} + +pub struct CommandAction<'a> { + message_config: &'a MessageConfig, + cmd_config: &'a CommandConfig, +} + +impl<'a> CommandAction<'a> { + pub fn new(message_config: &'a MessageConfig, cmd_config: &'a CommandConfig) -> Result { + Ok(Self { + message_config, + cmd_config, + }) + } +} + +#[async_trait] +impl Action<'_> for CommandAction<'_> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()> { + let mut cmd = Command::new(&self.cmd_config.cmd); + for arg in self.cmd_config.args.iter() { + cmd.arg(if let Some(p) = params { + self.message_config.replace_template_params(arg, p)? + } else { + arg.clone() + }); + } + + if self.cmd_config.clear_parent_env { + cmd.env_clear(); + } + cmd.envs(&self.cmd_config.envs); + + if let Some(working_dir) = &self.cmd_config.working_dir { + cmd.current_dir(working_dir); + } + + cmd.status()?; + Ok(()) + } +} diff --git a/src/actions/desktop_notification.rs b/src/actions/desktop_notification.rs new file mode 100644 index 0000000..d831a59 --- /dev/null +++ b/src/actions/desktop_notification.rs @@ -0,0 +1,28 @@ +use super::Action; +use crate::message::MessageConfig; +use crate::message::MessageParams; +use anyhow::Result; +use async_trait::async_trait; + +#[derive(Debug)] +pub struct DesktopNotificationAction<'a> { + message_config: &'a MessageConfig, +} + +impl<'a> DesktopNotificationAction<'a> { + pub fn new(message_config: &'a MessageConfig) -> Self { + Self { message_config } + } +} + +#[async_trait] +impl Action<'_> for DesktopNotificationAction<'_> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()> { + use notify_rust::Notification; + Notification::new() + .summary(&self.message_config.subject(params)?) + .body(&self.message_config.body(params)?) + .show()?; + Ok(()) + } +} diff --git a/src/actions/email.rs b/src/actions/email.rs new file mode 100644 index 0000000..272f650 --- /dev/null +++ b/src/actions/email.rs @@ -0,0 +1,142 @@ +use super::Action; +use crate::message::MessageConfig; +use crate::message::MessageFormat; +use crate::message::MessageParams; +use anyhow::{Context, Result}; +use async_trait::async_trait; +use lettre::message::header::ContentType; +use lettre::message::MessageBuilder; +use lettre::message::MultiPart; +use lettre::message::SinglePart; +use lettre::transport::smtp::authentication::Credentials; +use lettre::transport::smtp::client::Tls; +use lettre::transport::smtp::client::TlsParametersBuilder; +use lettre::AsyncSmtpTransport; +use lettre::AsyncTransport; +use lettre::Message; +use lettre::Tokio1Executor; +use serde::Deserialize; + +#[derive(Deserialize, Debug, Copy, Clone)] +#[serde(rename_all = "lowercase")] +pub enum EmailConnectionType { + Plain, + StartTls, + Tls, +} + +#[derive(Deserialize, Debug)] +pub struct EmailConfig { + server: String, + port: Option, + credentials: Option, + connection: Option, + self_signed_cert: Option, + from: String, + to: Option, +} + +impl EmailConfig { + pub fn server(&self) -> &str { + &self.server + } + + pub fn connection(&self) -> EmailConnectionType { + self.connection.unwrap_or(EmailConnectionType::Tls) + } + + pub fn self_signed_cert(&self) -> bool { + self.self_signed_cert.unwrap_or(false) + } + + pub fn port(&self) -> u16 { + self.port.unwrap_or(match self.connection() { + EmailConnectionType::Tls => 587, + EmailConnectionType::StartTls => 465, + EmailConnectionType::Plain => 25, + }) + } + + pub fn to(&self) -> &str { + self.to.as_deref().unwrap_or(self.from.as_ref()) + } +} + +pub struct EmailAction<'a> { + message_config: &'a MessageConfig, + mailer: AsyncSmtpTransport, + message_builder: MessageBuilder, +} +impl<'a> EmailAction<'a> { + pub fn new(message_config: &'a MessageConfig, email_config: &'a EmailConfig) -> Result { + let tls_builder = TlsParametersBuilder::new(email_config.server().into()) + .dangerous_accept_invalid_certs(email_config.self_signed_cert()); + let tls_parameters = tls_builder.build()?; + + let mut smtp_builder = + AsyncSmtpTransport::::builder_dangerous(email_config.server()) + .port(email_config.port()) + .tls(match email_config.connection() { + EmailConnectionType::Tls => Tls::Wrapper(tls_parameters), + EmailConnectionType::StartTls => Tls::Required(tls_parameters), + EmailConnectionType::Plain => Tls::None, + }); + if let Some(cred) = &email_config.credentials { + smtp_builder = smtp_builder.credentials(cred.clone()) + } + Ok(Self { + message_config, + mailer: smtp_builder.build(), + message_builder: Message::builder() + .from( + email_config + .from + .parse() + .with_context(|| format!("invalid from address '{}'", email_config.from))?, + ) + .to(email_config + .to() + .parse() + .with_context(|| format!("invalid to address '{}'", email_config.to()))?), + }) + } +} + +#[async_trait] +impl Action<'_> for EmailAction<'_> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()> { + let body = self.message_config.body(params)?; + let html_body = match self.message_config.format() { + MessageFormat::Markdown => format!( + "{}", + markdown::to_html(&body) + ), + MessageFormat::Html => body.clone(), + MessageFormat::Plain => Default::default(), + }; + let email_builder = self + .message_builder + .clone() + .subject(self.message_config.subject(params)?); + let email = match self.message_config.format() { + MessageFormat::Plain => email_builder + .header(ContentType::TEXT_PLAIN) + .body(body.clone())?, + MessageFormat::Markdown | MessageFormat::Html => email_builder.multipart( + MultiPart::alternative() + .singlepart( + SinglePart::builder() + .header(ContentType::TEXT_PLAIN) + .body(body.clone()), + ) + .singlepart( + SinglePart::builder() + .header(ContentType::TEXT_HTML) + .body(html_body.clone()), + ), + )?, + }; + self.mailer.send(email).await?; + Ok(()) + } +} diff --git a/src/actions/mod.rs b/src/actions/mod.rs new file mode 100644 index 0000000..14ab279 --- /dev/null +++ b/src/actions/mod.rs @@ -0,0 +1,120 @@ +use std::fmt; + +use anyhow::Result; +use async_trait::async_trait; +use serde::Deserialize; + +use crate::message::MessageConfig; +use crate::message::MessageParams; + +mod command; +#[cfg(feature = "desktop")] +mod desktop_notification; +#[cfg(feature = "email")] +mod email; +#[cfg(feature = "nostr")] +mod nostr; +#[cfg(feature = "ntfy")] +mod ntfy; +#[cfg(feature = "telegram")] +mod telegram; +mod terminal_print; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "type")] +pub enum AnyActionConfig { + TerminalPrint, + Command(self::command::CommandConfig), + #[cfg(feature = "desktop")] + DesktopNotification, + #[cfg(feature = "ntfy")] + Ntfy(self::ntfy::NtfyConfig), + #[cfg(feature = "email")] + Email(self::email::EmailConfig), + #[cfg(feature = "telegram")] + Telegram(self::telegram::TelegramConfig), + #[cfg(feature = "nostr")] + Nostr(self::nostr::NostrConfig), +} + +impl fmt::Display for AnyActionConfig { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AnyActionConfig::TerminalPrint => write!(f, "terminal_print"), + AnyActionConfig::Command(_) => write!(f, "command"), + #[cfg(feature = "desktop")] + AnyActionConfig::DesktopNotification => write!(f, "desktop_notification"), + #[cfg(feature = "ntfy")] + AnyActionConfig::Ntfy(_) => write!(f, "ntfy"), + #[cfg(feature = "email")] + AnyActionConfig::Email(_) => write!(f, "email"), + #[cfg(feature = "telegram")] + AnyActionConfig::Telegram(_) => write!(f, "telegram"), + #[cfg(feature = "nostr")] + AnyActionConfig::Nostr(_) => write!(f, "nostr"), + } + } +} + +#[async_trait] +pub trait Action<'a> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()>; +} + +pub async fn get_action<'a>( + message_config: &'a MessageConfig, + action_config: &'a AnyActionConfig, +) -> Result + 'a + Sync>> { + Ok(match action_config { + AnyActionConfig::TerminalPrint => Box::new(self::terminal_print::TerminalPrintAction::new( + message_config, + )), + AnyActionConfig::Command(config) => { + Box::new(self::command::CommandAction::new(message_config, config)?) + } + #[cfg(feature = "desktop")] + AnyActionConfig::DesktopNotification => Box::new( + self::desktop_notification::DesktopNotificationAction::new(message_config), + ), + #[cfg(feature = "ntfy")] + AnyActionConfig::Ntfy(config) => { + Box::new(self::ntfy::NtfyAction::new(message_config, config)?) + } + #[cfg(feature = "email")] + AnyActionConfig::Email(config) => { + Box::new(self::email::EmailAction::new(message_config, config)?) + } + #[cfg(feature = "telegram")] + AnyActionConfig::Telegram(config) => { + Box::new(self::telegram::TelegramAction::new(message_config, config)?) + } + #[cfg(feature = "nostr")] + AnyActionConfig::Nostr(config) => { + Box::new(self::nostr::NostrAction::new(message_config, config).await?) + } + }) +} + +pub async fn get_actions<'a>( + message_config: &'a MessageConfig, + actions_config: &'a [AnyActionConfig], +) -> Vec + 'a + Sync>> { + let mut result: Vec> = Default::default(); + + // TODO: parallelize this. It's hard because the result vector needs to be shared. + for action_config in actions_config { + debug!("registering action '{}'", action_config); + match get_action(message_config, action_config).await { + Ok(action) => { + info!("registered action '{}'", action_config); + result.push(action); + } + Err(e) => { + warn!("could not register action '{}': {}", action_config, e); + } + } + } + + result +} diff --git a/src/actions/nostr.rs b/src/actions/nostr.rs new file mode 100644 index 0000000..aebccba --- /dev/null +++ b/src/actions/nostr.rs @@ -0,0 +1,192 @@ +use super::Action; +use crate::message::MessageConfig; +use crate::message::MessageParams; +use anyhow::{Context, Result}; +use async_scoped::TokioScope; +use async_trait::async_trait; +use const_format::formatcp; +use nostr_relay_pool::RelayOptions; +use nostr_sdk::nips::nip05; +use nostr_sdk::serde_json::from_reader; +use nostr_sdk::serde_json::to_string; +use nostr_sdk::Client; +use nostr_sdk::Keys; +use nostr_sdk::Metadata; +use nostr_sdk::PublicKey; +use nostr_sdk::ToBech32; +use serde::Deserialize; +use serde::Serialize; +use std::fs::File; +use std::io::BufReader; +use std::io::Write; +use std::net::SocketAddr; +use std::path::PathBuf; + +#[derive(Debug, Clone, Deserialize, Serialize)] +struct NostrData { + key: String, + metadata_set: bool, +} + +impl Default for NostrData { + fn default() -> Self { + NostrData { + key: Keys::generate().secret_key().unwrap().to_bech32().unwrap(), + metadata_set: false, + } + } +} + +fn get_nostr_data_filepath() -> PathBuf { + dirs::cache_dir() + .unwrap_or(PathBuf::from("cache")) + .join(env!("CARGO_PKG_NAME")) + .join("nostr.json") +} + +fn get_nostr_data() -> Result { + let path = get_nostr_data_filepath(); + match File::open(&path) { + Ok(file) => { + let reader = BufReader::new(file); + from_reader(reader) + .with_context(|| format!("cannot read nostr data from '{}'", path.display())) + } + Err(_) => { + let nostr_data = NostrData::default(); + let mut file = File::create(&path)?; + file.write_all(to_string(&nostr_data)?.as_bytes()) + .with_context(|| format!("could not write nostr data to '{}'", path.display()))?; + Ok(nostr_data) + } + } +} + +fn get_default_relays() -> Vec { + vec![ + "wss://nostr.bitcoiner.social", + "wss://nostr.oxtr.dev", + "wss://nostr.orangepill.dev", + "wss://relay.damus.io", + ] + .into_iter() + .map(String::from) + .collect() +} + +fn get_default_bot_metadata() -> Metadata { + Metadata::new() + .name(formatcp!("{}bot", env!("CARGO_PKG_NAME"))) + .display_name(formatcp!("{} bot", env!("CARGO_PKG_NAME"))) + .about(env!("CARGO_PKG_DESCRIPTION")) + .website(env!("CARGO_PKG_REPOSITORY").parse().unwrap()) + .picture("https://robohash.org/sentrumbot.png".parse().unwrap()) + .banner( + "https://void.cat/d/HX1pPeqz21hvneLDibs5JD.webp" + .parse() + .unwrap(), + ) + .lud06(formatcp!( + "https://sommerfeld.dev/.well-known/lnurlp/{}", + env!("CARGO_PKG_NAME") + )) + .lud16(formatcp!("{}@sommerfeld.dev", env!("CARGO_PKG_NAME"))) +} + +fn mark_bot_metadata_as_set(mut nostr_data: NostrData) -> Result<()> { + let path = get_nostr_data_filepath(); + nostr_data.metadata_set = true; + let mut file = File::create(&path)?; + file.write_all(to_string(&nostr_data)?.as_bytes()) + .with_context(|| format!("could not write nostr data to '{}'", path.display()))?; + Ok(()) +} + +#[derive(Deserialize, Debug)] +pub struct NostrConfig { + #[serde(default = "get_default_relays")] + relays: Vec, + proxy: Option, + #[serde(default = "get_default_bot_metadata")] + bot_metadata: Metadata, + #[serde(default)] + resend_bot_metadata: bool, + recipient: String, + #[serde(default)] + sealed_dm: bool, +} + +impl NostrConfig {} + +pub struct NostrAction<'a> { + message_config: &'a MessageConfig, + client: Client, + recipient: PublicKey, + sealed_dm: bool, +} + +impl<'a> NostrAction<'a> { + pub async fn new( + message_config: &'a MessageConfig, + nostr_config: &'a NostrConfig, + ) -> Result { + let nostr_data = get_nostr_data()?; + let keys = Keys::parse(&nostr_data.key) + .with_context(|| format!("could not parse nostr secret key '{}'", nostr_data.key))?; + + let client = Client::new(&keys); + + let relay_opts = RelayOptions::new().read(false).proxy(nostr_config.proxy); + TokioScope::scope_and_block(|s| { + for relay in nostr_config.relays.iter() { + s.spawn(client.add_relay_with_opts(relay.clone(), relay_opts.clone())); + } + }); + + client.connect().await; + + if !nostr_data.metadata_set || nostr_config.resend_bot_metadata { + client.set_metadata(&nostr_config.bot_metadata).await?; + mark_bot_metadata_as_set(nostr_data)?; + } + + let recipient = match PublicKey::parse(&nostr_config.recipient) { + Ok(p) => p, + Err(e) => { + nip05::get_profile(&nostr_config.recipient, nostr_config.proxy) + .await + .with_context(|| { + format!("invalid recipient '{}': {}", nostr_config.recipient, e) + })? + .public_key + } + }; + + Ok(Self { + message_config, + client, + recipient, + sealed_dm: nostr_config.sealed_dm, + }) + } +} + +#[async_trait] +impl Action<'_> for NostrAction<'_> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()> { + let subject = self.message_config.subject(params)?; + let body = self.message_config.body(params)?; + let message = format!("{}\n{}", subject, body); + + if self.sealed_dm { + self.client + .send_sealed_msg(self.recipient, message, None) + .await?; + } else { + self.client + .send_direct_msg(self.recipient, message, None) + .await?; + } + Ok(()) + } +} diff --git a/src/actions/ntfy.rs b/src/actions/ntfy.rs new file mode 100644 index 0000000..7d83b87 --- /dev/null +++ b/src/actions/ntfy.rs @@ -0,0 +1,119 @@ +use super::Action; +use crate::message::MessageConfig; +use crate::message::MessageFormat; +use crate::message::MessageParams; +use anyhow::Result; +use async_trait::async_trait; +use ntfy::Auth; +use ntfy::Dispatcher; +use ntfy::Payload; +use ntfy::Priority; +use ntfy::Url; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(remote = "Priority")] +#[serde(rename_all = "snake_case")] +pub enum NtfyPriority { + Max = 5, + High = 4, + Default = 3, + Low = 2, + Min = 1, +} + +#[derive(Deserialize, Debug)] +pub struct NtfyCredentials { + username: String, + password: String, +} + +#[derive(Deserialize, Debug)] +pub struct NtfyConfig { + url: Option, + proxy: Option, + topic: Option, + pub credentials: Option, + #[serde(with = "NtfyPriority")] + #[serde(default)] + pub priority: Priority, + pub tags: Option>, + pub attach: Option, + pub filename: Option, + pub delay: Option, + pub email: Option, +} + +impl NtfyConfig { + pub fn url(&self) -> &str { + self.url.as_deref().unwrap_or("https://ntfy.sh") + } + + pub fn topic(&self) -> &str { + self.topic.as_deref().unwrap_or(env!("CARGO_PKG_NAME")) + } +} + +pub struct NtfyAction<'a> { + message_config: &'a MessageConfig, + dispatcher: Dispatcher, + payload_template: Payload, +} + +impl<'a> NtfyAction<'a> { + pub fn new(message_config: &'a MessageConfig, ntfy_config: &'a NtfyConfig) -> Result { + let mut dispatcher_builder = Dispatcher::builder(ntfy_config.url()); + if let Some(cred) = &ntfy_config.credentials { + dispatcher_builder = + dispatcher_builder.credentials(Auth::new(&cred.username, &cred.password)); + } + if let Some(proxy) = &ntfy_config.proxy { + dispatcher_builder = dispatcher_builder.proxy(proxy); + } + + let mut payload = Payload::new(ntfy_config.topic()) + .markdown(match message_config.format() { + MessageFormat::Plain => false, + MessageFormat::Markdown => true, + MessageFormat::Html => true, + }) + .priority(ntfy_config.priority.clone()) + .tags( + ntfy_config + .tags + .as_deref() + .unwrap_or(&["rotating_light".to_string()]), + ); + if let Some(attach) = &ntfy_config.attach { + payload = payload.attach(attach.clone()); + } + if let Some(filename) = &ntfy_config.filename { + payload = payload.filename(filename.clone()); + } + if let Some(delay) = &ntfy_config.delay { + payload = payload.delay(delay.parse()?); + } + if let Some(email) = &ntfy_config.email { + payload = payload.email(email.clone()); + } + Ok(Self { + message_config, + dispatcher: dispatcher_builder.build()?, + payload_template: payload, + }) + } +} + +#[async_trait] +impl Action<'_> for NtfyAction<'_> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()> { + let payload = self + .payload_template + .clone() + .title(self.message_config.subject(params)?) + .message(self.message_config.body(params)?) + .click(self.message_config.get_tx_url(params)?.parse()?); + self.dispatcher.send(&payload).await?; + Ok(()) + } +} diff --git a/src/actions/telegram.rs b/src/actions/telegram.rs new file mode 100644 index 0000000..b489864 --- /dev/null +++ b/src/actions/telegram.rs @@ -0,0 +1,56 @@ +use super::Action; +use crate::message::MessageConfig; +use crate::message::MessageParams; +use anyhow::Result; +use async_trait::async_trait; +use serde::Deserialize; +use teloxide::requests::Requester; +use teloxide::types::UserId; +use teloxide::Bot; + +#[derive(Deserialize, Debug)] +pub struct TelegramConfig { + bot_token: String, + user_id: u64, +} + +impl TelegramConfig { + pub fn bot_token(&self) -> &str { + &self.bot_token + } + + pub fn user_id(&self) -> u64 { + self.user_id + } +} + +pub struct TelegramAction<'a> { + message_config: &'a MessageConfig, + bot: Bot, + user_id: UserId, +} + +impl<'a> TelegramAction<'a> { + pub fn new( + message_config: &'a MessageConfig, + telegram_config: &'a TelegramConfig, + ) -> Result { + Ok(Self { + message_config, + bot: Bot::new(telegram_config.bot_token()), + user_id: UserId(telegram_config.user_id()), + }) + } +} + +#[async_trait] +impl Action<'_> for TelegramAction<'_> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()> { + let subject = self.message_config.subject(params)?; + let body = self.message_config.body(params)?; + self.bot + .send_message(self.user_id, format!("{}\n{}", subject, body)) + .await?; + Ok(()) + } +} diff --git a/src/actions/terminal_print.rs b/src/actions/terminal_print.rs new file mode 100644 index 0000000..02536c7 --- /dev/null +++ b/src/actions/terminal_print.rs @@ -0,0 +1,28 @@ +use super::Action; +use crate::message::MessageConfig; +use crate::message::MessageParams; +use anyhow::Result; +use async_trait::async_trait; + +#[derive(Debug)] +pub struct TerminalPrintAction<'a> { + message_config: &'a MessageConfig, +} + +impl<'a> TerminalPrintAction<'a> { + pub fn new(message_config: &'a MessageConfig) -> Self { + Self { message_config } + } +} + +#[async_trait] +impl Action<'_> for TerminalPrintAction<'_> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()> { + println!( + "{}\n{}\n", + self.message_config.subject(params)?, + self.message_config.body(params)? + ); + Ok(()) + } +} diff --git a/src/blockchain.rs b/src/blockchain.rs new file mode 100644 index 0000000..2f5014b --- /dev/null +++ b/src/blockchain.rs @@ -0,0 +1,96 @@ +use anyhow::{Context, Result}; +use bdk::{ + bitcoin::Network, + blockchain::{ElectrumBlockchain, GetHeight}, + electrum_client::{Client, ConfigBuilder, Socks5Config}, +}; +use serde::Deserialize; + +fn get_default_electrum_server(network: Network) -> &'static str { + match network { + Network::Bitcoin => "ssl://fulcrum.sethforprivacy.com:50002", + Network::Testnet => "ssl://electrum.blockstream.info:60002", + Network::Signet => "ssl://mempool.space:60602", + _ => panic!("unsupported network"), + } +} + +#[derive(Deserialize, Default, Debug)] +pub struct ElectrumConfig { + url: Option, + + network: Option, + + socks5: Option, + + #[serde(default)] + certificate_validation: bool, +} + +impl ElectrumConfig { + pub fn url(&self) -> &str { + self.url + .as_deref() + .unwrap_or(get_default_electrum_server(self.network())) + } + + pub fn network(&self) -> Network { + self.network.unwrap_or(Network::Bitcoin) + } + + pub fn certificate_validation(&self) -> bool { + self.certificate_validation + } + + pub fn socks5(&self) -> Option { + self.socks5.as_ref().map(Socks5Config::new) + } +} + +pub fn get_blockchain(electrum_cfg: &ElectrumConfig) -> Result { + let server_cfg = ConfigBuilder::new() + .validate_domain(electrum_cfg.certificate_validation()) + .socks5(electrum_cfg.socks5()) + .build(); + let electrum_url = electrum_cfg.url(); + let client = Client::from_config(electrum_url, server_cfg) + .with_context(|| "could not configure electrum client".to_string())?; + Ok(ElectrumBlockchain::from(client)) +} + +pub struct BlockchainState { + height: Option, + url: String, + blockchain: ElectrumBlockchain, +} + +impl BlockchainState { + pub fn new(electrum_cfg: &ElectrumConfig) -> Result { + Ok(Self { + height: Default::default(), + url: String::from(electrum_cfg.url()), + blockchain: get_blockchain(electrum_cfg)?, + }) + } + + pub fn update_height(&mut self) { + match self.blockchain.get_height() { + Ok(polled_height) => { + match self.height { + Some(h) => { + if polled_height != h { + self.height = Some(polled_height); + debug!("current block height: {}", polled_height); + } + } + None => { + self.height = Some(polled_height); + info!("connected to '{}'", self.url); + info!("current block height: {}", polled_height); + } + }; + } + Err(e) => warn!("could not reach '{}': {}", self.url, e), + }; + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..2f3e5ed --- /dev/null +++ b/src/config.rs @@ -0,0 +1,143 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +use anyhow::{bail, Context, Result}; +use clap::Parser; +use const_format::{formatcp, map_ascii_case, Case}; +use serde::Deserialize; + +use crate::{ + actions::AnyActionConfig, blockchain::ElectrumConfig, message::MessageConfig, + wallets::WalletConfig, +}; + +#[derive(Parser, Debug)] +#[command(version, about)] +pub struct Args { + /// Path to toml configuration file + config: Option, + /// Perform configured actions on a test notification + #[arg(short, long)] + test: bool, + /// Notify for every past transaction (careful: if you have a long transaction history, this + /// can SPAM your configured actions + #[arg(short, long)] + notify_past_txs: bool, +} + +impl Args { + pub fn config(&self) -> Option<&str> { + self.config.as_deref() + } + + pub fn test(&self) -> bool { + self.test + } + + pub fn notify_past_txs(&self) -> bool { + self.notify_past_txs + } +} + +fn get_config_filename() -> &'static str { + formatcp!("{}.toml", env!("CARGO_PKG_NAME")) +} + +fn get_config_env_var() -> &'static str { + formatcp!( + "{}_CONFIG", + map_ascii_case!(Case::Upper, env!("CARGO_PKG_NAME")) + ) +} + +fn get_cwd_config_path() -> PathBuf { + PathBuf::from(".").join(get_config_filename()) +} + +fn get_config_path_impl(user_config_dir: &Path) -> PathBuf { + user_config_dir + .join(env!("CARGO_PKG_NAME")) + .join(get_config_filename()) +} + +fn get_user_config_path() -> Option { + dirs::config_dir().map(|p| get_config_path_impl(&p)) +} + +fn get_system_config_path() -> PathBuf { + get_config_path_impl(&systemd_directories::config_dir().unwrap_or(PathBuf::from("/etc"))) +} + +fn get_config_path(maybe_arg_config: &Option<&str>) -> Result { + if let Some(arg_path) = maybe_arg_config { + return Ok(PathBuf::from(arg_path)); + } + + if let Ok(env_path) = env::var(get_config_env_var()) { + return Ok(PathBuf::from(env_path)); + } + + let cwd_config_path = get_cwd_config_path(); + if cwd_config_path.try_exists().is_ok_and(|x| x) { + return Ok(cwd_config_path); + } + + if let Some(user_config_path) = get_user_config_path() { + if user_config_path.try_exists().is_ok_and(|x| x) { + return Ok(user_config_path); + } + } + + let system_config_path = get_system_config_path(); + if system_config_path.try_exists().is_ok_and(|x| x) { + return Ok(system_config_path); + } + + bail!( + "no configuration file was passed as first argument, nor by the '{}' environment variable, nor did one exist in the default search paths: '{}', '{}', '{}'", + get_config_env_var(), + get_cwd_config_path().display(), + get_user_config_path().unwrap_or_default().display(), + get_system_config_path().display() + ); +} + +#[derive(Deserialize, Debug)] +pub struct Config { + wallets: Vec, + #[serde(default)] + electrum: ElectrumConfig, + #[serde(default)] + message: MessageConfig, + #[serde(default)] + actions: Vec, +} + +impl Config { + pub fn electrum(&self) -> &ElectrumConfig { + &self.electrum + } + + pub fn wallets(&self) -> &[WalletConfig] { + &self.wallets + } + + pub fn message(&self) -> &MessageConfig { + &self.message + } + + pub fn actions(&self) -> &[AnyActionConfig] { + &self.actions + } +} + +pub fn get_config(maybe_arg_config: &Option<&str>) -> Result { + let config_path = get_config_path(maybe_arg_config)?; + info!("reading configuration from '{}'", config_path.display()); + let config_content = fs::read_to_string(&config_path) + .with_context(|| format!("could not read config file '{}'", config_path.display()))?; + toml::from_str(&config_content) + .with_context(|| format!("could not parse config file '{}'", config_path.display(),)) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9b10935 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,179 @@ +extern crate pretty_env_logger; +#[macro_use] +extern crate log; + +use std::process::exit; +use std::time::Duration; + +use actions::Action; +use async_scoped::TokioScope; +use clap::Parser; +use human_panic::setup_panic; + +use anyhow::{bail, Context, Result}; +use tokio::signal::unix::{signal, SignalKind}; +use tokio::time::sleep; + +mod actions; +mod blockchain; +mod config; +mod message; +mod wallets; + +use crate::actions::get_actions; +use crate::message::MessageParams; +use crate::{ + blockchain::BlockchainState, + config::{get_config, Args}, + wallets::{get_wallets, SafeWalletInfo}, +}; + +fn set_logger() { + pretty_env_logger::formatted_builder() + .filter_module(env!("CARGO_PKG_NAME"), log::LevelFilter::Info) + .parse_default_env() + .init(); +} + +fn set_signal_handlers() -> Result<()> { + tokio::spawn(async move { + if let Err(e) = tokio::signal::ctrl_c().await { + return e; + } + warn!("received ctrl-c signal. Exiting..."); + exit(0) + }); + tokio::spawn(async move { + let mut stream = match signal(SignalKind::terminate()) { + Err(e) => return e, + Ok(s) => s, + }; + stream.recv().await; + warn!("received process termination signal. Exiting..."); + exit(0) + }); + tokio::spawn(async move { + let mut stream = match signal(SignalKind::hangup()) { + Err(e) => return e, + Ok(s) => s, + }; + stream.recv().await; + warn!("received process hangup signal. Exiting..."); + exit(0) + }); + Ok(()) +} + +async fn run_test_actions(actions: &[&(dyn Action<'_> + Sync)]) { + TokioScope::scope_and_block(|s| { + for &action in actions { + s.spawn(action.run(None)); + } + }); +} + +fn get_and_handle_new_txs( + wallet_info: &SafeWalletInfo, + actions: &[&(dyn Action<'_> + Sync)], +) -> Result<()> { + let mut locked_wallet_info = wallet_info.lock().unwrap(); + let txs = locked_wallet_info.get_new_txs(); + TokioScope::scope_and_block(|s| { + for tx in txs.iter() { + let params = MessageParams::new(tx, &locked_wallet_info); + s.spawn(async move { + TokioScope::scope_and_block(|s| { + for &action in actions { + s.spawn(action.run(Some(¶ms))); + } + }); + }); + } + }); + Ok(()) +} + +async fn update_blockchain_thread(blockchain_state: &mut BlockchainState) { + loop { + blockchain_state.update_height(); + sleep(Duration::from_secs(60)).await; + } +} + +async fn watch_wallet_thread(wallet_info: &SafeWalletInfo, actions: &[&(dyn Action<'_> + Sync)]) { + loop { + if let Err(e) = get_and_handle_new_txs(wallet_info, actions) { + warn!("{:?}", e); + } + } +} + +async fn initial_wallet_sync(blockchain_state: &mut BlockchainState, wallets: &[SafeWalletInfo]) { + TokioScope::scope_and_block(|s| { + s.spawn(async { blockchain_state.update_height() }); + for wallet_info in wallets { + s.spawn(async { + if let Err(e) = get_and_handle_new_txs(wallet_info, &[]) { + warn!("{:?}", e); + } + }); + } + }); +} + +async fn watch_wallets( + blockchain_state: &mut BlockchainState, + wallets: &[SafeWalletInfo], + actions: &[&(dyn Action<'_> + Sync)], +) { + TokioScope::scope_and_block(|s| { + s.spawn(update_blockchain_thread(blockchain_state)); + for wallet_info in wallets { + s.spawn(watch_wallet_thread(wallet_info, actions)); + } + }); +} + +async fn do_main() -> Result<()> { + setup_panic!(); + let args = Args::parse(); + set_logger(); + set_signal_handlers().context("failed to setup a signal termination handler")?; + + let config = get_config(&args.config())?; + + let actions = get_actions(config.message(), config.actions()).await; + if actions.is_empty() { + bail!("no actions properly configured"); + } + let actions_ref = actions.iter().map(Box::as_ref).collect::>(); + + if args.test() { + run_test_actions(&actions_ref).await; + return Ok(()); + } + + let mut blockchain_state = BlockchainState::new(config.electrum())?; + + let wallets = get_wallets(config.wallets(), config.electrum()); + if wallets.is_empty() { + bail!("no wallets properly configured"); + } + + if !args.notify_past_txs() { + info!("initial wallet sync"); + initial_wallet_sync(&mut blockchain_state, &wallets).await; + } + info!("listening for new relevant events"); + watch_wallets(&mut blockchain_state, &wallets, &actions_ref).await; + + Ok(()) +} + +#[tokio::main] +async fn main() { + if let Err(e) = do_main().await { + error!("{:?}", e); + exit(1); + } +} diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 0000000..419bb4d --- /dev/null +++ b/src/message.rs @@ -0,0 +1,203 @@ +extern crate chrono; +extern crate strfmt; + +use anyhow::{bail, Context, Result}; +use bdk::{bitcoin::Network, TransactionDetails}; +use chrono::DateTime; +use serde::Deserialize; +use strfmt::strfmt; + +use crate::wallets::WalletInfo; + +pub struct MessageParams<'a, 'b> { + tx: &'a TransactionDetails, + wallet: &'b str, + total_balance: u64, + current_height: u32, + network: Network, +} + +impl<'a, 'b> MessageParams<'a, 'b> { + pub fn new(tx: &'a TransactionDetails, wallet: &'b WalletInfo) -> Self { + Self { + tx, + wallet: wallet.name(), + total_balance: wallet.total_balance().unwrap_or_default(), + current_height: wallet.get_height().unwrap_or_default(), + network: wallet.get_network(), + } + } + + fn tx_net(&self) -> i64 { + (self.tx.received as i64) - (self.tx.sent as i64) + } + + fn tx_height(&self) -> Option { + self.tx.confirmation_time.as_ref().map(|x| x.height) + } + + fn confs(&self) -> u32 { + let current_height = self.current_height; + self.tx_height() + .map(|h| { + if current_height >= h { + current_height - h + } else { + 0 + } + }) + .unwrap_or_default() + } + + fn conf_timestamp(&self) -> String { + self.tx + .confirmation_time + .as_ref() + .map(|x| { + DateTime::from_timestamp(x.timestamp as i64, 0) + .unwrap_or_default() + .format("%Y-%m-%d %H:%M:%S UTC") + .to_string() + }) + .unwrap_or_default() + } + + fn txid(&self) -> String { + self.tx.txid.to_string() + } + fn txid_short(&self) -> String { + let txid = self.txid(); + format!("{}...{}", &txid[..6], &txid[txid.len() - 6..]) + } + + fn tx(&self) -> &TransactionDetails { + self.tx + } + + pub fn network(&self) -> Network { + self.network + } +} + +#[derive(Deserialize, Debug, PartialEq, Copy, Clone)] +pub enum MessageFormat { + Plain, + Markdown, + Html, +} + +#[derive(Deserialize, Default, Debug)] +pub struct BlockExplorers { + mainnet: Option, + testnet: Option, + signet: Option, +} + +impl BlockExplorers { + fn mainnet(&self) -> &str { + self.mainnet + .as_deref() + .unwrap_or("https://mempool.space/tx/{txid}") + } + + fn testnet(&self) -> &str { + self.testnet + .as_deref() + .unwrap_or("https://mempool.space/testnet/tx/{txid}") + } + + fn signet(&self) -> &str { + self.signet + .as_deref() + .unwrap_or("https://mempool.space/signet/tx/{txid}") + } + + pub fn get_tx_url_template(&self, network: &Network) -> Result<&str> { + Ok(match network { + Network::Bitcoin => self.mainnet(), + Network::Testnet => self.testnet(), + Network::Signet => self.signet(), + _ => bail!("unsupported network"), + }) + } + pub fn get_tx_url(&self, network: &Network, txid: &str) -> Result { + let template = self.get_tx_url_template(network)?; + strfmt!(template, txid => txid.to_string()) + .with_context(|| format!("bad block explorer URL template '{}'", template)) + } +} + +#[derive(Deserialize, Default, Debug)] +pub struct MessageConfig { + subject: Option, + body: Option, + format: Option, + #[serde(default)] + block_explorers: BlockExplorers, +} + +impl MessageConfig { + pub fn subject_template(&self) -> &str { + self.subject + .as_deref() + .unwrap_or("[{wallet}] new transaction") + } + + pub fn body_template(&self) -> &str { + self.body + .as_deref() + .unwrap_or("net: {tx_net} sats, balance: {total_balance} sats, txid: {txid_short}") + } + + pub fn replace_template_params( + &self, + template: &str, + params: &MessageParams, + ) -> Result { + strfmt!(template, + tx_net => params.tx_net(), + wallet => params.wallet.to_string(), + total_balance => params.total_balance, + txid => params.txid(), + txid_short => params.txid_short(), + received => params.tx().received, + sent => params.tx().sent, + fee => params.tx().fee.unwrap_or_default(), + current_height => params.current_height, + tx_height => params.tx_height().unwrap_or_default(), + confs => params.confs(), + conf_timestamp => params.conf_timestamp(), + tx_url => self.get_tx_url(Some(params))? + ) + .with_context(|| format!("invalid template '{}'", template)) + } + + pub fn subject(&self, params: Option<&MessageParams>) -> Result { + match params { + Some(p) => self.replace_template_params(self.subject_template(), p), + None => Ok(self.subject_template().to_string()), + } + } + + pub fn body(&self, params: Option<&MessageParams>) -> Result { + match params { + Some(p) => self.replace_template_params(self.body_template(), p), + None => Ok(self.body_template().to_string()), + } + } + + #[allow(dead_code)] + pub fn format(&self) -> &MessageFormat { + self.format.as_ref().unwrap_or(&MessageFormat::Plain) + } + + pub fn get_tx_url(&self, params: Option<&MessageParams>) -> Result { + match params { + Some(p) => self.block_explorers.get_tx_url(&p.network(), &p.txid()), + None => Ok(self + .block_explorers + .get_tx_url_template(&Network::Bitcoin)? + .to_string()), + } + } +} diff --git a/src/wallets.rs b/src/wallets.rs new file mode 100644 index 0000000..ca43e0a --- /dev/null +++ b/src/wallets.rs @@ -0,0 +1,230 @@ +use std::{ + collections::{hash_map::DefaultHasher, HashSet}, + hash::{Hash, Hasher}, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use anyhow::{Context, Result}; +use bdk::{ + bitcoin::{bip32::ExtendedPubKey, Network, Txid}, + blockchain::{ElectrumBlockchain, GetHeight}, + sled, + template::{Bip44Public, Bip49Public, Bip84Public, Bip86Public}, + KeychainKind, SyncOptions, TransactionDetails, Wallet, +}; +use serde::Deserialize; + +use crate::blockchain::{get_blockchain, ElectrumConfig}; + +#[derive(Deserialize, Debug, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum AddressKind { + Legacy, + NestedSegwit, + Segwit, + Taproot, +} + +#[derive(Deserialize, Debug)] +pub struct XpubSpec { + name: String, + xpub: String, + kind: Option, +} + +impl XpubSpec { + pub fn kind(&self) -> AddressKind { + self.kind.unwrap_or(AddressKind::Segwit) + } + + pub fn xpub(&self) -> &str { + &self.xpub + } + + pub fn name(&self) -> &str { + &self.name + } +} + +#[derive(Deserialize, Debug, Hash)] +pub struct DescriptorsSpec { + name: String, + primary: String, + change: Option, +} + +impl DescriptorsSpec { + pub fn get_hash(&self) -> String { + let mut s = DefaultHasher::new(); + self.hash(&mut s); + s.finish().to_string() + } + + pub fn primary(&self) -> &str { + &self.primary + } + + pub fn change(&self) -> Option<&String> { + self.change.as_ref() + } + + pub fn name(&self) -> &str { + &self.name + } +} + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub enum WalletConfig { + Xpub(XpubSpec), + Descriptors(DescriptorsSpec), +} + +impl WalletConfig { + pub fn name(&self) -> &str { + match self { + WalletConfig::Xpub(xpub_spec) => xpub_spec.name(), + WalletConfig::Descriptors(descriptors_spec) => descriptors_spec.name(), + } + } +} + +fn get_cache_dir(db_name: &str) -> PathBuf { + dirs::cache_dir() + .unwrap_or(PathBuf::from("cache")) + .join(env!("CARGO_PKG_NAME")) + .join(db_name) +} + +fn get_xpub_wallet(xpub_spec: &XpubSpec, network: Network) -> Result> { + let xpub: ExtendedPubKey = xpub_spec.xpub().parse().unwrap(); + let fingerprint = xpub.fingerprint(); + let sled = sled::open(get_cache_dir(&fingerprint.to_string()))?.open_tree("wallet")?; + match xpub_spec.kind() { + AddressKind::Legacy => Wallet::new( + Bip44Public(xpub, fingerprint, KeychainKind::External), + Some(Bip44Public(xpub, fingerprint, KeychainKind::Internal)), + network, + sled, + ), + AddressKind::NestedSegwit => Wallet::new( + Bip49Public(xpub, fingerprint, KeychainKind::External), + Some(Bip49Public(xpub, fingerprint, KeychainKind::Internal)), + network, + sled, + ), + AddressKind::Segwit => Wallet::new( + Bip84Public(xpub, fingerprint, KeychainKind::External), + Some(Bip84Public(xpub, fingerprint, KeychainKind::Internal)), + network, + sled, + ), + AddressKind::Taproot => Wallet::new( + Bip86Public(xpub, fingerprint, KeychainKind::External), + Some(Bip86Public(xpub, fingerprint, KeychainKind::Internal)), + network, + sled, + ), + } + .with_context(|| format!("invalid xpub wallet '{}'", xpub)) +} + +fn get_descriptors_wallet( + descriptors_spec: &DescriptorsSpec, + network: Network, +) -> Result> { + let sled = sled::open(get_cache_dir(&descriptors_spec.get_hash()))?.open_tree("wallet")?; + Wallet::new( + descriptors_spec.primary(), + descriptors_spec.change().map(String::as_ref), + network, + sled, + ) + .with_context(|| format!("invalid descriptor wallet '{:?}'", descriptors_spec)) +} + +fn get_wallet(wallet_config: &WalletConfig, network: Network) -> Result> { + match &wallet_config { + WalletConfig::Xpub(xpub_spec) => get_xpub_wallet(xpub_spec, network), + WalletConfig::Descriptors(descriptors_spec) => { + get_descriptors_wallet(descriptors_spec, network) + } + } +} + +pub struct WalletInfo { + name: String, + wallet: Wallet, + old_txs: HashSet, + blockchain: ElectrumBlockchain, +} + +pub type SafeWalletInfo = Arc>; + +impl WalletInfo { + pub fn name(&self) -> &str { + &self.name + } + + pub fn get_height(&self) -> Result { + self.blockchain.get_height() + } + + pub fn get_network(&self) -> Network { + self.wallet.network() + } + + pub fn total_balance(&self) -> Result { + self.wallet.get_balance().map(|b| b.get_total()) + } + + pub fn get_new_txs(&mut self) -> Vec { + debug!("[{}] syncing wallet", self.name); + if let Err(e) = self.wallet.sync(&self.blockchain, SyncOptions::default()) { + warn!("[{}] cannot sync wallet: {}", self.name, e); + return Default::default(); + } + let tx_list = match self.wallet.list_transactions(false) { + Ok(txs) => txs, + Err(e) => { + warn!("[{}] cannot retrieve transactions: {}", self.name, e); + Default::default() + } + }; + + let new_txs: Vec = tx_list + .iter() + .filter(|&tx| !self.old_txs.contains(&tx.txid)) + .cloned() + .collect(); + new_txs.iter().for_each(|tx| { + self.old_txs.insert(tx.txid); + }); + new_txs + } +} + +pub fn get_wallets( + wallet_configs: &[WalletConfig], + electrum_cfg: &ElectrumConfig, +) -> Vec { + let mut result: Vec = vec![]; + for wallet_config in wallet_configs.iter() { + let name = wallet_config.name(); + match get_wallet(wallet_config, electrum_cfg.network()) { + Ok(w) => { + result.push(Arc::new(Mutex::new(WalletInfo { + name: name.to_string(), + wallet: w, + old_txs: Default::default(), + blockchain: get_blockchain(electrum_cfg).unwrap(), + }))); + } + Err(e) => { + error!("[{}] cannot setup wallet: {}", name, e); + } + } + } + result +} -- cgit v1.2.3-70-g09d2