Nhảy đến nội dung chính

Creating a frontend

Now that we have successfully created a contract, it's time to build a frontend to provide a user-friendly interface for interacting with it. Up until now, we have been using the CLI to send transactions and view the contract's state. However, frontends offer a more intuitive way for end users to interact with the contract. They can display all the relevant information in one place, allow users to make calls with a simple button click, and only require a wallet as a prerequisite.

Starting the frontend

Before we look at the code let's start up the frontend and have a peak at what it looks like. Feel free to interact with the application and place some bids. To place bids you will need to retrieve some testnet DAI from the faucet.

Navigate to the frontend directory then install dependencies and start the frontend.

yarn install
yarn dev

Frontend structure

In our frontend directory, we have a simple Next.js frontend that we'll walk through to understand the basics of creating a frontend for a NEAR smart contract.

For starters, let's take a look at how the code in the frontend is structured by doing a quick overview of the important files.

FileDescription
_app.jsResponsible for rending the page, initiates the wallet object and adds it to global context
index.jsThe main page where most of the projects components are loaded into and contains most of the logic for the application like viewing the state of the contract and logic for placing a bid
near.jsContains the wallet class that has methods to interact with the wallet and blockchain
context.jsHolds the global context - the wallet object and the signed in account ID - that can be accessed anywhere
config.jsSpecifies the account ID of the auction contract
Navigation.jsxA component that contains a button to sign users in and out of wallets
Bid.jsxA component allowing a user to make a bid
LastBid.jsxA component that displays the highest bid and when the highest bid will next refresh
AuctionItem.jsxA component that displays information about the NFT being auctioned
Timer.jsxA component that shows how long till the auction is over, or, if over, displays a button to claim the auction and then states the auction is over

Specifying the contract

We have a config file that specifies the contract name of the auction that the frontend will interact with. The example given is a pre-deployed contract from part 4 of the tutorial. The example contract is set up to accept bids in DAI (dai.fakes.testnet), has an NFT token pre-minted and owned by the contract account, and has an end auction time far in the future. Feel free to change the specified contract to your own auction that you deploy.


Setting up wallets

To be able to fully interact with the contract - send bids or claim the auction - you'll need a wallet to sign transactions. Wallets securely store your private keys and allow you to sign transactions without exposing your private key to the frontend. The wallet selector allows users to choose between a selection of wallets.

We abstract the wallet selector in our near.js file by exposing methods to complete various tasks. Feel free to explore the file to understand how the wallet selector is implemented.

We implement a sign-in and sign-out button in the navigation component to call the respective methods in the near.js file. When a wallet is signed in a function call access key is created. This allows the frontend to sign nonpayable transactions on behalf of the user, to the specified contract, without requiring the user to sign each transaction in the wallet; this allows for a better user experience. However, in this example, the main transaction we'll send is to make bids, which is payable so the wallet will prompt the user to sign each transaction.

We add the wallet and the account ID that is signed in to the global context making it easier to access anywhere in the application.


Displaying the highest bid

To get all the information about the auction we call the method get_auction_info. This will be used to display the highest bidder, the auction end time, the NFT contract ID and token ID, and FT contract IDs.

In the wallet file, you'll see that we make a query to the RPC provider, since we are not signing a transaction the wallet isn't required here. Here we are using https://rpc.testnet.near.org but note there are many different providers available. We are querying the RPC with optimistic finality, which queries the latest block recorded on the node. Alternatively, one could use final finality where the block has been validated by at least 66% of the validators on the network but this will provide slightly delayed information (only by a couple of seconds).

We then pass the information about the highest bidder into the LastBid component to display the bid amount and the bidder's account ID.

When we display the latest bid, instead of just showing the bid amount directly we divide the amount by the decimals of the FT. In this example, we are using DAI which has 18 decimals meaning that 1 DAI equals 10^18 units. We also display information about the token that is being used. We get this information from the FT contract by calling the ft_metadata method (remember that the FT contract ID is stored on the auction contract).


Updating the highest bid

We want to know the highest bid at all times, someone else could have placed a higher bid since the page was loaded. To solve this we fetch the contract information every 5 seconds using setInterval.


Auction end time

The contract stores the end time of the auction in the number of nanoseconds since the Unix epoch (1 January 1970 00:00:00 UTC). To make this look nicer we will display the time left in days, hours, minutes, and seconds.


Displaying the NFT

We want to show what NFT is being auctioned. To do this we will call nft_token on the NFT contract to get the NFT metadata. To call this method we need to specify the NFT contractId and the token_id, which can be found in the auction information. nft_token also returns the owner of the NFT, so we'll check this against the contract account to verify that the auction is valid.

Note that this effect will only run once the auctionInfo updates because we first need the NFT contract ID and token ID from auctionInfo to make a valid call to nft_token.

In the AuctionItem component we display the NFT image, name, and description.

Note that an image caching service is used to display the NFT image for better performance.


Making a bid

To make a bid we call the ft_transfer_call method on the FT contract which subsequently calls ft_on_transfer on the auction contract and attaches fungible tokens to the call.

We now multiply the bid amount by the decimals of the FT to get the correct amount to send. Since this method requires a 1 yoctoNEAR deposit the wallet will prompt the user to sign the transaction.


Claiming the auction

Once the auction is over (the current time is greater than the end time) the auction can be claimed. At this point, the timer will be hidden and a button to claim the auction will be displayed. Once clicked the claim method will be called on the auction contract to send the highest bidder the NFT and the auctioneer the FTs.


Conclusion

In this part of the tutorial, we have implemented a simple frontend for a NEAR contract. Along the way, you have learned how to use the wallet selector to sign the user in and out, how to view the contract’s state, how to sign and send transactions, and use ft_transfer_call from a frontend.

While we can see the highest bid, we may want to see the auction's bidding history. Since the contract only stores the most recent bid, we need to use an indexer to pull historical data. In the next part of the tutorial, we'll look at querying historical data using an API endpoint.

Was this page helpful?