Skip to content
Deploy TON Contract Tutorial

Deploy TON Contract Tutorial

October 21st, 2022 by Nikita Kuznetov

In this tutorial, you deploy a single smart contract in one of TON networks. You have the option to run and review the implementation of the code with interaction with the contract via web API.

This tutorial's steps are presented from a dApp developer perspective. You review the smart contract on the FunC language and React components for interaction with the blockchain. You can complete most of these steps on a local machine or create a dApp with the same features.

💡
The tutorial can be run in mainnet and testnet networks.
OpenMask team recommends to use testnet for this tutorial.

For example, you deploy smart contracts from tonstarter-contracts template. The full source code available via link.

Overview

The smart contract contains cell data with store counter and owner_address and contracts public methods to retrieve this data from a contract. These make the contract suitable for this tutorial.

(slice, int) load_data() inline {
  var ds = get_data().begin_parse();
  return (
    ds~load_msg_addr(), ;; owner_address
    ds~load_uint(64)    ;; counter
  );
}

---------------

slice owner_address() method_id {
  var (owner_address, _) = load_data();
  return owner_address;
}

int counter() method_id {
  var (_, counter) = load_data();
  return counter;
}

Build smart contract

To build this smart contract you may download the repository, install dependencies as notes in README.md and run the command npm run build.

As a result in the file build/main.compiled.json you may find a build for this smart contract. If you don't touch the source in main.fc file it would be the same.

{"hex":"b5ee9c72c1020f01000109000000000d00120017001c0023002800360040004e0053006c0071008701040114ff00f4a413f4bcf2c80b010201620902020120080302014805040009b59f10055002015807060017ad0c76a2687d20699f9818c0000faf607c13b79118400017bffecf6a2687d20699f981840202cd0b0a002dd7c13b7911829815f797033c1044c4b4050895b047800c0201480d0c00271c20063232c15400f3c5807e80b2dab25cfec02001f30cb434c0fe900c005c6c2456f83b51343e9034cfcc00f4c7f4cfcc4860840dd247cbeea7c408d7c0c069321633c5b2cff27b553808608411f550e46ea497c1780860841060da602ea7cc4cd9b1c17cb819807e800c007c01380060840b68e2abeea384d671c17cb819be900c00721633c5b2cff27b553817c1200e0006f2c065ea043d88"}

Create initial data and message

For contract deploying developer has to provide initial data. It would be stored in the smart contract cell. The second step for developer is to provide the initial message which will be run with the deploy transaction.

For this smart contract initial data is a cell with initial counter and initial owner_address values.

To build the initial data cell you can use our TON js client library - @openproduct/web-sdk package.

The following function creates a cell and store address and int64 counter values and format it to a hexadecimal string.

const getInitData = async (ownerAddress, counter) => {
  return await beginCell()
    .storeAddress(new Address(ownerAddress))
    .storeUint(counter, 64) // 64 - mean the counter value take 64 bits in a cell - int64 type
    .endCell()
    .toHex();
};

The following function creates an initial message. The message executes together with the deploy transaction, right after initialization. The example contract handles a few different messages. This message runs the code with an increment counter and stores the new value.

const getInitMessage = async () => {
  return await beginCell()
    .storeUint(0x37491f2f, 32)
    .storeUint(0, 64)
    .endCell()
    .toHex();
};

The toHex method of cell will return a promise that resolves with a string hexadecimal cell value.

Combine code and send it to OpenMask

ton_deployContract RPC method have paramters:

interface DeployParams {
    // Smart contract code hexadecimal string
    initCodeCell: string;
    // Smart contract initial data hexadecimal string
    initDataCell: string;
    // Initial mesasge hexadecimal string (optional)
    initMessageCell?: string;
    // Amount to funding smart contract in nanotons, to pay network and storage fee
    amount: string;
}

And all together, the code will deploy a smart contract:

import { useState } from "react";
import { beginCell, Address } from "@openproduct/web-sdk";

const initCode =
  "b5ee9c72c1020f01000109000000000d00120017001c0023002800360040004e0053006c0071008701040114ff00f4a413f4bcf2c80b010201620902020120080302014805040009b59f10055002015807060017ad0c76a2687d20699f9818c0000faf607c13b79118400017bffecf6a2687d20699f981840202cd0b0a002dd7c13b7911829815f797033c1044c4b4050895b047800c0201480d0c00271c20063232c15400f3c5807e80b2dab25cfec02001f30cb434c0fe900c005c6c2456f83b51343e9034cfcc00f4c7f4cfcc4860840dd247cbeea7c408d7c0c069321633c5b2cff27b553808608411f550e46ea497c1780860841060da602ea7cc4cd9b1c17cb819807e800c007c01380060840b68e2abeea384d671c17cb819be900c00721633c5b2cff27b553817c1200e0006f2c065ea043d88";

