> ## Documentation Index
> Fetch the complete documentation index at: https://docs.li.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Supported Protocols & Chains

> Every protocol, vault, and chain Composer routes into — across both integration paths.

export const ComposeEdges = () => {
  const CHAIN_NAMES = {
    1: "Ethereum",
    10: "Optimism",
    14: "Avalanche",
    25: "Cronos",
    30: "Rootstock",
    50: "XDC",
    56: "BNB",
    100: "Gnosis",
    122: "Fuse",
    130: "Unichain",
    137: "Polygon",
    143: "Monad",
    146: "Sonic",
    148: "ShimmerEVM",
    196: "X Layer",
    242: "Plinga",
    250: "Fantom Opera",
    252: "Fraxtal",
    288: "Boba",
    324: "zkSync",
    480: "World Chain",
    988: "Stable",
    999: "HyperEVM",
    1088: "Metis Andromeda",
    1101: "Polygon zkEVM",
    1135: "Lisk",
    1284: "Moonbeam",
    1285: "Moonriver",
    1329: "Sei",
    1501: "BEVM Canary",
    1625: "Gravity Alpha",
    1923: "Swellchain",
    2741: "Abstract",
    4326: "MegaETH",
    5000: "Mantle",
    8217: "Kaia",
    8453: "Base",
    9745: "Plasma",
    13371: "Immutable zkEVM",
    33139: "ApeChain",
    34443: "Mode",
    42161: "Arbitrum",
    42220: "Celo",
    42793: "Etherlink",
    43114: "Avalanche",
    55244: "Superposition",
    57073: "Ink",
    59144: "Linea",
    60808: "BOB",
    80094: "Berachain",
    81457: "Blast",
    167000: "Taiko",
    534352: "Scroll",
    747474: "Katana",
    888888888: "Ancient8",
    1313161554: "Aurora"
  };
  const chainName = id => CHAIN_NAMES[id] || `Chain ${id}`;
  const COMPOSE_ENVS = [{
    id: "production",
    label: "Production",
    base: "https://composer.li.quest",
    host: "composer.li.quest"
  }, {
    id: "ethglobal",
    label: "ETHGlobal preview",
    base: "https://ethglobal-composer.li.quest",
    host: "ethglobal-composer.li.quest"
  }];
  const ENV_STORAGE_KEY = "lifi-composer-docs-env";
  const [envId, setEnvId] = useState(() => {
    if (typeof window === "undefined") return "production";
    try {
      const stored = window.localStorage?.getItem(ENV_STORAGE_KEY);
      return stored && COMPOSE_ENVS.some(e => e.id === stored) ? stored : "production";
    } catch {
      return "production";
    }
  });
  const env = COMPOSE_ENVS.find(e => e.id === envId) ?? COMPOSE_ENVS[0];
  const selectEnv = id => {
    if (!COMPOSE_ENVS.some(e => e.id === id)) return;
    setEnvId(id);
    try {
      window.localStorage?.setItem(ENV_STORAGE_KEY, id);
    } catch {}
  };
  const [state, setState] = useState({
    data: null,
    error: null
  });
  const [filter, setFilter] = useState("");
  const [copied, setCopied] = useState(null);
  useEffect(() => {
    let cancelled = false;
    setState({
      data: null,
      error: null
    });
    const run = async () => {
      try {
        const response = await fetch(`${env.base}/compose/zap-packs`);
        if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
        const body = await response.json();
        if (!body || body.success !== true) {
          throw new Error(body?.error?.message ?? "Unexpected response shape");
        }
        if (!cancelled) setState({
          data: body.data,
          error: null
        });
      } catch (err) {
        if (!cancelled) setState({
          data: null,
          error: err.message ?? String(err)
        });
      }
    };
    run();
    return () => {
      cancelled = true;
    };
  }, [env.base]);
  const envTabs = <div style={{
    marginBottom: "1rem"
  }}>
      <div role="tablist" aria-label="Compose backend environment" style={{
    display: "inline-flex",
    gap: "0.25rem",
    padding: "0.25rem",
    borderRadius: "8px",
    border: "1px solid rgba(127,127,127,0.3)",
    background: "rgba(127,127,127,0.08)"
  }}>
        {COMPOSE_ENVS.map(e => {
    const active = e.id === env.id;
    return <button key={e.id} type="button" role="tab" aria-selected={active} title={`Fetch live data from ${e.base}`} onClick={() => selectEnv(e.id)} style={{
      cursor: "pointer",
      border: "none",
      borderRadius: "6px",
      padding: "0.35rem 0.9rem",
      fontSize: "0.9em",
      fontWeight: active ? 600 : 500,
      background: active ? "#3b82f6" : "transparent",
      color: active ? "#fff" : "inherit",
      transition: "background 0.12s ease"
    }}>
              {e.label}
            </button>;
  })}
      </div>
      <div style={{
    marginTop: "0.35rem",
    fontSize: "0.8em",
    opacity: 0.65
  }}>
        Live data from <code>{env.host}</code>
        {env.id !== "production" ? " · preview of unreleased features, may be unstable" : ""}
      </div>
    </div>;
  if (state.error) {
    return <>
        {envTabs}
        <div style={{
      padding: "0.75rem 1rem",
      border: "1px solid #f5c6cb",
      background: "#f8d7da",
      color: "#721c24",
      borderRadius: "6px"
    }}>
          <strong>Failed to load routing edges.</strong>
          <div style={{
      fontFamily: "monospace",
      marginTop: "0.25rem",
      fontSize: "0.9em"
    }}>
            {state.error}
          </div>
        </div>
      </>;
  }
  if (!state.data) return <>
        {envTabs}
        <div>Loading routing edges…</div>
      </>;
  const packs = state.data;
  const byProtocol = {};
  for (const pack of packs) {
    if (!byProtocol[pack.protocol]) {
      byProtocol[pack.protocol] = {
        chains: new Set(),
        edgeCount: 0
      };
    }
    byProtocol[pack.protocol].edgeCount += pack.edges.length;
    for (const edge of pack.edges) {
      if (edge.in?.chainId) byProtocol[pack.protocol].chains.add(edge.in.chainId);
      if (edge.out?.chainId) byProtocol[pack.protocol].chains.add(edge.out.chainId);
    }
  }
  const protocolSummary = Object.entries(byProtocol).map(([protocol, info]) => ({
    protocol,
    chains: [...info.chains].sort((a, b) => a - b),
    edgeCount: info.edgeCount
  })).sort((a, b) => a.protocol.localeCompare(b.protocol));
  const rows = packs.flatMap(pack => pack.edges.map(edge => ({
    protocol: pack.protocol,
    type: edge.type,
    inAddress: edge.in?.address ?? "",
    inChainId: edge.in?.chainId ?? "",
    outAddress: edge.out?.address ?? "",
    outChainId: edge.out?.chainId ?? ""
  })));
  const needle = filter.trim().toLowerCase();
  const visible = needle ? rows.filter(row => [row.protocol, row.type, row.inAddress, String(row.inChainId), row.outAddress, String(row.outChainId)].some(field => field.toLowerCase().includes(needle))) : rows;
  const shortAddress = addr => addr && addr.length > 10 ? `${addr.slice(0, 6)}…${addr.slice(-4)}` : addr;
  const copyAddress = async (address, key) => {
    if (!address) return;
    try {
      if (navigator?.clipboard?.writeText) {
        await navigator.clipboard.writeText(address);
      } else {
        const el = document.createElement("textarea");
        el.value = address;
        el.setAttribute("readonly", "");
        el.style.position = "absolute";
        el.style.left = "-9999px";
        document.body.appendChild(el);
        el.select();
        document.execCommand("copy");
        document.body.removeChild(el);
      }
      setCopied(key);
      setTimeout(() => setCopied(current => current === key ? null : current), 1200);
    } catch {}
  };
  const addressCell = (address, chainId, rowIdx, side) => {
    const key = `${rowIdx}-${side}`;
    const isCopied = copied === key;
    return <>
        <code role="button" tabIndex={0} title={isCopied ? "Copied!" : `Click to copy ${address}`} onClick={() => copyAddress(address, key)} onKeyDown={e => {
      if (e.key === "Enter" || e.key === " ") {
        e.preventDefault();
        copyAddress(address, key);
      }
    }} style={{
      cursor: "pointer",
      background: isCopied ? "#d4edda" : undefined,
      color: isCopied ? "#155724" : undefined,
      textDecoration: "underline dotted",
      textDecorationColor: "#999",
      borderRadius: "3px",
      padding: isCopied ? "0 2px" : undefined
    }}>
          {isCopied ? "Copied!" : shortAddress(address)}
        </code>
        <span style={{
      opacity: 0.6
    }}> · {chainName(chainId)}</span>
      </>;
  };
  return <>
      {envTabs}
      <h3 style={{
    marginTop: "1.5rem",
    marginBottom: "0.5rem"
  }}>
        Supported protocols ({protocolSummary.length})
      </h3>
      <div style={{
    marginBottom: "0.75rem",
    fontSize: "0.9em",
    opacity: 0.75
  }}>
        Every protocol Composer routes into, with the chains it covers. See the
        vault-level detail in the table below.
      </div>
      <table>
        <thead>
          <tr>
            <th className="text-left">
              <strong>Protocol</strong>
            </th>
            <th className="text-left">
              <strong>Chains</strong>
            </th>
          </tr>
        </thead>
        <tbody>
          {protocolSummary.map(({protocol, chains}) => <tr key={protocol}>
              <td style={{
    textTransform: "capitalize"
  }}>
                <strong>{protocol}</strong>
              </td>
              <td>{chains.map(chainName).join(", ")}</td>
            </tr>)}
        </tbody>
      </table>

      <h3 style={{
    marginTop: "1.75rem",
    marginBottom: "0.5rem"
  }}>
        Vaults ({rows.length})
      </h3>
      <div style={{
    marginBottom: "0.5rem",
    fontSize: "0.9em",
    opacity: 0.75
  }}>
        Every vault, lending market, staking pod, and mint/burn pair Composer
        routes into, with input and output token addresses.
      </div>
      <div style={{
    marginBottom: "0.5rem"
  }}>
        <input type="text" value={filter} onChange={e => setFilter(e.target.value)} placeholder="Filter by protocol, type, address, chainId…" style={{
    width: "100%",
    padding: "0.5rem 0.75rem",
    border: "1px solid #ccc",
    borderRadius: "6px",
    fontSize: "0.95em"
  }} />
        <div style={{
    marginTop: "0.25rem",
    fontSize: "0.85em",
    opacity: 0.7
  }}>
          Showing <code>{visible.length}</code> of <code>{rows.length}</code>{" "}
          edges across <code>{packs.length}</code> protocols · click an address
          to copy
        </div>
      </div>
      <table>
        <thead>
          <tr>
            <th className="text-left">
              <strong>Protocol</strong>
            </th>
            <th className="text-left">
              <strong>Type</strong>
            </th>
            <th className="text-left">
              <strong>In</strong>
            </th>
            <th className="text-left">
              <strong>Out</strong>
            </th>
          </tr>
        </thead>
        <tbody>
          {visible.map((row, idx) => <tr key={`${row.protocol}-${row.type}-${row.inAddress}-${row.outAddress}-${idx}`}>
              <td>
                <code>{row.protocol}</code>
              </td>
              <td>
                <code>{row.type}</code>
              </td>
              <td>{addressCell(row.inAddress, row.inChainId, idx, "in")}</td>
              <td>{addressCell(row.outAddress, row.outChainId, idx, "out")}</td>
            </tr>)}
        </tbody>
      </table>
    </>;
};

