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

# lifi.zap

> 通过 edge 目录将代币路由进某个 DeFi 协议仓位

export const ComposeItemDetail = ({kind, id}) => {
  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(`https://composer.li.quest${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]);
  if (state.error) {
    return <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 <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 notFound('routing-edges');
    const edges = pack.edges ?? [];
    return <>
      <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 notFound(`${kind}s`);
  return <>
    <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)}
  </>;
};

<Note>
  `lifi.zap` 是 Composer 支持的众多 op 之一。它在这里拥有专属页面，是因为值得深入讲解 —— 有关每一个 op 的完整、实时列表，请参见 [Op 目录](/composer/composer-api/ops)。
</Note>

`lifi.zap` 是任何注册为 compose 路由边的仓位的入口点：ERC-4626 金库、Aave 式借贷市场、Pendle、Yearn 等等。它消费一个输入资源，并产出一个与 `resourceOut` 匹配的输出资源，在内部将一次交换（如有需要）与协议专属的 `enter-position` 边组合在一起。

与 [`lifi.swap`](/composer/composer-api/ops/lifi-swap) 不同，聚合器**不**保证 zap 输出端口上的最小产出 —— 请始终在 `amountOut` 上附加一个 `slippage` guard。有关当前支持的协议和代币，请参见[路由边目录](/composer/protocols-and-chains)。

<ComposeItemDetail kind="op" id="lifi.zap" />

## 示例

在 Ethereum 主网上将 USDC zap 进 Aave v3 aEthUSDC：

```ts theme={"system"}
builder.lifi.zap('zap', {
  bind: { amountIn: builder.inputs.amountIn },
  config: {
    resourceOut: resources.erc20(A_ETH_USDC, 1),
  },
  guards: [guards.slippage({ port: 'amountOut', bps: 100 })],
});
```

完整可运行的配方：[Swap and Deposit](/composer/composer-api/recipes/swap-and-zap)。
