TON Labs SDK. Hello World

ton tonlabs skd solidity node.js

avatar arkadiy 1 month ago

TON Labs SDK. Hello World

Создание и компиляция смарт-контракта

  1. Создадим папку для проекта и поместим в нее файл hello.sol с нашим смарт-контрактом:
    pragma solidity >=0.5.0 <0.6.0;
    contract HelloTON {
        uint32 deployTime;
      constructor() public {
            deployTime = uint32(now);
        }
        function sayHello() public view returns (uint32) {
            return deployTime;
        }
    }
  2. Скомпилируем смарт-контракт с помощью Sol2TVM:

    $ tondev sol hello -l js -L deploy

    -l js используется для генерации JS-кода, который содержит вспомогательную обвязку для смарт-контракта (abi, код и несколько методов). Позже этот код мы будем использовать в нашем клиентском приложении.
    -L deploy кодирует скомпилированный TVC код смарт-контракта в Вase64 и добавляет его в JS-файл.

В итоге, в дополнение к hello.sol в директории  у нас появятся три файла:

  • hello.abi.json - JSON с описанием ABI смарт-контракта
  • hello.tvc - бинарник с контрактом
  • helloContract.js - JS-модуль, содержащий базовый функционал для размещения смарт-контракта в сети и взаимодействия с ним через клиентское приложение

Создание клиентского приложения

Прежде всего, мы должны убедиться, что у нас запущен тестовый узел. Для этого нужно выполнить команду:

$ tondev info

tondev вернет список запущенных docker-контейнеров. Вывод должен быть примерно таким:

TON Labs Dev Tools 0.12.4

default network/blockchain:

  Used version: latest
  Bound to host port: 80
  Docker image: tonlabs/local-node:latest
  Docker container: tonlabs-local-node-arkadiy running

Compilers:

  Used version: latest
  Docker image: tonlabs/compilers:latest
  Docker container: tonlabs-compilers-arkadiy running

О том, что контейнер запущен, говорит строчка Docker container: … running. В противном случае, нужно разбираться. Может быть такое, что ранее запущенные контейнеры были остановлены, либо установка вообще не была выполнена. В большинстве случаев достаточно выполнить команду:

$ tondev start

Приступим непосредственно к написанию Node.js приложения.

  1. Создадим точку входа для нашего приложения (index.js) и проинициализируем проект: 
    $ touch index.js
    $ npm init
  2. Установим библиотеку ton-client-node-js в виде зависимости:

    $ npm i --save ton-client-node-js
  3. В index.js разместим следующий код:

    const { TONClient } = require('ton-client-node-js');
    
    async function main(client) {
    }
    
    (async () => {
        try {
            const client = new TONClient();
            client.config.setData({
                servers: ['http://0.0.0.0']
            });
            await client.setup();
            await main(client);
            console.log('Hello TON Done');
        } catch (error) {
            console.error(error);
        }
    })();
  4. Проверим работоспособность приложения:

    $ node index.js
  5. Следующий вывод означает, что мы все сделали правильно:

    Hello TON Done

    Остановим процесс комбинацией Ctrl + C.

