Axelar Code: dApp и кроссчейн передача активов

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

Axelar Code: dApp и кроссчейн передача активов

В этом руководстве два опытных сотрудника Axelar: Vlad и Jacky, не только расскажут, но и покажут вам, как использовать сервисы и функции Axelar, для создания децентрализованного приложения, которое вызывает контракт в “сети B”, из “сети A” и передает токены, например, для покупки NFT, отправки airdrop или распределения токенов из DAO.

Контракт в “сети B” может делать что угодно, пока он реализует интерфейс `IAxelarExecutable`

Axelar Code: dApp и кроссчейн передача активов

1. Установите: nodejs

Первое, что вам нужно сделать, это установить ноду, если у вас ее еще нет. Запустите, `node -v` чтобы проверить установку. Проверка должна показать примерно это: `$ node -v v17.8.0`

2. Клонируйте репозиторий: axelar-local-gmp-examples

Мы создали библиотеку примеров приложений — просто клонируйте ее на свой компьютер.

`git clone `

3. Создайте контракты и тесты

Установите все модули ноды и создайте контракты.

`npm update && npm install npm run build`

4. Разберитесь в коде: `call-contract-with-tokens`

💡Эти фрагменты кода приведены исключительно в виде примера. Они не полные и не должны просто копироваться. Вместо этого, следуйте инструкциям, чтобы клонировать GitHub и запустить код оттуда.

Для этого урока мы будем использовать пример приложения в папке: `examples/call-contract-with-tokens`

`cd examples/call-contract-with-tokens`

Эта папка должна содержать два файла, `index.js` и `DistributionExecutable.sol`.

Давайте посмотрим на `index.js`. Это «интерфейс» нашего dApp, который содержит две функции: `deploy` и `test`. При вызове `deploy` будет развернут наш внутренний контракт Solidity `DistributionExecutable.sol` в каждой сети с классом, инициализированным значениями из файла конфигурации. Мы сделаем это позже.

“‘use strict’; const { getDefaultProvider, Contract, constants: { AddressZero }, } = require(‘ethers’); const { utils: { deployContract }, } = require(‘@axelar-network/axelar-local-dev’); const DistributionExecutable = require(‘../../artifacts/examples/call-contract-with-token/DistributionExecutable.sol/DistributionExecutable.json’); const Gateway = require(‘../../artifacts/@axelar-network/axelar-cgp-solidity/contracts/interfaces/IAxelarGateway.sol/IAxelarGateway.json’); const IERC20 = require(‘../../artifacts/@axelar-network/axelar-cgp-solidity/contracts/interfaces/IERC20.sol/IERC20.json’); async function deploy(chain, wallet) { console.log(`Deploying DistributionExecutable for ${chain.name}.`); const contract = await deployContract(wallet, DistributionExecutable, [chain.gateway, chain.gasReceiver]); chain.distributionExecutable = contract.address; console.log(`Deployed DistributionExecutable for ${chain.name} at ${chain.distributionExecutable}.`); }”

Теперь давайте посмотрим на функцию `test`, которая вызывает функции в коде смарт-контракта. В созданном вами приложении, вероятно, будет настоящий интерфейс, в котором взаимодействие со смарт-контрактом будет инициироваться элементами пользовательского интерфейса.

Во-первых, функция `test` инициализирует контракт `distributionExecutable`, в каждой сети (`distributionExecutable`будет запущен в каждой сети).

“async function test(chains, wallet, options) { … for (const chain of [source, destination]) { const provider = getDefaultProvider(chain.rpc); chain.wallet = wallet.connect(provider); chain.contract = new Contract(chain.distributionExecutable, DistributionExecutable.abi, chain.wallet); chain.gateway = new Contract(chain.gateway, Gateway.abi, chain.wallet); const usdcAddress = chain.gateway.tokenAddresses(‘aUSDC’); chain.usdc = new Contract(usdcAddress, IERC20.abi, chain.wallet); } … }”

Функция `test`принимает параметры командной строки. Первый параметр: “сеть A”, из которой вы вызываете контракт с токенами . Второй параметр: “сеть B”, в которую вы отправляете сообщение с токенами.

“async function test(chains, wallet, options) { const args = options.args || []; … const source = chains.find((chain) => chain.name == (args[0] || ‘Avalanche’)); const destination = chains.find((chain) => chain.name == (args[1] || ‘Fantom’)); const amount = Math.floor(parseFloat(args[2])) * 1e6 || 10e6; const accounts = args.slice(3); … }”

Приложение, которое хочет, чтобы Axelar автоматически выполнял вызовы контрактов в “сети B”, должно предварительно оплатить стоимость газа. Для расчета ориентировочной стоимости газа:

Оценить:

`gasLimit`

которые потребует вызов контракта в “сети B”.

Запрос:

(

`getGasPrice`

) по относительной стоимости газа.

