Library / SDK
nntaoli-project/goex avatar
nntaoli-project/goex

goex: one Go interface across OKX and Binance, with a bootstrap you cannot skip

Cryptocurrency Exchange REST API SDK Wrapper Implemented With the golang, Supporting OKX, Binance

1,998 stars645 forksGoMIT

At a glance

What is it?
goex wraps exchange REST endpoints behind a shared Go interface, but the API demands that you call GetExchangeInfo before anything else and build every request around a CurrencyPair that carries the exchange's own precision rules. The tree holds a bitget package the README never claims, and the newest packaged version predates the working tree by over a year.
Who is it for?
Adopt goex if you need one Go interface over more than one exchange and you value a single CurrencyPair type that carries price_precision, qty_precision and min_qty from the exchange itself, because that metadata is the part most hand-rolled clients get wrong.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 6, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The goal is one interface, and the module path says v2

goex is a Go SDK that aims to unify and standardise the interfaces of several cryptocurrency trading platforms. The README lists the supported exchanges as OKX and Binance, and the intent is that you write strategy code once and change a symbol to move between them.

The package layout shows how that is organised. Top-level directories include okx/, binance/ and bitget/, alongside shared pieces: model/ for types, options/ for functional option setters, httpcli/ for the HTTP client wrapper, logger/ for logging, and util/. api.go and main.go sit at the root as the package surface.

The import path carries a major version suffix, so the module is github.com/nntaoli-project/goex/v2 and every example imports it as goexv2. That matters in practice: the module path and the directory name disagree, so an import that looks like a nested package will fail to resolve, and go.mod declares go 1.23.0 as the floor.

One discrepancy is worth flagging before you plan around it. The README's supported list names two exchanges while the tree carries a bitget/ directory. Either the package is work in progress or it is support the documentation has not caught up with, and the reliable answer is to look at the directory rather than at the list.

The bootstrap step: exchange metadata first, always

The single most important thing in the README example is the first call:

golang
_, _, err := goexv2.OKx.Spot.GetExchangeInfo() //must call
if err != nil {
	panic(err)
}

That comment is not decoration. Exchange metadata is what populates the precision and filter rules for every trading pair, and until it is loaded there is nothing to validate an order against. Call it once at start-up, before any pair is constructed.

Around that call sit the global knobs, all reached through the default HTTP client and the package logger:

golang
logger.SetLevel(logger.DEBUG)                             // set log level
//goexv2.DefaultHttpCli.SetProxy("socks5://127.0.0.1:1080") //socks5 proxy
goexv2.DefaultHttpCli.SetTimeout(5)                       // 5 second

These are process-global rather than per-client, which is the main structural limitation of the design: two exchanges with different proxy needs, or two strategies with different timeouts, share one configuration. The HTTP layer itself is built on fasthttp at 1.47.0 rather than the standard library, which is a deliberate choice for connection reuse on high-frequency polling and means you do not get net/http's proxy and transport behaviour for free.

CurrencyPair is where the exchange's rules live

Once metadata is loaded you build a pair, and the pair object is the reason to use this kind of wrapper at all. The README describes the structure as containing various information about the trading pair, such as price_precision and qty_precision and min_qty:

golang
btcUSDTCurrencyPair, err := goexv2.OKx.Spot.NewCurrencyPair(model.BTC, model.USDT)
if err != nil {
	panic(err)
}

Symbols are enumerated constants from model/, so BTC and USDT are identifiers in your source rather than strings that could be mistyped. NewCurrencyPair returns an error, which means an unsupported symbol fails loudly at construction instead of at order time.

The payload of the pair is what makes the abstraction worth the indirection. Two exchanges can disagree on how many decimals a pair accepts and on the smallest order they take, and neither is discoverable from the symbol alone. Once the pair object exists, public and private calls both take it, so the precision rules travel with the request.

The cost is indirection on every call. Public market data looks like this:

golang
log.Println(goexv2.OKx.Spot.GetTicker(btcUSDTCurrencyPair))

