> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ton.org/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/standard/wallets/v5",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Wallet V5

export const Image = ({src, darkSrc, alt = '', darkAlt, href, target, height = 342, width = 608, noZoom = false, center = false}) => {
  const isSVG = src.match(/\.svg(?:[#?].*?)?$/i) !== null;
  const shouldInvert = isSVG && !darkSrc;
  const shouldCreateLink = href !== undefined;
  const minPx = 9;
  const maxPx = 608;
  const expectedPx = `a number or a string with a number that is greater than ${minPx - 1} and less than or equal to ${maxPx}`;
  const createInvalidPropCallout = (title, received, expected) => {
    return <Danger>
        <span className="font-bold">
          Invalid <code>{title.toString()}</code> passed!
        </span>
        <br />
        <span className="font-bold">Received: </span>
        {received.toString()}
        <br />
        <span className="font-bold">Expected: </span>
        {expected.toString()}
        {}
      </Danger>;
  };
  const checkValidDimensionValue = value => {
    switch (typeof value) {
      case "string":
      case "number":
        const num = Number(value);
        return Number.isSafeInteger(num) && num >= minPx && num <= maxPx;
      default:
        return false;
    }
  };
  let callouts = [];
  if (height && !checkValidDimensionValue(height)) {
    callouts.push(createInvalidPropCallout("height", height, expectedPx));
  }
  if (width && !checkValidDimensionValue(width)) {
    callouts.push(createInvalidPropCallout("width", width, expectedPx));
  }
  if (callouts.length !== 0) {
    return callouts;
  }
  const heightPx = Number(height);
  const widthPx = Number(width);
  const shouldCenter = center === "true" || center === true ? true : false;
  const shouldNotZoom = noZoom === "true" || noZoom === true ? true : false;
  const images = <>
      <img className="block dark:hidden" src={src} alt={alt} {...height && ({
    height: heightPx
  })} {...width && ({
    width: widthPx
  })} {...(shouldCreateLink || shouldInvert || shouldNotZoom) && ({
    noZoom: "true"
  })} />
      <img className={`hidden dark:block ${shouldInvert ? "invert" : ""}`} src={darkSrc ?? src} alt={darkAlt ?? alt} {...height && ({
    height: heightPx
  })} {...width && ({
    width: widthPx
  })} {...(shouldCreateLink || shouldInvert || shouldNotZoom) && ({
    noZoom: "true"
  })} />
    </>;
  if (shouldCreateLink) {
    if (shouldCenter) {
      return <div style={{
        display: "flex",
        justifyContent: "center"
      }}>
          <a href={href} target={target ?? "_self"}>
            {images}
          </a>
        </div>;
    }
    return <a href={href} target={target ?? "_self"}>
        {images}
      </a>;
  }
  if (shouldCenter) {
    return <div style={{
      display: "flex",
      justifyContent: "center"
    }}>{images}</div>;
  }
  return images;
};

The V5 wallet standard offers many benefits that improve the experience for both users and developers. V5 supports gasless transactions, account delegation and recovery, subscription payments using Jettons and Toncoin, and low-cost multi-transfers. In addition to retaining the previous functionality (V4), the new contract allows you to send up to 255 messages at a time.

This article provides only a high-level wallet V5 contract overview. If you want to learn more about how to use its interfaces or develop extensions, check out the [wallet V5 API](/standard/wallets/v5-api) article.

Source links:

* [Wallet V5 source code](https://github.com/ton-blockchain/wallet-contract-v5).
* [TL-B scheme for V5](https://github.com/ton-blockchain/wallet-contract-v5/blob/321186127e8cc5e395ad3b2f1870839237c56f5f/types.tlb)

## Persistent memory layout

```tlb theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
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.
* `wallet_id`: 32-bit wallet ID (equivalent to subwallet\_id in previous versions).
* `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.

## Message layout

### External message body layout

* `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 actual actions to perform.
* `signature`: 512-bit long [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) signature.

### Internal message body layout

Learn more about internal message serialization [here](/standard/wallets/v5-api).

## Authentication process

```tlb theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
internal_signed#73696e74
  signed:SignedRequest = InternalMsgBody;

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

Before we get to the actual payload of our messages — `InnerRequest` — let's first look at how version 5 differs from previous versions in the authentication process. The `InternalMsgBody` combinator describes two ways to access wallet actions through internal messages. The first method is one we are already familiar with from version 4: authentication as a previously registered extension, the address of which is stored in `extensions_dict`. The second method is authentication through the stored public key and signature, similar to external requests.

At first, this might seem like an unnecessary feature, but it actually enables requests to be processed through external services (smart contracts) that are not part of your wallet's extension infrastructure — a key feature of V5. Gasless transactions rely on this functionality.

Any received internal message that doesn't pass the authentication process will be considered a transfer.

## Actions

The first thing that we should notice is `InnerRequest`, which we have already seen in the authentication process. In contrast to the previous version, both external and internal messages have access to the same functionality, except for changing the signature mode (i.e., the `is_signature_allowed` flag).

We can consider `InnerRequest` as two lists of actions: the first, `OutList`, is an optional chain of cell references, each containing a send message request led by the message mode. The second, `ActionList`, is led by a one-bit flag, `has_other_actions`, which marks the presence of extended actions, starting from the first cell and continuing as a chain of cell references. We are already familiar with the first two extended actions, `action_add_ext` and `action_delete_ext`, followed by the internal address that we want to add or delete from the extensions dictionary. The third, `action_set_signature_auth_allowed`, restricts or allows authentication through the public key, leaving the only way to interact with the wallet through extensions. This functionality might be extremely important in the case of a lost or compromised private key.

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

## 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       | Enforce that `send_mode` has the +2 bit (ignore errors) set for external messages. |
| 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 through external message                            |
| 147       | Invalid `c5`, `action_send_msg` verification failed                                |
| 0         | Standard successful execution exit code.                                           |

<Aside type="caution">
  Note that the `142`, `144`, and `146` wallet exit codes are designed to prevent you from losing access to wallet functionality. Nevertheless, you should still remember that the wallet doesn't check whether the stored extension addresses actually exist in TON. You can also deploy a wallet with initial data consisting of an empty extensions dictionary and restricted signature mode. In that case, you will still be able to access the wallet through the public key until you add your first extension. So, be careful with these scenarios.
</Aside>

## Get methods

1. `int is_signature_allowed()` returns stored `is_signature_allowed` flag.
2. `int seqno()` returns current stored seqno.
3. `int get_wallet_id()` returns current wallet ID.
4. `int get_public_key()` returns current stored public key.
5. `cell get_extensions()` returns extensions dictionary.

## Gasless transactions

Starting with v5, the wallet smart contract supports owner-signed internal messages (`internal_signed`), which enables gasless transactions—for example, paying network fees in USDT when transferring USDT. Gasless transactions are supported not at the network protocol level, meaning that to pay fees in USDT it is necessary to rely on some off-chain infrastructure, e.g., like in Tonkeeper.

Flow scheme for Tonkeeper gasless transactions looks like this:

<Image src="/resources/images/wallets/gasless_light.png" darkSrc="/resources/images/wallets/gasless_dark.png" alt="gasless" />

### 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 Toncoin for network fees.

The beta version of the gasless backend API is available at [`tonapi.io/api-v2`](https://tonapi.io/api-v2). If you are developing a wallet app and have feedback about these methods, please share it in [`@tonapitech`](https://t.me/tonapitech) chat.
