visit
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.
One example is how
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
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.
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.
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:
git clone //gitlab.com/johnjvester/coinbase-wallet-example.git
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.
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.
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
// 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.
// 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);
}
}
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
// 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('//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.
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.
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('//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;
“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.
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.