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::try_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        let addr = format!("wallet-{}", key.address);
125        ctx.keystore.write().put(&addr, key.key_info)?;
126        Ok(key.address)
127    }
128}
129
130pub enum WalletList {}
131impl RpcMethod<0> for WalletList {
132    const NAME: &'static str = "Filecoin.WalletList";
133    const PARAM_NAMES: [&'static str; 0] = [];
134    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
135    const PERMISSION: Permission = Permission::Write;
136    const DESCRIPTION: Option<&'static str> =
137        Some("Returns a list of all addresses in the wallet.");
138
139    type Params = ();
140    type Ok = Vec<Address>;
141
142    async fn handle(
143        ctx: Ctx<impl Blockstore>,
144        (): Self::Params,
145        _: &http::Extensions,
146    ) -> Result<Self::Ok, ServerError> {
147        let keystore = ctx.keystore.read();
148        Ok(crate::key_management::list_addrs(&keystore)?)
149    }
150}
151
152pub enum WalletNew {}
153impl RpcMethod<1> for WalletNew {
154    const NAME: &'static str = "Filecoin.WalletNew";
155    const PARAM_NAMES: [&'static str; 1] = ["signature_type"];
156    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
157    const PERMISSION: Permission = Permission::Write;
158
159    type Params = (SignatureType,);
160    type Ok = Address;
161
162    async fn handle(
163        ctx: Ctx<impl Blockstore>,
164        (signature_type,): Self::Params,
165        _: &http::Extensions,
166    ) -> Result<Self::Ok, ServerError> {
167        let key = crate::key_management::generate_key(signature_type)?;
168        let addr = format!("wallet-{}", key.address);
169        let mut keystore = ctx.keystore.write();
170        keystore.put(&addr, key.key_info.clone())?;
171        let value = keystore.get("default");
172        if value.is_err() {
173            keystore.put("default", key.key_info)?
174        }
175
176        Ok(key.address)
177    }
178}
179
180pub enum WalletSetDefault {}
181impl RpcMethod<1> for WalletSetDefault {
182    const NAME: &'static str = "Filecoin.WalletSetDefault";
183    const PARAM_NAMES: [&'static str; 1] = ["address"];
184    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
185    const PERMISSION: Permission = Permission::Write;
186
187    type Params = (Address,);
188    type Ok = ();
189
190    async fn handle(
191        ctx: Ctx<impl Blockstore>,
192        (address,): Self::Params,
193        _: &http::Extensions,
194    ) -> Result<Self::Ok, ServerError> {
195        let mut keystore = ctx.keystore.write();
196        let addr_string = format!("wallet-{address}");
197        let key_info = keystore.get(&addr_string)?;
198        keystore.remove("default")?; // This line should unregister current default key then continue
199        keystore.put("default", key_info)?;
200        Ok(())
201    }
202}
203
204pub enum WalletSign {}
205impl RpcMethod<2> for WalletSign {
206    const NAME: &'static str = "Filecoin.WalletSign";
207    const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
208    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
209    const PERMISSION: Permission = Permission::Sign;
210    const DESCRIPTION: Option<&'static str> =
211        Some("Signs the given bytes using the specified address.");
212
213    type Params = (Address, Vec<u8>);
214    type Ok = Signature;
215
216    async fn handle(
217        ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
218        (address, message): Self::Params,
219        _: &http::Extensions,
220    ) -> Result<Self::Ok, ServerError> {
221        let heaviest_tipset = ctx.chain_store().heaviest_tipset();
222        let key_addr = ctx
223            .state_manager
224            .resolve_to_key_addr(&address, &heaviest_tipset)
225            .await?;
226        let keystore = ctx.keystore.read();
227        let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
228        let sig = crate::key_management::sign(
229            *key.key_info.key_type(),
230            key.key_info.private_key(),
231            &message,
232        )?;
233
234        Ok(sig)
235    }
236}
237
238pub enum WalletSignMessage {}
239impl RpcMethod<2> for WalletSignMessage {
240    const NAME: &'static str = "Filecoin.WalletSignMessage";
241    const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
242    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
243    const PERMISSION: Permission = Permission::Sign;
244    const DESCRIPTION: Option<&'static str> =
245        Some("Signs the given message using the specified address.");
246
247    type Params = (Address, Message);
248    type Ok = SignedMessage;
249
250    async fn handle(
251        ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
252        (address, message): Self::Params,
253        _: &http::Extensions,
254    ) -> Result<Self::Ok, ServerError> {
255        let ts = ctx.chain_store().heaviest_tipset();
256        let key_addr = ctx
257            .state_manager
258            .resolve_to_deterministic_address(address, &ts)
259            .await?;
260        let keystore = ctx.keystore.read();
261        let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
262        let eth_chain_id = ctx.chain_config().eth_chain_id;
263        let smsg = crate::key_management::sign_message(&key, &message, eth_chain_id)?;
264        Ok(smsg)
265    }
266}
267
268pub enum WalletValidateAddress {}
269impl RpcMethod<1> for WalletValidateAddress {
270    const NAME: &'static str = "Filecoin.WalletValidateAddress";
271    const PARAM_NAMES: [&'static str; 1] = ["address"];
272    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
273    const PERMISSION: Permission = Permission::Read;
274
275    type Params = (String,);
276    type Ok = Address;
277
278    async fn handle(
279        _: Ctx<impl Any>,
280        (s,): Self::Params,
281        _: &http::Extensions,
282    ) -> Result<Self::Ok, ServerError> {
283        Ok(s.parse()?)
284    }
285}
286
287pub enum WalletVerify {}
288impl RpcMethod<3> for WalletVerify {
289    const NAME: &'static str = "Filecoin.WalletVerify";
290    const PARAM_NAMES: [&'static str; 3] = ["address", "message", "signature"];
291    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
292    const PERMISSION: Permission = Permission::Read;
293
294    type Params = (Address, Vec<u8>, Signature);
295    type Ok = bool;
296
297    async fn handle(
298        _: Ctx<impl Any>,
299        (address, message, signature): Self::Params,
300        _: &http::Extensions,
301    ) -> Result<Self::Ok, ServerError> {
302        Ok(signature.verify(&message, &address).is_ok())
303    }
304}
305
306pub enum WalletDelete {}
307impl RpcMethod<1> for WalletDelete {
308    const NAME: &'static str = "Filecoin.WalletDelete";
309    const PARAM_NAMES: [&'static str; 1] = ["address"];
310    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
311    const PERMISSION: Permission = Permission::Write;
312
313    type Params = (Address,);
314    type Ok = ();
315
316    async fn handle(
317        ctx: Ctx<impl Blockstore>,
318        (address,): Self::Params,
319        _: &http::Extensions,
320    ) -> Result<Self::Ok, ServerError> {
321        crate::key_management::remove_key(&address, &mut ctx.keystore.write())?;
322        Ok(())
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use crate::{KeyStore, shim::crypto::SignatureType};
329
330    #[tokio::test]
331    async fn wallet_delete_existing_key() {
332        let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
333        let addr = format!("wallet-{}", key.address);
334        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
335        keystore.put(&addr, key.key_info.clone()).unwrap();
336        crate::key_management::remove_key(&key.address, &mut keystore).unwrap();
337        assert!(keystore.get(&addr).is_err());
338    }
339
340    #[tokio::test]
341    async fn wallet_delete_empty_keystore() {
342        let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
343        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
344        assert!(crate::key_management::remove_key(&key.address, &mut keystore).is_err());
345    }
346
347    #[tokio::test]
348    async fn wallet_delete_non_existent_key() {
349        let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
350        let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
351        let addr1 = format!("wallet-{}", key1.address);
352        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
353        keystore.put(&addr1, key1.key_info.clone()).unwrap();
354        assert!(crate::key_management::remove_key(&key2.address, &mut keystore).is_err());
355    }
356
357    #[tokio::test]
358    async fn wallet_delete_default_key() {
359        let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
360        let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
361        let addr1 = format!("wallet-{}", key1.address);
362        let addr2 = format!("wallet-{}", key2.address);
363        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
364        keystore.put(&addr1, key1.key_info.clone()).unwrap();
365        keystore.put(&addr2, key2.key_info.clone()).unwrap();
366        keystore.put("default", key2.key_info.clone()).unwrap();
367        crate::key_management::remove_key(&key2.address, &mut keystore).unwrap();
368        assert!(
369            crate::key_management::get_default(&keystore)
370                .unwrap()
371                .is_none()
372        );
373    }
374}