Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ alloy-signer-gcp = { version = "1.0.42", default-features = false }
alloy-signer-ledger = { version = "1.0.42", default-features = false }
alloy-signer-local = { version = "1.0.42", default-features = false }
alloy-signer-trezor = { version = "1.0.42", default-features = false }
alloy-signer-turnkey = { version = "1.0.42", default-features = false }
alloy-transport = { version = "1.0.42", default-features = false }
alloy-transport-http = { version = "1.0.42", default-features = false }
alloy-transport-ipc = { version = "1.0.42", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ COPY . .
RUN git update-index --force-write-index

RUN --mount=type=cache,target=/root/.cargo/registry --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/opt/foundry/target \
source $HOME/.profile && cargo build --release --features anvil/js-tracer,cast/aws-kms,cast/gcp-kms,forge/aws-kms,forge/gcp-kms \
source $HOME/.profile && cargo build --release --features anvil/js-tracer,cast/aws-kms,cast/gcp-kms,cast/turnkey,forge/aws-kms,forge/gcp-kms,forge/turnkey \
&& mkdir out \
&& mv target/release/forge out/forge \
&& mv target/release/cast out/cast \
Expand Down
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ CARGO_TARGET_DIR ?= target
# List of features to use when building. Can be overridden via the environment.
# No jemalloc on Windows
ifeq ($(OS),Windows_NT)
FEATURES ?= aws-kms gcp-kms cli asm-keccak
FEATURES ?= aws-kms gcp-kms turnkey cli asm-keccak
else
FEATURES ?= jemalloc aws-kms gcp-kms cli asm-keccak
FEATURES ?= jemalloc aws-kms gcp-kms turnkey cli asm-keccak
endif

##@ Help
Expand Down Expand Up @@ -47,15 +47,15 @@ build-%:
.PHONY: docker-build-push
docker-build-push: docker-build-prepare ## Build and push a cross-arch Docker image tagged with DOCKER_IMAGE_NAME.
# Build x86_64-unknown-linux-gnu.
cargo build --target x86_64-unknown-linux-gnu --features "jemalloc aws-kms gcp-kms cli asm-keccak js-tracer" --profile "$(PROFILE)"
cargo build --target x86_64-unknown-linux-gnu --features "jemalloc aws-kms gcp-kms turnkey cli asm-keccak js-tracer" --profile "$(PROFILE)"
mkdir -p $(BIN_DIR)/amd64
for bin in anvil cast chisel forge; do \
cp $(CARGO_TARGET_DIR)/x86_64-unknown-linux-gnu/$(PROFILE)/$$bin $(BIN_DIR)/amd64/; \
done

# Build aarch64-unknown-linux-gnu.
rustup target add aarch64-unknown-linux-gnu
RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" cargo build --target aarch64-unknown-linux-gnu --features "aws-kms gcp-kms cli asm-keccak js-tracer" --profile "$(PROFILE)"
RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" cargo build --target aarch64-unknown-linux-gnu --features "aws-kms gcp-kms turnkey cli asm-keccak js-tracer" --profile "$(PROFILE)"
mkdir -p $(BIN_DIR)/arm64
for bin in anvil cast chisel forge; do \
cp $(CARGO_TARGET_DIR)/aarch64-unknown-linux-gnu/$(PROFILE)/$$bin $(BIN_DIR)/arm64/; \
Expand Down
1 change: 1 addition & 0 deletions crates/cast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,5 @@ mimalloc = ["foundry-cli/mimalloc"]
tracy-allocator = ["foundry-cli/tracy-allocator"]
aws-kms = ["foundry-wallets/aws-kms"]
gcp-kms = ["foundry-wallets/gcp-kms"]
turnkey = ["foundry-wallets/turnkey"]
isolate-by-default = ["foundry-config/isolate-by-default"]
7 changes: 4 additions & 3 deletions crates/forge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ forge-script.workspace = true
forge-sol-macro-gen.workspace = true
foundry-cli.workspace = true
foundry-debugger.workspace = true
foundry-wallets = { workspace = true, optional = true }

alloy-chains.workspace = true
alloy-dyn-abi.workspace = true
Expand Down Expand Up @@ -101,7 +102,6 @@ alloy-hardforks.workspace = true
anvil.workspace = true
forge-script-sequence.workspace = true
foundry-test-utils.workspace = true
foundry-wallets.workspace = true
futures.workspace = true
reqwest = { workspace = true, features = ["json"] }

Expand All @@ -120,6 +120,7 @@ asm-keccak = ["alloy-primitives/asm-keccak"]
jemalloc = ["foundry-cli/jemalloc"]
mimalloc = ["foundry-cli/mimalloc"]
tracy-allocator = ["foundry-cli/tracy-allocator"]
aws-kms = ["foundry-wallets/aws-kms"]
gcp-kms = ["foundry-wallets/gcp-kms"]
aws-kms = ["dep:foundry-wallets", "foundry-wallets/aws-kms"]
gcp-kms = ["dep:foundry-wallets", "foundry-wallets/gcp-kms"]
turnkey = ["dep:foundry-wallets", "foundry-wallets/turnkey"]
isolate-by-default = ["foundry-config/isolate-by-default"]
4 changes: 4 additions & 0 deletions crates/forge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ extern crate foundry_common;
#[macro_use]
extern crate tracing;

// Required for optional features (aws-kms, gcp-kms, turnkey)
#[cfg(any(feature = "aws-kms", feature = "gcp-kms", feature = "turnkey"))]
use foundry_wallets as _;

pub mod args;
pub mod cmd;
pub mod opts;
Expand Down
4 changes: 4 additions & 0 deletions crates/wallets/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ aws-config = { version = "1", default-features = true, optional = true }
# gcp-kms
alloy-signer-gcp = { workspace = true, features = ["eip712"], optional = true }

# turnkey
alloy-signer-turnkey = { workspace = true, features = ["eip712"], optional = true }

async-trait.workspace = true
clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] }
derive_builder = "0.20"
Expand All @@ -48,3 +51,4 @@ tokio = { workspace = true, features = ["macros"] }
[features]
aws-kms = ["dep:alloy-signer-aws", "dep:aws-config"]
gcp-kms = ["dep:alloy-signer-gcp"]
turnkey = ["dep:alloy-signer-turnkey"]
10 changes: 10 additions & 0 deletions crates/wallets/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ use alloy_signer_aws::AwsSignerError;
#[cfg(feature = "gcp-kms")]
use alloy_signer_gcp::GcpSignerError;

