Dành cho resolver

Khớp các swap với thanh khoản Aqua: vai trò resolver đòi hỏi gì, một lần khớp diễn ra on-chain như thế nào và cách báo giá rồi thực hiện bằng SwapVM SDK.

đọc 10 phútCập nhật tháng 7 năm 2026

Resolver làm gì trong Aqua

Trên toàn 1inch Network, các swap khớp thanh khoản Aqua được định tuyến bởi 1inch Resolvers: các nhà tạo lập thị trường và nhà giao dịch arbitrage đã hoàn tất quy trình xác minh của 1inch.

Bản thân Aqua không làm khâu tìm kiếm hay khớp lệnh. Mỗi lần gọi swap, nó chỉ định giá và thực hiện một vị thế với một bên đối ứng. Việc tìm vị thế nào để khớp, với khối lượng bao nhiêu, là việc của resolver: định tuyến 1inch chuyển swap cho mạng lưới, và hệ thống riêng của bạn có thể gọi thẳng các hợp đồng.

Mới làm quen với cơ chế nền tảng? Hãy bắt đầu với cách Aqua hoạt động.

Trước khi bắt đầu

  • Xác minh resolver qua cổng 1inch Business, nơi cấp token truy cập theo từng chain
  • Token truy cập trên EOA vận hành gửi giao dịch swap
  • Quyền truy cập node trên chain mục tiêu, cùng token đầu vào và gas cho các lần khớp
  • Các địa chỉ hợp đồng chuẩn, giống hệt nhau trên mọi chain được hỗ trợ và được liệt kê bên dưới
  • Một khóa API từ Business portal cho đường tìm kiếm qua Aqua API

Địa chỉ hợp đồng

Aqua và router SwapVM là các bản triển khai tất định với cùng một địa chỉ trên mọi chain được hỗ trợ. Chỉ tương tác với hai hợp đồng này. Bất kỳ thứ gì khác đều không phải Aqua.

Aqua (sổ đăng ký)
0x1111113ccf1426a8e30e2bff5e005d929bf6a90a
Router SwapVM
0x111111338c5091e8440b67b168bae16a668ac0de

Một lần khớp diễn ra thế nào

  1. 1

    Được xác minh

    Hoàn tất onboarding resolver qua cổng Business và nhận token truy cập cho từng chain bạn khớp lệnh.

  2. 2

    Tìm một vị thế

    Lấy vị thế đang mở từ Aqua API, dựng lại tập hoạt động từ sự kiện Shipped và Docked, hoặc nhận swap từ định tuyến 1inch với tư cách resolver của mạng.

  3. 3

    Báo giá ngay trước khi gửi

    Gọi hàm quote của router bằng static call với lệnh đã giải mã. Báo giá đọc mức bảo chứng ví theo thời gian thực, nên sẽ thay đổi khi các lần khớp khác diễn ra.

  4. 4

    Đặt giới hạn của bạn

    Suy ra mức đầu ra tối thiểu từ báo giá mới nhất và đặt deadline ngắn. Khi đường cong dịch chuyển, giao dịch sẽ revert thay vì khớp ở tỷ giá xấu hơn.

  5. 5

    Swap và xác nhận

    Gửi swap từ EOA nắm token truy cập, yêu cầu biên lai thành công và đọc số lượng đã thực hiện từ sự kiện Swapped.

Bắt đầu nhanh: báo giá và khớp một vị thế

Các mẫu bên dưới tìm một vị thế đang mở qua Aqua API, lấy báo giá và gửi lệnh khớp được bảo vệ. Chúng chạy trên Node 22 với các SDK TypeScript. Hợp đồng, sự kiện, SDK và API đều gọi vị thế là strategy, nên mã cũng vậy.

Cài đặt SDKbash
pnpm add @1inch/aqua-sdk @1inch/swap-vm-sdk viem
Tìm vị thế đang mở qua 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)
Báo giá, khớp và xác nhậntypescript
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)

Hãy thử toàn bộ vòng lặp trên fork trước

Mọi thứ ở trên đều có thể diễn tập an toàn trên một fork cục bộ. Anvil của Foundry sao chép chain tại một block, nên bạn có hợp đồng thật và các vị thế đang hoạt động thật mà không đặt cược gì. Bật gửi không cần khóa, bạn có thể hành động như bất kỳ EOA nào đã giữ token truy cập và đi hết vòng lặp, tìm, báo giá, khớp, xác minh, trước giao dịch thật đầu tiên.

Chạy thử trên fork 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

Lệnh khớp được thực thi on-chain như thế nào