const getInitData = async (ownerAddress, counter) => {
  return await beginCell()
    .storeAddress(new Address(ownerAddress))
    .storeUint(counter, 64) // 64 - mean the counter value take 64 bits in a cell - int64 type
    .endCell()
    .toHex();
};

const getInitMessage = async () => {
  return await beginCell()
    .storeUint(0x37491f2f, 32)
    .storeUint(0, 64)
    .endCell()
    .toHex();
};

export default () => {
  const [disabled, setDisabled] = useState(false);
  const [isSent, setSent] = useState(false);
  const [isConfirm, setConfirm] = useState(false);
  const [address, setAddress] = useState("");

  const send = async () => {
    const provider = window.ton;
    setSent(false);
    setConfirm(false);

    setDisabled(true);
    try {
      const [ownerAddress] = await provider.send("ton_requestAccounts");

      const { walletSeqNo, newContractAddress } = await provider.send(
        "ton_deployContract",
        [
          {
            initCodeCell: initCode,
            initDataCell: await getInitData(ownerAddress, 10),
            initMessageCell: await getInitMessage(),
            amount: "50000000", // 0.05 TON
          },
          ownerAddress,
        ]
      );
      setSent(true);
      await provider.send("ton_confirmWalletSeqNo", [walletSeqNo, ownerAddress]);

      setAddress(newContractAddress);
      setConfirm(true);
    } catch (e) {
      console.error(e);
    } finally {
      setDisabled(false);
    }
  };

  return (
    <div>
      <button disabled={disabled} onClick={send}>
        Deploy Smart Contract
      </button>
      {isSent && (
        <div>
          {isConfirm
            ? "Done. New contract address: " + address
            : "Deploy Pending... ~15 sec"}
        </div>
      )}
    </div>
  );
};

Finishing touch

To check smart conract is working properly after deployment you may use @openproduct/web-sdk package.

The simple way to communicate with TON it's using an SDK and public API. SDK provides developers-friendly methods to read blockchain entries, and service functions to analyze and parse this data.

import { TonHttpProvider } from "@openproduct/web-sdk";

const host =
  network === "mainnet"
    ? "https://toncenter.com/api/v2/jsonRPC"
    : "https://testnet.toncenter.com/api/v2/jsonRPC";

const client = new TonHttpProvider(host);

The client has a method call2 to allow call a contract public functions and parse the result from blockchain entries to valuable web entries.

import { parseAddress } from "@openproduct/web-sdk";

// it's a BN
const counter = await client.call2(address, "counter");
console.log(counter.toString())

// it's a Cell
const result = await client.call2(address, "owner_address");
console.log(parseAddress(result).toString(true, true, true));

All code together a react component calls contract and shows the results in a web application:

import { useMemo, useState } from "react";
import { TonHttpProvider, parseAddress } from "@openproduct/web-sdk";
import { useNetwork } from "./use-network";

const CheckResult = ({ address }) => {
  const network = useNetwork();

  const [counter, setCounter] = useState("");
  const [owner, setOwner] = useState("");

  const client = useMemo(() => {
    const host =
      network === "mainnet"
        ? "https://toncenter.com/api/v2/jsonRPC"
        : "https://testnet.toncenter.com/api/v2/jsonRPC";

    // The client without API key have a limitation in 1 request set second,
    // please don't click buttons too often
    return new TonHttpProvider(host);
  }, [network]);

  const getCounter = async () => {
    // it's a BN
    const counter = await client.call2(address, "counter");
    setCounter(counter.toString());
  };

  const getOwner = async () => {
    // it's a Cell
    const result = await client.call2(address, "owner_address");
    setOwner(parseAddress(result).toString(true, true, true));
  };

  return (
    <>
      <button onClick={getCounter}>
        Get Counter
      </button>
      {counter && <div>{counter}</div>}
      <button onClick={getOwner}>
        Get contract owner
      </button>
      {owner && <div>{owner}</div>}
    </>
  );
};

Result

Congratulations! You've just deployed a smart contract to TON network.

For the counter the correct value is 11. The initial value is 10 and the initial transaction is increment with increment 10 and stored to contract cell.

For the contract owner it has to be your user-friendly bounceable wallet address.

Please leave a comment in our telegram channel with your contract address, than we know it useful for you!

The source code of this buttons above you may find in our GitHub. You may find more integration examples from our developer's documentation in this GitHub folder.