Pour les resolvers

Exécuter des swaps contre la liquidité Aqua : ce que le rôle de resolver exige, comment un fill se déroule on-chain et comment le coter puis l’exécuter avec le SwapVM SDK.

10 min de lectureMis à jour le juillet 2026

Ce que font les resolvers dans Aqua

Sur l'ensemble du 1inch Network, les swaps qui exécutent la liquidité Aqua sont routés par les 1inch Resolvers : des market makers et des traders d'arbitrage qui passent le processus de vérification de 1inch.

Aqua ne fait ni discovery ni matching. Il cote et exécute une position contre une contrepartie par appel de swap. Trouver quelle position exécuter, et pour quelle taille, revient au resolver : le routage 1inch transmet les swaps au réseau, et vos propres systèmes peuvent appeler les contrats directement.

Les mécanismes sous-jacents sont nouveaux pour vous ? Commencez par comment Aqua fonctionne.

Avant de commencer

  • La vérification resolver via le portail 1inch Business, qui accorde le jeton d’accès par chain
  • Le jeton d’accès sur l’EOA opératrice qui envoie la transaction de swap
  • Un accès nœud sur la chain cible, plus les jetons d’entrée et le gas pour les fills
  • Les adresses canoniques des contrats, identiques sur chaque chain prise en charge et listées ci-dessous
  • Une clé d'API du Business portal pour la voie de découverte via l'Aqua API

Adresses des contrats

Aqua et le routeur SwapVM sont des déploiements déterministes avec la même adresse sur chaque chain prise en charge. N'interagissez qu'avec ces deux contrats. Tout le reste n'est pas Aqua.

Aqua (registre)
0x1111113ccf1426a8e30e2bff5e005d929bf6a90a
Routeur SwapVM
0x111111338c5091e8440b67b168bae16a668ac0de

Comment un fill se déroule

  1. 1

    Faites-vous vérifier

    Terminez l’onboarding resolver via le portail Business et recevez le jeton d’accès pour chaque chain où vous exécutez.

  2. 2

    Découvrez une position

    Récupérez les positions ouvertes via l'Aqua API, reconstruisez l'ensemble actif à partir des événements Shipped et Docked, ou recevez des swaps du routage 1inch en tant que resolver du réseau.

  3. 3

    Cotez juste avant d’envoyer

    Appelez la fonction quote du routeur en static call avec l’ordre décodé. Les cotations lisent la couverture réelle du portefeuille et bougent dès que d’autres fills atterrissent.

  4. 4

    Fixez vos bornes

    Dérivez un output minimum de la cotation fraîche et une deadline courte. Une courbe déplacée déclenche alors un revert au lieu d’exécuter à un moins bon taux.

  5. 5

    Swapez et vérifiez

    Envoyez le swap depuis l’EOA détenant le jeton d’accès, exigez un reçu réussi et lisez les montants exécutés dans l’événement Swapped.

Quickstart : coter et exécuter une position

Les exemples ci-dessous découvrent une position ouverte via l'Aqua API, demandent un quote et soumettent une exécution protégée. Ils tournent sur Node 22 avec les SDK TypeScript. Les contrats, les événements, les SDK et l'API appellent une position une strategy, le code aussi.

Installer les SDKbash
pnpm add @1inch/aqua-sdk @1inch/swap-vm-sdk viem
Découvrir les positions ouvertes via l'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)
Coter, exécuter et vérifiertypescript
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)

Essayez d'abord la boucle sur un fork

Tout ce qui précède se répète sans risque sur un fork local. L'anvil de Foundry clone la chain à un bloc, vous obtenez donc les vrais contrats et les vraies positions actives sans rien en jeu. Avec les envois sans clé activés, vous pouvez agir comme n'importe quelle EOA détenant déjà le token d'accès et dérouler toute la boucle, découvrir, coter, exécuter, vérifier, avant votre première transaction réelle.

Répétition sur un fork du mainnetbash
# 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

Comment un fill s'exécute on-chain