Một lệnh khớp là một giao dịch duy nhất tới router SwapVM. Router chạy chương trình chỉ thị của vị thế: kiểm tra truy cập trước, rồi đường cong giá, rồi các chuyển động token. Token đầu vào đi từ EOA của bạn sang ví của LP và token đầu ra quay về bạn trong cùng giao dịch, trong khi sổ đăng ký Aqua cập nhật số dư của vị thế và phát Pushed cùng Pulled. Không có gì nằm trong pool ở bất kỳ thời điểm nào.

Tất cả là được ăn cả ngã về không. Nếu bất kỳ kiểm tra nào thất bại lúc thực thi, thiếu token truy cập, threshold không đạt, deadline đã qua hay ví không đủ đảm bảo, toàn bộ giao dịch revert và không token nào di chuyển. Một lệnh khớp bị revert chỉ tốn gas.

Router cung cấp quote và swap, cả hai chạy cùng một đường giá. Không cái nào là hàm view, vì vậy hãy gọi quote qua eth_call như một lời gọi tĩnh: nó đọc trạng thái trực tiếp, định giá chính xác giao dịch bạn truyền vào và không thay đổi gì. Bản xem trước đó không phải cam kết. Chỉ những traits gửi kèm swap mới ràng buộc việc thực thi.

TakerTraits mang các giới hạn thực thi của bạn vào giao dịch. Chuỗi thực thi chúng, không phải SDK. Những cái bạn dùng nhiều nhất:

exactIn
Bên nào được cố định. Với đầu vào chính xác, bạn đặt số lượng gửi đi và chặn dưới số nhận về. Với đầu ra chính xác, bạn cố định số nhận và chặn trên số gửi.
threshold
Chính giới hạn đó. Với đầu vào chính xác là đầu ra tối thiểu lệnh khớp phải trả. Với đầu ra chính xác là mức trần bạn chi. Threshold bằng 0 sẽ tắt kiểm tra, nên luôn suy ra nó từ một báo giá mới.
deadline
Hạn chót theo giây unix. Giao dịch được đào sau đó sẽ revert, nên lệnh khớp kẹt trong mempool không thể thực thi trên đường cong cũ.
customReceiver
Chuyển đầu ra tới địa chỉ khác EOA gửi, ví dụ địa chỉ ngân quỹ. Kiểm tra truy cập vẫn chạy trên nguồn gốc giao dịch.
shouldUnwrap
Nhận token gốc thay vì dạng bọc khi đầu ra là token gốc được bọc.

Đừng tin phép tính của chính mình về kết quả. Yêu cầu biên nhận thành công, rồi giải mã sự kiện Swapped của router: orderHash, maker, taker, tokenIn, tokenOut, amountIn và amountOut. Đó là các số lượng đã thực thi, và đó là thứ sổ sách của bạn phải ghi.

Các tình huống lỗi cần xử lý

Vị thế đã đóng
LP có thể đóng vị thế bất cứ lúc nào, và các lần đọc số dư sẽ revert khi nó biến mất. Vị thế vắng mặt trong danh sách opened của API nghĩa là đã đóng, nhưng giữa các lần thăm dò, revert on-chain vẫn là nguồn sự thật. Kiểm tra lại trước mỗi lần khớp.
Bảo chứng ví thấp
Vị thế báo giá từ số dư ví dùng chung. Một lệnh khớp cạnh tranh có thể rút cạn trước, khiến giao dịch của bạn revert dù báo giá trông ổn. Trường balance và allowance từ API giúp sàng lọc trước, nhưng được lập chỉ mục có độ trễ. Chỉ báo giá on-chain mới là đáng tin.
Giá dịch chuyển
Báo giá cũ dần khi các lần khớp diễn ra và thời gian trôi. Sàn đầu ra tối thiểu và deadline biến một lần khớp xấu thành một revert sạch.
Thiếu token truy cập
Không có thông tin xác thực trên nguồn gốc giao dịch, các vị thế tạo trong dApp sẽ revert swap trước cả khi logic giá chạy.
Địa chỉ không chuẩn
Chỉ hai địa chỉ chuẩn ở trên mới là Aqua. Lệnh khớp gửi tới nơi khác sẽ trượt các vị thế đang hoạt động. Không có gì phải xác minh theo từng chain, các bản triển khai giống hệt nhau ở mọi nơi.

Vận hành trong môi trường thật

Hãy coi EOA vận hành là một thông tin xác thực, không chỉ là ví. Nó giữ token truy cập theo từng chain, vì vậy hãy cách ly khóa của nó trong một trình ký chuyên dụng, chỉ giữ tồn kho làm việc và gas trên địa chỉ đó, và đừng dùng lại cho việc khác.