Deployment

  1. В файле index.js импортируем код с обвязкой для смарт-контракта:
    ...
    
    // Define contract package
    
    const HelloContract = require('./helloContract');
    
    ...
    async function main(client) {
    }
    ...
  2. Сгенерируем пару публичный/приватный ключ. Для этого в файле index.js внутри метода main() вызовем метод ton.crypto.ed25519Keypair, который идет вместе с библиотекой ton-client-node-js:

    const helloKeys = await client.crypto.ed25519Keypair();
  3. Там же, внутри функции main() укажем инструкции, необходимые для деплоя:

    ...
    
    async function main(client) {
        const helloKeys = await client.crypto.ed25519Keypair();
        const helloAddress = (await client.contracts.deploy({
            package: HelloContract.package,
            constructorParams: {},
            keyPair: helloKeys,
        })).address;
        console.log(`Hello contract was deployed at address: ${helloAddress}`);
    }
    
    ...
  4. Запустим наше приложение:

    $ node index.js

    На данный момент приложение возвращает ошибку.

    { code: 3050, message: 'Deploy failed' }

    Все правильно, ведь для того, чтобы залить смарт-контракт на какой-то адрес, на нем должны быть средства. К счастью, разработчики TON Labs об этом уже позаботились и включили в поставку тестгивер, который расположен по адресу: ce709b5bfca589eb621b5a5786d0b562761144ac48f59e0b0d35ad0973bcdb8.
    Все, что нам требуется, это обратиться к тестгиверу и отправить на адрес будущего смарт-контракта пару монет.

  5. Создадим файл в giverContract.js с обвязкой, необходимой для обращения к тестгиверу:

    const giverAddress = 'ce709b5bfca589eb621b5a5786d0b562761144ac48f59e0b0d35ad0973bcdb86';
    const giverAbi = 
    {
        "ABI version": 0,
        "functions": [{
            "name": "constructor",
            "inputs": [],
            "outputs": []
        }, {
            "name": "sendGrams",
            "inputs": [
                {"name":"dest","type":"uint256"},
                {"name":"amount","type":"uint64"}
            ],
            "outputs": []
        }]
    };
    
    Module.exports = async function get_grams_from_giver(client, account) {
        const { contracts, queries } = client;
        const result = await contracts.run({
            address: giverAddress,
            functionName: 'sendGrams',
            abi: giverAbi,
            input: {
                dest: `0x${account}`,
                amount: 10000000000
            },
            keyPair: null,
        });
    
        const wait = await queries.accounts.waitFor(
            {
                id: { eq: account },
                storage: {
                    balance: {
                        Grams: { gt: "0" }
                    }
                }
            },
            'id storage {balance {Grams}}'
        );
    };
  6. Импортируем функцию, запрашивающую монетки. Для этого добавим в начало index.js следующую строчку:

    const get_grams_from_giver = require('./giverContract');
  7. Теперь нам нужно узнать, на каком адресе будет размещаться смарт-контракт (куда отправлять монетки с тестгивера). Для этого в начало функции main() добавим следующий код:

    async function main(client) {
        const futureHelloAddress = (await client.contracts.createDeployMessage({
            package: HelloContract.package,
            constructorParams: {},
            keyPair: helloKeys,
        })).address;
        console.log(`Future address of the contract will be: ${futureHelloAddress}`);
        ...
    }

    Этот код формирует сообщение для деплоя, извлекает из него адрес смарт-контракта и сохраняет его в константу futureHelloAddress.

  8. Попросим тестгивер перевести монетки на этот адрес. Для этого добавим вызовем функцию get_grams_from_giver() и передадим в нее адрес futureHelloAddress, который мы получили на предыдущем этапе:

    async function main(client) {
        ...
        console.log(`Future address of the contract will be: ${futureHelloAddress}`);
    
        await get_grams_from_giver(client, futureHelloAddress);
        console.log(`Grams were transfered from giver to ${futureHelloAddress}`);
    
        ...
    }
  9. Запустим наше приложение и убедимся, что все работает:

    $ node index.js
    Hello contract was deployed at address: 516c7a2bc72c5728526eb73064da07a2876d964c3da5ed2488e1aba3da20be3f

Взаимодействие со смарт-контрактом

  1. Добавим в функцию main() код, который вызовет метод смарт-контракта sayHello() сразу после деплоя:
    ...
    
    async function main(client) {
        
        ...
    
        const response = await client.contracts.run({
            address: helloAddress,
            abi: HelloContract.package.abi,
            functionName: 'sayHello',
            input: {},
            keyPair: helloKeys,
        });
        console.log('Hello contract was responded to sayHello:', response);
    }
    
    ...
  2. Запустим приложение:

    $ node index.js
    Hello contract was deployed at address: 516c7a2bc72c5728526eb73064da07a2876d964c3da5ed2488e1aba3da20be3f
    Hello contract was responded to sayHello: { output: { value0: '0x5d6fba2e' } }
    Hello TON Done

    Интересно, что библиотека ton-client-node-js позволяет тестировать смарт-контракт без подключения к локальному TVM-узлу. Метод runLocal выполнит код в TVM, поставляемой вместе с библиотекой. Такой подход позволяет экономить время на тестировании операций, которые не изменяют стейт смарт-контракта.

    ...
    
    async function main(client) {
    
        ...
    
        const localResponse = await client.contracts.runLocal({
            address: helloAddress,
            abi: HelloContract.package.abi,
            functionName: 'sayHello',
            input: {},
            keyPair: helloKeys,
        });
        console.log('Hello contract was ran on a client TVM and also responded to sayHello:', localResponse);
    }
    
    ...

    В результате к выводу добавится еще одна строчка:

    $ node index.js
    Hello contract was deployed at address: 516c7a2bc72c5728526eb73064da07a2876d964c3da5ed2488e1aba3da20be3f
    Hello contract was responded to sayHello: { output: { value0: 0x5da4832f } }
    Hello contract was ran on a client TVM and also responded to sayHello: { output: { value0: 0x5da4832f } }
    Hello TON Done