SDK Overview

Two published TypeScript packages in the 1inch/sdks monorepo drive Aqua end-to-end. (Python/Rust are placeholders today.)

Package Role
@1inch/aqua-sdk Registry writes and events: AquaProtocolContract.ship()/dock()CallInfo, calculateStrategyHash(), encodeShip/DockCallData, AQUA_CONTRACT_ADDRESSES, and ShippedEvent/DockedEvent/PulledEvent/PushedEvent.fromLog()
@1inch/swap-vm-sdk Program building and router: AquaProgramBuilder/ProgramBuilder, strategy builders AquaXYCAmmStrategy.new()/.newConcentrate() and AquaPeggedAmmStrategy (fluent withTxOriginAccessToken, withFeeTokenIn, withProtocolFee, withDecayPeriod, withSalt), Order/MakerTraits/TakerTraits, SwapVMContract.quote()/swap()/hashOrder(), AQUA_SWAP_VM_CONTRACT_ADDRESSES, SwappedEvent.fromLog()

Install

Bash
1
pnpm add @1inch/aqua-sdk @1inch/swap-vm-sdk viem

Provide liquidity (maker)

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
// build the program with the recommended high-level builder
const strategy = AquaXYCAmmStrategy.newConcentrate({ rawPriceMin, rawPriceMax })
  .withFeeTokenIn(30) // conventional bps: 30 = 0.30% (scaled x100000 internally)
  .withTxOriginAccessToken(aquaKycToken); // attach the taker access gate

const order = Order.new({ maker, program: strategy.build(), traits: MakerTraits.default() });
const { to, data } = aqua.ship({
  app: AQUA_SWAP_VM_CONTRACT_ADDRESSES[chainId],
  strategy: order.encode(),
  amountsAndTokens
});
// sign in the maker wallet and broadcast via your own RPC provider

Take a swap (taker)

TypeScript
1
2
3
4
5
6
// The deployed v1.0.2 router takes the token pair explicitly (tokenIn/tokenOut = direction);
// the SDK mirrors this. amount is the taker's exact-in input in base units.
const args = { order, tokenIn: USDC, tokenOut: WETH, amount, takerTraits };
const out = await swapVm.quote(args); // static simulation -> (amountIn, amountOut, orderHash)
const tx = swapVm.swap(args); // pull() + push() atomically
// decode fills: SwappedEvent.fromLog(log)  // orderHash === strategyHash

Runtime caveat. The SDK can encode every SwapVM opcode, but deployed AquaSwapVMRouter contracts only execute the aquaInstructions subset. Use AquaProgramBuilder (pre-wired to aquaInstructions), which rejects an unregistered opcode at build time (OpcodeNotFound). If you hand-encode one anyway, at runtime a reserved gap index is a no-op (_notInstruction) and an index past the table reverts with a Solidity array-out-of-bounds Panic(0x32) — there is no named runtime error. Pin versions as build-time; strategy bytecode is opcode-table-versioned and not portable across router versions.

SDK additions: Swap VM taker & program building

The Aqua developer surface ships as two TypeScript packages published from the 1inch/sdks monorepo. The aqua-sdk section above covers the maker-side registry calls (ship/dock); this section adds the Swap VM package that a taker uses to quote and execute swaps, plus the AquaProgramBuilder flow for hand-assembling a strategy program.

Packages & exact versions

Package Version viem Other runtime deps
@1inch/aqua-sdk 0.3.0 ^2.48.4 (direct dependency) @1inch/sdk-core 0.1.2, tslib
@1inch/swap-vm-sdk 0.4.0 ^2.21.0 (peer dependency) @1inch/byte-utils ^3.1.7, @1inch/sdk-core 0.1.2, tslib

Note: @1inch/aqua-sdk 0.3.0 and @1inch/swap-vm-sdk 0.4.0 carry the canonical vanity contract addresses (registry 0x1111113ccf…a90a, router 0x111111338c…c0de) in their address constants — verified from the published packages on 2026-07-29. Earlier releases (0.1.1 / 0.2.2 and older) still return superseded addresses: upgrade, or take addresses from the Verified Contract Addresses page.

@1inch/sdk-core is a real dependency of both packages (declared as workspace:* in the monorepo, resolved to a pinned version on publish). You do not install it separately: the primitives you need — Address, HexString, NetworkEnum, and the CallInfo type — are re-exported from each SDK's root, so import them from @1inch/swap-vm-sdk / @1inch/aqua-sdk directly.

