From deeea22699c619617e41ac28ffe1f4d6ca9f3f87 Mon Sep 17 00:00:00 2001 From: Fran Domovic <93442516+frdomovic@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:53:24 +0100 Subject: [PATCH] feat(node): implement starknet json rpc query method + logic (#849) Co-authored-by: alenmestrov --- .husky/pre-commit | 1 - Cargo.lock | 206 ++++++++---- Cargo.toml | 7 +- crates/context/Cargo.toml | 1 + crates/context/config/Cargo.toml | 3 + crates/context/config/src/client.rs | 97 +++++- crates/context/config/src/client/config.rs | 118 +++---- crates/context/config/src/client/near.rs | 67 +++- crates/context/config/src/client/relayer.rs | 3 + crates/context/config/src/client/starknet.rs | 302 ++++++++++++++++++ crates/context/src/lib.rs | 19 +- crates/merod/Cargo.toml | 2 + crates/merod/src/cli/config.rs | 18 ++ crates/merod/src/cli/init.rs | 127 ++++++-- crates/merod/src/cli/relay.rs | 72 ++++- .../node/src/interactive_cli/transactions.rs | 3 +- crates/primitives/src/context.rs | 9 +- crates/server/Cargo.toml | 3 +- .../src/verifywalletsignatures/starknet.rs | 8 +- crates/store/src/types/context.rs | 9 +- 20 files changed, 877 insertions(+), 198 deletions(-) create mode 100644 crates/context/config/src/client/starknet.rs diff --git a/.husky/pre-commit b/.husky/pre-commit index 39bb3f979..3ed7257d8 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -13,7 +13,6 @@ fi if git diff --cached --name-only | grep -qE '\.rs$'; then echo "Running checks for the Rust code..." cargo +nightly fmt - cargo clippy fi # Check for changes in the 'node-ui' directory (Next.js app) diff --git a/Cargo.lock b/Cargo.lock index 6ad058fa2..fd5fdfb87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -865,6 +865,7 @@ dependencies = [ "calimero-primitives", "calimero-store", "camino", + "clap", "ed25519-dalek", "eyre", "futures-util", @@ -891,6 +892,9 @@ dependencies = [ "reqwest 0.12.5", "serde", "serde_json", + "starknet", + "starknet-crypto", + "starknet-types-core", "thiserror", "url", ] @@ -1050,7 +1054,7 @@ dependencies = [ "near-account-id", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with", "thiserror", ] @@ -1089,9 +1093,8 @@ dependencies = [ "serde", "serde_json", "sha2", - "starknet-core", + "starknet", "starknet-crypto", - "starknet-providers", "thiserror", "tokio", "tower", @@ -2241,6 +2244,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest 0.10.7", + "hex", + "hmac", + "pbkdf2", + "rand 0.8.5", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror", + "uuid 0.8.2", +] + [[package]] name = "ethabi" version = "18.0.0" @@ -3655,9 +3680,9 @@ dependencies = [ [[package]] name = "lambdaworks-crypto" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb5d4f22241504f7c7b8d2c3a7d7835d7c07117f10bff2a7d96a9ef6ef217c3" +checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ "lambdaworks-math", "serde", @@ -3667,9 +3692,9 @@ dependencies = [ [[package]] name = "lambdaworks-math" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "358e172628e713b80a530a59654154bfc45783a6ed70ea284839800cebdf8f97" +checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" dependencies = [ "serde", "serde_json", @@ -4504,6 +4529,8 @@ dependencies = [ "multiaddr", "near-crypto", "rand 0.8.5", + "starknet", + "starknet-crypto", "tokio", "toml_edit 0.22.20", "tracing", @@ -4876,7 +4903,7 @@ dependencies = [ "rand_chacha", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with", "sha3", "smart-default", "strum 0.24.1", @@ -5844,9 +5871,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -6008,9 +6035,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -6649,6 +6676,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -6724,6 +6760,18 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac", + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "sct" version = "0.7.1" @@ -6935,22 +6983,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" -dependencies = [ - "base64 0.13.1", - "chrono", - "hex", - "indexmap 1.9.3", - "serde", - "serde_json", - "serde_with_macros 2.3.3", - "time", -] - [[package]] name = "serde_with" version = "3.9.0" @@ -6965,22 +6997,10 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "serde_with_macros 3.9.0", + "serde_with_macros", "time", ] -[[package]] -name = "serde_with_macros" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" -dependencies = [ - "darling 0.20.10", - "proc-macro2", - "quote", - "syn 2.0.72", -] - [[package]] name = "serde_with_macros" version = "3.9.0" @@ -7233,11 +7253,56 @@ dependencies = [ "winapi", ] +[[package]] +name = "starknet" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0c9ac3809cc7630784e8c8565fa3013af819d83c97aa2720d566016d439011" +dependencies = [ + "starknet-accounts", + "starknet-contract", + "starknet-core", + "starknet-crypto", + "starknet-macros", + "starknet-providers", + "starknet-signers", +] + +[[package]] +name = "starknet-accounts" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee27ded58ade61da410fccafd57ed5429b0e79a9d62a4ae8b65818cb9d6f400" +dependencies = [ + "async-trait", + "auto_impl", + "starknet-core", + "starknet-crypto", + "starknet-providers", + "starknet-signers", + "thiserror", +] + +[[package]] +name = "starknet-contract" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6ee5762d24c4f06ab7e9406550925df406712e73719bd2de905c879c674a87" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "starknet-accounts", + "starknet-core", + "starknet-providers", + "thiserror", +] + [[package]] name = "starknet-core" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d506e02a4083290d13b427dfe437fd95aa8b56315c455bb2f9cdeca76620d457" +checksum = "2538240cbe6663c673fe77465f294da707080f39678dd7066761554899e46100" dependencies = [ "base64 0.21.7", "crypto-bigint", @@ -7246,7 +7311,7 @@ dependencies = [ "serde", "serde_json", "serde_json_pythonic", - "serde_with 2.3.3", + "serde_with", "sha3", "starknet-crypto", "starknet-types-core", @@ -7254,9 +7319,9 @@ dependencies = [ [[package]] name = "starknet-crypto" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2a821ad8d98c6c3e4d0e5097f3fe6e2ed120ada9d32be87cd1330c7923a2f0" +checksum = "60a5064173a8e8d2675e67744fd07f310de44573924b6b7af225a6bdd8102913" dependencies = [ "crypto-bigint", "hex", @@ -7266,37 +7331,35 @@ dependencies = [ "num-traits", "rfc6979", "sha2", - "starknet-crypto-codegen", "starknet-curve", "starknet-types-core", "zeroize", ] [[package]] -name = "starknet-crypto-codegen" -version = "0.4.0" +name = "starknet-curve" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e179dedc3fa6da064e56811d3e05d446aa2f7459e4eb0e3e49378a337235437" +checksum = "bcde6bd74269b8161948190ace6cf069ef20ac6e79cd2ba09b320efa7500b6de" dependencies = [ - "starknet-curve", "starknet-types-core", - "syn 2.0.72", ] [[package]] -name = "starknet-curve" -version = "0.5.0" +name = "starknet-macros" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56935b306dcf0b8f14bb2a1257164b8478bb8be4801dfae0923f5b266d1b457c" +checksum = "8986a940af916fc0a034f4e42c6ba76d94f1e97216d75447693dfd7aefaf3ef2" dependencies = [ - "starknet-types-core", + "starknet-core", + "syn 2.0.72", ] [[package]] name = "starknet-providers" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c85e0a0f4563ae95dfeae14ea0f0c70610efc0ec2462505c64eff5765e7b97" +checksum = "60e8e69ba7a36dea2d28333be82b4011f8784333d3ae5618482b6587c1ffb66c" dependencies = [ "async-trait", "auto_impl", @@ -7307,17 +7370,34 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "serde_with 2.3.3", + "serde_with", "starknet-core", "thiserror", "url", ] +[[package]] +name = "starknet-signers" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b9e01b61ae51d722e2b100d6ef913c5a2e70d1ea672733d385f7296d6055ef" +dependencies = [ + "async-trait", + "auto_impl", + "crypto-bigint", + "eth-keystore", + "getrandom", + "rand 0.8.5", + "starknet-core", + "starknet-crypto", + "thiserror", +] + [[package]] name = "starknet-types-core" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6bacf0ba19bc721e518bc4bf389ff13daa8a7c5db5fd320600473b8aa9fcbd" +checksum = "fa1b9e01ccb217ab6d475c5cda05dbb22c30029f7bb52b192a010a00d77a3d74" dependencies = [ "lambdaworks-crypto", "lambdaworks-math", @@ -8289,6 +8369,10 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] [[package]] name = "uuid" diff --git a/Cargo.toml b/Cargo.toml index cd1b12dbe..6d492eb76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ owo-colors = "3.5.0" parking_lot = "0.12.3" prettyplease = "0.2.17" proc-macro2 = "1.0" -quote = "1.0" +quote = "1.0.37" rand = "0.8.5" rand_chacha = "0.3.1" reqwest = "0.12.2" @@ -96,10 +96,9 @@ semver = "1.0.22" serde = "1.0.196" serde_json = "1.0.113" serde_with = "3.9.0" -starknet-core = "0.11.1" +starknet = "0.12.0" starknet-crypto = "0.7.1" -starknet-macros = "0.2.0" -starknet-providers = "0.11.0" +starknet-types-core = "0.1.7" strum = "0.26.2" syn = "2.0" tempdir = "0.3.7" diff --git a/crates/context/Cargo.toml b/crates/context/Cargo.toml index 86e8d3d17..95abc9075 100644 --- a/crates/context/Cargo.toml +++ b/crates/context/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] camino = { workspace = true, features = ["serde1"] } +clap.workspace = true ed25519-dalek.workspace = true eyre.workspace = true futures-util.workspace = true diff --git a/crates/context/config/Cargo.toml b/crates/context/config/Cargo.toml index 4eae2b8fa..1fbebc8f5 100644 --- a/crates/context/config/Cargo.toml +++ b/crates/context/config/Cargo.toml @@ -18,6 +18,9 @@ near-primitives = { workspace = true, optional = true } reqwest = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +starknet.workspace = true +starknet-crypto.workspace = true +starknet-types-core.workspace = true thiserror.workspace = true url = { workspace = true, optional = true } diff --git a/crates/context/config/src/client.rs b/crates/context/config/src/client.rs index a34905eca..258607eba 100644 --- a/crates/context/config/src/client.rs +++ b/crates/context/config/src/client.rs @@ -18,8 +18,9 @@ use crate::{ContextRequest, ContextRequestKind, Request, RequestKind}; pub mod config; pub mod near; pub mod relayer; +pub mod starknet; -use config::{ContextConfigClientConfig, ContextConfigClientSelectedSigner}; +use config::{ContextConfigClientConfig, ContextConfigClientSelectedSigner, Credentials, Protocol}; pub trait Transport { type Error: CoreError; @@ -50,6 +51,7 @@ impl Transport for Either { #[derive(Debug)] #[non_exhaustive] pub struct TransportRequest<'a> { + pub protocol: Protocol, pub network_id: Cow<'a, str>, pub contract_id: Cow<'a, str>, pub operation: Operation<'a>, @@ -58,11 +60,13 @@ pub struct TransportRequest<'a> { impl<'a> TransportRequest<'a> { #[must_use] pub const fn new( + protocol: Protocol, network_id: Cow<'a, str>, contract_id: Cow<'a, str>, operation: Operation<'a>, ) -> Self { Self { + protocol, network_id, contract_id, operation, @@ -88,36 +92,103 @@ impl ContextConfigClient { } } -pub type RelayOrNearTransport = Either>; +pub type AnyTransport = Either< + relayer::RelayerTransport, + BothTransport, starknet::StarknetTransport<'static>>, +>; -impl ContextConfigClient { +#[expect(clippy::exhaustive_structs, reason = "this is exhaustive")] +#[derive(Debug, Clone)] +pub struct BothTransport { + pub near: L, + pub starknet: R, +} + +impl Transport for BothTransport { + type Error = Either; + + async fn send( + &self, + request: TransportRequest<'_>, + payload: Vec, + ) -> Result, Self::Error> { + match request.protocol { + Protocol::Near => self.near.send(request, payload).await.map_err(Either::Left), + Protocol::Starknet => self + .starknet + .send(request, payload) + .await + .map_err(Either::Right), + } + } +} + +impl ContextConfigClient { #[must_use] pub fn from_config(config: &ContextConfigClientConfig) -> Self { let transport = match config.signer.selected { ContextConfigClientSelectedSigner::Relayer => { + // If the selected signer is Relayer, use the Left variant. Either::Left(relayer::RelayerTransport::new(&relayer::RelayerConfig { url: config.signer.relayer.url.clone(), })) } - ContextConfigClientSelectedSigner::Local => { - Either::Right(near::NearTransport::new(&near::NearConfig { + + ContextConfigClientSelectedSigner::Local => Either::Right(BothTransport { + near: near::NearTransport::new(&near::NearConfig { networks: config .signer .local + .near .iter() .map(|(network, config)| { + let (account_id, secret_key) = match &config.credentials { + Credentials::Near(credentials) => ( + credentials.account_id.clone(), + credentials.secret_key.clone(), + ), + Credentials::Starknet(_) => { + panic!("Expected Near credentials but got something else.") + } + }; ( network.clone().into(), near::NetworkConfig { rpc_url: config.rpc_url.clone(), - account_id: config.credentials.account_id.clone(), - access_key: config.credentials.secret_key.clone(), + account_id, + access_key: secret_key, }, ) }) .collect(), - })) - } + }), + starknet: starknet::StarknetTransport::new(&starknet::StarknetConfig { + networks: config + .signer + .local + .starknet + .iter() + .map(|(network, config)| { + let (account_id, secret_key) = match &config.credentials { + Credentials::Starknet(credentials) => { + (credentials.account_id, credentials.secret_key) + } + Credentials::Near(_) => { + panic!("Expected Starknet credentials but got something else.") + } + }; + ( + network.clone().into(), + starknet::NetworkConfig { + rpc_url: config.rpc_url.clone(), + account_id, + access_key: secret_key, + }, + ) + }) + .collect(), + }), + }), }; Self::new(transport) @@ -127,10 +198,12 @@ impl ContextConfigClient { impl ContextConfigClient { pub const fn query<'a>( &'a self, + protocol: Protocol, network_id: Cow<'a, str>, contract_id: Cow<'a, str>, ) -> ContextConfigQueryClient<'a, T> { ContextConfigQueryClient { + protocol, network_id, contract_id, transport: &self.transport, @@ -139,11 +212,13 @@ impl ContextConfigClient { pub const fn mutate<'a>( &'a self, + protocol: Protocol, network_id: Cow<'a, str>, contract_id: Cow<'a, str>, signer_id: SignerId, ) -> ContextConfigMutateClient<'a, T> { ContextConfigMutateClient { + protocol, network_id, contract_id, signer_id, @@ -185,6 +260,7 @@ impl Response { #[derive(Debug)] pub struct ContextConfigQueryClient<'a, T> { + protocol: Protocol, network_id: Cow<'a, str>, contract_id: Cow<'a, str>, transport: &'a T, @@ -199,6 +275,7 @@ impl<'a, T: Transport> ContextConfigQueryClient<'a, T> { let payload = serde_json::to_vec(&body).map_err(|err| ConfigError::Other(err.into()))?; let request = TransportRequest { + protocol: self.protocol, network_id: Cow::Borrowed(&self.network_id), contract_id: Cow::Borrowed(&self.contract_id), operation: Operation::Read { @@ -267,6 +344,7 @@ impl<'a, T: Transport> ContextConfigQueryClient<'a, T> { #[derive(Debug)] pub struct ContextConfigMutateClient<'a, T> { + protocol: Protocol, network_id: Cow<'a, str>, contract_id: Cow<'a, str>, signer_id: SignerId, @@ -284,6 +362,7 @@ impl ClientRequest<'_, '_, T> { let signed = Signed::new(&Request::new(self.client.signer_id, self.kind), sign)?; let request = TransportRequest { + protocol: self.client.protocol, network_id: Cow::Borrowed(&self.client.network_id), contract_id: Cow::Borrowed(&self.client.contract_id), operation: Operation::Write { diff --git a/crates/context/config/src/client/config.rs b/crates/context/config/src/client/config.rs index 8d3b9f7c3..f2dbe4396 100644 --- a/crates/context/config/src/client/config.rs +++ b/crates/context/config/src/client/config.rs @@ -1,21 +1,65 @@ #![allow(clippy::exhaustive_structs, reason = "TODO: Allowed until reviewed")] use std::collections::BTreeMap; +use std::str::FromStr; -use near_crypto::{PublicKey, SecretKey}; -use near_primitives::types::AccountId; use serde::{Deserialize, Serialize}; +use thiserror::Error; use url::Url; +use super::{near, starknet}; + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ContextConfigClientConfig { pub new: ContextConfigClientNew, pub signer: ContextConfigClientSigner, } +#[non_exhaustive] +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Protocol { + Near, + Starknet, +} + +impl Protocol { + pub fn as_str(&self) -> &'static str { + match self { + Protocol::Near => "near", + Protocol::Starknet => "starknet", + } + } +} + +#[derive(Debug, Error, Copy, Clone)] +#[error("Failed to parse protocol")] +pub struct ProtocolParseError { + _priv: (), +} + +impl FromStr for Protocol { + type Err = ProtocolParseError; + + fn from_str(input: &str) -> Result { + match input.to_lowercase().as_str() { + "near" => Ok(Protocol::Near), + "starknet" => Ok(Protocol::Starknet), + _ => Err(ProtocolParseError { _priv: () }), + } + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ContextConfigClientNew { + pub protocol: Protocol, pub network: String, - pub contract_id: AccountId, + pub contract_id: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct LocalConfig { + pub near: BTreeMap, + pub starknet: BTreeMap, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -24,7 +68,7 @@ pub struct ContextConfigClientSigner { pub selected: ContextConfigClientSelectedSigner, pub relayer: ContextConfigClientRelayerSigner, #[serde(rename = "self")] - pub local: BTreeMap, + pub local: LocalConfig, } #[derive(Copy, Clone, Debug, Deserialize, Serialize)] @@ -48,66 +92,10 @@ pub struct ContextConfigClientLocalSigner { pub credentials: Credentials, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(try_from = "serde_creds::Credentials")] -pub struct Credentials { - pub account_id: AccountId, - pub public_key: PublicKey, - pub secret_key: SecretKey, -} - -mod serde_creds { - use near_crypto::{PublicKey, SecretKey}; - use near_primitives::types::AccountId; - use serde::{Deserialize, Serialize}; - - #[derive(Debug, Deserialize, Serialize)] - pub struct Credentials { - account_id: AccountId, - public_key: PublicKey, - secret_key: SecretKey, - } - - impl TryFrom for super::Credentials { - type Error = &'static str; - - fn try_from(creds: Credentials) -> Result { - 'pass: { - if let SecretKey::ED25519(key) = &creds.secret_key { - let mut buf = [0; 32]; - - buf.copy_from_slice(&key.0[..32]); - - if ed25519_dalek::SigningKey::from_bytes(&buf) - .verifying_key() - .as_bytes() - == &key.0[32..] - { - break 'pass; - } - } else if creds.public_key == creds.secret_key.public_key() { - break 'pass; - } - - return Err("public key and secret key do not match"); - }; - - if creds.account_id.get_account_type().is_implicit() { - let Ok(public_key) = PublicKey::from_near_implicit_account(&creds.account_id) - else { - return Err("fatal: failed to derive public key from implicit account ID"); - }; - - if creds.public_key != public_key { - return Err("implicit account ID and public key do not match"); - } - } - - Ok(Self { - account_id: creds.account_id, - public_key: creds.public_key, - secret_key: creds.secret_key, - }) - } - } +#[serde(untagged)] +pub enum Credentials { + Near(near::Credentials), + Starknet(starknet::Credentials), } diff --git a/crates/context/config/src/client/near.rs b/crates/context/config/src/client/near.rs index 8217bebc2..063a970f5 100644 --- a/crates/context/config/src/client/near.rs +++ b/crates/context/config/src/client/near.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use std::time; pub use near_crypto::SecretKey; -use near_crypto::{InMemorySigner, Signer}; +use near_crypto::{InMemorySigner, PublicKey, Signer}; use near_jsonrpc_client::methods::query::{RpcQueryRequest, RpcQueryResponse}; use near_jsonrpc_client::methods::send_tx::RpcSendTransactionRequest; use near_jsonrpc_client::methods::tx::RpcTransactionStatusRequest; @@ -22,11 +22,76 @@ use near_primitives::views::{ AccessKeyPermissionView, AccessKeyView, CallResult, FinalExecutionStatus, QueryRequest, TxExecutionStatus, }; +use serde::{Deserialize, Serialize}; use thiserror::Error; use url::Url; use super::{Operation, Transport, TransportRequest}; +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(try_from = "serde_creds::Credentials")] +pub struct Credentials { + pub account_id: AccountId, + pub public_key: PublicKey, + pub secret_key: SecretKey, +} + +mod serde_creds { + use near_crypto::{PublicKey, SecretKey}; + use near_primitives::types::AccountId; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Deserialize, Serialize)] + pub struct Credentials { + account_id: AccountId, + public_key: PublicKey, + secret_key: SecretKey, + } + + impl TryFrom for super::Credentials { + type Error = &'static str; + + fn try_from(creds: Credentials) -> Result { + 'pass: { + if let SecretKey::ED25519(key) = &creds.secret_key { + let mut buf = [0; 32]; + + buf.copy_from_slice(&key.0[..32]); + + if ed25519_dalek::SigningKey::from_bytes(&buf) + .verifying_key() + .as_bytes() + == &key.0[32..] + { + break 'pass; + } + } else if creds.public_key == creds.secret_key.public_key() { + break 'pass; + } + + return Err("public key and secret key do not match"); + }; + + if creds.account_id.get_account_type().is_implicit() { + let Ok(public_key) = PublicKey::from_near_implicit_account(&creds.account_id) + else { + return Err("fatal: failed to derive public key from implicit account ID"); + }; + + if creds.public_key != public_key { + return Err("implicit account ID and public key do not match"); + } + } + + 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, diff --git a/crates/context/config/src/client/relayer.rs b/crates/context/config/src/client/relayer.rs index 1d06a9993..91f3431dc 100644 --- a/crates/context/config/src/client/relayer.rs +++ b/crates/context/config/src/client/relayer.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; use url::Url; +use super::config::Protocol; use super::{Operation, Transport, TransportRequest}; #[derive(Debug)] @@ -32,6 +33,7 @@ impl RelayerTransport { #[derive(Debug, Serialize, Deserialize)] #[non_exhaustive] pub struct RelayRequest<'a> { + pub protocol: Protocol, pub network_id: Cow<'a, str>, pub contract_id: Cow<'a, str>, pub operation: Operation<'a>, @@ -50,6 +52,7 @@ impl Transport for RelayerTransport { .client .post(self.url.clone()) .json(&RelayRequest { + protocol: request.protocol, network_id: request.network_id, contract_id: request.contract_id, operation: request.operation, diff --git a/crates/context/config/src/client/starknet.rs b/crates/context/config/src/client/starknet.rs new file mode 100644 index 000000000..f29746f1e --- /dev/null +++ b/crates/context/config/src/client/starknet.rs @@ -0,0 +1,302 @@ +#![allow(clippy::exhaustive_structs, reason = "TODO: Allowed until reviewed")] + +use core::str::FromStr; +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; +use starknet::accounts::{Account, ExecutionEncoding, SingleOwnerAccount}; +use starknet::core::types::{BlockId, BlockTag, Call, Felt, FunctionCall}; +use starknet::core::utils::get_selector_from_name; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider, Url}; +use starknet::signers::{LocalWallet, SigningKey}; +use thiserror::Error; + +use super::{Operation, Transport, TransportRequest}; + +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +#[serde(try_from = "serde_creds::Credentials")] +pub struct Credentials { + pub account_id: Felt, + pub public_key: Felt, + pub secret_key: Felt, +} + +mod serde_creds { + use core::str::FromStr; + + use serde::{Deserialize, Serialize}; + use starknet_crypto::Felt; + use starknet_types_core::felt::FromStrError; + use thiserror::Error; + + #[derive(Debug, Deserialize, Serialize)] + pub struct Credentials { + secret_key: String, + public_key: String, + account_id: String, + } + + #[derive(Debug, Error)] + pub enum CredentialsError { + #[error("failed to parse Felt from string")] + ParseError(#[from] FromStrError), + #[error("public key extracted from secret key does not match the provided public key")] + PublicKeyMismatch, + } + + impl TryFrom for super::Credentials { + type Error = CredentialsError; + + fn try_from(creds: Credentials) -> Result { + let secret_key_felt = Felt::from_str(&creds.secret_key) + .map_err(|_| CredentialsError::ParseError(FromStrError))?; + let public_key_felt = Felt::from_str(&creds.public_key) + .map_err(|_| CredentialsError::ParseError(FromStrError))?; + let extracted_public_key = starknet_crypto::get_public_key(&secret_key_felt); + + if public_key_felt != extracted_public_key { + return Err(CredentialsError::PublicKeyMismatch); + } + + let account_id_felt = Felt::from_str(&creds.account_id) + .map_err(|_| CredentialsError::ParseError(FromStrError))?; + + Ok(Self { + account_id: account_id_felt, + public_key: public_key_felt, + secret_key: secret_key_felt, + }) + } + } +} + +#[derive(Debug)] +pub struct NetworkConfig { + pub rpc_url: Url, + pub account_id: Felt, + pub access_key: Felt, +} + +#[derive(Debug)] +pub struct StarknetConfig<'a> { + pub networks: BTreeMap, NetworkConfig>, +} + +#[derive(Clone, Debug)] +struct Network { + client: Arc>, + account_id: Felt, + secret_key: Felt, +} + +#[derive(Clone, Debug)] +pub struct StarknetTransport<'a> { + networks: BTreeMap, Network>, +} + +impl<'a> StarknetTransport<'a> { + #[must_use] + pub fn new(config: &StarknetConfig<'a>) -> Self { + let mut networks = BTreeMap::new(); + + for (network_id, network_config) in &config.networks { + let client = JsonRpcClient::new(HttpTransport::new(network_config.rpc_url.clone())); + let _ignored = networks.insert( + network_id.clone(), + Network { + client: client.into(), + account_id: network_config.account_id, + secret_key: network_config.access_key, + }, + ); + } + Self { networks } + } +} + +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum StarknetError { + #[error("unknown network `{0}`")] + UnknownNetwork(String), + #[error("invalid response from RPC while {operation}")] + InvalidResponse { operation: ErrorOperation }, + #[error("invalid contract ID `{0}`")] + InvalidContractId(String), + #[error("invalid method name `{0}`")] + InvalidMethodName(String), + #[error("access key does not have permission to call contract `{0}`")] + NotPermittedToCallContract(String), + #[error( + "access key does not have permission to call method `{method}` on contract {contract}" + )] + NotPermittedToCallMethod { contract: String, method: String }, + #[error("transaction timed out")] + TransactionTimeout, + #[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, + #[error("fetching account")] + FetchAccount, + #[error("fetching nonce")] + FetchNonce, +} + +impl Transport for StarknetTransport<'_> { + type Error = StarknetError; + + async fn send( + &self, + request: TransportRequest<'_>, + payload: Vec, + ) -> Result, Self::Error> { + let Some(network) = self.networks.get(&request.network_id) else { + return Err(StarknetError::UnknownNetwork( + request.network_id.into_owned(), + )); + }; + + let contract_id = request.contract_id.as_ref(); + + match request.operation { + Operation::Read { method } => { + let response = network.query(contract_id, &method, payload).await?; + Ok(response) + } + Operation::Write { method } => { + let response = network.mutate(contract_id, &method, payload).await?; + Ok(response) + } + } + } +} + +impl Network { + async fn query( + &self, + contract_id: &str, + method: &str, + args: Vec, + ) -> Result, StarknetError> { + let contract_id = Felt::from_str(contract_id) + .map_err(|_| StarknetError::InvalidContractId(contract_id.to_owned()))?; + + let entry_point_selector = get_selector_from_name(method) + .map_err(|_| StarknetError::InvalidMethodName(method.to_owned()))?; + + let calldata: Vec = if args.is_empty() { + vec![] + } else { + args.chunks(32) + .map(|chunk| { + let mut padded_chunk = [0_u8; 32]; + for (i, byte) in chunk.iter().enumerate() { + padded_chunk[i] = *byte; + } + Felt::from_bytes_be(&padded_chunk) + }) + .collect() + }; + + let function_call = FunctionCall { + contract_address: contract_id, + entry_point_selector, + calldata, + }; + + let response = self + .client + .call(&function_call, BlockId::Tag(BlockTag::Latest)) + .await; + + response.map_or( + Err(StarknetError::InvalidResponse { + operation: ErrorOperation::Query, + }), + |result| { + Ok(result + .into_iter() + .flat_map(|felt| felt.to_bytes_be().to_vec()) + .collect::>()) + }, + ) + } + + async fn mutate( + &self, + contract_id: &str, + method: &str, + args: Vec, + ) -> Result, StarknetError> { + let sender_address: Felt = self.account_id; + let secret_key: Felt = self.secret_key; + let contract_id = Felt::from_str(contract_id) + .map_err(|_| StarknetError::InvalidContractId(contract_id.to_owned()))?; + + let entry_point_selector = get_selector_from_name(method) + .map_err(|_| StarknetError::InvalidMethodName(method.to_owned()))?; + + let calldata: Vec = if args.is_empty() { + vec![] + } else { + args.chunks(32) + .map(|chunk| { + let mut padded_chunk = [0_u8; 32]; + for (i, byte) in chunk.iter().enumerate() { + padded_chunk[i] = *byte; + } + Felt::from_bytes_be(&padded_chunk) + }) + .collect() + }; + + let current_network = match self.client.chain_id().await { + Ok(chain_id) => chain_id, + Err(e) => { + return Err(StarknetError::Custom { + operation: ErrorOperation::Query, + reason: e.to_string(), + }) + } + }; + + let relayer_signing_key = SigningKey::from_secret_scalar(secret_key); + let relayer_wallet = LocalWallet::from(relayer_signing_key); + let mut account = SingleOwnerAccount::new( + Arc::clone(&self.client), + relayer_wallet, + sender_address, + current_network, + ExecutionEncoding::New, + ); + + let _ = account.set_block_id(BlockId::Tag(BlockTag::Pending)); + + let response = account + .execute_v1(vec![Call { + to: contract_id, + selector: entry_point_selector, + calldata, + }]) + .send() + .await + .unwrap(); + + let transaction_hash: Vec = vec![response.transaction_hash.to_bytes_be()[0]]; + Ok(transaction_hash) + } +} diff --git a/crates/context/src/lib.rs b/crates/context/src/lib.rs index 4b500f8d0..242bc5cb7 100644 --- a/crates/context/src/lib.rs +++ b/crates/context/src/lib.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::collections::HashSet; use std::io::Error as IoError; use std::str::FromStr; @@ -5,7 +6,7 @@ use std::sync::Arc; use calimero_blobstore::{Blob, BlobManager, Size}; use calimero_context_config::client::config::ContextConfigClientConfig; -use calimero_context_config::client::{ContextConfigClient, RelayOrNearTransport}; +use calimero_context_config::client::{AnyTransport, ContextConfigClient}; use calimero_context_config::repr::{Repr, ReprBytes, ReprTransmute}; use calimero_context_config::types::{ Application as ApplicationConfig, ApplicationMetadata as ApplicationMetadataConfig, @@ -51,7 +52,7 @@ use config::ContextConfig; pub struct ContextManager { store: Store, client_config: ContextConfigClientConfig, - config_client: ContextConfigClient, + config_client: ContextConfigClient, blob_manager: BlobManager, network_client: NetworkClient, server_sender: ServerSender, @@ -202,6 +203,7 @@ impl ContextManager { this.config_client .mutate( + this.client_config.new.protocol, this.client_config.new.network.as_str().into(), this.client_config.new.contract_id.as_str().into(), context.id.rt().expect("infallible conversion"), @@ -259,6 +261,7 @@ impl ContextManager { handle.put( &ContextConfigKey::new(context.id), &ContextConfigValue::new( + self.client_config.new.protocol.as_str().into(), self.client_config.new.network.as_str().into(), self.client_config.new.contract_id.as_str().into(), ), @@ -296,7 +299,8 @@ impl ContextManager { identity_secret: PrivateKey, invitation_payload: ContextInvitationPayload, ) -> EyreResult> { - let (context_id, invitee_id, network_id, contract_id) = invitation_payload.parts()?; + let (context_id, invitee_id, protocol, network_id, contract_id) = + invitation_payload.parts()?; if identity_secret.public_key() != invitee_id { bail!("identity mismatch") @@ -310,9 +314,9 @@ impl ContextManager { return Ok(None); } - let client = self - .config_client - .query(network_id.into(), contract_id.into()); + let client = + self.config_client + .query(protocol.parse()?, network_id.into(), contract_id.into()); for (offset, length) in (0..).map(|i| (100_usize.saturating_mul(i), 100)) { let members = client @@ -411,6 +415,7 @@ impl ContextManager { self.config_client .mutate( + context_config.protocol.parse()?, context_config.network.as_ref().into(), context_config.contract.as_ref().into(), inviter_id.rt().expect("infallible conversion"), @@ -425,6 +430,7 @@ impl ContextManager { let invitation_payload = ContextInvitationPayload::new( context_id, invitee_id, + context_config.protocol.into_string().into(), context_config.network.into_string().into(), context_config.contract.into_string().into(), )?; @@ -851,6 +857,7 @@ impl ContextManager { pub async fn get_latest_application(&self, context_id: ContextId) -> EyreResult { let client = self.config_client.query( + self.client_config.new.protocol, self.client_config.new.network.as_str().into(), self.client_config.new.contract_id.as_str().into(), ); diff --git a/crates/merod/Cargo.toml b/crates/merod/Cargo.toml index 4fbbb39a8..f448b4551 100644 --- a/crates/merod/Cargo.toml +++ b/crates/merod/Cargo.toml @@ -22,6 +22,8 @@ libp2p.workspace = true multiaddr.workspace = true near-crypto.workspace = true rand.workspace = true +starknet.workspace = true +starknet-crypto.workspace = true tokio = { workspace = true, features = ["io-std", "macros"] } toml_edit.workspace = true tracing.workspace = true diff --git a/crates/merod/src/cli/config.rs b/crates/merod/src/cli/config.rs index 15ea64fa4..efed2890c 100644 --- a/crates/merod/src/cli/config.rs +++ b/crates/merod/src/cli/config.rs @@ -3,6 +3,7 @@ use core::net::IpAddr; use std::fs::{read_to_string, write}; +use calimero_context_config::client::config::Protocol as CoreProtocol; use calimero_network::config::BootstrapNodes; use clap::{Args, Parser, ValueEnum}; use eyre::{eyre, Result as EyreResult}; @@ -12,6 +13,23 @@ use tracing::info; use crate::cli; +#[derive(Copy, Clone, Debug, ValueEnum)] +#[value(rename_all = "lowercase")] +pub enum ConfigProtocol { + Near, + Starknet, +} + +// Implement conversion from CLI Protocol to Core Protocol +impl From for CoreProtocol { + fn from(protocol: ConfigProtocol) -> Self { + match protocol { + ConfigProtocol::Near => CoreProtocol::Near, + ConfigProtocol::Starknet => CoreProtocol::Starknet, + } + } +} + /// Configure the node #[derive(Debug, Parser)] pub struct ConfigCommand { diff --git a/crates/merod/src/cli/init.rs b/crates/merod/src/cli/init.rs index 1600a0211..ff06c12da 100644 --- a/crates/merod/src/cli/init.rs +++ b/crates/merod/src/cli/init.rs @@ -9,8 +9,9 @@ use calimero_context::config::ContextConfig; use calimero_context_config::client::config::{ ContextConfigClientConfig, ContextConfigClientLocalSigner, ContextConfigClientNew, ContextConfigClientRelayerSigner, ContextConfigClientSelectedSigner, ContextConfigClientSigner, - Credentials, + Credentials, LocalConfig, }; +use calimero_context_config::client::{near, starknet as starknetCredentials}; use calimero_network::config::{ BootstrapConfig, BootstrapNodes, CatchupConfig, DiscoveryConfig, RelayConfig, RendezvousConfig, SwarmConfig, @@ -22,11 +23,13 @@ use calimero_store::config::StoreConfig; use calimero_store::db::RocksDB; use calimero_store::Store; use clap::{Parser, ValueEnum}; +use cli::config::ConfigProtocol; use eyre::{bail, Result as EyreResult, WrapErr}; use libp2p::identity::Keypair; use multiaddr::{Multiaddr, Protocol}; use near_crypto::{KeyType, SecretKey}; use rand::{thread_rng, Rng}; +use starknet::signers::SigningKey; use tracing::{info, warn}; use url::Url; @@ -69,6 +72,11 @@ pub struct InitCommand { #[clap(long, value_name = "URL")] pub relayer_url: Option, + /// Name of protocol + #[clap(long, value_name = "PROTOCOL", default_value = "near")] + #[clap(value_enum)] + pub protocol: ConfigProtocol, + /// Enable mDNS discovery #[clap(long, default_value_t = true)] #[clap(overrides_with("no_mdns"))] @@ -173,22 +181,61 @@ impl InitCommand { signer: ContextConfigClientSigner { selected: ContextConfigClientSelectedSigner::Relayer, relayer: ContextConfigClientRelayerSigner { url: relayer }, - local: [ - ( - "mainnet".to_owned(), - generate_local_signer("https://rpc.mainnet.near.org".parse()?)?, - ), - ( - "testnet".to_owned(), - generate_local_signer("https://rpc.testnet.near.org".parse()?)?, - ), - ] - .into_iter() - .collect(), + local: LocalConfig { + near: [ + ( + "mainnet".to_owned(), + generate_local_signer( + "https://rpc.mainnet.near.org".parse()?, + ConfigProtocol::Near, + )?, + ), + ( + "testnet".to_owned(), + generate_local_signer( + "https://rpc.testnet.near.org".parse()?, + ConfigProtocol::Near, + )?, + ), + ] + .into_iter() + .collect(), + starknet: [ + ( + "mainnet".to_owned(), + generate_local_signer( + "https://cloud.argent-api.com/v1/starknet/mainnet/rpc/v0.7" + .parse()?, + ConfigProtocol::Starknet, + )?, + ), + ( + "sepolia".to_owned(), + generate_local_signer( + "https://free-rpc.nethermind.io/sepolia-juno/".parse()?, + ConfigProtocol::Starknet, + )?, + ), + ] + .into_iter() + .collect(), + }, }, new: ContextConfigClientNew { - network: "testnet".into(), - contract_id: "calimero-context-config.testnet".parse()?, + network: match self.protocol { + ConfigProtocol::Near => "testnet".into(), + ConfigProtocol::Starknet => "sepolia".into(), + _ => "unknown".into(), + }, + protocol: self.protocol.into(), + contract_id: match self.protocol { + ConfigProtocol::Near => "calimero-context-config.testnet".parse()?, + ConfigProtocol::Starknet => { + "0x1ee8182d5dd595be9797ccae1488bdf84b19a0f05a93ce6148b0efae04f4568" + .parse()? + } + _ => "unknown.contract.id".parse()?, + }, }, }, }, @@ -230,19 +277,39 @@ impl InitCommand { } } -fn generate_local_signer(rpc_url: Url) -> EyreResult { - let secret_key = SecretKey::from_random(KeyType::ED25519); - - let public_key = secret_key.public_key(); - - let account_id = public_key.unwrap_as_ed25519().0; - - Ok(ContextConfigClientLocalSigner { - rpc_url, - credentials: Credentials { - account_id: hex::encode(account_id).parse()?, - public_key, - secret_key, - }, - }) +fn generate_local_signer( + rpc_url: Url, + config_protocol: ConfigProtocol, +) -> EyreResult { + match config_protocol { + ConfigProtocol::Near => { + let secret_key = SecretKey::from_random(KeyType::ED25519); + let public_key = secret_key.public_key(); + let account_id = public_key.unwrap_as_ed25519().0; + + Ok(ContextConfigClientLocalSigner { + rpc_url, + credentials: Credentials::Near(near::Credentials { + account_id: hex::encode(account_id).parse()?, + public_key, + secret_key, + }), + }) + } + ConfigProtocol::Starknet => { + let keypair = SigningKey::from_random(); + let secret_key = SigningKey::secret_scalar(&keypair); + let public_key = keypair.verifying_key().scalar(); + + Ok(ContextConfigClientLocalSigner { + rpc_url, + credentials: Credentials::Starknet(starknetCredentials::Credentials { + account_id: public_key, + public_key, + secret_key, + }), + }) + } + _ => todo!(), + } } diff --git a/crates/merod/src/cli/relay.rs b/crates/merod/src/cli/relay.rs index d8a07bd24..ebaae8905 100644 --- a/crates/merod/src/cli/relay.rs +++ b/crates/merod/src/cli/relay.rs @@ -1,4 +1,5 @@ use core::net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr}; +use std::borrow::Cow; use std::env; use axum::extract::State; @@ -7,8 +8,9 @@ use axum::response::IntoResponse; use axum::routing::post; use axum::{Json, Router}; use calimero_config::ConfigFile; +use calimero_context_config::client::config::Credentials; use calimero_context_config::client::relayer::RelayRequest; -use calimero_context_config::client::{near, Transport, TransportRequest}; +use calimero_context_config::client::{near, starknet, BothTransport, Transport, TransportRequest}; use clap::{Parser, ValueEnum}; use eyre::{bail, Result as EyreResult}; use futures_util::FutureExt; @@ -50,38 +52,88 @@ impl RelayCommand { let (tx, mut rx) = mpsc::channel::(32); - let transport = near::NearTransport::new(&near::NearConfig { + let near_transport = near::NearTransport::new(&near::NearConfig { networks: config .context .client .signer .local + .near .iter() .map(|(network, config)| { - ( - network.into(), + let (account_id, access_key) = match &config.credentials { + Credentials::Near(credentials) => ( + credentials.account_id.clone(), + credentials.secret_key.clone(), + ), + Credentials::Starknet(_) => { + bail!("Expected NEAR credentials, but got Starknet credentials.") + } + _ => bail!("Expected NEAR credentials."), + }; + Ok(( + Cow::from(network.clone()), near::NetworkConfig { rpc_url: config.rpc_url.clone(), - account_id: config.credentials.account_id.clone(), - access_key: config.credentials.secret_key.clone(), + account_id, + access_key, }, - ) + )) }) - .collect(), + .collect::>()?, }); + let starknet_transport = starknet::StarknetTransport::new(&starknet::StarknetConfig { + networks: config + .context + .client + .signer + .local + .starknet + .iter() + .map(|(network, config)| { + let (account_id, access_key) = match &config.credentials { + Credentials::Starknet(credentials) => ( + credentials.account_id.clone(), + credentials.secret_key.clone(), + ), + Credentials::Near(_) => bail!("Expected Starknet credentials."), + _ => bail!("Expected NEAR credentials."), + }; + Ok(( + Cow::from(network.clone()), + starknet::NetworkConfig { + rpc_url: config.rpc_url.clone(), + account_id, + access_key, + }, + )) + }) + .collect::>()?, + }); + + let both_transport = BothTransport { + near: near_transport, + starknet: starknet_transport, + }; + let handle = async move { while let Some((request, res_tx)) = rx.recv().await { let payload = request.payload; let request = TransportRequest::new( + request.protocol, request.network_id, request.contract_id, request.operation, ); - let _ignored = - res_tx.send(transport.send(request, payload).await.map_err(Into::into)); + let _ignored = res_tx.send( + both_transport + .send(request, payload) + .await + .map_err(Into::into), + ); } }; diff --git a/crates/node/src/interactive_cli/transactions.rs b/crates/node/src/interactive_cli/transactions.rs index f492f46f2..c2cafb06b 100644 --- a/crates/node/src/interactive_cli/transactions.rs +++ b/crates/node/src/interactive_cli/transactions.rs @@ -1,6 +1,7 @@ use std::str::FromStr; -use calimero_primitives::{context::ContextId, hash::Hash}; +use calimero_primitives::context::ContextId; +use calimero_primitives::hash::Hash; use calimero_store::key::ContextTransaction as ContextTransactionKey; use clap::Parser; use eyre::Result; diff --git a/crates/primitives/src/context.rs b/crates/primitives/src/context.rs index e9d82cb00..c84fa2b17 100644 --- a/crates/primitives/src/context.rs +++ b/crates/primitives/src/context.rs @@ -81,12 +81,13 @@ impl fmt::Debug for ContextInvitationPayload { { let is_alternate = f.alternate(); let mut d = f.debug_struct("ContextInvitationPayload"); - let (context_id, invitee_id, network, contract_id) = + let (context_id, invitee_id, protocol, network, contract_id) = self.parts().map_err(|_| fmt::Error)?; _ = d .field("context_id", &context_id) .field("invitee_id", &invitee_id) + .field("protocol", &protocol) .field("network", &network) .field("contract_id", &contract_id); @@ -146,6 +147,7 @@ const _: () = { struct InvitationPayload<'a> { context_id: [u8; 32], invitee_id: [u8; 32], + protocol: Cow<'a, str>, network: Cow<'a, str>, contract_id: Cow<'a, str>, } @@ -154,12 +156,14 @@ const _: () = { pub fn new( context_id: ContextId, invitee_id: PublicKey, + protocol: Cow<'_, str>, network: Cow<'_, str>, contract_id: Cow<'_, str>, ) -> io::Result { let payload = InvitationPayload { context_id: *context_id, invitee_id: *invitee_id, + protocol, network, contract_id, }; @@ -167,12 +171,13 @@ const _: () = { borsh::to_vec(&payload).map(Self) } - pub fn parts(&self) -> io::Result<(ContextId, PublicKey, String, String)> { + pub fn parts(&self) -> io::Result<(ContextId, PublicKey, String, String, String)> { let payload: InvitationPayload<'_> = borsh::from_slice(&self.0)?; Ok(( payload.context_id.into(), payload.invitee_id.into(), + payload.protocol.into_owned(), payload.network.into_owned(), payload.contract_id.into_owned(), )) diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 688e28c48..47a71403b 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -32,9 +32,8 @@ rust-embed = { workspace = true, features = ["mime-guess"] } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true sha2.workspace = true -starknet-core.workspace = true +starknet.workspace = true starknet-crypto.workspace = true -starknet-providers.workspace = true thiserror.workspace = true tokio.workspace = true tower.workspace = true diff --git a/crates/server/src/verifywalletsignatures/starknet.rs b/crates/server/src/verifywalletsignatures/starknet.rs index 1ab6f6de5..860850967 100644 --- a/crates/server/src/verifywalletsignatures/starknet.rs +++ b/crates/server/src/verifywalletsignatures/starknet.rs @@ -5,11 +5,11 @@ use std::vec; use eyre::{bail, eyre, Result as EyreResult}; use serde::{Deserialize, Serialize}; use serde_json::{from_str as from_json_str, json, Value}; -use starknet_core::types::{BlockId, BlockTag, Felt, FunctionCall}; -use starknet_core::utils::get_selector_from_name; +use starknet::core::types::{BlockId, BlockTag, Felt, FunctionCall}; +use starknet::core::utils::get_selector_from_name; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::{JsonRpcClient, Provider, Url}; use starknet_crypto::{poseidon_hash_many, verify}; -use starknet_providers::jsonrpc::HttpTransport; -use starknet_providers::{JsonRpcClient, Provider, Url}; /// A field in a StarkNet type with a name and type. #[derive(Debug, Deserialize, Serialize)] diff --git a/crates/store/src/types/context.rs b/crates/store/src/types/context.rs index 12b8e8733..bae6f6f7a 100644 --- a/crates/store/src/types/context.rs +++ b/crates/store/src/types/context.rs @@ -36,14 +36,19 @@ impl PredefinedEntry for ContextMetaKey { #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub struct ContextConfig { + pub protocol: Box, pub network: Box, pub contract: Box, } impl ContextConfig { #[must_use] - pub const fn new(network: Box, contract: Box) -> Self { - Self { network, contract } + pub const fn new(protocol: Box, network: Box, contract: Box) -> Self { + Self { + protocol, + network, + contract, + } } }