Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Codex SDK

The TypeScript SDK for the [Codex API](https://www.codex.io) — real-time blockchain data, token prices, OHLCV charts, wallet balances, and holder analytics for 60M+ tokens across 80+ networks including Ethereum, Solana, Base, Arbitrum, BSC, and Polygon.
The TypeScript SDK for the [Codex API](https://www.codex.io) — real-time blockchain data, token prices, OHLCV charts, wallet balances, and holder analytics for 60M+ tokens across 80+ networks including Ethereum, Solana, Base, Arbitrum, BNB, and Polygon.

Codex is a GraphQL API used by teams like Coinbase, TradingView, Uniswap, Rainbow, FOMO, Farcaster, and hundreds of others.

Expand Down Expand Up @@ -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 BNB (`56`) route to `wss://iad.edge.codex.io/graphql` in `us-east-1`. Connections are created on demand and reused per endpoint.

```typescript
import { Codex, EventCommitmentLevel } from "@codex-data/sdk";
Expand All @@ -316,7 +316,7 @@ const sdk = new Codex(process.env.CODEX_API_KEY!);

const unsubscribe = sdk.subscriptions.onTokenEventsCreated(
{
input: { tokenAddress: "0x...", networkId: 4663 },
input: { tokenAddress: "0x...", networkId: 56 },
commitmentLevel: [EventCommitmentLevel.Processed],
},
{
Expand All @@ -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 Robinhood or BNB connection, pass `url: ROBINHOOD_EDGE_URL` or `url: BNB_EDGE_URL` to `createEdgeClient`, importing them 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

Expand Down Expand Up @@ -443,7 +443,7 @@ See the [codegen example](./examples/codegen) for a complete working project.
| Network | ID |
| --------- | ------------ |
| Ethereum | `1` |
| BSC | `56` |
| BNB | `56` |
| Polygon | `137` |
| Arbitrum | `42161` |
| Base | `8453` |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codex-data/sdk",
"version": "2.5.24",
"version": "2.6.0",
"engines": {
"node": ">=17.5.0"
},
Expand Down
5 changes: 3 additions & 2 deletions src/edge.bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ beforeAll(async () => {
}
const result = await build({
stdin: {
contents: `export { createClient, createEdgeClient, DEFAULT_EDGE_URL, ROBINHOOD_EDGE_URL, withEdge } from "@codex-data/sdk/edge";`,
contents: `export { createClient, createEdgeClient, DEFAULT_EDGE_URL, ROBINHOOD_EDGE_URL, BNB_EDGE_URL, withEdge } from "@codex-data/sdk/edge";`,
resolveDir: fixture,
sourcefile: "consumer.js",
},
Expand Down Expand Up @@ -96,7 +96,8 @@ it.each(["import", "require"])(
assert.equal(typeof sdk.createEdgeClient, "function");
assert.equal(typeof sdk.withEdge, "function");
assert.equal(sdk.DEFAULT_EDGE_URL, "wss://fra.edge.codex.io/graphql");
assert.equal(sdk.ROBINHOOD_EDGE_URL, "wss://iad.edge.codex.io/graphql");`,
assert.equal(sdk.ROBINHOOD_EDGE_URL, "wss://iad.edge.codex.io/graphql");
assert.equal(sdk.BNB_EDGE_URL, "wss://iad.edge.codex.io/graphql");`,
],
{ cwd: fixture, stdio: "pipe" },
);
Expand Down
1 change: 1 addition & 0 deletions src/edge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {
BNB_EDGE_URL,
createClient,
createEdgeClient,
DEFAULT_EDGE_URL,
Expand Down
36 changes: 28 additions & 8 deletions src/sdk/edge.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type ClientOptions, createClient } from "graphql-ws";

import {
BNB_EDGE_URL,
createEdgeClient,
DEFAULT_EDGE_URL,
ROBINHOOD_EDGE_URL,
Expand Down Expand Up @@ -35,6 +36,10 @@ const robinhoodVariables = {
...variables,
input: { ...variables.input, networkId: 4663 },
};
const bnbVariables = {
...variables,
input: { ...variables.input, networkId: 56 },
};

beforeEach(() => {
transports.length = 0;
Expand Down Expand Up @@ -63,7 +68,7 @@ it("creates a standalone edge transport without creating a main transport", () =
});

it.each(["sdk", "withEdge"])(
"%s routes each chain to its own reusable edge transport and disposes all transports",
"%s routes each chain to its regional endpoint, reuses transports, and disposes all transports",
async (api) => {
const sdk = api === "sdk" ? new Codex("customer") : undefined;
const client =
Expand All @@ -79,8 +84,11 @@ it.each(["sdk", "withEdge"])(
useEdge,
});
subscribe(robinhoodVariables, false);
subscribe(bnbVariables, false);
subscribe(variables);
subscribe(robinhoodVariables);
subscribe(bnbVariables);
subscribe(bnbVariables);
subscribe(variables);
subscribe(robinhoodVariables);
expect(transports.map(({ options }) => options.url)).toEqual([
Expand All @@ -90,11 +98,16 @@ it.each(["sdk", "withEdge"])(
]);
expect(
transports.map(({ subscribe }) => subscribe.mock.calls.length),
).toEqual([1, 2, 2]);
).toEqual([2, 2, 4]);
expect(transports[2].subscribe).toHaveBeenCalledWith(
{ query: events, variables: robinhoodVariables },
sink,
);
expect(transports[2].options.url).toBe(BNB_EDGE_URL);
expect(transports[2].subscribe).toHaveBeenCalledWith(
{ query: events, variables: bnbVariables },
sink,
);
for (const { options } of transports) {
expect(options.connectionParams).toEqual({ Authorization: "customer" });
}
Expand All @@ -113,15 +126,15 @@ it.each(["sdk", "withEdge"])(
);

it.each(["sdk", "withEdge"])(
"%s honors endpoint overrides for both chains",
"%s honors endpoint overrides for all supported chains",
async (api) => {
const url = "wss://edge.staging.example/graphql";
const sdk =
api === "sdk" ? new Codex("customer", { apiEdgeUrl: url }) : undefined;
const client =
sdk ??
createClientWithEdge({ url: "wss://main.example/graphql", edgeUrl: url });
for (const values of [variables, robinhoodVariables]) {
for (const values of [variables, robinhoodVariables, bnbVariables]) {
if (client instanceof Codex)
client.subscribe(events, values, sink, { useEdge: true });
else
Expand All @@ -131,15 +144,15 @@ it.each(["sdk", "withEdge"])(
}
expect(transports).toHaveLength(2);
expect(transports[1].options.url).toBe(url);
expect(transports[1].subscribe).toHaveBeenCalledTimes(2);
expect(transports[1].subscribe).toHaveBeenCalledTimes(3);
await client.dispose();
},
);

it("routes generated Robinhood subscriptions and recreates both edge clients after config updates", async () => {
it("routes generated Robinhood and BNB subscriptions and recreates both edge clients after config updates", async () => {
const sdk = new Codex("customer");
const subscribe = () => {
for (const { input } of [variables, robinhoodVariables]) {
for (const { input } of [variables, robinhoodVariables, bnbVariables]) {
sdk.subscriptions.onTokenEventsCreated(
{ input, commitmentLevel: [EventCommitmentLevel.Processed] },
sink,
Expand All @@ -152,6 +165,9 @@ it("routes generated Robinhood subscriptions and recreates both edge clients aft
expect(transports[2].subscribe.mock.calls[0][0].variables.input).toEqual(
robinhoodVariables.input,
);
expect(transports[2].subscribe.mock.calls[1][0].variables.input).toEqual(
bnbVariables.input,
);
sdk.updateConfig({ wsHeaders: { Authorization: "refreshed" } });
expect(
transports
Expand Down Expand Up @@ -276,6 +292,7 @@ it.each([
for (const [networkId, url] of [
[1399811149, DEFAULT_EDGE_URL],
[4663, ROBINHOOD_EDGE_URL],
[56, BNB_EDGE_URL],
]) {
const client = createClientWithEdge({
url: "wss://main.example/graphql",
Expand Down Expand Up @@ -337,6 +354,9 @@ it.each([
`subscription { onEventsCreated(id: "pair:46630", commitmentLevel: [Processed]) { events { transactionHash } } }`,
`subscription { onEventsCreated(id: "pair:4663", commitmentLevel: [Confirmed]) { events { transactionHash } } }`,
`subscription { onEventsCreated(id: "pair:4663", commitmentLevel: [Processed, Finalized]) { events { transactionHash } } }`,
`subscription { onEventsCreated(id: "pair:56", networkId: 4663, commitmentLevel: [Processed]) { events { transactionHash } } }`,
`subscription { onEventsCreated(id: "pair:56", commitmentLevel: [Confirmed]) { events { transactionHash } } }`,
`subscription { onEventsCreated(id: "pair:560", commitmentLevel: [Processed]) { events { transactionHash } } }`,
`subscription { onEventsCreated(commitmentLevel: [Processed]) { events { transactionHash } } }`,
`subscription { onTokenBarsUpdated(tokenId: "token", commitmentLevel: [Processed]) { timestamp } }`,
`subscription { a: onEventsCreated(networkId: 1399811149, commitmentLevel: [Processed]) { id } b: onEventsCreated(networkId: 1399811149, commitmentLevel: [Processed]) { id } }`,
Expand All @@ -350,7 +370,7 @@ it.each([
expect(transports).toHaveLength(1);
});

it.each([1399811149, 4663])(
it.each([1399811149, 4663, 56])(
"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" });
Expand Down
7 changes: 5 additions & 2 deletions src/sdk/edge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,25 @@ import {

import {
assertEdgeSubscription,
BNB_NETWORK_ID,
ROBINHOOD_NETWORK_ID,
} from "./edgeSubscription";

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 BNB_EDGE_URL = "wss://iad.edge.codex.io/graphql";

export function edgeUrlForNetwork(networkId: number): string {
if (networkId === BNB_NETWORK_ID) return BNB_EDGE_URL;
return networkId === ROBINHOOD_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 BNB streams whose commitments include Processed. */
useEdge?: boolean;
}

Expand All @@ -45,7 +48,7 @@ export interface ClientWithEdge extends Client {
}

export interface EdgeClientOptions extends Omit<ClientOptions, "url"> {
/** Defaults to the Solana edge endpoint. Use ROBINHOOD_EDGE_URL for Robinhood. */
/** Defaults to the Solana edge endpoint. Use ROBINHOOD_EDGE_URL or BNB_EDGE_URL for those networks. */
url?: ClientOptions["url"];
}

Expand Down
7 changes: 5 additions & 2 deletions src/sdk/edgeSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SubscribePayload } from "graphql-ws";

const SOLANA_NETWORK_ID = 1399811149;
export const ROBINHOOD_NETWORK_ID = 4663;
export const BNB_NETWORK_ID = 56;
const SUPPORTED_OPERATIONS = new Set([
"onEventsCreated",
"onTokenEventsCreated",
Expand All @@ -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 BNB subscription with commitmentLevel including Processed",
);
}
const document = parse(payload.query);
Expand Down Expand Up @@ -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 !== BNB_NETWORK_ID) ||
networks.some((network) => network !== networkId)
)
fail();
Expand Down
Loading