Documentation

Aggregator overview

What ArmoryTradeManager is, how venues are addressed by dexId, and what routing through it gives an integrator.

On this page

ArmoryTradeManager is the aggregator's single on-chain entry point. It takes one trade at a time, denominated in native APE on one side and an ERC-20 on the other, resolves a dexId through an append-only adapter registry, and hands the trade to the adapter that owns that venue.

There are exactly two trading functions. buy is payable and swaps native APE into a token. sell pulls a token from the caller and returns native APE. There is no token-to-token entry point and no WAPE entry point. The APE side is what the platform fee is taken from and what the accounting is measured in, so a trade without an APE leg has no shape the manager can execute.

Buys encode WAPE → token. Sells encode token → WAPE.

The path in a sell Route runs from the token to WAPE. The path in a buy Route runs from WAPE to the token. Both shipped adapters check the first and last token on every venue kind, so reusing a buy path for a sell reverts with TokenMismatch before any router is called.

This is the single most common way an integration breaks. It is not symmetric, it is not auto-detected, and there is no flag for it. See Path encoding.

Venues and dexIds

A dexId is a uint8 key into adapters. The launch registry is:

dexIdvenuepath fieldpath shape
0Armory V2v2Pathaddress[]
1Armory V3v3PathUniswap-encoded (20-byte token, 3-byte pool fee)
2Camelot V2v2Pathaddress[]
3Camelot V3 (Algebra V1.9)v3Pathpacked 20-byte addresses, no fee bytes

dexIds 0 and 1 are both served by ArmoryAdapter. dexIds 2 and 3 by CamelotAdapter. One adapter contract may back several dexIds: it branches internally on route.dex.

Treat the on-chain adapter registry as the authority for each dexId.

What routing through the manager buys you

  • One approval, forever. Users approve the manager, not a router and not an adapter. Adapters never touch user allowances and hold no funds between transactions. When a venue is added, no user re-approves anything.
  • One calldata shape across every venue. Route is the same tuple whether the trade lands on a constant-product pair, a Uniswap-style concentrated pool, or an Algebra pool. A new venue reinterprets the tuple. It never changes it.
  • Balance-delta accounting on the APE side. Sell proceeds are what actually arrived at the manager, not what a router claimed to send. Adapter return values are informational.
  • Automatic refund of unconsumed APE on a buy, emitted as EthRefunded.
  • A slippage floor the manager enforces itself, on top of whatever floor the venue applies.
  • One event to index. TradeExecuted carries the venue, both sides of the trade, the fee and the referrer.

What it costs you: a platform fee on the APE side, and one extra hop of gas versus calling the venue's router directly. If you already have direct access to a pool the manager routes to, and you do not need the uniform surface, calling that router yourself is cheaper.

Trust model

  • addAdapter is owner-only and append-only. A dexId that has been bound can never be re-pointed. addAdapter reverts with AdapterExists(dexId). Trades on an existing route therefore carry exactly the trust assumptions that route had at the moment it was registered.
  • The only mutable routing bit is dexPaused[dexId]. Pausing can disable a venue. It cannot redirect one.
  • Adapters are not owned and expose no admin surface. Every address an adapter touches (its manager, WAPE, its routers, its Camelot referrer) is immutable. Replacing venue plumbing means deploying a new adapter under a new dexId.
  • Ownership is ApeOwnable. onlyOwner reads manager.owner() live off a hardcoded IGovernanceManager constant, which is the platform multisig. The manager itself has no owner storage slot and no transferOwnership.

The platform fee

ArmoryTradeManager charges a platform fee on the APE side of every trade. The rate is a per-venue on-chain parameter: feeBpsFor(dexId) returns the venue's override if one is set, otherwise the contract-wide default.

Read feeBpsFor live before you quote

feeBpsFor(dexId) is the authority on what a trade costs. It can change, so read it on chain as part of the same batch used to quote the trade.

Where the fee lands is also configurable. feeReceiver is an ITradeFeeReceiver implementation and is swappable by the owner. An optional IReferralManager can take a share of the fee when a caller passes a non-zero ref. Both seams, and what a third party may implement against them, are covered in Hooks.

Addresses

ContractAddressApeScan
ArmoryTradeManager0x2cea…Dd9F
ArmoryAdapter0xfF7D…c06d
CamelotAdapter0xe3C1…2497
ArmoryTradeCashBackReceiver0x9B10…4a03
ReferralRouter0xc6be…7E36
The aggregator, its two adapters, the reference fee receiver, and the referral router.

See Hooks before integrating referralRouter. Every other deployed address is in Addresses.

Check venue availability

Before quoting, confirm that the dexId has an adapter and is not paused. A registered venue can still lack a pool or sufficient liquidity for the requested pair, so handle that separately from UnknownDex and DexIsPaused.

routable.ts
import { zeroAddress } from "viem";
 
const [adapter, paused] = await Promise.all([
  client.readContract({ address: tradeManager, abi, functionName: "adapters", args: [dexId] }),
  client.readContract({ address: tradeManager, abi, functionName: "dexPaused", args: [dexId] }),
]);
 
const routable = adapter !== zeroAddress && !paused;

Where to go next