Skip to content

Commit

Permalink
fix: dynamic chain support
Browse files Browse the repository at this point in the history
  • Loading branch information
chris13524 committed Mar 8, 2024
1 parent f7ff25c commit 228f017
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 83 deletions.
11 changes: 4 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ authors = ["WalletConnect Team"]
license = "Apache-2.0"

[workspace]
members = [
"relay_client",
"relay_rpc"
]
members = ["blockchain_api", "relay_client", "relay_rpc"]

[features]
default = ["full"]
Expand All @@ -35,12 +32,12 @@ once_cell = "1.19"

[[example]]
name = "websocket_client"
required-features = ["client","rpc"]
required-features = ["client", "rpc"]

[[example]]
name = "http_client"
required-features = ["client","rpc"]
required-features = ["client", "rpc"]

[[example]]
name = "webhook"
required-features = ["client","rpc"]
required-features = ["client", "rpc"]
13 changes: 13 additions & 0 deletions blockchain_api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "blockchain_api"
version = "0.1.0"
edition = "2021"

[dependencies]
once_cell = "1.16"
relay_rpc = { path = "../relay_rpc" }
reqwest = "0.11"
serde = "1.0"
tokio = { version = "1.0", features = ["test-util", "macros"] }
tracing = "0.1.40"
url = "2"
133 changes: 133 additions & 0 deletions blockchain_api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
pub use reqwest::Error;
use {
relay_rpc::{auth::cacao::signature::eip1271::get_rpc_url::GetRpcUrl, domain::ProjectId},
serde::Deserialize,
std::{collections::HashSet, convert::Infallible, sync::Arc, time::Duration},
tokio::{sync::RwLock, task::JoinHandle},
tracing::error,
url::Url,
};

const BLOCKCHAIN_API_SUPPORTED_CHAINS_ENDPOINT_STR: &str = "/v1/supported-chains";
const BLOCKCHAIN_API_RPC_ENDPOINT_STR: &str = "/v1";
const BLOCKCHAIN_API_RPC_CHAIN_ID_PARAM: &str = "chainId";
const BLOCKCHAIN_API_RPC_PROJECT_ID_PARAM: &str = "projectId";

const SUPPORTED_CHAINS_REFRESH_INTERVAL: Duration = Duration::from_secs(60 * 60 * 4);

#[derive(Debug, Deserialize)]
struct SupportedChainsResponse {
pub http: HashSet<String>,
}

#[derive(Debug, Clone)]
pub struct BlockchainApiProvider {
project_id: ProjectId,
blockchain_api_rpc_endpoint: Url,
supported_chains: Arc<RwLock<HashSet<String>>>,
refresh_job: Arc<JoinHandle<Infallible>>,
}

impl Drop for BlockchainApiProvider {
fn drop(&mut self) {
self.refresh_job.abort();
}
}

async fn refresh_supported_chains(
blockchain_api_supported_chains_endpoint: Url,
supported_chains: &RwLock<HashSet<String>>,
) -> Result<(), Error> {
let response = reqwest::get(blockchain_api_supported_chains_endpoint)
.await?
.json::<SupportedChainsResponse>()
.await?;
*supported_chains.write().await = response.http;
Ok(())
}

impl BlockchainApiProvider {
pub async fn new(project_id: ProjectId, blockchain_api_endpoint: Url) -> Result<Self, Error> {
let blockchain_api_rpc_endpoint = blockchain_api_endpoint
.join(BLOCKCHAIN_API_RPC_ENDPOINT_STR)
.expect("Safe unwrap: hardcoded URL: BLOCKCHAIN_API_RPC_ENDPOINT_STR");
let blockchain_api_supported_chains_endpoint = blockchain_api_endpoint
.join(BLOCKCHAIN_API_SUPPORTED_CHAINS_ENDPOINT_STR)
.expect("Safe unwrap: hardcoded URL: BLOCKCHAIN_API_SUPPORTED_CHAINS_ENDPOINT_STR");

let supported_chains = Arc::new(RwLock::new(HashSet::new()));
refresh_supported_chains(
blockchain_api_supported_chains_endpoint.clone(),
&supported_chains,
)
.await?;
let mut interval = tokio::time::interval(SUPPORTED_CHAINS_REFRESH_INTERVAL);
interval.tick().await;
let refresh_job = tokio::task::spawn({
let supported_chains = supported_chains.clone();
let blockchain_api_supported_chains_endpoint =
blockchain_api_supported_chains_endpoint.clone();
async move {
loop {
interval.tick().await;
if let Err(e) = refresh_supported_chains(
blockchain_api_supported_chains_endpoint.clone(),
&supported_chains,
)
.await
{
error!("Failed to refresh supported chains: {e}");
}
}
}
});
Ok(Self {
project_id,
blockchain_api_rpc_endpoint,
supported_chains,
refresh_job: Arc::new(refresh_job),
})
}
}

