diff --git a/contract-dev/first-smart-contract.mdx b/contract-dev/first-smart-contract.mdx index 773cebc10..05b5d48ec 100644 --- a/contract-dev/first-smart-contract.mdx +++ b/contract-dev/first-smart-contract.mdx @@ -3,288 +3,263 @@ title: "Your first smart contract" --- import { Aside } from "/snippets/aside.jsx"; +import { FenceTable } from "/snippets/fence-table.jsx"; -Welcome to your journey into TON smart contract development! In this comprehensive tutorial, you'll learn to build, deploy, and interact with a smart contract from scratch. - -## What you'll learn - -By the end of this tutorial, you'll have: - -- ✅ **Built** a complete smart contract in Tolk -- ✅ **Deployed** it to TON testnet -- ✅ **Interacted** with it using TypeScript scripts -- ✅ **Mastered** the fundamentals of TON development - -## What is a TON smart contract? - -### Understanding the basics - -A **smart contract** is a computer program stored on TON Blockchain — a distributed database that many computers maintain together. It runs on the [TVM](/tvm/overview) (TON Virtual Machine) — the "computer" that runs smart contract code on TON. - -The contract is made of two parts: - -- **Code** (compiled TVM instructions) - the "rules" or "program logic" -- **Data** (persistent state) - the "memory" that remembers things between interactions - -Both are stored at a specific **address** on TON Blockchain, a unique identifier for each smart contract. +This tutorial covers building, deploying, and interacting with a smart contract on TON from start to finish. ## Prerequisites -- **Basic programming** - Understanding of variables, functions, if/else statements -- **Command line basics** - Comfortable opening terminal and running commands -- **Node.js** (v22 or later) — [Download here](https://nodejs.org/) +- Basic programming: variables, functions, if/else statements +- Basic familiarity with a command‑line interface and executing commands +- Node.js—`v22` or later— [download here](https://nodejs.org/) - Check if installed: `node -v` in terminal -- **TON wallet** +- Installed [TON wallet](/ecosystem/wallet-apps/web) with [Toncoin on testnet](/ecosystem/wallet-apps/get-coins) -## Tutorial overview +## Development environment -This tutorial is organized into six clear steps that build upon each other: + + + Use the [Blueprint](/contract-dev/blueprint/overview) development toolkit for smart contracts. Start a new project with: -| Step | What You'll Do | Key Skills | -| ------------------------------------------------------------------ | --------------------------- | ---------------------------------------------------- | -| **[Step 1](#step-1%3A-development-environment-setup)** | Set up Blueprint toolkit | Project structure, development environment | -| **[Step 2](#step-2%3A-understanding-smart-contract-architecture)** | Learn contract architecture | Storage, messages, getters concept | -| **[Step 3](#step-3%3A-writing-the-smart-contract)** | Write contract in Tolk | Programming, message handling, data structures | -| **[Step 4](#step-4%3A-compiling-your-contract)** | Compile to bytecode | Build process, TVM compilation | -| **[Step 5](#step-5%3A-deploying-to-testnet)** | Deploy to blockchain | Testnet deployment, wallet integration | -| **[Step 6](#step-6%3A-contract-interaction)** | Interact with contract | Message sending, get methods, TypeScript integration | + ```bash + npm create ton@latest -- Example --contractName FirstContract --type tolk-empty + ``` -Let's dive into development! + This command creates a project named "Example", containing a contract named "FirstContract". -## Step 1: Development environment setup + The generated project structure is: -We'll use [**Blueprint**](/contract-dev/blueprint/overview) as our development toolkit for smart contracts. Start a new project with: + + Example/ + ├──contracts/ # smart contract source code + │ └── first\_contract.tolk # main contract file + ├── scripts/ # deployment and on-chain interaction scripts + │ └── deployFirstContract.ts # script to deploy the contract + ├── tests/ # testing specifications + │ └── FirstContract.spec.ts # contract test file + ├── wrappers/ # TypeScript wrappers for contract interaction + │ ├── FirstContract.ts # wrapper class for the smart contract + │ └── FirstContract.compile.ts # configuration for compiling contract + -This will create a project **Example** with a contract **FirstContract**. - -The project structure will look like this: - -```text -Example/ -├── contracts/ # Smart contract source code -│ └── first_contract.tolk # Main contract file -├── scripts/ # Deployment and on-chain interaction scripts -│ └── deployFirstContract.ts # Script to deploy the contract -├── tests/ # Testing specifications -│ └── FirstContract.spec.ts # Contract test file -└── wrappers/ # TypeScript wrappers for contract interaction - ├── FirstContract.ts # Wrapper class for the smart contract - └── FirstContract.compile.ts # Configuration for compiling contract -``` + + - + + ```bash + cd Example + ``` + + -Now, move into the project directory: +## What is a smart contract -```bash -cd Example -``` +A smart contract is a program stored on TON blockchain and executed by the TVM. +On-chain, every contract consists of two components: -## Step 2: Understanding smart contract architecture +- Code — compiled [TVM instructions](/tvm/instructions), defines the contract's logic. +- Data — persistent state, stores information between interactions. -Every smart contract in TON is typically divided into three sections: **storage**, **messages**, and **getters**. +Both are stored at a specific [address](/foundations/addresses/overview) on TON blockchain, which is a unique identifier for each smart contract. Smart contracts interact with each other only through [messages](/foundations/messages/overview). -- **Storage**: Defines the contract’s persistent data. For example, our _counter_ variable must keep its value across calls from different users. -- **Messages**: Define how the contract reacts to incoming messages. On TON, the primary way to interact with contracts is by sending [messages](/foundations/messages/ordinary-tx). Each processed message produces a [transaction](/foundations/messages/ordinary-tx) — a recorded change on the blockchain (like "Alice sent 5 TON to Bob"). -- **Getters**: Provide read-only access to contract data without modifying state. For example, we’ll create a getter to return the current value of the counter. +### Smart contract layout + +A contract's code consists of three functional sections: storage, messages, and get methods: + +- Storage holds the contract’s persistent state. Example: the `counter` variable keeps its value across calls from different users. +- Messages are receivers defined in the contract’s code that specify how the contract should react to each incoming message. Each message triggers a specific action or changes the state according to the contract's logic. +- [Get methods](/tvm/get-method) are read-only functions that return contract data without modifying state. Example: a get method that returns the current `counter` value. -## Step 3: Writing the smart contract - -We’ll build a simple **counter** contract: - -- The counter starts from an initial number. -- Users can send an `increase` message to increment it, or a `reset` message to drop it to zero. -- A `getter` function will let anyone query the current counter value. - -We'll use [**Tolk**](/languages/tolk) to implement this. Tolk looks familiar if you know TypeScript or Rust, but it's designed specifically for smart contract development. - -### 3.1 Defining contract storage - -First, we need a way to store the counter value. Tolk makes this simple with structures: - -```tolk title="./contracts/first_contract.tolk" -struct Storage { - counter: uint64; // the current counter value -} - -// load contract data from persistent storage -fun Storage.load() { - return Storage.fromCell(contract.getData()) -} - -// save contract data to persistent storage -fun Storage.save(self) { - contract.setData(self.toCell()) -} -``` - -Behind the scenes, structures know how to serialize and deserialize themselves into [cells](/foundations/serialization/cells) — the fundamental way TON stores data. This happens through the `fromCell` and `toCell` functions - Tolk automatically converts between your nice structures and the cell format that TON understands. -You may think of cells like containers that hold data on TON: - -- Each cell can store up to 1023 bits of data. -- Cells can reference other cells (like links). -- Everything on TON (contracts, messages, storage) is made of cells. - -Now that we can store data, let’s handle our first messages. - -### 3.2 Implementing message handlers - -The main entry point for processing messages in a Tolk contract is the `onInternalMessage` function. It receives one argument — the incoming message. Among its fields, the most important one for us is `body`, which contains the payload sent by a user or another contract. - -Tolk structures are also useful for defining message bodies. In our case, we’ll define two messages: - -- `IncreaseCounter` — with one field `increaseBy`, used to increment the counter. -- `ResetCounter` — used to reset the counter to zero. +## Write a smart contract -Each structure has a unique prefix (`0x7e8764ef` and `0x3a752f06`), widely called opcodes, that lets the contract distinguish between them. +To build a simple counter contract: -```tolk title="./contracts/first_contract.tolk" -struct(0x7e8764ef) IncreaseCounter { - increaseBy: uint32 -} - -struct(0x3a752f06) ResetCounter {} -``` - -To group them together, we'll use a union. Unions allow multiple types to be bundled into a single type that can be serialized and deserialized automatically: - -```tolk title="./contracts/first_contract.tolk" -type AllowedMessage = IncreaseCounter | ResetCounter; -``` +- Start with an initial `counter` value. +- Send `increase` messages to add to the counter or `reset` messages to set it to 0. +- Call a get method to return the current `counter` value. -Now we can write our message handler: +The contract uses [**Tolk**](/languages/tolk) language. -```tolk title="./contracts/first_contract.tolk" -fun onInternalMessage(in: InMessage) { - // use `lazy` to defer parsing until fields are accessed - val msg = lazy AllowedMessage.fromSlice(in.body); + + + Open the `./contracts/first_contract.tolk` file. - // matching our union to determine body structure - match (msg) { - IncreaseCounter => { - // load contract storage lazily (efficient for large or partial reads/updates) - var storage = lazy Storage.load(); - storage.counter += msg.increaseBy; - storage.save(); - } - - ResetCounter => { - var storage = lazy Storage.load(); - storage.counter = 0; - storage.save(); - } + To define contract storage, store the `counter` value. Tolk makes it simple with structures: - // this match branch would be executed if the message body does not match IncreaseCounter or ResetCounter structures - else => { - // reject user message (throw) if body is not empty - assert(in.body.isEmpty()) throw 0xFFFF - } + ```tolk title="./contracts/first_contract.tolk" + struct Storage { + // the current counter value + counter: uint64; } -} -``` - - -### 3.3 Adding getter functions - -Finally, let’s implement a getter so users can read the current counter value: - -```tolk title="./contracts/first_contract.tolk" -get fun currentCounter(): int { - val storage = lazy Storage.load(); - return storage.counter; -} -``` + // load contract data from persistent storage + fun Storage.load() { + return Storage.fromCell(contract.getData()) + } -### 3.4 Complete contract code + // save contract data to persistent storage + fun Storage.save(self) { + contract.setData(self.toCell()) + } + ``` -We now have a complete smart contract with: + Structures serialize and deserialize automatically into [cells](/foundations/serialization/cells), the storage unit in TON. The `fromCell` and `toCell` functions handle conversion between structures and cells. + -- **Storage**: persistent `counter` value -- **Messages**: `IncreaseCounter` and `ResetCounter` handlers -- **Getter**: `currentCounter` + + To process messages, implement the `onInternalMessage` function. It receives one argument — the incoming message. Focus on the `body` field, which contains the payload sent by a user or another contract. -Here’s the full source code of `contracts/first_contract.tolk`: + Define two message structures: -```tolk title="./contracts/first_contract.tolk" expandable -struct Storage { - counter: uint64; -} + - `IncreaseCounter` — contains one field `increaseBy` to increment the counter. + - `ResetCounter` — resets the counter to 0. -fun Storage.load() { - return Storage.fromCell(contract.getData()); -} + Each structure has a unique prefix —`0x7e8764ef` and `0x3a752f06`— called opcodes, that which allows the contract to distinguish between messages. -fun Storage.save(self) { - contract.setData(self.toCell()); -} + ```tolk title="./contracts/first_contract.tolk" + struct(0x7e8764ef) IncreaseCounter { + increaseBy: uint32 + } -struct(0x7e8764ef) IncreaseCounter { - increaseBy: uint32 -} + struct(0x3a752f06) ResetCounter {} + ``` -struct(0x3a752f06) ResetCounter {} + To avoid manual deserialization of each message, group the messages into a union. A union bundles multiple types into a single type and supports automatic serialization and deserialization. -type AllowedMessage = IncreaseCounter | ResetCounter; + ```tolk title="./contracts/first_contract.tolk" + type AllowedMessage = IncreaseCounter | ResetCounter; + ``` -fun onInternalMessage(in: InMessage) { - val msg = lazy AllowedMessage.fromSlice(in.body); + Now implement the message handler: - match (msg) { - IncreaseCounter => { - var storage = lazy Storage.load(); - storage.counter += msg.increaseBy; - storage.save(); - } + ```tolk title="./contracts/first_contract.tolk" + fun onInternalMessage(in: InMessage) { + // use `lazy` to defer parsing until fields are accessed + val msg = lazy AllowedMessage.fromSlice(in.body); - ResetCounter => { - var storage = lazy Storage.load(); - storage.counter = 0; - storage.save(); + // matching the union to determine body structure + match (msg) { + IncreaseCounter => { + // load contract storage lazily (efficient for large or partial reads/updates) + var storage = lazy Storage.load(); + storage.counter += msg.increaseBy; + storage.save(); } - else => { - assert(in.body.isEmpty()) throw 0xFFFF; + ResetCounter => { + var storage = lazy Storage.load(); + storage.counter = 0; + storage.save(); + } + + // this match branch would be executed if the message body does not match IncreaseCounter or ResetCounter structures + else => { + // reject user message (throw) if body is not empty + assert(in.body.isEmpty()) throw 0xFFFF + } } } -} - -get fun currentCounter(): int { - val storage = lazy Storage.load(); - return storage.counter; -} -``` - -🎉 Congratulations — you've built your first smart contract in **Tolk**! - - - -## Step 4: Compiling your contract - -The next step is to build our contract — compile it into bytecode that can be executed by the TVM. With **Blueprint**, this takes one command: + ``` + + + + Write a getter function to return the current counter: + + ```tolk title="./contracts/first_contract.tolk" + get fun currentCounter(): int { + val storage = lazy Storage.load(); + return storage.counter; + } + ``` + + + + The contract now includes: + + - Storage — persistent `counter` value + - Messages — `IncreaseCounter` and `ResetCounter` handlers + - Get methods — `currentCounter` + + + ```tolk title="./contracts/first_contract.tolk" + struct Storage { + counter: uint64; + } + + fun Storage.load() { + return Storage.fromCell(contract.getData()); + } + + fun Storage.save(self) { + contract.setData(self.toCell()); + } + + struct(0x7e8764ef) IncreaseCounter { + increaseBy: uint32 + } + + struct(0x3a752f06) ResetCounter {} + + type AllowedMessage = IncreaseCounter | ResetCounter; + + fun onInternalMessage(in: InMessage) { + val msg = lazy AllowedMessage.fromSlice(in.body); + + match (msg) { + IncreaseCounter => { + var storage = lazy Storage.load(); + storage.counter += msg.increaseBy; + storage.save(); + } + + ResetCounter => { + var storage = lazy Storage.load(); + storage.counter = 0; + storage.save(); + } + + else => { + assert(in.body.isEmpty()) throw 0xFFFF; + } + } + } + + get fun currentCounter(): int { + val storage = lazy Storage.load(); + return storage.counter; + } + ``` + + + + +## Compile the contract + +To build the contract, compile it into bytecode for execution by the TVM. Use Blueprint with command: ```bash npx blueprint build FirstContract @@ -307,354 +282,389 @@ Build script running, compiling FirstContract ✅ Wrote compilation artifact to build/FirstContract.compiled.json ``` -This compilation artifact contains the **contract bytecode** and will be used in the deployment step. +The compilation artifact contains the contract bytecode. This file is required for deployment. -In the next section, we'll learn how to **deploy this contract to the TON blockchain** and interact with it using scripts and wrappers. +Next, deploy the contract to the TON blockchain and interact with it using scripts and wrappers. -## Step 5: Deploying to testnet +## Deploy to testnet -Ready to put your contract on-chain? 🚀 + + + To deploy, create a wrapper class. Wrappers make it easy to interact with contracts from TypeScript. -To deploy, we first need a **wrapper class**. Wrappers implement the `Contract` interface and make it easy to interact with contracts from TypeScript. + Open the `./wrappers/FirstContract.ts` file and replace its content with the following code: -Create a file `./wrappers/FirstContract.ts` with the following code: + ```typescript title="./wrappers/FirstContract.ts" + import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core'; -```typescript title="./wrappers/FirstContract.ts" -import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core'; + export class FirstContract implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell }, + ) {} -export class FirstContract implements Contract { - constructor( - readonly address: Address, - readonly init?: { code: Cell; data: Cell }, - ) {} + static createFromConfig(config: { counter: number }, code: Cell, workchain = 0) { + const data = beginCell().storeUint(config.counter, 64).endCell(); + const init = { code, data }; + return new FirstContract(contractAddress(workchain, init), init); + } - static createFromConfig(config: { counter: number }, code: Cell, workchain = 0) { - const data = beginCell().storeUint(config.counter, 64).endCell(); - const init = { code, data }; - return new FirstContract(contractAddress(workchain, init), init); + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); + } } - - async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { - await provider.internal(via, { - value, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); + ``` + + Wrapper class details: + + - [`@ton/core`](https://www.npmjs.com/package/@ton/core) — a library with base TON types. + - The function `createFromConfig` builds a wrapper using: + - code — compiled bytecode + - data — the initial storage layout + - The contract [address](/foundations/addresses/overview) is derived deterministically from `code` and `data` using `contractAddress`. + - The method `sendDeploy` sends the first message with `stateInit`, the structure holding the contract's initial code and data, which triggers deployment. In practice, this can be an empty message with Toncoin attached. + + + + TON provides two networks for deployment: + + - **testnet** — developer sandbox. + - **mainnet** — production blockchain. + + This tutorial uses testnet. Mainnet deployment is possible once the contract is verified and ready for production. + + + + Open the `./scripts/deployFirstContract.ts` file and replace its content with the following code. + It deploys the contract. + + ```typescript title="./scripts/deployFirstContract.ts" + import { toNano } from '@ton/core'; + import { FirstContract } from '../wrappers/FirstContract'; + import { compile, NetworkProvider } from '@ton/blueprint'; + + export async function run(provider: NetworkProvider) { + const firstContract = provider.open( + FirstContract.createFromConfig( + { counter: Math.floor(Math.random() * 10000000) }, + await compile('FirstContract') + ) + ); + + await firstContract.sendDeploy(provider.sender(), toNano('0.05')); + + await provider.waitForDeploy(firstContract.address); } -} -``` - -### 5.1 Understanding the wrapper class - -- We depend on [`@ton/core`](https://www.npmjs.com/package/@ton/core) — a library with base TON types. -- The function `createFromConfig` constructs a wrapper using **code** (compiled bytecode) and **data** (the initial storage layout). -- The contract [address](/foundations/addresses/overview) is derived deterministically from `code + data` using `contractAddress`. If two contracts have the same code and init data, the calculation of the address will result in the same value. -- The method `sendDeploy` sends the first message with [`stateInit`](/foundations/messages/deploy), which triggers deployment. In practice, this can be an empty message with some TON coins attached. - -### 5.2 Choosing your network - -TON has two networks available for deployment: - -- **testnet** — developer sandbox. -- **mainnet** — production blockchain. - -For this tutorial, we'll use **testnet** since it's free and perfect for learning. You can always deploy to mainnet later once you're confident in your contract. - -### 5.3 Creating the deployment script - -Blueprint makes deployment simple. Create a new script `./scripts/deployFirstContract.ts`: - -```typescript title="./scripts/deployFirstContract.ts" -import { toNano } from '@ton/core'; -import { FirstContract } from '../wrappers/FirstContract'; -import { compile, NetworkProvider } from '@ton/blueprint'; - -export async function run(provider: NetworkProvider) { - const firstContract = provider.open( - FirstContract.createFromConfig( - { counter: Math.floor(Math.random() * 10000000) }, - await compile('FirstContract') - ) - ); - - await firstContract.sendDeploy(provider.sender(), toNano('0.05')); - - await provider.waitForDeploy(firstContract.address); -} -``` - -The `sendDeploy` method accepts three arguments, but we only pass two because `provider.open` automatically supplies the ContractProvider as the first argument. - -Run the script with ([learn more about Blueprint deployment](/contract-dev/blueprint/deploy)): - -```bash -npx blueprint run deployFirstContract --testnet --tonconnect --tonviewer -``` + ``` + + The `sendDeploy` method accepts three arguments. Only two arguments are passed because `provider.open` automatically provides the `ContractProvider` as the first argument. + + + + + + Run the script with: + + ```bash + npx blueprint run deployFirstContract --testnet --tonconnect --tonviewer + ``` + + For flags and options, see the [Blueprint deployment guide](/contract-dev/blueprint/deploy). + + + + + + Scan the QR code displayed in the console, and confirm the transaction in the wallet app. + + Expected output: + + ```text + Using file: deployFirstContract + ? Choose your wallet Tonkeeper + + + + Connected to wallet at address: ... + Sending transaction. Approve in your wallet... + Sent transaction + Contract deployed at address kQBz-OQQ0Olnd4IPdLGZCqHkpuAO3zdPqAy92y6G-UUpiC_o + You can view it at https://testnet.tonviewer.com/kQBz-OQQ0Olnd4IPdLGZCqHkpuAO3zdPqAy92y6G-UUpiC_o + ``` + + The link opens the contract on [Tonviewer](/ecosystem/explorers/tonviewer), a [blockchain explorer](/ecosystem/explorers/overview) showing transactions, messages and [account states](/foundations/status). + + Next, interact with the contract by sending messages and calling getter functions. + + + +## Contract interaction + +Deployment also counts as a message sent to the contract. The next step is to send a message with a body to trigger contract logic. + + + + Update the wrapper class with three methods: `sendIncrease`, `sendReset`, and `getCounter`: + + ```typescript title="./wrappers/FirstContract.ts" expandable + import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core'; + + export class FirstContract implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell }, + ) {} + + static createFromConfig(config: { counter: number }, code: Cell, workchain = 0) { + const data = beginCell().storeUint(config.counter, 64).endCell(); + const init = { code, data }; + return new FirstContract(contractAddress(workchain, init), init); + } -Choose your wallet, scan the QR code shown in the console, and approve the transaction in your wallet app. + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().endCell(), + }); + } -Expected output: + async sendIncrease( + provider: ContractProvider, + via: Sender, + opts: { + increaseBy: number; + value: bigint; + }, + ) { + await provider.internal(via, { + value: opts.value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().storeUint(0x7e8764ef, 32).storeUint(opts.increaseBy, 32).endCell(), + }); + } -```text -Using file: deployFirstContract -? Choose your wallet Tonkeeper + async sendReset( + provider: ContractProvider, + via: Sender, + opts: { + value: bigint; + }, + ) { + await provider.internal(via, { + value: opts.value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().storeUint(0x3a752f06, 32).endCell(), + }); + } - + async getCounter(provider: ContractProvider) { + const result = await provider.get('currentCounter', []); + return result.stack.readNumber(); + } + } + ``` -Connected to wallet at address: ... -Sending transaction. Approve in your wallet... -Sent transaction -Contract deployed at address kQBz-OQQ0Olnd4IPdLGZCqHkpuAO3zdPqAy92y6G-UUpiC_o -You can view it at https://testnet.tonviewer.com/kQBz-OQQ0Olnd4IPdLGZCqHkpuAO3zdPqAy92y6G-UUpiC_o -``` + The main difference from the deploy message is that these methods include a message body. The body is a cell that contains the instructions. -Follow the link in the console to see your contract on the **Tonviewer**. Blockchain explorers like [Tonviewer](/ecosystem/explorers/tonviewer) allow you to inspect transactions, smart contracts, and account states on the TON blockchain. + **Building message bodies** -🎉 Congratulations! Your contract is live on testnet. Let's interact with it by sending messages and calling get methods. + Cells are constructed using the `beginCell` method: -## Step 6: Contract interaction + - `beginCell()` creates a new cell builder. + - `storeUint(value, bits)` appends an unsigned integer with a fixed bit length. + - `endCell()` finalizes the cell. -Technically speaking, we've already sent messages to the contract - the deploy message in previous steps. Now let's see how to send messages with a body. + **Example** -First of all, we should update our wrapper class with three methods: `sendIncrease`, `sendReset`, and `getCounter`: + `beginCell().storeUint(0x7e8764ef, 32).storeUint(42, 32).endCell()` -```typescript title="./wrappers/FirstContract.ts" -import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core'; + - First 32 bits: `0x7e8764ef` — opcode for "increase" + - Next 32 bits: `42` — increase by this amount + -export class FirstContract implements Contract { - constructor( - readonly address: Address, - readonly init?: { code: Cell; data: Cell }, - ) {} + + With the contract deployed and wrapper methods in place, the next step is to send messages to it. - static createFromConfig(config: { counter: number }, code: Cell, workchain = 0) { - const data = beginCell().storeUint(config.counter, 64).endCell(); - const init = { code, data }; - return new FirstContract(contractAddress(workchain, init), init); - } + Create a script `./scripts/sendIncrease.ts` that increases the counter: - async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { - await provider.internal(via, { - value, - sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell().endCell(), - }); - } + ```typescript title="./scripts/sendIncrease.ts" + import { Address, toNano } from '@ton/core'; + import { FirstContract } from '../wrappers/FirstContract'; + import { NetworkProvider } from '@ton/blueprint'; - async sendIncrease( - provider: ContractProvider, - via: Sender, - opts: { - increaseBy: number; - value: bigint; - }, - ) { - await provider.internal(via, { - value: opts.value, - sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell().storeUint(0x7e8764ef, 32).storeUint(opts.increaseBy, 32).endCell(), - }); - } + // `Address.parse()` converts string to address object + const contractAddress = Address.parse(''); - async sendReset( - provider: ContractProvider, - via: Sender, - opts: { - value: bigint; - }, - ) { - await provider.internal(via, { - value: opts.value, - sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell().storeUint(0x3a752f06, 32).endCell(), - }); + export async function run(provider: NetworkProvider) { + // `provider.open()` creates a connection to the deployed contract + const firstContract = provider.open(new FirstContract(contractAddress)); + // `toNano('0.05')` converts 0.05 TON to nanotons + // `increaseBy: 42` tells the contract to increase the counter by 42 + await firstContract.sendIncrease(provider.sender(), { value: toNano('0.05'), increaseBy: 42 }); + // `waitForLastTransaction()` waits for the transaction to be processed on-chain + await provider.waitForLastTransaction(); } - - async getCounter(provider: ContractProvider) { - const result = await provider.get('currentCounter', []); - return result.stack.readNumber(); + ``` + + Replace `` with the address obtained in the deployment step. + + + + To run this script: + + ```bash + npx blueprint run sendIncrease --testnet --tonconnect --tonviewer + ``` + + Expected result: + + ```text + Using file: sendIncrease + Connected to wallet at address: ... + Sending transaction. Approve in your wallet... + Sent transaction + Transaction 0fc1421b06b01c65963fa76f5d24473effd6d63fc4ea3b6ea7739cc533ba62ee successfully applied! + You can view it at https://testnet.tonviewer.com/transaction/fe6380dc2e4fab5c2caf41164d204e2f41bebe7a3ad2cb258803759be41b5734 + ``` + + What happens during execution: + + 1. Blueprint connects to the wallet using the [TON Connect](/ecosystem/ton-connect/overview) protocol. + 1. The script builds a transaction with a message body containing opcode `0x7e8764ef` and value `42`. + 1. The wallet displays transaction details for confirmation. + 1. After approval, the transaction is sent to the network. + 1. Validators include the transaction in a newly produced block. + 1. The contract receives the message, processes it in `onInternalMessage`, and updates the counter. + 1. The script returns the resulting transaction hash; inspect it in the explorer. + + + + + + To reset the counter, create a script `./scripts/sendReset.ts`: + + ```typescript title="./scripts/sendReset.ts" + import { Address, toNano } from '@ton/core'; + import { FirstContract } from '../wrappers/FirstContract'; + import { NetworkProvider } from '@ton/blueprint'; + + const contractAddress = Address.parse(''); + + export async function run(provider: NetworkProvider) { + const firstContract = provider.open(new FirstContract(contractAddress)); + await firstContract.sendReset(provider.sender(), { value: toNano('0.05') }); + await provider.waitForLastTransaction(); } -} -``` - -The only difference from the deploy message is that we pass a **body** to it — remember the cells I talked about previously? The message body is a cell that contains our instructions. - -### Building message bodies - -Construction of cells starts with the `beginCell` method ([learn more about cell serialization](/foundations/serialization/cells)): - -- `beginCell()` - creates a new cell builder -- `storeUint(value, bits)` - adds an unsigned integer of specified bit length -- `endCell()` - finalizes the cell - -**Example**: `beginCell().storeUint(0x7e8764ef, 32).storeUint(42, 32).endCell()` - -- First 32 bits: `0x7e8764ef` (opcode for "increase") -- Next 32 bits: `42` (increase by this amount) - -### 6.1 Sending messages to your contract - -Now that our contract is deployed and we have wrapper methods, let's interact with it by sending messages. - -Let's create a script `./scripts/sendIncrease.ts` that would increase the counter: - -```typescript title="./scripts/sendIncrease.ts" -import { Address, toNano } from '@ton/core'; -import { FirstContract } from '../wrappers/FirstContract'; -import { NetworkProvider } from '@ton/blueprint'; - -const contractAddress = Address.parse(''); - -export async function run(provider: NetworkProvider) { - const firstContract = provider.open(new FirstContract(contractAddress)); - await firstContract.sendIncrease(provider.sender(), { value: toNano('0.05'), increaseBy: 42 }); - await provider.waitForLastTransaction(); -} -``` - -Do not forget to replace `` with your actual contract address from Step 5! - -#### Understanding the script breakdown: - -- **Address parsing**: `Address.parse()` converts the string address to a TON Address object -- **Contract opening**: `provider.open()` creates a connection to the deployed contract -- **Value attachment**: `toNano('0.05')` converts 0.05 TON to nanotons (the smallest TON unit) -- **Message parameters**: `increaseBy: 42` tells the contract to increase the counter by 42 -- **Transaction waiting**: `waitForLastTransaction()` waits for the transaction to be processed on-chain - -To run this script: - -```bash -npx blueprint run sendIncrease --testnet --tonconnect --tonviewer -``` - -Expected result: - -```text -Using file: sendIncrease -Connected to wallet at address: ... -Sending transaction. Approve in your wallet... -Sent transaction -Transaction 0fc1421b06b01c65963fa76f5d24473effd6d63fc4ea3b6ea7739cc533ba62ee successfully applied! -You can view it at https://testnet.tonviewer.com/transaction/fe6380dc2e4fab5c2caf41164d204e2f41bebe7a3ad2cb258803759be41b5734 -``` - -#### What happens during execution: - -1. **Wallet Connection**: Blueprint connects to your wallet using [TON Connect](/ecosystem/ton-connect/overview) protocol -1. **Transaction Building**: The script creates a transaction with the message body containing the opcode `0x7e8764ef` and the value `42` -1. **User Approval**: Your wallet app shows the transaction details for approval -1. **Blockchain Processing**: Once approved, the transaction is sent to the TON network -1. **Validator Consensus**: Validators need to produce a new block containing your transaction -1. **Contract Execution**: The contract receives the message, processes it in the `onInternalMessage` function, and updates the counter -1. **Confirmation**: The transaction hash is returned, and you can view it on the explorer - - - -Let's create a script `./scripts/sendReset.ts` that would reset the counter: - -```typescript title="./scripts/sendReset.ts" -import { Address, toNano } from '@ton/core'; -import { FirstContract } from '../wrappers/FirstContract'; -import { NetworkProvider } from '@ton/blueprint'; - -const contractAddress = Address.parse(''); - -export async function run(provider: NetworkProvider) { - const firstContract = provider.open(new FirstContract(contractAddress)); - await firstContract.sendReset(provider.sender(), { value: toNano('0.05') }); - await provider.waitForLastTransaction(); -} -``` - -To run this script: - -```bash -npx blueprint run sendReset --testnet --tonconnect --tonviewer -``` - -Expected result: - -```text -Using file: sendReset -Connected to wallet at address: ... -Sending transaction. Approve in your wallet... -Sent transaction -Transaction 0fc1421b06b01c65963fa76f5d24473effd6d63fc4ea3b6ea7739cc533ba62ee successfully applied! -You can view it at https://testnet.tonviewer.com/transaction/fe6380dc2e4fab5c2caf41164d204e2f41bebe7a3ad2cb258803759be41b5734 -``` - -### 6.2 Reading contract data with get methods - -Get methods are special functions in TON smart contracts that allow you to read data without modifying the contract state or spending gas fees. Unlike message-based interactions, get methods: - -- **Cost nothing**: No gas fees required since they don't modify blockchain state -- **Execute instantly**: No need to wait for blockchain confirmation -- **Read-only**: Cannot change contract storage or send messages - -To call a get method, use `provider.get()`: - -```typescript title="./scripts/getCounter.ts" -import { Address } from '@ton/core'; -import { FirstContract } from '../wrappers/FirstContract'; -import { NetworkProvider } from '@ton/blueprint'; - -const contractAddress = Address.parse(''); - -export async function run(provider: NetworkProvider) { - const firstContract = provider.open(new FirstContract(contractAddress)); - const counter = await firstContract.getCounter(); - console.log('Counter: ', counter); -} -``` - -#### Understanding the get method execution: - -1. **Direct contract call**: The `getCounter()` method directly calls the contract's `currentCounter` getter -1. **Instant response**: The result is returned immediately without blockchain confirmation -1. **Data parsing**: Our wrapper automatically converts the returned stack value to a JavaScript number - - - -To run this script: - -```bash -npx blueprint run getCounter --testnet --tonconnect -``` - -Expected output: - -```bash -Using file: getCounter -Counter: 42 -``` - -## 🎉 Tutorial complete! - -**Congratulations!** You've successfully built, deployed, and interacted with your first TON smart contract from scratch. This is a significant achievement in your blockchain development journey! - -### Continue Your Learning Journey - -Ready to build more advanced contracts? Here's your roadmap: - -#### **Next Steps** - -- [Tolk Language Guide](/languages/tolk) - Master advanced Tolk features and syntax -- [Blueprint Documentation](/contract-dev/blueprint/overview) - Learn advanced development patterns -- [Tutorial Example Repository](https://github.com/ton-org/docs-examples/tree/main/guidebook/first-smart-contract/Example) - Complete working code from this tutorial - -#### **Advanced Topics** - -- [Gas Optimization](/contract-dev/gas) - Reduce transaction costs -- [Security Best Practices](/contract-dev/security) - Protect your contracts - - - -**🎉 Happy coding on TON!** You're now equipped with the fundamentals to build amazing smart contracts. The blockchain world awaits your innovations! 🚀 + ``` + + To run this script: + + ```bash + npx blueprint run sendReset --testnet --tonconnect --tonviewer + ``` + + Expected result: + + ```text + Using file: sendReset + Connected to wallet at address: ... + Sending transaction. Approve in your wallet... + Sent transaction + Transaction 0fc1421b06b01c65963fa76f5d24473effd6d63fc4ea3b6ea7739cc533ba62ee successfully applied! + You can view it at https://testnet.tonviewer.com/transaction/fe6380dc2e4fab5c2caf41164d204e2f41bebe7a3ad2cb258803759be41b5734 + ``` + + + + Get methods are special functions in TON smart contracts that run locally on a node. Unlike message-based interactions, get methods are: + + - Free — no gas fees, as the call does not modify the blockchain state. + - Instant — no need to wait for blockchain confirmation. + - Read-only — can only read data; cannot modify storage or send messages. + + To call a get method, use `getCounter()`, which calls the contract's getter `provider.get('currentCounter')`: + + ```typescript title="./scripts/getCounter.ts" + import { Address } from '@ton/core'; + import { FirstContract } from '../wrappers/FirstContract'; + import { NetworkProvider } from '@ton/blueprint'; + + const contractAddress = Address.parse(''); + + export async function run(provider: NetworkProvider) { + const firstContract = provider.open(new FirstContract(contractAddress)); + // `getCounter()` сalls the contract's `currentCounter` getter + const counter = await firstContract.getCounter(); // returns instantly + console.log('Counter: ', counter); // wrapper parses stack into JS number + } + ``` + + + + + + To run this script: + + ```bash + npx blueprint run getCounter --testnet --tonconnect + ``` + + Expected output: + + ```bash + Using file: getCounter + Counter: 42 + ``` + + + +The full code for this tutorial is available in the [GitHub repository](https://github.com/ton-org/docs-examples/tree/main/guidebook/first-smart-contract/Example). It includes all contract files, scripts, and wrappers ready to use.