
В этом руководстве мы покажем вам, как использовать сервисы и функции Axelar для создания децентрализованного приложения, которое вызывает контракт в цепочке B из цепочки A и передает токены, например, для покупки NFT, отправки аирдропа или распределения токенов из ДАО. Контракт в цепочке B может делать что угодно, пока он реализует IAxelarExecutable
интерфейс.
- Предпочитаете видео? Посмотрите, как инженеры Axelar увидят живую демонстрацию кодирования
- Готовы пропустить вперед? Посетите docs.axelar.dev и ознакомьтесь с нашим репозиторием примеров децентрализованных приложений на GitHub.
1. Установите ноды .
Первое, что вам нужно сделать, это установить Node, если у вас его еще нет.
Запустите node -v
, чтобы проверить вашу установку. Это должно показать что-то вроде этого:
$ node -v
v17.8.0
2. Клонируйте репозиторий axelar-local-gmp-examples .
Мы создали библиотеку примеров приложений — клонируйте ее на свой компьютер.
git clone <https://github.com/axelarnetwork/axelar-local-gmp-examples.git>
3. Создавайте контракты и тесты.
Установите все модули узлов и создайте контракты.
npm update && npm install
npm run build
4. Разберитесь в call-contract-with-tokens
коде.
💡 The following code snippets are for explanatory purposes only. They are incomplete and should not be directly copied. Instead, follow the instructions to clone the
GitHub
and run the code from there.
Для этого конкретного урока мы будем использовать пример приложения в examples/call-contract-with-tokens
папке.
cd examples/call-contract-with-tokens
Эта папка должна содержать два файла, index.js
и DistributionExecutable.sol
.
Давайте взглянем на нашу index.js
. Это «интерфейс» нашего dApp, который содержит две функции: a deploy
и a test
. При вызове deploy
будет развернут наш внутренний контракт Solidity DistributionExecutable.sol
в каждой цепочке с классом, инициализированным значениями из файла конфигурации. Мы сделаем это позже.
'использовать строгий'; const { getDefaultProvider, Contract, const: { 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'); асинхронная функция deploy(chain, wallet) { console.log(`Развертывание DistributionExecutable для ${chain.name}.`); const contract = await deployContract(wallet, DistributionExecutable, [chain. шлюз, chain.gasReceiver]); chain.distributionExecutable = адрес контракта; console.log(`Развернутый DistributionExecutable для ${chain.name} в ${chain.distributionExecutable}.`); }
Теперь давайте посмотрим на test
функцию, которая вызывает функции в коде смарт-контракта. В вашем собственном приложении у вас, вероятно, будет настоящий интерфейс, в котором взаимодействие со смарт-контрактом будет инициироваться элементами пользовательского интерфейса.
Во-первых, test
функция инициализирует distributionExecutable
контракт в каждой цепочке. (Каждая цепь будет иметь ход).distributionExecutable
тест асинхронной функции (цепочки, кошелек, параметры) { ... for (константная цепочка [источник, пункт назначения]) { const provider = getDefaultProvider(chain.rpc); chain.wallet = wallet.connect(поставщик); chain.contract = новый контракт (chain.distributionExecutable, DistributionExecutable.abi, chain.wallet); chain.gateway = новый контракт (chain.gateway, Gateway.abi, chain.wallet); const usdcAddress = chain.gateway.tokenAddresses('aUSDC'); chain.usdc = новый контракт (usdcAddress, IERC20.abi, chain.wallet); } ... }
Функция test
принимает параметры командной строки. Первый параметр — цепочка A, из которой вы вызываете контракт с токенами . Второй параметр — цепочка B, в которую вы отправляете сообщение с токенами .
проверка асинхронной функции (цепочки, кошелек, опции) { const args = options.args || []; ... const source = chains.find((chain) => chain.name == (args[0] || 'Лавина')); const destination = chains.find((chain) => chain.name == (args[1] || 'Фантом')); константная сумма = Math.floor(parseFloat(args[2])) * 1e6 || 10е6; константные счета = args.slice(3); ... }
Приложение, которое хочет, чтобы Axelar автоматически выполнял вызовы контрактов в цепочке B, должно предварительно оплатить стоимость газа. Для расчета ориентировочной стоимости газа:
- Оценить
gasLimit
которые вызов контракта потребует в цепочке B.
- Запрос
(
getGasPrice
) для относительной стоимости газа.
- Рассчитайте количество токенов, подлежащих выплате, как
gasLimit * gasPrice
тест асинхронной функции (цепочки, кошелек, опции) { ... const getGasPrice = options.getGasPrice; ... //Установите gasLimit на 3e6 (безопасное завышение) и получите цену на газ. const gasLimit = 3e6; const gasPrice = await getGasPrice (источник, пункт назначения, AddressZero); ... }
Наконец, мы вызываем sendToMany()
метод для DistributionExecutable
контракта, который был развернут в цепочке A. Этот метод позволяет нам выполнять контракт и отправлять токены на несколько учетных записей одновременно.
Метод sendToMany()
принимает имя цепочки B, адрес контракта в цепочке B, счета для отправки токенов, символ токена и сумму токена, которая будет зачислена, а также количество газа, рассчитанное выше:
тест асинхронной функции (цепочки, кошелек, параметры) { ... ожидание ( await source.contract.**sendToMany**(destination.name, destination.distributionExecutable, account, 'aUSDC', sum, { value: BigInt(Math. пол (лимит газа * цена газа)), }) ).wait(); в то время как (BigInt (ожидание назначения.usdc.balanceOf (счета [0])) == баланс) { ожидание сна (2000); } ... }
Теперь давайте перейдем к тому, DistributionExecutable.sol
чтобы увидеть, что на самом деле делает смарт-контракт.
Axelar предоставляет услугу ретрансляции IAxelarGasService
, которая обеспечивает выполнение одобренных сообщений. Вы можете импортировать сервис из основных библиотек Axelar Solidity.
//SPDX-License-Identifier: прагма MIT Solidity 0.8.9; импортировать {IAxelarExecutable} из '@axelar-network/axelar-cgp-solidity/contracts/interfaces/IAxelarExecutable.sol'; импортировать {IERC20} из '@axelar-network/axelar-cgp-solidity/contracts/interfaces/IERC20.sol'; импортировать {IAxelarGasService} из '@axelar-network/axelar-cgp-solidity/contracts/interfaces/IAxelarGasService.sol'; контракт DistributionExecutable is IAxelarExecutable { IAxelarGasService gasReceiver; конструктор (адрес _gateway, адрес _gasReceiver) IAxelarExecutable (_gateway) { gasReceiver = IAxelarGasService (_gasReceiver); } ... }
Газ IAxelarGasService
можно получать несколькими различными способами, но в этом примере приложения мы будем платить за газ собственным токеном цепочки А, вызывая payNativeGasForContractCall
метод в сервисе. Как только газ будет оплачен, мы можем сделать соответствующий вызов службе Axelar Gateway, развернутой в цепочке A.
функция sendToMany( ... ) внешняя подлежащая оплате { адрес tokenAddress = gateway.tokenAddresses(symbol); IERC20 (tokenAddress).transferFrom (msg.sender, адрес (этот), сумма); IERC20(tokenAddress).approve(адрес(шлюз), сумма); полезная нагрузка памяти в байтах = abi.encode(destinationAddresses); if (msg.value > 0) { gasReceiver.**payNativeGasForContractCallWithToken**{ value: msg.value }( ... ); } gateway.callContractWithToken(цепочка назначения, адрес назначения, полезная нагрузка, символ, сумма); }
Чтобы вызвать цепочку B из цепочки A и отправить по пути какие-то токены, пользователю необходимо обратиться callContractWithToken
к шлюзу цепочки A, указав:
- Цепочка назначения: должна быть цепочкой EVM из
.
- Адрес назначения контракта: должен реализовать
IAxelarExecutable
интерфейс, определенный в
.
- Полезная нагрузка
bytes
перейти к целевому контракту.
- Символ передаваемого токена: должен быть поддерживаемым активом [
|
|
].
- Сумма токена для перевода.
💡 To call a contract and send tokens, the user needs to specify the destination chain, the destination contract address, the payload bytes to be sent, the symbol of the token**,** and the amount.
Наконец, IAxelarExecutable
имеет _executeWithToken
функцию, которая будет запущена сетью Axelar после выполнения callContractWithToken
функции. Другими словами, когда контракт в цепочке B вызывается через callContractWithToken
из цепочки A, _executeWithToken
запускается метод контракта в цепочке B.
executeWithToken()
имеет следующую подпись:функция _executeWithToken (строковая память sourceChain, строковая память sourceAddress, байты полезной нагрузки calldata, строковая память tokenSymbol, количество uint256) внутренний виртуальный {}
Вы можете написать любую пользовательскую логику внутри файла _executeWithToken
. В нашей _executeWithToken
функции мы расшифровываем предполагаемых получателей токенов, затем передаем токены каждому из получателей.
функция _executeWithToken (строковая память, строковая память, байты полезной нагрузки calldata, строковая память tokenSymbol, количество uint256) внутреннее переопределение {адрес [] получатели памяти = abi.decode (полезная нагрузка, (адрес [])); адрес tokenAddress = gateway.tokenAddresses(tokenSymbol); uint256 sentAmount = количество/получатели.длина; 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, адрес (этот), сумма); IERC20(tokenAddress).approve(адрес(шлюз), сумма); байт полезной нагрузки памяти = abi.encode("purchaseNFT(uint256)", 56, destinationAddress) if (msg.value > 0) { gasReceiver.**payNativeGasForContractCallWithToken**{ value: msg.value }( ... ); } gateway.callContractWithToken(цепочка назначения, адрес назначения, полезная нагрузка, символ, сумма); }
_executeWithToken
Затем вы можете напрямую address(this).call(payload)
выполнить действие, purchaseNFT(56)
прежде чем пересылать полученные токены на адрес продавца NFT .
function _executeWithToken(строковая память, строковая память, байты полезной нагрузки calldata, строковая память tokenSymbol, количество uint256) внутреннее переопределение { // вызвать покупку NFT(56) address(this).call(payload) (/*игнорировать сигнатуру и аргумент метода*/, адрес продавца) = abi.decode(полезная нагрузка, (строка, uint256, адрес)); // получить адрес ERC-20 из адреса шлюза tokenAddress = gateway.tokenAddresses(tokenSymbol); // передать полученные токены получателю IERC20(tokenAddress).transfer(recipient, amount); }
5. Запустите локальную ноду Axelar
Теперь, когда мы поняли call-contract-with-token
код, давайте посмотрим, как он работает. Во-первых, раскрутите локальный узел Axelar.
скрипты узла/createLocal
Оставьте этот узел работающим на отдельном терминале, прежде чем развертывать и тестировать dApps.
6. Разверните контракт
Разверните контракт с помощью команды ниже.
сценарии узлов/примеры развертывания/локальный вызов контракта с токеном
Вы должны увидеть распечатку, подобную этой, которая показывает, что DistributionExecutable
он был развернут в каждой цепочке.
сценарии узла/примеры развертывания/вызов-контракт-с-токеном локальный Развертывание DistributionExecutable для Moonbeam. Развертывание DistributionExecutable для Avalanche. Развертывание DistributionExecutable для Fantom. Развертывание DistributionExecutable для Ethereum. Развертывание DistributionExecutable для Polygon. Развернутый DistributionExecutable для Fantom по адресу 0x775C53cd1F4c36ac74Cb4Aa1a3CA1508e9C4Bd24. Развернутый DistributionExecutable для Ethereum по адресу 0x775C53cd1F4c36ac74Cb4Aa1a3CA1508e9C4Bd24. Развернутый DistributionExecutable для Avalanche по адресу 0x775C53cd1F4c36ac74Cb4Aa1a3CA1508e9C4Bd24. Развернутый DistributionExecutable для Moonbeam по адресу 0x775C53cd1F4c36ac74Cb4Aa1a3CA1508e9C4Bd24. Развернутый DistributionExecutable для Polygon по адресу 0x775C5
7. Запустите тест
Наконец, запустите test
(который вызывает ExecutableSample
). Команда ниже отправляет 100 токенов от Moonbeam на адрес Ethereum 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb
.
сценарии узла/тестовые примеры/контракт вызова с токеном локальный "Moonbeam" "Ethereum" 100 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb
Вы должны увидеть подобную распечатку, которая показывает, что передача сообщения прошла успешно. (Axelar берет комиссию за транзакцию, которая составляет разницу между 99 и 100).
--- Изначально --- 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb имеет 100 aUSDC --- После --- 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb имеет 199 aUSDC
Чтобы запустить тест в тестовой сети Axelar, а не на локальном узле, вы можете использовать следующие команды:
сценарии узла/примеры развертывания/контракт вызова с токеном в тестовой сети сценарии узла/примеры тестирования/контракт вызова с токеном в тестовой сети "Moonbeam" "Ethereum" 100 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb