1use crate::eth::{EthError, Provider};
2use crate::kimap::contract::getCall;
3use crate::net;
4use alloy::rpc::types::request::{TransactionInput, TransactionRequest};
5use alloy::{hex, primitives::keccak256};
6use alloy_primitives::{Address, Bytes, FixedBytes, B256};
7use alloy_sol_types::{SolCall, SolEvent, SolValue};
8use contract::tokenCall;
9use serde::{Deserialize, Serialize};
10use std::error::Error;
11use std::fmt;
12use std::str::FromStr;
13
14pub const KIMAP_ADDRESS: &'static str = "0x000000000033e5CCbC52Ec7BDa87dB768f9aA93F";
16pub const KIMAP_CHAIN_ID: u64 = 8453;
18pub const KIMAP_FIRST_BLOCK: u64 = 25_346_377;
20pub const KIMAP_ROOT_HASH: &'static str =
22 "0x0000000000000000000000000000000000000000000000000000000000000000";
23
24pub mod contract {
26 use alloy_sol_macro::sol;
27
28 sol! {
29 event Mint(
35 bytes32 indexed parenthash,
36 bytes32 indexed childhash,
37 bytes indexed labelhash,
38 bytes label
39 );
40
41 event Fact(
50 bytes32 indexed parenthash,
51 bytes32 indexed facthash,
52 bytes indexed labelhash,
53 bytes label,
54 bytes data
55 );
56
57 event Note(
66 bytes32 indexed parenthash,
67 bytes32 indexed notehash,
68 bytes indexed labelhash,
69 bytes label,
70 bytes data
71 );
72
73 event Gene(bytes32 indexed entry, address indexed gene);
79
80 event Zero(address indexed zeroTba);
84
85 event Transfer(
91 address indexed from,
92 address indexed to,
93 uint256 indexed id
94 );
95
96 event Approval(
101 address indexed owner,
102 address indexed spender,
103 uint256 indexed id
104 );
105
106 event ApprovalForAll(
112 address indexed owner,
113 address indexed operator,
114 bool approved
115 );
116
117 function get(
127 bytes32 namehash
128 ) external view returns (address tba, address owner, bytes memory data);
129
130 function mint(
145 address who,
146 bytes calldata label,
147 bytes calldata initialization,
148 bytes calldata erc721Data,
149 address implementation
150 ) external returns (address tba);
151
152 function gene(address _gene) external;
156
157 function fact(
165 bytes calldata fact,
166 bytes calldata data
167 ) external returns (bytes32 facthash);
168
169 function note(
176 bytes calldata note,
177 bytes calldata data
178 ) external returns (bytes32 notehash);
179
180 function tbaOf(uint256 entry) external view returns (address tba);
187
188 function balanceOf(address owner) external view returns (uint256);
189
190 function getApproved(uint256 entry) external view returns (address);
191
192 function isApprovedForAll(
193 address owner,
194 address operator
195 ) external view returns (bool);
196
197 function ownerOf(uint256 entry) external view returns (address);
198
199 function setApprovalForAll(address operator, bool approved) external;
200
201 function approve(address spender, uint256 entry) external;
202
203 function safeTransferFrom(address from, address to, uint256 id) external;
204
205 function safeTransferFrom(
206 address from,
207 address to,
208 uint256 id,
209 bytes calldata data
210 ) external;
211
212 function transferFrom(address from, address to, uint256 id) external;
213
214 function supportsInterface(bytes4 interfaceId) external view returns (bool);
215
216 function token()
226 external
227 view
228 returns (uint256 chainId, address tokenContract, uint256 tokenId);
229 }
230}
231
232#[derive(Clone, Debug, Deserialize, Serialize)]
235pub struct Mint {
236 pub name: String,
237 pub parent_path: String,
238}
239
240#[derive(Clone, Debug, Deserialize, Serialize)]
243pub struct Note {
244 pub note: String,
245 pub parent_path: String,
246 pub data: Bytes,
247}
248
249#[derive(Clone, Debug, Deserialize, Serialize)]
252pub struct Fact {
253 pub fact: String,
254 pub parent_path: String,
255 pub data: Bytes,
256}
257
258#[derive(Clone, Debug, Deserialize, Serialize)]
261pub enum DecodeLogError {
262 UnexpectedTopic(B256),
264 InvalidName(String),
266 DecodeError(String),
268 UnresolvedParent(String),
270}
271
272impl fmt::Display for DecodeLogError {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 match self {
275 DecodeLogError::UnexpectedTopic(topic) => write!(f, "Unexpected topic: {:?}", topic),
276 DecodeLogError::InvalidName(name) => write!(f, "Invalid name: {}", name),
277 DecodeLogError::DecodeError(err) => write!(f, "Decode error: {}", err),
278 DecodeLogError::UnresolvedParent(parent) => {
279 write!(f, "Could not resolve parent: {}", parent)
280 }
281 }
282 }
283}
284
285impl Error for DecodeLogError {}
286
287pub fn valid_entry(entry: &str, note: bool, fact: bool) -> bool {
295 if note && fact {
296 return false;
297 }
298 if note {
299 valid_note(entry)
300 } else if fact {
301 valid_fact(entry)
302 } else {
303 valid_name(entry)
304 }
305}
306
307pub fn valid_name(name: &str) -> bool {
308 name.is_ascii()
309 && name.len() >= 1
310 && name
311 .chars()
312 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
313}
314
315pub fn valid_note(note: &str) -> bool {
316 note.is_ascii()
317 && note.len() >= 2
318 && note.chars().next() == Some('~')
319 && note
320 .chars()
321 .skip(1)
322 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
323}
324
325pub fn valid_fact(fact: &str) -> bool {
326 fact.is_ascii()
327 && fact.len() >= 2
328 && fact.chars().next() == Some('!')
329 && fact
330 .chars()
331 .skip(1)
332 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
333}
334
335pub fn namehash(name: &str) -> String {
337 let mut node = B256::default();
338
339 let mut labels: Vec<&str> = name.split('.').collect();
340 labels.reverse();
341
342 for label in labels.iter() {
343 let l = keccak256(label);
344 node = keccak256((node, l).abi_encode_packed());
345 }
346 format!("0x{}", hex::encode(node))
347}
348
349pub fn decode_mint_log(log: &crate::eth::Log) -> Result<Mint, DecodeLogError> {
353 let contract::Note::SIGNATURE_HASH = log.topics()[0] else {
354 return Err(DecodeLogError::UnexpectedTopic(log.topics()[0]));
355 };
356 let decoded = contract::Mint::decode_log_data(log.data(), true)
357 .map_err(|e| DecodeLogError::DecodeError(e.to_string()))?;
358 let name = String::from_utf8_lossy(&decoded.label).to_string();
359 if !valid_name(&name) {
360 return Err(DecodeLogError::InvalidName(name));
361 }
362 match resolve_parent(log, None) {
363 Some(parent_path) => Ok(Mint { name, parent_path }),
364 None => Err(DecodeLogError::UnresolvedParent(name)),
365 }
366}
367
368pub fn decode_note_log(log: &crate::eth::Log) -> Result<Note, DecodeLogError> {
372 let contract::Note::SIGNATURE_HASH = log.topics()[0] else {
373 return Err(DecodeLogError::UnexpectedTopic(log.topics()[0]));
374 };
375 let decoded = contract::Note::decode_log_data(log.data(), true)
376 .map_err(|e| DecodeLogError::DecodeError(e.to_string()))?;
377 let note = String::from_utf8_lossy(&decoded.label).to_string();
378 if !valid_note(¬e) {
379 return Err(DecodeLogError::InvalidName(note));
380 }
381 match resolve_parent(log, None) {
382 Some(parent_path) => Ok(Note {
383 note,
384 parent_path,
385 data: decoded.data,
386 }),
387 None => Err(DecodeLogError::UnresolvedParent(note)),
388 }
389}
390
391pub fn decode_fact_log(log: &crate::eth::Log) -> Result<Fact, DecodeLogError> {
392 let contract::Fact::SIGNATURE_HASH = log.topics()[0] else {
393 return Err(DecodeLogError::UnexpectedTopic(log.topics()[0]));
394 };
395 let decoded = contract::Fact::decode_log_data(log.data(), true)
396 .map_err(|e| DecodeLogError::DecodeError(e.to_string()))?;
397 let fact = String::from_utf8_lossy(&decoded.label).to_string();
398 if !valid_fact(&fact) {
399 return Err(DecodeLogError::InvalidName(fact));
400 }
401 match resolve_parent(log, None) {
402 Some(parent_path) => Ok(Fact {
403 fact,
404 parent_path,
405 data: decoded.data,
406 }),
407 None => Err(DecodeLogError::UnresolvedParent(fact)),
408 }
409}
410
411pub fn resolve_parent(log: &crate::eth::Log, timeout: Option<u64>) -> Option<String> {
414 let parent_hash = log.topics()[1].to_string();
415 net::get_name(&parent_hash, log.block_number, timeout)
416}
417
418pub fn resolve_full_name(log: &crate::eth::Log, timeout: Option<u64>) -> Option<String> {
423 let parent_hash = log.topics()[1].to_string();
424 let parent_name = net::get_name(&parent_hash, log.block_number, timeout)?;
425 let log_name = match log.topics()[0] {
426 contract::Mint::SIGNATURE_HASH => {
427 let decoded = contract::Mint::decode_log_data(log.data(), true).unwrap();
428 decoded.label
429 }
430 contract::Note::SIGNATURE_HASH => {
431 let decoded = contract::Note::decode_log_data(log.data(), true).unwrap();
432 decoded.label
433 }
434 contract::Fact::SIGNATURE_HASH => {
435 let decoded = contract::Fact::decode_log_data(log.data(), true).unwrap();
436 decoded.label
437 }
438 _ => return None,
439 };
440 let name = String::from_utf8_lossy(&log_name);
441 if !valid_entry(
442 &name,
443 log.topics()[0] == contract::Note::SIGNATURE_HASH,
444 log.topics()[0] == contract::Fact::SIGNATURE_HASH,
445 ) {
446 return None;
447 }
448 Some(format!("{name}.{parent_name}"))
449}
450
451#[derive(Clone, Debug, Deserialize, Serialize)]
453pub struct Kimap {
454 pub provider: Provider,
455 address: Address,
456}
457
458impl Kimap {
459 pub fn new(provider: Provider, address: Address) -> Self {
465 Self { provider, address }
466 }
467
468 pub fn default(timeout: u64) -> Self {
470 let provider = Provider::new(KIMAP_CHAIN_ID, timeout);
471 Self::new(provider, Address::from_str(KIMAP_ADDRESS).unwrap())
472 }
473
474 pub fn address(&self) -> &Address {
476 &self.address
477 }
478
479 pub fn get(&self, path: &str) -> Result<(Address, Address, Option<Bytes>), EthError> {
487 let get_call = getCall {
488 namehash: FixedBytes::<32>::from_str(&namehash(path))
489 .map_err(|_| EthError::InvalidParams)?,
490 }
491 .abi_encode();
492
493 let tx_req = TransactionRequest::default()
494 .input(TransactionInput::new(get_call.into()))
495 .to(self.address);
496
497 let res_bytes = self.provider.call(tx_req, None)?;
498
499 let res = getCall::abi_decode_returns(&res_bytes, false)
500 .map_err(|_| EthError::RpcMalformedResponse)?;
501
502 let note_data = if res.data == Bytes::default() {
503 None
504 } else {
505 Some(res.data)
506 };
507
508 Ok((res.tba, res.owner, note_data))
509 }
510
511 pub fn get_hash(&self, entryhash: &str) -> Result<(Address, Address, Option<Bytes>), EthError> {
519 let get_call = getCall {
520 namehash: FixedBytes::<32>::from_str(entryhash).map_err(|_| EthError::InvalidParams)?,
521 }
522 .abi_encode();
523
524 let tx_req = TransactionRequest::default()
525 .input(TransactionInput::new(get_call.into()))
526 .to(self.address);
527
528 let res_bytes = self.provider.call(tx_req, None)?;
529
530 let res = getCall::abi_decode_returns(&res_bytes, false)
531 .map_err(|_| EthError::RpcMalformedResponse)?;
532
533 let note_data = if res.data == Bytes::default() {
534 None
535 } else {
536 Some(res.data)
537 };
538
539 Ok((res.tba, res.owner, note_data))
540 }
541
542 pub fn get_namehash_from_tba(&self, tba: Address) -> Result<String, EthError> {
549 let token_call = tokenCall {}.abi_encode();
550
551 let tx_req = TransactionRequest::default()
552 .input(TransactionInput::new(token_call.into()))
553 .to(tba);
554
555 let res_bytes = self.provider.call(tx_req, None)?;
556
557 let res = tokenCall::abi_decode_returns(&res_bytes, false)
558 .map_err(|_| EthError::RpcMalformedResponse)?;
559
560 let namehash: FixedBytes<32> = res.tokenId.into();
561 Ok(format!("0x{}", hex::encode(namehash)))
562 }
563
564 pub fn mint_filter(&self) -> crate::eth::Filter {
566 crate::eth::Filter::new()
567 .address(self.address)
568 .event(contract::Mint::SIGNATURE)
569 }
570
571 pub fn note_filter(&self) -> crate::eth::Filter {
573 crate::eth::Filter::new()
574 .address(self.address)
575 .event(contract::Note::SIGNATURE)
576 }
577
578 pub fn fact_filter(&self) -> crate::eth::Filter {
580 crate::eth::Filter::new()
581 .address(self.address)
582 .event(contract::Fact::SIGNATURE)
583 }
584
585 pub fn notes_filter(&self, notes: &[&str]) -> crate::eth::Filter {
593 self.note_filter().topic3(
594 notes
595 .into_iter()
596 .map(|note| keccak256(note))
597 .collect::<Vec<_>>(),
598 )
599 }
600
601 pub fn facts_filter(&self, facts: &[&str]) -> crate::eth::Filter {
609 self.fact_filter().topic3(
610 facts
611 .into_iter()
612 .map(|fact| keccak256(fact))
613 .collect::<Vec<_>>(),
614 )
615 }
616}