---
title: "Ordens vs. trades vs. fills na Hyperliquid"
description: "Na Hyperliquid, ordens, trades e fills são três registros diferentes. Veja o payload de cada um no HyperCore, como eles se ligam por oid e tid e como reconciliá-los."
---

# Ordens vs. trades vs. fills na Hyperliquid

<ImageBlock
  src="https://media.alchemy.com/overviews/hyperliquid-orders-trades-fills-hero-16x9-20261020.png"
  alt="Ordens vs. trades vs. fills na Hyperliquid"
  width={1920}
  height={1080}
  priority
/>

Na Hyperliquid, uma ordem é uma instrução de compra ou venda, um trade é um casamento entre duas ordens e um fill é o lado de uma conta nesse casamento. Todo trade produz exatamente dois fills, um por conta. Este guia percorre o payload de cada registro no HyperCore, as chaves que os ligam e os casos de falha que quebram a reconciliação em produção.

Os três registros chegam em streams diferentes e mudam em momentos diferentes, então um código que os trata como a mesma coisa sai de sincronia com a exchange. Tudo abaixo foi verificado com dados ao vivo da mainnet da Hyperliquid, e os dois scripts deste guia rodam hoje contra a API pública da Hyperliquid.

## Qual é a diferença entre uma ordem, um trade e um fill?

O HyperCore é o motor de exchange nativo da Hyperliquid, separado do ambiente de smart contracts HyperEVM. Ele roda um order book totalmente onchain, então toda criação de ordem, todo cancelamento e todo casamento são registrados em um bloco.

Os três registros descrevem essa atividade de ângulos diferentes:

<EmbeddedTable
  table={{
    columns: [
      { key: "record", width: 180, title: "Registro", dataType: "object" },
      {
        key: "what_it_describes",
        width: 180,
        title: "O que descreve",
        dataType: "object",
      },
      {
        key: "identifier",
        width: 180,
        title: "Identificador",
        dataType: "object",
      },
      { key: "scope", width: 180, title: "Escopo", dataType: "object" },
      { key: "stream", width: 180, title: "Stream", dataType: "object" },
    ],
    data: [
      {
        record: { title: "Atualização de ordem", tooltip: "", icon: "" },
        what_it_describes: {
          title:
            "Uma mudança no status de uma ordem (open, filled, canceled, rejected)",
          tooltip: "",
          icon: "",
        },
        identifier: { title: "<code>oid</code>", tooltip: "", icon: "" },
        scope: { title: "Uma conta", tooltip: "", icon: "" },
        stream: {
          title:
            "<code>orderUpdates</code> (nativo), <code>userOrderUpdates</code> (Alchemy)",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        record: { title: "Trade", tooltip: "", icon: "" },
        what_it_describes: {
          title: "Um casamento entre um comprador e um vendedor em um mercado",
          tooltip: "",
          icon: "",
        },
        identifier: { title: "<code>tid</code>", tooltip: "", icon: "" },
        scope: { title: "Um mercado, as duas contas", tooltip: "", icon: "" },
        stream: { title: "<code>trades</code>", tooltip: "", icon: "" },
        id: 1,
      },
      {
        record: { title: "Fill", tooltip: "", icon: "" },
        what_it_describes: {
          title: "O lado de uma conta em um trade, com taxas e PnL",
          tooltip: "",
          icon: "",
        },
        identifier: {
          title: "<code>tid</code> e <code>oid</code>",
          tooltip: "",
          icon: "",
        },
        scope: { title: "Uma conta", tooltip: "", icon: "" },
        stream: { title: "<code>userFills</code>", tooltip: "", icon: "" },
        id: 2,
      },
    ],
  }}
/>

Um trade é a visão do mercado sobre um casamento, por isso lista as duas contas e nenhuma taxa. Um fill é a visão da conta sobre o mesmo casamento, então traz a taxa que essa conta pagou, a posição que ela tinha antes e a ordem de onde veio. As atualizações de ordem descrevem a própria ordem e, como mostra a próxima seção, são mais esparsas do que a maioria dos desenvolvedores espera.

Se você está construindo um bot de trading ou um rastreador de portfólio, os fills são a fonte da verdade sobre o que foi executado, e as atualizações de ordem são a fonte da verdade sobre o que ainda está no order book.

## Como é uma atualização de ordem?

Uma atualização de ordem é emitida quando o status de uma ordem muda. O payload envolve a ordem em um registro de status:

<CodeSnippet
  language="json"
  code={`{
  "status": "open",
  "statusTimestamp": 1780000000000,
  "hash": "0xabc...def",
  "order": {
    "oid": 12345,
    "coin": "BTC",
    "side": "B",
    "limitPx": "100000.0",
    "sz": "0.10",
    "origSz": "0.10",
    "orderType": "Limit",
    "tif": "Gtc",
    "reduceOnly": false,
    "isTrigger": false,
    "triggerPx": "0.0",
    "cloid": null
  }
}`}
/>

Os campos que mais importam para o rastreamento de estado são:

- **`oid`.** O ID da ordem atribuído pela exchange. Os fills fazem referência a ele, então ele é a chave de junção entre uma ordem e suas execuções.
- **`origSz` e `sz`.** O tamanho na criação da ordem e o tamanho ainda em aberto no momento deste registro de status.
- **`status`.** O novo estado. `open`, `filled`, `canceled` e `triggered` são os mais comuns, e existe uma cauda longa de motivos específicos de rejeição e cancelamento, como `badAloPxRejected`, `reduceOnlyCanceled`, `selfTradeCanceled` e `liquidatedCanceled`.
- **`tif`.** Validade da ordem (time in force). `Gtc` fica no book até ser cancelada, `Alo` (add liquidity only) é rejeitada se cruzar o spread, e `Ioc` (immediate or cancel) executa o que conseguir e descarta o resto.
- **`cloid`.** Um ID de ordem opcional fornecido pelo cliente, útil para associar uma ordem à requisição que seu sistema enviou.

A [referência de userOrderUpdates](https://www.alchemy.com/docs/chains/websockets/hypercore/streams/user-order-updates) lista todos os valores de status observados na saída dos nós e afirma que a lista não é exaustiva. Trate uma string de status desconhecida como um estado terminal que você registra em log e investiga, e não como um erro fatal.

Três comportamentos das atualizações de ordem quebram máquinas de estado ingênuas.

Um fill parcial não produz uma atualização de ordem. Quando observamos contas ao vivo, uma ordem no book que foi parcialmente executada emitiu `open`, depois um ou mais fills, e depois nada até chegar a um estado terminal. Quando uma ordem assim é cancelada mais tarde, o registro `canceled` traz o tamanho restante em `sz`. Se você precisa do tamanho restante atual de uma ordem aberta, calcule-o a partir dos fills ou consulte as ordens abertas sob demanda.

Um status `filled` nem sempre significa que a ordem foi totalmente executada. Uma ordem `Ioc` que casa parte do seu tamanho termina com status `filled` e um `sz` diferente de zero, que é o restante não executado que foi descartado. Em uma amostra de 45 segundos de uma conta ativa, 72 das 127 ordens `Ioc` que chegaram a `filled` ainda tinham tamanho sobrando. Verifique `sz` antes de tratar uma ordem como concluída.

O campo `hash` só é preenchido para `open`. Registros cancelados e rejeitados têm hash nulo, então o hash da transação não serve como chave para o histórico de ordens.

## Como é um trade?

O stream `trades` publica todos os casamentos de um mercado. Cada registro é pequeno:

<CodeSnippet
  language="json"
  code={`{
  "coin": "ETH",
  "side": "A",
  "px": "2658.9",
  "sz": "1.0",
  "time": 1790608928951,
  "hash": "0xd8c5bd7a...25ae9e33",
  "tid": 825882878864874,
  "users": ["0x4cf1...e717", "0xe4f5...c0c4"]
}`}
/>

`side` é o lado do taker, em que `A` (ask) significa que o taker vendeu e `B` (bid) significa que o taker comprou. `users` sempre lista o comprador primeiro e o vendedor em segundo, então o taker é `users[0]` quando `side` é `B` e `users[1]` quando `side` é `A`. No exemplo acima o taker vendeu, o que faz de `0xe4f5...c0c4` o taker e de `0x4cf1...e717` o comprador que estava no book.

Uma ordem taker que varre várias ordens no book produz vários trades. Cada um tem seu próprio `tid`, e eles compartilham o mesmo `hash` e `time` porque uma única transação causou todos eles. Isso significa que `hash` identifica a transação, não o trade.

Alguns trades trazem um hash todo em zeros. Em uma amostra de 80 trades recentes em oito mercados, 7 tinham hash zerado. O que rastreamos até a origem era uma fatia de TWAP, que a exchange executa em nome do usuário sem uma nova transação assinada, e a [documentação do endpoint info da Hyperliquid](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint) confirma que fills de TWAP trazem hash zerado. Um código que liga trades a transações pelo hash precisa tratar esse caso.

Use `trades` para dados do mercado como um todo, como tape, volume e gráficos de preço. Para qualquer coisa sobre uma conta específica, assine os fills, porque o registro de trade não tem taxas, ID de ordem nem contexto de posição.

## Como é um fill?

Um fill é o lado de uma conta em um trade. Aqui estão os dois fills do trade de ETH acima, extraídos da mainnet:

<CodeSnippet
  language="json"
  code={`[
  {
    "coin": "ETH", "px": "2658.9", "sz": "1.0", "side": "B",
    "time": 1790608928951, "startPosition": "19.3599", "dir": "Open Long",
    "closedPnl": "0.0", "hash": "0xd8c5bd7a...25ae9e33",
    "oid": 559115773954, "crossed": false, "fee": "-0.079767",
    "tid": 825882878864874, "feeToken": "USDC", "twapId": null,
    "cloid": "0xdd0cd482496f45f79c5ffbe7c27e1511"
  },
  {
    "coin": "ETH", "px": "2658.9", "sz": "1.0", "side": "A",
    "time": 1790608928951, "startPosition": "-776.0", "dir": "Open Short",
    "closedPnl": "0.0", "hash": "0xd8c5bd7a...25ae9e33",
    "oid": 559115788953, "crossed": true, "fee": "1.196505",
    "builderFee": "0.26589",
    "tid": 825882878864874, "feeToken": "USDC", "twapId": null
  }
]`}
/>

Os dois fills compartilham o `tid`, o preço, o tamanho e o horário do trade. Todo o resto é específico da conta:

- **`oid`.** A ordem que produziu este fill. Comprador e vendedor têm cada um a sua.
- **`crossed`.** `true` para o taker, `false` para o maker (a ordem que estava no order book).
- **`fee`.** A taxa que esta conta pagou. Um valor negativo é um rebate de maker, como no fill do comprador acima. `builderFee`, quando presente, é a parte paga a um builder (um frontend ou app que roteou a ordem) e já está incluída em `fee`.
- **`startPosition`, `dir` e `closedPnl`.** O tamanho da posição antes do fill, um rótulo de exibição como `Open Long` ou `Close Short`, e o PnL realizado que este fill fechou. Com eles você reconstrói o histórico de posições só a partir dos fills.
- **`liquidation`.** Nulo para fills normais. Em uma liquidação, indica a conta liquidada, o preço de marcação e se a liquidação passou pelo mercado ou pelo backstop.
- **`twapId`.** A ordem TWAP que produziu o fill, quando houver uma.

Taxas, rebates, PnL realizado e detalhes de liquidação existem apenas nos fills, então qualquer livro-razão ou relatório fiscal precisa ser construído a partir deles.

## Como ordens, trades e fills se ligam entre si?

Duas chaves cobrem quase todas as junções:

<EmbeddedTable
  table={{
    columns: [
      { key: "join", width: 260, title: "Junção", dataType: "object" },
      { key: "key", width: 260, title: "Chave", dataType: "object" },
      {
        key: "cardinality",
        width: 260,
        title: "Cardinalidade",
        dataType: "object",
      },
    ],
    data: [
      {
        join: { title: "Trade para fills", tooltip: "", icon: "" },
        key: {
          title: "<code>tid</code>, com <code>coin</code> e <code>time</code>",
          tooltip: "",
          icon: "",
        },
        cardinality: {
          title: "Um trade, exatamente dois fills",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        join: { title: "Fill para ordem", tooltip: "", icon: "" },
        key: { title: "<code>oid</code>", tooltip: "", icon: "" },
        cardinality: {
          title: "Uma ordem, zero ou mais fills",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
      {
        join: {
          title: "Ordem para requisição do cliente",
          tooltip: "",
          icon: "",
        },
        key: { title: "<code>cloid</code>", tooltip: "", icon: "" },
        cardinality: {
          title: "Um para um, quando você o define",
          tooltip: "",
          icon: "",
        },
        id: 2,
      },
      {
        join: { title: "Fill ou trade para transação", tooltip: "", icon: "" },
        key: { title: "<code>hash</code>", tooltip: "", icon: "" },
        cardinality: {
          title: "Muitos para um, com hash todo zerado para fatias de TWAP",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
    ],
  }}
/>

As duas primeiras linhas são as que você deve usar como base. Verificamos a junção de trade para fill com dados ao vivo puxando um trade de `trades` e depois consultando os fills dos dois endereços no array `users`. Cada endereço tinha exatamente um fill com o `tid` do trade, um com `crossed: true` e outro com `crossed: false`.

O tamanho restante de uma ordem decorre da junção de fill para ordem:

<CodeSnippet
  language="text"
  code={`remaining = origSz - sum(fill.sz for every fill where fill.oid == order.oid)`}
/>

Essa fórmula vale para toda ordem, e é nela que você deve confiar, e não no `sz` da última atualização de ordem que você viu.

## Como reconciliar o estado das ordens a partir dos fills?

O script abaixo assina as atualizações de ordem e os fills de uma conta, acompanha o tamanho executado de cada ordem a partir dos seus fills e confere esse cálculo com o `sz` que a exchange informa quando a ordem chega a um estado terminal. Ele roda em uma versão recente do Node com suporte nativo a WebSocket e não precisa de dependências.

<CodeSnippet
  language="javascript"
  code={`// Tracks one account's orders by joining order-status events with fills.
// Usage: node reconcile.mjs 0xYourAddress
const WS_URL = process.env.HL_WS_URL ?? "wss://api.hyperliquid.xyz/ws";
const user = process.argv[2];
if (!/^0x[0-9a-fA-F]{40}$/.test(user ?? "")) throw new Error("pass a 0x address");
// Sizes arrive as decimal strings. Sum them as scaled integers so partial fills add up exactly.
const SCALE = 10;
const units = (s) => {
  const [i, f = ""] = s.split(".");
  return BigInt(i + f.padEnd(SCALE, "0").slice(0, SCALE));
};
const fmt = (u) => (Number(u) / 10 ** SCALE).toString();
const orders = new Map(); // oid -> { coin, origSz, filled, status, reported }
const seenFills = new Set(); // coin|time|tid, so a fill delivered twice is counted once
const fillKey = (f) => \`\${f.coin}|\${f.time}|\${f.tid}\`;
const order = (oid) => {
  if (!orders.has(oid)) orders.set(oid, { filled: 0n, status: "unknown" });
  return orders.get(oid);
};
// Runs whenever a terminal order learns something new, so either stream can arrive first.
function check(oid, o) {
  if (o.reported === undefined) return; // not terminal yet
  const remaining = o.origSz - o.filled;
  if (remaining === o.reported && o.filled === 0n) return; // plain cancel or reject
  const result =
    remaining === o.reported
      ? "ok"
      : remaining > o.reported
        ? \`waiting for fills (\${fmt(remaining - o.reported)} outstanding)\`
        : \`MISMATCH (fills say \${fmt(remaining)})\`;
  console.log(\`\${oid} \${o.coin} \${o.status}: filled \${fmt(o.filled)} of \${fmt(o.origSz)}, sz=\${fmt(o.reported)} \${result}\`);
}
function onOrderUpdate(u) {
  const o = order(u.order.oid);
  Object.assign(o, { coin: u.order.coin, origSz: units(u.order.origSz), status: u.status });
  if (u.status === "open") return;
  o.reported = units(u.order.sz);
  check(u.order.oid, o);
}
function onFill(f, quiet = false) {
  const key = fillKey(f);
  if (seenFills.has(key)) return;
  seenFills.add(key);
  const o = order(f.oid);
  o.filled += units(f.sz);
  if (!quiet) {
    const role = f.crossed ? "taker" : "maker";
    console.log(\`\${f.oid} \${f.coin} fill tid=\${f.tid} \${f.side} \${f.sz}@\${f.px} \${role} fee=\${f.fee}\${f.liquidation ? " LIQUIDATION" : ""}\`);
  }
  check(f.oid, o);
}
const ws = new WebSocket(WS_URL);
ws.onopen = () => {
  for (const type of ["orderUpdates", "userFills"]) {
    ws.send(JSON.stringify({ method: "subscribe", subscription: { type, user } }));
  }
};
ws.onmessage = ({ data }) => {
  const msg = JSON.parse(data);
  if (msg.channel === "orderUpdates") msg.data.forEach(onOrderUpdate);
  if (msg.channel !== "userFills") return;
  // The first message is a snapshot of recent fills. Count it, so orders already partly
  // filled at startup add up, but don't print it. The dedupe set absorbs it on reconnect.
  const { fills, isSnapshot } = msg.data;
  fills.forEach((f) => onFill(f, isSnapshot));
  if (isSnapshot) console.log(\`loaded \${fills.length} fills from the snapshot\`);
};
ws.onclose = () => console.log("closed; reconnect and backfill from your last stored fill time");`}
/>

Rode-o contra qualquer endereço ativo, por exemplo um tirado do array `users` de um trade recente. A saída de uma execução de 45 segundos contra uma conta movimentada ficou assim:

<CodeSnippet
  language="text"
  code={`loaded 30 fills from the snapshot
580847595557 HYPE fill tid=524471650157202 B 153.74@81.33 taker fee=1.800529
580847595557 HYPE fill tid=1042881790178215 B 60.73@81.33 taker fee=0.71124
580847595557 HYPE filled: filled 214.47 of 221.99, sz=7.52 ok
580847576684 ZEC fill tid=38729269326008 B 0.02@1250.0 maker fee=-0.0005
580847576684 ZEC canceled: filled 0.02 of 2.47, sz=2.45 ok`}
/>

A primeira linha é o snapshot com que a assinatura começa. As três seguintes são uma única ordem `Ioc` que varreu duas ordens no book, produziu dois fills e terminou como `filled` com 7,52 HYPE não executados. As duas últimas são uma ordem `Alo` que ficou no book, teve 0,02 ZEC executados como maker e foi cancelada com o restante ainda em aberto. Nas 115 ordens que chegaram a um estado terminal com fills nessa execução, o tamanho restante derivado dos fills bateu com o `sz` informado pela exchange todas as vezes.

Três detalhes do script importam. Os tamanhos são strings, e somá-los como floats de JavaScript gera desvios de arredondamento em ordens de longa duração com muitos fills parciais, então o script os soma como inteiros escalados. A verificação também roda a partir dos dois handlers. Um status terminal armazena o `sz` informado, e qualquer fill que chegue depois refaz a comparação, então uma ordem cujo último fill chega depois do seu status `filled` mostra `waiting for fills` e depois `ok` em vez de uma falsa divergência. Por fim, os fills são deduplicados antes de alterar o estado, então um fill que chega duas vezes, por exemplo no snapshot após uma reconexão ou de um backfill que se sobrepõe ao stream ao vivo, é contado uma vez.

A [documentação de WebSocket da Hyperliquid](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions) descreve `tid` como um hash de 50 bits e aponta o horário do bloco, `coin` e `tid` juntos como o ID de trade globalmente único, então o script indexa os fills por `coin`, `time` e `tid`. Um trade e seus dois fills compartilham os três. Essa chave é única para uma conta. Se você acompanha muitas contas em um processo, adicione `user` a ela, porque os dois lados de um trade compartilham o restante.

## O que quebra em produção?

A maioria dos bugs de reconciliação vem dos casos a seguir.

- **Entregas sobrepostas.** Quando você assina de novo, o stream nativo `userFills` começa com um snapshot dos fills recentes (`isSnapshot: true`), e um backfill se sobrepõe ao que o stream ao vivo já entregou. Deduplique fills por `user`, `coin`, `time` e `tid`, e atualizações de ordem por `oid` mais `status` mais `statusTimestamp`.
- **Ordenação entre streams.** Atualizações de ordem e fills chegam em canais separados. Não presuma que um fill chega antes do status `filled` da sua ordem, nem o contrário. Indexar o estado por `oid`, deixar qualquer um dos eventos criar o registro e refazer a verificação quando um fill atrasado chega, como o script faz, elimina essa dependência.
- **Desconexões.** Uma assinatura de WebSocket entrega apenas o que acontece enquanto ela está conectada, então qualquer período fora do ar exige um backfill. Armazene o horário do último fill processado e consulte os fills a partir desse ponto, como mostrado abaixo.
- **Fills de fatias de TWAP.** Fatias de TWAP são executadas com `hash` zerado. Na API pública da Hyperliquid, o fill de fatia que rastreamos apareceu em `userTwapSliceFills` e não em `userFillsByTime`, então um backfill que lê apenas `userFillsByTime` pode perdê-los.
- **Status desconhecidos.** Novos motivos de rejeição e cancelamento são adicionados. Lide com valores desconhecidos sem quebrar e trate `triggerCondition` como uma string de exibição. O preço de gatilho legível por máquina é `triggerPx`.
- **Identificadores de mercado.** As strings de coin codificam o tipo de mercado. `BTC` é o perpétuo, mercados spot usam strings `@index` como `@107`, mercados de resultado usam `#N`, e mercados HIP-3 implantados por terceiros usam `dex:name`, conforme descrito na [documentação de mercados, ativos e DEXes](https://www.alchemy.com/docs/data/hypercore/concepts/markets-assets-and-dexes). As strings de coin diferenciam maiúsculas de minúsculas, então compare-as exatamente e nunca as converta para minúsculas.
- **Avançar um checkpoint cedo demais.** Persista o horário do último fill somente depois que o fill tiver sido gravado no armazenamento. Avançar primeiro significa que uma falha entre as duas etapas descarta um fill silenciosamente.

Fazer o backfill de fills de um intervalo de tempo exige um loop curto. `userFillsByTime` retorna os fills do mais antigo para o mais recente, no máximo 2.000 por resposta segundo a [documentação do endpoint info da Hyperliquid](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint), então o loop recomeça no horário mais recente que já viu e deixa a mesma chave de deduplicação absorver a sobreposição:

<CodeSnippet
  language="javascript"
  code={`// Pulls every fill for an account between two times, for use after a stream gap.
// Usage: node backfill.mjs 0xYourAddress <startMs> [endMs]
const INFO_URL = process.env.HL_INFO_URL ?? "https://api.hyperliquid.xyz/info";
const [user, start, end = Date.now()] = process.argv.slice(2);
async function fillsBetween(user, startTime, endTime) {
  const byKey = new Map();
  let from = Number(startTime);
  while (true) {
    const res = await fetch(INFO_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ type: "userFillsByTime", user, startTime: from, endTime: Number(endTime) }),
    });
    const page = await res.json();
    const before = byKey.size;
    for (const f of page) byKey.set(\`\${f.coin}|\${f.time}|\${f.tid}\`, f);
    // Pages are capped. Restart at the newest time seen; the dedupe key absorbs the overlap.
    if (page.length === 0 || byKey.size === before) break;
    from = Math.max(...page.map((f) => f.time));
  }
  return [...byKey.values()].sort((a, b) => a.time - b.time);
}
const fills = await fillsBetween(user, start, end);
console.log(\`\${fills.length} fills\`);`}
/>

Contra a mesma conta movimentada, uma janela de 30 minutos retornou 6.049 fills em quatro páginas. Passe o resultado pelo mesmo handler `onFill` que o stream usa, e o conjunto de deduplicação impede que a sobreposição entre o backfill e o stream ao vivo seja contada duas vezes.

A mesma documentação observa que apenas os 10.000 fills mais recentes estão disponíveis via `userFillsByTime`. No ritmo acima, isso dá menos de uma hora de histórico, então uma interrupção mais longa em uma conta tão ativa fica fora do que o endpoint público consegue retornar. Dimensione seus alertas para que um consumidor desconectado seja notado bem antes do fim dessa janela.

## Como o streaming de HyperCore da Alchemy muda isso?

O [streaming de HyperCore na Alchemy](https://www.alchemy.com/docs/chains/websockets/hypercore) usa o envelope de assinatura nativo, então os payloads acima e a lógica de junção continuam os mesmos. As diferenças estão no que cada quadro informa sobre seu bloco de origem e em quanto uma única assinatura consegue cobrir.

Todo quadro de dados traz metadados de nível superior junto ao payload nativo, incluindo `subscriptionId`, `blockHeight` e `blockTime`. Atualizações de ordem e fills continuam chegando em canais separados, mas cada quadro indica o bloco de onde veio, o que dá a você um ponto de referência comum ao alinhar os dois streams.

As assinaturas também aceitam arrays. As assinaturas nativas recebem um único `user` ou `coin`, enquanto as nossas aceitam `users`, `coins` e `builders`, então uma assinatura pode cobrir um conjunto inteiro de contas. Todo registro de fill e de ordem traz seu `user`, o que torna a chave de deduplicação para várias contas descrita acima o caso padrão em vez de um caso extremo.

Alguns streams existem para que você não precise derivá-los de um feed amplo:

- **`liquidationFills`.** Apenas fills que envolvem uma liquidação, filtrados por mercado.
- **`builderFills` e `builderOrderUpdates`.** Fills e atualizações de ordem atribuídos a um endereço de builder, para apps que roteiam ordens e precisam acompanhar o próprio fluxo e as próprias taxas.
- **`allFills`.** Fills de todas as contas nos mercados selecionados, com o schema completo de fill em vez do registro de trade mais enxuto.

Para rodar o script de reconciliação contra o nosso endpoint, aponte `HL_WS_URL` para a URL de WebSocket que você recebe com o acesso antecipado, autentique-se como descrito na [documentação do ciclo de vida da conexão do HyperCore](https://www.alchemy.com/docs/chains/websockets/hypercore/connection-lifecycle) e assine `userOrderUpdates` em vez de `orderUpdates`. Nossos quadros de atualização de ordem envolvem os registros de status em um array `orders`, então leia-os de `msg.data.orders` em vez de `msg.data`. Para histórico, nossa API REST do HyperCore vai adicionar `userFillsByTime` e `historicalOrders` com paginação baseada em cursor. Os dois estão documentados no [guia de consulta do histórico de trading de carteiras](https://www.alchemy.com/docs/data/hypercore/guides/query-wallet-trading-history), mas ainda não podem ser chamados, então o script de backfill acima usa o endpoint público da Hyperliquid enquanto isso.

## Como obter acesso ao streaming de HyperCore?

O streaming de HyperCore está em acesso antecipado, como parte do [trabalho com HyperCore que anunciamos em junho](https://www.alchemy.com/blog/alchemy-going-all-in-on-hyperliquid). Você faz streaming a partir de nós da Hyperliquid que operamos, então não há `hl-node` para rodar, e se você já constrói na Alchemy, habilitamos o recurso na chave de API que você usa hoje. Comece pela [visão geral da HyperCore Data API](https://www.alchemy.com/docs/data/hypercore/overview) para ver os streams e os métodos REST e depois [solicite acesso antecipado ao HyperCore](https://www.alchemy.com/hypercore-early-access) para receber os detalhes de conexão e trabalhar diretamente com o nosso time.

Se você está escolhendo entre transportes de streaming para o resto do seu stack, nossa comparação de [webhooks, WebSockets e gRPC](https://www.alchemy.com/overviews/webhooks-vs-websockets-vs-grpc) cobre os trade-offs. Para o lado HyperEVM da Hyperliquid, veja nosso [guia de RPC da Hyperliquid](https://www.alchemy.com/overviews/complete-guide-to-hyperliquid-rpc-providers-top-8-solutions-for-2026).
