Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion standard/wallets/how-it-works.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,18 @@ It also makes retries safe: a client may re-send the same signed message until t

`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.

By varying `subwallet_id`, you can deploy multiple independent wallet contracts controlled by the same public key, each with its own address and `seqno`. This is useful for separating accounts, environments, or business lines while keeping a single keypair.
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.

<Aside
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.
</Aside>

<Aside
type="note"
title="Wallet V5 and `wallet_id`"
>
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 Wallet V5 it returns this derived `wallet_id`. See the [Wallet V5 overview](/standard/wallets/v5#wallet-id-scheme) for the exact scheme.
Comment on lines +68 to +72

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Stub “See …” sentence in Wallet V5 aside

The Wallet V5 aside ends with a standalone sentence, “See the Wallet V5 overview for the exact scheme.” This is a “stub” sentence whose sole purpose is to tell the reader to “See …” a link, which the style guide explicitly forbids in running prose. The guideline requires integrating the link naturally into the sentence instead of wrapping it in meta text like “See …”. Rephrasing the sentence keeps the content intact while aligning with the documented link-text and stub-sentence style rule.

Please leave a reaction 👍/👎 to this suggestion to improve future reviews for everyone!

</Aside>
59 changes: 35 additions & 24 deletions standard/wallets/interact.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,50 @@ This means that the mnemonic alone does not determine an address uniquely: `wall

For Wallet V4R2 `wallet_id` is defined as first 4 bytes from TON mainnet blockchain initial state hash. There is no specific logic why this number was chosen, community needed some default value and this one works well enough.

For Wallet V5, `wallet_id` is different between Mainnet and Testnet for security reasons. There will be different wallet addresses for different networks with the same mnemonic. This prevents replaying transactions from testnet on mainnet, and ensuing loss of funds. The `wallet_id` value is obtained from
For Wallet V5, `wallet_id` is different between mainnet and testnet for security reasons. Wallet addresses for the same mnemonic differ across networks. This prevents replaying transactions from testnet on mainnet and ensuing loss of funds. The `wallet_id` value is obtained from

- `network_id`: `-239` for mainnet, `-3` for testnet,
- `wallet_version`: currently always `0`,
- `subwallet_number`: `0` by default,
- `workchain`: `0` for basechain
- `network_id`: `-239` for mainnet, `-3` for testnet;
- `wallet_version`: currently always `0`;
- `subwallet_number`: `0` by default;
- `workchain`: `0` for basechain;

by the following algorithm:
using the following derivation scheme (client context):

```ts
type WalletIdV5 = {
// currently always 0
readonly walletVersion: number;
// -239 for mainnet, -3 for testnet
readonly networkGlobalId: number;
// 0 for basechain
readonly workchain: number;
// 0 for the first wallet with this mnemonic
readonly subwalletNumber: number;
}
import { beginCell, Builder } from '@ton/core';

type WalletIdV5ClientContext = {
// -239 for mainnet, -3 for testnet
readonly networkGlobalId: number;
// 0 for basechain
readonly workchain: number;
// currently always 0 for V5R1
readonly walletVersion: number;
// 0 for the first wallet with this mnemonic
readonly subwalletNumber: number; // 0..2^15-1
};

export function storeWalletIdV5(walletId: WalletIdV5) {
return (builder: Builder) => {
builder.storeInt(walletId.networkGlobalId, 32);
builder.storeInt(walletId.workchain, 8);
builder.storeUint(walletId.walletVersion, 8);
builder.storeUint(walletId.subwalletNumber, 32);
}
export function storeWalletIdV5R1(ctx: WalletIdV5ClientContext) {
return (builder: Builder) => {
// context_id_client$1 = wc:int8 wallet_version:uint8 counter:uint15
const context = beginCell()
.storeUint(1, 1)
.storeInt(ctx.workchain, 8)
.storeUint(ctx.walletVersion, 8)
.storeUint(ctx.subwalletNumber, 15)
.endCell()
.beginParse()
.loadInt(32);

const walletId = ctx.networkGlobalId ^ context;

builder.storeInt(walletId, 32);
};
}
```

<Aside type="note">
Algorithm here is presented for educational purposes, in most cases there is no need to reimplement it. Use the existing implementation from TON SDKs instead.
This code mirrors the official `WalletV5R1WalletId` implementation in the [`@ton/ton`](https://github.com/ton-org/ton) SDK and is shown for educational purposes. In production code, use the SDK implementation directly instead of reimplementing it.
</Aside>

### Examples
Expand Down
4 changes: 2 additions & 2 deletions standard/wallets/v5-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -411,14 +411,14 @@ export function createWalletTransferV5R1<T extends WalletV5SendArgs>(


// now we store common part for both messages
signingMessage.storeUint(args.timeout || Math.floor(Date.now() / 1e3) + 60, 32); // Default timeout: 60 seconds
signingMessage.storeUint(args.validUntil, 32);

signingMessage
.storeUint(args.seqno, 32)
.store(storeOutListExtendedV5(args.actions));

// now we need to sign message
const signature = sign(signingMessage.endCell().hash(), args.secretKey);
const signature = sign(signingMessage.endCell().hash(), args.privateKey);

return beginCell().storeBuilder(signingMessage).storeBuffer(signature).endCell();
}
Expand Down
31 changes: 28 additions & 3 deletions standard/wallets/v5.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,27 @@ contract_state$_

- `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 (equivalent to subwallet\_id in previous versions).
- `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).

As you can see, the `ContractState`, compared to previous versions, hasn't changed much. The main difference is the new `is_signature_allowed` 1-bit flag, which restricts or allows access through the signature and stored public key. We will describe the importance of this change in later topics.
## 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 described above rather than a plain `subwallet_id`.

## Message layout

Expand Down Expand Up @@ -72,6 +88,15 @@ We can consider `InnerRequest` as two lists of actions: the first, `OutList`, is

Learn more about actions [here](/standard/wallets/v5-api).

### 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` (see `verify_c5_actions` in `wallet_v5.fc`):

- 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 messages, `send_mode` must have the `+2` (“ignore errors”) bit set; otherwise exit code `137` is 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 |
Expand Down Expand Up @@ -104,7 +129,7 @@ Learn more about actions [here](/standard/wallets/v5-api).

1. `int is_signature_allowed()` returns stored `is_signature_allowed` flag.
1. `int seqno()` returns current stored seqno.
1. `int get_wallet_id()` returns current wallet ID.
1. `int get_subwallet_id()` returns current `wallet_id` value (for Wallet V5 this is the derived identifier described in the wallet ID scheme).
1. `int get_public_key()` returns current stored public key.
1. `cell get_extensions()` returns extensions dictionary.

Expand Down
Loading