lwk_app/
client.rs

1use std::net::SocketAddr;
2
3use lwk_jade::TIMEOUT;
4use lwk_wollet::UnvalidatedRecipient;
5use serde::de::DeserializeOwned;
6use serde::Serialize;
7use serde_json::value::to_raw_value;
8use serde_json::value::RawValue;
9use serde_json::Value;
10
11use crate::error::Error;
12use crate::method::Method;
13use crate::reqwest_transport::ReqwestHttpTransport;
14use crate::{request, response};
15
16pub struct Client {
17    client: jsonrpc::Client,
18}
19
20impl Client {
21    pub fn new(addr: SocketAddr) -> Result<Self, Error> {
22        let url = format!("http://{addr}");
23        let transport = ReqwestHttpTransport::new(url, TIMEOUT);
24        let client = jsonrpc::Client::with_transport(transport);
25        Ok(Self { client })
26    }
27
28    pub(crate) fn make_request<Req, Res>(
29        &self,
30        method: Method,
31        req: Option<Req>,
32    ) -> std::result::Result<Res, Error>
33    where
34        Req: Serialize,
35        Res: DeserializeOwned,
36    {
37        let params = req.map(|req| to_raw_value(&req)).transpose()?;
38        let method = method.to_string();
39        let request = self.client.build_request(&method, params.as_deref());
40        log::trace!("---> {}", serde_json::to_string(&request)?);
41        let response = self.client.send_request(request)?;
42        log::trace!("<--- {}", serde_json::to_string(&response)?);
43        match response.result.as_ref() {
44            Some(result) => Ok(serde_json::from_str(result.get())?),
45            None => match response.error {
46                Some(rpc_err) => Err(Error::RpcError(rpc_err)),
47                None => Err(Error::NeitherResultNorErrorSet),
48            },
49        }
50    }
51
52    pub fn version(&self) -> Result<response::Version, Error> {
53        self.make_request(Method::Version, None::<Box<RawValue>>)
54    }
55
56    pub fn signer_generate(&self) -> Result<response::SignerGenerate, Error> {
57        self.make_request(Method::SignerGenerate, None::<Box<RawValue>>)
58    }
59
60    pub fn signer_load_software(
61        &self,
62        name: String,
63        mnemonic: String,
64        persist: bool,
65    ) -> Result<response::Signer, Error> {
66        let req = request::SignerLoadSoftware {
67            name,
68            mnemonic,
69            persist,
70        };
71        self.make_request(Method::SignerLoadSoftware, Some(req))
72    }
73
74    pub fn signer_load_jade(
75        &self,
76        name: String,
77        id: String,
78        emulator: Option<SocketAddr>,
79    ) -> Result<response::Signer, Error> {
80        let req = request::SignerLoadJade { name, id, emulator };
81        self.make_request(Method::SignerLoadJade, Some(req))
82    }
83
84    pub fn signer_load_external(
85        &self,
86        name: String,
87        fingerprint: String,
88    ) -> Result<response::Signer, Error> {
89        let req = request::SignerLoadExternal { name, fingerprint };
90        self.make_request(Method::SignerLoadExternal, Some(req))
91    }
92
93    pub fn wallet_list(&self) -> Result<response::WalletList, Error> {
94        self.make_request(Method::WalletList, None::<Box<RawValue>>)
95    }
96
97    pub fn wallet_load(&self, descriptor: String, name: String) -> Result<response::Wallet, Error> {
98        let req = request::WalletLoad { descriptor, name };
99        self.make_request(Method::WalletLoad, Some(req))
100    }
101
102    pub fn wallet_unload(&self, name: String) -> Result<response::WalletUnload, Error> {
103        let req = request::WalletUnload { name };
104        self.make_request(Method::WalletUnload, Some(req))
105    }
106
107    pub fn signer_unload(&self, name: String) -> Result<response::SignerUnload, Error> {
108        let req = request::SignerUnload { name };
109        self.make_request(Method::SignerUnload, Some(req))
110    }
111
112    pub fn signer_list(&self) -> Result<response::SignerList, Error> {
113        self.make_request(Method::SignerList, None::<Box<RawValue>>)
114    }
115
116    pub fn wallet_balance(
117        &self,
118        name: String,
119        with_tickers: bool,
120    ) -> Result<response::WalletBalance, Error> {
121        let req = request::WalletBalance { name, with_tickers };
122        self.make_request(Method::WalletBalance, Some(req))
123    }
124
125    pub fn wallet_address(
126        &self,
127        name: String,
128        index: Option<u32>,
129        signer: Option<String>,
130        with_text_qr: bool,
131        with_uri_qr: Option<u8>,
132    ) -> Result<response::WalletAddress, Error> {
133        let req = request::WalletAddress {
134            name,
135            index,
136            signer,
137            with_text_qr,
138            with_uri_qr,
139        };
140        self.make_request(Method::WalletAddress, Some(req))
141    }
142
143    pub fn wallet_send_many(
144        &self,
145        name: String,
146        addressees: Vec<UnvalidatedRecipient>,
147        fee_rate: Option<f32>,
148    ) -> Result<response::Pset, Error> {
149        let req = request::WalletSendMany {
150            addressees: addressees.into_iter().map(unvalidate_addressee).collect(),
151            fee_rate,
152            name,
153        };
154        self.make_request(Method::WalletSendMany, Some(req))
155    }
156
157    pub fn wallet_drain(
158        &self,
159        name: String,
160        address: String,
161        fee_rate: Option<f32>,
162    ) -> Result<response::Pset, Error> {
163        let req = request::WalletDrain {
164            address,
165            fee_rate,
166            name,
167        };
168        self.make_request(Method::WalletDrain, Some(req))
169    }
170
171    pub fn signer_singlesig_descriptor(
172        &self,
173        name: String,
174        descriptor_blinding_key: String,
175        singlesig_kind: String,
176    ) -> Result<response::SignerSinglesigDescriptor, Error> {
177        let req = request::SignerSinglesigDescriptor {
178            name,
179            descriptor_blinding_key,
180            singlesig_kind,
181        };
182        self.make_request(Method::SignerSinglesigDescriptor, Some(req))
183    }
184
185    pub fn wallet_multisig_descriptor(
186        &self,
187        descriptor_blinding_key: String,
188        multisig_kind: String,
189        threshold: u32,
190        keyorigin_xpubs: Vec<String>,
191    ) -> Result<response::WalletMultisigDescriptor, Error> {
192        let req = request::WalletMultisigDescriptor {
193            descriptor_blinding_key,
194            multisig_kind,
195            threshold,
196            keyorigin_xpubs,
197        };
198        self.make_request(Method::WalletMultisigDescriptor, Some(req))
199    }
200
201    pub fn signer_xpub(
202        &self,
203        name: String,
204        xpub_kind: String,
205    ) -> Result<response::SignerXpub, Error> {
206        let req = request::SignerXpub { name, xpub_kind };
207        self.make_request(Method::SignerXpub, Some(req))
208    }
209
210    pub fn signer_register_multisig(
211        &self,
212        name: String,
213        wallet: String,
214    ) -> Result<response::Empty, Error> {
215        let req = request::SignerRegisterMultisig { name, wallet };
216        self.make_request(Method::SignerRegisterMultisig, Some(req))
217    }
218
219    pub fn signer_sign(&self, name: String, pset: String) -> Result<response::Pset, Error> {
220        let req = request::SignerSign { name, pset };
221        self.make_request(Method::SignerSign, Some(req))
222    }
223
224    pub fn wallet_broadcast(
225        &self,
226        name: String,
227        dry_run: bool,
228        pset: String,
229    ) -> Result<response::WalletBroadcast, Error> {
230        let req = request::WalletBroadcast {
231            name,
232            dry_run,
233            pset,
234        };
235        self.make_request(Method::WalletBroadcast, Some(req))
236    }
237
238    pub fn wallet_details(&self, name: String) -> Result<response::WalletDetails, Error> {
239        let req = request::WalletDetails { name };
240        self.make_request(Method::WalletDetails, Some(req))
241    }
242
243    pub fn signer_details(&self, name: String) -> Result<response::SignerDetails, Error> {
244        let req = request::SignerDetails { name };
245        self.make_request(Method::SignerDetails, Some(req))
246    }
247
248    pub fn wallet_combine(
249        &self,
250        name: String,
251        pset: Vec<String>,
252    ) -> Result<response::WalletCombine, Error> {
253        let req = request::WalletCombine { name, pset };
254        self.make_request(Method::WalletCombine, Some(req))
255    }
256
257    pub fn wallet_pset_details(
258        &self,
259        name: String,
260        pset: String,
261        with_tickers: bool,
262    ) -> Result<response::WalletPsetDetails, Error> {
263        let req = request::WalletPsetDetails {
264            name,
265            pset,
266            with_tickers,
267        };
268        self.make_request(Method::WalletPsetDetails, Some(req))
269    }
270
271    pub fn wallet_utxos(&self, name: String) -> Result<response::WalletUtxos, Error> {
272        let req = request::WalletUtxos { name };
273        self.make_request(Method::WalletUtxos, Some(req))
274    }
275
276    pub fn wallet_txs(
277        &self,
278        name: String,
279        with_tickers: bool,
280    ) -> Result<response::WalletTxs, Error> {
281        let req = request::WalletTxs { name, with_tickers };
282        self.make_request(Method::WalletTxs, Some(req))
283    }
284
285    pub fn wallet_tx(
286        &self,
287        name: String,
288        txid: String,
289        from_explorer: bool,
290    ) -> Result<response::WalletTx, Error> {
291        let req = request::WalletTx {
292            name,
293            txid,
294            from_explorer,
295        };
296        self.make_request(Method::WalletTx, Some(req))
297    }
298
299    pub fn wallet_set_tx_memo(
300        &self,
301        name: String,
302        txid: String,
303        memo: String,
304    ) -> Result<response::Empty, Error> {
305        let req = request::WalletSetTxMemo { name, txid, memo };
306        self.make_request(Method::WalletSetTxMemo, Some(req))
307    }
308
309    pub fn wallet_set_addr_memo(
310        &self,
311        name: String,
312        address: String,
313        memo: String,
314    ) -> Result<response::Empty, Error> {
315        let req = request::WalletSetAddrMemo {
316            name,
317            address,
318            memo,
319        };
320        self.make_request(Method::WalletSetAddrMemo, Some(req))
321    }
322
323    pub fn liquidex_make(
324        &self,
325        name: String,
326        txid: String,
327        vout: u32,
328        asset: String,
329        satoshi: u64,
330    ) -> Result<response::Pset, Error> {
331        let req = request::LiquidexMake {
332            name,
333            txid,
334            vout,
335            asset,
336            satoshi,
337        };
338        self.make_request(Method::LiquidexMake, Some(req))
339    }
340
341    pub fn liquidex_take(&self, name: String, proposal: String) -> Result<response::Pset, Error> {
342        let req = request::LiquidexTake { name, proposal };
343        self.make_request(Method::LiquidexTake, Some(req))
344    }
345
346    pub fn liquidex_to_proposal(&self, pset: String) -> Result<response::LiquidexProposal, Error> {
347        let req = request::LiquidexToProposal { pset };
348        self.make_request(Method::LiquidexToProposal, Some(req))
349    }
350
351    #[allow(clippy::too_many_arguments)]
352    pub fn wallet_issue(
353        &self,
354        name: String,
355        satoshi_asset: u64,
356        address_asset: Option<String>,
357        satoshi_token: u64,
358        address_token: Option<String>,
359        contract: Option<String>,
360        fee_rate: Option<f32>,
361    ) -> Result<response::Pset, Error> {
362        let req = request::WalletIssue {
363            name,
364            satoshi_asset,
365            address_asset,
366            satoshi_token,
367            address_token,
368            contract,
369            fee_rate,
370        };
371        self.make_request(Method::WalletIssue, Some(req))
372    }
373
374    pub fn wallet_reissue(
375        &self,
376        name: String,
377        asset: String,
378        satoshi_asset: u64,
379        address_asset: Option<String>,
380        fee_rate: Option<f32>,
381    ) -> Result<response::Pset, Error> {
382        let req = request::WalletReissue {
383            name,
384            asset,
385            satoshi_asset,
386            address_asset,
387            fee_rate,
388        };
389        self.make_request(Method::WalletReissue, Some(req))
390    }
391
392    pub fn wallet_burn(
393        &self,
394        name: String,
395        asset: String,
396        satoshi_asset: u64,
397        fee_rate: Option<f32>,
398    ) -> Result<response::Pset, Error> {
399        let req = request::WalletBurn {
400            name,
401            asset,
402            satoshi_asset,
403            fee_rate,
404        };
405        self.make_request(Method::WalletBurn, Some(req))
406    }
407
408    pub fn asset_contract(
409        &self,
410        domain: String,
411        issuer_pubkey: String,
412        name: String,
413        precision: u8,
414        ticker: String,
415        version: u8,
416    ) -> Result<response::AssetContract, Error> {
417        let req = request::AssetContract {
418            domain,
419            issuer_pubkey,
420            name,
421            precision,
422            ticker,
423            version,
424        };
425        self.make_request(Method::AssetContract, Some(req))
426    }
427
428    pub fn asset_details(&self, asset_id: String) -> Result<response::AssetDetails, Error> {
429        let req = request::AssetDetails { asset_id };
430        self.make_request(Method::AssetDetails, Some(req))
431    }
432
433    pub fn asset_list(&self) -> Result<response::AssetList, Error> {
434        self.make_request(Method::AssetList, None::<Box<RawValue>>)
435    }
436
437    pub fn asset_insert(
438        &self,
439        asset_id: String,
440        issuance_tx: String,
441        contract: String,
442    ) -> Result<response::Empty, Error> {
443        let req = request::AssetInsert {
444            asset_id,
445            issuance_tx,
446            contract,
447        };
448        self.make_request(Method::AssetInsert, Some(req))
449    }
450
451    pub fn asset_remove(&self, asset_id: String) -> Result<response::Empty, Error> {
452        let req = request::AssetRemove { asset_id };
453        self.make_request(Method::AssetRemove, Some(req))
454    }
455
456    pub fn asset_from_explorer(&self, asset_id: String) -> Result<response::Empty, Error> {
457        let req = request::AssetFromExplorer { asset_id };
458        self.make_request(Method::AssetFromExplorer, Some(req))
459    }
460
461    pub fn asset_publish(&self, asset_id: String) -> Result<response::AssetPublish, Error> {
462        let req = request::AssetPublish { asset_id };
463        self.make_request(Method::AssetPublish, Some(req))
464    }
465
466    pub fn amp2_descriptor(&self, name: String) -> Result<response::Amp2Descriptor, Error> {
467        let req = request::Amp2Descriptor { name };
468        self.make_request(Method::Amp2Descriptor, Some(req))
469    }
470
471    pub fn amp2_register(&self, name: String) -> Result<response::Amp2Register, Error> {
472        let req = request::Amp2Register { name };
473        self.make_request(Method::Amp2Register, Some(req))
474    }
475
476    pub fn amp2_cosign(&self, pset: String) -> Result<response::Amp2Cosign, Error> {
477        let req = request::Amp2Cosign { pset };
478        self.make_request(Method::Amp2Cosign, Some(req))
479    }
480
481    pub fn schema(&self, arg: Method, direction: request::Direction) -> Result<Value, Error> {
482        let req = request::Schema {
483            method: arg.to_string(),
484            direction,
485        };
486        self.make_request(Method::Schema, Some(req))
487    }
488
489    pub fn signer_jade_id(&self, emulator: Option<SocketAddr>) -> Result<Value, Error> {
490        let req = request::SignerJadeId { emulator };
491        self.make_request(Method::SignerJadeId, Some(req))
492    }
493
494    pub fn scan(&self) -> Result<Value, Error> {
495        self.make_request(Method::Scan, None::<Box<RawValue>>)
496    }
497
498    pub fn stop(&self) -> Result<Value, Error> {
499        // TODO discriminate only stop error
500        let _: Result<Value, Error> = self.make_request(Method::Stop, None::<Box<RawValue>>);
501        Ok(Value::Null)
502    }
503}
504
505fn unvalidate_addressee(a: lwk_wollet::UnvalidatedRecipient) -> request::UnvalidatedAddressee {
506    request::UnvalidatedAddressee {
507        satoshi: a.satoshi,
508        address: a.address,
509        asset: a.asset,
510    }
511}