diff --git a/docs/2.build/2.smart-contracts/anatomy/anatomy.md b/docs/2.build/2.smart-contracts/anatomy/anatomy.md index 4c14cfae5bc..03bb4ecf413 100644 --- a/docs/2.build/2.smart-contracts/anatomy/anatomy.md +++ b/docs/2.build/2.smart-contracts/anatomy/anatomy.md @@ -23,8 +23,8 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code - ### Contract's Class / Structure - The contract is described through a `Class` / `Struct` : + ### Contract's Main Structure + The contract is described through a structure: - The attributes define which data the contract stores - The functions define its public (and private) interface @@ -38,8 +38,9 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code 1. What to fetch from storage when the contract is loaded 2. What to store when the contract is done executing 3. The methods that are exposed to the outside world + 4. If the contract needs to be initialized (we will cover this later) - **Note:** Only one class can be decorated with the `@NearBindgen` decorator. + **Note:** Only one class can be decorated with the `@NearBindgen` decorator @@ -86,7 +87,13 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code - + + + Javascript contracts need to further include a `schema` object that defines the contract's state and its types. This object is used by the SDK to correctly serialize and deserialize the contract's state. + + + + ### Read Only Functions Contract's functions can be read-only, meaning they don't modify the state. Calling them is free for everyone, and does not require to have a NEAR account. @@ -95,7 +102,7 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code - + ### State Mutating Functions Functions that modify the state or call other contracts are considered state mutating functions. It is necessary to have a NEAR account to call them, as they require a transaction to be sent to the network. @@ -106,7 +113,7 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code + start="2" end="32" /> + +--- + +## Prerequisites + +Before starting, make sure to set up your development environment! + +
+Working on Windows? + + See our blog post [getting started on NEAR using Windows](/blog/getting-started-on-windows) for a step-by-step guide on how to setup WSL and your environment + +
+ + + + + ```bash + # Install Node.js using nvm (more option in: https://nodejs.org/en/download) + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash + nvm install latest + + # Install the NEAR CLI to deploy and interact with the contract + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh + ``` + + + + + + ```bash + # Install Rust: https://www.rust-lang.org/tools/install + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + + # Contracts will be compiled to wasm, so we need to add the wasm target + rustup target add wasm32-unknown-unknown + + # Install the NEAR CLI to deploy and interact with the contract + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh + + # Install cargo near to help building the contract + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh + ``` + + + + + +We will be using the tool [NEAR CLI](../../4.tools/cli.md) to interact with the blockchain through the terminal, and you can choose between JavaScript or Rust to write the contract. + + +--- + +## Overview + +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. [The Auction Contract](./1-basic.md): We cover a simple auction smart contract +2. [Updating and Locking a Contract](./2-locking.md): Discover what it means to lock a contract +3. Giving an NFT to the Winner (soon) : Give the highest bidder an NFT to signal their win +4. Integrating Fungible Tokens (soon) : Allow people to use fungible tokens to bid (e.g. stable coins) + +#### 2. Frontend + +1. Creating the frontend : Lets learn how to connect a frontend with your smart contract +2. Easily query on-chain data : Use open APIs to keep track of the users and their bidding price + +#### 3. Factory +1. Creating a factory: Allow users to easily deploy and initialize their own auction contracts + + +--- + +## Next steps + +Ready to start? Let's jump to the [The Auction Contract](./1-basic.md) and begin your learning journey! + +:::note Versioning for this article + +- near-cli: `0.12.0` +- near-sdk-js: `2.0.0` +- near-sdk-rs: `5.1.0` +- near-workspaces-js: `3.5.0` +- node: `21.6.1` +- near-workspaces-rs: `0.10.0` +- rustc: `1.78.0` +- cargo-near: `0.6.2` + +::: \ No newline at end of file diff --git a/docs/3.tutorials/auction/1.1-basic.md b/docs/3.tutorials/auction/1.1-basic.md new file mode 100644 index 00000000000..ecc7ac27d69 --- /dev/null +++ b/docs/3.tutorials/auction/1.1-basic.md @@ -0,0 +1,264 @@ +--- +id: basic-auction +title: Basic Auction +--- + +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 the 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 + +::: + +:::tip Prerequisites + +Make sure to read the [Prerequisites](./0-intro.md) section and install the necessary tools before starting this tutorial + +::: + +--- + +## 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`). + +Navigate to the folder of the language you prefer, and then to the `01-basic-auction` folder. + + + + + ```bash + git clone git@github.com:near-examples/auctions-tutorial.git + + cd contract-ts/01-basic-auction + ``` + + + + + ```bash + git clone git@github.com:near-examples/auctions-tutorial.git + + cd contract-rs/01-basic-auction + ``` + + + + + +:::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 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. + + + + + + + #### 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 a `Bid` which stores: + - `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** + + + + + + + #### 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 a `Bid` which stores: + - `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** + + + + + +:::tip Learn More + +You can read more about the contract's structure and 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) + +::: + +--- + +## 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. + + + + + + + + #### 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. + + + + + + + + #### 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. + +:::tip Learn More + +You can read more about the contract's interface in our [contract functions documentation](../../2.build/2.smart-contracts/anatomy/functions.md), and learn about data types on the [data types documentation](../../2.build/2.smart-contracts/anatomy/types.md). + +::: + +--- + +## 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. + + + + + + + + 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. + + + + + + + + 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. + +:::tip Learn More + +You can read more about the contract's interface in our [contract functions documentation](../../2.build/2.smart-contracts/anatomy/functions.md), and learn about data types on the [data types documentation](../../2.build/2.smart-contracts/anatomy/types.md). + +::: + + +--- + +## 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. + + + + + + + + + + + + + + + + + +#### 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 transfer 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. + +
+ +:::tip Learn More + +You can read more about the environment variables, payable functions and which actions the contract can perform here: +- [Environment Variables](../../2.build/2.smart-contracts/anatomy/environment.md) +- [Payable Functions](../../2.build/2.smart-contracts/anatomy/functions.md) +- [Transfers and Actions](../../2.build/2.smart-contracts/anatomy/actions.md) + +::: + +--- + +## 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 the 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 new file mode 100644 index 00000000000..20b03a31c0f --- /dev/null +++ b/docs/3.tutorials/auction/1.2-testing.md @@ -0,0 +1,207 @@ +--- +id: sandbox-testing +title: Sandbox Testing +--- + +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. + +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. + +:::info unit testing + +Unit tests are built-in in the language, and used to test the contract functions individually. These tests work well when little context is required. However, they cannot test chain interactions - like sending accounts $NEAR tokens - since they need to be processed by the network. + +::: + +--- + +## 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. + + + + + + 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. + + + + + + Notice that the sandbox compiles the code itself, so we do not need to pre-compile the contract before running the tests. + + + +--- + +## Contract Initialization + +To initialize the contract the contract's account calls itself, invoking the `init` function with an `end_time` set to 60 seconds in the future. + + + + + + + +:::warning Time Units + +The contract measures time in **nanoseconds**, for which we need to multiply the result of `Date.now()` (expressed in milliseconds) by `10^6` + +::: + + + + + + + +:::warning Time Units + +The contract measures time in **nanoseconds**, for which we need to multiply the result of `Utc::now().timestamp()` (expressed in seconds) by `10^9` + +::: + + + + +:::info Time is a String + +Notice that the time is passed as a `String` to the contract, this is because smart contracts cannot receive numbers larger than `52 bits` and we want to pass a `unix timestamp` in **nanoseconds** + +::: + +--- + +## Bidding + +Now that the contract is deployed and initialized, we can start biding 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. + + + + + + + + + + + + + + + + + +#### Checking the balance +It is important to notice how we check if `alice` was refunded. We query her balance after her first bid, and then check if it has increased by 1 NEAR after `bob` makes his bid. + +You might be tempted to check if `alice`'s balance is exactly 10 NEAR after she gets refunded, but `alice` balance cannot be 10 NEAR anymore, because some $NEAR was **consumed as `gas` fees** when `alice` called `bid`. + +#### Testing invalid calls + +When testing we should also check that the contract does not allow invalid calls. The next part checks that the contract doesn't allow for bids with fewer $NEAR tokens than the previous to be made. + + + + + + + + + + + + + + + + + +--- + +## 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. + +Any bid made after the auction ends should be rejected. + + + + + + + + + + + + + + +--- + +## Executing the tests + +Now that we understand what we are testing, let's go ahead and run the tests! + + + + + + + ```bash + # if you haven't already, install the dependencies + npm install + + # run the tests + npm run test + ``` + + + + + + ```bash + cargo test + ``` + + + + + +All tests should pass, and you should see the output of the tests in the console. If you see any errors, please contact us in the [NEAR Discord](https://near.chat) or through [Telegram](https://t.me/neardev) and we'll help you out! + +--- + +## 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. + +You are now ready to move to the next section, in which we will deploy the contract to the 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 new file mode 100644 index 00000000000..685207fa356 --- /dev/null +++ b/docs/3.tutorials/auction/1.3-deploy.md @@ -0,0 +1,125 @@ +--- +id: deploy +title: Deploying to Testnet +--- + +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. + +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 + +::: + +--- + +## Testnet Account + +To deploy a contract, you need a testnet account. If you don't have one, you can create one using the following command: + +```bash +# create-account using near-cli (contractId has end with .testnet) +near create --useFaucet +``` + +Replace `` with the name you want to give to your account, and make sure it ends with `.testnet`. + +The account will be created with **10 NEAR** (this are test tokens). + +:::info Testnet Faucet + +Notice that we are using the `--useFaucet` flag to automatically request test tokens from the NEAR faucet. + +The faucet is only available on the testnet network - which is the default network for the CLI + +::: + +--- + +## Deploying the Contract + +To deploy the contract, you need to compile the contract code into WebAssembly (WASM) and then deploy it to the network + + + + + + ```bash + # compile the contract + npm run build + + # deploy the contract + near deploy ./build/auction.wasm + + # initialize the contract, it finishes in 2 minutes + MINUTE_FROM_NOW=$(date -v+2M +%s000000000) + near call init '{"end_time": "'$MINUTE_FROM_NOW'"}' --accountId + ``` + + + + + + ```bash + # compile the contract using cargo-near + cargo near build + + # deploy the contract + near deploy ./target/near/contract_rs.wasm + + # initialize the contract, it finishes in 2 minutes + MINUTE_FROM_NOW=$(date -v+2M +%s000000000) + near call init '{"end_time": "'$MINUTE_FROM_NOW'"}' --accountId + ``` + + + + + + +Now that the contract is deployed and initialized 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 +::: + +--- + +## 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. + +```bash +# call the contract to bid +near call bid --accountId --amount 1 + +# get the highest bid +near view get_highest_bid +``` + +Notice that we call the `bid` function without arguments, but attach 1 NEAR to the transaction. This is the amount we are bidding. + +For the `get_highest_bid` function, we don't need to specify which user is calling it, as it is a view function and does not require gas to be executed. + +--- + +## Conclusion + +We have now seen how to deploy a contract to the testnet and interact with it using the NEAR CLI. + +A word of advice before moving forward. When people learn how to use the CLI, they get lazy and start testing new contract features directly on the testnet. While this is tempting, it is not recommended. + +Do not use testnet as your **only way** to test contracts. Always test your contracts on the **sandbox environment first**, and only deploy to the testnet when you are confident that everything is working as expected. + +:::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 + +::: \ No newline at end of file diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md new file mode 100644 index 00000000000..96668b19ea6 --- /dev/null +++ b/docs/3.tutorials/auction/2-locking.md @@ -0,0 +1,121 @@ +--- +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 also 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 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, we 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/3-nft.md b/docs/3.tutorials/auction/3-nft.md new file mode 100644 index 00000000000..1794842b888 --- /dev/null +++ b/docs/3.tutorials/auction/3-nft.md @@ -0,0 +1,185 @@ +--- +id: winning-an-nft +title: Winning an NFT +sidebar_label: Winning an NFT +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {Github, Language} from "@site/src/components/codetabs" + +No one will enter an auction if there's nothing to win, so let's add a prize. Why not an [NFT](../../2.build/5.primitives/nft.md)? 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: + + + + + + + + + + + + + + + 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 auction is ended - by calling the method `claim` - 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`. + + + + + + + + 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. + + + + + + + + + + +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](../../2.build/2.smart-contracts/security/one_yocto.md). + +--- + +## 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. + +--- + +## 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](https://github.com/near-examples/NFT/tree/master). + +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. + + + + + + + + + + + + + + + + + +--- + +## 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. + + + + + + + + + + + + + + + + + +--- + +## 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. + + + + + + + + + + + + + + + + + +--- + +## 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 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 +``` + +You can also just buy an NFT with testnet $NEAR on a testnet marketplace like [Mintbase](https://testnet.mintbase.xyz/explore/new/0). + +--- + +## 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 diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md new file mode 100644 index 00000000000..ed13cd4cbdf --- /dev/null +++ b/docs/3.tutorials/auction/4-ft.md @@ -0,0 +1,427 @@ +--- +id: bidding-with-fts +title: Bidding with FTs +sidebar_label: Bidding with FTs +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {Github, Language} from "@site/src/components/codetabs" + +To further develop this contract we will introduce another primitive: [fungible tokens](../../2.build/5.primitives/ft.md). Instead of placing bids in $NEAR tokens, they will be placed in FTs. This may be useful if, for example, an auctioneer wants to keep the bid amounts constant in terms of dollars as an auction is carried out, so bids can be placed in stablecoins such as $USDC. Another use case is if a project like Ref Finance was holding its own auction and wanted the auction to happen in its project's token $REF. + +--- + +## Specifying the FT contract + +We want to only accept bids in one type of fungible token; accepting many different FTs would make the value of each bid difficult to compare. We're also going to adjust the contract so that the auctioneer can specify a starting bid amount for the auction. + + + + + + + + + + + + + + + + + +--- + +## Accepting bids in FTs + +When we were making bids in $NEAR tokens we would call the auction contract directly and attach $NEAR tokens to the call. With fungible tokens, since an account's balance lives on a separate contract, we call the FT contract which then calls the auction contract and transfers tokens. The method on the FT contract to do this is named `ft_transfer_call` and it will always call a method in the target contract named `ft_on_transfer`. Take a look [here](../../2.build/5.primitives/ft.md#attaching-fts-to-a-call) for more information. + +![ft_transfer_call-flow](/docs/assets/auction/auction-ft-transfer.png) + +The `ft_on_transfer` method always has the same interface; the FT contract will pass it the `sender`, the `amount` of FTs being sent and a `msg` which can be empty (which it will be here) or it can contain some information needed by the method (if you want to send multiple arguments in msg it is best practice to deliver this in JSON then parse it in the contract). The method returns the number of tokens to refund the user, in our case we will use all the tokens attached to the call for the bid unless the contract panics in which case the user will automatically be refunded their FTs in full. + + + + + + + + + + + + + + + + + + +We need to confirm that the user is attaching fungible tokens when calling the method and that they are using the right FT, this is done by checking the predecessor's account ID. Since it's the FT contract that directly calls the auction contract, the `predecessor` is now the account ID of the FT contract. + + + + + + + + + + + + + + + + + +The bidder's account ID is now given by the argument `sender_id`. + + + + + + + + + + + + + + + + + +When we want to return the funds to the previous bidder we now make a cross-contract call to the FT contract. + + + + + + + + + + + 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. + + + + + + + + + + + We then return 0 because the method uses all the FTs in the call. + + + + + + + +
+ + What happens if the cross-contract call fails? + + The first time this method is called the contract will try to send itself FTs. Most fungible token contracts don't allow one to send themselves FTs so the cross-contract call will fail. However, since cross-contract calls are asynchronous and independent and we are not checking the result of the call then the auction contract does not care that the call failed and ft_on_transfer will complete successfully. + + In the other cases, the call to the fungible token contract could only fail if the receiver does not exist, the FT contract does not exist, the auction contract doesn't have enough fungible tokens to cover the amount being sent, or the receiver is not registered in the FT contract. Our contract is set up such that these errors cannot occur, the receiver must exist since they placed the previous bid, the FT contract exists since it was used to place the bid, the auction contract has enough FTs to cover the amount since it was sent that amount by the previous bid, and the receiver must be registered in the FT contract since they needed to have held the token in the first place to make a bid. + +
+ +--- + +## Claiming the FTs + +When the auction is complete we need to send the fungible tokens to the auctioneer when we send the NFT to the highest bidder, we implement a similar call as when we were returning the funds just changing the arguments. + + + + + + + + In JavaScript, since we need to return each cross-contract call we chain the NFT and FT transfer. + + + + + + + + + + + +--- + +## Creating a new FT + +Just as with the NFT contract, we will deploy an FT contract in the sandbox tests using a WASM file compiled from [this repo](https://github.com/near-examples/FT). + +When the contract is deployed it is initialized with `new_default_meta` which sets the token's metadata, including things like its name and symbol, to default values while requiring the owner (where the token supply will sent), and the total supply of the token. + + + + + + + + + + + + + + + + + +--- + +## Registering users in the FT contract + +For one to receive fungible tokens, first their account ID must be [registered](../../2.build/5.primitives/ft.md#registering-a-user) in the FT contract. A user has to register in an FT contract to pay for the storage used to track their amount of tokens. By default, a contract pays for its own storage, but not requiring a user to register and pay for storage would drain the contract of $NEAR tokens. When the contract is live we don't need to register the accounts that we transfer tokens back to since to make a bid in the first place they would have needed to be registered, but we do need to register the auction contract in the FT contract to receive bids and the auctioneer to receive the funds at the end of the auction. It is most convenient to register users from the frontend rather than the contract. + +In our tests, since we are creating a new fungible token and new accounts we will actually have to register every account that will interact with FTs. + + + + + + + + + + + + + + + + + +--- + +## Simple FT transfer to bidders + +Then we will transfer the bidders FTs so they can use them to bid. A simple transfer of FTs is done using the method `ft_transfer` on the FT contract. + + + + + + + + + + + + + + + + + + + + +--- + +## FT transfer call + +As stated previously, to bid on the auction the bidder now calls `ft_transfer_call` on the FT contract which subsequently calls the auction contract's `ft_on_transfer` method with fungible tokens attached. + + + + + + + + + + + + + + + + + + + + +--- + +## Checking users' FT balance + +Previously, to check a user's $NEAR balance, we pulled the details from their account. Now we are using FTs we query the balance on the FT contract using `ft_balance_of`, let's check that the contract's balance increased by the bid amount and the user's balance decreased by the bid amount. + + + + + + + + + + + + + + + + + + + + +--- + +## Invalid FT transfer call + +If we make a lower bid than the previous this will cause the auction contract to panic. One might expect that `ft_transfer_call` will fail, but it does not. `ft_on_transfer` will fail and the FT contract will recognize this and reverse the transfer of tokens. So after making an invalid bid, we should check that the call was successful but the parties involved in the transaction (the bidder and the contract) have the same balance of fungible tokens as they did before the call. + +Previous to this, Bob made a bid of 60,000 and Alice was returned her bid bringing her balance back up to 150,000. Now when Alice makes an invalid of 50,000 Alice's balance should remain at 150,000 and the contract should remain at a balance of 60,000. + + + + + + + + + + + + + + + + + +--- + +## Using FTs with the CLI + +If you want to interact with the auction contract you're going to need FTs. For this example, we'll use $DAI where the contract address is `dai.fakes.testnet`. One can easily acquire FTs through the [testnet faucet](https://near-faucet.io/). Select DAI and withdraw to the account you will use to place a bid. If you take a look at the transaction details you can see that the faucet registers your account in the FT contract and then sends you DAI from the faucet account. + +When deploying the contract make sure to specify the FT contract `dai.fakes.testnet`. + +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' +``` + +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' +``` + +## Auction architecture + +When creating an application there are numerous ways to structure it. Here, we have one contract per auction meaning we have to deploy a new contract each time we want to host an auction. To make this easier we will leverage a factory contract to deploy auction contracts for an auctioneer. Deploying code for each auction gets expensive, with 100kb of storage costing 1 $NEAR, since each auction stores all the same type of information and implements the same methods one could instead decide to have multiple auctions per contract. + +In such case, the Contract struct would be a map of auctions. We would implement a method to create a new auction by adding an entry to the map with the specific details of that individual auction. + + + + + + ```javascript + class Contract { + auctions: UnorderedMap + ``` + + + + + + ```rust + pub struct Contract { + auctions: IterableMap + ``` + + + + + +However, this architecture could be deemed less secure since if a bad actor were to gain access to the contract they would have access to every auction instead of just one. + +--- + +## Conclusion + +In this section, we 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. + +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 diff --git a/docs/3.tutorials/auction/5-frontend.md b/docs/3.tutorials/auction/5-frontend.md new file mode 100644 index 00000000000..274c4f3e3ff --- /dev/null +++ b/docs/3.tutorials/auction/5-frontend.md @@ -0,0 +1,220 @@ +--- +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. 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 is used to allow users to use 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 we 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. + +Whilst 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 quering historical data using an API endpoint. diff --git a/docs/3.tutorials/auction/6-indexing.md b/docs/3.tutorials/auction/6-indexing.md new file mode 100644 index 00000000000..7863fb3ff56 --- /dev/null +++ b/docs/3.tutorials/auction/6-indexing.md @@ -0,0 +1,85 @@ +--- +id: indexing-historical-data +title: Indexing historical data +sidebar_label: Indexing historical data +--- + +import {Github, Language} from "@site/src/components/codetabs" + +TODO: change github to main when merged + +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. + +--- + +## NEAR Blocks API key + +NEAR Blocks provides a free tier that allows you to make 6 calls per minute, this will be plenty for our usecase. To get an API key head over to https://dash.nearblocks.io/user/overview and sign up. Once signed go to `API Keys` then click `Add key` and give it whatever name you like. + +We'll create a new file named `.env.local` to store our API key. Swap the API key in the example with your own. + + + + + +We put the API key in a `.env.local` file so the user cannot access it in the browser and use our key elsewhere. We should also add `.env.local` to our `.gitignore` file so it is not pushed to GitHub. However, for the purposes of this tutorial, it has been omitted. + +--- + +## Calling the API endpoint + +NextJS allows us to easily create server-side functions with API routes. We need to make this API call on the server-side rather than the client side so as to not expose our API key. We'll create a new file in src/pages/api named `getBidHistory.js`. Here we'll define our function to get the bid history. + + + + + +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: + +- 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 our API key to authenticate the request. + +--- + +## Filtering out invalid transactions + +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. + + + + + +--- + +## 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. + + + + + +The `pastBids` will then be passed into the `Bid` component to be displayed. + +--- + +You may like to explore NEAR Blocks APIs further to see what other data you can retrieve from the blockchain. You can find the documentation at https://api.nearblocks.io/api-docs/ + +--- + +## 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 we'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. \ No newline at end of file diff --git a/docs/3.tutorials/auction/7-factory.md b/docs/3.tutorials/auction/7-factory.md new file mode 100644 index 00000000000..8df903d04a1 --- /dev/null +++ b/docs/3.tutorials/auction/7-factory.md @@ -0,0 +1,106 @@ +--- +id: auction-factory +title: Auction factory +sidebar_label: Auction factory +--- + +import {Github, Language} from "@site/src/components/codetabs" + +TODO change code once branch is merged + +Since an auction contract hosts a single auction, each time you would like to host a new auction you will need to deploy a new contract. Rather than finding the compiled WASM file, creating a new account, deploying the contract, and then initializing it each time, you can use a factory contract to do this for you. + +Luckily for us, there is already a [factory contract example](https://github.com/near-examples/factory-rust)! We will fork this example and slightly modify it to suit our use case. If you would like to learn more about how the factory contract works, you can take a look at the [associated documentation](https://docs.near.org/tutorials/examples/factory#generic-factory). + +The factory example only comes in rust since, currently, the JavaScript SDK does not allow you to embed the WASM file in the contract. This is a limitation of the SDK and not the blockchain itself. + +--- + +## Changing the default contract + +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 of `contract-rs` + +``` +cargo near build +``` + +You will find the resulting WASM file in `target/near`; copy this file and use it to replace the WASM of the donation contract in the factory contract's source folder. Now edit the auction contract changing the path to the auction contract. + + + + + + +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. + + + +--- + +## Modifying deploy method + +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, we want auctions to be locked. + +--- + +## Using the factory + +Build and deploy the factory like you would any other contract, this time without any initialization parameters. + +``` +cargo near build +``` + +then + +``` +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' +``` + +Note that we attach 1.6 $NEAR to the call to cover the storage costs of deploying the new auction. Storage cost on NEAR is 1 $NEAR per 100 kb and our auction contract is around 140 kb, but we'll add a little to cover storage used on initialization. + +
+ + Deposit and storage costs + + We attach 1.6 $NEAR to the call to cover the storage costs of deploying the new auction. Storage cost on NEAR is 1 $NEAR per 100 kb and our auction contract is around 140 kb, but we'll add a little to cover storage used on initialization. + +
+ +The command results in a fresh auction contract being deployed and initialized at `new-auction.auction-factory.testnet`. + +--- + +## Conclusion + +In this part of the tutorial, we have learned how to fork and modify the factory contract example to deploy our auction contracts. We have also learned how to use the factory to deploy new auction contracts. If you're feeling adventurous you could create a frontend to interact with the factory contract to make it even easier to deploy new auctions. If you do so feel free to share it in our developer [Telegram](https://t.me/neardev) or [Discord](https://discord.gg/vMGH5QywTH) channels! + +And with that, this tutorial series is over, congratulations! Through this tutorial, we've built an auction contract and iterated on it adding improvements and extending its functionality, created a frontend to interact with the auction, used an API to index previous bids, and deployed a factory contract to make deploying new auctions easier. Along the way we've learned a great deal about NEAR, we learned about the anatomy of smart contracts, how to lock a contract to make it more secure, how to use primitives such as NFTs and FTs, how to perform cross-contract calls, how to use wallets from a frontend to interact with the blockchain and display data about a smart contract, how to pull historical data from the blockchain using an API, how to deploy contracts from other contracts and a lot of other little bits that will help you in the future. + +That's a lot, so once again congratulations! + +--- + +## What's next? + +TODO add some sort of what they can do from here, particpate in hackathon, build own project, they may be a dev working for someone else, component or page for this...? \ No newline at end of file diff --git a/docs/3.tutorials/welcome.md b/docs/3.tutorials/welcome.md index b96e10b7942..228b27eb65f 100644 --- a/docs/3.tutorials/welcome.md +++ b/docs/3.tutorials/welcome.md @@ -28,6 +28,8 @@ Explore our collection of Examples and Tutorials subtitle="Use our Data Lake to listen for events" image="monitor.png" /> + > `Storage` >> `Local Storage` diff --git a/website/sidebars.js b/website/sidebars.js index 2c72e8e241e..2ac11d48c21 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -382,6 +382,21 @@ const sidebar = { "type": "html", "value": " Tutorials " }, + { + "NEAR 101: Building Web3 Apps": [ + "tutorials/auction/introduction", + { + "Smart Contracts 101": [ + "tutorials/auction/basic-auction", + "tutorials/auction/sandbox-testing", + "tutorials/auction/deploy", + ] + }, + // "tutorials/auction/locking-the-contract", + // "tutorials/auction/winning-an-nft", + // "tutorials/auction/bidding-with-FTs", + ] + }, { "Components": [ "tutorials/near-components/bos-loader", diff --git a/website/src/components/codetabs.js b/website/src/components/codetabs.js index 000afad5bfa..bbebbbc3905 100644 --- a/website/src/components/codetabs.js +++ b/website/src/components/codetabs.js @@ -27,14 +27,14 @@ export function CodeTabs({ children }) { ); } -export function Language({ children, language }) { +export function Language({ children, language, showSingleFName }) { if (!Array.isArray(children)) { children = [children]; } children = children.map( component => change_language_to(component, language)); - if (children.length == 1) { + if (children.length == 1 && !showSingleFName) { return ( {children[0]} diff --git a/website/src/theme/Footer/index.js b/website/src/theme/Footer/index.js index 6fe21cd244a..592493d6d7e 100644 --- a/website/src/theme/Footer/index.js +++ b/website/src/theme/Footer/index.js @@ -399,7 +399,7 @@ function Footer() { className="footer-menu list-reset mt-5 text-16 md:text-16" >
  • - + DevHub
  • diff --git a/website/static/docs/assets/auction/auction-ft-transfer.png b/website/static/docs/assets/auction/auction-ft-transfer.png new file mode 100644 index 00000000000..9caeb504378 Binary files /dev/null and b/website/static/docs/assets/auction/auction-ft-transfer.png differ diff --git a/website/static/docs/assets/welcome-pages/near-zero-to-hero.png b/website/static/docs/assets/welcome-pages/near-zero-to-hero.png new file mode 100644 index 00000000000..0c4aa88a32c Binary files /dev/null and b/website/static/docs/assets/welcome-pages/near-zero-to-hero.png differ