Skip to content
Hookline

Build on Hookline

Put best-price swaps on Robinhood Chain inside your bot, agent, wallet or app. Earn your own fee on every swap you route, paid on-chain in the same transaction.

  • Up to 0.5% of every swap, straight to your address
  • Featured on the showcase and announced on X
  • Your own key, higher limits, Proof of Savings for your users

One swap, paid out in one transaction

  • Your user receivesto their wallet
  • Hookline feeto the protocol
  • Your fee, up to 0.5%to your address
Illustration. Both fees come out of what the swap returns, inside the same Uniswap Universal Router transaction, so nobody has to trust a payout later.

What you can build

One API and one MCP server for every pool on the chain, native Uniswap and outside pools through Aggregator Hooks alike.

Trading bots

Quote, build and track swaps over plain HTTP. Every route checked, every swap simulated before you send it.

AI agents through MCP

Give any agent best-price swaps with one URL. The agent prepares, the user's wallet signs.

Wallets and frontends

Best-price swaps on Robinhood Chain inside your app, with your own fee on top.

Analytics on hook data

Every Aggregator Hook pool with its liquidity, volume and swaps, ready to chart.

Telegram bots

Swap in a chat, track to settlement, share the Proof of Savings link.

Something else?

Tell us in the application

What you get

0.5%

Your own fee on every swap you route

Set partnerFeeBps on your quotes, up to 50 bps. It is paid on-chain to your payout address in the same transaction, right after the Hookline fee. No invoices, no monthly claims.

fee.partner = { bps, amount, recipient }

Featured and announced

Approved projects join the Built on Hookline showcase, and we announce them on X.

Your own key, higher limits

A builder key lifts the default 60 quotes a minute and shows your traffic on your dashboard.

Proof of Savings for your users

Every swap gets a public proof of how much more it paid than the best Uniswap route. Share it, link it, show it.

The toolkit

Everything is public. Start building before you apply; the key adds limits and your fee.

The Built on Hookline badge

Add it to your site or README. It links to this page.

Built on Hookline badge, darkBuilt on Hookline badge, light
<a href="https://hookline.trade/build"><img src="https://hookline.trade/badge.svg" alt="Built on Hookline" height="36"></a>

Ship in 10 minutes

A whole bot in one file: quote for your wallet, approve and sign when asked, build, verify the transaction, send, track. With your key it earns your fee.