This page lists every protocol Composer routes into — vaults, lending markets, staking pods, and yield strategies — with the exact token addresses and chains. Use it to verify coverage before integration, build vendor-comparison material, or look up a specific vault's address. The list applies to both the Composer API and the LI.FI API integration.

The view has two parts:

* **Supported protocols** — a row-per-protocol list showing the chains each one covers.
* **Vaults** — a searchable table of every vault, lending market, staking pod, and mint/burn pair, with input and output token addresses.

<details>
  <summary><strong>How edges work under the hood</strong> — for engineers</summary>

  Routing edges are the compose-native replacement for legacy zap packs: protocol-specific lowerings that turn a generic `lifi.zap` call into concrete VM instructions. Each edge declares an input token and an output token on a specific chain, plus an edge type (`enter-position`, `exit-position`, `mint-burn`, …). The planner searches across edges to compose multi-hop routes. Live data is fetched from `/compose/zap-packs` on the selected backend — use the toggle above the table to switch between **Production** (`composer.li.quest`, the default) and the **ETHGlobal preview** (`ethglobal-composer.li.quest`). The ETHGlobal preview is an **unaudited** deployment for the hackathon and experimentation only — don't route significant value through it.
</details>

<ComposeEdges />

## Requesting a new protocol integration

Composer integrates protocols that meet two requirements:

* The protocol must be on an **EVM-compatible chain**.
* The protocol must return **tokenised positions** (e.g., vault tokens, aTokens, LSTs).

See [New Protocol Onboarding](/composer/for-protocols/integration-guide) or contact the LI.FI team to start the process.
