ethers_providers/ext/
erc.rs

1//! ERC related utilities. Only supporting NFTs for now.
2use ethers_core::types::{Address, Selector, U256};
3
4use serde::Deserialize;
5use std::str::FromStr;
6use url::Url;
7
8/// ownerOf(uint256 tokenId)
9pub const ERC721_OWNER_SELECTOR: Selector = [0x63, 0x52, 0x21, 0x1e];
10
11/// balanceOf(address owner, uint256 tokenId)
12pub const ERC1155_BALANCE_SELECTOR: Selector = [0x00, 0xfd, 0xd5, 0x8e];
13
14const IPFS_GATEWAY: &str = "https://ipfs.io/ipfs/";
15
16/// An ERC 721 or 1155 token
17pub struct ERCNFT {
18    /// Type of the NFT
19    pub type_: ERCNFTType,
20    /// Address of the NFT contract
21    pub contract: Address,
22    /// NFT ID in that contract
23    pub id: [u8; 32],
24}
25
26impl FromStr for ERCNFT {
27    type Err = String;
28    fn from_str(input: &str) -> Result<ERCNFT, Self::Err> {
29        let split: Vec<&str> =
30            input.trim_start_matches("eip155:").trim_start_matches("1/").split(':').collect();
31        let (token_type, inner_path) = if split.len() == 2 {
32            (
33                ERCNFTType::from_str(split[0])
34                    .map_err(|_| "Unsupported ERC token type".to_string())?,
35                split[1],
36            )
37        } else {
38            return Err("Unsupported ERC link".to_string())
39        };
40
41        let token_split: Vec<&str> = inner_path.split('/').collect();
42        let (contract_addr, token_id) = if token_split.len() == 2 {
43            let token_id = U256::from_dec_str(token_split[1])
44                .map_err(|e| format!("Unsupported token id type: {} {e}", token_split[1]))?;
45            let mut token_id_bytes = [0x0; 32];
46            token_id.to_big_endian(&mut token_id_bytes);
47            (
48                Address::from_str(token_split[0].trim_start_matches("0x"))
49                    .map_err(|e| format!("Invalid contract address: {} {e}", token_split[0]))?,
50                token_id_bytes,
51            )
52        } else {
53            return Err("Unsupported ERC link path".to_string())
54        };
55        Ok(ERCNFT { id: token_id, type_: token_type, contract: contract_addr })
56    }
57}
58
59/// Supported ERCs
60#[derive(PartialEq, Eq)]
61pub enum ERCNFTType {
62    /// ERC721
63    ERC721,
64    /// ERC1155
65    ERC1155,
66}
67
68impl FromStr for ERCNFTType {
69    type Err = ();
70    fn from_str(input: &str) -> Result<ERCNFTType, Self::Err> {
71        match input {
72            "erc721" => Ok(ERCNFTType::ERC721),
73            "erc1155" => Ok(ERCNFTType::ERC1155),
74            _ => Err(()),
75        }
76    }
77}
78
79impl ERCNFTType {
80    /// Get the method selector
81    pub const fn resolution_selector(&self) -> Selector {
82        match self {
83            // tokenURI(uint256)
84            ERCNFTType::ERC721 => [0xc8, 0x7b, 0x56, 0xdd],
85            // url(uint256)
86            ERCNFTType::ERC1155 => [0x0e, 0x89, 0x34, 0x1c],
87        }
88    }
89}
90
91/// ERC-1155 and ERC-721 metadata document.
92#[derive(Deserialize)]
93pub struct Metadata {
94    /// The URL of the image for the NFT
95    pub image: String,
96}
97
98/// Returns a HTTP url for an IPFS object.
99pub fn http_link_ipfs(url: Url) -> Result<Url, String> {
100    Url::parse(IPFS_GATEWAY)
101        .unwrap()
102        .join(url.to_string().trim_start_matches("ipfs://").trim_start_matches("ipfs/"))
103        .map_err(|e| e.to_string())
104}