Skip to content

Commit

Permalink
feat: chain abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
chris13524 committed Nov 12, 2024
1 parent 04554d3 commit 840be8d
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 0 deletions.
1 change: 1 addition & 0 deletions crates/yttrium/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ alloy = { workspace = true, features = [
] }
alloy-provider = { workspace = true, features = ["erc4337-api"] }
erc6492.workspace = true
relay_rpc = { git = "https://github.com/WalletConnect/WalletConnectRust.git" }

# foundry-block-explorers = "0.2.3"
getrandom = { version = "0.2", features = ["js"] }
Expand Down
20 changes: 20 additions & 0 deletions crates/yttrium/src/chain_abstraction/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use alloy::primitives::Address;
use serde::{Deserialize, Serialize};

pub mod route;
pub mod status;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
pub from: Address,
pub to: Address,
pub value: String,
pub gas: String,
pub gas_price: String,
pub data: String,
pub nonce: String,
pub max_fee_per_gas: String,
pub max_priority_fee_per_gas: String,
pub chain_id: String,
}
98 changes: 98 additions & 0 deletions crates/yttrium/src/chain_abstraction/api/route.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use super::Transaction;
use alloy::primitives::Address;
use relay_rpc::domain::ProjectId;
use serde::{Deserialize, Serialize};
use std::convert::Infallible;

pub const ROUTE_ENDPOINT_PATH: &str = "/v1/ca/orchestrator/route";

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RouteQueryParams {
pub project_id: ProjectId,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RouteRequest {
pub transaction: Transaction,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
pub funding_from: Vec<FundingMetadata>,
pub check_in: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FundingMetadata {
pub chain_id: String,
pub token_contract: Address,
pub symbol: String,
pub amount: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RouteResponseAvailable {
pub orchestration_id: String,
pub transactions: Vec<Transaction>,
pub metadata: Metadata,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RouteResponseNotRequired {
#[serde(rename = "transactions")]
_flag: [Infallible; 0],
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RouteResponseSuccess {
Available(RouteResponseAvailable),
NotRequired(RouteResponseNotRequired),
}

impl RouteResponseSuccess {
pub fn into_option(self) -> Option<RouteResponseAvailable> {
match self {
Self::Available(a) => Some(a),
Self::NotRequired(_) => None,
}
}
}

/// Bridging check error response that should be returned as a normal HTTP 200
/// response
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RouteResponseError {
pub error: BridgingError,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BridgingError {
NoRoutesAvailable,
InsufficientFunds,
InsufficientGasFunds,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RouteResponse {
Success(RouteResponseSuccess),
Error(RouteResponseError),
}

impl RouteResponse {
pub fn into_result(
self,
) -> Result<RouteResponseSuccess, RouteResponseError> {
match self {
Self::Success(success) => Ok(success),
Self::Error(error) => Err(error),
}
}
}
53 changes: 53 additions & 0 deletions crates/yttrium/src/chain_abstraction/api/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use relay_rpc::domain::ProjectId;
use serde::{Deserialize, Serialize};

pub const STATUS_ENDPOINT_PATH: &str = "/v1/ca/orchestrator/status";

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct StatusQueryParams {
pub project_id: ProjectId,
pub orchestration_id: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatusResponseSuccessPending {
created_at: usize,
/// Polling interval in ms for the client
check_in: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatusResponseSuccessCompleted {
created_at: usize,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatusResponseSuccessError {
created_at: usize,
error_reason: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "status")]
pub enum StatusResponseSuccess {
Pending(StatusResponseSuccessPending),
Completed(StatusResponseSuccessCompleted),
Error(StatusResponseSuccessError),
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatusResponseError {
pub error: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StatusResponse {
Success(StatusResponseSuccess),
Error(StatusResponseError),
}
67 changes: 67 additions & 0 deletions crates/yttrium/src/chain_abstraction/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use super::{
api::{
route::{
RouteQueryParams, RouteRequest, RouteResponse, ROUTE_ENDPOINT_PATH,
},
status::{StatusQueryParams, StatusResponse, STATUS_ENDPOINT_PATH},
Transaction,
},
error::RouteError,
};
use relay_rpc::domain::ProjectId;
use reqwest::{Client as ReqwestClient, Url};

pub struct Client {
client: ReqwestClient,
base_url: Url,
project_id: ProjectId,
}

impl Client {
pub fn new(project_id: ProjectId) -> Self {
Self {
client: ReqwestClient::new(),
base_url: "https://rpc.walletconnect.com".parse().unwrap(),
project_id,
}
}

pub async fn route(
&self,
transaction: Transaction,
) -> Result<RouteResponse, RouteError> {
let response = self
.client
.post(self.base_url.join(ROUTE_ENDPOINT_PATH).unwrap())
.json(&RouteRequest { transaction })
.query(&RouteQueryParams { project_id: self.project_id.clone() })
.send()
.await
.map_err(RouteError::Request)?;
if response.status().is_success() {
response.json().await.map_err(RouteError::Request)
} else {
Err(RouteError::RequestFailed(response.text().await))
}
}

pub async fn status(
&self,
orchestration_id: String,
) -> Result<StatusResponse, RouteError> {
self.client
.get(self.base_url.join(STATUS_ENDPOINT_PATH).unwrap())
.query(&StatusQueryParams {
project_id: self.project_id.clone(),
orchestration_id,
})
.send()
.await
.map_err(RouteError::Request)?
.error_for_status()
.map_err(RouteError::Request)?
.json()
.await
.map_err(RouteError::Request)
}
}
10 changes: 10 additions & 0 deletions crates/yttrium/src/chain_abstraction/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#[derive(thiserror::Error, Debug)]
pub enum RouteError {
/// Retryable error
#[error("HTTP request: {0}")]
Request(reqwest::Error),

/// Retryable error
#[error("HTTP request failed: {0:?}")]
RequestFailed(Result<String, reqwest::Error>),
}
3 changes: 3 additions & 0 deletions crates/yttrium/src/chain_abstraction/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod api;
pub mod client;
pub mod error;
1 change: 1 addition & 0 deletions crates/yttrium/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod account_client;
#[cfg(not(target_arch = "wasm32"))]
pub mod bundler;
pub mod chain;
pub mod chain_abstraction;
pub mod config;
pub mod eip7702;
pub mod entry_point;
Expand Down

0 comments on commit 840be8d

Please sign in to comment.