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:
<!DOCTYPE html>
<meta charset="utf-8" />
<title>SIWE Quickstart</title>
.hidden {
display: none
td {
border: 1px black solid;
.avatar {
border-radius: 8px;
border: 1px solid #ccc;
width: 20px;
height: 20px;
<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 class="hidden" id="profile">
<h3>ENS Metadata:</h3>
<div id="ensLoader"></div>
<div id="ensContainer" class="hidden">
<table id="ensTable">
<div class="hidden" id="noProfile">
No ENS Profile Found.
<div class="hidden" id="nft">
<h3>NFT Ownership</h3>
<div id="nftLoader"></div>
<div id="nftContainer" class="hidden">
<table id="nftTable">
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.
import { BrowserProvider } from 'ethers';
import { SiweMessage } from 'siwe';
const domain =;
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({
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(`${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 => {
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';
let tableHtml = "<tr><th>Name</th><th>Address</th><th>Token ID</th></tr>";
nfts.forEach((nft) => {
tableHtml += `<tr><td>${}</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(
'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}`);
console.log(await res.text());
async function getInformation() {
const res = await fetch(`${BACKEND_ADDR}/personal_information`, {
credentials: 'include',
if (!res.ok) {
console.error(`Failed in getInformation: ${res.statusText}`);
let result = await res.text();
address = result.split(" ")[result.split(" ").length - 1];
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 start
Then go into backend and run:
yarn start
Now, when a user signs in, information on NFT holdings is displayed below the ENS information (if available).