Tezos via JSOO - 01, Node access

This mini tutorial series shows how to access Tezos network from web browsers using JSOO, js_of_ocaml library.

Please note that this Tezos+JSOO is not a mainstream Tezos off-chain programming at all. Usually it is recommended to use more popular framework such as Taquito. That being said, some people who are proficient in OCaml like us in DaiLambda may find this tutorial interesting.

This tutorial assumes that the reader knows how to use OCaml, Lwt, and JSOO.

Access Tezos via RPC

For Tezos dApps, RPC is the basic method to access Tezos network. Every Tezos features should be available via RPC. This first tutorial shows its basics.

Tezos RPCs have 2 categories:

Protocol RPCs are dependent on protocols. How to call them and their return values may be different for different protocols. If you want to call a protocol RPC on some block, then you have to follow its specification of the protocol of the block.

In this example, we get the information about the head block. The RPC to be called is documented at http://tezos.gitlab.io/active/rpc.html#get-block-id . The endpoint is GET ../<block_id>. This is the abbreviation of the following:

 GET /chains/<chain_id>/blocks/<block_id>

To get the information of the head block, we use:

 GET /chains/main/blocks/head

<chain_id> is always main, and head is the alias of the id of the head block.

All you need is to access a Tezos node at this endpoint via HTTP/HTTPS. There are several public Tezos nodes available and we use https://mainnet.smartpy.io in this example. In the command line, you can get the information of the head by:

$ curl https://mainnet.smartpy.io/chains/main/blocks/head
{"protocol":"Psithaca2MLRFYargivpo7YvUr7wUDqyxrdhC5CQq78mRvimz6A","chain_id":"NetXdQprcVkpaWU"
... This is huge JSON ...

It should print out a huge JSON data of the head block. The response of Tezos RPC is in JSON. Don’t get confused with JSON-RPC, which Tezos does not use.

Your first Tezos via JSOO

Let’s do the same in a web browser, using JSOO. Here we show code excerpts. The full code is attached at the end of the article.

First, we define the URL to the RPC:

(* The node.  It must be CORS enabled. *)
let node = "https://mainnet.smartpy.io"

(* Tezos RPC path to get the latest block info *)
let url = Option.get @@ Url.url_of_string (node ^ "/chains/main/blocks/head")

In JSOO, we can use XmlHttpRequest.perform to access the endpoint: Since it is an async operation, we must use monadic map (let+) to get the result:

(* Access the URL *)
let+ http_frame = XmlHttpRequest.perform url in (* let+ = monadmic map *) in

We build the result string depending on the HTTP code:

let s = 
  match http_frame.code with
  | 200 -> http_frame.content
  | _ -> Printf.sprintf "HTTP code %d" http_frame.code
in

Finally, we insert the result string to an HTML element with id="result":

let elem = Html.getElementById "result" in
elem##.innerText := Js.string s

If successful, you should see a huge JSON output from the node.

CORS must be enabled!

One thing you have to be careful: the Tezos node must be CORS enabled. Note that not all the public nodes are CORS enabled. See https://tezos.stackexchange.com/questions/274/how-to-make-a-tezos-node-set-cors-headers for more details, especially when you use your own node.

Conclusion

That’s the first article of Tezos via JSOO tutorial. Key takeaways are:

  • Every Tezos features are available via its RPC.
  • The RPC returns JSON.
  • Tezos node must enable CORS so that web browsers make use of the RPC.

Next time we will investigate the JSON response from the node.

Appendix

The full code is available at https://gitlab.com/dailambda/tezos-jsoo/-/tree/master/mini/01-cors . You can try it as follows:

$ git clone https://gitlab.com/dailambda/tezos-jsoo
$ cd tezos-jsoo
$ opam switch create . ocaml-base-compiler.4.12.1 --deps-only -y
$ cd mini/01-cors
$ dune build
# open index.html by a web browser

We also show the full code here:

test.ml:

open Js_of_ocaml
open Js_of_ocaml_lwt
open Lwt.Syntax

(* The node.  It must be CORS enabled. *)
let node = "https://mainnet.smartpy.io"

(* Tezos RPC path to get the latest block info *)
let url = Option.get @@ Url.url_of_string (node ^ "/chains/main/blocks/head")

module Html = Dom_html

let start () =
  (* Node access via XmlHttpRequest.

     The node must be CORS enabled. See:
     https://tezos.stackexchange.com/questions/274/how-to-make-a-tezos-node-set-cors-headers
  *)
  let+ http_frame = XmlHttpRequest.perform url in (* let+ = monadmic map *)
  let s =
    match http_frame.code with
    | 200 -> http_frame.content
    | _ -> Printf.sprintf "HTTP code %d" http_frame.code
  in
  let elem = Html.getElementById "result" in
  elem##.innerText := Js.string s

let _ =
  (* Call [start] when the page is loaded *)
  Html.window##.onload :=
    Html.handler (fun _ -> ignore @@ start (); Js._false)

dune:

(executables
  (names test)
  (modes js)
  (libraries
    js_of_ocaml
    js_of_ocaml-lwt)
  (preprocess (pps js_of_ocaml-ppx)))

index.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Access to Tezos node RPC using HTTP/HTTPS</title>
    <meta charset="utf-8" />
    <script src="../../_build/default/mini/01-cors/test.bc.js"></script>
  </head>
  <body>
    <p id="result"/>
  </body>
</html>