<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Рустам Хасаншин</title>
    <link>https://khasanshin.com/ru/</link>
    <description>Recent content on Рустам Хасаншин</description>
    <generator>Hugo</generator>
    <language>ru</language>
    <copyright>© 2026 Рустам Хасаншин</copyright>
    <lastBuildDate>Mon, 19 Sep 2022 19:46:31 +0200</lastBuildDate>
    <atom:link href="https://khasanshin.com/ru/rss.xml" rel="self" type="application/rss+xml" />
    
    
    
    
    <item>
      <title>Вызов метода смарт-контракта в Go</title>
      <link>https://khasanshin.com/ru/posts/smart-contract-method-call-in-go/</link>
      <pubDate>Mon, 19 Sep 2022 19:46:31 +0200</pubDate>
      <guid>https://khasanshin.com/ru/posts/smart-contract-method-call-in-go/</guid>
      <description>&lt;p&gt;Рассмотрим метод работы с блокчейном в Go. Предположим у вас есть контракт в эфириум блокчейне, и вы хотите вызвать его метод.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Рассмотрим метод работы с блокчейном в Go. Предположим у вас есть контракт в эфириум блокчейне, и вы хотите вызвать его метод.</p>
<p>Основной библиотекой для работы с такими контрактами является <a href="https://pkg.go.dev/github.com/ethereum/go-ethereum/ethclient">https://pkg.go.dev/github.com/ethereum/go-ethereum/ethclient</a>. Возьмем к примеру контракт <a href="https://bscscan.com/token/0xE0b1EF69BC4AB4173989C1190f0d77A813f3B726">токена моих знакомых</a>  запущенный на блокчейне бинанса (также основан на эфире) и попробуем вызвать метод для получения общего количества токенов.</p>
<p>Для начала нужно создать экземпляр клиента чтобы подключаться к нашему блокчейну и потом выполнять к нему запросы.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;github.com/ethereum/go-ethereum/ethclient&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">client</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">ethclient</span><span class="p">.</span><span class="nf">Dial</span><span class="p">(</span><span class="s">&#34;https://bsc-dataseed.binance.org/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Теперь надо определиться, как мы будем взаимодействовать с контрактом.</p>
<blockquote>
<p>В джаваскрипте вызвать метод контракта достаточно просто — вы можете загрузить ABI контракта и все сделает библиотека web3. В Go придется повозиться.</p></blockquote>
<p>Для того чтобы вызвать тот или иной метод в Go, нужно этот метод написать. Логично. И с контрактами работает таже логика. Но переписывать все методы это долго и муторно, и люди придумали генерацию кода этого контракта из его ABI. Для этого нам нужна библиотека <code>abigen</code>. На мак можно поставить через Homebrew.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ brew tap ethereum/ethereum
</span></span><span class="line"><span class="cl">$ brew install ethereum
</span></span></code></pre></div><p>Для проверки успешности установки, попробуйте вывести версию abigen.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ abigen -v
</span></span><span class="line"><span class="cl">&gt; abigen version 1.10.25-stable
</span></span></code></pre></div><p>Abigen успешно установлен, пора сгенерировать Go код. Перейдем на страницу контракта в bscscan — <a href="https://bscscan.com/address/0xE0b1EF69BC4AB4173989C1190f0d77A813f3B726">https://bscscan.com/address/0xE0b1EF69BC4AB4173989C1190f0d77A813f3B726</a>, и скачаем его ABI.</p>
<p>Contract -&gt; Code -&gt; Contract ABI -&gt; Export ABI -&gt; RAW/Text Format.</p>
<p>Сохраним файл в директории программы и назовем token.abi. Далее вызываем abigen. В параметре <code>--abi</code> указываем наш файл, в параметре <code>--pkg</code> указываем название пакета для нашего кода, в параметре <code>--type</code> имя структуры к которой будут привязаны все методы, в параметре <code>--out</code> - имя Go файла.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ abigen --abi<span class="o">=</span>token.abi --pkg<span class="o">=</span>main --type<span class="o">=</span>token --out<span class="o">=</span>token.go
</span></span></code></pre></div><p>В директории должен появится файл token.go с кодом который уже можно использовать. Но сначала давайте создадим экземпляр адреса из нашего строкового адреса контракта. Он нам понадобится чтобы создать потом экземляр токена. Делается это с помощью библиотеки github.com/ethereum/go-ethereum/common.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;github.com/ethereum/go-ethereum/common&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">address</span> <span class="o">:=</span> <span class="nx">common</span><span class="p">.</span><span class="nf">HexToAddress</span><span class="p">(</span><span class="s">&#34;0xE0b1EF69BC4AB4173989C1190f0d77A813f3B726&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Теперь создаем собственно сам экземляр токена.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">token</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">NewToken</span><span class="p">(</span><span class="nx">address</span><span class="p">,</span> <span class="nx">client</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>И пробуем выполнить метод для получения общего количества токенов — <code>TotalSupply</code>. Но этот метод требует передачи параметров специального типа <code>&amp;bind.CallOpts{}</code>. Параметры мы передавать не хотим, но пустое значение передать надо. А сам тип можно загрузить из библиотеки github.com/ethereum/go-ethereum/accounts/abi/bind.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/ethereum/go-ethereum/accounts/abi/bind&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">totalSupply</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">token</span><span class="p">.</span><span class="nf">TotalSupply</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">bind</span><span class="p">.</span><span class="nx">CallOpts</span><span class="p">{})</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>В целом на этом этапе мы уже можем наблюдать общее количество токенов в <code>totalSupply</code>. Но мы не можем знать является ли этот токен делимым или нет. И может быть такое что полученное значение может быть дробным, но мы не знаем где находится точка.  Для того чтобы понять на сколько этот токен делится надо узнать его параметр <code>Decimals</code>. Для этого у нас есть специальный метод.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">decimals</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">token</span><span class="p">.</span><span class="nf">Decimals</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">bind</span><span class="p">.</span><span class="nx">CallOpts</span><span class="p">{})</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Грубо говорят параметр Decimals, это количество цифр после точки. Формирование числа с плавающей точкой и правильного его отображение достойно отдельной статьи. Поэтому давайте просто выведем число с точкой используя то что имееем.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">s</span> <span class="o">:=</span> <span class="nx">totalSupply</span><span class="p">.</span><span class="nf">String</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">s</span><span class="p">[:</span><span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="o">-</span><span class="nb">int</span><span class="p">(</span><span class="nx">decimals</span><span class="p">)]</span> <span class="o">+</span> <span class="s">&#34;.&#34;</span> <span class="o">+</span> <span class="nx">s</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="o">-</span><span class="nb">int</span><span class="p">(</span><span class="nx">decimals</span><span class="p">):])</span>
</span></span></code></pre></div><p>На этом собстевенно и все. Мы научились подключаться к блокчейну и выполнять метод контракта с возвращаемым значением. Исходный код можно найти на <a href="https://github.com/Rustery/rustery-go-eth-client">гитхабе</a>.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
