From 1a773f3cddad9105994fc892f9f33c82322a651f Mon Sep 17 00:00:00 2001 From: PiVortex Date: Tue, 16 Jul 2024 15:04:29 +0100 Subject: [PATCH 01/32] add auction series --- docs/3.tutorials/auction/0-intro.md | 41 ++++++ docs/3.tutorials/auction/1-bidding.md | 177 ++++++++++++++++++++++++++ docs/3.tutorials/auction/2-claim.md | 49 +++++++ docs/3.tutorials/auction/3-nft.md | 80 ++++++++++++ docs/3.tutorials/auction/4-ft.md | 11 ++ 5 files changed, 358 insertions(+) create mode 100644 docs/3.tutorials/auction/0-intro.md create mode 100644 docs/3.tutorials/auction/1-bidding.md create mode 100644 docs/3.tutorials/auction/2-claim.md create mode 100644 docs/3.tutorials/auction/3-nft.md create mode 100644 docs/3.tutorials/auction/4-ft.md diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md new file mode 100644 index 00000000000..447a7b49c7e --- /dev/null +++ b/docs/3.tutorials/auction/0-intro.md @@ -0,0 +1,41 @@ +--- +id: introduction +title: Auction Zero to Hero +sidebar_label: Introduction +--- + +In this Zero to Hero series, you'll learn to build an auction smart contract that allows an auctioneer to list an NFT and have people bid with FTs This series will slowly build up in complexity, teaching you how to use several key concepts in a NEAR smart contract: +- NFTs +- FTs +- Locking contract +- Timing with blocks +- cross contract calls + +This tutorial comes with contracts written in both JavaScript and Rust. + +--- + +## Prerequisites + +To complete this series you'll need: + +- [Rust](https://www.rust-lang.org/tools/install) +- [A Testnet wallet](https://testnet.mynearwallet.com/create) +- [NEAR-CLI-RS](../../4.tools/cli-rs.md#setup) +- [cargo-near](https://github.com/near/cargo-near) + +--- + + +| Step | Name | Description | +|------|-----------------------------------|-------------| +| 1 | [Bid]() | Fill in | +| 2 | [Claim]() | Fill in | +| 3 | [Owner Claims & Winner Gets NFT]() | Fill in | +| 4 | [Integrating Fungible Tokens]() | Fill in | + + +Finish table +Update preqrequesists +Update what learnt +Add more appropriate titles \ No newline at end of file diff --git a/docs/3.tutorials/auction/1-bidding.md b/docs/3.tutorials/auction/1-bidding.md new file mode 100644 index 00000000000..293c87a036a --- /dev/null +++ b/docs/3.tutorials/auction/1-bidding.md @@ -0,0 +1,177 @@ +--- +id: bidding +title: Bidding +sidebar_label: Bidding +--- + +In this section of the tutorial, you'll learn how to create a basic NEAR smart contract from scratch. The contract will allow users to place bids and track the highest bidder. + +  + +## Creating a new project + +To get started you'll need to create a new NEAR project. + +In your command line run: + +``` +$ cargo near new auction-contract +``` + +Enter your new project + +``` +$ cd auction-contract +``` + +Then go ahead and open up a code editor such as VS Code + +``` +$ code . +``` + +Enter src > lib.rs, there already exists an example contract there so we'll just go ahead and delete everything and start from the start! + +  + +## Defining the contract structure + +Smart contracts are simply structures that store data and implement functions to mutate and view that data. First, we'll define what data we are storing. We'll also need some imports along the way. + +```rust +use near_sdk::json_types::U64; +use near_sdk::{env, near, require, AccountId, NearToken, PanicOnDefault, Promise}; + +#[near(serializers = [json, borsh])] +#[derive(Clone)] +pub struct Bid { + pub bidder: AccountId, + pub bid: NearToken, +} + +#[near(contract_state, serializers = [json, borsh])] +#[derive(PanicOnDefault)] +pub struct Contract { + highest_bid: Bid, + auction_end_time: U64, +} +``` + +Here we define the Contract structure that has fields: +- **highest_bid**: another structure named Bid that stores the information about the highest bid. +- **auction_end_time**: specifies the block_timestamp (which will be explained later) at which the auction will end. + +Contract also has attributes: +- **contract_state**: enables borsh serialization and decentralization to read and write the structure to the blockchain in binary form and adds NearSchmema **IDK what this does?** +- **serializers**: enables both borsh and JSON serialization and decentralization. The JSON serializer allows for the whole contract state to be outputted in JSON form. Although borsh is already enabled by contract_state, we have to specify it again in serializers if we want to add JSON. +- **PanicOnDefault**: forces the contract to have custom initialization (we will see this later). + +  + +Since highest_bid stores type Bid we have a nested structure. Bid itself has fields: +- **bidder**: specifies the unique human readable NEAR account ID of the bidder. +- **bid**: specifies the bid amount in YoctoNEAR (10^-24 NEAR). + +Bid has attributes: +- **serializers**: enables both borsh and JSON serialization and decentralization. +- **Clone**: allows a Bid object to be duplicated. + +  + +## Initializing the contract + +Now we have defined the data structures for the contract we next define its functions. + +First, we set up an initialization function that will determine the initial state of the contract. As stated earlier this contract only offers custom initialization where the user is required to input parameters. + +```rust +#[near] +impl Contract { + #[init] + #[private] // Only callable by the contract's account + pub fn init(end_time: U64) -> Self { + Self { + highest_bid: Bid { + bidder: env::current_account_id(), + bid: NearToken::from_yoctonear(1), + }, + auction_end_time: end_time, + } + } +``` + +We decorate the implementation of Contract with `near` to **IDK** . The `init` function has attributes `init` to denote it is an initialization function and `private` to restrict the function to only be called by the account on which the contract is deployed. The function takes the parameter `end_time` which specifies the block_timestamp of when the auction will end. + +When this function is called the `auction_end_time` will be set and `highest_bid` will be set with `bidder` as the account on which the the contract is deployed and `bid` as 1 YoctoNEAR (10^-24 NEAR) so that if no one bids no one will win the auction. + +  + +## Placing a bid + +An auction isn't an auction if you can't place a bid! We'll now add a method that allows a user to place a bid by attaching NEAR tokens to the method call. The method needs to implement logic, it should check whether the auction is still ongoing, check whether their bid is higher than the previous and if it meets both criteria it should update the `highest_bid` field accordingly and return the funds back to the previous bidder. + +```rust +#[payable] +pub fn bid(&mut self) -> Promise { + // Assert the auction is still ongoing + require!( + env::block_timestamp() < self.auction_end_time.into(), + "Auction has ended" + ); + + // Current bid + let bid = env::attached_deposit(); + let bidder = env::predecessor_account_id(); + + // Last bid + let Bid { + bidder: last_bidder, + bid: last_bid, + } = self.highest_bid.clone(); + + // Check if the deposit is higher than the current bid + require!(bid > last_bid, "You must place a higher bid"); + + // Update the highest bid + self.highest_bid = Bid { bidder, bid }; + + // Transfer tokens back to the last bidder + Promise::new(last_bidder).transfer(last_bid) +} +``` + +When the user calls the `bid` method they will transfer NEAR tokens to the contract; to enable the transfer of NEAR tokens we need to decorate the method with the `payable` attribute. This method is what we call a "change method", also known as a "call method"; we know this since it passes a mutable reference to itself ($mut self). Change methods require the user to pay gas and can change the state of the contract. + +- First, the method checks that the auction is still ongoing by checking that the current `block_timestamp` is less than the auction end time. `block_timestamp` gives the time as the number of nanoseconds since January 1, 1970, 0:00:00 UTC. Here we use `require` as opposed to `assert` as it reduces the contract size by not including file and rust-specific data in the panic message. + +- Next, the method sets the variables `bid` to the attached NEAR deposit in the method call (type NearToken) and `bidder` to the predecessor's account ID (type AccountId). Note that the predecessor is the account (or contract) that directly calls the bid method, this can be different from the signer (env::signer_account_id) who initially signed the transaction leading to the execution of this method. If pivortex.near calls a contract proxy-contract.near which then calls bid on this contract the predecessor would be proxy-contract.near and the signer would be pivortex.near. For security purposes, we stick with predecessor_account_id here. + +- Then, the method deconstructs the `highest_bid` into two distinct variables and checks that the last bid was higher than the current bid. If so then the `highest_bid` will be updated to the current bid. + +- The contract then returns a `Promise` that will transfer NEAR of the amount `last_bid` to the `last_bidder`. + +  + +## Viewing the contract state + +We also implement "view methods" into this contract; this allows the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. We know it is a view method since it takes an immutable reference to self (&self). An example use case here is being able to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bet. + +```rust +pub fn get_highest_bid(&self) -> Bid { + self.highest_bid.clone() +} +``` + +In the repo there are further view methods that follow a very similar structure. + +Congratulations, you just created a basic NEAR smart contract! + + + + +Add tests here or all at the end, delete tests folder when creating exmaple? +Add cargo near build, cargo test, near-cli-rs here? + + + + diff --git a/docs/3.tutorials/auction/2-claim.md b/docs/3.tutorials/auction/2-claim.md new file mode 100644 index 00000000000..9e679821cf1 --- /dev/null +++ b/docs/3.tutorials/auction/2-claim.md @@ -0,0 +1,49 @@ +--- +id: claim +title: Claim +sidebar_label: Claim +--- + +Up to now to claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security flaw. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus we will [lock]() the contract by removing all access keys and implementing a new method to claim the funds. + +## Adding an auctioneer + +We want to restrict this method to claim the funds to only be called by the individual or entity that sets up the auction. To do this we now initialize the contract with an `auctioneer` (type AccountId). + +```rust + #[init] + #[private] // only callable by the contract's account + pub fn init(end_time: U64, auctioneer: AccountId) -> Self { + Self { + highest_bid: Bid { + bidder: env::current_account_id(), + bid: NearToken::from_yoctonear(1), + }, + auction_end_time: end_time, + claimed: false, + auctioneer, + } + } +``` + +Let's also introduce a boolean `claimed` field to track whether the funds 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 funds to the auctioneer. We implement this as so: + +```rust + pub fn claim(&mut self) -> Promise { + require!( + env::block_timestamp() > self.auction_end_time.into(), + "Auction has not ended yet" + ); + + require!(!self.claimed, "Auction has already been claimed"); + self.claimed = true; + + // Transfer tokens to the auctioneer + Promise::new(self.auctioneer.clone()).transfer(self.highest_bid.bid) + } +``` + diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md new file mode 100644 index 00000000000..777340f99cf --- /dev/null +++ b/docs/3.tutorials/auction/3-nft.md @@ -0,0 +1,80 @@ +--- +id: nft +title: NFT +sidebar_label: NFT +--- + +No one will enter an auction if there's nothing to win, so let's add a prize. Why not an [NFT]()? NFTs are uniquely identifiable, easily swappable and their logic comes from an external contract with audited standards so the prize will exist without the auction contract. Let's get to work! + +## Defining the NFT interface + +Since NFTs follow standards all NFT contracts implement the same interface that defines what methods can be called, what parameters these methods take and what the method will return. When we are making calls to other contracts we write the interface of each method in a separate file `ext.rs` as a `trait`. + +```rust +use near_sdk::{ext_contract, AccountId}; + +use crate::TokenId; + +// NFT interface for cross-contract calls +#[ext_contract(nft_contract)] +trait NFT { + fn nft_transfer(&self, receiver_id: AccountId, token_id: TokenId) -> String; +} +``` + +`nft_transfer` transfers the ownership of an NFT from one account ID to another. The method takes the arguments `receiver_id` dictating who will now own the NFT and the `token_id` specifying which token in the NFT contract is having its ownership transferred. **It returns a string, i dont think so?** +The `ext_contract` attribute converts the NFT trait into a module with the method `nft_transfer`. + +Note that we declare `TokenId` in our main file `lib.rs`. A type alias is used for future-proofing. + +```rust +pub type TokenId = String; +``` + +We import our interface into `lib.rs` as such: + +```rust +pub mod ext; +pub use crate::ext::*; +``` + +## 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` as such: + +```rust + #[init] + #[private] // only callable by the contract's account + pub fn init( + end_time: U64, + auctioneer: AccountId, + nft_contract: AccountId, + token_id: TokenId, + ) -> Self { + Self { + highest_bid: Bid { + bidder: env::current_account_id(), + bid: NearToken::from_yoctonear(1), + }, + auction_end_time: end_time, + auctioneer, + claimed: false, + nft_contract, + token_id, + } + } +``` + +## 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. We make a [cross-contract]() call to the NFT contract via the external method we defined at the end of `claim`. + +```rust + nft_contract::ext(self.nft_contract.clone()) + .with_static_gas(Gas::from_tgas(30)) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .nft_transfer(self.highest_bid.bidder.clone(), self.token_id.clone()); +``` + +Write about gas + deposit and maybe the method args. + diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md new file mode 100644 index 00000000000..a08beba231c --- /dev/null +++ b/docs/3.tutorials/auction/4-ft.md @@ -0,0 +1,11 @@ +--- +id: ft +title: FT +sidebar_label: FT +--- + + + + + +Write about factory contract to deploy auctions, talk about how you could have multiple auctions in one smart contract cheaper on storage but could be more vulnerable (if get access to one contract get access to all) \ No newline at end of file From e5e4afb3cdc419b8f4dfc51eb7be7035679ebd7c Mon Sep 17 00:00:00 2001 From: PiVortex Date: Tue, 23 Jul 2024 15:19:17 +0100 Subject: [PATCH 02/32] add more content --- docs/3.tutorials/auction/1-bidding.md | 8 +-- docs/3.tutorials/auction/2-claim.md | 6 +- docs/3.tutorials/auction/3-nft.md | 18 ++++-- docs/3.tutorials/auction/4-ft.md | 87 +++++++++++++++++++++++++++ docs/3.tutorials/auction/5-testing.md | 4 ++ 5 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 docs/3.tutorials/auction/5-testing.md diff --git a/docs/3.tutorials/auction/1-bidding.md b/docs/3.tutorials/auction/1-bidding.md index 293c87a036a..eba1f0018f8 100644 --- a/docs/3.tutorials/auction/1-bidding.md +++ b/docs/3.tutorials/auction/1-bidding.md @@ -4,7 +4,7 @@ title: Bidding sidebar_label: Bidding --- -In this section of the tutorial, you'll learn how to create a basic NEAR smart contract from scratch. The contract will allow users to place bids and track the highest bidder. +In this section of the tutorial, you'll learn how to create a basic auction smart contract from scratch. The contract will allow users to place bids and track the highest bidder.   @@ -58,7 +58,7 @@ pub struct Contract { ``` Here we define the Contract structure that has fields: -- **highest_bid**: another structure named Bid that stores the information about the highest bid. +- **highest_bid**: stores the information of the highest bidder in an instance of another struct named Bid. - **auction_end_time**: specifies the block_timestamp (which will be explained later) at which the auction will end. Contract also has attributes: @@ -70,7 +70,7 @@ Contract also has attributes: Since highest_bid stores type Bid we have a nested structure. Bid itself has fields: - **bidder**: specifies the unique human readable NEAR account ID of the bidder. -- **bid**: specifies the bid amount in YoctoNEAR (10^-24 NEAR). +- **bid**: specifies the bid amount in YoctoNEAR (10^-24 NEAR). Bid has attributes: - **serializers**: enables both borsh and JSON serialization and decentralization. @@ -80,7 +80,7 @@ Bid has attributes: ## Initializing the contract -Now we have defined the data structures for the contract we next define its functions. +Now we have defined the data structures for the contract we next define its methods. First, we set up an initialization function that will determine the initial state of the contract. As stated earlier this contract only offers custom initialization where the user is required to input parameters. diff --git a/docs/3.tutorials/auction/2-claim.md b/docs/3.tutorials/auction/2-claim.md index 9e679821cf1..fd2adc6f657 100644 --- a/docs/3.tutorials/auction/2-claim.md +++ b/docs/3.tutorials/auction/2-claim.md @@ -4,7 +4,9 @@ title: Claim sidebar_label: Claim --- -Up to now to claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security flaw. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus we will [lock]() the contract by removing all access keys and implementing a new method to claim the funds. +Up till now to claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security flaw. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus we will [lock]() the contract by removing all access keys and implementing a new method to claim the funds. + +  ## Adding an auctioneer @@ -28,6 +30,8 @@ We want to restrict this method to claim the funds to only be called by the indi Let's also introduce a boolean `claimed` field to track whether the funds 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 funds to the auctioneer. We implement this as so: diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index 777340f99cf..613cb1b4858 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -6,6 +6,8 @@ sidebar_label: NFT No one will enter an auction if there's nothing to win, so let's add a prize. Why not an [NFT]()? NFTs are uniquely identifiable, easily swappable and their logic comes from an external contract with audited standards so the prize will exist without the auction contract. Let's get to work! +  + ## Defining the NFT interface Since NFTs follow standards all NFT contracts implement the same interface that defines what methods can be called, what parameters these methods take and what the method will return. When we are making calls to other contracts we write the interface of each method in a separate file `ext.rs` as a `trait`. @@ -18,12 +20,11 @@ use crate::TokenId; // NFT interface for cross-contract calls #[ext_contract(nft_contract)] trait NFT { - fn nft_transfer(&self, receiver_id: AccountId, token_id: TokenId) -> String; + fn nft_transfer(&self, receiver_id: AccountId, token_id: TokenId); } ``` -`nft_transfer` transfers the ownership of an NFT from one account ID to another. The method takes the arguments `receiver_id` dictating who will now own the NFT and the `token_id` specifying which token in the NFT contract is having its ownership transferred. **It returns a string, i dont think so?** -The `ext_contract` attribute converts the NFT trait into a module with the method `nft_transfer`. +`nft_transfer` transfers the ownership of an NFT from one account ID to another. The method takes the arguments `receiver_id` dictating who will now own the NFT and the `token_id` specifying which token in the NFT contract is having its ownership transferred. The `ext_contract` attribute converts the NFT trait into a module with the method `nft_transfer`. Note that we declare `TokenId` in our main file `lib.rs`. A type alias is used for future-proofing. @@ -38,6 +39,8 @@ pub mod ext; pub use crate::ext::*; ``` +  + ## 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` as such: @@ -65,6 +68,8 @@ When we create an auction we need to list the NFT. To specify which NFT is being } ``` +  + ## 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. We make a [cross-contract]() call to the NFT contract via the external method we defined at the end of `claim`. @@ -76,5 +81,10 @@ When the auction is ended, by calling the method `claim`, the NFT needs to be tr .nft_transfer(self.highest_bid.bidder.clone(), self.token_id.clone()); ``` -Write about gas + deposit and maybe the method args. +When calling this external method we specify the NFT contract name, that we are attaching 30 Tgas to the call, that we are attaching 1 YoctoNEAR to the call (since the NFT contract requires this for [security reasons]()), and the arguments `reciever_id` and `token_id`. + +  + +## 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 with an NFT that the contract doesn't own 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 by only displaying valid auctions. \ No newline at end of file diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index a08beba231c..9252ebd3995 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -4,8 +4,95 @@ title: FT sidebar_label: FT --- +To make this contract more interesting we're going to introduce another primitive: [fungible tokens](). 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, or if a project like Ref Finance were holding their own auction and would want the auction to happen in their project's token $REF. +  +## Defining the FT interface +Unlike NEAR tokens, fungible token amounts are not tracked on the user's account but rather they have their own contract (as do NFTs); so we're going to have to define an interface to interact with the FT contract. We will use the method `fr_transfer` to send fungible tokens to a specified account from our contract. + +```rust +// FT interface for cross-contract calls +#[ext_contract(ft_contract)] +trait FT { + fn ft_transfer(&self, receiver_id: AccountId, amount: U128) -> String; +} +``` + +  + +## Specifying the FT contract + +We want our bids to only happen in one type of fungible token; accepting many 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 price for the NFT. + +```rust + #[init] + #[private] // only callable by the contract's account + pub fn init( + end_time: U64, + auctioneer: AccountId, + ft_contract: AccountId, + nft_contract: AccountId, + token_id: TokenId, + starting_price: U128, + ) -> Self { + Self { + highest_bid: Bid { + bidder: env::current_account_id(), + bid: starting_price, + }, + auction_end_time: end_time, + auctioneer, + claimed: false, + ft_contract, + nft_contract, + token_id, + } + } +``` + +## Accepting bids in FTs + +For making bids in NEAR we call the contract directly and add NEAR tokens to the call. With fungible tokens, since the balance lives on a separate contract, we call the FT contract to call the auction contract and to transfer 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]() for more information. + +**insert FT diagram** + +Thus we swap our `bid` method for `ft_on_transfer`. + +```rust + pub fn ft_on_transfer(&mut self, sender_id: AccountId, amount: U128, msg: String) -> U128 { +``` + +We do a check to confirm that the user is using the required fungible token contract by checking the predecessor's account ID. Since it is the FT contract that called the auction contract the predecessor is the FT contract. + +```rust + let ft = env::predecessor_account_id(); + require!(ft == self.ft_contract, "The token is not supported"); +``` + +The bidder's account ID is now given as an argument `sender_id`. + +```rust + self.highest_bid = Bid { + bidder: sender_id, + bid: amount, + }; +``` + +Now when we want to return the funds to the previous bidder we make a cross-contract call the the FT contract. We shouldn't transfer funds if it is the first bid so we'll implement a check for that as well. + +```rust + if last_bidder != env::current_account_id() { + ft_contract::ext(self.ft_contract.clone()) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .with_static_gas(Gas::from_tgas(30)) + .ft_transfer(last_bidder, last_bid); + } +``` + + + +register ft Write about factory contract to deploy auctions, talk about how you could have multiple auctions in one smart contract cheaper on storage but could be more vulnerable (if get access to one contract get access to all) \ No newline at end of file diff --git a/docs/3.tutorials/auction/5-testing.md b/docs/3.tutorials/auction/5-testing.md new file mode 100644 index 00000000000..a661b90ed8d --- /dev/null +++ b/docs/3.tutorials/auction/5-testing.md @@ -0,0 +1,4 @@ +testing and deploying, maybe deploying on another page + +Add tests here +Add cargo test, carog near build, cargo near deploy, near-cli-rs here \ No newline at end of file From 30c1d5c3f1c200bf41378011661253d5f81d97fc Mon Sep 17 00:00:00 2001 From: PiVortex Date: Thu, 25 Jul 2024 15:44:08 +0100 Subject: [PATCH 03/32] updates --- docs/3.tutorials/auction/0-intro.md | 28 +-- .../auction/{1-bidding.md => 1-basic.md} | 220 +++++++++++++++++- .../auction/{2-claim.md => 2-locking.md} | 6 +- docs/3.tutorials/auction/3-nft.md | 8 +- docs/3.tutorials/auction/4-ft.md | 51 +++- docs/3.tutorials/auction/5-testing.md | 4 - 6 files changed, 273 insertions(+), 44 deletions(-) rename docs/3.tutorials/auction/{1-bidding.md => 1-basic.md} (50%) rename docs/3.tutorials/auction/{2-claim.md => 2-locking.md} (95%) delete mode 100644 docs/3.tutorials/auction/5-testing.md diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index 447a7b49c7e..de0ab9fac38 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -4,14 +4,14 @@ title: Auction Zero to Hero sidebar_label: Introduction --- -In this Zero to Hero series, you'll learn to build an auction smart contract that allows an auctioneer to list an NFT and have people bid with FTs This series will slowly build up in complexity, teaching you how to use several key concepts in a NEAR smart contract: -- NFTs -- FTs -- Locking contract -- Timing with blocks -- cross contract calls +In this Zero to Hero series, you'll learn to build an auction smart contract that allows an auctioneer to list an NFT and have people bid with FTs This series will slowly build up in complexity, teaching you about several key primitives and concepts along the way: +- Using block_timestamp +- Locking a contract +- Making cross-contract calls +- Sending Non-Fungible Tokens +- Sending and receiving Fungible Tokens -This tutorial comes with contracts written in both JavaScript and Rust. +This tutorial comes with contracts written in both TypeScript and Rust. --- @@ -20,7 +20,6 @@ This tutorial comes with contracts written in both JavaScript and Rust. To complete this series you'll need: - [Rust](https://www.rust-lang.org/tools/install) -- [A Testnet wallet](https://testnet.mynearwallet.com/create) - [NEAR-CLI-RS](../../4.tools/cli-rs.md#setup) - [cargo-near](https://github.com/near/cargo-near) @@ -29,13 +28,8 @@ To complete this series you'll need: | Step | Name | Description | |------|-----------------------------------|-------------| -| 1 | [Bid]() | Fill in | -| 2 | [Claim]() | Fill in | -| 3 | [Owner Claims & Winner Gets NFT]() | Fill in | -| 4 | [Integrating Fungible Tokens]() | Fill in | +| 1 | [Basic contract](./1-basic.md) | Learn how to create a basic smart contract from scratch, use block time and test a contract | +| 2 | [Locking the contract](./2-locking.md) | Learn to create contracts with no access keys | +| 3 | [Winning an NFT](./3-nft.md) | Learn about sending NFTs using cross-contract calls | +| 4 | [Bidding with FTs](./4-ft.md) | Learn about sending and recieving FTs | - -Finish table -Update preqrequesists -Update what learnt -Add more appropriate titles \ No newline at end of file diff --git a/docs/3.tutorials/auction/1-bidding.md b/docs/3.tutorials/auction/1-basic.md similarity index 50% rename from docs/3.tutorials/auction/1-bidding.md rename to docs/3.tutorials/auction/1-basic.md index eba1f0018f8..2e1f9470568 100644 --- a/docs/3.tutorials/auction/1-bidding.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -1,7 +1,7 @@ --- -id: bidding -title: Bidding -sidebar_label: Bidding +id: winning-an-nft +title: Winning an NFT +sidebar_label: Winning an NFT --- In this section of the tutorial, you'll learn how to create a basic auction smart contract from scratch. The contract will allow users to place bids and track the highest bidder. @@ -36,7 +36,7 @@ Enter src > lib.rs, there already exists an example contract there so we'll just ## Defining the contract structure -Smart contracts are simply structures that store data and implement functions to mutate and view that data. First, we'll define what data we are storing. We'll also need some imports along the way. +Smart contracts are simply structures that store data and implement methods to mutate and view that data. First, we'll define what data we are storing. We'll also need some imports along the way. ```rust use near_sdk::json_types::U64; @@ -61,8 +61,8 @@ Here we define the Contract structure that has fields: - **highest_bid**: stores the information of the highest bidder in an instance of another struct named Bid. - **auction_end_time**: specifies the block_timestamp (which will be explained later) at which the auction will end. -Contract also has attributes: -- **contract_state**: enables borsh serialization and decentralization to read and write the structure to the blockchain in binary form and adds NearSchmema **IDK what this does?** +Contract also has the macros: +- **contract_state**: enables borsh serialization and decentralization to read and write the structure to the blockchain in binary form and generates a schema. - **serializers**: enables both borsh and JSON serialization and decentralization. The JSON serializer allows for the whole contract state to be outputted in JSON form. Although borsh is already enabled by contract_state, we have to specify it again in serializers if we want to add JSON. - **PanicOnDefault**: forces the contract to have custom initialization (we will see this later). @@ -72,7 +72,7 @@ Since highest_bid stores type Bid we have a nested structure. Bid itself has fie - **bidder**: specifies the unique human readable NEAR account ID of the bidder. - **bid**: specifies the bid amount in YoctoNEAR (10^-24 NEAR). -Bid has attributes: +Bid has macros: - **serializers**: enables both borsh and JSON serialization and decentralization. - **Clone**: allows a Bid object to be duplicated. @@ -100,7 +100,7 @@ impl Contract { } ``` -We decorate the implementation of Contract with `near` to **IDK** . The `init` function has attributes `init` to denote it is an initialization function and `private` to restrict the function to only be called by the account on which the contract is deployed. The function takes the parameter `end_time` which specifies the block_timestamp of when the auction will end. +We decorate the implementation of Contract with the `near` macro to declare that the methods inside of Contract can be called by the outside world. The `init` function has attributes `init` to denote it is an initialization function and `private` to restrict the function to only be called by the account on which the contract is deployed. The function takes the parameter `end_time` which specifies the block_timestamp of when the auction will end. When this function is called the `auction_end_time` will be set and `highest_bid` will be set with `bidder` as the account on which the the contract is deployed and `bid` as 1 YoctoNEAR (10^-24 NEAR) so that if no one bids no one will win the auction. @@ -164,13 +164,211 @@ pub fn get_highest_bid(&self) -> Bid { In the repo there are further view methods that follow a very similar structure. -Congratulations, you just created a basic NEAR smart contract! +  + +# Adding tests + +Ok now we have the contract we need to make sure it works as expected and is secure. It's good practice to implement exhaustive tests so you can ensure that any little to your change down the line doesn't break your contract. For further information on testing take a look at [this]() section in the docs. + +## Unit tests + +Unit tests allow you to test contract methods individually. For each change method, we should implement a unit test. + +Let's create a test to ensure that the contract is initialized properly. + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn init_contract() { + let contract = Contract::init(U64::from(1000)); + + let default_bid = contract.get_highest_bid(); + assert_eq!(default_bid.bidder, env::current_account_id()); + assert_eq!(default_bid.bid, NearToken::from_yoctonear(1)); + + let end_time = contract.get_auction_end_time(); + assert_eq!(end_time, U64::from(1000)); + } +} +``` + +- First, we create a new contract and initialize it with an auction_end_time of 1000 (in reality this is very low but it does not matter here). + +- Next, we call get_highest_bid and check that the bidder and bid are as expected. + +- Lastly, we call get_auction_end_time and check that the end_time is as expected. + +Simple enough. + +We'll next implement a test to ensure the functionality of `bid` works as expected. + +```rust + #[test] + fn bid() { + // Create context for the call + let mut builder = VMContextBuilder::new(); + builder.predecessor_account_id("bob.near".parse().unwrap()); + builder.attached_deposit(ONE_NEAR); + testing_env!(builder.build()); + + let mut contract = Contract::init(U64::from(1000)); + contract.bid(); + + let first_bid = contract.get_highest_bid(); + assert_eq!(first_bid.bidder, "bob.near"); + assert_eq!(first_bid.bid, ONE_NEAR); + } +``` + +Here we now create context with `VMContextBuilder` to set the predecessor to "bob.near" and the deposit amount to one NEAR token when we call bid. + +  + + +## Integration tests + +Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment. + +For testing, we're going to have to import some crates. We will need to import the Bid struct from our contract so in our Cargo.toml we'll rename the package "auction-contract" and add chrono to our dependencies + +``` +chrono = "0.4.38" +``` + +Enter tests > test_basics.rs, there already exists an example test there so we'll just go ahead and delete everything. + +First, we need to define our test function, create the sandbox testing environment and get the compiled auction contract to interact with. + +```rust +#[tokio::test] +async fn test_contract_is_operational() -> Result<(), Box> { + let sandbox = near_workspaces::sandbox().await?; + let contract_wasm = near_workspaces::compile_project("./").await?; +``` + +Next, we'll create a couple of user accounts that we'll use to send transactions from. We'll give each an account ID and an initial balance: + +```rust + let alice = root + .create_subaccount("alice") + .initial_balance(FIVE_NEAR) + .transact() + .await? + .unwrap(); +``` + +We'll do the same for the "contract" account and deploy the contract WASM to it. + +```rust +let contract = contract_account.deploy(&contract_wasm).await?.unwrap(); +``` + +When we initialize the contract we need to pass `end_time` so we'll set that to 60 seconds in the future. After calling `init` we'll check that the transaction was successful. + +```rust + let now = Utc::now().timestamp(); + let a_minute_from_now = (now + 60) * 1000000000; + log!("a_minute_from_now: {}", a_minute_from_now); + + let init = contract + .call("init") + .args_json(json!({"end_time": a_minute_from_now.to_string()})) + .transact() + .await?; + + assert!(init.is_success()); +``` + +Now we have a contract deployed and initialized we can make bids to the contract from an account and check that the state changes as intended. + +```rust + let bob_bid = bob + .call(contract.id(), "bid") + .deposit(NearToken::from_near(2)) + .transact() + .await?; + + assert!(bob_bid.is_success()); + + let highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; + + assert_eq!(highest_bid.bid, NearToken::from_near(2)); + assert_eq!(highest_bid.bidder, *bob.id()); +``` + +When testing we should also check that the contract does not allow invalid calls. We will check that the contract doesn't allow us to make a bid with less NEAR tokens than the previous. + +```rust + let alice_bid = alice + .call(contract.id(), "bid") + .deposit(NearToken::from_near(1)) + .transact() + .await?; + + assert!(alice_bid.is_failure()); + + let highest_bid_json = contract.view("get_highest_bid").await?; + let highest_bid: Bid = highest_bid_json.json::()?; + + assert_eq!(highest_bid.bid, NearToken::from_near(2)); + assert_eq!(highest_bid.bidder, *bob.id()); +``` + +  + +## Testing and deploying + +Now we have the tests written let's actually test it. + +``` +$ cargo test +``` + +If all the tests run well we can build and deploy the contract to testnet. + +``` +$ cargo near build +``` + +We'll need a testnet account to deploy the contract to, so if you don't have one you can use: + +``` +$ cargo near create-dev-account use-random-account-id +``` + +Then deploy with: + +``` +$ cargo near deploy with-init-call init json-args '{"end_time": "3000000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet +``` + +Now the contract is deployed and initialized we can send transaction to it using the CLI, NEAR's CLI is interactive meaning you can type `near` and click through all the possible options. But you can use the following full commands to call the contract methods: + +Call `bid` + +``` +$ +``` + +Call + +``` +$ +``` + + + + + + -Add tests here or all at the end, delete tests folder when creating exmaple? -Add cargo near build, cargo test, near-cli-rs here? diff --git a/docs/3.tutorials/auction/2-claim.md b/docs/3.tutorials/auction/2-locking.md similarity index 95% rename from docs/3.tutorials/auction/2-claim.md rename to docs/3.tutorials/auction/2-locking.md index fd2adc6f657..b557d926f80 100644 --- a/docs/3.tutorials/auction/2-claim.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -1,7 +1,7 @@ --- -id: claim -title: Claim -sidebar_label: Claim +id: locking-the-contract +title: Locking the contract +sidebar_label: Locking the contract --- Up till now to claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security flaw. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus we will [lock]() the contract by removing all access keys and implementing a new method to claim the funds. diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index 613cb1b4858..7245202c2b7 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -1,7 +1,7 @@ --- -id: nft -title: NFT -sidebar_label: NFT +id: winning-an-nft +title: Winning an NFT +sidebar_label: Winning an NFT --- No one will enter an auction if there's nothing to win, so let's add a prize. Why not an [NFT]()? NFTs are uniquely identifiable, easily swappable and their logic comes from an external contract with audited standards so the prize will exist without the auction contract. Let's get to work! @@ -24,7 +24,7 @@ trait NFT { } ``` -`nft_transfer` transfers the ownership of an NFT from one account ID to another. The method takes the arguments `receiver_id` dictating who will now own the NFT and the `token_id` specifying which token in the NFT contract is having its ownership transferred. The `ext_contract` attribute converts the NFT trait into a module with the method `nft_transfer`. +`nft_transfer` transfers the ownership of an NFT from one account ID to another. The method takes the arguments `receiver_id` dictating who will now own the NFT and the `token_id` specifying which token in the NFT contract is having its ownership transferred. The `ext_contract` macro converts the NFT trait into a module with the method `nft_transfer`. Note that we declare `TokenId` in our main file `lib.rs`. A type alias is used for future-proofing. diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index 9252ebd3995..d312a2e783a 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -1,7 +1,7 @@ --- -id: ft -title: FT -sidebar_label: FT +id: bidding-with-FTs +title: Bidding with FTs +sidebar_label: Bidding with FTs --- To make this contract more interesting we're going to introduce another primitive: [fungible tokens](). 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, or if a project like Ref Finance were holding their own auction and would want the auction to happen in their project's token $REF. @@ -52,6 +52,8 @@ We want our bids to only happen in one type of fungible token; accepting many wo } ``` +  + ## Accepting bids in FTs For making bids in NEAR we call the contract directly and add NEAR tokens to the call. With fungible tokens, since the balance lives on a separate contract, we call the FT contract to call the auction contract and to transfer 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]() for more information. @@ -91,8 +93,47 @@ Now when we want to return the funds to the previous bidder we make a cross-cont } ``` +The method `ft_on_transfer` needs to return the number of unused tokens in the call so the FT contract can refund the sender. We will always use the full amount of tokens in this call so we simply return 0. + +```rust +U128(0) +``` + +  + +## Claiming the FTs +When the auction is complete we need to send the fungible tokens to the auctioneer, we implement a similar call as when we were returning the funds just changing the arguments. -register ft +```rust + ft_contract::ext(self.ft_contract.clone()) + .with_attached_deposit(NearToken::from_yoctonear(1)) + .with_static_gas(Gas::from_tgas(30)) + .ft_transfer(self.auctioneer.clone(), self.highest_bid.bid); +``` + +  + +## Registering the user in the FT contract + +For one to receive fungible tokens first their account ID must be [registered]() in the FT contract. 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 need 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. + +  + +--- + +There we have it, a completed auction smart contract! + +## 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 would 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. + +```rust +pub struct Contract { + auctions: unorderedMap +} +``` -Write about factory contract to deploy auctions, talk about how you could have multiple auctions in one smart contract cheaper on storage but could be more vulnerable (if get access to one contract get access to all) \ No newline at end of file +However, this architecture could be deemed as 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. \ No newline at end of file diff --git a/docs/3.tutorials/auction/5-testing.md b/docs/3.tutorials/auction/5-testing.md deleted file mode 100644 index a661b90ed8d..00000000000 --- a/docs/3.tutorials/auction/5-testing.md +++ /dev/null @@ -1,4 +0,0 @@ -testing and deploying, maybe deploying on another page - -Add tests here -Add cargo test, carog near build, cargo near deploy, near-cli-rs here \ No newline at end of file From dc7d2088266815625a86faf68ccb6303af25488a Mon Sep 17 00:00:00 2001 From: PiVortex Date: Fri, 26 Jul 2024 13:20:28 +0100 Subject: [PATCH 04/32] more additions --- docs/3.tutorials/auction/0-intro.md | 60 +++- docs/3.tutorials/auction/1-basic.md | 477 +++++++++++++++----------- docs/3.tutorials/auction/2-locking.md | 2 +- docs/3.tutorials/auction/3-nft.md | 6 +- docs/3.tutorials/auction/4-ft.md | 8 +- docs/3.tutorials/welcome.md | 2 + 6 files changed, 342 insertions(+), 213 deletions(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index de0ab9fac38..9536154b262 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -4,14 +4,17 @@ title: Auction Zero to Hero sidebar_label: Introduction --- -In this Zero to Hero series, you'll learn to build an auction smart contract that allows an auctioneer to list an NFT and have people bid with FTs This series will slowly build up in complexity, teaching you about several key primitives and concepts along the way: +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +In this Zero to Hero series, you'll build an auction smart contract from start to finish, deploy a factory contract to launch multiple auctions, , and craft a frontend to interact with the auctions. This series will slowly build up in complexity, teaching you about several key primitives and concepts along the way: - Using block_timestamp - Locking a contract - Making cross-contract calls - Sending Non-Fungible Tokens - Sending and receiving Fungible Tokens -This tutorial comes with contracts written in both TypeScript and Rust. +This tutorial comes with contracts written in both JavaScript and Rust. --- @@ -19,9 +22,25 @@ This tutorial comes with contracts written in both TypeScript and Rust. To complete this series you'll need: -- [Rust](https://www.rust-lang.org/tools/install) -- [NEAR-CLI-RS](../../4.tools/cli-rs.md#setup) -- [cargo-near](https://github.com/near/cargo-near) + + + + + + + + + + + - [Rust](https://www.rust-lang.org/tools/install) + - [NEAR-CLI-RS](../../4.tools/cli-rs.md#setup) + - [cargo-near](https://github.com/near/cargo-near) + + + + + + --- @@ -33,3 +52,34 @@ To complete this series you'll need: | 3 | [Winning an NFT](./3-nft.md) | Learn about sending NFTs using cross-contract calls | | 4 | [Bidding with FTs](./4-ft.md) | Learn about sending and recieving FTs | +--- + +## Versioning + +:::note Versioning for this tutorial +At the time of this writing, this example works with the following versions: + +- rustc: `1.78.0` +- near-sdk-rs: `5.1.0` +- cargo-near: `0.6.2` +- near-cli-rs `0.12.0` + +::: + + + + + + + + + TODO + + + + + + + + + \ No newline at end of file diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index 2e1f9470568..ff1ae3bb903 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -4,71 +4,89 @@ title: Winning an NFT sidebar_label: Winning an NFT --- -In this section of the tutorial, you'll learn how to create a basic auction smart contract from scratch. The contract will allow users to place bids and track the highest bidder. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {Github} from "@site/src/components/codetabs" + + +In this section of the tutorial, you'll learn how to create a basic auction smart contract from scratch. The contract will allow users to place bids and track the highest bidder. We'll also look at how to create and run tests, and then how to deploy and interact with the contract on testnet. -  ## Creating a new project To get started you'll need to create a new NEAR project. -In your command line run: + -``` -$ cargo near new auction-contract -``` + -Enter your new project + TODO -``` -$ cd auction-contract -``` + -Then go ahead and open up a code editor such as VS Code + -``` -$ code . -``` + In your command line run + + ``` + $ cargo near new auction-contract + ``` + + Enter your new project + + ``` + $ cd auction-contract + ``` + + Then go ahead and open up a code editor such as VS Code + + ``` + $ code . + ``` + + Enter src > lib.rs, there already exists an example contract there so we'll just go ahead and delete it and start from the start! + + + + -Enter src > lib.rs, there already exists an example contract there so we'll just go ahead and delete everything and start from the start! -  ## Defining the contract structure Smart contracts are simply structures that store data and implement methods to mutate and view that data. First, we'll define what data we are storing. We'll also need some imports along the way. -```rust -use near_sdk::json_types::U64; -use near_sdk::{env, near, require, AccountId, NearToken, PanicOnDefault, Promise}; - -#[near(serializers = [json, borsh])] -#[derive(Clone)] -pub struct Bid { - pub bidder: AccountId, - pub bid: NearToken, -} - -#[near(contract_state, serializers = [json, borsh])] -#[derive(PanicOnDefault)] -pub struct Contract { - highest_bid: Bid, - auction_end_time: U64, -} -``` + -Here we define the Contract structure that has fields: -- **highest_bid**: stores the information of the highest bidder in an instance of another struct named Bid. + + + TODO + + + + + + + + + + + + +Here we define the `Contract` structure that has fields: +- **highest_bid**: stores the information about the highest bidder in another struct named Bid. - **auction_end_time**: specifies the block_timestamp (which will be explained later) at which the auction will end. -Contract also has the macros: +`Contract` also has the macros: - **contract_state**: enables borsh serialization and decentralization to read and write the structure to the blockchain in binary form and generates a schema. - **serializers**: enables both borsh and JSON serialization and decentralization. The JSON serializer allows for the whole contract state to be outputted in JSON form. Although borsh is already enabled by contract_state, we have to specify it again in serializers if we want to add JSON. - **PanicOnDefault**: forces the contract to have custom initialization (we will see this later).   -Since highest_bid stores type Bid we have a nested structure. Bid itself has fields: +Since `highest_bid` stores type `Bid` we have a nested structure. Bid itself has fields: - **bidder**: specifies the unique human readable NEAR account ID of the bidder. - **bid**: specifies the bid amount in YoctoNEAR (10^-24 NEAR). @@ -76,71 +94,62 @@ Bid has macros: - **serializers**: enables both borsh and JSON serialization and decentralization. - **Clone**: allows a Bid object to be duplicated. -  ## Initializing the contract Now we have defined the data structures for the contract we next define its methods. -First, we set up an initialization function that will determine the initial state of the contract. As stated earlier this contract only offers custom initialization where the user is required to input parameters. - -```rust -#[near] -impl Contract { - #[init] - #[private] // Only callable by the contract's account - pub fn init(end_time: U64) -> Self { - Self { - highest_bid: Bid { - bidder: env::current_account_id(), - bid: NearToken::from_yoctonear(1), - }, - auction_end_time: end_time, - } - } -``` +First, we set up an initialization function that will determine the initial state of the contract. As stated earlier, this contract requires custom initialization where the user is required to input parameters on initialization. + + + + + + + + + + + -We decorate the implementation of Contract with the `near` macro to declare that the methods inside of Contract can be called by the outside world. The `init` function has attributes `init` to denote it is an initialization function and `private` to restrict the function to only be called by the account on which the contract is deployed. The function takes the parameter `end_time` which specifies the block_timestamp of when the auction will end. + -When this function is called the `auction_end_time` will be set and `highest_bid` will be set with `bidder` as the account on which the the contract is deployed and `bid` as 1 YoctoNEAR (10^-24 NEAR) so that if no one bids no one will win the auction. + + + + + +We decorate the implementation of Contract with the `near` macro to declare that the methods inside of Contract can be called by the outside world. The `init` function has macros `init` to denote it is an initialization function and `private` to restrict the function to only be called by the account on which the contract is deployed. The function takes the parameter `end_time` which specifies the block_timestamp of when the auction will end. + +When this function is called the `auction_end_time` will be set and `highest_bid` will be set with `bid` as 1 YoctoNEAR (the smallest unit of NEAR) and `bidder` as the account on which the contract is deployed so that if no one bids no one will win the auction. -  ## Placing a bid -An auction isn't an auction if you can't place a bid! We'll now add a method that allows a user to place a bid by attaching NEAR tokens to the method call. The method needs to implement logic, it should check whether the auction is still ongoing, check whether their bid is higher than the previous and if it meets both criteria it should update the `highest_bid` field accordingly and return the funds back to the previous bidder. +An auction isn't an auction if you can't place a bid! We'll now add a method that allows a user to place a bid by attaching NEAR tokens to the method call. The method should check whether the auction is still ongoing, check whether their bid is higher than the previous and if it meets both criteria it should update the `highest_bid` field accordingly and return the funds back to the previous bidder. -```rust -#[payable] -pub fn bid(&mut self) -> Promise { - // Assert the auction is still ongoing - require!( - env::block_timestamp() < self.auction_end_time.into(), - "Auction has ended" - ); - - // Current bid - let bid = env::attached_deposit(); - let bidder = env::predecessor_account_id(); - - // Last bid - let Bid { - bidder: last_bidder, - bid: last_bid, - } = self.highest_bid.clone(); - - // Check if the deposit is higher than the current bid - require!(bid > last_bid, "You must place a higher bid"); - - // Update the highest bid - self.highest_bid = Bid { bidder, bid }; - - // Transfer tokens back to the last bidder - Promise::new(last_bidder).transfer(last_bid) -} -``` + + + + + TODO + + -When the user calls the `bid` method they will transfer NEAR tokens to the contract; to enable the transfer of NEAR tokens we need to decorate the method with the `payable` attribute. This method is what we call a "change method", also known as a "call method"; we know this since it passes a mutable reference to itself ($mut self). Change methods require the user to pay gas and can change the state of the contract. + + + + + + + + + +When the user calls the `bid` method they will transfer NEAR tokens to the contract; to enable the transfer of NEAR tokens we need to decorate the method with the `payable` macro. This method is what we call a "change method", also known as a "call method"; we know this since it passes a mutable reference to itself ($mut self). Change methods require the user to pay gas and can change the state of the contract. - First, the method checks that the auction is still ongoing by checking that the current `block_timestamp` is less than the auction end time. `block_timestamp` gives the time as the number of nanoseconds since January 1, 1970, 0:00:00 UTC. Here we use `require` as opposed to `assert` as it reduces the contract size by not including file and rust-specific data in the panic message. @@ -150,52 +159,63 @@ When the user calls the `bid` method they will transfer NEAR tokens to the contr - The contract then returns a `Promise` that will transfer NEAR of the amount `last_bid` to the `last_bidder`. -  ## Viewing the contract state We also implement "view methods" into this contract; this allows the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. We know it is a view method since it takes an immutable reference to self (&self). An example use case here is being able to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bet. -```rust -pub fn get_highest_bid(&self) -> Bid { - self.highest_bid.clone() -} -``` + + + + + TODO + + + + + + + + + + + In the repo there are further view methods that follow a very similar structure. -  # Adding tests -Ok now we have the contract we need to make sure it works as expected and is secure. It's good practice to implement exhaustive tests so you can ensure that any little to your change down the line doesn't break your contract. For further information on testing take a look at [this]() section in the docs. +Ok now we have the contract we need to make sure it works as expected and is secure. It's good practice to implement exhaustive tests so you can ensure that any little to your change down the line doesn't break your contract. For further information on testing take a look at [this](../../2.build/2.smart-contracts/testing/introduction.md) section in the docs. ## Unit tests -Unit tests allow you to test contract methods individually. For each change method, we should implement a unit test. +Unit tests allow you to test contract methods individually. For each change method, we should implement a unit test and check that it changes the state of the contract in the expected way with a view method. Let's create a test to ensure that the contract is initialized properly. -```rust -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn init_contract() { - let contract = Contract::init(U64::from(1000)); + - let default_bid = contract.get_highest_bid(); - assert_eq!(default_bid.bidder, env::current_account_id()); - assert_eq!(default_bid.bid, NearToken::from_yoctonear(1)); + - let end_time = contract.get_auction_end_time(); - assert_eq!(end_time, U64::from(1000)); - } -} -``` + TODO + + + + + + -- First, we create a new contract and initialize it with an auction_end_time of 1000 (in reality this is very low but it does not matter here). + + + + +- First, we create a new contract and initialize it with an auction_end_time of 1000 (in reality this is very low but it doesn't matter here). - Next, we call get_highest_bid and check that the bidder and bid are as expected. @@ -203,8 +223,10 @@ mod tests { Simple enough. -We'll next implement a test to ensure the functionality of `bid` works as expected. +We'll next implement a test to ensure the `bid` method works as expected. + +**Not in repo yet** ```rust #[test] fn bid() { @@ -223,152 +245,207 @@ We'll next implement a test to ensure the functionality of `bid` works as expect } ``` -Here we now create context with `VMContextBuilder` to set the predecessor to "bob.near" and the deposit amount to one NEAR token when we call bid. - -  +Since this method requires an attached deposit and gets the predecessor's account ID we create context with `VMContextBuilder` to set the predecessor to "bob.near" and the deposit amount to one NEAR token when we call bid. ## Integration tests Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment. -For testing, we're going to have to import some crates. We will need to import the Bid struct from our contract so in our Cargo.toml we'll rename the package "auction-contract" and add chrono to our dependencies +For testing, we're going to have to import some crates. We will need to import the Bid struct from our contract so in our `Cargo.toml` we'll rename the package "auction-contract" and add `chrono` to our dependencies ``` chrono = "0.4.38" ``` -Enter tests > test_basics.rs, there already exists an example test there so we'll just go ahead and delete everything. +Enter tests > test_basics.rs, there already exists an example test there so we'll just go ahead and delete it. First, we need to define our test function, create the sandbox testing environment and get the compiled auction contract to interact with. -```rust -#[tokio::test] -async fn test_contract_is_operational() -> Result<(), Box> { - let sandbox = near_workspaces::sandbox().await?; - let contract_wasm = near_workspaces::compile_project("./").await?; -``` + + + + + + TODO + + + + + + + + + + + Next, we'll create a couple of user accounts that we'll use to send transactions from. We'll give each an account ID and an initial balance: -```rust - let alice = root - .create_subaccount("alice") - .initial_balance(FIVE_NEAR) - .transact() - .await? - .unwrap(); -``` + + + + + TODO + + + + + + + + + + + We'll do the same for the "contract" account and deploy the contract WASM to it. -```rust -let contract = contract_account.deploy(&contract_wasm).await?.unwrap(); -``` + -When we initialize the contract we need to pass `end_time` so we'll set that to 60 seconds in the future. After calling `init` we'll check that the transaction was successful. + -```rust - let now = Utc::now().timestamp(); - let a_minute_from_now = (now + 60) * 1000000000; - log!("a_minute_from_now: {}", a_minute_from_now); + TODO - let init = contract - .call("init") - .args_json(json!({"end_time": a_minute_from_now.to_string()})) - .transact() - .await?; + - assert!(init.is_success()); -``` + -Now we have a contract deployed and initialized we can make bids to the contract from an account and check that the state changes as intended. + -```rust - let bob_bid = bob - .call(contract.id(), "bid") - .deposit(NearToken::from_near(2)) - .transact() - .await?; + - assert!(bob_bid.is_success()); + - let highest_bid_json = contract.view("get_highest_bid").await?; - let highest_bid: Bid = highest_bid_json.json::()?; - assert_eq!(highest_bid.bid, NearToken::from_near(2)); - assert_eq!(highest_bid.bidder, *bob.id()); -``` +When we initialize the contract we need to pass `end_time`, we'll set it to 60 seconds in the future. After calling `init` we'll check that the transaction was successful. + + + + + + TODO + + + + + + + + + + + + +Now we have the contract deployed and initialized we can make bids to the contract from an account and check that the state changes as intended. + + + + + + TODO + + + + + + + + + + + When testing we should also check that the contract does not allow invalid calls. We will check that the contract doesn't allow us to make a bid with less NEAR tokens than the previous. -```rust - let alice_bid = alice - .call(contract.id(), "bid") - .deposit(NearToken::from_near(1)) - .transact() - .await?; + - assert!(alice_bid.is_failure()); + - let highest_bid_json = contract.view("get_highest_bid").await?; - let highest_bid: Bid = highest_bid_json.json::()?; + TODO - assert_eq!(highest_bid.bid, NearToken::from_near(2)); - assert_eq!(highest_bid.bidder, *bob.id()); -``` + + + + + + + + + -  ## Testing and deploying Now we have the tests written let's actually test it. -``` -$ cargo test -``` + -If all the tests run well we can build and deploy the contract to testnet. + -``` -$ cargo near build -``` + TODO -We'll need a testnet account to deploy the contract to, so if you don't have one you can use: + -``` -$ cargo near create-dev-account use-random-account-id -``` + -Then deploy with: + ``` + $ cargo test + ``` -``` -$ cargo near deploy with-init-call init json-args '{"end_time": "3000000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet -``` + If all the tests are successful we can build and deploy the contract to testnet -Now the contract is deployed and initialized we can send transaction to it using the CLI, NEAR's CLI is interactive meaning you can type `near` and click through all the possible options. But you can use the following full commands to call the contract methods: + ``` + $ cargo near build + ``` -Call `bid` + We'll need a testnet account to deploy the contract to, so if you don't have one you can use -``` -$ -``` + ``` + $ cargo near create-dev-account use-random-account-id + ``` -Call + Then deploy with -``` -$ -``` + ``` + $ cargo near deploy with-init-call init json-args '{"end_time": "3000000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet + ``` + + + +Now the contract is deployed and initialized we can send transaction to it using the CLI. NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands. But here you can use the following full commands to call the contract methods: +Call `bid`, you may want to create another testnet account for the signer +``` +$ near contract call-function as-transaction bid json-args {} prepaid-gas '100.0 Tgas' attached-deposit '1 NEAR' sign-as network-config testnet +``` +Call `get_highest_bid` +``` +$ near contract call-function as-read-only get_highest_bid json-args {} network-config testnet now +``` +## Conclusion +TODO diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md index b557d926f80..aa2fc2a64f6 100644 --- a/docs/3.tutorials/auction/2-locking.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -4,7 +4,7 @@ title: Locking the contract sidebar_label: Locking the contract --- -Up till now to claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security flaw. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus we will [lock]() the contract by removing all access keys and implementing a new method to claim the funds. +Up till now to claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security flaw. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus 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 funds.   diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index 7245202c2b7..a6ba83dd1f7 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -4,7 +4,7 @@ title: Winning an NFT sidebar_label: Winning an NFT --- -No one will enter an auction if there's nothing to win, so let's add a prize. Why not an [NFT]()? NFTs are uniquely identifiable, easily swappable and their logic comes from an external contract with audited standards so the prize will exist without the auction contract. Let's get to work! +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 with audited standards so the prize will exist without the auction contract. Let's get to work!   @@ -72,7 +72,7 @@ When we create an auction we need to list the NFT. To specify which NFT is being ## 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. We make a [cross-contract]() call to the NFT contract via the external method we defined at the end of `claim`. +When the auction is ended, by calling the method `claim`, the NFT needs to be transferred to the highest bidder. We make a cross-contract call to the NFT contract via the external method we defined at the end of `claim`. ```rust nft_contract::ext(self.nft_contract.clone()) @@ -81,7 +81,7 @@ When the auction is ended, by calling the method `claim`, the NFT needs to be tr .nft_transfer(self.highest_bid.bidder.clone(), self.token_id.clone()); ``` -When calling this external method we specify the NFT contract name, that we are attaching 30 Tgas to the call, that we are attaching 1 YoctoNEAR to the call (since the NFT contract requires this for [security reasons]()), and the arguments `reciever_id` and `token_id`. +When calling this external method we specify the NFT contract name, that we are attaching 30 Tgas to the call, that we are attaching 1 YoctoNEAR to the call (since the NFT contract requires this for [security reasons](../../2.build/2.smart-contracts/security/one_yocto.md)), and the arguments `reciever_id` and `token_id`.   diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index d312a2e783a..aaf950d5a96 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -4,7 +4,7 @@ title: Bidding with FTs sidebar_label: Bidding with FTs --- -To make this contract more interesting we're going to introduce another primitive: [fungible tokens](). 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, or if a project like Ref Finance were holding their own auction and would want the auction to happen in their project's token $REF. +To make this contract more interesting we're going to 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, or if a project like Ref Finance were holding their own auction and would want the auction to happen in their project's token $REF.   @@ -56,7 +56,7 @@ We want our bids to only happen in one type of fungible token; accepting many wo ## Accepting bids in FTs -For making bids in NEAR we call the contract directly and add NEAR tokens to the call. With fungible tokens, since the balance lives on a separate contract, we call the FT contract to call the auction contract and to transfer 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]() for more information. +For making bids in NEAR we call the contract directly and add NEAR tokens to the call. With fungible tokens, since the balance lives on a separate contract, we call the FT contract to call the auction contract and to transfer 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. **insert FT diagram** @@ -116,7 +116,7 @@ When the auction is complete we need to send the fungible tokens to the auctione ## Registering the user in the FT contract -For one to receive fungible tokens first their account ID must be [registered]() in the FT contract. 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 need 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. +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. 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 need 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.   @@ -126,7 +126,7 @@ There we have it, a completed auction smart contract! ## 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 would 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. +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 would 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. diff --git a/docs/3.tutorials/welcome.md b/docs/3.tutorials/welcome.md index b96e10b7942..071b8eb04e3 100644 --- a/docs/3.tutorials/welcome.md +++ b/docs/3.tutorials/welcome.md @@ -32,6 +32,8 @@ Explore our collection of Examples and Tutorials + From 8c438f0b88ca17f249c3022a78d1815ab6fe558e Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 26 Jul 2024 15:08:20 +0200 Subject: [PATCH 05/32] small changes --- docs/3.tutorials/auction/0-intro.md | 79 ++++++++++++++++++++++------- docs/3.tutorials/auction/1-basic.md | 26 ++++------ website/sidebars.js | 11 +++- 3 files changed, 79 insertions(+), 37 deletions(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index 9536154b262..8e42e994113 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -7,12 +7,15 @@ sidebar_label: Introduction import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -In this Zero to Hero series, you'll build an auction smart contract from start to finish, deploy a factory contract to launch multiple auctions, , and craft a frontend to interact with the auctions. This series will slowly build up in complexity, teaching you about several key primitives and concepts along the way: -- Using block_timestamp -- Locking a contract +In this Zero to Hero series, you'll learn everything you need to know about building Web3 applications on NEAR. + +We will start by building a simple auction contract, and slowly build up on it, adding more and more concepts as we go. By the time you finish this tutorial you would have touch on several key primitives and concepts along the way: + +- Building a smart contract +- Deploying and locking a contract - Making cross-contract calls -- Sending Non-Fungible Tokens -- Sending and receiving Fungible Tokens +- Using Non-Fungible Tokens +- Using Fungible Tokens This tutorial comes with contracts written in both JavaScript and Rust. @@ -20,37 +23,75 @@ This tutorial comes with contracts written in both JavaScript and Rust. ## Prerequisites -To complete this series you'll need: +Before starting, make sure to setup 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 NEAR CLI to deploy and interact with the contract +npm i -g near-cli +``` + - + - +```bash +# Install Rust: https://www.rust-lang.org/tools/install +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - - [Rust](https://www.rust-lang.org/tools/install) - - [NEAR-CLI-RS](../../4.tools/cli-rs.md#setup) - - [cargo-near](https://github.com/near/cargo-near) +# Contracts will be compiled to wasm, so we need to add the wasm target +rustup target add wasm32-unknown-unknown - +# Install NEAR CLI-RS 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 +``` + + +:::tip Testnet Account + +There is no need to have a `testnet` account to follow this tutorial. + +However, if you want to create one, you can do so through [a wallet](https://testnet.mynearwallet.com), and use it from the `near-cli` by invoking `near login`. + +::: + + --- +## Overview + +These are the steps that will bring you from Zero to Hero in no time! πŸ’ͺ -| Step | Name | Description | -|------|-----------------------------------|-------------| -| 1 | [Basic contract](./1-basic.md) | Learn how to create a basic smart contract from scratch, use block time and test a contract | -| 2 | [Locking the contract](./2-locking.md) | Learn to create contracts with no access keys | -| 3 | [Winning an NFT](./3-nft.md) | Learn about sending NFTs using cross-contract calls | -| 4 | [Bidding with FTs](./4-ft.md) | Learn about sending and recieving FTs | +| Step | Name | Description | +|------|----------------------------------------|-----------------------------------------------------------------| +| 1 | [Basic contract](./1-basic.md) | Learn how to create an auction contract from scratch | +| 2 | [Locking the contract](./2-locking.md) | Learn to create contracts with no access keys | +| 3 | [Winning an NFT](./3-nft.md) | Learn about sending NFTs using cross-contract calls | +| 4 | [Bidding with FTs](./4-ft.md) | Learn about sending and receiving FTs | +| 5 | [Create a Frontend](#) | Create a frontend to interact with your smart contract | +| 6 | [Monitor the Bids](#) | Learn how to use an indexer to monitor your contract's activity | +| 6 | [Factory](#) | Create a contract that will create auctions | --- diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index ff1ae3bb903..babff09f155 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -1,32 +1,32 @@ --- -id: winning-an-nft -title: Winning an NFT -sidebar_label: Winning an NFT +id: basic +title: Basic Auction --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github} from "@site/src/components/codetabs" - In this section of the tutorial, you'll learn how to create a basic auction smart contract from scratch. The contract will allow users to place bids and track the highest bidder. We'll also look at how to create and run tests, and then how to deploy and interact with the contract on testnet. +--- ## Creating a new project -To get started you'll need to create a new NEAR project. +To get started you'll need to create a new NEAR smart contract: - TODO + ``` + npx create-near-app@latest + ``` - In your command line run ``` $ cargo near new auction-contract @@ -38,19 +38,11 @@ To get started you'll need to create a new NEAR project. $ cd auction-contract ``` - Then go ahead and open up a code editor such as VS Code - - ``` - $ code . - ``` - - Enter src > lib.rs, there already exists an example contract there so we'll just go ahead and delete it and start from the start! - - +--- ## Defining the contract structure @@ -88,7 +80,7 @@ Here we define the `Contract` structure that has fields: Since `highest_bid` stores type `Bid` we have a nested structure. Bid itself has fields: - **bidder**: specifies the unique human readable NEAR account ID of the bidder. -- **bid**: specifies the bid amount in YoctoNEAR (10^-24 NEAR). +- **bid**: specifies the bid amount in yoctoNEAR (10^-24 NEAR). Bid has macros: - **serializers**: enables both borsh and JSON serialization and decentralization. diff --git a/website/sidebars.js b/website/sidebars.js index d03fcef679a..3f7e1e27180 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -281,7 +281,7 @@ const sidebar = { }, { "Lake Framework": [ - "concepts/advanced/near-lake-framework", + "concepts/advanced/near-lake-framework", "build/data-infrastructure/lake-framework/near-lake", "build/data-infrastructure/lake-framework/near-lake-state-changes-indexer", "build/data-infrastructure/lake-framework/migrating-to-near-lake-framework", @@ -369,6 +369,15 @@ const sidebar = { "type": "html", "value": " Tutorials " }, + { + "Zero to Hero NEAR": [ + "tutorials/auction/introduction", + "tutorials/auction/basic", + "tutorials/auction/locking-the-contract", + "tutorials/auction/winning-an-nft", + "tutorials/auction/bidding-with-FTs", + ] + }, { "Components": [ "tutorials/near-components/bos-loader", From a79aae126d647d09bb8917b79a67c8df919753da Mon Sep 17 00:00:00 2001 From: Owen <124691791+PiVortex@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:13:16 +0100 Subject: [PATCH 06/32] add updates (#2190) * add auction series * add more content * updates * more additions * accept changes * Monday additions * Tuesday changes --------- Co-authored-by: PiVortex Co-authored-by: Guille --- docs/3.tutorials/auction/0-intro.md | 75 ++---- docs/3.tutorials/auction/1-basic.md | 128 +++++----- docs/3.tutorials/auction/2-locking.md | 195 ++++++++++++--- docs/3.tutorials/auction/3-nft.md | 199 ++++++++++----- docs/3.tutorials/auction/4-ft.md | 333 ++++++++++++++++++++------ website/sidebars.js | 2 +- 6 files changed, 652 insertions(+), 280 deletions(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index 8e42e994113..c40bb62e574 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -7,9 +7,9 @@ sidebar_label: Introduction import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -In this Zero to Hero series, you'll learn everything you need to know about building Web3 applications on NEAR. +In this Zero to Hero series, you'll learn how to build a Web3 application from start to finish on NEAR. -We will start by building a simple auction contract, and slowly build up on it, adding more and more concepts as we go. By the time you finish this tutorial you would have touch on several key primitives and concepts along the way: +We'll start by cloning a simple auction smart contract and slowly add features by introducing new concepts as we go. By the time you finish this tutorial, you will have learned how to use several key primitives and concepts along the way: - Building a smart contract - Deploying and locking a contract @@ -35,48 +35,37 @@ Before starting, make sure to setup your development 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 + ```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 NEAR CLI to deploy and interact with the contract -npm i -g near-cli -``` + # 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 + ```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 + # Contracts will be compiled to wasm, so we need to add the wasm target + rustup target add wasm32-unknown-unknown -# Install NEAR CLI-RS 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 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 -``` + # 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 + ``` - + -:::tip Testnet Account - -There is no need to have a `testnet` account to follow this tutorial. - -However, if you want to create one, you can do so through [a wallet](https://testnet.mynearwallet.com), and use it from the `near-cli` by invoking `near login`. - -::: - - - - --- ## Overview @@ -105,22 +94,4 @@ At the time of this writing, this example works with the following versions: - cargo-near: `0.6.2` - near-cli-rs `0.12.0` -::: - - - - - - - - - TODO - - - - - - - - - \ No newline at end of file +::: \ No newline at end of file diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index babff09f155..615f37eb105 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -1,52 +1,51 @@ --- -id: basic +id: basic-auction title: Basic Auction +sidebar_label: Basic Auction --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github} from "@site/src/components/codetabs" -In this section of the tutorial, you'll learn how to create a basic auction smart contract from scratch. The contract will allow users to place bids and track the highest bidder. We'll also look at how to create and run tests, and then how to deploy and interact with the contract on testnet. +In this section of the tutorial, we'll clone a simple auction smart contract and analyze each section in-depth. The contract allows users to place bids and track the highest bidder. We'll also look at how to test a smart contract and then run tests, and then how to deploy and interact with the contract on testnet. --- -## Creating a new project +## Cloning the contract -To get started you'll need to create a new NEAR smart contract: +To get started we'll clone the [tutorial repo](https://github.com/near-examples/auctions-tutorial) + +``` +$ git clone https://github.com/near-examples/auctions-tutorial.git +``` + +Then enter part one of the tutorial - ``` - npx create-near-app@latest - ``` + TODO - - ``` - $ cargo near new auction-contract - ``` - - Enter your new project - ``` - $ cd auction-contract + cd auctions-tutorial/contract-rs/01-basic-auction ``` + --- -## Defining the contract structure +## The contracts storage -Smart contracts are simply structures that store data and implement methods to mutate and view that data. First, we'll define what data we are storing. We'll also need some imports along the way. +Smart contracts are simply structures that store data and implement methods to mutate and view that data. Let's see what data the contract is storing. @@ -58,22 +57,24 @@ Smart contracts are simply structures that store data and implement methods to m + Enter src > lib.rs + + start="5" end="17" /> -Here we define the `Contract` structure that has fields: +The `Contract` structure that has fields: - **highest_bid**: stores the information about the highest bidder in another struct named Bid. - **auction_end_time**: specifies the block_timestamp (which will be explained later) at which the auction will end. `Contract` also has the macros: -- **contract_state**: enables borsh serialization and decentralization to read and write the structure to the blockchain in binary form and generates a schema. -- **serializers**: enables both borsh and JSON serialization and decentralization. The JSON serializer allows for the whole contract state to be outputted in JSON form. Although borsh is already enabled by contract_state, we have to specify it again in serializers if we want to add JSON. +- **contract_state**: enables borsh serialization and deserialization to read and write the structure to the blockchain in binary form and generates a schema. +- **serializers**: enables both borsh and JSON serialization and deserialization. The JSON serializer allows for the whole contract state to be outputted in JSON form. Although borsh is already enabled by contract_state, we have to specify it again in serializers if we want to add JSON. - **PanicOnDefault**: forces the contract to have custom initialization (we will see this later).   @@ -83,15 +84,16 @@ Since `highest_bid` stores type `Bid` we have a nested structure. Bid itself has - **bid**: specifies the bid amount in yoctoNEAR (10^-24 NEAR). Bid has macros: -- **serializers**: enables both borsh and JSON serialization and decentralization. +- **serializers**: enables both borsh and JSON serialization and deserialization. - **Clone**: allows a Bid object to be duplicated. +--- ## Initializing the contract -Now we have defined the data structures for the contract we next define its methods. +Now let's take a look at the contract's methods -First, we set up an initialization function that will determine the initial state of the contract. As stated earlier, this contract requires custom initialization where the user is required to input parameters on initialization. +First, the contract implements an initialization function that determines the initial state of the contract. As stated earlier, this contract requires custom initialization meaning the user is required to input parameters on initialization. @@ -113,14 +115,15 @@ First, we set up an initialization function that will determine the initial stat -We decorate the implementation of Contract with the `near` macro to declare that the methods inside of Contract can be called by the outside world. The `init` function has macros `init` to denote it is an initialization function and `private` to restrict the function to only be called by the account on which the contract is deployed. The function takes the parameter `end_time` which specifies the block_timestamp of when the auction will end. +The implementation of Contract is decorated with the `near` macro to declare that the methods inside of Contract can be called by the outside world. The `init` function has macros `init` to denote it is an initialization function and `private` to restrict the function to only be called by the account on which the contract is deployed. The function takes the parameter `end_time` which specifies the block_timestamp of when the auction will end. When this function is called the `auction_end_time` will be set and `highest_bid` will be set with `bid` as 1 YoctoNEAR (the smallest unit of NEAR) and `bidder` as the account on which the contract is deployed so that if no one bids no one will win the auction. +--- ## Placing a bid -An auction isn't an auction if you can't place a bid! We'll now add a method that allows a user to place a bid by attaching NEAR tokens to the method call. The method should check whether the auction is still ongoing, check whether their bid is higher than the previous and if it meets both criteria it should update the `highest_bid` field accordingly and return the funds back to the previous bidder. +An auction isn't an auction if you can't place a bid! The contract has a `bid` method that allows a user to place a bid by attaching NEAR tokens to the method call. A successful implementation of the `bid` method will check whether the auction is still ongoing, check whether their bid is higher than the previous and if it meets both criteria it will update the `highest_bid` field accordingly and return tokens back to the previous bidder. @@ -140,8 +143,9 @@ An auction isn't an auction if you can't place a bid! We'll now add a method tha +--- -When the user calls the `bid` method they will transfer NEAR tokens to the contract; to enable the transfer of NEAR tokens we need to decorate the method with the `payable` macro. This method is what we call a "change method", also known as a "call method"; we know this since it passes a mutable reference to itself ($mut self). Change methods require the user to pay gas and can change the state of the contract. +When the user calls the `bid` method they will transfer NEAR tokens to the contract; to enable the transfer of NEAR tokens we need to decorate the method with the `payable` macro. This method is what we call a "change method", also known as a "call method"; we know this since it passes a mutable reference to itself ($mut self). Change methods, by name, change the state of the contract and require the user to attach gas. - First, the method checks that the auction is still ongoing by checking that the current `block_timestamp` is less than the auction end time. `block_timestamp` gives the time as the number of nanoseconds since January 1, 1970, 0:00:00 UTC. Here we use `require` as opposed to `assert` as it reduces the contract size by not including file and rust-specific data in the panic message. @@ -151,10 +155,11 @@ When the user calls the `bid` method they will transfer NEAR tokens to the contr - The contract then returns a `Promise` that will transfer NEAR of the amount `last_bid` to the `last_bidder`. +--- ## Viewing the contract state -We also implement "view methods" into this contract; this allows the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. We know it is a view method since it takes an immutable reference to self (&self). An example use case here is being able to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bet. +The contract also implements "view methods"; this allows the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. View methods take an immutable reference to self (&self). An example use case here is being able to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bet. @@ -175,18 +180,21 @@ We also implement "view methods" into this contract; this allows the user to vie -In the repo there are further view methods that follow a very similar structure. +The contract has further view methods that follow a very similar structure. + +--- +# Testing the contract -# Adding tests +Ok, now we've seen the contract we need to make sure it works as expected; this is done through testing. It's good practice to implement exhaustive tests so you can ensure that any little change to your code down the line doesn't break your contract. For further information on testing take a look at [this](../../2.build/2.smart-contracts/testing/introduction.md) section in the docs. -Ok now we have the contract we need to make sure it works as expected and is secure. It's good practice to implement exhaustive tests so you can ensure that any little to your change down the line doesn't break your contract. For further information on testing take a look at [this](../../2.build/2.smart-contracts/testing/introduction.md) section in the docs. +--- ## Unit tests -Unit tests allow you to test contract methods individually. For each change method, we should implement a unit test and check that it changes the state of the contract in the expected way with a view method. +Unit tests allow you to test contract methods individually. For each change method, there should be a unit test and check that it changes the state of the contract in the expected way. -Let's create a test to ensure that the contract is initialized properly. +Let's look at a test that ensures the contract is initialized properly. @@ -207,11 +215,11 @@ Let's create a test to ensure that the contract is initialized properly. -- First, we create a new contract and initialize it with an auction_end_time of 1000 (in reality this is very low but it doesn't matter here). +- First, the test creates a new contract and initializse it with an `auction_end_time` of 1000 (in reality this is very low but it doesn't matter in this context). -- Next, we call get_highest_bid and check that the bidder and bid are as expected. +- Next, the test calls `get_highest_bid` and check that the `bidder` and `bid` are as expected. -- Lastly, we call get_auction_end_time and check that the end_time is as expected. +- Lastly, the test calls `get_auction_end_time` and checks that the `end_time` is as expected. Simple enough. @@ -239,21 +247,16 @@ We'll next implement a test to ensure the `bid` method works as expected. Since this method requires an attached deposit and gets the predecessor's account ID we create context with `VMContextBuilder` to set the predecessor to "bob.near" and the deposit amount to one NEAR token when we call bid. +--- ## Integration tests Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment. -For testing, we're going to have to import some crates. We will need to import the Bid struct from our contract so in our `Cargo.toml` we'll rename the package "auction-contract" and add `chrono` to our dependencies - -``` -chrono = "0.4.38" -``` - -Enter tests > test_basics.rs, there already exists an example test there so we'll just go ahead and delete it. +For testing, you'll notice that some crates are imported. The Bid structure is imported from auction-contract which you'll notice is the name of the package in `Cargo.toml` and UTC is imported from chrono which is listed as a dependency in the toml. -First, we need to define our test function, create the sandbox testing environment and get the compiled auction contract to interact with. +First, in the tests file a test function is defined, a sandbox testing environment is created and the compiled auction contract is fetched to be interacted with. @@ -265,7 +268,9 @@ First, we need to define our test function, create the sandbox testing environme - test_basics.rs + + @@ -274,7 +279,7 @@ First, we need to define our test function, create the sandbox testing environme -Next, we'll create a couple of user accounts that we'll use to send transactions from. We'll give each an account ID and an initial balance: +Next, the test creates a couple of user accounts that are used to send transactions from. Each account is given an account ID and an initial balance. @@ -286,7 +291,7 @@ Next, we'll create a couple of user accounts that we'll use to send transactions - @@ -294,8 +299,7 @@ Next, we'll create a couple of user accounts that we'll use to send transactions - -We'll do the same for the "contract" account and deploy the contract WASM to it. +Likewise, a "contract" account is created but here the contract WASM is deployed to it. @@ -307,7 +311,7 @@ We'll do the same for the "contract" account and deploy the contract WASM to it. - @@ -315,8 +319,7 @@ We'll do the same for the "contract" account and deploy the contract WASM to it. - -When we initialize the contract we need to pass `end_time`, we'll set it to 60 seconds in the future. After calling `init` we'll check that the transaction was successful. +When the contract is initialized `end_time` is passed, which is set to 60 seconds in the future. After calling `init`, the test checks that the transaction was successful. @@ -328,7 +331,7 @@ When we initialize the contract we need to pass `end_time`, we'll set it to 60 s - @@ -336,8 +339,7 @@ When we initialize the contract we need to pass `end_time`, we'll set it to 60 s - -Now we have the contract deployed and initialized we can make bids to the contract from an account and check that the state changes as intended. +Now the contract is deployed and initialized, bids are made to the contract from the account and it's checked that the state changes as intended. @@ -349,7 +351,7 @@ Now we have the contract deployed and initialized we can make bids to the contra - @@ -358,7 +360,7 @@ Now we have the contract deployed and initialized we can make bids to the contra -When testing we should also check that the contract does not allow invalid calls. We will check that the contract doesn't allow us to make a bid with less NEAR tokens than the previous. +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. @@ -370,7 +372,7 @@ When testing we should also check that the contract does not allow invalid calls - @@ -378,10 +380,11 @@ When testing we should also check that the contract does not allow invalid calls +--- ## Testing and deploying -Now we have the tests written let's actually test it. +Cool, we've seen how tests are written. Now let's actually run the tests. @@ -420,7 +423,7 @@ Now we have the tests written let's actually test it. -Now the contract is deployed and initialized we can send transaction to it using the CLI. NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands. But here you can use the following full commands to call the contract methods: +Now the contract is deployed and initialized we can send transactions to it using the CLI. NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands. But here you can use the following full commands to call the contract methods: Call `bid`, you may want to create another testnet account for the signer @@ -434,10 +437,13 @@ Call `get_highest_bid` $ near contract call-function as-read-only get_highest_bid json-args {} network-config testnet now ``` +--- ## Conclusion -TODO +In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data and views the data. We also looked at how both unit and integration tests are written and how to execute them. Finally, we saw how to compile, deploy and interact with the contract through the CLI on testnet. In the [next part](./2-locking.md), we'll edit the existing contract and add a new method so the contract can be locked. We'll also update the tests accordingly to reflect the changes to the contract. + + diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md index aa2fc2a64f6..9dd6617019b 100644 --- a/docs/3.tutorials/auction/2-locking.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -4,50 +4,179 @@ title: Locking the contract sidebar_label: Locking the contract --- -Up till now to claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security flaw. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus 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 funds. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {Github} from "@site/src/components/codetabs" -  +In the basic contract claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security issue. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus 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 funds. + +--- ## Adding an auctioneer -We want to restrict this method to claim the funds to only be called by the individual or entity that sets up the auction. To do this we now initialize the contract with an `auctioneer` (type AccountId). - -```rust - #[init] - #[private] // only callable by the contract's account - pub fn init(end_time: U64, auctioneer: AccountId) -> Self { - Self { - highest_bid: Bid { - bidder: env::current_account_id(), - bid: NearToken::from_yoctonear(1), - }, - auction_end_time: end_time, - claimed: false, - auctioneer, - } - } -``` +We want to restrict this method, to claim the funds, to only be called by the individual or entity that sets up the auction. To do this we now change the `init` method to initialize the contract with an `auctioneer` (type AccountId). + + + + + + TODO + + + + + + + + + + Let's also introduce a boolean `claimed` field to track whether the funds 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 funds to the auctioneer. We implement this as so: +The `claim` method should only be callable when the auction is over, can only be executed once and should transfer the funds to the auctioneer. We'll implement this as so: + + + + + + TODO + + + + + + + + + + + +--- + +## Updating the tests + +If we update our contract to add a new functionality then we should test it. Also if the inputs/outputs of our methods change we should update our existing tests accordingly. + +--- + +## Unit tests + +TODO + +--- + +## Integration tests + +Now we have added the `claim` method we need to initialize our contract with an auctioneer. We just create a new account, as in part 1, called "auctioneer" and call the `init` function with this account as an argument. + + + + + + TODO + + + + + + + + -```rust - pub fn claim(&mut self) -> Promise { - require!( - env::block_timestamp() > self.auction_end_time.into(), - "Auction has not ended yet" - ); + - require!(!self.claimed, "Auction has already been claimed"); - self.claimed = true; +We will now test the `claim` method itself. First, we try to call claim without changing the sandbox environment. We expect this to fail since the current time is less than the auction's `end_time`. + + + + + + TODO + + + + + + + + + + + +Next, we will call claim again but first, we need to advance the time inside of the sandbox. + + + + + + TODO + + + + + + + + + + + +Once an auction is properly claimed the NEAR tokens are sent to the auctioneer. We should check that the auctioneer account has received these tokens. + + + + + + TODO + + + + + + + + + + + +Whilst the auctioneer starts with 5 tokens and receives 2 from the auction, note that we don't check for an account balance of strictly 7 since some tokens are used for gas when calling claim. + +We also can add one more test to check that the auction cannot be claimed twice. + +--- + +## Deploying and locking + +Go ahead and test, build and deploy your new contract as in part 1. Remember to add the "auctioneer" argument now when initializing. + +Now we have an auctioneer and the claim method we can deploy the contact 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 > specify your account ID > β†’ (to delete all) > + +:::caution +Be extra careful to delete the keys from the correct account as you'll never be able to access the account again! +::: + +--- - // Transfer tokens to the auctioneer - Promise::new(self.auctioneer.clone()).transfer(self.highest_bid.bid) - } -``` +## Conclusion +In this part of the tutorial, we've learned how to lock a contract by creating a new method to claim tokens, specifying an account on initialization that can 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. \ No newline at end of file diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index a6ba83dd1f7..d55c54c1b44 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -4,87 +4,174 @@ title: Winning an NFT sidebar_label: Winning an NFT --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {Github} 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 with audited standards so the prize will exist without the auction contract. Let's get to work! -  +--- ## Defining the NFT interface -Since NFTs follow standards all NFT contracts implement the same interface that defines what methods can be called, what parameters these methods take and what the method will return. When we are making calls to other contracts we write the interface of each method in a separate file `ext.rs` as a `trait`. -```rust -use near_sdk::{ext_contract, AccountId}; + + + + + TODO -use crate::TokenId; + -// NFT interface for cross-contract calls -#[ext_contract(nft_contract)] -trait NFT { - fn nft_transfer(&self, receiver_id: AccountId, token_id: TokenId); -} -``` + -`nft_transfer` transfers the ownership of an NFT from one account ID to another. The method takes the arguments `receiver_id` dictating who will now own the NFT and the `token_id` specifying which token in the NFT contract is having its ownership transferred. The `ext_contract` macro converts the NFT trait into a module with the method `nft_transfer`. + Since NFTs follow standards, all NFT contracts implement the same interface that sets out the contract's methods, what parameters these methods take and what the method will return. When we are making calls to other contracts we write the interface of each method in a separate file `ext.rs` as a `trait`. -Note that we declare `TokenId` in our main file `lib.rs`. A type alias is used for future-proofing. + -```rust -pub type TokenId = String; -``` + `nft_transfer` transfers the ownership of an NFT from one account ID to another. The method takes the arguments `receiver_id` dictating who will now own the NFT and the `token_id` specifying which token in the NFT contract is having its ownership transferred. The `ext_contract` macro converts the NFT trait into a module with the method `nft_transfer`. -We import our interface into `lib.rs` as such: + Note that we declare `TokenId` in our main file `lib.rs`. The NFT standards use a type alias for future-proofing. -```rust -pub mod ext; -pub use crate::ext::*; -``` + -  + We import our interface into `lib.rs` as such: + + + + + + + +--- ## 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` as such: -```rust - #[init] - #[private] // only callable by the contract's account - pub fn init( - end_time: U64, - auctioneer: AccountId, - nft_contract: AccountId, - token_id: TokenId, - ) -> Self { - Self { - highest_bid: Bid { - bidder: env::current_account_id(), - bid: NearToken::from_yoctonear(1), - }, - auction_end_time: end_time, - auctioneer, - claimed: false, - nft_contract, - token_id, - } - } -``` - -  + + + + + + TODO + + + + + + + + + + + +--- ## 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. We make a cross-contract call to the NFT contract via the external method we defined at the end of `claim`. + + + + + TODO + + + + + + + When the auction is ended - by calling the method `claim` - the NFT needs to be transferred to the highest bidder. We make a cross-contract call to the NFT contract, via the external method we defined, at the end of `claim`. + + -```rust - nft_contract::ext(self.nft_contract.clone()) - .with_static_gas(Gas::from_tgas(30)) - .with_attached_deposit(NearToken::from_yoctonear(1)) - .nft_transfer(self.highest_bid.bidder.clone(), self.token_id.clone()); -``` + -When calling this external method we specify the NFT contract name, that we are attaching 30 Tgas to the call, that we are attaching 1 YoctoNEAR to the call (since the NFT contract requires this for [security reasons](../../2.build/2.smart-contracts/security/one_yocto.md)), and the arguments `reciever_id` and `token_id`. + -  +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 (since the NFT contract requires this for [security reasons](../../2.build/2.smart-contracts/security/one_yocto.md)), and the arguments `reciever_id` and `token_id`. + +--- ## 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 with an NFT that the contract doesn't own 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 by only displaying valid auctions. \ No newline at end of file +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 with an NFT that the contract doesn't own 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. + +--- + +## Updating the tests + +Again let's go and update our tests to reflect the changes we've made to our contract. + +--- + +## Unit tests + +TODO + +--- + +## Integration tests + +In our integration tests, we're now going to be interacting with two contracts; the auction contract and an NFT contract. Integration tests are perfect for testing multiple contracts that interact. + +In our tests folder, we need the WASM for an NFT contract. **Add more here** + +We deploy the NFT contract WASM using `dev_deploy` which creates an account with a random ID and deploys the contract to it. + + + + + + TODO + + + + + + + + + + + +To get the NFT to be auctioned, the auction contract calls the NFT contract to mint a new NFT with the provided data. + + + + + + TODO + + + + + + + + + + + +After `claim` is called, the tests should verify that the auction winner now owns the NFT. This is done by calling `nt_token` on the NFT contract and specifying the token ID. + +--- + +## Conclusion + +This 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 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. + diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index aaf950d5a96..38d8e051b0a 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -4,55 +4,62 @@ title: Bidding with FTs sidebar_label: Bidding with FTs --- -To make this contract more interesting we're going to 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, or if a project like Ref Finance were holding their own auction and would want the auction to happen in their project's token $REF. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {Github} from "@site/src/components/codetabs" -  +To make this contract more interesting we're going to 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 usecase is if a project like Ref Finance were holding their own auction and would want the auction to happen in their project's token $REF. + +--- ## Defining the FT interface -Unlike NEAR tokens, fungible token amounts are not tracked on the user's account but rather they have their own contract (as do NFTs); so we're going to have to define an interface to interact with the FT contract. We will use the method `fr_transfer` to send fungible tokens to a specified account from our contract. -```rust -// FT interface for cross-contract calls -#[ext_contract(ft_contract)] -trait FT { - fn ft_transfer(&self, receiver_id: AccountId, amount: U128) -> String; -} -``` + + + + + TODO + + + + -  + Unlike NEAR tokens, fungible token amounts are not tracked on the user's account but rather they have their own contract (as do NFTs); so we're going to have to define an interface to interact with the FT contract in `ext.rs`. We will use the method `ft_transfer` to send fungible tokens to a specified account from our contract. + + + + + + + +--- ## Specifying the FT contract -We want our bids to only happen in one type of fungible token; accepting many 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 price for the NFT. - -```rust - #[init] - #[private] // only callable by the contract's account - pub fn init( - end_time: U64, - auctioneer: AccountId, - ft_contract: AccountId, - nft_contract: AccountId, - token_id: TokenId, - starting_price: U128, - ) -> Self { - Self { - highest_bid: Bid { - bidder: env::current_account_id(), - bid: starting_price, - }, - auction_end_time: end_time, - auctioneer, - claimed: false, - ft_contract, - nft_contract, - token_id, - } - } -``` +We want our bids to only happen 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 price for the NFT. + + + + -  + TODO + + + + + + + + + + + +--- ## Accepting bids in FTs @@ -62,67 +69,234 @@ For making bids in NEAR we call the contract directly and add NEAR tokens to the Thus we swap our `bid` method for `ft_on_transfer`. -```rust - pub fn ft_on_transfer(&mut self, sender_id: AccountId, amount: U128, msg: String) -> U128 { -``` + -We do a check to confirm that the user is using the required fungible token contract by checking the predecessor's account ID. Since it is the FT contract that called the auction contract the predecessor is the FT contract. + -```rust - let ft = env::predecessor_account_id(); - require!(ft == self.ft_contract, "The token is not supported"); -``` + TODO -The bidder's account ID is now given as an argument `sender_id`. + -```rust - self.highest_bid = Bid { - bidder: sender_id, - bid: amount, - }; -``` + + + + + + + + + +We do a check to confirm that the user is using the required fungible token contract by checking the predecessor's account ID. Since it is the FT contract that called the auction contract, the predecessor is now the FT contract. + + + + + + TODO + + + + + + + + + + + +The bidder's account ID is now given by the argument `sender_id`. + + + + + + TODO + + + + + + + + + + Now when we want to return the funds to the previous bidder we make a cross-contract call the the FT contract. We shouldn't transfer funds if it is the first bid so we'll implement a check for that as well. -```rust - if last_bidder != env::current_account_id() { - ft_contract::ext(self.ft_contract.clone()) - .with_attached_deposit(NearToken::from_yoctonear(1)) - .with_static_gas(Gas::from_tgas(30)) - .ft_transfer(last_bidder, last_bid); - } -``` + + + + + TODO + + + + + + + + + + The method `ft_on_transfer` needs to return the number of unused tokens in the call so the FT contract can refund the sender. We will always use the full amount of tokens in this call so we simply return 0. -```rust -U128(0) -``` + + + + + TODO + + + + + + -  + + + + +--- ## Claiming the FTs When the auction is complete we need to send the fungible tokens to the auctioneer, we implement a similar call as when we were returning the funds just changing the arguments. -```rust - ft_contract::ext(self.ft_contract.clone()) - .with_attached_deposit(NearToken::from_yoctonear(1)) - .with_static_gas(Gas::from_tgas(30)) - .ft_transfer(self.auctioneer.clone(), self.highest_bid.bid); -``` + + + + + TODO + + + + -  + + + + + + +--- ## Registering the user 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. 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 need 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. -  +--- + +## Updating the tests + + --- -There we have it, a completed auction smart contract! +## Unit tests + +TODO + +--- + +## Integration tests + +Just as with the NFT contract, we will deploy an FT contract in workspaces using a WASM file from **ADD MORE**. + +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. + + + + + + TODO + + + + + + + + + + + +As mentioned previously, to own FTs you have to be registered in the FT contract. So let's register all the accounts that are going to interact with FTs. + + + + + + TODO + + + + + + + + + + + +Then we will transfer the bidders FTs so they can use them to bid. + + + + + + TODO + + + + + + + + + + + +As stated previously, to bid on the auction the bidder now calls `ft_transfer_call` on the FT contract and specifies the auction contract as an argument. + + + + + + TODO + + + + + + + + + + + +--- ## Auction architecture @@ -132,8 +306,13 @@ In such case, the Contract struct would be a map of auctions. We would implement ```rust pub struct Contract { - auctions: unorderedMap + auctions: IterableMap } ``` -However, this architecture could be deemed as 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. \ No newline at end of file +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 + diff --git a/website/sidebars.js b/website/sidebars.js index 3f7e1e27180..e68b70a82a6 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -372,7 +372,7 @@ const sidebar = { { "Zero to Hero NEAR": [ "tutorials/auction/introduction", - "tutorials/auction/basic", + "tutorials/auction/basic-auction", "tutorials/auction/locking-the-contract", "tutorials/auction/winning-an-nft", "tutorials/auction/bidding-with-FTs", From e40022cf469e8f1a5af836532b87d6d92a2f1a28 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Tue, 30 Jul 2024 17:02:36 +0200 Subject: [PATCH 07/32] wip: proposal --- docs/3.tutorials/auction/1-basic.md | 49 +++++++++++++++++------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index 615f37eb105..4e952030dd0 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -16,55 +16,64 @@ In this section of the tutorial, we'll clone a simple auction smart contract and To get started we'll clone the [tutorial repo](https://github.com/near-examples/auctions-tutorial) -``` -$ git clone https://github.com/near-examples/auctions-tutorial.git -``` - -Then enter part one of the tutorial - - - TODO + ``` + git clone git@github.com:near-examples/auctions-tutorial.git - + cd contract-ts/01-basic-auction + ``` + ``` - cd auctions-tutorial/contract-rs/01-basic-auction + git clone git@github.com:near-examples/auctions-tutorial.git + + cd contract-rs/01-basic-auction ``` - +The repository is structured in three folders, two contain the same smart contracts written in JavaScript and Rust and the third contains a frontend that interacts with the contracts. + +Navigate to the folder of the language you prefer, and then to the `01-basic-auction` folder. --- -## The contracts storage +## Anatomy of the Contract -Smart contracts are simply structures that store data and implement methods to mutate and view that data. Let's see what data the contract is storing. +Let's take a look at the contract structure. The contract is a simple auction contract that allows users to place bids and track the highest bidder. - - +### Contract's Definition - TODO +The contract stores two main attributes: what was the highest bid so far, and when the auction will end. To simplify storing the highest bid, the contract uses an auxiliary structure called `Bid` that contains the bidder's account ID and the amount they bid. - + + + + - Enter src > lib.rs - - +
+ + Macros + This is a dropdown + +
+ +
From ea3bfe608922eb3d733ef93d0df98806c7784b39 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Wed, 31 Jul 2024 16:09:39 +0100 Subject: [PATCH 08/32] Wednesday changes --- docs/3.tutorials/auction/1-basic.md | 388 ++++++++++++++------------ docs/3.tutorials/auction/2-locking.md | 102 ++----- 2 files changed, 227 insertions(+), 263 deletions(-) diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index 4e952030dd0..d8291bf642f 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -8,13 +8,13 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github} from "@site/src/components/codetabs" -In this section of the tutorial, we'll clone a simple auction smart contract and analyze each section in-depth. The contract allows users to place bids and track the highest bidder. We'll also look at how to test a smart contract and then run tests, and then how to deploy and interact with the contract on testnet. +In this section of the tutorial, we'll clone a simple auction smart contract and analyze each section of the contract in-depth. We'll also look at how to test a smart contract, and then how to deploy and interact with the contract on testnet. --- ## Cloning the contract -To get started we'll clone the [tutorial repo](https://github.com/near-examples/auctions-tutorial) +To get started we'll clone the [tutorial repo](https://github.com/near-examples/auctions-tutorial). @@ -50,14 +50,30 @@ Let's take a look at the contract structure. The contract is a simple auction co ### Contract's Definition -The contract stores two main attributes: what was the highest bid so far, and when the auction will end. To simplify storing the highest bid, the contract uses an auxiliary structure called `Bid` that contains the bidder's account ID and the amount they bid. +The contract stores two main fields: what was the highest bid so far, and when the auction will end. To simplify storing the highest bid, the contract uses an auxillary structure called `Bid` that contains the bidder's account ID and the amount they bid. + +
+ + Decorators + + Let's take a closer look at the decorator on `AuctionContract` and what it does: + + - `NearBindgen` extends the class and its methods to make the contract serializable and deserializable to read and write to the blockchain in binary form. It also exposes the contract's methods to the outside world, allowing the contract to be interacted with. + - `requireInit` specifies that the contract has to be initialized using a method. + +
+ + - `bid` is typed `BigInt` for storing a number of $NEAR tokens in `yoctonear` (10^-24 NEAR). + - `bidder` is typed `AccountId` to automatically check whether the account address is valid. + +
@@ -69,47 +85,54 @@ The contract stores two main attributes: what was the highest bid so far, and wh Macros - This is a dropdown - - - - -
+ Let's take a closer look at the macros the structures implement and what they do. + `Contract` has the macros: + - **contract_state**: enables borsh serialization and deserialization to read and write the structure to the blockchain in binary form, and generates a schema for the structure. + - **serializers**: enables both borsh and JSON serialization and deserialization. The JSON serializer allows for the whole contract state to be outputted in JSON form. Although borsh is already enabled by contract_state, we have to specify it again in serializers if we want to add JSON. + - **PanicOnDefault**: forces the contract to have custom initialization (we will see this later). -The `Contract` structure that has fields: -- **highest_bid**: stores the information about the highest bidder in another struct named Bid. -- **auction_end_time**: specifies the block_timestamp (which will be explained later) at which the auction will end. + `Bid` has the macros: + - **serializers**: enables both borsh and JSON serialization and deserialization. + - **Clone**: allows a Bid object to be duplicated. -`Contract` also has the macros: -- **contract_state**: enables borsh serialization and deserialization to read and write the structure to the blockchain in binary form and generates a schema. -- **serializers**: enables both borsh and JSON serialization and deserialization. The JSON serializer allows for the whole contract state to be outputted in JSON form. Although borsh is already enabled by contract_state, we have to specify it again in serializers if we want to add JSON. -- **PanicOnDefault**: forces the contract to have custom initialization (we will see this later). + -  + - `bid` is typed `NearToken` for storing a number of $NEAR tokens. The type makes it easier to handle $NEAR tokens by implementing methods to express the value in `yoctonear` (10^-24 $NEAR), `milinear` and `near`. + - `bidder` is typed `AccountId` to automatically check whether the account address is valid. + - `auction_end_time` is of type `U64`. Since u64 has to be converted to a string for input and output U64 automates the type casting between u64 and string. -Since `highest_bid` stores type `Bid` we have a nested structure. Bid itself has fields: -- **bidder**: specifies the unique human readable NEAR account ID of the bidder. -- **bid**: specifies the bid amount in yoctoNEAR (10^-24 NEAR). +
+
-Bid has macros: -- **serializers**: enables both borsh and JSON serialization and deserialization. -- **Clone**: allows a Bid object to be duplicated. --- -## Initializing the contract +### Initializing the contract -Now let's take a look at the contract's methods +Now let's take a look at the contract's methods. -First, the contract implements an initialization function that determines the initial state of the contract. As stated earlier, this contract requires custom initialization meaning the user is required to input parameters on initialization. +First, the contract has an initialization function that determines the initial state of the contract. This contract requires custom initialization meaning the user is required to input parameters to determine the initial state of the contract. + + +
+ + Decorators + + Let's take a closer look at the decorator on `init` and what it does: + - `initialize` denotes that `init` is an initialization function and can only be called once. + - `privateFunction` is used to restrict the function to only be called by the account on which the contract is deployed. + +
@@ -119,26 +142,43 @@ First, the contract implements an initialization function that determines the in url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L19-L31" start="19" end="31" /> - -
+
+ + Macros + Let's take a closer look at the macros and what they do: -The implementation of Contract is decorated with the `near` macro to declare that the methods inside of Contract can be called by the outside world. The `init` function has macros `init` to denote it is an initialization function and `private` to restrict the function to only be called by the account on which the contract is deployed. The function takes the parameter `end_time` which specifies the block_timestamp of when the auction will end. + - The implementation of `Contract` is decorated with the `near` macro to declare that the methods inside of Contract can be called by the outside world. + - `init` has the macro `init` to denote it is an initialization function and can only be called once. + - `init` has the macro `private` to restrict the function to only be called by the account on which the contract is deployed. -When this function is called the `auction_end_time` will be set and `highest_bid` will be set with `bid` as 1 YoctoNEAR (the smallest unit of NEAR) and `bidder` as the account on which the contract is deployed so that if no one bids no one will win the auction. +
+ + + + +
+ +When this function is called, the caller will decide the `end_time`, `bid` will be set to one yoctoNEAR and the `bidder` will be set to the account on which the contract is deployed so that if no one bids, no one will win the auction. --- -## Placing a bid +### Placing a bid -An auction isn't an auction if you can't place a bid! The contract has a `bid` method that allows a user to place a bid by attaching NEAR tokens to the method call. A successful implementation of the `bid` method will check whether the auction is still ongoing, check whether their bid is higher than the previous and if it meets both criteria it will update the `highest_bid` field accordingly and return tokens back to the previous bidder. +An auction isn't an auction if you can't place a bid! The contract has a `bid` method that allows a user to place a bid by attaching $NEAR tokens to the method call. A successful implementation of the `bid` method will check whether the auction is still ongoing, check whether their bid is higher than the previous and, if it meets both criteria, it will update the `highest_bid` field accordingly and return tokens back to the previous bidder. - TODO + + + When the user calls the `bid` method they will transfer $NEAR tokens to the contract; to enable the transfer of $NEAR tokens we set `payableFunction` to `true`. This method is what we call a "change method", also known as a "call method"; we know this as it has the `call` decorator. Change methods, by name, change the state of the contract and require the user to attach gas. + + There are some specifics to this method that we should take a closer look at: @@ -148,38 +188,42 @@ An auction isn't an auction if you can't place a bid! The contract has a `bid` m url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L33-L59" start="33" end="59" /> - + When the user calls the `bid` method they will transfer $NEAR tokens to the contract; to enable the transfer of $NEAR tokens we need to decorate the method with the `payable` macro. This method is what we call a "change method", also known as a "call method"; we know this since it passes a mutable reference to `self` ($mut self). Change methods, by name, change the state of the contract and require the user to attach gas. - - ---- + There are some specifics to this method that we should take a closer look at: -When the user calls the `bid` method they will transfer NEAR tokens to the contract; to enable the transfer of NEAR tokens we need to decorate the method with the `payable` macro. This method is what we call a "change method", also known as a "call method"; we know this since it passes a mutable reference to itself ($mut self). Change methods, by name, change the state of the contract and require the user to attach gas. + - We use `require` as opposed to `assert` as it reduces the contract size by not including file and rust-specific data in the panic message. -- First, the method checks that the auction is still ongoing by checking that the current `block_timestamp` is less than the auction end time. `block_timestamp` gives the time as the number of nanoseconds since January 1, 1970, 0:00:00 UTC. Here we use `require` as opposed to `assert` as it reduces the contract size by not including file and rust-specific data in the panic message. - -- Next, the method sets the variables `bid` to the attached NEAR deposit in the method call (type NearToken) and `bidder` to the predecessor's account ID (type AccountId). Note that the predecessor is the account (or contract) that directly calls the bid method, this can be different from the signer (env::signer_account_id) who initially signed the transaction leading to the execution of this method. If pivortex.near calls a contract proxy-contract.near which then calls bid on this contract the predecessor would be proxy-contract.near and the signer would be pivortex.near. For security purposes, we stick with predecessor_account_id here. + -- Then, the method deconstructs the `highest_bid` into two distinct variables and checks that the last bid was higher than the current bid. If so then the `highest_bid` will be updated to the current bid. + -- The contract then returns a `Promise` that will transfer NEAR of the amount `last_bid` to the `last_bidder`. +- The `block timestamp` is used to determine whether the auction is still ongoing. It gives the time as the number of nanoseconds since January 1, 1970, 0:00:00 UTC. +- The `predecessor` gives the account (or contract) that directly called the bid method, this can be different to the signer who initially signed the transaction leading to the execution of this method. If pivortex.near calls a contract proxy-contract.near which then calls bid on this contract the predecessor would be proxy-contract.near and the signer would be pivortex.near. For security purposes, so a malicious contract can't place a bid, we stick with predecessor using here. +- The contract returns a `promise` that will execute the transfer of $NEAR tokens back to the previous bidder. --- -## Viewing the contract state - -The contract also implements "view methods"; this allows the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. View methods take an immutable reference to self (&self). An example use case here is being able to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bet. +### Viewing the contract state + +The contract also implements "view methods"; these allows the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. For example, this could be used to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bid. - TODO + View methods are decorated with `view`. + + + View methods take an immutable reference to `self` (&self). + @@ -193,238 +237,226 @@ The contract has further view methods that follow a very similar structure. --- -# Testing the contract +## Contract tests -Ok, now we've seen the contract we need to make sure it works as expected; this is done through testing. It's good practice to implement exhaustive tests so you can ensure that any little change to your code down the line doesn't break your contract. For further information on testing take a look at [this](../../2.build/2.smart-contracts/testing/introduction.md) section in the docs. +Ok, now we've seen the contract we need to make sure it works as expected; this is done through testing. It's good practice to implement exhaustive tests so you can ensure that any little change to your code down the line doesn't break your contract. --- -## Unit tests - -Unit tests allow you to test contract methods individually. For each change method, there should be a unit test and check that it changes the state of the contract in the expected way. - -Let's look at a test that ensures the contract is initialized properly. - - + + Enter sandbox-test > main.ava.js - TODO + NEAR Workspaces allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment. Here we are using AVA for testing. - + --- - + #### Setup before a test - + Before running a test it's essential to perform some setup steps. + 1) Create a sandbox environment. + 2) Create accounts to interact with the contract. + 3) Deploy the contract. + 4) Initialise the contract + 5) Store the sandbox and accounts in the test's context. - + Here the sandbox is created. - + -- First, the test creates a new contract and initializse it with an `auction_end_time` of 1000 (in reality this is very low but it doesn't matter in this context). + Next, the test creates a couple of user accounts that are used to send transactions from. Each account is given an account ID and an initial balance. -- Next, the test calls `get_highest_bid` and check that the `bidder` and `bid` are as expected. + -- Lastly, the test calls `get_auction_end_time` and checks that the `end_time` is as expected. + Then the contract is deployed to an account named "contract". The compiled contract comes from the test running the `build` script in the `package.json` file. -Simple enough. + -We'll next implement a test to ensure the `bid` method works as expected. + To initialize the contract, `init` is called with `end_time` set to 60 seconds in the future. + -**Not in repo yet** -```rust - #[test] - fn bid() { - // Create context for the call - let mut builder = VMContextBuilder::new(); - builder.predecessor_account_id("bob.near".parse().unwrap()); - builder.attached_deposit(ONE_NEAR); - testing_env!(builder.build()); + Lastly, the sandbox and accounts are saved in the test's context so they can be used later. - let mut contract = Contract::init(U64::from(1000)); - contract.bid(); + - let first_bid = contract.get_highest_bid(); - assert_eq!(first_bid.bidder, "bob.near"); - assert_eq!(first_bid.bid, ONE_NEAR); - } -``` + --- -Since this method requires an attached deposit and gets the predecessor's account ID we create context with `VMContextBuilder` to set the predecessor to "bob.near" and the deposit amount to one NEAR token when we call bid. + #### Tests ---- + In our file there are three different tests: + 1) One bid is placed. + 2) Two bids are placed. + 3) A bid is placed, the auction ends, and a second bid is attempted. -## Integration tests + We'll take a look at the last two. -Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment. + In this test, it checks that the highest bidder is properly recorded when two bids are placed and that the lesser bidder is refunded. -For testing, you'll notice that some crates are imported. The Bid structure is imported from auction-contract which you'll notice is the name of the package in `Cargo.toml` and UTC is imported from chrono which is listed as a dependency in the toml. + + In the next test it simply checks that bids cannot be placed after the auction closes. Time in the sandbox is moved forward using `fastForward`. -First, in the tests file a test function is defined, a sandbox testing environment is created and the compiled auction contract is fetched to be interacted with. + - + --- - + #### Closing the sandbox - TODO + After a test is run the sandbox should be closed. + + - Enter tests > test_basics.rs - - - - + #### Unit tests - + Unit tests are used to test contract methods individually. Let's look at a test that ensures the contract is initialized properly. + -Next, the test creates a couple of user accounts that are used to send transactions from. Each account is given an account ID and an initial balance. + - First, the test creates a new contract and initializes it with an `auction_end_time` of 1000 (in reality this is very low but it doesn't matter in this context). - + - Next, the test calls `get_highest_bid` and checks that the `bidder` and `bid` are as expected. - - - TODO + - Lastly, the test calls `get_auction_end_time` and checks that the `end_time` is as expected. - + Simple enough! - + --- - + #### Integration tests - + Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment where, for example, accounts have trackable balances. - + Enter tests > test_basics.rs -Likewise, a "contract" account is created but here the contract WASM is deployed to it. + First, the sandbox testing environment is created and the compiled auction contract is fetched. - + - - TODO + Next, the test creates a couple of user accounts that are used to send transactions from. Each account is given an account ID and an initial balance. - + - + Likewise, a "contract" account is created and the contract WASM is deployed to it. - - - - -When the contract is initialized `end_time` is passed, which is set to 60 seconds in the future. After calling `init`, the test checks that the transaction was successful. - - - - - - TODO - - - - + To initialize the contract `init` is called with `end_time` set to 60 seconds in the future. Afterward, it's checked that the transaction was successful. - - - -Now the contract is deployed and initialized, bids are made to the contract from the account and it's checked that the state changes as intended. - - + Now the contract is deployed and initialized, bids are made to the contract and it's checked that the state changes as intended. - - - TODO + - - + 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. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L82-L94" + start="82" end="94" /> +--- +## Testing and deploying -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. +Cool, now we've seen how tests are written, let's go ahead and run the tests. We'll then build and deploy the contract to testnet. - TODO + Install dependencies - - - - - + ``` + npm install + ``` - + Run tests and build the contract WASM - + ``` + npm run test + ``` ---- + You'll need a testnet account to deploy the contract to, so if you don't have one you can use -## Testing and deploying + ``` + near account create-account sponsor-by-faucet-service autogenerate-new-keypair + ``` -Cool, we've seen how tests are written. Now let's actually run the tests. + Then deploy and initialize the contract with - - + ``` + near contract deploy use-file build/hello_near.wasm with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet + ``` - TODO + Run tests + ``` - $ cargo test + cargo test ``` - If all the tests are successful we can build and deploy the contract to testnet + If all the tests are successful we can build the contract WASM ``` - $ cargo near build + cargo near build ``` - We'll need a testnet account to deploy the contract to, so if you don't have one you can use + You'll need a testnet account to deploy the contract to, so if you don't have one you can use ``` - $ cargo near create-dev-account use-random-account-id + cargo near create-dev-account use-random-account-id ``` - Then deploy with + Then deploy and initialize the contract with ``` - $ cargo near deploy with-init-call init json-args '{"end_time": "3000000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet + cargo near dep0loy with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet ``` @@ -432,7 +464,7 @@ Cool, we've seen how tests are written. Now let's actually run the tests. -Now the contract is deployed and initialized we can send transactions to it using the CLI. NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands. But here you can use the following full commands to call the contract methods: +Now the contract is deployed and initialized we can send transactions to it using the CLI. NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands. But here you can use the following full commands to call the contract's methods: Call `bid`, you may want to create another testnet account for the signer @@ -450,10 +482,4 @@ $ near contract call-function as-read-only get_highest_bid json-arg ## Conclusion -In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data and views the data. We also looked at how both unit and integration tests are written and how to execute them. Finally, we saw how to compile, deploy and interact with the contract through the CLI on testnet. In the [next part](./2-locking.md), we'll edit the existing contract and add a new method so the contract can be locked. We'll also update the tests accordingly to reflect the changes to the contract. - - - - - - +In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data and views the data. We also looked at how tests are written and how to execute them. Finally, we learned to compile, deploy and interact with the contract through the CLI on testnet. In the [next part](./2-locking.md), we'll edit the existing contract and add a new method so the contract to learn how to lock a 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 index 9dd6617019b..e26b22058af 100644 --- a/docs/3.tutorials/auction/2-locking.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -8,19 +8,21 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github} from "@site/src/components/codetabs" -In the basic contract claim the funds from bids on the contract the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides poor UX but even more importantly it is a security issue. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The whole idea behind smart contracts is that you don't have to trust anyone when interacting with an application, thus 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 funds. +In the basic contract to claim the tokens from the final bid on the contract, the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides a poor UX, but even more importantly it is a security issue. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The core principle of smart contracts is that they eliminate the need for trust when interacting with an application, thus 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 -We want to restrict this method, to claim the funds, to only be called by the individual or entity that sets up the auction. To do this we now change the `init` method to initialize the contract with an `auctioneer` (type AccountId). +We want to restrict the method to claim the tokens, to only the individual or entity that sets up the auction. To do this we now change the `init` method to initialize the contract with an `auctioneer`. - TODO + @@ -46,7 +48,9 @@ The `claim` method should only be callable when the auction is over, can only be - TODO + @@ -64,109 +68,43 @@ The `claim` method should only be callable when the auction is over, can only be ## Updating the tests -If we update our contract to add a new functionality then we should test it. Also if the inputs/outputs of our methods change we should update our existing tests accordingly. +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`. ---- - -## Unit tests -TODO - ---- - -## Integration tests - -Now we have added the `claim` method we need to initialize our contract with an auctioneer. We just create a new account, as in part 1, called "auctioneer" and call the `init` function with this account as an argument. +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. - TODO - - - - + - - - - - - -We will now test the `claim` method itself. First, we try to call claim without changing the sandbox environment. We expect this to fail since the current time is less than the auction's `end_time`. - - - - - - TODO + The auctioneer should now have 2 more $NEAR tokens to match the highest bid on the auction. Note that the balances are rounded since some tokens are used for gas when calling `claim`. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/02-owner-claims-money/tests/test_basics.rs#L114-128" + start="114" end="128" /> - - - - -Next, we will call claim again but first, we need to advance the time inside of the sandbox. - - - - - - TODO - - - - - - - - - - - -Once an auction is properly claimed the NEAR tokens are sent to the auctioneer. We should check that the auctioneer account has received these tokens. - - - - - - TODO - - - - - - + Whilst the auctioneer starts with 5 tokens and receives 2 from the auction, note that we don't check for an account balance of strictly 7 since some tokens are used for gas when calling `claim`. -Whilst the auctioneer starts with 5 tokens and receives 2 from the auction, note that we don't check for an account balance of strictly 7 since some tokens are used for gas when calling claim. - -We also can add one more test to check that the auction cannot be claimed twice. --- ## Deploying and locking -Go ahead and test, build and deploy your new contract as in part 1. Remember to add the "auctioneer" argument now when initializing. +Go ahead and test, build and deploy your new contract, as in part 1. Remember to add the "auctioneer" arguments now when initializing. -Now we have an auctioneer and the claim method we can deploy the contact 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. +Now we have an auctioneer and 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 > specify your account ID > β†’ (to delete all) > @@ -179,4 +117,4 @@ Be extra careful to delete the keys from the correct account as you'll never be ## Conclusion -In this part of the tutorial, we've learned how to lock a contract by creating a new method to claim tokens, specifying an account on initialization that can 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. \ No newline at end of file +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. \ No newline at end of file From 5a2f3a91512e198d1a320fb84a113bd8effc8a39 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Mon, 5 Aug 2024 15:22:52 +0100 Subject: [PATCH 09/32] finish part 1 and part 2 --- docs/3.tutorials/auction/0-intro.md | 56 +++- docs/3.tutorials/auction/1-basic.md | 265 +++++++++++------- docs/3.tutorials/auction/2-locking.md | 29 +- docs/3.tutorials/auction/3-nft.md | 5 - docs/3.tutorials/welcome.md | 4 +- website/sidebars.js | 2 +- .../welcome-pages/near-zero-to-hero.png | Bin 0 -> 40418 bytes 7 files changed, 225 insertions(+), 136 deletions(-) create mode 100644 website/static/docs/assets/welcome-pages/near-zero-to-hero.png diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index c40bb62e574..911eae84db8 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -1,6 +1,6 @@ --- id: introduction -title: Auction Zero to Hero +title: NEAR Zero to Hero sidebar_label: Introduction --- @@ -12,18 +12,22 @@ In this Zero to Hero series, you'll learn how to build a Web3 application from s We'll start by cloning a simple auction smart contract and slowly add features by introducing new concepts as we go. By the time you finish this tutorial, you will have learned how to use several key primitives and concepts along the way: - Building a smart contract +- Testing a contract in a realistic environment - Deploying and locking a contract - Making cross-contract calls - Using Non-Fungible Tokens - Using Fungible Tokens +- Creating a frontend for a contract +- Setting up an indexer +- Creating a factory contract -This tutorial comes with contracts written in both JavaScript and Rust. +In this tutorial, you have the option to follow along in JavaScript or Rust. --- ## Prerequisites -Before starting, make sure to setup your development environment. +Before starting, make sure to set up your development environment.
Working on Windows? @@ -70,28 +74,54 @@ Before starting, make sure to setup your development environment. ## Overview -These are the steps that will bring you from Zero to Hero in no time! πŸ’ͺ +These are the steps that will bring you from **Zero** to **Hero** in no time! πŸ’ͺ | Step | Name | Description | |------|----------------------------------------|-----------------------------------------------------------------| -| 1 | [Basic contract](./1-basic.md) | Learn how to create an auction contract from scratch | +| 1 | [Basic contract](./1-basic.md) | Learn the how a basic smart contract is structured | | 2 | [Locking the contract](./2-locking.md) | Learn to create contracts with no access keys | | 3 | [Winning an NFT](./3-nft.md) | Learn about sending NFTs using cross-contract calls | | 4 | [Bidding with FTs](./4-ft.md) | Learn about sending and receiving FTs | | 5 | [Create a Frontend](#) | Create a frontend to interact with your smart contract | | 6 | [Monitor the Bids](#) | Learn how to use an indexer to monitor your contract's activity | -| 6 | [Factory](#) | Create a contract that will create auctions | +| 7 | [Factory](#) | Create a contract that will create auctions | + +--- + +## Next steps + +Ready to start? Let's jump to the [Basic contract](./1-basic.md) and begin your learning journey! + +If you would like to learn about a specific concept feel free to skip to the relevant section. --- ## Versioning -:::note Versioning for this tutorial -At the time of this writing, this example works with the following versions: + + -- rustc: `1.78.0` -- near-sdk-rs: `5.1.0` -- cargo-near: `0.6.2` -- near-cli-rs `0.12.0` + Versioning for this tutorial + At the time of this writing, this example works with the following versions: -::: \ No newline at end of file + - near-cli-rs: `0.12.0` + - near-sdk-js: `2.0.0` + - near-workspaces-js: `3.5.0` + - node: `21.6.1` + + + + + + Versioning for this tutorial + At the time of this writing, this example works with the following versions: + + - near-cli-rs: `0.12.0` + - near-sdk-rs: `5.1.0` + - near-workspaces-rs: `0.10.0` + - rustc: `1.78.0` + - cargo-near: `0.6.2` + + + + diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index d8291bf642f..76fea8e7eb6 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -70,8 +70,8 @@ The contract stores two main fields: what was the highest bid so far, and when t
- - `bid` is typed `BigInt` for storing a number of $NEAR tokens in `yoctonear` (10^-24 NEAR). - - `bidder` is typed `AccountId` to automatically check whether the account address is valid. + - `bid` is typed `BigInt` which stores a number of $NEAR tokens in `yoctonear` (10^-24 NEAR). + - `bidder` is typed `AccountId` which automatically check whether the account address is valid.
@@ -99,8 +99,8 @@ The contract stores two main fields: what was the highest bid so far, and when t - `bid` is typed `NearToken` for storing a number of $NEAR tokens. The type makes it easier to handle $NEAR tokens by implementing methods to express the value in `yoctonear` (10^-24 $NEAR), `milinear` and `near`. - - `bidder` is typed `AccountId` to automatically check whether the account address is valid. - - `auction_end_time` is of type `U64`. Since u64 has to be converted to a string for input and output U64 automates the type casting between u64 and string. + - `bidder` is typed `AccountId` which automatically check whether the account address is valid. + - `auction_end_time` is of type `U64`. Since u64 has to be converted to a string for input and output, U64 automates the type casting between u64 and string.
@@ -112,7 +112,7 @@ The contract stores two main fields: what was the highest bid so far, and when t Now let's take a look at the contract's methods. -First, the contract has an initialization function that determines the initial state of the contract. This contract requires custom initialization meaning the user is required to input parameters to determine the initial state of the contract. +First, the contract has an initialization method that determines the initial state of the contract. This contract requires custom initialization meaning the user is required to input parameters to determine the initial state. @@ -129,8 +129,8 @@ First, the contract has an initialization function that determines the initial s Let's take a closer look at the decorator on `init` and what it does: - - `initialize` denotes that `init` is an initialization function and can only be called once. - - `privateFunction` is used to restrict the function to only be called by the account on which the contract is deployed. + - `initialize` denotes that `init` is an initialization method meaning it can only be called once and has to be the first method to be called. + - `privateFunction` is used to restrict the method to only be called by the account on which the contract is deployed. @@ -142,7 +142,6 @@ First, the contract has an initialization function that determines the initial s url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L19-L31" start="19" end="31" /> -
Macros @@ -150,17 +149,16 @@ First, the contract has an initialization function that determines the initial s Let's take a closer look at the macros and what they do: - The implementation of `Contract` is decorated with the `near` macro to declare that the methods inside of Contract can be called by the outside world. - - `init` has the macro `init` to denote it is an initialization function and can only be called once. - - `init` has the macro `private` to restrict the function to only be called by the account on which the contract is deployed. + - `init` has the macro `init` to denote it is an initialization method meaning it can only be called once and has to be the first method to be called. + - `init` has the macro `private` to restrict the method to only be called by the account on which the contract is deployed.
-
-When this function is called, the caller will decide the `end_time`, `bid` will be set to one yoctoNEAR and the `bidder` will be set to the account on which the contract is deployed so that if no one bids, no one will win the auction. +When this method is called, the caller will decide the `end_time`, `bid` will be set to one yoctoNEAR and the `bidder` will be set to the account on which the contract is deployed so that if no one bids, no one will win the auction. --- @@ -199,14 +197,14 @@ An auction isn't an auction if you can't place a bid! The contract has a `bid` m - The `block timestamp` is used to determine whether the auction is still ongoing. It gives the time as the number of nanoseconds since January 1, 1970, 0:00:00 UTC. -- The `predecessor` gives the account (or contract) that directly called the bid method, this can be different to the signer who initially signed the transaction leading to the execution of this method. If pivortex.near calls a contract proxy-contract.near which then calls bid on this contract the predecessor would be proxy-contract.near and the signer would be pivortex.near. For security purposes, so a malicious contract can't place a bid, we stick with predecessor using here. -- The contract returns a `promise` that will execute the transfer of $NEAR tokens back to the previous bidder. +- The `predecessor` gives the account (or contract) that directly called the bid method, this can be different to the signer who initially signed the transaction leading to the execution of this method. If pivortex.near calls a contract proxy-contract.near which then calls bid on this contract the predecessor would be proxy-contract.near and the signer would be pivortex.near. For security purposes, so a malicious contract can't place a bid in your name, we stick with predecessor using here. +- The contract returns a `Promise` that will execute the transfer of $NEAR tokens back to the previous bidder. --- ### Viewing the contract state -The contract also implements "view methods"; these allows the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. For example, this could be used to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bid. +The contract also implements "view methods"; these allow the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. For example, this could be used to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bid. @@ -233,7 +231,7 @@ The contract also implements "view methods"; these allows the user to view the d -The contract has further view methods that follow a very similar structure. +The contract has further view methods that follow a similar structure. --- @@ -241,160 +239,231 @@ The contract has further view methods that follow a very similar structure. Ok, now we've seen the contract we need to make sure it works as expected; this is done through testing. It's good practice to implement exhaustive tests so you can ensure that any little change to your code down the line doesn't break your contract. +In our rust repository, we have a unit test to check that the contract is initialized properly. Unit tests are used to test contract methods individually, these tests work well when little context is required. However, because our contract has operations like sending accounts $NEAR tokens, which happens external to the contract, we need an environment to test the contract. + --- +### Integration tests + +Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment where, for example, accounts have trackable balances Throughout this section, you'll see there is just one large test. This is because the contract only has one possible flow meaning all methods can be properly tested in one test. + +The first thing the test does is create the sandbox environment. + - + Enter sandbox-test > main.ava.js - NEAR Workspaces allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment. Here we are using AVA for testing. + - --- + - #### Setup before a test + - Before running a test it's essential to perform some setup steps. - 1) Create a sandbox environment. - 2) Create accounts to interact with the contract. - 3) Deploy the contract. - 4) Initialise the contract - 5) Store the sandbox and accounts in the test's context. + Enter tests > test_basics.rs - Here the sandbox is created. + - + + + + + +Next, the test creates a couple of user accounts that will be used to send transactions to the contract. Each account is given an account ID and an initial balance. + + - Next, the test creates a couple of user accounts that are used to send transactions from. Each account is given an account ID and an initial balance. + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L15-L18" + start="15" end="18" /> + + + + + + - Then the contract is deployed to an account named "contract". The compiled contract comes from the test running the `build` script in the `package.json` file. + + + + + + + +Likewise, a "contract" account is created and the contract WASM is deployed to it. + + + + + + The contract comes from compiling the contract to WASM using the build script in the package.json file and then reading it. - To initialize the contract, `init` is called with `end_time` set to 60 seconds in the future. + + + + + The contract comes from the test compiling the contract to WASM using `cargo near build` and then reading it. + + + + + + + + + + +To initialize the contract `init` is called with `end_time` set to 60 seconds in the future. + + + + - Lastly, the sandbox and accounts are saved in the test's context so they can be used later. + - + + + - --- + - #### Tests + - In our file there are three different tests: - 1) One bid is placed. - 2) Two bids are placed. - 3) A bid is placed, the auction ends, and a second bid is attempted. +Now the contract is deployed and initialized, bids are made to the contract and it's checked that the state changes as intended. - We'll take a look at the last two. + - In this test, it checks that the highest bidder is properly recorded when two bids are placed and that the lesser bidder is refunded. + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L45-L48" + start="45" end="48" /> - In the next test it simply checks that bids cannot be placed after the auction closes. Time in the sandbox is moved forward using `fastForward`. + - + + + + + + + - --- +When a higher bid is placed the previous bidder should be returned the $NEAR they bid. This is checked by querying the $NEAR balance of the user account. We might think that the test would check whether Alice's balance is back to 10 $NEAR but it does not. This is because some $NEAR is consumed as `gas` fees when Alice calls `bid`. Instead, Alice's balance is recorded after she bids, and once another user bids, it checks that exactly 1 $NEAR has been added to her balance. - #### Closing the sandbox + - After a test is run the sandbox should be closed. + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L50-L60" + start="50" end="60" /> - #### Unit tests + - Unit tests are used to test contract methods individually. Let's look at a test that ensures the contract is initialized properly. + - + - - First, the test creates a new contract and initializes it with an `auction_end_time` of 1000 (in reality this is very low but it doesn't matter in this context). +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. - - Next, the test calls `get_highest_bid` and checks that the `bidder` and `bid` are as expected. + - - Lastly, the test calls `get_auction_end_time` and checks that the `end_time` is as expected. + - Simple enough! + - --- + - #### Integration tests + - Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment where, for example, accounts have trackable balances. + - Enter tests > test_basics.rs + - First, the sandbox testing environment is created and the compiled auction contract is fetched. + - +To test the contract when the auction is over the test uses `fast forward` to advance time in the sandbox. Note that fast forward takes the number of blocks to advance not the number of seconds. The test advances 200 blocks so the time will now be past the minute auction end time that was set. + - Next, the test creates a couple of user accounts that are used to send transactions from. Each account is given an account ID and an initial balance. + - + - Likewise, a "contract" account is created and the contract WASM is deployed to it. + + + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L77-L79" + start="77" end="79" /> - To initialize the contract `init` is called with `end_time` set to 60 seconds in the future. Afterward, it's checked that the transaction was successful. + - + +Now that the auction has ended the contract shouldn't allow any more bids. - Now the contract is deployed and initialized, bids are made to the contract and it's checked that the state changes as intended. + - + + + + - 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. + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L82-L88" + start="82" end="88" /> + --- ## Testing and deploying @@ -427,7 +496,7 @@ Cool, now we've seen how tests are written, let's go ahead and run the tests. We ``` - near contract deploy use-file build/hello_near.wasm with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet + near contract deploy use-file with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet ``` @@ -456,7 +525,7 @@ Cool, now we've seen how tests are written, let's go ahead and run the tests. We Then deploy and initialize the contract with ``` - cargo near dep0loy with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet + cargo near deploy with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet ``` @@ -469,13 +538,13 @@ Now the contract is deployed and initialized we can send transactions to it usin Call `bid`, you may want to create another testnet account for the signer ``` -$ near contract call-function as-transaction bid json-args {} prepaid-gas '100.0 Tgas' attached-deposit '1 NEAR' sign-as network-config testnet +near contract call-method as-transaction bid json-args {} prepaid-gas '100.0 Tgas' attached-deposit '1 NEAR' sign-as network-config testnet ``` Call `get_highest_bid` ``` -$ near contract call-function as-read-only get_highest_bid json-args {} network-config testnet now +near contract call-method as-read-only get_highest_bid json-args {} network-config testnet now ``` --- diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md index e26b22058af..ddf21d2b456 100644 --- a/docs/3.tutorials/auction/2-locking.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -8,13 +8,13 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github} from "@site/src/components/codetabs" -In the basic contract to claim the tokens from the final bid on the contract, the contract owner would have to log into a wallet using a key a withdraw NEAR to their main wallet. This provides a poor UX, but even more importantly it is a security issue. Since there is a key to the contract a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The core principle of smart contracts is that they eliminate the need for trust when interacting with an application, thus 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. +In the basic contract to claim the tokens from the final bid on the contract, the contract owner would have to log into a wallet using a key and withdraw NEAR to their main wallet. This provides a poor UX, but even more importantly it is a security issue. Since there is a key to the contract, a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The core principle of smart contracts is that they eliminate the need for trust when interacting with an application, thus 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 -We want to restrict the method to claim the tokens, to only the individual or entity that sets up the auction. To do this we now change the `init` method to initialize the contract with 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`. @@ -36,13 +36,13 @@ We want to restrict the method to claim the tokens, to only the individual or en -Let's also introduce a boolean `claimed` field to track whether the funds have been claimed by the auctioneer yet. +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 funds to the auctioneer. We'll implement this as so: +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: @@ -70,7 +70,6 @@ The `claim` method should only be callable when the auction is over, can only be 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. @@ -78,36 +77,32 @@ We will also now also test the `claim` method. The test will check that the `auc - - The auctioneer should now have 2 more $NEAR tokens to match the highest bid on the auction. Note that the balances are rounded since some tokens are used for gas when calling `claim`. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/02-owner-claims-money/sandbox-test/main.ava.js#L70-L81" + start="70" end="81" /> - - Whilst the auctioneer starts with 5 tokens and receives 2 from the auction, note that we don't check for an account balance of strictly 7 since some tokens are used for gas when calling `claim`. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/02-owner-claims-money/tests/test_basics.rs#L89-L105" + start="89" end="105" /> +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" arguments now when initializing. - -Now we have an auctioneer and 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. +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 > specify your account ID > β†’ (to delete all) > +near > account > delete-keys > specify your account ID > β†’ (to delete all) > testnet :::caution Be extra careful to delete the keys from the correct account as you'll never be able to access the account again! diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index d55c54c1b44..c06f0dd745f 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -115,11 +115,6 @@ Again let's go and update our tests to reflect the changes we've made to our con --- -## Unit tests - -TODO - ---- ## Integration tests diff --git a/docs/3.tutorials/welcome.md b/docs/3.tutorials/welcome.md index 071b8eb04e3..228b27eb65f 100644 --- a/docs/3.tutorials/welcome.md +++ b/docs/3.tutorials/welcome.md @@ -28,12 +28,12 @@ Explore our collection of Examples and Tutorials subtitle="Use our Data Lake to listen for events" image="monitor.png" /> + - diff --git a/website/sidebars.js b/website/sidebars.js index e68b70a82a6..d46595a521f 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -370,7 +370,7 @@ const sidebar = { "value": " Tutorials " }, { - "Zero to Hero NEAR": [ + "NEAR Zero to Hero": [ "tutorials/auction/introduction", "tutorials/auction/basic-auction", "tutorials/auction/locking-the-contract", 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 0000000000000000000000000000000000000000..0c4aa88a32c1d0ccb6c49ab3bd3bd641162c0edc GIT binary patch literal 40418 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

