Developers
Swap integration
Build the pool key and route a swap through the Universal Router.
Pool key
address(0), the native currency.0. The Arklis fee is a hook fee, not an LP fee.200factory.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
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
eth_call). Both run the hook, so the result already includes the fee. Set amountOutMinimum from that quote minus your slippage.