use crate::eth::{EthError, Provider};
use crate::kimap::contract::getCall;
use crate::net;
use alloy::rpc::types::request::{TransactionInput, TransactionRequest};
use alloy::{hex, primitives::keccak256};
use alloy_primitives::{Address, Bytes, FixedBytes, B256};
use alloy_sol_types::{SolCall, SolEvent, SolValue};
use contract::tokenCall;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt;
use std::str::FromStr;
pub const KIMAP_ADDRESS: &'static str = "0x000000000033e5CCbC52Ec7BDa87dB768f9aA93F";
pub const KIMAP_CHAIN_ID: u64 = 8453;
pub const KIMAP_FIRST_BLOCK: u64 = 25_346_377;
pub const KIMAP_ROOT_HASH: &'static str =
"0x0000000000000000000000000000000000000000000000000000000000000000";
pub mod contract {
use alloy_sol_macro::sol;
sol! {
event Mint(
bytes32 indexed parenthash,
bytes32 indexed childhash,
bytes indexed labelhash,
bytes label
);
event Fact(
bytes32 indexed parenthash,
bytes32 indexed facthash,
bytes indexed labelhash,
bytes label,
bytes data
);
event Note(
bytes32 indexed parenthash,
bytes32 indexed notehash,
bytes indexed labelhash,
bytes label,
bytes data
);
event Gene(bytes32 indexed entry, address indexed gene);
event Zero(address indexed zeroTba);
event Transfer(
address indexed from,
address indexed to,
uint256 indexed id
);
event Approval(
address indexed owner,
address indexed spender,
uint256 indexed id
);
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
function get(
bytes32 namehash
) external view returns (address tba, address owner, bytes memory data);
function mint(
address who,
bytes calldata label,
bytes calldata initialization,
bytes calldata erc721Data,
address implementation
) external returns (address tba);
function gene(address _gene) external;
function fact(
bytes calldata fact,
bytes calldata data
) external returns (bytes32 facthash);
function note(
bytes calldata note,
bytes calldata data
) external returns (bytes32 notehash);
function tbaOf(uint256 entry) external view returns (address tba);
function balanceOf(address owner) external view returns (uint256);
function getApproved(uint256 entry) external view returns (address);
function isApprovedForAll(
address owner,
address operator
) external view returns (bool);
function ownerOf(uint256 entry) external view returns (address);
function setApprovalForAll(address operator, bool approved) external;
function approve(address spender, uint256 entry) external;
function safeTransferFrom(address from, address to, uint256 id) external;
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) external;
function transferFrom(address from, address to, uint256 id) external;
function supportsInterface(bytes4 interfaceId) external view returns (bool);
function token()
external
view
returns (uint256 chainId, address tokenContract, uint256 tokenId);
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Mint {
pub name: String,
pub parent_path: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Note {
pub note: String,
pub parent_path: String,
pub data: Bytes,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Fact {
pub fact: String,
pub parent_path: String,
pub data: Bytes,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum DecodeLogError {
UnexpectedTopic(B256),
InvalidName(String),
DecodeError(String),
UnresolvedParent(String),
}
impl fmt::Display for DecodeLogError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecodeLogError::UnexpectedTopic(topic) => write!(f, "Unexpected topic: {:?}", topic),
DecodeLogError::InvalidName(name) => write!(f, "Invalid name: {}", name),
DecodeLogError::DecodeError(err) => write!(f, "Decode error: {}", err),
DecodeLogError::UnresolvedParent(parent) => {
write!(f, "Could not resolve parent: {}", parent)
}
}
}
}
impl Error for DecodeLogError {}
pub fn valid_entry(entry: &str, note: bool, fact: bool) -> bool {
if note && fact {
return false;
}
if note {
valid_note(entry)
} else if fact {
valid_fact(entry)
} else {
valid_name(entry)
}
}
pub fn valid_name(name: &str) -> bool {
name.is_ascii()
&& name.len() >= 1
&& name
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
}
pub fn valid_note(note: &str) -> bool {
note.is_ascii()
&& note.len() >= 2
&& note.chars().next() == Some('~')
&& note
.chars()
.skip(1)
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
}
pub fn valid_fact(fact: &str) -> bool {
fact.is_ascii()
&& fact.len() >= 2
&& fact.chars().next() == Some('!')
&& fact
.chars()
.skip(1)
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
}
pub fn namehash(name: &str) -> String {
let mut node = B256::default();
let mut labels: Vec<&str> = name.split('.').collect();
labels.reverse();
for label in labels.iter() {
let l = keccak256(label);
node = keccak256((node, l).abi_encode_packed());
}
format!("0x{}", hex::encode(node))
}
pub fn decode_mint_log(log: &crate::eth::Log) -> Result<Mint, DecodeLogError> {
let contract::Note::SIGNATURE_HASH = log.topics()[0] else {
return Err(DecodeLogError::UnexpectedTopic(log.topics()[0]));
};
let decoded = contract::Mint::decode_log_data(log.data(), true)
.map_err(|e| DecodeLogError::DecodeError(e.to_string()))?;
let name = String::from_utf8_lossy(&decoded.label).to_string();
if !valid_name(&name) {
return Err(DecodeLogError::InvalidName(name));
}
match resolve_parent(log, None) {
Some(parent_path) => Ok(Mint { name, parent_path }),
None => Err(DecodeLogError::UnresolvedParent(name)),
}
}
pub fn decode_note_log(log: &crate::eth::Log) -> Result<Note, DecodeLogError> {
let contract::Note::SIGNATURE_HASH = log.topics()[0] else {
return Err(DecodeLogError::UnexpectedTopic(log.topics()[0]));
};
let decoded = contract::Note::decode_log_data(log.data(), true)
.map_err(|e| DecodeLogError::DecodeError(e.to_string()))?;
let note = String::from_utf8_lossy(&decoded.label).to_string();
if !valid_note(¬e) {
return Err(DecodeLogError::InvalidName(note));
}
match resolve_parent(log, None) {
Some(parent_path) => Ok(Note {
note,
parent_path,
data: decoded.data,
}),
None => Err(DecodeLogError::UnresolvedParent(note)),
}
}
pub fn decode_fact_log(log: &crate::eth::Log) -> Result<Fact, DecodeLogError> {
let contract::Fact::SIGNATURE_HASH = log.topics()[0] else {
return Err(DecodeLogError::UnexpectedTopic(log.topics()[0]));
};
let decoded = contract::Fact::decode_log_data(log.data(), true)
.map_err(|e| DecodeLogError::DecodeError(e.to_string()))?;
let fact = String::from_utf8_lossy(&decoded.label).to_string();
if !valid_fact(&fact) {
return Err(DecodeLogError::InvalidName(fact));
}
match resolve_parent(log, None) {
Some(parent_path) => Ok(Fact {
fact,
parent_path,
data: decoded.data,
}),
None => Err(DecodeLogError::UnresolvedParent(fact)),
}
}
pub fn resolve_parent(log: &crate::eth::Log, timeout: Option<u64>) -> Option<String> {
let parent_hash = log.topics()[1].to_string();
net::get_name(&parent_hash, log.block_number, timeout)
}
pub fn resolve_full_name(log: &crate::eth::Log, timeout: Option<u64>) -> Option<String> {
let parent_hash = log.topics()[1].to_string();
let parent_name = net::get_name(&parent_hash, log.block_number, timeout)?;
let log_name = match log.topics()[0] {
contract::Mint::SIGNATURE_HASH => {
let decoded = contract::Mint::decode_log_data(log.data(), true).unwrap();
decoded.label
}
contract::Note::SIGNATURE_HASH => {
let decoded = contract::Note::decode_log_data(log.data(), true).unwrap();
decoded.label
}
contract::Fact::SIGNATURE_HASH => {
let decoded = contract::Fact::decode_log_data(log.data(), true).unwrap();
decoded.label
}
_ => return None,
};
let name = String::from_utf8_lossy(&log_name);
if !valid_entry(
&name,
log.topics()[0] == contract::Note::SIGNATURE_HASH,
log.topics()[0] == contract::Fact::SIGNATURE_HASH,
) {
return None;
}
Some(format!("{name}.{parent_name}"))
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Kimap {
pub provider: Provider,
address: Address,
}
impl Kimap {
pub fn new(provider: Provider, address: Address) -> Self {
Self { provider, address }
}
pub fn default(timeout: u64) -> Self {
let provider = Provider::new(KIMAP_CHAIN_ID, timeout);
Self::new(provider, Address::from_str(KIMAP_ADDRESS).unwrap())
}
pub fn address(&self) -> &Address {
&self.address
}
pub fn get(&self, path: &str) -> Result<(Address, Address, Option<Bytes>), EthError> {
let get_call = getCall {
namehash: FixedBytes::<32>::from_str(&namehash(path))
.map_err(|_| EthError::InvalidParams)?,
}
.abi_encode();
let tx_req = TransactionRequest::default()
.input(TransactionInput::new(get_call.into()))
.to(self.address);
let res_bytes = self.provider.call(tx_req, None)?;
let res = getCall::abi_decode_returns(&res_bytes, false)
.map_err(|_| EthError::RpcMalformedResponse)?;
let note_data = if res.data == Bytes::default() {
None
} else {
Some(res.data)
};
Ok((res.tba, res.owner, note_data))
}
pub fn get_hash(&self, entryhash: &str) -> Result<(Address, Address, Option<Bytes>), EthError> {
let get_call = getCall {
namehash: FixedBytes::<32>::from_str(entryhash).map_err(|_| EthError::InvalidParams)?,
}
.abi_encode();
let tx_req = TransactionRequest::default()
.input(TransactionInput::new(get_call.into()))
.to(self.address);
let res_bytes = self.provider.call(tx_req, None)?;
let res = getCall::abi_decode_returns(&res_bytes, false)
.map_err(|_| EthError::RpcMalformedResponse)?;
let note_data = if res.data == Bytes::default() {
None
} else {
Some(res.data)
};
Ok((res.tba, res.owner, note_data))
}
pub fn get_namehash_from_tba(&self, tba: Address) -> Result<String, EthError> {
let token_call = tokenCall {}.abi_encode();
let tx_req = TransactionRequest::default()
.input(TransactionInput::new(token_call.into()))
.to(tba);
let res_bytes = self.provider.call(tx_req, None)?;
let res = tokenCall::abi_decode_returns(&res_bytes, false)
.map_err(|_| EthError::RpcMalformedResponse)?;
let namehash: FixedBytes<32> = res.tokenId.into();
Ok(format!("0x{}", hex::encode(namehash)))
}
pub fn mint_filter(&self) -> crate::eth::Filter {
crate::eth::Filter::new()
.address(self.address)
.event(contract::Mint::SIGNATURE)
}
pub fn note_filter(&self) -> crate::eth::Filter {
crate::eth::Filter::new()
.address(self.address)
.event(contract::Note::SIGNATURE)
}
pub fn fact_filter(&self) -> crate::eth::Filter {
crate::eth::Filter::new()
.address(self.address)
.event(contract::Fact::SIGNATURE)
}
pub fn notes_filter(&self, notes: &[&str]) -> crate::eth::Filter {
self.note_filter().topic3(
notes
.into_iter()
.map(|note| keccak256(note))
.collect::<Vec<_>>(),
)
}
pub fn facts_filter(&self, facts: &[&str]) -> crate::eth::Filter {
self.fact_filter().topic3(
facts
.into_iter()
.map(|fact| keccak256(fact))
.collect::<Vec<_>>(),
)
}
}