#[cfg(feature = "turnkey")]
use alloy_signer_turnkey::TurnkeySignerError;

#[derive(Debug, thiserror::Error)]
pub enum PrivateKeyError {
#[error("Failed to create wallet from private key. Private key is invalid hex: {0}")]
Expand Down Expand Up @@ -37,6 +40,9 @@ pub enum WalletSignerError {
#[cfg(feature = "gcp-kms")]
Gcp(#[from] Box<GcpSignerError>),
#[error(transparent)]
#[cfg(feature = "turnkey")]
Turnkey(#[from] TurnkeySignerError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
InvalidHex(#[from] FromHexError),
Expand All @@ -54,4 +60,8 @@ impl WalletSignerError {
pub fn gcp_unsupported() -> Self {
Self::UnsupportedSigner("Google Cloud KMS")
}

pub fn turnkey_unsupported() -> Self {
Self::UnsupportedSigner("Turnkey")
}
}
29 changes: 29 additions & 0 deletions crates/wallets/src/multi_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ macro_rules! create_hw_wallets {
/// 5. Private Keys (cleartext in CLI)
/// 6. Private Keys (interactively via secure prompt)
/// 7. AWS KMS
/// 8. Turnkey
#[derive(Builder, Clone, Debug, Default, Serialize, Parser)]
#[command(next_help_heading = "Wallet options", about = None, long_about = None)]
pub struct MultiWalletOpts {
Expand Down Expand Up @@ -221,6 +222,15 @@ pub struct MultiWalletOpts {
/// See: <https://cloud.google.com/kms/docs>
#[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "gcp-kms"))]
pub gcp: bool,

/// Use Turnkey.
///
/// Ensure the following environment variables are set: TURNKEY_API_PRIVATE_KEY,
/// TURNKEY_ORGANIZATION_ID, TURNKEY_ADDRESS.
///
/// See: <https://docs.turnkey.com/getting-started/quickstart>
#[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "turnkey"))]
pub turnkey: bool,
}

impl MultiWalletOpts {
Expand All @@ -241,6 +251,9 @@ impl MultiWalletOpts {
if let Some(gcp_signer) = self.gcp_signers().await? {
signers.extend(gcp_signer);
}
if let Some(turnkey_signers) = self.turnkey_signers()? {
signers.extend(turnkey_signers);
}
if let Some((pending_keystores, unlocked)) = self.keystores()? {
pending.extend(pending_keystores);
signers.extend(unlocked);
Expand Down Expand Up @@ -449,6 +462,20 @@ impl MultiWalletOpts {

Ok(None)
}

pub fn turnkey_signers(&self) -> Result<Option<Vec<WalletSigner>>> {
#[cfg(feature = "turnkey")]
if self.turnkey {
let api_private_key = std::env::var("TURNKEY_API_PRIVATE_KEY")?;
let organization_id = std::env::var("TURNKEY_ORGANIZATION_ID")?;
let address = std::env::var("TURNKEY_ADDRESS")?.parse()?;

let signer = WalletSigner::from_turnkey(api_private_key, organization_id, address)?;
return Ok(Some(vec![signer]));
}

Ok(None)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -507,6 +534,7 @@ mod tests {
("ledger", "--mnemonic-indexes", 1),
("trezor", "--mnemonic-indexes", 2),
("aws", "--mnemonic-indexes", 10),
("turnkey", "--mnemonic-indexes", 11),
];

for test_case in wallet_options {
Expand All @@ -521,6 +549,7 @@ mod tests {
"ledger" => assert!(args.ledger),
"trezor" => assert!(args.trezor),
"aws" => assert!(args.aws),
"turnkey" => assert!(args.turnkey),
_ => panic!("Should have matched one of the previous wallet options"),
}

Expand Down
20 changes: 20 additions & 0 deletions crates/wallets/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use serde::Serialize;
/// 4. Keystore (via file path)
/// 5. AWS KMS
/// 6. Google Cloud KMS
/// 7. Turnkey
#[derive(Clone, Debug, Default, Serialize, Parser)]
#[command(next_help_heading = "Wallet options", about = None, long_about = None)]
pub struct WalletOpts {
Expand Down Expand Up @@ -91,6 +92,15 @@ pub struct WalletOpts {
/// See: <https://cloud.google.com/kms/docs>
#[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "gcp-kms"))]
pub gcp: bool,

/// Use Turnkey.
///
/// Ensure the following environment variables are set: TURNKEY_API_PRIVATE_KEY,
/// TURNKEY_ORGANIZATION_ID, TURNKEY_ADDRESS.
///
/// See: <https://docs.turnkey.com/getting-started/quickstart>
#[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "turnkey"))]
pub turnkey: bool,
}

impl WalletOpts {
Expand Down Expand Up @@ -120,6 +130,14 @@ impl WalletOpts {
.parse()
.map_err(|_| eyre::eyre!("GCP_KEY_VERSION could not be parsed into u64"))?;
WalletSigner::from_gcp(project_id, location, keyring, key_name, key_version).await?
} else if self.turnkey {
let api_private_key = get_env("TURNKEY_API_PRIVATE_KEY")?;
let organization_id = get_env("TURNKEY_ORGANIZATION_ID")?;
let address_str = get_env("TURNKEY_ADDRESS")?;
let address = address_str.parse().map_err(|_| {
eyre::eyre!("TURNKEY_ADDRESS could not be parsed as an Ethereum address")
})?;
WalletSigner::from_turnkey(api_private_key, organization_id, address)?
} else if let Some(raw_wallet) = self.raw.signer()? {
raw_wallet
} else if let Some(path) = utils::maybe_get_keystore_path(
Expand Down Expand Up @@ -152,6 +170,7 @@ flag to set your key via:
--mnemonic-path
--aws
--gcp
--turnkey
--trezor
--ledger

Expand Down Expand Up @@ -222,6 +241,7 @@ mod tests {
trezor: false,
aws: false,
gcp: false,
turnkey: false,
};
match wallet.signer().await {
Ok(_) => {
Expand Down
Loading
Loading