use serde::{Deserialize, Serialize};
pub type U256 = [u8; 32];
#[must_use]
pub fn u256_from_u128(v: u128) -> U256 {
let mut out = [0_u8; 32];
out[16..].copy_from_slice(&v.to_be_bytes());
out
}
#[must_use]
pub fn u256_to_u128(v: &U256) -> Option<u128> {
if v[..16].iter().any(|&b| b != 0) {
return None;
}
let mut low = [0_u8; 16];
low.copy_from_slice(&v[16..]);
Some(u128::from_be_bytes(low))
}
#[must_use]
pub fn u256_is_unlimited(v: &U256) -> bool {
v[..16].iter().any(|&b| b != 0)
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Chain {
Evm {
chain_id: u64,
},
Solana {
cluster: String,
},
}
impl Chain {
#[must_use]
pub fn key(&self) -> String {
match self {
Chain::Evm { chain_id } => format!("evm:{chain_id}"),
Chain::Solana { cluster } => format!("solana:{cluster}"),
}
}
#[must_use]
pub fn is_evm(&self) -> bool {
matches!(self, Chain::Evm { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Authorization7702 {
pub chain_id: u64,
pub address: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct SwapQuote {
pub quoted_out: u128,
pub min_out: u128,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SolanaInstruction {
pub program_id: String,
pub accounts: Vec<String>,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Action {
EvmTx {
chain_id: u64,
to: Option<String>,
value: U256,
data: Vec<u8>,
#[serde(default)]
authorizations: Vec<Authorization7702>,
},
EvmTypedData {
chain_id: u64,
primary_type: String,
verifying_contract: String,
spender: Option<String>,
amount: Option<U256>,
},
SolanaTx {
cluster: String,
instructions: Vec<SolanaInstruction>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Request {
pub venture: String,
pub subject: String,
pub from: String,
pub action: Action,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub swap: Option<SwapQuote>,
}
impl Request {
#[must_use]
pub fn chain(&self) -> Chain {
match &self.action {
Action::EvmTx { chain_id, .. } | Action::EvmTypedData { chain_id, .. } => Chain::Evm {
chain_id: *chain_id,
},
Action::SolanaTx { cluster, .. } => Chain::Solana {
cluster: cluster.clone(),
},
}
}
#[must_use]
pub fn summary(&self) -> String {
match &self.action {
Action::EvmTx {
chain_id,
to,
data,
authorizations,
..
} => {
let sel = data.first_chunk::<4>().map_or_else(
|| "native".to_owned(),
|s| format!("sel=0x{:08x}", u32::from_be_bytes(*s)),
);
let auth = if authorizations.is_empty() {
String::new()
} else {
format!(" 7702x{}", authorizations.len())
};
format!(
"evm_tx chain={chain_id} to={} {sel}{auth}",
to.as_deref().unwrap_or("create")
)
}
Action::EvmTypedData {
chain_id,
primary_type,
verifying_contract,
..
} => {
format!(
"evm_typed_data chain={chain_id} type={primary_type} contract={verifying_contract}"
)
}
Action::SolanaTx {
cluster,
instructions,
} => {
format!("solana_tx cluster={cluster} ix={}", instructions.len())
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct TokenKey {
pub chain: Chain,
pub token: String,
}
impl TokenKey {
#[must_use]
pub fn native(chain: Chain) -> Self {
let native = "native".to_owned();
Self {
chain,
token: native,
}
}
#[must_use]
pub fn key(&self) -> String {
format!("{}:{}", self.chain.key(), self.token)
}
}