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:
Copy <!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>
OpenSea's API is a great resource for interacting with NFT data off-chain. Learn more here .
Copy 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;
Now, when a user signs in, information on NFT holdings is displayed below the ENS information (if available).