Repository: tangle-network/gadget (language: rust)

Issue:
[TASK] Cache EVM Providers and Subxt Client in Context Extensions
Currently we do not cache `EVM` alloy providers and `Tangle` subxt client, every time you call `ctx.evm_provider()` or `ctx.tangle_client()` you will get a new instance as a new connection. That is no ideal especially if there is many concurrent jobs which would lead to hitting the Rate-Limit for each RPC endpoint.


Discussion / hints:

Fix patch:
diff --git a/blueprints/incredible-squaring/contracts/lib/forge-std b/blueprints/incredible-squaring/contracts/lib/forge-std
index 8f24d6b04..c28115db8 160000
--- a/blueprints/incredible-squaring/contracts/lib/forge-std
+++ b/blueprints/incredible-squaring/contracts/lib/forge-std
@@ -1,1 +1,1 @@
-Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa
+Subproject commit c28115db8d90ebffb41953cf83aac63130f4bd40
diff --git a/macros/context-derive/src/evm.rs b/macros/context-derive/src/evm.rs
index a5505676b..8384c7e9d 100644
--- a/macros/context-derive/src/evm.rs
+++ b/macros/context-derive/src/evm.rs
@@ -30,17 +30,37 @@ pub fn generate_context_impl(
                 Self::Network,
             >;
             fn evm_provider(&self) -> impl core::future::Future<Output = Result<Self::Provider, alloy_transport::TransportError>> {
-                // TODO: think about caching the provider
-                // see: https://github.com/webb-tools/gadget/issues/320
-                async {
-                    let rpc_url = #field_access.rpc_endpoint.as_str();
-                    let provider = alloy_provider::ProviderBuilder::new()
-                        .with_recommended_fillers()
-                        .on_builtin(rpc_url)
-                        .await?;
-                    Ok(provider)
+                    type Provider = alloy_provider::fillers::FillProvider<
+                        alloy_provider::fillers::RecommendedFiller,
+                        alloy_provider::RootProvider<alloy_transport::BoxTransport>,
+                        alloy_transport::BoxTransport,
+                        alloy_network::Ethereum,
+                    >;
+                    static PROVIDER: std::sync::OnceLock<Provider> = std::sync::OnceLock::new();
+                    async {
+                        match PROVIDER.get() {
+                            Some(provider) => Ok(provider.clone()),
+                            None => {
+                                let rpc_url = #field_access.rpc_endpoint.as_str();
+                                let provider = alloy_provider::ProviderBuilder::new()
+                                    .with_recommended_fillers()
+                                    .on_builtin(rpc_url)
+                                    .await?;
+                                PROVIDER
+                                    .set(provider.clone())
+                                    .map(|_| provider)
+                                    .map_err(|_| {
+                                        alloy_transport::TransportError::LocalUsageError(Box::new(
+                                            std::io::Error::new(
+                                                std::io::ErrorKind::Other,
+                                                "Failed to set provider",
+                                            ),
+                                        ))
+                                    })
+                            }
+                        }
+                    }
                 }
-            }
         }
     }
 }
diff --git a/macros/context-derive/src/subxt.rs b/macros/context-derive/src/subxt.rs
index b0ef9c79a..637f8a003 100644
--- a/macros/context-derive/src/subxt.rs
+++ b/macros/context-derive/src/subxt.rs
@@ -23,12 +23,24 @@ pub fn generate_context_impl(
         impl #impl_generics gadget_sdk::ctx::TangleClientContext for #name #ty_generics #where_clause {
             type Config = gadget_sdk::ext::subxt::PolkadotConfig;
             fn tangle_client(&self) -> impl core::future::Future<Output = Result<gadget_sdk::ext::subxt::OnlineClient<Self::Config>, gadget_sdk::ext::subxt::Error>> {
-                // TODO: think about caching the client
-                // see: https://github.com/webb-tools/gadget/issues/320
+                use gadget_sdk::ext::subxt;
+
+                type Config = subxt::PolkadotConfig;
+                static CLIENT: std::sync::OnceLock<subxt::OnlineClient<Config>> = std::sync::OnceLock::new();
                 async {
-                    let rpc_url = #field_access.rpc_endpoint.as_str();
-                    let client = gadget_sdk::ext::subxt::OnlineClient::from_url(rpc_url).await?;
-                    Ok(client)
+                    match CLIENT.get() {
+                        Some(client) => Ok(client.clone()),
+                        None => {
+                            let rpc_url = #field_access.rpc_endpoint.as_str();
+                            let client = subxt::OnlineClient::from_url(rpc_url).await?;
+                            CLIENT.set(client.clone()).map(|_| client).map_err(|_| {
+                                subxt::Error::Io(std::io::Error::new(
+                                    std::io::ErrorKind::Other,
+                                    "Failed to set client",
+                                ))
+                            })
+                        }
+                    }
                 }
             }
         }


Test patch: