Skip to content

Commit

Permalink
pipelines, conditions and a draft of TradingEngine
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrostr committed Feb 4, 2025
1 parent f096b74 commit cae9c65
Show file tree
Hide file tree
Showing 11 changed files with 451 additions and 144 deletions.
82 changes: 82 additions & 0 deletions listen-trading-engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions listen-trading-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0.95"
base64 = "0.22.1"
chrono = { version = "0.4.39", features = ["serde"] }
ctor = "0.2.9"
dotenv = "0.15.0"
reqwest = { version = "0.12.12", features = ["json"] }
Expand All @@ -14,3 +15,4 @@ serde_json = "1.0.138"
tokio = { version = "1.43.0", features = ["rt", "macros"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
uuid = { version = "1.12.1", features = ["serde"] }
2 changes: 2 additions & 0 deletions listen-trading-engine/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod trading_engine;

pub use trading_engine::TradingEngine;

#[ctor::ctor]
fn init() {
tracing_subscriber::fmt::init();
Expand Down
1 change: 1 addition & 0 deletions listen-trading-engine/src/trading_engine/caip2.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub struct Caip2;

impl Caip2 {
pub const SOLANA: &str = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
pub const ARBITRUM: &str = "eip155:42161";
Expand Down
27 changes: 27 additions & 0 deletions listen-trading-engine/src/trading_engine/evaluator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use std::collections::HashMap;

use super::pipeline::{Condition, ConditionType};
pub struct Evaluator;

impl Evaluator {
pub fn evaluate_conditions(conditions: &[Condition], prices: &HashMap<String, f64>) -> bool {
conditions
.iter()
.all(|c| Self::evaluate_condition(c, prices))
}

fn evaluate_condition(condition: &Condition, prices: &HashMap<String, f64>) -> bool {
match &condition.condition_type {
ConditionType::PriceAbove { asset, threshold } => {
prices.get(asset).map(|p| p >= threshold).unwrap_or(false)
}
ConditionType::PriceBelow { asset, threshold } => {
prices.get(asset).map(|p| p <= threshold).unwrap_or(false)
}
ConditionType::And(sub) => sub.iter().all(|c| Self::evaluate_condition(c, prices)),
ConditionType::Or(sub) => sub.iter().any(|c| Self::evaluate_condition(c, prices)),
// PercentageChange would require historical data tracking
_ => false,
}
}
}
147 changes: 147 additions & 0 deletions listen-trading-engine/src/trading_engine/executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use super::order::Order;
use super::privy_config::PrivyConfig;
use super::types::{SignAndSendEvmTransactionParams, SignAndSendEvmTransactionRequest};
use super::types::{
SignAndSendTransactionParams, SignAndSendTransactionRequest, SignAndSendTransactionResponse,
};
use super::util::create_http_client;
use anyhow::{anyhow, Result};

pub struct Executor {
http_client: reqwest::Client,
}

impl Executor {
pub fn from_env() -> Result<Self> {
let privy_config = PrivyConfig::from_env()?;
let http_client = create_http_client(&privy_config);
Ok(Self { http_client })
}

pub async fn execute_order(&self, order: Order) -> Result<String> {
if order.is_solana() {
if order.solana_transaction.is_none() {
return Err(anyhow!("Solana transaction required for Solana order"));
}
self.execute_solana_transaction(
order.address,
order.solana_transaction.unwrap(),
order.caip2,
)
.await
} else {
if order.evm_transaction.is_none() {
return Err(anyhow!("EVM transaction required for EVM order"));
}
self.execute_evm_transaction(order.address, order.evm_transaction.unwrap(), order.caip2)
.await
}
}

async fn execute_evm_transaction(
&self,
address: String,
transaction: serde_json::Value,
caip2: String,
) -> Result<String> {
tracing::info!(?address, "Executing EVM transaction");
let request = SignAndSendEvmTransactionRequest {
address,
chain_type: "ethereum".to_string(),
method: "eth_sendTransaction".to_string(),
caip2,
params: SignAndSendEvmTransactionParams { transaction },
};

let response = self
.http_client
.post("https://auth.privy.io/api/v1/wallets/rpc")
.json(&request)
.send()
.await?;

if !response.status().is_success() {
return Err(anyhow!(
"Failed to send transaction: {}",
response.text().await?
));
}

let result: SignAndSendTransactionResponse = response.json().await?;
tracing::info!(
?result.method,
?result.data.hash,
?result.data.caip2,
"Transaction sent",
);
Ok(result.data.hash)
}

async fn execute_solana_transaction(
&self,
address: String,
transaction: String,
caip2: String,
) -> Result<String> {
tracing::info!(?address, "Executing Solana transaction");
let request = SignAndSendTransactionRequest {
address,
chain_type: "solana".to_string(),
method: "signAndSendTransaction".to_string(),
caip2,
params: SignAndSendTransactionParams {
transaction,
encoding: "base64".to_string(),
},
};

let response = self
.http_client
.post("https://api.privy.io/v1/wallets/rpc")
.json(&request)
.send()
.await?;

if !response.status().is_success() {
return Err(anyhow!(
"Failed to sign transaction: {}",
response.text().await?
));
}

let result: SignAndSendTransactionResponse = response.json().await?;
tracing::info!(
?result.method,
?result.data.hash,
?result.data.caip2,
"Transaction sent",
);
Ok(result.data.hash)
}
}

#[cfg(test)]
mod tests {
use crate::trading_engine::caip2::Caip2;
use crate::trading_engine::constants::*;
use crate::trading_engine::executor::Executor;
use crate::trading_engine::order::Order;

#[tokio::test]
async fn test_execute_order_eth() {
let engine = Executor::from_env().unwrap();
let order = Order {
user_id: "-".to_string(),
address: TEST_ADDRESS_EVM.to_string(),
caip2: Caip2::ARBITRUM.to_string(),
evm_transaction: Some(serde_json::json!({
"from": TEST_ADDRESS_EVM,
"to": TEST_ADDRESS_EVM,
"value": "0x111",
})),
solana_transaction: None,
};
let result = engine.execute_order(order).await.unwrap();
assert_eq!(result.len(), 66);
}
}
Loading

0 comments on commit cae9c65

Please sign in to comment.