Integrating with NEAR Protocol

A write up of our NEAR integration journey

Lets start with what the 3 pillars of NEAR Protocol are:

  • Proof of Stake
  • Sharded
  • WASM Smart Contracts

As a Proof of Stake blockchain consensus is achieved by staking not by mining. Near has fast 1 second blocks with short finality.

The blockchain is sharded meaning calls between contracts are asynchronous (not to be confused with synchronous like on Ethereum or Solana). Coding Near contracts you need to take the mindset of working with actor model (or microservices, but I hate that term). The requirement of asynchronicity is needed so shards can be processed in parallel leading to much larger transaction throughput. Blockchains where smart contracts are not sharded will simply will hit a scalability ceiling, offloading to GPU or ASIC (while still synchronous) can only horizontally delay this ceiling, but it will not lead to exponential scaling.

Near contracts are WASM code, so you can code in any language that can compile down to WASM. Currently AssemblyScript (subset of TypeScript) and RUST are most supported with proper tool chains.

To build a WebApp on NEAR (which is exactly what Near accels at) is very easy compared to other blockchains.

Lets clone the counter example here https://github.com/near-examples/counter.

git clone https://github.com/near-examples/counter
yarn

assembly/ is where our smartcontract is (that will compile down to WASM).
src/ is where our frontend code goes that interacts with the contract.
package.json is where our extra javascript dependencies go.

The core of our contract includes on chain storage, control logging for the generated receipt, and exported public functions. Functions that set storage must be called otherwise they can be viewed (without a NEAR account even).

Near supports multiple types of collections (that self describe themselves) under the near-sdk-as.

The main ones you will need:

  • storage.getPrimitive — get/set
  • persistentMap — set/get
  • persistentUnorderedMap — set/get/query/length

I do not include persistentVector because you should avoid using it, its very easy to burn tons of gas iterating vectors, try to use sets whenever possible.

Default values for persistentMap collections need to be suffixed with !

let balance = myPersistentMap.get("myacc.near", u128.Zero)!

logging.log will print log output on your receipt when calling a function, this is useful for control flow to what went on, especially as contracts get more complex. Example of a human readable minting contract can have a log message of “alice.near has minted 5.0 ZOD from zod.near”.

Lets deploy our contract, for this we need near-cli.

npm install near-cli -g

And to deploy we can create a dummy testnet account. Accounts are contracts and you can only have 1 contract per account. To create multiple contracts you need to use sub accounts, contract1.me.near, contract2.me.near.

Lets deploy and call the getCounter function.

yarn build
ACC1=$(near dev-deploy out/main.wasm | sed 's/.*id: \(.*\), node.*/\1/' | head -n 1)
near view --accountId $ACC1 $ACC1 getCounter '{}'

Output is

View call: dev-1623290109414-64922580028342.getCounter({})
0

Lets increment the counter

near call --accountId $ACC1 $ACC1 incrementCounter '{"value": 1}'

Lets view this call on the block explorer https://explorer.testnet.near.org/accounts/dev-1623290109414-64922580028342

Awesome.

To view a function we can use curl but need to parse some commandline output, it is simpler to write a quick node script. We do not even need a near account.

node << EOF
const fetch = require('node-fetch');
async function go() {
const args_base64 = Buffer.from('{}').toString('base64')
const params = {account_id: "zodtv.near", method_name: "mint_status",
request_type: "call_function", finality: "final", "args_base64": args_base64}
const json_args = {jsonrpc: "2.0", id: "1", method: "query", params: params}

const fetch_args = {
method: "POST",
body: JSON.stringify(json_args),
headers: {
"Content-Type": "application/json"
}
}
const response = await fetch("https://rpc.mainnet.near.org", fetch_args);
const {result} = await response.json();

const mint_status = JSON.parse((new TextDecoder()).decode(new Uint8Array(result.result)))
console.log(mint_status)
}
go()
EOF

The following snippet uses RPC API https://docs.near.org/docs/api/rpc to query the state of of the zodtv.near mint contract.

{
"total_minted": "3120000000000000000000000000",
"level": "0",
"near_price": "3000000000000000000000000",
"zod_price": "10000000000000000000000"
}

Near denominates to the 24, the yocto.

3_000_000_000_000__000_000_000_000

is 3.0

That ends the first series here the next on would be working with a web frontend and correctly getting balance, state and more.