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

# Materialisers catalog

> Every materialiser registered in the compose manifest.

export const ComposeMaterialisers = () => {
  const DEDICATED_MATERIALISERS = new Set(['balanceOf', 'call', 'directDeposit', 'flashloan']);
  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
  });
  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 slugify = s => String(s).replace(/\./g, '-').replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/_/g, '-').toLowerCase();
  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 materialisers.</strong>
        <div style={{
        fontFamily: 'monospace',
        marginTop: '0.25rem',
        fontSize: '0.9em'
      }}>{state.error}</div>
      </div>;
    }
    if (!state.data) return <div>Loading materialisers…</div>;
    const manifest = state.data;
    const materialisers = [...manifest.materialisers ?? []].sort((a, b) => a.kind.localeCompare(b.kind));
    return <>
      <div style={{
      marginBottom: '0.5rem',
      fontSize: '0.9em',
      opacity: 0.75
    }}>
        Manifest version <code>{manifest.manifestVersion}</code> · <code>{materialisers.length}</code> materialisers
      </div>
      <table>
        <thead>
          <tr>
            <th className="text-left"><strong>Materialiser</strong></th>
            <th className="text-left"><strong>Description</strong></th>
            <th className="text-left"><strong>Accepts</strong></th>
          </tr>
        </thead>
        <tbody>
          {materialisers.map(m => <tr key={m.kind}>
              <td>
                {DEDICATED_MATERIALISERS.has(m.kind) ? <a href={`/composer/composer-api/materialisers/${slugify(m.kind)}`}><code>{m.kind}</code></a> : <code>{m.kind}</code>}
              </td>
              <td>{m.description ?? ''}</td>
              <td><code>{m.accepts}</code></td>
            </tr>)}
        </tbody>
      </table>
    </>;
  };
  return <>
    {envTabs}
    {renderBody()}
  </>;
};

This page lists every materialiser registered in the compose manifest. For the definition of a materialiser, when to use one, and the preconditions they produce, see [Build a Flow](/composer/composer-api/guides/build-a-flow#wire-runtime-values).

The list below is rendered live from the compose manifest. Use the toggle above the table to switch between **Production** (`composer.li.quest`, the default) and the **ETHGlobal preview** (`ethglobal-composer.li.quest`). Each entry links to a per-materialiser page with its config JSON Schema and a usage example.

<Warning>
  **The ETHGlobal preview is unaudited.** Materialisers that appear only under the ETHGlobal preview toggle (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>

<ComposeMaterialisers />