Hãy để ước tính gas là cánh cổng cuối. Thư viện ví ước tính gas trước khi phát, và một ước tính thất bại chính là revert lộ ra sớm, trước khi bất kỳ thứ gì được gửi. Coi đó là một lần khớp bị bỏ qua và chuyển sang ứng viên tiếp theo thay vì thử lại mù quáng.

Theo dõi ba tín hiệu. Tỷ lệ revert của bạn, tăng khi dữ liệu tìm kiếm cũ đi hoặc cỡ lệnh sát mức đảm bảo. Tuổi của báo giá sau mỗi lần khớp, nên giữ ở mức vài giây. Và số lượng thực thi từ Swapped so với báo giá, giúp bắt độ lệch trước khi nó gây thiệt hại.

Tìm vị thế trong môi trường production

Có hai đường để lấy tập vị thế đang hoạt động. Aqua API là cách khởi đầu nhanh nhất. Dựng lại từ sự kiện thì trustless và không thêm phụ thuộc bên ngoài. Cả hai đều dẫn vào cùng một luồng báo giá và khớp lệnh.

Aqua API liệt kê mọi vị thế đang mở của tất cả LP: GET /v1.0/strategies/opened trên api.1inch.com/aqua, với phân trang con trỏ tối đa 500 mục mỗi trang cùng bộ lọc chain và app. Mỗi mục chứa strategyBytes để truyền vào Order.decode, kèm balance và allowance theo từng token để sàng lọc trước ứng viên. Yêu cầu cần cùng khóa API như các API 1inch khác, được cấp trong Business portal. Chỉ mục đi sau chain một chút, vì vậy luôn lấy báo giá on-chain mới trước khi khớp.

Registry Aqua, hợp đồng lõi của giao thức, phát Shipped khi một vị thế mở và Docked khi nó đóng. Tập đang mở là Shipped trừ Docked, dựng lại từ log bắt đầu từ block triển khai của từng chain. Các trường sự kiện nằm trong dữ liệu log chứ không trong topics: lọc theo địa chỉ hợp đồng và chữ ký sự kiện, rồi giải mã và đối chiếu phía client.

Cấu trúc sự kiện rất gọn. Shipped mang maker, app, strategyHash và toàn bộ bytes chiến lược. Docked mang maker, app và strategyHash. SDK có sẵn trợ giúp có kiểu cho cả hai, ShippedEvent và DockedEvent, kèm bộ giải mã fromLog và hằng số TOPIC, nên bạn không bao giờ phải tự viết ABI.

Dựng lại tập đang hoạt động từ sự kiệntypescript
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)

Đọc on-chain cho phần còn lại. Các view rawBalances và safeBalances của sổ đăng ký báo mức đảm bảo ví đứng sau một vị thế, và sự kiện Swapped của router mang các giao dịch đã thực thi. Giữ báo giá luôn mới thay vì lưu đệm.

Định cỡ lệnh khớp bắt đầu từ mức đảm bảo, không phải từ báo giá. Đọc mức đảm bảo của vị thế bằng các view của sổ đăng ký, giữ cỡ lệnh thấp hơn hẳn, rồi lấy báo giá mới cho đúng cỡ đó ngay trước khi gửi. Trường balance và allowance của API là bộ lọc đầu rẻ cho nhiều ứng viên, còn view và báo giá là sự thật cho một.

FAQ cho resolver

Không. Kiểm tra truy cập yêu cầu nguồn gốc giao dịch phải là EOA giữ token truy cập, nên ví hợp đồng và bundler không thể vượt qua. Gửi lệnh khớp từ EOA vận hành đã xác minh.

Không. Hàm quote là bản xem trước tĩnh của trạng thái trực tiếp và không di chuyển gì. Chỉ TakerTraits gửi kèm swap mới ràng buộc việc thực thi: threshold và deadline được thực thi on-chain.

Chỉ gas. Bất kỳ kiểm tra nào thất bại đều revert toàn bộ giao dịch, nên không token nào di chuyển ở cả hai phía.

Xác minh qua 1inch Business portal cấp token truy cập theo từng chain. EOA vận hành cần token đó trên mọi chain bạn khớp lệnh.

Tài liệu Aqua API Endpoint, tham số và định dạng phản hồi cho việc tìm vị thế và thống kê, trong Business portal. Aqua SDK & SwapVM SDK Builder có kiểu, bộ giải mã và bộ phân tích sự kiện cho luồng khớp lệnh, trong repo 1inch/sdks. Onboarding resolver Các bước xác minh và tài liệu resolver trên cổng 1inch Business. Whitepaper của Aqua Thiết kế đầy đủ của lớp thanh khoản chia sẻ.

Sẵn sàng khớp thanh khoản Aqua?

Bắt đầu xác minh và liên hệ đội ngũ qua cổng 1inch Business.

Mở cổng Business