diff --git a/Cargo.lock b/Cargo.lock index 84dabeda7..f786269ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -654,6 +654,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -898,6 +914,7 @@ dependencies = [ "futures-util", "rand 0.8.5", "reqwest 0.12.9", + "secp256k1 0.30.0", "serde", "tokio 1.41.1", "tokio-util 0.7.12", @@ -922,6 +939,7 @@ dependencies = [ "near-jsonrpc-primitives", "near-primitives", "reqwest 0.12.9", + "secp256k1 0.30.0", "serde", "serde_json", "starknet", @@ -2328,7 +2346,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" dependencies = [ - "ethereum-types", + "ethereum-types 0.14.1", "hex", "once_cell", "regex", @@ -2336,7 +2354,7 @@ dependencies = [ "serde_json", "sha3", "thiserror 1.0.69", - "uint", + "uint 0.9.5", ] [[package]] @@ -2347,8 +2365,21 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash 0.8.0", - "impl-rlp", - "impl-serde", + "impl-rlp 0.3.0", + "impl-serde 0.4.0", + "tiny-keccak", +] + +[[package]] +name = "ethbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b" +dependencies = [ + "crunchy", + "fixed-hash 0.8.0", + "impl-rlp 0.4.0", + "impl-serde 0.5.0", "tiny-keccak", ] @@ -2358,12 +2389,26 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ - "ethbloom", + "ethbloom 0.13.0", "fixed-hash 0.8.0", - "impl-rlp", - "impl-serde", + "impl-rlp 0.3.0", + "impl-serde 0.4.0", "primitive-types 0.12.2", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "ethereum-types" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" +dependencies = [ + "ethbloom 0.14.1", + "fixed-hash 0.8.0", + "impl-rlp 0.4.0", + "impl-serde 0.5.0", + "primitive-types 0.13.1", + "uint 0.10.0", ] [[package]] @@ -2969,6 +3014,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec 0.7.6", +] + [[package]] name = "hex_fmt" version = "0.3.0" @@ -3843,13 +3897,31 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" +dependencies = [ + "parity-scale-codec", +] + [[package]] name = "impl-rlp" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" dependencies = [ - "rlp", + "rlp 0.5.2", +] + +[[package]] +name = "impl-rlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" +dependencies = [ + "rlp 0.6.1", ] [[package]] @@ -3861,6 +3933,15 @@ dependencies = [ "serde", ] +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.3" @@ -4436,7 +4517,7 @@ dependencies = [ "smallvec", "thiserror 1.0.69", "tracing", - "uint", + "uint 0.9.5", "void", ] @@ -4986,6 +5067,7 @@ dependencies = [ "const_format", "dirs", "ed25519-consensus", + "ethereum-types 0.15.1", "eyre", "futures-util", "hex", @@ -4994,7 +5076,9 @@ dependencies = [ "multiaddr", "near-crypto", "rand 0.8.5", + "secp256k1 0.30.0", "starknet", + "tiny-keccak", "tokio 1.41.1", "toml_edit", "tracing", @@ -5291,7 +5375,7 @@ dependencies = [ "near-stdx", "primitive-types 0.10.1", "rand 0.8.5", - "secp256k1", + "secp256k1 0.27.0", "serde", "serde_json", "subtle", @@ -6337,7 +6421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" dependencies = [ "fixed-hash 0.7.0", - "uint", + "uint 0.9.5", ] [[package]] @@ -6347,10 +6431,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash 0.8.0", - "impl-codec", - "impl-rlp", - "impl-serde", - "uint", + "impl-codec 0.6.0", + "impl-rlp 0.3.0", + "impl-serde 0.4.0", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash 0.8.0", + "impl-codec 0.7.0", + "impl-rlp 0.4.0", + "impl-serde 0.5.0", + "uint 0.10.0", ] [[package]] @@ -7027,6 +7124,16 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes 1.9.0", + "rustc-hex", +] + [[package]] name = "rocksdb" version = "0.22.0" @@ -7361,7 +7468,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.8.1", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys 0.10.1", ] [[package]] @@ -7373,6 +7491,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -8006,7 +8133,7 @@ source = "git+https://github.com/xJonathanLEI/starknet-rs?rev=5c676a6#5c676a6403 dependencies = [ "async-trait", "auto_impl", - "ethereum-types", + "ethereum-types 0.14.1", "flate2", "getrandom", "log", @@ -8925,6 +9052,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicase" version = "2.8.0" @@ -9464,7 +9603,7 @@ dependencies = [ "bytes 1.9.0", "derive_more", "ethabi", - "ethereum-types", + "ethereum-types 0.14.1", "futures", "futures-timer", "headers", @@ -9476,8 +9615,8 @@ dependencies = [ "parking_lot", "pin-project", "reqwest 0.11.27", - "rlp", - "secp256k1", + "rlp 0.5.2", + "secp256k1 0.27.0", "serde", "serde_json", "soketto", diff --git a/Cargo.toml b/Cargo.toml index 111625350..c3b8337a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ dirs = "5.0.1" ed25519-dalek = "2.1.1" ed25519-consensus = "2.1.0" either = "1.13.0" +ethereum-types = "0.15.1" eyre = "0.6.12" fixedstr = "0.5.7" fragile = "2.0.0" @@ -115,11 +116,13 @@ starknet = "0.12.0" starknet-crypto = "0.7.1" starknet-types-core = "0.1.7" strum = "0.26.2" +secp256k1 = "0.30.0" syn = "2.0" tempdir = "0.3.7" tempfile = "3.12.0" thiserror = "1.0.56" thunderdome = "0.6.1" +tiny-keccak = "2.0.2" tokio = "1.35.1" tokio-test = "0.4.4" tokio-tungstenite = "0.24.0" diff --git a/crates/context/Cargo.toml b/crates/context/Cargo.toml index 742122381..0991898df 100644 --- a/crates/context/Cargo.toml +++ b/crates/context/Cargo.toml @@ -12,6 +12,7 @@ eyre.workspace = true futures-util.workspace = true rand.workspace = true reqwest = { workspace = true, features = ["stream"] } +secp256k1.workspace = true serde.workspace = true tokio = { workspace = true, features = ["sync", "macros"] } tokio-util.workspace = true diff --git a/crates/context/config/Cargo.toml b/crates/context/config/Cargo.toml index 70088b520..fc10058a2 100644 --- a/crates/context/config/Cargo.toml +++ b/crates/context/config/Cargo.toml @@ -21,6 +21,7 @@ near-jsonrpc-client = { workspace = true, optional = true } near-jsonrpc-primitives = { workspace = true, optional = true } near-primitives = { workspace = true, optional = true } reqwest = { workspace = true, optional = true } +secp256k1.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true starknet = { workspace = true, optional = true } diff --git a/crates/context/config/src/client.rs b/crates/context/config/src/client.rs index 2cba2ae7b..fea8574bb 100644 --- a/crates/context/config/src/client.rs +++ b/crates/context/config/src/client.rs @@ -14,12 +14,15 @@ pub mod transport; pub mod utils; use config::{ClientConfig, ClientSelectedSigner, Credentials, LocalConfig}; -use protocol::{icp, near, starknet, Protocol}; +use protocol::{evm, icp, near, starknet, Protocol}; use transport::{Both, Transport, TransportArguments, TransportRequest, UnsupportedProtocol}; pub type LocalTransports = Both< near::NearTransport<'static>, - Both, icp::IcpTransport<'static>>, + Both< + starknet::StarknetTransport<'static>, + Both, evm::EvmTransport<'static>>, + >, >; pub type AnyTransport = Either; @@ -66,7 +69,7 @@ impl Client { credentials.account_id.clone(), credentials.secret_key.clone(), ), - Credentials::Starknet(_) | Credentials::Icp(_) => { + Credentials::Starknet(_) | Credentials::Icp(_) | Credentials::Evm(_) => { eyre::bail!( "Expected Near credentials but got {:?}", config.credentials @@ -94,7 +97,7 @@ impl Client { Credentials::Starknet(credentials) => { (credentials.account_id, credentials.secret_key) } - Credentials::Near(_) | Credentials::Icp(_) => { + Credentials::Near(_) | Credentials::Icp(_) | Credentials::Evm(_) => { eyre::bail!( "Expected Starknet credentials but got {:?}", config.credentials @@ -123,7 +126,7 @@ impl Client { credentials.account_id.clone(), credentials.secret_key.clone(), ), - Credentials::Near(_) | Credentials::Starknet(_) => { + Credentials::Near(_) | Credentials::Starknet(_) | Credentials::Evm(_) => { eyre::bail!("Expected ICP credentials but got {:?}", config.credentials) } }; @@ -139,11 +142,40 @@ impl Client { .collect::>()?, }); + let evm_transport = evm::EvmTransport::new(&evm::EvmConfig { + networks: config + .icp + .iter() + .map(|(network, config)| { + let (account_id, secret_key) = match &config.credentials { + Credentials::Evm(credentials) => ( + credentials.account_id.clone(), + credentials.secret_key.clone(), + ), + Credentials::Near(_) | Credentials::Starknet(_) | Credentials::Icp(_) => { + eyre::bail!("Expected EVM credentials but got {:?}", config.credentials) + } + }; + Ok(( + network.clone().into(), + evm::NetworkConfig { + rpc_url: config.rpc_url.clone(), + account_id, + access_key: secret_key, + }, + )) + }) + .collect::>()?, + }); + let all_transports = Both { left: near_transport, right: Both { left: starknet_transport, - right: icp_transport, + right: Both { + left: icp_transport, + right: evm_transport, + }, }, }; diff --git a/crates/context/config/src/client/config.rs b/crates/context/config/src/client/config.rs index b09a150e9..8fa51a845 100644 --- a/crates/context/config/src/client/config.rs +++ b/crates/context/config/src/client/config.rs @@ -4,6 +4,7 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; use url::Url; +use crate::client::protocol::evm::Credentials as EvmCredentials; use crate::client::protocol::icp::Credentials as IcpCredentials; use crate::client::protocol::near::Credentials as NearCredentials; use crate::client::protocol::starknet::Credentials as StarknetCredentials; @@ -26,6 +27,7 @@ pub struct LocalConfig { pub near: BTreeMap, pub starknet: BTreeMap, pub icp: BTreeMap, + pub evm: BTreeMap, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -65,4 +67,5 @@ pub enum Credentials { Near(NearCredentials), Starknet(StarknetCredentials), Icp(IcpCredentials), + Evm(EvmCredentials), } diff --git a/crates/context/config/src/client/protocol.rs b/crates/context/config/src/client/protocol.rs index e3c3ad7b4..6543ae8c0 100644 --- a/crates/context/config/src/client/protocol.rs +++ b/crates/context/config/src/client/protocol.rs @@ -1,3 +1,4 @@ +pub mod evm; pub mod icp; pub mod near; pub mod starknet; diff --git a/crates/context/config/src/client/protocol/evm.rs b/crates/context/config/src/client/protocol/evm.rs new file mode 100644 index 000000000..c7aa62948 --- /dev/null +++ b/crates/context/config/src/client/protocol/evm.rs @@ -0,0 +1,171 @@ +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::vec; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use url::Url; + +use super::Protocol; +use crate::client::transport::{ + AssociatedTransport, Operation, ProtocolTransport, TransportRequest, +}; + +#[derive(Copy, Clone, Debug)] +pub enum Evm {} + +impl Protocol for Evm { + const PROTOCOL: &'static str = "evm"; +} + +impl AssociatedTransport for EvmTransport<'_> { + type Protocol = Evm; +} +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(try_from = "serde_creds::Credentials")] +pub struct Credentials { + pub account_id: String, + pub public_key: String, + pub secret_key: String, +} + +mod serde_creds { + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Deserialize, Serialize)] + pub struct Credentials { + account_id: String, + public_key: String, + secret_key: String, + } + + impl TryFrom for super::Credentials { + type Error = &'static str; + + fn try_from(creds: Credentials) -> Result { + Ok(Self { + account_id: creds.account_id, + public_key: creds.public_key, + secret_key: creds.secret_key, + }) + } + } +} + +#[derive(Debug)] +pub struct NetworkConfig { + pub rpc_url: Url, + pub account_id: String, + pub access_key: String, +} + +#[derive(Debug)] +pub struct EvmConfig<'a> { + pub networks: BTreeMap, NetworkConfig>, +} + +#[derive(Clone, Debug)] +struct Network { + client: String, + account_id: String, + secret_key: String, +} + +#[derive(Clone, Debug)] +pub struct EvmTransport<'a> { + networks: BTreeMap, Network>, +} + +impl<'a> EvmTransport<'a> { + #[must_use] + pub fn new(config: &EvmConfig<'a>) -> Self { + let mut networks = BTreeMap::new(); + + for (network_id, network_config) in &config.networks { + let client = "client".to_string(); + + let _ignored = networks.insert( + network_id.clone(), + Network { + client, + account_id: network_config.account_id.clone(), + secret_key: network_config.access_key.clone(), + }, + ); + } + + Self { networks } + } +} + +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum EvmError { + #[error("unknown network `{0}`")] + UnknownNetwork(String), + #[error("invalid response from RPC while {operation}")] + InvalidResponse { operation: ErrorOperation }, + #[error("error while {operation}: {reason}")] + Custom { + operation: ErrorOperation, + reason: String, + }, +} + +#[derive(Copy, Clone, Debug, Error)] +#[non_exhaustive] +pub enum ErrorOperation { + #[error("querying contract")] + Query, + #[error("mutating contract")] + Mutate, +} + +impl ProtocolTransport for EvmTransport<'_> { + type Error = EvmError; + + async fn send( + &self, + request: TransportRequest<'_>, + payload: Vec, + ) -> Result, Self::Error> { + let Some(network) = self.networks.get(&request.network_id) else { + return Err(EvmError::UnknownNetwork(request.network_id.into_owned())); + }; + + let contract_id = request.contract_id.into_owned(); + + match request.operation { + Operation::Read { method } => { + network + .query(contract_id, method.into_owned(), payload) + .await + } + Operation::Write { method } => { + network + .mutate(contract_id, method.into_owned(), payload) + .await + } + } + } +} + +impl Network { + async fn query( + &self, + _contract_id: String, + _method: String, + _args: Vec, + ) -> Result, EvmError> { + Ok(vec![]) + } + + async fn mutate( + &self, + _contract_id: String, + _method: String, + _args: Vec, + ) -> Result, EvmError> { + Ok(vec![]) + } +} diff --git a/crates/merod/Cargo.toml b/crates/merod/Cargo.toml index 367e9a575..3b2588e38 100644 --- a/crates/merod/Cargo.toml +++ b/crates/merod/Cargo.toml @@ -17,6 +17,7 @@ const_format.workspace = true ic-agent.workspace = true dirs.workspace = true ed25519-consensus.workspace = true +ethereum-types.workspace = true eyre.workspace = true futures-util.workspace = true hex.workspace = true @@ -24,7 +25,9 @@ libp2p.workspace = true multiaddr.workspace = true near-crypto.workspace = true rand.workspace = true +secp256k1 = { workspace = true, features = ["rand"] } starknet.workspace = true +tiny-keccak = { workspace = true, features = ["keccak"] } tokio = { workspace = true, features = ["io-std", "macros"] } toml_edit.workspace = true tracing.workspace = true diff --git a/crates/merod/src/cli/init.rs b/crates/merod/src/cli/init.rs index c032767f9..7f533b20e 100644 --- a/crates/merod/src/cli/init.rs +++ b/crates/merod/src/cli/init.rs @@ -12,7 +12,7 @@ use calimero_context_config::client::config::{ ClientSigner, Credentials, LocalConfig, }; use calimero_context_config::client::protocol::{ - icp as icp_protocol, near as near_protocol, starknet as starknet_protocol, + evm as evm_protocol, icp as icp_protocol, near as near_protocol, starknet as starknet_protocol, }; use calimero_network::config::{ BootstrapConfig, BootstrapNodes, DiscoveryConfig, RelayConfig, RendezvousConfig, SwarmConfig, @@ -33,7 +33,9 @@ use libp2p::identity::Keypair; use multiaddr::{Multiaddr, Protocol}; use near_crypto::{KeyType, SecretKey}; use rand::rngs::OsRng; +use secp256k1::Secp256k1; use starknet::signers::SigningKey; +use tiny_keccak::{Hasher, Keccak}; use tracing::{info, warn}; use url::Url; @@ -44,6 +46,7 @@ pub enum ConfigProtocol { Near, Starknet, Icp, + Evm, } impl ConfigProtocol { @@ -52,6 +55,7 @@ impl ConfigProtocol { ConfigProtocol::Near => "near", ConfigProtocol::Starknet => "starknet", ConfigProtocol::Icp => "icp", + ConfigProtocol::Evm => "evm", } } } @@ -227,6 +231,7 @@ impl InitCommand { ConfigProtocol::Near => ClientSelectedSigner::Relayer, ConfigProtocol::Starknet => ClientSelectedSigner::Relayer, ConfigProtocol::Icp => ClientSelectedSigner::Local, + ConfigProtocol::Evm => ClientSelectedSigner::Relayer, }, relayer: ClientRelayerSigner { url: relayer }, local: LocalConfig { @@ -285,6 +290,24 @@ impl InitCommand { ] .into_iter() .collect(), + evm: [ + ( + "mainnet".to_owned(), + generate_local_signer( + "https://mainnet.infura.io".parse()?, + ConfigProtocol::Evm, + )?, + ), + ( + "sepolia".to_owned(), + generate_local_signer( + "https://sepolia.infura.io".parse()?, + ConfigProtocol::Evm, + )?, + ), + ] + .into_iter() + .collect(), }, }, new: ClientNew { @@ -292,6 +315,7 @@ impl InitCommand { ConfigProtocol::Near => "testnet".into(), ConfigProtocol::Starknet => "sepolia".into(), ConfigProtocol::Icp => "local".into(), + ConfigProtocol::Evm => "sepolia".into(), }, protocol: self.protocol.as_str().to_owned(), contract_id: match self.protocol { @@ -301,6 +325,10 @@ impl InitCommand { .parse()? } ConfigProtocol::Icp => "bkyz2-fmaaa-aaaaa-qaaaq-cai".parse()?, + ConfigProtocol::Evm => { + "0x1b991ee006e2d1e372ab96d0a957401fa200358f317b681df2948f30e17c29c" + .parse()? + } }, }, }, @@ -370,5 +398,29 @@ fn generate_local_signer( }), }) } + ConfigProtocol::Evm => { + let secp = Secp256k1::new(); + let (secret_key, public_key) = secp.generate_keypair(&mut OsRng); + + let secret_key_hex = encode(secret_key.secret_bytes()); + let public_key_hex = encode(&public_key.serialize()); + + let public_key_bytes = public_key.serialize_uncompressed(); + let mut hasher = Keccak::v256(); + hasher.update(&public_key_bytes[1..]); + let mut hash = [0u8; 32]; + hasher.finalize(&mut hash); + + let address = format!("0x{}", encode(&hash[12..])); + + Ok(ClientLocalSigner { + rpc_url, + credentials: Credentials::Evm(evm_protocol::Credentials { + account_id: address, + public_key: public_key_hex, + secret_key: secret_key_hex, + }), + }) + } } }