Skip to content

Commit

Permalink
add resolve_avatar method with CCIP support (#5)
Browse files Browse the repository at this point in the history
* add resolve_avatar method with CCIP support

* fmt

* add doc

* in case of widlcard_support check, do not panic

* fmt

* graceful error handling

* fmt

* version bump
  • Loading branch information
mdtanrikulu authored May 26, 2023
1 parent 0b1f453 commit 5669e42
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 7 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ethers-ccip-read"
version = "0.1.0"
version = "0.1.1"
authors = ["Muhamed Tanrikulu <[email protected]>"]
license = "MIT"
edition = "2018"
Expand Down Expand Up @@ -34,6 +34,7 @@ async-trait = { version = "0.1.50", default-features = false }
# Ethers
ethers-core = "2.0.4"
ethers-providers = "2.0.4"
futures-util = "0.3.28"

[dev-dependencies]
tokio = { version = "1.7.1", features = ["macros", "rt-multi-thread"] }
Expand Down
9 changes: 9 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ pub enum CCIPReadMiddlewareError<M: Middleware> {
/// Invalid reverse ENS name
#[error("Reversed ens name not pointing to itself: {0}")]
EnsNotOwned(String),

#[error("Error(s) during parsing avatar url: {0}")]
URLParseError(String),

#[error("Error(s) during NFT ownership verification: {0}")]
NFTOwnerError(String),

#[error("Unsupported URL scheme")]
UnsupportedURLSchemeError,
}

impl<M: Middleware> MiddlewareError for CCIPReadMiddlewareError<M> {
Expand Down
91 changes: 85 additions & 6 deletions src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use ethers_core::{
},
utils::{self, hex},
};
use ethers_providers::{ens, Middleware, MiddlewareError};
use ethers_providers::{ens, erc, Middleware, MiddlewareError};
use futures_util::try_join;
use hex::FromHex;
use reqwest::Response;
use reqwest::{Response, Url};
use serde_json::Value;

use crate::{
Expand Down Expand Up @@ -75,7 +76,14 @@ where
..Default::default()
};

let _tx = self.call(&_tx_request.into(), None).await?;
let _tx_result: Result<Bytes, _> = self.call(&_tx_request.into(), None).await;
let _tx = match _tx_result {
Ok(_tx) => _tx,
Err(_error) => {
println!("Error calling: {:?}", _error);
Bytes::from([])
}
};

// If the response is empty, the resolver does not support wildcard resolution
if _tx.0.is_empty() {
Expand Down Expand Up @@ -271,7 +279,10 @@ where
Ok(response) => response.to_string(),
Err(provider_error) => {
let content = provider_error.as_error_response().unwrap();
let data = content.data.as_ref().unwrap();
let data = content.data.as_ref().unwrap_or(&serde_json::Value::Null);
if data.is_null() {
return Err(CCIPReadMiddlewareError::GatewayError(content.to_string()));
}
data.to_string()
.trim_matches('"')
.trim_start_matches("0x")
Expand Down Expand Up @@ -348,10 +359,12 @@ where
return self._call(&new_transaction, block_id, attempt + 1).await;
}
}

let result = match Bytes::from_str(&result) {
Ok(bytes) => bytes,
Err(error) => return Err(CCIPReadMiddlewareError::GatewayError(error.to_string())),
Err(error) => {
println!("error: {:?}", error);
return Err(CCIPReadMiddlewareError::GatewayError(error.to_string()));
}
};

Ok(result)
Expand Down Expand Up @@ -396,6 +409,72 @@ where
Ok(field)
}

/// Resolve avatar field of an ENS name
async fn resolve_avatar(&self, ens_name: &str) -> Result<Url, Self::Error> {
let (field, owner) = try_join!(
self.resolve_field(ens_name, "avatar"),
self.resolve_name(ens_name)
)?;
let url = Url::from_str(&field)
.map_err(|e| CCIPReadMiddlewareError::URLParseError(e.to_string()))?;
match url.scheme() {
"https" | "data" => Ok(url),
"ipfs" => erc::http_link_ipfs(url).map_err(CCIPReadMiddlewareError::URLParseError),
"eip155" => {
let token = erc::ERCNFT::from_str(url.path())
.map_err(CCIPReadMiddlewareError::URLParseError)?;
match token.type_ {
erc::ERCNFTType::ERC721 => {
let tx = TransactionRequest {
data: Some(
[&erc::ERC721_OWNER_SELECTOR[..], &token.id].concat().into(),
),
to: Some(NameOrAddress::Address(token.contract)),
..Default::default()
};
let data = self.call(&tx.into(), None).await?;
if decode_bytes::<Address>(ParamType::Address, data) != owner {
return Err(CCIPReadMiddlewareError::NFTOwnerError(
"Incorrect owner.".to_string(),
));
}
}
erc::ERCNFTType::ERC1155 => {
let tx = TransactionRequest {
data: Some(
[
&erc::ERC1155_BALANCE_SELECTOR[..],
&[0x0; 12],
&owner.0,
&token.id,
]
.concat()
.into(),
),
to: Some(NameOrAddress::Address(token.contract)),
..Default::default()
};
let data = self.call(&tx.into(), None).await?;
if decode_bytes::<u64>(ParamType::Uint(64), data) == 0 {
return Err(CCIPReadMiddlewareError::NFTOwnerError(
"Incorrect balance.".to_string(),
));
}
}
}

let image_url = self.resolve_nft(token).await?;
match image_url.scheme() {
"https" | "data" => Ok(image_url),
"ipfs" => erc::http_link_ipfs(image_url)
.map_err(CCIPReadMiddlewareError::URLParseError),
_ => Err(CCIPReadMiddlewareError::UnsupportedURLSchemeError),
}
}
_ => Err(CCIPReadMiddlewareError::UnsupportedURLSchemeError),
}
}

/// Resolve an ENS name to an address
async fn resolve_name(&self, ens_name: &str) -> Result<Address, Self::Error> {
self.query_resolver(ParamType::Address, ens_name, ens::ADDR_SELECTOR)
Expand Down

0 comments on commit 5669e42

Please sign in to comment.