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

# Edge Boost

> Put Goldsky's cache in front of your existing RPC endpoint. Pay only for cache hits.

// Boost is free: a cache hit costs nothing, so every hit saves the full
// provider rate.
export const HowItWorks = () => {
  const GOLD = '#D97706';
  const GREEN = '#059669';
  const BLUE = '#2563EB';
  const CYAN = '#0E7490';
  const GRAY = '#6B7280';
  const INK = '#111827';
  const REQ = '#64748B';
  const DOT_REQUESTS = 50000;
  const PROVIDER_PER_M = 5;
  const CACHE_PER_M = 0;
  const SAVE_PER_HIT = DOT_REQUESTS * (PROVIDER_PER_M - CACHE_PER_M) / 1e6;
  const SCENARIOS = [{
    method: 'eth_getTransactionReceipt',
    outcome: 'HIT',
    badge: GREEN,
    note: 'served from cache',
    route: 'lake',
    hit: true
  }, {
    method: "eth_getBlockByNumber('latest')",
    outcome: 'FORWARDED',
    badge: GRAY,
    note: 'block tag → your endpoint',
    route: 'endpoint'
  }, {
    method: 'eth_getLogs (hex range)',
    outcome: 'HIT',
    badge: GREEN,
    note: 'served from cache',
    route: 'lake',
    hit: true
  }, {
    method: 'eth_call',
    outcome: 'FORWARDED',
    badge: GRAY,
    note: 'not cacheable → your endpoint',
    route: 'endpoint'
  }, {
    method: 'eth_chainId',
    outcome: 'STATIC',
    badge: BLUE,
    note: 'answered at the edge',
    route: 'static',
    hit: true
  }, {
    method: 'eth_getBlockByHash',
    outcome: 'HIT',
    badge: GREEN,
    note: 'served from cache',
    route: 'lake',
    hit: true
  }];
  const [stats, setStats] = useState({
    hits: 0,
    fwd: 0,
    saved: 0
  });
  const [active, setActive] = useState(SCENARIOS[0]);
  const mLake = useRef(null);
  const mEnd = useRef(null);
  const mStatic = useRef(null);
  const dotLayer = useRef(null);
  useEffect(() => {
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const layer = dotLayer.current;
    if (!layer) return;
    const NS = 'http://www.w3.org/2000/svg';
    const SPEED = 0.34;
    const SPAWN_MS = 420;
    const CHIP_MS = 2200;
    let raf = 0;
    let idx = 0;
    let chipIdx = 0;
    let lastSpawn = -SPAWN_MS;
    let lastChip = 0;
    const flights = [];
    const pathFor = s => s.route === 'lake' ? mLake.current : s.route === 'endpoint' ? mEnd.current : mStatic.current;
    const ease = t => {
      const c = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
      return 0.35 * t + 0.65 * c;
    };
    const mkCircle = (r, op) => {
      const c = document.createElementNS(NS, 'circle');
      c.setAttribute('r', r);
      c.setAttribute('opacity', op);
      layer.appendChild(c);
      return c;
    };
    const spawn = now => {
      const s = SCENARIOS[idx % SCENARIOS.length];
      idx += 1;
      const path = pathFor(s);
      if (!path) return;
      const len = path.getTotalLength();
      flights.push({
        s,
        path,
        len,
        born: now,
        dur: len / (SPEED * (0.8 + 0.4 * Math.random())),
        halo: mkCircle('8', '0.18'),
        core: mkCircle('3.5', '1')
      });
    };
    const retire = (f, i) => {
      setStats(v => ({
        hits: v.hits + (f.s.hit ? 1 : 0),
        fwd: v.fwd + (f.s.hit ? 0 : 1),
        saved: v.saved + (f.s.hit ? SAVE_PER_HIT : 0)
      }));
      f.halo.remove();
      f.core.remove();
      flights.splice(i, 1);
    };
    const tick = now => {
      raf = requestAnimationFrame(tick);
      if (now - lastSpawn >= SPAWN_MS) {
        lastSpawn = now;
        spawn(now);
      }
      if (now - lastChip >= CHIP_MS) {
        lastChip = now;
        chipIdx += 1;
        setActive(SCENARIOS[chipIdx % SCENARIOS.length]);
      }
      for (let i = flights.length - 1; i >= 0; i -= 1) {
        const f = flights[i];
        const el = now - f.born;
        const back = f.s.hit ? GREEN : GOLD;
        let pos;
        let color;
        if (el < f.dur) {
          pos = ease(el / f.dur) * f.len;
          color = REQ;
        } else if (el < f.dur * 2) {
          pos = (1 - ease(el / f.dur - 1)) * f.len;
          color = back;
        } else {
          retire(f, i);
          continue;
        }
        const pt = f.path.getPointAtLength(pos);
        for (const c of [f.halo, f.core]) {
          c.setAttribute('cx', pt.x);
          c.setAttribute('cy', pt.y);
          c.setAttribute('fill', color);
        }
      }
    };
    raf = requestAnimationFrame(tick);
    return () => {
      cancelAnimationFrame(raf);
      for (const f of flights) {
        f.halo.remove();
        f.core.remove();
      }
    };
  }, []);
  const MONO = 'ui-monospace, monospace';
  const fmtReqs = dots => {
    const n = dots * DOT_REQUESTS;
    return n >= 1e6 ? parseFloat((n / 1e6).toFixed(2)) + 'M' : n / 1e3 + 'k';
  };
  const card = (stroke, fill) => ({
    fill,
    stroke,
    strokeWidth: 1.2,
    rx: 12
  });
  const title = {
    fontFamily: MONO,
    fontSize: '17px',
    fontWeight: 700,
    letterSpacing: '0.5px'
  };
  const sub = {
    fontFamily: MONO,
    fontSize: '11px',
    fill: '#6B7280'
  };
  const wire = {
    fill: 'none',
    stroke: 'rgba(100,116,139,0.55)',
    strokeWidth: 1.2,
    strokeDasharray: '5 5'
  };
  return <div style={{
    border: '1px solid rgba(17,24,39,0.12)',
    borderRadius: '14px',
    padding: '16px',
    margin: '16px 0',
    background: '#FCFCFB'
  }}>
      <svg viewBox="0 0 780 320" style={{
    width: '100%',
    height: 'auto',
    display: 'block'
  }}>
        <path d="M 196 175 L 292 175" style={wire} />
        <path d="M 488 152 C 534 132, 546 104, 588 96" style={wire} />
        <path d="M 488 198 C 534 218, 546 246, 588 254" style={wire} />
        <path ref={mLake} d="M 196 175 L 292 175 L 488 152 C 534 132, 546 104, 588 96 L 676 97" fill="none" stroke="none" />
        <path ref={mEnd} d="M 196 175 L 292 175 L 488 198 C 534 218, 546 246, 588 254 L 676 255" fill="none" stroke="none" />
        <path ref={mStatic} d="M 196 175 L 292 175 L 390 175" fill="none" stroke="none" />
        <rect x="24" y="126" width="172" height="98" style={card('rgba(14,116,144,0.5)', 'rgba(14,116,144,0.04)')} />
        <text x="110" y="162" textAnchor="middle" style={{
    ...title,
    fill: CYAN
  }}>YOUR APP</text>
        <text x="110" y="185" textAnchor="middle" style={sub}>wallet · backend</text>
        <text x="110" y="202" textAnchor="middle" style={sub}>indexer</text>
        <rect x="292" y="118" width="196" height="114" style={card('rgba(217,119,6,0.55)', 'rgba(217,119,6,0.05)')} />
        <text x="390" y="158" textAnchor="middle" style={{
    ...title,
    fill: GOLD
  }}>EDGE BOOST</text>
        <text x="390" y="182" textAnchor="middle" style={sub}>cache-first proxy</text>
        <text x="390" y="199" textAnchor="middle" style={sub}>statics answered here</text>
        <rect x="588" y="52" width="176" height="90" style={card('rgba(5,150,105,0.5)', 'rgba(5,150,105,0.05)')} />
        <text x="676" y="90" textAnchor="middle" style={{
    ...title,
    fill: GREEN
  }}>GOLDSKY DATA</text>
        <text x="676" y="113" textAnchor="middle" style={sub}>indexed chain history</text>
        <rect x="588" y="210" width="176" height="90" style={card('rgba(107,114,128,0.45)', 'rgba(107,114,128,0.04)')} />
        <text x="676" y="248" textAnchor="middle" style={{
    ...title,
    fill: GRAY
  }}>YOUR ENDPOINT</text>
        <text x="676" y="271" textAnchor="middle" style={sub}>any RPC provider</text>
        <g ref={dotLayer} />
      </svg>
      <div style={{
    display: 'flex',
    flexWrap: 'wrap',
    gap: '10px',
    alignItems: 'center',
    justifyContent: 'center',
    marginTop: '12px',
    fontFamily: MONO,
    fontSize: '12px'
  }}>
        <span style={{
    padding: '3px 10px',
    borderRadius: '999px',
    border: '1px solid rgba(17,24,39,0.2)',
    color: INK
  }}>{active.method}</span>
        <span style={{
    padding: '3px 10px',
    borderRadius: '999px',
    border: '1px solid ' + active.badge,
    color: active.badge
  }}>{active.outcome}</span>
        <span style={{
    color: '#6B7280'
  }}>{active.note}</span>
      </div>
      <div style={{
    display: 'flex',
    justifyContent: 'center',
    gap: '20px',
    marginTop: '12px',
    fontFamily: MONO,
    fontSize: '14px',
    fontWeight: 600
  }}>
        <span style={{
    color: GRAY
  }}>forwarded {fmtReqs(stats.fwd)}</span>
        <span style={{
    color: GOLD
  }}>hits {fmtReqs(stats.hits)}</span>
        <span style={{
    color: GREEN
  }}>saved ${stats.saved.toFixed(2)}</span>
      </div>
      <div style={{
    display: 'flex',
    justifyContent: 'center',
    flexWrap: 'wrap',
    gap: '14px',
    marginTop: '12px',
    fontFamily: MONO,
    fontSize: '11px',
    color: '#6B7280'
  }}>
        <span>each dot ≈ {DOT_REQUESTS / 1000}k requests</span>
        <span><span style={{
    color: REQ
  }}>●</span> request</span>
        <span><span style={{
    color: GREEN
  }}>●</span> served from cache</span>
        <span><span style={{
    color: GOLD
  }}>●</span> forwarded to your node</span>
        <span>assumes ${PROVIDER_PER_M}/M at your provider — cached requests are free</span>
      </div>
    </div>;
};


