Что делают резолверы в Aqua
По всей 1inch Network свопы, исполняющие ликвидность Aqua, маршрутизируются 1inch Resolver'ами: маркет-мейкерами и арбитражными трейдерами, которые проходят процесс верификации 1inch.
Сама Aqua не занимается ни поиском, ни матчингом. За один вызов свопа она оценивает и исполняет одну позицию против одного контрагента. Какую позицию и в каком объёме исполнять, решает резолвер: маршрутизация 1inch передаёт свопы сети, а ваши собственные системы могут вызывать контракты напрямую.
Впервые сталкиваетесь с базовой механикой? Начните с раздела как работает Aqua.
Перед началом
- Верификация резолвера через портал 1inch Business, которая выдаёт токен доступа для каждой сети
- Токен доступа на операторской EOA, отправляющей транзакцию свопа
- Доступ к ноде в целевой сети, а также входные токены и газ для исполнения
- Канонические адреса контрактов, одинаковые в каждой поддерживаемой сети и перечисленные ниже
- API-ключ из Business portal для пути обнаружения через Aqua API
Адреса контрактов
Aqua и роутер SwapVM развёрнуты детерминированно, с одинаковым адресом в каждой поддерживаемой сети. Взаимодействуйте только с этими двумя контрактами. Всё остальное не Aqua.
- Aqua (реестр)
0x1111113ccf1426a8e30e2bff5e005d929bf6a90a- Роутер SwapVM
0x111111338c5091e8440b67b168bae16a668ac0de
Как проходит исполнение
- 1
Пройдите верификацию
Завершите онбординг резолвера через портал Business и получите токен доступа для каждой сети, где вы исполняете свопы.
- 2
Найдите позицию
Получайте открытые позиции из Aqua API, восстанавливайте живой набор по событиям Shipped и Docked или принимайте свопы из маршрутизации 1inch как сетевой резолвер.
- 3
Котируйте прямо перед отправкой
Вызовите функцию quote роутера статическим вызовом с декодированным ордером. Котировки читают живое обеспечение кошелька и меняются, как только приходят другие исполнения.
- 4
Задайте границы
Выведите минимальный выход из свежей котировки и установите короткий дедлайн. Сдвинувшаяся кривая тогда откатит транзакцию вместо исполнения по худшему курсу.
- 5
Выполните своп и проверьте
Отправьте своп с EOA, владеющей токеном доступа, требуйте успешный чек и читайте исполненные суммы из события Swapped.
Быстрый старт: котировка и исполнение одной позиции
Примеры ниже находят открытую позицию через Aqua API, запрашивают котировку и отправляют защищённое исполнение. Они работают на Node 22 с TypeScript SDK. Контракты, события, SDK и API называют позицию strategy, поэтому код тоже.
pnpm add @1inch/aqua-sdk @1inch/swap-vm-sdk viemimport assert from 'node:assert'
import { HexString, Order } from '@1inch/swap-vm-sdk'
// Every currently open position from every LP, newest first. Auth is the
// same API key the other 1inch APIs use - issued in the Business portal.
const response = await fetch('https://api.1inch.com/aqua/v1.0/strategies/opened?chainIds=1&limit=50', {
headers: { Authorization: `Bearer ${process.env.ONEINCH_API_KEY}` },
})
assert(response.ok, `Aqua API responded ${response.status}`)
const { items, nextCursor } = await response.json()
// Pick by your own criteria. Per-token balance and allowance in each item are
// useful pre-filters, but they are indexed with a delay - only a fresh
// on-chain quote() is authoritative.
const strategy = items[0]
const order = Order.decode(new HexString(strategy.strategyBytes))
console.log('candidate:', strategy.strategyHash, 'tokens:', strategy.tokens.length, 'more:', nextCursor !== null)import assert from 'node:assert'
import { ABI, Address, HexString, Order, SwappedEvent, SwapVMContract, TakerTraits } from '@1inch/swap-vm-sdk'
import { createPublicClient, createWalletClient, decodeFunctionResult, erc20Abi, http, isHex } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'
// The SwapVM router is a deterministic deployment - the same address on every
// supported chain (see the contract addresses above). The strategy bytes come
// from the Aqua API or a Shipped event. The operator key belongs to the EOA
// holding the resolver credential - it must be the transaction origin.
const router = new Address('0x111111338c5091e8440b67b168bae16a668ac0de')
const strategyBytes = process.env.STRATEGY_BYTES
const operatorPrivateKey = process.env.OPERATOR_PRIVATE_KEY
assert(isHex(strategyBytes) && isHex(operatorPrivateKey))
const order = Order.decode(new HexString(strategyBytes))
const USDC = new Address('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48')
const WETH = new Address('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2')
const amountIn = 3_000n * 10n ** 6n
const operator = privateKeyToAccount(operatorPrivateKey)
const publicClient = createPublicClient({ chain: mainnet, transport: http() })
const wallet = createWalletClient({ chain: mainnet, transport: http(), account: operator })
// 1. Preview the swap with a static quote() call, right before you submit.
const quoteTx = SwapVMContract.buildQuoteTx(router, {
order,
tokenIn: USDC,
tokenOut: WETH,
amount: amountIn,
takerTraits: TakerTraits.new({ exactIn: true }),
})
const quoted = await publicClient.call({ to: quoteTx.to, data: quoteTx.data })
assert(quoted.data, 'quote call returned no data')
const [, quotedOut] = decodeFunctionResult({ abi: ABI.SWAP_VM_ABI, functionName: 'quote', data: quoted.data })
console.log('quoted output:', quotedOut)
// 2. Approve the router to move the input token (once per token and chain).
const approveHash = await wallet.writeContract({
address: USDC.toString(),
abi: erc20Abi,
functionName: 'approve',
args: [router.toString(), amountIn],
})
await publicClient.waitForTransactionReceipt({ hash: approveHash })
// 3. Swap with a minimum-output floor and a short deadline. Balances and
// curves move between quote and fill - never submit unprotected traits.
const swapTx = SwapVMContract.buildSwapTx(router, {
order,
tokenIn: USDC,
tokenOut: WETH,
amount: amountIn,
takerTraits: TakerTraits.new({
exactIn: true,
threshold: (quotedOut * 995n) / 1_000n,
deadline: BigInt(Math.floor(Date.now() / 1_000) + 60),
}),
})
const fillHash = await wallet.sendTransaction(swapTx)
const receipt = await publicClient.waitForTransactionReceipt({ hash: fillHash })
assert(receipt.status === 'success', 'fill reverted')
// 4. Verify the executed amounts from the router's Swapped event.
const swappedLog = receipt.logs.find((log) => log.topics[0] === SwappedEvent.TOPIC.toString())
assert(swappedLog, 'no Swapped event in the receipt')
const swapped = SwappedEvent.fromLog(swappedLog)
console.log('filled:', swapped.amountIn, '->', swapped.amountOut)Сначала прогоните цикл на форке
Всё описанное выше безопасно репетируется на локальном форке. Anvil из Foundry клонирует сеть на определённом блоке, так что вы получаете настоящие контракты и настоящие живые позиции, ничем не рискуя. С включёнными бесключевыми отправками можно действовать от имени любой EOA, уже держащей токен доступа, и пройти весь цикл, обнаружение, котировку, исполнение, проверку, до первой реальной транзакции.
# Real Aqua bytecode and live positions at the forked block, zero real spend
anvil --fork-url $YOUR_RPC_URL --auto-impersonate
# Point the quickstart at http://127.0.0.1:8545 and send from any EOA that
# already holds the per-chain access token - no key needed on a forkКак исполнение проходит ончейн
Исполнение — это одна транзакция в роутер SwapVM. Роутер выполняет программу инструкций позиции: сначала проверку доступа, затем ценовую кривую, затем перемещения токенов. Входной токен уходит с вашей EOA в кошелёк LP, а выходной возвращается вам в той же транзакции, при этом реестр Aqua обновляет балансы позиции и эмитирует Pushed и Pulled. Ничто ни в какой момент не лежит в пуле.
Всё по принципу всё или ничего. Если при исполнении не проходит любая проверка, нет токена доступа, не выполнен threshold, истёк deadline или не хватает обеспечения кошелька, вся транзакция ревертится и токены не двигаются. Ревертнутое исполнение стоит газа и больше ничего.
Роутер даёт quote и swap, и оба проходят один и тот же ценовой путь. Ни один не является view-функцией, поэтому вызывайте quote через eth_call статически: он читает живое состояние, оценивает ровно тот своп, что вы передали, и ничего не меняет. Этот предпросмотр не обязательство. Исполнение связывают только traits, отправленные вместе со swap.
TakerTraits несут ваши границы исполнения в транзакцию. Их обеспечивает сеть, а не SDK. Чаще всего нужны:
- exactIn
- Какая сторона фиксирована. При точном входе вы задаёте отправляемую сумму и ограничиваете получаемую. При точном выходе фиксируете получение и ограничиваете отправку.
- threshold
- Сама граница. При точном входе это минимальный выход, который обязано дать исполнение. При точном выходе — потолок ваших затрат. Нулевой threshold отключает проверку, поэтому всегда выводите его из свежей котировки.
- deadline
- Срок в unix-секундах. Транзакция, замайненная позже, ревертится, поэтому исполнение, застрявшее в мемпуле, не может пройти по устаревшей кривой.
- customReceiver
- Доставляет выход на адрес, отличный от отправляющей EOA, например на казначейский. Проверка доступа всё равно идёт по источнику транзакции.
- shouldUnwrap
- Получайте нативный токен вместо обёрнутой формы, когда выход — обёрнутый нативный токен.
Не доверяйте результату по собственным расчётам. Требуйте успешный receipt, затем декодируйте событие Swapped роутера: orderHash, maker, taker, tokenIn, tokenOut, amountIn и amountOut. Это исполненные суммы, и именно их должна фиксировать ваша учётная система.
Сценарии сбоев, которые нужно обрабатывать
- Закрытая позиция
- LP может закрыть позицию в любой момент, и после этого чтения баланса ревертятся. Позиция, отсутствующая в списке opened у API, закрыта, но между опросами источником истины остаётся ончейн-реверт. Проверяйте заново перед каждым исполнением.
- Низкое обеспечение кошелька
- Позиции котируются из общего баланса кошелька. Конкурирующее исполнение может опустошить его первым, и ваш перевод ревертнется, хотя котировка выглядела хорошо. Поля balance и allowance из API помогают в предварительном отборе, но индексируются с задержкой. Авторитетна только свежая ончейн-котировка.
- Сдвинувшаяся цена
- Котировки устаревают по мере прихода исполнений и хода времени. Минимальный выход и дедлайн превращают плохое исполнение в чистый откат.
- Нет токена доступа
- Без учётных данных на источнике транзакции позиции, созданные в dApp, откатывают своп ещё до расчёта цены.
- Неканонические адреса
- Aqua — только два канонических адреса выше. Исполнения, отправленные куда-либо ещё, не попадают в живые позиции. Проверять по сетям нечего, развёртывания везде одинаковы.
Работа в продакшене
Относитесь к операторской EOA как к учётным данным, а не просто кошельку. Она держит токен доступа для каждой сети, поэтому изолируйте её ключ в выделенном подписанте, держите на адресе только рабочий запас и газ и не используйте её ни для чего другого.
Пусть оценка газа будет вашими последними воротами. Библиотеки кошельков оценивают газ перед отправкой, и неудачная оценка — это реверт, всплывший рано, до того как что-либо отправлено. Считайте её пропущенным исполнением и переходите к следующему кандидату, а не повторяйте вслепую.
Следите за тремя сигналами. Доля ревертов, которая растёт, когда данные обнаружения устаревают или размер подходит слишком близко к обеспечению. Возраст котировки за каждым исполнением, который должен держаться в секундах. И исполненные суммы из Swapped против котированных, что ловит дрейф до того, как он обойдётся вам.
Поиск позиций в продакшене
Живой набор можно получить двумя путями. Aqua API — самый быстрый старт. Восстановление по событиям трастлесс и не добавляет внешних зависимостей. Оба пути ведут в один и тот же поток котировки и исполнения.
Aqua API выдаёт все открытые сейчас позиции всех LP: GET /v1.0/strategies/opened на api.1inch.com/aqua, с курсорной пагинацией до 500 записей на страницу и фильтрами по сети и app. Каждая запись содержит strategyBytes для Order.decode, а также balance и allowance по каждому токену для предварительного отбора кандидатов. Запросам нужен тот же API-ключ, что и другим API 1inch, он выдаётся в Business portal. Индекс немного отстаёт от сети, поэтому перед исполнением всегда запрашивайте свежую ончейн-котировку.
Реестр Aqua, ключевой контракт протокола, эмитит Shipped при открытии позиции и Docked при закрытии. Живой набор — это Shipped минус Docked, восстановленный из логов начиная с блока деплоя в каждой сети. Поля событий лежат в данных лога, а не в topics: фильтруйте по адресу контракта и сигнатуре события, затем декодируйте и сопоставляйте на стороне клиента.
Формы событий небольшие. Shipped несёт maker, app, strategyHash и полные байты стратегии. Docked несёт maker, app и strategyHash. SDK даёт типизированные помощники для обоих, ShippedEvent и DockedEvent, с декодерами fromLog и константами TOPIC, так что ABI вручную вы не собираете никогда.
import { DockedEvent, ShippedEvent } from '@1inch/aqua-sdk'
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const AQUA = '0x1111113ccf1426a8e30e2bff5e005d929bf6a90a'
const publicClient = createPublicClient({ chain: mainnet, transport: http() })
// Shipped and Docked carry every field in the log data, none in topics, so
// pull the registry's logs and match on the event signature client-side.
const logs = await publicClient.getLogs({
address: AQUA,
fromBlock: 23_000_000n, // the per-chain deployment block, or your last checkpoint
toBlock: 'latest',
})
const live = new Map<string, ShippedEvent>()
for (const log of logs) {
if (log.topics[0] === ShippedEvent.TOPIC.toString()) {
const shipped = ShippedEvent.fromLog(log)
live.set(shipped.strategyHash.toString(), shipped)
} else if (log.topics[0] === DockedEvent.TOPIC.toString()) {
live.delete(DockedEvent.fromLog(log).strategyHash.toString())
}
}
// live now maps strategyHash -> Shipped payload: maker, app and the strategy
// bytes you decode with Order.decode before quoting.
console.log('open positions:', live.size)Остальное дают ончейн-чтения. Вью rawBalances и safeBalances реестра показывают обеспечение кошелька за позицией, а события Swapped роутера несут исполненные свопы. Держите котировки свежими, а не кэшируйте их.
Размер исполнения начинается с обеспечения, а не с котировки. Прочитайте обеспечение позиции через вью реестра, держите размер с запасом внутри него и возьмите свежую котировку ровно на этот размер прямо перед отправкой. Поля balance и allowance из API — дешёвый первый фильтр по многим кандидатам, вью и котировка — истина для одного.
FAQ для резолверов
Нет. Проверка доступа требует, чтобы источником транзакции была EOA с токеном доступа, поэтому контрактные кошельки и бандлеры её не проходят. Отправляйте исполнения с верифицированной операторской EOA.
Нет. Функция quote — статический предпросмотр живого состояния, она ничего не двигает. Исполнение связывают только TakerTraits, отправленные со свопом: threshold и deadline обеспечиваются ончейн.
Газ и больше ничего. Любая непройденная проверка ревертит всю транзакцию, токены не двигаются ни с одной стороны.
Верификация через 1inch Business portal выдаёт токен доступа для каждой сети. Операторской EOA этот токен нужен в каждой сети, где вы исполняете.
Готовы исполнять ликвидность Aqua?
Начните верификацию и свяжитесь с командой через портал 1inch Business.