> ## 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.

# Op Catalog

> Every op registered in the compose manifest, rendered live.

export const ComposeOps = () => {
  const DEDICATED_OPS = new Set(['lifi.swap', 'lifi.zap', 'core.call']);
  const slugify = s => String(s).replace(/\./g, '-').replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/_/g, '-').toLowerCase();
  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 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>;
  const [state, setState] = useState({
    data: null,
    error: null
  });
  const [filter, setFilter] = useState('');
  useEffect(() => {
    let cancelled = false;
    setState({
      data: null,
      error: null
    });
    const run = async () => {
      try {
        const response = await fetch(`${env.base}/compose/manifest`);
        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 renderBody = () => {
    if (state.error) {
      return <div style={{
        padding: '0.75rem 1rem',
        border: '1px solid #f5c6cb',
        background: '#f8d7da',
        color: '#721c24',
        borderRadius: '6px'
      }}>
        <strong>Failed to load ops.</strong>
        <div style={{
        fontFamily: 'monospace',
        marginTop: '0.25rem',
        fontSize: '0.9em'
      }}>{state.error}</div>
      </div>;
    }
    if (!state.data) return <div>Loading ops…</div>;
    const manifest = state.data;
    const ops = [...manifest.operations ?? []].sort((a, b) => a.id.localeCompare(b.id));
    const needle = filter.trim().toLowerCase();
    const visible = needle ? ops.filter(op => [op.id, op.description ?? ''].some(field => field.toLowerCase().includes(needle))) : ops;
    return <>
      <div style={{
      marginBottom: '0.5rem'
    }}>
        <input type="text" value={filter} onChange={e => setFilter(e.target.value)} placeholder="Filter by op id or description…" 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>{ops.length}</code> ops · manifest version <code>{manifest.manifestVersion}</code>
        </div>
      </div>
      <table>
        <thead>
          <tr>
            <th className="text-left"><strong>Op</strong></th>
            <th className="text-left"><strong>Description</strong></th>
            <th className="text-left"><strong>Inputs</strong></th>
            <th className="text-left"><strong>Outputs</strong></th>
          </tr>
        </thead>
        <tbody>
          {visible.map(op => <tr key={op.id}>
              <td>
                {DEDICATED_OPS.has(op.id) ? <a href={`/composer/composer-api/ops/${slugify(op.id)}`}><code>{op.id}</code></a> : <code>{op.id}</code>}
              </td>
              <td>{op.description ?? ''}</td>
              <td>{op.inputs?.length ?? 0}</td>
              <td>{op.outputs?.length ?? 0}</td>
            </tr>)}
        </tbody>
      </table>
    </>;
  };
  return <>
    {envTabs}
    {renderBody()}
  </>;
};

Ops are the named primitive operations you invoke inside a `Flow`. Each op declares its input and output ports, a config JSON Schema, and a human-readable description. The table below is rendered live from the compose manifest, so it always reflects exactly what the selected backend accepts. Use the toggle above the table to switch between **Production** (`composer.li.quest`, the default) and the **ETHGlobal preview** (`ethglobal-composer.li.quest`).

This catalog is the **complete, authoritative list** of every supported op — many more than the handful that have their own pages. A few of the most commonly used ops have dedicated reference pages with worked examples and prose context: [`lifi.swap`](/composer/composer-api/ops/lifi-swap), [`lifi.zap`](/composer/composer-api/ops/lifi-zap), and [`core.call`](/composer/composer-api/ops/core-call). Those pages explain a single op in depth — they are not the full set. Every other op is listed in the table below with its signature; for type-level detail use IDE autocomplete on `@lifi/composer-sdk`.

For how to wire ops into a flow, see [Build a Flow](/composer/composer-api/guides/build-a-flow#chain-ops-in-your-flow).

<Warning>
  **The ETHGlobal preview is unaudited.** Ops that appear only under the ETHGlobal preview toggle (those backing flashloans, for example) are part of an **unaudited** preview deployment. Use them for the hackathon and experimentation only — not in production or with significant funds.
</Warning>

<ComposeOps />
