Skip to main content

forest/rpc/methods/
wallet.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::any::Any;
5
6use crate::key_management::{Key, KeyInfo};
7use crate::message::SignedMessage;
8use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError};
9use crate::shim::{
10    address::Address,
11    crypto::{Signature, SignatureType},
12    econ::TokenAmount,
13    message::Message,
14    state_tree::StateTree,
15};
16use enumflags2::BitFlags;
17use fvm_ipld_blockstore::Blockstore;
18
19pub enum WalletBalance {}
20impl RpcMethod<1> for WalletBalance {
21    const NAME: &'static str = "Filecoin.WalletBalance";
22    const PARAM_NAMES: [&'static str; 1] = ["address"];
23    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
24    const PERMISSION: Permission = Permission::Read;
25    const DESCRIPTION: Option<&'static str> = Some("Returns the balance of a wallet.");
26
27    type Params = (Address,);
28    type Ok = TokenAmount;
29
30    async fn handle(
31        ctx: Ctx<impl Blockstore>,
32        (address,): Self::Params,
33        _: &http::Extensions,
34    ) -> Result<Self::Ok, ServerError> {
35        let heaviest_ts = ctx.chain_store().heaviest_tipset();
36        let cid = heaviest_ts.parent_state();
37
38        Ok(StateTree::new_from_root(ctx.store_owned(), cid)?
39            .get_actor(&address)?
40            .map(|it| it.balance.clone().into())
41            .unwrap_or_default())
42    }
43}
44
45pub enum WalletDefaultAddress {}
46impl RpcMethod<0> for WalletDefaultAddress {
47    const NAME: &'static str = "Filecoin.WalletDefaultAddress";
48    const PARAM_NAMES: [&'static str; 0] = [];
49    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
50    const PERMISSION: Permission = Permission::Read;
51
52    type Params = ();
53    type Ok = Option<Address>;
54
55    async fn handle(
56        ctx: Ctx<impl Blockstore>,
57        (): Self::Params,
58        _: &http::Extensions,
59    ) -> Result<Self::Ok, ServerError> {
60        let keystore = ctx.keystore.read();
61        Ok(crate::key_management::get_default(&keystore)?)
62    }
63}
64
65pub enum WalletExport {}
66impl RpcMethod<1> for WalletExport {
67    const NAME: &'static str = "Filecoin.WalletExport";
68    const PARAM_NAMES: [&'static str; 1] = ["address"];
69    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
70    const PERMISSION: Permission = Permission::Admin;
71
72    type Params = (Address,);
73    type Ok = KeyInfo;
74
75    async fn handle(
76        ctx: Ctx<impl Blockstore>,
77        (address,): Self::Params,
78        _: &http::Extensions,
79    ) -> Result<Self::Ok, ServerError> {
80        let keystore = ctx.keystore.read();
81        let key_info = crate::key_management::export_key_info(&address, &keystore)?;
82        Ok(key_info)
83    }
84}
85
86pub enum WalletHas {}
87impl RpcMethod<1> for WalletHas {
88    const NAME: &'static str = "Filecoin.WalletHas";
89    const PARAM_NAMES: [&'static str; 1] = ["address"];
90    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
91    const PERMISSION: Permission = Permission::Write;
92    const DESCRIPTION: Option<&'static str> =
93        Some("Indicates whether the given address exists in the wallet.");
94
95    type Params = (Address,);
96    type Ok = bool;
97
98    async fn handle(
99        ctx: Ctx<impl Blockstore>,
100        (address,): Self::Params,
101        _: &http::Extensions,
102    ) -> Result<Self::Ok, ServerError> {
103        let keystore = ctx.keystore.read();
104        Ok(crate::key_management::find_key(&address, &keystore).is_ok())
105    }
106}
107
108pub enum WalletImport {}
109impl RpcMethod<1> for WalletImport {
110    const NAME: &'static str = "Filecoin.WalletImport";
111    const PARAM_NAMES: [&'static str; 1] = ["key"];
112    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
113    const PERMISSION: Permission = Permission::Admin;
114
115    type Params = (KeyInfo,);
116    type Ok = Address;
117
118    async fn handle(
119        ctx: Ctx<impl Blockstore>,
120        (key_info,): Self::Params,
121        _: &http::Extensions,
122    ) -> Result<Self::Ok, ServerError> {
123        let key = Key::try_from(key_info)?;
124
125        let addr = format!("wallet-{}", key.address);
126
127        let mut keystore = ctx.keystore.write();
128        keystore.put(&addr, key.key_info)?;
129        Ok(key.address)
130    }
131}
132
133pub enum WalletList {}
134impl RpcMethod<0> for WalletList {
135    const NAME: &'static str = "Filecoin.WalletList";
136    const PARAM_NAMES: [&'static str; 0] = [];
137    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
138    const PERMISSION: Permission = Permission::Write;
139    const DESCRIPTION: Option<&'static str> =
140        Some("Returns a list of all addresses in the wallet.");
141
142    type Params = ();
143    type Ok = Vec<Address>;
144
145    async fn handle(
146        ctx: Ctx<impl Blockstore>,
147        (): Self::Params,
148        _: &http::Extensions,
149    ) -> Result<Self::Ok, ServerError> {
150        let keystore = ctx.keystore.read();
151        Ok(crate::key_management::list_addrs(&keystore)?)
152    }
153}
154
155pub enum WalletNew {}
156impl RpcMethod<1> for WalletNew {
157    const NAME: &'static str = "Filecoin.WalletNew";
158    const PARAM_NAMES: [&'static str; 1] = ["signature_type"];
159    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
160    const PERMISSION: Permission = Permission::Write;
161
162    type Params = (SignatureType,);
163    type Ok = Address;
164
165    async fn handle(
166        ctx: Ctx<impl Blockstore>,
167        (signature_type,): Self::Params,
168        _: &http::Extensions,
169    ) -> Result<Self::Ok, ServerError> {
170        let mut keystore = ctx.keystore.write();
171        let key = crate::key_management::generate_key(signature_type)?;
172
173        let addr = format!("wallet-{}", key.address);
174        keystore.put(&addr, key.key_info.clone())?;
175        let value = keystore.get("default");
176        if value.is_err() {
177            keystore.put("default", key.key_info)?
178        }
179
180        Ok(key.address)
181    }
182}
183
184pub enum WalletSetDefault {}
185impl RpcMethod<1> for WalletSetDefault {
186    const NAME: &'static str = "Filecoin.WalletSetDefault";
187    const PARAM_NAMES: [&'static str; 1] = ["address"];
188    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
189    const PERMISSION: Permission = Permission::Write;
190
191    type Params = (Address,);
192    type Ok = ();
193
194    async fn handle(
195        ctx: Ctx<impl Blockstore>,
196        (address,): Self::Params,
197        _: &http::Extensions,
198    ) -> Result<Self::Ok, ServerError> {
199        let mut keystore = ctx.keystore.write();
200        let addr_string = format!("wallet-{address}");
201        let key_info = keystore.get(&addr_string)?;
202        keystore.remove("default")?; // This line should unregister current default key then continue
203        keystore.put("default", key_info)?;
204        Ok(())
205    }
206}
207
208pub enum WalletSign {}
209impl RpcMethod<2> for WalletSign {
210    const NAME: &'static str = "Filecoin.WalletSign";
211    const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
212    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
213    const PERMISSION: Permission = Permission::Sign;
214    const DESCRIPTION: Option<&'static str> =
215        Some("Signs the given bytes using the specified address.");
216
217    type Params = (Address, Vec<u8>);
218    type Ok = Signature;
219
220    async fn handle(
221        ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
222        (address, message): Self::Params,
223        _: &http::Extensions,
224    ) -> Result<Self::Ok, ServerError> {
225        let heaviest_tipset = ctx.chain_store().heaviest_tipset();
226        let key_addr = ctx
227            .state_manager
228            .resolve_to_key_addr(&address, &heaviest_tipset)
229            .await?;
230        let keystore = &mut *ctx.keystore.write();
231        let key = match crate::key_management::find_key(&key_addr, keystore) {
232            Ok(key) => key,
233            Err(_) => {
234                let key_info = crate::key_management::try_find(&key_addr, keystore)?;
235                Key::try_from(key_info)?
236            }
237        };
238
239        let sig = crate::key_management::sign(
240            *key.key_info.key_type(),
241            key.key_info.private_key(),
242            &message,
243        )?;
244
245        Ok(sig)
246    }
247}
248
249pub enum WalletSignMessage {}
250impl RpcMethod<2> for WalletSignMessage {
251    const NAME: &'static str = "Filecoin.WalletSignMessage";
252    const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
253    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
254    const PERMISSION: Permission = Permission::Sign;
255    const DESCRIPTION: Option<&'static str> =
256        Some("Signs the given message using the specified address.");
257
258    type Params = (Address, Message);
259    type Ok = SignedMessage;
260
261    async fn handle(
262        ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
263        (address, message): Self::Params,
264        _: &http::Extensions,
265    ) -> Result<Self::Ok, ServerError> {
266        let ts = ctx.chain_store().heaviest_tipset();
267        let key_addr = ctx
268            .state_manager
269            .resolve_to_deterministic_address(address, &ts)
270            .await?;
271
272        let keystore = &mut *ctx.keystore.write();
273        let key = match crate::key_management::find_key(&key_addr, keystore) {
274            Ok(key) => key,
275            Err(_) => {
276                let key_info = crate::key_management::try_find(&key_addr, keystore)?;
277                Key::try_from(key_info)?
278            }
279        };
280
281        let sig = crate::key_management::sign(
282            *key.key_info.key_type(),
283            key.key_info.private_key(),
284            message.cid().to_bytes().as_slice(),
285        )?;
286
287        // Could use `SignedMessage::new_unchecked` here but let's make sure
288        // we're actually signing the message as expected.
289        let smsg = SignedMessage::new_from_parts(message, sig).expect(
290            "This is infallible. We just generated the signature, so it cannot be invalid.",
291        );
292
293        Ok(smsg)
294    }
295}
296
297pub enum WalletValidateAddress {}
298impl RpcMethod<1> for WalletValidateAddress {
299    const NAME: &'static str = "Filecoin.WalletValidateAddress";
300    const PARAM_NAMES: [&'static str; 1] = ["address"];
301    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
302    const PERMISSION: Permission = Permission::Read;
303
304    type Params = (String,);
305    type Ok = Address;
306
307    async fn handle(
308        _: Ctx<impl Any>,
309        (s,): Self::Params,
310        _: &http::Extensions,
311    ) -> Result<Self::Ok, ServerError> {
312        Ok(s.parse()?)
313    }
314}
315
316pub enum WalletVerify {}
317impl RpcMethod<3> for WalletVerify {
318    const NAME: &'static str = "Filecoin.WalletVerify";
319    const PARAM_NAMES: [&'static str; 3] = ["address", "message", "signature"];
320    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
321    const PERMISSION: Permission = Permission::Read;
322
323    type Params = (Address, Vec<u8>, Signature);
324    type Ok = bool;
325
326    async fn handle(
327        _: Ctx<impl Any>,
328        (address, message, signature): Self::Params,
329        _: &http::Extensions,
330    ) -> Result<Self::Ok, ServerError> {
331        Ok(signature.verify(&message, &address).is_ok())
332    }
333}
334
335pub enum WalletDelete {}
336impl RpcMethod<1> for WalletDelete {
337    const NAME: &'static str = "Filecoin.WalletDelete";
338    const PARAM_NAMES: [&'static str; 1] = ["address"];
339    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
340    const PERMISSION: Permission = Permission::Write;
341
342    type Params = (Address,);
343    type Ok = ();
344
345    async fn handle(
346        ctx: Ctx<impl Blockstore>,
347        (address,): Self::Params,
348        _: &http::Extensions,
349    ) -> Result<Self::Ok, ServerError> {
350        let mut keystore = ctx.keystore.write();
351        crate::key_management::remove_key(&address, &mut keystore)?;
352        Ok(())
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use crate::{KeyStore, shim::crypto::SignatureType};
359
360    #[tokio::test]
361    async fn wallet_delete_existing_key() {
362        let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
363        let addr = format!("wallet-{}", key.address);
364        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
365        keystore.put(&addr, key.key_info.clone()).unwrap();
366        crate::key_management::remove_key(&key.address, &mut keystore).unwrap();
367        assert!(keystore.get(&addr).is_err());
368    }
369
370    #[tokio::test]
371    async fn wallet_delete_empty_keystore() {
372        let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
373        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
374        assert!(crate::key_management::remove_key(&key.address, &mut keystore).is_err());
375    }
376
377    #[tokio::test]
378    async fn wallet_delete_non_existent_key() {
379        let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
380        let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
381        let addr1 = format!("wallet-{}", key1.address);
382        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
383        keystore.put(&addr1, key1.key_info.clone()).unwrap();
384        assert!(crate::key_management::remove_key(&key2.address, &mut keystore).is_err());
385    }
386
387    #[tokio::test]
388    async fn wallet_delete_default_key() {
389        let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
390        let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
391        let addr1 = format!("wallet-{}", key1.address);
392        let addr2 = format!("wallet-{}", key2.address);
393        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
394        keystore.put(&addr1, key1.key_info.clone()).unwrap();
395        keystore.put(&addr2, key2.key_info.clone()).unwrap();
396        keystore.put("default", key2.key_info.clone()).unwrap();
397        crate::key_management::remove_key(&key2.address, &mut keystore).unwrap();
398        assert!(
399            crate::key_management::get_default(&keystore)
400                .unwrap()
401                .is_none()
402        );
403    }
404}