Because @1inch/swap-vm-sdk declares viem as a peer dependency, you must install viem yourself. @1inch/aqua-sdk pins viem ^2.48.4 as a direct dependency, so install a viem that satisfies both — ^2.48.4 is the safe floor.

Bash
1
2
# viem must be installed explicitly (peer dep of swap-vm-sdk)
pnpm add @1inch/swap-vm-sdk @1inch/aqua-sdk viem@^2.48.4

Version drift & pinning. The published npm package and the 1inch/swap-vm GitHub repo are not the same build, and they have diverged in ways that will silently emit broken calldata if you cross them:

  • Constructor arity. AquaSwapVMRouter's constructor takes 5 argumentsconstructor(address aqua, address weth, address owner, string name, string version) (verified in src/routers/AquaSwapVMRouter.sol). Builds floating around npm / templates have been observed with a 3-argument constructor, plus different import paths and different TakerTraits.Args field layouts.
  • Canonical source of truth. Treat the deployed AquaSwapVMRouter v1.0.2 at 0x111111338c5091e8440b67b168bae16a668ac0de (13 Aqua chains) together with the 1inch/swap-vm v1.0.2 git tag as authoritative — not the main branch, whose refactored surface is undeployed. The superseded builds (0x016b41…b070, 0x8fdd04…958f) are decode-only — do not target them.
  • Pin exact versions. Install @1inch/swap-vm-sdk and @1inch/aqua-sdk with exact versions (no ^) and lock your lockfile, so an off-cycle patch cannot shift the opcode table under you.
  • Stale opcode enums. A hand-maintained TypeScript opcode enum (e.g. a SwapVMHelpers.ts that lists opcode numbers) can drift out of sync with the on-chain AquaOpcodes table — the numbers stop matching the deployed Solidity (duplicate/reordered entries), and encoding still succeeds while producing a program that executes the wrong instruction. Do not trust a standalone enum: build programs through AquaProgramBuilder, which is pre-wired with aquaInstructions and validates every opcode against that set. (For the same reason, note the README snippet that calls Order.parse(...) is stale — the shipped method is Order.decode(...).)

Taker flow: quote() then swap()

A taker never builds a program. It fetches the maker's encoded order (from the Shipped event or an API), rehydrates it with Order.decode, simulates quote() to read the output amount, applies its own slippage/receiver/deadline via TakerTraits, then sends swap().

The Swap VM has no dedicated minReturn or slippage parameter. Slippage protection is the single TakerTraits.threshold field: in exactIn mode it is the minimum acceptable output (revert if amountOut < threshold); in exactOut mode it is the maximum acceptable input. The receiver override is customReceiver (defaults to the taker), and deadline is a uint40 Unix-seconds guard (0n = no deadline).

Solidity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import {
  Order,
  HexString,
  TakerTraits,
  Address,
  AQUA_SWAP_VM_CONTRACT_ADDRESSES,
  NetworkEnum,
  SwapVMContract,
  SwappedEvent,
  ABI,
} from '@1inch/swap-vm-sdk'
import { decodeFunctionResult } from 'viem'

const chainId = NetworkEnum.ETHEREUM
const swapVM = new SwapVMContract(AQUA_SWAP_VM_CONTRACT_ADDRESSES[chainId])

const USDC = new Address('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48')
const WETH = new Address('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2')

// Maker order, fetched from the Shipped event or the API, then rehydrated.
const encodedOrder = '0x...'
const order = Order.decode(new HexString(encodedOrder))

const srcAmount = 100n * 10n ** 6n // 100 USDC (exactIn)

// 1) Simulate quote() with a permissive TakerTraits to read amountOut.
//    quote() returns (amountIn, amountOut, orderHash) per the SwapVM ABI.
const quoteParams = {
  order,
  tokenIn: USDC,
  tokenOut: WETH,
  amount: srcAmount,
  takerTraits: TakerTraits.default(),
}

const sim = await taker.call(swapVM.quote(quoteParams)) // taker = viem client w/ publicActions
const [amountIn, amountOut, orderHash] = decodeFunctionResult({
  abi: ABI.SWAP_VM_ABI,
  functionName: 'quote',
  data: sim.data!,
})

