How TON wallets work
Wallets are often the entry point of transactions in TON. See external messages.
General principle
Many seqno-based wallet contracts store a public key and the sequence number (seqno). The contract uses the stored key to authenticate signed requests and seqno to prevent replay.
Why wallet contracts don't store a balance field
In TON, an account's balance is not stored inside the smart contract's persistent data. It is a part of the protocol-level account state, maintained by validators and updated automatically when value is received/sent and when fees are paid.
Therefore, a wallet contract does not and should not keep a separate balance variable in storage. Doing so would be redundant and could drift out of sync with the actual account state.
From outside, the balance is exposed via RPC and blockchain explorers as part of the public account state.
How ownership verification works
Wallet contracts use the Ed25519 signature scheme. You generate a private key (keep it safe) and a public key (stored in the contract; readable by anyone).
An external signed request carries a 512-bit signature and a payload. The wallet verifies the signature before processing the request. WalletTg also accepts owner-signed internal requests, using distinct opcodes for the two paths; see its message reference.
How replay protection works
Why do we need replay protection?
Imagine Alice has 100 Gram. Alice sends 10 Gram to Bob as a gift. Bob, being sneaky, forwards an identical message to Alice's wallet and would receive another 10 Gram — unless the wallet prevents replays.
Seqno-based wallets put one seqno in each signed request, even when it contains several outgoing messages. The contract checks it against stored state; its rules determine when a valid request consumes it. Once consumed, the old request fails the seqno check.
Role of valid_until
valid_until is a Unix timestamp that sets a signed request's expiry. Contracts that use it reject expired requests. WalletTg checks expiry on both external and internal signed requests.
Retry an unchanged signed request only while its seqno is current and it has not expired. If the result is unknown, read wallet state before retrying; another accepted request may have advanced seqno. If the request expired without being processed, sign a replacement with the current seqno and a new deadline.
Subwallet ID
subwallet_id is a 32-bit identifier tied to a wallet instance. It is set at wallet creation time and is included in the signing message. On each signed request, the wallet verifies that the provided subwallet_id matches its own; otherwise, the message is rejected.
Multiple independent wallet contracts can share the same public key by using different subwallet_id values, each with its own address and seqno. This is useful for separating accounts, environments, or business lines while keeping a single keypair.
Important
Selecting a different subwallet_id in the original StateInit derives a different wallet address. Each address is a separate account; funds are not shared even if the public key is the same.
Wallet V5 and wallet_id
The wallet V5 replaces subwallet_id with a derived 32-bit wallet_id field that encodes network, wallet version, workchain, and subwallet number in a single value. The on-chain getter is still named get_subwallet_id() for backward compatibility, but for the wallet V5 it returns this derived wallet_id.
See the wallet V5 overview for the exact scheme.