export const CurlBuilder = () => {
  const chains = [{
    name: 'ethereum',
    label: 'Ethereum (1)'
  }, {
    name: 'polygon',
    label: 'Polygon (137)'
  }, {
    name: 'base',
    label: 'Base (8453)'
  }, {
    name: 'arbitrum',
    label: 'Arbitrum (42161)'
  }, {
    name: 'optimism',
    label: 'Optimism (10)'
  }];
  const [key, setKey] = useState('');
  const [chain, setChain] = useState('ethereum');
  const [copied, setCopied] = useState(false);
  const url = 'https://edge.goldsky.com/boost/' + chain + '?key=' + (key || 'YOUR_KEY');
  const cmd = 'curl "' + url + '" \\\n  -i -X POST \\\n  -H "Content-Type: application/json" \\\n  -d \'{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x0",false]}\'';
  const copy = () => {
    navigator.clipboard.writeText(cmd).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 1500);
    });
  };
  const inputStyle = {
    padding: '8px 12px',
    borderRadius: '8px',
    border: '1px solid rgba(17,24,39,0.2)',
    background: '#FFFFFF',
    color: '#111827',
    fontSize: '14px'
  };
  return <div style={{
    border: '1px solid rgba(17,24,39,0.12)',
    borderRadius: '12px',
    padding: '16px',
    margin: '16px 0'
  }}>
      <div style={{
    display: 'flex',
    gap: '8px',
    flexWrap: 'wrap',
    marginBottom: '4px'
  }}>
        <input type="text" value={key} onChange={e => setKey(e.target.value)} placeholder="YOUR_KEY" spellCheck={false} style={{
    ...inputStyle,
    flex: '1 1 280px',
    fontFamily: 'ui-monospace, monospace'
  }} />
        <select value={chain} onChange={e => setChain(e.target.value)} style={{
    ...inputStyle,
    flex: '0 0 auto',
    cursor: 'pointer'
  }}>
          {chains.map(c => <option key={c.name} value={c.name}>{c.label}</option>)}
        </select>
      </div>
      <div style={{
    position: 'relative',
    marginTop: '12px'
  }}>
        <pre style={{
    background: '#F6F6F5',
    color: '#1F2937',
    borderRadius: '8px',
    padding: '12px',
    paddingRight: '76px',
    margin: 0,
    fontSize: '13px',
    lineHeight: '1.5',
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-all',
    fontFamily: 'ui-monospace, monospace'
  }}>
          {cmd}
        </pre>
        <button onClick={copy} style={{
    position: 'absolute',
    top: '8px',
    right: '8px',
    padding: '4px 10px',
    borderRadius: '6px',
    border: '1px solid rgba(17,24,39,0.2)',
    background: '#FFFFFF',
    color: '#374151',
    fontSize: '12px',
    cursor: 'pointer'
  }}>
          {copied ? 'Copied!' : 'Copy'}
        </button>
      </div>
      <div style={{
    fontSize: '13px',
    color: '#6B7280',
    marginTop: '8px'
  }}>
        Block <code>0x0</code> is the genesis block, so it exists on every chain — expect <code>x-cache: HIT</code>. The key must belong to an endpoint configured for the chain you pick.
      </div>
    </div>;
};


