use alloy_primitives::Address;
use clickhouse::Row;
use redefined::{self_convert_redefined, Redefined};
use rkyv::{Archive, Deserialize as rDeserialize, Serialize as rSerialize};
use serde::{Deserialize, Serialize};
use crate::{
db::redefined_types::primitives::AddressRedefined,
implement_table_value_codecs_with_zc,
serde_utils::{option_contract_info, socials},
};
#[derive(Debug, Default, Row, PartialEq, Clone, Eq, Serialize, Deserialize, Redefined)]
#[redefined_attr(derive(Debug, PartialEq, Clone, Serialize, rSerialize, rDeserialize, Archive))]
pub struct AddressMetadata {
pub entity_name: Option<String>,
pub nametag: Option<String>,
pub labels: Vec<String>,
#[serde(rename = "type")]
pub address_type: Option<String>,
#[serde(deserialize_with = "option_contract_info::deserialize")]
pub contract_info: Option<ContractInfo>,
pub ens: Option<String>,
#[serde(deserialize_with = "socials::deserialize")]
#[serde(serialize_with = "socials::serialize")]
#[redefined(same_fields)]
pub social_metadata: Socials,
}
impl AddressMetadata {
pub fn is_verified(&self) -> bool {
self.contract_info
.as_ref()
.map_or(false, |c| c.verified_contract.unwrap_or(false))
}
pub fn describe(&self) -> Option<String> {
self.nametag
.clone()
.or_else(|| self.entity_name.clone())
.or_else(|| self.address_type.clone())
.or_else(|| self.ens.clone())
.or_else(|| self.social_metadata.twitter.clone())
.or_else(|| self.labels.first().cloned())
}
pub fn get_contract_type(&self) -> ContractType {
if self.is_cex_exchange() {
return ContractType::CexExchange;
}
if self.is_settlement_contract() {
return ContractType::SolverSettlement;
}
if self.is_automation_contract() {
return ContractType::DefiAutomation;
}
if self.is_cex() {
return ContractType::Cex;
}
if self.is_aggregator() {
return ContractType::Router;
}
if let Some(contract_type) = self.get_contract_type_from_nametag() {
return contract_type;
}
self.get_contract_type_from_labels()
.unwrap_or(ContractType::Unknown)
}
fn is_automation_contract(&self) -> bool {
self.labels
.iter()
.any(|label| label.to_lowercase().contains("automation"))
}
fn is_settlement_contract(&self) -> bool {
if let Some(nametag) = &self.nametag {
if nametag.eq_ignore_ascii_case("UniswapX")
|| nametag.eq_ignore_ascii_case("CoW Protocol")
{
return true;
}
}
self.labels
.iter()
.any(|label| label.eq_ignore_ascii_case("settlement"))
}
fn is_cex(&self) -> bool {
self.address_type
.as_deref()
.map_or(false, |t| t.eq_ignore_ascii_case("cex"))
}
fn is_aggregator(&self) -> bool {
self.address_type
.as_deref()
.map_or(false, |t| t.eq_ignore_ascii_case("aggregator"))
}
fn is_cex_exchange(&self) -> bool {
self.labels
.iter()
.any(|label| label.to_lowercase().contains("exchange"))
&& self.is_cex()
}
fn get_contract_type_from_nametag(&self) -> Option<ContractType> {
self.nametag.as_ref().and_then(|nametag| {
let nametag_lower = nametag.to_lowercase();
match nametag_lower.as_str() {
n if n.starts_with("mev bot:") => Some(ContractType::MevBot),
n if n.contains("router") => Some(ContractType::Router),
n if n.contains("protocol") => Some(ContractType::Protocol),
n if n.contains("exchange") => Some(ContractType::Exchange),
n if n.contains("bridge") => Some(ContractType::Bridge),
_ => None,
}
})
}
fn get_contract_type_from_labels(&self) -> Option<ContractType> {
self.labels.iter().find_map(|label| {
let label_lower = label.to_lowercase();
match label_lower.as_str() {
l if l.contains("mev bot") => Some(ContractType::MevBot),
l if l.contains("router") => Some(ContractType::Router),
l if l.contains("protocol") => Some(ContractType::Protocol),
l if l.contains("exchange") => Some(ContractType::Exchange),
_ => None,
}
})
}
pub fn merge(&mut self, other: Self) {
if other.entity_name.is_some() {
self.entity_name = other.entity_name;
}
for label in other.labels.into_iter() {
if !self.labels.iter().any(|l| l.eq_ignore_ascii_case(&label)) {
self.labels.push(label);
}
}
if other.nametag.is_some() {
self.nametag = other.nametag
}
if other.address_type.is_some() {
self.address_type = other.address_type
}
if other.ens.is_some() {
self.ens = other.ens
}
if let Some(other_contract_info) = other.contract_info {
match &mut self.contract_info {
Some(c) => c.merge(other_contract_info),
None => self.contract_info = Some(other_contract_info),
}
}
self.social_metadata.merge(other.social_metadata);
}
}
#[derive(Debug, Clone)]
pub enum ContractType {
MevBot,
Router,
Exchange,
Cex,
Protocol,
CexExchange,
Bridge,
SolverSettlement,
DefiAutomation,
Unknown,
}
impl ContractType {
pub fn could_be_mev_contract(&self) -> bool {
matches!(self, ContractType::MevBot | ContractType::Unknown)
}
pub fn is_solver_settlement(&self) -> bool {
matches!(self, ContractType::SolverSettlement)
}
pub fn is_mev_contract(&self) -> bool {
matches!(self, ContractType::MevBot)
}
pub fn is_defi_automation(&self) -> bool {
matches!(self, ContractType::DefiAutomation)
}
}
implement_table_value_codecs_with_zc!(AddressMetadataRedefined);
#[derive(Debug, Default, PartialEq, Clone, Eq, Serialize, Deserialize, Redefined)]
#[redefined_attr(derive(Debug, PartialEq, Clone, Serialize, rSerialize, rDeserialize, Archive))]
pub struct ContractInfo {
pub verified_contract: Option<bool>,
pub contract_creator: Option<Address>,
pub reputation: Option<u8>,
}
impl ContractInfo {
fn merge(&mut self, other: ContractInfo) {
if let Some(verified_contract) = other.verified_contract {
self.verified_contract = Some(verified_contract);
}
if let Some(contract_creator) = other.contract_creator {
self.contract_creator = Some(contract_creator);
}
if let Some(reputation) = other.reputation {
self.reputation = Some(reputation);
}
}
}
#[derive(
Debug, Default, PartialEq, Clone, Eq, Serialize, Deserialize, rSerialize, rDeserialize, Archive,
)]
pub struct Socials {
pub twitter: Option<String>,
pub twitter_followers: Option<u64>,
pub website_url: Option<String>,
pub crunchbase: Option<String>,
pub linkedin: Option<String>,
}
impl Socials {
fn merge(&mut self, other: Socials) {
if let Some(twitter) = other.twitter {
self.twitter = Some(twitter);
}
if let Some(twitter_followers) = other.twitter_followers {
self.twitter_followers = Some(twitter_followers);
}
if let Some(website_url) = other.website_url {
self.website_url = Some(website_url);
}
if let Some(crunchbase) = other.crunchbase {
self.crunchbase = Some(crunchbase);
}
if let Some(linkedin) = other.linkedin {
self.linkedin = Some(linkedin);
}
}
}
self_convert_redefined!(Socials);