algo_rust_sdk/
kmd.rs

1use serde::Deserialize;
2
3use crate::crypto::MultisigSignature;
4use crate::kmd::requests::*;
5use crate::kmd::responses::*;
6use crate::transaction::Transaction;
7use crate::{Ed25519PublicKey, Error, MasterDerivationKey};
8
9const KMD_TOKEN_HEADER: &str = "X-KMD-API-Token";
10
11/// Client for interacting with the key management daemon
12pub struct KmdClient {
13    address: String,
14    token: String,
15    http_client: reqwest::Client,
16}
17
18impl KmdClient {
19    pub fn new(address: &str, token: &str) -> KmdClient {
20        KmdClient {
21            address: address.to_string(),
22            token: token.to_string(),
23            http_client: reqwest::Client::new(),
24        }
25    }
26
27    /// Retrieves the current version
28    pub fn versions(&self) -> Result<VersionsResponse, Error> {
29        self.do_v1_request(VersionsRequest)
30    }
31
32    /// List all of the wallets that kmd is aware of
33    pub fn list_wallets(&self) -> Result<ListWalletsResponse, Error> {
34        self.do_v1_request(ListWalletsRequest)
35    }
36
37    /// Creates a wallet
38    pub fn create_wallet(
39        &self,
40        wallet_name: &str,
41        wallet_password: &str,
42        wallet_driver_name: &str,
43        master_derivation_key: MasterDerivationKey,
44    ) -> Result<CreateWalletResponse, Error> {
45        let req = CreateWalletRequest {
46            master_derivation_key,
47            wallet_driver_name: wallet_driver_name.to_string(),
48            wallet_name: wallet_name.to_string(),
49            wallet_password: wallet_password.to_string(),
50        };
51        self.do_v1_request(req)
52    }
53
54    /// Unlock the wallet and return a wallet token that can be used for subsequent operations
55    ///
56    /// These tokens expire periodically and must be renewed.
57    /// You can see how much time remains until expiration with [get_wallet](KmdClient::get_wallet) and renew it with [renew_wallet_handle](KmdClient::renew_wallet_handle).
58    /// When you're done, you can invalidate the token with [release_wallet_handle](KmdClient::release_wallet_handle)
59    pub fn init_wallet_handle(
60        &self,
61        wallet_id: &str,
62        wallet_password: &str,
63    ) -> Result<InitWalletHandleResponse, Error> {
64        let req = InitWalletHandleRequest {
65            wallet_id: wallet_id.to_string(),
66            wallet_password: wallet_password.to_string(),
67        };
68        self.do_v1_request(req)
69    }
70
71    /// Release a wallet handle token
72    pub fn release_wallet_handle(
73        &self,
74        wallet_handle: &str,
75    ) -> Result<ReleaseWalletHandleResponse, Error> {
76        let req = ReleaseWalletHandleRequest {
77            wallet_handle_token: wallet_handle.to_string(),
78        };
79        self.do_v1_request(req)
80    }
81
82    /// Renew a wallet handle token
83    pub fn renew_wallet_handle(
84        &self,
85        wallet_handle: &str,
86    ) -> Result<RenewWalletHandleResponse, Error> {
87        let req = RenewWalletHandleRequest {
88            wallet_handle_token: wallet_handle.to_string(),
89        };
90        self.do_v1_request(req)
91    }
92
93    /// Rename a wallet
94    pub fn rename_wallet(
95        &self,
96        wallet_id: &str,
97        wallet_password: &str,
98        new_name: &str,
99    ) -> Result<RenameWalletResponse, Error> {
100        let req = RenameWalletRequest {
101            wallet_id: wallet_id.to_string(),
102            wallet_password: wallet_password.to_string(),
103            wallet_name: new_name.to_string(),
104        };
105        self.do_v1_request(req)
106    }
107
108    /// Get wallet info
109    pub fn get_wallet(&self, wallet_handle: &str) -> Result<GetWalletResponse, Error> {
110        let req = GetWalletRequest {
111            wallet_handle_token: wallet_handle.to_string(),
112        };
113        self.do_v1_request(req)
114    }
115
116    /// Export the master derivation key from a wallet
117    pub fn export_master_derivation_key(
118        &self,
119        wallet_handle: &str,
120        wallet_password: &str,
121    ) -> Result<ExportMasterDerivationKeyResponse, Error> {
122        let req = ExportMasterDerivationKeyRequest {
123            wallet_handle_token: wallet_handle.to_string(),
124            wallet_password: wallet_password.to_string(),
125        };
126        self.do_v1_request(req)
127    }
128
129    /// Import an externally generated key into the wallet
130    pub fn import_key(
131        &self,
132        wallet_handle: &str,
133        private_key: [u8; 32],
134    ) -> Result<ImportKeyResponse, Error> {
135        let req = ImportKeyRequest {
136            wallet_handle_token: wallet_handle.to_string(),
137            private_key,
138        };
139        self.do_v1_request(req)
140    }
141
142    /// Export the Ed25519 seed associated with the passed address
143    ///
144    /// Note the first 32 bytes of the returned value is the seed, the second 32 bytes is the public key
145    pub fn export_key(
146        &self,
147        wallet_handle: &str,
148        wallet_password: &str,
149        address: &str,
150    ) -> Result<ExportKeyResponse, Error> {
151        let req = ExportKeyRequest {
152            wallet_handle_token: wallet_handle.to_string(),
153            address: address.to_string(),
154            wallet_password: wallet_password.to_string(),
155        };
156        self.do_v1_request(req)
157    }
158
159    /// Generates a key and adds it to the wallet, returning the public key
160    pub fn generate_key(&self, wallet_handle: &str) -> Result<GenerateKeyResponse, Error> {
161        let req = GenerateKeyRequest {
162            wallet_handle_token: wallet_handle.to_string(),
163            display_mnemonic: false,
164        };
165        self.do_v1_request(req)
166    }
167
168    /// Deletes the key from the wallet
169    pub fn delete_key(
170        &self,
171        wallet_handle: &str,
172        wallet_password: &str,
173        address: &str,
174    ) -> Result<DeleteKeyResponse, Error> {
175        let req = DeleteKeyRequest {
176            wallet_handle_token: wallet_handle.to_string(),
177            wallet_password: wallet_password.to_string(),
178            address: address.to_string(),
179        };
180        self.do_v1_request(req)
181    }
182
183    /// List all of the public keys in the wallet
184    pub fn list_keys(&self, wallet_handle: &str) -> Result<ListKeysResponse, Error> {
185        let req = ListKeysRequest {
186            wallet_handle_token: wallet_handle.to_string(),
187        };
188        self.do_v1_request(req)
189    }
190
191    /// Sign a transaction
192    pub fn sign_transaction(
193        &self,
194        wallet_handle: &str,
195        wallet_password: &str,
196        transaction: &Transaction,
197    ) -> Result<SignTransactionResponse, Error> {
198        let transaction_bytes = rmp_serde::to_vec_named(transaction)?;
199        let req = SignTransactionRequest {
200            wallet_handle_token: wallet_handle.to_string(),
201            transaction: transaction_bytes,
202            wallet_password: wallet_password.to_string(),
203        };
204        self.do_v1_request(req)
205    }
206
207    /// Lists all of the multisig accounts whose preimages this wallet stores
208    pub fn list_multisig(&self, wallet_handle: &str) -> Result<ListMultisigResponse, Error> {
209        let req = ListMultisigRequest {
210            wallet_handle_token: wallet_handle.to_string(),
211        };
212        self.do_v1_request(req)
213    }
214
215    /// Import a multisig account
216    pub fn import_multisig(
217        &self,
218        wallet_handle: &str,
219        version: u8,
220        threshold: u8,
221        pks: &[Ed25519PublicKey],
222    ) -> Result<ImportMultisigResponse, Error> {
223        let req = ImportMultisigRequest {
224            wallet_handle_token: wallet_handle.to_string(),
225            multisig_version: version,
226            threshold,
227            pks: pks.to_vec(),
228        };
229        self.do_v1_request(req)
230    }
231
232    /// Export multisig address metadata
233    pub fn export_multisig(
234        &self,
235        wallet_handle: &str,
236        address: &str,
237    ) -> Result<ExportMultisigResponse, Error> {
238        let req = ExportMultisigRequest {
239            wallet_handle_token: wallet_handle.to_string(),
240            address: address.to_string(),
241        };
242        self.do_v1_request(req)
243    }
244
245    /// Delete a multisig from the wallet
246    pub fn delete_multisig(
247        &self,
248        wallet_handle: &str,
249        wallet_password: &str,
250        address: &str,
251    ) -> Result<DeleteMultisigResponse, Error> {
252        let req = DeleteMultisigRequest {
253            wallet_handle_token: wallet_handle.to_string(),
254            wallet_password: wallet_password.to_string(),
255            address: address.to_string(),
256        };
257        self.do_v1_request(req)
258    }
259
260    /// Start a multisig signature or add a signature to a partially completed multisig signature
261    pub fn sign_multisig_transaction(
262        &self,
263        wallet_handle: &str,
264        wallet_password: &str,
265        transaction: &Transaction,
266        public_key: Ed25519PublicKey,
267        partial_multisig: Option<MultisigSignature>,
268    ) -> Result<SignMultisigTransactionResponse, Error> {
269        let transaction_bytes = rmp_serde::to_vec_named(transaction)?;
270        let req = SignMultisigTransactionRequest {
271            wallet_handle_token: wallet_handle.to_string(),
272            transaction: transaction_bytes,
273            public_key,
274            partial_multisig,
275            wallet_password: wallet_password.to_string(),
276        };
277        self.do_v1_request(req)
278    }
279
280    fn do_v1_request<R>(&self, req: R) -> Result<R::Response, Error>
281    where
282        R: APIV1Request,
283    {
284        let response = self
285            .http_client
286            .request(R::METHOD, &format!("{}/{}", self.address, R::PATH))
287            .header(KMD_TOKEN_HEADER, &self.token)
288            .header("Accept", "application/json")
289            .json(&req)
290            .send()?
291            .text()?;
292        if let Ok(envelope) = serde_json::from_str::<APIV1ResponseEnvelope>(&response) {
293            if envelope.error {
294                return Err(Error::Api(envelope.message));
295            }
296        }
297        Ok(serde_json::from_str(&response)?)
298    }
299}
300
301pub mod requests {
302    use reqwest::Method;
303    use serde::de::DeserializeOwned;
304    use serde::Serialize;
305
306    use crate::crypto::MultisigSignature;
307    use crate::kmd::responses::*;
308    use crate::util::serialize_bytes;
309    use crate::{Ed25519PublicKey, MasterDerivationKey};
310
311    pub trait APIV1Request: Serialize {
312        type Response: DeserializeOwned;
313        const PATH: &'static str;
314        const METHOD: Method;
315    }
316
317    /// VersionsRequest is the request for `GET /versions`
318    #[derive(Serialize)]
319    pub struct VersionsRequest;
320
321    /// ListWalletsRequest is the request for `GET /v1/wallets`
322    #[derive(Serialize)]
323    pub struct ListWalletsRequest;
324
325    #[derive(Serialize)]
326    pub struct CreateWalletRequest {
327        pub master_derivation_key: MasterDerivationKey,
328        pub wallet_driver_name: String,
329        pub wallet_name: String,
330        pub wallet_password: String,
331    }
332
333    /// InitWalletHandleRequest is the request for `POST /v1/wallet/init`
334    #[derive(Serialize)]
335    pub struct InitWalletHandleRequest {
336        pub wallet_id: String,
337        pub wallet_password: String,
338    }
339
340    /// ReleaseWalletHandleRequest is the request for `POST /v1/wallet/release`
341    #[derive(Serialize)]
342    pub struct ReleaseWalletHandleRequest {
343        pub wallet_handle_token: String,
344    }
345
346    /// RenewWalletHandleRequest is the request for `POST /v1/wallet/renew`
347    #[derive(Serialize)]
348    pub struct RenewWalletHandleRequest {
349        pub wallet_handle_token: String,
350    }
351
352    /// RenameWalletRequest is the request for `POST /v1/wallet/rename`
353    #[derive(Serialize)]
354    pub struct RenameWalletRequest {
355        pub wallet_id: String,
356        pub wallet_password: String,
357        pub wallet_name: String,
358    }
359
360    /// GetWalletRequest is the request for `POST /v1/wallet/info`
361    #[derive(Serialize)]
362    pub struct GetWalletRequest {
363        pub wallet_handle_token: String,
364    }
365
366    /// ExportMasterDerivationKeyRequest is the request for `POST /v1/master-key/export`
367    #[derive(Serialize)]
368    pub struct ExportMasterDerivationKeyRequest {
369        pub wallet_handle_token: String,
370        pub wallet_password: String,
371    }
372
373    /// ImportKeyRequest is the request for `POST /v1/key/import`
374    #[derive(Serialize)]
375    pub struct ImportKeyRequest {
376        pub wallet_handle_token: String,
377        #[serde(serialize_with = "serialize_bytes")]
378        pub private_key: [u8; 32],
379    }
380
381    /// ExportKeyRequest is the request for `POST /v1/key/export`
382    #[derive(Serialize)]
383    pub struct ExportKeyRequest {
384        pub wallet_handle_token: String,
385        pub address: String,
386        pub wallet_password: String,
387    }
388
389    /// GenerateKeyRequest is the request for `POST /v1/key`
390    #[derive(Serialize)]
391    pub struct GenerateKeyRequest {
392        pub wallet_handle_token: String,
393        pub display_mnemonic: bool,
394    }
395
396    /// DeleteKeyRequest is the request for `DELETE /v1/key`
397    #[derive(Serialize)]
398    pub struct DeleteKeyRequest {
399        pub wallet_handle_token: String,
400        pub address: String,
401        pub wallet_password: String,
402    }
403
404    /// ListKeysRequest is the request for `POST /v1/key/list`
405    #[derive(Serialize)]
406    pub struct ListKeysRequest {
407        pub wallet_handle_token: String,
408    }
409
410    /// SignTransactionRequest is the request for `POST /v1/transaction/sign`
411    #[derive(Serialize)]
412    pub struct SignTransactionRequest {
413        pub wallet_handle_token: String,
414        #[serde(serialize_with = "serialize_bytes")]
415        pub transaction: Vec<u8>,
416        pub wallet_password: String,
417    }
418
419    /// ListMultisigRequest is the request for `POST /v1/multisig/list`
420    #[derive(Serialize)]
421    pub struct ListMultisigRequest {
422        pub wallet_handle_token: String,
423    }
424
425    /// ImportMultisigRequest is the request for `POST /v1/multisig/import`
426    #[derive(Serialize)]
427    pub struct ImportMultisigRequest {
428        pub wallet_handle_token: String,
429        pub multisig_version: u8,
430        pub threshold: u8,
431        pub pks: Vec<Ed25519PublicKey>,
432    }
433
434    /// ExportMultisigRequest is the request for `POST /v1/multisig/export`
435    #[derive(Serialize)]
436    pub struct ExportMultisigRequest {
437        pub wallet_handle_token: String,
438        pub address: String,
439    }
440
441    /// DeleteMultisigRequest is the request for `DELETE /v1/multisig`
442    #[derive(Serialize)]
443    pub struct DeleteMultisigRequest {
444        pub wallet_handle_token: String,
445        pub address: String,
446        pub wallet_password: String,
447    }
448
449    /// SignMultisigTransactionRequest is the request for `POST /v1/multisig/sign`
450    #[derive(Serialize)]
451    pub struct SignMultisigTransactionRequest {
452        pub wallet_handle_token: String,
453        #[serde(serialize_with = "serialize_bytes")]
454        pub transaction: Vec<u8>,
455        pub public_key: Ed25519PublicKey,
456        pub partial_multisig: Option<MultisigSignature>,
457        pub wallet_password: String,
458    }
459
460    impl APIV1Request for VersionsRequest {
461        type Response = VersionsResponse;
462        const PATH: &'static str = "versions";
463        const METHOD: Method = Method::GET;
464    }
465
466    impl APIV1Request for ListWalletsRequest {
467        type Response = ListWalletsResponse;
468        const PATH: &'static str = "v1/wallets";
469        const METHOD: Method = Method::GET;
470    }
471
472    impl APIV1Request for CreateWalletRequest {
473        type Response = CreateWalletResponse;
474        const PATH: &'static str = "v1/wallet";
475        const METHOD: Method = Method::POST;
476    }
477
478    impl APIV1Request for InitWalletHandleRequest {
479        type Response = InitWalletHandleResponse;
480        const PATH: &'static str = "v1/wallet/init";
481        const METHOD: Method = Method::POST;
482    }
483
484    impl APIV1Request for ReleaseWalletHandleRequest {
485        type Response = ReleaseWalletHandleResponse;
486        const PATH: &'static str = "v1/wallet/release";
487        const METHOD: Method = Method::POST;
488    }
489
490    impl APIV1Request for RenewWalletHandleRequest {
491        type Response = RenewWalletHandleResponse;
492        const PATH: &'static str = "v1/wallet/renew";
493        const METHOD: Method = Method::POST;
494    }
495
496    impl APIV1Request for RenameWalletRequest {
497        type Response = RenameWalletResponse;
498        const PATH: &'static str = "v1/wallet/rename";
499        const METHOD: Method = Method::POST;
500    }
501
502    impl APIV1Request for GetWalletRequest {
503        type Response = GetWalletResponse;
504        const PATH: &'static str = "v1/wallet/info";
505        const METHOD: Method = Method::POST;
506    }
507
508    impl APIV1Request for ExportMasterDerivationKeyRequest {
509        type Response = ExportMasterDerivationKeyResponse;
510        const PATH: &'static str = "v1/master-key/export";
511        const METHOD: Method = Method::POST;
512    }
513
514    impl APIV1Request for ImportKeyRequest {
515        type Response = ImportKeyResponse;
516        const PATH: &'static str = "v1/key/import";
517        const METHOD: Method = Method::POST;
518    }
519
520    impl APIV1Request for ExportKeyRequest {
521        type Response = ExportKeyResponse;
522        const PATH: &'static str = "v1/key/export";
523        const METHOD: Method = Method::POST;
524    }
525
526    impl APIV1Request for GenerateKeyRequest {
527        type Response = GenerateKeyResponse;
528        const PATH: &'static str = "v1/key";
529        const METHOD: Method = Method::POST;
530    }
531
532    impl APIV1Request for DeleteKeyRequest {
533        type Response = DeleteKeyResponse;
534        const PATH: &'static str = "v1/key";
535        const METHOD: Method = Method::DELETE;
536    }
537
538    impl APIV1Request for ListKeysRequest {
539        type Response = ListKeysResponse;
540        const PATH: &'static str = "v1/key/list";
541        const METHOD: Method = Method::POST;
542    }
543
544    impl APIV1Request for SignTransactionRequest {
545        type Response = SignTransactionResponse;
546        const PATH: &'static str = "v1/transaction/sign";
547        const METHOD: Method = Method::POST;
548    }
549
550    impl APIV1Request for ListMultisigRequest {
551        type Response = ListMultisigResponse;
552        const PATH: &'static str = "v1/multisig/list";
553        const METHOD: Method = Method::POST;
554    }
555
556    impl APIV1Request for ImportMultisigRequest {
557        type Response = ImportMultisigResponse;
558        const PATH: &'static str = "v1/multisig/import";
559        const METHOD: Method = Method::POST;
560    }
561
562    impl APIV1Request for ExportMultisigRequest {
563        type Response = ExportMultisigResponse;
564        const PATH: &'static str = "v1/multisig/export";
565        const METHOD: Method = Method::POST;
566    }
567
568    impl APIV1Request for DeleteMultisigRequest {
569        type Response = DeleteMultisigResponse;
570        const PATH: &'static str = "v1/multisig";
571        const METHOD: Method = Method::DELETE;
572    }
573
574    impl APIV1Request for SignMultisigTransactionRequest {
575        type Response = SignMultisigTransactionResponse;
576        const PATH: &'static str = "v1/multisig/sign";
577        const METHOD: Method = Method::POST;
578    }
579}
580
581pub mod responses {
582    use data_encoding::BASE64;
583    use serde::{Deserialize, Deserializer};
584
585    use crate::util::{deserialize_bytes, deserialize_bytes64, deserialize_mdk};
586
587    use crate::{Ed25519PublicKey, MasterDerivationKey};
588
589    use crate::kmd::{APIV1Wallet, APIV1WalletHandle};
590
591    #[derive(Debug, Deserialize)]
592    pub struct APIV1ResponseEnvelope {
593        pub error: bool,
594        pub message: String,
595    }
596
597    /// VersionsResponse is the response to `GET /versions`
598    #[derive(Debug, Deserialize)]
599    pub struct VersionsResponse {
600        #[serde(default)]
601        pub versions: Vec<String>,
602    }
603
604    /// ListWalletsResponse is the response to `GET /v1/wallets`
605    #[derive(Debug, Deserialize)]
606    pub struct ListWalletsResponse {
607        #[serde(default)]
608        pub wallets: Vec<APIV1Wallet>,
609    }
610
611    #[derive(Debug, Deserialize)]
612    pub struct CreateWalletResponse {
613        pub wallet: APIV1Wallet,
614    }
615
616    /// InitWalletHandleResponse is the response to `POST /v1/wallet/init`
617    #[derive(Debug, Deserialize)]
618    pub struct InitWalletHandleResponse {
619        pub wallet_handle_token: String,
620    }
621
622    /// ReleaseWalletHandleResponse is the response to `POST /v1/wallet/release`
623    #[derive(Debug, Deserialize)]
624    pub struct ReleaseWalletHandleResponse {}
625
626    /// RenewWalletHandleResponse is the response to `POST /v1/wallet/renew`
627    #[derive(Debug, Deserialize)]
628    pub struct RenewWalletHandleResponse {
629        pub wallet_handle: APIV1WalletHandle,
630    }
631
632    /// RenameWalletResponse is the response to `POST /v1/wallet/rename`
633    #[derive(Debug, Deserialize)]
634    pub struct RenameWalletResponse {
635        pub wallet: APIV1Wallet,
636    }
637
638    /// GetWalletResponse is the response to `POST /v1/wallet/info`
639    #[derive(Debug, Deserialize)]
640    pub struct GetWalletResponse {
641        pub wallet_handle: APIV1WalletHandle,
642    }
643
644    /// ExportMasterDerivationKeyResponse is the response to `POST /v1/master-key/export`
645    #[derive(Debug, Deserialize)]
646    pub struct ExportMasterDerivationKeyResponse {
647        #[serde(deserialize_with = "deserialize_mdk")]
648        pub master_derivation_key: MasterDerivationKey,
649    }
650
651    /// ImportKeyResponse is the response to `POST /v1/key/import`
652    #[derive(Debug, Deserialize)]
653    pub struct ImportKeyResponse {
654        pub address: String,
655    }
656
657    /// ExportKeyResponse is the response to `POST /v1/key/export`
658    #[derive(Deserialize)]
659    pub struct ExportKeyResponse {
660        #[serde(deserialize_with = "deserialize_bytes64")]
661        pub private_key: [u8; 64],
662    }
663
664    /// GenerateKeyResponse is the response to `POST /v1/key`
665    #[derive(Debug, Deserialize)]
666    pub struct GenerateKeyResponse {
667        pub address: String,
668    }
669
670    /// DeleteKeyResponse is the response to `DELETE /v1/key`
671    #[derive(Debug, Deserialize)]
672    pub struct DeleteKeyResponse {}
673
674    /// ListKeysResponse is the response to `POST /v1/key/list`
675    #[derive(Debug, Deserialize)]
676    pub struct ListKeysResponse {
677        pub addresses: Vec<String>,
678    }
679
680    /// SignTransactionResponse is the response to `POST /v1/transaction/sign`
681    #[derive(Debug, Deserialize)]
682    pub struct SignTransactionResponse {
683        #[serde(deserialize_with = "deserialize_bytes")]
684        pub signed_transaction: Vec<u8>,
685    }
686
687    /// ListMultisigResponse is the response to `POST /v1/multisig/list`
688    #[derive(Debug, Deserialize)]
689    pub struct ListMultisigResponse {
690        #[serde(default)]
691        pub addresses: Vec<String>,
692    }
693
694    /// ImportMultisigResponse is the response to `POST /v1/multisig/import`
695    #[derive(Debug, Deserialize)]
696    pub struct ImportMultisigResponse {
697        pub address: String,
698    }
699
700    /// ExportMultisigResponse is the response to `POST /v1/multisig/export`
701    #[derive(Debug, Deserialize)]
702    pub struct ExportMultisigResponse {
703        pub multisig_version: u8,
704        pub threshold: u8,
705        #[serde(deserialize_with = "deserialize_public_keys")]
706        pub pks: Vec<Ed25519PublicKey>,
707    }
708
709    /// DeleteMultisigResponse is the response to POST /v1/multisig/delete`
710    #[derive(Debug, Deserialize)]
711    pub struct DeleteMultisigResponse {}
712
713    /// SignMultisigTransactionResponse is the response to `POST /v1/multisig/sign`
714    #[derive(Debug, Deserialize)]
715    pub struct SignMultisigTransactionResponse {
716        #[serde(deserialize_with = "deserialize_bytes")]
717        pub multisig: Vec<u8>,
718    }
719
720    fn deserialize_public_keys<'de, D>(deserializer: D) -> Result<Vec<Ed25519PublicKey>, D::Error>
721    where
722        D: Deserializer<'de>,
723    {
724        use serde::de::Error;
725        <Vec<String>>::deserialize(deserializer)?
726            .iter()
727            .map(|string| {
728                let mut decoded = [0; 32];
729                let bytes = BASE64.decode(string.as_bytes()).map_err(D::Error::custom)?;
730                decoded.copy_from_slice(&bytes);
731                Ok(Ed25519PublicKey(decoded))
732            })
733            .collect()
734    }
735}
736
737#[derive(Debug, Deserialize)]
738pub struct APIV1Wallet {
739    pub id: String,
740    pub name: String,
741    pub driver_name: String,
742    pub driver_version: u32,
743    pub mnemonic_ux: bool,
744    pub supported_txs: Vec<String>,
745}
746
747#[derive(Debug, Deserialize)]
748pub struct APIV1WalletHandle {
749    pub wallet: APIV1Wallet,
750    pub expires_seconds: i64,
751}