|
1 | | -# Magical Bitcoin Wallet |
| 1 | +<div align="center"> |
| 2 | + <h1>Magical Bitcoin Library</h1> |
2 | 3 |
|
3 | | -A modern, lightweight, descriptor-based wallet written in Rust! |
| 4 | + <img src="./static/wizard.svg" width="220" /> |
4 | 5 |
|
5 | | -## Getting Started |
| 6 | + <p> |
| 7 | + <strong>A modern, lightweight, descriptor-based wallet library written in Rust!</strong> |
| 8 | + </p> |
6 | 9 |
|
7 | | -See the documentation at [magicalbitcoin.org](https://magicalbitcoin.org) |
| 10 | + <p> |
| 11 | + <a href="https://crates.io/crates/magical"><img alt="Crate Info" src="https://img.shields.io/crates/v/magical.svg"/></a> |
| 12 | + <a href="https://docs.rs/magical/"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-magical-green"/></a> |
| 13 | + <a href="https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html"><img alt="Rustc Version 1.45+" src="https://img.shields.io/badge/rustc-1.45%2B-lightgrey.svg"/></a> |
| 14 | + </p> |
| 15 | + |
| 16 | + <h4> |
| 17 | + <a href="https://magicalbitcoin.org">Project Homepage</a> |
| 18 | + <span> | </span> |
| 19 | + <a href="https://docs.rs/magical">Documentation</a> |
| 20 | + </h4> |
| 21 | +</div> |
| 22 | + |
| 23 | +## About |
| 24 | + |
| 25 | +The `magical` library aims to be the core building block for Bitcoin wallets of any kind. |
| 26 | + |
| 27 | +* It uses [Miniscript](https://github.com/rust-bitcoin/rust-miniscript) to support descriptors with generalized conditions. This exact same library can be used to build |
| 28 | + single-sig wallets, multisigs, timelocked contracts and more. |
| 29 | +* It supports multiple blockchain backends and databases, allowing developers to choose exactly what's right for their projects. |
| 30 | +* It's built to be cross-platform: the core logic works on desktop, mobile, and even WebAssembly. |
| 31 | +* It's very easy to extend: developers can implement customized logic for blockchain backends, databases, signers, coin selection, and more, without having to fork and modify this library. |
| 32 | + |
| 33 | +## Examples |
| 34 | + |
| 35 | +### Sync the balance of a descriptor |
| 36 | + |
| 37 | +```no_run |
| 38 | +use magical::Wallet; |
| 39 | +use magical::database::MemoryDatabase; |
| 40 | +use magical::blockchain::{noop_progress, ElectrumBlockchain}; |
| 41 | +
|
| 42 | +use magical::electrum_client::Client; |
| 43 | +
|
| 44 | +fn main() -> Result<(), magical::Error> { |
| 45 | + let client = Client::new("ssl://electrum.blockstream.info:60002", None)?; |
| 46 | + let wallet = Wallet::new( |
| 47 | + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", |
| 48 | + Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), |
| 49 | + bitcoin::Network::Testnet, |
| 50 | + MemoryDatabase::default(), |
| 51 | + ElectrumBlockchain::from(client) |
| 52 | + )?; |
| 53 | +
|
| 54 | + wallet.sync(noop_progress(), None)?; |
| 55 | +
|
| 56 | + println!("Descriptor balance: {} SAT", wallet.get_balance()?); |
| 57 | +
|
| 58 | + Ok(()) |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +### Generate a few addresses |
| 63 | + |
| 64 | +``` |
| 65 | +use magical::{Wallet, OfflineWallet}; |
| 66 | +use magical::database::MemoryDatabase; |
| 67 | +
|
| 68 | +fn main() -> Result<(), magical::Error> { |
| 69 | + let wallet: OfflineWallet<_> = Wallet::new_offline( |
| 70 | + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", |
| 71 | + Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), |
| 72 | + bitcoin::Network::Testnet, |
| 73 | + MemoryDatabase::default(), |
| 74 | + )?; |
| 75 | +
|
| 76 | + println!("Address #0: {}", wallet.get_new_address()?); |
| 77 | + println!("Address #1: {}", wallet.get_new_address()?); |
| 78 | + println!("Address #2: {}", wallet.get_new_address()?); |
| 79 | +
|
| 80 | + Ok(()) |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +### Create a transaction |
| 85 | + |
| 86 | +```no_run |
| 87 | +use magical::{FeeRate, TxBuilder, Wallet}; |
| 88 | +use magical::database::MemoryDatabase; |
| 89 | +use magical::blockchain::{noop_progress, ElectrumBlockchain}; |
| 90 | +
|
| 91 | +use magical::electrum_client::Client; |
| 92 | +
|
| 93 | +use bitcoin::consensus::serialize; |
| 94 | +
|
| 95 | +fn main() -> Result<(), magical::Error> { |
| 96 | + let client = Client::new("ssl://electrum.blockstream.info:60002", None)?; |
| 97 | + let wallet = Wallet::new( |
| 98 | + "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", |
| 99 | + Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), |
| 100 | + bitcoin::Network::Testnet, |
| 101 | + MemoryDatabase::default(), |
| 102 | + ElectrumBlockchain::from(client) |
| 103 | + )?; |
| 104 | +
|
| 105 | + wallet.sync(noop_progress(), None)?; |
| 106 | +
|
| 107 | + let send_to = wallet.get_new_address()?; |
| 108 | + let (psbt, details) = wallet.create_tx( |
| 109 | + TxBuilder::with_recipients(vec![(send_to.script_pubkey(), 50_000)]) |
| 110 | + .enable_rbf() |
| 111 | + .do_not_spend_change() |
| 112 | + .fee_rate(FeeRate::from_sat_per_vb(5.0)) |
| 113 | + )?; |
| 114 | +
|
| 115 | + println!("Transaction details: {:#?}", details); |
| 116 | + println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt))); |
| 117 | +
|
| 118 | + Ok(()) |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +### Sign a transaction |
| 123 | + |
| 124 | +```no_run |
| 125 | +use magical::{Wallet, OfflineWallet}; |
| 126 | +use magical::database::MemoryDatabase; |
| 127 | +
|
| 128 | +use bitcoin::consensus::deserialize; |
| 129 | +
|
| 130 | +fn main() -> Result<(), magical::Error> { |
| 131 | + let wallet: OfflineWallet<_> = Wallet::new_offline( |
| 132 | + "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", |
| 133 | + Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), |
| 134 | + bitcoin::Network::Testnet, |
| 135 | + MemoryDatabase::default(), |
| 136 | + )?; |
| 137 | +
|
| 138 | + let psbt = "..."; |
| 139 | + let psbt = deserialize(&base64::decode(psbt).unwrap())?; |
| 140 | +
|
| 141 | + let (signed_psbt, finalized) = wallet.sign(psbt, None)?; |
| 142 | +
|
| 143 | + Ok(()) |
| 144 | +} |
| 145 | +``` |
0 commit comments