Deploying Smart Contracts to TON
TON is not EVM. This is a fundamentally different architecture, and Ethereum deployment experience does not transfer here. Contracts are written in FunC (or Tact — higher-level language), compiled to TVM bytecode, and interact via asynchronous messages. Synchronous call contract.someFunction() does not exist — only sending a message and waiting for a response message.
Language and Tooling
FunC vs Tact
FunC — low-level TON language. Direct control over TVM instructions, storage layout, gas. Complex to learn, but all optimizations are in your hands. Standard contracts (Jetton, NFT) written in FunC.
Tact — higher-level, syntax closer to TypeScript. Compiles to FunC → TVM bytecode. Faster to develop, less chance of storage serialization errors. For new contracts — recommended choice if no specific optimization requirements.
// Simple counter on Tact
contract Counter {
counter: Int as uint32;
init() {
self.counter = 0;
}
receive("increment") {
self.counter = self.counter + 1;
}
get fun value(): Int {
return self.counter;
}
}
Equivalent contract in FunC would take three times more lines with explicit storage management via store_uint / load_uint.
Blueprint: Standard Toolchain
Blueprint — official TON contract development framework (analog of Hardhat for EVM):
npm create ton@latest my-project
cd my-project
npm install
Project structure:
contracts/ # .tact or .fc files
tests/ # Jest-based tests
scripts/ # deploy scripts
wrappers/ # TypeScript wrappers for contracts
Compilation and Tests
npx blueprint build # compilation
npx blueprint test # run tests
Tests run on local TVM emulator (@ton/sandbox) — transactions execute locally without network access, tests are fast:
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { Counter } from '../wrappers/Counter';
describe('Counter', () => {
let blockchain: Blockchain;
let deployer: SandboxContract<TreasuryContract>;
let counter: SandboxContract<Counter>;
beforeEach(async () => {
blockchain = await Blockchain.create();
deployer = await blockchain.treasury('deployer');
counter = blockchain.openContract(await Counter.fromInit());
const deployResult = await counter.send(
deployer.getSender(),
{ value: toNano('0.05') },
{ $$type: 'Deploy', queryId: 0n }
);
expect(deployResult.transactions).toHaveTransaction({
from: deployer.address,
to: counter.address,
deploy: true,
});
});
it('should increment counter', async () => {
await counter.send(deployer.getSender(), { value: toNano('0.01') }, 'increment');
expect(await counter.getValue()).toBe(1n);
});
});
Deployment to Testnet and Mainnet
TON uses StateInit concept for deployment: contract is deployed by sending message with init (code + initial data) to contract address. Address is computed deterministically from hash(code, data) — you know address before deployment.
// scripts/deployCounter.ts
import { toNano } from '@ton/core';
import { Counter } from '../wrappers/Counter';
import { NetworkProvider } from '@ton/blueprint';
export async function run(provider: NetworkProvider) {
const counter = provider.open(await Counter.fromInit());
await counter.send(
provider.sender(),
{ value: toNano('0.05') }, // TON for gas + storage rent
{ $$type: 'Deploy', queryId: 0n }
);
await provider.waitForDeploy(counter.address);
console.log('Deployed at:', counter.address.toString());
}
# Testnet
npx blueprint run --testnet
# Mainnet
npx blueprint run --mainnet
Blueprint will prompt for seed phrase or Tonkeeper connection via QR.
Storage Rent — Important TON Specifics
Unlike Ethereum, TON charges rent for data storage. Contract with zero balance will be "frozen" after ~6 months, and data deleted in years (uninit state). For production contracts you need to:
- Allocate sufficient balance on deployment (for simple contract — 0.1-0.5 TON)
- Implement mechanism to replenish balance
- Monitor balance via TonAPI
Approximate storage cost: 1 bit of data per year = 0.000000001 TON × 365 × ... Practically: contract with 1KB storage costs ~0.015 TON/year.
Jetton (ERC-20 Analog) and NFT
Standard tokens in TON implemented via template contracts TEP-74 (Jetton) and TEP-62 (NFT). Architecture radically differs from ERC-20:
In ERC-20 one contract stores mapping of all balances. In TON each holder has own separate contract — JettonWallet. Main JettonMaster contract only mints tokens and stores global info. Transfer — message from sender's JettonWallet to recipient's JettonWallet.
This means: address of Jetton wallet for specific holder must be computed via getWalletAddress(ownerAddress) on JettonMaster contract. Cannot simply use Jetton master address for transfers.
Blueprint provides ready wrappers for standard Jetton and NFT contracts from TON Foundation — no need to write from scratch.
Contract Verification
TON verifier — verifier.ton.org. Upload sources and proof-of-compilation, verifier confirms bytecode match. After verification tonviewer.com and tonscan.org show source code and allow calling getter functions.
Timeline Estimates
Deploying ready Tact/FunC contract to testnet + mainnet with verification — from several hours. Developing custom contract from scratch + tests + deployment: simple contract — 1-2 days, contract with complex business logic (AMM, staking, NFT marketplace) — 1-2+ weeks.







