Persistent memory layout
is_signature_allowed: 1-bit flag that restricts or allows access through the signature and stored public key.seqno: 32-bit sequence number.wallet_id: 32-bit wallet ID (see the wallet ID scheme below).public_key: 256-bit public key.extensions_dict: dictionary containing extensions (may be empty).
Wallet ID scheme
In Wallet V5 thewallet_id field is a 32-bit signed integer derived from the network global identifier and a 32-bit context value:
wallet_id = network_global_id ^ context_id
network_global_id: 32-bit network identifier (-239for mainnet,-3for testnet).context_id: 32-bit value that encodes either a client context or a custom context:- Client context:
context_id_client$1 = wc:int8 wallet_version:uint8 counter:uint15wc: workchain identifier.wallet_version: version discriminator; for Wallet V5R1 it is0.counter: 15-bit subwallet number.
- Custom context:
context_id_backoffice$0 = counter:uint31, reserved for specialized infrastructure and back-office uses.
- Client context:
get_subwallet_id(), but in Wallet V5 it returns the derived wallet_id value described above rather than a plain subwallet_id.
Message layout
External message body layout
wallet_id: 32-bit long wallet ID.valid_until: 32-bit long Unix time integer.msg_seqno: 32-bit long sequence number.inner: InnerRequest containing the actual actions to perform.signature: 512-bit long Ed25519 signature.
Internal message body layout
Learn more about internal message serialization here.Authentication process
InnerRequest — let’s first look at how version 5 differs from previous versions in the authentication process. The InternalMsgBody combinator describes two ways to access wallet actions through internal messages. The first method is one we are already familiar with from version 4: authentication as a previously registered extension, the address of which is stored in extensions_dict. The second method is authentication through the stored public key and signature, similar to external requests.
At first, this might seem like an unnecessary feature, but it actually enables requests to be processed through external services (smart contracts) that are not part of your wallet’s extension infrastructure — a key feature of V5. Gasless transactions rely on this functionality.
Any received internal message that doesn’t pass the authentication process will be considered a transfer.
Actions
The first thing that we should notice isInnerRequest, which we have already seen in the authentication process. In contrast to the previous version, both external and internal messages have access to the same functionality, except for changing the signature mode (i.e., the is_signature_allowed flag).
We can consider InnerRequest as two lists of actions: the first, OutList, is an optional chain of cell references, each containing a send message request led by the message mode. The second, ActionList, is led by a one-bit flag, has_other_actions, which marks the presence of extended actions, starting from the first cell and continuing as a chain of cell references. We are already familiar with the first two extended actions, action_add_ext and action_delete_ext, followed by the internal address that we want to add or delete from the extensions dictionary. The third, action_set_signature_auth_allowed, restricts or allows authentication through the public key, leaving the only way to interact with the wallet through extensions. This functionality might be extremely important in the case of a lost or compromised private key.
Learn more about actions here.
Action list validation
Wallet V5 allows precomputed outgoing actions to be placed in the TVMC5 register as a raw OutList. Before executing them, the contract validates C5 (see verify_c5_actions in wallet_v5.fc):
- only
action_send_msgactions are allowed; actions such asset_code,reserve_currency, orchange_libraryare rejected; - each action must contain an 8-bit
send_modeand exactly two references (next-action reference andMessageRelaxedreference); - for external messages,
send_modemust have the+2(“ignore errors”) bit set; otherwise exit code137is thrown; - the number of actions is limited to 255; exceeding this bound or violating the structure results in exit code
147(invalid_c5).
Exit codes
| Exit code | Description |
|---|---|
| 132 | Authentication attempt through signature while it’s disabled |
| 133 | seqno check failed, replay protection occurred |
| 134 | wallet_id does not correspond to the stored one |
| 135 | signature check failed |
| 136 | valid_until check failed |
| 137 | Enforce that send_mode has the +2 bit (ignore errors) set for external messages. |
| 138 | external_signed prefix doesn’t correspond to the received one |
| 139 | Add extension operation was not successful |
| 140 | Remove extension operation was not successful |
| 141 | Unsupported extended message prefix |
| 142 | Tried to disable auth by signature while the extension dictionary is empty |
| 143 | Attempt to set signature to an already set state |
| 144 | Tried to remove the last extension when signature is disabled |
| 145 | Extension has the wrong workchain |
| 146 | Tried to change signature mode through external message |
| 147 | Invalid c5, action_send_msg verification failed |
| 0 | Standard successful execution exit code. |
Get methods
int is_signature_allowed()returns storedis_signature_allowedflag.int seqno()returns current stored seqno.int get_subwallet_id()returns currentwallet_idvalue (for Wallet V5 this is the derived identifier described in the wallet ID scheme).int get_public_key()returns current stored public key.cell get_extensions()returns extensions dictionary.
Gasless transactions
Starting with v5, the wallet smart contract supports owner-signed internal messages (internal_signed), which enables gasless transactions—for example, paying network fees in USDT when transferring USDT. Gasless transactions are supported not at the network protocol level, meaning that to pay fees in USDT it is necessary to rely on some off-chain infrastructure, e.g., like in Tonkeeper.
Flow scheme for Tonkeeper gasless transactions looks like this:
Flow details
- When sending USDT, the user signs one message containing two outgoing USDT transfers:
- USDT transfer to the recipient’s address.
- Transfer of a small amount of USDT in favor of the service.
- This signed message is sent off-chain by HTTPS to the service backend. The service backend sends it to the TON Blockchain, paying Toncoin for network fees.
tonapi.io/api-v2. If you are developing a wallet app and have feedback about these methods, please share it in @tonapitech chat.