tonutils library. It covers key operations such as:
- Creating and deploying wallets
- Importing wallets from mnemonic or private key
- Reading on-chain wallet information
- Sending transactions (TON, NFTs, jettons)
- Performing batch and sequential transfers
- Sending gasless (relayed) jetton transfers
- Rotating the mnemonic of a Telegram Wallet
- Encrypting and decrypting on-chain messages
Supported Wallets
The library supports multiple wallet versions and types, all importable fromtonutils.contracts:
Create Wallet
To create a new wallet, use the.create() classmethod of the wallet class you select. It returns a 4-tuple: the wallet instance, its public key, private key, and a freshly generated mnemonic phrase. Each wallet class takes an optional config= argument (WalletV4Config, WalletV5Config, etc. from ton_core), where you can customize subwallet_id to derive multiple wallets from the same mnemonic.
By default .create() generates a 24-word TON mnemonic. Without mnemonic_type, mnemonic_length=12 gives a Multichain (BIP-39) mnemonic and a longer one a TON mnemonic; pass mnemonic_type to choose the scheme explicitly, for example a 24-word Multichain mnemonic. WalletTg always generates a 12-word Multichain mnemonic, the format used by Telegram Wallet.
To use a different wallet version, swap the class and its config together: for example
WalletV5R1 with WalletV5Config, or WalletHighloadV3R1 with WalletHighloadV3Config. Configs are imported from ton_core.Import Wallet
You can import a wallet either from a mnemonic phrase or directly from a private key.from_mnemonic() returns the same 4-tuple as .create(); from_private_key() returns only the wallet instance.
From Mnemonic
Both mnemonic schemes are supported: TON mnemonics and Multichain (BIP-39) mnemonics. The scheme is detected by checksum, and a phrase valid in both schemes uses the wallet default; to set it explicitly, passmnemonic_type=MnemonicType.TON or mnemonic_type=MnemonicType.MULTICHAIN. WalletTg uses the rotation mnemonic format: 12 words, or 24 after a key rotation; see Telegram Wallet.
From Private Key
PrivateKey from ton_core accepts the key in multiple formats: raw bytes, a hex string, a base64 string, or an integer.
Deploy Wallet
Wallets deploy automatically on their first outgoing transaction. To deploy explicitly, reconstruct the wallet from a mnemonic and send a small amount to its own address — after the transaction, the wallet transitions fromuninit to active status.
wallet.transfer(...) returns an ExternalMessage. Its normalized_hash is computed locally before sending — it is not the on-chain transaction hash. Use it to track whether the message was accepted on-chain via explorers or API queries.Wallet Information
To inspect a wallet without signing anything, initialize it from an address with.from_address() (read-only mode). Call await wallet.refresh() to fetch the latest on-chain state before reading properties.
Transfers
Send TON
Usewallet.transfer() to send TON with an optional text comment.
The recipient can be an address or a .ton / .t.me domain; a domain resolves to its wallet record and is sent non-bounceable. The same applies to destination of TONTransferBuilder, JettonTransferBuilder and NFTTransferBuilder.
destination— recipient address (Address).amount— amount in nanotons; useto_nano()to convert from TON (int).body— optional text comment, visible in explorers and the recipient wallet (strorCell).
Send NFT
NFT transfers useNFTTransferBuilder with wallet.transfer_message(). The sending wallet must be the current owner of the NFT.
destination— new owner address (Address).nft_address— NFT item contract address (Address).forward_payload— optional message forwarded to the new owner, visible in the notification (strorCell).forward_amount— nanotons sent to the new owner with theownership_assignednotification (TEP-62); must be greater than 0 to trigger the notification (int).amount— TON attached to the NFT item for gas fees; 0.05 TON is typically sufficient, increase it ifforward_amountis higher (int).
Send Jetton
Jetton transfers useJettonTransferBuilder with wallet.transfer_message(). The wallet must hold a jetton balance in its jetton wallet.
destination— recipient address that receives the jettons (Address).jetton_amount— amount in base units, respecting the token’s decimals (int).jetton_master_address— jetton master contract address identifying the token (Address).forward_payload— optional message forwarded to the recipient, visible in the notification (strorCell).forward_amount— nanotons sent to the recipient with thetransfer_notification(TEP-74); must be greater than 0 to trigger the notification (int).amount— TON attached to the jetton wallet for gas fees; 0.05 TON is typically sufficient (int).
Jetton amounts respect the token’s decimals, which are not always 9. For USDT-like jettons with 6 decimals, convert with
to_nano(1, decimals=6).Batch Transfers
Send multiple transfers in a single transaction by passing a list of builders towallet.batch_transfer_message(). You can mix TONTransferBuilder, NFTTransferBuilder, and JettonTransferBuilder in one batch.
Message limits per wallet version are listed in Supported Wallets; exceeding the limit raises an error.
Sequential Transfers
Standard wallets reject a transaction whose seqno has already been used. If you send several transfers in quick succession, the second one can reuse the same on-chain seqno and fail. Wrap the wallet inSeqnoGuard to send safely in sequence — it waits for the on-chain seqno to advance after each send before allowing the next.
Gasless Transfers
Gasless transfers let a wallet send jettons without holding TON for gas: a relay pays the TON fees, and its commission is deducted from the sender’s jetton balance. The flow is two steps —gasless_estimate() to build and price the transfer, then gasless_send() to sign and relay it.
Gasless transfers require TonapiClient on mainnet and are only available for WalletV5 (WalletV5R1 and WalletV5Beta).
A Tonapi API key is required — get one at tonconsole.com. Always review
estimate.commission, estimate.valid_until, and the emulation result before calling gasless_send(); once sent, the transaction is irreversible.Telegram Wallet
WalletTg is the wallet contract used by Telegram. Its account code is a small immutable trampoline that runs shared logic stored in the blockchain config, so every WalletTg uses the same latest contract version, updated by validator voting. It supports up to 255 messages per transaction and adds one capability the standard wallets do not have: rotating its signing key on-chain. Its default subwallet_id is network-specific and resolved automatically from the client’s network.
A Telegram Wallet mnemonic has 12 words, or 24 after a key rotation. Words 1–12 derive the address and never change; words 13–24 hold the current signing key. Each half is a separate 12-word Multichain (BIP-39) mnemonic. from_mnemonic() accepts both forms; TON mnemonics are not supported, open such a wallet with from_private_key(). With only the private key of a rotated wallet, open it by address: WalletTg(client, address, private_key=key).
Rotate Mnemonic
rotate_mnemonic() replaces the wallet’s on-chain signing key with the key from words 13–24 of a new mnemonic. The wallet address does not change: the balance and history stay in place.
Encrypted Messages
The library supports end-to-end encrypted comments using elliptic curve Diffie-Hellman (ECDH): only the sender and the recipient can decrypt the message.Encrypt
Passencrypt=True to transfer() (or to TONTransferBuilder in a batch) to send the text comment encrypted; JettonTransferBuilder and NFTTransferBuilder take the same flag for a text forward_payload. The recipient’s public key is fetched from its wallet via the get_public_key get-method; for an undeployed recipient, pass it as recipient_public_key.
encrypt— encrypt the textbodyfor the recipient (bool).recipient_public_key— recipient’s public key, orNoneto fetch it (PublicKey | None).
Decrypt
Decrypt an incoming encrypted comment withTextCipher.decrypt(). The sender’s public key is recovered from the encrypted payload (it is stored XORed with the recipient’s public key), so only the sender’s address and the recipient’s private key are needed.
payload— encrypted message asbytes, hex string, base64 string (cipher text only), or aCellcontaining the opcode0x2167DA4Band cipher text.sender_address— original sender’s address, used for verification (Address).our_private_key— recipient’s private key for ECDH decryption (PrivateKey).
Wallet Examples
Full runnable wallet example scripts on GitHub.