Overview
POAP (Proof of Attendance Protocol) tokens are unique ERC-721 tokens that represent attendance or participation in an event. Interacting with POAP smart contracts requires the contract address, ABI of the contract and a node to read data from the blockchain.
In this Function example, we will get POAP balances on Ethereum and Gnosis mainnet networks along with their token URIs, which contain the ERC-721 token metadata, using QuickNode RPC.
Sample Function
We will be using the ethers.js library to interact with the Etherem and Gnosis networks. Thus, we will need to create a zip file and upload it to our Functions dashboard.
We will need to install ethers
and dotenv
with the following command:
npm i ethers dotenv
Create a .env
file to store RPC endpoint URLs from QuickNode. Create new endpoints on QuickNode.
ETHEREUM_RPC_URL=REPLACE_WITH_RPC_URL
GNOSIS_RPC_URL=REPLACE_WITH_RPC_URL
Replace the REPLACE_WITH_RPC_URL placeholder with a URL for the specified chain.
Now, create an index.js
file and paste the following code into it:
// Import dotenv and ethers packages
require('dotenv').config();
const { ethers } = require("ethers");
// Environment variable names for various blockchains
const envVarNames = {
ethereum: "ETHEREUM_RPC_URL",
gnosis: "GNOSIS_RPC_URL"
};
// POAP contract addresses on various blockchains
const poapContracts = {
ethereum: "0x22C1f6050E56d2876009903609a2cC3fEf83B415",
gnosis: "0x22C1f6050E56d2876009903609a2cC3fEf83B415"
};
// ABI for POAP contract
const POAP_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)",
"function tokenURI(uint256 tokenId) view returns (string)"
];
// Function to POAP balance for a wallet address
async function getPOAPBalance(providerUrl, contractAddress, walletAddress) {
const provider = new ethers.JsonRpcProvider(providerUrl); // Create new ethers JsonRpcProvider provider
const contract = new ethers.Contract(contractAddress, POAP_ABI, provider); // Create new contract intance using contract address, abi, provider
try {
const balance = await contract.balanceOf(walletAddress);
return balance.toString();
} catch (error) {
console.error(`Error getting POAP balance: ${error.message}`);
return "0";
}
}
// Function to get POAP token URIs
async function getPOAPTokenURIs(providerUrl, contractAddress, walletAddress, balance) {
const provider = new ethers.JsonRpcProvider(providerUrl);
const contract = new ethers.Contract(contractAddress, POAP_ABI, provider);
const tokenURIs = [];
const promises = [];
for (let i = 0; i < balance; i++) {
promises.push((async () => {
try {
const tokenId = await contract.tokenOfOwnerByIndex(walletAddress, i);
const tokenURI = await contract.tokenURI(tokenId);
if (tokenURI) tokenURIs.push(tokenURI);
} catch (error) {
console.error(`Error processing token ${i}: ${error.message}`);
}
})());
}
await Promise.all(promises);
return tokenURIs;
}
async function main(params) {
console.log('Starting main function with params:', params);
const walletAddress = params.user_data.wallet_address; // Get wallet address from user
try {
const results = {};
let missingRpcUrls = false;
/* - A function to iterate over mentioned chains in the envVarNames
- Fetch RPC URLs from .env file and check if the URL is set
- If URL is set run getPOAPBalance function
- If POAP balance greater than zero get the token URIs for the POAPs
- Handle missing URLs by setting missingRpcUrls true
*/
for (const [chain, envVarName] of Object.entries(envVarNames)) {
const rpcUrl = process.env[envVarName];
if (rpcUrl) {
const contractAddress = poapContracts[chain];
const balance = await getPOAPBalance(rpcUrl, contractAddress, walletAddress);
results[chain] = { balance, tokenURIs: [] };
if (parseInt(balance) > 0) {
results[chain].tokenURIs = await getPOAPTokenURIs(rpcUrl, contractAddress, walletAddress, parseInt(balance));
}
} else {
results[chain] = { balance: `Add ${envVarName} in the .env file.`, tokenURIs: [] };
missingRpcUrls = true;
}
}
// Log final results and return the results with a message
console.log('Final results:', results);
return {
message: "POAP Balances and Token URIs across supported blockchains.",
results: results,
};
} catch (error) {
console.error('Error in main function:', error);
throw error;
}
}
module.exports = { main };
Zip the Files
QuickNode Functions supports several Node.js v20 core modules, but since we've used ethers.js and dotenv which are third-party libraries, we will need to upload a .zip file of index.js
, .env
, package.json
, and package-lock.json
along with node_modules
folder so that our function can work on the QuickNode Functions platform.
Learn more about QuickNode Functions Code Editor and .Zip file uploading.
Request
We will invoke the function with the following cURL command. Be sure to replace the YOUR_API_KEY with your own QuickNode API key and the POST URL with the URL of your Function. You can send the request with any wallet address to check the balance.
curl -X POST "https://api.quicknode.com/functions/rest/v1/namespaces/0f6812dd-a17f-4cbc-9ab4-7a529eb33940/functions/poap-balances/call" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"network": "ethereum-mainnet",
"dataset": "block",
"block": "latest",
"user_data": {
"wallet_address": "0x8D97689C9818892B700e27F316cc3E41e17fBeb9"
}
}'
Response
Resulting in the following response:
{
"response" : {
"activationId" : "c3a6e64140f84606a6e64140f8b6065f",
"annotations" : [
{
"key" : "path",
"value" : "ca43f206-1786-4d85-8521-952f2a528f49/poap-balances"
},
{
"key" : "waitTime",
"value" : 64
},
{
"key" : "kind",
"value" : "nodejs:20"
},
{
"key" : "timeout",
"value" : false
},
{
"key" : "limits",
"value" : {
"concurrency" : 1,
"logs" : 10,
"memory" : 256,
"timeout" : 60000
}
}
],
"duration" : 94,
"end" : 1719579049362,
"logs" : [],
"name" : "poap-balances",
"namespace" : "ca43f206-1786-4d85-8521-952f2a528f49",
"publish" : false,
"response" : {
"result" : {
"message" : "POAP Balances and Token URIs across supported blockchains.",
"results" : {
"ethereum" : {
"balance" : "0",
"tokenURIs" : []
},
"gnosis" : {
"balance" : "9",
"tokenURIs" : [
"https://api.poap.tech/metadata/128853/6664840",
"https://api.poap.tech/metadata/128613/6692964",
"https://api.poap.tech/metadata/158870/6908599",
"https://api.poap.tech/metadata/159576/6924591",
"https://api.poap.tech/metadata/37664/6938171",
"https://api.poap.tech/metadata/92917/6938172",
"https://api.poap.tech/metadata/165990/6944028",
"https://api.poap.tech/metadata/165263/6945054",
"https://api.poap.tech/metadata/169288/7029677"
]
}
}
},
"size" : 595,
"status" : "success",
"success" : true
},
"start" : 1719579049268,
"subject" : "0f6812dd-a17f-4cbc-9ab4-7a529eb33940",
"version" : "0.0.6"
}
}
Learn more about QuickNode Functions.
We ❤️ Feedback!
Let us know if you have any feedback or requests for new topics. We'd love to hear from you.