Un fill est une seule transaction vers le routeur SwapVM. Le routeur exécute le programme d'instructions de la position : d'abord le contrôle d'accès, puis la courbe de prix, puis les mouvements de tokens. Le token d'entrée va de votre EOA au wallet du LP et le token de sortie vous revient dans la même transaction, pendant que le registre Aqua met à jour les soldes de la position et émet Pushed et Pulled. Rien ne reste dans un pool à aucun moment.

Tout est tout ou rien. Si un contrôle échoue à l'exécution, token d'accès manquant, threshold non atteint, deadline dépassée ou couverture de wallet trop faible, la transaction entière revert et aucun token ne bouge. Un fill reverté coûte du gas et rien d'autre.

Le routeur expose quote et swap, et les deux suivent le même chemin de prix. Aucun n'est une fonction view, appelez donc quote via eth_call en appel statique : il lit l'état en direct, valorise exactement le swap transmis et ne change rien. Cet aperçu n'est pas un engagement. Seuls les traits envoyés avec swap lient l'exécution.

Les TakerTraits portent vos bornes d'exécution dans la transaction. C'est la chain qui les applique, pas le SDK. Les plus utiles :

exactIn
Quel côté est fixé. En entrée exacte, vous fixez le montant envoyé et bornez ce que vous recevez. En sortie exacte, vous fixez ce que vous recevez et bornez ce que vous envoyez.
threshold
La borne elle-même. En entrée exacte, la sortie minimale que le fill doit livrer. En sortie exacte, le plafond de ce que vous dépensez. Un threshold à zéro désactive le contrôle, dérivez-le donc toujours d'un quote frais.
deadline
Une expiration en secondes unix. Une transaction minée après revert, un fill coincé dans la mempool ne peut donc pas s'exécuter contre une courbe périmée.
customReceiver
Livre la sortie à une autre adresse que l'EOA émettrice, par exemple une trésorerie. Le contrôle d'accès s'exécute toujours contre l'origine de la transaction.
shouldUnwrap
Recevez le token natif au lieu de la forme wrappée quand la sortie est un token natif wrappé.

Ne faites pas confiance à vos propres calculs pour le résultat. Exigez un receipt réussi, puis décodez l'événement Swapped du routeur : orderHash, maker, taker, tokenIn, tokenOut, amountIn et amountOut. Ce sont les montants exécutés, et ce sont eux que votre comptabilité doit enregistrer.

Modes d’échec à gérer

Position fermée
Le LP peut fermer une position à tout moment, et les lectures de balance revert dès qu'elle disparaît. Une position absente de la liste opened de l'API est fermée, mais entre deux interrogations le revert on-chain reste la source de vérité. Revérifiez avant chaque exécution.
Couverture de portefeuille basse
Les positions cotent sur un solde de wallet partagé. Une exécution concurrente peut le vider en premier, et votre transfert revert alors même que le quote semblait bon. Les champs balance et allowance de l'API aident à préfiltrer, mais ils sont indexés avec un délai. Seul un quote on-chain frais fait foi.
Prix déplacé
Les cotations se périment à mesure que des fills atterrissent et que le temps passe. L’output minimum et la deadline transforment un mauvais fill en revert propre.
Jeton d’accès manquant
Sans la créance sur l’origine de la transaction, les positions créées dans la dApp revertent le swap avant même la logique de prix.
Adresses non canoniques
Seules les deux adresses canoniques ci-dessus sont Aqua. Les exécutions envoyées ailleurs manquent les positions actives. Il n'y a rien à vérifier par chain, les déploiements sont identiques partout.

Exploiter en production

Traitez l'EOA opératrice comme un identifiant, pas seulement un wallet. Elle détient le token d'accès par chain, isolez donc sa clé dans un signeur dédié, ne gardez sur l'adresse que l'inventaire de travail et le gas, et ne la réutilisez pour rien d'autre.

Faites de l'estimation de gas votre dernière porte. Les bibliothèques de wallet estiment le gas avant diffusion, et une estimation qui échoue est le revert qui affleure tôt, avant tout envoi. Traitez-la comme une exécution sautée et passez au candidat suivant plutôt que de réessayer à l'aveugle.

