Was Resolver in Aqua tun
Im gesamten 1inch Network werden Swaps, die Aqua-Liquidität füllen, von 1inch Resolvern geroutet: Market Maker und Arbitrage-Trader, die den 1inch-Verifizierungsprozess durchlaufen haben.
Aqua selbst übernimmt weder Discovery noch Matching. Es bepreist und führt pro Swap-Aufruf eine Position gegen eine Gegenpartei aus. Welche Position in welcher Größe gefüllt wird, entscheidet der Resolver: Das 1inch-Routing reicht Swaps an das Netzwerk weiter, und eigene Systeme können die Verträge direkt aufrufen.
Neu bei den zugrunde liegenden Mechaniken? Beginnen Sie mit So funktioniert Aqua.
Bevor Sie starten
- Resolver-Verifizierung über das 1inch Business-Portal, die das Access-Token pro Chain erteilt
- Das Access-Token auf der Operator-EOA, die die Swap-Transaktion sendet
- Node-Zugang auf der Ziel-Chain sowie die Input-Token und Gas für Fills
- Die kanonischen Contract-Adressen, identisch auf jeder unterstützten Chain und unten aufgeführt
- Ein API-Schlüssel aus dem Business portal für den Aqua-API-Discovery-Pfad
Contract-Adressen
Aqua und der SwapVM-Router sind deterministische Deployments mit derselben Adresse auf jeder unterstützten Chain. Interagieren Sie nur mit diesen beiden Contracts. Alles andere ist nicht Aqua.
- Aqua (Registry)
0x1111113ccf1426a8e30e2bff5e005d929bf6a90a- SwapVM-Router
0x111111338c5091e8440b67b168bae16a668ac0de
So läuft ein Fill ab
- 1
Verifizieren lassen
Schließen Sie das Resolver-Onboarding über das Business-Portal ab und erhalten Sie das Access-Token für jede Chain, auf der Sie füllen.
- 2
Eine Position finden
Beziehen Sie offene Positionen über die Aqua API, bauen Sie das Live-Set aus Shipped- und Docked-Events neu auf, oder empfangen Sie Swaps als Netzwerk-Resolver über das 1inch-Routing.
- 3
Direkt vor dem Senden quotieren
Rufen Sie die Quote-Funktion des Routers per Static Call mit der dekodierten Order auf. Quotes lesen die Live-Wallet-Deckung und bewegen sich, sobald andere Fills landen.
- 4
Grenzen setzen
Leiten Sie aus der frischen Quote einen Mindest-Output ab und setzen Sie eine kurze Deadline. Eine verschobene Kurve führt dann zu einem Revert statt zu einem schlechteren Kurs.
- 5
Swappen und verifizieren
Senden Sie den Swap von der EOA mit dem Access-Token, verlangen Sie eine erfolgreiche Quittung und lesen Sie die ausgeführten Beträge aus dem Swapped-Event.
Quickstart: eine Position quotieren und füllen
Die Beispiele unten entdecken eine offene Position über die Aqua API, holen einen Quote ein und senden einen geschützten Fill. Sie laufen auf Node 22 mit den TypeScript-SDKs. Contracts, Events, SDKs und API nennen eine Position Strategy, der Code also auch.
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)Testen Sie den Ablauf zuerst auf einem Fork
Alles oben lässt sich gefahrlos auf einem lokalen Fork durchspielen. Foundrys anvil klont die Chain an einem Block, Sie bekommen also die echten Contracts und die echten Live-Positionen, ohne dass etwas auf dem Spiel steht. Mit aktivierten schlüssellosen Sends können Sie als beliebige EOA agieren, die den Access-Token bereits hält, und die ganze Schleife durchlaufen, entdecken, quoten, füllen, verifizieren, vor Ihrer ersten echten Transaktion.
# 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 forkWie ein Fill on-chain ausgeführt wird
Ein Fill ist eine einzige Transaktion an den SwapVM-Router. Der Router führt das Instruktionsprogramm der Position aus: zuerst die Zugangsprüfung, dann die Preiskurve, dann die Token-Bewegungen. Der Input-Token wandert in derselben Transaktion von Ihrer EOA in die Wallet des LP und der Output-Token zurück zu Ihnen, während die Aqua-Registry die Balances der Position aktualisiert und Pushed und Pulled emittiert. Nichts liegt zu irgendeinem Zeitpunkt in einem Pool.
Alles ist ganz oder gar nicht. Schlägt bei der Ausführung eine Prüfung fehl, fehlender Access-Token, verfehlter Threshold, abgelaufene Deadline oder zu geringe Wallet-Deckung, revertet die gesamte Transaktion und keine Token bewegen sich. Ein revertierter Fill kostet Gas und sonst nichts.
Der Router bietet quote und swap, und beide durchlaufen denselben Preis-Pfad. Keine der beiden ist eine View-Funktion, rufen Sie quote also per eth_call als Static Call auf: es liest den Live-Zustand, bepreist exakt den übergebenen Swap und ändert nichts. Diese Vorschau ist keine Zusage. Nur die Traits, die Sie mit swap senden, binden die Ausführung.
TakerTraits tragen Ihre Ausführungsgrenzen in die Transaktion. Die Chain erzwingt sie, nicht das SDK. Die wichtigsten:
- exactIn
- Welche Seite fixiert ist. Mit exaktem Input legen Sie den gesendeten Betrag fest und begrenzen den Empfang. Mit exaktem Output fixieren Sie den Empfang und begrenzen den Einsatz.
- threshold
- Die Grenze selbst. Bei exaktem Input der Mindest-Output, den der Fill liefern muss. Bei exaktem Output die Obergrenze Ihres Einsatzes. Ein Threshold von null deaktiviert die Prüfung, leiten Sie ihn daher immer aus einem frischen Quote ab.
- deadline
- Ein Ablauf in Unix-Sekunden. Eine danach geminte Transaktion revertet, ein im Mempool hängender Fill kann also nicht gegen eine veraltete Kurve ausgeführt werden.
- customReceiver
- Liefert den Output an eine andere Adresse als die sendende EOA, etwa eine Treasury-Adresse. Die Zugangsprüfung läuft weiterhin gegen den Transaktionsursprung.
- shouldUnwrap
- Erhalten Sie den nativen Token statt der Wrapped-Form, wenn der Output ein Wrapped Native Token ist.
Vertrauen Sie beim Ergebnis nicht Ihrer eigenen Rechnung. Verlangen Sie einen erfolgreichen Receipt und dekodieren Sie dann das Swapped-Event des Routers: orderHash, maker, taker, tokenIn, tokenOut, amountIn und amountOut. Das sind die ausgeführten Beträge, und sie gehören in Ihre Buchführung.
Fehlerfälle, die Sie behandeln sollten
- Geschlossene Position
- Der LP kann eine Position jederzeit schließen, und Balance-Reads reverten, sobald sie weg ist. Eine Position, die in der Opened-Liste der API fehlt, ist geschlossen, zwischen den Abfragen bleibt aber der On-Chain-Revert die Wahrheit. Prüfen Sie vor jedem Fill erneut.
- Geringe Wallet-Deckung
- Positionen quoten aus einem geteilten Wallet-Guthaben. Ein konkurrierender Fill kann es zuerst leeren, sodass Ihr Transfer revertet, obwohl der Quote gut aussah. Die Felder balance und allowance aus der API helfen beim Vorfiltern, werden aber mit Verzögerung indexiert. Nur ein frischer On-Chain-Quote ist verbindlich.
- Verschobener Preis
- Quotes veralten, während Fills landen und Zeit vergeht. Mindest-Output und Deadline machen aus einem schlechten Fill einen sauberen Revert.
- Fehlendes Access-Token
- Ohne das Credential auf dem Transaktionsursprung reverten dApp-erstellte Positionen den Swap, bevor die Preislogik läuft.
- Nicht-kanonische Adressen
- Nur die beiden kanonischen Adressen oben sind Aqua. Fills an andere Adressen verfehlen die Live-Positionen. Es gibt nichts pro Chain zu verifizieren, die Deployments sind überall identisch.
Betrieb in Produktion
Behandeln Sie die Operator-EOA als Credential, nicht nur als Wallet. Sie hält den per-Chain-Access-Token, isolieren Sie ihren Schlüssel also in einem dedizierten Signer, halten Sie nur Arbeitsbestand und Gas auf der Adresse und verwenden Sie sie für nichts anderes.
Machen Sie die Gas-Schätzung zu Ihrem letzten Tor. Wallet-Bibliotheken schätzen Gas vor dem Broadcast, und eine fehlschlagende Schätzung ist der Revert, der früh sichtbar wird, bevor etwas gesendet ist. Behandeln Sie sie als übersprungenen Fill und gehen Sie zum nächsten Kandidaten, statt blind zu wiederholen.
Beobachten Sie drei Signale. Ihre Revert-Rate, die steigt, wenn Discovery-Daten veralten oder die Größe zu nah an der Deckung liegt. Das Alter des Quotes hinter jedem Fill, das bei Sekunden bleiben sollte. Und die ausgeführten Beträge aus Swapped gegen Ihre Quotes, was Drift auffängt, bevor sie Sie etwas kostet.
Positionen in Produktion finden
Zwei Wege liefern Ihnen das Live-Set. Die Aqua API ist der schnellste Einstieg. Der Neuaufbau aus Events ist trustless und kommt ohne externe Abhängigkeit aus. Beide speisen denselben Quote-und-Fill-Ablauf.
Die Aqua API listet jede aktuell offene Position aller LPs: GET /v1.0/strategies/opened auf api.1inch.com/aqua, mit Cursor-Pagination bis 500 Einträge pro Seite sowie Chain- und App-Filtern. Jeder Eintrag enthält die strategyBytes für Order.decode, dazu balance und allowance pro Token zum Vorfiltern von Kandidaten. Anfragen brauchen denselben API-Schlüssel wie die anderen 1inch APIs, ausgestellt im Business portal. Der Index läuft der Chain leicht hinterher, holen Sie vor dem Fill also immer einen frischen On-Chain-Quote ein.
Die Aqua-Registry, der Kernvertrag des Protokolls, emittiert Shipped, wenn eine Position öffnet, und Docked, wenn sie schließt. Der Live-Bestand ist Shipped minus Docked, rekonstruiert aus Logs ab dem Deployment-Block der jeweiligen Chain. Event-Felder liegen in den Log-Daten statt in Topics: Filtern Sie nach Vertragsadresse und Event-Signatur, dann dekodieren und matchen Sie clientseitig.
Die Event-Formen sind klein. Shipped enthält maker, app, strategyHash und die vollständigen Strategy-Bytes. Docked enthält maker, app und strategyHash. Das SDK liefert typisierte Helfer für beide, ShippedEvent und DockedEvent, mit fromLog-Decodern und TOPIC-Konstanten, Sie bauen die ABI also nie von Hand.
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)On-Chain-Reads liefern den Rest. Die Registry-Views rawBalances und safeBalances zeigen die Wallet-Deckung hinter einer Position, und die Swapped-Events des Routers tragen die ausgeführten Swaps. Halten Sie Quotes frisch, statt sie zu cachen.
Die Fill-Größe beginnt bei der Deckung, nicht beim Quote. Lesen Sie die Deckung der Position über die Registry-Views, bleiben Sie mit Ihrer Größe deutlich darunter und holen Sie unmittelbar vor dem Senden einen frischen Quote für genau diese Größe. Die balance- und allowance-Felder der API sind ein günstiger erster Filter über viele Kandidaten, die Views und der Quote sind die Wahrheit für einen.
Resolver-FAQ
Nein. Die Zugangsprüfung verlangt, dass der Transaktionsursprung die EOA mit dem Access-Token ist, Contract-Wallets und Bundler können sie also nicht bestehen. Senden Sie Fills von der verifizierten Operator-EOA.
Nein. Die quote-Funktion ist eine statische Vorschau des Live-Zustands und bewegt nichts. Nur die TakerTraits, die Sie mit dem Swap senden, binden die Ausführung: Threshold und Deadline werden on-chain erzwungen.
Gas und sonst nichts. Jede fehlgeschlagene Prüfung revertet die gesamte Transaktion, es bewegen sich also auf keiner Seite Token.
Die Verifizierung über das 1inch Business portal gewährt einen per-Chain-Access-Token. Die Operator-EOA braucht diesen Token auf jeder Chain, auf der Sie füllen.
Bereit, Aqua-Liquidität zu füllen?
Starten Sie die Verifizierung und erreichen Sie das Team über das 1inch Business-Portal.