O que fazem os resolvers na Aqua
Em toda a 1inch Network, os swaps que executam a liquidez da Aqua são encaminhados pelos 1inch Resolvers: criadores de mercado e traders de arbitragem que concluem o processo de verificação da 1inch.
A Aqua em si não faz discovery nem matching. Cota e executa uma posição contra uma contraparte por cada chamada de swap. Encontrar que posição executar, e com que dimensão, é trabalho do resolver: o encaminhamento da 1inch entrega swaps à rede, e os seus próprios sistemas podem chamar os contratos diretamente.
Novo na mecânica subjacente? Comece por como funciona a Aqua.
Antes de começar
- Verificação de resolver através do portal 1inch Business, que concede o token de acesso por chain
- O token de acesso na EOA operadora que envia a transação de swap
- Acesso a um nó na chain alvo, mais os tokens de entrada e o gás para os fills
- Os endereços canónicos dos contratos, idênticos em todas as chains suportadas e listados abaixo
- Uma chave de API do Business portal para a via de descoberta com a Aqua API
Endereços dos contratos
A Aqua e o router SwapVM são deployments determinísticos com o mesmo endereço em todas as chains suportadas. Interaja apenas com estes dois contratos. Tudo o resto não é Aqua.
- Aqua (registo)
0x1111113ccf1426a8e30e2bff5e005d929bf6a90a- Router SwapVM
0x111111338c5091e8440b67b168bae16a668ac0de
Como decorre um fill
- 1
Obtenha verificação
Conclua o onboarding de resolver através do portal Business e receba o token de acesso para cada chain onde executa.
- 2
Descubra uma posição
Obtenha posições abertas na Aqua API, reconstrua o conjunto ativo a partir dos eventos Shipped e Docked, ou receba swaps do encaminhamento da 1inch como resolver da rede.
- 3
Cote mesmo antes de submeter
Chame a função quote do router numa static call com a ordem descodificada. As cotações leem o suporte vivo da carteira, por isso movem-se quando outros fills aterram.
- 4
Defina os seus limites
Derive um output mínimo da cotação fresca e uma deadline curta. Uma curva deslocada reverte então em vez de executar a uma taxa pior.
- 5
Faça o swap e verifique
Envie o swap a partir da EOA que detém o token de acesso, exija um recibo bem-sucedido e leia os montantes executados no evento Swapped.
Quickstart: cotar e executar uma posição
Os exemplos abaixo descobrem uma posição aberta com a Aqua API, pedem um quote e submetem uma execução protegida. Correm em Node 22 com os SDK de TypeScript. Os contratos, os eventos, os SDK e a API chamam strategy a uma posição, por isso o código também.
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)Experimente primeiro o ciclo num fork
Tudo o que está acima ensaia-se em segurança num fork local. O anvil da Foundry clona a chain num bloco, por isso obtém os contratos reais e as posições ativas reais sem nada em risco. Com envios sem chave ativados pode agir como qualquer EOA que já detenha o token de acesso e percorrer o ciclo completo, descobrir, cotar, executar, verificar, antes da sua primeira transação 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 forkComo um fill é executado on-chain
Um fill é uma única transação para o router SwapVM. O router executa o programa de instruções da posição: primeiro a verificação de acesso, depois a curva de preços, depois os movimentos de tokens. O token de entrada vai da sua EOA para a wallet do LP e o de saída volta para si na mesma transação, enquanto o registo da Aqua atualiza os saldos da posição e emite Pushed e Pulled. Nada fica num pool em momento algum.
É tudo ou nada. Se alguma verificação falhar na execução, token de acesso em falta, threshold não atingido, deadline ultrapassado ou cobertura de wallet insuficiente, toda a transação reverte e nenhum token se move. Um fill revertido custa gás e nada mais.
O router expõe quote e swap, e ambos percorrem o mesmo caminho de preços. Nenhum é uma função view, por isso chame quote através de eth_call como chamada estática: lê o estado em direto, avalia exatamente o swap que lhe passa e não altera nada. Essa pré-visualização não é um compromisso. Só os traits que envia com swap vinculam a execução.
Os TakerTraits levam os seus limites de execução para a transação. É a chain que os impõe, não o SDK. Os que mais vai usar:
- exactIn
- Que lado fica fixo. Com entrada exata fixa o que envia e limita o que recebe. Com saída exata fixa o que recebe e limita o que envia.
- threshold
- O próprio limite. Na entrada exata, a saída mínima que o fill tem de entregar. Na saída exata, o teto do que gasta. Um threshold a zero desativa a verificação, por isso derive-o sempre de um quote fresco.
- deadline
- Uma expiração em segundos unix. Uma transação minerada depois reverte, pelo que um fill preso na mempool não pode executar contra uma curva desatualizada.
- customReceiver
- Entrega a saída a um endereço diferente da EOA emissora, por exemplo uma tesouraria. A verificação de acesso continua a correr contra a origem da transação.
- shouldUnwrap
- Receba o token nativo em vez da forma embrulhada quando a saída é um token nativo embrulhado.
Não confie nas suas próprias contas para o resultado. Exija um recibo bem-sucedido e depois descodifique o evento Swapped do router: orderHash, maker, taker, tokenIn, tokenOut, amountIn e amountOut. Esses são os montantes executados, e são eles que a sua contabilidade deve registar.
Modos de falha a tratar
- Posição fechada
- O LP pode fechar uma posição a qualquer momento, e as leituras de saldo revertem assim que ela desaparece. Uma posição ausente da lista opened da API está fechada, mas entre sondagens o revert on-chain continua a ser a fonte de verdade. Verifique de novo antes de cada execução.
- Suporte baixo na carteira
- As posições cotam a partir de um saldo de wallet partilhado. Uma execução concorrente pode esvaziá-lo primeiro, e a sua transferência reverte mesmo que o quote parecesse bom. Os campos balance e allowance da API ajudam a pré-filtrar, mas são indexados com atraso. Só um quote on-chain fresco é autoritativo.
- Preço deslocado
- As cotações envelhecem à medida que os fills aterram e o tempo passa. O output mínimo e a deadline transformam um mau fill num revert limpo.
- Token de acesso em falta
- Sem a credencial na origem da transação, as posições criadas na dApp revertem o swap antes de a lógica de preços correr.
- Endereços não canónicos
- Apenas os dois endereços canónicos acima são Aqua. Execuções enviadas para qualquer outro sítio falham as posições ativas. Não há nada a verificar por chain, os deployments são idênticos em todo o lado.
Operar em produção
Trate a EOA operadora como uma credencial, não apenas uma wallet. Ela detém o token de acesso por chain, por isso isole a sua chave num assinante dedicado, mantenha no endereço apenas inventário de trabalho e gás, e nunca a reutilize para outra coisa.
Deixe a estimativa de gás ser o seu último portão. As bibliotecas de wallet estimam o gás antes de difundir, e uma estimativa falhada é o revert a aparecer cedo, antes de algo ser enviado. Trate-a como uma execução saltada e passe ao candidato seguinte em vez de repetir às cegas.
Vigie três sinais. A sua taxa de reverts, que sobe quando os dados de descoberta envelhecem ou o tamanho fica demasiado colado à cobertura. A idade do quote por trás de cada execução, que deve manter-se em segundos. E os montantes executados de Swapped face ao cotado, que apanha a deriva antes de custar.
Descobrir posições em produção
Há duas vias para obter o conjunto ativo. A Aqua API é a forma mais rápida de começar. Reconstruir a partir de eventos é trustless e não acrescenta dependências externas. Ambas alimentam o mesmo fluxo de quote e execução.
A Aqua API lista todas as posições atualmente abertas de todos os LP: GET /v1.0/strategies/opened em api.1inch.com/aqua, com paginação por cursor até 500 itens por página e filtros por chain e app. Cada item traz os strategyBytes que passa a Order.decode, além de balance e allowance por token para pré-filtrar candidatos. Os pedidos exigem a mesma chave de API das outras APIs da 1inch, emitida no Business portal. O índice segue a chain com um ligeiro atraso, por isso peça sempre um quote on-chain fresco antes de executar.
O registo Aqua, o contrato central do protocolo, emite Shipped quando uma posição abre e Docked quando fecha. O conjunto vivo é Shipped menos Docked, reconstruído a partir dos logs desde o bloco de deployment de cada chain. Os campos do evento vivem nos dados do log e não nos topics: filtre por endereço de contrato e assinatura de evento, depois descodifique e faça o match do lado do cliente.
As formas dos eventos são pequenas. Shipped transporta maker, app, strategyHash e os bytes completos da estratégia. Docked transporta maker, app e strategyHash. O SDK inclui auxiliares tipados para ambos, ShippedEvent e DockedEvent, com descodificadores fromLog e constantes TOPIC, por isso nunca escreve a ABI à mão.
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)As leituras on-chain dão o resto. As vistas rawBalances e safeBalances do registo reportam a cobertura de wallet por trás de uma posição, e os eventos Swapped do router transportam os swaps executados. Mantenha os quotes frescos em vez de os guardar em cache.
Dimensionar um fill começa pela cobertura, não pelo quote. Leia a cobertura da posição com as vistas do registo, mantenha o seu tamanho bem abaixo, depois peça um quote fresco para esse tamanho exato mesmo antes de submeter. Os campos balance e allowance da API são uma primeira triagem barata sobre muitos candidatos, as vistas e o quote são a verdade para um.
FAQ de resolvers
Não. A verificação de acesso exige que a origem da transação seja a EOA detentora do token de acesso, pelo que wallets de contrato e bundlers não a passam. Envie as execuções a partir da EOA operadora verificada.
Não. A função quote é uma pré-visualização estática do estado em direto e não move nada. Só os TakerTraits enviados com o swap vinculam a execução: o threshold e o deadline são impostos on-chain.
Gás e nada mais. Qualquer verificação falhada reverte toda a transação, pelo que nenhum token se move de nenhum dos lados.
A verificação através do 1inch Business portal concede um token de acesso por chain. A EOA operadora precisa desse token em cada chain onde executa.
Pronto para executar a liquidez da Aqua?
Inicie a verificação e fale com a equipa através do portal 1inch Business.