Пост

Отправка ERC-20 токенов в PHP

Отправка ERC-20 токенов в PHP

Что такое ERC-20

Представим, что нам нужно перевести USDT токены с одного адреса на другой. Как это сделать в PHP? Перевод ERC-20 токена уже будет немного посложнее чем просто перевод ETH.

Итак, что вообще означает “ERC-20 токен”? Каждый токен в блокчейне - это отдельный смарт-контракт. ERC-20 это стандарт (Ethereum Request for Comments № 20), в котором была представлена идея с “взаимозаменяемыми токенами” и интерфейс для них. То есть стандарт — это по сути интерфейс и если токен следует этому стандарту, то у него есть набор методов, которые токен или его смарт-контракт должны реализовывать. Иными словами если смарт-контракт реализует эти методы, то он является ERC-20 токеном. Это удобно тем, что нам не нужно писать логику под каждый отдельный токен. Достаточно просто работать с этим интерфейсом.

ABI

Сперва перед отправкой всегда нужно проверить, что у нас достаточно этого токена на балансе. Как это сделать? Так как токен в блокчейне — это отдельный смарт-контракт, то здесь нам уже нужно обращаться не просто к блокчейн ноде, а нужно выполнить запрос к смарт-контракту. Как это сделать? Помним, что ERC-20 токены реализуют один общий интерфейс. Интерфейс смарт-контракта состоит из методов и ивентов. Вот список методов ERC-20 токена:

1
2
3
4
5
6
7
8
9
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

Из этого списка нам сейчас интересен метод balanceOf() — получение баланса для указанного адреса. Каким же образом нам из PHP вызвать этот метод смарт-контракта в блокчейне? Для взаимодействия со смарт-контрактами используется такая концепция как ABI. Application Binary Interface (ABI) используется для кодирования вызовов контракта для виртуальной машины Ethereum и для считывания данных из транзакций. Целью ABI является определение функций в контракте, которые могут быть вызваны и описание того, как каждая функция будет принимать аргументы и возвращать свой результат. ABI смарт-контракта указывается как JSON-массив описаний функций и ивентов этого контракта. Каждое описание это json-объект. Вот пример функций balanceOf() из erc20 токена:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "constant": true,
  "inputs": [
    {
      "name": "who",
      "type": "address"
    }
  ],
  "name": "balanceOf",
  "outputs": [
    {
      "name": "",
      "type": "uint256"
    }
  ],
  "payable": false,
  "stateMutability": "view",
  "type": "function"
}

Где взять ABI json?

Можно просто загуглить ABI ERC-20. Можно в эксплорере, перейдя в раздел с кодом смарт-контракта. Например 0x7169d38820dfd117c3fa1f22a697dba58d90ba06 — адрес тестового USDT токена в блокчейне Ethereum Sepolia. Переходим на вкладку contract. Там будет поле с Contract ABI и возможность скопировать или сразу скачать json.

Теперь имея этот json, в котором представлен интерфейс всех ERC-20 токенов, можно начинать взаимодействовать с этими смарт-контрактами. Создадим инстанс-обертку для работы со смарт-контрактами. Для этого нам потребуется адрес смарт-контракта и его ABI в виде json-а.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use SWeb3\SWeb3_Contract;
use SWeb3\SWeb3;

$node = new SWeb3('https://eth.public-rpc.com');
$node->chainId = 0xaa36a7; // Sepolia network

$node->setPersonalData(
    '0x7e2f443930Ad42609b29efeFef7E132d9022DFBc', 
    'your-private-key-value',
);

$usdtAddress = '0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0';
$erc20ABI = '{...}';
$contract = new SWeb3_contract($node, $usdtAddress, $erc20ABI);

Получение баланса

Далее можно запросить баланс любого адреса. Например, запросим наш баланс. Результат вернется в минимальном номинале. Для USDT это 6 знаков после запятой. Чтобы привести к читаемому числу используем хэлпер Utils::fromWeiToDecimalsString():

1
2
3
4
5
use SWeb3\Utils;

$res = $contract->call('balanceOf', [$node->personal->address]);
$senderBalance = Utils::fromWeiToDecimalsString($res->result, 6);
echo sprintf('Sender balance: %s USDT', $senderBalance) . PHP_EOL;

Так как получение баланса — операция чтения из блокчейна, то она бесплатна и не требует создания и подписи транзакции.

Получить себе немного тестовых токенов можно тут же во вкладке “Write Contract”. Нужно вызвать метод _mint() и указать свой адрес в поле receiver и количество токенов в amount. Важно помнить, что количество указывается в минимальном номинале (1 USDT = 1000000). Таким образом если хотим получить себе 10 USDT, то нам нужно будет подписать транзакцию с таким вызовом:

Отправка токенов

Теперь попробуем отправить немного токенов на другой адрес. Помним, что вместе с транзакцией нам нужно будет отправить nonce. И вызываем метод transfer() у смарт-контракта.

1
2
3
4
5
6
$receiver = '0xEe28a49397BdB477C8FE79a723aDbD51fB1CCb51';
$value = 1000000;
$extraData = ['nonce' => $node->personal->getNonce()];

$result = $contract->send('transfer', [$receiver, $value], $extraData);
echo 'Transaction hash: ' . $result->result . PHP_EOL;

В результате будет создана блокчейн транзакция, где отправителем будем мы, а получателем — смарт-контракт. Внутри транзакции будет закодирован вызов метода transfer() с нашими параметрами. По сути мы просто меняем стейт контракта: у нас баланс уменьшается, а у получателя — увеличивается. В ответ на вызов получим хэш транзакции.

Обратите внимание, что когда мы читаем данные из контракта — используется метод call(), когда записываем — send().

А что если я хочу указать своё значение gasLimit?

Своё значение gas-а можно передать вместе с nonce-ом в $extraData. Если этого не сделать, то под капотом будет просто вызван метод eth_estimateGas у блокчейн ноды.