fn build_rpc_url(blockchain_api_rpc_endpoint: Url, chain_id: &str, project_id: &str) -> Url {
let mut url = blockchain_api_rpc_endpoint;
url.query_pairs_mut()
.append_pair(BLOCKCHAIN_API_RPC_CHAIN_ID_PARAM, chain_id)
.append_pair(BLOCKCHAIN_API_RPC_PROJECT_ID_PARAM, project_id);
url
}

impl GetRpcUrl for BlockchainApiProvider {
async fn get_rpc_url(&self, chain_id: String) -> Option<Url> {
self.supported_chains
.read()
.await
.contains(&chain_id)
.then(|| {
build_rpc_url(
self.blockchain_api_rpc_endpoint.clone(),
&chain_id,
self.project_id.as_ref(),
)
})
}
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn rpc_endpoint() {
assert_eq!(
build_rpc_url(
"https://rpc.walletconnect.com/v1".parse().unwrap(),
"eip155:1",
"my-project-id"
)
.as_str(),
"https://rpc.walletconnect.com/v1?chainId=eip155%3A1&projectId=my-project-id"
);
}
}
5 changes: 4 additions & 1 deletion relay_rpc/src/auth/cacao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub enum CacaoError {
#[error("Invalid address")]
AddressInvalid,

#[error("EIP-1271 signatures not supported")]
Eip1271NotSupported,

#[error("Unsupported signature type")]
UnsupportedSignature,

Expand Down Expand Up @@ -94,7 +97,7 @@ pub struct Cacao {
impl Cacao {
const ETHEREUM: &'static str = "Ethereum";

pub async fn verify(&self, provider: &impl GetRpcUrl) -> Result<bool, CacaoError> {
pub async fn verify(&self, provider: Option<&impl GetRpcUrl>) -> Result<bool, CacaoError> {
self.p.validate()?;
self.h.validate()?;
self.s.verify(self, provider).await
Expand Down
59 changes: 0 additions & 59 deletions relay_rpc/src/auth/cacao/signature/eip1271/blockchain_api.rs

This file was deleted.

3 changes: 2 additions & 1 deletion relay_rpc/src/auth/cacao/signature/eip1271/get_rpc_url.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use url::Url;

pub trait GetRpcUrl {
fn get_rpc_url(&self, chain_id: String) -> Option<Url>;
#[allow(async_fn_in_trait)]
async fn get_rpc_url(&self, chain_id: String) -> Option<Url>;
}
1 change: 0 additions & 1 deletion relay_rpc/src/auth/cacao/signature/eip1271/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use {
url::Url,
};

pub mod blockchain_api;
pub mod get_rpc_url;

pub const EIP1271: &str = "eip1271";
Expand Down
30 changes: 17 additions & 13 deletions relay_rpc/src/auth/cacao/signature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl Signature {
pub async fn verify(
&self,
cacao: &Cacao,
get_provider: &impl GetRpcUrl,
provider: Option<&impl GetRpcUrl>,
) -> Result<bool, CacaoError> {
let address = cacao.p.address()?;

Expand All @@ -36,20 +36,24 @@ impl Signature {
match self.t.as_str() {
EIP191 => verify_eip191(&signature, &address, hash),
EIP1271 => {
let chain_id = cacao.p.chain_id_reference()?;
let provider = get_provider.get_rpc_url(chain_id);
if let Some(provider) = provider {
verify_eip1271(
signature,
Address::from_str(&address).map_err(|_| CacaoError::AddressInvalid)?,
&hash.finalize()[..]
.try_into()
.expect("hash length is 32 bytes"),
provider,
)
.await
let chain_id = cacao.p.chain_id_reference()?;
let provider = provider.get_rpc_url(chain_id).await;
if let Some(provider) = provider {
verify_eip1271(
signature,
Address::from_str(&address).map_err(|_| CacaoError::AddressInvalid)?,
&hash.finalize()[..]
.try_into()
.expect("hash length is 32 bytes"),
provider,
)
.await
} else {
Err(CacaoError::ProviderNotAvailable)
}
} else {
Err(CacaoError::ProviderNotAvailable)
Err(CacaoError::Eip1271NotSupported)
}
}
_ => Err(CacaoError::UnsupportedSignature),
Expand Down
2 changes: 1 addition & 1 deletion relay_rpc/src/auth/cacao/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use {super::signature::eip1271::get_rpc_url::GetRpcUrl, crate::auth::cacao::Caca
struct MockGetRpcUrl;

impl GetRpcUrl for MockGetRpcUrl {
fn get_rpc_url(&self, _: String) -> Option<Url> {
async fn get_rpc_url(&self, _: String) -> Option<Url> {
None
}
}
Expand Down

0 comments on commit 228f017

Please sign in to comment.