Mix.install([
  :ethers,
  :kino,
  :req
])
Application.put_env(:ethereumex, :url, "https://cloudflare-eth.com/v1/mainnet")defmodule Loader do
  def load("ipfs://" <> id) do
    Req.get!("https://ipfs.io/ipfs/#{id}").body
  end
  def load("http" <> _ = url) do
    Req.get!(url).body
  end
end{:module, Loader, <<70, 79, 82, 49, 0, 0, 8, ...>>, {:load, 1}}
ERC-721 Tokens have a metadata standard which can be found in this link. This standard enforces a structure to the JSON value inside the token URI. (Token URI can be any valid URI and not necessarily an HTTP(s) URL)
contract_address = "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"
token_id = 1010
Ethers.Contracts.ERC721.name()
|> Ethers.call!(to: contract_address)"BoredApeYachtClub"
Ethers.Contracts.ERC721.symbol()
|> Ethers.call!(to: contract_address)"BAYC"
token_uri =
  Ethers.Contracts.ERC721.token_uri(token_id)
  |> Ethers.call!(to: contract_address)"ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/10"
token_data = Loader.load(token_uri)%{
  "attributes" => [
    %{"trait_type" => "Clothes", "value" => "Navy Striped Tee"},
    %{"trait_type" => "Background", "value" => "Aquamarine"},
    %{"trait_type" => "Hat", "value" => "Bayc Hat Red"},
    %{"trait_type" => "Fur", "value" => "Dmt"},
    %{"trait_type" => "Eyes", "value" => "Eyepatch"},
    %{"trait_type" => "Mouth", "value" => "Bored"}
  ],
  "image" => "ipfs://QmPQdVU1riwzijhCs1Lk6CHmDo4LpmwPPLuDauY3i8gSzL"
}
The Json structure in ERC-721 tokens is very simple and conforms to the spec below.
{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents"
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        }
    }
}Now we can even load the image.
Loader.load(token_data["image"])<<137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 2, 119, 0, 0, 2, 119, 8, 6, 0,
  0, 0, 246, 202, 119, 98, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11, 19, 0, 0, 11, 19, 1, ...>>
Now we can combine all these calls together for efficiency using Multicall.
calls = [
  {Ethers.Contracts.ERC721.name(), to: contract_address},
  {Ethers.Contracts.ERC721.symbol(), to: contract_address},
  {Ethers.Contracts.ERC721.token_uri(token_id), to: contract_address}
]
Ethers.Multicall.aggregate3(calls)
|> Ethers.call!()
|> Ethers.Multicall.decode(calls)[
  true: "BoredApeYachtClub",
  true: "BAYC",
  true: "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/10"
]
ERC-1155 Tokens have a metadata standard which can be found in this link. This standard enforces a structure to the JSON value inside the token URI. (Token URI can be any valid URI and not necessarily an HTTP(s) URL)
erc_1155_contract_address = "0x53894ec021245adb6a7c556bb0f0ad83544c0e33"
erc_1155_token_id = 11
token_uri =
  Ethers.Contracts.ERC1155.uri(erc_1155_token_id)
  |> Ethers.call!(to: erc_1155_contract_address)"ipfs://bafkreiesnznz43fnowny77t442vhx46b2ksvbid53klekhq5c5rsymzuiq"
token_data = Loader.load(token_uri)%{
  "description" => "Paddy's Pass is the doorway into the UA3 Community where art is our first love, but sharing wins with the community remains a priority! The UA3 ecosystem has been strategically structured through art, fashion, gaming and more to provide strategical one-of-a-kind revenue to the community! Paddy's community wins... While being BORED!",
  "image" => "ipfs://bafybeihhjtr2diy7cqmwx74hifobn4qigr3etwio2nfjaznnmdqieausja",
  "name" => "UA3 Paddy's Pass"
}
Loader.load(token_data["image"])<<71, 73, 70, 56, 57, 97, 56, 4, 56, 4, 247, 184, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 3, 1, 0, 5, 2, 1, 5,
  4, 3, 7, 5, 3, 10, 6, 2, 10, 7, 6, 11, 7, 4, 13, 10, 9, 16, 9, 2, 16, ...>>
Given a contract address, without prior knowledge we cannot know if that token is of ERC721 or ERC1155 type. This will not be a problem since we can still use all these calls in a Multicall3 aggregation and the unsupported ones can be ignored.
calls = [
  {Ethers.Contracts.ERC721.name(), to: erc_1155_contract_address},
  {Ethers.Contracts.ERC721.symbol(), to: erc_1155_contract_address},
  {Ethers.Contracts.ERC721.token_uri(erc_1155_token_id), to: erc_1155_contract_address},
  {Ethers.Contracts.ERC1155.uri(erc_1155_token_id), to: erc_1155_contract_address}
]
Ethers.Multicall.aggregate3(calls)
|> Ethers.call!()
|> Ethers.Multicall.decode(calls)[
  false: nil,
  false: nil,
  false: nil,
  true: "ipfs://bafkreiesnznz43fnowny77t442vhx46b2ksvbid53klekhq5c5rsymzuiq"
]
The above example shows what the response will look like for an ERC1155 token.