# How TON wallets work (https://docs-fpm2731fy-ton-core-docs.vercel.app/llms/standard/wallets/how-it-works/content.md)



Wallets are often the entry point of transactions in TON. See [external messages](/llms/foundations/whitepapers/ton/content.md).

### General principle [#general-principle]

A wallet smart contract stores two critical variables — the public key and the sequence number (`seqno`). Together, they secure the wallet.

Using the public key, the contract verifies that the request comes from the wallet owner; using `seqno`, it protects against replayed transactions (see [How replay protection works](#how-replay-protection-works)).

## Why wallet contracts don't store a `balance` field [#why-wallet-contracts-dont-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 [#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).

<Callout type="caution" title="Important">
  The public key is not the wallet address. The address is [derived](/llms/foundations/addresses/derive/content.md) from the contract's [`StateInit`](/llms/foundations/messages/deploy/content.md).
</Callout>

An external message is sent to the contract (containing a 512-bit signature and the desired payload). Ed25519 verifies that the provided signature matches the hash of the message.

## How replay protection works [#how-replay-protection-works]

<Callout type="note" title="Why do we need replay protection?">
  Imagine Alice has 100 TON. Alice sends 10 Toncoin to Bob as a gift. Bob, being sneaky, forwards an identical message to Alice's wallet and would receive another 10 TON — unless the wallet prevents replays.
</Callout>

Each transaction carries a counter value (in our case, `seqno`) that must be unique and strictly increasing for every outgoing transaction. Because `seqno` changes, the message hash changes as well, making it impossible to reuse a previous signature.

### Role of `valid_until` [#role-of-valid_until]

`valid_until` is a Unix timestamp embedded into the wallet’s signing message. The wallet rejects any external message if the current time exceeds this value. This limits the lifetime of a signature and prevents an attacker from replaying a captured message indefinitely.

It also makes retries safe: a client may re-send the same signed message until the deadline; after it expires, the client must re-sign with a new `valid_until`. Combined with `seqno` equality (the contract accepts only the exact current `seqno` and increments it on success), this provides robust replay protection.

## Subwallet ID [#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 external call, 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.

<Callout type="caution" title="Important">
  Changing `subwallet_id` creates a different wallet address. Funds are not shared across subwallets even if they use the same public key.
</Callout>

<Callout type="note" title="<>Wallet V5 and <code>wallet_id</code></>">
  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](/llms/standard/wallets/v5/content.md) for the exact scheme.
</Callout>
