Wallet V5
| Contract version | Hash | Link |
|---|---|---|
| V5 R1 | 20834b7b72b112147e1b2fb457b84e74d1a30f04f737d4f62a668e9552d2b72f | Source |
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 (-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:
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 prefix0x7369676e(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:InnerRequestcontaining 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_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 requests,
send_modemust have the+2flag set; otherwise exit code137is 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 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 | An external signed request contains a send action without the +2 flag |
| 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 without extension authorization |
| 147 | Invalid c5, action_send_msg verification failed |
| 0 | Standard 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
int is_signature_allowed()returns storedis_signature_allowedflag.int seqno()returns current stored seqno.int get_subwallet_id()returns currentwallet_idvalue: 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
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
- 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 Gram for network fees.