diff --git a/Cargo.toml b/Cargo.toml
index 87c371e..e4cbf07 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,7 +8,7 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-bdk = { version = "0.3.0", default-features = false }
+bdk = { git = "https://github.com/bitcoindevkit/bdk.git", rev = "c456a25", default-features = false }
bdk-macros = "^0.2"
simple-server = "0.4.0"
log = "0.4.0"
diff --git a/README.md b/README.md
index c113d4f..78368e6 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ Another Bitcoin payment service, based on [bdk](https://github.com/bitcoindevkit
### Get it start
Build and run service (default port is 8080):
```
-cargo run server
+RUST_LOG=info cargo run
```
Open the local web page on your browser using url [localhost:8080/bitcoin](http://localhost:8080/bitcoin).
diff --git a/assets/index.html b/assets/index.html
index fcec15a..09c987b 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -128,7 +128,7 @@
Bitcoin
- Generate new address
+ Get unused address
diff --git a/config_example.ini b/config_example.ini
index ece5f98..86e3ad7 100644
--- a/config_example.ini
+++ b/config_example.ini
@@ -1,5 +1,8 @@
[BDK]
-datadir = ~/.bdk-bitcoin
+datadir = .bdk-bitcoin
network = testnet
wallet = test
descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
+
+# electrum server URL must support network specified above
+electrum = "ssl://electrum.blockstream.info:60002"
diff --git a/src/main.rs b/src/main.rs
index 999bedc..0813337 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,31 +1,39 @@
#[macro_use]
extern crate log;
-extern crate env_logger;
-extern crate simple_server;
extern crate bdk;
-extern crate serde_json;
extern crate bdk_macros;
+extern crate env_logger;
extern crate ini;
+extern crate serde_json;
+extern crate simple_server;
use ini::Ini;
+use std::env;
use std::fs;
use std::path::PathBuf;
-use std::str::FromStr;
use std::str;
-use std::env;
+use std::str::FromStr;
+use bdk::bitcoin::{Address, Network};
use bdk::sled;
-use bdk::{Wallet};
-use bdk::bitcoin::Address;
+use bdk::Wallet;
+use bdk::electrum_client::{Client, ElectrumApi, ListUnspentRes};
use simple_server::{Method, Server, StatusCode};
-use bdk::electrum_client::{Client, ElectrumApi, ListUnspentRes, Error};
-fn prepare_home_dir() -> PathBuf {
+use bdk::blockchain::{
+ log_progress, AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain,
+ ElectrumBlockchainConfig,
+};
+use bdk::sled::Tree;
+use bdk::wallet::AddressIndex::LastUnused;
+use std::sync::{Arc, Mutex};
+
+fn prepare_home_dir(datadir: &str) -> PathBuf {
let mut dir = PathBuf::new();
dir.push(&dirs_next::home_dir().unwrap());
- dir.push(".bdk-bitcoin");
+ dir.push(datadir);
if !dir.exists() {
info!("Creating home directory {}", dir.as_path().display());
@@ -36,64 +44,34 @@ fn prepare_home_dir() -> PathBuf {
dir
}
-fn new_address() -> Result {
- let conf = Ini::load_from_file("config.ini").unwrap();
-
- let section_bdk = conf.section(Some("BDK")).unwrap();
- // let dir = section_bdk.get("datadir").unwrap();
- let descriptor = section_bdk.get("descriptor").unwrap();
- let network = section_bdk.get("network").unwrap();
- let wallet = section_bdk.get("wallet").unwrap();
-
- let database = sled::open(prepare_home_dir().to_str().unwrap()).unwrap();
- let tree = database.open_tree(wallet).unwrap();
-
- let wallet = Wallet::new_offline(
- descriptor,
- None,
- network.parse().unwrap(),
- tree,
- )?;
-
- let addr = wallet.get_new_address()?;
- Ok(addr)
+fn last_unused_address(wallet: &Wallet) -> Result {
+ wallet.sync(log_progress(), None)?;
+ wallet.get_address(LastUnused)
}
-fn client() -> Result {
- let conf = Ini::load_from_file("config.ini").unwrap();
- let section_bdk = conf.section(Some("BDK")).unwrap();
- let network = section_bdk.get("network").unwrap();
- let url = match network.parse().unwrap() {
- bdk::bitcoin::Network::Bitcoin => { "ssl://electrum.blockstream.info:50002" }
- bdk::bitcoin::Network::Testnet => { "ssl://electrum.blockstream.info:60002"}
- _ => { "" }
- };
- Client::new(url)
-}
-
-fn check_address(client: &Client, addr: &str, from_height: Option) -> Result, bdk::Error> {
-
- let monitor_script = Address::from_str(addr)
- .unwrap()
- .script_pubkey();
+fn check_address(
+ client: &Client,
+ addr: &str,
+ from_height: Option,
+) -> Result, bdk::Error> {
+ let monitor_script = Address::from_str(addr).unwrap().script_pubkey();
- let unspents = client
- .script_list_unspent(&monitor_script)
- .unwrap();
+ let unspents = client.script_list_unspent(&monitor_script).unwrap();
- let array = unspents.into_iter()
+ let array = unspents
+ .into_iter()
.filter(|x| x.height >= from_height.unwrap_or(0))
.collect();
Ok(array)
}
-fn html(address: &str) -> Result {
- let client = client().unwrap();
+fn html(electrum: &str, address: &str) -> Result {
+ let client = Client::new(electrum).unwrap();
let list = check_address(&client, &address, Option::from(0)).unwrap();
let status = match list.last() {
- None => { "No onchain tx found yet".to_string() }
+ None => "No onchain tx found yet".to_string(),
Some(unspent) => {
let location = match unspent.height {
0 => "in mempool".to_string(),
@@ -114,8 +92,7 @@ fn html(address: &str) -> Result {
Ok(txt)
}
-fn redirect() -> Result {
- let address = new_address().unwrap();
+fn redirect(address: Address) -> Result {
let link = format!("/bitcoin/?{}", address);
let html = format!("", link);
Ok(html)
@@ -123,34 +100,73 @@ fn redirect() -> Result {
/// Look up our server port number in PORT, for compatibility with Heroku.
fn get_server_port() -> u16 {
- env::var("PORT").ok().and_then(|p| p.parse().ok()).unwrap_or(8080)
+ env::var("PORT")
+ .ok()
+ .and_then(|p| p.parse().ok())
+ .unwrap_or(8080)
}
fn main() {
+ env_logger::init();
+
+ // load config from ini file
+ let conf = Ini::load_from_file("config.ini").unwrap();
+ let section_bdk = conf.section(Some("BDK")).unwrap();
+ let datadir = section_bdk.get("datadir").unwrap();
+ let descriptor = section_bdk.get("descriptor").unwrap();
+ let network = section_bdk.get("network").unwrap();
+ let network = Network::from_str(network).unwrap();
+ let wallet = section_bdk.get("wallet").unwrap();
+ let electrum = section_bdk.get("electrum").unwrap().to_string();
- let server = Server::new(|request, mut response| {
- println!("Request: {} {}", request.method(), request.uri());
- println!("Body: {}", str::from_utf8(request.body()).unwrap());
- println!("Headers:");
+ // setup database
+ let database = sled::open(prepare_home_dir(datadir).to_str().unwrap()).unwrap();
+ let tree = database.open_tree(wallet).unwrap();
+
+ // setup electrum blockchain client
+ let electrum_config = AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
+ url: electrum.clone(),
+ socks5: None,
+ retry: 3,
+ timeout: Some(2),
+ });
+
+ // create wallet shared by all requests
+ let wallet = Wallet::new(
+ descriptor,
+ None,
+ network,
+ tree,
+ AnyBlockchain::from_config(&electrum_config).unwrap(),
+ )
+ .unwrap();
+ wallet.sync(log_progress(), None).unwrap();
+ let wallet_mutex = Arc::new(Mutex::new(wallet));
+
+ let server = Server::new(move |request, mut response| {
+ debug!("Request: {} {}", request.method(), request.uri());
+ debug!("Body: {}", str::from_utf8(request.body()).unwrap());
+ debug!("Headers:");
for (key, value) in request.headers() {
- println!("{}: {}", key, value.to_str().unwrap());
+ debug!("{}: {}", key, value.to_str().unwrap());
}
+ // unlock wallet mutex for this request
+ let wallet = wallet_mutex.lock().unwrap();
+
match (request.method(), request.uri().path()) {
- (&Method::GET, "/bitcoin/api/new") => {
- // curl 127.0.0.1:7878/bitcoin/api/new
- let addr = new_address();
- //info!("addr {}", addr.to_string());
- return match addr {
+ (&Method::GET, "/bitcoin/api/last_unused") => {
+ let address = last_unused_address(&*wallet);
+ return match address {
Ok(a) => {
- info!("new addr {}", a.to_string());
+ info!("last unused addr {}", a.to_string());
let value = serde_json::json!({
- "network": a.network.to_string(),
- "address": a.to_string()
- });
+ "network": a.network.to_string(),
+ "address": a.to_string()
+ });
Ok(response.body(value.to_string().as_bytes().to_vec())?)
- },
- Err(e) => Ok(response.body(e.to_string().as_bytes().to_vec())?)
+ }
+ Err(e) => Ok(response.body(e.to_string().as_bytes().to_vec())?),
};
}
(&Method::GET, "/bitcoin/api/check") => {
@@ -160,13 +176,13 @@ fn main() {
let height = query.next().unwrap();
let h: usize = height.parse::().unwrap();
- let client = client().unwrap();
+ let client = Client::new(electrum.as_str()).unwrap();
let list = check_address(&client, &addr, Option::from(h));
return match list {
Ok(list) => {
- println!("addr {} height {}", addr, h);
+ debug!("addr {} height {}", addr, h);
for item in list.iter() {
- println!("{} {}", item.value, item.height);
+ debug!("{} {}", item.value, item.height);
let _value = serde_json::json!({
"value": item.value,
"height": item.height,
@@ -174,26 +190,23 @@ fn main() {
});
}
Ok(response.body("".as_bytes().to_vec())?)
- },
- Err(e) => Ok(response.body(e.to_string().as_bytes().to_vec())?)
- }
+ }
+ Err(e) => Ok(response.body(e.to_string().as_bytes().to_vec())?),
+ };
}
(&Method::GET, "/bitcoin/") => {
- let address = request.uri().query().unwrap();
- return match html(address) {
- Ok(txt) => {
- Ok(response.body(txt.as_bytes().to_vec())?)
- },
- Err(e) => Ok(response.body(e.to_string().as_bytes().to_vec())?)
- }
+ let address = request.uri().query().unwrap(); // TODO handle missing address
+ return match html(electrum.as_str(), address) {
+ Ok(txt) => Ok(response.body(txt.as_bytes().to_vec())?),
+ Err(e) => Ok(response.body(e.to_string().as_bytes().to_vec())?),
+ };
}
(&Method::GET, "/bitcoin") => {
- return match redirect() {
- Ok(txt) => {
- Ok(response.body(txt.as_bytes().to_vec())?)
- },
- Err(e) => Ok(response.body(e.to_string().as_bytes().to_vec())?)
- }
+ let address = last_unused_address(&*wallet).unwrap();
+ return match redirect(address) {
+ Ok(txt) => Ok(response.body(txt.as_bytes().to_vec())?),
+ Err(e) => Ok(response.body(e.to_string().as_bytes().to_vec())?),
+ };
}
(&Method::GET, "/") => {
let link = "/bitcoin";