Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1f240dc
Add OKX compatibility adapter
MiniTiago Nov 11, 2026
69ba202
Add OKX documentation: compatibility/browser-config
MiniTiago Nov 11, 2026
dd8f5bf
Add OKX documentation: compatibility/native-config
MiniTiago Nov 11, 2026
dc41bfa
Add OKX documentation: sdks/okx/docs
MiniTiago Nov 11, 2026
2fed6fe
Add OKX documentation: sdks/okx/docs/guides
MiniTiago Nov 11, 2026
7a30537
Add OKX documentation: sdks/okx/examples
MiniTiago Nov 11, 2026
2fc4d67
Add OKX documentation: sdks/okx
MiniTiago Nov 11, 2026
4d94464
Add OKX documentation: starters/okx/bun
MiniTiago Nov 11, 2026
a849cce
Add OKX documentation: starters/okx/deno
MiniTiago Nov 11, 2026
1acf550
Add OKX documentation: starters/okx/expo
MiniTiago Nov 11, 2026
abf9ccc
Add OKX documentation: starters/okx/next
MiniTiago Nov 11, 2026
6824a52
Add OKX documentation: starters/okx/next/app/api/price
MiniTiago Nov 11, 2026
7b5cac3
Add OKX documentation: starters/okx/next/app
MiniTiago Nov 11, 2026
0adca53
Add OKX documentation: starters/okx/node
MiniTiago Nov 11, 2026
081ab1d
Add OKX documentation: starters/okx/react
MiniTiago Nov 11, 2026
48b9da4
Add OKX documentation: starters/okx/typescript
MiniTiago Nov 11, 2026
c6b6589
Add OKX documentation: starters/okx/vite
MiniTiago Nov 11, 2026
10f78c8
Add OKX documentation: .github
MiniTiago Nov 11, 2026
920fbc6
Add OKX documentation: releases
MiniTiago Nov 11, 2026
b162137
Add OKX documentation: scripts
MiniTiago Nov 11, 2026
ad6ac84
Add OKX documentation: shared/environments/direct
MiniTiago Nov 11, 2026
369d3f5
Add OKX documentation: starters
MiniTiago Nov 11, 2026
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
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ updates:
- dependency-name: binance
- dependency-name: '@siebly/kraken-api'
- dependency-name: coinbase-api
- dependency-name: okx-api
groups:
sdk-releases:
patterns:
Expand All @@ -22,10 +23,12 @@ updates:
- binance
- '@siebly/kraken-api'
- coinbase-api
- okx-api
cooldown:
exclude:
- '@siebly/hyperliquid-api'
- bybit-api
- binance
- '@siebly/kraken-api'
- coinbase-api
- okx-api
25 changes: 25 additions & 0 deletions compatibility/adapters/okx.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const operations = {
rest: 'RestClient.getTicker()',
websocket: 'WebsocketClient.subscribe()',
};
export function client(sdk) {
return new sdk.RestClient({}, { timeout: 15000 });
}
export async function read(client) {
const tickers = await client.getTicker({ instId: 'BTC-USDT' });
return tickers[0]?.last;
}
export function stream(sdk) {
return new sdk.WebsocketClient();
}
export const event = 'update';
export function value(message) {
return message.arg?.channel === 'tickers' ? message.data?.[0]?.last : undefined;
}
export async function subscribe(ws) {
await Promise.all(ws.subscribe({ channel: 'tickers', instId: 'BTC-USDT' }));
}
export const typeExample = `import { RestClient, WebsocketClient } from 'okx-api';
new RestClient().getTicker({ instId: 'BTC-USDT' });
new WebsocketClient().closeAll();
`;
37 changes: 37 additions & 0 deletions compatibility/browser-config/okx.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { fileURLToPath } from 'node:url';

const browserAgent = '\0okx-browser-agent';
const sdkRoot = fileURLToPath(new URL('./node_modules/okx-api/', import.meta.url));

