Moore’s law, which was really more of a rule of thumb, is the observation that the number of transistors in a dense integrated circuit doubles about every two years. I felt the impact of Moore’s law in the early years of my career as the PC revolution began to boom.
During the height of the PC revolution, things were moving fast while corporations like Microsoft, Novell, and Borland claimed ownership and helped set standards for this new era of computing. The fact that CPU power was doubling every 18 to 24 months became a much-needed asset at the time to allow complex features and functionalities to become realities.
I feel like Web3 is in a similar position, with new tools, frameworks, protocols, and concepts like multi-chain being released constantly. Clearly the number of options in place for Web3 18 to 24 months ago pale in comparison to what is available today.
One example is how Coinbase Cloud is growing their suite of tools – with the introduction of their Node service with Advanced APIs and a comprehensive NFT API.
In this article, I will dive in and see how easy it is to get started and create something of value. But first, let’s make sure everyone is on the same page.
Quick Recap on Getting Started With Coinbase
In my last article, we explored the Coinbase API from a Web2 starting point. For those who may have missed it, I created a simple frontend that allowed us to connect our wallet and send some ETH using the Coinbase APIs.
The goal of that publication was to give Web2 developers a basic introduction to interacting with the blockchain. It allowed early adopters to transfer funds to themselves – all for a small gas fee.
This was certainly not all that the Coinbase APIs have to offer, which is why a natural progression is to explore some new tech by Coinbase that can make our dapp more functional. The recently-announced Coinbase Cloud Node and NFT APIs really caught my eye.
More on the Coinbase Cloud Node and NFT APIs
The Coinbase Cloud Node and NFT APIs come with some attractive benefits to drive developer adoption:
- Enterprise-grade security is included by default.
- The solution is built by one of the Web3 leaders.
- Developers can get started for free – with options to scale as you succeed.
One of the challenges Web3 developers have faced is the ability to query/transact over multiple blockchain networks. The Coinbase Cloud Node service and Advanced APIs makes querying and transacting on multiple blockchain networks a breeze – and including comprehensive data from your queries.
From an NFT perspective, the Coinbase NFT APIs can collect NFT metadata from collections on multiple blockchains quickly and easily, solving the same problem in the ever-growing NFT aspect of Web3.
Let’s see how we can put them together to build a dapp that will display a user’s NFTs in a collection. It’s important to note that in their present states, these APIs are currently only available on the Ethereum Network, so that’s what we will be using.
Demonstration
For the demonstration portion of this article, we will build on top of the previous project from my last article and make our dapp more functional using the Coinbase Cloud Node and NFT APIs. We will include the following functionality:
- Enter in an NFT contract address
- Coinbase Cloud Node’s Advanced API reads the contract metadata to show the collection name
- The Advanced API checks the connected account’s wallet for the number of NFTs in the collection
- Depending on the number of NFTs, input fields pop up to enter the NFT ID numbers
- The NFT API uses those ID numbers to return the images of the account’s NFTs from that collection
1. Setting Up Your Account
Before getting started with the project though, sign up for a Coinbase Cloud account here.
After setting up your account, select the Go to Node option on your dashboard and then Create new project.
As you can see from the list of networks on this page, there’s a lot to choose from! However, to make things easy, we’ll choose Ethereum Mainnet.
Select the Free plan and give your project a name, then click the Go to project button.
VERY IMPORTANT: After clicking the button, a dialogue will pop up containing your Username and Password. DO NOT CLOSE THIS DIALOGUE UNTIL YOU’VE COPIED YOUR PASSWORD. Coinbase doesn’t store your password, so if you fail to copy your password, you’ll have to start a new project.
After you’ve copied your Username and Password, click Next to receive your Endpoint. Copy this along with your Username and Password, and we’ll use it all in the project.
2. Setting Up The Project
To continue with the rest of the project, you will need the following:
- Git
- Your favorite code editor
- Node Package Manager (NPM)
- A Web3 wallet browser extension, either Coinbase Wallet, or MetaMask
- An NFT on Ethereum Mainnet (NFT Contract address and NFT ID number)
If you followed along with my last tutorial, that’s great! You will already have a starting point for this one. If not, don’t worry, you can find the project we will be starting with here:
https://gitlab.com/johnjvester/coinbase-wallet-example
Or just run this command from your terminal in the directory you wish to work from:
git clone https://gitlab.com/johnjvester/coinbase-wallet-example.git
Next, change directories into the new folder and install dependencies:
cd coinbase-wallet-example && npm i
Now run npm start
just to make sure everything is working correctly up to this point. Navigate to http://localhost:3000/ and you should have a page that looks like this.
With your Web3 wallet browser extension installed, select Connect Wallet to connect and switch to the Ropsten Network. Once that’s complete, you should see your wallet address as the Connected Account.
Awesome! Now that we know everything is working, let’s add our new functionality. Open up the project in your code editor and navigate to the ./src/App.js file.
3. Change The Code
3.a. Credentials and Headers
First, let’s add a little code of our own. Below the import statements, we’ll add our Coinbase URL, Username, Password, and some code to create the headers we will be passing along with our API calls.
Next, we need to adjust the DEFAULT_CHAIN_ID
and DEFAULT_ETHEREUM_CHAIN_ID
and DEFAULT_ETH_JSONRPC_URL
. We’ll change those to 1
, '0x1'
, and COINBASE_URL
respectively. This is because we will be interacting with the Ethereum Mainnet, rather than Ropsten Testnet.
Next, we can delete the line declaring the DONATION_ADDRESS
variable, as we won’t be using it.
Now we will add a little code of our own.
All the code before the App declaration should now look like this:
import React, { useEffect, useState } from 'react';
import './App.css';
import CoinbaseWalletSDK from '@coinbase/wallet-sdk'
import Web3 from 'web3';
// Coinbase Credentials
const COINBASE_URL = {YOUR_API_ENDPOINT};
const USERNAME = {YOUR_USERNAME};
const PASSWORD = {YOUR_PASSWORD};
// Create the headers
const STRING = `${USERNAME}:${PASSWORD}`;
const BASE64STRING = Buffer.from(STRING).toString('base64');
const HEADERS = new Headers ({
'Content-Type':'application/json',
'Authorization':`Basic ${BASE64STRING}`
});
// Coinbase Wallet Initialization
const APP_NAME = 'coinbase-wallet-example';
const APP_LOGO_URL = './coinbase-logo.png';
const DEFAULT_ETH_JSONRPC_URL = COINBASE_URL;
const DEFAULT_CHAIN_ID = 1; // 1=Ethereum (mainnet), 3=Ropsten, 5=Gorli
const DEFAULT_ETHEREUM_CHAIN_ID = '0x1'; // Should match DEFAULT_CHAIN_ID above, but with leading 0x
Note: Be sure to add your own API Endpoint, Username, and Password in the above code.
3.b. React State Variables
Add several new React state variables and delete the responseMessage variable. Our State Variables section should now look this:
// React State Variables
const [isWalletConnected, setIsWalletConnected] = useState(false);
const [account, setAccount] = useState();
const [walletSDKProvider, setWalletSDKProvider] = useState();
const [web3, setWeb3] = useState();
const [nftAddress, setNftAddress] = useState();
const [ownedNFTs, setOwnedNFTs] = useState(0);
const [nftContractName, setNftContractName] = useState();
const [imageSource, setImageSource] = useState([]);
const [displayNFTs, setDisplayNFTs] = useState(false);
3.c. Create New Functions - Coinbase Cloud Node Advanced API Calls
The rest of the code up to the donate function does not need to change, unless you want to change the message 'Successfully switched to Ropsten Network'
to 'Successfully switched to Ethereum Mainnet'
to be more accurate.
Next, we can delete the donate function in its entirety, as we won’t be using it anymore. So that just leaves the html. However, before we get to that, let’s add some new functions.
First, add the function for getting the NFT collection’s name and checking the connected account to see how many NFTs from the collection they own:
// Function for getting the NFT Contract and NFT balance in connected account
const getNftContract = async () => {
setDisplayNFTs(false);
const NFT_ADDRESS = document.querySelector('#nftContract').value;
setNftAddress(NFT_ADDRESS);
try {
// Get NFT Contract Metadata
let response = await fetch(COINBASE_URL, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
'id':1,
'jsonrpc':'2.0',
'method':'coinbaseCloud_getTokenMetadata',
'params': {
'contract': NFT_ADDRESS,
'blockchain':'Ethereum',
'network':'Mainnet'
}
})
})
let data = await response.json();
setNftContractName(data.result.tokenMetadata.name);
// Get NFT balance in account
response = await fetch(COINBASE_URL, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
'id':2,
'jsonrpc':'2.0',
'method':'coinbaseCloud_getBalances',
'params': {
'addressAndContractList': [
{
'address':account,
'contract':NFT_ADDRESS
}
],
'blockchain':'Ethereum',
'network':'Mainnet'
}
})
})
data = await response.json();
let value = data.result.balances[0].tokenBalances[0].amount;
value = web3.utils.hexToNumber(value);
setOwnedNFTs(value);
} catch (error) {
console.error(error);
}
}
Let’s walk through this code so we can understand what’s going on.
First, we are setting the state variable displayNFTs
to false
so when we enter in a new contract address it will reset our interface. (We will change the html for this afterwards.)
Next, we are getting the NFT contract address from the user input and setting it to our nft_Address
state variable.
Then we make two fetch requests to the Coinbase Cloud Node Advanced API using our COINBASE_URL
, HEADERS
, and NFT_ADDRESS
variables and set our nftContractName
and ownedNFT
state variables with data from the responses. Notice we are calling the coinbaseCloud_getTokenMetadata and coinbaseCloud_getBalances Advanced API methods.
3.d. Create New Functions - Add Input Fields
Awesome. Next we’ll add a function to create input fields based on how many NFTs the account owns from the collection:
// Add input fields based on how many NFTs the account owns
const addFields = () => {
return Array.from(
{ length: ownedNFTs },
(_, i) => (
<div key={`input-${i}`}>
<input
type='text'
id={`input-${i}`}
/>
</div>
)
);
}
3.e. Create New Functions - Get NFT Images
And the last function we’ll add is one to query the Coinbase NFT API to return the NFT image URLs based on the NFT IDs the user adds and push them to the imageSource state variable array. We then set the displayNFTs state variable to true:
const getImages = async () => {
// Function for getting NFT image
const nftIDs = [];
const imageUrls = [];
const newURLs = [];
// Add users NFT IDs to the array from input fields
for (let i = 0; i < ownedNFTs; i++) {
nftIDs.push(document.querySelector(`#input-${i}`).value);
imageUrls.push('https://mainnet.ethereum.coinbasecloud.net/api/nft/v2/contracts/' + nftAddress + '/tokens/' + (`${nftIDs[i]}`) + '?networkName=ethereum-mainnet');
try {
let response = await fetch(imageUrls[i], {
method: 'GET',
headers: HEADERS
})
let data = await response.json();
let url = data.token.imageUrl.cachedPathSmall;
newURLs.push(url);
} catch (error) {
console.log(error);
}
}
setImageSource(newURLs);
setDisplayNFTs(true);
}
And that’s all the JavaScript out of the way. Now let’s change the html
portion of our React code to display everything properly.
3.f. Changing The HTML
We will make several changes to the html so it displays everything we need. The html portion of your code should look like the following:
return (
<div className="App">
<header className="App-header">
<img src={APP_LOGO_URL} className="App-logo" alt="logo" />
{isWalletConnected ? (
<>
<h4>Show your NFTs!</h4>
<p>Connected Account: {account}</p>
<p>Please enter an NFT contract</p>
<div>
<input
type='string'
id='nftContract'
defaultValue={'0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D'}
/>
</div>
<br></br>
<div>
<button onClick={getNftContract} id="getNfts" type="button">
Check Contract
</button>
<br></br>
{nftContractName &&
<p>You have {ownedNFTs} NFTs in the {nftContractName} collection</p>
}
{!displayNFTs && ownedNFTs > 0 &&
<div id='imageDiv'>
<p>Please enter your NFT IDs</p>
{addFields()}
<br></br>
<button onClick={getImages} id='getImages' type='button'>
Get NFTs
</button>
<br></br>
</div>
}
{displayNFTs && imageSource.map((image) => (
<img key={image} src={image}/>
))}
</div>
</>
) : (
<button onClick={checkIfWalletIsConnected} id="connect" type="button">
Connect Wallet
</button>
)}
</header>
</div>
);
}
There are a few things to note in the above code. First, we are supplying a default NFT contract address in case the user doesn’t have one. In this case, the Bored Ape Yacht Club contract address.
Our Check Contract
button calls the getNftContract
function and then displays how many NFTs the connected account owns from the contract address, and also the collection’s name.
If the user owns NFTs from the supplied contract address, the addFields
function is called and a number of input fields will appear based on how many they own.
Finally, the Get NFTs
button will call the getImages
function and the following code iterates through the imageSource array to display the images from the NFT URLs it contains.
4. See It In Action
That’s it! If everything was entered correctly and you supplied your own Coinbase Node API Endpoint, Username, and Password, you should have a project that functions like the following animated GIF:
The entire source code for this project should look like this:
import React, { useEffect, useState } from 'react';
import './App.css';
import CoinbaseWalletSDK from '@coinbase/wallet-sdk'
import Web3 from 'web3';
// Coinbase Credentials
const COINBASE_URL = {YOUR_API_ENDPOINT};
const USERNAME = {YOUR_USERNAME};
const PASSWORD = {YOUR_PASSWORD};
// Create the headers
const STRING = `${USERNAME}:${PASSWORD}`;
const BASE64STRING = Buffer.from(STRING).toString('base64');
const HEADERS = new Headers ({
'Content-Type':'application/json',
'Authorization':`Basic ${BASE64STRING}`
});
// Coinbase Wallet Initialization
const APP_NAME = 'coinbase-wallet-example';
const APP_LOGO_URL = './coinbase-logo.png';
const DEFAULT_ETH_JSONRPC_URL = COINBASE_URL;
const DEFAULT_CHAIN_ID = 1; // 1=Ethereum (mainnet), 3=Ropsten, 5=Gorli
const DEFAULT_ETHEREUM_CHAIN_ID = '0x1'; // Should match DEFAULT_CHAIN_ID above, but with leading 0x
const App = () => {
// React State Variables
const [isWalletConnected, setIsWalletConnected] = useState(false);
const [account, setAccount] = useState();
const [walletSDKProvider, setWalletSDKProvider] = useState();
const [web3, setWeb3] = useState();
const [nftAddress, setNftAddress] = useState();
const [ownedNFTs, setOwnedNFTs] = useState(0);
const [nftContractName, setNftContractName] = useState();
const [imageSource, setImageSource] = useState([]);
const [displayNFTs, setDisplayNFTs] = useState(false);
useEffect(() => {
const coinbaseWallet = new CoinbaseWalletSDK({
appName: APP_NAME,
appLogoUrl: APP_LOGO_URL,
});
const walletSDKProvider = coinbaseWallet.makeWeb3Provider(
DEFAULT_ETH_JSONRPC_URL,
DEFAULT_CHAIN_ID
);
setWalletSDKProvider(walletSDKProvider);
const web3 = new Web3(walletSDKProvider);
setWeb3(web3);
}, []);
const checkIfWalletIsConnected = () => {
if (!window.ethereum) {
console.log(
'No ethereum object found. Please install Coinbase Wallet extension or similar.'
);
web3.setProvider(walletSDKProvider.enable());
return;
}
console.log('Found the ethereum object:', window.ethereum);
connectWallet();
};
const connectWallet = async () => {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts',
});
if (!accounts.length) {
console.log('No authorized account found');
return;
}
if (accounts.length) {
const account = accounts[0];
console.log('Found an authorized account:', account);
setAccount(account);
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: DEFAULT_ETHEREUM_CHAIN_ID }],
});
console.log('Successfully switched to Ropsten Network');
} catch (error) {
console.error(error);
}
}
setIsWalletConnected(true);
};
// Function for getting the NFT Contract and NFT balance in connected account
const getNftContract = async () => {
setDisplayNFTs(false);
const NFT_ADDRESS = document.querySelector('#nftContract').value;
setNftAddress(NFT_ADDRESS);
try {
// Get NFT Contract Metadata
let response = await fetch(COINBASE_URL, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
'id':1,
'jsonrpc':'2.0',
'method':'coinbaseCloud_getTokenMetadata',
'params': {
'contract': NFT_ADDRESS,
'blockchain':'Ethereum',
'network':'Mainnet'
}
})
})
let data = await response.json();
setNftContractName(data.result.tokenMetadata.name);
// Get NFT balance in account
response = await fetch(COINBASE_URL, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
'id':2,
'jsonrpc':'2.0',
'method':'coinbaseCloud_getBalances',
'params': {
'addressAndContractList': [
{
'address':account,
'contract':NFT_ADDRESS
}
],
'blockchain':'Ethereum',
'network':'Mainnet'
}
})
})
data = await response.json();
let value = data.result.balances[0].tokenBalances[0].amount;
value = web3.utils.hexToNumber(value);
setOwnedNFTs(value);
} catch (error) {
console.error(error);
}
}
// Add input fields based on how many NFTs the account owns
const addFields = () => {
return Array.from(
{ length: ownedNFTs },
(_, i) => (
<div key={`input-${i}`}>
<input
type='text'
id={`input-${i}`}
/>
</div>
)
);
}
const getImages = async () => {
// Function for getting NFT image
const nftIDs = [];
const imageUrls = [];
const newURLs = [];
// Add users NFT IDs to the array from input fields
for (let i = 0; i < ownedNFTs; i++) {
nftIDs.push(document.querySelector(`#input-${i}`).value);
imageUrls.push('https://mainnet.ethereum.coinbasecloud.net/api/nft/v2/contracts/' + nftAddress + '/tokens/' + (`${nftIDs[i]}`) + '?networkName=ethereum-mainnet');
try {
let response = await fetch(imageUrls[i], {
method: 'GET',
headers: HEADERS
})
let data = await response.json();
let url = data.token.imageUrl.cachedPathSmall;
newURLs.push(url);
} catch (error) {
console.log(error);
}
}
setImageSource(newURLs);
setDisplayNFTs(true);
}
return (
<div className="App">
<header className="App-header">
<img src={APP_LOGO_URL} className="App-logo" alt="logo" />
{isWalletConnected ? (
<>
<h4>Show your NFTs!</h4>
<p>Connected Account: {account}</p>
<p>Please enter an NFT contract</p>
<div>
<input
type='string'
id='nftContract'
defaultValue={'0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D'}
/>
</div>
<br></br>
<div>
<button onClick={getNftContract} id="getNfts" type="button">
Check Contract
</button>
<br></br>
{nftContractName &&
<p>You have {ownedNFTs} NFTs in the {nftContractName} collection</p>
}
{!displayNFTs && ownedNFTs > 0 &&
<div id='imageDiv'>
<p>Please enter your NFT IDs</p>
{addFields()}
<br></br>
<button onClick={getImages} id='getImages' type='button'>
Get NFTs
</button>
<br></br>
</div>
}
{displayNFTs && imageSource.map((image) => (
<img key={image} src={image}/>
))}
</div>
</>
) : (
<button onClick={checkIfWalletIsConnected} id="connect" type="button">
Connect Wallet
</button>
)}
</header>
</div>
);
}
export default App;
Conclusion
Since 2021, I have been trying to live by the following mission statement, which I feel can apply to any technology professional:
“Focus your time on delivering features/functionality that extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.”
J. Vester
Web3 developers find themselves in a similar position I was in at the start of my career, navigating through the hopes and dreams of the PC revolution. With computing power doubling every 18 to 24 months, it seemed like no feature or functionality was out of scope. However, we all quickly learned that there is more than just CPU power that should be driving decisions.
Web3 developers need the right set of tools, frameworks, products and services to help them become successful. In this publication we explored the Coinbase Cloud Node and NFT APIs which strongly adhere to my personal mission statement. Within a reasonable set of program logic we were able to successfully use these APIs to read NFT data and even display the user’s NFTs. The time saved by leveraging the Coinbase Cloud options will allow developers to focus on their primary goals and objectives.
If you are interested in the source code for this article, it can be found at the following URL:
https://gitlab.com/johnjvester/coinbase-node-nft-api-tutorial
Moore’s law started slowing down back in 2010 and most agree that it will reach its end of life in about three years – due to physical limitations. This will likely pave the way for new solutions to pave the way – like quantum computing, artificial intelligence, and machine learning. This is not too different in how Web3 is becoming an attractive option to meeting business needs.
Have a really great day!