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 crate::key_management::{Key, KeyInfo};
5use crate::message::SignedMessage;
6use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError};
7use crate::shim::{
8    address::Address,
9    crypto::{Signature, SignatureType},
10    econ::TokenAmount,
11    message::Message,
12    state_tree::StateTree,
13};
14use enumflags2::BitFlags;
15
16pub enum WalletBalance {}
17impl RpcMethod<1> for WalletBalance {
18    const NAME: &'static str = "Filecoin.WalletBalance";
19    const PARAM_NAMES: [&'static str; 1] = ["address"];
20    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
21    const PERMISSION: Permission = Permission::Read;
22    const DESCRIPTION: &'static str = "Returns the balance of a wallet.";
23
24    type Params = (Address,);
25    type Ok = TokenAmount;
26
27    async fn handle(
28        ctx: Ctx,
29        (address,): Self::Params,
30        _: &http::Extensions,
31    ) -> Result<Self::Ok, ServerError> {
32        let heaviest_ts = ctx.chain_store().heaviest_tipset();
33        let cid = heaviest_ts.parent_state();
34
35        Ok(StateTree::new_from_root(ctx.db(), cid)?
36            .get_actor(&address)?
37            .map(|it| it.balance.clone().into())
38            .unwrap_or_default())
39    }
40}
41
42pub enum WalletDefaultAddress {}
43impl RpcMethod<0> for WalletDefaultAddress {
44    const NAME: &'static str = "Filecoin.WalletDefaultAddress";
45    const PARAM_NAMES: [&'static str; 0] = [];
46    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
47    const PERMISSION: Permission = Permission::Read;
48    const DESCRIPTION: &'static str =
49        "Returns the default wallet address, or null if no default is set.";
50
51    type Params = ();
52    type Ok = Option<Address>;
53
54    async fn handle(
55        ctx: Ctx,
56        (): Self::Params,
57        _: &http::Extensions,
58    ) -> Result<Self::Ok, ServerError> {
59        let keystore = ctx.keystore.read();
60        Ok(crate::key_management::get_default(&keystore)?)
61    }
62}
63
64pub enum WalletExport {}
65impl RpcMethod<1> for WalletExport {
66    const NAME: &'static str = "Filecoin.WalletExport";
67    const PARAM_NAMES: [&'static str; 1] = ["address"];
68    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
69    const PERMISSION: Permission = Permission::Admin;
70    const DESCRIPTION: &'static str =
71        "Exports the key information, including the private key, for the given wallet address.";
72
73    type Params = (Address,);
74    type Ok = KeyInfo;
75
76    async fn handle(
77        ctx: Ctx,
78        (address,): Self::Params,
79        _: &http::Extensions,
80    ) -> Result<Self::Ok, ServerError> {
81        let keystore = ctx.keystore.read();
82        let key_info = crate::key_management::export_key_info(&address, &keystore)?;
83        Ok(key_info)
84    }
85}
86
87pub enum WalletHas {}
88impl RpcMethod<1> for WalletHas {
89    const NAME: &'static str = "Filecoin.WalletHas";
90    const PARAM_NAMES: [&'static str; 1] = ["address"];
91    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
92    const PERMISSION: Permission = Permission::Write;
93    const DESCRIPTION: &'static str = "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,
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    const DESCRIPTION: &'static str = "Imports a key into the wallet and returns its address.";
115
116    type Params = (KeyInfo,);
117    type Ok = Address;
118
119    async fn handle(
120        ctx: Ctx,
121        (key_info,): Self::Params,
122        _: &http::Extensions,
123    ) -> Result<Self::Ok, ServerError> {
124        let key = Key::try_from(key_info)?;
125        let addr = format!("wallet-{}", key.address);
126        ctx.keystore.write().put(&addr, key.key_info)?;
127        Ok(key.address)
128    }
129}
130
131pub enum WalletList {}
132impl RpcMethod<0> for WalletList {
133    const NAME: &'static str = "Filecoin.WalletList";
134    const PARAM_NAMES: [&'static str; 0] = [];
135    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
136    const PERMISSION: Permission = Permission::Write;
137    const DESCRIPTION: &'static str = "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,
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] = ["signatureType"];
156    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
157    const PERMISSION: Permission = Permission::Write;
158    const DESCRIPTION: &'static str = "Generates a new key of the given signature type, adds it to the wallet, and returns its address.";
159
160    type Params = (SignatureType,);
161    type Ok = Address;
162
163    async fn handle(
164        ctx: Ctx,
165        (signature_type,): Self::Params,
166        _: &http::Extensions,
167    ) -> Result<Self::Ok, ServerError> {
168        let key = crate::key_management::generate_key(signature_type)?;
169        let addr = format!("wallet-{}", key.address);
170        let mut keystore = ctx.keystore.write();
171        keystore.put(&addr, key.key_info.clone())?;
172        let value = keystore.get("default");
173        if value.is_err() {
174            keystore.put("default", key.key_info)?
175        }
176
177        Ok(key.address)
178    }
179}
180
181pub enum WalletSetDefault {}
182impl RpcMethod<1> for WalletSetDefault {
183    const NAME: &'static str = "Filecoin.WalletSetDefault";
184    const PARAM_NAMES: [&'static str; 1] = ["address"];
185    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
186    const PERMISSION: Permission = Permission::Write;
187    const DESCRIPTION: &'static str = "Sets the given address as the default wallet address.";
188
189    type Params = (Address,);
190    type Ok = ();
191
192    async fn handle(
193        ctx: Ctx,
194        (address,): Self::Params,
195        _: &http::Extensions,
196    ) -> Result<Self::Ok, ServerError> {
197        let mut keystore = ctx.keystore.write();
198        let key_info = crate::key_management::try_find(&address, &keystore)?;
199        keystore.set_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: &'static str = "Signs the given bytes using the specified address.";
211
212    type Params = (Address, Vec<u8>);
213    type Ok = Signature;
214
215    async fn handle(
216        ctx: Ctx,
217        (address, message): Self::Params,
218        _: &http::Extensions,
219    ) -> Result<Self::Ok, ServerError> {
220        let heaviest_tipset = ctx.chain_store().heaviest_tipset();
221        let key_addr = ctx
222            .state_manager
223            .resolve_to_deterministic_address(address, &heaviest_tipset)
224            .await?;
225        let keystore = ctx.keystore.read();
226        let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
227        let sig = crate::key_management::sign(
228            *key.key_info.key_type(),
229            key.key_info.private_key(),
230            &message,
231        )?;
232
233        Ok(sig)
234    }
235}
236
237pub enum WalletSignMessage {}
238impl RpcMethod<2> for WalletSignMessage {
239    const NAME: &'static str = "Filecoin.WalletSignMessage";
240    const PARAM_NAMES: [&'static str; 2] = ["address", "message"];
241    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
242    const PERMISSION: Permission = Permission::Sign;
243    const DESCRIPTION: &'static str = "Signs the given message using the specified address.";
244
245    type Params = (Address, Message);
246    type Ok = SignedMessage;
247
248    async fn handle(
249        ctx: Ctx,
250        (address, message): Self::Params,
251        _: &http::Extensions,
252    ) -> Result<Self::Ok, ServerError> {
253        let ts = ctx.chain_store().heaviest_tipset();
254        let key_addr = ctx
255            .state_manager
256            .resolve_to_deterministic_address(address, &ts)
257            .await?;
258        let keystore = ctx.keystore.read();
259        let key = crate::key_management::try_find_key(&key_addr, &keystore)?;
260        let eth_chain_id = ctx.chain_config().eth_chain_id;
261        let smsg = crate::key_management::sign_message(&key, &message, eth_chain_id)?;
262        Ok(smsg)
263    }
264}
265
266pub enum WalletValidateAddress {}
267impl RpcMethod<1> for WalletValidateAddress {
268    const NAME: &'static str = "Filecoin.WalletValidateAddress";
269    const PARAM_NAMES: [&'static str; 1] = ["address"];
270    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
271    const PERMISSION: Permission = Permission::Read;
272    const DESCRIPTION: &'static str =
273        "Validates the given string as a Filecoin address and returns the parsed address.";
274
275    type Params = (String,);
276    type Ok = Address;
277
278    async fn handle(
279        _: Ctx,
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    const DESCRIPTION: &'static str =
294        "Returns whether the given signature is valid for the message and address.";
295
296    type Params = (Address, Vec<u8>, Signature);
297    type Ok = bool;
298
299    async fn handle(
300        _: Ctx,
301        (address, message, signature): Self::Params,
302        _: &http::Extensions,
303    ) -> Result<Self::Ok, ServerError> {
304        Ok(signature.verify(&message, &address).is_ok())
305    }
306}
307
308pub enum WalletDelete {}
309impl RpcMethod<1> for WalletDelete {
310    const NAME: &'static str = "Filecoin.WalletDelete";
311    const PARAM_NAMES: [&'static str; 1] = ["address"];
312    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
313    const PERMISSION: Permission = Permission::Write;
314    const DESCRIPTION: &'static str = "Removes the key for the given address from the wallet.";
315
316    type Params = (Address,);
317    type Ok = ();
318
319    async fn handle(
320        ctx: Ctx,
321        (address,): Self::Params,
322        _: &http::Extensions,
323    ) -> Result<Self::Ok, ServerError> {
324        crate::key_management::remove_key(&address, &mut ctx.keystore.write())?;
325        Ok(())
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use crate::{KeyStore, shim::crypto::SignatureType};
332
333    #[tokio::test]
334    async fn wallet_delete_existing_key() {
335        let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
336        let addr = format!("wallet-{}", key.address);
337        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
338        keystore.put(&addr, key.key_info.clone()).unwrap();
339        crate::key_management::remove_key(&key.address, &mut keystore).unwrap();
340        assert!(keystore.get(&addr).is_err());
341    }
342
343    #[tokio::test]
344    async fn wallet_delete_empty_keystore() {
345        let key = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
346        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
347        assert!(crate::key_management::remove_key(&key.address, &mut keystore).is_err());
348    }
349
350    #[tokio::test]
351    async fn wallet_delete_non_existent_key() {
352        let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
353        let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
354        let addr1 = format!("wallet-{}", key1.address);
355        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
356        keystore.put(&addr1, key1.key_info.clone()).unwrap();
357        assert!(crate::key_management::remove_key(&key2.address, &mut keystore).is_err());
358    }
359
360    #[tokio::test]
361    async fn wallet_delete_default_key() {
362        let key1 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
363        let key2 = crate::key_management::generate_key(SignatureType::Secp256k1).unwrap();
364        let addr1 = format!("wallet-{}", key1.address);
365        let addr2 = format!("wallet-{}", key2.address);
366        let mut keystore = KeyStore::new(crate::KeyStoreConfig::Memory).unwrap();
367        keystore.put(&addr1, key1.key_info.clone()).unwrap();
368        keystore.put(&addr2, key2.key_info.clone()).unwrap();
369        keystore.put("default", key2.key_info.clone()).unwrap();
370        crate::key_management::remove_key(&key2.address, &mut keystore).unwrap();
371        assert!(
372            crate::key_management::get_default(&keystore)
373                .unwrap()
374                .is_none()
375        );
376    }
377}