bot.ts
// bot.ts: best-price swaps on Robinhood Chain through Hookline. Run: npm i viem && PRIVATE_KEY=0x… API_KEY=… npx tsx bot.ts
import { createPublicClient, createWalletClient, defineChain, getAddress, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const BASE = "https://hookline.trade/api";
const API_KEY = process.env.API_KEY; // your builder key: higher limits, and your own fee
const PARTNER_FEE_BPS = 30; // 0.30% for you, paid on-chain to your payout address (max 50 bps, capped by your key)
const UNIVERSAL_ROUTER = getAddress("0x204faca1764b154221e35c0d20abb3c525710498");
const PERMIT2 = getAddress("0x000000000022d473030f116ddee9f6b43ac78ba3");
const NATIVE_ETH = "0x0000000000000000000000000000000000000000";

const robinhood = defineChain({
  id: 4663,
  name: "Robinhood Chain",
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
  rpcUrls: { default: { http: ["https://rpc.mainnet.chain.robinhood.com"] } },
});
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const wallet = createWalletClient({ account, chain: robinhood, transport: http() });
const client = createPublicClient({ chain: robinhood, transport: http() });

type Field = { name: string; type: string };
type Permit = {
  domain: { name: string; chainId: number; verifyingContract: Hex };
  types: Record<string, Field[]>;
  message: { details: { token: Hex; amount: string; expiration: number; nonce: number }; spender: Hex; sigDeadline: string };
};
type Step = { kind: "approve"; token: Hex; spender: Hex; tx: { to: Hex; data: Hex } } | { kind: "permit"; token: Hex; typedData: Permit };
type Quote = { id: string; sell: { token: string; amountAtomic: string }; buy: { amount: string; symbol: string }; steps: Step[]; fee: { partner: { amount: string } | null } };
type Built = { quoteId: string; tx: { to: Hex; data: Hex; value: string; chainId: number; gas: string | null } };
type Tracked = { status: "pending" | "confirmed" | "failed" | "unknown"; amountOut: string | null };

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function api<T>(path: string, body?: unknown): Promise<T> {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(BASE + path, {
      method: body === undefined ? "GET" : "POST",
      headers: { "Content-Type": "application/json", ...(API_KEY ? { "X-Api-Key": API_KEY } : {}) },
      body: body === undefined ? undefined : JSON.stringify(body),
    });
    if (res.status === 429) {
      await sleep(Number(res.headers.get("Retry-After") ?? "5") * 1000);
      continue;
    }
    const data = await res.json();
    if (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
    return data as T;
  }
  throw new Error("Still rate limited after 5 attempts.");
}

async function swap(sell: string, buy: string, amount: string): Promise<Tracked> {
  const ask = { sell, buy, amount, taker: account.address, slippageBps: 50, ...(API_KEY ? { partnerFeeBps: PARTNER_FEE_BPS } : {}) };
  let quote = await api<Quote>("/quote", ask);

  // 1. Approve Permit2 once per token, then quote again (the steps change once it lands).
  const approve = quote.steps.find((s) => s.kind === "approve");
  if (approve) {
    if (getAddress(approve.spender) !== PERMIT2 || getAddress(approve.tx.to) !== getAddress(approve.token)) throw new Error("Unexpected approval");
    const hash = await wallet.sendTransaction({ to: approve.tx.to, data: approve.tx.data });
    if ((await client.waitForTransactionReceipt({ hash })).status !== "success") throw new Error("Approval reverted");
    quote = await api<Quote>("/quote", ask);
  }

  // 2. Sign the Permit2 allowance when asked (no gas). viem builds the EIP712Domain type itself.
  let permitSignature: Hex | undefined;
  const permit = quote.steps.find((s) => s.kind === "permit");
  if (permit) {
    const { domain, message } = permit.typedData;
    const { EIP712Domain: _domainType, ...types } = permit.typedData.types;
    if (getAddress(message.spender) !== UNIVERSAL_ROUTER || getAddress(domain.verifyingContract) !== PERMIT2 || domain.chainId !== 4663) throw new Error("Unexpected Permit2 request");
    permitSignature = await wallet.signTypedData({
      domain,
      types,
      primaryType: "PermitSingle",
      message: { details: { ...message.details, amount: BigInt(message.details.amount) }, spender: message.spender, sigDeadline: BigInt(message.sigDeadline) },
    });
  }

  // 3. Build: simulated on the live chain before it comes back.
  const { tx, quoteId } = await api<Built>("/swap", { quoteId: quote.id, permitSignature });

  // 4. Verify before signing anything.
  const expectedValue = quote.sell.token === NATIVE_ETH ? quote.sell.amountAtomic : "0";
  if (quoteId !== quote.id) throw new Error("Refusing: built for another quote");
  if (getAddress(tx.to) !== UNIVERSAL_ROUTER) throw new Error("Refusing: tx.to is not the Uniswap Universal Router");
  if (tx.chainId !== 4663) throw new Error("Refusing: wrong chain");
  if (tx.value !== expectedValue) throw new Error("Refusing: unexpected ETH value");

  // 5. Send, then 6. track until settled.
  const hash = await wallet.sendTransaction({ to: tx.to, data: tx.data, value: BigInt(tx.value), gas: tx.gas ? BigInt(tx.gas) : undefined });
  let status = await api<Tracked>("/swaps/track", { quoteId: quote.id, txHash: hash });
  while (status.status === "pending") {
    await sleep(2_000);
    status = await api<Tracked>(`/swaps/${hash}`);
  }
  console.log(status.status, quote.fee.partner ? `you earned ${quote.fee.partner.amount} ${quote.buy.symbol}` : "");
  console.log("Proof of Savings:", `https://hookline.trade/proof/${hash}`);
  return status;
}

swap("ETH", "USDG", "0.01").catch((err) => {
  console.error(err);
  process.exit(1);
});

Earn on a quote

Shell
curl -s "https://hookline.trade/api/quote?sell=ETH&buy=USDG&amount=0.5&taker=0xYourUser&partnerFeeBps=30" \
  -H "X-Api-Key: $HOOKLINE_KEY"
# fee.partner = { bps: 30, amount, amountAtomic, recipient: your payout address }

Or give it to an agent

MCP config
{
  "mcpServers": {
    "hookline": {
      "url": "https://hookline.trade/api/mcp",
      "headers": {
        "X-Api-Key": "your_builder_key (optional)"
      }
    }
  }
}

In MCP, pass partner_fee_bps to get_quote. More clients in the MCP docs.

How review works

  1. 1

    Apply

    Tell us what you are building and where to reach you. Add a payout address to earn fees.

  2. 2

    We review

    A person looks at every project, usually within a few days. It costs nothing and there is no lock-in.

  3. 3

    Ship

    Your key arrives through the contact you gave. Set partnerFeeBps, go live, get featured.

Apply for a builder key

Two minutes. We read every application.

Already approved? Open your builder dashboard to see your traffic and fees.

0/40
0/90

One line people will read on the showcase.

0/600

How it uses Hookline, who it is for, where it stands today.

What it uses

Links

At least one of website, X or GitHub.

Email, Telegram or X. Your API key goes here once approved.

Optional, a rough idea is enough.

Optional, but needed to earn your fee (up to 0.5% per swap), paid on-chain to this address on Robinhood Chain.

By applying you agree that Hookline may list the project once approved.