export const AGENT_PROMPT = `Use Goldsky Edge Boost to cut the RPC bill on an EVM app I already run. Boost sits in front of the RPC provider I already pay for: reads it can answer come from Goldsky's indexed data, everything else forwards to my own endpoint unchanged and free.

Endpoint: https://edge.goldsky.com/boost/{chain}?key={GOLDSKY_API_KEY}
{chain} is a chain name or a chain ID, case-insensitive — /boost/ethereum and /boost/1 are the same endpoint. Cache lookups cover Ethereum (1), Optimism (10), Polygon (137), Base (8453), Arbitrum (42161).

Setup: in https://app.goldsky.com, pick a chain and paste my provider's RPC URL plus any auth headers it needs, then take an API key from the same project. Boost configuration lives on the key, so the key in the URL decides which upstream we forward to. My provider URL is never part of the request.

Drop-in swap — nothing else in the client changes:

import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
  chain: mainnet,
  transport: http('https://edge.goldsky.com/boost/ethereum?key=YOUR_KEY'),
})

Served from cache: eth_getBlockByNumber and eth_getBlockReceipts at a concrete hex height, eth_getBlockByHash, eth_getTransactionByHash, eth_getTransactionReceipt, and eth_getLogs with concrete hex fromBlock/toBlock or a blockHash. eth_chainId and net_version are answered at the edge. Block tags (latest, pending, safe, finalized, earliest) always forward and are never served from cache, because my endpoint is the authority on the head. Every other method — writes, traces, eth_call, eth_getBalance — forwards free.

Billing: free. Cached and forwarded requests both cost nothing from Goldsky; forwarded requests go to my own provider, which I already pay for.

Check who answered from the response headers: x-cache (HIT/MISS), x-edge-source (cache/static/endpoint), x-edge-billable, x-edge-duration-ms, x-edge-region. A JSON-RPC batch is served per item and billed per item, so read x-edge-billable rather than x-cache; give every item a unique id, and keep arrays at 100 items or fewer or the whole array forwards.

Help me point my client at Boost, then measure the hit rate and what it saves. Docs: https://docs.goldsky.com/edge-boost
More context for agents: the Goldsky docs MCP server at https://docs.goldsky.com/mcp, or the skill pack via \`npx skills add goldsky-io/goldsky-agent\`.`;


