Winning an NFT
No one will enter an auction if there's nothing to win, so let's add a prize. Why not an NFT? NFTs are uniquely identifiable, easily swappable and their logic comes from an external contract so the prize will exist without the auction contract. Let's get to work!
Listing the NFT
When we create an auction we need to list the NFT. To specify which NFT is being auctioned off we need the account ID of the NFT contract and the token ID of the NFT. We will specify these when the contract is initialized; amend init
to add nft_contract
and token_id
as such:
- 🌐 JavaScript
- 🦀 Rust
Loading...
Loading...
Note that token_id
is of type TokenId
which is a String type alias that the NFT standards use for future-proofing.
Transferring the NFT to the winner
When the method claim
is called the NFT needs to be transferred to the highest bidder. Operations regarding NFTs live on the NFT contract, so we make a cross-contract call to the NFT contract telling it to swap the owner of the NFT to the highest bidder. The method on the NFT contract to do this is nft_transfer
.
- 🌐 JavaScript
- 🦀 Rust
Loading...
In near-sdk-js we cannot transfer the NFT and send the $NEAR independently so we will chain the promises.
We will create a new file in our source folder named ext.rs
; here we are going to define the interface for the nft_transfer
method. We define this interface as a trait
and use the ext_contract
macro to convert the NFT trait into a module with the method nft_transfer
. Defining external methods in a separate file helps improve the readability of our code.
We then use this method in our lib.rs
file to transfer the NFT.
- lib.rs
- ext.rs
Loading...
Loading...
When calling this method we specify the NFT contract name, that we are attaching 30 Tgas to the call, that we are attaching a deposit of 1 YoctoNEAR to the call, and give the arguments receiver_id
and token_id
. The NFT requires that we attach 1 YoctoNEAR for security reasons.
NFT ownership problems
In our contract, we perform no checks to verify whether the contract actually owns the specified NFT. A bad actor could set up an auction where the NFT being auctioned doesn't belong to the auction contract, causing nft_transfer
to fail and the winning bidder to lose their bid funds with nothing in return. We could make a cross-contract call to the NFT contract to verify ownership on initialization but this would become quite complex. Instead, we will do this check off-chain and validate the auction in the frontend.
Displaying the contract object
Since we are now dealing with more information in our contract, instead of implementing a function to display each field we'll create a function to display the entire contract object. Since the contract doesn't include large complex data structures like a map displaying the contract state in its entirety is easily done.
- 🌐 JavaScript
- 🦀 Rust
Loading...
Loading...
We add the serilizers
macro to enable json serialization so the object as a whole can easily be displayed to the frontend without having to output each field individually.
Loading...
Testing with multiple contracts
In our tests, we're now going to be using two contracts; the auction contract and an NFT contract. Sandbox testing is great as it allows us to test multiple contracts in a realistic environment.
In our tests folder, we need the WASM for an NFT contract. For this tutorial, we compiled an example NFT contract from this repo.
To deploy the NFT contract, this time we're going to use dev deploy
which creates an account with a random ID and deploys the contract to it by specifying the path to the WASM file. After deploying we will initialize the contract with default metadata and specify an account ID which will be the owner of the NFT contract (though the owner of the NFT contract is irrelevant in this example). Default metadata sets information such as the name and symbol of the NFT contract to default values.
- 🌐 JavaScript
- 🦀 Rust
Loading...
Loading...
Minting an NFT
To start a proper auction the auction contract should own an NFT. To do this the auction account calls the NFT contract to mint a new NFT providing information such as the image for the NFT.
- 🌐 JavaScript
- 🦀 Rust
Loading...
Loading...
Verifying ownership of an NFT
After claim
is called, the test should verify that the auction winner now owns the NFT. This is done by calling nft_token
on the NFT contract and specifying the token ID which will return the account ID that the token belongs to.
- 🌐 JavaScript
- 🦀 Rust
Loading...
Loading...
Getting an NFT
If you would like to interact with the new contract via the CLI you can mint an NFT from a pre-deployed NFT contract
near call nft.examples.testnet nft_mint '{"token_id": "TYPE_A_UNIQUE_VALUE_HERE", "receiver_id": "<accountId>", "metadata": { "title": "GO TEAM", "description": "The Team Goes", "media": "https://bafybeidl4hjbpdr6u6xvlrizwxbrfcyqurzvcnn5xoilmcqbxfbdwrmp5m.ipfs.dweb.link/", "copies": 1}}' --accountId <accountId> --deposit 0.1
You can also just buy an NFT with testnet $NEAR on a testnet marketplace like Mintbase.
Conclusion
In this part of the tutorial we have added NFTs as a reward which has taught us how to interact with NFT standards, make cross-contract calls and test multiple contracts that interact with each other in workspaces. In the next part we'll learn how to interact with fungible token standards by adapting the auction to receive bids in FTs. This will allow users to launch auctions in different tokens, including stablecoins.