i+%?WzNC^rr=Aw7A@{ z-XSLpX!Q&_B`<#+=-JVDnqqnT8WKebRW?ts0=0)Na{p2G}^i5 zMSQQ_?8_!BI`Cn0YG9pB0$9LH`q*#!Ad!AOa4;(~Pe?z%dL8c=pl%Mt6`whFqReN2 zu0~+Rvu`NGd}>(B^sQ4?4WHx(dI~G0fyRK{_;w}TmaY7h%iP-@dAY)5%*iu zzO@!zebP47;(P8QgK^FAo33dY7+$9R2Q4+m5DO0G?lIguV2*A^QnSFQ43$R0ZPD1h z;ZS;yNS9bGe%vAjcw#q969X|qq7yADoQk6-X2htwv+y!Yp#y&LF8C{4spa_hxl>EU z4`5F3*MC}zf_;%gf=H*;5QR6wOrjZ)US=PJN|WUm-%lO8Gn55{y_GjZF{Hx=pwz3xMEz4O!;0d6Y2&!akDdz8LMV> z#Onr2M&t>8rVxg}IZ!mP?b+iJ%^W_H13Ev1?{gfeR)@fSo3lHa;%7 z!sn*hG9f@H;v|tcpK~8uC?$Y^YZxw_yEN|s@*o0LuH_+jjA1Q&GO~w!4Dx8YoTlp= z8lVBXZk_V~UOD*E6Z0M%`wIpD^zZf;;hq}sobCTWssU?3BP${i3*&@nGu*w?k1+!g zEN|sF(GDsIq0!V7mESGA7c{!xE_Y2z0NmA|1V#HRTxwxE4g`ljHv$bYgWcR>d1G9- z-1PWAlO6hm*KudO>FkP~K22MmXd-&`dNadr77B)qA9~q86(%hS&~NY0PNd!UDa}g* zi637mmjb3FbL&Xz$(E~ig6*Ir_R+A;Q-&p^f|!BSjNi0{Q#iEv!bH+e1Abcjw;&Ss z=f^o1pBm;&!==I#DE9h%dlTPom9gF-!@Yc7I%X-rZYEmCU8Eq7_9>O-gbl;h-hh|4 zIWhKEj-H@;RA`-vb68Un)dTAnEnE2&^o)XsB_YTD>`4GmPjab;_7f#9oSn-!3{j8GiH~_O8E&iJ!M54oXpD(?JHG;Rm%S@U@64 zD44f`tFko>SPO77(hd}em zQ9FV)=ERK#J)H}!3MW^2MA4>R!;YzIjo+D+mLl^vR|cQ7F}C1>zGeZt49ZjX11^)J z%G;`p9Z{~RZ~`ybfN%0_*i!9XaLr|ES|SB|yYV}4$_}K?i#Afj>ijh7CGNkU{>h2I z2roQ+6}$=4MQE-kjT95Ri>ZVUU|10YP&Ns*0VlUoq4wWKCo2MzjTjdKFOV3!dq#|L zcXxJ(rcPSI8MW~(vpQ$a45bMLpGrPI(@p;3ex!{whj#3NfBE{j?^n};V zsd1sfGmEzto`c`-V7t8!>B;Q6`}+Uy73xF12L%YlrZ37v4-BWcTnl2Fth3W*__4X! zp=xd+F-F+Zi~wdK0X9R?`(I+BgJ%ST$9qnmXWr;5IeloOpYRsm!9^NVtdSiedfK5$ kV=DY3mgrK0c5&3Pn*GgW>pciA004hiHT5-$2t?BV0s67D8~^|S literal 0 HcmV?d00001 From 75baadbaa348d220abd19582ef64157b0c4474a4 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Wed, 7 Aug 2024 15:17:58 +0100 Subject: [PATCH 10/32] small changes + remove future tutorials from sidebar --- docs/3.tutorials/auction/0-intro.md | 12 ++-- docs/3.tutorials/auction/1-basic.md | 86 +++++++++++++++++++-------- docs/3.tutorials/auction/2-locking.md | 8 ++- docs/3.tutorials/auction/3-nft.md | 83 +++++++++----------------- website/sidebars.js | 4 +- 5 files changed, 104 insertions(+), 89 deletions(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index 911eae84db8..66ca88194f3 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -14,14 +14,15 @@ We'll start by cloning a simple auction smart contract and slowly add features b - Building a smart contract - Testing a contract in a realistic environment - Deploying and locking a contract -- Making cross-contract calls + + -In this tutorial, you have the option to follow along in JavaScript or Rust. +For this tutorial, you have the option to follow along in JavaScript or Rust. --- @@ -80,11 +81,12 @@ These are the steps that will bring you from **Zero** to **Hero** in no time! |------|----------------------------------------|-----------------------------------------------------------------| | 1 | [Basic contract](./1-basic.md) | Learn the how a basic smart contract is structured | | 2 | [Locking the contract](./2-locking.md) | Learn to create contracts with no access keys | -| 3 | [Winning an NFT](./3-nft.md) | Learn about sending NFTs using cross-contract calls | + + --- diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index 76fea8e7eb6..4d88f6ccd6c 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -6,7 +6,7 @@ sidebar_label: Basic Auction import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import {Github} from "@site/src/components/codetabs" +import {Github, Language} from "@site/src/components/codetabs" In this section of the tutorial, we'll clone a simple auction smart contract and analyze each section of the contract in-depth. We'll also look at how to test a smart contract, and then how to deploy and interact with the contract on testnet. @@ -174,9 +174,7 @@ An auction isn't an auction if you can't place a bid! The contract has a `bid` m url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/src/contract.ts#L20-L40" start="20" end="40" /> - When the user calls the `bid` method they will transfer $NEAR tokens to the contract; to enable the transfer of $NEAR tokens we set `payableFunction` to `true`. This method is what we call a "change method", also known as a "call method"; we know this as it has the `call` decorator. Change methods, by name, change the state of the contract and require the user to attach gas. - - There are some specifics to this method that we should take a closer look at: + When the user calls the `bid` method they will transfer $NEAR tokens to the contract; to enable the transfer of $NEAR tokens we set `payableFunction` to `true`. The method is decorated with `call` so the state of the contract can be changed within the method. These types of methods require a user to sign the transaction that calls the method and requires the user to attach gas. @@ -186,31 +184,41 @@ An auction isn't an auction if you can't place a bid! The contract has a `bid` m url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L33-L59" start="33" end="59" /> - When the user calls the `bid` method they will transfer $NEAR tokens to the contract; to enable the transfer of $NEAR tokens we need to decorate the method with the `payable` macro. This method is what we call a "change method", also known as a "call method"; we know this since it passes a mutable reference to `self` ($mut self). Change methods, by name, change the state of the contract and require the user to attach gas. + When the user calls the `bid` method they will transfer $NEAR tokens to the contract; to enable the transfer of $NEAR tokens we need to decorate the method with the `payable` macro. The method takes a mutable reference to `self` (&mut self) so the state of the contract can be changed within the method. These types of methods require a user to sign the transaction that calls the method and requires the user to attach gas. - There are some specifics to this method that we should take a closer look at: +

+ + require over assert - - We use `require` as opposed to `assert` as it reduces the contract size by not including file and rust-specific data in the panic message. + Require has been used here as opposed to assert as it reduces the contract + size by not including file and rust-specific data in the panic message. +
+There are some specifics to this method that we should take a closer look at: + + - The `block timestamp` is used to determine whether the auction is still ongoing. It gives the time as the number of nanoseconds since January 1, 1970, 0:00:00 UTC. - The `predecessor` gives the account (or contract) that directly called the bid method, this can be different to the signer who initially signed the transaction leading to the execution of this method. If pivortex.near calls a contract proxy-contract.near which then calls bid on this contract the predecessor would be proxy-contract.near and the signer would be pivortex.near. For security purposes, so a malicious contract can't place a bid in your name, we stick with predecessor using here. - The contract returns a `Promise` that will execute the transfer of $NEAR tokens back to the previous bidder. +Note that in the case of 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. + --- ### Viewing the contract state -The contract also implements "view methods"; these allow the user to view the data stored on the contract. View methods don't require any gas but cannot change the state of the contract. For example, this could be used to view, in the frontend, what the highest bid amount was so we make sure that we place a higher bid. +The contract implements methods that do not change the contract's state but just view it. These methods don't require gas. We would want, for example, a method to see, in the frontend, what the highest bid amount was so we make sure to place a higher bid. + - View methods are decorated with `view`. + View methods are decorated with `view` so they cannot change the contract's state. - View methods take an immutable reference to `self` (&self). + View methods take an immutable reference to `self` (&self) so they cannot change the contract's state. -The contract has further view methods that follow a similar structure. +The contract has two further view methods: the first retrieves the end time of the auction and the second retrieves all the information stored in the `Contract` struct. --- @@ -239,14 +247,18 @@ The contract has further view methods that follow a similar structure. Ok, now we've seen the contract we need to make sure it works as expected; this is done through testing. It's good practice to implement exhaustive tests so you can ensure that any little change to your code down the line doesn't break your contract. -In our rust repository, we have a unit test to check that the contract is initialized properly. Unit tests are used to test contract methods individually, these tests work well when little context is required. However, because our contract has operations like sending accounts $NEAR tokens, which happens external to the contract, we need an environment to test the contract. +In our rust repository, we have a unit test to check that the contract is initialized properly. Unit tests are used to test contract methods individually, these tests work well when little context is required. However, because our contract has operations like sending accounts $NEAR tokens, which happen external to the contract, we need an environment to test the contract. --- -### Integration tests +## Sandbox testing Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment where, for example, accounts have trackable balances Throughout this section, you'll see there is just one large test. This is because the contract only has one possible flow meaning all methods can be properly tested in one test. +--- + +### Creating the sandbox + The first thing the test does is create the sandbox environment. @@ -273,6 +285,9 @@ The first thing the test does is create the sandbox environment. +--- + +### Creating user accounts Next, the test creates a couple of user accounts that will be used to send transactions to the contract. Each account is given an account ID and an initial balance. @@ -288,18 +303,22 @@ Next, the test creates a couple of user accounts that will be used to send trans - + - - + + +--- + +### Deploying the contract Likewise, a "contract" account is created and the contract WASM is deployed to it. @@ -307,7 +326,7 @@ Likewise, a "contract" account is created and the contract WASM is deployed to i - The contract comes from compiling the contract to WASM using the build script in the package.json file and then reading it. + The contract comes from compiling the contract to WASM using the build script in the `package.json` file and then reading it. - - + start="20" end="21" /> +--- + +### Initializing the contract To initialize the contract `init` is called with `end_time` set to 60 seconds in the future. @@ -354,6 +372,10 @@ To initialize the contract `init` is called with `end_time` set to 60 seconds in +--- + +### Testing methods + Now the contract is deployed and initialized, bids are made to the contract and it's checked that the state changes as intended. @@ -376,7 +398,11 @@ Now the contract is deployed and initialized, bids are made to the contract and -When a higher bid is placed the previous bidder should be returned the $NEAR they bid. This is checked by querying the $NEAR balance of the user account. We might think that the test would check whether Alice's balance is back to 10 $NEAR but it does not. This is because some $NEAR is consumed as `gas` fees when Alice calls `bid`. Instead, Alice's balance is recorded after she bids, and once another user bids, it checks that exactly 1 $NEAR has been added to her balance. +--- + +### Checking user balances + +When a higher bid is placed the previous bidder should be returned the $NEAR they bid. This is checked by querying the $NEAR balance of the user account. We might think that the test would check whether Alice's balance is back to 10 $NEAR but it does not. This is because some $NEAR is consumed as `gas` fees when Alice calls `bid`. Instead, Alice's balance is recorded after she bids, then once another user bids it checks that exactly 1 $NEAR has been added to her balance. @@ -398,6 +424,10 @@ When a higher bid is placed the previous bidder should be returned the $NEAR the +--- + +### 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. @@ -420,6 +450,10 @@ When testing we should also check that the contract does not allow invalid calls +--- + +### Fast forwarding time + To test the contract when the auction is over the test uses `fast forward` to advance time in the sandbox. Note that fast forward takes the number of blocks to advance not the number of seconds. The test advances 200 blocks so the time will now be past the minute auction end time that was set. @@ -551,4 +585,6 @@ near contract call-method as-read-only get_highest_bid json-args {} ## Conclusion -In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data and views the data. We also looked at how tests are written and how to execute them. Finally, we learned to compile, deploy and interact with the contract through the CLI on testnet. In the [next part](./2-locking.md), we'll edit the existing contract and add a new method so the contract to learn how to lock a contract. \ No newline at end of file +In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data and views the data. We also looked at how tests are written and how to execute them. Finally, we learned to compile, deploy and interact with the contract through the CLI on testnet. + +There is a core problem to this contract; there needs to exist a key to the contract for the auctioneer to claim the funds. This allows for the key holder to do with the contract as they please, for example withdrawing funds from the contract at any point. In the [next part](./2-locking.md), we'll learn how to lock a contract by specifying an auctioneer on initialization who can claim the funds through a new method we'll introduce. \ No newline at end of file diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md index ddf21d2b456..9e59b38e38b 100644 --- a/docs/3.tutorials/auction/2-locking.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -8,7 +8,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github} from "@site/src/components/codetabs" -In the basic contract to claim the tokens from the final bid on the contract, the contract owner would have to log into a wallet using a key and withdraw NEAR to their main wallet. This provides a poor UX, but even more importantly it is a security issue. Since there is a key to the contract, a keyholder can maliciously mutate the contract's storage as they wish, for example, alter the highest bidder to list their own account. The core principle of smart contracts is that they eliminate the need for trust when interacting with an application, thus 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. +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. --- @@ -76,7 +76,7 @@ We will also now also test the `claim` method. The test will check that the `auc - @@ -112,4 +112,6 @@ Be extra careful to delete the keys from the correct account as you'll never be ## 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. \ No newline at end of file +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 (coming soon), 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 index c06f0dd745f..3fe9cbca285 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -8,42 +8,32 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github} 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 with audited standards so the prize will exist without the auction contract. Let's get to work! +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! --- -## Defining the NFT interface +## 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` as such: - TODO + - Since NFTs follow standards, all NFT contracts implement the same interface that sets out the contract's methods, what parameters these methods take and what the method will return. When we are making calls to other contracts we write the interface of each method in a separate file `ext.rs` as a `trait`. - - - - `nft_transfer` transfers the ownership of an NFT from one account ID to another. The method takes the arguments `receiver_id` dictating who will now own the NFT and the `token_id` specifying which token in the NFT contract is having its ownership transferred. The `ext_contract` macro converts the NFT trait into a module with the method `nft_transfer`. - - Note that we declare `TokenId` in our main file `lib.rs`. The NFT standards use a type alias for future-proofing. - + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/03-owner-claims-winner-gets-nft/src/lib.rs#L32-L49" + start="32" end="49" /> - We import our interface into `lib.rs` as such: - - + Note that `token_id` is of type `TokenId` which is a String type alias that the NFT standards use for future-proofing. @@ -51,45 +41,37 @@ No one will enter an auction if there's nothing to win, so let's add a prize. Wh --- -## 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` as such: +## 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 need to make a cross-contract call to the NFT contract telling it that the highest bidder now owns the NFT, not the auction contract. The method on the NFT contract is called `nft_transfer`. - TODO + - + 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`. - - - - ---- - -## Transferring the NFT to the winner - - + - + Defining external methods in a separate file helps improve the readability of our code. - TODO + Make sure to import the interface into lib.rs. - - - + - - When the auction is ended - by calling the method `claim` - the NFT needs to be transferred to the highest bidder. We make a cross-contract call to the NFT contract, via the external method we defined, at the end of `claim`. + Then we use this method to transfer the NFT. Date: Wed, 7 Aug 2024 15:22:53 +0100 Subject: [PATCH 11/32] fix spelling mistake --- docs/3.tutorials/auction/3-nft.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index 3fe9cbca285..9ca32bb4deb 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -81,7 +81,7 @@ When the auction is ended - by calling the method `claim` - the NFT needs to be -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 (since the NFT contract requires this for [security reasons](../../2.build/2.smart-contracts/security/one_yocto.md)), and the arguments `reciever_id` and `token_id`. +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 (since the NFT contract requires this for [security reasons](../../2.build/2.smart-contracts/security/one_yocto.md)), and the arguments `receiver_id` and `token_id`. --- From 1506f0e20d834c3f4bfeda8e9a5337142b68bcc5 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Fri, 9 Aug 2024 12:35:31 +0100 Subject: [PATCH 12/32] update part 3 --- docs/3.tutorials/auction/0-intro.md | 10 ++-- docs/3.tutorials/auction/1-basic.md | 2 +- docs/3.tutorials/auction/3-nft.md | 93 ++++++++++++++++++----------- docs/3.tutorials/auction/4-ft.md | 2 +- 4 files changed, 66 insertions(+), 41 deletions(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index 66ca88194f3..4b37db012d7 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -14,10 +14,10 @@ We'll start by cloning a simple auction smart contract and slowly add features b - Building a smart contract - Testing a contract in a realistic environment - Deploying and locking a contract - - @@ -81,9 +81,9 @@ These are the steps that will bring you from **Zero** to **Hero** in no time! |------|----------------------------------------|-----------------------------------------------------------------| | 1 | [Basic contract](./1-basic.md) | Learn the how a basic smart contract is structured | | 2 | [Locking the contract](./2-locking.md) | Learn to create contracts with no access keys | +| 3 | [Winning an NFT](./3-nft.md) | Learn about sending NFTs using cross-contract calls | - diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index 4d88f6ccd6c..4d8c102cbcb 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -303,7 +303,7 @@ Next, the test creates a couple of user accounts that will be used to send trans - + diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index 9ca32bb4deb..dd145dab1a5 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -6,7 +6,7 @@ sidebar_label: Winning an NFT import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import {Github} from "@site/src/components/codetabs" +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! @@ -14,7 +14,7 @@ No one will enter an auction if there's nothing to win, so let's add a prize. Wh ## 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` as such: +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: @@ -43,45 +43,38 @@ When we create an auction we need to list the NFT. To specify which NFT is being ## 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 need to make a cross-contract call to the NFT contract telling it that the highest bidder now owns the NFT, not the auction contract. The method on the NFT contract is called `nft_transfer`. +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`. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/03-owner-claims-winner-gets-nft/src/contract.ts#L68-L70" + start="68" end="70" /> - 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`. + 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. - + - - Defining external methods in a separate file helps improve the readability of our code. - - Make sure to import the interface into lib.rs. - - - - Then we use this method to transfer the NFT. - - + start="93" end="96" /> + -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 (since the NFT contract requires this for [security reasons](../../2.build/2.smart-contracts/security/one_yocto.md)), and the arguments `receiver_id` and `token_id`. +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). --- @@ -91,57 +84,89 @@ In our contract, we perform no checks to verify whether the contract actually ow --- -## Testing with external contracts +## Testing with multiple contracts -In our tests, we're now going to be using two contracts; the auction contract and an NFT contract. Integration tests allow us to test multiple contracts in a realistic environment. +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) +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). -We deploy the NFT contract WASM using `dev_deploy` which creates an account with a random ID and deploys the contract to it. +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). - TODO + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/03-owner-claims-winner-gets-nft/tests/test_basics.rs#L23-L24" + start="23" end="32" /> -To get the NFT to be auctioned, the auction contract calls the NFT contract to mint a new NFT with the provided data. +--- + +## Minting an NFT + +To start a proper auction the auction contract should own an NFT. To do this the auction contract calls the NFT contract to mint a new NFT with the provided data. - TODO + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/03-owner-claims-winner-gets-nft/tests/test_basics.rs#L35-L53" + start="35" end="53" /> -After `claim` is called, the tests should verify that the auction winner now owns the NFT. This is done by calling `nt_token` on the NFT contract and specifying the token ID. +--- + +## 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. + + + + + + + + + + + + + + + + --- ## Conclusion -This 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 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. +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 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. diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index 38d8e051b0a..495d9e9a7af 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -8,7 +8,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github} from "@site/src/components/codetabs" -To make this contract more interesting we're going to 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 usecase is if a project like Ref Finance were holding their own auction and would want the auction to happen in their project's token $REF. +To further develop this contract we're going to 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 were holding their own auction and want the auction to happen in their project's token $REF. --- From d601a3cc20b9bf97533afa58da4e00395312f343 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Fri, 9 Aug 2024 12:42:00 +0100 Subject: [PATCH 13/32] hide nft tuotrial on intro page --- docs/3.tutorials/auction/0-intro.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index 4b37db012d7..cfbcb7fa9e7 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -14,13 +14,13 @@ We'll start by cloning a simple auction smart contract and slowly add features b - Building a smart contract - Testing a contract in a realistic environment - Deploying and locking a contract -- Making cross-contract calls -- Using Non-Fungible Tokens - +- Creating a factory contract --> For this tutorial, you have the option to follow along in JavaScript or Rust. @@ -81,9 +81,9 @@ These are the steps that will bring you from **Zero** to **Hero** in no time! |------|----------------------------------------|-----------------------------------------------------------------| | 1 | [Basic contract](./1-basic.md) | Learn the how a basic smart contract is structured | | 2 | [Locking the contract](./2-locking.md) | Learn to create contracts with no access keys | -| 3 | [Winning an NFT](./3-nft.md) | Learn about sending NFTs using cross-contract calls | - From 0143d88ba754c79afa2858f1b1db145e089c2087 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Thu, 15 Aug 2024 16:01:51 +0100 Subject: [PATCH 14/32] improved part 4 --- docs/3.tutorials/auction/2-locking.md | 6 +- docs/3.tutorials/auction/3-nft.md | 30 ++- docs/3.tutorials/auction/4-ft.md | 251 ++++++++++++------ .../assets/auction/auction-ft-transfer.png | Bin 0 -> 47596 bytes 4 files changed, 198 insertions(+), 89 deletions(-) create mode 100644 website/static/docs/assets/auction/auction-ft-transfer.png diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md index 9e59b38e38b..f93173a437a 100644 --- a/docs/3.tutorials/auction/2-locking.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -102,7 +102,11 @@ Go ahead and test, build and deploy your new contract, as in part 1. Remember to 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 > specify your account ID > β†’ (to delete all) > testnet +``` +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! diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index dd145dab1a5..48ff278c16e 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -53,6 +53,8 @@ When the auction is ended - by calling the method `claim` - the NFT needs to be url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/03-owner-claims-winner-gets-nft/src/contract.ts#L68-L70" start="68" end="70" /> + In near-sdk-js we cannot transfer the NFT and send the $NEAR independently so we will chain the promises. + @@ -62,12 +64,12 @@ When the auction is ended - by calling the method `claim` - the NFT needs to be We then use this method in our `lib.rs` file to transfer the NFT. - + @@ -88,15 +90,15 @@ In our contract, we perform no checks to verify whether the contract actually ow 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). +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). +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). The default metadata sets the information such as the name of the token and its image to some pre-set values. - @@ -122,7 +124,7 @@ To start a proper auction the auction contract should own an NFT. To do this the - @@ -148,7 +150,7 @@ After `claim` is called, the test should verify that the auction winner now owns - @@ -166,6 +168,18 @@ After `claim` is called, the test should verify that the auction winner now owns --- +## 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 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. diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index 495d9e9a7af..9921fad52c4 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -1,51 +1,28 @@ --- -id: bidding-with-FTs +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} from "@site/src/components/codetabs" +import {Github, Language} from "@site/src/components/codetabs" To further develop this contract we're going to 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 were holding their own auction and want the auction to happen in their project's token $REF. --- -## Defining the FT interface - - - - - - - TODO - - - - - - Unlike NEAR tokens, fungible token amounts are not tracked on the user's account but rather they have their own contract (as do NFTs); so we're going to have to define an interface to interact with the FT contract in `ext.rs`. We will use the method `ft_transfer` to send fungible tokens to a specified account from our contract. - - - - - - - ---- - ## Specifying the FT contract -We want our bids to only happen 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 price for the NFT. +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 for the auction. - TODO + @@ -63,17 +40,19 @@ We want our bids to only happen in one type of fungible token; accepting many di ## Accepting bids in FTs -For making bids in NEAR we call the contract directly and add NEAR tokens to the call. With fungible tokens, since the balance lives on a separate contract, we call the FT contract to call the auction contract and to transfer 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. +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. -**insert FT diagram** +![ft_transfer_call-flow](/docs/assets/auction/auction-ft-transfer.png) -Thus we swap our `bid` method for `ft_on_transfer`. +The `ft_on_transfer` method always has the same interface; the FT contract will pass it the `sender`, the `amount` of FT 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. - TODO + @@ -88,13 +67,15 @@ Thus we swap our `bid` method for `ft_on_transfer`. -We do a check to confirm that the user is using the required fungible token contract by checking the predecessor's account ID. Since it is the FT contract that called the auction contract, the predecessor is now the FT contract. +We need to confirm that the user is using 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 ID` is now the ID of the FT contract. - TODO + @@ -114,7 +95,9 @@ The bidder's account ID is now given by the argument `sender_id`. - TODO + @@ -122,47 +105,85 @@ The bidder's account ID is now given by the argument `sender_id`. + start="75" end="78" /> -Now when we want to return the funds to the previous bidder we make a cross-contract call the the FT contract. We shouldn't transfer funds if it is the first bid so we'll implement a check for that as well. +Now when we want to return the funds to the previous bidder we make a cross-contract call to the FT contract. - TODO + + + + + + 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. + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/04-ft-owner-claims-winner-gets-nft/src/lib.rs#L86" + start="86" end="86" /> -The method `ft_on_transfer` needs to return the number of unused tokens in the call so the FT contract can refund the sender. We will always use the full amount of tokens in this call so we simply return 0. +
+ + 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. - TODO + + + In JavaScript, since we need to return each cross-contract call we chain the NFT and FT transfer. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/04-ft-owner-claims-winner-gets-nft/src/lib.rs#L99-L109" + start="99" end="109" /> @@ -170,23 +191,27 @@ The method `ft_on_transfer` needs to return the number of unused tokens in the c --- -## Claiming the FTs +## Creating a new FT -When the auction is complete we need to send the fungible tokens to the auctioneer, we implement a similar call as when we were returning the funds just changing the arguments. +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. - TODO + - + @@ -194,103 +219,148 @@ When the auction is complete we need to send the fungible tokens to the auctione --- -## Registering the user in the FT contract +## 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. 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 need 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. +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. 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. -## Updating the tests + + + ---- + + + -## Unit tests + -TODO + ---- + -## Integration tests +--- -Just as with the NFT contract, we will deploy an FT contract in workspaces using a WASM file from **ADD MORE**. +## Simple FT transfer to bidders -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. +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. - TODO + - + + + + -As mentioned previously, to own FTs you have to be registered in the FT contract. So let's register all the accounts that are going to interact with FTs. +--- + +## 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. - TODO + - + + + + -Then we will transfer the bidders FTs so they can use them to bid. +--- + +## Checking user's 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. - TODO + - + + + + -As stated previously, to bid on the auction the bidder now calls `ft_transfer_call` on the FT contract and specifies the auction contract as an argument. +--- + +## 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. - TODO + + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/test_basics.rs#L183-L200" + start="183" end="200" /> @@ -298,6 +368,20 @@ As stated previously, to bid on the auction the bidder now calls `ft_transfer_ca --- +## FT transfer call using the CLI + +If we want to interact with the contract now we're going to need to own the same FT as the auction is in. + +For this example, we're going to use the FT contract cusd.fakes.testnet. This FT contract was set with 24 decimals meaning that 1 cUSD is made up of 10^24 smallest units. + +Go over the steps in part 1 to build the contract and then we can initialize it, this time specifying the FT contract. + +``` + +``` + + + ## 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 would 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. @@ -315,4 +399,11 @@ However, this architecture could be deemed less secure since if a bad actor were --- ## Conclusion +In this section, we learned a lot about fungible tokens: how to receive FTs in a contract, how to send FTs from 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 + + 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 0000000000000000000000000000000000000000..9caeb5043782a2bb591d4868f21bd7a3b1c2bb46 GIT binary patch literal 47596 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 MBZ~_!4X)n%fBrkXi2wiq literal 0 HcmV?d00001 From 331815f2c9a0e4e5fa4d0aa91c191ba9ab34a789 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Mon, 19 Aug 2024 11:58:12 +0100 Subject: [PATCH 15/32] complete part 3 & 4 (hidden) --- docs/3.tutorials/auction/1-basic.md | 17 ++++--- docs/3.tutorials/auction/3-nft.md | 9 ++-- docs/3.tutorials/auction/4-ft.md | 72 ++++++++++++++++++----------- 3 files changed, 57 insertions(+), 41 deletions(-) diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1-basic.md index 4d8c102cbcb..6e51b5838b3 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -78,7 +78,7 @@ The contract stores two main fields: what was the highest bid so far, and when t
@@ -205,7 +205,7 @@ There are some specifics to this method that we should take a closer look at: - The `predecessor` gives the account (or contract) that directly called the bid method, this can be different to the signer who initially signed the transaction leading to the execution of this method. If pivortex.near calls a contract proxy-contract.near which then calls bid on this contract the predecessor would be proxy-contract.near and the signer would be pivortex.near. For security purposes, so a malicious contract can't place a bid in your name, we stick with predecessor using here. - The contract returns a `Promise` that will execute the transfer of $NEAR tokens back to the previous bidder. -Note that in the case of 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 in the case of 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. --- @@ -305,8 +305,8 @@ Next, the test creates a couple of user accounts that will be used to send trans + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L12-L16" + start="12" end="16" /> @@ -329,8 +329,8 @@ Likewise, a "contract" account is created and the contract WASM is deployed to i The contract comes from compiling the contract to WASM using the build script in the `package.json` file and then reading it. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/sandbox-test/main.ava.js#L19-L22" + start="19" end="22" /> @@ -339,8 +339,8 @@ Likewise, a "contract" account is created and the contract WASM is deployed to i The contract comes from the test compiling the contract to WASM using `cargo near build` and then reading it. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/tests/test_basics.rs#L17-L21" + start="17" end="21" /> @@ -528,7 +528,6 @@ Cool, now we've seen how tests are written, let's go ahead and run the tests. We Then deploy and initialize the contract with - ``` near contract deploy use-file with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet ``` diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index 48ff278c16e..1794842b888 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -65,7 +65,7 @@ When the auction is ended - by calling the method `claim` - the NFT needs to be @@ -118,7 +118,7 @@ To deploy the NFT contract, this time we're going to use `dev deploy` which crea ## Minting an NFT -To start a proper auction the auction contract should own an NFT. To do this the auction contract calls the NFT contract to mint a new NFT with the provided data. +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. @@ -182,5 +182,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 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. - +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 index 9921fad52c4..3df6a04509a 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -8,13 +8,13 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github, Language} from "@site/src/components/codetabs" -To further develop this contract we're going to 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 were holding their own auction and want the auction to happen in their project's token $REF. +To further develop this contract we're going to 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 were holding their own auction and want the auction to happen in their 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 for the auction. +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. @@ -29,7 +29,7 @@ We want to only accept bids in one type of fungible token; accepting many differ @@ -44,7 +44,7 @@ When we were making bids in $NEAR tokens we would call the auction contract dire ![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 FT 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. +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. @@ -67,7 +67,7 @@ The `ft_on_transfer` method always has the same interface; the FT contract will -We need to confirm that the user is using 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 ID` is now the ID of the FT contract. +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. @@ -96,7 +96,7 @@ The bidder's account ID is now given by the argument `sender_id`. @@ -104,14 +104,14 @@ The bidder's account ID is now given by the argument `sender_id`. -Now when we want to return the funds to the previous bidder we make a cross-contract call to the FT contract. +When we want to return the funds to the previous bidder we now make a cross-contract call to the FT contract. @@ -126,7 +126,7 @@ Now when we want to return the funds to the previous bidder we make a cross-cont start="78" end="81" /> - 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. + 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. @@ -221,7 +221,7 @@ When the contract is deployed it is initialized with `new_default_meta` which se ## 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. 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. +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. @@ -268,7 +268,7 @@ Then we will transfer the bidders FTs so they can use them to bid. A simple tran url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/04-ft-owner-claims-winner-gets-nft/tests/test_basics.rs#L95-L100" start="95" end="100" /> @@ -368,42 +368,60 @@ Previous to this, Bob made a bid of 60,000 and Alice was returned her bid bringi --- -## FT transfer call using the CLI +## Using FTs with the CLI -If we want to interact with the contract now we're going to need to own the same FT as the auction is in. +If you want to interact with the auction contract you're going to need FTs. For this example, we'll use $cUSD where the contract address is `cusd.fakes.testnet`. One can easily acquire FTs through the [testnet faucet](https://near-faucet.io/). Select Celo Dollar 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 cUSD from the faucet account. -For this example, we're going to use the FT contract cusd.fakes.testnet. This FT contract was set with 24 decimals meaning that 1 cUSD is made up of 10^24 smallest units. +When deploying the contract make sure to specify the FT contract `cusd.fakes.testnet`. -Go over the steps in part 1 to build the contract and then we can initialize it, this time specifying the FT contract. +The auction contract will need to be registered as well, you could do this by sending it an arbitrary amount of $cUSD 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 cusd.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. cUSD has 24 decimals meaning that 1 $cUSD is made up of 10^24 smallest units. To make a bid of 2 $cUSD you can use the command: +``` +near contract call-function as-transaction cusd.fakes.testnet ft_transfer_call json-args '{"receiver_id": "", "amount": "2000000000000000000000000", "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 would 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. +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. -```rust -pub struct Contract { - auctions: IterableMap -} -``` + + + + + ```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 receive FTs in a contract, how to send FTs from 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 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, 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 From f61a12eb5cf0b3ed40fa2dd0eae401e5febd026c Mon Sep 17 00:00:00 2001 From: PiVortex Date: Mon, 26 Aug 2024 09:43:11 +0100 Subject: [PATCH 16/32] change cusd to dai --- docs/3.tutorials/auction/4-ft.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index 3df6a04509a..d94b860a241 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -370,20 +370,20 @@ Previous to this, Bob made a bid of 60,000 and Alice was returned her bid bringi ## 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 $cUSD where the contract address is `cusd.fakes.testnet`. One can easily acquire FTs through the [testnet faucet](https://near-faucet.io/). Select Celo Dollar 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 cUSD from the faucet account. +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 `cusd.fakes.testnet`. +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 $cUSD from the faucet or you can just register it since it doesn't need any FTs. You should also register the auctioneer, +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 cusd.fakes.testnet storage_deposit json-args '{"account_id": ""}' prepaid-gas '100.0 Tgas' attached-deposit '0.1 NEAR' +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. cUSD has 24 decimals meaning that 1 $cUSD is made up of 10^24 smallest units. To make a bid of 2 $cUSD you can use the command: +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 cusd.fakes.testnet ft_transfer_call json-args '{"receiver_id": "", "amount": "2000000000000000000000000", "msg": ""}' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' +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 From d4b3ce4e6b3bc5a2b99b30994f58ab4e507adc82 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Mon, 26 Aug 2024 15:29:46 +0100 Subject: [PATCH 17/32] add frontend docs WIP --- docs/3.tutorials/auction/5-frontend.md | 177 +++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 docs/3.tutorials/auction/5-frontend.md diff --git a/docs/3.tutorials/auction/5-frontend.md b/docs/3.tutorials/auction/5-frontend.md new file mode 100644 index 00000000000..eeead9c4b73 --- /dev/null +++ b/docs/3.tutorials/auction/5-frontend.md @@ -0,0 +1,177 @@ +--- +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 we have created a complete contract, we'll need a frontend to make it easy to interact with it. Up until now, we've just been using the CLI to interact with the contract. Frontends make interacting with the contract much easier for end users as all the contracts information can be displayed infront of the user simultaneously, calls can be made with a click of a button and the only prequesit is a wallet. + +## 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. + +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. + +Lets take a look at how the code in the frontend is structured. + +``` +TODO add file structure of frontend + +nft-contract +β”œβ”€β”€ Cargo.lock +β”œβ”€β”€ Cargo.toml +β”œβ”€β”€ README.md +└── src + β”œβ”€β”€ approval.rs + β”œβ”€β”€ enumeration.rs + β”œβ”€β”€ lib.rs + β”œβ”€β”€ metadata.rs + β”œβ”€β”€ mint.rs + β”œβ”€β”€ nft_core.rs + β”œβ”€β”€ events.rs + └── royalty.rs +``` + +TODO add info about each file + +| File | Description | +|----------------------------------|---------------------------------------------------------------------------------| +| approval.rs | Has the functions that controls the access and transfers of non-fungible tokens | +| enumeration.rs | Contains the methods to list NFT tokens and their owners | +| lib.rs | Holds the smart contract initialization functions | +| metadata.rs | Defines the token and metadata structure | +| mint.rs | Contains token minting logic | +| nft_core.rs | Core logic that allows you to transfer NFTs between users. | +| royalty.rs | Contains payout-related functions | +| events.rs | Contains events related structures | + +--- + +## 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. + +TODO add config file code + +--- + +## Setting up wallets + +To be able to interact with the contract - send bids or claim the auction - you'll need a `wallet` to sign transactions. Wallets store your private keys in a secure way and allow you to sign transactions without exposing your private key to the frontend. For this we use the wallet selector. + +We abstract the wallet selector in our `near.js` file by exposing methods to complete various tasks. Feel free to explore the file to understand how the wallet selector is implemented. TODO insert link to near.js file. + +We implement a sign in and sign out button 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 non payable transactions on behalf of the user, to the specified contract, without requiring the user to sign each transaction in the wallet; this creates 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. + +TODO sign in and out button + near.js file code + +We add the wallet and the account ID that is signed in to the frontend's context making it easier to access in each componenet. + +TODO add code for context and where context is added + +--- + +## Displaying the highest bid + +We call the method `get_auction_info` to get all the information about the auction. This will be used to display the highest bidder, the auction end time, and the NFT and FT contract IDs. + +TODO add code for get_auction_info and the view call. + +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 avaiable](../../5.api/rpc/providers.md). We are querying the RPC with optimistic finality, which uses the queries the latest block recorded on the node. Alternatively, one can 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 and dispay the bid amount and the bidder's account ID in the component. + +TODO add code for passing props into Last Bid component and the LastBid component code. + +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). + +TODO add code for calling FT contract ft metadata + +--- + +## 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`. + +TODO add code for updating the highest bid every 5 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). To make this look nicer we will display the time left in days, hours, minutes and seconds. + +TODO add code for displaying the time left + +--- + +## 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 NFTs 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. + +TODO Show getNftInfo method + +Note that this effect will only run once the `auctionInfo` updates because we first need the 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. + +TODO add code for displaying the NFT + +Note that the 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. + +TODO add bid code in index.js and the call method in near.js + +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 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 contract account the FTs. + +TODO add claim code in index.js + +--- + +## Conclusion + +In this part of the tutorial we have implemented a simple frontend for a NEAR contract. Along the way we have learnt how to use the wallet selector to sign the user in and out, how to view the contracts 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 auctions bidding history. Since the contract only stores the most recent bid we need to use an indexer to pull historical data. In the next part of the tutorial we'll look at using an indexer to query historical data. From bd668a802a60549bf8711fde2e6adfadfbd1a7e8 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Mon, 26 Aug 2024 15:47:58 +0100 Subject: [PATCH 18/32] spelling --- docs/3.tutorials/auction/5-frontend.md | 53 ++++++++++++++------------ 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/docs/3.tutorials/auction/5-frontend.md b/docs/3.tutorials/auction/5-frontend.md index eeead9c4b73..25035548f17 100644 --- a/docs/3.tutorials/auction/5-frontend.md +++ b/docs/3.tutorials/auction/5-frontend.md @@ -8,9 +8,10 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github, Language} from "@site/src/components/codetabs" -Now we have created a complete contract, we'll need a frontend to make it easy to interact with it. Up until now, we've just been using the CLI to interact with the contract. Frontends make interacting with the contract much easier for end users as all the contracts information can be displayed infront of the user simultaneously, calls can be made with a click of a button and the only prequesit is a wallet. -## Starting the frontend +Now we have created a complete contract, we'll need a frontend to make it easy to interact with it. Until now, we've just been using the CLI to interact with the contract. Frontends make interacting with the contract much easier for end users as all the contract’s information can be displayed in front of the user simultaneously, calls can be made with a click of a button and the only prerequisite is a wallet. + +## 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. @@ -38,12 +39,11 @@ Navigate to the `frontend` directory then install dependencies and start the fro - ## 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. +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. -Lets take a look at how the code in the frontend is structured. +Let's take a look at how the code in the frontend is structured. ``` TODO add file structure of frontend @@ -65,6 +65,7 @@ nft-contract TODO add info about each file + | File | Description | |----------------------------------|---------------------------------------------------------------------------------| | approval.rs | Has the functions that controls the access and transfers of non-fungible tokens | @@ -80,23 +81,24 @@ TODO add info about each file ## 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. -TODO add config file code +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. + +TODO add config file code --- -## Setting up wallets +## Setting up wallets -To be able to interact with the contract - send bids or claim the auction - you'll need a `wallet` to sign transactions. Wallets store your private keys in a secure way and allow you to sign transactions without exposing your private key to the frontend. For this we use the wallet selector. +To be able to 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. For this, we use the wallet selector. We abstract the wallet selector in our `near.js` file by exposing methods to complete various tasks. Feel free to explore the file to understand how the wallet selector is implemented. TODO insert link to near.js file. -We implement a sign in and sign out button 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 non payable transactions on behalf of the user, to the specified contract, without requiring the user to sign each transaction in the wallet; this creates 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 implement a sign-in and sign-out button 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 creates 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. -TODO sign in and out button + near.js file code +TODO sign in and out button + near.js file code -We add the wallet and the account ID that is signed in to the frontend's context making it easier to access in each componenet. +We add the wallet and the account ID that is signed in to the frontend's context making it easier to access in each component. TODO add code for context and where context is added @@ -104,22 +106,23 @@ TODO add code for context and where context is added ## Displaying the highest bid -We call the method `get_auction_info` to get all the information about the auction. This will be used to display the highest bidder, the auction end time, and the NFT and FT contract IDs. +We call the method `get_auction_info` to get all the information about the auction. This will be used to display the highest bidder, the auction end time, and the NFT and FT contract IDs. TODO add code for get_auction_info and the view call. -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 avaiable](../../5.api/rpc/providers.md). We are querying the RPC with optimistic finality, which uses the queries the latest block recorded on the node. Alternatively, one can 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). +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 can 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 and dispay the bid amount and the bidder's account ID in the component. +We then pass the information about the highest bidder into the `LastBid` component and display the bid amount and the bidder's account ID in the component. -TODO add code for passing props into Last Bid component and the LastBid component code. +TODO add code for passing props into the Last Bid component and the LastBid component code. -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). +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). TODO add code for calling FT contract ft metadata --- + ## 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`. @@ -130,7 +133,7 @@ TODO add code for updating the highest bid every 5 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). To make this look nicer we will display the time left in days, hours, minutes and seconds. +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. TODO add code for displaying the time left @@ -138,17 +141,17 @@ TODO add code for displaying the time left ## 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 NFTs 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. +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. TODO Show getNftInfo method -Note that this effect will only run once the `auctionInfo` updates because we first need the the NFT contract ID and token ID from `auctionInfo` to make a valid call to `nft_token`. +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. +In the `AuctionItem` component we display the NFT image, name, and description. TODO add code for displaying the NFT -Note that the an image caching service is used to display the NFT image for better performance. +Note that an image caching service is used to display the NFT image for better performance. --- @@ -164,7 +167,7 @@ We now multiply the bid amount by the decimals of the FT to get the correct amou ## Claiming the auction -Once the 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 contract account the FTs. +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 contract account the FTs. TODO add claim code in index.js @@ -172,6 +175,6 @@ TODO add claim code in index.js ## Conclusion -In this part of the tutorial we have implemented a simple frontend for a NEAR contract. Along the way we have learnt how to use the wallet selector to sign the user in and out, how to view the contracts state, how to sign and send transactions, and use ft_transfer_call from a frontend. +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 auctions bidding history. Since the contract only stores the most recent bid we need to use an indexer to pull historical data. In the next part of the tutorial we'll look at using an indexer to query historical data. +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 of the tutorial, we'll look at using an indexer to query historical data. From 1b6285102cdcd43ddab31dbe03cc1c6913daf2f7 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Thu, 29 Aug 2024 12:22:57 +0100 Subject: [PATCH 19/32] update 5-frontend --- docs/3.tutorials/auction/4-ft.md | 10 +- docs/3.tutorials/auction/5-frontend.md | 139 +++++++++++++++---------- 2 files changed, 89 insertions(+), 60 deletions(-) diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index d94b860a241..461dd5b3d0e 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -8,7 +8,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github, Language} from "@site/src/components/codetabs" -To further develop this contract we're going to 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 were holding their own auction and want the auction to happen in their project's token $REF. +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. --- @@ -126,7 +126,7 @@ When we want to return the funds to the previous bidder we now make a cross-cont start="78" end="81" /> - 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. + 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. @@ -309,7 +309,7 @@ As stated previously, to bid on the auction the bidder now calls `ft_transfer_ca --- -## Checking user's FT balance +## 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. @@ -420,8 +420,8 @@ However, this architecture could be deemed less secure since if a bad actor were ## 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! +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. +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, 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 index 25035548f17..e663cda34bf 100644 --- a/docs/3.tutorials/auction/5-frontend.md +++ b/docs/3.tutorials/auction/5-frontend.md @@ -8,12 +8,11 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github, Language} from "@site/src/components/codetabs" - -Now we have created a complete contract, we'll need a frontend to make it easy to interact with it. Until now, we've just been using the CLI to interact with the contract. Frontends make interacting with the contract much easier for end users as all the contract’s information can be displayed in front of the user simultaneously, calls can be made with a click of a button and the only prerequisite is a wallet. +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. +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. @@ -39,95 +38,110 @@ Navigate to the `frontend` directory then install dependencies and start the fro + ## 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. -Let's take a look at how the code in the frontend is structured. - -``` -TODO add file structure of frontend - -nft-contract -β”œβ”€β”€ Cargo.lock -β”œβ”€β”€ Cargo.toml -β”œβ”€β”€ README.md -└── src - β”œβ”€β”€ approval.rs - β”œβ”€β”€ enumeration.rs - β”œβ”€β”€ lib.rs - β”œβ”€β”€ metadata.rs - β”œβ”€β”€ mint.rs - β”œβ”€β”€ nft_core.rs - β”œβ”€β”€ events.rs - └── royalty.rs -``` - -TODO add info about each file - +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 | |----------------------------------|---------------------------------------------------------------------------------| -| approval.rs | Has the functions that controls the access and transfers of non-fungible tokens | -| enumeration.rs | Contains the methods to list NFT tokens and their owners | -| lib.rs | Holds the smart contract initialization functions | -| metadata.rs | Defines the token and metadata structure | -| mint.rs | Contains token minting logic | -| nft_core.rs | Core logic that allows you to transfer NFTs between users. | -| royalty.rs | Contains payout-related functions | -| events.rs | Contains events related structures | +| **_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. -TODO add config file code + + --- ## Setting up wallets -To be able to 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. For this, we use the wallet selector. +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 to understand how the wallet selector is implemented. TODO insert link to near.js file. +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 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 creates 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 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. -TODO sign in and out button + near.js file code + + + + -We add the wallet and the account ID that is signed in to the frontend's context making it easier to access in each component. +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. -TODO add code for context and where context is added + + + + --- ## Displaying the highest bid -We call the method `get_auction_info` to get all the information about the auction. This will be used to display the highest bidder, the auction end time, and the NFT and FT contract IDs. +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. -TODO add code for get_auction_info and the view call. + + + + -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 can 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). +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 and display the bid amount and the bidder's account ID in the component. +We then pass the information about the highest bidder into the `LastBid` component to display the bid amount and the bidder's account ID. -TODO add code for passing props into the Last Bid component and the LastBid component code. + + + + 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). -TODO add code for calling FT contract ft metadata + --- - ## 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`. -TODO add code for updating the highest bid every 5 seconds + --- @@ -135,7 +149,9 @@ TODO add code for updating the highest bid every 5 seconds 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. -TODO add code for displaying the time left + --- @@ -143,13 +159,17 @@ TODO add code for displaying the time left 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. -TODO Show getNftInfo method + 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. -TODO add code for displaying the NFT + Note that an image caching service is used to display the NFT image for better performance. @@ -159,7 +179,14 @@ Note that an image caching service is used to display the NFT image for better p 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. -TODO add bid code in index.js and the call method in near.js + + + + 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. @@ -167,9 +194,11 @@ We now multiply the bid amount by the decimals of the FT to get the correct amou ## 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 contract account the FTs. +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. -TODO add claim code in index.js + --- From f3bd686258bc354c331892dc0816b9e47690c0de Mon Sep 17 00:00:00 2001 From: PiVortex Date: Fri, 30 Aug 2024 11:14:11 +0100 Subject: [PATCH 20/32] add showSingleFName --- docs/3.tutorials/auction/5-frontend.md | 55 +++++++++++++++----------- website/src/components/codetabs.js | 4 +- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/docs/3.tutorials/auction/5-frontend.md b/docs/3.tutorials/auction/5-frontend.md index e663cda34bf..0a34dde3a4b 100644 --- a/docs/3.tutorials/auction/5-frontend.md +++ b/docs/3.tutorials/auction/5-frontend.md @@ -64,10 +64,11 @@ For starters, let's take a look at how the code in the frontend is structured by 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. - - + + + --- @@ -129,9 +130,11 @@ We then pass the information about the highest bidder into the `LastBid` compone 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). - + + + --- @@ -139,37 +142,43 @@ When we display the latest bid, instead of just showing the bid amount directly 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. @@ -196,9 +205,11 @@ We now multiply the bid amount by the decimals of the FT to get the correct amou 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. - + + + --- 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]} From 01461c76bee8b5f96a32e6bc7e34345cd960c93a Mon Sep 17 00:00:00 2001 From: PiVortex Date: Tue, 3 Sep 2024 15:47:01 +0100 Subject: [PATCH 21/32] add factory contract docs --- docs/3.tutorials/auction/6-indexing.md | 6 ++ docs/3.tutorials/auction/7-factory.md | 106 +++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 docs/3.tutorials/auction/6-indexing.md create mode 100644 docs/3.tutorials/auction/7-factory.md diff --git a/docs/3.tutorials/auction/6-indexing.md b/docs/3.tutorials/auction/6-indexing.md new file mode 100644 index 00000000000..a0549ac4e27 --- /dev/null +++ b/docs/3.tutorials/auction/6-indexing.md @@ -0,0 +1,6 @@ +--- +id: indexing-historical-data +title: Indexing historical data +sidebar_label: Indexing historical data +--- + 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 From 89ad010a39c8dbff99804d2da93e01fec401bdf0 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Fri, 6 Sep 2024 21:06:56 +0200 Subject: [PATCH 22/32] wip: re-structuring --- .../2.smart-contracts/anatomy/anatomy.md | 19 +- .../2.smart-contracts/anatomy/environment.md | 2 +- docs/3.tutorials/auction/0-intro.md | 86 ++- docs/3.tutorials/auction/1-basic.md | 539 ++++-------------- docs/3.tutorials/auction/1.1-testing.md | 207 +++++++ docs/3.tutorials/auction/1.3-deploy.md | 287 ++++++++++ website/sidebars.js | 4 +- 7 files changed, 655 insertions(+), 489 deletions(-) create mode 100644 docs/3.tutorials/auction/1.1-testing.md create mode 100644 docs/3.tutorials/auction/1.3-deploy.md 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" /> -For this tutorial, you have the option to follow along in JavaScript or Rust. - --- ## Prerequisites -Before starting, make sure to set up your development environment. +Before starting, make sure to set up your development environment!
Working on Windows? @@ -71,59 +73,45 @@ Before starting, make sure to set up your development environment. +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 -These are the steps that will bring you from **Zero** to **Hero** in no time! πŸ’ͺ +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. -| Step | Name | Description | -|------|----------------------------------------|-----------------------------------------------------------------| -| 1 | [Basic contract](./1-basic.md) | Learn the how a basic smart contract is structured | -| 2 | [Locking the contract](./2-locking.md) | Learn to create contracts with no access keys | +#### 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 ---- - -## Next steps +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 -Ready to start? Let's jump to the [Basic contract](./1-basic.md) and begin your learning journey! +#### 3. Factory +1. Creating a factory: Allow users to easily deploy and initialize their own auction contracts -If you would like to learn about a specific concept feel free to skip to the relevant section. --- -## Versioning - - - - - Versioning for this tutorial - At the time of this writing, this example works with the following versions: - - - near-cli-rs: `0.12.0` - - near-sdk-js: `2.0.0` - - near-workspaces-js: `3.5.0` - - node: `21.6.1` - +## Next steps - - +Ready to start? Let's jump to the [The Auction Contract](./1-basic.md) and begin your learning journey! - Versioning for this tutorial - At the time of this writing, this example works with the following versions: +:::note Versioning for this article - - near-cli-rs: `0.12.0` - - near-sdk-rs: `5.1.0` - - near-workspaces-rs: `0.10.0` - - rustc: `1.78.0` - - cargo-near: `0.6.2` +- 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-basic.md b/docs/3.tutorials/auction/1-basic.md index 6e51b5838b3..ecc7ac27d69 100644 --- a/docs/3.tutorials/auction/1-basic.md +++ b/docs/3.tutorials/auction/1-basic.md @@ -1,25 +1,38 @@ --- id: basic-auction title: Basic Auction -sidebar_label: Basic Auction --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import {Github, Language} from "@site/src/components/codetabs" -In this section of the tutorial, we'll clone a simple auction smart contract and analyze each section of the contract in-depth. We'll also look at how to test a smart contract, and then how to deploy and interact with the contract on testnet. +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 repo](https://github.com/near-examples/auctions-tutorial). +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 @@ -28,7 +41,7 @@ To get started we'll clone the [tutorial repo](https://github.com/near-examples/ - ``` + ```bash git clone git@github.com:near-examples/auctions-tutorial.git cd contract-rs/01-basic-auction @@ -37,20 +50,18 @@ To get started we'll clone the [tutorial repo](https://github.com/near-examples/ -The repository is structured in three folders, two contain the same smart contracts written in JavaScript and Rust and the third contains a frontend that interacts with the contracts. - -Navigate to the folder of the language you prefer, and then to the `01-basic-auction` folder. ---- +:::info Frontend -## Anatomy of the Contract +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 -Let's take a look at the contract structure. The contract is a simple auction contract that allows users to place bids and track the highest bidder. +::: +--- -### Contract's Definition +## The Contract's State -The contract stores two main fields: what was the highest bid so far, and when the auction will end. To simplify storing the highest bid, the contract uses an auxillary structure called `Bid` that contains the bidder's account ID and the amount they bid. +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. @@ -59,20 +70,16 @@ The contract stores two main fields: what was the highest bid so far, and when t url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/src/contract.ts#L4-L12" start="4" end="12" /> -
- - Decorators - - Let's take a closer look at the decorator on `AuctionContract` and what it does: - - - `NearBindgen` extends the class and its methods to make the contract serializable and deserializable to read and write to the blockchain in binary form. It also exposes the contract's methods to the outside world, allowing the contract to be interacted with. - - `requireInit` specifies that the contract has to be initialized using a method. + #### 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. -
- - - `bid` is typed `BigInt` which stores a number of $NEAR tokens in `yoctonear` (10^-24 NEAR). - - `bidder` is typed `AccountId` which automatically check whether the account address is valid. + #### 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**
@@ -81,509 +88,177 @@ The contract stores two main fields: what was the highest bid so far, and when t url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs#L5-L17" start="5" end="17" /> -
- - Macros - - Let's take a closer look at the macros the structures implement and what they do. - - `Contract` has the macros: - - **contract_state**: enables borsh serialization and deserialization to read and write the structure to the blockchain in binary form, and generates a schema for the structure. - - **serializers**: enables both borsh and JSON serialization and deserialization. The JSON serializer allows for the whole contract state to be outputted in JSON form. Although borsh is already enabled by contract_state, we have to specify it again in serializers if we want to add JSON. - - **PanicOnDefault**: forces the contract to have custom initialization (we will see this later). + #### 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. - `Bid` has the macros: - - **serializers**: enables both borsh and JSON serialization and deserialization. - - **Clone**: allows a Bid object to be duplicated. + 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. - - `bid` is typed `NearToken` for storing a number of $NEAR tokens. The type makes it easier to handle $NEAR tokens by implementing methods to express the value in `yoctonear` (10^-24 $NEAR), `milinear` and `near`. - - `bidder` is typed `AccountId` which automatically check whether the account address is valid. - - `auction_end_time` is of type `U64`. Since u64 has to be converted to a string for input and output, U64 automates the type casting between u64 and string. + - `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**
---- - -### Initializing the contract - -Now let's take a look at the contract's methods. - -First, the contract has an initialization method that determines the initial state of the contract. This contract requires custom initialization meaning the user is required to input parameters to determine the initial state. +:::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) - - - - - - -
- - Decorators - - Let's take a closer look at the decorator on `init` and what it does: - - - `initialize` denotes that `init` is an initialization method meaning it can only be called once and has to be the first method to be called. - - `privateFunction` is used to restrict the method to only be called by the account on which the contract is deployed. - -
- -
- - - - - -
- - Macros - - Let's take a closer look at the macros and what they do: - - - The implementation of `Contract` is decorated with the `near` macro to declare that the methods inside of Contract can be called by the outside world. - - `init` has the macro `init` to denote it is an initialization method meaning it can only be called once and has to be the first method to be called. - - `init` has the macro `private` to restrict the method to only be called by the account on which the contract is deployed. - -
- -
- -
- -When this method is called, the caller will decide the `end_time`, `bid` will be set to one yoctoNEAR and the `bidder` will be set to the account on which the contract is deployed so that if no one bids, no one will win the auction. +::: --- -### Placing a bid +## Initialization Function -An auction isn't an auction if you can't place a bid! The contract has a `bid` method that allows a user to place a bid by attaching $NEAR tokens to the method call. A successful implementation of the `bid` method will check whether the auction is still ongoing, check whether their bid is higher than the previous and, if it meets both criteria, it will update the `highest_bid` field accordingly and return tokens back to the previous bidder. +Lets now take a look at the initialization function, which we need to call to determine the time at which the auction will end. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-ts/01-basic-auction/src/contract.ts" + start="14" end="18" /> - When the user calls the `bid` method they will transfer $NEAR tokens to the contract; to enable the transfer of $NEAR tokens we set `payableFunction` to `true`. The method is decorated with `call` so the state of the contract can be changed within the method. These types of methods require a user to sign the transaction that calls the method and requires the user to attach gas. + #### 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. - - When the user calls the `bid` method they will transfer $NEAR tokens to the contract; to enable the transfer of $NEAR tokens we need to decorate the method with the `payable` macro. The method takes a mutable reference to `self` (&mut self) so the state of the contract can be changed within the method. These types of methods require a user to sign the transaction that calls the method and requires the user to attach gas. + url="https://github.com/near-examples/auctions-tutorial/blob/main/contract-rs/01-basic-auction/src/lib.rs" + start="19" end="31" /> -
+ #### 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. - require over assert - - Require has been used here as opposed to assert as it reduces the contract - size by not including file and rust-specific data in the panic message. - -
-
- -
- -There are some specifics to this method that we should take a closer look at: - - -- The `block timestamp` is used to determine whether the auction is still ongoing. It gives the time as the number of nanoseconds since January 1, 1970, 0:00:00 UTC. -- The `predecessor` gives the account (or contract) that directly called the bid method, this can be different to the signer who initially signed the transaction leading to the execution of this method. If pivortex.near calls a contract proxy-contract.near which then calls bid on this contract the predecessor would be proxy-contract.near and the signer would be pivortex.near. For security purposes, so a malicious contract can't place a bid in your name, we stick with predecessor using here. -- The contract returns a `Promise` that will execute the transfer of $NEAR tokens back to the previous bidder. - -Note that in the case of 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. - ---- - -### Viewing the contract state - -The contract implements methods that do not change the contract's state but just view it. These methods don't require gas. We would want, for example, a method to see, in the frontend, what the highest bid amount was so we make sure to place a higher bid. - - - - - - - View methods are decorated with `view` so they cannot change the contract's state. - - - - - - - - View methods take an immutable reference to `self` (&self) so they cannot change the contract's state. - - - - - - - - -The contract has two further view methods: the first retrieves the end time of the auction and the second retrieves all the information stored in the `Contract` struct. - ---- - -## Contract tests - -Ok, now we've seen the contract we need to make sure it works as expected; this is done through testing. It's good practice to implement exhaustive tests so you can ensure that any little change to your code down the line doesn't break your contract. - -In our rust repository, we have a unit test to check that the contract is initialized properly. Unit tests are used to test contract methods individually, these tests work well when little context is required. However, because our contract has operations like sending accounts $NEAR tokens, which happen external to the contract, we need an environment to test the contract. - ---- - -## Sandbox testing - -Integration tests allow you to deploy your contract (or contracts) in a sandbox to interact with it in a realistic environment where, for example, accounts have trackable balances Throughout this section, you'll see there is just one large test. This is because the contract only has one possible flow meaning all methods can be properly tested in one test. - ---- - -### Creating the sandbox - -The first thing the test does is create the sandbox environment. - - - - - - Enter sandbox-test > main.ava.js - - - - - - - - Enter tests > test_basics.rs - - - - - - - ---- - -### Creating user accounts - -Next, the test creates a couple of user accounts that will be used to send transactions to the contract. Each account is given an account ID and an initial balance. - - - - - - - - - - - - - - - + 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`. -### Deploying the contract +#### 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. -Likewise, a "contract" account is created and the contract WASM is deployed to it. - - - - +:::tip Learn More - The contract comes from compiling the contract to WASM using the build script in the `package.json` file and then reading it. +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). - - - - - - - The contract comes from the test compiling the contract to WASM using `cargo near build` and then reading it. - - - - - - +::: --- -### Initializing the contract +## Read-only Functions -To initialize the contract `init` is called with `end_time` set to 60 seconds in the future. +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. - - - - - - - - - - - - ---- - -### Testing methods - -Now the contract is deployed and initialized, bids are made to the contract and it's checked that the state changes as intended. - - - - + - + 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. - - - - - - ---- - -### Checking user balances - -When a higher bid is placed the previous bidder should be returned the $NEAR they bid. This is checked by querying the $NEAR balance of the user account. We might think that the test would check whether Alice's balance is back to 10 $NEAR but it does not. This is because some $NEAR is consumed as `gas` fees when Alice calls `bid`. Instead, Alice's balance is recorded after she bids, then once another user bids it checks that exactly 1 $NEAR has been added to her balance. - - - - - - - - - - + - + 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`). ---- - -### 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. - - - - - - - - +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). - +::: - --- -### Fast forwarding time - -To test the contract when the auction is over the test uses `fast forward` to advance time in the sandbox. Note that fast forward takes the number of blocks to advance not the number of seconds. The test advances 200 blocks so the time will now be past the minute auction end time that was set. - - - - - - +## 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. -Now that the auction has ended the contract shouldn't allow any more bids. +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. - + - + ---- - -## Testing and deploying +#### 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. -Cool, now we've seen how tests are written, let's go ahead and run the tests. We'll then build and deploy the contract to testnet. +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. - Install dependencies +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. - ``` - npm install - ``` +
- Run tests and build the contract WASM + Handling Funds - ``` - npm run test - ``` +When a user attaches tokens to a call, the tokens are deposited on the contract's account before the function is executed. However, if the function raises an error during its execution, the tokens are immediately refunded to the user. - You'll need a testnet account to deploy the contract to, so if you don't have one you can use +
- ``` - near account create-account sponsor-by-faucet-service autogenerate-new-keypair - ``` +:::tip Learn More - Then deploy and initialize the contract with +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) - ``` - near contract deploy use-file with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet - ``` - - -
- - - - Run tests - - ``` - cargo test - ``` - - If all the tests are successful we can build the contract WASM - - ``` - cargo near build - ``` - - You'll need a testnet account to deploy the contract to, so if you don't have one you can use - - ``` - cargo near create-dev-account use-random-account-id - ``` - - Then deploy and initialize the contract with - - ``` - cargo near deploy with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet - ``` - - - -
- - -Now the contract is deployed and initialized we can send transactions to it using the CLI. NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands. But here you can use the following full commands to call the contract's methods: - -Call `bid`, you may want to create another testnet account for the signer - -``` -near contract call-method as-transaction bid json-args {} prepaid-gas '100.0 Tgas' attached-deposit '1 NEAR' sign-as network-config testnet -``` - -Call `get_highest_bid` - -``` -near contract call-method as-read-only get_highest_bid json-args {} network-config testnet now -``` +::: --- ## Conclusion -In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data and views the data. We also looked at how tests are written and how to execute them. Finally, we learned to compile, deploy and interact with the contract through the CLI on testnet. - -There is a core problem to this contract; there needs to exist a key to the contract for the auctioneer to claim the funds. This allows for the key holder to do with the contract as they please, for example withdrawing funds from the contract at any point. In the [next part](./2-locking.md), we'll learn how to lock a contract by specifying an auctioneer on initialization who can claim the funds through a new method we'll introduce. \ No newline at end of file +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.1-testing.md b/docs/3.tutorials/auction/1.1-testing.md new file mode 100644 index 00000000000..5eb8d0838fd --- /dev/null +++ b/docs/3.tutorials/auction/1.1-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! + + + + + + + ``` + # if you haven't already, install the dependencies + npm install + + # run the tests + npm run test + ``` + + + + + + ``` + 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..e576d509cca --- /dev/null +++ b/docs/3.tutorials/auction/1.3-deploy.md @@ -0,0 +1,287 @@ +--- +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 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! + + + + + + + ``` + # if you haven't already, install the dependencies + npm install + + # run the tests + npm run test + ``` + + + + + + ``` + 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. + + + + + + ``` + # if you haven't already, install the dependencies + npm install + + # run the tests + npm run test + ``` + + You'll need a testnet account to deploy the contract to, so if you don't have one you can use + + ``` + near account create-account sponsor-by-faucet-service autogenerate-new-keypair + ``` + + Then deploy and initialize the contract with + + ``` + near contract deploy use-file with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet + ``` + + + + + + + Run tests + + ``` + cargo test + ``` + + If all the tests are successful we can build the contract WASM + + ``` + cargo near build + ``` + + You'll need a testnet account to deploy the contract to, so if you don't have one you can use + + ``` + cargo near create-dev-account use-random-account-id + ``` + + Then deploy and initialize the contract with + + ``` + cargo near deploy with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet + ``` + + + + + + +Now the contract is deployed and initialized we can send transactions to it using the CLI. NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands. But here you can use the following full commands to call the contract's methods: + +Call `bid`, you may want to create another testnet account for the signer + +``` +near contract call-method as-transaction bid json-args {} prepaid-gas '100.0 Tgas' attached-deposit '1 NEAR' sign-as network-config testnet +``` + +Call `get_highest_bid` + +``` +near contract call-method as-read-only get_highest_bid json-args {} network-config testnet now +``` + +--- + +## Conclusion + +In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data and views the data. We also looked at how tests are written and how to execute them. Finally, we learned to compile, deploy and interact with the contract through the CLI on testnet. + +There is a core problem to this contract; there needs to exist a key to the contract for the auctioneer to claim the funds. This allows for the key holder to do with the contract as they please, for example withdrawing funds from the contract at any point. In the [next part](./2-locking.md), we'll learn how to lock a contract by specifying an auctioneer on initialization who can claim the funds through a new method we'll introduce. \ No newline at end of file diff --git a/website/sidebars.js b/website/sidebars.js index 099225a9c19..e980b054047 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -383,9 +383,11 @@ const sidebar = { "value": " Tutorials " }, { - "NEAR Zero to Hero": [ + "NEAR 101: Building Web3 Apps": [ "tutorials/auction/introduction", "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", From cb431d26942625814aedb598bb4ea04ccf05e292 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Mon, 9 Sep 2024 15:37:39 +0100 Subject: [PATCH 23/32] add indexing page --- docs/3.tutorials/auction/2-locking.md | 2 +- docs/3.tutorials/auction/4-ft.md | 2 +- docs/3.tutorials/auction/5-frontend.md | 2 +- docs/3.tutorials/auction/6-indexing.md | 79 ++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md index f93173a437a..96668b19ea6 100644 --- a/docs/3.tutorials/auction/2-locking.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -118,4 +118,4 @@ Be extra careful to delete the keys from the correct account as you'll never be 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 (coming soon), 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 +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/4-ft.md b/docs/3.tutorials/auction/4-ft.md index 461dd5b3d0e..ed13cd4cbdf 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -424,4 +424,4 @@ In this section, we learned a lot about fungible tokens: how to send and receive 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, 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 +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 index 0a34dde3a4b..274c4f3e3ff 100644 --- a/docs/3.tutorials/auction/5-frontend.md +++ b/docs/3.tutorials/auction/5-frontend.md @@ -217,4 +217,4 @@ Once the auction is over (the current time is greater than the end time) the auc 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 of the tutorial, we'll look at using an indexer to query historical data. +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 index a0549ac4e27..7863fb3ff56 100644 --- a/docs/3.tutorials/auction/6-indexing.md +++ b/docs/3.tutorials/auction/6-indexing.md @@ -4,3 +4,82 @@ 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 From 12a49f7153f7465f0367d152943e331b14a63643 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Mon, 9 Sep 2024 13:02:18 -0400 Subject: [PATCH 24/32] finished 1st part --- docs/3.tutorials/auction/1.1-testing.md | 4 +- docs/3.tutorials/auction/1.3-deploy.md | 276 +++++------------------- 2 files changed, 59 insertions(+), 221 deletions(-) diff --git a/docs/3.tutorials/auction/1.1-testing.md b/docs/3.tutorials/auction/1.1-testing.md index 5eb8d0838fd..20b03a31c0f 100644 --- a/docs/3.tutorials/auction/1.1-testing.md +++ b/docs/3.tutorials/auction/1.1-testing.md @@ -176,7 +176,7 @@ 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 @@ -188,7 +188,7 @@ Now that we understand what we are testing, let's go ahead and run the tests! - ``` + ```bash cargo test ``` diff --git a/docs/3.tutorials/auction/1.3-deploy.md b/docs/3.tutorials/auction/1.3-deploy.md index e576d509cca..685207fa356 100644 --- a/docs/3.tutorials/auction/1.3-deploy.md +++ b/docs/3.tutorials/auction/1.3-deploy.md @@ -7,281 +7,119 @@ 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 sections we saw how a simple auction smart contract is implemented, and checked its correctness using 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. +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 unit testing +:::info Networks -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. +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 ::: --- -## Account Creation +## Testnet Account -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 a contract, you need a testnet account. If you don't have one, you can create one using the following command: - - - - - 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` - -::: +```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 -:::warning Time Units +Notice that we are using the `--useFaucet` flag to automatically request test tokens from the NEAR faucet. -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** +The faucet is only available on the testnet network - which is the default network for the CLI ::: --- -## Bidding +## Deploying the Contract -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. +To deploy the contract, you need to compile the contract code into WebAssembly (WASM) and then deploy it to the network - - - - - - - - - - - - -#### 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`. + ```bash + # compile the contract + npm run build -#### Testing invalid calls + # deploy the contract + near deploy ./build/auction.wasm -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. - - - - - - + # 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 -## 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. - - - + # 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 + ``` - - - - ---- - -## Executing the tests -Now that we understand what we are testing, let's go ahead and run the tests! +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 +::: - - - - - ``` - # if you haven't already, install the dependencies - npm install - - # run the tests - npm run test - ``` +--- - +## 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 - ``` - cargo test - ``` - - +# 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. -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! +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 -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. - - - - - - ``` - # if you haven't already, install the dependencies - npm install - - # run the tests - npm run test - ``` +We have now seen how to deploy a contract to the testnet and interact with it using the NEAR CLI. - You'll need a testnet account to deploy the contract to, so if you don't have one you can use +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. - ``` - near account create-account sponsor-by-faucet-service autogenerate-new-keypair - ``` +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. - Then deploy and initialize the contract with +:::tip Frontend - ``` - near contract deploy use-file with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet - ``` - - - - - - - Run tests - - ``` - cargo test - ``` - - If all the tests are successful we can build the contract WASM - - ``` - cargo near build - ``` - - You'll need a testnet account to deploy the contract to, so if you don't have one you can use - - ``` - cargo near create-dev-account use-random-account-id - ``` - - Then deploy and initialize the contract with - - ``` - cargo near deploy with-init-call init json-args '{"end_time": "300000000000000000"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet - ``` - - - - - - -Now the contract is deployed and initialized we can send transactions to it using the CLI. NEAR's CLI is interactive meaning you can type `near` and click through all the possible options without having to remember certain commands. But here you can use the following full commands to call the contract's methods: - -Call `bid`, you may want to create another testnet account for the signer - -``` -near contract call-method as-transaction bid json-args {} prepaid-gas '100.0 Tgas' attached-deposit '1 NEAR' sign-as network-config testnet -``` - -Call `get_highest_bid` - -``` -near contract call-method as-read-only get_highest_bid json-args {} network-config testnet now -``` - ---- - -## Conclusion +Generally you will use the CLI only to deploy and initialize the contract. After, all interactions will be made from a frontend -In this part of the tutorial, we've seen how a smart contract stores data, mutates the stored data and views the data. We also looked at how tests are written and how to execute them. Finally, we learned to compile, deploy and interact with the contract through the CLI on testnet. +We will cover this topic in the future, after we have finished adding more features to the auction contract -There is a core problem to this contract; there needs to exist a key to the contract for the auctioneer to claim the funds. This allows for the key holder to do with the contract as they please, for example withdrawing funds from the contract at any point. In the [next part](./2-locking.md), we'll learn how to lock a contract by specifying an auctioneer on initialization who can claim the funds through a new method we'll introduce. \ No newline at end of file +::: \ No newline at end of file From e46e7569e6f67eb89cfeb64d17bbf774d8276106 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Mon, 9 Sep 2024 13:02:24 -0400 Subject: [PATCH 25/32] finished 1st part --- website/sidebars.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/website/sidebars.js b/website/sidebars.js index e980b054047..2ac11d48c21 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -385,10 +385,14 @@ const sidebar = { { "NEAR 101: Building Web3 Apps": [ "tutorials/auction/introduction", - "tutorials/auction/basic-auction", - "tutorials/auction/sandbox-testing", - "tutorials/auction/deploy", - "tutorials/auction/locking-the-contract", + { + "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", ] From 8d94ea40faff47a8f3dd2a6b4813e9541c3ac514 Mon Sep 17 00:00:00 2001 From: gagdiez Date: Mon, 9 Sep 2024 13:11:14 -0400 Subject: [PATCH 26/32] rename files --- docs/3.tutorials/auction/{1-basic.md => 1.1-basic.md} | 0 docs/3.tutorials/auction/{1.1-testing.md => 1.2-testing.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/3.tutorials/auction/{1-basic.md => 1.1-basic.md} (100%) rename docs/3.tutorials/auction/{1.1-testing.md => 1.2-testing.md} (100%) diff --git a/docs/3.tutorials/auction/1-basic.md b/docs/3.tutorials/auction/1.1-basic.md similarity index 100% rename from docs/3.tutorials/auction/1-basic.md rename to docs/3.tutorials/auction/1.1-basic.md diff --git a/docs/3.tutorials/auction/1.1-testing.md b/docs/3.tutorials/auction/1.2-testing.md similarity index 100% rename from docs/3.tutorials/auction/1.1-testing.md rename to docs/3.tutorials/auction/1.2-testing.md From ea8aa5ae38cb5c5b936ae79002c5a77a07041eaa Mon Sep 17 00:00:00 2001 From: gagdiez Date: Mon, 9 Sep 2024 13:15:55 -0400 Subject: [PATCH 27/32] fix: links --- docs/2.build/2.smart-contracts/what-is.md | 8 ++++---- docs/6.integrations/create-transactions.md | 2 +- website/src/theme/Footer/index.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/2.build/2.smart-contracts/what-is.md b/docs/2.build/2.smart-contracts/what-is.md index b57eefc2d99..2ffa0a055a5 100644 --- a/docs/2.build/2.smart-contracts/what-is.md +++ b/docs/2.build/2.smart-contracts/what-is.md @@ -53,10 +53,10 @@ Besides, smart contracts can store data in the account's storage. This allows co ## What are they used for? Smart contracts are useful to create **decentralized applications**. Some traditional examples include: -- [Decentralized Autonomous Organizations](https://dev.near.org/nearcatalog.near/widget/Index?cat=dao), where users create and vote proposals -- [Marketplaces](https://dev.near.org/nearcatalog.near/widget/Index?cat=marketplaces), where users create and commercialize digital art pieces -- [Decentralized exchanges](https://dev.near.org/nearcatalog.near/widget/Index?cat=exchanges), where users can trade different currencies -- [And many more...](https://dev.near.org/nearcatalog.near/widget/Index) +- [Decentralized Autonomous Organizations](https://dev.near.org/applications?cat=dao), where users create and vote proposals +- [Marketplaces](https://dev.near.org/applications?cat=marketplaces), where users create and commercialize digital art pieces +- [Decentralized exchanges](https://dev.near.org/applications?cat=exchanges), where users can trade different currencies +- [And many more...](https://dev.near.org/applications) For instance, you can easily create a crowdfunding contract that accepts $NEAR. If the goal is met in time, the creator can claim the funds. Otherwise, the backers are refunded. diff --git a/docs/6.integrations/create-transactions.md b/docs/6.integrations/create-transactions.md index fd177598ab1..07bf66a737f 100644 --- a/docs/6.integrations/create-transactions.md +++ b/docs/6.integrations/create-transactions.md @@ -198,7 +198,7 @@ const provider = new nearAPI.providers.JsonRpcProvider( To sign a transaction to send NEAR Ⓝ, we will need a `FullAccess` key to the sender's account. -- If you created the account using [`near-cli`](../4.tools/cli) or ran [`near login`](../4.tools/cli#import) in your terminal, your private key can be found in the your machine keychain. +- If you created the account using [`near-cli`](../4.tools/cli.md) or ran [`near login`](../4.tools/cli#import) in your terminal, your private key can be found in the your machine keychain. - If you created an account using [NEAR Wallet](https://testnet.mynearwallet.com/), your key will be found in your browser's `Local Storage`. - In your browser's dev tools... `Application` >> `Storage` >> `Local Storage` 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
  • From 6652854ff87df9a2a888c0e73d62850cf9d364aa Mon Sep 17 00:00:00 2001 From: Owen <124691791+PiVortex@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:30:35 +0100 Subject: [PATCH 28/32] Update docs/6.integrations/create-transactions.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: DamiΓ‘n Parrino --- docs/6.integrations/create-transactions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/6.integrations/create-transactions.md b/docs/6.integrations/create-transactions.md index 07bf66a737f..b9cfcd4e80b 100644 --- a/docs/6.integrations/create-transactions.md +++ b/docs/6.integrations/create-transactions.md @@ -198,7 +198,7 @@ const provider = new nearAPI.providers.JsonRpcProvider( To sign a transaction to send NEAR Ⓝ, we will need a `FullAccess` key to the sender's account. -- If you created the account using [`near-cli`](../4.tools/cli.md) or ran [`near login`](../4.tools/cli#import) in your terminal, your private key can be found in the your machine keychain. +- If you created the account using [`near-cli`](../4.tools/cli.md) or ran [`near login`](../4.tools/cli.md#import) in your terminal, your private key can be found in your machine's keychain. - If you created an account using [NEAR Wallet](https://testnet.mynearwallet.com/), your key will be found in your browser's `Local Storage`. - In your browser's dev tools... `Application` >> `Storage` >> `Local Storage` From 7f2f6c6763c0ce57dbe83de5808acbdd1b2cbe36 Mon Sep 17 00:00:00 2001 From: PiVortex Date: Tue, 17 Sep 2024 13:02:49 +0100 Subject: [PATCH 29/32] fix suggested changes --- docs/3.tutorials/auction/0-intro.md | 42 +++++++++++-------------- docs/3.tutorials/auction/1.1-basic.md | 6 ++-- docs/3.tutorials/auction/1.2-testing.md | 6 ++-- docs/3.tutorials/auction/1.3-deploy.md | 4 +-- docs/3.tutorials/auction/2-locking.md | 8 ++--- docs/3.tutorials/auction/3-nft.md | 2 +- docs/3.tutorials/auction/4-ft.md | 2 +- docs/3.tutorials/auction/5-frontend.md | 34 ++++++++++---------- docs/3.tutorials/auction/6-indexing.md | 28 +++++++---------- docs/3.tutorials/auction/7-factory.md | 26 ++++----------- 10 files changed, 68 insertions(+), 90 deletions(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index 88b997fdfaa..de8c71ae71e 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -9,22 +9,21 @@ import TabItem from '@theme/TabItem'; Welcome! In this guide we will help you navigate NEAR tech stack, so you can build Web3 applications from start to finish in no-time. -We'll start from a simple auction contract and slowly build on top of it a full Web3 application to carry on-chain auctions. +We'll start from a simple auction contract and slowly build on top of it to create a full Web3 application to carry out on-chain auctions. -By the time you finish this tutorial, you will have learned how to use several key primitives and concepts along the way: +By the time you finish this tutorial, you will have learned several concepts and how to use many key privitives along the way: -- Building and testing a contract -- Deploying, updating and locking a contract -- Creating a frontend to interact with the contract -- Using an indexing API to keep track of the contract's activity -- Creating a factory to deploy new contracts +- [Creating a simple smart contract](./1.1-basic.md) +- [Writing tests for a contract](./1.2-testing.md) +- [Deploying a contract to testnet](./1.3-deploy.md) - + --- @@ -73,8 +72,7 @@ Before starting, make sure to set up your development environment! -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. - +We will be use [NEAR CLI](../../4.tools/cli.md) to interact with the blockchain through the terminal, and you can choose between JavaScript and Rust to write the contract. --- @@ -90,11 +88,11 @@ This series will touch on different level of the NEAR tech stack. Each section w #### 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 +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. Factory -1. Creating a factory: Allow users to easily deploy and initialize their own auction contracts +1. Creating a factory (soon): Allow users to easily deploy and initialize their own auction contracts --- @@ -106,12 +104,10 @@ Ready to start? Let's jump to the [The Auction Contract](./1-basic.md) and begin :::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: `1.80.1` - cargo-near: `0.6.2` +- rustc: `1.78.0` +- node: `21.6.1` ::: \ 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 index ecc7ac27d69..5780fe97420 100644 --- a/docs/3.tutorials/auction/1.1-basic.md +++ b/docs/3.tutorials/auction/1.1-basic.md @@ -7,7 +7,7 @@ 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. +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`. :::info Documentation @@ -236,7 +236,7 @@ Second, the function is marked as `payable`, this is because by default **functi 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. +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. @@ -261,4 +261,4 @@ You can read more about the environment variables, payable functions and which a ## 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 +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`. \ 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 20b03a31c0f..9cd74afc198 100644 --- a/docs/3.tutorials/auction/1.2-testing.md +++ b/docs/3.tutorials/auction/1.2-testing.md @@ -13,7 +13,7 @@ Here, we will focus on the sandbox testing, as it enables to deploy the contract :::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. +Unit tests are built into the language and are 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. ::: @@ -45,7 +45,7 @@ The first thing our test does is to create multiple accounts with 10 $NEAR token ## 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. +To initialize, the contract's account calls itself, invoking the `init` function with an `end_time` set to 60 seconds in the future. @@ -204,4 +204,4 @@ All tests should pass, and you should see the output of the tests in the console 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 +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. \ 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 685207fa356..955c9a3ff33 100644 --- a/docs/3.tutorials/auction/1.3-deploy.md +++ b/docs/3.tutorials/auction/1.3-deploy.md @@ -30,7 +30,7 @@ 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). +The account will be created with **10 NEAR** (these are test tokens). :::info Testnet Faucet @@ -110,7 +110,7 @@ For the `get_highest_bid` function, we don't need to specify which user is calli ## Conclusion -We have now seen how to deploy a contract to the testnet and interact with it using the NEAR CLI. +We have now seen how to deploy a contract to `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. diff --git a/docs/3.tutorials/auction/2-locking.md b/docs/3.tutorials/auction/2-locking.md index 96668b19ea6..a89952cc2e5 100644 --- a/docs/3.tutorials/auction/2-locking.md +++ b/docs/3.tutorials/auction/2-locking.md @@ -70,7 +70,7 @@ The `claim` method should only be callable when the auction is over, can only be 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. +We will now also test the `claim` method. The test will check that the `auctioneer` account has received the correct amount of $NEAR tokens. @@ -98,9 +98,9 @@ Note that the test doesn't check that the auctioneer has exactly 12 $NEAR since ## 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. +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. +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 @@ -116,6 +116,6 @@ Be extra careful to delete the keys from the correct account as you'll never be ## 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 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/3-nft.md b/docs/3.tutorials/auction/3-nft.md index 1794842b888..0f6d73773f7 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -43,7 +43,7 @@ When we create an auction we need to list the NFT. To specify which NFT is being ## 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`. +When the method `claim` is called the NFT needs to be transferred to the highest bidder. Operations regarding NFTs live on the NFT contract, so we make a cross-contract call to the NFT contract telling it to swap the owner of the NFT to the highest bidder. The method on the NFT contract to do this is `nft_transfer`. diff --git a/docs/3.tutorials/auction/4-ft.md b/docs/3.tutorials/auction/4-ft.md index ed13cd4cbdf..e2d6d8bc463 100644 --- a/docs/3.tutorials/auction/4-ft.md +++ b/docs/3.tutorials/auction/4-ft.md @@ -420,7 +420,7 @@ However, this architecture could be deemed less secure since if a bad actor were ## 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! +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. diff --git a/docs/3.tutorials/auction/5-frontend.md b/docs/3.tutorials/auction/5-frontend.md index 274c4f3e3ff..e24357dbab3 100644 --- a/docs/3.tutorials/auction/5-frontend.md +++ b/docs/3.tutorials/auction/5-frontend.md @@ -62,7 +62,7 @@ For starters, let's take a look at how the code in the frontend is structured by ## 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. +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. + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/pages/index.js#L129" + start="129" end="129" /> @@ -132,8 +132,8 @@ When we display the latest bid, instead of just showing the bid amount directly + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/pages/index.js#L75-L93" + start="75" end="93" /> --- @@ -144,8 +144,8 @@ We want to know the highest bid at all times, someone else could have placed a h + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/pages/index.js#L41-L55" + start="41" end="55" /> --- @@ -155,7 +155,7 @@ The contract stores the end time of the auction in the number of nanoseconds sin --- @@ -166,8 +166,8 @@ We want to show what NFT is being auctioned. To do this we will call `nft_token` + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/pages/index.js#L57-L73" + start="57" end="73" /> 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`. @@ -190,8 +190,8 @@ To make a bid we call the `ft_transfer_call` method on the FT contract which sub + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/pages/index.js#L95-L105" + start="95" end="105" /> @@ -207,14 +207,14 @@ Once the auction is over (the current time is greater than the end time) the auc + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/pages/index.js#L107-L114" + start="107" end="114" /> --- ## 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. +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. -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. +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. \ No newline at end of file diff --git a/docs/3.tutorials/auction/6-indexing.md b/docs/3.tutorials/auction/6-indexing.md index 7863fb3ff56..a81a86876fc 100644 --- a/docs/3.tutorials/auction/6-indexing.md +++ b/docs/3.tutorials/auction/6-indexing.md @@ -6,25 +6,21 @@ 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. +NEAR Blocks provides a free tier that allows you to make 6 calls per minute, which will be plenty for our use case. 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'll create a new file named `.env.local` to store our API key. - - - +```env +API_KEY=YOUR_API_KEY_GOES_HERE +``` -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. +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. --- @@ -34,8 +30,8 @@ NextJS allows us to easily create server-side functions with API routes. We need + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/pages/api/getBidHistory.js#L1-L13" + start="1" 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: @@ -54,8 +50,8 @@ The API call itself does not filter out invalid transactions. A transaction may + url="https://github.com/near-examples/auctions-tutorial/blob/main/frontend/src/pages/api/getBidHistory.js#L15-L43" + start="15" end="43" /> --- @@ -66,7 +62,7 @@ In our main page, we'll define a function to call the API route we just created. @@ -82,4 +78,4 @@ You may like to explore NEAR Blocks APIs further to see what other data you can 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 +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. \ No newline at end of file diff --git a/docs/3.tutorials/auction/7-factory.md b/docs/3.tutorials/auction/7-factory.md index 8df903d04a1..95ce83a7f74 100644 --- a/docs/3.tutorials/auction/7-factory.md +++ b/docs/3.tutorials/auction/7-factory.md @@ -6,8 +6,6 @@ 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). @@ -20,7 +18,7 @@ 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 of `contract-rs` +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` ``` cargo near build @@ -53,7 +51,7 @@ The method to deploy a new contract is specific to the contract being deployed ( url="https://github.com/near-examples/auctions-tutorial/blob/add-factory/factory/src/deploy.rs#L9-L82" start="9" end="82" /> -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. +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. --- @@ -77,15 +75,9 @@ You can now use the factory to deploy new auction contracts, here is an example 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. - -
    +:::info Deposit and storage costs +Note that we attach 1.6 $NEAR to the call to cover the storage costs of deploying the new auction. The 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 the storage used on initialization. +::: The command results in a fresh auction contract being deployed and initialized at `new-auction.auction-factory.testnet`. @@ -93,14 +85,8 @@ The command results in a fresh auction contract being deployed and initialized a ## 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! +In this part of the tutorial, you have learned how to fork and modify the factory contract example to deploy our auction contracts. You 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 From 32dbbe91fa436115a5f8ed589cbd4bdeac2cfc97 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 19 Sep 2024 15:07:28 +0200 Subject: [PATCH 30/32] Update docs/3.tutorials/auction/0-intro.md --- docs/3.tutorials/auction/0-intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index 998434637b6..af3f9ed7228 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -11,7 +11,7 @@ Welcome! In this guide we will help you navigate NEAR tech stack, so you can bui We'll start from a simple auction contract and slowly build on top of it to create a full Web3 application to carry out on-chain auctions. -By the time you finish this tutorial, you will have learned several concepts and how to use many key privitives along the way: +By the time you finish this tutorial, you will have learned several key concepts: - [Creating a simple smart contract](./1.1-basic.md) - [Writing tests for a contract](./1.2-testing.md) From b62f355c172716393b57050f6a1970a21ede30d4 Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 19 Sep 2024 15:08:20 +0200 Subject: [PATCH 31/32] Update docs/3.tutorials/auction/0-intro.md --- docs/3.tutorials/auction/0-intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/3.tutorials/auction/0-intro.md b/docs/3.tutorials/auction/0-intro.md index af3f9ed7228..7796328497e 100644 --- a/docs/3.tutorials/auction/0-intro.md +++ b/docs/3.tutorials/auction/0-intro.md @@ -72,7 +72,7 @@ Before starting, make sure to set up your development environment!
    -We will be use [NEAR CLI](../../4.tools/cli.md) to interact with the blockchain through the terminal, and you can choose between JavaScript and Rust to write the contract. +We will use [NEAR CLI](../../4.tools/cli.md) to interact with the blockchain through the terminal, and you can choose between JavaScript and Rust to write the contract. --- From c356345c43b7e3b2ce4ada6584e5b0518344363c Mon Sep 17 00:00:00 2001 From: Guille Date: Thu, 19 Sep 2024 15:10:40 +0200 Subject: [PATCH 32/32] Update docs/3.tutorials/auction/3-nft.md --- docs/3.tutorials/auction/3-nft.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/3.tutorials/auction/3-nft.md b/docs/3.tutorials/auction/3-nft.md index 0f6d73773f7..3590afe0cee 100644 --- a/docs/3.tutorials/auction/3-nft.md +++ b/docs/3.tutorials/auction/3-nft.md @@ -43,7 +43,7 @@ When we create an auction we need to list the NFT. To specify which NFT is being ## Transferring the NFT to the winner -When the method `claim` is called the NFT needs to be transferred to the highest bidder. Operations regarding NFTs live on the NFT contract, so we make a cross-contract call to the NFT contract telling it to swap the owner of the NFT to the highest bidder. The method on the NFT contract to do this is `nft_transfer`. +When the method `claim` is called the NFT needs to be transferred to the highest bidder. Operations regarding NFTs live on the NFT contract, so we make a cross-contract call to the NFT contract telling it to swap the owner of the NFT to the highest bidder. The method on the NFT contract to do this is `nft_transfer`.