Отправка 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
у блокчейн ноды.