Swap integration

Build the pool key and route a swap through the Universal Router.

Pool key

currency0addressAlways address(0), the native currency.
currency1addressThe token.
feeuint240. The Arklis fee is a hook fee, not an LP fee.
tickSpacingint24200
hooksaddressThe hook of the factory that launched the token. Read factory.hook(), or use the address on Contract addresses.

Hook behaviour

  • The fee is 1.25% of the native leg. Exact-input buys pay it from the input before the swap; exact-input sells pay it from the output after the swap.
  • Exact-output sells revert (ExactOutSell). Always sell with an exact token input. Exact-output buys are allowed.
  • Only the network's factory can create pools on the hook.

hookData

Pass abi.encode(address trader, address calloutAuthor). The trader receives cashback and is matched to their referrer; the callout author, if not zero and not the trader, earns the callout share. With empty hookData the trader is the swap's sender (usually the router), then tx.origin if that is zero.

Aggregators

Send the end user as trader. Without it, cashback and referral credit may go to your router contract instead of the person trading.

Routing

Arc Universal RouterChain 5042
0x4fcA…9Fb1
Robinhood Chain Universal RouterChain 4663
0x8876…0904

Example: exact-input swap

Universal Router V4_SWAPts

import { concat, encodeAbiParameters, encodeFunctionData, zeroAddress } from "viem";

const poolKey = {
  currency0: zeroAddress,          // native
  currency1: token,                // the Arklis token
  fee: 0,
  tickSpacing: 200,
  hooks: "0x6224ddAA9075EDca81Bb870297C2841256B020cC",            // read factory.hook() for the token's factory
};

// hookData credits the trader (cashback, referrer) and an optional callout author.
const hookData = encodeAbiParameters(
  [{ type: "address" }, { type: "address" }],
  [trader, calloutAuthor ?? zeroAddress],
);

const zeroForOne = true;           // true = buy (native in), false = sell (token in)
const actions = concat(["0x06", "0x0c", "0x0f"]); // SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_ALL

const params = [
  encodeAbiParameters(
    [{ type: "tuple", components: [
      { name: "poolKey", type: "tuple", components: [
        { name: "currency0", type: "address" },
        { name: "currency1", type: "address" },
        { name: "fee", type: "uint24" },
        { name: "tickSpacing", type: "int24" },
        { name: "hooks", type: "address" },
      ] },
      { name: "zeroForOne", type: "bool" },
      { name: "amountIn", type: "uint128" },
      { name: "amountOutMinimum", type: "uint128" },
      { name: "minHopPriceX36", type: "uint256" },
      { name: "hookData", type: "bytes" },
    ] }],
    [{ poolKey, zeroForOne, amountIn, amountOutMinimum, minHopPriceX36: 0n, hookData }],
  ),
  encodeAbiParameters([{ type: "address" }, { type: "uint256" }], [zeroForOne ? zeroAddress : token, amountIn]),
  encodeAbiParameters([{ type: "address" }, { type: "uint256" }], [zeroForOne ? token : zeroAddress, amountOutMinimum]),
];

const data = encodeFunctionData({
  abi: [{ type: "function", name: "execute", stateMutability: "payable",
    inputs: [{ type: "bytes" }, { type: "bytes[]" }, { type: "uint256" }], outputs: [] }],
  functionName: "execute",
  args: ["0x10", [encodeAbiParameters([{ type: "bytes" }, { type: "bytes[]" }], [actions, params])], deadline],
});

// Buy: send value = amountIn. Sell: approve the token to Permit2, then Permit2 to the router.
await wallet.sendTransaction({ to: "0x4fcA4a51Ab4F23A7447b3284fBd7D73289A89Fb1", data, value: zeroForOne ? amountIn : 0n });

Quotes

Quote with the Uniswap v4 Quoter or by simulating the router call (eth_call). Both run the hook, so the result already includes the fee. Set amountOutMinimum from that quote minus your slippage.