From aaaf365a7abbdc13b8b0bef45604d616521e1466 Mon Sep 17 00:00:00 2001 From: Kyle Poole Date: Wed, 21 Oct 2026 16:25:08 -0700 Subject: [PATCH] feat(edge): route Base processed subscriptions to Virginia --- README.md | 4 ++-- src/edge.ts | 1 + src/sdk/edge.test.ts | 26 +++++++++++++++++++++++--- src/sdk/edge.ts | 8 +++++--- src/sdk/edgeSubscription.ts | 7 +++++-- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8e314b1..c2eaede 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,7 @@ const unsubscribe = sdk.subscriptions.onEventsCreated( ## Edge Subscriptions -Pass `{ useEdge: true }` as the third argument to a generated subscription method (or the fourth argument to `sdk.subscribe`) to use a regional edge node. Solana (`1399811149`) routes to `wss://fra.edge.codex.io/graphql`; Robinhood Chain (`4663`) routes to `wss://iad.edge.codex.io/graphql`. Connections are created on demand and reused per endpoint. +Pass `{ useEdge: true }` as the third argument to a generated subscription method (or the fourth argument to `sdk.subscribe`) to use a regional edge node. Solana (`1399811149`) routes to `wss://fra.edge.codex.io/graphql`; Robinhood Chain (`4663`) and Base (`8453`) route to `wss://iad.edge.codex.io/graphql`. Connections are created on demand and reused per endpoint. ```typescript import { Codex, EventCommitmentLevel } from "@codex-data/sdk"; @@ -330,7 +330,7 @@ const unsubscribe = sdk.subscriptions.onTokenEventsCreated( Edge supports `onEventsCreated`, `onTokenEventsCreated`, `onBarsUpdated`, and `onTokenBarsUpdated`. The commitment levels must include `Processed`, and `onBarsUpdated` requires an explicit `quoteToken`. Each subscription must target one supported chain. Subscriptions without `useEdge` use the main endpoint. -The lightweight `withEdge` helper from `@codex-data/sdk/edge` uses the same routing. For a standalone Robinhood connection, pass `url: ROBINHOOD_EDGE_URL` to `createEdgeClient`, importing both from `@codex-data/sdk/edge`. The SDK's `apiEdgeUrl` and the helper's `edgeUrl` override routing for all chains. +The lightweight `withEdge` helper from `@codex-data/sdk/edge` uses the same routing. For a standalone Virginia connection, pass `url: ROBINHOOD_EDGE_URL` or `url: BASE_EDGE_URL` to `createEdgeClient`, importing both from `@codex-data/sdk/edge`. The SDK's `apiEdgeUrl` and the helper's `edgeUrl` override routing for all chains. ## Custom Queries: Request Only the Fields You Need diff --git a/src/edge.ts b/src/edge.ts index 1f816a6..584c66e 100644 --- a/src/edge.ts +++ b/src/edge.ts @@ -2,6 +2,7 @@ export { createClient, createEdgeClient, DEFAULT_EDGE_URL, + BASE_EDGE_URL, ROBINHOOD_EDGE_URL, withEdge, } from "./sdk/edge"; diff --git a/src/sdk/edge.test.ts b/src/sdk/edge.test.ts index de44406..ad1c878 100644 --- a/src/sdk/edge.test.ts +++ b/src/sdk/edge.test.ts @@ -1,6 +1,7 @@ import { type ClientOptions, createClient } from "graphql-ws"; import { + BASE_EDGE_URL, createEdgeClient, DEFAULT_EDGE_URL, ROBINHOOD_EDGE_URL, @@ -276,6 +277,7 @@ it.each([ for (const [networkId, url] of [ [1399811149, DEFAULT_EDGE_URL], [4663, ROBINHOOD_EDGE_URL], + [8453, BASE_EDGE_URL], ]) { const client = createClientWithEdge({ url: "wss://main.example/graphql", @@ -305,7 +307,7 @@ it.each([ { levels: ["Processed", "Finalized"] }, { levels: ["Processed", null] }, { levels: ["Processed", 1] }, - { input: { networkId: 8453 } }, + { input: { networkId: 1 } }, { input: { networkId: "1399811149" } }, { input: { tokenAddress: "token" } }, ])( @@ -330,7 +332,7 @@ it.each([ `query { onEventsCreated(networkId: 1399811149, commitmentLevel: [Processed]) { events { transactionHash } } }`, `subscription { onPriceUpdated(networkId: 1399811149, commitmentLevel: [Processed]) { priceUsd } }`, `subscription { onEventsCreatedByMaker(input: {networkId: 1399811149}, commitmentLevel: [Processed]) { events { transactionHash } } }`, - `subscription { onEventsCreated(id: "pair:8453", commitmentLevel: [Processed]) { events { transactionHash } } }`, + `subscription { onEventsCreated(id: "pair:1", commitmentLevel: [Processed]) { events { transactionHash } } }`, `subscription { onEventsCreated(id: "pair:1399811149", networkId: 1, commitmentLevel: [Processed]) { events { transactionHash } } }`, `subscription { onEventsCreated(id: "pair:4663", networkId: 1399811149, commitmentLevel: [Processed]) { events { transactionHash } } }`, `subscription { onEventsCreated(id: "pair:1399811149", networkId: 4663, commitmentLevel: [Processed]) { events { transactionHash } } }`, @@ -350,7 +352,7 @@ it.each([ expect(transports).toHaveLength(1); }); -it.each([1399811149, 4663])( +it.each([1399811149, 4663, 8453])( "rejects implicit pair quote selection for network %s instead of letting edge relay it through Oregon", (networkId) => { const client = createClientWithEdge({ url: "wss://main.example/graphql" }); @@ -452,3 +454,21 @@ it("validates serialized variables rather than a misleading toJSON value", () => ).toThrow("useEdge requires"); expect(transports).toHaveLength(1); }); + +it("reuses the Virginia transport for separate Base and Robinhood operations", async () => { + const client = createClientWithEdge({ url: "wss://main.example/graphql" }); + for (const networkId of [4663, 8453]) { + client.subscribe( + { + query: events, + variables: { ...variables, input: { ...variables.input, networkId } }, + }, + sink, + { useEdge: true }, + ); + } + expect(transports).toHaveLength(2); + expect(transports[1].options.url).toBe(BASE_EDGE_URL); + expect(transports[1].subscribe).toHaveBeenCalledTimes(2); + await client.dispose(); +}); diff --git a/src/sdk/edge.ts b/src/sdk/edge.ts index 2421080..c46636c 100644 --- a/src/sdk/edge.ts +++ b/src/sdk/edge.ts @@ -11,6 +11,7 @@ import { import { assertEdgeSubscription, + BASE_NETWORK_ID, ROBINHOOD_NETWORK_ID, } from "./edgeSubscription"; @@ -18,15 +19,16 @@ export { createClient }; export const DEFAULT_EDGE_URL = "wss://fra.edge.codex.io/graphql"; export const ROBINHOOD_EDGE_URL = "wss://iad.edge.codex.io/graphql"; +export const BASE_EDGE_URL = ROBINHOOD_EDGE_URL; export function edgeUrlForNetwork(networkId: number): string { - return networkId === ROBINHOOD_NETWORK_ID + return networkId === ROBINHOOD_NETWORK_ID || networkId === BASE_NETWORK_ID ? ROBINHOOD_EDGE_URL : DEFAULT_EDGE_URL; } export interface SubscriptionOptions { - /** Experimental: use edge for supported Solana or Robinhood streams whose commitments include Processed. */ + /** Experimental: use edge for supported Solana, Robinhood or Base streams whose commitments include Processed. */ useEdge?: boolean; } @@ -45,7 +47,7 @@ export interface ClientWithEdge extends Client { } export interface EdgeClientOptions extends Omit { - /** Defaults to the Solana edge endpoint. Use ROBINHOOD_EDGE_URL for Robinhood. */ + /** Defaults to the Solana edge endpoint. Use ROBINHOOD_EDGE_URL or BASE_EDGE_URL for Virginia. */ url?: ClientOptions["url"]; } diff --git a/src/sdk/edgeSubscription.ts b/src/sdk/edgeSubscription.ts index ef06000..f24c7d2 100644 --- a/src/sdk/edgeSubscription.ts +++ b/src/sdk/edgeSubscription.ts @@ -3,6 +3,7 @@ import type { SubscribePayload } from "graphql-ws"; const SOLANA_NETWORK_ID = 1399811149; export const ROBINHOOD_NETWORK_ID = 4663; +export const BASE_NETWORK_ID = 8453; const SUPPORTED_OPERATIONS = new Set([ "onEventsCreated", "onTokenEventsCreated", @@ -14,7 +15,7 @@ const SUPPORTED_OPERATIONS = new Set([ export function assertEdgeSubscription(payload: SubscribePayload): number { function fail(): never { throw new Error( - "useEdge requires a supported Solana or Robinhood subscription with commitmentLevel including Processed", + "useEdge requires a supported Solana, Robinhood or Base subscription with commitmentLevel including Processed", ); } const document = parse(payload.query); @@ -74,7 +75,9 @@ export function assertEdgeSubscription(payload: SubscribePayload): number { collect(args); const networkId = networks[0]; if ( - (networkId !== SOLANA_NETWORK_ID && networkId !== ROBINHOOD_NETWORK_ID) || + (networkId !== SOLANA_NETWORK_ID && + networkId !== ROBINHOOD_NETWORK_ID && + networkId !== BASE_NETWORK_ID) || networks.some((network) => network !== networkId) ) fail();