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

# call

> Execute an arbitrary contract call and measure the resulting token balance diff

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)}
  </>;
};

The `call` materialiser resolves an input resource by executing a user-supplied contract call before the flow starts and measuring the resulting token balance delta. Use it when the amount you want to spend is produced by an external action — for example, "claim rewards from staking contract X, then deposit the claimed amount into vault Y".

At compile time, the materialiser executes the supplied `calldata` against `target` from the sender's execution proxy, measures the change in the input resource's declared token balance, and injects that delta as the input amount. The resulting flow calldata performs the same call on-chain, then consumes the produced balance as the flow's input resource.

<Note>
  **`materialisers.call` ≠ `core.call`.** The `core.call` op (covered in the [Op catalog](/composer/composer-api/ops/core-call)) accepts a `functionSignature` plus typed `args` in its config and ABI-encodes the call for you. The `call` **materialiser** is a separate construct: it takes **pre-encoded** `calldata` (a raw hex string) and a `target` address. Callers are responsible for ABI-encoding the call themselves (`viem`'s `encodeFunctionData`, `ethers.Interface.encodeFunctionData`, etc.).
</Note>

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

## Example

Consume the output of `claimRewards()` on a staking contract as the flow input. ABI-encode the call once before invoking:

```ts theme={"system"}
import { encodeFunctionData } from 'viem';

const claimRewardsCalldata = encodeFunctionData({
  abi: [{ type: 'function', name: 'claimRewards', inputs: [], outputs: [] }],
  functionName: 'claimRewards',
});

const request = sdk.request(builder.build(), {
  signer: OWNER,
  inputs: {
    amountIn: materialisers.call({
      target: STAKING_CONTRACT,
      calldata: claimRewardsCalldata, // pre-encoded hex string
    }),
  },
  sweepTo: builder.context.sender,
});
```
