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

# flashloan

> Source a flow input by borrowing the amount from a flashloan provider, repaid within the same transaction.

export const ComposeItemDetail = ({kind, 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 = next => {
    if (!COMPOSE_ENVS.some(e => e.id === next)) return;
    setEnvId(next);
    try {
      window.localStorage?.setItem(ENV_STORAGE_KEY, next);
    } catch {}
  };
  const [state, setState] = useState({
    data: null,
    error: null
  });
  const [copied, setCopied] = useState(null);
  useEffect(() => {
    let cancelled = false;
    setState({
      data: null,
      error: null
    });
    const path = kind === 'edge' ? '/compose/zap-packs' : '/compose/manifest';
    const run = async () => {
      try {
        const response = await fetch(`${env.base}${path}`);
        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;
    };
  }, [kind, id, 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 {kind} <code>{id}</code>.</strong>
        <div style={{
      fontFamily: 'monospace',
      marginTop: '0.25rem',
      fontSize: '0.9em'
    }}>{state.error}</div>
      </div>
    </>;
  }
  if (!state.data) return <>{envTabs}<div>Loading {kind} details…</div></>;
  const notFound = label => <div style={{
    padding: '0.75rem 1rem',
    border: '1px solid #ffeaa7',
    background: '#fff3cd',
    color: '#856404',
    borderRadius: '6px'
  }}>
      <strong>No {kind} found for id <code>{id}</code>.</strong>
      <div style={{
    marginTop: '0.25rem',
    fontSize: '0.9em'
  }}>
        It may have been renamed or removed. See the <a href={`/composer/${label}`}>{label} catalog</a> for the current list.
      </div>
    </div>;
  const renderSchema = schema => {
    if (!schema) return <div style={{
      opacity: 0.6,
      fontSize: '0.9em'
    }}>No config schema.</div>;
    return <pre style={{
      background: '#0d1117',
      color: '#e6edf3',
      padding: '0.75rem',
      borderRadius: '6px',
      overflow: 'auto',
      fontSize: '0.85em',
      border: '1px solid #30363d'
    }}><code style={{
      color: 'inherit',
      background: 'transparent'
    }}>{JSON.stringify(schema, null, 2)}</code></pre>;
  };
  const renderPortRows = ports => {
    if (!ports || ports.length === 0) return <div style={{
      opacity: 0.6,
      fontSize: '0.9em'
    }}>None.</div>;
    return <table>
      <thead>
        <tr>
          <th className="text-left"><strong>Name</strong></th>
          <th className="text-left"><strong>Kind</strong></th>
          <th className="text-left"><strong>Details</strong></th>
        </tr>
      </thead>
      <tbody>
        {ports.map((port, idx) => {
      const {name, kind: portKind, ...rest} = port;
      const entries = Object.entries(rest);
      return <tr key={`${name ?? 'port'}-${idx}`}>
            <td><code>{name ?? '—'}</code></td>
            <td><code>{portKind ?? '—'}</code></td>
            <td>
              {entries.length === 0 ? <span style={{
        opacity: 0.5
      }}>—</span> : entries.map(([k, v]) => <span key={k} style={{
        display: 'inline-block',
        marginRight: '0.5rem'
      }}>
                      <span style={{
        opacity: 0.7
      }}>{k}:</span> <code>{typeof v === 'string' ? v : JSON.stringify(v)}</code>
                    </span>)}
            </td>
          </tr>;
    })}
      </tbody>
    </table>;
  };
  const renderSelectors = selectors => {
    if (!selectors || selectors.length === 0) return <div style={{
      opacity: 0.6,
      fontSize: '0.9em'
    }}>No selectors.</div>;
    return <table>
      <thead>
        <tr>
          <th className="text-left"><strong>Binding</strong></th>
          <th className="text-left"><strong>Source</strong></th>
          <th className="text-left"><strong>Match</strong></th>
          <th className="text-left"><strong>Selection</strong></th>
        </tr>
      </thead>
      <tbody>
        {selectors.map((sel, idx) => <tr key={`${sel.binding ?? 'sel'}-${idx}`}>
            <td><code>{sel.binding ?? '—'}</code></td>
            <td><code>{sel.source ?? '—'}</code></td>
            <td><code>{sel.match ? JSON.stringify(sel.match) : '—'}</code></td>
            <td><code>{sel.selection ? JSON.stringify(sel.selection) : '—'}</code></td>
          </tr>)}
      </tbody>
    </table>;
  };
  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
    }}> · chain {chainId}</span>
    </>;
  };
  if (kind === 'edge') {
    const packs = state.data;
    const pack = packs.find(p => p.protocol === id);
    if (!pack) return <>{envTabs}{notFound('routing-edges')}</>;
    const edges = pack.edges ?? [];
    return <>
      {envTabs}
      <div style={{
      marginBottom: '0.5rem',
      fontSize: '0.9em',
      opacity: 0.75
    }}>
        Protocol <code>{pack.protocol}</code> · <code>{edges.length}</code> edges
      </div>
      <h3>Edges</h3>
      {edges.length === 0 ? <div style={{
      opacity: 0.6,
      fontSize: '0.9em'
    }}>No edges registered.</div> : <table>
            <thead>
              <tr>
                <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>
              {edges.map((edge, idx) => <tr key={`${edge.type}-${edge.in?.address}-${edge.out?.address}-${idx}`}>
                  <td><code>{edge.type}</code></td>
                  <td>{addressCell(edge.in?.address ?? '', edge.in?.chainId ?? '', idx, 'in')}</td>
                  <td>{addressCell(edge.out?.address ?? '', edge.out?.chainId ?? '', idx, 'out')}</td>
                </tr>)}
            </tbody>
          </table>}
    </>;
  }
  const manifest = state.data;
  let item = null;
  if (kind === 'op') item = (manifest.operations ?? []).find(op => op.id === id); else if (kind === 'materialiser') item = (manifest.materialisers ?? []).find(m => m.kind === id); else if (kind === 'guard') item = (manifest.guards ?? []).find(g => g.kind === id);
  if (!item) return <>{envTabs}{notFound(`${kind}s`)}</>;
  return <>
    {envTabs}
    <div style={{
    marginBottom: '0.5rem',
    fontSize: '0.9em',
    opacity: 0.75
  }}>
      Manifest version <code>{manifest.manifestVersion}</code>
      {item.description ? <> · {item.description}</> : null}
    </div>
    <h3>Signature</h3>
    <ul>
      <li><strong>Kind:</strong> <code>{kind}</code></li>
      <li><strong>Id:</strong> <code>{id}</code></li>
      {item.accepts ? <li><strong>Accepts:</strong> <code>{item.accepts}</code></li> : null}
    </ul>
    {kind === 'op' ? <>
      <h3>Input ports</h3>
      {renderPortRows(item.inputs)}
      <h3>Output ports</h3>
      {renderPortRows(item.outputs)}
    </> : null}
    {kind === 'guard' ? <>
      <h3>Selectors</h3>
      {renderSelectors(item.compatibility?.selectors)}
    </> : null}
    <h3>Config schema</h3>
    {renderSchema(item.configSchema)}
  </>;
};

