Applying NFT Images
In the customization menu we prepared an interface to allow the user to paste in the contract address and token ID of the NFT they want to use.
There is a function for setting the contract address and one for setting the token id. Both of them will trim the whitespace and check for the validity of the input data. In case the data is invalid we will show an error message toast. When both the address and the id are correct we allow pressing of the fetch button.
setContractAddress = async() => {
const contractAddress = await navigator.clipboard.readText()
const trimmedAddress = contractAddress.trim()
if(!this.isValidAddress(trimmedAddress)) {
console.log("Invalid contract address")
Toastify({
text: "Invalid token id",
duration: 3000,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #790909, #ff5e00)",
},
}).showToast()
return
}
this.imageContractAddress = trimmedAddress
this.contractAddressText.text = trimmedAddress
if(this.isValidId(this.imageTokenId) && this.isValidAddress(this.imageContractAddress)) {
this.fetchButton.setAlpha(constants.INTERFACE.ACTIVE_ALPHA)
} else {
this.fetchButton.setAlpha(constants.INTERFACE.INACTIVE_ALPHA)
}
}
//...
setTokenId = async() => {
const tokenId = await navigator.clipboard.readText()
const trimmedTokenId = tokenId.trim()
if(!this.isValidId(trimmedTokenId)) {
console.log("Invalid token id")
Toastify({
text: "Invalid token id",
duration: 3000,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #790909, #ff5e00)",
},
}).showToast()
return
}
this.imageTokenId = trimmedTokenId
this.tokenIdText.text = trimmedTokenId
if(this.isValidId(this.imageTokenId) && this.isValidAddress(this.imageContractAddress)) {
this.fetchButton.setAlpha(1)
} else {
this.fetchButton.setAlpha(constants.INTERFACE.INACTIVE_ALPHA)
}
}
//...
isValidAddress = (address) => {
return ethers.utils.isAddress(address)
}
isValidId = (tokenId) => {
if(typeof tokenId != "string") { return false }
if(tokenId.substring(0, 2) === '0x') { return false }
return !isNaN(tokenId) && !isNaN(parseFloat(tokenId))
}
When the fetch button is pressed our Menu.js will call fetchTokenImage
on Web3Connection.js.
This block of code will first try to instantiate a new contract object through ethers using the contract address, the web3 provider and an erc721Abi.
The erc721Abi can be found src/assets/contracts/Erc721Abi.json
and needs to be imported at the top.
We then do a rudimentary ownership check by asking the user to sign a message with their wallet and only allow usage of the NFT image if they actually own it.
For this to work the NFT needs to be a proper ERC721 which implements the tokenURI
method.
Web3Connection.js
import erc721Abi from "../../assets/contracts/Erc721Abi.json"
// Initialize contract of nft, do an owner check and get image data
fetchTokenImage = async(address, tokenId) => {
try {
this.nftContract = new ethers.Contract(address, erc721Abi, this.web3Provider)
} catch(e) {
console.log(e)
console.log("Couldn't initialize Web3")
Toastify({
text: "Couldn't initialize contract",
duration: 3000,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #790909, #ff5e00)",
},
}).showToast()
return
}
// Get owner address of nft
let nftOwnerAddress
try {
nftOwnerAddress = await this.nftContract.ownerOf(ethers.BigNumber.from(tokenId))
} catch(e) {
Toastify({
text: "Couldn't get token owner! Are you sure you are connected to the correct network and the token is a valid NFT?",
duration: 5000,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #790909, #ff5e00)",
},
}).showToast()
console.log(e)
throw e
}
// Have user sign a message to verify they own the nft
const message = 'Verify NFT Ownership to fetch image'
let sig
try{
sig = await this.web3Signer.signMessage(message)
} catch(e) {
Toastify({
text: "User denied message signature",
duration: 3000,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #790909, #ff5e00)",
},
}).showToast()
console.log("User denied message signature")
throw 'User denied message signature'
}
if(nftOwnerAddress !== ethers.utils.verifyMessage(message, sig)) {
Toastify({
text: "You are not the owner of this NFT. Please select an NFT you own in this wallet.",
duration: 3000,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #790909, #ff5e00)",
},
}).showToast()
console.log("NOT THE OWNER")
throw 'Not The Owner'
}
let tokenUri
try {
tokenUri = await this.nftContract.tokenURI(ethers.BigNumber.from(tokenId))
} catch(e) {
console.log(e)
Toastify({
text: "Couldn't get tokenURI! Are you sure you are connected to the correct network and the token is a valid NFT?",
duration: 5000,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #790909, #ff5e00)",
},
}).showToast()
return
}
let response
try {
response = await fetch(tokenUri)
} catch(e) {
console.log(e)
Toastify({
text: "Couldn't fetch token metadata.",
duration: 3000,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #790909, #ff5e00)",
},
}).showToast()
return
}
const metadata = await response.json()
return metadata.image
}
The NFT image will be shown in the menu if all checks pass. The user can then apply it as a player, obstacle or cloud and we will save that data in the local storage.
This art can then be chosen when returning to the main menu.