This mini tutorial series shows how to access Tezos network from web browsers using JSOO, js_of_ocaml library. This is the 3rd tutorial. The previous tutorials are available at:
Structured value from Tezos RPC
In the last tutorial, we have got the balance of an account. Tezos account balance is returned as a JSON string like "12345678"
. We used JS’s JSON parser _JSON##parse
to convert it to a JS string, then to an arbitrary precision integer Z.t
.
This time, let’s get more structured data from Tezos node, the account information which includes this balance. The RPC endpoint is:
GET ../<block_id>/context/contracts/<contract_id>
which is documented here. Let’s get the account info of tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9 via this RPC. Its URL is https://mainnet.smartpy.io/chains/main/blocks/head/context/contracts/tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9:
$ curl https://mainnet.smartpy.io/chains/main/blocks/head/context/contracts/tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9
{"balance":"424555322368","delegate":"tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9","counter":"11"}
This time the RPC returns a JSON record.
For KT1
account, for example KT1BGQR7t4izzKZ7eRodKWTodAsM23P38v7N
, the JSON response contains field script
with long data of its smart contract:
$ curl https://mainnet.smartpy.io/chains/main/blocks/head/context/contracts/KT1BGQR7t4izzKZ7eRodKWTodAsM23P38v7N
{"balance":"1210133578","delegate":"tz1WCd2jm4uSt4vntk4vSuUWoZQGhLcDuR9q","script":{"code":[{"prim":"parameter","args":[{"prim":"or","args":[{"prim":"or","args":[{"prim":"or","args"
...
JSON schema
Tezos RPC reference shows the schema of the JSON return value of the RPC in “Json output” tab:
This is not a JSON schema but you should be able to understand what it means:
balance
is a required field of type$012-Psithaca.mutez
delegate
is an optional field of type$Signature.Public_key_hash
script
is an optional field of type$012-Psithaca.scripted.contracts
counter
is an optional field of type$positive_bignum
The definitions of the field types are followed. You will find they are all strings except $012-Psithaca.scripted.contracts
if you track them down.
OCaml class type for JSON
In JSOO, the JSON value can be parsed by _JSON##parse
as a JS object. We need to prepare the OCaml type for the result. We define the following object type of properties:
open Js
type unknown
type contract =
< (* balance : required string field *)
balance : js_string t readonly_prop;
(* delegate : optional string field *)
delegate : js_string t optdef readonly_prop;
(* script : optional. We ignore the contents for now *)
script : unknown t optdef readonly_prop;
(* counter : optional string field *)
counter : js_string t optdef readonly_prop
>
Several things we have to note:
- JS’s string type is not
string
butjs_string t
. It is common to misusestring
here since no static typing helps you. - For optional field use
optdef
. - We use an abstract type
unknown
for the contents ofscript
field which we are not interested in this tutorial.
Using this object type contract
now we can parse the JSON in JSOO:
let c : contract Js.t = Js._JSON##parse (Js.string http_frame.content)
Now we can access the fields via ##.name
JSOO property accessor:
report "%s : @[<v>balance: %s;@ delegate: %s;@ script: %s;@ counter: %s@]"
address
(Js.to_string c##.balance)
(Js.Optdef.case c##.delegate (fun () -> "none") Js.to_string)
(Js.Optdef.case c##.script (fun () -> "none") (fun x ->
(* It is an abstract value in OCaml,
but we can print it using _JSON##stringify *)
(Js.to_string (Js._JSON##stringify x))))
(Js.Optdef.case c##.counter (fun () -> "none") Js.to_string)
The values of the optinal fields are handled by Js.Optdef.case
.
The script value unknown t
is abstracted in OCaml, but it is still encodable to JSON
using _JSON##stringify
.
Full OCaml code
open Js_of_ocaml
open Js_of_ocaml_lwt
open Lwt.Syntax
module Html = Dom_html
(* Append the format result to "result" HTML element *)
let report fmt =
let elem = Html.getElementById "result" in
Format.kasprintf
(fun s ->
elem##.innerText := elem##.innerText##concat (Js.string (s ^ "\n")))
fmt
(* The node. It must be CORS enabled. *)
let node = "https://mainnet.smartpy.io"
(* This time, we get more complex data of a contract *)
let url address =
Option.get
@@ Url.url_of_string
@@ node ^ "/chains/main/blocks/head/context/contracts/" ^ address
(* Type for the data we skip parsing this time (script) *)
type unknown
(* The data returned from the RPC is an JSON object, specified at
https://tezos.gitlab.io/active/rpc.html#get-block-id-context-contracts-contract-id
Here is the OCaml object type for the JS object isomorphic to it.
*)
open Js
type contract =
< (* balance : required string field *)
balance : js_string t readonly_prop;
(* delegate : optional string field *)
delegate : js_string t optdef readonly_prop;
(* script : optional. We ignore the contents for now *)
script : unknown t optdef readonly_prop;
(* counter : optional string field *)
counter : js_string t optdef readonly_prop
>
let query_contract address =
let+ http_frame = XmlHttpRequest.perform (url address) in
match http_frame.code with
| 200 ->
(* Parse the string as an JSON of type contract *)
let c : contract Js.t = Js._JSON##parse (Js.string http_frame.content)
in
report "%s : @[<v>balance: %s;@ delegate: %s;@ script: %s;@ counter: %s@]"
address
(Js.to_string c##.balance)
(Js.Optdef.case c##.delegate (fun () -> "none") Js.to_string)
(Js.Optdef.case c##.script (fun () -> "none") (fun x ->
(* It is an abstract value in OCaml,
but we can print it using _JSON##stringify *)
(Js.to_string (Js._JSON##stringify x))))
(Js.Optdef.case c##.counter (fun () -> "none") Js.to_string)
| _ ->
report "HTTP code %d" http_frame.code
let _ =
Html.window##.onload := Html.handler (fun _ ->
ignore @@ query_contract "tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9";
ignore @@ query_contract "KT1BGQR7t4izzKZ7eRodKWTodAsM23P38v7N";
Js._false)
The entire code set (this code + dune
+ index.html
) is available
here.
How to compile the tutorial examples are explained in the Appendix of the first tutorial.
Conclusion
In this 3rd tutorial of Tezos via JSOO, we have seen how to parse complex JSON data returned from Tezos RPC:
- Check the JSON schema in the RPC reference.
- Define an OCaml class type to match the returned JSON record.
- Each field becomes
readonly_prop
- Use
Js.optdef
for optional fields - Abstract types are usable for fields you are not interested in.
- Each field becomes
- Once parsed as a JS object, fields can be accessed via
##.name
JSOO property accessor.
The next tutorial will conclude the parsing of JSON response from the Tezos node.