<Warning>
  **Unaudited preview — not for production funds.** Flashloans are available in the [ETHGlobal preview](/composer/composer-api/reference/addresses#ethglobal-hackathon) only — the `FlashloanRegistry` and `LiFiFlashloanAdapter` contracts are not deployed to production and are **unaudited**. Use this materialiser for the hackathon and experimentation only; do not route significant value through it.
</Warning>

`flashloan` sources a flow input by **borrowing** the amount from a flashloan provider instead of pulling it from the signer. The borrowed amount is made available to the flow as its input, so the rest of the flow can spend capital the user doesn't hold upfront.

Because a flashloan must be repaid in the same transaction, a flow that borrows via this materialiser **must** settle the loan (principal + the provider's fee) before it ends — typically with one or more [`lifi.flashloanRepay`](/composer/composer-api/ops) ops. If the loan isn't repaid, simulation reverts.

Config:

| Field          | Type                                                       | Notes                                                                                                                  |
| -------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `providerKind` | `'aave-v3' \| 'erc3156' \| 'balancer-v2' \| 'morpho-blue'` | Which flashloan provider to borrow from. Fees vary by provider (e.g. Aave V3 charges 5 bps; Balancer V2 charges none). |
| `amount`       | integer string                                             | The amount to borrow, in the token's smallest units.                                                                   |

<ComposeItemDetail kind="materialiser" id="flashloan" />

## Example

Borrow USDC via an Aave V3 flashloan and settle it within the flow:

```ts theme={"system"}
const builder = sdk.flow(8453, {
  name: 'flashloan-borrow',
  inputs: {
    borrowed: resources.erc20(USDC, 8453),
  },
});

// ... spend `builder.inputs.borrowed` across ops, then repay the loan
// (principal + fee) with `lifi.flashloanRepay` before the flow ends ...

const request = sdk.request(builder.build(), {
  signer: OWNER,
  inputs: {
    borrowed: materialisers.flashloan({
      providerKind: 'aave-v3',
      amount: '1000000000', // 1,000 USDC (6 decimals)
    }),
  },
});
```

For a complete, tested flow that uses two concurrent flashloans to migrate a borrow position, see the [debt-migration recipe](/composer/composer-api/recipes/debt-migration) and [`aaveToMorphoDebtMigration.ts`](https://github.com/lifinance/composer-sdk-examples/blob/main/examples/staged/aaveToMorphoDebtMigration.ts). For the minimal borrow-and-repay shape, see [`flashloanRepay.ts`](https://github.com/lifinance/composer-sdk-examples/blob/main/examples/staged/flashloanRepay.ts).
