Skip to content

Commit

Permalink
Merge pull request #16 from danhper/cast-keystore
Browse files Browse the repository at this point in the history
Cast keystore
  • Loading branch information
danhper authored Aug 1, 2024
2 parents d2533e8 + eac7732 commit 30bd2e3
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 21 additions & 0 deletions docs/src/account_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 7 additions & 0 deletions docs/src/builtin_values.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<name>` 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.
Expand Down
1 change: 1 addition & 0 deletions src/interpreter/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
36 changes: 34 additions & 2 deletions src/interpreter/builtins/repl.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Value> {
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))
}

Expand Down Expand Up @@ -240,6 +261,17 @@ lazy_static! {
load_private_key,
vec![vec![], vec![FunctionParam::new("privateKey", Type::String)]]
);
pub static ref REPL_LOAD_KEYSTORE: Arc<dyn FunctionDef> = 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<dyn FunctionDef> = AsyncMethod::arc(
"listLedgerWallets",
list_ledgers,
Expand Down
8 changes: 5 additions & 3 deletions src/interpreter/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -126,8 +126,10 @@ impl Env {
.map(NetworkWallet::<AnyNetwork>::default_signer_address)
}

pub fn set_private_key(&mut self, private_key: &str) -> Result<()> {
let signer: PrivateKeySigner = private_key.parse()?;
pub fn set_signer<S>(&mut self, signer: S) -> Result<()>
where
S: TxSigner<Signature> + Send + Sync + 'static,
{
self.set_wallet(signer)
}

Expand Down

0 comments on commit 30bd2e3

Please sign in to comment.