Serializer

Most data read from and written to Antelope-based blockchains is serialized using an ABI. The Serializer within the Antelope SDK is a utility that facilitates the encoding and decoding of this type of data from the blockchain. All of the data encoding it handles has its functionality mirrored from the C++ implementation that runs the blockchain.

Usage

The Serializer can be called directly without instantiation to perform encoding, decoding, and various utility methods. To make use of all these methods, the Serializer namespace needs to be included in the project. Once included, any of the methods can be called directly without any sort of instantiation.

import { Serializer } from "@wharfkit/antelope"

Encode

The .encode() method on the Serializer accepts multiple variations of parameters, all of which will return an encoded instance of the data as a Bytes object.

Native Antelope Types

In the simplest example, any core Antelope data type can be passed as the object parameter of the call.

This example shows passing an Asset typed object and encoding it into the Bytes type.

import { Asset, Serializer } from "@wharfkit/antelope"

const asset = Asset.from("1.0000 FOO")

const encoded = Serializer.encode({ object: asset })

console.log(encoded)
/*
Bytes {
  array: Uint8Array(16) [
    16, 39, 0,  0,  0,  0,
     0,  0, 4, 70, 79, 79,
     0,  0, 0,  0
  ]
}
*/

console.log(String(encoded))
// 102700000000000004464f4f00000000

Custom Struct

The same encoding can also be performed against a custom Struct as defined in your application or by the Contract Kit.

This example defines a custom Struct that matches an eosio.token contract transfer action and creates an instance of it using the Struct.from() method. This object can then be passed into the Serializer in order to convert it to Bytes.

import { Asset, Name, Struct, Serializer } from "@wharfkit/antelope"

@Struct.type("transfer")
class Transfer extends Struct {
  @Struct.field("name") from!: Name
  @Struct.field("name") to!: Name
  @Struct.field("asset") quantity!: Asset
  @Struct.field("string") memo!: string
}

const object = Transfer.from({
  from: "foo",
  to: "bar",
  quantity: "1.0000 EOS",
  memo: "hello",
})

const encoded = Serializer.encode({ object })

console.log(encoded)
/*
Bytes {
  array: Uint8Array(38) [
    0, 0, 0,   0,   0,   0,  40,  93, 0, 0,
    0, 0, 0,   0, 174,  57,  16,  39, 0, 0,
    0, 0, 0,   0,   4,  69,  79,  83, 0, 0,
    0, 0, 5, 104, 101, 108, 108, 111
  ]
}
*/

console.log(String(encoded))
// 000000000000285d000000000000ae39102700000000000004454f53000000000568656c6c6f

Untyped Data and Struct

The serializer is also capable of assembling a Struct dynamically before the encoding if passed any object and a compatible Struct or native Antelope type.

In this example the object passed to the encoder is a string representation of an Asset, which is accompanied by a secondary type parameter where the actual Struct is passed to define it.

import { Asset, Serializer } from "@wharfkit/antelope"

const encoded = Serializer.encode({
  object: "1.0000 FOO",
  type: Asset,
})

console.log(encoded)
/*
Bytes {
  array: Uint8Array(16) [
    16, 39, 0,  0,  0,  0,
     0,  0, 4, 70, 79, 79,
     0,  0, 0,  0
  ]
}
*/

console.log(String(encoded))
// 102700000000000004464f4f00000000

Untyped Data and ABI

The Serializer is capable of accepting an untyped object, ABI, and struct name as a string to encode data and return a Bytes object.

The following example shows an untyped variable named object, alongside an ABI that defines a struct named my_struct that defines the object’s format. The object, ABI, and a 3rd string parameter with the string name of the struct are passed in to the encode method in order to serialize the data and return an instance of Bytes.

import { ABI, Serializer } from "@wharfkit/antelope"

const object = {
  foo: "bar",
}

const abi = ABI.from({
  structs: [
    {
      name: "my_struct",
      base: "",
      fields: [
        {
          name: "foo",
          type: "string",
        },
      ],
    },
  ],
})

const encoded = Serializer.encode({ object, abi, type: "my_struct" })

console.log(encoded)
// Bytes { array: Uint8Array(4) [ 3, 98, 97, 114 ] }

console.log(String(encoded))
// 03626172

Decode

The .decode() method of the Serializer accepts any encoded version of data and will return either a native Antelope type or an instance of a custom Struct.

Using Encoded Data