// 2) Turn the quote into slippage-protected TakerTraits.
const slippageBps = 50n // 0.5%
const minOut = (amountOut * (10_000n - slippageBps)) / 10_000n

const takerTraits = TakerTraits.new({
  exactIn: true,           // amount is the input; threshold is the min output
  threshold: minOut,       // revert if amountOut < minOut
  customReceiver: new Address('0xReceiver...'), // omit to receive as the taker
  deadline: BigInt(Math.floor(Date.now() / 1000) + 300), // uint40 unix seconds
})

// 3) Send the swap with the protected traits (same order/tokens/amount).
const swapTx = swapVM.swap({ ...quoteParams, takerTraits })
const hash = await taker.send(swapTx)
const receipt = await taker.waitForTransactionReceipt({ hash })

// 4) Decode the Swapped event from the router logs.
for (const log of receipt.logs) {
  try {
    const swapped = SwappedEvent.fromLog(log)
    console.log(swapped.orderHash, swapped.maker, swapped.taker)
    console.log(swapped.tokenIn, swapped.tokenOut)
    console.log(swapped.amountIn, swapped.amountOut)
    break
  } catch {
    // not a Swapped log; skip
  }
}

swapVM.quote(...) and swapVM.swap(...) both return a CallInfo ({ to, data, value }) — you pass it straight to your viem client's call / sendTransaction. SwappedEvent.fromLog throws on a non-matching log, so the try/catch scan above is the idiomatic way to pull it out of a receipt.

Program building: AquaProgramBuilder (Path B)

There are two ways to produce the SwapVmProgram a maker ships. Path A is the high-level strategy — AquaXYCAmmStrategy.newConcentrate({ rawPriceMin, rawPriceMax }).build() — which is enough for standard AMM pools. Path B, below, drops to AquaProgramBuilder and appends instructions by hand when you need control the strategy classes don't expose.

Only aquaInstructions execute on the deployed router. The SDK can encode/decode the full Swap VM opcode set, but today's on-chain AquaSwapVMRouter implements only the Aqua subset. Any program using an opcode outside aquaInstructions will encode fine and then revert at runtime. AquaProgramBuilder is pre-wired with aquaInstructions and its .add() throws (listing the supported opcode IDs) if you hand it anything outside that set — use it, not the bare ProgramBuilder, for anything meant to run on Aqua now.

Instruction order matters. Instructions execute in the order you append them, and the builder does not reorder. The valid pipeline mirrors AquaXYCAmmStrategy.build(): dynamic-balance / liquidity setup first (e.g. concentrateGrowLiquidity2D), then any fee-on-input, then the swap formula (xycSwapXD), then trailing controls such as salt.

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { AquaProgramBuilder, instructions, Order, MakerTraits, Address } from "@1inch/swap-vm-sdk";

const { concentrate, fee } = instructions;
const { ONE_E18 } = concentrate;

const maker = new Address("0xMaker...");

// Build the program by appending instructions in execution order.
const builder = new AquaProgramBuilder();

// (1) Dynamic balances: concentrate/grow liquidity within a price band.
//     Price P = tokenGt / tokenLt in 1e18 fixed point.
//     e.g. 1500-3000 USDC per WETH  ->  P = WETH-per-USDC = 1/3000 .. 1/1500.
builder.add(
  concentrate.concentrateGrowLiquidity2D.createIx(
    concentrate.ConcentrateGrowLiquidity2DArgs.fromRawPrices(ONE_E18 / 3000n, ONE_E18 / 1500n)
  )
);

// (2) Fee: 5 bps taker fee charged on amountIn.
builder.add(fee.flatFeeAmountInXD.createIx(fee.FlatFeeArgs.fromBps(5)));

// (3) Swap formula: constant-product XYC swap (always the core step).
builder.xycSwapXD();

// (4) Control: salt makes the order hash unique (replay protection).
builder.salt({ salt: 1n });

const program = builder.build(); // -> SwapVmProgram

// Embed the program in an Order the maker will ship.
const order = Order.new({
  maker,
  program,
  traits: MakerTraits.default()
});

const encodedOrder = order.encode(); // HexString, pass as `strategy` to aqua.ship(...)

