diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index e42a596e7ad..af1bb2255ed 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -13,17 +13,16 @@ We'll start from a simple auction contract and slowly build on top of it to crea By the time you finish this tutorial, you will have learned several concepts and how to use many key privitives along the way: -- [Creating a simple smart contract](./1.1-basic.md) +- [Creating a simple smart contract](./1.1-basic.md#the-contracts-state) - [Writing tests for a contract](./1.2-testing.md) - [Deploying a contract to testnet](./1.3-deploy.md) - - +- [Locking a contract](./1.3-deploy.md#locking-the-contract) +- [Creating a frontend to interact with the contract](./2.1-frontend.md) +- [Using an indexing API to view historical bids](./2.2-indexing.md) +- [Making cross-contract calls](./3.1-nft.md#transferring-the-nft-to-the-winner) +- [Using Non-Fungible Tokens](./3.1-nft.md) +- [Using Fungible Tokens](./3.2-ft.md) +- [Modifying a factory contract to deploy your own contracts](./4-factory.md) --- @@ -80,21 +79,23 @@ We will be using [NEAR CLI](../../4.tools/cli.md) to interact with the blockchai This series will touch on different level of the NEAR tech stack. Each section will be independent of the previous one, so feel free to jump into the section that interests you the most. -#### 1. Smart Contract +#### 1. Smart contracts 101 1. [The Auction Contract](./1.1-basic.md): We cover a simple auction smart contract 2. [Testing the Contract](./1.2-testing.md): Learn how to test your contract in a realistic environment 3. [Deploying the Contract](./1.3-deploy.md): Deploy your contract to the NEAR blockchain -4. Updating and Locking a Contract (soon): Discover what it means to lock a contract -5. Giving an NFT to the Winner (soon) : Give the highest bidder an NFT to signal their win -6. Integrating Fungible Tokens (soon) : Allow people to use fungible tokens to bid (e.g. stable coins) -#### 2. Frontend +#### 2. Frontends 101 + +1. [Creating the frontend](./2.1-frontend.md): Lets learn how to connect a frontend with your smart contract +2. [indexing historical data](./2.2-indexing.md): Use APIs to keep track of historical bids -1. Creating the frontend (soon): Lets learn how to connect a frontend with your smart contract -2. Easily query on-chain data (soon): Use open APIs to keep track of the users and their bidding price +#### 3. Using Primitives +1. [Giving an NFT to the Winner](./3.1-nft.md): Give the highest bidder an NFT to signal their win +2. [Integrating Fungible Tokens](./3.2-ft.md): Allow people to use fungible tokens to bid (e.g. stable coins) +3. [Updating the frontend](./3.3-new-frontend.md): Update the frontend to use the extended functionality of the contract. -#### 3. Factory -1. Creating a factory (soon): Allow users to easily deploy and initialize their own auction contracts +#### 3. Auction Factory +1. [Creating a factory](./4-factory.md): Allow users to easily deploy and initialize their own auction contracts --- diff --git a/docs/3.tutorials/auction/1.1-basic.md b/docs/3.tutorials/auction/1.1-basic.md index 6896591972f..60a966f7856 100644 --- a/docs/3.tutorials/auction/1.1-basic.md +++ b/docs/3.tutorials/auction/1.1-basic.md @@ -7,11 +7,11 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github, Language} from "@site/src/components/codetabs" -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`. +In this section, we will analyze a simple auction contract, which allows users to place bids, track the highest bidder and claim tokens at the end of the auction. After, we will cover how to test the contract, as well as how to deploy it on `testnet`. :::info Documentation -During this tutorial we will be relying on the [Smart Contract Documentation](../../2.build/2.smart-contracts/quickstart.md) and its different sections +During this tutorial, we will be relying on the [Smart Contract Documentation](../../2.build/2.smart-contracts/quickstart.md) and its different sections ::: @@ -25,7 +25,7 @@ Make sure to read the [Prerequisites](./0-intro.md) section and install the nece ## Cloning the contract -To get started we'll clone the [tutorial's repository](https://github.com/near-examples/auctions-tutorial) from Github. The repository contains the same smart contracts written in JavaScript (`./contract-ts`) and Rust (`./contract-rs`). +To get started we'll clone the [tutorial's repository](https://github.com/near-examples/auctions-tutorial) 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. @@ -56,7 +56,7 @@ Navigate to the folder of the language you prefer, and then to the `01-basic-auc :::info Frontend -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 repository also contains a frontend application that interacts with the contract. You can find it in the `frontends` folder. We will cover the frontend in a future section ::: @@ -71,11 +71,11 @@ The contract allows users to place bids using $NEAR tokens and keeps track of th + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/src/contract.ts#L4-L14" + start="4" end="14" /> #### 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. + The 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: @@ -84,17 +84,19 @@ The contract allows users to place bids using $NEAR tokens and keeps track of th - `bid`: a `BigInt` representing an amount of $NEAR tokens in `yoctonear` (`1Ⓝ = 10^24 yⓃ`) - `bidder`: an `AccountId` that represents which account placed the bid - `auction_end_time` a `BigInt` representing a `unix timestamp` in **nanoseconds** + - `auctioneer` an `AccountId` that states who can withdraw the funds at the end of the auction + - `claimed` a `boolean` that tracks if the auctioneer has claimed the funds + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L5-L19" + start="5" end="19" /> #### 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. + 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. @@ -105,6 +107,8 @@ The contract allows users to place bids using $NEAR tokens and keeps track of th - `bid`: a `NearToken` which simplifies handling $NEAR token amounts - `bidder`: the `AccountId` that placed the bid - `auction_end_time` is a `U64` representing a `unix timestamp` in **nanoseconds** + - `auctioneer` an `AccountId` that states who can withdraw the funds at the end of the auction + - `claimed` a `boolean` that tracks if the auctioneer has claimed the funds @@ -113,7 +117,7 @@ The contract allows users to place bids using $NEAR tokens and keeps track of th :::tip Learn More -You can read more about the contract's structure and type of data it can store in the following documentation pages: +You can read more about the contract's structure and the type of data it can store in the following documentation pages: - [Basic Contract's Anatomy](../../2.build/2.smart-contracts/anatomy/anatomy.md) - [Contract's State](../../2.build/2.smart-contracts/anatomy/storage.md) - [Data Types](../../2.build/2.smart-contracts/anatomy/types.md) @@ -131,8 +135,8 @@ Lets now take a look at the initialization function, which we need to call to de + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/src/contract.ts#L16-#L21" + start="16" end="21" /> #### 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. @@ -142,11 +146,11 @@ Lets now take a look at the initialization function, which we need to call to de + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L23-L35" + start="23" end="35" /> #### 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. + We denote the initialization function using the `#[init]` macro. Notice that the initialization function needs to return an 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. @@ -158,7 +162,13 @@ Lets now take a look at the initialization function, which we need to call to de 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. +Notice that we initialize the contract with a `1 yoctonear` bid, made from the `current account id`. This means that, after the contract is initialized, the first bid will be placed by the contract at 10^-24 NEAR. + +#### Claimed +The `claimed` field is initialized as `false`, as the auctioneer has not claimed the funds yet. + +#### Auctioneer +The auctioneer is set by the deployer on initialization and is the account that will be able to claim the funds at the end of the auction. :::tip Learn More @@ -170,15 +180,15 @@ You can read more about the contract's interface in our [contract functions docu ## 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. +The contract implements four functions to give access to its stored data, i.e. the highest bid so far (the amount and by whom), the time at which the auction ends, the auctioneer, and whether the auction has been claimed. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/src/contract.ts#L53-L71" + start="53" end="71" /> 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. @@ -187,10 +197,10 @@ The contract implements two functions to give access to its stored data, i.e. th + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L78-L92" + start="78" end="92" /> - 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`). + 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`). @@ -218,16 +228,16 @@ The function is quite simple: it verifies if the auction is still active and com + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/src/contract.ts#L23-L43" + start="23" end="43" /> + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L37-L63" + start="37" end="63" /> @@ -239,18 +249,18 @@ The first thing to notice is that the function changes the state, and thus is ma 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`). +Notice that the function can access information about the environment in which it is running, such as who called the function (`predecessor account`), how many 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. +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. +When a user attaches tokens to a call, the tokens are deposited to 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.
@@ -265,6 +275,44 @@ You can read more about the environment variables, payable functions and which a --- +## Claim function + +You'll notice that the contract has a final function called `claim`, this allows the auctioneer to claim the funds from the contract at the end of the auction. Since, on NEAR, a smart contract account and user account are the same, contracts can still have keys when they are deployed, thus a user could just claim the funds from the contract via a wallet. However, this presents a security issue since by having a key the key holder can take the funds from the contract at any point, maliciously change the contract's state or just delete the contract as a whole. By implementing a `claim` function we can later lock the contract by removing all access keys and have the auctioneer claim the funds via preset conditions via code. + + + + + + + + + + + + + + + + + +This function is quite simple it does four things: +1) Checks that the auction has ended (the current timestamp is past the auction end time). +2) Checks that the auction has not yet been claimed. +3) Sets the auction as now claimed. +4) And if these conditions hold true it transfers $NEAR equal to the highest bid to the auctioneer. + +:::tip Learn More + +You can read more about locking contracts in this section of the documentation: [locked accounts](../../1.concepts/protocol/access-keys.md#locked-accounts) + +::: + +--- + ## 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`. +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](./1.2-testing.md), we will cover how to test the contract, so we can ensure it works as expected before deploying it to `testnet`. \ No newline at end of file diff --git a/docs/3.tutorials/auction/1.2-testing.md b/docs/3.tutorials/auction/1.2-testing.md index b21413fa87c..394658bc880 100644 --- a/docs/3.tutorials/auction/1.2-testing.md +++ b/docs/3.tutorials/auction/1.2-testing.md @@ -7,9 +7,9 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github, Language} from "@site/src/components/codetabs" -In the previous section we went through the contract's code, analyzing how it worked. Now, we need to test it and make sure it works as expected! For contracts there are two types of testing you can do: unit testing and sandbox testing. +In the previous section, we went through the contract's code, analyzing how it worked. Now, we need to test it and make sure it works as expected! For contracts, there are two types of testing you can do: unit testing and sandbox testing. -Here, we will focus on the sandbox testing, as it enables to deploy the contract in a realistic environment, allowing us to create multiple accounts and interact with the contract as if it was deployed on the blockchain. +Here, we will focus on sandbox testing, as it enables one to deploy the contract in a realistic environment, allowing us to create multiple accounts and interact with the contract as if it was deployed on the blockchain. :::info unit testing @@ -21,21 +21,21 @@ Unit tests are built into the language and are used to test the contract functio ## Account Creation -The first thing our test does is to create multiple accounts with 10 $NEAR tokens each, and deploy the contract into one of them. +The first thing our test does is to create multiple accounts with 10 $NEAR tokens each and deploy the contract to one of them. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L12-L23" + start="12" end="23" /> To deploy the contract, we pass the path to the compiled WASM contract as an argument to the test in `package.json`. Indeed, when executing `npm run test`, the command will first compile the contract and then run the tests. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L17-l29" + start="17" end="29" /> Notice that the sandbox compiles the code itself, so we do not need to pre-compile the contract before running the tests. @@ -52,8 +52,8 @@ To initialize, the contract's account calls itself, invoking the `init` function + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L26-L29" + start="26" end="29" /> :::warning Time Units @@ -66,8 +66,8 @@ The contract measures time in **nanoseconds**, for which we need to multiply the + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L31-L38" + start="31" end="38" /> :::warning Time Units @@ -88,17 +88,17 @@ Notice that the time is passed as a `String` to the contract, this is because sm ## Bidding -Now that the contract is deployed and initialized, we can start biding and checking if the contract behaves as expected. +Now that the contract is deployed and initialized, we can start bidding and checking if the contract behaves as expected. -We first make `alice` place a bid of 1 NEAR, and check that the contract correctly registers the bid. Then, we make `bob` place a bid of 2 NEAR, and check that the highest bid is updated, and that `alice` gets its NEAR refunded. +We first make `alice` place a bid of 1 NEAR, and check that the contract correctly registers the bid. Then, we have `bob` place a bid of 2 NEAR, and check that the highest bid is updated, and that `alice` gets her NEAR refunded. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L46-L61" + start="46" end="61" /> @@ -106,7 +106,7 @@ We first make `alice` place a bid of 1 NEAR, and check that the contract correct + start="42" end="74" /> @@ -126,16 +126,16 @@ When testing we should also check that the contract does not allow invalid calls + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L64" + start="64" end="64" /> + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L77-L83" + start="77" end="83" /> @@ -144,27 +144,29 @@ When testing we should also check that the contract does not allow invalid calls --- ## Fast Forwarding Time -The sandbox allows us to fast forward time, which is useful to test the contract when the auction is over. The test advances 200 blocks in order to pass a minute, and thus finishing the auction. +The sandbox allows us to fast-forward time, which is useful for testing the contract when the auction is over. The test advances 200 blocks in order to pass a minute, and thus allowing the auction to be claimed. -Any bid made after the auction ends should be rejected. +After which the auction can now be claimed. Once claimed the test checks that the auctioneer has received the correct amount of $NEAR tokens. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L69-L81" + start="69" end="81" /> + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L95-L112" + start="95" end="112" /> +If you review the tests in full you'll see that we also test other invalid calls such as the auctioneer trying to claim the auction before it is over and a user attempting to bid once the auction is over. + --- ## Executing the tests @@ -202,6 +204,6 @@ All tests should pass, and you should see the output of the tests in the console ## Conclusion -In this part of the tutorial, we've seen how to use our sandbox testing environment to test the contract. We've tested the contract's initialization, biding, and time advancement. +In this part of the tutorial, we've seen how to use our sandbox testing environment to test the contract. We've tested the contract's initialization, bidding, and time advancement. -You are now ready to move to the next section, where we will deploy the contract to `testnet` and interact with it through the CLI. +You are now ready to move to the [next section](./1.3-deploy.md), where we will deploy the contract to `testnet` and interact with it through the CLI. \ No newline at end of file diff --git a/docs/3.tutorials/auction/1.3-deploy.md b/docs/3.tutorials/auction/1.3-deploy.md index 955c9a3ff33..89eebfe9d66 100644 --- a/docs/3.tutorials/auction/1.3-deploy.md +++ b/docs/3.tutorials/auction/1.3-deploy.md @@ -7,13 +7,13 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github, Language} from "@site/src/components/codetabs" -In the previous sections we saw how a simple auction smart contract is implemented, and checked its correctness using sandbox testing. +In the previous sections, we saw how a simple auction smart contract is implemented, and checked its correctness using sandbox testing. -The time has come to release it on the actual blockchain and interact with it! In this section, we will show you how to create a simple testnet account, deploy the contract and interact with it from the CLI. +The time has come to release it on the actual blockchain and interact with it! In this section, we will show you how to create a simple testnet account, deploy the contract, and interact with it from the CLI. :::info Networks -NEAR has two main networks for you to use: `testnet` and `mainnet`. The `testnet` network behaves exactly as the main network, but uses test tokens with no real value +NEAR has two main networks for you to use: `testnet` and `mainnet`. The `testnet` network behaves exactly as the main network but uses test tokens with no real value ::: @@ -59,9 +59,11 @@ To deploy the contract, you need to compile the contract code into WebAssembly ( # initialize the contract, it finishes in 2 minutes MINUTE_FROM_NOW=$(date -v+2M +%s000000000) - near call init '{"end_time": "'$MINUTE_FROM_NOW'"}' --accountId + near call init '{"end_time": "'$MINUTE_FROM_NOW'", "auctioneer": ""}' --accountId ``` + Replace `` with the name of another account, this should not be the same as the contract account as we intend on removing its keys. + @@ -82,8 +84,24 @@ To deploy the contract, you need to compile the contract code into WebAssembly ( +--- + +## Locking the contract -Now that the contract is deployed and initialized we can send transactions to it using the CLI. +As mentioned previously we should lock the account by removing the keys. This allows our users to interact with the contract without having to trust the account owner. + +```bash +near account delete-keys +``` + +Next, specify the contract account and click the right arrow β†’ to delete all the keys. Make sure you select testnet. + +:::caution +Be extra careful to delete the keys from the correct account as you'll never be able to access the account again! +::: + + +Now that the contract is deployed, initialized, and locked we can send transactions to it using the CLI. :::tip Interactive CLI NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands @@ -92,11 +110,11 @@ NEAR's CLI is interactive meaning you can type `near` and click through all the --- ## Interacting with the Contract -We are now ready to start bidding by calling the `bid` function on the contract. We recommend you to create **two new accounts** to simulate different bidders. +We are now ready to start bidding by calling the `bid` function on the contract. We recommend that you create **two new accounts** to simulate different bidders. ```bash # call the contract to bid -near call bid --accountId --amount 1 +near call bid --accountId --amount 1 # get the highest bid near view get_highest_bid @@ -118,8 +136,6 @@ Do not use testnet as your **only way** to test contracts. Always test your cont :::tip Frontend -Generally you will use the CLI only to deploy and initialize the contract. After, all interactions will be made from a frontend - -We will cover this topic in the future, after we have finished adding more features to the auction contract +Generally, you will use the CLI only to deploy and initialize the contract. Afterward, all interactions will be made from a frontend. This is why in the [next section](./2.1-frontend.md) we'll move on to creating a frontend to interact with the contract. ::: \ No newline at end of file diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md deleted file mode 100644 index 88fa98b9033..00000000000 --- a/docs/3.tutorials/auction/2-locking.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -id: locking-the-contract -title: Locking the contract -sidebar_label: Locking the contract ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import {Github} from "@site/src/components/codetabs" - -In the basic contract, the auctioneer would claim the tokens from the final bid of the contract via logging into the contract accounts wallet using a key. It is a security issue for there to exist a key for a smart contract since the key holder can take the funds from the contract at any point, maliciously change the contract or just delete the contract as a whole. To stop exploitation we will [lock](../../1.concepts/protocol/access-keys.md#locked-accounts) the contract by removing all access keys and implementing a new method to `claim` the tokens. - ---- - -## Adding an auctioneer - -When we introduce the `claim` method we want to make sure that the individual or entity that set up the auction receives the $NEAR tokens. To do this we now change the `init` method to initialize the contract with an `auctioneer`. - - - - - - - - - - - - - - - - - -Let's also introduce a boolean field named `claimed` to track whether the tokens have been claimed by the auctioneer yet. - ---- - -## Adding the claim method - -The `claim` method should only be callable when the auction is over, can only be executed once and should transfer the tokens to the auctioneer. We'll implement this as so: - - - - - - - - - - - - - - - - - ---- - -## Updating the tests - -If we update our contract then we should update our tests accordingly. For example, the tests will now need to add `auctioneer` to the arguments of `init`. - -We will now also test the `claim` method. The test will check that the `auctioneer` account has received the correct amount of $NEAR tokens. - - - - - - - - - - - - - - - - - -Note that the test doesn't check that the auctioneer has exactly 12 $NEAR since the auctioneer uses tokens through gas fees when calling `claim`. - ---- - -## Deploying and locking - -Go ahead and test, build, and deploy your new contract, as in part 1. Remember to add the `auctioneer` argument when initializing. - -Now that we have the `claim` method, we can deploy the contract without keys. Later, we will introduce a factory contract that deploys auctions to a locked account, but for now, we can manually remove the keys using the CLI to lock the account. - -``` -near account delete-keys -``` - -Next specify the contract account and click the right arrow β†’ to delete all the keys. Make sure to select testnet - -:::caution -Be extra careful to delete the keys from the correct account as you'll never be able to access the account again! -::: - ---- - -## Conclusion - -In this part of the tutorial, you learned how to lock a contract by creating a new method to claim tokens, specify an account on initialization that will claim the tokens, and how to delete the contract account's keys with the CLI. - -In the [next part](./3-nft.md), we'll add a prize to the auction by introducing a new primitive; spoiler, the primitive is an NFT. We'll look at how to use non-fungible token standards to send NFTs and interact with multiple interacting contracts in sandbox testing. \ No newline at end of file diff --git a/docs/3.tutorials/auction/2.1-frontend.md b/docs/3.tutorials/auction/2.1-frontend.md new file mode 100644 index 00000000000..923256b0351 --- /dev/null +++ b/docs/3.tutorials/auction/2.1-frontend.md @@ -0,0 +1,175 @@ +--- +id: creating-a-frontend +title: Creating a Frontend +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {Github, Language} from "@site/src/components/codetabs" + +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. + +--- + +## Frontend structure + +Navigate to the auction frontend. + +```bash +cd frotends/01-frontend +``` + +Here 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. + +| File | Description | +|----------------------------------|---------------------------------------------------------------------------------| +| **_app.js** | Responsible for rending the page, initiates the wallet object and adds it to global context | +| **index.js** | The main page where the project's 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.js** | Contains the wallet class that has methods to interact with the wallet and blockchain | +| **context.js** | Holds the global context - the wallet object and the signed-in account ID - that can be accessed anywhere | +| **config.js** | Specifies the account ID of the auction contract | +| **Navigation.jsx** | A component that contains a button to sign users in and out of wallets | +| **Bid.jsx** | A component allowing a user to make a bid | +| **LastBid.jsx** | A component that displays the highest bid and when the highest bid will next refresh | +| **Timer.jsx** | A 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. There has been an example auction contract deployed and specified already but feel free to change the contract to your own auction contract you deployed. + + + + + +--- + +## Setting up wallets + +To be able to fully interact with the contract - send bids and 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](https://github.com/near-examples/auctions-tutorial/blob/main/frontends/01-frontend/src/wallets/near.js) to understand fully how the wallet selector is implemented. + +The wallet object is initiated in the `app.js` file and its added to the global context along with the account that is signed in to make it easier to access anywhere in the application. + + + + + + +:::tip Access keys + +On NEAR, in additional to normal full access keys, we have `function-call access keys` that are given to applications to allow them to sign `non-payable` transactions on behalf of the user. This is so the wallet doesn't have to pop up for each non-critical transaction, improving the user experience. When creating the wallet object you can decide to create an access key for the application to use. However, in this example we opt out since the main function - `bid` - we'll be calling is `payable`. + +You can read further about NEAR's key model [here](../../1.concepts/protocol/access-keys.md). + +::: + +We add a sign-in and sign-out button in the `navigation` component to call the respective methods in the `near.js` file. + + + + + + +--- + +## Displaying the highest bid + +To get the highest bid from the auction and who made it we call `get_highest_bid`. Since this function returns the highest bid in `yoctoNEAR` we divide by `10^24` to get the amount in NEAR. + + + + + + +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](../../5.api/rpc/providers.md). 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. + + + + + + +--- + +## 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 20 seconds using `setInterval` and update the highest bid if it has changed. In reality you would want to refresh the bid amount more requently but for the sake of saving on RPC calls we are doing it every 20 seconds. + + + + + +--- + +## 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). In our frontend we will display the time left in days, hours, minutes, and seconds. + + + + + +--- + +## Making a bid + +To make a bid we make a call to the contract using the `bid` function. We specify the deposit amount in `yoctoNEAR` which will be the bid amount. The input box will take the bid amount in NEAR so we multiply by `10^24` to get the correct amount to send. We also specify the amount of gas to attach to the transaction, here we are attaching 30Tgas which is more than enough for the transaction to go through, we are refunded any unused gas anyway. + +Here, since the user is changing the state of the contract, not just viewing it, the user needs to sign the transaction. Thus the wallet will pop up displaying the transaction details. + + + + + + +--- + +## 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` function 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. + +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 (to reduce storage costs), we need to use an indexer to pull historical data. In the [next part](./2.2-indexing.md) of the tutorial, we'll look at querying historical data using an API endpoint. diff --git a/docs/3.tutorials/auction/6-indexing.md b/docs/3.tutorials/auction/2.2-indexing.md similarity index 51% rename from docs/3.tutorials/auction/6-indexing.md rename to docs/3.tutorials/auction/2.2-indexing.md index dd4964de609..381dd718458 100644 --- a/docs/3.tutorials/auction/6-indexing.md +++ b/docs/3.tutorials/auction/2.2-indexing.md @@ -1,12 +1,11 @@ --- id: indexing-historical-data -title: Indexing historical data -sidebar_label: Indexing historical data +title: Indexing Historical Data --- import {Github, Language} from "@site/src/components/codetabs" -In our frontend, we can easily display the previous bid since it's stored in the contract's state. However, we're unable to see previous bids to the auction. An indexer is used to fetch historical data from the blockchain and store it in a database. Since indexers can take a while to set up and can be expensive to run, we will use a pre-defined API point provided by NEAR Blocks to query an indexer they run that will fetch us the data we need. +In our frontend, we can easily display the previous bid since it's stored in the contract's state. However, we're unable to see previous bids to the auction. An indexer is used to fetch historical data from the blockchain and store it in a database. Since indexers can take a while to set up and can be expensive to run, we will use a pre-defined API endpoint provided by NEAR Blocks to query an indexer they run that will fetch us the data we need. --- @@ -30,40 +29,52 @@ NextJS allows us to easily create server-side functions with API routes. We need + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontends/01-frontend/src/pages/api/getBidHistory.js#L1-L13" + start="3" end="13" /> -Here we are retrieving the auction contract ID and fungible token contract ID from the API route call and then calling the NEAR Blocks API. This specific API endpoint allows us to retrieve transactions made to a specific contract calling a specific method. Some details are worth discussing here: +Here we are retrieving the auction contract ID from the API route call and then calling the NEAR Blocks API. This specific API endpoint allows us to retrieve transactions made to a specific contract calling a specific function. Some details are worth discussing here: -- We pass the account ID of the auction contract, which is `auction-example.testnet` in the example repo. -- We specify the method name on the auction contract that we want the transactions for, this will be `ft_on_transfer` as it will give all bids made to the auction. -- We pass the fungible token account ID as the sender since we know only transactions from the correct FT contract will be successful. -- We'll receive a JSON object of 25 transactions, ordered by the most recent first. +- We pass the account ID of the auction contract, which is `basic-auction-example.testnet` in the example repo. +- We specify the function name on the auction contract that we want the transactions for, in our case it will be `bid` +- We'll receive a JSON object of up to 25 transactions, ordered by the most recent first. - We pass our API key to authenticate the request. --- -## Filtering out invalid transactions +## Retrieving the bids from the API result -The API call itself does not filter out invalid transactions. A transaction may be rejected for example if the bid is lower than the current highest bid. To check whether a transaction was successful, therefore the bid was valid, we check that the `receipt outcome status` is `true`. If a transaction is valid we store the account ID of the bidder and the amount they bid, gathered from the args of the transaction. We loop through each transaction until we either have 5 valid transactions or we've looped through the whole page of 25. Note that, in our example, if the previous 25 bids were invalid the API will return an empty array. +From our API call, we receive a JSON object containing up to 25 transactions made to the bid function on the auction contract. + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontends/01-frontend/src/pages/api/getBidHistory.js#L15-L37" + start="15" end="37" /> +We want to display the 5 most recent valid bids. To do this we loop through each transaction and check whether the transaction was successful by checking `receipt_outcome.status` is `true`. If so we check the first action (since there should only be one function call action in this case) and store the `deposit`, which is equal to the bid amount, and store the `predecessor account ID`, which is the account ID of the bidder. + +Once we have 5 valid bids we can stop looping through the transactions. + +Note that in our example if the previous 25 bids were invalid the API will return an empty array. The function could be set up such that it calls the API again to get the new page of transactions if this is the case. + + +:::tip Learn More + +You can read more about transaction actions in this section of the documentation: [Actions](../../1.concepts/protocol/transaction-anatomy.md#Actions) + +::: + --- ## Using the API Route -In our main page, we'll define a function to call the API route we just created. This function will be called as soon as the fungible token account ID is set and each time the page timer reaches zero. +In our main page, we'll define a function to call the API route we just created. This function will be called each time the page timer reaches zero. + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontends/01-frontend/src/pages/index.js#L84-L92" + start="84" end="92" /> The `pastBids` will then be passed into the `Bid` component to be displayed. @@ -74,8 +85,23 @@ You may like to explore NEAR Blocks APIs further to see what other data you can --- +## Using the frontend + +Now we have implemented the frontend and indexer you can go ahead and actually use the frontend. From the root of the frontend directory run the following commands: + +```bash +# install dependencies +npm install + +# run the frontend locally +npm run dev +``` + +--- + ## Conclusion In this short part of the tutorial, we've added the ability to display the previous 5 valid bids made to the auction contract. In doing this we learned how to interact with the NEAR Blocks APIs to retrieve historical data from the blockchain and how to make server-side calls in NextJS to not expose our API key. Now we have a pretty good frontend that displays all the information we need about the auction contract. -In the [final part](./7-factory.md) of this tutorial series you'll learn how to deploy a factory contract - a contract that deploys other contracts - to make it easier for anyone to launch a new auction. +In the [next section of the tutorial](./3.1-nft.md) we're going to improve our contract by adding primitives to the auction contract starting with adding NFTs as a prize. + diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3.1-nft.md similarity index 71% rename from docs/3.tutorials/auction/3-nft.md rename to docs/3.tutorials/auction/3.1-nft.md index 941c6133b0a..db8441afc7e 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3.1-nft.md @@ -1,7 +1,6 @@ --- id: winning-an-nft title: Winning an NFT -sidebar_label: Winning an NFT --- import Tabs from '@theme/Tabs'; @@ -16,13 +15,12 @@ No one will enter an auction if there's nothing to win, so let's add a prize. Wh 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: - @@ -30,7 +28,7 @@ When we create an auction we need to list the NFT. To specify which NFT is being Note that `token_id` is of type `TokenId` which is a String type alias that the NFT standards use for future-proofing. @@ -50,8 +48,8 @@ When the method `claim` is called the NFT needs to be transferred to the highest + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/02-winner-gets-nft/src/contract.ts#L58-L60" + start="58" end="60" /> In near-sdk-js we cannot transfer the NFT and send the $NEAR independently so we will chain the promises. @@ -65,10 +63,10 @@ When the method `claim` is called the NFT needs to be transferred to the highest @@ -86,6 +84,38 @@ In our contract, we perform no checks to verify whether the contract actually ow --- +## 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. + + + + + + + + + + + + + + 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. + + + + + + + + + ## 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. @@ -99,7 +129,7 @@ To deploy the NFT contract, this time we're going to use `dev deploy` which crea @@ -107,7 +137,7 @@ To deploy the NFT contract, this time we're going to use `dev deploy` which crea @@ -125,7 +155,7 @@ To start a proper auction the auction contract should own an NFT. To do this the @@ -133,7 +163,7 @@ To start a proper auction the auction contract should own an NFT. To do this the @@ -151,7 +181,7 @@ After `claim` is called, the test should verify that the auction winner now owns @@ -159,7 +189,7 @@ After `claim` is called, the test should verify that the auction winner now owns @@ -172,8 +202,8 @@ After `claim` is called, the test should verify that the auction winner now owns 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 contract call-function as-transaction nft.examples.testnet nft_mint json-args '{"token_id": "TYPE_A_UNIQUE_VALUE_HERE", "receiver_id": "", "metadata": { "title": "GO TEAM", "description": "The Team Goes", "media": "https://bafybeidl4hjbpdr6u6xvlrizwxbrfcyqurzvcnn5xoilmcqbxfbdwrmp5m.ipfs.dweb.link/", "copies": 1}}' prepaid-gas '100.0 Tgas' attached-deposit '0.1 NEAR' sign-as network-config testnet +```bash +near call nft.examples.testnet nft_mint '{"token_id": "TYPE_A_UNIQUE_VALUE_HERE", "receiver_id": "", "metadata": { "title": "GO TEAM", "description": "The Team Goes", "media": "https://bafybeidl4hjbpdr6u6xvlrizwxbrfcyqurzvcnn5xoilmcqbxfbdwrmp5m.ipfs.dweb.link/", "copies": 1}}' --accountId --deposit 0.1 ``` You can also just buy an NFT with testnet $NEAR on a testnet marketplace like [Mintbase](https://testnet.mintbase.xyz/explore/new/0). @@ -182,4 +212,4 @@ You can also just buy an NFT with testnet $NEAR on a testnet marketplace like [M ## 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](./4-ft.md) 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. \ No newline at end of file +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](./3.2-ft.md) 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. \ No newline at end of file diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/3.2-ft.md similarity index 81% rename from docs/3.tutorials/auction/4-ft.md rename to docs/3.tutorials/auction/3.2-ft.md index f23cd3c984e..93e8efe961c 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/3.2-ft.md @@ -1,7 +1,6 @@ --- id: bidding-with-fts title: Bidding with FTs -sidebar_label: Bidding with FTs --- import Tabs from '@theme/Tabs'; @@ -21,7 +20,7 @@ We want to only accept bids in one type of fungible token; accepting many differ @@ -29,7 +28,7 @@ We want to only accept bids in one type of fungible token; accepting many differ @@ -51,15 +50,15 @@ The `ft_on_transfer` method always has the same interface; the FT contract will + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/03-bid-with-fts/src/contract.ts#L33" + start="33" end="33" /> @@ -74,37 +73,37 @@ We need to confirm that the user is attaching fungible tokens when calling the m + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/03-bid-with-fts/src/contract.ts#L38" + start="38" end="38" /> -The bidder's account ID is now given by the argument `sender_id`. +The bidder's account ID is now given by the argument `sender_id` and the bid amount is passed as an argument named `amount`. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/03-bid-with-fts/src/contract.ts#L40-L43" + start="40" end="43" /> @@ -119,11 +118,11 @@ When we want to return the funds to the previous bidder we now make a cross-cont + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/03-bid-with-fts/src/contract.ts#L45-L51" + start="45" end="51" /> + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/03-bid-with-fts/src/contract.ts#L78-L81" + start="68" end="71" /> In JavaScript, we have to return the Promise to transfer the FTs but we also need to return how much to refund the user. So after transferring the FTs, we make a `callback` to our own contract to resume the contract flow. Note that the callback is private so it can only be called by the contract. We return 0 because the method uses all the FTs in the call. @@ -134,23 +133,25 @@ When we want to return the funds to the previous bidder we now make a cross-cont We then return 0 because the method uses all the FTs in the call.
+ If the call was to fail the FT contract will automatically refund the user their FTs. +
What happens if the cross-contract call fails? @@ -172,8 +173,8 @@ When the auction is complete we need to send the fungible tokens to the auctione + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/03-bid-with-fts/src/contract.ts#L61-L65" + start="61" end="65" /> In JavaScript, since we need to return each cross-contract call we chain the NFT and FT transfer. @@ -182,7 +183,7 @@ When the auction is complete we need to send the fungible tokens to the auctione @@ -202,7 +203,7 @@ When the contract is deployed it is initialized with `new_default_meta` which se @@ -210,7 +211,7 @@ When the contract is deployed it is initialized with `new_default_meta` which se @@ -230,7 +231,7 @@ In our tests, since we are creating a new fungible token and new accounts we wil @@ -238,7 +239,7 @@ In our tests, since we are creating a new fungible token and new accounts we wil @@ -256,7 +257,7 @@ Then we will transfer the bidders FTs so they can use them to bid. A simple tran @@ -265,10 +266,10 @@ Then we will transfer the bidders FTs so they can use them to bid. A simple tran @@ -287,7 +288,7 @@ As stated previously, to bid on the auction the bidder now calls `ft_transfer_ca @@ -296,10 +297,10 @@ As stated previously, to bid on the auction the bidder now calls `ft_transfer_ca @@ -318,7 +319,7 @@ Previously, to check a user's $NEAR balance, we pulled the details from their ac @@ -327,10 +328,10 @@ Previously, to check a user's $NEAR balance, we pulled the details from their ac @@ -351,7 +352,7 @@ Previous to this, Bob made a bid of 60,000 and Alice was returned her bid bringi @@ -359,7 +360,7 @@ Previous to this, Bob made a bid of 60,000 and Alice was returned her bid bringi @@ -376,14 +377,14 @@ When deploying the contract make sure to specify the FT contract `dai.fakes.test The auction contract will need to be registered as well, you could do this by sending it an arbitrary amount of $DAI from the faucet or you can just register it since it doesn't need any FTs. You should also register the auctioneer, -``` -near contract call-function as-transaction dai.fakes.testnet storage_deposit json-args '{"account_id": ""}' prepaid-gas '100.0 Tgas' attached-deposit '0.1 NEAR' +```bash +near call dai.fakes.testnet storage_deposit '{"account_id": ""}' --accountId --deposit 0.1 ``` Now you can go ahead and place a bid. DAI has 18 decimals meaning that 1 $DAI is made up of 10^24 smallest units. To make a bid of 2 $DAI you can use the command: -``` -near contract call-function as-transaction dai.fakes.testnet ft_transfer_call json-args '{"receiver_id": "", "amount": "2000000000000000000", "msg": ""}' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' +```bash +near call dai.fakes.testnet ft_transfer_call '{"receiver_id": "", "amount": "2000000000000000000", "msg": ""}' --accountId --depositYocto 1 ``` ## Auction architecture @@ -422,6 +423,6 @@ However, this architecture could be deemed less secure since if a bad actor were In this section, you learned a lot about fungible tokens: how to send and receive FTs in a smart contract, and then in sandbox tests how to deploy and initialize an FT contract, how to register a user in an FT contract, and send them some tokens, how to attach FTs to a smart contract call and finally how to view the FT balance of a user. With that, we now have our completed auction smart contract! -Taking a further step back we've taken a very simple auction contract and transformed it into a more production contract with thorough testing. To improve the auction we learned how to make a contract more secure by locking it, added a prize by introducing NFTs, and enabled auctioneers to host auctions with FTs. +Taking a further step back we've taken a very simple auction contract and transformed it into a more production contract with thorough testing. To improve the auction we learned how to add a prize by introducing NFTs, and enabled auctioneers to host auctions with FTs. -Up to now, we've just interacted with the contract via the CLI. In the [next part](./5-frontend.md), we'll learn the basics of creating frontends for NEAR contracts by creating a simple frontend for our auction contract so users can seamlessly interact with it. \ No newline at end of file +In the [next part of the tutorial](./3.3-new-frontend.md), we're going to update the frontend to interact with the new features of the contract. \ No newline at end of file diff --git a/docs/3.tutorials/auction/3.3-new-frontend.md b/docs/3.tutorials/auction/3.3-new-frontend.md new file mode 100644 index 00000000000..588ebc984c8 --- /dev/null +++ b/docs/3.tutorials/auction/3.3-new-frontend.md @@ -0,0 +1,100 @@ +--- +id: updating-the-frontend +title: Updating the Frontend +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {Github, Language} from "@site/src/components/codetabs" + +Now we've updated the contract to include an NFT as a reward and changed the contract such that it accepts bids in fungible tokens, we need to update the frontend accordingly. + +## Getting the data from the contract + +Now we have a function to output the whole contract state we will call this function in our frontend + + + + + +This call will deliver us the contract Ids of the FT and NFT contracts along with the token Id of the NFT. We will then use this information to call the `ft_metadata` and `nft_token` methods on the FT and NFT contracts respectively to get information about the FT and NFT. + +--- + +## 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 a new component named `AuctionItem` we display the NFT image, name, and description. + + + + + +Note that an image caching service is used to display the NFT image for better performance. + +--- + +## Fetching FT information + +Using the FT contract ID from the auction information, we can call the `ft_metadata` method on the FT contract to get information about the fungible token that is being used for the auction. + + + + + +We set the FT image, symbol, icon, and decimals in state. We use the decimals to format the amount of tokens being bid. In the case of DAI it divides the amount by 10^18. The reverse process is used when making a bid, the bid amount is multiplied by 10^18 before being sent to the contract. + +--- + +## Bidding with FTs + +Instead of calling the function `bid` on the contract we now call the `ft_transfer_call` function on the FT contract. This function transfers the FTs to the auction contract and calls the `ft_on_transfer` on the auction contract. + + + + + +--- + +## Updating the indexing API call + +We need to update the API call that fetches historical bids to now index each time `ft_on_transfer` is called on the auction contract from the FT contract. + + + + + +And now instead of getting the the bid amount from the deposit, it is now retrieved from the calls argument, from `amount`. The case is the same for the account Id of the bidder, from `sender_id`. + + + + + +--- + +## Conclusion + +Ok nice, that didn't take too long. To look back, we updated the frontend to now display the NFT being auctioned, to display bid amounts - both the current and historical bids - in terms of the FT being used, and changed the bidding process to now use FTs. + +In the [final section](./4-factory.md) of this mega tutorial we'll create an auction factory contract that is used to deploy and initialize new auction contracts. \ No newline at end of file diff --git a/docs/3.tutorials/auction/7-factory.md b/docs/3.tutorials/auction/4-factory.md similarity index 85% rename from docs/3.tutorials/auction/7-factory.md rename to docs/3.tutorials/auction/4-factory.md index 95ce83a7f74..f1915126828 100644 --- a/docs/3.tutorials/auction/7-factory.md +++ b/docs/3.tutorials/auction/4-factory.md @@ -1,7 +1,7 @@ --- id: auction-factory title: Auction factory -sidebar_label: Auction factory +sidebar_label: Auction Factory --- import {Github, Language} from "@site/src/components/codetabs" @@ -18,9 +18,9 @@ The factory example only comes in rust since, currently, the JavaScript SDK does In the current example, the factory contract deploys the donation contract example. We will change this to deploy our auction contract instead. -Firstly, we'll need the compiled auction contract WASM file. You can get this by running the following command in [part four](4-ft.md) of `contract-rs` +Firstly, we'll need the compiled auction contract WASM file. You can get this by running the following command in [03-bid-with-fts](https://github.com/near-examples/auctions-tutorial/tree/reorg-auction/contract-rs/03-bid-with-fts) of `contract-rs` -``` +```bash cargo near build ``` @@ -28,17 +28,17 @@ You will find the resulting WASM file in `target/near`; copy this file and use i On initialization, the factory will add the auction contracts WASM, as bytes, to the factory's state. It is more efficient to not store the WASM in the factory's state, however, we may want to update the auction contract if we find a bug or want to add new features. The factory implements a method to update the auction contract - we'll change the name to `update_auction_contract` as this factory will only deploy auction contracts. --- @@ -48,10 +48,10 @@ On initialization, the factory will add the auction contracts WASM, as bytes, to The method to deploy a new contract is specific to the contract being deployed (in the case the contract has custom initialization parameters). We will modify the method to take in the auction contract's initialization parameters. -In this fork, we have also removed the option to add an access key to the contract account since, as discussed in [part 2](2-locking.md), we want auctions to be locked. +In this fork, we have also removed the option to add an access key to the contract account since, as discussed [earlier](./1.3-deploy.md#locking-the-contract), we want auctions to be locked. --- @@ -59,20 +59,20 @@ In this fork, we have also removed the option to add an access key to the contra Build and deploy the factory like you would any other contract, this time without any initialization parameters. -``` +```bash cargo near build ``` then -``` +```bash cargo near deploy without-init-call network-config testnet sign-with-legacy-keychain send ``` You can now use the factory to deploy new auction contracts, here is an example command. -``` -near contract call-function as-transaction auction-factory.testnet deploy_new_auction json-args '{"name": "new-auction", "end_time": "3000000000000000000", "auctioneer": "pivortex.testnet", "ft_contract": "dai.fakes.testnet", "nft_contract": "nft.examples.testnet", "token_id": "7777", "starting_price": "1000000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '1.6 NEAR' +```bash +near call auction-factory.testnet deploy_new_auction '{"name": "new-auction", "end_time": "3000000000000000000", "auctioneer": "pivortex.testnet", "ft_contract": "dai.fakes.testnet", "nft_contract": "nft.examples.testnet", "token_id": "7777", "starting_price": "1000000000000000000"}' --accountId --deposit '1.6 NEAR' ``` :::info Deposit and storage costs diff --git a/docs/3.tutorials/auction/5-frontend.md b/docs/3.tutorials/auction/5-frontend.md deleted file mode 100644 index 2cfbfb351e9..00000000000 --- a/docs/3.tutorials/auction/5-frontend.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -id: creating-a-frontend -title: Creating a frontend -sidebar_label: Creating a frontend ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import {Github, Language} from "@site/src/components/codetabs" - -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](https://near-faucet.io/). - -Navigate to the `frontend` directory then install dependencies and start the frontend. - - - - - - ``` - yarn install - yarn dev - ``` - - - - - - ``` - npm install - npm run 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. - -| File | Description | -|----------------------------------|---------------------------------------------------------------------------------| -| **_app.js** | Responsible for rending the page, initiates the wallet object and adds it to global context | -| **index.js** | The 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.js** | Contains the wallet class that has methods to interact with the wallet and blockchain | -| **context.js** | Holds the global context - the wallet object and the signed in account ID - that can be accessed anywhere | -| **config.js** | Specifies the account ID of the auction contract | -| **Navigation.jsx** | A component that contains a button to sign users in and out of wallets | -| **Bid.jsx** | A component allowing a user to make a bid | -| **LastBid.jsx** | A component that displays the highest bid and when the highest bid will next refresh | -| **AuctionItem.jsx** | A component that displays information about the NFT being auctioned | -| **Timer.jsx** | A 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](4-ft.md). 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](https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/wallets/near.js) 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](../../5.api/rpc/providers.md). 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](./6-indexing.md) of the tutorial, we'll look at querying historical data using an API endpoint. diff --git a/docs/3.tutorials/welcome.md b/docs/3.tutorials/welcome.md index e1a279212a6..d5b0e2d60a3 100644 --- a/docs/3.tutorials/welcome.md +++ b/docs/3.tutorials/welcome.md @@ -29,7 +29,7 @@ Explore our collection of Examples and Tutorials + subtitle="Learn to build a Web3 application from start to finish" image="near-zero-to-hero.png" />