The data parameter passed in to the .decode() method can be passed either as hex data or as an instance of Bytes.

import { Asset, Serializer } from "@wharfkit/antelope"

const data = "102700000000000004464f4f00000000"
// or
// const data = Bytes.from('102700000000000004464f4f00000000')

const decoded = Serializer.decode({ data, type: Asset })

console.log(decoded)
/*
Asset {
  units: Int64 {
    value: BN { negative: 0, words: [Array], length: 1, red: null }
  },
  symbol: Symbol { value: UInt64 { value: [BN] } }
}
*/

console.log(String(decoded))
// 1.0000 FOO

Here the decoder is instructed to use the Asset Antelope type during the decoding of the raw data being passed in. This results in an Asset typed object being returned as the result.

Using Custom Struct

A custom defined Struct may also be passed in as the type alongside the data, resulting in the data being decoded using a specific type.

import { Asset, Name, Serializer, Struct } from "@wharfkit/antelope"

const data =
  "000000000000285d000000000000ae39102700000000000004454f53000000000568656c6c6f"

@Struct.type("transfer")
class Transfer extends Struct {
  @Struct.field("name") from!: Name
  @Struct.field("name") to!: Name
  @Struct.field("asset") quantity!: Asset
  @Struct.field("string") memo!: string
}

const decoded = Serializer.decode({ data, type: Transfer })

console.log(decoded)
/*
Transfer {
    from: Name { value: UInt64 { value: [BN] } },
    to: Name { value: UInt64 { value: [BN] } },
    quantity: Asset {
        units: Int64 { value: [BN] },
        symbol: Symbol { value: [UInt64] }
    },
    memo: 'hello'
}
*/

console.log(JSON.stringify(decoded))
// {"from":"foo","to":"bar","quantity":"1.0000 EOS","memo":"hello"}

Using an ABI

An ABI parameter may also be passed in alongside a type parameter to allow decoding using an ABI directly.

import { APIClient, Serializer } from "@wharfkit/antelope"

const data =
  "000000000000285d000000000000ae39102700000000000004454f53000000000568656c6c6f"

const client = new APIClient({ url: "https://jungle4.greymass.com" })
const { abi } = await client.v1.chain.get_abi("eosio.token")

const decoded = Serializer.decode({ data, abi, type: "transfer" })

console.log(decoded)
/*
Transfer {
    from: Name { value: UInt64 { value: [BN] } },
    to: Name { value: UInt64 { value: [BN] } },
    quantity: Asset {
        units: Int64 { value: [BN] },
        symbol: Symbol { value: [UInt64] }
    },
    memo: 'hello'
}
*/

console.log(JSON.stringify(decoded))
// {"from":"foo","to":"bar","quantity":"1.0000 EOS","memo":"hello"}

The ABI can either be embedded directly within the application or it can be retrieved from an APIClient. In the example above, the code is loading the ABI for the eosio.token contract from the Jungle 4 blockchain. It then passes the encoded data, the ABI, and the type to retrieve from the ABI as a string.

Utilities

Objectify

The Serializer provides a method that is capable of converting Struct data types into native Javascript data types. This can be useful when interfacing with code which isn’t expecting the Antelope native data types or when outputting JSON data.

import { Asset, Name, Serializer, Struct } from "@wharfkit/antelope"

@Struct.type("transfer")
class Transfer extends Struct {
  @Struct.field("name") from!: Name
  @Struct.field("name") to!: Name
  @Struct.field("asset") quantity!: Asset
  @Struct.field("string") memo!: string
}

const transfer = Transfer.from({
  from: "foo",
  to: "bar",
  quantity: "1.0000 EOS",
  memo: "hello",
})

console.log(transfer)
/*
Transfer {
  from: Name { value: UInt64 { value: [BN] } },
  to: Name { value: UInt64 { value: [BN] } },
  quantity: Asset {
    units: Int64 { value: [BN] },
    symbol: Symbol { value: [UInt64] }
  },
  memo: 'hello'
}
*/

const object = Serializer.objectify(transfer)

console.log(object)
/*
{ 
    from: 'foo', 
    to: 'bar', 
    quantity: '1.0000 EOS', 
    memo: 'hello' 
}
*/

This example shows how to create a strongly typed transfer object, which when output through console.log shows all of the Antelope data types. The transfer object is then passed to the Serializer.objectify method, which walks the object and converts all of its properties into native Javascript types (like strings and numbers).