Qué hacen los resolvers en Aqua
En toda la 1inch Network, los swaps que ejecutan la liquidez de Aqua los enrutan los 1inch Resolvers: creadores de mercado y traders de arbitraje que completan el proceso de verificación de 1inch.
Aqua no hace discovery ni matching. Cotiza y ejecuta una posición contra una contraparte por cada llamada de swap. Encontrar qué posición ejecutar, y con qué tamaño, es trabajo del resolver: el enrutamiento de 1inch entrega swaps a la red, y tus propios sistemas pueden llamar a los contratos directamente.
¿Nuevo en la mecánica subyacente? Empieza por cómo funciona Aqua.
Antes de empezar
- Verificación de resolver a través del portal 1inch Business, que concede el token de acceso por chain
- El token de acceso en la EOA operadora que envía la transacción de swap
- Acceso a un nodo en la chain objetivo, más los tokens de entrada y el gas para los fills
- Las direcciones canónicas de los contratos, idénticas en todas las chains compatibles y listadas más abajo
- Una clave de API del Business portal para la vía de descubrimiento con la Aqua API
Direcciones de los contratos
Aqua y el router SwapVM son despliegues deterministas con la misma dirección en todas las chains compatibles. Interactúa solo con estos dos contratos. Cualquier otra cosa no es Aqua.
- Aqua (registro)
0x1111113ccf1426a8e30e2bff5e005d929bf6a90a- Router SwapVM
0x111111338c5091e8440b67b168bae16a668ac0de
Cómo funciona un fill
- 1
Verifícate
Completa el onboarding de resolver en el portal Business y recibe el token de acceso para cada chain en la que ejecutes.
- 2
Descubre una posición
Obtén posiciones abiertas desde la Aqua API, reconstruye el conjunto activo con los eventos Shipped y Docked, o recibe swaps del enrutado de 1inch como resolver de la red.
- 3
Cotiza justo antes de enviar
Llama a la función quote del router mediante una static call con la orden decodificada. Las cotizaciones leen el respaldo vivo de la wallet, así que se mueven cuando aterrizan otros fills.
- 4
Fija tus límites
Deriva un output mínimo de la cotización fresca y una deadline corta. Una curva desplazada revierte entonces en lugar de ejecutar a peor precio.
- 5
Swapea y verifica
Envía el swap desde la EOA que tiene el token de acceso, exige un recibo exitoso y lee los importes ejecutados del evento Swapped.
Quickstart: cotiza y ejecuta una posición
Los ejemplos siguientes descubren una posición abierta con la Aqua API, piden un quote y envían una ejecución protegida. Funcionan en Node 22 con los SDK de TypeScript. Los contratos, los eventos, los SDK y la API llaman strategy a una posición, así que el código también.
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)Prueba primero el ciclo en un fork
Todo lo anterior se ensaya sin riesgo en un fork local. El anvil de Foundry clona la chain en un bloque, así que obtienes los contratos reales y las posiciones activas reales sin nada en juego. Con los envíos sin clave activados puedes actuar como cualquier EOA que ya tenga el token de acceso y recorrer el ciclo completo, descubrir, cotizar, ejecutar y verificar, antes de tu primera transacción real.
# 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 forkCómo se ejecuta un fill on-chain
Un fill es una única transacción al router SwapVM. El router ejecuta el programa de instrucciones de la posición: primero la comprobación de acceso, luego la curva de precios y después los movimientos de tokens. El token de entrada va de tu EOA a la wallet del LP y el de salida vuelve a ti en la misma transacción, mientras el registro de Aqua actualiza los saldos de la posición y emite Pushed y Pulled. Nada queda en un pool en ningún momento.
Todo es a todo o nada. Si alguna comprobación falla en la ejecución, falta el token de acceso, no se alcanza el threshold, venció el deadline o el respaldo de la wallet es insuficiente, toda la transacción revierte y ningún token se mueve. Un fill revertido cuesta gas y nada más.
El router expone quote y swap, y ambos recorren la misma ruta de precios. Ninguno es una función view, así que llama a quote mediante eth_call como llamada estática: lee el estado en vivo, valora exactamente el swap que le pasas y no cambia nada. Esa vista previa no es un compromiso. Solo los traits que envías con swap vinculan la ejecución.
Los TakerTraits llevan tus límites de ejecución a la transacción. Los hace cumplir la chain, no el SDK. Los que más usarás:
- exactIn
- Qué lado queda fijo. Con entrada exacta fijas lo que envías y acotas lo que recibes. Con salida exacta fijas lo que recibes y acotas lo que envías.
- threshold
- El propio límite. Con entrada exacta, la salida mínima que debe entregar el fill. Con salida exacta, el tope de lo que gastas. Un threshold a cero desactiva la comprobación, así que derívalo siempre de un quote reciente.
- deadline
- Un vencimiento en segundos unix. Una transacción minada después revierte, de modo que un fill atascado en la mempool no puede ejecutarse contra una curva obsoleta.
- customReceiver
- Entrega la salida a una dirección distinta de la EOA emisora, por ejemplo una tesorería. La comprobación de acceso sigue ejecutándose contra el origen de la transacción.
- shouldUnwrap
- Recibe el token nativo en lugar de la forma envuelta cuando la salida es un token nativo envuelto.
No confíes en tus propias cuentas para el resultado. Exige un recibo exitoso y decodifica después el evento Swapped del router: orderHash, maker, taker, tokenIn, tokenOut, amountIn y amountOut. Esos son los importes ejecutados, y son los que tu contabilidad debe registrar.
Modos de fallo que debes manejar
- Posición cerrada
- El LP puede cerrar una posición en cualquier momento, y las lecturas de balance revierten cuando desaparece. Una posición que falta en la lista opened de la API está cerrada, pero entre sondeos la reversión on-chain sigue siendo la fuente de verdad. Comprueba antes de cada ejecución.
- Respaldo bajo en la wallet
- Las posiciones cotizan contra un saldo de wallet compartido. Una ejecución competidora puede vaciarlo primero, y tu transferencia revierte aunque el quote pareciera correcto. Los campos balance y allowance de la API ayudan a prefiltrar, pero se indexan con retraso. Solo un quote on-chain reciente es fiable.
- Precio desplazado
- Las cotizaciones caducan mientras aterrizan fills y pasa el tiempo. El output mínimo y la deadline convierten un mal fill en un revert limpio.
- Token de acceso ausente
- Sin la credencial en el origen de la transacción, las posiciones creadas en la dApp revierten el swap antes de ejecutar la lógica de precios.
- Direcciones no canónicas
- Solo las dos direcciones canónicas de arriba son Aqua. Las ejecuciones enviadas a cualquier otra dirección no alcanzan las posiciones activas. No hay nada que verificar por chain, los despliegues son idénticos en todas partes.
Operar en producción
Trata la EOA operadora como una credencial, no solo una wallet. Tiene el token de acceso por chain, así que aísla su clave en un firmante dedicado, mantén en la dirección solo inventario de trabajo y gas, y no la reutilices para nada más.
Deja que la estimación de gas sea tu última puerta. Las bibliotecas de wallet estiman gas antes de emitir, y una estimación fallida es el revert aflorando pronto, antes de enviar nada. Trátala como una ejecución omitida y pasa al siguiente candidato en lugar de reintentar a ciegas.
Vigila tres señales. Tu tasa de reverts, que sube cuando los datos de descubrimiento envejecen o el tamaño queda muy pegado al respaldo. La edad del quote detrás de cada ejecución, que debería mantenerse en segundos. Y los importes ejecutados de Swapped frente a lo cotizado, que detecta la deriva antes de que te cueste.
Descubrir posiciones en producción
Dos vías te dan el conjunto activo. La Aqua API es la forma más rápida de empezar. Reconstruirlo desde eventos es trustless y no añade dependencias externas. Ambas alimentan el mismo flujo de quote y ejecución.
La Aqua API lista todas las posiciones abiertas de todos los LP: GET /v1.0/strategies/opened en api.1inch.com/aqua, con paginación por cursor de hasta 500 elementos por página y filtros por chain y app. Cada elemento incluye los strategyBytes que pasas a Order.decode, más balance y allowance por token para prefiltrar candidatos. Las peticiones requieren la misma clave de API que el resto de APIs de 1inch, emitida en el Business portal. El índice va ligeramente por detrás de la chain, así que vuelve a pedir un quote on-chain antes de ejecutar.
El registro de Aqua, el contrato central del protocolo, emite Shipped cuando una posición abre y Docked cuando cierra. El conjunto vivo es Shipped menos Docked, reconstruido desde los logs a partir del bloque de despliegue de cada chain. Los campos del evento viven en los datos del log y no en los topics: filtra por dirección de contrato y firma del evento, y luego decodifica y haz el match en el cliente.
Las formas de los eventos son pequeñas. Shipped lleva maker, app, strategyHash y los bytes completos de la estrategia. Docked lleva maker, app y strategyHash. El SDK incluye ayudantes tipados para ambos, ShippedEvent y DockedEvent, con decodificadores fromLog y constantes TOPIC, así que nunca escribes la ABI a mano.
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)Las lecturas on-chain dan el resto. Las vistas rawBalances y safeBalances del registro muestran el respaldo de wallet detrás de una posición, y los eventos Swapped del router llevan los swaps ejecutados. Mantén los quotes frescos en lugar de cachearlos.
Dimensionar un fill empieza por el respaldo, no por el quote. Lee el respaldo de la posición con las vistas del registro, mantén tu tamaño bien por debajo y pide un quote reciente para ese tamaño exacto justo antes de enviar. Los campos balance y allowance de la API son un primer filtro barato sobre muchos candidatos, las vistas y el quote son la verdad para uno.
FAQ de resolvers
No. La comprobación de acceso exige que el origen de la transacción sea la EOA que tiene el token de acceso, así que las wallets de contrato y los bundlers no pueden pasarla. Envía las ejecuciones desde la EOA operadora verificada.
No. La función quote es una vista previa estática del estado en vivo y no mueve nada. Solo los TakerTraits que envías con el swap vinculan la ejecución: el threshold y el deadline se imponen on-chain.
Gas y nada más. Cualquier comprobación fallida revierte toda la transacción, así que no se mueven tokens en ningún lado.
La verificación a través del 1inch Business portal otorga un token de acceso por chain. La EOA operadora necesita ese token en cada chain donde ejecutes.
¿Listo para ejecutar liquidez de Aqua?
Inicia la verificación y contacta con el equipo a través del portal 1inch Business.