1mod image;
2
3use elements::{Address, BlockHash};
4use serde::{de::DeserializeOwned, Deserialize};
5
6#[jsonrpc_client::api]
7pub trait Rpc {
8 async fn getbestblockhash(&self) -> BlockHash;
10 async fn getblockcount(&self) -> u64;
11 async fn generatetoaddress(
14 &self,
15 nblocks: usize,
16 address: &str,
17 maxtries: Option<u64>,
18 ) -> Vec<BlockHash>;
19 async fn sendtoaddress(
20 &self,
21 address: &str,
22 amount: f64,
23 comment: Option<&str>,
24 comment_to: Option<&str>,
25 subtractfeefromamount: Option<bool>,
26 replaceable: Option<bool>,
27 conf_target: Option<u64>,
28 estimate_mode: Option<&str>,
29 avoid_reuse: Option<bool>,
30 assetlabel: Option<&str>,
31 ignoreblindfail: Option<bool>,
32 fee_rate: Option<f64>,
33 verbose: Option<bool>,
34 ) -> SendToAddress;
35 async fn estimatesmartfee(&self, conf_target: u64, estimate_mode: &str) -> EstimateSmartFee;
41 async fn createwallet(
43 &self,
44 wallet_name: &str,
45 disable_private_keys: Option<bool>,
46 blank: Option<bool>,
47 passphrase: Option<&str>,
48 avoid_reuse: Option<bool>,
49 descriptors: Option<bool>,
50 load_on_startup: Option<bool>,
51 external_signer: Option<bool>,
52 ) -> CreateWallet;
53 async fn getaddressinfo(&self, address: &str) -> AddressInfo;
54 async fn getnewaddress(&self, label: Option<&str>, address_type: Option<&str>) -> String;
55 async fn getwalletinfo(&self) -> WalletInfo;
56 }
58
59#[derive(Debug, Deserialize)]
60pub struct CreateWallet {
61 name: String,
62 warning: String,
63}
64
65#[derive(Debug, Deserialize)]
66pub struct EstimateSmartFee {
67 feerate: Option<f64>,
68 errors: Option<Vec<String>>,
69 blocks: u64,
70}
71
72#[derive(Debug, Deserialize)]
73pub struct AddressInfo {
74 address: String,
75 #[serde(rename = "scriptPubKey")]
76 script_pubkey: String,
77 ismine: bool,
78 iswatchonly: bool,
79 solvable: bool,
80 desc: Option<String>,
81 parent_desc: Option<String>,
82 isscript: bool,
83 ischange: bool,
84 iswitness: bool,
85 witness_version: Option<u64>,
86 witness_program: Option<String>,
87 script: Option<String>,
88 hex: Option<String>,
89 pubkeys: Option<Vec<String>>,
90 sigsrequired: Option<u64>,
91 pubkey: Option<String>,
92 iscompressed: Option<bool>,
94 confidential_key: String,
95 unconfidential: String,
96 confidential: String,
97 timestamp: u64,
98 hdkeypath: Option<String>,
99 hdseedid: Option<String>,
100 hdmasterfingerprint: Option<String>,
101 labels: Vec<String>,
102}
103
104#[derive(Debug, Deserialize)]
105pub struct SendToAddress {
106 txid: String,
107 fee_reason: String,
108}
109
110#[derive(Debug, Deserialize)]
111pub struct WalletInfo {
112 walletname: String,
113 walletversion: u64,
114 format: String,
115 balance: f64,
116 unconfirmed_balance: f64,
117 immature_balance: f64,
118 txcount: u64,
119 keypoololdest: u64,
120 keypoolsize: u64,
121 keypoolsize_hd_internal: u64,
122 unlocked_until: Option<u64>,
123 paytxfee: f64,
124 hdseedid: Option<String>,
125 private_keys_enabled: bool,
126 avoid_reuse: bool,
127 descriptors: bool,
129}
130
131#[jsonrpc_client::implement(Rpc)]
132pub struct Client {
133 inner: reqwest::Client,
134 base_url: reqwest::Url,
135}
136
137impl Client {
138 pub fn new(base_url: String) -> Result<Self, url::ParseError> {
139 Ok(Self {
140 inner: reqwest::Client::new(),
141 base_url: base_url.parse()?,
142 })
143 }
144}
145
146#[cfg(test)]
147mod test {
148 use testcontainers::clients;
149
150 use super::*;
151
152 #[tokio::test]
153 async fn simple() {
154 let docker = clients::Cli::default();
155 let container = docker.run(image::Elementsd::default());
156 let port = container.get_host_port_ipv4(18444);
157
158 let client = Client::new(format!("http://user:pass@127.0.0.1:{}", port)).unwrap();
159
160 let wallet_name = "asdf".to_string();
161 let wallet = client
162 .createwallet(&wallet_name, None, None, None, None, None, None, None)
163 .await
164 .unwrap();
165 dbg!(&wallet);
166 assert_eq!(wallet.name, wallet_name);
167 assert!(wallet.warning.is_empty());
168
169 let address = client.getnewaddress(None, None).await.unwrap();
170 dbg!(address);
171 let address = client.getnewaddress(None, Some("legacy")).await.unwrap();
172 dbg!(address);
173 let address = client
174 .getnewaddress(None, Some("p2sh-segwit"))
175 .await
176 .unwrap();
177 dbg!(address);
178 let address = client.getnewaddress(None, Some("bech32")).await.unwrap();
179 dbg!(&address);
180
181 let hash = client.getbestblockhash().await.unwrap();
182 dbg!(hash);
183
184 let count = client.getblockcount().await.unwrap();
185 dbg!(count);
186 assert_eq!(count, 0);
187
188 let nblocks = 501;
189 let hashes = client
190 .generatetoaddress(nblocks, &address, None)
191 .await
192 .unwrap();
193 dbg!(&hashes);
194 assert_eq!(hashes.len(), nblocks);
195
196 let info = client.getwalletinfo().await.unwrap();
197 dbg!(&info);
198
199 let sent = client
200 .sendtoaddress(
201 &address,
202 0.1,
203 None,
204 None,
205 None,
206 None,
207 None,
208 None,
209 None,
210 None,
211 None,
212 None,
213 Some(true),
214 )
215 .await
216 .unwrap();
217 dbg!(&sent);
218
219 let info = client.getaddressinfo(&address).await.unwrap();
220 dbg!(&info);
221
222 let temp = client.estimatesmartfee(1, "conservative").await.unwrap();
223 dbg!(&temp);
224 }
225}