export const ENCODED_PROMPT = encodeURIComponent(AGENT_PROMPT);


<Note>
  Edge Boost is in private beta. The API may change without notice, and there's no SLA yet. Share feedback with your Goldsky contact.
</Note>

<CardGroup cols={2}>
  <div
    onClickCapture={(e) => {
    if (typeof navigator !== "undefined" && navigator.clipboard) {
      e.preventDefault();
      e.stopPropagation();
      navigator.clipboard.writeText(AGENT_PROMPT);
    }
  }}
  >
    <Card title="Copy prompt" icon="copy" href="#" />
  </div>

  <Card title="Open in Claude" icon="https://mintcdn.com/goldsky-38/ZGItD__kHOxygOLb/images/logos/icon-claude.svg?fit=max&auto=format&n=ZGItD__kHOxygOLb&q=85&s=03d140b3922818ce4710109268b60b7f" href={`https://claude.ai/new?q=${ENCODED_PROMPT}`} width="16" height="16" data-path="images/logos/icon-claude.svg" />

  <Card title="Open in ChatGPT" icon="https://mintcdn.com/goldsky-38/ZGItD__kHOxygOLb/images/logos/icon-chatgpt.svg?fit=max&auto=format&n=ZGItD__kHOxygOLb&q=85&s=34f8245d14a73b48e649eed7a232c718" href={`https://chatgpt.com/?q=${ENCODED_PROMPT}`} width="16" height="16" data-path="images/logos/icon-chatgpt.svg" />

  <Card title="Open in Perplexity" icon="https://mintcdn.com/goldsky-38/ZGItD__kHOxygOLb/images/logos/icon-perplexity.svg?fit=max&auto=format&n=ZGItD__kHOxygOLb&q=85&s=bad0886723c9e2c2bc2bfb3485cfea3d" href={`https://www.perplexity.ai/?q=${ENCODED_PROMPT}`} width="15" height="16" data-path="images/logos/icon-perplexity.svg" />
