Note: This article was updated on Sep 20th, 2022 to use Cairo 0.10
Adding Metadata
- Create a new ERC721 contract that supports metadata. You can use this contract as a base
- The base token URI is the chosen IPFS gateway
- You can upload your NFTs directly on this website
- Your tokens should be visible on Oasis once minted!
- Deploy your new contract
- Call submit_exercise() in the Evaluator to configure the contract you want evaluated
- Claim points on ex7_add_metadata (2 pts)
Looking for exercise 6? Click here.
Solution
To truly understand what’s expected from us on this exercise we have to start by exploring what ex7_add_metadata does.
@external
func ex7_add_metadata{...}() {
alloc_locals;
// Reading caller address
let (sender_address) = get_caller_address();
// Retrieve exercise address
let (submited_exercise_address) = player_exercise_solution_storage.read(sender_address);
// Get evaluator address
let (evaluator_address) = get_contract_address();
// Retrieve dummy token address
let (dummy_metadata_erc721_address) = dummy_metadata_erc721_storage.read();
// Reading metadata URI for token 1 on both contracts. For these to show up in Oasis, they should be equal
let token_id = Uint256(1, 0);
with_attr error_message("Couldn't retrieve the metadata URI") {
let (metadata_player_len, metadata_player) = IERC721_metadata.tokenURI(
contract_address=submited_exercise_address, token_id=token_id
);
}
let (metadata_dummy_len, metadata_dummy) = IERC721_metadata.tokenURI(
contract_address=dummy_metadata_erc721_address, token_id=token_id
);
with_attr error_message("Your token uri is not the same length as the dummy metadata") {
// Verifying they are equal
assert metadata_dummy_len = metadata_player_len;
}
with_attr error_message("Your token uri is not the same as the dummy metadata") {
ex7_check_arrays_are_equal(metadata_dummy_len, metadata_dummy, metadata_player);
}
// Checking if player has validated this exercise before
let (has_validated) = has_validated_exercise(sender_address, 7);
if (has_validated == 0) {
// player has validated
validate_exercise(sender_address, 7);
// Sending points
distribute_points(sender_address, 2);
return ();
else {
return ();
}
}
In a nutshell, ex7_add_metadata is calling the function tokenURI for the NFT with token_id 1 on our smart contract and comparing if the returned value is the same as the value returned by the Dummy ERC721 Token provided at the beginning of the workshop.
We can check what that value returned by the dummy token is by going to Voyager (0x4fc2…) and calling the function tokenURI passing the number 1 as the token_id.
Concatenating the returned array we can see that the URI is https://gateway.pinata.cloud/ipfs/QmWUB2TAYFrj1Uhvrgo69NDsycXfbfznNURj1zVbzNTVZv/1.json. If we follow the link we get the metadata of the NFT.
{
"name": "Gan generated image 1",
"image": "https://gateway.pinata.cloud/ipfs/Qmd9PegtrP3c7r6uJMWTC3CMCQUTVTzqg8jtmZsxnuUAeD/1.jpeg"
}
If we follow the URI for the image we can see the NFT itself.
Now that we know what our smart contract is supposed to return as tokenURI we can move on to the implementation. We are going to follow the recommendation from the exercise and copy over the base files we need to have an ERC721 smart contract with the Metadata extension.
In particular, this is what we need to do:
- Copy over the content of ERC721_metadata.cairo into src/ERC721_ex7.cairo
- Copy over the content of ERC721_Metadata_base.cairo into src/ERC721_Metadata.cairo
- Copy over the content of ShortString.cairo into libs/ShortString.cairo
- Copy over the content of Array.cairo into libs/Array.cairo
Once we finish copying over the relevant files, we should end up with the following folder structure:
$ tree .
>>>
.
├── README.md
├── abis
│ ├── ERC721_ex3.json
│ └── ERC721_ex4.json
├── comp
│ ├── ERC721_ex1.json
│ ├── ERC721_ex2.json
│ ├── ERC721_ex3.json
│ ├── ERC721_ex4.json
│ └── ERC721_ex5.json
├── src
│ ├── ERC721_Metadata.cairo
│ ├── ERC721_ex1.cairo
│ ├── ERC721_ex2.cairo
│ ├── ERC721_ex3.cairo
│ ├── ERC721_ex4.cairo
│ ├── ERC721_ex5.cairo
│ └── ERC721_ex7.cairo
└── libs
├── Array.cairo
└── ShortString.cairo
└── utils.py
Because we have changed some names and have a different folder structure than the base files, we need to make some changes on how some files are imported. The changes are shown below.
Changes to src/ERC721_ex7.cairo
// before
from contracts.token.ERC721.ERC721_Metadata_base import (
ERC721_Metadata_initializer,
ERC721_Metadata_tokenURI,
ERC721_Metadata_setBaseTokenURI,
)
// after
from contracts.ERC721_Metadata import (
ERC721_Metadata_initializer,
ERC721_Metadata_tokenURI,
ERC721_Metadata_setBaseTokenURI,
)
Changes to src/ERC721_Metadata.cairo
// before
from contracts.utils.ShortString import uint256_to_ss
from contracts.utils.Array import concat_arr
// after
from shared.ShortString import uint256_to_ss
from shared.Array import concat_arr
With these changes in place we should be ready to compile our smart contract.
$ starknet-compile src/ERC721_ex7.cairo \
--output comp/ERC721_ex7.json --abi abis/ERC721_ex7.json
In order to deploy our smart contract we need to know what arguments we need to pass to the constructor.
src/ERC721_ex7.cairo
@constructor
func constructor{...}(
name : felt,
symbol : felt,
owner : felt,
base_token_uri_len : felt,
base_token_uri : felt*,
token_uri_suffix : felt,
) {
...
}
I want to name my NFT collection “Ready Doggo One” with the symbol “RD1”. The owner will be my Argent X test account (0x0113…), the base URI https://gateway.pinata.cloud/ipfs/QmWUB2TAYFrj1Uhvrgo69NDsycXfbfznNURj1zVbzNTVZv/ and the prefix “.json”.
Note: You can use Braavos as an alternative to Argent X.
Let’s get the felt version of the short strings (less than 31 characters) first by using utils.py.
$ python -i utils.py
>>> str_to_felt("Ready Doggo One")
427824581996521952334490376445324901
>>> str_to_felt("RD1")
5391409
>>> str_to_felt(".json")
199354445678
Next we can get the felt version of the Argent X test account as we have done in previous exercises.
>>> hex_to_felt("0x0113349F3B0Cf24A953BBD1Bb3B9ea20cedaf49a00e918F56A9B3327164A39D5")
486246126474359946192348700142268263967120013078464126154508728538516568533
Finally, let’s get the felt version of the base URI.
>>> str_to_felt_array("https://gateway.pinata.cloud/ipfs/QmWUB2TAYFrj1Uhvrgo69NDsycXfbfznNURj1zVbzNTVZv/")
[184555836509371486644298270517380613565396767415278678887948391494588524912, 181013377130045435659890581909640190867353010602592517226438742938315085926, 2194400143691614193218323824727442803459257903]
Notice that when dealing with a long string (more than 31 characters) the returned value is not a single felt number but an array of them. When we want to pass an array as an input for a function like in the case of our constructor, we have to pass first the length of the array and then the elements of the array in order.
The deploy command for our smart contract will be a long one. Notice that the number 3 provided as part of the inputs is the size of the array that describes the baseURI.
$ starknet deploy --contract comp/ERC721_ex7.json \
--network alpha-goerli --no_wallet \
--inputs 427824581996521952334490376445324901 5391409 486246126474359946192348700142268263967120013078464126154508728538516568533 3 184555836509371486644298270517380613565396767415278678887948391494588524912 181013377130045435659890581909640190867353010602592517226438742938315085926 2194400143691614193218323824727442803459257903 199354445678
>>>
Deploy transaction was sent.
Contract address: 0x051ea806002f77025c64e53fe425fa53e5d73c497ead8db27f6300e4791a2bdb
...
Once the contract is deployed we will need to head over to Voyager (0x051e…) to mint an NFT with token_id 1.
Once the NFT is minted we can verify that our smart contract is returning the same token URI for token id 1 as the Dummy ERC721 did.
Concatenating the returned array we get the exact same link as before: https://gateway.pinata.cloud/ipfs/QmWUB2TAYFrj1Uhvrgo69NDsycXfbfznNURj1zVbzNTVZv/1.json.
This should be all we need to submit our exercise for evaluation. We start by invoking the function submit_exercise on the Evaluator (0x06d2…) passing the address of our smart contract.
We then evaluate our solution by invoking the function ex7_add_metadata on the Evaluator (0x06d2…).
If everything works as expected we should have been granted two more points on the Points Counter smart contract (0x00a0…).
And sure enough, we have been awarded two more points. Challenge completed successfully.
To see the final implementation of the ERC721 token for exercise 7 go to my GitHub repo.
Hello David,
Thank you so much for this, was super helpful.
If you don’t mind, could you probably do one on the messaging bridge when you are chanced?
https://github.com/starknet-edu/starknet-messaging-bridge
Hi, I’m glad the article was of help. I’m considering what to do next. I was inclined to do a deep dive on unit testing StarkNet’s smart contracts first but I’ll keep your suggestion in mind as well.