Several of the fee/control opcodes above also have fluent shorthands on the builder — builder.flatFeeAmountInXD({ fee }), builder.concentrateGrowLiquidity2D({ sqrtPriceMin, sqrtPriceMax }), builder.decayXD({ decayPeriod }), builder.salt({ salt }), and taker-gating guards like builder.onlyTakerTokenBalanceGte({ token, minAmount }). Use .createIx(...) + .add(...) when you want the args class directly (e.g. fromRawPrices / fromBps helpers), and the fluent methods for terseness. To round-trip an existing program, AquaProgramBuilder.decode(program) maps the on-chain opcode indices back to typed instructions — which only works when your SDK's aquaInstructions table matches the deployed router, the exact reason to pin versions.

Browser wallet & raw ABI

This section shows two lower-level integration paths that sit underneath the TypeScript SDKs: driving Aqua from a browser wallet with viem over an injected EIP-1193 provider, and encoding calldata by hand (Python/Go/Rust) directly against the on-chain function ABIs. Both paths sign in the user's wallet and broadcast through your own RPC — no hosted intermediary is involved.

Contracts referenced here (identical address across every listed EVM network): the Aqua registry (IAqua) at 0x1111113ccf1426a8e30e2bff5e005d929bf6a90a, and the AquaSwapVMRouter (ISwapVM, the swap engine and the app for AMM strategies) at 0x111111338c5091e8440b67b168bae16a668ac0de. Makers keep custody in their own wallet; Aqua moves funds only when a strategy pulls.

1. Browser wallet with viem (injected EIP-1193 provider)

Use two viem clients: a walletClient bound to window.ethereum for signing, and a publicClient bound to your own RPC endpoint for reads and quote simulation. Nothing is routed through a hosted gateway.

Solidity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { createWalletClient, createPublicClient, custom, http, parseUnits } from 'viem'
import { mainnet } from 'viem/chains'
import {
  AquaProtocolContract,
  AQUA_CONTRACT_ADDRESSES,
} from '@1inch/aqua-sdk'
import {
  Order,
  MakerTraits,
  TakerTraits,
  AquaXYCAmmStrategy,
  SwapVMContract,
  AQUA_SWAP_VM_CONTRACT_ADDRESSES,
  Address,
  HexString,
  NetworkEnum,
  instructions,
  ABI,
} from '@1inch/swap-vm-sdk'
import { decodeFunctionResult } from 'viem'

const chainId = NetworkEnum.ETHEREUM

// Signs via the browser wallet; broadcasts through the wallet's provider.
const walletClient = createWalletClient({ chain: mainnet, transport: custom(window.ethereum) })
// Reads & quote simulation go through YOUR OWN RPC — not a hosted endpoint.
const publicClient = createPublicClient({ chain: mainnet, transport: http('https://YOUR_OWN_RPC') })

// Step A — connect an EOA
const [account] = await walletClient.requestAddresses()

const USDC = new Address('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48')
const WETH = new Address('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2')

const registry = AQUA_CONTRACT_ADDRESSES[chainId]              // Aqua registry
const swapVM   = AQUA_SWAP_VM_CONTRACT_ADDRESSES[chainId]      // AquaSwapVMRouter (= app)

const erc20Approve = [{
  type: 'function', name: 'approve', stateMutability: 'nonpayable',
  inputs: [{ name: 'spender', type: 'address' }, { name: 'value', type: 'uint256' }],
  outputs: [{ name: '', type: 'bool' }],
}]

// Step B — maker grants the Aqua REGISTRY an ERC-20 allowance for the liquidity it will provide
await walletClient.writeContract({
  address: USDC.toString(), abi: erc20Approve, functionName: 'approve',
  args: [registry.toString(), parseUnits('10000', 6)], account, chain: mainnet,
})

// Step C — build a strategy program with the swap-vm-sdk builder, wrap it in an Order
const { ONE_E18 } = instructions.concentrate
const program = AquaXYCAmmStrategy.newConcentrate({
  rawPriceMin: ONE_E18 / 3000n,   // price P = tokenGt/tokenLt in 1e18 fixed-point
  rawPriceMax: ONE_E18 / 1500n,
}).build()

const order = Order.new({
  maker: new Address(account),
  program,
  traits: MakerTraits.default(),  // Aqua-authenticated (no signature)
})

