You can define schema of contract get-methods and messages going to and from contract in just one JSON schema.
Anton mainly determines contracts by the presence of get-methods in the contract code. But if it is impossible to identify your contracts by only get-methods (as in Telemint NFT collection contracts), you should define contract addresses in the network or a contract code Bag of Cells.
{
"interface_name": "", // name of the contract
"addresses": [], // optional contract addresses
"code_boc": "", // optional contract code BoC
"definitions": {}, // map definition name to cell schema
"in_messages": [], // possible incoming messages schema
"out_messages": [], // possible outgoing messages schema
"get_methods": [] // get-method names, return values and arguments
}
Each message schema has operation name, operation code and field definitions.
Each field definition has name, TL-B type and format, which shows how to parse cell.
Also, it is possible to define similarly described embedded structures in each field in struct_fields
.
{
"op_name": "nft_start_auction", // operation name
"op_code": "0x5fcc3d14", // TL-B constructor prefix code (operation code)
"type": "external_out", // message type: internal, external_in, external_out
"body": [
{ // fields definitions
"name": "query_id", // field name
"tlb_type": "## 64", // field TL-B type
"format": "uint64" // describes how we should parse the field
},
{
"name": "auction_config",
"tlb_type": "^",
"format": "struct",
"struct_fields": [ // fields of inner structure
{
"name": "beneficiary_address",
"tlb_type": "addr",
"format": "addr"
}
]
}
]
}
While parsing TL-B cells by fields description, we are trying to parse data according to TL-B type and map it into some Golang type or structure.
Each TL-B type used in schemas has value equal to the structure tags in tonutils-go.
If it is not possible to parse the field using tlb.LoadFromCell
,
you can define your custom type with LoadFromCell
method in abi
package (for example, TelemintText
) and register it in tlb_types.go
.
Accepted TL-B types in tlb_type
:
## N
- integer with N bits; by default maps touintX
orbig.Int
^
- data is stored in the referenced cell; by default maps tocell.Cell
or to custom struct, ifstruct_fields
is defined.
- inner struct; by default maps tocell.Cell
or to custom struct, ifstruct_fields
is defined[^]dict [inline] N [-> [^]]
- dictionary with key sizeN
, transformation tomap
is done through->
bits N
- bit slice N len; by default maps to[]byte
bool
- 1 bit boolean; by default maps tobool
addr
- ton address; by default maps toaddr.Address
maybe
- reads 1 bit, and loads rest if its 1, can be used in combination with others only; by default maps tocell.Cell
or to custom struct, ifstruct_fields
is definedeither X Y
- reads 1 bit, if its 0 - loads X, if 1 - loads Y; by default maps tocell.Cell
or to custom struct, ifstruct_fields
is defined
Accepted types of format
:
struct
- embed structure, maps into structure described bystruct_fields
bytes
- byte slice, maps into[]byte
bool
- boolean (can be used only ontlb_type = bool
)uint8
,uint16
,uint32
,uint64
- unsigned integersint8
,int16
,int32
,int64
- unsigned integersbigInt
- integer with more than 64 bits, maps intobig.Int
wrappercell
- TL-B cell, maps intocell.Cell
dict
- TL-B dictionary (hashmap), maps intocell.Dictionary
tag
- TL-B constructor prefixcoins
- varInt 16, maps intobig.Int
wrapperaddr
- TON address, maps intoaddress.Address
wrapper- [TODO]
content_cell
- token data as in TEP-64; implementation string
- string snake is stored in the celltelemintText
- variable length string with this TL-B constructor
Each get-method consists of name (which is then used to get method_id
), arguments and return values.
{
"interface_name": "jetton_minter",
"get_methods": [
{
"name": "get_wallet_address", // get-method name
"arguments": [
{
"name": "owner_address", // argument name
"stack_type": "slice",
"format": "addr"
}
],
"return_values": [
{
"name": "jetton_wallet_address", // return value name
"stack_type": "slice", // type we load
"format": "addr" // type we parse into
}
]
},
{
"name": "get_jetton_data",
"return_values": [
{
"name": "total_supply",
"stack_type": "int",
"format": "bigInt"
},
{
"name": "mintable",
"stack_type": "int",
"format": "bool"
},
{
"name": "admin_address",
"stack_type": "slice",
"format": "addr"
}
]
}
]
}
Accepted argument stack types:
int
- integer; by default maps frombig.Int
cell
- map from BoCslice
- cell slice
Accepted return values stack types:
int
- integer; by default maps intobig.Int
cell
- map to BoCslice
- load slice- [TODO]
tuple
Accepted types to map from or parse into in format
field:
addr
- MsgAddress slice typebool
- map int to booleanuint8
,uint16
,uint32
,uint64
- map int to an unsigned integerint8
,int16
,int32
,int64
- map int to an signed integerbigInt
- map integer bigger than 64 bitsstring
- load string snake from cellbytes
- convert big int to bytescontent
- load TEP-64 standard token data intonft.ContentAny
struct
- define struct_fields to parse cell
You can define some cell schema in definitions
field of contract interface.
You can use those definitions in message schemas:
{
"interface_name": "telemint_nft_item",
"addresses": [
"EQAOQdwdw8kGftJCSFgOErM1mBjYPe4DBPq8-AhF6vr9si5N",
"EQCA14o1-VWhS2efqoh_9M1b_A9DtKTuoqfmkn83AbJzwnPi"
],
"definitions": {
"auction_config": [
{
"name": "beneficiary_address",
"tlb_type": "addr"
}
]
},
"in_messages": [
{
"op_name": "teleitem_start_auction",
"op_code": "0x487a8e81",
"body": [
{
"name": "query_id",
"tlb_type": "## 64"
},
{
"name": "auction_config",
"tlb_type": "^",
"format": "auction_config"
}
]
}
]
}
Or use them in get-method return values' schema:
{
"interface_name": "amm",
"definitions": {
"amm_state": [
{
"name": "quote_asset_reserve",
"tlb_type": ".",
"format": "coins"
},
// ...
]
},
"get_methods": [
{
"name": "get_amm_data",
"return_values": [
// ...
{
"name": "amm_state",
"stack_type": "cell",
"format": "amm_state"
},
]
}
]
}
You can make some definitions with tags in the beginning of cell and use them later in unions. See the following example:
{
"interface": "jetton_vault",
"definitions": {
"native_asset": [
{
"name": "native_asset",
"tlb_type": "$0000",
"format": "tag"
}
],
"jetton_asset": [
{
"name": "jetton_asset",
"tlb_type": "$0001",
"format": "tag"
},
// ...
],
"pool_params": [
// ...
{
"name": "asset0",
"tlb_type": "[native_asset,jetton_asset]"
},
// ...
],
"deposit_liquidity": [
{
"name": "deposit_liquidity",
"tlb_type": "#40e108d6",
"format": "tag"
},
{
"name": "pool_params",
"tlb_type": ".",
"format": "pool_params"
},
// ...
],
"swap": [
{
"name": "swap",
"tlb_type": "#e3a0d482",
"format": "tag"
},
{
"name": "swap_step",
"tlb_type": ".",
"format": "swap_step"
},
// ...
]
},
"in_messages": [
{
"op_name": "jetton_transfer_notification",
"op_code": "0x7362d09c",
"body": [
{
"name": "query_id",
"tlb_type": "## 64",
"format": "uint64"
},
{
"name": "amount",
"tlb_type": ".",
"format": "coins"
},
{
"name": "sender",
"tlb_type": "addr",
"format": "addr"
},
{
"name": "forward_payload",
"tlb_type": "either . ^",
"format": "struct",
"struct_fields": [{
"name": "value",
"tlb_type": "[deposit_liquidity,swap]"
}]
}
]
}
]
}
Here we define two structs in the interface: deposit_liquidity
and swap
.
Then our contract interface accepts incoming jetton_transfer_notification
.
Inside forward payload there may be a cell, which corresponds to either deposit_liquidity
, either swap
.
If Anton finds a message with jetton_transfer_notification
operation, he will try to determine the structure
of forward payload by tag in the beginning of cell.
After parsing deposit_liquidity
transfer notification message body will look like this:
{
"query_id": 3638120226682551939,
"amount": "1253854400825677",
"sender": "EQDz0wQL6EEdgbPkFgS7nNmywzr468AvgLyhH7PIMALxPB6G",
"forward_payload": {
"value": {
"deposit_liquidity": {},
"pool_params": {
"is_stable": false,
"asset_0": {
"native_asset": {}
},
"asset_1": {
"jetton_asset": {},
"workchain_id": 0,
"jetton_address": 2422642597
}
},
"min_lp_amount": "49289848313582100",
"asset_0_target_balance": "135747634478277169790071850",
"asset_1_target_balance": "30291957672135140790470162860"
}
}
}
You can define the format of the dictionary values, so Anton will be able to parse it into the golang map
.
In the following example, we use defined limit_order
as a dictionary value:
{
// ...
"definitions": {
"limit_order": [
{
"name": "order_tag",
"tlb_type": "$0010",
"format": "tag"
},
{
"name": "expiration",
"tlb_type": "## 32"
},
// ...
]
},
// ...
"in_message": {
// ...
"body": [
{
"name": "dict_3_bit_key",
"tlb_type": "dict inline 3 -> ^",
"format": "limit_order"
}
]
}
}
Or we can use defined orders
union as a dictionary value, but for the union we're setting tlb_type
field instead of format
.
{
// ...
"definitions": {
"take_order": [
{
"name": "take_order_tag",
"tlb_type": "$0001",
"format": "tag"
},
{
"name": "expiration",
"tlb_type": "## 32"
},
// ...
],
"limit_order": [
{
"name": "order_tag",
"tlb_type": "$0010",
"format": "tag"
},
{
"name": "expiration",
"tlb_type": "## 32"
},
// ...
]
},
// ...
"in_message": {
// ...
"body": [
{
"name": "dict_3_bit_key",
"tlb_type": "dict inline 3 -> ^ [take_order,limit_order]"
}
]
}
}
- TEP-62 NFT Standard: interfaces, description, contract code
- TEP-74 Fungible tokens (Jettons) standard: interfaces, description, contract code
- TEP-81 DNS contracts: interface, description
- TEP-85 NFT SBT tokens: interfaces, description
- Telemint contracts: interfaces, contract code
- Getgems contracts: interfaces, contract code
- Wallets: interfaces, tonweb
- STON.fi DEX: architecture, contract code
- Megaton.fi DEX: architecture
- Tonpay: go-sdk, js-sdk
You can convert Golang struct with described tlb tags to the JSON schema by using abi.NewTLBDesc
and abi.NewOperationDesc
functions.
See an example in tlb_test.go
file.