Resolve NFT Holdings

Here we learn how to pull information on a users' NFT holdings

A completed version of the updated frontend can be found here: (05_nft_resolution/frontend).

Similar to the ENS look-up, we can also query the user's NFT ownership. In this example, we will display basic information about the user's NFTs in a table, via the OpenSea API. However, this could also be extended to even give the user a visual gallery view of their NFTs once connected. First, we need to change index.html to include a new table. We'll use the same structure as in the last chapter, separating the two tables with an <hr> tag:

src/index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>SIWE Quickstart</title>
  <style>
    .hidden {
      display: none
    }

    table,
    th,
    td {
      border: 1px black solid;
    }

    .avatar {
      border-radius: 8px;
      border: 1px solid #ccc;
      width: 20px;
      height: 20px;
    }
  </style>
</head>

<body>
  <div><button id="connectWalletBtn">Connect wallet</button></div>
  <div><button id="siweBtn">Sign-in with Ethereum</button></div>
  <div><button id="infoBtn">Get session information</button></div>
  <div class="hidden" id="welcome">
  </div>
  <div class="hidden" id="profile">
    <h3>ENS Metadata:</h3>
    <div id="ensLoader"></div>
    <div id="ensContainer" class="hidden">
      <table id="ensTable">
      </table>
    </div>
  </div>
  <div class="hidden" id="noProfile">
    No ENS Profile Found.
  </div>
  <div class="hidden" id="nft">
    <h3>NFT Ownership</h3>
    <div id="nftLoader"></div>
    <div id="nftContainer" class="hidden">
      <table id="nftTable">
      </table>
    </div>
  </div>
</body>

</html>

Next, we'll update the index.js file to reach out to OpenSea's API using the logged-in user's address, then format the output to place the information in the table. If the user has no NFTs, we'll display a "No NFTs found" message in the loader div.

OpenSea's API is a great resource for interacting with NFT data off-chain. Learn more here.

src/index.js
import { BrowserProvider } from 'ethers';
import { SiweMessage } from 'siwe';

const domain = window.location.host;
const origin = window.location.origin;
const provider = new BrowserProvider(window.ethereum);

const profileElm = document.getElementById('profile');
const noProfileElm = document.getElementById('noProfile');
const welcomeElm = document.getElementById('welcome');

const ensLoaderElm = document.getElementById('ensLoader');
const ensContainerElm = document.getElementById('ensContainer');
const ensTableElm = document.getElementById('ensTable');

const nftElm = document.getElementById('nft');
const nftLoaderElm = document.getElementById('nftLoader');
const nftContainerElm = document.getElementById('nftContainer');
const nftTableElm = document.getElementById('nftTable');

let address;

const BACKEND_ADDR = "http://localhost:3000";
async function createSiweMessage(address, statement) {
    const res = await fetch(`${BACKEND_ADDR}/nonce`, {
        credentials: 'include',
    });
    const message = new SiweMessage({
        domain,
        address,
        statement,
        uri: origin,
        version: '1',
        chainId: '1',
        nonce: await res.text()
    });
    return message.prepareMessage();
}

function connectWallet() {
    provider.send('eth_requestAccounts', [])
        .catch(() => console.log('user rejected request'));
}

async function displayENSProfile() {
    const ensName = await provider.lookupAddress(address);

    if (ensName) {
        profileElm.classList = '';

        welcomeElm.innerHTML = `Hello, ${ensName}`;
        let avatar = await provider.getAvatar(ensName);
        if (avatar) {
            welcomeElm.innerHTML += ` <img class="avatar" src=${avatar}/>`;
        }

        ensLoaderElm.innerHTML = 'Loading...';
        ensTableElm.innerHTML.concat(`<tr><th>ENS Text Key</th><th>Value</th></tr>`);
        const resolver = await provider.getResolver(ensName);

        const keys = ["email", "url", "description", "com.twitter"];
        ensTableElm.innerHTML += `<tr><td>name:</td><td>${ensName}</td></tr>`;
        for (const key of keys)
            ensTableElm.innerHTML += `<tr><td>${key}:</td><td>${await resolver.getText(key)}</td></tr>`;
        ensLoaderElm.innerHTML = '';
        ensContainerElm.classList = '';
    } else {
        welcomeElm.innerHTML = `Hello, ${address}`;
        noProfileElm.classList = '';
    }

    welcomeElm.classList = '';
}

async function getNFTs() {
    try {
        let res = await fetch(`https://api.opensea.io/api/v1/assets?owner=${address}`);
        if (!res.ok) {
            throw new Error(res.statusText)
        }

        let body = await res.json();

        if (!body.assets || !Array.isArray(body.assets) || body.assets.length === 0) {
            return []
        }

        return body.assets.map((asset) => {
            let {name, asset_contract, token_id} = asset;
            let {address} = asset_contract;
            return {name, address, token_id};
        });
    } catch (err) {
        console.error(`Failed to resolve nfts: ${err.message}`);
        return [];
    }
}

async function displayNFTs() {
    nftLoaderElm.innerHTML = 'Loading NFT Ownership...';
    nftElm.classList = '';

    let nfts = await getNFTs();
    if (nfts.length === 0) {
        nftLoaderElm.innerHTML = 'No NFTs found';
        return;
    }

    let tableHtml = "<tr><th>Name</th><th>Address</th><th>Token ID</th></tr>";
    nfts.forEach((nft) => {
        tableHtml += `<tr><td>${nft.name}</td><td>${nft.address}</td><td>${nft.token_id}</td></tr>`
    });

    nftTableElm.innerHTML = tableHtml;
    nftContainerElm.classList = '';
    nftLoaderElm.innerHTML = '';
}

async function signInWithEthereum() {
    const signer = await provider.getSigner();
    profileElm.classList = 'hidden';
    noProfileElm.classList = 'hidden';
    welcomeElm.classList = 'hidden';

    address = await signer.getAddress()
    const message = await createSiweMessage(
        address,
        'Sign in with Ethereum to the app.'
    );
    const signature = await signer.signMessage(message);

    const res = await fetch(`${BACKEND_ADDR}/verify`, {
        method: "POST",
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ message, signature }),
        credentials: 'include'
    });

    if (!res.ok) {
        console.error(`Failed in getInformation: ${res.statusText}`);
        return 
    }
    console.log(await res.text());

    displayENSProfile();
    displayNFTs();
}

async function getInformation() {
    const res = await fetch(`${BACKEND_ADDR}/personal_information`, {
        credentials: 'include',
    });

    if (!res.ok) {
        console.error(`Failed in getInformation: ${res.statusText}`);
        return 
    }

    let result = await res.text();
    console.log(result);
    address = result.split(" ")[result.split(" ").length - 1];
    displayENSProfile();
    displayNFTs();
}

const connectWalletBtn = document.getElementById('connectWalletBtn');
const siweBtn = document.getElementById('siweBtn');
const infoBtn = document.getElementById('infoBtn');
connectWalletBtn.onclick = connectWallet;
siweBtn.onclick = signInWithEthereum;
infoBtn.onclick = getInformation;

Similar to the previous guide, to see the result, go into frontend and run:

yarn
yarn start

Then go into backend and run:

yarn
yarn start

Now, when a user signs in, information on NFT holdings is displayed below the ENS information (if available).

Last updated