</CardGroup>

Edge Boost sits in front of the RPC endpoint you already use. Every request is checked against Goldsky's indexed data first. If we have the answer, you get it from us — fast, and without touching your provider. If we don't, we forward the request to your endpoint unchanged and return its response verbatim.

You keep your existing provider, your existing keys, and your existing behavior. You just stop paying them for the reads we can serve.

## How it works

<HowItWorks />

Three things are true of every request:

* **Nothing is interpreted.** A forwarded call reaches your endpoint with its method and params exactly as you sent them, carrying your own headers ([header forwarding](#header-forwarding)), and its result comes back to you untouched.
* **A miss costs you a little latency, never correctness.** If our lookup is slow, errors, or doesn't have the data, we fall back to your endpoint.
* **You pay only for hits.** A forwarded request is free passthrough.

## Quickstart

Edge Boost gives you one address per chain:

```text wrap theme={null}
https://edge.goldsky.com/boost/{chain}?key={your-api-key}
```

`{chain}` is a chain name or a chain ID — `/boost/ethereum` and `/boost/1` are the same endpoint, and names are case-insensitive.

Your own endpoint is not part of the URL. You configure it once against your API key, and Edge Boost forwards to it whenever it can't answer from cache.

<Steps>
  <Step title="Configure your endpoint">
    In the [dashboard](https://app.goldsky.com), pick the chain you want to boost and paste your provider's RPC URL. Add any custom headers your provider needs.

    Then take an API key from the same project. Boost configuration belongs to the key, so an API key *is* a Boost endpoint: the key in the URL decides which upstream we forward to.
  </Step>

  <Step title="Make a request that hits the cache">
    Drop in your key, pick a chain, and copy the generated command:

    <CurlBuilder />

    <Accordion title="Prefer a static example?">
      ```bash wrap theme={null}
      curl "https://edge.goldsky.com/boost/ethereum?key=YOUR_KEY" \
        -i -X POST \
        -H "Content-Type: application/json" \
        -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x0",false]}'
      ```
    </Accordion>

    <Note>
      The first call for a block we haven't served recently may still come back
      `x-cache: MISS` while the reader warms that range from storage — run it
      twice. Repeats answer from cache in single-digit milliseconds.
    </Note>

    The response headers tell you who answered:

    ```text  theme={null}
    x-cache: HIT
    x-edge-source: cache
    x-edge-billable: 1
    x-edge-duration-ms: 14
    x-edge-region: us-west-2
    ```
  </Step>

  <Step title="Compare against a miss">
    Ask for something we don't index — say, a method we don't serve — and the same request forwards to your endpoint:

    ```text  theme={null}
    x-cache: MISS
    x-edge-source: endpoint
    x-edge-billable: 0
    ```
  </Step>
</Steps>

Point an existing client at the URL and nothing else changes:

```typescript  theme={null}
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

const client = createPublicClient({
  chain: mainnet,
  transport: http('https://edge.goldsky.com/boost/ethereum?key=YOUR_KEY')
})
```

<Note>
  Your endpoint URL usually embeds a provider API key. It lives in your endpoint configuration rather than riding along on every request, and Edge Boost never writes it to logs, errors, or metrics. Credentials given as URL userinfo (`https://user:pass@…`) are converted to an `Authorization: Basic` header before the request leaves us. The URL is still stored by Goldsky, so treat the beta accordingly.
</Note>

## Configuring your endpoint

Everything about where a request forwards to lives in the dashboard, on the API key — not in the request. Per chain you configure:

* **A provider URL**, plus any **custom headers** the provider needs for auth.
* Optional **standby upstreams** for the same chain. Only one is active at a time; you switch with an explicit action, so failover is a configuration change you make, not something we do behind your back.
* An optional **forward timeout**, per chain, for how long we wait on your endpoint before giving up.

The chain picker lists the chains you can configure. A chain with no active upstream is rejected — you can only call chains you've set up.

## What gets served from cache

These read methods can be answered from Goldsky's data:

| Method                      | Served from cache when                                                    |
| --------------------------- | ------------------------------------------------------------------------- |
| `eth_getBlockByNumber`      | The block is a concrete hex height                                        |
| `eth_getBlockByHash`        | Always eligible                                                           |
| `eth_getTransactionByHash`  | Always eligible                                                           |
| `eth_getTransactionReceipt` | Always eligible                                                           |
| `eth_getLogs`               | `fromBlock` and `toBlock` are concrete hex heights, or `blockHash` is set |
| `eth_getBlockReceipts`      | The block is a concrete hex height                                        |

`eth_chainId` and `net_version` are answered directly by Edge Boost without touching either backend.

**Every other method forwards to your endpoint.** Writes, traces, state reads like `eth_call` and `eth_getBalance` — all passthrough, all free. Eligible is not the same as guaranteed: a method in this table still forwards if we don't have that specific data yet.

### Block tags always forward

A request for a tagged block — `latest`, `pending`, `safe`, `finalized`, or `earliest` — is always forwarded to your endpoint, never served from cache.

This is deliberate. We resolve a tag against our own view of the chain, and our view of the head can trail yours by a block or two. Serving `latest` from cache would occasionally hand you a stale or soon-reorged block. Your endpoint is the authority on where the head is, so tags go there. Ask for a concrete height and you get the cache.

The same rule applies to `eth_getLogs` with a tag as a range bound.

## Batch requests

Send a JSON-RPC batch and Edge Boost serves it **per item**: the calls we hold are answered from cache, the rest are forwarded to your endpoint in a single onward batch. You are billed only for the items we served. Nothing to enable — post an array to the same address.

For a batch, `x-edge-billable` carries the count of items we served, and `x-cache` is `HIT` only when *every* item was one of ours. A batch where 44 of 45 items came from cache still reports `MISS`, so read the billable count rather than `x-cache` alone.

Two things to know:

* **Give every item a unique `id`.** JSON-RPC doesn't guarantee the order of a batch response, so match results by `id` rather than by position.
* **Arrays over 100 items forward whole.** They reach your endpoint verbatim as one free passthrough call, with no cache lookups. Split larger arrays into 100-item batches to keep the cache hits.

## Supported networks

Cache lookups are available for:

| Network  | Chain ID |
| -------- | -------- |
| Ethereum | 1        |
| Polygon  | 137      |
| Base     | 8453     |
| Arbitrum | 42161    |
| Optimism | 10       |

Use either the name or the chain ID in the path — `/boost/ethereum` and `/boost/1` are equivalent.

## Response headers

Every response carries these:

| Header               | Values                        | Meaning                                                                                                 |
| -------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------- |
| `x-cache`            | `HIT`, `MISS`                 | Whether we served it (`HIT`) or forwarded it (`MISS`)                                                   |
| `x-edge-source`      | `cache`, `static`, `endpoint` | Who actually answered                                                                                   |
| `x-edge-billable`    | integer                       | Billable units — `1`/`0` for a single call, or the count of items served for a [batch](#batch-requests) |
| `x-edge-duration-ms` | integer                       | Wall-clock time we spent serving it                                                                     |
| `x-edge-version`     | build string                  | The Edge Boost build that served it                                                                     |
| `x-edge-region`      | AWS region, e.g. `us-west-2`  | Which edge region served it                                                                             |

`x-cache` is the one to graph. It's the same header CDNs use, so most tooling already understands it.

## Header forwarding

Edge Boost is a proxy in the middle of a chain you built: your client, us, your provider. Headers pass through in both directions. We touch only the ones that are ours to manage.

### What reaches your provider

On a forwarded request (`x-cache: MISS`), your provider receives the headers your client sent, plus any custom headers you configured on the endpoint. Your configured headers are applied last, so they win over anything a caller sends under the same name.

We keep back only the headers we manage:

| Header                                                                     | Why                                                                                                                  |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `X-ERPC-Secret-Token`                                                      | Reserved by the edge. Removed from every request, never forwarded.                                                   |
| `Host`                                                                     | Set from your endpoint URL.                                                                                          |
| `Content-Type`, `Accept`                                                   | Pinned to `application/json`. This hop is JSON-RPC.                                                                  |
| `Content-Length`                                                           | Recomputed for the request we send.                                                                                  |
| `Accept-Encoding`                                                          | We ask your provider for gzip on every forward, whatever your client asked us for, and inflate the answer ourselves. |
| `Content-Encoding`                                                         | We send your provider an uncompressed body.                                                                          |
| `Cookie`, `Proxy-Authorization`                                            | Session credentials. Never relayed to a third party.                                                                 |
| Hop-by-hop headers (`Connection`, `TE`, `Upgrade`, `Transfer-Encoding`, …) | They belong to one connection, per RFC 9110.                                                                         |

Everything else passes through unchanged — trace context (`traceparent`, `tracestate`, `b3`, `baggage`), `User-Agent`, and any custom `x-*` header your provider expects.

### The caller's IP

**`X-Forwarded-For` reaches your provider**, relayed as a whole chain, unmodified, on every forwarded request.

Read the **last** entry. That is the address our load balancer observed:

```text  theme={null}
x-forwarded-for: 10.0.0.1, 198.51.100.7, 203.0.113.9
                                         ^^^^^^^^^^^ the caller we saw
```

Earlier entries were supplied by the caller and can say anything, so treat them as untrusted — the same rule that applies to any `X-Forwarded-For` you receive.

`X-Real-IP`, `Forwarded`, `CF-Connecting-IP`, and `True-Client-IP` are relayed too if your client sends them. We never set them ourselves.

This was briefly a per-endpoint toggle, and it defaulted to off. It is now the default for every endpoint: your provider is the party that rate-limits you per IP, applies your per-IP allowlists, and draws your per-IP analytics. None of that works if the only address it ever sees is ours.

If you need us to withhold the caller's address from your provider instead, [contact us](/getting-support) — it is a per-endpoint setting we can turn off for you.

### Sending auth headers to your provider

Some providers want a bearer token or an API-key header rather than a key in the URL. That works, and it works because **your Goldsky key goes in the query string**:

```text wrap theme={null}
https://edge.goldsky.com/boost/{chain}?key={your-goldsky-key}
```

With our key there, the auth *headers* on the request are unambiguously yours. `Authorization` and `X-API-Key` are forwarded to your provider untouched.

<Warning>
  Always include `?key=`. It is what separates your provider's credential from ours — without it we have no way to tell that an `Authorization` header was meant for someone else.
</Warning>

For a credential that never changes, prefer the endpoint's custom headers in the dashboard. They are stored once, applied to every forwarded request, and your callers never have to hold them.

### What comes back to you

Your provider's response headers ride back to your client: `Cache-Control`, `Retry-After`, `ETag`, and any custom `x-*` it sets.

These are dropped:

| Header                           | Why                                                                                                                                            |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Encoding`               | We inflate your provider's gzip and negotiate compression with your client separately. Relaying it would label a plaintext body as compressed. |
| `Content-Type`, `Content-Length` | Set for the response we build.                                                                                                                 |
| `Set-Cookie`                     | Your provider must not set a cookie on the `edge.goldsky.com` origin.                                                                          |
| `x-cache`, `x-edge-*`            | We stamp these ourselves — see [Response headers](#response-headers).                                                                          |
| `x-goldsky-*`                    | The internal accounting between Edge Boost and the edge.                                                                                       |
| `Access-Control-*`               | CORS for `edge.goldsky.com` is answered at the edge — see below.                                                                               |
| Hop-by-hop headers               | Same as above.                                                                                                                                 |

### CORS

Browser access is controlled by the endpoint's **Allowed domains** list in the dashboard, not by your provider. The edge answers the browser's preflight itself and reflects an allowed origin. Credentialed requests (cookies, HTTP auth) are not allowed, so your provider's `Access-Control-*` headers — which describe their origin, not ours — are dropped rather than merged.

Every response header we send is readable from browser JavaScript (`Access-Control-Expose-Headers: *`), so `x-cache` is available to `fetch` and `XHR`.

### Where our requests come from

We forward from AWS Fargate tasks in three regions: `us-east-1`, `us-west-2`, and `eu-central-1`. Those tasks take public addresses from each region's AWS pool and get new ones whenever a task is replaced — on a deploy, a scale-out, or a restart. **There is no fixed list of egress IPs to allowlist.**

So authenticate us rather than allowlisting us. Keep your provider's credential in the endpoint URL, or configure it as a custom header on the endpoint; either reaches your provider on every forwarded request, from whichever task serves it.

If your provider supports *only* IP allowlisting, talk to us before you build on it. A stable egress address is infrastructure we would have to add, not a setting we can turn on.

## Billing

Boost is **free to use**. Cached requests and forwarded requests both cost nothing from Goldsky — forwarded requests go straight to your endpoint, which you already pay your provider for.

Usage is still measured, and both appear on your invoice as zero-dollar line items: cache hits (`x-cache: HIT`, equivalently `x-edge-billable: 1`) and forwarded requests, separately. That keeps the economics easy to reason about — the hits line is exactly the calls we saved you from paying your provider for.

## Limits

* Every request needs a `key`. Get one from the [dashboard](https://app.goldsky.com) — it carries your Boost configuration
  and your budget. Keyless requests are answered with an
  [x402 payment challenge](/edge-rpc/capabilities/x402) rather than served.
* The chain in the URL must be one your key is configured for. A chain with no active upstream comes back as a JSON-RPC error.
* Your configured endpoint must be reachable over `https` from the public internet. Private, loopback, and link-local addresses are rejected.

## Getting help

Can't find what you're looking for? Reach out to us at [support@goldsky.com](mailto:support@goldsky.com) for help.


## Related topics

- [Why Edge RPC](/edge-rpc/why-edge.md)
- [Benefits](/benefits.md)
- [Create low-code subgraphs](/subgraphs/guides/create-a-low-code-subgraph.md)
- [Edge RPC](/edge-rpc/introduction.md)
- [Edge EVM Sources](/turbo-pipelines/sources/edge-evm.md)
