A modern C++ SDK for interacting with the Coinbase Advanced API, providing both REST and WebSocket functionality for trading, market data, and account management.
- REST API Support: Complete implementation of Coinbase Advanced API endpoints for accounts, orders, products, trades, and more
- WebSocket Support: Real-time market data streaming with level2, ticker, market trades, and user data channels
- Connection lifecycle callbacks for connect/disconnect events
- Per-client sequence number tracking for multiple concurrent connections
- Explicit shutdown control with
stop()method
- Shared Multiplexer: Multiple
WebSocketClientinstances share oneslick::stream_buffer_multiplexer— unified lock-free ring-buffer allocation and a single fan-in queue across all symbols - Cross-process IPC: Producer buffers and the fan-in queue can be placed in named shared memory; a second process can attach and read the same raw JSON stream without an extra network connection
- Comprehensive Order Types: Support for market, limit, stop limit, bracket, and TWAP orders
- Type Safety: Full C++ type definitions for all API responses with JSON serialization/deserialization
- Modern C++: Uses C++20 features, RAII, smart pointers, and modern C++ best practices
- Async/Await Support: Built-in async support using C++ coroutines
- Thread-Safe: Designed for multi-threaded applications with lock-free data structures
The SDK is organized into several key components:
include/coinbase/
├── account.hpp # Account and balance management
├── auth.hpp # Authentication utilities
├── candle.hpp # Candlestick data
├── common.hpp # Common types and enums
├── convert.hpp # Currency conversion (Convert) data models
├── fill.hpp # Fill data
├── futures.hpp # Futures (CFM) data models
├── key_permissions.hpp # API key permissions (Data API) data models
├── logging.hpp # Deprecated logging compatibility wrapper
├── market_data.hpp # Market data structures
├── order.hpp # Order management
├── payment_method.hpp # Payment methods data models
├── perpetuals.hpp # Perpetuals (INTX) data models
├── portfolio.hpp # Portfolios data models
├── position.hpp # Position management
├── price_book.hpp # Price book data
├── product.hpp # Product information
├── rest.hpp # REST client implementation
├── rest_awaitable.hpp # Async REST operations
├── side.hpp # Order side definitions
├── trades.hpp # Trade data
├── utils.hpp # Utility functions
└── websocket.hpp # WebSocket client implementation
- C++20 compatible compiler (GCC 10+, Clang 10+, MSVC 2019+)
- CMake 3.20+
- OpenSSL
- nlohmann/json (JSON library)
- jwt-cpp (JSON Web Token library)
- slick-net (networking library - automatically fetched via CMake)
- vcpkg (optional, dependency management)
mkdir build
cd build
cmake ..
cmake --build . --config Release
cmake --install . --prefix /usr/local # or your preferred install locationThe library is built as a static library for optimal compilation performance in downstream projects.
This repo includes a vcpkg.json manifest. If you use vcpkg, dependencies are installed automatically via the toolchain file. If a vcpkg port for slick-net is available, it will be used; otherwise CMake falls back to FetchContent.
cmake -S . -B build \
-DCMAKE_TOOLCHAIN_FILE=C:/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build build#include <coinbase/rest.hpp>
// Create client
coinbase::CoinbaseRestClient client;
// Get server time
auto timestamp = client.get_server_time();
// List accounts
auto accounts = client.list_accounts();
// Get product information
auto product = client.get_product("BTC-USD");
// Create order
auto response = client.create_order(
"client_order_id_123",
"BTC-USD",
coinbase::Side::BUY,
coinbase::OrderType::LIMIT,
coinbase::TimeInForce::GOOD_UNTIL_CANCELLED,
0.001,
50000.0,
true // post_only
);
// Cancel order
auto cancel_response = client.cancel_orders({"order_id_123"});
// Portfolios
auto portfolios = client.list_portfolios();
auto breakdown = client.get_portfolio_breakdown(portfolios.front().uuid);
// Convert (quote only - commit_convert_trade executes a real conversion)
auto quote = client.create_convert_quote(from_account_uuid, to_account_uuid, 10.0);
// Futures (CFM)
auto balance_summary = client.get_futures_balance_summary();
auto positions = client.list_futures_positions();CoinbaseAwaitableRestClient mirrors every CoinbaseRestClient method as a C++20 coroutine returning asio::awaitable<T>, so the same endpoints (including all of the ones listed in API Endpoints) can be awaited from coroutine-based code:
#include <coinbase/rest_awaitable.hpp>
coinbase::CoinbaseAwaitableRestClient client;
asio::awaitable<void> run() {
auto accounts = co_await client.list_accounts();
auto portfolios = co_await client.list_portfolios();
auto permissions = co_await client.get_api_key_permissions();
// ...
}The SDK provides two callback mechanisms for handling WebSocket data:
-
WebsocketCallbacks: Callbacks are invoked directly on the WebSocket thread. Use this for simple applications or when you want immediate processing. -
UserThreadWebsocketCallbacks: Callbacks are invoked on your own thread. The WebSocket data is queued in a lock-free queue, and you control when to process it by callingprocessData(). Use this for better control over threading and to avoid blocking the WebSocket thread.
#include <coinbase/websocket.hpp>
class MyCallbacks : public coinbase::WebsocketCallbacks {
public:
// Connection lifecycle callbacks
void onMarketDataConnected(coinbase::WebSocketClient* client) override {
// Handle market data connection established
}
void onMarketDataDisconnected(coinbase::WebSocketClient* client) override {
// Handle market data disconnection
}
void onUserDataConnected(coinbase::WebSocketClient* client) override {
// Handle user data connection established
}
void onUserDataDisconnected(coinbase::WebSocketClient* client) override {
// Handle user data disconnection
}
// Data callbacks
void onLevel2Snapshot(coinbase::WebSocketClient* client, uint64_t seq_num,
const coinbase::Level2UpdateBatch& snapshot) override {
// Handle level2 snapshot
}
void onLevel2Updates(coinbase::WebSocketClient* client, uint64_t seq_num,
const coinbase::Level2UpdateBatch& updates) override {
// Handle level2 updates
}
void onMarketTrades(coinbase::WebSocketClient* client, uint64_t seq_num,
const std::vector<coinbase::MarketTrade>& trades) override {
// Handle market trades
}
// Error callbacks
void onMarketDataError(coinbase::WebSocketClient* client, std::string&& err) override {
// Handle market data errors
}
void onUserDataError(coinbase::WebSocketClient* client, std::string&& err) override {
// Handle user data errors
}
// ... other callback methods
};
// Create WebSocket client
MyCallbacks callbacks;
coinbase::WebSocketClient client(&callbacks);
// Subscribe to channels
std::vector<std::string> product_ids = {"BTC-USD", "ETH-USD"};
std::vector<coinbase::WebSocketChannel> channels = {
coinbase::WebSocketChannel::LEVEL2,
coinbase::WebSocketChannel::TICKER
};
client.subscribe(product_ids, channels);
// Explicitly stop WebSocket connections when done
client.stop();#include <coinbase/websocket.hpp>
class MyCallbacks : public coinbase::UserThreadWebsocketCallbacks {
public:
// Same callback signatures as WebsocketCallbacks
void onMarketDataConnected(coinbase::WebSocketClient* client) override {
// Handle market data connection established
}
void onLevel2Snapshot(coinbase::WebSocketClient* client, uint64_t seq_num,
const coinbase::Level2UpdateBatch& snapshot) override {
// Handle level2 snapshot
}
// ... other callback methods
};
// Create callbacks and WebSocket client
MyCallbacks callbacks;
coinbase::WebSocketClient client(&callbacks);
// Subscribe to channels
client.subscribe({"BTC-USD", "ETH-USD"}, {
coinbase::WebSocketChannel::LEVEL2,
coinbase::WebSocketChannel::TICKER
});
// In your main loop or dedicated thread, process queued data
while (running) {
// Process up to 100 queued messages per call
callbacks.processData(100);
// Your other application logic here
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
client.stop();Key Differences:
WebsocketCallbacks: Immediate processing on WebSocket I/O thread. Simple but can block WebSocket operations if callbacks are slow.UserThreadWebsocketCallbacks: Deferred processing on your thread. Better performance and control, but requires callingprocessData()regularly. Uses lock-free queues for efficient data transfer between threads.
Multiple WebSocketClient instances can share one slick::stream_buffer_multiplexer. Each client is assigned a non-overlapping range of producer IDs via producer_offset. A single processData() drains messages from all symbols in arrival order.
slick::stream_buffer_multiplexer mux(1u << 17);
// BTC uses producer IDs 0–3, ETH uses 4–7
coinbase::WebSocketClient ws_btc(&callbacks, mux, MD_URL, "", 0);
coinbase::WebSocketClient ws_eth(&callbacks, mux, MD_URL, "",
coinbase::ProducerType::_PRODUCER_TYPE_COUNT_);
while (running) {
callbacks.processData(200); // drains both symbols
}A slick::stream_buffer_multiplexer keeps its producers registered for its whole
lifetime, so producers registered by a client outlive that client. Destroying a
client and creating a new one with the same producer_offset on the same
multiplexer reuses the already registered producer buffers (and their buffered
data) instead of failing — the buffer-sizing and shared-memory-name arguments of
the new client are ignored for producer IDs that are already registered, and a
warning is logged when the requested sizes differ from the registered ones.
Only a destroyed client's producers can be reused. A producer ID stays owned for
as long as the client that registered it is alive, so constructing a second client
at a producer_offset that overlaps a live client throws std::invalid_argument
rather than silently interleaving two WebSockets into one buffer. Closing a client
with stop() is not enough — ownership is released by the destructor.
Ownership outlives the client object itself when a session is still shutting down.
detach() and close() only start an asynchronous teardown, and slick-net's read
loop keeps shared ownership of the producer buffer until its session actually ends —
so handing the IDs straight back would let a new client write to a buffer the old
session is still writing to.
Nothing waits for that. ~WebSocketClient() releases the IDs whose buffers are
already free and parks the rest; it never blocks. A parked ID is reclaimed by the
next construction (or isProducerOffsetAvailable() call) that finds its buffer free,
so the usual "stop, wait for the disconnect callback, destroy, re-create" sequence
sees no delay at all. Only re-creating at the same producer_offset while the
previous session is still closing is refused, with std::runtime_error:
old_client.reset(); // returns immediately, even mid-teardown
// Non-blocking readiness check — poll it on your own schedule instead of blocking
// a trading thread or catching exceptions in a retry loop.
if (coinbase::WebSocketClient::isProducerOffsetAvailable(mux, offset)) {
new_client = std::make_unique<coinbase::WebSocketClient>(&callbacks, mux, MD_URL, "", offset);
}The two failure modes are deliberately different exception types: std::invalid_argument
for an offset owned by a live client (a bug in the caller — retrying will not help) and
std::runtime_error for a session that is still closing (transient — retry, or poll
isProducerOffsetAvailable()).
Records already published stay in the multiplexer, so a client destroyed before
processData() has drained them leaves control and data records behind.
~WebSocketClient() drops every reference to itself, and those leftover records are
discarded instead of being dispatched to a callback with a dangling
WebSocketClient*. Control records carry client->clientId() — a process-wide
unique ID that is never recycled — so a record from a destroyed client is never
mistaken for one from a new client the allocator happens to place at the same
address.
This bookkeeping is not synchronized: destroy a WebSocketClient on the thread
that calls processData(), or stop calling processData() first.
Passing md_read_buffer_shm_name to the WebSocketClient constructor places the MD_DATA producer buffer in named shared memory. Combining this with a shared-memory fan-in queue lets a second process attach and read the same raw JSON stream — no extra network connection required.
// Process A — producer
slick::stream_buffer_multiplexer mux(1u << 17, "my_mux_queue");
coinbase::WebSocketClient ws(&callbacks, mux, MD_URL, "", 0,
1u << 26, 1u << 16, "my_btc_md_buf");
// Process B — reader (no WebSocket, no coinbase parsing)
slick::stream_buffer_multiplexer reader_mux("my_mux_queue");
reader_mux.add_producer(0 /*MD_DATA producer_id*/, "my_btc_md_buf");
uint64_t cursor = reader_mux.initial_reading_index();
while (running) {
if (auto rec = reader_mux.read(cursor)) {
// rec.data points to raw JSON bytes; rec.length is the frame size
std::cout.write(reinterpret_cast<const char*>(rec.data), rec.length);
}
}See examples/multi_websockets_ws_callbacks.cpp (producer) and examples/multi_websockets_ws_callbacks_reader.cpp (cross-process reader) for a complete two-symbol demo.
The SDK uses slick-net's logging hooks directly. Include <slick/net/logging.hpp>, install a process-wide handler with slick::net::set_log_handler(), and then use the LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, and LOG_FATAL macros from slick-net.
For source compatibility, <coinbase/logging.hpp> remains available as a deprecated forwarding header. New code should include <slick/net/logging.hpp> and use slick::net::* directly.
set_log_handler() takes two callables:
- A
LogHandler:void(slick::net::LogLevel level, const char* format_text, std::format_args args) - An optional
LogLevelGetter:slick::net::LogLevel(), which returns the current minimum enabled level
The level getter is checked before the macro evaluates the format arguments. For example, when the getter returns LogLevel::Info, LOG_DEBUG("{}", expensive()) does not call expensive(). Return LogLevel::Off to disable every log level.
Install the handler before starting REST or WebSocket work, and clear it after shutdown if the objects captured by the handler are about to be destroyed. The handler can run on slick-net worker threads, so captured sinks should be thread-safe or otherwise synchronized.
#include <format>
#include <iostream>
#include <slick/net/logging.hpp>
namespace {
const char* log_level_name(slick::net::LogLevel level) noexcept {
switch (level) {
case slick::net::LogLevel::Trace: return "TRACE";
case slick::net::LogLevel::Debug: return "DEBUG";
case slick::net::LogLevel::Info: return "INFO";
case slick::net::LogLevel::Warn: return "WARN";
case slick::net::LogLevel::Error: return "ERROR";
case slick::net::LogLevel::Fatal: return "FATAL";
case slick::net::LogLevel::Off: return "OFF";
}
return "UNKNOWN";
}
} // namespace
int main() {
slick::net::set_log_handler(
[](slick::net::LogLevel level, const char* format_text, std::format_args args) {
std::cout << '[' << log_level_name(level) << "] ";
try {
std::cout << std::vformat(format_text, args);
} catch (...) {
// Keep logging non-throwing even if formatting fails.
std::cout << format_text;
}
std::cout << '\n';
},
[] {
return slick::net::LogLevel::Info; // Trace/Debug are skipped.
}
);
LOG_INFO("Coinbase SDK initialized for {}", "BTC-USD");
LOG_DEBUG("This message and its arguments are skipped at Info level");
// Create/use Coinbase REST or WebSocket clients here.
slick::net::clear_log_handler();
}If your application already uses slick-logger, bridge slick-net into it instead of formatting messages yourself:
#include <slick/logger.hpp>
#include <slick/net/logging.hpp>
void configure_logging() {
auto& logger = slick::logger::Logger::instance();
logger.clear_sinks();
logger.add_console_sink(true, true);
logger.set_level(slick::logger::LogLevel::L_DEBUG);
logger.init(1024, 16 * 1024 * 1024);
slick::net::set_log_handler(
[&logger](slick::net::LogLevel level, const char* format_text,
std::format_args args) {
logger.log(static_cast<slick::logger::LogLevel>(level),
format_text, args);
},
[] {
return static_cast<slick::net::LogLevel>(logger.get_level());
}
);
}
void shutdown_logging() {
slick::net::clear_log_handler();
}When using the slick-logger bridge, link your application to slick::logger in addition to this SDK:
target_link_libraries(my_app PRIVATE coinbase-advanced-cpp slick::logger)- Accounts: List accounts, get account details
- Products: List products, get product details
- Orders: Create, list, get, modify, and cancel orders
- Fills: List fills
- Fees: Get taker and maker fee rates
- Market Data: Get best bid/ask, price book, market trades, candles
- Time: Get server time
- Portfolios: List portfolios, create/edit/delete a portfolio, get portfolio breakdown, move funds between portfolios
- Convert: Create a convert quote, get a convert trade, commit a convert trade
- Payment Methods: List payment methods, get payment method details
- Data API: Get API key permissions
- Futures (CFM): Get balance summary, list/get positions, schedule/list/cancel sweeps, get/set intraday margin settings, get current margin window
- Perpetuals (INTX): Allocate portfolio, get portfolio summary, list/get positions, get portfolio balances, opt in/out of multi-asset collateral
All REST endpoints are available on both the synchronous CoinbaseRestClient and the coroutine-based CoinbaseAwaitableRestClient (see Async REST Client).
- Level2: Order book updates
- Ticker: Price updates
- Market Trades: Trade updates
- User: User-specific data (orders, positions)
- Candles: Candlestick data
- Status: Product status updates
The SDK supports multiple order types:
- Market Orders: Execute immediately at market price
- Limit Orders: Execute at specified price
- Stop Limit Orders: Execute when stop price is hit, then as limit order
- Bracket Orders: Place both stop loss and take profit orders
- TWAP Orders: Split order execution over time
The SDK handles JWT authentication automatically using the coinbase::generate_coinbase_jwt function. You need to provide your API key and secret.
The examples/ directory contains five self-contained programs. Build them with -DBUILD_COINBASE_ADVANCED_EXAMPLES=ON:
| Executable | Description |
|---|---|
market_data_ws_callbacks |
Single-client market data with WebsocketCallbacks (I/O thread) |
market_data_user_thread_callbacks |
Single-client market data with UserThreadWebsocketCallbacks; also queries REST endpoints |
multi_websockets_ws_callbacks |
BTC-USD + ETH-USD, one WebSocketClient each, shared mux; callbacks on I/O thread; mux and MD buffers in named shared memory |
multi_websockets_user_thread_callbacks |
BTC-USD + ETH-USD, shared mux, single processData() loop; per-symbol order books printed every 5 s |
multi_websockets_ws_callbacks_reader |
Cross-process reader — attaches to the shared memory written by multi_websockets_ws_callbacks and logs raw JSON; start the producer first |
All examples require no API credentials for public market-data channels (TICKER, LEVEL2, MARKET_TRADES).
The SDK includes comprehensive unit tests using Google Test. To run tests:
cd build
cmake -DBUILD_COINBASE_ADVANCED_TESTS=ON ..
cmake --build .
cd tests
./coinbase_advance_testsMIT License - see LICENSE file for details.
Contributions are welcome! Please submit a pull request with your changes.
For issues and questions, please open an issue on the GitHub repository.