From 84eedea76b18bf0a2724d10f8700c4e1d9459ac9 Mon Sep 17 00:00:00 2001 From: aigerimu <89766357+aigerimu@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:01:01 +0700 Subject: [PATCH] jetton-payload --- languages/tolk/features/jetton-payload.mdx | 83 +++++++++------------- 1 file changed, 32 insertions(+), 51 deletions(-) diff --git a/languages/tolk/features/jetton-payload.mdx b/languages/tolk/features/jetton-payload.mdx index 824711883..efd950dfb 100644 --- a/languages/tolk/features/jetton-payload.mdx +++ b/languages/tolk/features/jetton-payload.mdx @@ -3,36 +3,26 @@ title: "Forward payload in jettons" sidebarTitle: "Payload in jettons" --- -import { Aside } from '/snippets/aside.jsx'; +A jetton transfer may include a `forwardPayload` to provide custom data for the transaction recipient. This is a convention, not a language feature. -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 +## Jetton payload schema 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_) +- bit 0 indicates inline payload: all subsequent bits and references; +- bit 1 indicates ref payload: the next reference. -Since it may be inline, it's always positioned in the end of a message. +When inline, the payload is positioned at the end of a message. -However, many **existing jetton implementations do not follow the schema**. +Some 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. +- Some allow empty data, no bits at all, which is invalid because at least one bit must exist. An empty payload should be encoded as bit 0 – empty inline payload. +- Some do not verify that no extra data remains after bit 1. +- Error codes vary across implementations. -## Canonical typing of the payload +## Canonical payload typing -TL-B `(Either X Y)` is essentially a union type `X | Y` in Tolk. Hence, this will work: +TL-B `(Either X Y)` is a union type `X | Y` in Tolk. This can be defined as: ```tolk struct Transfer { @@ -41,24 +31,20 @@ struct Transfer { } ``` -It will be parsed/serialized exactly as it should: either bit '0' + inline data or bit '1' + ref. +It is parsed and serialized according to the schema: either bit 0 + inline data, or bit 1 + ref. -It is convenient for assignment and client metadata, but **has some disadvantages**: +This approach can be used for assignment and client metadata. Trade-offs include: -- 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' +- consumes more gas due to runtime branching (`IF` bit 0); +- does not verify that no extra data remains after bit 1. -## Questions to ask yourself +## Payload typing cases -To choose the correct typing, **answer the following questions**: +The approach to representing a jetton `forwardPayload` depends on the intended usage and validation requirements. -- 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? +### Proxy data without validation -## Various approaches depending on answers - -To proxy any data **without validation**, shape the "payload" as "all the rest": +To proxy any data as-is, use `RemainingBitsAndRefs`: ```tolk struct Transfer { @@ -67,7 +53,9 @@ struct Transfer { } ``` -To add **validation of a canonical union** `RemainingBitsAndRefs | cell`, check that no extra data exists after a _ref payload_: +### Canonical union with validation + +To validate a canonical union `RemainingBitsAndRefs | cell`, ensure that no extra data remains after a ref payload: ```tolk struct Transfer { @@ -84,8 +72,9 @@ fun Transfer.validatePayload(self) { } ``` -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: +### Validation with slices + +If gas consumption is critical but validation is required, avoid allocating unions on the stack. Instead, validate a slice and keep it for further serialization: ```tolk struct Transfer { @@ -106,8 +95,9 @@ fun ForwardPayload.checkIsCorrectTLBEither(self) { } ``` -To throw **custom error codes** (not errCode 9 "cell underflow"), even calling `loadMaybeRef()` like above is discouraged. -The solution can be: +### Custom error codes + +To throw custom error codes instead of an error with [exit code 9](/tvm/exit-codes#9:-cell-underflow), calling `loadMaybeRef()` is discouraged. ```tolk type ForwardPayload = RemainingBitsAndRefs @@ -142,9 +132,9 @@ fun ForwardPayload.checkIsCorrectTLBEither(self) { } ``` -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: +### Dynamic assignment + +Keeping a remainder reduces gas usage and enables validation, but it is less convenient when a payload must be assigned dynamically. The remainder is a plain `slice` containing an encoded union. For example, creating a ref payload from a `cell` requires manual construction. ```tolk fun createRefPayload(ref: cell) { @@ -160,13 +150,4 @@ fun createRefPayload(ref: cell) { } ``` -Of course, `RemainingBitsAndRefs | cell` is **much more convenient for assignment**, but as shown above, -has its own disadvantages. - - +Using `RemainingBitsAndRefs | cell` remains convenient for assignment but may incur additional gas costs.