Рассчитайте количество токенов, подлежащих к оплате:

`gasLimit * gasPrice`

“async function test(chains, wallet, options) { … const getGasPrice = options.getGasPrice; … //Set the gasLimit to 3e6 (a safe overestimate) and get the gas price. const gasLimit = 3e6; const gasPrice = await getGasPrice(source, destination, AddressZero); … }”

Наконец, мы вызываем `sendToMany()`, метод для контракта `DistributionExecutable`, который был развернут в “сети A”. Этот метод позволяет нам выполнять контракт и отправлять токены, на несколько учетных записей одновременно.

Метод `sendToMany()` принимает имя “сети B”, адрес контракта в “сети B”, счета для отправки токенов, символ токена и сумму токена, которая будет зачислена, а также количество газа, рассчитанное выше:

“async function test(chains, wallet, options) { … await ( await source.contract.**sendToMany**(destination.name, destination.distributionExecutable, accounts, ‘aUSDC’, amount, { value: BigInt(Math.floor(gasLimit * gasPrice)), }) ).wait(); while (BigInt(await destination.usdc.balanceOf(accounts[0])) == balance) { await sleep(2000); } … }”

Теперь давайте перейдем `DistributionExecutable.sol`, чтобы увидеть, что на самом деле делает смарт-контракт.

Axelar предоставляет услугу ретрансляции: `IAxelarGasService`, которая обеспечивает выполнение подтвержденных сообщений. Вы можете импортировать сервис из основных библиотек Axelar Solidity.

“//SPDX-License-Identifier: MIT pragma solidity 0.8.9; import { IAxelarExecutable } from ‘@axelar-network/axelar-cgp-solidity/contracts/interfaces/IAxelarExecutable.sol’; import { IERC20 } from ‘@axelar-network/axelar-cgp-solidity/contracts/interfaces/IERC20.sol’; import { IAxelarGasService } from ‘@axelar-network/axelar-cgp-solidity/contracts/interfaces/IAxelarGasService.sol’; contract DistributionExecutable is IAxelarExecutable { IAxelarGasService gasReceiver; constructor(address _gateway, address _gasReceiver) IAxelarExecutable(_gateway) { gasReceiver = IAxelarGasService(_gasReceiver); } … }”

`IAxelarGasService` может получать газ, несколькими различными способами, но в этом примере приложения, мы будем платить за газ нативным токеном “сети А”, вызывая в сервисе метод `payNativeGasForContractCall`. Как только газ будет оплачен, мы можем сделать соответствующий вызов службе Axelar Gateway, развернутой в сети A.

“function sendToMany( … ) external payable { address tokenAddress = gateway.tokenAddresses(symbol); IERC20(tokenAddress).transferFrom(msg.sender, address(this), amount); IERC20(tokenAddress).approve(address(gateway), amount); bytes memory payload = abi.encode(destinationAddresses); if (msg.value > 0) { gasReceiver.**payNativeGasForContractCallWithToken**{ value: msg.value }( … ); } gateway.callContractWithToken(destinationChain, destinationAddress, payload, symbol, amount); }”

Чтобы вызвать “сеть B” из “сети A” и отправить вместе с этим немного токенов, пользователю необходимо вызвать `callContractWithToken`, на шлюзе “сети A”, указав:

Сеть назначения: должна быть цепочкой EVM из

.

Адрес контракта назначения: должен реализовать

`IAxelarExecutable`

интерфейс, определенный в

.

Полезная нагрузка

`bytes`

перейти к контракту назначения.

Символ токена для передачи: должен быть поддерживаемым активом [

|

|

].

Сумма токена для перевода.

💡 Чтобы вызвать контракт и отправить токены, пользователю необходимо указать сеть назначения, адрес контракта получателя, байты полезной нагрузки для отправки, символ токена **,** и сумму.

`IAxelarExecutable` имеет функцию `_executeWithToken`, которая будет запущена сетью Axelar после выполнения функции `callContractWithToken`. Другими словами, когда контракт в “сети B”, вызывается через `callContractWithToken` из “сети A”, запускается `_executeWithToken` — метод контракта в “сети B”.

`executeWithToken()` имеет подпись:function _executeWithToken( string memory sourceChain, string memory sourceAddress, bytes calldata payload, string memory tokenSymbol, uint256 amount ) internal virtual {}

Вы можете написать любую пользовательскую логику внутри файла `_executeWithToken`. В нашей функции `_executeWithToken` мы декодируем предполагаемых получателей токенов, затем передаем токены каждому из получателей.

“function _executeWithToken( string memory, string memory, bytes calldata payload, string memory tokenSymbol, uint256 amount ) internal override { address[] memory recipients = abi.decode(payload, (address[])); address tokenAddress = gateway.tokenAddresses(tokenSymbol); uint256 sentAmount = amount / recipients.length; for (uint256 i = 0; i < recipients.length; i++) { IERC20(tokenAddress).transfer(recipients[i], sentAmount); } }”

Мы можем представить и другие сценарии, в которых вы захотели вызвать контракт в другой сети и передать некоторые токены. Предположим, что вы создаете кроссчейн NFT маркетплейс. Вы хотите сделать так, чтобы покупатель мог купить NFT, для продажи в любой сети. В этом случае ваша функция `_executeWithToken` может не только передать токен на адрес назначения, но и вызвать другой метод контракта в сети назначения, например `purchaseNFT(uint256 nftId)`.

Чтобы сделать что-то подобное, вы должны сначала закодировать строку сигнатуры функции `PurchaseNFT`, используя `abi.encode` вместе со значениями любых параметров функции. Вы также хотите закодировать адрес назначения.

“function purchaseNFT( … ) external payable { address tokenAddress = gateway.tokenAddresses(symbol); IERC20(tokenAddress).transferFrom(msg.sender, address(this), amount); IERC20(tokenAddress).approve(address(gateway), amount); bytes memory payload = abi.encode(“purchaseNFT(uint256)”, 56, destinationAddress) if (msg.value > 0) { gasReceiver.**payNativeGasForContractCallWithToken**{ value: msg.value }( … ); } gateway.callContractWithToken(destinationChain, destinationAddress, payload, symbol, amount); }”

В `_executeWithToken` вы можете напрямую запустить `address(this).call(payload)`, для выполнения `PurchaseNFT(56)` перед пересылкой полученных токенов, на адрес продавца NFT.

“function _executeWithToken( string memory, string memory, bytes calldata payload, string memory tokenSymbol, uint256 amount ) internal override { // call purchaseNFT(56) address(this).call(payload) (/*ignore method signature and argument*/, address seller) = abi.decode(payload, (string, uint256, address)); // get ERC-20 address from gateway address tokenAddress = gateway.tokenAddresses(tokenSymbol); // transfer received tokens to the recipient IERC20(tokenAddress).transfer(recipient, amount); }”

5. Запустите локальную ноду Axelar

Теперь, когда мы поняли код `call-contract-with-token`, давайте посмотрим, как он работает. Сначала разверните локальный узел Axelar.

“node scripts/createLocal”

Оставьте этот узел работающим на отдельном терминале, прежде чем развертывать и тестировать dApps.

6. Разверните контракт

Разверните контракт с помощью приведенной ниже команды.

“node scripts/deploy examples/call-contract-with-token local”

Вы должны увидеть подобную этой распечатку, которая показывает, что `DistributionExecutable` был развернут в каждой сети.

“node scripts/deploy examples/call-contract-with-token local Deploying DistributionExecutable for Moonbeam. Deploying DistributionExecutable for Avalanche. Deploying DistributionExecutable for Fantom. Deploying DistributionExecutable for Ethereum. Deploying DistributionExecutable for Polygon. Deployed DistributionExecutable for Fantom at 0x775C53cd1F4c36ac74Cb4Aa1a3CA1508e9C4Bd24. Deployed DistributionExecutable for Ethereum at 0x775C53cd1F4c36ac74Cb4Aa1a3CA1508e9C4Bd24. Deployed DistributionExecutable for Avalanche at 0x775C53cd1F4c36ac74Cb4Aa1a3CA1508e9C4Bd24. Deployed DistributionExecutable for Moonbeam at 0x775C53cd1F4c36ac74Cb4Aa1a3CA1508e9C4Bd24. Deployed DistributionExecutable for Polygon at 0x775C5”

7. Запустите тест

Запустите `test` (который вызывает `ExecutableSample`). Приведенная ниже команда отправляет 100 токенов из Moonbeam на адрес Ethereum `0xBa86A5719722B02a5D5e388999C25f3333c7A9fb`.

“node scripts/test examples/call-contract-with-token local “Moonbeam” “Ethereum” 100 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb”

Вы должны увидеть подобную распечатку, которая показывает, что передача сообщения произошла успешно. (Axelar берет комиссию за транзакцию, которая составляет разницу между 99 и 100).

“— Initially— 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb has 100 aUSDC — After— 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb has 199 aUSDC”

Чтобы запустить тест в testnet Axelar, а не на локальном узле, вы можете использовать следующие команды:

“node scripts/deploy examples/call-contract-with-token testnet node scripts/test examples/call-contract-with-token testnet “Moonbeam” “Ethereum” 100 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb”

Axelar Code: dApp и кроссчейн передача активов

Ну вот и все — пробуйте, тестируйте, изучайте и развивайтесь! Мы еще встретимся именно в технических материалах, а вот следующий будет про все разрастающуюся сеть партнеров Axelar и открывающиеся в связи с этим, новые возможности! До скорой встречи!

Начать дискуссию