From 80939f8c907f1180b270b2253f1256a6672736de Mon Sep 17 00:00:00 2001 From: Daniel Perez Date: Thu, 1 Aug 2024 23:25:55 +0100 Subject: [PATCH 1/2] Add support for cast keystore --- Cargo.lock | 2 ++ Cargo.toml | 6 +++++- docs/src/account_management.md | 21 +++++++++++++++++++ docs/src/builtin_values.md | 7 +++++++ src/interpreter/builtins/mod.rs | 1 + src/interpreter/builtins/repl.rs | 36 ++++++++++++++++++++++++++++++-- src/interpreter/env.rs | 8 ++++--- 7 files changed, 75 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5acd272..6f848a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -713,6 +713,8 @@ dependencies = [ "alloy-primitives", "alloy-signer 0.2.0", "async-trait", + "elliptic-curve", + "eth-keystore", "k256", "rand", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 7a5ee5e..3e01954 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,11 @@ chisel = { git = "https://github.com/foundry-rs/foundry", tag = "nightly-5ac78a9 url = "2.5.1" futures = "0.3.30" tokio = "1.38.0" -alloy = { version = "0.2.0", features = ["full", "signer-ledger"] } +alloy = { version = "0.2.0", features = [ + "full", + "signer-ledger", + "signer-keystore", +] } itertools = "0.13.0" rpassword = "7.3.1" coins-ledger = "0.11.1" diff --git a/docs/src/account_management.md b/docs/src/account_management.md index e63b04d..bee4107 100644 --- a/docs/src/account_management.md +++ b/docs/src/account_management.md @@ -32,3 +32,24 @@ Note that only the ledger live derivation is supported for now. >> repl.account 0x669F44bB2DFb534707E6FAE940d7558ab0FE254D ``` + + +## Using a keystore + +Accounts can be loaded from a keystore created by [`cast`](https://book.getfoundry.sh/reference/cli/cast/wallet/import), using the `repl.loadKeystore` function. + +The keystore can be created using the `cast wallet import` command: + +``` +cast wallet import my-account --interactive +``` + +This will create a keystore file in `~/.foundry/keystore/my-account`. +It can then be loaded into Eclair using: + +```javascript +>> repl.loadKeystore("my-account") +``` + +`loadKeystore` will prompt for the password to decrypt the keystore. +The password can also be passed as a second argument. diff --git a/docs/src/builtin_values.md b/docs/src/builtin_values.md index ecd31e4..b0f113c 100644 --- a/docs/src/builtin_values.md +++ b/docs/src/builtin_values.md @@ -155,6 +155,13 @@ Returns the currently loaded account or null if none. Sets the current account to the one corresponding to the provided private key and returns the loaded address. If no private key is provided, the user will be prompted to enter one. +### `repl.loadKeystore(string name, string? password) -> address` + +Loads the keystore located in `~/.foundry/keystore/` and sets the current account to the one corresponding to the keystore. +If the password is not provided as the second argument, it will be prompted. + +See [Using a keystore](./account_management.md#using-a-keystore) for more information. + ### `repl.listLedgerWallets(uint256 count) -> address[]` Returns a list of `count` wallets in the ledger. diff --git a/src/interpreter/builtins/mod.rs b/src/interpreter/builtins/mod.rs index 65cb6d0..5c87009 100644 --- a/src/interpreter/builtins/mod.rs +++ b/src/interpreter/builtins/mod.rs @@ -146,6 +146,7 @@ lazy_static! { "loadPrivateKey".to_string(), repl::REPL_LOAD_PRIVATE_KEY.clone(), ); + repl_methods.insert("loadKeystore".to_string(), repl::REPL_LOAD_KEYSTORE.clone()); repl_methods.insert( "listLedgerWallets".to_string(), repl::REPL_LIST_LEDGER_WALLETS.clone(), diff --git a/src/interpreter/builtins/repl.rs b/src/interpreter/builtins/repl.rs index 29961cb..ae38454 100644 --- a/src/interpreter/builtins/repl.rs +++ b/src/interpreter/builtins/repl.rs @@ -1,6 +1,9 @@ use std::{process::Command, sync::Arc}; -use alloy::providers::Provider; +use alloy::{ + providers::Provider, + signers::local::{LocalSigner, PrivateKeySigner}, +}; use anyhow::{anyhow, bail, Ok, Result}; use futures::{future::BoxFuture, FutureExt}; use lazy_static::lazy_static; @@ -140,7 +143,25 @@ fn load_private_key(env: &mut Env, _receiver: &Value, args: &[Value]) -> Result< [] => rpassword::prompt_password("Enter private key: ")?, _ => bail!("loadPrivateKey: invalid arguments"), }; - env.set_private_key(key.as_str())?; + let signer: PrivateKeySigner = key.parse()?; + env.set_signer(signer)?; + Ok(get_default_sender(env)) +} + +fn load_keystore(env: &mut Env, _receiver: &Value, args: &[Value]) -> Result { + let (account, password) = match args { + [Value::Str(account)] => ( + account.clone(), + rpassword::prompt_password("Enter password: ")?, + ), + [Value::Str(account), Value::Str(password)] => (account.clone(), password.clone()), + _ => bail!("loadKeystore: invalid arguments"), + }; + let foundry_dir = + foundry_config::Config::foundry_dir().ok_or(anyhow!("foundry dir not found"))?; + let keystore_file_path = foundry_dir.join("keystores").join(account.as_str()); + let signer = LocalSigner::decrypt_keystore(keystore_file_path, password)?; + env.set_signer(signer)?; Ok(get_default_sender(env)) } @@ -240,6 +261,17 @@ lazy_static! { load_private_key, vec![vec![], vec![FunctionParam::new("privateKey", Type::String)]] ); + pub static ref REPL_LOAD_KEYSTORE: Arc = SyncMethod::arc( + "loadKeystore", + load_keystore, + vec![ + vec![FunctionParam::new("account", Type::String)], + vec![ + FunctionParam::new("account", Type::String), + FunctionParam::new("password", Type::String) + ] + ] + ); pub static ref REPL_LIST_LEDGER_WALLETS: Arc = AsyncMethod::arc( "listLedgerWallets", list_ledgers, diff --git a/src/interpreter/env.rs b/src/interpreter/env.rs index 0db7af3..aa99876 100644 --- a/src/interpreter/env.rs +++ b/src/interpreter/env.rs @@ -11,7 +11,7 @@ use alloy::{ network::{AnyNetwork, Ethereum, EthereumWallet, NetworkWallet, TxSigner}, primitives::Address, providers::{Provider, ProviderBuilder}, - signers::{ledger::HDPath, local::PrivateKeySigner, Signature}, + signers::{ledger::HDPath, Signature}, transports::http::{Client, Http}, }; use anyhow::{anyhow, bail, Result}; @@ -126,8 +126,10 @@ impl Env { .map(NetworkWallet::::default_signer_address) } - pub fn set_private_key(&mut self, private_key: &str) -> Result<()> { - let signer: PrivateKeySigner = private_key.parse()?; + pub fn set_signer(&mut self, signer: S) -> Result<()> + where + S: TxSigner + Send + Sync + 'static, + { self.set_wallet(signer) } From eac7732ef4fb0e2856df927f6e61df9eae8ec477 Mon Sep 17 00:00:00 2001 From: Daniel Perez Date: Thu, 1 Aug 2024 23:27:06 +0100 Subject: [PATCH 2/2] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 399d0a4..4910459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * `priorityFee` - priority fee to pay for the transaction * `gasPrice` - gas price to use for the (legacy) transaction * Allow to select version of Eclair when installing using install script +* Add `repl.loadKeystore` to load keystore from a file created by `cast` ## 0.1.1 (2024-07-30)