Skip to main content

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.

A vanity contract allows customization of the address of a smart contract being deployed. It does this by making its own StateInit depend on constant data that is randomly generated many times until a desired address is found. It is often used to deploy contracts with a specific prefix or suffix so the address is visible in block explorers. The contract code and data are included in the vanity deploy message. The vanity contract is first deployed with a StateInit that produces the desired address, and then immediately sets its actual state from the payload. This is a special case of upgrading contract’s code.

Prerequisites

How it works

The vanity contract code:
Tolk
struct VanityStorage {
    padding: uint5
    owner: address
    salt: uint256
}

struct DeployPayload {
    code: cell
    data: cell
}

fun onInternalMessage(in: InMessage) {
    val storage = lazy VanityStorage.fromCell(contract.getData());

    // Read sender address
    val sender = in.senderAddress;

    // Allow deployment only to the owner
    assert (sender == storage.owner) throw 100;

    // Set code and data
    val payload = lazy DeployPayload.fromSlice(in.body);
    contract.setCodePostponed(payload.code);
    contract.setData(payload.data);
}
It loads the owner from the typed VanityStorage, checks whether the message comes from this owner, then loads DeployPayload and replaces the contract’s code and data with the cells from the incoming message. In a vanity contract, a salt is a 256-bit value stored in StateInit along with five padding bits and the owner address. The contract declares this layout in VanityStorage, but only the owner field is needed at runtime. The salt does not affect runtime behavior and only influences the resulting address via the StateInit hash. The owner field is required because someone could intercept an external message, extract the salt, and deploy their own contract with the same salt concurrently. Since the owner value also unpredictably affects the address, the intercepted salt is useless unless the attacker can send the message from the same owner address. Because a contract address is derived from the StateInit hash, changing the salt changes the address deterministically. The search for a suitable salt happens entirely off-chain: a Python script with an OpenCL kernel for speed generates many random salt values, computes the resulting address, and reports matches. The on-chain vanity contract does not brute-force salts; it only verifies the owner and then applies the provided code and data when deployed.

Generate salt

To generate the salt, copy the code from src/generator in the same repository. It includes the run.py script and the vanity.cl OpenCL kernel. Run the command with the desired search parameters, including -w for the workchain and the owner address allowed to perform the deployment. The example below searches on the basechain for the specified suffix.
python3 run.py -w 0 --end '<SUFFIX>' --case-sensitive <OWNER_ADDR>
Where:
  • <SUFFIX> – desired address suffix; case sensitive when --case-sensitive is set.
  • <OWNER_ADDR> – address allowed to deploy via the vanity contract.
After running, the script prints logs and starts the search, printing every found salt. It also writes found salt to the found.txt file. The search continues until it is stopped or exits after the first match when --only-one is set.
Example output
Searching wallets case-sensitive, with "TEST" in the end
Owner:  UQCSQnz9h3iilIHMueOPs8RaryGqzb-bJpReZuZAUsm6TDRo
Flags:  1100
Kernel conditions: result[44] == 'T' && result[45] == 'E' && result[46] == 'S' && result[47] == 'T'

Using device:  Apple M2 Max
Speed: 198 Mh/s, miss: 4, found: 0
Speed: 204 Mh/s, miss: 2, found: 0
Found:  EQBas7IlwGKmd6CT7_l0PLynkUv2fmrANn2FFgcMntBATEST salt:  1045adb4ffb9af72021354a07a6f3e64ebc9822775f80b7d98beb195f57093df
Speed: 207 Mh/s, miss: 1, found: 1
Speed: 206 Mh/s, miss: 4, found: 1
Found:  EQB1p467NtIyNpwVAF0qZYDCaXzA56mk8P6nqt6QJFeQTEST salt:  fa683a39082696af7bafecaa63f6172b615f5b7d89fea24c941d52aa3310bbc3
Speed: 208 Mh/s, miss: 0, found: 2
Speed: 205 Mh/s, miss: 2, found: 2
Speed: 208 Mh/s, miss: 2, found: 2
Found:  EQBXaec9-r5Ge65hXTQopw7akH6LQr4rms9DdzkhxcUiTEST salt:  e7336b387099b3f8a31fa114ff801b799f14f3fe7f6c27c6cf0ccbb542ab743d
Speed: 206 Mh/s, miss: 2, found: 3
Speed: 203 Mh/s, miss: 2, found: 3
Speed: 203 Mh/s, miss: 3, found: 3
The more specific the search, the rarer the matches, and the more compute is required to find one. A 4-character match typically appears in a few seconds on a laptop. TON user-friendly addresses are base64, so each character encodes 6 bits; four characters correspond to 24 bits, i.e., about 1 in 224 trials on average. Once a salt is found, it can be used to deploy an arbitrary smart contract at that address.