// Step D — ship() via the aqua-sdk; the app is the AquaSwapVMRouter
const aqua = new AquaProtocolContract(registry)
const shipTx = aqua.ship({
  app: new Address(swapVM.toString()),
  strategy: order.encode(),       // abi.encode of the Order tuple
  amountsAndTokens: [
    { token: USDC, amount: parseUnits('10000', 6) },
    { token: WETH, amount: parseUnits('5', 18) },
  ],
})
// Signed in the wallet, broadcast through the wallet's provider (your infra — not a hosted gateway)
await walletClient.sendTransaction({
  to: shipTx.to, data: shipTx.data, value: shipTx.value, account, chain: mainnet,
})

Any taker (possibly a different EOA) then swaps against that shipped strategy. The maker's encoded Order is available from the Aqua Shipped event (its strategy field). The taker approves the router for the input token, simulates quote() over your own RPC, then sends swap().

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const swapVMContract = new SwapVMContract(swapVM);

// Reconstruct the maker Order from the Shipped event's `strategy` bytes
const makerOrder = Order.decode(new HexString(encodedStrategyFromShippedEvent));

const srcAmount = parseUnits("100", 6); // 100 USDC in
const params = {
  order: makerOrder,
  tokenIn: USDC,
  tokenOut: WETH,
  amount: srcAmount,
  takerTraits: TakerTraits.default() // exact-in, no hooks
};

// Taker approves the ROUTER to pull the input token
await walletClient.writeContract({
  address: USDC.toString(),
  abi: erc20Approve,
  functionName: "approve",
  args: [swapVM.toString(), srcAmount],
  account,
  chain: mainnet
});

// quote() is a read — simulate it via eth_call on YOUR OWN RPC
const sim = await publicClient.call(swapVMContract.quote(params));
const [amountIn, amountOut] = decodeFunctionResult({
  abi: ABI.SWAP_VM_ABI,
  functionName: "quote",
  data: sim.data
});

// swap() — signed in the wallet, broadcast through your provider
const swapTx = swapVMContract.swap(params);
await walletClient.sendTransaction({
  to: swapTx.to,
  data: swapTx.data,
  value: swapTx.value,
  account,
  chain: mainnet
});

Every write above is signed in the user's wallet and broadcast through the transport you configure. If instead you want a hosted API, private/protected transaction broadcast, or an MCP integration, that is a separate 1inch Business offering — see business.1inch.com/portal/documentation. Do not route these calls through any hosted gateway to use Aqua.

2. Encode ship / dock / pull / push / quote / swap without the TS SDK

The SDKs are thin ABI encoders. A Python (eth-abi/web3.py), Go (abigen), or Rust (ethers-rs/alloy) team can encode the exact same calldata from the function ABIs below and broadcast it through their own RPC (eth_sendRawTransaction); read quote() via eth_call. These signatures are taken from the deployed contracts — IAqua on the registry and the deployed AquaSwapVMRouter's ISwapVM.

Contract Function Selector Mutability
Aqua registry ship(address,bytes,address[],uint256[]) 0xf50b870f nonpayable → bytes32
Aqua registry dock(address,bytes32,address[]) 0x28defc17 nonpayable
Aqua registry pull(address,bytes32,address,uint256,address) 0xb00bbd10 nonpayable
Aqua registry push(address,address,bytes32,address,uint256) 0x47d72768 nonpayable
AquaSwapVMRouter hash((address,uint256,bytes)) 0xf5d08521 view → bytes32
AquaSwapVMRouter quote((address,uint256,bytes),address,address,uint256,bytes) 0x44aa5f14 view (eth_call)
AquaSwapVMRouter swap((address,uint256,bytes),address,address,uint256,bytes) 0xf4d2d412 nonpayable

The Order tuple is (address maker, uint256 traits, bytes data), where traits is the packed MakerTraits bitfield and data is hooksData ++ program. Full JSON ABI for the seven entry points:

