use alloy_sol_types::sol;
sol!(
#[derive(Debug)]
#[allow(missing_docs)]
#[sol(rpc)]
MidribFactory,
"artifacts/MidribFactory.json"
);
pub mod config_pb {
pub use crate::commands::config::config_pb::*;
}
use config_pb::config_service_client::ConfigServiceClient;
use config_pb::{
DeleteChainRequest, DeleteChainResponse, DeleteMarketRequest, DeleteMarketResponse,
DeleteTokenRequest, DeleteTokenResponse, DeleteTradeContractRequest,
DeleteTradeContractResponse, DeployContractRequest, DeployContractResponse, Empty,
GetConfigRequest, GetDeployCalldataRequest, GetDeployCalldataResponse, ProbeChainRpcRequest,
SetChainRequest, SetChainResponse, SetChainRpcsRequest, SetMarketRequest, SetMarketResponse,
SetOperatorAdminRequest, SetOperatorAdminResponse, SetOperatorFeeRequest,
SetOperatorFeeResponse, SetTokenRequest, SetTokenResponse, SetTradeContractRequest,
SetTradeContractResponse, UpdateAdminRequest, UpdateAdminResponse, VersionInfo,
};
use eyre::Result;
use tonic::Request;
use tonic::metadata::MetadataValue;
use crate::grpc::create_channel;
fn authenticated_request<T>(jwt: &str, payload: T) -> Request<T> {
let mut request = Request::new(payload);
let bearer = format!("Bearer {}", jwt);
if let Ok(value) = bearer.parse::<MetadataValue<_>>() {
request.metadata_mut().insert("authorization", value);
}
request
}
pub async fn update_admin(
url: String,
jwt: String,
admin_address: String,
) -> Result<UpdateAdminResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(&jwt, UpdateAdminRequest { admin_address });
let response = client.update_admin(request).await?;
Ok(response.into_inner())
}
pub async fn get_deploy_calldata(
url: String,
jwt: String,
chain_network: String,
fee_bps: u32,
) -> Result<GetDeployCalldataResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(
&jwt,
GetDeployCalldataRequest {
chain_network,
fee_bps,
},
);
let response = client.get_deploy_calldata(request).await?;
Ok(response.into_inner())
}
pub async fn deploy_contract(
url: String,
jwt: String,
chain_network: String,
tx_hash: String,
force: bool,
fee_bps: u32,
) -> Result<DeployContractResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(
&jwt,
DeployContractRequest {
chain_network,
tx_hash,
force,
fee_bps,
},
);
let response = client.deploy_contract(request).await?;
Ok(response.into_inner())
}
#[derive(Debug, Clone)]
pub struct CreateInstanceParams {
pub factory_address: String,
pub calldata: Vec<u8>,
pub rpc_url: String,
pub chain_id: u64,
pub privkey: String,
}
pub async fn build_create_instance_tx(params: CreateInstanceParams) -> Result<Vec<u8>> {
use alloy::consensus::{SignableTransaction, TxEip1559, TxEnvelope};
use alloy::network::{EthereumWallet, TransactionBuilder, TxSigner};
use alloy::primitives::{Address, Bytes, TxKind, U256};
use alloy::providers::{Provider, ProviderBuilder};
use alloy::rpc::types::TransactionRequest;
use alloy::signers::local::PrivateKeySigner;
use url::Url;
let factory_addr: Address = params.factory_address.parse()?;
let signer: PrivateKeySigner = params.privkey.parse()?;
let wallet = EthereumWallet::new(signer.clone());
let from_address = signer.address();
let rpc_url = Url::parse(¶ms.rpc_url)?;
let provider = ProviderBuilder::new()
.with_chain_id(params.chain_id)
.wallet(wallet)
.connect_http(rpc_url);
let nonce = provider.get_transaction_count(from_address).await?;
let calldata_bytes = Bytes::from(params.calldata.clone());
let tx_request = TransactionRequest::default()
.with_from(from_address)
.with_to(factory_addr)
.with_input(calldata_bytes.clone());
let gas_estimate = provider.estimate_gas(tx_request).await?;
let fee_estimate = provider.estimate_eip1559_fees().await?;
let mut tx = TxEip1559 {
chain_id: params.chain_id,
nonce,
gas_limit: gas_estimate + (gas_estimate / 10), max_fee_per_gas: fee_estimate.max_fee_per_gas,
max_priority_fee_per_gas: fee_estimate.max_priority_fee_per_gas,
to: TxKind::Call(factory_addr),
value: U256::ZERO,
access_list: Default::default(),
input: calldata_bytes,
};
let signature = signer.sign_transaction(&mut tx).await?;
let signed_tx = TxEnvelope::Eip1559(tx.into_signed(signature));
use alloy::eips::eip2718::Encodable2718;
let mut encoded = Vec::new();
signed_tx.encode_2718(&mut encoded);
Ok(encoded)
}
pub async fn broadcast_transaction(rpc_url: String, signed_tx: Vec<u8>) -> Result<String> {
use alloy::providers::{Provider, ProviderBuilder};
use url::Url;
let rpc_url_parsed = Url::parse(&rpc_url)?;
let provider = ProviderBuilder::new().connect_http(rpc_url_parsed);
let pending_tx = provider
.send_raw_transaction(&signed_tx)
.await
.map_err(|e| eyre::eyre!("Failed to broadcast transaction: {}", e))?;
let tx_hash = pending_tx.tx_hash();
Ok(format!("{:?}", tx_hash))
}
pub async fn set_trade_contract(
url: String,
jwt: String,
address: String,
chain_network: String,
) -> Result<SetTradeContractResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(
&jwt,
SetTradeContractRequest {
address,
chain_network,
},
);
let response = client.set_trade_contract(request).await?;
Ok(response.into_inner())
}
pub async fn set_operator_fee(
url: String,
jwt: String,
chain_network: String,
recipient: String,
bps: u32,
) -> Result<SetOperatorFeeResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(
&jwt,
SetOperatorFeeRequest {
chain_network,
recipient,
bps,
},
);
let response = client.set_operator_fee(request).await?;
Ok(response.into_inner())
}
pub async fn set_operator_admin(
url: String,
jwt: String,
chain_network: String,
new_admin: String,
) -> Result<SetOperatorAdminResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(
&jwt,
SetOperatorAdminRequest {
chain_network,
new_admin,
},
);
let response = client.set_operator_admin(request).await?;
Ok(response.into_inner())
}
pub async fn delete_trade_contract(
url: String,
jwt: String,
chain_network: String,
) -> Result<DeleteTradeContractResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(&jwt, DeleteTradeContractRequest { chain_network });
let response = client.delete_trade_contract(request).await?;
Ok(response.into_inner())
}
pub async fn set_chain(url: String, jwt: String, chain: Chain) -> Result<SetChainResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(&jwt, SetChainRequest { chain: Some(chain) });
let response = client.set_chain(request).await?;
Ok(response.into_inner())
}
pub async fn delete_chain(
url: String,
jwt: String,
chain_network: String,
) -> Result<DeleteChainResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(&jwt, DeleteChainRequest { chain_network });
let response = client.delete_chain(request).await?;
Ok(response.into_inner())
}
pub async fn set_chain_rpcs(
url: String,
jwt: String,
network: String,
rpcs: Vec<RpcEndpoint>,
) -> Result<SetChainRpcsResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(&jwt, SetChainRpcsRequest { network, rpcs });
let response = client.set_chain_rpcs(request).await?;
Ok(response.into_inner())
}
pub async fn probe_chain_rpc(
url: String,
jwt: String,
network: String,
endpoint: RpcEndpoint,
) -> Result<ProbeChainRpcResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(
&jwt,
ProbeChainRpcRequest {
network,
endpoint: Some(endpoint),
},
);
let response = client.probe_chain_rpc(request).await?;
Ok(response.into_inner())
}
pub async fn get_chain_rpcs(url: String, network: String) -> Result<Vec<RpcEndpoint>> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = Request::new(GetConfigRequest {});
let response = client.get_config(request).await?;
let config = response
.into_inner()
.config
.ok_or_else(|| eyre::eyre!("server returned no configuration"))?;
config
.chains
.into_iter()
.find(|chain| chain.network == network)
.map(|chain| chain.rpcs)
.ok_or_else(|| {
eyre::eyre!(
"unknown network '{}': no such chain in the configuration",
network
)
})
}
pub async fn set_token(
url: String,
jwt: String,
chain_network: String,
token: Token,
) -> Result<SetTokenResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(
&jwt,
SetTokenRequest {
chain_network,
token: Some(token),
},
);
let response = client.set_token(request).await?;
Ok(response.into_inner())
}
pub async fn delete_token(
url: String,
jwt: String,
chain_network: String,
token_symbol: String,
) -> Result<DeleteTokenResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(
&jwt,
DeleteTokenRequest {
chain_network,
token_symbol,
},
);
let response = client.delete_token(request).await?;
Ok(response.into_inner())
}
#[derive(Debug, Clone)]
pub struct SetMarketParams {
pub base_chain_network: String,
pub quote_chain_network: String,
pub base_chain_token_symbol: String,
pub quote_chain_token_symbol: String,
pub base_chain_token_address: String,
pub quote_chain_token_address: String,
pub pair_decimals: i32,
}
fn set_market_request(params: SetMarketParams) -> SetMarketRequest {
SetMarketRequest {
base_chain_network: params.base_chain_network,
quote_chain_network: params.quote_chain_network,
base_chain_token_symbol: params.base_chain_token_symbol,
quote_chain_token_symbol: params.quote_chain_token_symbol,
base_chain_token_address: params.base_chain_token_address,
quote_chain_token_address: params.quote_chain_token_address,
pair_decimals: params.pair_decimals,
}
}
pub async fn set_market(
url: String,
jwt: String,
params: SetMarketParams,
) -> Result<SetMarketResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(&jwt, set_market_request(params));
let response = client.set_market(request).await?;
Ok(response.into_inner())
}
pub async fn delete_market(
url: String,
jwt: String,
market_id: String,
) -> Result<DeleteMarketResponse> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = authenticated_request(&jwt, DeleteMarketRequest { market_id });
let response = client.delete_market(request).await?;
Ok(response.into_inner())
}
pub async fn get_version(url: String) -> Result<VersionInfo> {
let channel = create_channel(&url).await?;
let mut client = ConfigServiceClient::new(channel);
let request = Request::new(Empty {});
let response = client.get_version(request).await?;
Ok(response.into_inner())
}
pub use config_pb::Chain;
pub use config_pb::ProbeChainRpcResponse;
pub use config_pb::RpcAuthScheme;
pub use config_pb::RpcEndpoint;
pub use config_pb::SetChainRpcsResponse;
pub use config_pb::Token;
pub use config_pb::TradeContract;
#[cfg(test)]
mod tests {
use super::*;
const CHECKSUMMED: &str = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const LOWERCASE_OTHER: &str = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
fn params() -> SetMarketParams {
SetMarketParams {
base_chain_network: "base-net".into(),
quote_chain_network: "quote-net".into(),
base_chain_token_symbol: "WETH".into(),
quote_chain_token_symbol: "USDC".into(),
base_chain_token_address: CHECKSUMMED.into(),
quote_chain_token_address: LOWERCASE_OTHER.into(),
pair_decimals: 12,
}
}
#[test]
fn set_market_transmits_token_addresses_byte_for_byte() {
let req = set_market_request(params());
assert_eq!(req.base_chain_token_address, CHECKSUMMED);
assert_eq!(req.quote_chain_token_address, LOWERCASE_OTHER);
assert_ne!(
req.base_chain_token_address,
CHECKSUMMED.to_lowercase(),
"the checksummed base address must not be lower-cased in transit"
);
}
#[test]
fn set_market_does_not_cross_the_base_and_quote_legs() {
let req = set_market_request(params());
assert_eq!(req.base_chain_network, "base-net");
assert_eq!(req.quote_chain_network, "quote-net");
assert_eq!(req.base_chain_token_symbol, "WETH");
assert_eq!(req.quote_chain_token_symbol, "USDC");
assert_eq!(req.pair_decimals, 12);
}
}