Deploy the contract

Deploy of the vanity contract and the message that replaces its code and data usually come in a single message:
init:
    code: vanity contract code
    data:
        owner: owner's address
        salt: generated salt
body:
    code: new contract's code
    data: new contract's data
This example uses Blueprint to create and send this message. Define a vanity contract wrapper at wrappers/VanityContract.ts:
TypeScript
import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';

export type VanityContractConfig = {
    owner: Address;
    salt: Buffer;
};

export function vanityContractConfigToCell(
    config: VanityContractConfig
): Cell {
    return beginCell()
        .storeUint(0, 5)
        .storeAddress(config.owner)
        .storeBuffer(config.salt, 32)
        .endCell();
}

// from https://github.com/ton-community/vanity-contract/blob/6baeb39500de0fee79bd241047699ca65ee71f55/src/contract/vanity-address.cell
const vanityCode = Cell.fromBoc(
    Buffer.from(
        'b5ee9c72010102010032000114ff00f4a413f4bcf2c80b010046d3ed44d075d721fa408307d721d102d0d30331fa403058c705f288d4d4d101fb04ed54',
        'hex',
    ),
)[0];

export class VanityContract implements Contract {
    constructor(
        readonly address: Address,
        readonly init?: { code: Cell; data: Cell },
    ) {}

    static createFromAddress(address: Address) {
        return new VanityContract(address);
    }

    static createFromConfig(
        config: VanityContractConfig,
        workchain = 0
    ) {
        const data = vanityContractConfigToCell(config);
        const init = {
            code: vanityCode,
            data,
        };
        return new VanityContract(
            contractAddress(workchain, init),
            init,
        );
    }

    async sendDeploy(
        provider: ContractProvider,
        via: Sender,
        value: bigint,
        newCode: Cell,
        newData: Cell,
    ) {
        const body = beginCell()
            .storeRef(newCode)
            .storeRef(newData)
            .endCell();
        await provider.internal(via, {
            value,
            sendMode: SendMode.PAY_GAS_SEPARATELY,
            body,
        });
    }
}
Create scripts/deployExampleContract.ts:
TypeScript
import { toNano, Address } from '@ton/core';
import { ExampleContract } from '../wrappers/ExampleContract';
import { VanityContract } from '../wrappers/VanityContract';
import { compile, NetworkProvider } from '@ton/blueprint';

export async function run(provider: NetworkProvider) {
    const vanityContract = provider.open(
        VanityContract.createFromConfig({
            owner: Address.parse('<OWNER_ADDR>'),
            salt: Buffer.from('<SALT_HEX>', 'hex'),
        }),
    );

    const exampleContract = provider.open(
        ExampleContract.createFromConfig(
            {},
            await compile('ExampleContract'),
        ),
    );

    const init = exampleContract.init!;

    await vanityContract.sendDeploy(
        provider.sender(),
        toNano('0.01'), // attach value for deployment fees
        init.code,
        init.data,
    );

    await provider.waitForDeploy(vanityContract.address);
}
Where:
  • <OWNER_ADDR> – address allowed to deploy via the vanity contract.
  • <SALT_HEX> – 32-byte salt in hex found by the generator.
Run the script via npx blueprint run. The deployment succeeds when <OWNER_ADDR> matches the address of the wallet used for actual deployment. ExampleContract can be replaced with any contract; the vanity contract does not depend on the specifics of the code or data.