export default {
// Keep the same SDK transform during development and production builds.
optimizeDeps: {
exclude: ['okx-api'],
include: ['events', 'okx-api > isomorphic-ws'],
},
resolve: {
alias: {
events: fileURLToPath(new URL('./node_modules/events/events.js', import.meta.url)),
},
},
plugins: [{
name: 'okx-browser-transport',
enforce: 'pre',
resolveId(source, importer) {
if (source === 'https' && importer?.startsWith(sdkRoot)) {
return browserAgent;
}
},
load(id) {
if (id === browserAgent) {
// Browsers manage connections themselves. Reject an unsupported option
// rather than presenting a Node Agent as a working browser transport.
return `export class Agent {
constructor() {
throw new Error('keepAlive and HTTPS agents are Node.js-only. Omit keepAlive in a browser.');
}
}`;
}
},
}],
};
19 changes: 19 additions & 0 deletions compatibility/native-config/okx.metro.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = function configureSdkMetro(config, projectRoot) {
const aliases = {
https: require.resolve('https-browserify', { paths: [projectRoot] }),
http: require.resolve('stream-http', { paths: [projectRoot] }),
url: require.resolve('url/', { paths: [projectRoot] }),
};
const previousResolveRequest = config.resolver.resolveRequest;

config.resolver.resolveRequest = (context, moduleName, platform) => {
if (aliases[moduleName]) {
return { type: 'sourceFile', filePath: aliases[moduleName] };
}

const resolveRequest = previousResolveRequest || context.resolveRequest;
return resolveRequest(context, moduleName, platform);
};

return config;
};
2 changes: 1 addition & 1 deletion compatibility/native-config/recipe.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFile } from 'node:fs/promises';

