diff --git a/src/eth.rs b/src/eth.rs index f0fe7ac..16f3a1a 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -10,6 +10,8 @@ pub use alloy::rpc::types::{ pub use alloy_primitives::{Address, BlockHash, BlockNumber, Bytes, TxHash, U128, U256, U64, U8}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; +use std::error::Error; +use std::fmt; /// Subscription kind. Pulled directly from alloy (https://github.com/alloy-rs/alloy). /// Why? Because alloy is not yet 1.0 and the types in this interface must be stable. @@ -130,6 +132,24 @@ pub enum EthError { RpcMalformedResponse, } +impl fmt::Display for EthError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EthError::RpcError(e) => write!(f, "RPC error: {:?}", e), + EthError::MalformedRequest => write!(f, "Malformed request"), + EthError::NoRpcForChain => write!(f, "No RPC provider for chain"), + EthError::SubscriptionClosed(id) => write!(f, "Subscription {} closed", id), + EthError::InvalidMethod(m) => write!(f, "Invalid method: {}", m), + EthError::InvalidParams => write!(f, "Invalid parameters"), + EthError::PermissionDenied => write!(f, "Permission denied"), + EthError::RpcTimeout => write!(f, "RPC request timed out"), + EthError::RpcMalformedResponse => write!(f, "RPC returned malformed response"), + } + } +} + +impl Error for EthError {} + /// The action type used for configuring eth:distro:sys. Only processes which have the "root" /// [`crate::Capability`] from eth:distro:sys can successfully send this action. #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/src/kimap.rs b/src/kimap.rs index 19923c3..dfd0fb6 100644 --- a/src/kimap.rs +++ b/src/kimap.rs @@ -5,7 +5,10 @@ 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; /// kimap deployment address on optimism @@ -210,12 +213,19 @@ pub mod contract { function supportsInterface(bytes4 interfaceId) external view returns (bool); - /// Retrieves the address of the ERC-6551 implementation of the - /// zeroth entry. This is set once at initialization. + /// Gets the token identifier that owns this token-bound account (TBA). + /// This is a core function of the ERC-6551 standard that returns the + /// identifying information about the NFT that owns this account. + /// The return values are constant and cannot change over time. /// /// Returns: - /// - implementation: The address of the ERC-6551 implementation. - function get6551Implementation() external view returns (address); + /// - chainId: The EIP-155 chain ID where the owning NFT exists + /// - tokenContract: The contract address of the owning NFT + /// - tokenId: The token ID of the owning NFT + function token() + external + view + returns (uint256 chainId, address tokenContract, uint256 tokenId); } } @@ -259,6 +269,21 @@ pub enum DecodeLogError { 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 {} + /// Canonical function to determine if a kimap entry is valid. This should /// be used whenever reading a new kimap entry from a mints query, because /// while most frontends will enforce these rules, it is possible to post @@ -514,6 +539,28 @@ impl Kimap { Ok((res.tba, res.owner, note_data)) } + /// Gets a namehash from an existing TBA address. + /// + /// # Parameters + /// - `tba`: The TBA to get the namehash of. + /// # Returns + /// A `Result` representing the namehash of the TBA. + pub fn get_namehash_from_tba(&self, tba: Address) -> Result { + 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))) + } + /// Create a filter for all mint events. pub fn mint_filter(&self) -> crate::eth::Filter { crate::eth::Filter::new() diff --git a/src/net.rs b/src/net.rs index 231320d..2403b93 100644 --- a/src/net.rs +++ b/src/net.rs @@ -128,7 +128,7 @@ pub enum IndexerRequests { #[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)] pub struct NamehashToNameRequest { pub hash: String, - pub block: Option, + pub block: u64, } /// Response from `kns-indexer:kns-indexer:sys`. @@ -226,7 +226,7 @@ where .body( serde_json::to_vec(&IndexerRequests::NamehashToName(NamehashToNameRequest { hash: namehash.into(), - block, + block: block.unwrap_or(0), })) .unwrap(), )