Skip to main content
By convention, a jetton transfer may have some forwardPayload attached — to provide custom data for a transaction’s recipient. Below are several approaches to represent a “payload” depending on particular needs.

The schema of a jetton payload

By definition, the TL-B format is (Either Cell ^Cell): one bit plus the corresponding data depending on the bit:
  • bit ‘0’ means: payload = “all the next bits/refs” (inline payload)
  • bit ‘1’ means: payload = “the next ref” (ref payload)
Since it may be inline, it’s always positioned in the end of a message. However, many existing jetton implementations do not follow the schema.
  • Some implementations allow empty data to be passed (no bits at all). It is invalid, because at least one bit must exist. An empty payload should actually be encoded as bit ‘0’: empty inline payload.
  • Some implementations do not check that no extra data is left after bit ‘1’.
  • Error codes differ between various implementations.

Canonical typing of the payload

TL-B (Either X Y) is essentially a union type X | Y in Tolk. Hence, this will work:
It will be parsed/serialized exactly as it should: either bit ‘0’ + inline data or bit ‘1’ + ref. It is convenient for assignment and client metadata, but has some disadvantages:
  • if you trust the input and need just to proxy data as-is, this approach consumes more gas due to runtime branching (IF bit ‘0’, etc.)
  • it does not check, that no extra data is left after bit ‘1’

Questions to ask yourself

To choose the correct typing, answer the following questions:
  • Do you need validation or just proxy any data as-is?
  • Do you need custom error codes while validating?
  • Do you need to assign it dynamically or just to carry it forward?

Various approaches depending on answers

To proxy any data without validation, shape the “payload” as “all the rest”:
To add validation of a canonical union RemainingBitsAndRefs | cell, check that no extra data exists after a ref payload:
If gas consumption is critical, but validation is required — it’s cheaper not to allocate unions on the stack, but to load a slice, validate it, and keep a slice for further serialization:
To throw custom error codes (not errCode 9 “cell underflow”), even calling loadMaybeRef() like above is discouraged. The solution can be:
Keeping a “remainder” is cheaper and allows graceful validation, but it’s not convenient if you need to assign a payload dynamically. It’s a plain slice, holding an encoded union. For example, creating a ref payload from code having a cell requires manual work:
Of course, RemainingBitsAndRefs | cell is much more convenient for assignment, but as shown above, has its own disadvantages.