Intermediate

 

How to develop a smart contract on Near Blockchain – Part 1

If you want to develop a smart contract on the NEAR blockchain, there are a few prerequisites you need to fulfill. In this tutorial, we will cover the following requirements: installing Node.js, setting up Rust and Wasm, using modules, understanding native types, SDK collections, internal structures, and initializing the state.

We will begin by showing you how to download and install Node.js. After that, we will guide you through the steps to set up Rust and Wasm. We will then introduce you to modules, which will help you organize your code and reuse third-party libraries. You will also learn about native types, SDK collections, and internal structures, which will enable you to define and instantiate complex objects through classes and structures.

Finally, we will cover how to initialize the state of your contract, both by creating an initialization method and by using a default state. By the end of this tutorial, you will have all the prerequisites you need to start developing a smart contract on the NEAR blockchain.

 

Prerequisites

To develop a smart contract you will need to install Node.js. If you further want to use Rust as your main language, then you need to install rustup as well.

Node.js

Download and install Node.js.

TIP: We further recommend to install yarn using npm install -g yarn.

 

Rust and Wasm

Follow these instructions for setting up Rust. Then, add the wasm32-unknown-unknown toolchain which enables compiling Rust to Web Assembly (wasm), the low-level language used by the NEAR platform.

# Installing Rust in Linux and MacOS
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env

# Add the wasm toolchain
rustup target add wasm32-unknown-unknown

 

Modules

Modules help you to organize your code and reuse third-party libraries.

The main module you will use in your contract will be the NEAR SDK, which: gives you access to the execution environment, allows you to call other contracts, transfer tokens, and much more.

  • AssemblyScript

donation-as/contract/assembly/index.ts

loading...

 

See full example on GitHub

USING EXTERNAL LIBRARIES

As a general rule of thumb for Rust, anything that supports wasm32-unknown-unknown will be compatible with your smart contract. However, we do have a size limit for a compiled contract binary which is ~4.19 MB, so it is possible that certain large libraries will not be compatible.

 

Native Types

When writing contracts you have access to all the language’s native types.

  • AssemblyScript

u8, u16, u32, u64, i8, i16, i32, i64, Array<T>, Map<K,V> ...

 

TIP: Always prefer native types in the contract’s interface. The only exception is values larger than 52 bytes (such as u64 and u128), for which string-like alternatives are preferred.

DANGER: Always make sure to check for underflow and overflow errors. For Rust, simply add overflow-checks=true in your Cargo.

 

SDK Collections

Besides the native types, the NEAR SDK implements collections such as Vector and UnorderedMap to help you store complex data in the contract’s state.

  • AssemblyScript

storage-as/contract/assembly/index.ts

loading...

 

See full example on GitHub

TIP: Always prefer SDK collections over native ones in the contract’s attributes (state).

 

Internal Structures

You can define and instantiate complex objects through classes and structures.

  • AssemblyScript

donation-as/contract/assembly/model.ts

loading...

 

The Contract Class

Your contract’s logic and state (storage) is defined by the main class, in which:

  • The attributes define the contract’s state

  • The initialization methods define how to initialize the contract’s state

  • The public methods act as the contract’s interface with the rest of the network

 

Defining the Contract

The contract is just another class, with its own attributes and methods. To differentiate it from other internal classes simply decorate it using the NEAR Bindgen decorator/macro.

  • 🌐 JavaScript

contract/src/contract.ts

loading...

 

See full example on GitHub

Under the hood, the NEAR Bindgen decorator/macro traverses the class, generating the necessary code to:

  • Transform the code into a valid NEAR contract.

  • Expose public methods, so they can be called externally.

  • Serialize objects for internal storage and communication with external actors.

 

The State

Each account has its own state (storage), which only they can modify but anyone can see.

The state is defined and modified through the main class’ attributes.

Contracts pay for their storage by locking part of their balance. Currently it costs ~1 Ⓝ to store 100kb

KEY-VALUE STORAGE

The contract actually uses a key-value storage to persist values. This however is abstracted from you by the SDK through serialization.

PREFER SDK COLLECTIONS

When defining attributes, always prefer SDK collections over native ones, since they are optimized for serialization.

 

Initializing the State

There are two ways to initialize the account’s state, and they can co-exist:

  • An initialization method that receives the attributes needed for the state

  • A default state, which will be used until init is invoked, or a method writes into the state

 

Initialization Method

To define an initialization method simply decorate it with the initialization macro.

The method will now be able to define the initial state’s values, raising an error if invoked while the state is already initialized.

  • AssemblyScript

// Public - init function, define the beneficiary of donations
export function init(beneficiary: string): void {
  assert(context.predecessor == context.contractName, "Method new is private");
  set_beneficiary(beneficiary);
}

 

AssemblyScript has no #[init] macro, and any method can be called multiple times. Limit this by adding a flag:

const initialized: bool = storage.getPrimitive<bool>('init', false)
assert(!initialized, "Already initialized")
storage.set<bool>('init', true)

 

 

Default State

Contracts can define a default state to use if no initialize method is called. This is, if any method is invoked before an init happens, the contract will use the default values.

Once any method writes into the state, the state will be considered initialized.

  • JavaScript

contract/src/contract.ts

loading...

 

See full example on GitHub

In JavaScript, the default state is defined by the initialization parameters in the class definition.

CAUTION: In Javascript you must always assign values to all the class’ parameters. This ensures they get correctly deserialized to their intended type.

 

Interface

All the public methods are exposed to the network as the contract’s interface.

  • AssemblyScript

export function get_message({ ...  }) { ... }
export function set_owner({ ... }) { ... }
export function add_message({ ... }) { ... }
private function internal_search( ... ) { ... }

 

Public Methods

Public methods can be categorized in three types: init methods, view methods, and call methods.

  • Init Methods: They define how to initialize the state of the contract.

  • View Methods: Do not mutate the state nor call other contracts. They can be called for free by everyone, without needing a NEAR account.

  • Call Methods: They can mutate the state and perform actions such as calling other contracts.

CAUTION: By default view methods have 200TGas to execute, to increase this you can simple invoke them as call methods

DANGER: By default init methods are public, make sure to decorate them as private, or batch call the initialization on deploy

Private Methods

Sometimes you will want some methods to remain public, but only be callable by the contract’s account. Such is the case for example of cross-contract callbacks.

For this, you can use the private macro/decorator.

  • JavaScript

@call({privateFunction: true})
callback( ... ){
  // this method can only be called by the contract's account
}

 

Payable Methods

By default all methods panic if a user attaches money while calling them. To enable a method to receive money use the payable decorator.

  • JavaScript

@call({payableFunction: true})
deposit_and_stake( ... ){
  // this method can receive money from the user
}

 

Input & Return Types

The contract can receive and return any native type, including complex structures. However, since contracts communicate through their interface using JSON:

  • Always prefer native types over SDK Collections in the input & return types.

  • Replace u64/u128 for strings (U64/U128 in the Rust SDK).

Now move to part 2 of this tutorial.