Для резолверов

Исполнение свопов против ликвидности Aqua: что требуется для роли резолвера, как проходит ончейн-исполнение и как получить котировку и выполнить своп через SwapVM SDK.

10 мин чтенияОбновлено июль 2026 г.

Что делают резолверы в 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. 1

    Пройдите верификацию

    Завершите онбординг резолвера через портал Business и получите токен доступа для каждой сети, где вы исполняете свопы.

  2. 2

    Найдите позицию

    Получайте открытые позиции из Aqua API, восстанавливайте живой набор по событиям Shipped и Docked или принимайте свопы из маршрутизации 1inch как сетевой резолвер.

  3. 3

    Котируйте прямо перед отправкой

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

  4. 4

    Задайте границы

    Выведите минимальный выход из свежей котировки и установите короткий дедлайн. Сдвинувшаяся кривая тогда откатит транзакцию вместо исполнения по худшему курсу.

  5. 5

    Выполните своп и проверьте

    Отправьте своп с EOA, владеющей токеном доступа, требуйте успешный чек и читайте исполненные суммы из события Swapped.

Быстрый старт: котировка и исполнение одной позиции

Примеры ниже находят открытую позицию через Aqua API, запрашивают котировку и отправляют защищённое исполнение. Они работают на Node 22 с TypeScript SDK. Контракты, события, SDK и API называют позицию strategy, поэтому код тоже.

Установите SDKbash
pnpm add @1inch/aqua-sdk @1inch/swap-vm-sdk viem
Поиск открытых позиций через Aqua APItypescript
import 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)
Котировка, исполнение и проверкаtypescript
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, уже держащей токен доступа, и пройти весь цикл, обнаружение, котировку, исполнение, проверку, до первой реальной транзакции.

Прогон на форке мейннетаbash
# 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 вручную вы не собираете никогда.

Восстановить живой набор из событийtypescript
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 API Эндпоинты, параметры и форматы ответов для поиска позиций и статистики, в Business portal. Aqua SDK и SwapVM SDK Типизированные билдеры, декодеры и парсеры событий для флоу исполнения, в репозитории 1inch/sdks. Онбординг резолверов Шаги верификации и документация для резолверов на портале 1inch Business. White paper Aqua Полный дизайн слоя общей ликвидности.

Готовы исполнять ликвидность Aqua?

Начните верификацию и свяжитесь с командой через портал 1inch Business.

Открыть портал Business