Highload wallet contracts
When working with many messages in a short period of time, there is a need for a special wallet called a Highload wallet. Highload wallet v2 was the main wallet on TON for a long time, but you had to be very careful with it. Otherwise, you could lock all funds.
With the advent of Highload wallet v3, this problem has been solved at the contract-architecture level, and the contract consumes less gas. This chapter covers the basics of Highload wallet v3 and important nuances to remember.
Highload wallet v3
This wallet is made for those who need to send transactions at very high rates (e.g., crypto exchanges).
Any given external message (transfer request) to a Highload wallet v3 contains:
- a signature (512 bits) in the top-level cell; the other parameters are stored in a reference of that cell
- subwallet ID (32 bits)
- message to send as a reference (the serialized internal message that will be sent)
- send mode for the message (8 bits) composite query ID - 13 bits of "shift" and 10 bits of "bit number", however the 10 bits of bit number can only go up to 1022, not 1023, and also the last such usable query ID (8388605) is reserved for emergencies and should not be normally used
- created_at (message timestamp)
- timeout
Timeout is stored in the Highload wallet as a parameter and is checked against the timeout in all requests—so the timeout for all requests is equal. The message should not be older than the timeout at the time of arrival to the Highload wallet, or in code it is required that created_at > now() - timeout
. Query IDs are stored for the purposes of replay protection for at least timeout and possibly up to 2 * timeout; however, one should not expect them to be stored for longer than timeout. The subwallet ID is checked against the one stored in the wallet. The inner reference’s hash is checked along with the signature against the public key of the wallet.
Highload v3 can only send 1 message from any given external message, however it can send that message to itself with a special op code, allowing one to set any action cell on that internal message invocation, effectively making it possible to send up to 254 messages per 1 external message (possibly more if another message is sent to Highload wallet again among these 254).
Highload wallet v3 will always store the query ID (replay protection) once all the checks pass; however, a message may not be sent due to some conditions, including but not limited to:
- containing state init (such messages, if required, may be sent using the special op code to set the action cell after an internal message from Highload wallet to itself)
- not enough balance
- invalid message structure (that includes external-out messages — only internal messages may be sent straight from the external message)
Highload wallet v3 will never execute multiple external messages containing the same query_id
and created_at
— by the time it forgets any given query_id
, the created_at
condition will prevent such a message from executing. This effectively makes query_id
and created_at
together the "primary key" of a transfer request for Highload wallet v3.
When iterating (incrementing) query ID, it is cheaper (in terms of TON spent on fees) to iterate through bit number first, and then the shift, like when incrementing a regular number. After you've reached the last query ID (remember the emergency query ID — see above), you can reset query ID to 0, but if the Highload wallet's timeout period has not passed yet, then the replay protection dictionary will be full and you will have to wait for the timeout period to pass.
Highload wallet v2
Legacy contract, it is suggested to use Highload wallet v3.
This wallet is made for those who need to send hundreds of transactions in a short period of time. For example, crypto exchanges.
It allows you to send up to 254
transactions in one smart contract call. It also uses a slightly different approach to solve replay attacks instead of seqno, so you can call this wallet several times at once to send even thousands of transactions in a second.
Note: when dealing with the Highload wallet, the following limits must be considered.
- Storage size limit. The size of the contract storage must be less than 65,535 cells; if the size of
old_queries
grows beyond this limit, an exception will be thrown during the action phase and the transaction will fail, and the failed transaction may be replayed. - Gas limit. The gas limit is 1'000'000 gas units, which limits how many old queries can be cleaned in one transaction; if the number of expired queries is higher, the contract may become stuck.
It is not recommended to set too high an expiration date: the number of queries within the expiration window should not exceed 1,000, and the number of expired queries cleaned in one transaction should be no more than 100.
How to
You can also read Highload wallet tutorials
Wallet source code: