TON DocsTON Docs
OnboardingNodesApplicationsAPIsContractsSmart contractsTolkTolk languageTVMTON Virtual MachineFoundationsBlockchain foundations

Wallet V5

Contract versionHashLink
V5 R120834b7b72b112147e1b2fb457b84e74d1a30f04f737d4f62a668e9552d2b72fSource

V5 supports gasless transactions, account delegation and recovery through extensions, subscription payments using jettons and Gram, and batch transfers. A request can send up to 255 outgoing messages.

The wallet V5 API documents message serialization and extension interfaces.

Source links:

Persistent memory layout

contract_state$_
  is_signature_allowed:(## 1)
  seqno:(## 32)
  wallet_id:(## 32)
  public_key:(## 256)
  extensions_dict:(HashmapE 256 int1) = ContractState;
  • is_signature_allowed: 1-bit flag that restricts or allows access through the signature and stored public key.
  • seqno: 32-bit sequence number for replay protection.
  • wallet_id: 32-bit wallet ID, derived using the wallet ID scheme.
  • public_key: 256-bit public key.
  • extensions_dict: dictionary containing extensions (may be empty).

Wallet ID scheme

In Wallet V5 the wallet_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

The scheme used by the reference implementation and official wrapper is:

  • network_global_id: 32-bit network identifier (-239 for mainnet, -3 for 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:uint15
      • wc: workchain identifier.
      • wallet_version: version discriminator; for Wallet V5R1 it is 0.
      • counter: 15-bit subwallet number.
    • Custom context: context_id_backoffice$0 = counter:uint31, reserved for specialized infrastructure and back-office uses.

For compatibility, the get-method is still named get_subwallet_id(), but in Wallet V5 it returns the derived wallet_id value rather than a plain subwallet_id.

Message layout

External message body layout

  • opcode: 32-bit prefix 0x7369676e (external_signed).
  • 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 actions to perform.
  • signature: 512-bit Ed25519 signature.

Internal message body layout

The V5 message interface defines internal message serialization.

Authentication process

internal_signed#73696e74
  signed:SignedRequest = InternalMsgBody;

internal_extension#6578746e
  query_id:(## 64)
  inner:InnerRequest = InternalMsgBody;

The InternalMsgBody combinator describes two ways to access wallet actions through internal messages. An extension authenticates through its registered address in extensions_dict. An owner-signed request authenticates through the stored public key and signature, as external requests do.

Owner-signed internal requests let a service relay requests without becoming a registered extension. Gasless transactions use this path.

Short internal bodies, unrecognized opcodes, invalid signatures, and requests from unregistered extensions are ignored as deposits. A valid signature does not bypass the wallet ID, seqno, or expiration checks; failures in those checks throw an error.

Actions

InnerRequest carries actions for external and internal requests. Only a registered extension can change is_signature_allowed.

InnerRequest contains two lists. OutList is an optional chain of cell references, each containing a send-message action and its send mode. ActionList starts with the one-bit has_other_actions flag. Its extended actions start in the current cell and continue through cell references.

action_add_ext and action_delete_ext carry the internal address to add or remove. action_set_signature_auth_allowed enables or disables public-key authentication. Disabling it leaves extensions as the authorization path, which can help recover control after a signing key is lost or compromised.

The V5 action interface defines the serialization of both action lists.

Action list validation

Wallet V5 allows precomputed outgoing actions to be placed in the TVM c5 register as a raw OutList. Before executing them, the contract validates c5 in verify_c5_actions:

  • only action_send_msg actions are allowed; actions such as set_code, reserve_currency, or change_library are rejected;
  • each action must contain an 8-bit send_mode and exactly two references (next-action reference and MessageRelaxed reference);
  • for external requests, send_mode must have the +2 flag set; otherwise exit code 137 is thrown;
  • the number of actions is limited to 255; exceeding this bound or failing the bit-count or reference-count checks results in exit code 147 (invalid_c5). Invalid action prefixes fail the TVM parsing check.

Exit codes

Exit codeDescription
132Authentication attempt through signature while it's disabled
133seqno check failed, replay protection occurred
134wallet_id does not correspond to the stored one
135signature check failed
136valid_until check failed
137An external signed request contains a send action without the +2 flag
138external_signed prefix doesn't correspond to the received one
139Add extension operation was not successful
140Remove extension operation was not successful
141Unsupported extended message prefix
142Tried to disable auth by signature while the extension dictionary is empty
143Attempt to set signature to an already set state
144Tried to remove the last extension when signature is disabled
145Extension has the wrong workchain
146Tried to change signature mode without extension authorization
147Invalid c5, action_send_msg verification failed
0Standard successful execution exit code.

Exit codes 142, 144, and 146 prevent disabling signatures without an extension, removing the last extension while signatures are disabled, and changing signature mode without extension authorization. The wallet does not check whether registered extension accounts exist. Verify that an extension can authorize requests before disabling signatures.

If initial storage disables signatures while the extensions dictionary is empty, the contract still accepts a valid signed request and enables signature authentication. This permits recovery from that initial configuration error.

Get methods

  1. int is_signature_allowed() returns stored is_signature_allowed flag.
  2. int seqno() returns current stored seqno.
  3. int get_subwallet_id() returns current wallet_id value: the derived identifier described in the wallet ID scheme.
  4. int get_public_key() returns current stored public key.
  5. cell get_extensions() returns extensions dictionary.

Gasless transactions

V5 supports owner-signed internal messages, using internal_signed. A relayer can attach Gram to pay network fees and receive USDT as reimbursement. The blockchain still charges network fees in Gram.

Flow details

  1. When sending USDT, the user signs one message containing two outgoing USDT transfers:
    1. USDT transfer to the recipient's address.
    2. Transfer of a small amount of USDT in favor of the service.
  2. This signed message is sent off-chain by HTTPS to the service backend. The service backend sends it to the TON Blockchain, paying Gram for network fees.

On this page