Standard wallets reject transactions if the seqno has already been used. If you attempt to send multiple transfers in rapid succession, the second one might fail because it uses the same on-chain seqno.
To solve this, wrap your wallet in a SeqnoGuard. The guard waits for the on-chain seqno to advance after each successful send before allowing the next transaction to proceed.
Configuration:
timeout: Maximum seconds to wait for the seqno to advance (default: 30.0).poll_interval: Delay between seqno polls in seconds (default: 1.5).
SeqnoGuard provides transfer() and transfer_message() methods that mirror the signatures of the underlying wallet.
from ton_core import Address, NetworkGlobalID, to_nano
from tonutils.clients import ToncenterClient
from tonutils.contracts import SeqnoGuard, TONTransferBuilder, WalletV4R2
MNEMONIC = "word1 word2 word3 ..."
DESTINATION_1 = Address("UQ...")
DESTINATION_2 = Address("UQ...")
DESTINATION_3 = Address("UQ...")
async def main() -> None:
client = ToncenterClient(network=NetworkGlobalID.MAINNET)
await client.connect()
wallet, _, _, _ = WalletV4R2.from_mnemonic(client, MNEMONIC)
# Wrap wallet in SeqnoGuard for safe sequential sends
guard = SeqnoGuard(wallet, timeout=30.0, poll_interval=1.0)
# Each transfer is confirmed before the next one starts
msg1 = await guard.transfer(
destination=DESTINATION_1,
amount=to_nano(0.01),
body="First transfer",
)
print(f"Transaction hash: {msg1.normalized_hash}")
msg2 = await guard.transfer(
destination=DESTINATION_2,
amount=to_nano(0.02),
body="Second transfer",
)
print(f"Transaction hash: {msg2.normalized_hash}")
# transfer_message() also works with builders
msg3 = await guard.transfer_message(
TONTransferBuilder(
destination=DESTINATION_3,
amount=to_nano(0.03),
body="Third transfer via builder",
)
)
print(f"Transaction hash: {msg3.normalized_hash}")
await client.close()
if __name__ == "__main__":
import asyncio
asyncio.run(main())