TON contract interface
Overview
You can define schema of contract get-methods and messages going to and from contract in just one JSON schema.
Contract interface
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
}
Message schema
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 to uintX or big.Int
^ - data is stored in the referenced cell; by default maps to cell.Cell or to custom struct, if struct_fields is defined
. - inner struct; by default maps to cell.Cell or to custom struct, if struct_fields is defined
[^]dict [inline] N [-> [^]] - dictionary with key size N, transformation to map is done through ->
bits N - bit slice N len; by default maps to []byte
bool - 1 bit boolean; by default maps to bool
addr - ton address; by default maps to addr.Address
maybe - reads 1 bit, and loads rest if its 1, can be used in combination with others only; by default maps to cell.Cell or to custom struct, if struct_fields is defined
either X Y - reads 1 bit, if its 0 - loads X, if 1 - loads Y; by default maps to cell.Cell or to custom struct, if struct_fields is defined
Accepted types of format:
struct - embed structure, maps into structure described by struct_fields
bytes - byte slice, maps into []byte
bool - boolean (can be used only on tlb_type = bool)
uint8, uint16, uint32, uint64 - unsigned integers
int8, int16, int32, int64 - unsigned integers
bigInt - integer with more than 64 bits, maps into big.Int wrapper
cell - TL-B cell, maps into cell.Cell
dict - TL-B dictionary (hashmap), maps into cell.Dictionary
tag - TL-B constructor prefix
coins - varInt 16, maps into big.Int wrapper
addr - TON address, maps into address.Address wrapper
- [TODO]
content_cell - token data as in TEP-64; implementation
string - string snake is stored in the cell
telemintText - variable length string with this TL-B constructor
Get-methods
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 from big.Int
cell - map from BoC
slice - cell slice
Accepted return values stack types:
int - integer; by default maps into big.Int
cell - map to BoC
slice - load slice
- [TODO]
tuple
Accepted types to map from or parse into in format field:
addr - MsgAddress slice type
bool - map int to boolean
uint8, uint16, uint32, uint64 - map int to an unsigned integer
int8, int16, int32, int64 - map int to an signed integer
bigInt - map integer bigger than 64 bits
string - load string snake from cell
bytes - convert big int to bytes
content - load TEP-64 standard token data into nft.ContentAny
struct - define struct_fields to parse cell
Shared TL-B constructors
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"
},
]
}
]
}
Union of TLB types
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]"
}
]
}
}
Known contracts
- 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
Converting Golang struct to JSON schema
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.