Surveillez trois signaux. Votre taux de reverts, qui monte quand les données de découverte vieillissent ou que la taille colle trop à la couverture. L'âge du quote derrière chaque exécution, qui doit rester en secondes. Et les montants exécutés de Swapped face à vos quotes, qui attrape la dérive avant qu'elle ne coûte.

Découvrir les positions en production

Deux voies donnent accès à l'ensemble actif. L'Aqua API est le point de départ le plus rapide. La reconstruction depuis les événements est trustless et n'ajoute aucune dépendance externe. Les deux alimentent le même flux de quote et d'exécution.

L'Aqua API liste toutes les positions actuellement ouvertes de tous les LP : GET /v1.0/strategies/opened sur api.1inch.com/aqua, avec pagination par curseur jusqu'à 500 éléments par page et filtres par chain et par app. Chaque élément contient les strategyBytes à passer à Order.decode, plus balance et allowance par token pour préfiltrer les candidats. Les requêtes exigent la même clé d'API que les autres API 1inch, délivrée dans le Business portal. L'index suit la chain avec un léger retard, redemandez donc toujours un quote on-chain avant d'exécuter.

Le registre Aqua, le contrat central du protocole, émet Shipped quand une position ouvre et Docked quand elle ferme. L’ensemble vivant est Shipped moins Docked, reconstruit depuis les logs à partir du bloc de déploiement propre à chaque chain. Les champs d’événement vivent dans les données du log plutôt que dans les topics : filtrez par adresse de contrat et signature d’événement, puis décodez et faites le matching côté client.

Les formes d'événements sont petites. Shipped porte maker, app, strategyHash et les bytes complets de la stratégie. Docked porte maker, app et strategyHash. Le SDK fournit des assistants typés pour les deux, ShippedEvent et DockedEvent, avec décodeurs fromLog et constantes TOPIC, vous n'écrivez donc jamais l'ABI à la main.

Reconstruire l'ensemble actif depuis les événementstypescript
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)

Les lectures on-chain donnent le reste. Les vues rawBalances et safeBalances du registre indiquent la couverture de wallet derrière une position, et les événements Swapped du routeur portent les swaps exécutés. Gardez les quotes frais au lieu de les mettre en cache.

Dimensionner un fill part de la couverture, pas du quote. Lisez la couverture de la position avec les vues du registre, restez nettement en dessous, puis prenez un quote frais pour cette taille exacte juste avant d'envoyer. Les champs balance et allowance de l'API sont un premier tri économique sur beaucoup de candidats, les vues et le quote sont la vérité pour un seul.

FAQ resolvers

Non. Le contrôle d'accès exige que l'origine de la transaction soit l'EOA détenant le token d'accès, les wallets de contrat et les bundlers ne peuvent donc pas le passer. Envoyez les exécutions depuis l'EOA opératrice vérifiée.

Non. La fonction quote est un aperçu statique de l'état en direct et ne bouge rien. Seuls les TakerTraits envoyés avec le swap lient l'exécution : le threshold et la deadline sont imposés on-chain.

Du gas et rien d'autre. Tout contrôle en échec revert la transaction entière, aucun token ne bouge d'aucun côté.

La vérification via le 1inch Business portal accorde un token d'accès par chain. L'EOA opératrice doit détenir ce token sur chaque chain où vous exécutez.

Référence de l'Aqua API Endpoints, paramètres et formats de réponse pour la découverte de positions et les statistiques, dans le Business portal. SDK Aqua & SDK SwapVM Builders typés, décodeurs et parsers d’événements pour le flux de fill, dans le repo 1inch/sdks. Onboarding resolver Étapes de vérification et documentation resolver sur le portail 1inch Business. Livre blanc Aqua La conception complète de la couche de liquidité partagée.

Prêt à exécuter la liquidité Aqua ?

Lancez la vérification et contactez l’équipe via le portail 1inch Business.

Ouvrir le portail Business