Basic Auction
In this section, we will analyze a simple auction contract, which allows users to place bids and track the highest bidder. After, we will cover how to test the contract, as well as how to deploy it on testnet
.
During this tutorial we will be relying on the Smart Contract Documentation and its different sections
Make sure to read the Prerequisites section and install the necessary tools before starting this tutorial
Cloning the contractโ
To get started we'll clone the tutorial's repository from Github. The repository contains the same smart contracts written in JavaScript (./contract-ts
) and Rust (./contract-rs
).
Navigate to the folder of the language you prefer, and then to the 01-basic-auction
folder.
- ๐ JavaScript
- ๐ฆ Rust
git clone git@github.com:near-examples/auctions-tutorial.git
cd contract-ts/01-basic-auction
git clone git@github.com:near-examples/auctions-tutorial.git
cd contract-rs/01-basic-auction
The repository also contains a frontend application that interacts with the contract. You can find it in the frontend
folder. We will cover the frontend in a future section
The Contract's Stateโ
The contract allows users to place bids using $NEAR tokens and keeps track of the highest bidder. Lets start by looking at how we define the contract's state, this is, the data that the contract will store.
- ๐ JavaScript
- ๐ฆ Rust
Loading...
Decoratorโ
A first thing to notice is that the main class of the contract is marked using the @NearBindgen
decorator, which allows also to further specify that the contract must be initialized before being used.
Storage (aka State)โ
Another important information revealed by the code is that a contract can store different types of data, in this case:
highest_bid
is an instance of aBid
which stores:bid
: aBigInt
representing an amount of $NEAR tokens inyoctonear
(1โ = 10^24 yโ
)bidder
: anAccountId
that represents which account placed the bid
auction_end_time
aBigInt
representing aunix timestamp
in nanoseconds
Loading...
Macrosโ
A first thing to notice is the use of the #[near(contract_state)]
macro to denote the main structure, and derive the PanicOnDefault
to specify that the contract must be initialized before being used.
We also use the #[near(serializers = [json, borsh])]
macro to enable both borsh
and JSON
(de)serialization of the Bid
structure. As a rule of thumb: use the json
serializer for structs that will be used as input / output of functions, and borsh
for those that will be saved to state.
Storage (aka State)โ
Another important information revealed by the code is that the contract can store different types of data.
highest_bid
is an instance of aBid
which stores:bid
: aNearToken
which simplifies handling $NEAR token amountsbidder
: theAccountId
that placed the bid
auction_end_time
is aU64
representing aunix timestamp
in nanoseconds
You can read more about the contract's structure and type of data it can store in the following documentation pages:
Initialization Functionโ
Lets now take a look at the initialization function, which we need to call to determine the time at which the auction will end.
- ๐ JavaScript
- ๐ฆ Rust
Loading...
Decoratorโ
We denote the initialization function using the @initialize({ privateFunction: true })
decorator. The privateFunction:true
denotes that the function can only be called by the account on which the contract is deployed.
Loading...
Macrosโ
We denote the initialization function using the #[init]
macro. Notice that the initialization function needs to return a instance of Self
, i.e. the contract's structure.
Meanwhile, the #[private]
denotes that the function can only be called by the account on which the contract is deployed.
End Timeโ
The end time is represented using a unix timestamp
in nano seconds, and needs to be given as a String
when calling the initialization function. This is because smart contracts cannot receive numbers larger than 52 bits
and unix timestamps
are represented in 64 bits
.
Initial Bidโ
Notice that we initialize the contract with a 1 yoctonear
bid, made from the current account id
. This mean that, after the contract is initialized, the first bid will be placed by the contract at 10^-24 NEAR.
You can read more about the contract's interface in our contract functions documentation, and learn about data types on the data types documentation.
Read-only Functionsโ
The contract implements two functions to give access to its stored data, i.e. the time at which the auction ends, and the highest bid so far.
- ๐ JavaScript
- ๐ฆ Rust
Loading...
Functions that do not change the contract's state (i.e. that only read from it) are called view
functions, and are decorated using the @view
decorator.
Loading...
Functions that do not change the contract's state (i.e. that only read from it) are called view
functions, and take a non-mutable reference to self
(&self
).
View functions are free to call, and do not require a NEAR account to sign a transaction in order to call them.
You can read more about the contract's interface in our contract functions documentation, and learn about data types on the data types documentation.
Bidding Functionโ
An auction is not an auction if you can't place a bid! For this, the contract includes a bid
function, which users will call attaching some $NEAR tokens.
The function is quite simple: it verifies if the auction is still active and compares the attached deposit with the current highest bid. If the bid is higher, it updates the highest_bid
and refunds the previous bidder.
- ๐ JavaScript
- ๐ฆ Rust
Loading...
Loading...
Payable Functionsโ
The first thing to notice is that the function changes the state, and thus is marked with a @call
decorator in JS, while taking as input a mutable reference to self (&mut self
) on Rust. To call this function, a NEAR account needs to sign a transaction and expend GAS.
Second, the function is marked as payable
, this is because by default functions do not accept $NEAR tokens! If a user attaches tokens while calling a function that is not marked as payable
, the transaction will fail.
The Environmentโ
Notice that the function can access information about the environment in which it is running, such as who called the function (predecessor account
), how much tokens they attached as deposit (attached deposit
), and the approximate unix timestamp
at which the function is executing (block timestamp
).
Token Transferโ
The function finishes by creating a Promise
to transfer tokens to the previous bidder. This token amount will be deducted immediately and transferred in the next block after the current function has finished executing.
Note that on the first bid the contract will send 1 yoctonear to itself, this is fine as we can safely assume that the contract will have the lowest denomination of $NEAR available to send to itself.
Handling Funds
When a user attaches tokens to a call, the tokens are deposited on the contract's account before the function is executed. However, if the function raises an error during its execution, the tokens are immediately refunded to the user.
You can read more about the environment variables, payable functions and which actions the contract can perform here:
Conclusionโ
In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data, and views the data. In the next part, we will cover how to test the contract, so we can ensure it works as expected before deploying it to testnet
.