> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Solana block height to slot converter

> Translate Solana block heights into slot numbers via the public Portal. Used during gateway-to-Portal migrations.

Solana datasets on Portal index by **slot number**, not block height. Use this converter to translate `from:` heights in your squid's `.setBlockRange({...})` into slots. It bisects the chain through the public Portal.

{(() => {
const { useState } = React;
const [height, setHeight] = useState('');
const [status, setStatus] = useState('');
const [result, setResult] = useState(null);
const [logs, setLogs] = useState([]);
const [busy, setBusy] = useState(false);

// Flip to true to render the per-step bisection trace below the result.
const SHOW_TRACE = false;

const STREAM_URL = 'https://portal.sqd.dev/datasets/solana-mainnet/finalized-stream';
const HEAD_URL = 'https://portal.sqd.dev/datasets/solana-mainnet/finalized-head';

async function probeSlot(slot) {
  const res = await fetch(STREAM_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: 'solana',
      fields: { block: { number: true, height: true } },
      fromBlock: slot,
      toBlock: slot,
    }),
  });
  if (res.status === 204) return null;
  if (!res.ok) throw new Error(`Portal returned HTTP ${res.status}`);
  const text = await res.text();
  const line = text.split('\n').find((l) => l.trim());
  if (!line) return null;
  const obj = JSON.parse(line);
  return { slot: obj.header.number, height: obj.header.height };
}

async function findBlockNear(mid, lo, hi) {
  let up = mid;
  let down = mid - 1;
  let probed = 0;
  while (up <= hi || down >= lo) {
    if (up <= hi) {
      probed++;
      const b = await probeSlot(up);
      if (b) return { block: b, probed };
      up++;
    }
    if (down >= lo) {
      probed++;
      const b = await probeSlot(down);
      if (b) return { block: b, probed };
      down--;
    }
  }
  return { block: null, probed };
}

async function convert() {
  const target = Number.parseInt(height, 10);
  if (!Number.isFinite(target) || target < 0) {
    setStatus('Enter a non-negative integer block height.');
    return;
  }
  setBusy(true);
  setResult(null);
  setLogs([]);
  setStatus('Fetching the finalized head from Portal…');
  try {
    const headRes = await fetch(HEAD_URL);
    if (!headRes.ok) throw new Error(`Portal returned HTTP ${headRes.status}`);
    const head = await headRes.json();
    let lo = 0;
    let hi = head.number;
    let best = null;
    const trace = [];
    let iter = 0;
    let totalProbes = 0;
    while (lo <= hi && iter < 80) {
      iter++;
      const mid = Math.floor((lo + hi) / 2);
      const { block, probed } = await findBlockNear(mid, lo, hi);
      totalProbes += probed;
      let line = `#${iter}: range [${lo}, ${hi}], mid ${mid}, probed ${probed} slot${probed === 1 ? '' : 's'}`;
      if (!block) {
        line += ' → no block in range';
        break;
      } else if (block.height < target) {
        line += ` → slot ${block.slot} height ${block.height} (too low)`;
        lo = block.slot + 1;
      } else {
        line += ` → slot ${block.slot} height ${block.height} (≥ target)`;
        best = block.slot;
        hi = block.slot - 1;
      }
      trace.push(line);
      setLogs([...trace]);
      setStatus(`Bisecting… ${iter} step${iter === 1 ? '' : 's'}, ${totalProbes} probes`);
    }
    if (best === null) {
      setStatus(`No slot found with height ${target}. The chain may not have reached this height yet.`);
      return;
    }
    const verify = await probeSlot(best);
    totalProbes++;
    if (verify && verify.height === target) {
      setResult(best);
      setStatus(`Done in ${totalProbes} probes; block height ${target} is at slot ${best}.`);
    } else {
      const closestHeight = verify ? verify.height : null;
      setResult(best);
      setStatus(
        closestHeight !== null
          ? `Exact height ${target} not found. Closest slot with height ≥ target is ${best} (height ${closestHeight}). Total probes: ${totalProbes}.`
          : `Exact height ${target} not found. Closest candidate slot is ${best}. Total probes: ${totalProbes}.`
      );
    }
  } catch (e) {
    setStatus(`Error: ${e.message}`);
  } finally {
    setBusy(false);
  }
}

return (
  <div style={{ border: '1px solid #d0d7de', borderRadius: '6px', padding: '1rem', margin: '1rem 0' }}>
    <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
      <label htmlFor="height-input" style={{ fontWeight: 600 }}>Block height:</label>
      <input
        id="height-input"
        type="number"
        min="0"
        value={height}
        onChange={(e) => setHeight(e.target.value)}
        placeholder="e.g. 303262650"
        disabled={busy}
        style={{ flex: '1 1 200px', padding: '0.4rem 0.6rem', borderRadius: '4px', border: '1px solid #d0d7de' }}
      />
      <button
        type="button"
        onClick={convert}
        disabled={busy}
        style={{
          padding: '0.4rem 0.9rem',
          borderRadius: '4px',
          border: '1px solid #d0d7de',
          background: busy ? '#f3f4f6' : '#0969da',
          color: busy ? '#666' : '#fff',
          cursor: busy ? 'wait' : 'pointer',
          fontWeight: 600,
        }}
      >
        {busy ? 'Searching…' : 'Convert to slot'}
      </button>
    </div>
    {status && <div style={{ marginTop: '0.6rem', fontSize: '0.9rem' }}>{status}</div>}
    {result !== null && (
      <div style={{ marginTop: '0.4rem', fontSize: '1.05rem' }}>
        Slot: <code>{result}</code>
      </div>
    )}
    {SHOW_TRACE && logs.length > 0 && (
      <pre style={{ fontSize: '0.75rem', overflowX: 'auto', marginTop: '0.4rem' }}>{logs.join('\n')}</pre>
    )}
  </div>
);
})()}
