Если существует арбитражная возможность между несколькими парами, когда один токен дороже в одной паре, чем в другой, данная функция может получить с этого доход.
В нашем примере мы обнаруживаем, что PEPE/USDC стоит 0.90 USD, а PEPE/WETH — 1.00 USD. Значит мы могли бы занять USDC, купить за них PEPE, продать их за WETH и получить доход с разницы курсов.
- Занимать можно сколько угодно USDC, если эта сумма есть в паре и её можно будет вернуть в конце с +0.30% комиссии.
- В процессе арбитражного просвопа внутри пар мы также должны будем оплатить 0.30% комиссии за каждый своп.
- Если арбитраж невыгоден (то есть, взяв 100 USDC мы не сможем вернуть как минимум 100.30 USDC + хотя бы 0.000000...1 USDC доход) тогда попытка вызвать функцию вернёт ошибку.
- Расходы на газ не входят в расчёты внутри функции (то есть, если взяв 100 USDC мы можем вернуть как минимум 100.30 USDC и получить например 10 USDC дохода, это не отменяет того, что при этом может быть затрачено более 10 USDC на газ).
- Токен нельзя вернуть свопом в той же паре, где он был занят (то есть, если мы занимаем USDC из пары USDC/USDT и полученный доход в конце свопаем в паре WETH -> USDC — мы сможем вернуть USDC, но если мы заняли USDC в USDC/USDT и попытаемся полученный доход свопнуть в той же паре USDT -> USDC, роутер вернёт lock ошибку).
Исходя из этих правил мы формируем успешный путь арбитражного просвопа:
Занимаем USDC (в паре USDC/USDT) -> Меняем USDC на PEPE -> Меняем PEPE на WETH -> Меняем WETH на USDC -> Возвращаем USDC (в пару USDC/USDT).
Вызываем функцию flashSwapArbitrage
, передав в неё следующие параметры:
- address
_tokenBorrow
, // USDC - address
_tokenBorrowPair
, // USDT - uint
_borrowedAmount
, // 5000000000000 (0.000005 в wei) - address
_tokenWithProfit
, // PEPE - address
_tokenWithProfitPair
// WETH
Получаем успешный результат: https://goerli.etherscan.io/tx/0x381421258132f48036bb70af6951248d0e411aa5a51fbcbfafe322c892d3fa44
Доход в виде USDC остался на адресе контракта.
Чтобы его вывести, используем функцию withdrawToken
(только владелец контракта может вызывать). Передаём туда tokenAddress
адрес смарт-контракта токена и to
куда переводить. Будет выведена вся сумма. Газ оплачивает владелец контракта.
Если вывести нужно ether (основная валюта сети: ETH, BNB и т.д.) — вызываем функцию withdrawEther
и указываем to
куда вывести весь баланс.
Задеплоить контракт нужно со своего адреса, чтобы быть его владельцем и выводить с него деньги только себе.
- Находим в этом репозитории файлы
FlashSwapper.sol
иUniswapV2Interfaces.sol
- Заливаем эти два файла в Remix, например в отдельную папку, или просто чтобы они находились рядом
- Открываем файл
FlashSwapper.sol
и слева переходим на вкладку Solidity compiler. - Выписываем себе номер версии компилятора (например 0.8.21)
- Переключаемся в меню ниже на следующую вкладку Deploy & Run transactions
- Выбираем Injected provider — MetaMask
- Выбираем Account с которого будем деплоить
- Выбираем Contract — FlashSwapper
- Разворачиваем стрелочкой пункт Deploy и вставляем адреса контрактов роутера и фактори нужной сети и биржи (можно найти в их документации)
- Вызываем transact и деплоим
- Верифицируем контракт в эксплорере: переходим на вкладку Contract и выбираем Verify and Publish
- Выбираем Solidity (Single file), версию компилятора которую мы выписали на шаге 4., и лицензию MIT License (MIT)
- В Remix на файле
FlashSwapper.sol
нажимаем ПКМ и выбираем flatten. - Открываем этот flatten файл и копируем из него весь код, вставляем его в поле для Solidity Contract code и верифицируем. Так можно будет вызывать функции прямо из эксплорера если нужно.
- Прежде всего обязательно найдите MEV blocker RPC и установите его в качестве RPC нужной сети в кошельке (в Ethereum это mevblocker.io, в Binance это bsc.meowrpc.com и т.д.), иначе ваши арбитражные действия могут стать жертвами фронт-раннеров
- Теперь используя функцию
flashSwapArbitrage
можно вводить адреса и значения для арбитража (газ оплачивается с адреса вызывающего, вызывать может любой адрес, даже не владелец) - Вывести доходы можно используя
withdrawToken
илиwithdrawEther
только с адреса владельца контракта
Проект собран в Hardhat.
- Клонируем репозиторий
- Вызываем в консоли
npm install
- В
hardhat.config.js
указываем ссылки или API ключи сетей вurl
- В
hardhat.config.js
указываем приватный ключ кошелька/аккаунтаowner
вaccounts
дляowner
- Убеждаемся, что
routerAddress
иfactoryAddress
вdeploy.js
соответствуют адресам выбранной сети - Вызываем в консоли
npx hardhat run scripts/deploy.js --network bscTestnet
(либоalchemyGoerli
, либо любая другая сеть)
https://www.youtube.com/watch?v=MxTgk-kvtRM
https://github.com/stakewithus/defi-by-example
https://github.com/fxfactorial/uniswap-flash-swapper
https://github.com/LHerskind/ArbSwapper-v2/blob/master/src/BaseSwapperV2.sol (модуль под Curve)
https://github.com/Haehnchen/uniswap-arbitrage-flash-swap/blob/main/contracts/Flashswap.sol (между 2 биржами)
https://github.com/gweidart/evm-flashswap-arb/blob/main/contracts/FlashBot.sol (между 2 биржами)
https://forum.openzeppelin.com/t/flash-loan-on-uniswap-v2/35559