From 94b48e652091a01e81f05928530d526e6cebf956 Mon Sep 17 00:00:00 2001 From: matheus-alcuri-foxbit Date: Fri, 7 Aug 2026 14:05:10 -0300 Subject: [PATCH] feat(foxbit): add X-FB-CLIENT identification headers Send X-FB-CLIENT: ccxt and X-FB-CLIENT-VERSION: on every foxbit REST request so the exchange can identify and measure ccxt-originated traffic. Since the library version constant was not reachable from transpiled exchange code, this adds a getCcxtVersion() base helper implemented above the transpile marker (hand-written per language: TS/JS, Python, PHP, C#, Go, Java), and a testFoxbit broker-id test asserting both headers via last_request_headers. Co-Authored-By: Claude Fable 5 --- cs/ccxt/base/Exchange.Misc.cs | 6 +++++ go/v4/exchange_interface.go | 1 + go/v4/exchange_metadata.go | 5 +++++ .../java/io/github/ccxt/BaseExchange.java | 5 +++++ php/Exchange.php | 5 +++++ python/ccxt/base/exchange.py | 4 ++++ ts/src/base/Exchange.ts | 11 ++++++++++ ts/src/foxbit.ts | 2 ++ ts/src/test/tests.ts | 22 ++++++++++++++++++- 9 files changed, 60 insertions(+), 1 deletion(-) diff --git a/cs/ccxt/base/Exchange.Misc.cs b/cs/ccxt/base/Exchange.Misc.cs index de6ef606709e9..8c34dc4187b1e 100644 --- a/cs/ccxt/base/Exchange.Misc.cs +++ b/cs/ccxt/base/Exchange.Misc.cs @@ -62,4 +62,10 @@ public List> getFetchCache() // return null; // stub to implement // } + // returns the version of the ccxt library, e.g. "4.5.54" + public virtual object getCcxtVersion() + { + return ccxtVersion; + } + } diff --git a/go/v4/exchange_interface.go b/go/v4/exchange_interface.go index 08744fe1bf6d2..e4f48570de4b7 100644 --- a/go/v4/exchange_interface.go +++ b/go/v4/exchange_interface.go @@ -228,6 +228,7 @@ type ICoreExchange interface { Init(params map[string]any) FetchDeposits(optionalArgs ...any) <-chan any Milliseconds() int64 + GetCcxtVersion() string ParseNumber(v any, a ...any) any OmitZero(v any) any FetchOHLCV(symbol any, optionalArgs ...any) <-chan any diff --git a/go/v4/exchange_metadata.go b/go/v4/exchange_metadata.go index c5b7cc9d80f23..9ea258fac9563 100644 --- a/go/v4/exchange_metadata.go +++ b/go/v4/exchange_metadata.go @@ -2,4 +2,9 @@ package ccxt var Version string = "4.5.71" +// GetCcxtVersion returns the version of the ccxt library, e.g. "4.5.54" +func (this *Exchange) GetCcxtVersion() string { + return Version +} + var Exchanges []string = []string{"aftermath", "alpaca", "apex", "ascendex", "aster", "backpack", "bequant", "bigone", "binance", "binancecoinm", "binanceus", "binanceusdm", "bingx", "bit2c", "bitbank", "bitbns", "bitfinex", "bitflyer", "bitget", "bithumb", "bitmart", "bitmex", "bitopro", "bitrue", "bitso", "bitstamp", "bitteam", "bittrade", "bitvavo", "blockchaincom", "blofin", "btcbox", "btcmarkets", "btcturk", "bullish", "bybit", "bybiteu", "bydfi", "cex", "coinbase", "coinbaseexchange", "coinbaseinternational", "coincheck", "coinex", "coinmate", "coinmetro", "coinone", "coinsph", "coinspot", "cryptocom", "cryptomus", "deepcoin", "delta", "deribit", "derive", "digifinex", "dydx", "exmo", "extended", "fmfwio", "foxbit", "gate", "gemini", "grvt", "hashkey", "hibachi", "hitbtc", "hollaex", "htx", "hyperliquid", "independentreserve", "indodax", "kraken", "krakenfutures", "kucoin", "kucoinfutures", "latoken", "lbank", "lighter", "luno", "mercado", "mexc", "modetrade", "myokx", "ndax", "novadax", "okx", "okxus", "onetrading", "p2b", "pacifica", "paradex", "paymium", "phemex", "poloniex", "tokocrypto", "toobit", "upbit", "weex", "whitebit", "woo", "woofipro", "xt", "zaif", "zebpay"} diff --git a/java/lib/src/main/java/io/github/ccxt/BaseExchange.java b/java/lib/src/main/java/io/github/ccxt/BaseExchange.java index 51798df386fe6..ccd0034ec9510 100644 --- a/java/lib/src/main/java/io/github/ccxt/BaseExchange.java +++ b/java/lib/src/main/java/io/github/ccxt/BaseExchange.java @@ -1159,6 +1159,11 @@ public String uuid() { return Strings.uuid(); } + // returns the version of the ccxt library, e.g. "4.5.71" + public String getCcxtVersion() { + return Version.VERSION; + } + // ======================= // Time // ======================= diff --git a/php/Exchange.php b/php/Exchange.php index fc789309428b6..d82bfdf8d03ef 100644 --- a/php/Exchange.php +++ b/php/Exchange.php @@ -2542,6 +2542,11 @@ public static function has_web3() { return true; } + // returns the version of the ccxt library, e.g. "4.5.54" + public function get_ccxt_version() { + return static::VERSION; + } + public function check_required_dependencies() { if (!static::has_web3()) { throw new ExchangeError($this->id . ' requires web3 dependencies'); diff --git a/python/ccxt/base/exchange.py b/python/ccxt/base/exchange.py index 96d8bd17a0477..4598b9e17bd98 100644 --- a/python/ccxt/base/exchange.py +++ b/python/ccxt/base/exchange.py @@ -1837,6 +1837,10 @@ def decode(string): def to_array(value): return list(value.values()) if type(value) is dict else value + def get_ccxt_version(self): + """returns the version of the ccxt library, e.g. "4.5.54" """ + return __version__ + def precision_from_string(self, str): # support string formats like '1e-4' if 'e' in str or 'E' in str: diff --git a/ts/src/base/Exchange.ts b/ts/src/base/Exchange.ts index 1402b2144981c..64a5e0b5c4f1f 100644 --- a/ts/src/base/Exchange.ts +++ b/ts/src/base/Exchange.ts @@ -674,6 +674,17 @@ export class BaseExchange { return encodeURIComponent (...args); } + /** + * @method + * @name Exchange#getCcxtVersion + * @description returns the version of the ccxt library, e.g. "4.5.54", or "unknown" when the version constant is not initialized (e.g. when an exchange module is imported directly, bypassing the ccxt entry point) + * @returns {string} the semver version of the ccxt library, or "unknown" when unavailable + */ + getCcxtVersion (): string { + const staticVersion = (Exchange as any).ccxtVersion; + return (staticVersion === undefined) ? 'unknown' : staticVersion; + } + throttle (cost: Num = undefined) { return this.throttler.throttle (cost); } diff --git a/ts/src/foxbit.ts b/ts/src/foxbit.ts index 9129474d10286..d60712be013af 100644 --- a/ts/src/foxbit.ts +++ b/ts/src/foxbit.ts @@ -2075,6 +2075,8 @@ export default class foxbit extends Exchange { } headers = { 'Content-Type': 'application/json', + 'X-FB-CLIENT': 'ccxt', + 'X-FB-CLIENT-VERSION': this.getCcxtVersion (), }; if (urlPath === 'private') { this.checkRequiredCredentials (); diff --git a/ts/src/test/tests.ts b/ts/src/test/tests.ts index dd8ff1e539f28..f198ec6701856 100644 --- a/ts/src/test/tests.ts +++ b/ts/src/test/tests.ts @@ -2142,7 +2142,8 @@ class testMainClass { this.testModeTrade (), this.testBackpack (), this.testToobit (), - this.testWeex () + this.testWeex (), + this.testFoxbit () ]; await Promise.all (promises); const successMessage = '[' + this.lang + '][TEST_SUCCESS] brokerId tests passed.'; @@ -2807,6 +2808,25 @@ class testMainClass { clientOrderId = request['newClientOrderId']; assert (clientOrderId.startsWith (id), 'weex - newClientOrderId: ' + clientOrderId + ' for swap order does not start with id: ' + id); } + + async testFoxbit () { + const exchange = this.initOfflineExchange ('foxbit'); + let reqHeaders: Dict = {}; + const id = 'ccxt'; + try { + await exchange.createOrder ('BTC/BRL', 'limit', 'buy', 1, 20000); + } catch (e) { + // we expect an error here, we're only interested in the headers + reqHeaders = exchange.last_request_headers ? exchange.last_request_headers : {}; + } + assert (reqHeaders['X-FB-CLIENT'] === id, 'foxbit - id: ' + id + ' not in headers.'); + const version = exchange.getCcxtVersion (); + assert (reqHeaders['X-FB-CLIENT-VERSION'] === version, 'foxbit - version: ' + version + ' not in headers.'); + if (!isSync ()) { + await close (exchange); + } + return true; + } } export default testMainClass;