export function nativeMetroConfig(id) {
const filename = { bybit: 'bybit.metro.cjs', binance: 'metro.cjs', kraken: 'kraken.metro.cjs', coinbase: 'coinbase.metro.cjs' }[id];
const filename = { bybit: 'bybit.metro.cjs', binance: 'metro.cjs', kraken: 'kraken.metro.cjs', coinbase: 'coinbase.metro.cjs', okx: 'okx.metro.cjs' }[id];
if (!filename) {
throw new Error(`No Metro polyfill recipe for ${id}.`);
}
Expand Down
3 changes: 2 additions & 1 deletion releases/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"bybit-api": "4.8.5",
"binance": "3.6.12",
"@siebly/kraken-api": "1.1.10",
"coinbase-api": "1.3.1"
"coinbase-api": "1.3.1",
"okx-api": "3.2.9"
}
}
2 changes: 1 addition & 1 deletion scripts/check-public.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function checkPublic(directory) {
const stat = await lstat(filename);
if (stat.isDirectory()) continue;
if (stat.isFile() && path.extname(relative) === '.gz') {
const match = /^(hyperliquid|bybit|binance|kraken|coinbase)\/starters\/(node|typescript|react|bun|deno|vite|next|expo)\.tar\.gz$/.exec(parts.join('/'));
const match = /^(hyperliquid|bybit|binance|kraken|coinbase|okx)\/starters\/(node|typescript|react|bun|deno|vite|next|expo)\.tar\.gz$/.exec(parts.join('/'));
if (!match || stat.size > 131072) throw new Error(`Unexpected public archive: ${relative}`);
const sdk = (await import(`../sdks/${match[1]}/sdk.config.mjs`)).default;
await checkStarter(filename, await environmentFiles(match[1], sdk, match[2]));
Expand Down
2 changes: 1 addition & 1 deletion scripts/context.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url';
import { readFile } from 'node:fs/promises';

export const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
export const ids = ['hyperliquid', 'bybit', 'binance', 'kraken', 'coinbase'];
export const ids = ['hyperliquid', 'bybit', 'binance', 'kraken', 'coinbase', 'okx'];

export async function context(id) {
if (!ids.includes(id)) throw new Error(`Unknown SDK: ${id}. Choose ${ids.join(', ')}.`);
Expand Down
2 changes: 1 addition & 1 deletion scripts/environment-guides.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function guideFields(id, sdk, environment, files) {
app = orderedSteps(names, files);
}

if (id === 'coinbase') {
if (id === 'coinbase' || id === 'okx') {
streamFiles = streamFiles
.replace('it closes the client after the first price or a failure.', 'it streams prices until you press Ctrl+C. Exceptions and reconnections are logged without stopping the client.')
.replace('when you want a live update. Its promise resolves to a price string and its `finally` block closes the client.', 'to stream prices until Ctrl+C. Signal handlers close the client; exceptions and reconnections are logged without stopping it.')
Expand Down
13 changes: 12 additions & 1 deletion scripts/environments.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,15 @@ export async function generateEnvironments(ctx) {
if (!(key in substitutions)) throw new Error(`Missing environment template field: ${key}`);
return substitutions[key];
});
if (ctx.id === 'coinbase') {
if (exchanges[ctx.id].continuous) {
rendered = rendered
.replace('The script prints the first update, closes its WebSocket and exits.', 'The script keeps streaming until Ctrl+C. Exceptions and reconnections are logged; signal handlers close the client.')
.replace('The script receives one update, closes the client in `finally` and exits.', 'The script keeps streaming until Ctrl+C. Exceptions and reconnections are logged; signal handlers close the client.');
rendered = rendered
.replace('A failed request or no update within 30 seconds needs a network check.', 'A failed request or a stream without updates needs a network check.')
.replace('If a request fails or no update arrives within 30 seconds,', 'If a request fails or no stream updates arrive,');
}
if (ctx.id === 'coinbase') {
const note = browser || environment === 'expo'
? 'This is a public-market-data recipe only: the supplied configuration disables the SDK’s Node-only JWT signing helper. Keep the configuration and, for Expo, `coinbase-public-auth.cjs`. Run authenticated calls on your Node.js server. Browser REST access also depends on Coinbase CORS policy.'
: 'This project uses Advanced Trade public market data. Keep credentialed account and trading calls in a server-side application.';
Expand All @@ -255,6 +257,15 @@ export async function generateEnvironments(ctx) {
.replace('See Runtime compatibility for recorded verification of this configuration.', 'Keep the public-auth helper alongside the Metro configuration.')
.replaceAll('[Runtime compatibility](/compatibility) for tested versions', '[Runtime setup](/compatibility) for the other project recipes');
}
if (ctx.id === 'okx') {
const note = browser
? 'Keep the supplied Vite configuration: it resolves EventEmitter and rejects Node-only HTTPS agents. Browsers manage connections, so omit `keepAlive`. REST access depends on OKX CORS policy; use a server route if blocked. Keep credentials on your server.'
: 'These examples use public market data. Keep authenticated account and trading calls on your server. See [Runtime setup](/compatibility) for the scope of each recipe; a successful build does not verify live exchange access.';
rendered = rendered
.replace('## Create the project', note + '\n\n## Create the project')
.replace('See Runtime compatibility for recorded verification of this configuration.', 'See Runtime setup for the scope and limits of this recipe.')
.replaceAll('[Runtime compatibility](/compatibility) for tested versions', '[Runtime setup](/compatibility) for setup details');
}
if (ctx.id === 'kraken') {
const note = browser
? 'The public GET client below omits two SDK request headers that trigger a failing Kraken CORS preflight. This is an example-specific setup for public reads, not general browser REST support. See [Browser REST and CORS](/guides/troubleshooting#browser-rest-and-cors) for the limitation and a server-side alternative.'
Expand Down
7 changes: 7 additions & 0 deletions scripts/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ const app = await Application.bootstrapWithPlugins({
});
// Filter the documentation model before TypeDoc builds its groups and categories.
app.converter.on(Converter.EVENT_RESOLVE_BEGIN, ({ project }) => {
for (const name of sdk.referenceExcludeMembers || []) {
const reflection = project.getChildByName(name.split('.'));
if (!reflection) {
throw new Error(`Missing excluded member: ${name}`);
}
project.removeReflection(reflection);
}
for (const reflection of Object.values(project.reflections)) {
if (reflection.kind === ReflectionKind.Property && sdk.referenceExcludeProperties?.includes(reflection.name)) {
project.removeReflection(reflection);
Expand Down
2 changes: 1 addition & 1 deletion scripts/release-manifest.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFile } from 'node:fs/promises';

export const releaseManifestPath = 'releases/package.json';
export const sdkPackages = ['@siebly/hyperliquid-api', 'bybit-api', 'binance', '@siebly/kraken-api', 'coinbase-api'];
export const sdkPackages = ['@siebly/hyperliquid-api', 'bybit-api', 'binance', '@siebly/kraken-api', 'coinbase-api', 'okx-api'];
export const stableVersion = (version) => typeof version === 'string' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(version);

export function releaseVersions(manifest) {
Expand Down
2 changes: 1 addition & 1 deletion scripts/release-refresh.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { refreshReleases, updatedManifest, validateRefreshBranch, approveAndMerg
import { requireMainDispatch, repository, refreshBranch, createGitHubApi } from './github-automation.mjs';

const base = 'a'.repeat(40), proposal = 'b'.repeat(40), newer = 'c'.repeat(40);
const manifest = (hl = '1.0.1') => ({ name: 'siebly-sdk-release-inputs', private: true, dependencies: { '@siebly/hyperliquid-api': hl, 'bybit-api': '4.8.4', binance: '3.6.11', '@siebly/kraken-api': '1.1.10', 'coinbase-api': '1.3.1' } });
const manifest = (hl = '1.0.1') => ({ name: 'siebly-sdk-release-inputs', private: true, dependencies: { '@siebly/hyperliquid-api': hl, 'bybit-api': '4.8.4', binance: '3.6.11', '@siebly/kraken-api': '1.1.10', 'coinbase-api': '1.3.1', 'okx-api': '3.2.9' } });
const pr = (head = proposal) => ({ state: 'open', number: 7, html_url: `https://github.com/${repository}/pull/7`, user: { login: 'github-actions[bot]', type: 'Bot' }, head: { sha: head, ref: refreshBranch, repo: { full_name: repository } }, base: { ref: 'main', repo: { full_name: repository } } });
const comparison = () => ({ merge_base_commit: { sha: base }, total_commits: 1, files: [{ filename: 'releases/package.json', status: 'modified' }], commits: [{ author: { login: 'github-actions[bot]' } }] });

Expand Down
4 changes: 2 additions & 2 deletions scripts/release-update.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { refreshBranch } from './github-automation.mjs';
import { readReleaseVersions, releaseVersions, validateReleaseUpdate } from './release-manifest.mjs';

const main = 'a'.repeat(40), head = 'b'.repeat(40);
const manifest = () => ({ name: 'siebly-sdk-release-inputs', private: true, dependencies: { '@siebly/hyperliquid-api': '1.0.1', 'bybit-api': '4.8.4', binance: '3.6.11', '@siebly/kraken-api': '1.1.10', 'coinbase-api': '1.3.1' } });
const manifest = () => ({ name: 'siebly-sdk-release-inputs', private: true, dependencies: { '@siebly/hyperliquid-api': '1.0.1', 'bybit-api': '4.8.4', binance: '3.6.11', '@siebly/kraken-api': '1.1.10', 'coinbase-api': '1.3.1', 'okx-api': '3.2.9' } });
function candidate() {
const before = manifest(), after = manifest();
after.dependencies['bybit-api'] = '4.8.5';
Expand All @@ -18,7 +18,7 @@ function candidate() {
}

test('the committed manifest is the exact configured-package build input', async () => {
assert.deepEqual(Object.keys(await readReleaseVersions()).sort(), ['@siebly/hyperliquid-api', '@siebly/kraken-api', 'binance', 'bybit-api', 'coinbase-api']);
assert.deepEqual(Object.keys(await readReleaseVersions()).sort(), ['@siebly/hyperliquid-api', '@siebly/kraken-api', 'binance', 'bybit-api', 'coinbase-api', 'okx-api']);
for (const version of ['latest', '^4.8.5', '4.8.5-beta.1', '04.8.5']) {
const value = manifest(); value.dependencies['bybit-api'] = version;
assert.throws(() => releaseVersions(value));
Expand Down
6 changes: 4 additions & 2 deletions scripts/starters.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function readme(sdk, environment) {
? `Install ${environmentTitles[environment]} first.` : 'Use Node.js 24 and npm.';
const native = environment === 'expo' ? '\nOpen the project in a matching Expo Go installation. Press **i** for an iOS simulator or **a** for an Android emulator.\n' : '';
const controls = ['react', 'vite', 'expo'].includes(environment) ? '\nUse **Stop stream** to close the price subscription.\n' : '';
const optionalStream = streaming ? `\n${sdk.id === 'coinbase' ? 'To stream WebSocket prices until Ctrl+C:' : 'To receive one WebSocket price and close the connection:'}\n\n\`\`\`sh\n${stream}\n\`\`\`\n` : '';
const optionalStream = streaming ? `\n${['coinbase', 'okx'].includes(sdk.id) ? 'To stream WebSocket prices until Ctrl+C:' : 'To receive one WebSocket price and close the connection:'}\n\n\`\`\`sh\n${stream}\n\`\`\`\n` : '';
const commonjs = environment === 'node' ? '\nFor CommonJS instead, run `node price.cjs` or `node stream.cjs`.\n' : '';
const browserNote = sdk.id === 'kraken' && ['react', 'vite'].includes(environment)
? '\nThe public GET client removes the SDK’s `UserAgent` and `locale` headers to avoid Kraken’s failing CORS preflight. Keep this configuration for these public reads only. Run authenticated REST calls on your server, never in a browser bundle. See the guide for the server-side alternative.\n' : '';
Expand All @@ -27,7 +27,9 @@ function readme(sdk, environment) {
+ (['react', 'vite'].includes(environment) ? '\nKeep `sdk.vite.config.js`: it excludes Node-only JWT signing from this public-data application. Browser REST access depends on Coinbase CORS policy.\n' : '')
+ (environment === 'expo' ? '\nRetain `coinbase-public-auth.cjs` and both Metro files: they supply the public-data configuration and exclude Node-only JWT signing.\n' : '')
: '';
return `# ${sdk.name}: ${environmentTitles[environment]}\n\nA runnable public-price example using \`${sdk.packageName}\`. No API key is required.\n\n${prerequisite}\n\n## Run\n\nFrom this folder:\n\n\`\`\`sh\n${install}\n${start}\n\`\`\`\n${native}${controls}${optionalStream}${commonjs}${browserNote}${coinbaseNote}\n[Follow the complete step-by-step guide](https://sieblyio.github.io/docs/${sdk.id}/environments/${environment}.html).\n`;
const okxNote = sdk.id === 'okx'
? '\nKeep authenticated calls on your server. Browser projects must retain the Vite configuration and omit Node-only `keepAlive`; browser REST access depends on OKX CORS policy. See the guide for runtime setup limits.\n' : '';
return `# ${sdk.name}: ${environmentTitles[environment]}\n\nA runnable public-price example using \`${sdk.packageName}\`. No API key is required.\n\n${prerequisite}\n\n## Run\n\nFrom this folder:\n\n\`\`\`sh\n${install}\n${start}\n\`\`\`\n${native}${controls}${optionalStream}${commonjs}${browserNote}${coinbaseNote}${okxNote}\n[Follow the complete step-by-step guide](https://sieblyio.github.io/docs/${sdk.id}/environments/${environment}.html).\n`;
}

export async function starterFiles(id, sdk, environment) {
Expand Down
26 changes: 26 additions & 0 deletions sdks/okx/docs/clients.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: "Choose an OKX SDK client"
description: "Choose RestClient, WebsocketClient or WebsocketAPIClient for OKX requests and streams."
---

# Choose a client

<!-- #region client-list -->
- [RestClient](/api/classes/RestClient): public market data, authenticated account reads and order commands over REST.
- [WebsocketClient](/api/classes/WebsocketClient): public and private subscriptions, connection lifecycle events and low-level WebSocket API requests.
- [WebsocketAPIClient](/api/classes/WebsocketAPIClient): promise-based order commands over WebSockets.
<!-- #endregion client-list -->

## REST and subscriptions work together

Use one long-lived REST client for your account and region. Start with [`getTicker()`](/api/classes/RestClient#getticker), then configure credentials for [`getBalance()`](/api/classes/RestClient#getbalance). The same client covers Spot and derivatives: select the instrument and trading mode in each request rather than choosing a separate futures class.

Use `WebsocketClient` for ongoing prices and account state. It selects the public, private or business connection from the channel, signs private logins and restores subscriptions after reconnects. Your application still owns recovery of missed state.

## Awaitable WebSocket commands

<!--@include: @/.vitepress/shared/clients/awaitable-websocket.md-->

`WebsocketAPIClient` exposes methods such as `submitNewOrder()`, `amendOrder()` and `cancelOrder()`. It is not a replacement for the private orders subscription: command responses acknowledge requests, while order events describe subsequent execution.

New applications should use `WebsocketClient`, not the exported legacy client. See [WebSocket API requests](/guides/websocket-api) for the request and response differences.
Loading
Loading