NEAR ABI: First Stable Release

Summary

Traditionally, ABI (Application Binary Interface) defines the interface between two binary program modules. NEAR ABI is a standard way of describing how a contract in the NEAR ecosystem can be interacted with.

This post will go in detail why ABI was needed, announce our first stable ABI release and quickly go through the tooling available to use with ABI.

Motivation

Interaction with a contract means both on-chain (cross-contract calls) and off-chain (RPC calls from frontend). NEAR, being a multilingual ecosystem, needs a standardized way to do the former - imagine you have a TypeScript contract and you want to make a cross-contract call to a Rust contract. Assuming you have the source code of the Rust contract (which is a hefty assumption), you can look through it and find the function you want to call along with its signature. You then have to re-implement in TypeScript any of the custom types that the function is using and hope that you have not made a human error. If you do not have access to the source code, the situation is even worse as you are operating blindly with chain history as your only data source.

Assuming the contract API hasn’t gone through significant change in the near past, you can deduce what signature the function of your interest has. That being said, you can never be sure that you are not missing an optional argument or an optional field in the function result type.

Off-chain interactions face the same issues, just to a lesser degree. The developer of an off-chain frontend usually has better context around the contract they are trying to interact with. Human errors still occur though and the developer flow can be simplified if strongly-typed frontend APIs could just be auto-generated without any effort from the developer.

Solution

NEAR ABI is a custom JSON-based schema format that was designed to achieve the following high-level goals:

  1. Human-readability. The schema should be interpretable by humans, but not necessarily easily written by hand.

  2. Language/platform agnostic. The format should not contain anything specific to Rust/JS/AS (e.g., type paths, references to standard library).

  3. Versioning. Future-proofed for breaking changes that are bound to happen.

Internally, ABI describes metadata about the contract (author, version etc), a list of all exposed functions (both view and call) and a JSON Schema dictionary of custom types. We will not go into details of how the format is structured, but a curious person can see an ABI example here.

Some of you reading this might already be familiar with NEAR ABI and asking yourself “Wait, didn’t this thing come out a while ago?”. Yes, and no. We have been treating ABI as an internal development so far and I am proud to announce that after many months of work we are ready to announce the first stable public ABI release.

What does this actually mean? Well, first of all, the current version of ABI schema format (version 0.3.0) is stable and all ABI-related tooling will support it for the foreseeable future. Second, the support for JS SDK was recently delivered (TypeScript only for now) and now ABI can be generated from both major development platforms (near-sdk-rs and near-sdk-js). Third, we think that the tooling around ABI has matured enough to be useful to the general public.

If I sold you on the concept and you want try it out feel free to visit our landing page and get started!

To generate an ABI file:

  • near-sdk-rs users need to ensure that they are using near-sdk >=4.1.0 along with cargo-near >=0.3.0.

  • near-sdk-js users just need to add --generateABI to the near-sdk-js >=0.7.0 options. Warning: only TypeScript contracts support ABI for now.

Once you have an ABI file, a whole lot of opportunities are available to you:

  • near-abi-client-rs is useful to generate an RPC client for your Rust desktop application or perhaps a video game.

  • near-sdk-abi can help you make type-safe cross-contract calls to non-Rust contracts (or just contracts source code of which is not available to you).

  • near-api-js will help you make RPC calls from a JavaScript frontend. The functionality has not been released as of me writing this post, but will be very soon. For now, you can use it as a Git URL dependency.

  • near-abi-rs and near-abi-js contains ABI models and can be useful as building bricks for those who want to build on top of the existing ABI flow.

What is next?

The development of ABI does not stop here and we are working on a slew of exciting additions to ABI. Inclusion of events and errors in the ABI format, support for JavaScript, ABI/WASM verification means and much more. Stay tuned for the news!

FAQ

I am confused, is ABI just a file alongside the contract? It does not live on-chain?

Not necessarily. We provide an option to embed ABI into a contract (available as a standardized __contract_abi view function), but the author of the contract might decide not to embed ABI (to save up on space or some other reason) and publish it off-chain instead.

Why develop NEAR ABI when WASM ABI/WIT exist?

As you might know, the main compilation target for the runtime in NEAR is WebAssembly, a binary instruction format for a stack-based virtual machine. One thing needs to be made clear here: NEAR ABI is not aware of how the WebAssembly binary instruction format works, but rather operates on top of it. WebAssembly already has a binary interface and there are even some efforts to accompany it with a human-readable format (see WIT). So why would we need to come up with our own solution?

  1. NEAR contracts usually serialize all arguments as parts of a single object. Then a WASM function deserializes this object and splits it into individual arguments to then use as its input. This means that virtually all NEAR contract function would have WIT that looks like foo(serialized_bytes: list<u8>), which is of very limited use.

  2. There are different types of NEAR functions and each of them can come with its own modifiers. It is useful to distinguish payable transact functions from init functions and view functions, for example. This information is not inferrable from the WASM ABI.

  3. WASM does not provide domain-specific information about the contract, such as what kind of events it emits and what kind of errors its functions can fail with.

This is the reasoning that led us to develop our own ABI format.

What about Borsh? Is it not supported?

Borsh is supported in the ABI format (JSON Schema section is replaced with Borsh Schema section) and ABI can be generated from Rust contracts that make use of Borsh. Most of the other tooling, however, still lacks support for it.

Why use JSON Schema when you could have made your own description language?

JSON Schema is a widely used format that directly suits our need of describing JSON values. It also has a wide array of open-source tooling in various programming languages which supports NEAR as multilingual ecosystem. Shortly, the development of our own language seemed like an unwise investment.

How can I validate that a file contains a valid NEAR ABI schema?

We publish JSON Schema metaschema for each release of ABI schema format. You can use any of the existing JSON Schema validators to validate your file against the metaschema.

6 Likes

Gread work @daniyar !
Why we are not calling 1.0 if it’s a first stable release?