Skip to content

Commit 1f8c429

Browse files
committed
feat: fireblock support + pkcs11 slight changes
1 parent ed16ba5 commit 1f8c429

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2540
-104
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ playground/**/keystores
3939
crates/e2e-tests/gcp-service-account.json
4040
crates/e2e-tests/__TEST_RESULTS__
4141
crates/e2e-tests/rrelayer.yaml
42+
*.key
4243
documentation/rrelayer/docs/dist/**
4344
documentation/rrelayer/vocs.config.tsx.time*

README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
Note rrelayer is brand new and actively underdevelopment, things will change and bugs will exist—if you find any bugs or have any
44
feature requests, please open an issue on [github](https://github.com/joshstevens19/rrelayer/issues).
55

6-
rrelayer is an opensource powerful, high-performance blockchain transaction relay service built in Rust, designed for seamless
7-
integration with any EVM-compatible network. This tool transforms complex blockchain interactions into simple REST API calls,
8-
eliminating the need for applications to manage wallets, transaction queuing, or nonce management. For rrelayer
9-
supports advanced wallet infrastructure supporting multiple signing providers including AWS KMS hardware security modules,
10-
Turnkey self-custody solutions, Privy managed wallets, AWS Secrets Manager, GCP Secret Manager, and raw mnemonic development setups.
11-
It's highly scalable and production-ready, enabling you to build robust Web3 applications with reliability and focus exclusively on
12-
your business logic. rrelayer has some super cool out-of-the-box features, like automatic top-ups (with safe support), permissions
13-
config including allowlists, API keys with restricted access, webhooks, rate limiting, and the ability to configure the gas bump blocks.
6+
rrelayer is an opensource powerful, high-performance blockchain transaction relay service built in Rust, designed for seamless integration with any EVM-compatible network.
7+
This tool transforms complex blockchain interactions into simple REST API calls, eliminating the need for applications to manage wallets, transaction queuing, or nonce management.
8+
For rrelayer supports advanced wallet infrastructure supporting multiple signing providers including AWS KMS hardware security modules,
9+
Turnkey self-custody solutions, Fireblocks enterprise MPC custody, Privy managed wallets, AWS Secrets Manager, GCP Secret Manager, PKCS#11 hardware security modules, and raw mnemonic development setups.
10+
It's highly scalable and production-ready, enabling you to build robust Web3 applications with reliability and focus exclusively on your business logic.
11+
rrelayer has some super cool out-of-the-box features, like automatic top-ups (with safe support), permissions config including allowlists, API keys with restricted access,
12+
webhooks, rate limiting, and the ability to configure the gas bump blocks.
1413

1514
You can get to the full rrelayer documentation [here](https://rrelayer.xyz/).
1615

crates/cli/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ start_base:
1616
RUST_BACKTRACE=1 cargo run -- start --path $(CURDIR)/../../playground/base
1717
start_sepolia:
1818
RUST_BACKTRACE=1 cargo run -- start --path $(CURDIR)/../../playground/sepolia
19+
sepolia_ethereum_fireblocks:
20+
RUST_BACKTRACE=1 cargo run -- start --path $(CURDIR)/../../playground/sepolia_ethereum_fireblocks
1921

2022
auth_login:
2123
cargo run -- auth login

crates/core/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,5 @@ tower = "0.5.2"
6262
subtle = "2.6.1"
6363
cryptoki = "0.10"
6464
secrecy = "0.8"
65+
jsonwebtoken = "9.3"
66+
rsa = "0.9"

crates/core/src/provider/evm_provider.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::gas::BLOB_GAS_PER_BLOB;
22
use crate::provider::layer_extensions::RpcLoggingLayer;
33
use crate::wallet::{
4-
AwsKmsWalletManager, CompositeWalletManager, MnemonicWalletManager, Pkcs11WalletManager,
5-
PrivateKeyWalletManager, PrivyWalletManager, TurnkeyWalletManager, WalletError,
6-
WalletManagerTrait,
4+
AwsKmsWalletManager, CompositeWalletManager, FireblocksWalletManager, MnemonicWalletManager,
5+
Pkcs11WalletManager, PrivateKeyWalletManager, PrivyWalletManager, TurnkeyWalletManager,
6+
WalletError, WalletManagerTrait,
77
};
88
use crate::yaml::{
9-
AwsKmsSigningProviderConfig, Pkcs11SigningProviderConfig, TurnkeySigningProviderConfig,
9+
AwsKmsSigningProviderConfig, FireblocksSigningProviderConfig, Pkcs11SigningProviderConfig,
10+
TurnkeySigningProviderConfig,
1011
};
1112
use crate::{
1213
gas::{
@@ -59,6 +60,8 @@ pub struct EvmProvider {
5960
/// this is in milliseconds (min 250ms)
6061
pub blocks_every: u64,
6162
pub confirmations: u64,
63+
/// Whether this provider type supports cloning (for clone prevention logic)
64+
can_clone: bool,
6265
}
6366

6467
async fn calculate_block_time_difference(
@@ -161,7 +164,7 @@ impl EvmProvider {
161164
gas_estimator: Arc<dyn BaseGasFeeEstimator + Send + Sync>,
162165
) -> Result<Self, EvmProviderNewError> {
163166
let wallet_manager = Arc::new(MnemonicWalletManager::new(mnemonic));
164-
Self::new_internal(network_setup_config, wallet_manager, gas_estimator).await
167+
Self::new_internal(network_setup_config, wallet_manager, gas_estimator, true).await
165168
}
166169

167170
pub async fn new_with_privy(
@@ -172,7 +175,7 @@ impl EvmProvider {
172175
) -> Result<Self, EvmProviderNewError> {
173176
let privy_manager = PrivyWalletManager::new(app_id, app_secret).await?;
174177
let wallet_manager = Arc::new(privy_manager);
175-
Self::new_internal(network_setup_config, wallet_manager, gas_estimator).await
178+
Self::new_internal(network_setup_config, wallet_manager, gas_estimator, true).await
176179
}
177180

178181
pub async fn new_with_aws_kms(
@@ -181,7 +184,7 @@ impl EvmProvider {
181184
gas_estimator: Arc<dyn BaseGasFeeEstimator + Send + Sync>,
182185
) -> Result<Self, EvmProviderNewError> {
183186
let wallet_manager = Arc::new(AwsKmsWalletManager::new(aws_kms_config));
184-
Self::new_internal(network_setup_config, wallet_manager, gas_estimator).await
187+
Self::new_internal(network_setup_config, wallet_manager, gas_estimator, true).await
185188
}
186189

187190
pub async fn new_with_turnkey(
@@ -191,7 +194,7 @@ impl EvmProvider {
191194
) -> Result<Self, EvmProviderNewError> {
192195
let turnkey_manager = TurnkeyWalletManager::new(turnkey_config).await?;
193196
let wallet_manager = Arc::new(turnkey_manager);
194-
Self::new_internal(network_setup_config, wallet_manager, gas_estimator).await
197+
Self::new_internal(network_setup_config, wallet_manager, gas_estimator, true).await
195198
}
196199

197200
pub async fn new_with_private_keys(
@@ -200,7 +203,7 @@ impl EvmProvider {
200203
gas_estimator: Arc<dyn BaseGasFeeEstimator + Send + Sync>,
201204
) -> Result<Self, EvmProviderNewError> {
202205
let wallet_manager = Arc::new(PrivateKeyWalletManager::new(private_keys));
203-
Self::new_internal(network_setup_config, wallet_manager, gas_estimator).await
206+
Self::new_internal(network_setup_config, wallet_manager, gas_estimator, false).await
204207
}
205208

206209
pub async fn new_with_pkcs11(
@@ -209,7 +212,17 @@ impl EvmProvider {
209212
gas_estimator: Arc<dyn BaseGasFeeEstimator + Send + Sync>,
210213
) -> Result<Self, EvmProviderNewError> {
211214
let wallet_manager = Arc::new(Pkcs11WalletManager::new(pkcs11_config)?);
212-
Self::new_internal(network_setup_config, wallet_manager, gas_estimator).await
215+
Self::new_internal(network_setup_config, wallet_manager, gas_estimator, true).await
216+
}
217+
218+
pub async fn new_with_fireblocks(
219+
network_setup_config: &NetworkSetupConfig,
220+
fireblocks_config: FireblocksSigningProviderConfig,
221+
gas_estimator: Arc<dyn BaseGasFeeEstimator + Send + Sync>,
222+
) -> Result<Self, EvmProviderNewError> {
223+
let fireblocks_manager = FireblocksWalletManager::new(fireblocks_config).await?;
224+
let wallet_manager = Arc::new(fireblocks_manager);
225+
Self::new_internal(network_setup_config, wallet_manager, gas_estimator, false).await
213226
}
214227

215228
pub async fn new_with_composite(
@@ -224,13 +237,14 @@ impl EvmProvider {
224237

225238
let wallet_manager =
226239
Arc::new(CompositeWalletManager::new(primary_manager, private_key_manager));
227-
Self::new_internal(network_setup_config, wallet_manager, gas_estimator).await
240+
Self::new_internal(network_setup_config, wallet_manager, gas_estimator, true).await
228241
}
229242

230243
async fn new_internal(
231244
network_setup_config: &NetworkSetupConfig,
232245
wallet_manager: Arc<dyn WalletManagerTrait>,
233246
gas_estimator: Arc<dyn BaseGasFeeEstimator + Send + Sync>,
247+
can_clone: bool,
234248
) -> Result<Self, EvmProviderNewError> {
235249
let provider =
236250
create_retry_client(&network_setup_config.provider_urls[0]).await.map_err(|e| {
@@ -262,6 +276,7 @@ impl EvmProvider {
262276
name: network_setup_config.name.to_string(),
263277
provider_urls: network_setup_config.provider_urls.to_owned(),
264278
confirmations: network_setup_config.confirmations.unwrap_or(12),
279+
can_clone,
265280
})
266281
}
267282

@@ -279,6 +294,10 @@ impl EvmProvider {
279294
self.wallet_manager.get_address(wallet_index, &self.chain_id).await
280295
}
281296

297+
pub fn can_clone(&self) -> bool {
298+
self.can_clone
299+
}
300+
282301
pub async fn get_receipt(
283302
&self,
284303
transaction_hash: &TransactionHash,
@@ -361,15 +380,15 @@ impl EvmProvider {
361380
wallet_index: &u32,
362381
text: &str,
363382
) -> Result<Signature, WalletError> {
364-
self.wallet_manager.sign_text(*wallet_index, text).await
383+
self.wallet_manager.sign_text(*wallet_index, text, &self.chain_id).await
365384
}
366385

367386
pub async fn sign_typed_data(
368387
&self,
369388
wallet_index: &u32,
370389
typed_data: &TypedData,
371390
) -> Result<Signature, WalletError> {
372-
self.wallet_manager.sign_typed_data(*wallet_index, typed_data).await
391+
self.wallet_manager.sign_typed_data(*wallet_index, typed_data, &self.chain_id).await
373392
}
374393

375394
pub async fn estimate_gas(

crates/core/src/provider/mod.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ pub async fn load_providers(
6767
|| signing_key.aws_kms.is_some()
6868
|| signing_key.turnkey.is_some()
6969
|| signing_key.pkcs11.is_some()
70-
|| signing_key.raw.is_some();
70+
|| signing_key.fireblocks.is_some()
71+
|| signing_key.raw.is_some()
72+
|| signing_key.aws_secret_manager.is_some()
73+
|| signing_key.gcp_secret_manager.is_some();
7174

7275
// If we only have private keys and no main signing provider, use private key manager only
7376
if private_key_strings.is_some() && !has_main_signing_provider {
@@ -168,6 +171,26 @@ pub async fn load_providers(
168171
)
169172
.await?
170173
}
174+
} else if let Some(fireblocks) = &signing_key.fireblocks {
175+
if private_key_strings.is_some() {
176+
let fireblocks_manager = std::sync::Arc::new(
177+
crate::wallet::FireblocksWalletManager::new(fireblocks.clone()).await?,
178+
);
179+
EvmProvider::new_with_composite(
180+
config,
181+
fireblocks_manager,
182+
private_key_strings,
183+
get_gas_estimator(&config.provider_urls, setup_config, config).await?,
184+
)
185+
.await?
186+
} else {
187+
EvmProvider::new_with_fireblocks(
188+
config,
189+
fireblocks.clone(),
190+
get_gas_estimator(&config.provider_urls, setup_config, config).await?,
191+
)
192+
.await?
193+
}
171194
} else {
172195
let mnemonic = get_mnemonic_from_signing_key(project_path, signing_key).await?;
173196

crates/core/src/relayer/db/write.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ pub enum CreateRelayerError {
2929

3030
#[error("Cannot clone private key relayer '{0}' - private key relayers are automatically imported on all networks")]
3131
CannotClonePrivateKeyRelayer(String),
32+
33+
#[error("Cannot clone relayer '{0}' - provider type does not support cloning")]
34+
CannotCloneProviderRelayer(String),
3235
}
3336

3437
impl From<CreateRelayerError> for HttpError {
@@ -40,6 +43,9 @@ impl From<CreateRelayerError> for HttpError {
4043
CreateRelayerError::CannotClonePrivateKeyRelayer(_) => {
4144
crate::shared::bad_request(value.to_string())
4245
}
46+
CreateRelayerError::CannotCloneProviderRelayer(_) => {
47+
crate::shared::bad_request(value.to_string())
48+
}
4349
_ => internal_server_error(Some(value.to_string())),
4450
}
4551
}
@@ -84,6 +90,13 @@ impl PostgresClient {
8490
));
8591
}
8692

93+
// Prevent cloning providers that don't support cloning (e.g., Fireblocks)
94+
if !evm_provider.can_clone() {
95+
return Err(CreateRelayerError::CannotCloneProviderRelayer(
96+
source_relayer.name.clone(),
97+
));
98+
}
99+
87100
let wallet_index = source_relayer.wallet_index;
88101
let address = evm_provider
89102
.create_wallet(source_relayer.wallet_index_type().index())

crates/core/src/startup.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ async fn start_api(
190190
&& signing_provider.aws_kms.is_none()
191191
&& signing_provider.turnkey.is_none()
192192
&& signing_provider.pkcs11.is_none()
193+
&& signing_provider.fireblocks.is_none()
193194
{
194195
Some(network_config.chain_id)
195196
} else {

crates/core/src/wallet/aws_kms_wallet_manager.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -417,10 +417,13 @@ impl WalletManagerTrait for AwsKmsWalletManager {
417417
Ok(signature)
418418
}
419419

420-
async fn sign_text(&self, wallet_index: u32, text: &str) -> Result<Signature, WalletError> {
421-
// For text signing, we use chain ID 1 as default since it's not chain-specific
422-
let default_chain_id = ChainId::new(1);
423-
let signer = self.get_or_initialize_signer(wallet_index, &default_chain_id).await?;
420+
async fn sign_text(
421+
&self,
422+
wallet_index: u32,
423+
text: &str,
424+
chain_id: &ChainId,
425+
) -> Result<Signature, WalletError> {
426+
let signer = self.get_or_initialize_signer(wallet_index, chain_id).await?;
424427
let signature = signer.sign_message(text.as_bytes()).await?;
425428
Ok(signature)
426429
}
@@ -429,11 +432,9 @@ impl WalletManagerTrait for AwsKmsWalletManager {
429432
&self,
430433
wallet_index: u32,
431434
typed_data: &TypedData,
435+
chain_id: &ChainId,
432436
) -> Result<Signature, WalletError> {
433-
// For typed data signing, we use chain ID from the typed data or default to 1
434-
let chain_id_u64 = typed_data.domain().chain_id.map(|id| id.to::<u64>()).unwrap_or(1);
435-
let chain_id = ChainId::new(chain_id_u64);
436-
let signer = self.get_or_initialize_signer(wallet_index, &chain_id).await?;
437+
let signer = self.get_or_initialize_signer(wallet_index, chain_id).await?;
437438

438439
let hash = typed_data.eip712_signing_hash()?;
439440
let signature = signer.sign_hash(&hash).await?;

crates/core/src/wallet/composite_wallet_manager.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,24 @@ impl WalletManagerTrait for CompositeWalletManager {
8080
manager.sign_transaction(wallet_index, transaction, chain_id).await
8181
}
8282

83-
async fn sign_text(&self, wallet_index: u32, text: &str) -> Result<Signature, WalletError> {
83+
async fn sign_text(
84+
&self,
85+
wallet_index: u32,
86+
text: &str,
87+
chain_id: &ChainId,
88+
) -> Result<Signature, WalletError> {
8489
let manager = self.get_manager_for_index(wallet_index)?;
85-
manager.sign_text(wallet_index, text).await
90+
manager.sign_text(wallet_index, text, chain_id).await
8691
}
8792

8893
async fn sign_typed_data(
8994
&self,
9095
wallet_index: u32,
9196
typed_data: &TypedData,
97+
chain_id: &ChainId,
9298
) -> Result<Signature, WalletError> {
9399
let manager = self.get_manager_for_index(wallet_index)?;
94-
manager.sign_typed_data(wallet_index, typed_data).await
100+
manager.sign_typed_data(wallet_index, typed_data, chain_id).await
95101
}
96102

97103
fn supports_blobs(&self) -> bool {

0 commit comments

Comments
 (0)