From 28e3b27801f2933a1004f67f6ae4de98189954b3 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Fri, 24 Jul 2026 18:18:48 -0300 Subject: [PATCH 1/2] fix: update github components --- primitives/nft/sdk-contract-tools.mdx | 4 +- smart-contracts/anatomy/anatomy.mdx | 4 +- smart-contracts/anatomy/functions.mdx | 2 +- smart-contracts/testing/integration-test.mdx | 10 ++--- smart-contracts/testing/unit-test.mdx | 2 +- .../cross-contracts/advanced-xcc.mdx | 2 +- .../tutorials/cross-contracts/xcc.mdx | 2 +- .../tutorials/factories/global-contracts.mdx | 4 +- tools/near-api.mdx | 44 +++++++++---------- web3-apps/quickstart.mdx | 8 ++-- 10 files changed, 41 insertions(+), 41 deletions(-) diff --git a/primitives/nft/sdk-contract-tools.mdx b/primitives/nft/sdk-contract-tools.mdx index aaccb84220b..b6b7cff0c70 100644 --- a/primitives/nft/sdk-contract-tools.mdx +++ b/primitives/nft/sdk-contract-tools.mdx @@ -74,7 +74,7 @@ To initialize the basic NFT contract with a custom owner, metadata, and storage + start="14" end="31" /> --- @@ -118,7 +118,7 @@ To allow users to burn their tokens, you can add a `burn` method: + start="5" end="14" /> --- diff --git a/smart-contracts/anatomy/anatomy.mdx b/smart-contracts/anatomy/anatomy.mdx index e4416ddb022..ca5645616eb 100644 --- a/smart-contracts/anatomy/anatomy.mdx +++ b/smart-contracts/anatomy/anatomy.mdx @@ -140,12 +140,12 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code + start="2" end="22" /> - + diff --git a/smart-contracts/anatomy/functions.mdx b/smart-contracts/anatomy/functions.mdx index b8f7888f446..cedc28551d4 100644 --- a/smart-contracts/anatomy/functions.mdx +++ b/smart-contracts/anatomy/functions.mdx @@ -337,7 +337,7 @@ They are useful to return hardcoded values on the contract. - + diff --git a/smart-contracts/testing/integration-test.mdx b/smart-contracts/testing/integration-test.mdx index d0001248d30..86f417dbd23 100644 --- a/smart-contracts/testing/integration-test.mdx +++ b/smart-contracts/testing/integration-test.mdx @@ -563,11 +563,11 @@ Lets take a look at the test of our [Quickstart Project](../quickstart) [πŸ‘‹ He + url="https://github.com/near-examples/hello-near-examples/blob/main/contract-rs/tests/test_basics.rs" start="1" end="69"/> + url="https://github.com/near-examples/hello-near-examples/blob/main/contract-ts/sandbox-test/main.ava.js" start="11" end="67"/> - + + start="11" end="92" /> + start="47" end="84" /> diff --git a/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx b/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx index fa45180c69f..9dfe19ff1c9 100644 --- a/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx +++ b/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx @@ -160,7 +160,7 @@ value returned by each call, or an error message. start="3" end="20" /> + start="57" end="90" /> --- diff --git a/smart-contracts/tutorials/cross-contracts/xcc.mdx b/smart-contracts/tutorials/cross-contracts/xcc.mdx index 618a7b9bfe0..8b0dbbdc1e0 100644 --- a/smart-contracts/tutorials/cross-contracts/xcc.mdx +++ b/smart-contracts/tutorials/cross-contracts/xcc.mdx @@ -99,7 +99,7 @@ The contract performs a cross-contract call to `hello.near-example.testnet` to g + start="4" end="8" /> diff --git a/smart-contracts/tutorials/factories/global-contracts.mdx b/smart-contracts/tutorials/factories/global-contracts.mdx index c8b8198e812..ccbc6d49aab 100644 --- a/smart-contracts/tutorials/factories/global-contracts.mdx +++ b/smart-contracts/tutorials/factories/global-contracts.mdx @@ -204,8 +204,8 @@ Let’s see how you can reference and use your global contract from another acco To reference a global contract by account, you need to call the `useGlobalContract` function and pass the source `accountId` where the contract was originally deployed. + url="https://github.com/near-examples/near-api-examples/blob/main/near-api-js/examples/deploy-global-contract-by-account.ts#L32-L38" + start="32" end="38" /> diff --git a/tools/near-api.mdx b/tools/near-api.mdx index 87377c13bf5..669d3e90b1a 100644 --- a/tools/near-api.mdx +++ b/tools/near-api.mdx @@ -75,7 +75,7 @@ Gets the available and staked balance of an account in yoctoNEAR. + url="https://github.com/near-examples/near-api-examples/blob/main/rust/examples/account_details.rs" /> @@ -112,7 +112,7 @@ Get basic account information, such as its code hash and storage usage. @@ -148,7 +148,7 @@ To create a named account like `user.testnet`, you need to call the `create_acco @@ -156,8 +156,8 @@ To create a named account like `user.testnet`, you need to call the `create_acco You can also create an account via a randomly generated seed phrase. + url="https://github.com/near-examples/near-api-examples/blob/main/rust/examples/create_account_from_seed.rs#L32-L46" + start="32" end="46" /> @@ -191,8 +191,8 @@ Accounts on NEAR can create sub-accounts under their own namespace, which is use + url="https://github.com/near-examples/near-api-examples/blob/main/rust/examples/create_account.rs#L61-L76" + start="61" end="76" /> @@ -236,7 +236,7 @@ Accounts on NEAR can delete themselves, transferring any remaining balance to a @@ -282,7 +282,7 @@ Accounts can transfer different types of tokens to other accounts, including the @@ -343,11 +343,11 @@ A smart contract exposes its methods, and making a function call that modifies s @@ -380,7 +380,7 @@ You can send multiple [actions](/protocol/transactions/transaction-anatomy#actio @@ -406,7 +406,7 @@ The only way to have true simultaneous transactions is to use multiple access ke @@ -456,7 +456,7 @@ On NEAR, a smart contract is deployed as a WASM file. Every account has the pote Note that the `signer` here needs to be a signer for the same `account_id` as the one used to construct the `Contract` object. @@ -678,7 +678,7 @@ View functions are read-only methods on a smart contract that do not modify stat @@ -714,7 +714,7 @@ View functions are read-only methods on a smart contract that do not modify stat @@ -750,7 +750,7 @@ Anyone with this key can transfer funds, sign transactions, interact with contra @@ -788,7 +788,7 @@ You can further restrict this key by: @@ -831,8 +831,8 @@ Accounts on NEAR can delete their own keys. + url="https://github.com/near-examples/near-api-examples/blob/main/rust/examples/keys.rs#L57-L66" + start="57" end="66" /> @@ -953,14 +953,14 @@ Users can sign messages using the `wallet-selector` `signMessage` method, which - [Github](https://github.com/r-near/near-kit/tree/main) - - [Full Examples](https://github.com/near-examples/near-api-examples/tree/main/near-kit) + - [Full Examples](https://github.com/near-examples/near-api-examples/blob/main/near-kit) - [Documentation](https://docs.rs/near-api/latest/near_api/) - [Github](https://github.com/near/near-api-rs) - - [Full Examples](https://github.com/near-examples/near-api-examples/tree/main/rust) + - [Full Examples](https://github.com/near-examples/near-api-examples/blob/main/rust) diff --git a/web3-apps/quickstart.mdx b/web3-apps/quickstart.mdx index 8a92b961bd0..e67a5eee1f5 100644 --- a/web3-apps/quickstart.mdx +++ b/web3-apps/quickstart.mdx @@ -83,7 +83,7 @@ NEAR Connect is a library that allows users to select their preferred NEAR walle ### Navigation Bar The navigation bar implements a button to allow users to `login` and `logout` with their NEAR wallet. The main logic comes from the `useNearWallet` hook, which exposes all wallet related functionality. - + --- @@ -113,18 +113,18 @@ Just like the [navigation bar](#navigation-bar), we use the `useNearWallet` hook - `viewFunction` is used to call functions that are read-only - `callFunction` is used to call functions that modify the state of the contract - + #### Calling Read-Only Methods For example, when we want to fetch the current greeting stored in the contract, we use `viewFunction` inside a `useEffect` hook: - + #### Calling Change Methods On the other hand, when the user submits a new greeting, we use `callFunction` to send a transaction to the contract: - + --- From 9e3e5cd69cba068a8d414c90950ccda0d39d9312 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Tue, 28 Jul 2026 13:03:38 -0300 Subject: [PATCH 2/2] chore: make smart contract docs Rust-only, link community SDKs --- docs.json | 7 +- index.mdx | 11 +- primitives/nft/nft.mdx | 3 +- protocol/storage/storage-staking.mdx | 4 +- smart-contracts/anatomy/actions.mdx | 414 ------------ smart-contracts/anatomy/anatomy.mdx | 69 +- smart-contracts/anatomy/best-practices.mdx | 327 --------- smart-contracts/anatomy/collections.mdx | 590 +--------------- smart-contracts/anatomy/crosscontract.mdx | 531 ++------------- smart-contracts/anatomy/environment.mdx | 151 +---- smart-contracts/anatomy/events.mdx | 49 -- smart-contracts/anatomy/functions.mdx | 247 +------ smart-contracts/anatomy/reduce-size.mdx | 239 ------- .../anatomy/reproducible-builds.mdx | 63 +- smart-contracts/anatomy/serialization.mdx | 25 - smart-contracts/anatomy/storage.mdx | 108 +-- smart-contracts/anatomy/types.mdx | 156 +---- smart-contracts/anatomy/yield-resume.mdx | 285 +------- smart-contracts/quickstart.mdx | 425 +----------- smart-contracts/release/deploy.mdx | 29 +- smart-contracts/release/upgrade.mdx | 78 +-- smart-contracts/testing/integration-test.mdx | 637 +++--------------- smart-contracts/testing/unit-test.mdx | 68 +- smart-contracts/tutorials/basic-contracts.mdx | 67 +- .../cross-contracts/advanced-xcc.mdx | 175 +---- .../tutorials/cross-contracts/xcc.mdx | 101 +-- .../tutorials/factories/global-contracts.mdx | 60 -- .../tutorials/zero-to-hero/nfts-js.mdx | 50 -- smart-contracts/what-is.mdx | 20 +- snippets/explain-code.jsx | 31 +- tools/sdk.mdx | 93 +-- .../tutorials/mastering-near/0-intro.mdx | 40 +- .../tutorials/mastering-near/1.1-basic.mdx | 178 +---- .../tutorials/mastering-near/1.2-testing.mdx | 121 +--- .../tutorials/mastering-near/1.3-deploy.mdx | 40 +- .../tutorials/mastering-near/3.1-nft.mdx | 142 +--- web3-apps/tutorials/mastering-near/3.2-ft.mdx | 304 ++------- .../tutorials/mastering-near/4-factory.mdx | 2 - 38 files changed, 567 insertions(+), 5373 deletions(-) delete mode 100644 smart-contracts/tutorials/zero-to-hero/nfts-js.mdx diff --git a/docs.json b/docs.json index cf0208445aa..9931e68e235 100644 --- a/docs.json +++ b/docs.json @@ -59,6 +59,10 @@ } }, "redirects": [ + { + "source": "/smart-contracts/tutorials/zero-to-hero/nfts-js", + "destination": "/smart-contracts/tutorials/zero-to-hero/nfts" + }, { "source": "/smart-contracts/contracts-list", "destination": "/tools/contracts-list" @@ -382,8 +386,7 @@ "icon": "rocket", "pages": [ "smart-contracts/tutorials/zero-to-hero/fts", - "smart-contracts/tutorials/zero-to-hero/nfts", - "smart-contracts/tutorials/zero-to-hero/nfts-js" + "smart-contracts/tutorials/zero-to-hero/nfts" ] } ] diff --git a/index.mdx b/index.mdx index 585aba79b25..ffc16d8c94e 100644 --- a/index.mdx +++ b/index.mdx @@ -27,13 +27,10 @@ mode: wide
~5 min Rust - JavaScript - Python - Go

Deploy your first contract in minutes

- Write smart contracts in Rust, JavaScript, TypeScript or Python. Scaffold a working project with a single command, then build, test, and deploy. + Write smart contracts in Rust. Scaffold a working project with a single command, then build, test, and deploy.

Start the quickstart β†’ @@ -176,8 +173,8 @@ async function main() { Transaction fees under $0.001. Storage costs ~1 NEAR per 100 KB β€” fully refundable on deletion. - - Write in Rust, JavaScript/TypeScript, Python, or Go. Full WASM runtime, no new syntax to learn. + + Write contracts in Rust with a full WASM runtime β€” plus community SDKs for JavaScript, Python, and Go. Derive threshold keys for any chain β€” Bitcoin, Ethereum, Solana β€” from one NEAR account. @@ -220,7 +217,7 @@ async function main() { Accounts, access keys, gas, transactions, and the NEAR runtime. - Write and deploy contracts in Rust, TypeScript, or Python. + Write and deploy smart contracts in Rust. Connect frontends to NEAR using near-api-js and the Wallet Selector. diff --git a/primitives/nft/nft.mdx b/primitives/nft/nft.mdx index e81018800b5..8907992d258 100644 --- a/primitives/nft/nft.mdx +++ b/primitives/nft/nft.mdx @@ -531,5 +531,4 @@ While the NFT standard does not define a `burn` method, you can simply transfer ## Tutorials -- [NFT Tutorial](/smart-contracts/tutorials/zero-to-hero/nfts-js) _Zero to Hero_ (JavaScript SDK) - a set of tutorials that cover how to create a NFT contract using JavaScript. -- [NFT Tutorial](/smart-contracts/tutorials/zero-to-hero/nfts) _Zero to Hero_ (Rust SDK) - a set of tutorials that cover how to create a NFT contract using Rust. +- [NFT Tutorial](/smart-contracts/tutorials/zero-to-hero/nfts) _Zero to Hero_ - a set of tutorials that cover how to create a NFT contract using Rust. diff --git a/protocol/storage/storage-staking.mdx b/protocol/storage/storage-staking.mdx index 9eed36b5e69..63c86ba2c0e 100644 --- a/protocol/storage/storage-staking.mdx +++ b/protocol/storage/storage-staking.mdx @@ -94,9 +94,9 @@ Storing data on-chain isn't cheap for the people running the network, and NEAR p ### Use a binary serialization format, rather than JSON The core NEAR team maintains a library called [borsh](https://borsh.io/), -which is used automatically when you use `near-sdk-rs`. Someday, it will probably also be used by `near-sdk-js`. +which is used automatically when you use `near-sdk-rs`. -Imagine that you want to store an array like `[0, 1, 2, 3]`. You could serialize it as a string and store it as UTF-8 bytes. This is what `near-sdk-js` does today. Cutting out spaces, you end up using 9 bytes. +Imagine that you want to store an array like `[0, 1, 2, 3]`. You could serialize it as a JSON string and store it as UTF-8 bytes. Cutting out spaces, you end up using 9 bytes. Using borsh, this same array gets saved as 8 bytes: diff --git a/smart-contracts/anatomy/actions.mdx b/smart-contracts/anatomy/actions.mdx index 64bef85eb6d..23f4448d7ba 100644 --- a/smart-contracts/anatomy/actions.mdx +++ b/smart-contracts/anatomy/actions.mdx @@ -22,8 +22,6 @@ but **cannot** call two methods on different contracts. You can send `$NEAR` from your contract to any other account on the network. The Gas cost for transferring `$NEAR` is fixed and is based on the protocol's genesis config. Currently, it costs `~0.45 TGas`. - - ```rust use near_sdk::{near, AccountId, Promise, NearToken}; @@ -40,44 +38,6 @@ You can send `$NEAR` from your contract to any other account on the network. The } ``` - - - -```js - import { NearBindgen, NearPromise, call } from 'near-sdk-js' - import { AccountId } from 'near-sdk-js/lib/types' - - @NearBindgen({}) - class Contract{ - @call({}) - transfer({ to, amount }: { to: AccountId, amount: bigint }) { - return NearPromise.new(to).transfer(amount); - } - } -``` - - - - -```python -from near_sdk_py import call, Contract -from near_sdk_py.promises import Promise - -class ExampleContract(Contract): - @call - def transfer(self, to, amount): - """Transfers NEAR to another account""" - # Create a promise to transfer NEAR - return Promise.create_batch(to).transfer(amount) -``` - - - - - - **Why is there no callback?** @@ -96,8 +56,6 @@ Your smart contract can call methods in another contract. In the snippet below w in a deployed [Hello NEAR](../quickstart) contract, and check if everything went right in the callback. - - ```rust use near_sdk::{near, env, log, Promise, Gas, PromiseError}; @@ -135,94 +93,6 @@ right in the callback. } ``` - - - -```js - import { NearBindgen, near, call, bytes, NearPromise } from 'near-sdk-js' - import { AccountId } from 'near-sdk-js/lib/types' - - const HELLO_NEAR: AccountId = "hello-nearverse.testnet"; - const NO_DEPOSIT: bigint = BigInt(0); - const CALL_GAS: bigint = BigInt("10000000000000"); - - @NearBindgen({}) - class Contract { - @call({}) - call_method({}): NearPromise { - const args = bytes(JSON.stringify({ message: "howdy" })) - - return NearPromise.new(HELLO_NEAR) - .functionCall("set_greeting", args, NO_DEPOSIT, CALL_GAS) - .then( - NearPromise.new(near.currentAccountId()) - .functionCall("callback", bytes(JSON.stringify({})), NO_DEPOSIT, CALL_GAS) - ) - .asReturn() - } - - @call({privateFunction: true}) - callback({}): boolean { - let result, success; - - try{ result = near.promiseResult(0); success = true } - catch{ result = undefined; success = false } - - if (success) { - near.log(`Success!`) - return true - } else { - near.log("Promise failed...") - return false - } - } - } -``` - - - - -```python -from near_sdk_py import call, callback, Contract -from near_sdk_py.promises import CrossContract, Promise, PromiseResult -from near_sdk_py.constants import ONE_TGAS - -# Constants -HELLO_NEAR = "hello-nearverse.testnet" -NO_DEPOSIT = 0 -CALL_GAS = 10 * ONE_TGAS - -class ExampleContract(Contract): - @call - def call_method(self): - """Call a method on an external contract with a callback""" - # Create a contract reference - hello = CrossContract(HELLO_NEAR) - - # Call the external contract and use our callback to process the result - return hello.call("set_greeting", {"message": "howdy"}).then("callback") - - @callback - def callback(self, result: PromiseResult): - """Handle the result of the external contract call""" - # The @callback decorator automatically handles success/failure checking - if not result.success: - # The remote call failed - self.log_error("Promise failed...") - return False - - # The remote call succeeded - self.log_info("Success!") - return True -``` - - - - - - The snippet showed above is a low level way of calling other methods. We recommend make calls to other contracts as explained in the [Cross-contract Calls section](/smart-contracts/anatomy/crosscontract). @@ -237,8 +107,6 @@ Accounts do **NOT** have control over their sub-accounts, since they have their Sub-accounts are simply useful for organizing your accounts (e.g. `dao.project.near`, `token.project.near`). - - ```rust use near_sdk::{near, env, Promise, NearToken}; @@ -260,58 +128,6 @@ Sub-accounts are simply useful for organizing your accounts (e.g. `dao.project.n } ``` - - - -```js - import { NearBindgen, near, call, NearPromise } from 'near-sdk-js' - - const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ - - @NearBindgen({}) - class Contract { - @call({payableFunction:true}) - create({prefix}:{prefix: String}) { - const account_id = `${prefix}.${near.currentAccountId()}` - - NearPromise.new(account_id) - .createAccount() - .transfer(MIN_STORAGE) - } - } -``` - - - - -```python -from near_sdk_py import call, Contract -from near_sdk_py.promises import Promise -from near_sdk_py.constants import ONE_NEAR - -# Minimum amount needed for storage -MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ - -class ExampleContract(Contract): - @call - def create(self, prefix): - """Create a subaccount""" - # Generate the new account ID - account_id = f"{prefix}.{self.current_account_id}" - - # Create the new account and transfer some NEAR for storage - return Promise.create_batch(account_id)\ - .create_account()\ - .transfer(MIN_STORAGE) -``` - - - - - - Notice that in the snippet we are transferring some money to the new account for storage @@ -332,8 +148,6 @@ Accounts can only create immediate sub-accounts of themselves. If your contract wants to create a `.mainnet` or `.testnet` account, then it needs to [call](#function-call) the `create_account` method of `near` or `testnet` root contracts. - - ```rust use near_sdk::{near, Promise, Gas, NearToken }; @@ -361,69 +175,6 @@ the `create_account` method of `near` or `testnet` root contracts. } ``` - - - -```js - import { NearBindgen, near, call, bytes, NearPromise } from 'near-sdk-js' - - const MIN_STORAGE: bigint = BigInt("1820000000000000000000"); //0.00182Ⓝ - const CALL_GAS: bigint = BigInt("28000000000000"); - - @NearBindgen({}) - class Contract { - @call({}) - create_account({account_id, public_key}:{account_id: String, public_key: String}) { - const args = bytes(JSON.stringify({ - "new_account_id": account_id, - "new_public_key": public_key - })) - - NearPromise.new("testnet") - .functionCall("create_account", args, MIN_STORAGE, CALL_GAS); - } - } -``` - - - - -```python -from near_sdk_py import call, Contract -from near_sdk_py.promises import Promise -from near_sdk_py.constants import ONE_NEAR, ONE_TGAS - -# Constants -MIN_STORAGE = int(1.82 * ONE_NEAR / 1000) # 0.00182Ⓝ -CALL_GAS = 28 * ONE_TGAS - -class ExampleContract(Contract): - @call - def create_account(self, account_id, public_key): - """Create a testnet account by calling the testnet contract""" - # Create the arguments for the create_account method - args = { - "new_account_id": account_id, - "new_public_key": public_key - } - - # Call the testnet contract to create the account - return Promise.create_batch("testnet")\ - .function_call( - "create_account", - args, - MIN_STORAGE, - CALL_GAS - ) -``` - - - - - - --- @@ -431,8 +182,6 @@ class ExampleContract(Contract): When creating an account you can also batch the action of deploying a contract to it. Note that for this, you will need to pre-load the byte-code you want to deploy in your contract. - - ```rust use near_sdk::{near, env, Promise, NearToken}; @@ -456,42 +205,6 @@ When creating an account you can also batch the action of deploying a contract t } ``` - - - -```python -from near_sdk_py import call, Context, Contract -from near_sdk_py.promises import Promise -from near_sdk_py.constants import ONE_NEAR - -class ExampleContract(Contract): - @call - def deploy_contract(self, prefix): - """Create an account and deploy a contract to it""" - # This would require loading the contract bytes in Python - # Load contract bytes - for example purposes only - # In a real implementation, you'd need to read this from storage or include it - contract_bytes = b'...' # This should be actual WASM bytes - - MIN_STORAGE = 1.1 * ONE_NEAR # 1.1Ⓝ - - # Generate the new account ID - account_id = f"{prefix}.{self.current_account_id}" - - # Create batch of actions - return Promise.create_batch(account_id)\ - .create_account()\ - .transfer(MIN_STORAGE)\ - .deploy_contract(contract_bytes) -``` - - - - - - If an account with a contract deployed does **not** have any access keys, this is known as a locked contract. When the account is locked, it cannot sign transactions therefore, actions can **only** be performed from **within** the contract code. @@ -509,8 +222,6 @@ There are two options for adding keys to the account:
- - ```rust use near_sdk::{near, env, Promise, NearToken, PublicKey}; @@ -535,61 +246,6 @@ There are two options for adding keys to the account: } ``` - - - -```js - import { NearBindgen, near, call, NearPromise } from 'near-sdk-js' - import { PublicKey } from 'near-sdk-js/lib/types' - - const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ - - @NearBindgen({}) - class Contract { - @call({}) - create_hello({prefix, public_key}:{prefix: String, public_key: PublicKey}) { - const account_id = `${prefix}.${near.currentAccountId()}` - - NearPromise.new(account_id) - .createAccount() - .transfer(MIN_STORAGE) - .addFullAccessKey(public_key) - } - } -``` - - - - -```python -from near_sdk_py import call, Contract -from near_sdk_py.promises import Promise -from near_sdk_py.constants import ONE_NEAR - -# Minimum amount needed for storage -MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ - -class ExampleContract(Contract): - @call - def create_with_key(self, prefix, public_key): - """Create a subaccount with a full access key""" - # Generate the new account ID - account_id = f"{prefix}.{self.current_account_id}" - - # Create the new account, transfer some NEAR, and add a key - return Promise.create_batch(account_id)\ - .create_account()\ - .transfer(MIN_STORAGE)\ - .add_full_access_key(public_key) -``` - - - - - - Notice that what you actually add is a "public key". Whoever holds its private counterpart, i.e. the private-key, will be able to use the newly access key. @@ -609,8 +265,6 @@ There are two scenarios in which you can use the `delete_account` action: 1. As the **last** action in a chain of batched actions. 2. To make your smart contract delete its own account. - - ```rust use near_sdk::{near, env, Promise, NearToken, AccountId}; @@ -638,74 +292,6 @@ There are two scenarios in which you can use the `delete_account` action: } ``` - - - -```js - import { NearBindgen, near, call, NearPromise } from 'near-sdk-js' - import { AccountId } from 'near-sdk-js/lib/types' - - const MIN_STORAGE: bigint = BigInt("1000000000000000000000") // 0.001Ⓝ - - @NearBindgen({}) - class Contract { - @call({}) - create_delete({prefix, beneficiary}:{prefix: String, beneficiary: AccountId}) { - const account_id = `${prefix}.${near.currentAccountId()}` - - NearPromise.new(account_id) - .createAccount() - .transfer(MIN_STORAGE) - .deleteAccount(beneficiary) - } - - @call({}) - self_delete({beneficiary}:{beneficiary: AccountId}) { - NearPromise.new(near.currentAccountId()) - .deleteAccount(beneficiary) - } - } -``` - - - - -```python -from near_sdk_py import call, Contract -from near_sdk_py.promises import Promise -from near_sdk_py.constants import ONE_NEAR - -# Minimum amount needed for storage -MIN_STORAGE = ONE_NEAR // 1000 # 0.001Ⓝ - -class ExampleContract(Contract): - @call - def create_delete(self, prefix, beneficiary): - """Create an account and immediately delete it, sending funds to a beneficiary""" - # Generate the new account ID - account_id = f"{prefix}.{self.current_account_id}" - - # Create the account, transfer funds, then delete it - return Promise.create_batch(account_id)\ - .create_account()\ - .transfer(MIN_STORAGE)\ - .delete_account(beneficiary) - - @call - def self_delete(self, beneficiary): - """Delete this contract's account""" - # Delete the account and send remaining funds to beneficiary - return Promise.create_batch(self.current_account_id)\ - .delete_account(beneficiary) -``` - - - - - - **Token Loss** diff --git a/smart-contracts/anatomy/anatomy.mdx b/smart-contracts/anatomy/anatomy.mdx index ca5645616eb..d3dd1ce1646 100644 --- a/smart-contracts/anatomy/anatomy.mdx +++ b/smart-contracts/anatomy/anatomy.mdx @@ -8,9 +8,9 @@ import { ExplainCode, Block , File } from '/snippets/explain-code.jsx' Let's illustrate the basic anatomy of a simple "Hello World" contract. The code on this page comes from our [Hello NEAR repository](https://github.com/near-examples/hello-near-examples) on GitHub. - + - + ### Importing the SDK All contracts will import the **NEAR SDK**, enabling them to [access the execution environment](./environment), [call other contracts](./crosscontract), [transfer tokens](./actions), and much more. @@ -19,7 +19,7 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code - + ### Contract's Main Structure The contract is described through a structure: @@ -28,42 +28,6 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code - - - ### Comment Directives - - Unlike languages with built-in decorators, the Near Go SDK uses **Comment Directives** to control how your code is compiled into a smart contract. - - The `near-go` CLI scans your comments for `@contract:` tags β€” such as `@contract:state`, `@contract:init`, `@contract:view`, `@contract:mutating`, `@contract:payable`, and `@contract:promise_callback` β€” and generates the contract bindings automatically. - - **Note:** Methods exported to WASM are automatically converted to `snake_case` (e.g., `SetGreeting` becomes `set_greeting`). - - - - - - ### Contract Class Decorator - - Note that the contract's class is decorated with `@NearBindgen`. This decorator tells the SDK which class defines the contract, so it knows: - 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 - - - - - - ### Python Class Structure - - In Python, we use a class to define our contract. Unlike JavaScript or Rust, there isn't a specific decorator for the class itself. Instead, each method that should be exposed to the blockchain is decorated with the appropriate decorator (`@view`, `@call`, or `@init`). - - The contract's state is managed through instance variables and can be persisted using the Storage API or collections. - - - ### Contract Struct Macro @@ -94,7 +58,7 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code - + ### Storage (State) We call the data stored in the contract [the contract's state](./storage). @@ -105,22 +69,7 @@ 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. - - - - - - ### Method Decorators - - In Python, contract methods are decorated with `@view`, `@call`, or `@init` to define how they can be accessed. - These decorators handle input parsing and serializing return values automatically. - - - - + ### 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. @@ -129,7 +78,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. @@ -138,14 +87,8 @@ Let's illustrate the basic anatomy of a simple "Hello World" contract. The code - - - - diff --git a/smart-contracts/anatomy/best-practices.mdx b/smart-contracts/anatomy/best-practices.mdx index f7fd4338830..774383d3dac 100644 --- a/smart-contracts/anatomy/best-practices.mdx +++ b/smart-contracts/anatomy/best-practices.mdx @@ -6,8 +6,6 @@ This page provides a collection of best practices for writing smart contracts on # Best practices - - Here we lay out some best practices for writing smart contracts on NEAR, such as: @@ -159,328 +157,3 @@ impl Contract { Workspaces allow you to automate workflows and run tests for multiple contracts and cross-contract calls in a sandbox or testnet environment. Read more, [workspaces-rs](https://github.com/near/workspaces-rs) or [workspaces-js](https://github.com/near/workspaces-js). - - - -Here we lay out some best practices for writing smart contracts in Python on NEAR: - -- [Validate early](#validate-early) -- [Use proper logging](#use-proper-logging) -- [Return Promises](#return-promises) -- [Handle storage efficiently](#handle-storage-efficiently) -- [Use type hints](#use-type-hints) -- [Follow security patterns](#follow-security-patterns) - ---- - -## Validate early - -Validate inputs, context, and state as early as possible in your functions. This saves gas by failing fast before executing expensive operations. - -```python -from near_sdk_py import call, Context - -class Contract: - def __init__(self): - self.owner_id = Context.current_account_id() - - @call - def set_config(self, config_data): - # Validate permissions early - if Context.predecessor_account_id() != self.owner_id: - raise Exception("Only owner can modify config") - - # Validate inputs early - if "parameter" not in config_data or not isinstance(config_data["parameter"], int): - raise Exception("Invalid config: missing or invalid parameter") - - # Only proceed after validation - self._update_config(config_data) -``` - -## Use proper logging - -Use the `Log` utility for structured logging. This allows external services to parse events from your contract easily. - -```python -from near_sdk_py import call, Log - -@call -def transfer(self, receiver_id, amount): - # Business logic here... - - # Use informational logs for regular updates - Log.info(f"Transferred {amount} tokens to {receiver_id}") - - # Use structured event logging for indexable events - Log.event("transfer", { - "sender": Context.predecessor_account_id(), - "receiver": receiver_id, - "amount": amount - }) -``` - -## Return Promises - -When making cross-contract calls, return the Promise object to let the caller track the result. This is especially important for transactions that need to be monitored. - -```python -from near_sdk_py import call -from near_sdk_py.promises import Contract - -@call -def withdraw(self, amount, receiver_id): - # Perform validations and business logic... - - # Return the promise for better caller experience - return Contract(receiver_id).call( - "deposit", - amount=amount, - sender=Context.predecessor_account_id() - ) -``` - -## Handle storage efficiently - -Use the SDK collections for efficient storage handling, especially for growing data sets. - -```python -from near_sdk_py.collections import UnorderedMap, Vector - -class TokenContract: - def __init__(self): - # Use SDK collections for efficient storage - self.tokens = Vector("t") # Efficiently stores ordered items - self.balances = UnorderedMap("b") # Efficiently stores key-value pairs - - @call - def mint(self, token_id): - # Add to vector without loading all tokens - self.tokens.append(token_id) - - # Update balance without loading all balances - current = self.balances.get(Context.predecessor_account_id(), 0) - self.balances[Context.predecessor_account_id()] = current + 1 -``` - -## Use type hints - -Python's type hints improve code readability and can help catch errors during development. - -```python -from typing import Dict, List, Optional -from near_sdk_py import view, call - -class Contract: - def __init__(self): - self.data: Dict[str, int] = {} - - @view - def get_value(self, key: str) -> Optional[int]: - return self.data.get(key) - - @call - def set_values(self, updates: Dict[str, int]) -> List[str]: - updated_keys = [] - for key, value in updates.items(): - self.data[key] = value - updated_keys.append(key) - return updated_keys -``` - -## Follow security patterns - -Apply security best practices to protect your contract from common vulnerabilities. - -```python -from near_sdk_py import call, Context -from near_sdk_py.constants import ONE_NEAR - -class Contract: - def __init__(self): - self.owner = Context.current_account_id() - self.minimum_deposit = ONE_NEAR // 100 # 0.01 NEAR - - @call - def deposit(self): - # Check sufficient deposit to prevent spam - deposit = Context.attached_deposit() - if deposit < self.minimum_deposit: - raise Exception(f"Minimum deposit is {self.minimum_deposit}") - - # Re-entrancy protection pattern - current_balance = self.balances.get(Context.predecessor_account_id(), 0) - # Update state before external calls - self.balances[Context.predecessor_account_id()] = current_balance + deposit - - # Only after state update, perform any external calls - # ... -``` - - - - -Here we lay out some best practices for writing smart contracts in Go on NEAR: - -- [Validate early](#validate-early-go) -- [Use logging](#use-logging-go) -- [Return Promises](#return-promises-go) -- [Use SDK collections for large data](#use-sdk-collections-go) -- [Follow security patterns](#follow-security-patterns-go) - ---- - -## Validate early {#validate-early-go} - -Validate inputs, context, and state as early as possible before taking any actions. The earlier you panic, the more [gas](/protocol/transactions/gas) you save for the caller. - -```go -// @contract:mutating -func (c *Contract) SetFee(newFee uint64) error { - caller, err := env.GetPredecessorAccountID() - if err != nil { - return err - } - - // Validate permissions early - if caller != c.OwnerId { - env.PanicStr("Only the owner can set the fee") - } - - // Validate input early - if newFee > 10000 { - env.PanicStr("Fee cannot exceed 10000 basis points") - } - - // Only proceed after all checks pass - c.Fee = newFee - return nil -} -``` - ---- - -## Use logging {#use-logging-go} - -Use `env.LogString` for debug information and to notify users of important events. Log messages are stored on-chain and visible in transaction receipts. - -```go -// @contract:mutating -func (c *Contract) Transfer(to string, amount string) error { - caller, _ := env.GetPredecessorAccountID() - - // Log the transfer for indexers and frontends - // Avoid "fmt" in TinyGo β€” use string concatenation instead - env.LogString("Transferring " + amount + " yoctoNEAR from " + caller + " to " + to) - - transferAmount, err := types.U128FromString(amount) - if err != nil { - return err - } - - promise.CreateBatch(to).Transfer(transferAmount) - return nil -} -``` - ---- - -## Return Promises {#return-promises-go} - -When making cross-contract calls, use `.Value()` to return the promise result to the caller. This lets the caller (e.g. near-cli or near-api-js) wait for the full result and see failures in the transaction chain. - -```go -// @contract:payable min_deposit=0.00001NEAR -func (c *Contract) WithdrawAndNotify(receiverId string) { - gas := uint64(10 * types.ONE_TERA_GAS) - - // Return the promise chain β€” the caller will see the final result - promise.NewCrossContract(receiverId). - Gas(gas). - Call("on_deposit_received", map[string]string{}). - Value() -} -``` - ---- - -## Use SDK collections for large data {#use-sdk-collections-go} - -For data sets that grow over time, use SDK collections instead of native Go slices or maps. Native collections are fully loaded into memory on every call, which costs more gas as they grow. - -```go -import "github.com/vlmoon99/near-sdk-go/collections" - -// Good: SDK collections load entries lazily -// @contract:state -type TokenContract struct { - Balances *collections.LookupMap[string, string] `json:"balances"` - Owners *collections.UnorderedSet[string] `json:"owners"` -} - -// @contract:init -func (c *TokenContract) Init() { - c.Balances = collections.NewLookupMap[string, string]("b") - c.Owners = collections.NewUnorderedSet[string]("o") -} -``` - - - -Use native Go slices and maps only for **small, fixed-size** data (up to ~100 entries) that is always read together. For anything larger, use SDK collections. - - - ---- - -## Follow security patterns {#follow-security-patterns-go} - -Apply security best practices to protect your contract from common vulnerabilities. - -```go -// @contract:payable min_deposit=0.01NEAR -// @contract:mutating -func (c *Contract) Deposit() error { - caller, err := env.GetPredecessorAccountID() - if err != nil { - return err - } - - deposit, err := env.GetAttachedDeposit() - if err != nil { - return err - } - - // Re-entrancy protection: update state BEFORE any external calls - current, _ := c.Balances.Get(caller) - currentAmount, _ := types.U128FromString(current) - newAmount, _ := currentAmount.Add(deposit) - c.Balances.Insert(caller, newAmount.String()) - - // Only after state is updated, perform external calls if needed - env.LogString("Deposit recorded for " + caller) - return nil -} - -// Access control: only the contract itself can call this callback -// @contract:view -// @contract:promise_callback -func (c *Contract) OnExternalCallDone(result promise.PromiseResult) { - currentAccount, _ := env.GetCurrentAccountId() - caller, _ := env.GetPredecessorAccountID() - - if caller != currentAccount { - env.PanicStr("Callbacks can only be called by the contract itself") - } - - if !result.Success { - env.LogString("External call failed β€” rolling back state changes") - // manually revert any state changes made before the cross-contract call - } -} -``` - -
- -
- diff --git a/smart-contracts/anatomy/collections.mdx b/smart-contracts/anatomy/collections.mdx index 3e8c9af3903..6aac1ffe849 100644 --- a/smart-contracts/anatomy/collections.mdx +++ b/smart-contracts/anatomy/collections.mdx @@ -68,13 +68,13 @@ Be mindful of potential [small deposit attacks](../security/storage) ## Native Collections -Native collections are those provided by the language, such as `Array`, `Map`, `Set` in Javascript, or `Vec`, `HashMap`, `HashSet` in Rust. +Native collections are those provided by the language, such as `Vec`, `HashMap`, `HashSet` in Rust. All entries in a native collection are **serialized into a single value** and **stored together** into the state. This means that every time a function execute, the SDK will read and **deserialize all entries** in the native collection. -The array `[1,2,3,4]` will be serialized into the JSON string `"[1,2,3,4]"` in Javascript, and the Borsh byte-stream `[0,0,0,4,1,2,3,4]` in Rust before being stored +The array `[1,2,3,4]` will be serialized into the Borsh byte-stream `[0,0,0,4,1,2,3,4]` before being stored @@ -151,8 +151,6 @@ SDK collections are useful when you are planning to store large amounts of data All structures need to be initialized using a **unique `prefix`**, which will be used to index the collection's values in the account's state - - @@ -171,66 +169,6 @@ To fix it, derive [`BorshStorageKey`](https://docs.rs/near-sdk/latest/near_sdk/d - - - - - - - - -Do not forget to use the `schema` to define how your contract's state is structured - - - - - - -```python -from near_sdk_py import view, call, init -from near_sdk_py.collections import Vector, LookupMap, UnorderedMap, LookupSet, UnorderedSet - -class MyContract: -@init -def new(self): # Create a Vector with prefix "v" -self.my_vector = Vector("v") - - # Create a LookupMap with prefix "m" - self.my_lookup_map = LookupMap("m") - - # Create an UnorderedMap with prefix "um" - self.my_unordered_map = UnorderedMap("um") - - # Create a LookupSet with prefix "s" - self.my_lookup_set = LookupSet("s") - - # Create an UnorderedSet with prefix "us" - self.my_unordered_set = UnorderedSet("us") - - # For nested collections, use different prefixes - self.nested_maps = UnorderedMap("nested") - - return True - - @call - def create_nested_map(self, key: str): - # Create a new map that will be stored inside another map - inner_map = UnorderedMap(f"inner_{key}") - self.nested_maps[key] = inner_map - return {"success": True} - -```` - - - - - - - @@ -244,55 +182,8 @@ Be careful of not using the same prefix in two collections, otherwise, their sto Implements a [vector/array](https://en.wikipedia.org/wiki/Array_data_structure) which persists in the contract's storage. Please refer to the Rust and JS SDK's for a full reference on their interfaces. - - - - - - - - -```python -from near_sdk_py import view, call, init -from near_sdk_py.collections import Vector - -class VectorExample: - @init - def new(self): - # Create a Vector with prefix "v" - self.my_vector = Vector("v") - - @call - def add_number(self, number): - # Append a value to the vector - self.my_vector.append(number) - return len(self.my_vector) - - @view - def get_number(self, index): - # Get a value at specific index - try: - return self.my_vector[index] - except Exception: - return None - - @view - def get_all_numbers(self): - # Convert entire vector to a list - return [num for num in self.my_vector] -``` - - - - - - - +
@@ -300,55 +191,8 @@ class VectorExample: Implements a [map/dictionary](https://en.wikipedia.org/wiki/Associative_array) which persists in the contract's storage. Please refer to the Rust and JS SDK's for a full reference on their interfaces. - - - - - - - - -```python -from near_sdk_py import view, call, init -from near_sdk_py.collections import LookupMap - -class LookupMapExample: - @init - def new(self): - # Create a LookupMap with prefix "m" - self.balances = LookupMap("m") - - @call - def set_balance(self, account_id, amount): - # Set a value for a key - self.balances[account_id] = amount - return True - - @view - def get_balance(self, account_id): - # Get a value for a key with a default - return self.balances.get(account_id, 0) - - @call - def remove_balance(self, account_id): - # Remove a key - if account_id in self.balances: - del self.balances[account_id] - return True - return False -``` - - - - - - - +
@@ -362,59 +206,8 @@ Implements a [map/dictionary](https://en.wikipedia.org/wiki/Associative_array) w `UnorderedMap` belongs to the legacy `near_sdk::collections` module. `IterableMap` is its modern replacement in `near_sdk::store`, and should be preferred for new contracts. The `store` collections cache reads and writes within a single transaction, making them more gas-efficient when the same key is accessed multiple times. - - - - - - - - -```python -from near_sdk_py import view, call, init -from near_sdk_py.collections import UnorderedMap - -class UnorderedMapExample: - @init - def new(self): - # Create an UnorderedMap with prefix "um" - self.user_data = UnorderedMap("um") - - @call - def set_user_data(self, account_id, data): - # Set a value for a key - self.user_data[account_id] = data - return True - - @view - def get_user_data(self, account_id): - # Get a value for a key - try: - return self.user_data[account_id] - except Exception: - return None - - @view - def list_all_users(self): - # Iterate through keys and values - return { - "keys": self.user_data.keys(), - "values": self.user_data.values(), - "items": self.user_data.items() - } -``` - - - - - - - +
@@ -422,58 +215,8 @@ class UnorderedMapExample: Implements a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) which persists in the contract's storage. Please refer to the Rust and JS SDK's for a full reference on their interfaces. - - - - - - - - - - - -```python -from near_sdk_py import view, call, init -from near_sdk_py.collections import LookupSet - -class LookupSetExample: - @init - def new(self): - # Create a LookupSet with prefix "s" - self.whitelist = LookupSet("s") - - @call - def add_to_whitelist(self, account_id): - # Add a value to the set - self.whitelist.add(account_id) - return True - - @view - def is_whitelisted(self, account_id): - # Check if a value exists in the set - return account_id in self.whitelist - - @call - def remove_from_whitelist(self, account_id): - # Remove a value from the set - try: - self.whitelist.remove(account_id) - return True - except Exception: - return False -``` - - - - - - - +
@@ -483,8 +226,6 @@ Implements a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) which - - **UnorderedSet vs IterableSet** @@ -492,54 +233,6 @@ Implements a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) which - - - - - -```python -from near_sdk_py import view, call, init -from near_sdk_py.collections import UnorderedSet - -class UnorderedSetExample: - @init - def new(self): - # Create an UnorderedSet with prefix "us" - self.owners = UnorderedSet("us") - - @call - def register_owner(self, account_id): - # Add a value to the set - self.owners.add(account_id) - return True - - @view - def is_owner(self, account_id): - # Check if a value exists in the set - return account_id in self.owners - - @view - def list_all_owners(self): - # Get all values as a list - return self.owners.values() - - @call - def remove_owner(self, account_id): - # Try to remove a value if it exists - self.owners.discard(account_id) - return True -``` - - - - - - -
@@ -547,55 +240,8 @@ class UnorderedSetExample: An ordered equivalent of Map. The underlying implementation is based on an [AVL](https://en.wikipedia.org/wiki/AVL_tree). You should use this structure when you need to: have a consistent order, or access the min/max keys. - - - - - -```python -from near_sdk_py import view, call, init -from near_sdk_py.collections import TreeMap - -class TreeMapExample: - @init - def new(self): - # Create a TreeMap with prefix "tm" - self.scores = TreeMap("tm") - - @call - def add_score(self, user_id, score): - # Set score for a user - self.scores[user_id] = score - return True - - @view - def get_top_scores(self, limit=10): - # Get top scores using ordered keys - top_users = [] - max_key = self.scores.max_key() - current_key = max_key - count = 0 - - while current_key is not None and count < limit: - top_users.append({ - "user": current_key, - "score": self.scores[current_key] - }) - current_key = self.scores.floor_key(current_key - 1) - count += 1 - - return top_users -``` - - - - - - - + --- @@ -603,90 +249,16 @@ class TreeMapExample: When nesting SDK collections, be careful to **use different prefixes** for all collections, including the nested ones. - - - - - - - - Notice how we use `enums` that take a `String` argument to ensure all collections have a different prefix - - - - - - - - - - - - -```python -from near_sdk_py import view, call, init -from near_sdk_py.collections import UnorderedMap, Vector -from near_sdk_py.collections import create_prefix_guard - -class NestedCollectionsExample: - @init - def new(self): - # Main map of users to their assets - self.user_assets = UnorderedMap("users") - - @call - def add_asset(self, user_id, asset_id, metadata): - # Get or create the user's assets vector with a unique prefix - if user_id not in self.user_assets: - # Create a prefix guard to ensure unique prefixes for this user - prefix = f"assets:{user_id}" - # Create a new vector for this user's assets - self.user_assets[user_id] = Vector(prefix) - - # Add the asset to the user's assets vector - user_assets = self.user_assets[user_id] - user_assets.append({ - "asset_id": asset_id, - "metadata": metadata - }) - - # Update the vector in the map - self.user_assets[user_id] = user_assets - return True - - @view - def get_user_assets(self, user_id): - if user_id not in self.user_assets: - return [] - - # Get all assets for the user - user_assets = self.user_assets[user_id] - return [asset for asset in user_assets] -```` - - - -In Python, we create unique prefixes for nested collections by including the parent's identifier in the prefix string. The SDK also provides a `create_prefix_guard` utility to help manage prefixes. - - - - - - + -In Go, nested `Vector` collections must have their `Len` persisted separately (e.g., in a `LookupMap[string, uint64]`). When reconstructing a vector, pass the saved length via `&collections.Vector[string]{Prefix: prefix, Len: savedLen}`. Always use unique prefixes across all collections. +Notice how we use `enums` that take a `String` argument to ensure all collections have a different prefix - - --- @@ -696,8 +268,6 @@ Because the values are not kept in memory and are lazily loaded from storage, it Some error-prone patterns to avoid that cannot be restricted at the type level are: - - ```rust use near_sdk::store::UnorderedMap; @@ -765,55 +335,6 @@ m = IterableSet::new(b"l"); assert!(!m.contains(&1)); ``` - - - - -```ts -import { UnorderedMap, Vector } from "near-sdk-js"; - -const m = new UnorderedMap("m"); -m.set("1", "test"); - -// Bug 1: Don't replace a collection without clearing it first. The new object -// resets its metadata (length, keys), but the old elements stay in storage, -// leaving the collection inconsistent and orphaning the previous values. -// Fix: call `m.clear()` before reusing the prefix. -const m2 = new UnorderedMap("m"); -// m2.length is 0, but the "1" -> "test" entry is still in storage under prefix "m" - -// Bug 2: Don't use the same prefix for two different collections, or they will -// read and write the same keys and corrupt each other. Note that `UnorderedMap` -// internally creates sub-collections at `prefix + "u"` and `prefix + "m"`, so -// even prefixes that merely overlap can collide. -const v = new Vector("m"); // collides with the maps above - -// Bug 3: With nested collections, mutating the inner collection writes its -// elements immediately, but you must still write the inner collection back into -// the outer one so its metadata entry is persisted. -const outer = new UnorderedMap>("o"); -const inner = outer.get("id", { - reconstructor: UnorderedMap.reconstruct, - defaultValue: new UnorderedMap("i_id_"), -}); -inner.set("key", "value"); -outer.set("id", inner); // <- forgetting this loses the inner collection's metadata -``` - - - -Unlike Rust's `near_sdk::store`, `near-sdk-js` collections are not write-cached: every mutation writes to storage immediately, so there is no `flush()` to forget. Just don't mutate collections inside a `@view` method, since the contract state is only persisted after `@call` methods. - - - - - - - - - --- @@ -823,70 +344,27 @@ Persistent collections such as `IterableMap/UnorderedMap`, `IterableSet/Unordere contain more elements than the amount of gas available to read them all. In order to expose them all through view calls, we can use pagination. - - - With Rust this can be done using iterators with [`Skip`](https://doc.rust-lang.org/std/iter/struct.Skip.html) and [`Take`](https://doc.rust-lang.org/std/iter/struct.Take.html). This will only load elements from storage within the range. - - ```rust - #[near(contract_state)] - #[derive(PanicOnDefault)] - pub struct Contract { - pub status_updates: IterableMap, - } - - #[near] - impl Contract { - /// Retrieves multiple elements from the `IterableMap`. - /// - `from_index` is the index to start from. - /// - `limit` is the maximum number of elements to return. - pub fn get_updates(&self, from_index: usize, limit: usize) -> Vec<(AccountId, String)> { - self.status_updates - .iter() - .skip(from_index) - .take(limit) - .collect() - } - } - ``` - - +With Rust this can be done using iterators with [`Skip`](https://doc.rust-lang.org/std/iter/struct.Skip.html) and [`Take`](https://doc.rust-lang.org/std/iter/struct.Take.html). This will only load elements from storage within the range. - - With JavaScript this can be done using iterators with [`toArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/toArray) and [`slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice). - - ```ts - /// Returns multiple elements from the `UnorderedMap`. +```rust + #[near(contract_state)] + #[derive(PanicOnDefault)] + pub struct Contract { + pub status_updates: IterableMap, + } + + #[near] + impl Contract { + /// Retrieves multiple elements from the `IterableMap`. /// - `from_index` is the index to start from. /// - `limit` is the maximum number of elements to return. - @view({}) - get_updates({ from_index, limit }: { from_index: number, limit:number }) { - return this.status_updates.toArray().slice(from_index, limit); + pub fn get_updates(&self, from_index: usize, limit: usize) -> Vec<(AccountId, String)> { + self.status_updates + .iter() + .skip(from_index) + .take(limit) + .collect() } - ``` - - - - - ```python - # With Python this can be done using standard list slicing. - - @view - def get_updates(self, from_index: int = 0, limit: int = 50) -> list: - """Returns multiple elements from the collection with pagination.""" - all_items = self.status_updates.values() - start = min(from_index, len(all_items)) - end = min(start + limit, len(all_items)) - return all_items[start:end] - ``` - - - - - With Go this can be done using index-based iteration over a `Vector`, loading only the elements within the requested range. - - + } +``` - - diff --git a/smart-contracts/anatomy/crosscontract.mdx b/smart-contracts/anatomy/crosscontract.mdx index d3b7840e227..072398878ea 100644 --- a/smart-contracts/anatomy/crosscontract.mdx +++ b/smart-contracts/anatomy/crosscontract.mdx @@ -28,82 +28,16 @@ Each function β€” the one making the call, the external function, and the callba While making your contract, it is likely that you will want to query information from another contract. Below, you can see a basic example in which we query the greeting message from our [Hello NEAR](../quickstart) example. - - - - - - - - The high level API makes use of the interface defined in the [ext_contract.rs](https://github.com/near-examples/cross-contract-calls/blob/main/contract-simple-rs/src/external_contract.rs) - - + + - - - - - - -```python -from near_sdk_py import call, view, Contract, callback, PromiseResult, CrossContract, init - -class CrossContractExample(Contract): - # Contract we want to interact with - hello_contract = "hello-near.testnet" - - @init - def new(self): - """Initialize the contract""" - # Any initialization logic goes here - pass - - @view - def query_greeting_info(self): - """View function showing how to make a cross-contract call""" - # Create a reference to the Hello NEAR contract - # This is a simple call that will execute in the current transaction - hello = CrossContract(self.hello_contract) - return hello.call("get_greeting").value() - - @call - def query_greeting(self): - """Calls Hello NEAR contract to get the greeting with a callback""" - # Create a reference to the Hello NEAR contract - hello = CrossContract(self.hello_contract) - - # Call get_greeting and chain a callback - # The Promise API handles serialization and callback chaining - promise = hello.call("get_greeting").then("query_greeting_callback") - - return promise.value() - - @callback - def query_greeting_callback(self, result: PromiseResult): - """Processes the greeting result from Hello NEAR contract""" - # The @callback decorator automatically parses the promise result - # result will have a data property and a success boolean - if not result.success: - return {"success": False, "message": "Failed to get greeting"} - - return { - "success": True, - "greeting": result.data, - "message": f"Successfully got greeting: {result.data}" - } -``` - - - - + + + The high level API makes use of the interface defined in the [ext_contract.rs](https://github.com/near-examples/cross-contract-calls/blob/main/contract-simple-rs/src/external_contract.rs) @@ -113,77 +47,17 @@ class CrossContractExample(Contract): Calling another contract passing information is also a common scenario. Below you can see a function that interacts with the [Hello NEAR](../quickstart) example to change its greeting message. - - - - - - - - - The high level API makes use of the interface defined in the [ext_contract.rs](https://github.com/near-examples/cross-contract-calls/blob/main/contract-simple-rs/src/external_contract.rs) - - - - - - - - - -```python -from near_sdk_py import call, Contract, callback, PromiseResult, CrossContract - -class CrossContractExample(Contract): - # Contract we want to interact with - hello_contract = "hello-near.testnet" - - @call - def change_greeting(self, new_greeting): - """Changes the greeting on the Hello NEAR contract""" - # Create a reference to the Hello NEAR contract - hello = CrossContract(self.hello_contract) - - # Create a promise to call set_greeting with the new greeting - # Pass context data to the callback directly as kwargs - promise = hello.call( - "set_greeting", - message=new_greeting - ).then( - "change_greeting_callback", - original_greeting=new_greeting # Additional context passed to callback - ) - - return promise.value() - - @callback - def change_greeting_callback(self, result: PromiseResult, original_greeting): - """Processes the result of set_greeting""" - # The original_greeting parameter is passed from the change_greeting method - if not result.success: - return { - "success": False, - "message": f"Failed to set greeting to '{original_greeting}'" - } - - return { - "success": True, - "message": f"Successfully set greeting to '{original_greeting}'", - "result": result.data - } -``` + + + + - - + The high level API makes use of the interface defined in the [ext_contract.rs](https://github.com/near-examples/cross-contract-calls/blob/main/contract-simple-rs/src/external_contract.rs) @@ -215,76 +89,51 @@ The callback can be made to **any** contract. Meaning that the result could pote To create a cross-contract call with a callback, create two promises and use the `.then` method to link them: - - - - - ```rust - #[ext_contract(external_trait)] - trait Contract { - fn function_name(&self, param1: T, param2: T) -> T; - } - - external_trait::ext("external_address") - .with_attached_deposit(DEPOSIT) - .with_static_gas(GAS) - .function_name(arguments) - .then( - // this is the callback - Self::ext(env::current_account_id()) - .with_attached_deposit(DEPOSIT) - .with_static_gas(GAS) - .callback_name(arguments) - ); - - ``` - - - - - ```rust - let arguments = json!({ "foo": "bar" }) - .to_string() - .into_bytes(); - - let promise = Promise::new("external_address").function_call( - "function_name".to_owned(), - arguments, - DEPOSIT, - GAS - ); - - promise.then( - // Create a promise to callback query_greeting_callback - Self::ext(env::current_account_id()) - .with_static_gas(GAS) - .callback_name(), - ); - ``` - - - - + - ```ts - NearPromise.new("external_address").functionCall("function_name", JSON.stringify(arguments), DEPOSIT, GAS) + ```rust + #[ext_contract(external_trait)] + trait Contract { + fn function_name(&self, param1: T, param2: T) -> T; + } + + external_trait::ext("external_address") + .with_attached_deposit(DEPOSIT) + .with_static_gas(GAS) + .function_name(arguments) .then( - // this function is the callback - NearPromise.new(near.currentAccountId()).functionCall("callback_name", JSON.stringify(arguments), DEPOSIT, GAS) + // this is the callback + Self::ext(env::current_account_id()) + .with_attached_deposit(DEPOSIT) + .with_static_gas(GAS) + .callback_name(arguments) ); - ``` - - - - - - -In Go, `.Then()` works differently from Rust/JS. Instead of receiving a second promise object, it takes the callback method name as a **snake_case string** β€” the SDK automatically routes the callback to the current contract. You do not construct a second promise manually. - - + ``` + + + + + ```rust + let arguments = json!({ "foo": "bar" }) + .to_string() + .into_bytes(); + + let promise = Promise::new("external_address").function_call( + "function_name".to_owned(), + arguments, + DEPOSIT, + GAS + ); + + promise.then( + // Create a promise to callback query_greeting_callback + Self::ext(env::current_account_id()) + .with_static_gas(GAS) + .callback_name(), + ); + ``` + @@ -322,9 +171,7 @@ Each promise in a cross-contract call needs enough gas to execute. This includes Always reserve gas for the callback. It executes in a separate receipt, so it can fail independently if it does not receive enough gas. - - -In Rust's high level API, `with_static_gas(gas)` reserves a fixed amount of gas for a promise: +In the high level API, `with_static_gas(gas)` reserves a fixed amount of gas for a promise: ```rust const CALL_GAS: Gas = Gas::from_tgas(10); @@ -375,58 +222,6 @@ If you forget that the default weight is `1`, a callback or parallel promise can The low level API (`Promise::new(...).function_call(...)`) takes a fixed gas value. It behaves like `with_static_gas` and does not use unused gas weights. - - - - -In JavaScript and TypeScript, pass the gas amount to `functionCall`. Do this for both the external call and the callback: - -```ts -NearPromise.new("external_address") - .functionCall("function_name", JSON.stringify(arguments), DEPOSIT, GAS) - .then( - NearPromise.new(near.currentAccountId()) - .functionCall("callback_name", JSON.stringify(arguments), DEPOSIT, GAS) - ); -``` - -There is no unused gas weight equivalent. The gas value you pass is the fixed amount attached to that promise. - - - - - -In Python, pass gas when creating the promise call. The exact helper depends on whether you are using `CrossContract` or lower level promise helpers, but the idea is the same: reserve gas for each receipt that must execute. - -```python -promise = batch.then(Context.current_account_id()).function_call( - "callback_name", - {}, - gas=10 * ONE_TGAS -) -``` - -Callbacks need their own gas reservation. Do not assume unused gas from the external call will automatically be available to the callback. - - - - - -In Go, pass gas when creating the function call promise. The callback routed through `.Then()` still needs enough gas to execute. - -```go -gas := uint64(10 * types.ONE_TERA_GAS) - -promise.NewCrossContract(receiver). - Gas(gas). - Call("method_name", args). - Then("callback_name") -``` - -There is no unused gas weight equivalent. Treat the gas value as the fixed amount reserved for the promise. - - - --- ## Callback Function @@ -434,61 +229,9 @@ If your function finishes correctly, then eventually your callback function will In the callback function you will have access to the result, which will contain the status of the external function (if it worked or not), and the values in case of success. - - - - - - - - - - -```python -from near_sdk_py import callback, PromiseResult, Contract - -class CrossContractExample(Contract): - @callback - def query_greeting_callback(self, result: PromiseResult, additional_context=None): - """ - Process the result of a cross-contract call. - The @callback decorator automatically: - 1. Reads the promise result data - 2. Handles serialization/deserialization - 3. Provides proper error handling - - Parameters: - - result: The PromiseResult object with status and data - - additional_context: Optional context passed from the calling function - """ - if not result.success: - # This means the external call failed or returned nothing - return { - "success": False, - "message": "Failed to get greeting", - "context": additional_context - } - - # Process successful result - return { - "success": True, - "greeting": result.data, - "message": f"Successfully got greeting: {result.data}", - "context": additional_context - } -``` - - - - - - + **Callback with always execute** @@ -530,76 +273,11 @@ You can call multiple functions in the same external contract, which is known as An important property of batch calls is that they **act as a unit**: they execute in the same [receipt](/protocol/transactions/transaction-execution#receipts--finality), and if **any function fails**, then they **all get reverted**. - - - - - - + - - - - - - -```python -from near_sdk_py import call, Context, Contract, callback, PromiseResult, ONE_TGAS, CrossContract, init - -class BatchCallsExample(Contract): - # Contract we want to interact with - hello_contract = "hello-near.testnet" - - @init - def new(self): - """Initialize the contract""" - pass - - @call - def call_multiple_methods(self, greeting1, greeting2): - """Call multiple methods on the same contract in a batch""" - # Create a contract instance - hello = CrossContract(self.hello_contract) - - # Create a batch for the hello contract - batch = hello.batch() - - # Add function calls to the batch - batch.function_call("set_greeting", {"message": greeting1}) - batch.function_call("another_method", {"arg1": greeting2}) - - # Add a callback to process the result - promise = batch.then(Context.current_account_id()).function_call( - "batch_callback", - {"original_data": [greeting1, greeting2]}, - gas=10 * ONE_TGAS - ) - - return promise.value() - - @callback - def batch_callback(self, result: PromiseResult, original_data=None): - """Process batch result - only gets the result of the last operation""" - return { - "success": result.success, - "result": result.data, - "original_data": original_data - } -``` - - - - - - - @@ -613,80 +291,9 @@ Callbacks only have access to the result of the **last function** in a batch cal You can also call multiple functions in **different contracts**. These functions will be executed in parallel, and do not impact each other. This means that, if one fails, the others **will execute, and NOT be reverted**. - - - - - - - - - - -```python -from near_sdk_py import call, Contract, multi_callback, PromiseResult, CrossContract, init - -class MultiContractExample(Contract): - # Contract addresses we want to interact with - contract_a = "contract-a.testnet" - contract_b = "contract-b.testnet" - - @init - def new(self): - """Initialize the contract""" - pass - - @call - def call_multiple_contracts(self): - """Calls multiple different contracts in parallel""" - # Create promises for each contract - contract_a = CrossContract(self.contract_a) - promise_a = contract_a.call("method_a") - - contract_b = CrossContract(self.contract_b) - promise_b = contract_b.call("method_b") - - # Join the promises and add a callback - # The first promise's join method can combine multiple promises - combined_promise = promise_a.join( - [promise_b], - "multi_contract_callback", - contract_ids=[self.contract_a, self.contract_b] # Context data - ) - - return combined_promise.value() - - @multi_callback - def multi_contract_callback(self, results, contract_ids=None): - """Process results from multiple contracts""" - # results is an array containing all promise results in order - return { - "contract_a": { - "id": contract_ids[0], - "result": results[0].data, - "success": results[0].success - }, - "contract_b": { - "id": contract_ids[1], - "result": results[1].data, - "success": results[1].success - }, - "success": all(result.success for result in results) - } -``` - - - - - - - + diff --git a/smart-contracts/anatomy/environment.mdx b/smart-contracts/anatomy/environment.mdx index 0ff9c4ba373..29ed7923090 100644 --- a/smart-contracts/anatomy/environment.mdx +++ b/smart-contracts/anatomy/environment.mdx @@ -17,8 +17,6 @@ Every method execution has an environment associated with information such as: ## Environment Variables - - | Variable Name | SDK Variable | Description | |------------------------|---------------------------------|--------------------------------------------------------------------------------------| @@ -37,64 +35,6 @@ Every method execution has an environment associated with information such as: | Signer Public Key | `env::signer_account_pk()` | Sender Public Key | | Account Locked Balance | `env::account_locked_balance()` | Balance of this smart contract that is locked | - - - - -| Variable Name | SDK Variable | Description | -|------------------------|-------------------------------|--------------------------------------------------------------------------------------| -| Predecessor | `near.predecessorAccountId()` | Account ID that called this method | -| Current Account | `near.currentAccountId()` | Account ID of this smart contract | -| Signer | `near.signerAccountId()` | Account ID that signed the transaction leading to this execution | -| Attached Deposit | `near.attachedDeposit()` | Amount in yoctoNEAR attached to the call by the predecessor | -| Account Balance | `near.accountBalance()` | Balance of this smart contract (including Attached Deposit) | -| Prepaid Gas | `near.prepaidGas()` | Amount of gas available for execution | -| Timestamp | `near.blockTimestamp()` | Current timestamp (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC) | -| Current Epoch | `near.epochHeight()` | Current epoch in the blockchain | -| Block Index | `near.blockIndex()` | Current block index (a.k.a. block height) | -| Storage Used | `near.storageUsage()` | Current storage used by this smart contract | -| Used Gas | `near.usedGas()` | Amount of gas used for execution | -| Signer Public Key | `near.signerAccountPk()` | Sender Public Key | -| Account Locked Balance | `near.accountLockedBalance()` | Balance of this smart contract that is locked | - - - - - -| Variable Name | SDK Variable | Description | -|------------------------|------------------------------------|--------------------------------------------------------------------------------------| -| Predecessor | `Context.predecessor_account_id()` | Account ID that called this method | -| Current Account | `Context.current_account_id()` | Account ID of this smart contract | -| Signer | `Context.signer_account_id()` | Account ID that signed the transaction leading to this execution | -| Attached Deposit | `Context.attached_deposit()` | Amount in yoctoNEAR attached to the call by the predecessor | -| Prepaid Gas | `Context.prepaid_gas()` | Amount of gas available for execution | -| Used Gas | `Context.used_gas()` | Amount of gas used for execution | -| Block Height | `Context.block_height()` | Current block height | -| Timestamp | `Context.block_timestamp()` | Current timestamp (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC) | -| Current Epoch | `Context.epoch_height()` | Current epoch in the blockchain | - - - - -| Variable Name | SDK Variable | Description | -|------------------------|---------------------------------|--------------------------------------------------------------------------------------| -| Predecessor | `env.GetPredecessorAccountID()` | Account ID that called this method | -| Current Account | `env.GetCurrentAccountId()` | Account ID of this smart contract | -| Signer | `env.GetSignerAccountID()` | Account ID that signed the transaction leading to this execution | -| Attached Deposit | `env.GetAttachedDeposit()` | Amount in yoctoNEAR attached to the call by the predecessor | -| Account Balance | `env.GetAccountBalance()` | Balance of this smart contract (including Attached Deposit) | -| Prepaid Gas | `env.GetPrepaidGas()` | Amount of gas available for execution | -| Timestamp | `env.GetBlockTimeMs()` | Current timestamp (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC) | -| Current Epoch | `env.GetEpochHeight()` | Current epoch in the blockchain | -| Block Index | `env.GetCurrentBlockHeight()` | Current block index (a.k.a. block height) | -| Storage Used | `env.GetStorageUsage()` | Current storage used by this smart contract in bytes | -| Used Gas | `env.GetUsedGas()` | Amount of gas used for execution | -| Signer Public Key | `env.GetSignerAccountPK()` | Sender Public Key | -| Account Locked Balance | `env.GetAccountLockedBalance()` | Balance of this smart contract that is locked | - - - - --- @@ -120,7 +60,7 @@ In most scenarios you will **only need to know the predecessor**. However, there -`signer_account_pk` returns the signer's public key (a `PublicKey` in near-sdk-rs; raw bytes in near-sdk-js β€” not a preformatted `:` string). If the transaction was signed with a post-quantum [`ml-dsa-65`](/protocol/accounts-contracts/access-keys#signature-schemes) key, that public key is much larger β€” 1952 bytes, versus 32 for `ed25519` β€” so keep that in mind if your contract stores, compares, or serializes signer public keys. +`signer_account_pk` returns the signer's public key as a near-sdk-rs `PublicKey` β€” not a preformatted `:` string. If the transaction was signed with a post-quantum [`ml-dsa-65`](/protocol/accounts-contracts/access-keys#signature-schemes) key, that public key is much larger β€” 1952 bytes, versus 32 for `ed25519` β€” so keep that in mind if your contract stores, compares, or serializes signer public keys. --- @@ -199,46 +139,12 @@ During [cross-contract calls](./crosscontract) always make sure the callback has If you already [estimated the Gas](../../protocol/transactions/gas#estimating-costs-for-a-call) a method needs, you can ensure it never runs out of Gas by using `assert` - - ```rust const REQUIRED_GAS: Gas = Gas(20_000_000_000_000); // 20 TGas assert!(env::prepaid_gas() >= REQUIRED_GAS, "Please attach at least 20 TGas"); ``` - - - - -```python -from near_sdk_py import call, Context - -@call -def check_gas(required_gas=20_000_000_000_000): # 20 TGas - if Context.prepaid_gas() < required_gas: - raise Exception(f"Please attach at least {required_gas} gas") - return "Sufficient gas attached" -``` - - - - - -```go -const REQUIRED_GAS = uint64(20_000_000_000_000) // 20 TGas - -// @contract:mutating -func (c *MyContract) CheckGas() { - if env.GetPrepaidGas() < REQUIRED_GAS { - env.PanicStr("Please attach at least 20 TGas") - } -} -``` - - - - @@ -248,8 +154,6 @@ func (c *MyContract) CheckGas() { Besides environmental variables, the SDK also exposes some functions to perform basic cryptographic operations - - | Function Name | SDK method | Description | |-----------------------|---------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -266,57 +170,4 @@ Besides environmental variables, the SDK also exposes some functions to perform | Validator Stake | `env::validator_stake(account_id)` | For a given account return its current stake. If the account is not a validator, returns 0. | | Validator Total Stake | `env::validator_total_stake()` | Returns the total stake of validators in the current epoch. | - - - - -| Function Name | SDK method | Description | -|-----------------------|--------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| SHA 256 | `near.sha256(value)` | Hashes a sequence of bytes using sha256. | -| Keccak 256 | `near.keccak256(value)` | Hashes a sequence of bytes using keccak256. | -| Keccak 512 | `near.keccak512(value)` | Hashes a sequence of bytes using keccak512. | -| RIPEMD 160 | `near.ripemd160(value)` | Hashes the bytes using the RIPEMD-160 hash function. | -| EC Recover | `near.ecrecover(hash, sig, v, malleabilityFlag)` | Recovers an ECDSA signer address from a 32-byte message `hash` and a corresponding `signature` along with `v` recovery byte. Takes in an additional flag to check for malleability of the signature which is generally only ideal for transactions. Returns 64 bytes representing the public key if the recovery was successful. | -| Log String | `near.log(msg)` | Logs the string message. This message is stored on chain. | -| Validator Stake | `near.validatorStake(accountId)` | For a given account return its current stake. If the account is not a validator, returns 0. | -| Validator Total Stake | `near.validatorTotalStake()` | Returns the total stake of validators in the current epoch. | - - - - - -| Function Name | SDK method | Description | -|-----------------------|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| SHA 256 | `near.sha256(value)` | Hashes a sequence of bytes using sha256. | -| Keccak 256 | `near.keccak256(value)` | Hashes a sequence of bytes using keccak256. | -| Keccak 512 | `near.keccak512(value)` | Hashes a sequence of bytes using keccak512. | -| Log Info | `Log.info(message)` | Logs an informational message. This message is stored on chain. | -| Log Warning | `Log.warning(message)` | Logs a warning message. This message is stored on chain. | -| Log Error | `Log.error(message)` | Logs an error message. This message is stored on chain. | -| Log Event | `Log.event(event_type, data)` | Logs a structured event following the NEP standard for events. This is useful for indexers and frontend applications. | -| Raise Exception | `raise Exception(message)` | Terminates the execution of the function with an error message. Similar to panic in other languages. | - - - - -| Function Name | SDK method | Description | -|-----------------------|---------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| SHA 256 | `env.Sha256Hash(value)` | Hashes a sequence of bytes using sha256. | -| Keccak 256 | `env.Keccak256Hash(value)` | Hashes a sequence of bytes using keccak256. | -| Keccak 512 | `env.Keccak512Hash(value)` | Hashes a sequence of bytes using keccak512. | -| RIPEMD 160 | `env.Ripemd160Hash(value)` | Hashes the bytes using the RIPEMD-160 hash function. This returns a 20 byte hash. | -| EC Recover | `env.EcrecoverPubKey(hash, signature, v, malleability_flag)` | Recovers an ECDSA signer address from a 32-byte message `hash` and a corresponding `signature` along with `v` recovery byte. Takes in an additional flag to check for malleability of the signature which is generally only ideal for transactions. Returns 64 bytes representing the public key if the recovery was successful. | -| Panic String | `env.PanicStr(message)` | Terminates the execution of the program with the UTF-8 encoded message. | -| Log String | `env.LogString(message)` | Logs the string message. This message is stored on chain. | -| Validator Stake | `env.ValidatorStakeAmount(account_id)` | For a given account return its current stake. If the account is not a validator, returns 0. | -| Validator Total Stake | `env.ValidatorTotalStakeAmount()` | Returns the total stake of validators in the current epoch. | - - - - - - -In the JS SDK, `throw new Error("message")` mimics the behavior of Rust's `env::panic_str("message")`. - - --- diff --git a/smart-contracts/anatomy/events.mdx b/smart-contracts/anatomy/events.mdx index c8bf2bd025e..2c9f73c9249 100644 --- a/smart-contracts/anatomy/events.mdx +++ b/smart-contracts/anatomy/events.mdx @@ -15,8 +15,6 @@ EVENT_JSON:{"standard":"nep171","version":"1.0.0","event":"nft_mint","data":[{"o ## Defining and Emitting Events - - The `near-sdk` provides a high-level macro-based API for defining and emitting events. Annotate an enum with `#[near(event_json(standard = "..."))]` and mark each variant with `#[event_version("...")]`. @@ -87,35 +85,6 @@ The macro automatically serializes the enum variant to the `EVENT_JSON:` format You can emit multiple data entries in a single event by passing a `Vec` with more than one element. This keeps gas costs lower than emitting one event per entry. - - - - -In the JavaScript SDK, emit events by calling `near.log()` with a string formatted as `EVENT_JSON:` followed by a JSON payload matching the [NEP-297 schema](https://nomicon.io/Standards/EventsFormat). - -```js -import { NearBindgen, call, near } from 'near-sdk-js' - -@NearBindgen({}) -class Contract { - @call({}) - mint({ owner_id, token_id }: { owner_id: string; token_id: string }) { - // ... minting logic ... - - const event = { - standard: 'nep171', - version: '1.0.0', - event: 'nft_mint', - data: [{ owner_id, token_ids: [token_id] }], - } - - near.log(`EVENT_JSON:${JSON.stringify(event)}`) - } -} -``` - - - --- @@ -123,8 +92,6 @@ class Contract { You are not limited to standard NEP events. Define your own standard name for application-specific activity: - - ```rust use near_sdk::near; @@ -152,22 +119,6 @@ MarketplaceEvent::OrderPlaced(vec![OrderPlaced { .emit(); ``` - - - - -```js -const event = { - standard: 'my-marketplace', - version: '1.0.0', - event: 'order_placed', - data: [{ order_id: 42, buyer: 'alice.near', amount: '1000000' }], -} -near.log(`EVENT_JSON:${JSON.stringify(event)}`) -``` - - - --- diff --git a/smart-contracts/anatomy/functions.mdx b/smart-contracts/anatomy/functions.mdx index cedc28551d4..85c8c82c5c4 100644 --- a/smart-contracts/anatomy/functions.mdx +++ b/smart-contracts/anatomy/functions.mdx @@ -9,9 +9,9 @@ import { ExplainCode, Block , File } from '/snippets/explain-code.jsx' Smart contracts expose functions so users can interact with them. There are different types of functions including `read-only`, `private` and `payable`. - + - + ### Contract's Interface @@ -42,7 +42,7 @@ impl MyTrait for MyContractStructure { - + ### Initialization Functions @@ -50,14 +50,6 @@ A contract can opt to have an initialization function. If present, this function - - -#### `@initialize({ privateFunction: true })` - -The initialization function is marked with the `@initialize` decorator. - - - #### `#[init]` @@ -66,23 +58,7 @@ The initialization function is marked with the `#[init]` macro. - - -#### `@init` - -The initialization function is marked with the `@init` decorator in Python. - - - - - -#### `@contract:init` - -The initialization function is marked with the `@contract:init` comment directive in Go. - - - - + ### State Changing Functions @@ -90,14 +66,6 @@ The functions that modify the [state](./storage) or perform [actions](./actions) - - -#### `@call` - -State changing functions are marked with the `@call` decorator. - - - #### `&mut self` @@ -106,23 +74,7 @@ State changing functions are those that take a **mutable** reference to `self` i - - -#### `@call` - -State changing functions are marked with the `@call` decorator in Python. - - - - - -#### `@contract:mutating` - -State changing functions are marked with the `@contract:mutating` comment directive in Go. - - - - + @@ -132,7 +84,7 @@ The SDK provides [contextual information](./environment), such as which account - + ### Read-Only Functions @@ -140,14 +92,6 @@ Contract's functions can be read-only, meaning they don't modify the state. Call - - -#### `@view` - -Read-only functions are marked with the `@view` decorator in TS/JS. - - - #### `&self` @@ -156,23 +100,7 @@ Read-only functions are those that take an **immutable** reference to `self` in - - -#### `@view` - -Read-only functions are marked with the `@view` decorator in Python. - - - - - -#### `@contract:view` - -Read-only functions are marked with the `@contract:view` comment directive in Go. - - - - + ### Private Functions @@ -184,14 +112,6 @@ These functions are marked as `private` in the contract's code, and can only be - - -#### `decorator({privateFunction: true})` - -Private functions are marked by setting `privateFunction: true` in the `@call` or `@initialize` decorators. - - - #### [#private] @@ -200,15 +120,7 @@ Private functions are marked using the `#[private]` macro in Rust. - - -#### Private Functions in Python - -In Python, you can create callbacks by using the `@callback` decorator, which is designed specifically for handling cross-contract call results. For general private functions that should only be called by the contract, you can use validation inside the function to check that the caller is the contract itself. - - - - + ### Payable Functions @@ -218,14 +130,6 @@ Within the function, the user will have access to the [attached deposit](./envir - - -#### `@call({payableFunction: true})` - -Payable functions are marked by setting `payableFunction: true` in the `@call` decorator. - - - #### [#payable] @@ -234,34 +138,6 @@ Payable functions are marked using the `#[payable]` macro in Rust. - - -#### Handling payments in Python - -In Python, you need to check the deposit manually using the Context API. There isn't a specific decorator for payable functions, so you'll need to verify the deposit amount in your function code. - - - - - -#### `@contract:payable` - -In Go, functions that accept attached NEAR tokens can be marked with the `@contract:payable` comment directive. You can optionally specify a minimum deposit requirement: `@contract:payable min_deposit=1NEAR`. This directive is compatible with `@contract:mutating` and `@contract:init`. - - - - - -### Internal Functions - -All the functions we covered so far are part of the interface, meaning they can be called by an external actor. - -However, contracts can also have private internal functions - such as helper or utility functions - that are **not exposed** to the outside world. - -To create internal private methods in a JS contract, simply omit the `@view` and `@call` decorators. - - - ### Internal Functions @@ -274,30 +150,6 @@ To create internal private methods in a Rust contract, do not declare them as pu - - -### Internal Functions - -All the functions we covered so far are part of the interface, meaning they can be called by an external actor. - -However, contracts can also have private internal functions - such as helper or utility functions - that are **not exposed** to the outside world. - -To create internal private methods in a Python contract, simply define normal methods without the `@view`, `@call`, or `@init` decorators. - - - - - -### Internal Functions - -All the functions we covered so far are part of the interface, meaning they can be called by an external actor. - -However, contracts can also have private internal functions - such as helper or utility functions - that are **not exposed** to the outside world. - -To create internal private methods in a Go contract, simply define regular methods without any `@contract:` comment directives. - - - @@ -323,7 +175,7 @@ impl Contract { - + ### Pure Functions @@ -333,37 +185,8 @@ They are useful to return hardcoded values on the contract. - - - - - - - - -```js -@NearBindgen({}) -class Contract { - helper_function(params... ){ - // this function cannot be called from the outside - } - - @view({}) - interface_view(params...){ - // this function can be called from outside - } - - @call({privateFunction: true}){ - // this function can be called from outside, but - // only by the contract's account - } -} -``` - - - ```rs @@ -387,56 +210,4 @@ impl MyContractStructure { - - -```python -class Contract: - def helper_function(self, params...): - # This function cannot be called from the outside - # as it has no decorator - pass - - @call - def validate_owner(self): - if Context.predecessor_account_id() != self.owner_id: - raise Exception("Only the owner can call this method") - - @view - def get_static_value(self): - # This function returns a hardcoded value - return 42 -``` - - - - - -```go -package main - -const SOME_VALUE uint64 = 8 - -// @contract:state -type MyContract struct { - Counter uint64 `json:"counter"` -} - -// Internal helper β€” no @contract: directive, not exposed to the outside -func (c *MyContract) doubleValue(n uint64) uint64 { - return n * 2 -} - -// @contract:view -func (c *MyContract) GetStaticValue() uint64 { - return SOME_VALUE -} - -// @contract:view -func (c *MyContract) GetDoubledCounter() uint64 { - return c.doubleValue(c.Counter) -} -``` - - - diff --git a/smart-contracts/anatomy/reduce-size.mdx b/smart-contracts/anatomy/reduce-size.mdx index 3f92138678b..641b3804659 100644 --- a/smart-contracts/anatomy/reduce-size.mdx +++ b/smart-contracts/anatomy/reduce-size.mdx @@ -8,8 +8,6 @@ In this page, we will explore strategies for reducing the size of smart contract # Reducing a contract's size - - ## Advice & examples @@ -188,240 +186,3 @@ For a `no_std` approach to minimal contracts, observe the following examples: -
- - - -## Optimizing Python Contracts - -Since Python smart contracts on NEAR are interpreted rather than compiled to WebAssembly directly, the optimization strategies differ from Rust contracts. The Python interpreter itself is already optimized, but you can still make your contract more efficient. - -### Reducing Contract Size - -#### 1. Minimize Dependencies - -Only import the modules and functions you need: - -```python -# Less efficient - imports everything -from near_sdk_py import * - -# More efficient - imports only what's needed -from near_sdk_py import view, call, Context -``` - -#### 2. Use Efficient Data Structures - -Choose the right data structure for your needs: - -```python -# Less efficient for lookups -user_list = [] # O(n) lookup time -for user in user_list: - if user["id"] == user_id: - return user - -# More efficient for lookups -user_dict = {} # O(1) lookup time -if user_id in user_dict: - return user_dict[user_id] -``` - -#### 3. Optimize Storage Usage - -The NEAR SDK collections are optimized for on-chain storage. For large data sets, use these instead of native Python collections: - -```python -# Native collection - loaded entirely into memory -self.users = {} # Loaded entirely on each method call - -# SDK collection - lazily loaded -from near_sdk_py.collections import UnorderedMap -self.users = UnorderedMap("u") # Only loads what's needed -``` - -#### 4. Reduce String Operations - -String operations can be expensive. Minimize them when possible: - -```python -# Less efficient -result = "" -for i in range(100): - result += str(i) # Creates many intermediate strings - -# More efficient -parts = [] -for i in range(100): - parts.append(str(i)) -result = "".join(parts) # Creates strings once -``` - -#### 5. Avoid Recursion - -Deep recursion can lead to stack overflow issues. Consider iterative approaches instead: - -```python -# Recursive - could cause issues with deep structures -def process_tree(node): - result = process_node(node) - for child in node.children: - result += process_tree(child) - return result - -# Iterative - more efficient -def process_tree(root): - result = 0 - queue = [root] - while queue: - node = queue.pop(0) - result += process_node(node) - queue.extend(node.children) - return result -``` - -#### 6. Use Python's Built-in Functions - -Python's built-in functions are often optimized and more efficient: - -```python -# Less efficient -sum_value = 0 -for num in numbers: - sum_value += num - -# More efficient -sum_value = sum(numbers) -``` - -#### 7. Split Complex Logic - -If you have a very large contract, consider splitting it into multiple contracts with focused responsibilities. This can improve maintainability and reduce the cost of individual calls. - -#### 8. Profile and Optimize Hot Paths - -Focus optimization efforts on the most frequently called functions or those that handle large amounts of data: - -```python -@call -def frequently_called_method(self, data): - # Optimize this code carefully - # Consider using more efficient algorithms and data structures - pass -``` - - - - -## Advice & examples - -Go contracts are compiled to WASM via **TinyGo**, which already applies aggressive size optimizations compared to the standard Go compiler. The `near-go build` command uses size-optimized TinyGo settings by default. - -Below are additional strategies to further reduce your contract size. - ---- - -## Use `near-go build` (default) - -Always build with `near-go build` instead of raw `tinygo`. It applies the correct flags for NEAR and enables size optimizations automatically: - -```bash -near-go build -``` - -To inspect the intermediate generated code or set a custom output name: - -```bash -near-go build --output my_contract.wasm --keep-generated -``` - ---- - -## Minimize struct fields - -Every field stored in the contract state is serialized to JSON. Keep your state structs lean β€” only store what you need on-chain. - -```go -// Avoid: storing redundant or derived data -// @contract:state -type BadContract struct { - Balance string `json:"balance"` - BalanceStr string `json:"balance_str"` // redundant β€” derive it when needed - LastCaller string `json:"last_caller"` - CallCount uint64 `json:"call_count"` // may not be necessary -} - -// Better: store only essential state -// @contract:state -type GoodContract struct { - Balance string `json:"balance"` -} -``` - ---- - -## Avoid large dependencies - -TinyGo has a limited standard library. Avoid importing large packages that bring in code not needed at runtime. Prefer the SDK's built-in utilities: - -```go -// Avoid: importing "fmt" for formatting β€” it adds significant size -import "fmt" -env.LogString(fmt.Sprintf("value: %d", x)) - -// Better: use strconv or SDK helpers for simple conversions -import "strconv" -env.LogString("value: " + strconv.FormatUint(x, 10)) -``` - ---- - -## Use SDK collections for large data - -Native Go slices and maps are fully serialized into the `STATE` key on every call, making the contract state grow with the collection. Use SDK collections to store entries as separate storage keys: - -```go -import "github.com/vlmoon99/near-sdk-go/collections" - -// Avoid for large datasets: native map is fully loaded on every call -// @contract:state -type BadContract struct { - Balances map[string]string `json:"balances"` -} - -// Better: SDK LookupMap only loads entries on demand -// @contract:state -type GoodContract struct { - Balances *collections.LookupMap[string, string] `json:"balances"` -} -``` - ---- - -## Panic early, before doing expensive work - -Failing fast with `env.PanicStr` saves gas and avoids wasted computation. Validate all inputs and permissions before executing any logic: - -```go -// @contract:mutating -func (c *Contract) AdminAction(data string) { - caller, _ := env.GetPredecessorAccountID() - - // Validate permissions immediately - if caller != c.OwnerId { - env.PanicStr("Only the owner can call this method") - } - - // Validate input before processing - if len(data) == 0 { - env.PanicStr("Data must not be empty") - } - - // ... proceed with the actual logic -} -``` - - - - - diff --git a/smart-contracts/anatomy/reproducible-builds.mdx b/smart-contracts/anatomy/reproducible-builds.mdx index 9201fdfb73d..81faabcbe1e 100644 --- a/smart-contracts/anatomy/reproducible-builds.mdx +++ b/smart-contracts/anatomy/reproducible-builds.mdx @@ -6,9 +6,7 @@ Reproducible builds let different people build the same program and get the exac ## Problem - - - + If you build your contract on two different machines, most likely you will get two similar but not identical binaries. Your build artifact can be affected by the locale, timezone, build path, and billions of other factors in your build environment. Thus, the Rust community has a long history of addressing this issue. To achieve reproducible builds, NEAR utilizes several components such as [NEP-330-Source Metadata](https://github.com/near/NEPs/blob/master/neps/nep-0330.md), [cargo-near](https://github.com/near/cargo-near), [Docker](https://docker.com), and [SourceScan](https://github.com/SourceScan). @@ -77,67 +75,10 @@ near view contract_source_metadata In order to verify and publish your contract's code, you can use [NearBlocks](https://nearblocks.io) to trigger the verification process. Navigate to the contract's account page and under the **Contract** tab, you will see the **Verify and Publish** button. After the verification process is completed, the contract's source code, along with the metadata, will be publicly accessible on NearBlocks on the `Contract -> Contract Code` tab. ![reproducible-build](/assets/docs/smart-contracts/reproducible-build.png) - + For a step-by-step guide on how to verify your contract, check out the [Verification Guide](https://github.com/SourceScan/verification-guide). ## Additional Resources - [Verifying Smart Contracts on NEAR: Step-by-Step Guide](https://github.com/SourceScan/verification-guide) - [Tools Community Call #10 - Introducing Reproducible Builds (video)](https://youtu.be/RBIAcQj7nFs?t=1742) - - - - -For Python smart contracts, reproducibility is less of an issue since the Python SDK doesn't compile to WebAssembly directly. Instead, the Python code is executed in a Python-to-WASM interpreter that's embedded in the contract runtime. - -When you deploy a Python smart contract, you're deploying your Python source code directly (or in a lightly processed form), rather than a compiled binary. This makes the deployment process more deterministic, as there's less opportunity for build environment factors to influence the resulting artifact. - -### Ensuring Reproducible Python Contracts - -To ensure your Python contracts are reproducible: - -1. **Pin your dependencies**: If your contract uses any third-party libraries, make sure to specify exact versions. - -2. **Use consistent formatting**: Use tools like Black or YAPF to ensure consistent code formatting. - -3. **Document Python version**: Make sure to document which Python version your contract was developed with. - -4. **Avoid system-specific code**: Don't rely on features that might behave differently across operating systems. - - - - -## Go Contracts and Reproducible Builds - -Go contracts compiled with `near-go build` use **TinyGo** under the hood. Build outputs can vary between TinyGo versions and operating systems, similar to Rust before `cargo-near`. - - - -Official tooling for reproducible Go contract builds (equivalent to `cargo-near` + Docker for Rust) is not yet available. This feature is planned for a future release of `near-go`. - - - -### Best practices in the meantime - -To maximize the chance of consistent builds across environments: - -1. **Pin your `near-go` version**: Install a specific version of `near-go` and document it in your project's README. - - ```bash - # Check your near-go version - near-go --version - ``` - -2. **Pin your `near-sdk-go` version**: Use a fixed version in your `go.mod` rather than a floating tag. - - ```go - // go.mod - require github.com/vlmoon99/near-sdk-go v0.1.1 - ``` - -3. **Use CI for builds**: Build your WASM in a controlled CI environment (e.g. GitHub Actions) using a pinned `near-go` version. Publish the resulting WASM as a release artifact alongside your source code. - -4. **Provide the source**: Publish your contract's source code on a public repository so users can verify the logic independently, even before binary verification tooling is available. - - - \ No newline at end of file diff --git a/smart-contracts/anatomy/serialization.mdx b/smart-contracts/anatomy/serialization.mdx index 8dafe32de45..d081ca41c7e 100644 --- a/smart-contracts/anatomy/serialization.mdx +++ b/smart-contracts/anatomy/serialization.mdx @@ -114,19 +114,6 @@ The `NEAR SDK RS` currently implements the `near_sdk::json_types::{U64, I64, U12 that you can use for input / output of data. - -**Go: u64 as strings** -The Go SDK follows the same rule. Use `string` for `uint64` values in function parameters and return types when they may exceed 53 bits. For 128-bit token amounts, use `types.Uint128` from the Go SDK. - -```go -// Use string for large numbers passed as function arguments -type MyInput struct { - Amount string `json:"amount"` // pass as "1000000000000000000000000" -} -``` - - - --- ## Borsh: State Serialization @@ -137,18 +124,6 @@ the contract needs to translate complex states into simple key-value pairs. For this, NEAR contracts use [borsh](https://borsh.io) which is optimized for (de)serializing complex objects into smaller streams of bytes. - -**SDK-JS still uses json** -The JavaScript SDK uses JSON to serialize objects in the state, but the borsh implementation -should arrive soon - - - -**Go SDK uses JSON for state** - -The Go SDK also uses JSON (not Borsh) to serialize the contract state. Struct fields must have `json:"field_name"` tags for correct serialization and deserialization. - - #### Example Let's look at this example, written only for educational purposes: diff --git a/smart-contracts/anatomy/storage.mdx b/smart-contracts/anatomy/storage.mdx index 6fe2319160c..632f26289fb 100644 --- a/smart-contracts/anatomy/storage.mdx +++ b/smart-contracts/anatomy/storage.mdx @@ -11,20 +11,7 @@ Smart contracts store data in their account's state, which is public on the chai It is important to know that the account's **code** and account's **storage** are **independent**. [Updating the code](../release/upgrade) does **not erase** the state. - - - - - ### Defining the State - The attributes of the `class` marked as the contract define the data that will be stored. - - The contract can store all native types (e.g. `number`, `string`, `Array`, `Map`) as well as complex objects. - - For example, our Auction contract stores when the auction ends, and an object representing the highest bid. - - **Note:** The SDK also provides [collections](./collections) to efficiently store collections of data. - - + @@ -41,18 +28,7 @@ It is important to know that the account's **code** and account's **storage** ar - - - ### Defining the State - The attributes of the `struct` marked with `@contract:state` define the data that will be stored. - - The contract can store all native types (e.g. `uint64`, `string`, `bool`) as well as complex objects, serialized as JSON. - - For example, our Auction contract stores when the auction ends, and an object representing the highest bid. - - - - + @@ -64,7 +40,7 @@ It currently costs ~**1Ⓝ** to store **100KB** of data. - + ### Initializing the State After the contract is deployed, its state is empty and needs to be initialized with @@ -76,27 +52,6 @@ It currently costs ~**1Ⓝ** to store **100KB** of data. - - - ### I. Initialization Functions - An option to initialize the state is to create an `initialization` function, which needs to be called before executing any other function. - - In our Auction example, the contract has an initialization function that sets when the auction ends. Note the `@initialization` decorator, and the forced initialization on `NearBindgen`. - - **Note:** It is a good practice to mark initialization functions as private. We will cover function types in the [functions section](./functions). - - - - - - - -In Python, you need to manage the state initialization explicitly. The SDK doesn't enforce that initialization happens before other methods are called - you'll need to add your own checks if required. - - - - - ### I. Initialization Functions @@ -108,30 +63,6 @@ In Python, you need to manage the state initialization explicitly. The SDK doesn - - - ### I. Initialization Functions - An option to initialize the state is to create an `initialization` function, which needs to be called before executing any other function. - - In our Auction example, the contract has an initialization function that sets when the auction ends. The function is marked with the `@contract:init` comment directive. - - **Note:** It is a good practice to mark initialization functions as private. We will cover function types in the [functions section](./functions.md). - - - - - - ### II. Default State - Another option to initialize the state is to set default values for the attributes of the class. - - Such is the case for our "Hello World" contract, which stores a `greeting` with the default value `"Hello"`. - - The first time the contract is called (somebody executes `get_greeting` or `set_greeting`), the default values will be stored in the state, and the state will be considered initialized. - - **Note:** The state can only be initialized once. - - - ### II. Default State @@ -145,16 +76,7 @@ In Python, you need to manage the state initialization explicitly. The SDK doesn - - - ### II. Default State - In Go, when no `@contract:init` function is called, the contract's state struct is zero-initialized using Go's default zero values β€” `""` for strings, `0` for numbers, `false` for booleans. - - **Note:** The state can only be initialized once. - - - - + **Lifecycle of the State** @@ -168,7 +90,7 @@ When the method finishes executing successfully, all the changes to the state ar - + **State and Code** @@ -183,14 +105,6 @@ Make sure to read the [updating a contract](../release/upgrade) if you run into - - - - @@ -199,16 +113,4 @@ Make sure to read the [updating a contract](../release/upgrade) if you run into url="https://github.com/near-examples/hello-near-examples/blob/main/contract-rs/src/lib.rs" start="2" end="32" /> - - - - - - - - diff --git a/smart-contracts/anatomy/types.mdx b/smart-contracts/anatomy/types.mdx index 80990590bfe..4f4dba47e3e 100644 --- a/smart-contracts/anatomy/types.mdx +++ b/smart-contracts/anatomy/types.mdx @@ -8,21 +8,7 @@ import { ExplainCode, Block , File } from '/snippets/explain-code.jsx' Lets discuss which types smart contracts use to input and output data, as well as how such data is stored and handled in the contract's code. - - - - - ### Native Types - Smart contracts can receive, store and return data using JS native types: - - `string` - - `number` - - `boolean` - - `Array` - - `Map` - - `Object` - - `BigInt` - - + @@ -37,35 +23,6 @@ Lets discuss which types smart contracts use to input and output data, as well a - - - ### Native Types - Smart contracts can receive, store and return data using the following Python types: - - `str` - - `int` - - `float` - - `bool` - - `list` - - `dict` - - `set` - - `bytes` - - `None` - - - - - - ### Native Types - Smart contracts can receive, store and return data using the following Go types: - - `string` - - `uint64`, `int64`, `float64` - - `bool` - - Custom structs (with `json:"..."` tags for serialization) - - **Note:** Go does not have a native 128-bit integer type. Use `types.Uint128` from the SDK to handle large token amounts. - - - @@ -79,30 +36,7 @@ To simplify development, the SDK provides the `U64` and `U128` types which are a - - - -**Python Large Numbers** - -Python's `int` type has unlimited precision, so it can handle large integers (like yoctoNEAR values) without any special handling. All values are automatically serialized and deserialized correctly. - - - - - - - - -**`types.Uint128`** - -Go does not have a native 128-bit integer type. The SDK provides `types.Uint128` to handle large token amounts (e.g. yoctoNEAR values). - -Use `types.U128FromString(str)` to parse from a string, and `.String()` to convert back for output. Use `types.U64ToUint128(n)` for small constants. - - - - - + ### Complex Objects Smart contracts can store and return complex objects @@ -120,32 +54,6 @@ Use `types.U128FromString(str)` to parse from a string, and `.String()` to conve - - - #### JSON Tags - - In Go, struct fields used as input, output, or contract state must have `json:"field_name"` tags. The SDK uses these tags to serialize and deserialize data automatically. - - - - - - #### Serialization - Python objects are automatically serialized and deserialized using JSON for input/output and Pickle for internal storage. - - Complex nested objects like lists and dictionaries can be used directly without additional configuration. - - - - - - ### Handling Tokens - `$NEAR` tokens are typed as `BigInt` in JS, and their values represented in `yoctonear` - - **Note:** 1 NEAR = 10^24 yoctoNEAR - - - ### Handling Tokens @@ -155,77 +63,19 @@ Use `types.U128FromString(str)` to parse from a string, and `.String()` to conve - - - ### Handling Tokens - `$NEAR` tokens are represented as integers in Python, with values in `yoctoNEAR`. - The `near_sdk_py.constants` module provides `ONE_NEAR` and `ONE_TGAS` constants. - - **Note:** 1 NEAR = 10^24 yoctoNEAR - - - - - - ### Handling Tokens - `$NEAR` tokens are represented as `types.Uint128` in Go, with values in `yoctoNEAR`. - - - `env.GetAttachedDeposit()` returns the attached deposit as `types.Uint128` - - `types.U128FromString(str)` parses a token amount from string - - `types.U64ToUint128(n)` converts a small constant to `Uint128` - - `.String()` converts back to string for storage or output - - **Note:** 1 NEAR = 10^24 yoctoNEAR - - - - + ### Account The SDK exposes a special type to handle NEAR Accounts, which automatically checks if the account address is valid - - - ### Account IDs - In Python, NEAR account IDs are represented as `str` types. The SDK performs validation - when account IDs are used in contract calls or cross-contract interactions. - - - - - - ### Account IDs - In Go, NEAR account IDs are represented as `string`. Functions like `env.GetPredecessorAccountID()` and `env.GetCurrentAccountId()` return account IDs as plain strings. - - - - - - - - - - - - - - diff --git a/smart-contracts/anatomy/yield-resume.mdx b/smart-contracts/anatomy/yield-resume.mdx index ad1a9e5c040..3c7c9efec2a 100644 --- a/smart-contracts/anatomy/yield-resume.mdx +++ b/smart-contracts/anatomy/yield-resume.mdx @@ -21,75 +21,14 @@ The contract can wait for 200 blocks - around 2 minutes - after which the yielde Let's look at an example that takes a prompt from a user (e.g. "What is 2+2"), and yields the execution until an external service provides a response. - - - - - - - - - - - -```python -from near_sdk_py import call, view, near, Context -from near_sdk_py.collections import UnorderedMap -import json - -class YieldResumeContract: - def __init__(self): - # Store pending requests - self.requests = UnorderedMap("r") - self.request_id = 0 - - @call - def ask_assistant(self, prompt): - """ - Creates a yielded promise that will be resumed when an external service responds - - Args: - prompt: The question to ask the assistant - """ - # Create a new request ID - request_id = self.request_id - self.request_id += 1 - - # Create arguments for the callback function - callback_args = json.dumps({"request_id": request_id}) - - # Create a yielded promise to call return_external_response on this contract - yield_id = near.promise_create( - Context.current_account_id(), - "return_external_response", - callback_args, - 0, # No deposit - 30000000000000 # Gas - ) - - # Store the yield ID and prompt for the external service to find - self.requests[str(request_id)] = { - "yield_id": yield_id, - "prompt": prompt - } - - # Return the yield_id and request_id for tracking - return { - "yield_id": yield_id, - "request_id": request_id - } -``` - - + #### Creating a Yielded Promise In the example above, we are creating a [`Promise`](./crosscontract#promises) to call the contract's function `return_external_response`. -Notice that we create the `Promise` using `env::promise_yield_create` in Rust or `near.promise_create` in Python (the Python SDK uses standard promises for yielding), which will create an **identifier** for the yielded promise in the `YIELD_REGISTER`. +Notice that we create the `Promise` using `env::promise_yield_create`, which will create an **identifier** for the yielded promise in the `YIELD_REGISTER`. #### Retrieving the Yielded Promise ID We read the `YIELD_REGISTER` to retrieve the `ID` of our yielded promise. We store the `yield_id` and the user's `prompt` so the external service query them (the contract exposes has a function to list all requests). @@ -109,31 +48,11 @@ Since we only use it to simplify the process of keeping track of the requests, y ## Signaling the Resume -The `env::promise_yield_resume` function in Rust or `near.promise_yield_resume` in Python allows us to signal which yielded promise should execute, as well as which parameters to pass to the resumed function. +The `env::promise_yield_resume` function allows us to signal which yielded promise should execute, as well as which parameters to pass to the resumed function. - - - - - - - - - - - -```python -@call -def respond(self, yield_id, response): - """Called by the external service to provide a response to a prompt""" - return near.promise_yield_resume(yield_id, json.dumps({"response": response})) -``` - - + In the example above, the `respond` function would be called by an external service, passing which promise should be resume (`yield_id`), and the response to the prompt. @@ -150,49 +69,9 @@ Since the function used to signal the resume is public, developers must make sur The function being resumed will have access to all parameters passed to it, including those passed during the yield creation, or the external service response. - - - - - - - - - - - -```python -@call -def return_external_response(self, request_id, response=None): - """ - Function that gets called when the yielded promise resumes - - Args: - request_id: The ID of the request (passed when promise was created) - response: The response from the external service (or None if timed out) - """ - # Remove the request from our tracking - request_data = self.requests.get(str(request_id)) - if request_data: - del self.requests[str(request_id)] - - # Handle timeout case - if response is None: - return {"error": "Timeout waiting for external service", "request_id": request_id} - - # Return the response from the external service - return { - "request_id": request_id, - "response": response, - "success": True - } -``` - - + In the example above, the `return_external_response` receives parameters: @@ -229,145 +108,3 @@ It is best practice to check the validity of the response within the function wh Check more docs on [callback security](../security/callbacks) and [reentrancy attacks](../security/reentrancy) to avoid common pitfalls when dealing with asynchronous calls. - ---- - -## Complete Example - -Here's a more complete implementation of a yield-resume pattern in Python: - - - -```python -from near_sdk_py import view, call, near, Context -from near_sdk_py.collections import UnorderedMap -import json - -class AIAssistantContract: - def __init__(self): - # Track all pending requests - self.requests = UnorderedMap("r") - self.request_counter = 0 - - @call - def ask_question(self, question): - """ - Ask a question to the AI assistant - - The execution will yield until an external AI service responds - """ - # Create a unique ID for this request - request_id = self.request_counter - self.request_counter += 1 - - # Create callback args - will be passed to our callback function - callback_args = json.dumps({ - "request_id": request_id - }) - - # Create the promise - this will yield until resumed - promise_id = near.promise_create( - Context.current_account_id(), # Call this contract - "process_ai_response", # Call this method when resumed - callback_args, # Pass these arguments - 0, # No attached deposit - 30000000000000 # Gas for execution (30 TGas) - ) - - # Store the request for the external service to find - self.requests[str(request_id)] = { - "prompt": question, - "promise_id": promise_id, - "user": Context.predecessor_account_id(), - "timestamp": Context.block_timestamp() - } - - return { - "request_id": request_id, - "status": "processing" - } - - @view - def get_pending_requests(self): - """Returns all pending requests for the AI service to process""" - return [ - { - "request_id": int(req_id), - "data": self.requests[req_id] - } - for req_id in self.requests.keys() - ] - - @call - def provide_ai_response(self, request_id, response): - """ - Called by the AI service to provide a response - - Args: - request_id: ID of the request being answered - response: The AI's response to the question - """ - request_id_str = str(request_id) - - # Verify the request exists - if request_id_str not in self.requests: - raise Exception(f"No pending request with ID {request_id}") - - # Get the request data - request = self.requests[request_id_str] - - # Resume the promise with the AI's response - result = near.promise_yield_resume( - request["promise_id"], - json.dumps({"ai_response": response}) - ) - - return {"success": result} - - @call - def process_ai_response(self, request_id, ai_response=None): - """ - Called when a yielded promise resumes - - This is either called by provide_ai_response or by a timeout - - Args: - request_id: ID of the request - ai_response: The AI's response or None if timed out - """ - request_id_str = str(request_id) - - # Cleanup - remove from pending requests - if request_id_str in self.requests: - request = self.requests[request_id_str] - del self.requests[request_id_str] - else: - request = None - - # Handle timeout case - if ai_response is None: - return { - "request_id": request_id, - "status": "timeout", - "message": "The AI service did not respond in time" - } - - # Return the AI's response - return { - "request_id": request_id, - "status": "complete", - "question": request["prompt"] if request else "Unknown", - "answer": ai_response, - "user": request["user"] if request else "Unknown" - } -``` - - - -This example demonstrates a complete yield-resume pattern for an AI assistant contract where: - -1. A user asks a question through `ask_question` -2. The contract creates a yielded promise and stores the question -3. An external AI service periodically checks for new questions using `get_pending_requests` -4. When the AI has an answer, it calls `provide_ai_response` to resume the yielded promise -5. The `process_ai_response` function executes with the AI's answer (or timeout) and returns the result diff --git a/smart-contracts/quickstart.mdx b/smart-contracts/quickstart.mdx index 3bf3dc4d17e..883ddf38997 100644 --- a/smart-contracts/quickstart.mdx +++ b/smart-contracts/quickstart.mdx @@ -2,7 +2,7 @@ title: Your First Smart Contract icon: rocket sidebarTitle: Quickstart -description: "Create your first contract using your favorite language." +description: "Create your first smart contract in Rust and deploy it to the NEAR testnet." --- import {Github} from '/snippets/github.jsx'; @@ -12,11 +12,9 @@ Welcome! [NEAR accounts](../protocol/accounts-contracts/account-model) can store Join us in creating a friendly auction contract, which allows users to place bids, track the highest bidder and claim tokens at the end of the auction. -**Which language should I use?** +**Why Rust?** -In this tutorial we provide instructions to create contracts in multiple languages. - -We **strongly recommend using [Rust](https://www.rust-lang.org/)** for production apps β€” it offers the most mature tooling, best performance, and the strongest safety guarantees when handling real assets. If you are prototyping or learning, feel free to use **JavaScript**, **Python**, or **Go**! +NEAR smart contracts are written in [Rust](https://www.rust-lang.org/) and compiled to WebAssembly. Rust offers the most mature tooling, best performance, and the strongest safety guarantees when handling real assets on-chain. @@ -38,8 +36,6 @@ We **strongly recommend using [Rust](https://www.rust-lang.org/)** for productio Before starting, make sure to set up your development environment. - - ```bash # Install Rust: https://www.rust-lang.org/tools/install @@ -55,92 +51,13 @@ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releas curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh ``` - - - - -```bash -# Install Node.js using nvm (more options in: https://nodejs.org/en/download) -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash -nvm install latest - -# ⚠️ For Mac Silicon users only, Rosetta is needed to compile contracts -# /usr/sbin/softwareupdate --install-rosetta --agree-to-license - -# Install NEAR CLI to deploy and interact with the contract -npm install -g near-cli-rs@latest -``` - - - - - - ```bash -# Install Python (if not already installed) -# Use your system's package manager or download from https://www.python.org/downloads/ - -# Install Emscripten (required for compiling Python contracts to WebAssembly) -# For Linux/macOS: -git clone https://github.com/emscripten-core/emsdk.git -cd emsdk -./emsdk install latest -./emsdk activate latest -source ./emsdk_env.sh -# Add to your .bashrc or .zshrc for permanent installation: -# echo 'source "/path/to/emsdk/emsdk_env.sh"' >> ~/.bashrc -cd .. - -# For Windows: -# Download and extract: https://github.com/emscripten-core/emsdk -# Then in Command Prompt: -# cd emsdk -# emsdk install latest -# emsdk activate latest -# emsdk_env.bat - -# Verify installation with: -emcc --version - -# Install uv for Python package management -curl -LsSf https://astral.sh/uv/install.sh | sh - -# 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 -``` - - - - -```bash -#For Linux arm/x64 -sudo apt update && sudo apt upgrade -y -sudo apt install -y build-essential curl wget git libssl-dev pkg-config checkinstall -sudo apt install bison - -#For Mac -xcode-select --install -brew update -brew install mercurial -brew install binaryen - -bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) -gvm install go1.25.4 -B -gvm use go1.25.4 --default - -curl -LO https://github.com/vlmoon99/near-cli-go/releases/latest/download/install.sh && bash install.sh -``` - - - --- ## Creating the Contract -Create a smart contract by using one of the scaffolding tools and following their instructions: +Create a smart contract by using the `cargo near` scaffolding tool and following its instructions: - - ```bash cargo near @@ -149,66 +66,6 @@ cargo near ![img](/assets/docs/smart-contracts/hello-near-rs.gif) _Creating a project using `cargo near new`_ - - - - -```bash -npx create-near-app@latest -``` - -![img](/assets/docs/smart-contracts/hello-near-ts.gif) -_Creating a project using `npx create-near-app@latest`_ - - - -When prompted to choose a template, select the basic `Auction` template to scaffold the auction contract - - - - - - - - -Python quickstart tutorial is coming soon! - -In the meantime, please check out the [hello-near](https://github.com/near-examples/hello-near-examples/tree/main/contract-py) example. - - - - - - -```bash -near-go create --project-name auction --module-name auction --project-type smart-contract-empty -``` - -_Creating a project using `near-go create`_ - -This will generate a project with the following structure: - -```bash -auction/ -└── contract/ - β”œβ”€β”€ go.mod # Go module definition with near-sdk-go dependency - β”œβ”€β”€ go.sum - └── main.go # Main contract file β€” replace this with the auction contract below -``` - -```bash -cd auction/contract -``` - - - -Replace the contents of `main.go` with the auction contract code shown in the sections below. - - - - - - @@ -228,31 +85,9 @@ Let's take a look at the different components of the contract and how they work The contract stores the highest bid, auction end time, auctioneer address, and a flag to track if proceeds have been claimed. In order to set these parameters, an `init` function is provided, which must be called first to initialize the contract state. - - - - - - - - - - - - - - - - - + ### Placing Bids @@ -260,91 +95,25 @@ In order to place a bid, users will need to call the `bid` function while attach The `bid` function will then validate that the auction is ongoing, if the user bid a higher amount than the one stored, it will record the new bid and refund the previous bidder. - - - - - - - - - - - - - - - - - + ### Claiming Proceeds Once the auction ends, any user can call `claim` to transfer the winning bid to the auctioneer: - - - - - - - - - - - - - - - - - + ### View Methods At all times, users can query the current state of the auction by calling its view methods (`get_highest_bid`, `get_auction_end_time`, `get_auctioneer`, `get_claimed`): - - - - - - - - - - - - - - - - - + --- @@ -352,47 +121,11 @@ At all times, users can query the current state of the auction by calling its vi Lets make sure the contract is working as expected by running its tests. Simply run the `test` command, the contract will then be compiled and deployed to a local sandbox for testing: - - - - ```bash - cargo test - ``` - - - - - - ```bash - npm run test - ```` - - - - Make sure that you are using `node v24 / 22 / 20`, and that you have installed `Rosetta` if you have a Mac with Apple Silicon - - - - - - - - ```bash - uv run pytest - ``` - - - - - - - ```bash - near-go test project - ``` +```bash +cargo test +``` - - Feel free to check the test files to see how they interact with the contract. In short, a local NEAR sandbox is created, the contract is deployed, and different methods are called to verify the expected behavior. @@ -402,79 +135,11 @@ Feel free to check the test files to see how they interact with the contract. In Now that we know the tests are passing, let us deploy the contract! First, we need to compile it into WebAssembly: - - - - ```bash - cargo near build non-reproducible-wasm - ``` - - - - - - ```bash - npm run build - ``` - - - - - - ```bash - # Build with nearc through the uv executor (no installation needed) - uvx nearc contract.py - ``` - - The above command will compile your Python contract into WebAssembly (WASM) that can be deployed to the NEAR blockchain. - - - - The default `nearc` build configuration is appropriate for most contracts. You don't need to install nearc separately as we're using `uvx` to run it directly. - - - - - - This step requires [Emscripten](https://emscripten.org/) to be installed and accessible in your `PATH`. If you encounter errors during compilation, verify that Emscripten is properly installed with `emcc --version`. - - Common compilation errors and solutions: - - `emcc: command not found` - Emscripten is not in your PATH. Run `source /path/to/emsdk/emsdk_env.sh` to add it temporarily. - - `error: invalid version of emscripten` - Your Emscripten version might be too old. Try updating with `./emsdk install latest && ./emsdk activate latest`. - - `Could not find platform micropython-dev-wasm32` - This typically means the Emscripten installation is incomplete or not properly activated. - - - - - - - ```bash - near-go build - ``` - - - **Near Go SDK Build Process** - - 1. All code from the main package, including imports from other modules, is combined into a single **generated_build.go** file. - 2. The **generated_build.go** file is compiled into `wasm32-unknown-unknown` via **TinyGo**. - - - - - **Customizing the Build** - - The default `near-go build` command works for most standard projects, compiling source code from the current directory into `main.wasm`. - - However, if you want to specify a custom output name or **inspect the intermediate glue code** (generated JSON serialization and SDK integration wrappers) for debugging purposes, you can use the available flags: - ```bash - near-go build --output my_contract.wasm --keep-generated - ``` - - - +```bash +cargo near build non-reproducible-wasm +``` - ### Create an Account @@ -508,36 +173,9 @@ near account create-account sponsor-by-faucet-service With the contract ready, we can now deploy it to the `testnet` account we created earlier: - - - ```bash - near deploy ./target/near/auction.wasm - ``` - - - - ```bash - near deploy ./build/auction.wasm - ``` - - - - - ```bash - near deploy ./auction.wasm - ``` - - - - - - ```bash - near deploy ./main.wasm - ``` - - - - +```bash +near deploy ./target/near/auction.wasm +``` **Congrats!** Your contract now lives in the NEAR testnet network. @@ -632,11 +270,8 @@ The cost of deploying a contract depends on the contract's size, approximately 1 #### Can I update a contract after deploying? Yes. Redeploy with `near deploy `. The account stays the same, code updates. -#### Which language should I choose? -We **strongly recommend [Rust](https://www.rust-lang.org/)** for production apps β€” it provides the most mature tooling, best performance, and strongest safety guarantees when handling real assets on-chain. If you are prototyping or learning, you can use **JavaScript**, **Python**, or **Go**. - #### How do I test without deploying? -All languages support sandbox testing (shown in this guide). Tests run locally with a simulated NEAR environment. +Use sandbox testing (shown in this guide). Tests run locally with a simulated NEAR environment. --- @@ -660,16 +295,8 @@ All languages support sandbox testing (shown in this guide). Tests run locally w At the time of this writing, this example works with the following versions: -- node: `22.18.0` - rustc: `1.86.0` - near-cli-rs: `0.22.0` - cargo-near: `0.16.1` -- Python: `3.13` -- near-sdk-py: `0.7.3` -- uvx nearc: `0.9.2` -- emscripten: `4.0.9` (required for Python contracts) -- Go: `1.25.4` -- near-go (near-cli-go): `v0.1.1` -- near-sdk-go: `v0.1.1` diff --git a/smart-contracts/release/deploy.mdx b/smart-contracts/release/deploy.mdx index b8e51b3d9c3..1a8c8883136 100644 --- a/smart-contracts/release/deploy.mdx +++ b/smart-contracts/release/deploy.mdx @@ -24,32 +24,11 @@ Thanks to the `NEAR CLI` deploying a contract is as simple as: ### Compile the Contract - - - ```bash - cargo near build - ``` - - - - - - ```bash - yarn build - ``` - - - - - - ```bash - near-go build - ``` - - +```bash +cargo near build +``` - ### Create an Account and Deploy @@ -84,7 +63,7 @@ By default `near-cli` uses the `testnet` network. Define `NEAR_ENV=mainnet` to d **Naming Convention for Public-Facing Methods** Once the contract is deployed to the network, anyone and any other contract (i.e., any other account on NEAR) can interact with it by calling its methods. Furthermore, any transactions involving the contract will also be included in the network's data stream, which means its activity can also be visible to any who listens to particular events. -Considering this, we advise to name methods using `snake_case` in all SDKs as this is compatible with the remainder of the NEAR ecosystem which is predominantly comprised of Rust contracts. +Considering this, we advise to name methods using `snake_case`, which is the convention across the NEAR ecosystem. --- diff --git a/smart-contracts/release/upgrade.mdx b/smart-contracts/release/upgrade.mdx index 717fab1b7f8..a7e2d0e4abd 100644 --- a/smart-contracts/release/upgrade.mdx +++ b/smart-contracts/release/upgrade.mdx @@ -43,16 +43,9 @@ A smart contract can also update itself by implementing a method that: 1. Takes the new wasm contract as input 2. Creates a Promise to deploy it on itself - - - - - - + #### How to Invoke Such Method? @@ -66,22 +59,6 @@ near contract call-function as-transaction update_contract fi
- - -```js -// Load the contract's raw bytes -const code = fs.readFileSync("./path/to/wasm.wasm"); - -// Call the update_contract method -await wallet.callFunction({ - contractId: guestBook, - method: "update_contract", - args: code, - gas: "300000000000000", -}); -``` - - @@ -138,43 +115,19 @@ Imagine you have a Guest Book where you store messages, and the users can pay for such messages to be "premium". You keep track of the messages and payments using the following state: - - - - - - - - - #### Update Contract At some point you realize that you could keep track of the `payments` inside of the `PostedMessage` itself, so you change the contract to: - - - - - - - - - #### Incompatible States If you deploy the update into an initialized account the contract will fail to @@ -191,25 +144,11 @@ To fix the problem, you need to implement a method that goes through the old state, removes the `payments` vector and adds the information to the `PostedMessages`: - - - - - - - - - -Notice that in Rust/JS, `migrate` is actually an [initialization method](../anatomy/storage) that **ignores** the existing state (`[#init(ignore_state)]`), thus being able to execute and rewrite the state. - -In Go, `Migrate()` is a regular `@contract:mutating` method that reads the old state manually using `env.StateRead()`, transforms it, and writes the new state back β€” no special init directive is needed. +Notice that `migrate` is actually an [initialization method](../anatomy/storage) that **ignores** the existing state (`#[init(ignore_state)]`), thus being able to execute and rewrite the state. @@ -284,8 +223,5 @@ To remove them in `migrate` method, we call `clear()` method on payments vector You can follow a migration step by step in the [official migration example](https://github.com/near-examples/update-migrate-rust/tree/main/basic-updates/base) -Javascript migration example testfile can be found on here: -[test-basic-updates.ava.js](https://github.com/near/near-sdk-js/blob/develop/examples/__tests__/test-basic-updates.ava.js), -run by this command: `pnpm run test:basic-update` in examples directory. diff --git a/smart-contracts/testing/integration-test.mdx b/smart-contracts/testing/integration-test.mdx index 86f417dbd23..27d64ad1bad 100644 --- a/smart-contracts/testing/integration-test.mdx +++ b/smart-contracts/testing/integration-test.mdx @@ -15,14 +15,6 @@ Moreover, when using the local `sandbox` you gain complete control of the networ In NEAR, integration tests are implemented using a framework called **Sandbox**. Sandbox comes in two flavors: [πŸ¦€ Rust](https://github.com/near/near-sandbox-rs) and [🌐 Typescript](https://github.com/near/workspaces-js). -For **Go contracts**, unit tests are run with the `near-go` CLI: - -```bash -near-go test project -``` - -This runs unit tests using the `MockSystem` environment to simulate the NEAR blockchain locally β€” no sandbox required. For full integration tests that deploy to a sandbox, Go contracts use the Rust `workspaces-rs` framework. See the [Go Integration Testing](#go-integration-testing) section below. - **Sandbox Testing** @@ -35,79 +27,29 @@ NEAR Sandbox allows you to write tests once, and run them either on `testnet` or ### Account - - - + - -
-### Dev Account - - - - - - - - - -### Subaccount - - - - - - - - - ### Using Secret Key - - - - - - - - + - - ### Using Credentials From File - - - + - - - - - - - --- @@ -115,115 +57,46 @@ NEAR Sandbox allows you to write tests once, and run them either on `testnet` or ### Compile Contract Code - - - + - - You don't need to assert compiling process everytime. You can use `?` operator to get the result as `Vec` without dealing with `Result>, Error>` type. That way you can directly use this vector to deploy the wasm file into account. Your test will still fail if compiling process fails. - - ```rust - let contract_wasm_path = cargo_near_build::build_with_cli(Default::default())?; - ``` - - - - - If you want to compile a contract each time running tests, you can put following scripts into `package.json` file. In the code you can access path to compiled file using `process.argv[2]`. - - `package.json` file: - ```json - "scripts": { - "build": "near-sdk-js build src/contract.ts build/hello_near.wasm", - "test": "$npm_execpath run build && ava -- ./build/hello_near.wasm" - }, - ``` - - `main.ava.js` file: - ```js - const pathToWasm = process.argv[2]; - await contract.deploy(pathToWasm); - ``` + +You don't need to assert compiling process everytime. You can use `?` operator to get the result as `Vec` without dealing with `Result>, Error>` type. That way you can directly use this vector to deploy the wasm file into account. Your test will still fail if compiling process fails. - - +```rust +let contract_wasm_path = cargo_near_build::build_with_cli(Default::default())?; +``` + ### Loading From File - - - - - - The same as in the case of compilation wasm from code, you don't need to assert reading file process everytime. You can use `expect` method to get the reading file result as `Vec` and provide error message as a parameter. Your test will still fail if compiling process fails. + - ```rust - let contract_wasm = std::fs::read(artifact_path) - .expect(format!("Could not read WASM file from {}", artifact_path).as_str()); - ``` - - - - - If you want to use pre-compiled a contract, you can put following scripts into `package.json` file. In the code you can access path to pre-compiled file using `process.argv[2]`. - - `package.json` file: - ```json - "scripts": { - "build": "near-sdk-js build src/contract.ts build/hello_near.wasm", - "test": "ava -- ./build/hello_near.wasm" - }, - ``` - - `main.ava.js` file: - ```js - const pathToWasm = process.argv[2]; - await contract.deploy(pathToWasm); - ``` + +The same as in the case of compilation wasm from code, you don't need to assert reading file process everytime. You can use `expect` method to get the reading file result as `Vec` and provide error message as a parameter. Your test will still fail if compiling process fails. - - +```rust +let contract_wasm = std::fs::read(artifact_path) + .expect(format!("Could not read WASM file from {}", artifact_path).as_str()); +``` + --- ## Deploy Contracts -### Dev Deploy - - - - - - - - - ### Deploy To Account - - - + - - - - - - - --- @@ -231,77 +104,45 @@ NEAR Sandbox allows you to write tests once, and run them either on `testnet` or Show contract's logs. - - - - You can use `println` or `dbg!` when you want to see information from your code. - - - In Rust, the output from your code is captured by default and not displayed in the terminal. In order to see the output, you have to use the `--nocapture` flag +You can use `println` or `dbg!` when you want to see information from your code. - eg. `cargo test -- --nocapture` + - If you want to access the contracts logs, you can find them in the `tx_outcome.logs()` Vec. +In Rust, the output from your code is captured by default and not displayed in the terminal. In order to see the output, you have to use the `--nocapture` flag - ```rust - let tx_outcome = contract - .call_function("set_greeting", json!({"greeting": "Hello World!"})) - .transaction() - .gas(Gas::from_tgas(100)) - .with_signer(contract.account_id().clone(), signer.clone()) - .send_to(&sandbox_network) - .await?; - assert!(tx_outcome.is_success()); + eg. `cargo test -- --nocapture` - dbg!(tx_outcome.logs()); - // [tests/test_basics.rs:29:5] tx_outcome.logs() = [ - // "Saving greeting: Hello World!", - // ] - ``` + If you want to access the contracts logs, you can find them in the `tx_outcome.logs()` Vec. - - - - Use `console.log` method when you want to see debug information from your code. - - ```js - const balance = await account.balance(); - - console.log('balance: ', balance); - // balance: { - // total: , - // stateStaked: , - // staked: , - // available: - // } + ```rust + let tx_outcome = contract + .call_function("set_greeting", json!({"greeting": "Hello World!"})) + .transaction() + .gas(Gas::from_tgas(100)) + .with_signer(contract.account_id().clone(), signer.clone()) + .send_to(&sandbox_network) + .await?; + assert!(tx_outcome.is_success()); + + dbg!(tx_outcome.logs()); + // [tests/test_basics.rs:29:5] tx_outcome.logs() = [ + // "Saving greeting: Hello World!", + // ] ``` - - --- ## Account Balance - - - + - - - - - - - --- @@ -309,41 +150,19 @@ Show contract's logs. ### Call - - - - - - - - + - - ### View - - - + - - - - - - - --- @@ -353,27 +172,11 @@ In Sandbox-mode, you can add or modify any contract state, contract code, accoun You can alter contract code, accounts, and access keys using normal transactions via the `DeployContract`, `CreateAccount`, and `AddKey` [actions](https://nomicon.io/RuntimeSpec/Actions#addkeyaction). But this limits you to altering your own account or sub-account. `patchState` allows you to perform these operations on any account. - - - - - - - - + - - - To see a complete example of how to do this, see the [patch-state test](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts). - - - - As an alternative to `patchState`, you can stop the node, dump state at genesis, edit the genesis, and restart the node. @@ -386,26 +189,13 @@ This approach is more complex to do and also cannot be performed without restart `sandbox` offers support for forwarding the state of the blockchain to the future. This means contracts which require time sensitive data do not need to sit and wait the same amount of time for blocks on the sandbox to be produced. We can simply just call `sandbox.fast_forward` to get us further in time: - - - - - _[See the full example on Github](https://github.com/near/workspaces-rs/blob/main/examples/src/fast_forward.rs)._ + - +_[See the full example on Github](https://github.com/near/workspaces-rs/blob/main/examples/src/fast_forward.rs)._ - - - - - - - --- @@ -417,96 +207,15 @@ NEAR Sandbox is set up so that you can write tests once and run them against a l * You can test against deployed testnet contracts * If something seems off in Sandbox mode, you can compare it to testnet - - - - - - - If you can create a new account on each iteration as well. - - - - - - - You can switch to testnet mode in three ways: - + - When creating Worker set network to `testnet` and pass your master account (an account for which you have the private key): - - ```ts - const worker = await Worker.init({ - network: 'testnet', - testnetMasterAccountId: '', - initialBalance: NEAR.parse(" N").toString(), - }) - ``` - - - - - - Set the `NEAR_WORKSPACES_NETWORK` and `TESTNET_MASTER_ACCOUNT_ID` (an account for which you have the private key) environment variables when running your tests: - - ```bash - NEAR_WORKSPACES_NETWORK=testnet TESTNET_MASTER_ACCOUNT_ID= node test.js - ``` - - If you set this environment variables and pass `{network: 'testnet', testnetMasterAccountId: }` to `Worker.init`, the config object takes precedence. - - - - - - If you are using AVA, you can use a custom config file. Other test runners allow similar config files; adjust the following instructions for your situation. - - Create a file in the same directory as your `package.json` called `ava.testnet.config.cjs` with the following contents: - - ```js - module.exports = { - ...require('near-workspaces/ava.testnet.config.cjs'), - ...require('./ava.config.cjs'), - }; - module.exports.environmentVariables = { - TESTNET_MASTER_ACCOUNT_ID: '', - }; - ``` - - Where the master account is an account for which you have the private key. - - The [near-workspaces/ava.testnet.config.cjs](https://github.com/near/workspaces-js/blob/main/ava.testnet.config.cjs) import sets the `NEAR_WORKSPACES_NETWORK` environment variable for you. A benefit of this approach is that you can then easily ignore files that should only run in Sandbox mode. - - Now you'll also want to add a `test:testnet` script to your `package.json`'s `scripts` section: - - ```diff - "scripts": { - "test": "ava", - + "test:testnet": "ava --config ./ava.testnet.config.cjs" - } - ``` - - - - To use the accounts, you will need to create the `.near-credentials/workspaces/testnet` directory and add files for your master account, for example: - - ```js - // .near-credentials/workspaces/testnet/.testnet.json - {"account_id":".testnet","public_key":"ed25519:...","private_key":"ed25519:..."} - ``` - - Example: - - - - + +If you can create a new account on each iteration as well. + - --- @@ -514,43 +223,15 @@ NEAR Sandbox is set up so that you can write tests once and run them against a l [Spooning a blockchain](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) is copying the data from one network into a different network. NEAR Sandbox makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment: - - - - - Specify the contract name from `testnet` you want to be pulling, and a specific block ID referencing back to a specific time. (Just in case the contract you're referencing has been changed or updated) - - Create a function called `pull_contract` which will pull the contract's `.wasm` file from the chain and deploy it onto your local sandbox. You'll have to re-initialize it with all the data to run tests. This is because the contract's data is too big for the RPC service to pull down. (limits are set to 50Mb) - - - +Specify the contract name from `testnet` you want to be pulling, and a specific block ID referencing back to a specific time. (Just in case the contract you're referencing has been changed or updated) - - -```ts -const refFinance = await root.importContract({ - mainnetContract: 'v2.ref-finance.near', - blockId: 50_000_000, - withData: true, -}); -``` - -This would copy the Wasm bytes and contract state from [v2.ref-finance.near](https://nearblocks.io/address/v2.ref-finance.near) to your local blockchain as it existed at block `50_000_000`. This makes use of Sandbox's special [patch state](#patch-state-on-the-fly) feature to keep the contract name the same, even though the top level account might not exist locally (note that this means it only works in Sandbox testing mode). You can then interact with the contract in a deterministic way the same way you interact with all other accounts created with near-workspaces. - - - -`withData` will only work out-of-the-box if the contract's data is 50kB or less. This is due to the default configuration of RPC servers; see [the "Heads Up" note here](/api/rpc/contracts#view-contract-state). - - +Create a function called `pull_contract` which will pull the contract's `.wasm` file from the chain and deploy it onto your local sandbox. You'll have to re-initialize it with all the data to run tests. This is because the contract's data is too big for the RPC service to pull down. (limits are set to 50Mb) -See a [TypeScript example of spooning](https://github.com/near/workspaces-js/blob/main/__tests__/05.spoon-contract-to-sandbox.ava.ts) contracts. + - - - --- @@ -560,20 +241,8 @@ See a [TypeScript example of spooning](https://github.com/near/workspaces-js/blo Lets take a look at the test of our [Quickstart Project](../quickstart) [πŸ‘‹ Hello NEAR](https://github.com/near-examples/hello-near-examples), where we deploy the contract on an account and test it correctly retrieves and sets the greeting. - - - - - - - - - - - +
@@ -581,148 +250,8 @@ Lets take a look at the test of our [Quickstart Project](../quickstart) [πŸ‘‹ He In most cases we will want to test complex methods involving multiple users and money transfers. A perfect example for this is our [Donation Example](https://github.com/near-examples/donation-examples), which enables users to `donate` money to a beneficiary. Lets see its integration tests - - - - - - - - - - - - ---- - -## Go Integration Testing - -Go contracts use the Rust `workspaces-rs` framework for integration tests. The workflow is: -1. Build your contract to WASM with `near-go build` -2. Write integration tests in a separate Rust project using `workspaces-rs` -3. Run with `cargo test` - - -**Unit tests vs Integration tests** - -`near-go test project` runs **unit tests** using the `MockSystem` environment locally. For full integration tests that deploy to a sandbox, use the `workspaces-rs` Rust crate as described below. - - -### Project Structure - -```bash -my-contract/ -β”œβ”€β”€ main.go # your Go contract -β”œβ”€β”€ go.mod -β”œβ”€β”€ go.sum -└── integration-tests/ # Standalone Rust integration test project - β”œβ”€β”€ Cargo.toml - └── tests/ - └── test_my_contract.rs -``` - -### Running Tests - -```bash -# Step 1: Build the Go contract to WASM (from the contract root) -near-go build - -# Step 2: Run integration tests -cargo test --manifest-path integration-tests/Cargo.toml -``` - -### Writing Integration Tests - -Integration tests follow the `workspaces-rs` pattern. The compiled `main.wasm` sits one directory above `integration-tests/`, so tests load it with `std::fs::read("../main.wasm")`. - - -**Double-encoded return values** - -`near-sdk-go` wraps every return value in an extra JSON string encoding. When reading a view result, always deserialize in **two steps**: first parse the outer `String`, then parse the inner type. - -```rust -// βœ… Correct two-step deserialization -let raw: String = contract.view("get_greeting").args_json(json!({})).await?.json()?; -let greeting: String = serde_json::from_str(&raw)?; - -// ❌ Will fail – skips the outer wrapper -let greeting: String = contract.view("get_greeting").args_json(json!({})).await?.json()?; -``` - - -```rust -// integration-tests/tests/test_greeting.rs -use serde_json::json; - -#[tokio::test] -async fn test_greeting_init() -> anyhow::Result<()> { - let sandbox = near_workspaces::sandbox().await?; - let wasm = std::fs::read("../main.wasm")?; - let contract = sandbox.dev_deploy(&wasm).await?; - - contract - .call("init") - .args_json(json!({})) - .transact() - .await? - .into_result()?; - - // Two-step deserialization: outer String wrapper β†’ inner type - let raw: String = contract.view("get_greeting").args_json(json!({})).await?.json()?; - let greeting: String = serde_json::from_str(&raw)?; - assert_eq!(greeting, "Hello"); - Ok(()) -} - -#[tokio::test] -async fn test_set_greeting() -> anyhow::Result<()> { - let sandbox = near_workspaces::sandbox().await?; - let wasm = std::fs::read("../main.wasm")?; - let contract = sandbox.dev_deploy(&wasm).await?; - - contract.call("init").args_json(json!({})).transact().await?.into_result()?; - - let caller = sandbox.dev_create_account().await?; - caller - .call(contract.id(), "set_greeting") - .args_json(json!({ "greeting": "Howdy" })) - .transact() - .await? - .into_result()?; - - let raw: String = contract.view("get_greeting").args_json(json!({})).await?.json()?; - let greeting: String = serde_json::from_str(&raw)?; - assert_eq!(greeting, "Howdy"); - Ok(()) -} -``` - -```toml -# integration-tests/Cargo.toml -[package] -name = "greeting-integration-tests" -version = "0.1.0" -edition = "2021" - -[dev-dependencies] -near-workspaces = "0.22.0" -tokio = { version = "1.41.1", features = ["full"] } -anyhow = "1.0.93" -serde_json = "1.0.133" -serde = { version = "1.0.215", features = ["derive"] } -``` - - - -You can also test your Go contract on NEAR testnet by deploying it first with `near deploy` and calling methods via the `near` CLI. - -[See full example on GitHub](https://github.com/Emir-Asanov/near-go-examples/blob/example-release-1/greeting/integration-tests/tests/test_greeting.rs) - - + --- diff --git a/smart-contracts/testing/unit-test.mdx b/smart-contracts/testing/unit-test.mdx index 99821c0c085..88f9b5bbb51 100644 --- a/smart-contracts/testing/unit-test.mdx +++ b/smart-contracts/testing/unit-test.mdx @@ -5,31 +5,17 @@ description: "Learn how to write and run unit tests for NEAR smart contracts to import { Github } from '/snippets/github.jsx' -Unit tests allow you to test the contract methods individually. They are suitable to check the storage is updated correctly, and that methods return their expected values. They are written in the contract's language and execute locally. +Unit tests allow you to test the contract methods individually. They are suitable to check the storage is updated correctly, and that methods return their expected values. They are written in Rust alongside the contract's code and execute locally. -If you used one of our [examples](https://github.com/near-examples/docs-examples) as template, then you simply need to navigate to the contract's folder, and use `yarn test`. In case you didn't, then we recommend you copy the necessary node files (e.g. `package.json`) from one of our templates. - - -You can run `yarn test` from the root folder of each project to run both unit and [integration](/smart-contracts/testing/integration-test) tests. - - -For **Go contracts**, unit tests must be run with the `near-go` CLI, which uses **TinyGo** under the hood. The SDK's `system` package uses TinyGo-specific `//go:wasmimport` declarations that cannot be compiled by the standard Go toolchain. +To run the unit tests, simply navigate to the contract's folder and run `cargo test`: ```bash -# Run tests for the entire project -near-go test project - -# Run tests only for the current package -near-go test package +cargo test ``` -Go unit tests use the `MockSystem` from `near-sdk-go` to simulate the NEAR environment locally without a blockchain. - - -**Prerequisites** - -You need the `near-go` CLI installed. If you followed the [Quickstart](../quickstart), everything is already set up β€” TinyGo and all dependencies are handled by the `install.sh` script automatically. - + +`cargo test` runs both unit and [integration](/smart-contracts/testing/integration-test) tests present in the project. + --- @@ -37,21 +23,12 @@ You need the `near-go` CLI installed. If you followed the [Quickstart](../quicks The tests in the [Counter Example](https://github.com/near-examples/counters) rely on basic functions to check that the `increment`, `decrement`, and `reset` methods work properly. - - - - - - - - - + + --- @@ -59,21 +36,12 @@ The tests in the [Counter Example](https://github.com/near-examples/counters) re While doing unit testing you can modify the [Environment variables](../anatomy/environment) through the `VMContextBuilder`. This will enable you to, for example, simulate calls from different users, with specific attached deposit and GAS. Here we present a snippet on how we test the `donate` method from our [Donation Example](https://github.com/near-examples/donation-examples) by manipulating the `predecessor` and `attached_deposit`. - - - - - - - - - + + --- diff --git a/smart-contracts/tutorials/basic-contracts.mdx b/smart-contracts/tutorials/basic-contracts.mdx index 659006f03f8..7a67bbb45f7 100644 --- a/smart-contracts/tutorials/basic-contracts.mdx +++ b/smart-contracts/tutorials/basic-contracts.mdx @@ -67,7 +67,7 @@ Want to try these examples immediately? Click the **"Open in NearPlay"** button ## Structure of the Examples -All examples follow a consistent structure, making it easy to navigate between them. Each repository contains the **same smart contract** implemented in **Rust**, **Javascript**, and sometimes **Python**, along with a **simple frontend** to interact with the contract. +All examples follow a consistent structure, making it easy to navigate between them. Each repository contains the **smart contract** written in **Rust** (`contract-rs`), along with a **simple frontend** to interact with the contract. ```bash β”Œβ”€β”€ contract-rs # contract's code in Rust @@ -75,16 +75,6 @@ All examples follow a consistent structure, making it easy to navigate between t β”‚ β”œβ”€β”€ tests # sandbox test β”‚ β”œβ”€β”€ Cargo.toml β”‚ └── rust-toolchain.toml -β”œβ”€β”€ contract-ts # contract's code in Typescript -β”‚ β”œβ”€β”€ src # contract's code -β”‚ β”œβ”€β”€ sandbox-test # sandbox test -β”‚ β”œβ”€β”€ package.json -β”‚ └── tsconfig.json -β”œβ”€β”€ contract-py # contract's code in Python (some examples) -β”‚ β”œβ”€β”€ contract.py # contract's code -β”‚ β”œβ”€β”€ tests # sandbox test -β”‚ β”œβ”€β”€ pyproject.toml -β”‚ └── uv.lock β”œβ”€β”€ frontend # React + Next.JS frontend β”‚ β”œβ”€β”€ src # frontend's implementation β”‚ β”œβ”€β”€ public @@ -94,6 +84,10 @@ All examples follow a consistent structure, making it easy to navigate between t └── README.md ``` + +Some repositories also contain the same contract implemented in other languages using community-maintained SDKs β€” this guide only walks through the Rust implementation. + + --- ## Frontend @@ -151,41 +145,20 @@ export default function App() { ## Smart Contract -All repositories include the same smart contract implemented in different languages, including **Rust**, **Javascript**, and sometimes **Python**. +All repositories include the same smart contract implemented in **Rust**. -The contracts are implemented following the latest versions of each SDK, and include sandbox tests showcasing how to properly test smart contracts in a realistic environment. +The contracts are implemented following the latest version of the SDK, and include sandbox tests showcasing how to properly test smart contracts in a realistic environment. ### Testing Each contract includes sandbox tests that simulate real user interactions. For example, in the `Guest Book` example, the tests cover scenarios like having multiple accounts signing the guest book, including premium messages. - - - + ```bash cd contract-rs cargo test ``` - - - -```bash -cd contract-ts -yarn -yarn test -``` - - - - -```bash -cd contract-py -uv run pytest -``` - - - ### Creating an Account @@ -205,34 +178,12 @@ Here we are using the `--useFaucet` flag to create a new account and pre-fund it Once you created an account to host the contract, you can build and deploy it: - - - + ```bash cd contract-rs cargo near deploy build-non-reproducible-wasm ``` - - - -```bash -cd contract-ts -npm run build -near deploy ./build/.wasm -``` - - - - -```bash -cd contract-py -uvx nearc contract.py -near deploy .wasm -``` - - - ### Interacting via CLI diff --git a/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx b/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx index 9dfe19ff1c9..74c9eace8af 100644 --- a/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx +++ b/smart-contracts/tutorials/cross-contracts/advanced-xcc.mdx @@ -31,43 +31,16 @@ You have two options to start the Donation Example: ## Structure of the Example -The smart contract is available in two flavors: Rust and JavaScript +The smart contract is written in Rust and has the following structure: - - - - -```bash -β”Œβ”€β”€ sandbox-ts # sandbox testing -β”‚ β”œβ”€β”€ external-contracts -β”‚ β”‚ β”œβ”€β”€ counter.wasm -β”‚ β”‚ β”œβ”€β”€ guest-book.wasm -β”‚ β”‚ └── hello-near.wasm -β”‚ └── main.ava.ts -β”œβ”€β”€ src # contract's code -β”‚ β”œβ”€β”€ internal -β”‚ β”‚ β”œβ”€β”€ batch_actions.ts -β”‚ β”‚ β”œβ”€β”€ constants.ts -β”‚ β”‚ β”œβ”€β”€ multiple_contracts.ts -β”‚ β”‚ β”œβ”€β”€ similar_contracts.ts -β”‚ β”‚ └── utils.ts -β”‚ └── contract.ts -β”œβ”€β”€ package.json -β”œβ”€β”€ README.md -└── tsconfig.json -``` - - - - ```bash β”Œβ”€β”€ tests # sandbox testing -β”‚ β”œβ”€β”€ external-contracts +β”‚ β”œβ”€β”€ contracts # external contracts used in tests β”‚ β”‚ β”œβ”€β”€ counter.wasm β”‚ β”‚ β”œβ”€β”€ guest-book.wasm -β”‚ β”‚ └── hello-near.wasm -β”‚ └── main.ava.ts +β”‚ β”‚ └── hello-near +β”‚ └── tests.rs β”œβ”€β”€ src # contract's code β”‚ β”œβ”€β”€ batch_actions.rs β”‚ β”œβ”€β”€ lib.rs @@ -78,9 +51,6 @@ The smart contract is available in two flavors: Rust and JavaScript └── rust-toolchain.toml ``` - - - --- @@ -92,37 +62,18 @@ You can aggregate multiple actions directed towards one same contract into a bat Methods called this way are executed sequentially, with the added benefit that, if one fails then they **all get reverted**. - - - - - + #### Getting the Last Response In this case, the callback has access to the value returned by the **last action** from the chain. - - - - - - + --- @@ -131,37 +82,18 @@ action** from the chain. A contract can call multiple other contracts. This creates multiple transactions that execute all in parallel. If one of them fails the rest **ARE NOT REVERTED**. - - - - - + #### Getting All Responses In this case, the callback has access to an **array of responses**, which have either the value returned by each call, or an error message. - - - - - - + --- @@ -172,37 +104,18 @@ It simply showcases a different way to check the results by directly accessing t In this case, we call multiple contracts that will return the same type: - - - - - + #### Getting All Responses In this case, the callback again has access to an **array of responses**, which we can iterate checking the results. - - - - - - + --- @@ -210,26 +123,12 @@ results. The contract readily includes a set of unit and sandbox testing to validate its functionality. To execute the tests, run the following commands: - - - - ```bash - cd contract-advanced-ts - yarn - yarn test - ``` - - - - - ```bash - cd contract-advanced-rs - cargo test - ``` - +```bash +cd contract-advanced-rs +cargo test +``` - The `integration tests` use a sandbox to create NEAR users and simulate interactions with the contract. @@ -257,27 +156,13 @@ In order to deploy the contract you will need to create a NEAR account.
-Go into the directory containing the smart contract (`cd contract-advanced-ts` or `cd contract-advanced-rs`), build and deploy it: - - - - - - ```bash - npm run build - near deploy ./build/cross_contract.wasm --initFunction new --initArgs '{"hello_account":"hello.near-example.testnet","guestbook_account":"guestbook_account.near-example.testnet","counter_account":"counter_account.near-example.testnet"}' - ``` +Go into the directory containing the smart contract (`cd contract-advanced-rs`), build and deploy it: - - - - ```bash - cargo near deploy build-non-reproducible-wasm with-init-call new json-args '{"hello_account":"hello.near-example.testnet","guestbook_account":"guestbook_account.near-example.testnet","counter_account":"counter_account.near-example.testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send - ``` - +```bash +cargo near deploy build-non-reproducible-wasm with-init-call new json-args '{"hello_account":"hello.near-example.testnet","guestbook_account":"guestbook_account.near-example.testnet","counter_account":"counter_account.near-example.testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send +``` - ### CLI: Interacting with the Contract diff --git a/smart-contracts/tutorials/cross-contracts/xcc.mdx b/smart-contracts/tutorials/cross-contracts/xcc.mdx index 8b0dbbdc1e0..6aa1d983af7 100644 --- a/smart-contracts/tutorials/cross-contracts/xcc.mdx +++ b/smart-contracts/tutorials/cross-contracts/xcc.mdx @@ -30,27 +30,8 @@ You have two options to start the project: ## Structure of the Example -The smart contract is available in two flavors: Rust and JavaScript +The smart contract is written in Rust and has the following structure: - - - - -```bash -β”Œβ”€β”€ sandbox-ts # sandbox testing -β”‚ β”œβ”€β”€ hello-near -β”‚ β”‚ └── hello-near.wasm -β”‚ └── main.ava.ts -β”œβ”€β”€ src # contract's code -β”‚ └── contract.ts -β”œβ”€β”€ package.json -β”œβ”€β”€ README.md -└── tsconfig.json -``` - - - - ```bash β”Œβ”€β”€ tests # sandbox testing @@ -65,9 +46,6 @@ The smart contract is available in two flavors: Rust and JavaScript └── rust-toolchain.toml ``` - - - --- @@ -80,11 +58,6 @@ The contract exposes methods to query the greeting and change it. These methods The contract performs a cross-contract call to `hello.near-example.testnet` to get the greeting message, and then handles the response in a **callback function**. - - - - - - - - - - + --- @@ -128,25 +92,11 @@ Notice that the callback function is marked as **private**, meaning it can only The contract readily includes a set of unit and sandbox testing to validate its functionality. To execute the tests, run the following commands: - - - ```bash - cd contract-simple-ts - yarn - yarn test - ``` - - - - - ```bash - cd contract-simple-rs - cargo test - ``` - - - +```bash +cd contract-simple-rs +cargo test +``` The `integration tests` use a sandbox to create NEAR users and simulate interactions with the contract. @@ -154,16 +104,11 @@ The `integration tests` use a sandbox to create NEAR users and simulate interact In this project in particular, the integration tests first deploy the `hello-near` contract. Then, they test that the cross-contract call correctly sets and retrieves the message. You will find the integration tests -in `sandbox-ts/` for the JavaScript version and in `tests/` for the Rust version. +in `tests/`. - - - - + ### Deploying the Contract to the NEAR network @@ -187,28 +132,14 @@ In order to deploy the contract you will need to create a NEAR account. -Go into the directory containing the smart contract (`cd contract-advanced-ts` or `cd contract-advanced-rs`), build and deploy it: +Go into the directory containing the smart contract (`cd contract-simple-rs`), build and deploy it: - - - - - ```bash - npm run build - near deploy ./build/cross_contract.wasm --initFunction new --initArgs '{"hello_account":"hello.near-example.testnet"}' - ``` - - - - - ```bash - cargo near deploy build-non-reproducible-wasm with-init-call new json-args '{"hello_account":"hello.near-example.testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send - ``` +```bash +cargo near deploy build-non-reproducible-wasm with-init-call new json-args '{"hello_account":"hello.near-example.testnet"}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config testnet sign-with-keychain send - +``` - ### CLI: Interacting with the Contract diff --git a/smart-contracts/tutorials/factories/global-contracts.mdx b/smart-contracts/tutorials/factories/global-contracts.mdx index ccbc6d49aab..11179fcb196 100644 --- a/smart-contracts/tutorials/factories/global-contracts.mdx +++ b/smart-contracts/tutorials/factories/global-contracts.mdx @@ -89,37 +89,6 @@ Since there are two ways to reference a global contract ([by account](/smart-con - - - - Once you've created an Account instance, you can deploy your regular contract as a global contract. - - - - - Let’s look at an example of deploying a global contract by account. - - To do this, use the `deployGlobalContract` function and set the mode to `accountId`, along with the contract’s code bytes. - - - - - - - - Let’s look at an example of deploying a global contract by hash. - - To do this, use the `deployGlobalContract` function and set the mode to `codeHash`, along with the contract’s code bytes. - - - - - - @@ -195,35 +164,6 @@ Let’s see how you can reference and use your global contract from another acco - - - - - - - To reference a global contract by account, you need to call the `useGlobalContract` function and pass the source `accountId` where the contract was originally deployed. - - - - - - - - To reference a global contract by hash, you need to call the `useGlobalContract` function and pass the source `codeHash` of the original contract. - - ```js - const hash = bs58.encode(sha256(wasm)); - ``` - - - - - - diff --git a/smart-contracts/tutorials/zero-to-hero/nfts-js.mdx b/smart-contracts/tutorials/zero-to-hero/nfts-js.mdx deleted file mode 100644 index b6fa8e83f6e..00000000000 --- a/smart-contracts/tutorials/zero-to-hero/nfts-js.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: NFT Zero to Hero JavaScript Edition -sidebarTitle: Build a NFT Contract from Scratch (JS) -description: "Learn NFTs from minting to building a full-featured smart contract in this Zero to Hero series." ---- - -> In this _Zero to Hero_ series, you'll find a set of tutorials that will cover every aspect of a non-fungible token (NFT) smart contract. -> You'll start by minting an NFT using a pre-deployed contract and by the end you'll end up building a fully-fledged NFT smart contract that supports every extension. - -## Prerequisites - -To complete these tutorials successfully, you'll need: - -- [Node.js](/smart-contracts/quickstart?code-tabs=js) -- [A NEAR Wallet](https://testnet.mynearwallet.com/create) -- [NEAR-CLI](/tools/cli#installation) - ---- - -## Overview - -These are the steps that will bring you from **_Zero_** to **_Hero_** in no time! πŸ’ͺ - -| Step | Name | Description | -|------|------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| -| 1 | [Pre-deployed contract](https://near-examples.github.io/nft-tutorial-js/predeployed-contract) | Mint an NFT without the need to code, create, or deploy a smart contract. | -| 2 | [Contract architecture](https://near-examples.github.io/nft-tutorial-js/skeleton) | Learn the basic architecture of the NFT smart contract and compile code. | -| 3 | [Minting](https://near-examples.github.io/nft-tutorial-js/minting) | Flesh out the skeleton so the smart contract can mint a non-fungible token. | -| 4 | [Upgrade a contract](https://near-examples.github.io/nft-tutorial-js/upgrade-contract) | Discover the process to upgrade an existing smart contract. | -| 5 | [Enumeration](https://near-examples.github.io/nft-tutorial-js/enumeration) | Explore enumeration methods that can be used to return the smart contract's states. | -| 6 | [Core](https://near-examples.github.io/nft-tutorial-js/core) | Extend the NFT contract using the core standard which allows token transfer | -| 7 | [Approvals](https://near-examples.github.io/nft-tutorial-js/approvals) | Expand the contract allowing other accounts to transfer NFTs on your behalf. | -| 8 | [Royalty](https://near-examples.github.io/nft-tutorial-js/royalty) | Add NFT royalties allowing for a set percentage to be paid out to the token creator. | -| 9 | [Events](https://near-examples.github.io/nft-tutorial-js/events) | in this tutorial you'll explore the events extension, allowing the contract to react on certain events. | -| 10 | [Marketplace](https://near-examples.github.io/nft-tutorial-js/marketplace) | Learn about how common marketplaces operate on NEAR and dive into some of the code that allows buying and selling NFTs | - ---- - -## Next steps - -Ready to start? Jump to the [Pre-deployed Contract](https://near-examples.github.io/nft-tutorial-js/predeployed-contract) tutorial and begin your learning journey! - -If you already know about non-fungible tokens and smart contracts, feel free to skip and jump directly to the tutorial of your interest. The tutorials have been designed so you can start at any given point! - - -**Questions?** - -πŸ‘‰ Join us on [Discord](https://near.chat/) and let us know in the `#development` channels. πŸ‘ˆ - - diff --git a/smart-contracts/what-is.mdx b/smart-contracts/what-is.mdx index 7c6dee43703..0ab8c41e954 100644 --- a/smart-contracts/what-is.mdx +++ b/smart-contracts/what-is.mdx @@ -9,7 +9,7 @@ Smart contracts are pieces of **executable code** that live in a NEAR account. T ![img](/assets/docs/welcome-pages/4.smart-contracts.webp) -Developers can choose between using Javascript or Rust to write smart contracts in NEAR. Indistinctly from the language chosen, the contract will be compiled into WebAssembly, from which point it can be deployed and executed on the NEAR platform. +Smart contracts in NEAR are written in Rust. The contract is compiled into WebAssembly, from which point it can be deployed and executed on the NEAR platform. @@ -67,10 +67,10 @@ For instance, you can easily create a crowdfunding contract that accepts `$NEAR` Just like any piece of software, smart contracts have a development flow - starting with its creation and ending with monitoring it, all of which we cover in our documentation. ![img](/assets/docs/welcome-pages/contract-lifecycle.png) - "build/building-smart-contracts/testing/introduction", + The development flow can be summarized as follows: - [**Scaffold**](./quickstart): The simplest way to create a project is by starting from a template. -- [**Build**](./anatomy/anatomy): Write a contract using Rust or Javascript. +- [**Build**](./anatomy/anatomy): Write a contract using Rust. - [**Test**](./testing/introduction): Our Sandbox enables to simulate interactions with one or multiple contracts in a realistic environment. - [**Deploy**](./release/deploy): After making sure the contract is secure, developers can deploy the contract into their accounts. - [**Use**](https://mynearwallet.com): Any user can interact with the contract through their NEAR Wallet. @@ -78,17 +78,17 @@ The development flow can be summarized as follows: #### Supported Languages -During the whole cycle, developers can choose between [JavaScript](https://www.learn-js.org/) and [Rust](https://www.rust-lang.org/), and [Go](https://go.dev/), allowing them to use their favorite language at each step of their journey. +This documentation covers smart contract development in [Rust](https://www.rust-lang.org/) using [near-sdk-rs](https://github.com/near/near-sdk-rs). Rust offers the most mature tooling, best performance, and the strongest safety guarantees when dealing with real assets on-chain. - -We **strongly recommend using [Rust](https://www.rust-lang.org/)** for production contracts. Rust offers the most mature tooling, best performance, and the strongest safety guarantees when dealing with real assets on-chain. JavaScript, Python, and Go are great for prototyping and learning, but Rust is the right choice when it matters. - + - +Since contracts compile to WebAssembly, contracts can be written in other languages using community-maintained SDKs: -Theoretically, you can use any language that compiles to Wasm for developing NEAR smart contract. However, in order to have a user-friendly experience we would need to provide a library that wraps around low-level runtime APIs, while also offering other high-level functionalities. +- **JavaScript / TypeScript**: [near-sdk-js](https://github.com/near/near-sdk-js) +- **Python**: [near-sdk-py](https://github.com/r-near/near-sdk-py) +- **Go**: [near-sdk-go](https://github.com/vlmoon99/near-sdk-go) -We envision that in the future, more languages will be supported and the support will be done through the effort from the wider community, not just NEAR alone. +These SDKs are great for prototyping and learning, but they are maintained by the community and are not covered in this documentation. We **strongly recommend using Rust** for production contracts. diff --git a/snippets/explain-code.jsx b/snippets/explain-code.jsx index 2adfd247dfa..47ba6d937f9 100644 --- a/snippets/explain-code.jsx +++ b/snippets/explain-code.jsx @@ -71,23 +71,28 @@ export const ExplainCode = ({ children, languages }) => { return set; } - const childArray = Array.isArray(children) ? children.flat(Infinity) : (children ? [children] : []); - const blocks = []; const files = []; - for (const child of childArray) { - if (!child || typeof child !== 'object' || !child.props) continue; - if (child.props.highlights !== undefined) { + // Children may arrive wrapped in fragments or intermediate nodes depending on + // how MDX compiles the page, so walk the tree instead of assuming a flat list + function collect(node) { + if (!node) return; + if (Array.isArray(node)) { node.forEach(collect); return; } + if (typeof node !== 'object' || !node.props) return; + if (node.props.highlights !== undefined) { let hl = {}; - try { hl = JSON.parse(child.props.highlights); } catch { } + try { hl = JSON.parse(node.props.highlights); } catch { } if (language in hl) { - blocks.push({ text: child.props.children, highlight: hl[language], fname: child.props.fname, type: child.props.type }); + blocks.push({ text: node.props.children, highlight: hl[language], fname: node.props.fname, type: node.props.type }); } - } else if (child.props.language === language) { - files.push({ ...child.props }); + } else if (node.props.language !== undefined) { + if (node.props.language === language) files.push({ ...node.props }); + } else { + collect(node.props.children); } } + collect(children); const activeFname = blocks[activeBlock]?.fname || files[0]?.fname; const fileKey = `${activeFname}-${language}`; @@ -161,7 +166,12 @@ export const ExplainCode = ({ children, languages }) => { return (
- {/* Language tabs */} + {/* Language tabs (plain label when there is only one language) */} + {langList.length === 1 ? ( +
+ {lang2label[langList[0]] || langList[0]} +
+ ) : (
{langList.map((lang) => ( ))}
+ )} {/* Two-column layout */}
diff --git a/tools/sdk.mdx b/tools/sdk.mdx index 5b5bc6a552b..111b1009af5 100644 --- a/tools/sdk.mdx +++ b/tools/sdk.mdx @@ -1,78 +1,54 @@ --- title: SDK Libraries icon: package -description: "Choose an SDK to start building contracts." +description: "The Rust SDK for building smart contracts." --- -The NEAR SDK is a library that allows you to develop smart contracts. Currently, there are two versions of the NEAR SDK: one for Rust and one for JavaScript. - - -We **strongly recommend using the [Rust SDK](https://docs.rs/near-sdk/latest/near_sdk/)** for production contracts. Rust provides the most mature tooling, best performance, and the strongest safety guarantees when handling real assets on-chain. The JavaScript SDK is a great option for prototyping and learning. - +The NEAR SDK for Rust ([near-sdk-rs](https://github.com/near/near-sdk-rs)) is the official library for developing smart contracts. Rust provides the most mature tooling, best performance, and the strongest safety guarantees when handling real assets on-chain. The best place to start learning is our [QuickStart Guide](/smart-contracts/quickstart). - - - Rust SDK reference documentation. - - - JavaScript SDK reference documentation. - - + + Rust SDK reference documentation. + ## Smart contracts on NEAR -This is how a smart contract written in Rust and JavaScript using the NEAR SDK looks like: - - - - ```js - @NearBindgen({}) - class HelloNear { - greeting: string = 'Hello'; - - @view({}) // This method is read-only and can be called for free - get_greeting(): string { - return this.greeting; - } - - @call({}) // This method changes the state, for which it costs gas - set_greeting({ greeting }: { greeting: string }): void { - near.log(`Saving greeting ${greeting}`); - this.greeting = greeting; - } +This is how a smart contract written in Rust using the NEAR SDK looks like: + +```rust +#[near(contract_state)] +pub struct Contract { + greeting: String, +} + +impl Default for Contract { + fn default() -> Self { + Self { greeting: "Hello".to_string(), } } - ``` - - - ```rust - #[near(contract_state)] - pub struct Contract { - greeting: String, +} + +#[near] +impl Contract { + pub fn get_greeting(&self) -> String { + self.greeting.clone() } - impl Default for Contract { - fn default() -> Self { - Self { greeting: "Hello".to_string(), } - } + pub fn set_greeting(&mut self, greeting: String) { + self.greeting = greeting; } +} +``` - #[near] - impl Contract { - pub fn get_greeting(&self) -> String { - self.greeting.clone() - } +## Community SDKs - pub fn set_greeting(&mut self, greeting: String) { - self.greeting = greeting; - } - } - ``` - - +Since NEAR contracts compile to WebAssembly, the community maintains SDKs for other languages. They are great for prototyping and learning, but are not covered in this documentation: + +- [JavaScript / TypeScript SDK (near-sdk-js)](https://github.com/near/near-sdk-js) +- [Python SDK (near-sdk-py)](https://github.com/r-near/near-sdk-py) +- [Go SDK (near-sdk-go)](https://github.com/vlmoon99/near-sdk-go) ## Ready to start developing? @@ -84,7 +60,4 @@ We have a section dedicated to [tutorials and examples](/smart-contracts/tutoria ## Reference docs -If you need to find a specific function signature, or understand the SDK structs and classes, visit the SDK-specific pages: - -- [Rust SDK](https://docs.rs/near-sdk/latest/near_sdk/) -- [JavaScript SDK](https://near.github.io/near-sdk-js/) +If you need to find a specific function signature, or understand the SDK structs and classes, visit the [Rust SDK reference](https://docs.rs/near-sdk/latest/near_sdk/). diff --git a/web3-apps/tutorials/mastering-near/0-intro.mdx b/web3-apps/tutorials/mastering-near/0-intro.mdx index b7e173f2fda..e9608a01c60 100644 --- a/web3-apps/tutorials/mastering-near/0-intro.mdx +++ b/web3-apps/tutorials/mastering-near/0-intro.mdx @@ -33,41 +33,23 @@ Before starting, make sure to set up 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 Rust: https://www.rust-lang.org/tools/install +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | 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 - ``` +# Contracts will be compiled to wasm, so we need to add the wasm target +rustup target add wasm32-unknown-unknown - +# Install the NEAR CLI to deploy and interact with the contract +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh - +# Install cargo near to help building the contract +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh +``` - ```bash - # Install Rust: https://www.rust-lang.org/tools/install - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - # Contracts will be compiled to wasm, so we need to add the wasm target - rustup target add wasm32-unknown-unknown - - # Install the NEAR CLI to deploy and interact with the contract - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh | sh - - # Install cargo near to help building the contract - curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh - ``` - - - - - -We will be using [NEAR CLI](/tools/cli) to interact with the blockchain through the terminal, and you can choose between JavaScript and Rust to write the contract. +We will be using [NEAR CLI](/tools/cli) to interact with the blockchain through the terminal, and Rust to write the contract. --- diff --git a/web3-apps/tutorials/mastering-near/1.1-basic.mdx b/web3-apps/tutorials/mastering-near/1.1-basic.mdx index add5341e043..c18ef229a8c 100644 --- a/web3-apps/tutorials/mastering-near/1.1-basic.mdx +++ b/web3-apps/tutorials/mastering-near/1.1-basic.mdx @@ -25,33 +25,17 @@ Make sure to read the [Prerequisites](./0-intro) section and install the necessa ## Cloning the contract -To get started we'll clone the [tutorial's repository](https://github.com/near-examples/auctions-tutorial) from GitHub. The repository contains the same smart contracts written in JavaScript (`./contract-ts`) and Rust (`./contract-rs`). +To get started we'll clone the [tutorial's repository](https://github.com/near-examples/auctions-tutorial) from GitHub. The repository contains the smart contracts written in Rust (`./contract-rs`). -Navigate to the folder of the language you prefer, and then to the `01-basic-auction` folder. +Navigate to the `contract-rs` folder, and then to the `01-basic-auction` folder. - - +```bash +git clone git@github.com:near-examples/auctions-tutorial.git - ```bash - git clone git@github.com:near-examples/auctions-tutorial.git +cd contract-rs/01-basic-auction +``` - cd contract-ts/01-basic-auction - ``` - - - - - - ```bash - git clone git@github.com:near-examples/auctions-tutorial.git - - cd contract-rs/01-basic-auction - ``` - - - - **Frontend** @@ -66,53 +50,26 @@ The repository also contains a frontend application that interacts with the cont The contract allows users to place bids using `$NEAR` tokens and keeps track of the highest bidder. Lets start by looking at how we define the contract's state, this is, the data that the contract will store. - - - - - - - #### Decorator - The first thing to notice is that the main class of the contract is marked using the `@NearBindgen` decorator, which allows also to further specify that the contract **must be initialized** before being used. - #### Storage (aka State) - Another important information revealed by the code is that a contract can store different types of data, in this case: + - - `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** - - `auctioneer` an `AccountId` that states who can withdraw the funds at the end of the auction - - `claimed` a `boolean` that tracks if the auctioneer has claimed the funds +#### Macros +A first thing to notice is the use of the `#[near(contract_state)]` macro to denote the main structure and derive the `PanicOnDefault` to specify that the contract **must be initialized** before being used. - +We also use the `#[near(serializers = [json, borsh])]` macro to enable both `borsh` and `JSON` (de)serialization of the `Bid` structure. As a rule of thumb: use the `json` serializer for structs that will be used as input / output of functions, and `borsh` for those that will be saved to state. - +#### Storage (aka State) +Another important information revealed by the code is that the contract can store different types of data. - +- `highest_bid` is an instance of a `Bid` which stores: + - `bid`: a `NearToken` which simplifies handling `$NEAR` token amounts + - `bidder`: the `AccountId` that placed the bid +- `auction_end_time` is a `U64` representing a `unix timestamp` in **nanoseconds** +- `auctioneer` an `AccountId` that states who can withdraw the funds at the end of the auction +- `claimed` a `boolean` that tracks if the auctioneer has claimed the funds - #### Macros - A first thing to notice is the use of the `#[near(contract_state)]` macro to denote the main structure and derive the `PanicOnDefault` to specify that the contract **must be initialized** before being used. - - We also use the `#[near(serializers = [json, borsh])]` macro to enable both `borsh` and `JSON` (de)serialization of the `Bid` structure. As a rule of thumb: use the `json` serializer for structs that will be used as input / output of functions, and `borsh` for those that will be saved to state. - - #### Storage (aka State) - Another important information revealed by the code is that the contract can store different types of data. - - - `highest_bid` is an instance of a `Bid` which stores: - - `bid`: a `NearToken` which simplifies handling `$NEAR` token amounts - - `bidder`: the `AccountId` that placed the bid - - `auction_end_time` is a `U64` representing a `unix timestamp` in **nanoseconds** - - `auctioneer` an `AccountId` that states who can withdraw the funds at the end of the auction - - `claimed` a `boolean` that tracks if the auctioneer has claimed the funds - - - - **Learn More** @@ -130,33 +87,16 @@ You can read more about the contract's structure and the type of data it can sto Lets now take a look at the initialization function, which we need to call to determine the time at which the auction will end. - - - - - - - #### Decorator - We denote the initialization function using the `@initialize({ privateFunction: true })` decorator. The `privateFunction:true` denotes that the function can only be called by the account on which the contract is deployed. - - - + - +#### Macros +We denote the initialization function using the `#[init]` macro. Notice that the initialization function needs to return an instance of `Self`, i.e. the contract's structure. - #### Macros - We denote the initialization function using the `#[init]` macro. Notice that the initialization function needs to return an instance of `Self`, i.e. the contract's structure. +Meanwhile, the `#[private]` denotes that the function can only be called by the account on which the contract is deployed. - 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`. @@ -183,29 +123,13 @@ You can read more about the contract's interface in our [contract functions docu The contract implements four functions to give access to its stored data, i.e. the highest bid so far (the amount and by whom), the time at which the auction ends, the auctioneer, and whether the auction has been claimed. - - - - - - - Functions that do not change the contract's state (i.e. that only read from it) are called `view` functions, and are decorated using the `@view` decorator. - + - +Functions that do not change the contract's state (i.e. that only read from it) are called `view` functions and take a non-mutable reference to `self` (`&self`). - - - Functions that do not change the contract's state (i.e. that only read from it) are called `view` functions and take a non-mutable reference to `self` (`&self`). - - - - View functions are **free to call**, and do **not require** a NEAR account to sign a transaction in order to call them. @@ -224,28 +148,14 @@ An auction is not an auction if you can't place a bid! For this, the contract in The function is quite simple: it verifies if the auction is still active and compares the attached deposit with the current highest bid. If the bid is higher, it updates the `highest_bid` and refunds the previous bidder. - - - - + - - - - - - - - - #### Payable Functions -The first thing to notice is that the function changes the state, and thus is marked with a `@call` decorator in JS, while taking as input a mutable reference to self (`&mut self`) on Rust. To call this function, a NEAR account needs to sign a transaction and expend GAS. +The first thing to notice is that the function changes the state, and thus takes as input a mutable reference to self (`&mut self`). To call this function, a NEAR account needs to sign a transaction and expend GAS. Second, the function is marked as `payable`, this is because by default **functions do not accept `$NEAR` tokens**! If a user attaches tokens while calling a function that is not marked as `payable`, the transaction will fail. @@ -279,25 +189,11 @@ You can read more about the environment variables, payable functions and which a You'll notice that the contract has a final function called `claim`, this allows the auctioneer to claim the funds from the contract at the end of the auction. Since, on NEAR, a smart contract account and user account are the same, contracts can still have keys when they are deployed, thus a user could just claim the funds from the contract via a wallet. However, this presents a security issue since by having a key the key holder can take the funds from the contract at any point, maliciously change the contract's state or just delete the contract as a whole. By implementing a `claim` function we can later lock the contract by removing all access keys and have the auctioneer claim the funds via preset conditions via code. - - - - - - - - - - - - + - This function is quite simple it does four things: 1) Checks that the auction has ended (the current timestamp is past the auction end time). diff --git a/web3-apps/tutorials/mastering-near/1.2-testing.mdx b/web3-apps/tutorials/mastering-near/1.2-testing.mdx index e252eb2b46f..e74c0def2b9 100644 --- a/web3-apps/tutorials/mastering-near/1.2-testing.mdx +++ b/web3-apps/tutorials/mastering-near/1.2-testing.mdx @@ -22,23 +22,11 @@ Unit tests are built into the language and are used to test the contract functio The first thing our test does is to create multiple accounts with 10 `$NEAR` tokens each and deploy the contract to 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. - - +Notice that the sandbox compiles the code itself, so we do not need to pre-compile the contract before running the tests. --- @@ -46,24 +34,6 @@ The first thing our test does is to create multiple accounts with 10 `$NEAR` tok To initialize, the contract's account calls itself, invoking the `init` function with an `end_time` set to 60 seconds in the future. - - - - - - - -**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` - - - - - - - - **Time is a String** @@ -94,25 +62,11 @@ Now that the contract is deployed and initialized, we can start bidding and chec We first make `alice` place a bid of 1 NEAR, and check that the contract correctly registers the bid. Then, we have `bob` place a bid of 2 NEAR, and check that the highest bid is updated, and that `alice` gets her NEAR refunded. - - - - - - - - - - - - + - #### 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. @@ -123,25 +77,11 @@ You might be tempted to check if `alice`'s balance is exactly 10 NEAR after she 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. - - - - - - - - + - - - - - --- @@ -150,22 +90,11 @@ The sandbox allows us to fast-forward time, which is useful for testing the cont After which the auction can now be claimed. Once claimed the test checks that the auctioneer has received the correct amount of `$NEAR` tokens. - - - - - - + - - - - If you review the tests in full you'll see that we also test other invalid calls such as the auctioneer trying to claim the auction before it is over and a user attempting to bid once the auction is over. @@ -175,29 +104,11 @@ If you review the tests in full you'll see that we also test other invalid calls Now that we understand what we are testing, let's go ahead and run the tests! - - - - - ```bash - # if you haven't already, install the dependencies - npm install - - # run the tests - npm run test - ``` - - - - - - ```bash - cargo test - ``` - +```bash +cargo test +``` - All tests should pass, and you should see the output of the tests in the console. If you see any errors, please contact us in the [NEAR Discord](https://near.chat) or through [Telegram](https://t.me/neardev) and we'll help you out! diff --git a/web3-apps/tutorials/mastering-near/1.3-deploy.mdx b/web3-apps/tutorials/mastering-near/1.3-deploy.mdx index 174242b5246..bf3c888eaa1 100644 --- a/web3-apps/tutorials/mastering-near/1.3-deploy.mdx +++ b/web3-apps/tutorials/mastering-near/1.3-deploy.mdx @@ -46,41 +46,19 @@ The faucet is only available on the testnet network - which is the default netwo To deploy the contract, you need to compile the contract code into WebAssembly (WASM) and then deploy it to the network - - - - ```bash - # compile the contract - npm run build - - # deploy the contract - near deploy ./build/auction-contract.wasm - - # initialize the contract, it finishes in 2 minutes - TWO_MINUTES_FROM_NOW=$(date -v+2M +%s000000000) - near call init '{"end_time": "'$TWO_MINUTES_FROM_NOW'", "auctioneer": ""}' --useAccount - ``` - - - - - - ```bash - # compile the contract using cargo-near - cargo near build - - # deploy the contract - near deploy ./target/near/auction-contract.wasm +```bash +# compile the contract using cargo-near +cargo near build - # initialize the contract, it finishes in 2 minutes - TWO_MINUTES_FROM_NOW=$(date -v+2M +%s000000000) - near call init '{"end_time": "'$TWO_MINUTES_FROM_NOW'", "auctioneer": ""}' --useAccount - ``` +# deploy the contract +near deploy ./target/near/auction-contract.wasm - +# initialize the contract, it finishes in 2 minutes +TWO_MINUTES_FROM_NOW=$(date -v+2M +%s000000000) +near call init '{"end_time": "'$TWO_MINUTES_FROM_NOW'", "auctioneer": ""}' --useAccount +``` - Replace `` with the name of another account, this should not be the same as the contract account as we intend on removing its keys. diff --git a/web3-apps/tutorials/mastering-near/3.1-nft.mdx b/web3-apps/tutorials/mastering-near/3.1-nft.mdx index 9a441431e9f..530fc19f8de 100644 --- a/web3-apps/tutorials/mastering-near/3.1-nft.mdx +++ b/web3-apps/tutorials/mastering-near/3.1-nft.mdx @@ -13,27 +13,13 @@ No one will enter an auction if there's nothing to win, so let's add a prize. Wh When we create an auction we need to list the NFT. To specify which NFT is being auctioned off we need the account ID of the NFT contract and the token ID of the NFT. We will specify these when the contract is initialized; amend `init` to add `nft_contract` and `token_id` as such: - - + - +Note that `token_id` is of type `TokenId` which is a String type alias that the NFT standards use for future-proofing. - - - - - - - Note that `token_id` is of type `TokenId` which is a String type alias that the NFT standards use for future-proofing. - - - - --- @@ -41,34 +27,18 @@ When we create an auction we need to list the NFT. To specify which NFT is being 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`. - - - - - - - In near-sdk-js we cannot transfer the NFT and send the `$NEAR` independently so we will chain the promises. - +We will create a new file in our source folder named `ext.rs`; here we are going to define the interface for the `nft_transfer` method. We define this interface as a `trait` and use the `ext_contract` macro to convert the NFT trait into a module with the method `nft_transfer`. Defining external methods in a separate file helps improve the readability of our code. - +We then use this method in our `lib.rs` file to transfer the NFT. - We will create a new file in our source folder named `ext.rs`; here we are going to define the interface for the `nft_transfer` method. We define this interface as a `trait` and use the `ext_contract` macro to convert the NFT trait into a module with the method `nft_transfer`. Defining external methods in a separate file helps improve the readability of our code. - - We then use this method in our `lib.rs` file to transfer the NFT. + + - - - - - - When calling this method we specify the NFT contract name, that we are attaching 30 Tgas to the call, that we are attaching a deposit of 1 YoctoNEAR to the call, and give the arguments `receiver_id` and `token_id`. The NFT requires that we attach 1 YoctoNEAR for [security reasons](/smart-contracts/security/one_yocto). @@ -84,31 +54,17 @@ In our contract, we perform no checks to verify whether the contract actually ow Since we are now dealing with more information in our contract, instead of implementing a function to display each field we'll create a function to display the entire contract object. Since the contract doesn't include large complex data structures like a map displaying the contract state in its entirety is easily done. - - - - - - - - - - - - We add the `serilizers` macro to enable json serialization so the object as a whole can easily be displayed to the frontend without having to output each field individually. + - +We add the `serilizers` macro to enable json serialization so the object as a whole can easily be displayed to the frontend without having to output each field individually. - + - ## Testing with multiple contracts @@ -118,25 +74,11 @@ In our tests folder, we need the WASM for an NFT contract. For this tutorial, we To deploy the NFT contract, this time we're going to use `dev deploy` which creates an account with a random ID and deploys the contract to it by specifying the path to the WASM file. After deploying we will initialize the contract with default metadata and specify an account ID which will be the owner of the NFT contract (though the owner of the NFT contract is irrelevant in this example). Default metadata sets information such as the name and symbol of the NFT contract to default values. - - + - - - - - - - - - - - --- @@ -144,25 +86,11 @@ To deploy the NFT contract, this time we're going to use `dev deploy` which crea 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. - - - - - - - - + - - - - - --- @@ -170,25 +98,11 @@ To start a proper auction the auction contract should own an NFT. To do this the 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. - - - - - - - - - - - - + - --- diff --git a/web3-apps/tutorials/mastering-near/3.2-ft.mdx b/web3-apps/tutorials/mastering-near/3.2-ft.mdx index b79ae22f693..c7347c685be 100644 --- a/web3-apps/tutorials/mastering-near/3.2-ft.mdx +++ b/web3-apps/tutorials/mastering-near/3.2-ft.mdx @@ -13,25 +13,11 @@ To further develop this contract we will introduce another primitive: [fungible 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. - - + - - - - - - - - - - - --- @@ -43,107 +29,46 @@ When we were making bids in `$NEAR` tokens we would call the auction contract di The `ft_on_transfer` method always has the same interface; the FT contract will pass it the `sender`, the `amount` of FTs being sent and a `msg` which can be empty (which it will be here) or it can contain some information needed by the method (if you want to send multiple arguments in msg it is best practice to deliver this in JSON then parse it in the contract). The method returns the number of tokens to refund the user, in our case we will use all the tokens attached to the call for the bid unless the contract panics in which case the user will automatically be refunded their FTs in full. - - - - - - - - - - + - - - We need to confirm that the user is attaching fungible tokens when calling the method and that they are using the right FT, this is done by checking the predecessor's account ID. Since it's the FT contract that directly calls the auction contract, the `predecessor` is now the account ID of the FT contract. - - - - - - - - + - - - - - The bidder's account ID is now given by the argument `sender_id` and the bid amount is passed as an argument named `amount`. - - - - - - + - - - - - - - When we want to return the funds to the previous bidder we now make a cross-contract call to the FT contract. - - - - - - - - In JavaScript, we have to return the Promise to transfer the FTs but we also need to return how much to refund the user. So after transferring the FTs, we make a `callback` to our own contract to resume the contract flow. Note that the callback is private so it can only be called by the contract. We return 0 because the method uses all the FTs in the call. - - - - - - - - We then return 0 because the method uses all the FTs in the call. + + - + We then return 0 because the method uses all the FTs in the call. - + - - If the call was to fail the FT contract will automatically refund the user their FTs. +If the call was to fail the FT contract will automatically refund the user their FTs. @@ -159,27 +84,11 @@ When we want to return the funds to the previous bidder we now make a cross-cont When the auction is complete we need to send the fungible tokens to the auctioneer when we send the NFT to the highest bidder, we implement a similar call as when we were returning the funds just changing the arguments. - - + - - - In JavaScript, since we need to return each cross-contract call we chain the NFT and FT transfer. - - - - - - - - - - --- @@ -189,25 +98,11 @@ Just as with the NFT contract, we will deploy an FT contract in the sandbox test 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. - - - - - - + - - - - - - - --- @@ -217,25 +112,11 @@ For one to receive fungible tokens, first their account ID must be [registered]( 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. - - - - - - + - - - - - - - --- @@ -243,28 +124,14 @@ In our tests, since we are creating a new fungible token and new accounts we wil 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. - - - - + + - - - - - - - - - - --- @@ -272,28 +139,14 @@ Then we will transfer the bidders FTs so they can use them to bid. A simple tran 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. - - - - - - - - - - - + + - - - --- @@ -301,28 +154,14 @@ As stated previously, to bid on the auction the bidder now calls `ft_transfer_ca 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. - - - - - - - - - - - + + - - - --- @@ -332,25 +171,11 @@ If we make a lower bid than the previous this will cause the auction contract to 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. - - - - - - - - + - - - - - --- @@ -378,27 +203,12 @@ When creating an application there are numerous ways to structure it. Here, we h In such case, the Contract struct would be a map of auctions. We would implement a method to create a new auction by adding an entry to the map with the specific details of that individual auction. - - - - - ```javascript - class Contract { - auctions: UnorderedMap - ``` - - - - - ```rust - pub struct Contract { - auctions: IterableMap - ``` - - +```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. diff --git a/web3-apps/tutorials/mastering-near/4-factory.mdx b/web3-apps/tutorials/mastering-near/4-factory.mdx index d9867a14513..0028f0c89b5 100644 --- a/web3-apps/tutorials/mastering-near/4-factory.mdx +++ b/web3-apps/tutorials/mastering-near/4-factory.mdx @@ -10,8 +10,6 @@ Since an auction contract hosts a single auction, each time you would like to ho 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](/smart-contracts/tutorials/factories/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