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
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.
In my last article, we
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
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.
The Coinbase Cloud Node and NFT APIs come with some attractive benefits to drive developer adoption:
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.
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:
Before getting started with the project though, sign up for a Coinbase Cloud account
After setting up your account, select theGo 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.
To continue with the rest of the project, you will need the following:
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:
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
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.
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
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);
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
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>
)
);
}
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.
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, theGet 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.
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:
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;
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:
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!