which returns multiple values, so the logging call above discards most of what came back. That is fine for a quick check and misleading in production code, where you want to inspect the error.

Private endpoints, functional options and the OrderClientID rule

Private access is a separate object built with functional options:

golang
okxPrvApi := goexv2.OKx.Spot.NewPrvApi(
	options.WithApiKey(""), 
	options.WithApiSecretKey(""), 
	options.WithPassphrase(""))

The three setters are per exchange rather than global, which is better than the HTTP client situation, and the passphrase is OKX-specific. Placing an order takes the pair, an amount, a price, a side and an order type drawn from the model constants:

golang
order, _, err := okxPrvApi.CreateOrder(btcUSDTCurrencyPair, 0.01, 18000, model.Spot_Buy, model.OrderType_Limit)

Exchange-specific fields do not get their own parameters. They go through an optional struct built with method chaining, and the README's second FAQ entry exists solely to demonstrate it:

golang
ord, resp, err := okxPrvApi.CreateOrder(btcUSDTCurrencyPair, 0.01, 23000,
		model.Spot_Buy, model.OrderType_Limit,
		model.OptionParameter{}.OrderClientID("goex123027892")) //client id: goex123027892

That is a real operational requirement, not a nicety. OKX requires a client order id on submitted orders, and a strategy that omits it will have every request rejected by the exchange. The pattern generalises: when you need an exchange-specific field, you will be reading the exchange's docs and hoping this library models it, since the wrapper only exposes what someone implemented.

The README also routes OKX simulated trading to issue 238, which tells you demo trading is not a documented, supported path.

Model constants, request options and the shared client

Three small design choices explain most of the API surface. The first is the model package of constants, where symbols and order types are enumerated identifiers rather than strings, so a typo in a pair name is a compile error instead of an exchange rejection at runtime. The second is the options package of functional setters, which is how credentials reach a private client and how a per-client setting would be added if one were needed. The third is the DefaultHttpCli, a single shared client that every call goes through, configured with SetTimeout and SetProxy before the first request.

That shared client has a consequence worth planning around. Because it is a package-level default rather than an instance you construct, its timeout and proxy apply to every exchange in the process. A program that talks to OKX through a proxy and to a public endpoint directly has no way to express that with this API; you either route everything or you run two processes.

The dependency list reinforces the performance orientation: fasthttp at 1.47.0 for the transport, jsonparser and the google uuid package for parsing and identifiers, spf13/cast for type conversion, and a small go-tools helper. There is no ORM, no database and no configuration framework, which is consistent with a library whose job stops at the HTTP boundary.

For versioning, go.mod requires go 1.23.0, so a project pinned to an older toolchain cannot build it. Continuous integration here runs through a Travis configuration file, which dates the setup and tells you that the build checks are not part of what you inherit.

A first program end to end

Putting the pieces together, the smallest useful program configures logging, sets a timeout, loads exchange metadata, builds a pair, reads public data and places a private order:

golang
package main

import (
	goexv2 "github.com/nntaoli-project/goex/v2"
	"github.com/nntaoli-project/goex/v2/logger"
	"github.com/nntaoli-project/goex/v2/model"
	"github.com/nntaoli-project/goex/v2/options"
	"log"
)

func main() {
	logger.SetLevel(logger.DEBUG)
	goexv2.DefaultHttpCli.SetTimeout(5)

	_, _, err := goexv2.OKx.Spot.GetExchangeInfo()
	if err != nil {
		panic(err)
	}

	btcUSDTCurrencyPair, err := goexv2.OKx.Spot.NewCurrencyPair(model.BTC, model.USDT)
	if err != nil {
		panic(err)
	}

	log.Println(goexv2.OKx.Spot.GetTicker(btcUSDTCurrencyPair))

	okxPrvApi := goexv2.OKx.Spot.NewPrvApi(
		options.WithApiKey(""),
		options.WithApiSecretKey(""),
		options.WithPassphrase(""))

	order, _, err := okxPrvApi.CreateOrder(btcUSDTCurrencyPair, 0.01, 18000, model.Spot_Buy, model.OrderType_Limit)
	log.Println(err)
	log.Println(order)
}