Solidity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
[
  { "type": "function", "name": "ship", "stateMutability": "nonpayable",
    "inputs": [
      { "name": "app", "type": "address" },
      { "name": "strategy", "type": "bytes" },
      { "name": "tokens", "type": "address[]" },
      { "name": "amounts", "type": "uint256[]" }
    ],
    "outputs": [{ "name": "strategyHash", "type": "bytes32" }] },

  { "type": "function", "name": "dock", "stateMutability": "nonpayable",
    "inputs": [
      { "name": "app", "type": "address" },
      { "name": "strategyHash", "type": "bytes32" },
      { "name": "tokens", "type": "address[]" }
    ],
    "outputs": [] },

  { "type": "function", "name": "pull", "stateMutability": "nonpayable",
    "inputs": [
      { "name": "maker", "type": "address" },
      { "name": "strategyHash", "type": "bytes32" },
      { "name": "token", "type": "address" },
      { "name": "amount", "type": "uint256" },
      { "name": "to", "type": "address" }
    ],
    "outputs": [] },

  { "type": "function", "name": "push", "stateMutability": "nonpayable",
    "inputs": [
      { "name": "maker", "type": "address" },
      { "name": "app", "type": "address" },
      { "name": "strategyHash", "type": "bytes32" },
      { "name": "token", "type": "address" },
      { "name": "amount", "type": "uint256" }
    ],
    "outputs": [] },

  { "type": "function", "name": "hash", "stateMutability": "view",
    "inputs": [
      { "name": "order", "type": "tuple", "components": [
        { "name": "maker", "type": "address" },
        { "name": "traits", "type": "uint256" },
        { "name": "data", "type": "bytes" }
      ] }
    ],
    "outputs": [{ "name": "", "type": "bytes32" }] },

  { "type": "function", "name": "quote", "stateMutability": "view",
    "inputs": [
      { "name": "order", "type": "tuple", "components": [
        { "name": "maker", "type": "address" },
        { "name": "traits", "type": "uint256" },
        { "name": "data", "type": "bytes" }
      ] },
      { "name": "tokenIn", "type": "address" },
      { "name": "tokenOut", "type": "address" },
      { "name": "amount", "type": "uint256" },
      { "name": "takerTraitsAndData", "type": "bytes" }
    ],
    "outputs": [
      { "name": "amountIn", "type": "uint256" },
      { "name": "amountOut", "type": "uint256" },
      { "name": "orderHash", "type": "bytes32" }
    ] },

  { "type": "function", "name": "swap", "stateMutability": "nonpayable",
    "inputs": [
      { "name": "order", "type": "tuple", "components": [
        { "name": "maker", "type": "address" },
        { "name": "traits", "type": "uint256" },
        { "name": "data", "type": "bytes" }
      ] },
      { "name": "tokenIn", "type": "address" },
      { "name": "tokenOut", "type": "address" },
      { "name": "amount", "type": "uint256" },
      { "name": "takerTraitsAndData", "type": "bytes" }
    ],
    "outputs": [
      { "name": "amountIn", "type": "uint256" },
      { "name": "amountOut", "type": "uint256" },
      { "name": "orderHash", "type": "bytes32" }
    ] }
]

Getting the operands. A raw taker needs the maker's Order tuple and a takerTraitsAndData blob:

  • Order tuple — the Aqua registry emits Shipped(address maker, address app, bytes32 strategyHash, bytes strategy). The strategy field is exactly abi.encode((address,uint256,bytes)); ABI-decode it to (maker, traits, data) and pass it straight into quote/swap. Note strategyHash == keccak256(strategy), which is also the order hash used for Aqua balance lookups.
  • takerTraitsAndData — a packed blob of ten uint16 section offsets, a uint16 flags word, then the referenced sections (threshold, receiver, deadline, hook/callback data, instruction args) and an optional signature. For a plain exact-in swap with no hooks, no threshold and no custom receiver, all offsets are zero and only two flag bits are set — exactIn (bit 0) and useTransferFromAndAquaPush (bit 6) — giving flags 0x0041 and the constant value:
Solidity
1
2
3
takerTraitsAndData = 0x00000000000000000000000000000000000000000041
//                    └────────── 10 zero uint16 offsets ─────────┘└flags┘
//                                                              0x0041 = exactIn | useTransferFromAndAquaPush

For anything beyond the default (thresholds/slippage, deadlines, hooks, callbacks, a custom receiver, or signature-based rather than Aqua-authenticated orders) the offset table is non-trivial — mirror the packing exactly, or generate the blob once with the SDK's TakerTraits and reuse the bytes from your own language.

Broadcasting. ABI-encode the calldata, sign locally, and send via eth_sendRawTransaction to your own RPC provider. Read quote() with eth_call against the router. If you need a hosted API, protected/private broadcast, or an MCP surface instead of running your own RPC, that is offered separately through 1inch Business at business.1inch.com/portal/documentation.

Did you find what you need?