Read it as a sequence of obligations. Configuration is global, metadata must be fetched, the pair must exist before anything that trades it, and the private client must be built before you place an order. Nothing here is inferred at runtime, and nothing fails early if you reorder it, which is the main thing to internalise before going live.

The credential arguments are empty strings in the example, which of course means you should load them from configuration or the environment rather than committing them. The library reads them nowhere else in this example, so where you put them is your decision.

Where goex is the wrong choice

There is no streaming in this repository. The top-level entries are api.go, main.go and directories for the exchanges, the model, options, the HTTP client, the logger and utilities, and nothing in that list is a websocket or subscription package. If your strategy depends on live order book updates or execution events, this library is polling-only, and no amount of configuration changes that. That is the single biggest functional gap for anyone coming from an exchange's own SDK.

The second gap is drift. Exchange REST APIs change: endpoints get deprecated, parameter names move, and error payloads are reworded. A wrapper only reflects the API as it was when someone wrote the adapter, and the README documents no version matrix. A rejection you receive at 03:00 may be the exchange changing rather than your code.

The third is scope transparency. bitget/ exists in the tree while the support list names two exchanges, and the OKX demo trading question is answered with an issue link, which is a hint rather than a promise. Documentation lags code, which is normal, but it means you should read the package directories as the source of truth rather than the feature list.

The fourth is operational shape. Global HTTP configuration, a process-wide logger and per-exchange private clients are fine for a single strategy process and awkward for a multi-tenant or multi-account service where different clients need different proxies and rate limits.

Editorial conclusion

Adopt goex if you need one Go interface over more than one exchange and you value a single CurrencyPair type that carries price_precision, qty_precision and min_qty from the exchange itself, because that metadata is the part most hand-rolled clients get wrong. Do not adopt it if you need streaming market data or simulated trading on OKX today: the repository tree contains no websocket package, and the README routes the OKX demo-trading question to issue 238 rather than documenting support. Verify four things before writing code. Call GetExchangeInfo first, because the README marks it as mandatory with a must call comment, and any call made before exchange metadata is loaded is working against an empty precision map. Confirm your exchange and account type are actually supported, since the README lists OKX and Binance while the tree also contains a bitget package, so check the directory rather than the list. Decide how you will set request options, because OptionParameter is where exchange-specific fields such as OrderClientID go, and OKX rejects orders without one. And pin a version with care: the last tagged build is v2.0.1 from 2025-06-07 while the repository was pushed to on 2026-09-30. Start from the README example, with a proxy line commented out and a five second timeout set before the first call.

Frequently asked questions

Which exchanges does goex support?

The README lists OKX and Binance, and the repository tree also contains a bitget package. Check the directories rather than the list, since the documentation does not mention bitget.

Why must GetExchangeInfo be called before anything else?

The README marks it as must call. It loads the exchange metadata that gives each CurrencyPair its price_precision, qty_precision and min_qty, which is what later requests are validated against.

How do I place an order with the goex OKX API?

Build a private client with options.WithApiKey, WithApiSecretKey and WithPassphrase, then call CreateOrder with the pair, amount, price, a side and an order type from the model package. OKX also requires a client order id, passed through model.OptionParameter{}.OrderClientID.

Does goex support OKX simulated trading?

The README does not document it and points the simulated trading question at issue 238 in the tracker, which means demo trading is not a documented path in the library.

How do I configure timeouts and proxies in goex?

Through the default HTTP client before your first call, using DefaultHttpCli.SetTimeout(5) for a five second timeout and SetProxy for a socks5 proxy, plus logger.SetLevel(logger.DEBUG). These settings are process-global rather than per client.

Official sources

  1. License: MIT
  2. nntaoli-project/goex on GitHub
  3. Project website
  4. README
  5. Releases