A unified trading API with more than 100 crypto exchanges and prediction markets in JavaScript / TypeScript / Python / C# / PHP / Go / Java

RAW Rules

CLAUDE.md

# CCXT β€” Repository Guide for Contributors and AI Agents

CCXT is a unified cryptocurrency trading library with one source of truth (TypeScript) **transpiled** to JavaScript, Python, PHP, C#, Go and Java. The most common contributor mistake β€” especially by AI agents β€” is editing a generated file or shipping code without tests in all languages.

Authoritative rules: `CONTRIBUTING.md` (transpiler conventions), `wiki/Manual.md` (unified API spec), `wiki/Requirements.md` (new-exchange checklist).

---

## 1. Architecture

### REST vs Pro (WebSocket)

| | REST | Pro (WebSocket) |
|---|---|---|
| Source dir | `ts/src/<exchange>.ts` | `ts/src/pro/<exchange>.ts` |
| Class | `class <exchange> extends Exchange` | `class <exchange> extends <exchange>Rest` |
| Method prefix | `fetch*` (one-shot HTTP) | `watch*` / `unWatch*` (subscribe) |
| Base | `ts/src/base/Exchange.ts` | adds `ts/src/base/ws/{Client,Cache,OrderBook,Future,WsClient}.ts` |
| Transport (JS) | `node-fetch` / browser `fetch` | `ws` npm package |

The pro class imports the REST class as `<exchange>Rest` and extends it. `describe()` deep-extends the REST `describe()` to add `has.watch*` flags and `urls.api.ws`. WS subscriptions go through `client(url)` (one `Client` per URL) and resolve via `Future`s; live updates land in the right `Cache` (`ArrayCacheBySymbolById`, `ArrayCacheByTimestamp`, etc.). Read `ts/src/base/ws/Client.ts` and `ts/src/pro/binance.ts` before editing pro code.

### Per-language model

| Language | Sync | Async | WS transport | Notes |
|---|---|---|---|---|
| TS / JS | n/a | native `async/await` | `ws` / browser `WebSocket` | one ESM module |
| Python | `ccxt.<ex>` | `ccxt.async_support.<ex>` | `aiohttp` + `asyncio` | **sync auto-generated from async** |
| PHP | `ccxt\<ex>` | `ccxt\async\<ex>` | ReactPHP promises | **async auto-generated from sync** |
| C# | n/a | native `Task`/`async` | `System.Net.WebSockets` | PascalCase wrappers in `cs/ccxt/wrappers/` |
| Go | `(value, error)` returns | none | `gorilla/websocket` | three files per exchange: `<ex>.go`, `<ex>_api.go`, `<ex>_wrapper.go` |

### Two transpilers

1. **Regex** β€” `build/transpile.ts`, `build/transpileWS.ts` β†’ Python and PHP. Brittle; depends on TS formatting.
2. **AST** β€” `ast-transpiler` npm package, used by `build/csharpTranspiler.ts` and `build/goTranspiler.ts` β†’ C# and Go. More forgiving.

Code in `ts/src/` must satisfy **both**.

---

## 2. Source of truth β€” edit ONLY these

- `ts/src/*.ts` β€” REST exchange implementations
- `ts/src/pro/*.ts` β€” WS exchange implementations
- `ts/src/base/Exchange.ts` β€” base class (partly transpiled; see Β§4)
- `ts/src/base/ws/{Client,Cache,OrderBook,OrderBookSide,Future,WsClient}.ts` β€” pro base
- `ts/src/base/{errors,errorHierarchy,Precise,types,functions}.ts`
- `ts/src/test/Exchange/test.*.ts` β€” REST unified-method tests
- `ts/src/test/Exchange/base/test.<structure>.ts` β€” shared validators (orderBook, ticker, trade, …)
- `ts/src/pro/test/Exchange/test.watch*.ts` β€” WS unified-method tests
- `ts/src/test/base/**`, `ts/src/pro/test/base/**` β€” base unit tests
- Static fixtures: `ts/src/test/static/{request,response}/<exchange>.json`

---

## 3. NEVER commit edits to generated files

These are overwritten by the build:

- `js/**` (tsc output)
- `python/ccxt/*.py`, `python/ccxt/async_support/*.py` (per-exchange)
- `python/ccxt/test/tests_sync.py` and any transpiled test
- `php/*.php`, `php/async/*.php`, `php/pro/*.php` (per-exchange)
- `cs/ccxt/exchanges/**`, `cs/ccxt/ws/**`, `cs/ccxt/api/**`, `cs/ccxt/wrappers/**`
- `cs/ccxt/base/Exchange.BaseMethods.cs` (generated portion only)
- `go/v4/*.go` and `go/v4/pro/*.go` (every per-exchange Go file)
- `ts/src/abstract/*.ts` (emitted from each exchange's `api` block)
- `dist/**`, `build/ccxt.wiki`, `index.d.cts`
- `README.md` exchange tables, `wiki/Exchange-Markets*.md`
- `python/{README.md,LICENSE.txt,keys.json,package.json}` (copies)
- `package.json` version bumps (use `npm run vss`)

Generated files start with `// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN`. If you see it, find the TS source.

The per-language base type files are generated from `ts/src/base/types.ts` by `npm run transpile-types`: `python/ccxt/base/types.py`, `cs/ccxt/base/{Exchange.Types.cs,PredictionTypes.cs}`, `go/v4/exchange_types.go` and `java/lib/src/main/java/io/github/ccxt/types/*.java`. Edit `ts/src/base/types.ts`, then regenerate β€” CI runs `npm run transpile-types-check` and fails if they drifted. PHP has no type file (unified structures are plain arrays) and `js/` is `tsc` output.

---

## 4. Files that are PARTLY hand-written, PARTLY transpiled

The base `Exchange` class in each language has a delimiter. Everything **below** is regenerated from `ts/src/base/Exchange.ts`; everything **above** is hand-written (HTTP client, polyfills, crypto).

Marker: `METHODS BELOW THIS LINE ARE TRANSPILED FROM TYPESCRIPT`

| File | Marker (~line) | Above the marker |
|---|---|---|
| `python/ccxt/base/exchange.py` | 2547 | sync HTTP client, polyfills |
| `python/ccxt/async_support/base/exchange.py` | 680 | aiohttp client, asyncio glue |
| `php/Exchange.php` | 2771 | curl HTTP client, PHP polyfills |
| `php/async/Exchange.php` | 411 | ReactPHP HTTP client |

C#: only `cs/ccxt/base/Exchange.BaseMethods.cs` and the type files `cs/ccxt/base/{Exchange.Types.cs,PredictionTypes.cs}` are generated. Other `cs/ccxt/base/Exchange.*.cs` are hand-written. Behavioural changes go in `ts/src/base/Exchange.ts`, not per-language base files.

### Hand-written base files (safe to edit)

- `python/ccxt/base/{errors,precise,decimal_to_precision}.py`
- `python/ccxt/async_support/base/{throttler.py, ws/*.py}` + top of `exchange.py`
- `php/{<ErrorName>.php, Precise.php, Throttler.php}`, `php/static_dependencies/**`, `php/pro/{Client.php, ArrayCache*.php, OrderBook.php, BaseCache.php}`, top of `php/Exchange.php`
- `cs/ccxt/base/Exchange.*.cs` (except `BaseMethods.cs` and `Exchange.Types.cs`), `cs/ccxt/static/**`, `cs/ccxt/ws/{Client,ArrayCache,Future,OrderBook}.cs`
- Go base lives outside `go/v4/` (in separate base packages β€” confirm via imports in `go/v4/<exchange>.go`)

---

## 5. Testing β€” TDD-first workflow

**Default rule: every new method or behaviour change ships with a test.** Adding code without a test needs justification in the PR.

### 5.1 Test layers

| Layer | Source | Offline? | When |
|---|---|---|---|
| Base unit (REST) | `ts/src/test/base/test.*.ts` | yes | base utility changed |
| Base unit (WS) | `ts/src/pro/test/base/test.{cache,orderBook,close}.ts` | yes | `ts/src/base/ws/*` changed |
| Unified-method | `ts/src/test/Exchange/test.<method>.ts` (+ `pro/test/Exchange/test.watch<method>.ts`) | yes | new unified method / return shape changed |
| Structure validators | `ts/src/test/Exchange/base/test.<structure>.ts` | yes | unified return structure changed |
| Static request | `ts/src/test/static/request/<exchange>.json` | yes | exchange request URL/body changed |
| Static response | `ts/src/test/static/response/<exchange>.json` | yes | exchange response parsing changed |
| Static WS | `ts/src/test/static/ws/<exchange>.json` | yes | ws message handling / watch* parsing changed β€” replays canned frames into `handleMessage` via a mocked transport and asserts the watch result (`npm run ws-tests-<lang>`, entry needs the exact client `url`) |
| ID tests | `api` block | yes | new endpoints added |
| Live | real exchange | NO | smoke-check before merge; never primary gate |

### 5.2 TDD loop

1. Write/extend the test first (`ts/src/test/...` or static JSON fixture).
2. Edit `ts/src/`.
3. Fast verify: `npm run tsBuild && npm run lint`.
4. Offline tests in JS first (cheapest): `npm run test-base-rest-js`, `id-tests-js`, `request-js`, `response-js`.
5. Once green in JS, transpile and run in every other language (see Β§6).
6. Only after all five langs pass offline, run a live test: `node run-tests <exchange> --js` (and `--ws` if relevant).

A test passing only in TS means nothing β€” the regex transpiler can silently mangle code that compiles in TS, and AST-transpiled C#/Go can diverge on edge cases.

### 5.3 Static fixture workflow

Primary regression net for per-exchange behaviour. Regenerated via CLI:

```bash
node cli.js <exchange> <method> <args...> --report     # request entry
node cli.js <exchange> <method> <args...> --response   # response entry
```

Paste output into `methods.<methodName>` array of the respective JSON, then re-run:

```bash
npm run request-tests        # all langs
npm run response-tests       # all langs
# per-lang: npm run request-js/-py/-php/-cs/-go (same for response-*)
```

### 5.4 Per-language unified-method test entry points

| Lang | Entry | Lang | Entry |
|---|---|---|---|
| TS | `npm run ti-ts` | PHP | `npm run ti-php` (sync via `--sync`) |
| JS | `npm run ti-js` | C# | `npm run ti-cs` |
| Python | `npm run ti-py` (sync via `--sync`) | Go | `npm run ti-go` |

Combined offline matrix: `npm run id-tests`, `request-tests`, `response-tests`, `test-base-rest`, `test-base-ws`.

### 5.5 Live tests (last)

> ## 🚨 HARD SAFETY RULES β€” non-negotiable
>
> Applies to humans, agents, and CI β€” anything hitting a real exchange with real credentials.
>
> 1. **Never risk more than 25 USD equivalent per trade.** Compute notional before every `createOrder`: `notional = amount Γ— markPrice`. Abort/reduce if β‰₯ 25 USD. For derivatives, notional is the position value. The cap is per individual trade β€” including cleanup (a 24 USD buy + 24 USD sell to flatten is fine; 30 USD anything is not). For pairs whose minimum order size already exceeds 25 USD: **skip the live test** and rely on static fixtures.
> 2. **Never call `exchange.withdraw()` against a live exchange.** Ever. Not testnet, not sandbox, not "just to verify". Withdraw is fixture-only forever β€” capture via `--report`/`--response` and assert against those.
>
> If you cannot live-test a method under both rules, that's the correct outcome.

```bash
node run-tests <exchange>                      # all five langs
node run-tests <exchange> --js                 # one lang
node run-tests <exchange> --ws --python-async  # WS, async python
node run-tests <exchange> --sandbox            # testnet URLs
node run-tests <exchange> --useProxy           # proxy from skip-tests.json
```

Live tests are the **last** gate. Public endpoints need no keys; private endpoints do (see Β§5.6).

**Always clean up after a live write-endpoint test** in a `finally` block:

| Method | Cleanup |
|---|---|
| `createOrder` (limit, unfilled) | `cancelOrder`, then `fetchOrder` to confirm `canceled` |
| `createOrder` (market or filled limit) | opposite-side trade of filled amount; derivatives: `closePosition` or reduce-only |
| `editOrder` | `cancelOrder` on resulting id |
| `transfer` | reverse transfer |
| `setLeverage` / `setMarginMode` / `setPositionMode` | snapshot value first, restore after |
| `withdraw` | **don't live-test**; fixtures only |

Use the exchange's minimum order size. Log every mutating call. Prefer a sub-account or dedicated low-balance test account β€” never personal trading keys.

### 5.6 Testing private endpoints β€” API keys and sandbox

The runner resolves credentials from three sources, in this order (each overrides the previous):

1. **`keys.json`** at repo root β€” **committed**. Holds non-secret defaults (e.g. `options.defaultType`, sandbox flags, market preferences). Don't put real secrets here β€” it ships in the repo. Use it as the schema reference for which fields each exchange accepts.
2. **`keys.local.json`** at repo root β€” **gitignored**. Your real credentials live here. Deep-extends `keys.json`, so you only need to specify the fields you're overriding.
3. **Environment variables** β€” pattern `<EXCHANGE_ID>_<CREDENTIAL>` upper-cased (`BINANCE_APIKEY`, `BINANCE_SECRET`, `OKX_PASSWORD`, `KRAKEN_UID`, `HYPERLIQUID_WALLETADDRESS`, `HYPERLIQUID_PRIVATEKEY`, …). Only loaded when **`--loadKeys`** is passed, and only fills credentials still missing after steps 1–2. The credential names come from each exchange's `requiredCredentials` block (`apiKey`, `secret`, `password`, `uid`, `walletAddress`, `privateKey`, `token`, `twofa`).

Shape of `keys.local.json` (same shape applies to `keys.json`):

```json
{
    "binance":     { "apiKey": "xxx", "secret": "yyy", "options": { "defaultType": "spot" } },
    "okx":         { "apiKey": "xxx", "secret": "yyy", "password": "passphrase" },
    "hyperliquid": { "walletAddress": "0x...", "privateKey": "0x..." }
}
```

Fields under each exchange override same-named properties on the constructed instance. `options` flips per-exchange behaviour (account type, sandbox flags, market preferences).

Env-var usage (CI):

```bash
BINANCE_APIKEY=... BINANCE_SECRET=... node run-tests binance --js --loadKeys --private
```

`--loadKeys` is required β€” without it, env vars are ignored even if set.

**`--sandbox`.** Swaps `urls.api` ↔ `urls.test` (declared in `describe()`). Throws `NotSupported` if `urls.test` is missing.

**Where to get keys.** Sandbox/testnet is preferred β€” if `urls.test` exists in `describe()`, a testnet exists (Binance, Bybit, OKX demo, Deribit, Coinbase sandbox, BitMEX, Phemex…). Most fund play money on request. Some exchanges (Binance, Bybit, OKX, Bitget, Gate, etc.) also offer a **demo trading** mode inside the live API β€” keys generated from the exchange's demo portal hit the live host but trade against simulated balances. CCXT exposes this via the same `setSandboxMode(true)` / `--sandbox` flag when the exchange wires it up (some use a header/account-type switch rather than `urls.test`); check the exchange file before assuming behaviour. When no sandbox or demo exists, use a dedicated low-balance account with **withdrawal disabled** and an IP allowlist. With no keys you can still cover ~80% via offline tests + live public endpoints.

**Private flags.** `--private` (public + private), `--privateOnly`, `--verbose`, `--debug`.

### 5.7 Ad-hoc testing β€” per-language CLI

Working CLI in every language. Reads same `keys.local.json` / env vars as runner.

```bash
npm run cli.ts -- binance fetchTicker BTC/USDT --verbose
npm run cli.py -- kraken fetchOHLCV BTC/USDT 1h
npm run cli.cs -- coinbase fetchMarkets
# also: cli.js, cli.php, cli.go
```

Iterate in the language where the bug shows up (`cli.py` for a Python-only failure, etc.) β€” much faster than `node run-tests`. **Always pass `--verbose`** when implementing/debugging an endpoint; prints full HTTP request and raw response (needed for signing/parsing/rate-limit issues). Add `--sandbox` for testnet. Drives static-fixture capture via `--report`/`--response` (Β§5.3).

### 5.8 Skipping known-broken tests β€” `skip-tests.json`

For transient outages, exchange quirks, or unsupported features β€” never to silence a regression you introduced.

```json
{
    "<exchange>": {
        "skip": "exchange down",
        "skipWs": "no WS support yet",
        "until": "2026-06-07",
        "preferredSpotSymbol": "ETH/USDT",
        "skipMethods": {
            "fetchOHLCV": "endpoint 500s",
            "ticker": { "spread": "broken bid/ask" }
        },
        "httpProxy": "http://...",
        "wsProxy": "wss://..."
    }
}
```

Always set `until`. Skips without expiry rot.

### 5.9 The `has` capability flags

Unified-method tests only run when `exchange.has['<method>'] === true` (in `describe()`). Adding a new unified method? Set `has.<method>: true` or the test won't run. Don't lie: `true` without implementation fails tests; missing/`false` on something implemented means it's silently untested.

The `features` block declares finer-grained capabilities (`createOrder.triggerPrice`, `fetchOrders.daysBack`), verified by `test.features.ts`. Keep both accurate.

---

## 6. Build & verify β€” required after every change

A `ts/src/` change is **not done** until it transpiles cleanly to all five languages.

### 6.1 Fast inner loop

```bash
npm run tsBuild     # TS β†’ JS only β€” fastest sanity check
npm run lint        # ESLint on ts/src/*.ts and ts/src/pro/*.ts
npm run eslint "ts/src/<exchange>.ts"   # lint single exchange (CI scoped)
```

### 6.2 Per-language transpile (run all five before declaring done)

```bash
npm run transpile         # TS β†’ Python + PHP (regex, REST + WS)
npm run transpileCS       # TS β†’ C# (AST)
npm run transpileCSWs     # C# WebSocket
npm run transpileGO       # TS β†’ Go (AST)
npm run transpileJava     # TS β†’ Java (REST + WS + wrappers)
# scoped (single exchange):
npm run transpileRest --python <ex> && npm run transpileWs --python <ex>
npm run transpileRest -- --php <ex> && npm run transpileWs -- --php <ex>
npm run transpileCsSingle -- <ex>          # REST
npm run transpileCsSingle -- --ws <ex>     # WebSocket
npm run transpileJavaSingle -- <ex>        # REST
npm run transpileJavaSingle -- --ws <ex>   # WebSocket
npm run go-build-single -- <ex1> <ex2> # Go scoped
```

### 6.3 Compile each target

```bash
npm run buildCS              # dotnet build cs/ccxt.sln
npm run buildGO              # go build -C go ./v4 && go build -C go ./v4/pro
npm run buildJava            # cd java/ && ./gradlew build && cd ../
npm run check-python-syntax  # tox -e qa
npm run check-php-syntax
go -C go build ./tests/main.go   # Go test binary
go fmt .                         # Go format (in go/v4 and go/tests/base)
```

### 6.4 Full build (slow)

```bash
npm run build           # incremental: pre-transpile β†’ transpile β†’ CS β†’ docs
npm run force-build     # rebuild everything (very slow β€” reserve for releases)
```

### 6.4.1 Offline tests (per-language)

```bash
# Base tests (only when important_modified in CI):
npm run test-base-rest-{js,py,php,cs,go}    # REST base tests
npm run test-base-ws-{js,py,php,cs,go}      # WS base tests
npm run test-types-go                        # Go type tests
# ID tests:
npm run id-tests-{js,py,php,cs,go,java}
# Request/response tests (full or scoped with -- <exchange>):
npm run request-{js,py,php,cs,go,java}      # all exchanges
npm run request-py-sync -- <ex> && npm run request-py-async -- <ex>  # Python scoped
npm run request-php-sync -- <ex> && npm run request-php-async -- <ex> # PHP scoped
npm run response-{js,py,php,cs,go,java}     # same pattern
```

### 6.4.2 Live tests

```bash
./run-tests-simul.sh --js                              # all JS
./run-tests-simul.sh --js "<rest_exchanges>" "<ws_exchanges>"  # scoped
# Same pattern: --python-async, --php-async, --csharp, --go, --java
npm run live-tests -- --csharp && npm run live-tests-ws -- --csharp  # C# full
```

### 6.5 Verify-after-change checklist (paste into PR)

- [ ] `npm run lint`
- [ ] `npm run tsBuild`
- [ ] `npm run transpile` (Python + PHP)
- [ ] `npm run transpileCS` + `npm run buildCS`
- [ ] `npm run transpileGO` + `npm run buildGO`
- [ ] `npm run check-python-syntax` + `npm run check-php-syntax`
- [ ] Offline tests touched: `request-tests`, `response-tests`, `id-tests`; `test-base-rest`/`-ws` if base changed
- [ ] At least one live smoke test on the affected exchange
- [ ] Diff contains **only** `ts/src/**` (+ optional hand-written base / static JSON)

### 6.6 GitHub Actions CI

Seven parallel workflows (`.github/workflows/`), each on `ubuntu-latest` + Node 20. `build/utils/init_actions.sh` detects `important_modified` (base/build/test files β†’ full transpile + all tests) vs scoped (only changed exchanges). On `master` pushes, generated output is auto-committed.

| Lang | Workflow | Pre-transpile | Full transpile | Build | Live tests |
|---|---|---|---|---|---|
| JS | `js.yml` | `pre-transpile-js` | ↑ (includes tsc) | ↑ | `./run-tests-simul.sh --js` |
| Python | `python.yml` | `pre-transpile-py` | `force-transpile-fast-py` | `check-python-syntax` | `./run-tests-simul.sh --python-async` |
| PHP | `php.yml` | `pre-transpile-php` | `force-transpile-fast-php` | `check-php-syntax` | `./run-tests-simul.sh --php-async` |
| C# | `cs.yml` | `pre-transpile-cs` | `transpileCS && transpileCSWs` | `buildCS` | `./run-tests-simul.sh --csharp` |
| Go | `go-app.yml` | `export-exchanges && emitAPI` | `goTranspiler.ts && --ws` | `buildGO` + `go fmt` | `./run-tests-simul.sh --go` |
| Java | `java.yml` | `pre-transpile-java` | `transpileJava` | `buildJava` | `./run-tests-simul.sh --java` |
| Rust | `rust.yml` | β€” | early-stage, no transpile/test steps wired up yet | β€” | β€” |

**Reproduce locally:** `npm run export-exchanges && npm run emitAPI` first β†’ `npm run pre-transpile-<lang>` β†’ transpile β†’ build β†’ `npm run request-<lang> && npm run response-<lang>` β†’ `./run-tests-simul.sh --<lang> "<ex>" "<ex>"` for live.

---

## 7. Docstrings β€” required on every public method

Every public method in `ts/src/<exchange>.ts`, `ts/src/pro/<exchange>.ts`, and new methods in `ts/src/base/Exchange.ts` gets a JSDoc block. They drive `npm run build-docs`, IDE intellisense (JS/TS), and Python/PHP/C#/Go docstrings β€” missing/wrong docstrings show everywhere.

### Pattern

```ts
/**
 * @method
 * @name <id>#<methodName>
 * @description <one-line, lowercase, no trailing period>
 * @see https://docs.<exchange>.com/<endpoint>      // repeat @see per variant (spot/swap/future)
 * @param {string} symbol unified market symbol
 * @param {int} [since] timestamp in ms of the earliest entry to fetch
 * @param {int} [limit] the maximum number of entries to return
 * @param {object} [params] extra exchange-specific parameters
 * @param {string} [params.until] timestamp in ms of the latest entry
 * @returns {object[]} a list of [<structure>](https://docs.ccxt.com/#/?id=<anchor>-structure) objects
 */
async fetchMyTrades (symbol: Str = undefined, since: Int = undefined, limit: Int = undefined, params = {}): Promise<Trade[]> { ... }
```

### Rules

- `@name <id>#<methodName>` β€” `<id>` matches class id (e.g. `binance#fetchTime`). Required for docs generator.
- `@description` β€” one line, lowercase, no trailing period.
- `@see` β€” repeat per upstream doc; end-of-line comment when one method spans multiple endpoint variants (`// spot`, `// swap`).
- **Types:** wrap `{type}`, use `[name]` for optional params. Required: `@param {string} symbol`. Optional: `@param {int} [since]`, `@param {string|undefined} [code]` for nullables.
- `@param {object} [params]` is always present.
- `params.<key>` β€” document every param read from `params`: `@param {float} [params.triggerPrice] ...`.
- `not used by <id>.<method>` β€” for accepted-but-ignored unified params.
- `@returns` β€” link to manual structure: `[order structure](https://docs.ccxt.com/#/?id=order-structure)`. Arrays as `{object[]}`.
- **`@ignore` for internal helpers** β€” public methods that aren't part of the unified API (exchange-specific helpers, signed-amount calculators, request builders) still need JSDoc but must be `@ignore`'d. See `binance.ts`, `kraken.ts`. Unified methods (`fetchTicker`, `createOrder`, …) must NOT be `@ignore`.

Transpilers convert these to Python (`"""..."""` Sphinx), PHP (`/** */` PHPDoc), C# (`///` XML), Go (`//` package-level).

## 8. When you change behaviour, update the user-facing docs

| Changed… | Also update |
|---|---|
| Unified method signature | JSDoc + matching section in `wiki/Manual.md` |
| Returned structure | `wiki/Manual.md` (`<structure>-structure`) + validator in `ts/src/test/Exchange/base/test.<structure>.ts` |
| New global helper in `ts/src/base/Exchange.ts` | JSDoc; if user-facing, section in `wiki/Manual.md` |
| New unified method | JSDoc on every implementer, `has` flag in `describe()`, test in `ts/src/test/Exchange/`, section in `wiki/Manual.md` |
| Capability / feature flag | `wiki/Requirements.md` if new requirement; `features` block self-documents |
| Examples-worthy behaviour | Example under `examples/ts/` (`npm run tsBuildExamples`) |
| End-user usage docs | Matching section in `.claude/skills/ccxt-{typescript,python,php,csharp,go}/SKILL.md` |
| Top-level summaries (`README.md`, `llms.txt`, `llms-full.txt`) | Only for top-level capability additions/removals |

User-facing skills under `.claude/skills/ccxt-<lang>/` are for callers asking AI assistants "how do I use this?" β€” separate from this CLAUDE.md (contributor-facing). Public API changes update both: wiki for humans, skills for AI-assisted callers.

## 9. TS coding rules to keep the transpilers happy

Full list in `CONTRIBUTING.md`. Top recurring violations:

- 4-space indent, **no tabs**. Blank line between methods, **no blank lines inside a method body**.
- Single-quoted string keys: `obj['key']`, never `obj.key`.
- Use `safeString`/`safeNumber`/`safeInteger`/`safeDict`/`safeList`/`safeBool`. Never `obj['key'] || fallback` (breaks in Python/PHP).
- **Avoid `safeValue`** β€” typeless escape hatch, deprecated when type is known. Use typed variants; fall back only when value is truly any-of-several.
- Arithmetic via `Precise.stringAdd/Sub/Mul/Div/Gt/...`. `+` is string concatenation only.
- No `.includes()` β€” use `.indexOf(x) !== -1`. No `.map`/`.filter` arrow callbacks in derived classes. No `in` operator on arrays.
- **Never name a variable/param after a target-language reserved word** β€” `from`, `type`, `id`, `in`, `is`, `with`, `class`, `def`, `lambda`, `global`, `pass`, `print`, `var`, `function`, `list`, `dict`, `string`, `object`, `end`, `fn` break Python/PHP/C#/Go/Java. Use `fromAddress`, `typeVar`, etc.
- **Don't return `Promise<void>`** (leaves bare `void` in Python) and **don't write a bare `return;`** in a value-returning method (C# CS0126/CS0161). Use `Promise<any>` + `return undefined;`, and end such methods with `return undefined;`.
- **Integer division**: `x.length / 2` is a float in Python β€” wrap any quotient fed to `intToBase16`/array indexing in `this.parseToInt(...)`.
- **`.padStart`/`.padEnd` only on a bare identifier** β€” `expr().padStart(n,'0')` leaks a function call in PHP; assign to a local first.
- Always bracket ternaries: `(cond) ? a : b`. Don't nest them.
- Control chars: double quotes with inline disable: `"\n" // eslint-disable-line quotes`.
- Array length hint: `const n = arr.length;` on its own line tells regex transpiler it's an array.
- Send exchange-specific market IDs, never unified symbols: `this.market(symbol)['id']`. Parse via `this.safeSymbol(marketId, market)`.
- Crypto/signing must use base methods (`this.hmac`, `this.jwt`, `this.ecdsa`, `this.hash`, `this.totp`). No external libs in derived classes.
- Each `define`d endpoint becomes an implicit method (`publicGetEndpoint`). Don't write explicit HTTP wrappers β€” list URLs in the `api` block.

### Standard patterns (post-TS migration)

Verified across recent (`pacifica.ts`, `weex.ts`, `hyperliquid.ts`, `aster.ts`) and certified (`binance.ts`, `okx.ts`, `kraken.ts`, `bybit.ts`):

- **Typed `Promise<...>` return signatures are mandatory.** `Promise<Market[]>`, `Promise<Order>`, `Promise<OrderBook>`, etc. No `Promise<any>` in new code.
- **Import types from `./base/types.js`**: `Dict`, `Str`, `Int`, `Num`, `Strings`, structure types (`Market`, `Ticker`, `Trade`, `Order`, `OrderBook`, `Position`, `Balances`, `OHLCV`). Use `Str`/`Int`/`Num` for nullable scalars: `async fetchOrder (id: string, symbol: Str = undefined, params = {}): Promise<Order>`.
- **Use `handle*AndParams` extractors:**
  - `handleOptionAndParams(params, '<methodName>', '<key>', defaultValue)` β€” option β†’ params β†’ default.
  - `handleMarketTypeAndParams('<methodName>', market, params)` β†’ `[type, params]`, `'spot' | 'swap' | …`.
  - `handleSubTypeAndParams('<methodName>', market, params)` β†’ `'linear' | 'inverse'`.
  - `handleNetworkCodeAndParams(params)` β€” unified network code from `params['network']`.
- **Typed parser helpers.** `safeMarketStructure({...})` in `parseMarket`; `safeMarket(marketId, market, delimiter, marketType)`, `safeCurrency`, `safeCurrencyCode`; `safeOrder2`, `safeTicker` for unified post-processing.
- **Declarative error mapping.** In `handleErrors`, call `throwExactlyMatchedException(this.exceptions['exact'], errorCode, feedback)` and `throwBroadlyMatchedException(this.exceptions['broad'], errorMessage, feedback)`. Don't write `if (errorCode === ...) throw new ...` chains β€” keep mapping in `describe().exceptions`.
- **eslint-disable for control characters** still needed *when* you use them in a literal. Modern `sign()` impls use `urlencode()`/`json()` and usually don't trigger it.

---

## 10. Common contributor questions

**Q: Testnet exists but `--sandbox` doesn't work.** Add `urls.test` mirroring `urls.api`. `setSandboxMode(true)` swaps them. Some exchanges expose only some endpoints on testnet β€” set unsupported ones to live URL or guard with `NotSupported`.

**Q: A test is flaky outside my change.** Add to `skip-tests.json` with reason + `until:` date. Don't delete the test.

**Q: How do I scope a build to one exchange?** `npm run tsBuild` (TS→JS only); `tsx build/transpile.ts <exchange>` (one to Py/PHP); `npm run transpileCsSingle`, `npm run go-build-single`. Tests: `node run-tests <exchange> --js`.

**Q: Add a helper method?** Reusable across exchanges β†’ `ts/src/base/Exchange.ts`. Exchange-specific β†’ exchange file. Don't add a base method only one exchange uses.

**Q: New unified method vs. extend existing?** Match `wiki/Manual.md`. New unified methods need agreement β€” check `wiki/Requirements.md` and discuss first. Exchange-specific tweaks go through `params`.

**Q: `requiredCredentials` block?** Mark `apiKey`, `secret`, `password`, `uid`, `walletAddress`, `privateKey`, `token`, `twofa` as `true` only if the exchange uses them. Runner uses this for env-var names and private-test gating.

**Q: Exchange aliases?** Files like `binanceus.ts`, `coinbaseadvanced.ts` are thin URL/option overrides of a parent. Tests skip aliases (`if (exchange.alias) return`) β€” keep behaviour in the parent.

**Q: Lint fails on a transpiler-required pattern (e.g. `"\n"` in double quotes).** Use the inline disable: `// eslint-disable-line quotes`. Mandatory in those spots, not a workaround.

**Q: Hard-to-spot bug from a Py/PHP/C#/Go stack trace β€” can I edit the transpiled file?** Yes, as a **scratchpad** for `print`/`var_dump`/`Console.WriteLine`/`fmt.Println`. The fix itself must be ported back to `ts/src/<exchange>.ts` and verified via `npm run transpile`. Don't `git add` the transpiled file.

**Q: Endpoint response shape doesn't match exchange docs.** Trust the live response. Workflow: hit live with `npm run cli.ts -- <id> <method> <args> --verbose`, capture static-response fixture (`--response`), link both in the PR. Cite the doc URL in `@see` but don't assume it's correct.

---

## 11. Pull-request etiquette

- **One PR per exchange.** Don't bundle multiple exchanges.
- Commit only `ts/src/**` (+ rarely hand-written base / static JSON fixtures). Generated files in your diff means you committed build output β€” undo that.
- Set the pre-push hook once: `git config core.hooksPath .git-templates/hooks`.
- Don't add language-specific behaviour. If something can't be uniform across all five langs, ask in the PR first.

### PR title

Conventional commits scoped to the exchange:

```
<type>(<exchange>): <description>
```

`<type>` ∈ `fix | feat | chore | refactor | docs | test | perf`. `<exchange>` is the lowercase id. Use `base` for `ts/src/base/Exchange.ts`, `pro` for cross-cutting WS plumbing, `tests` for test-only edits, `build` for transpiler/build-script changes.

Examples: `fix(binance): correct fundingRate sign for short positions`, `feat(okx): add fetchMyLiquidations`.

### PR description

Use the Β§6.5 checklist as the body. Paste actual output (or one-line summary) under each item β€” checked boxes without evidence aren't review-able. Add `## Summary` (1–3 lines: what + why), `Fixes #<n>` / `Refs #<n>` if applicable, and `## Notes` for sandbox usage / manual repro / edge cases.

---

## 12. For AI agents β€” the rules that catch agents most often

- **Locate the source first.** A Python/PHP/C#/Go bug almost always needs a fix in `ts/src/<exchange>.ts` (or `ts/src/pro/<exchange>.ts`). Check the file banner β€” `PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED` means find the TS.
- **Pattern-match existing exchanges** (`binance.ts`, `kraken.ts`, `okx.ts`). Don't invent.
- **TDD: write/update the test with the code** (static JSON fixture or unified test).
- **Verify in all five languages** (run Β§6.5). Don't claim done after `tsBuild`. If you can't run a step, say so β€” don't skip silently.
- **Don't `npm run build` casually** β€” it's slow and rewrites thousands of files. Use `tsBuild` + `lint` for fast feedback; `transpile`, `transpileCS`, `transpileGO` to spot-check.
- **Don't commit generated diffs** in `js/`, `python/ccxt/<exchange>.py`, `php/<exchange>.php`, `cs/ccxt/exchanges/`, `go/v4/`, `ts/src/abstract/`, or `dist/`.  `go/v4/`  is an exception because some files like `exchange.go/exchange_*.go` are not automatically generated so those can be commited
- **Always write/update JSDoc** when adding/changing a public method (Β§7).
- **Pass `--verbose`** when implementing/debugging an endpoint.
- **Update docs when behaviour changes** (Β§8: wiki, examples, user-facing skills).
- **Trust live responses over exchange docs.** Verify with `cli.ts ... --verbose` and capture a static-response fixture.
- **Transpiled file = debugging scratchpad, not fix location.** Port back to `ts/src/`; never `git add` the transpiled file.

---

## 13. Keeping this guide alive

Update CLAUDE.md when: a rule is wrong/out-of-date, you hit an uncovered gotcha, a now-standard pattern (β‰₯3 recent files) isn't documented, or you learned something general about the architecture or workflow.

Don't update CLAUDE.md when: the lesson is exchange-specific (comment it in that file), it's already in `CONTRIBUTING.md` / `wiki/Manual.md` (link, don't duplicate), or it's a one-off.

Create a skill (`.claude/skills/<name>/SKILL.md`) when you've done the same multi-step workflow β‰₯3 times and it would benefit from being scriptable.

Update `.claude/skills/ccxt-<lang>/` when public surface area changes (Β§8).

Keep CLAUDE.md compact. If a section grows large, split into `.claude/rules/<topic>.md` with `paths:` frontmatter so it loads only when relevant files are open.

## 14. Tooling β€” automated PR review

`.claude/agents/ccxt-pr-reviewer.md` does an end-to-end review: reads the diff, transpiles and builds in all five languages, runs offline + live smoke tests, probes for race conditions / security / performance / regressions / breaking changes, and posts a single structured review (inline comments + verdict + test checklist + migration notes).

```
Use the ccxt-pr-reviewer agent to review PR 28543.
Use the ccxt-pr-reviewer agent to review this branch.
```

Caps inline comments at 12 with severity tags (🚨 Blocker / ⚠️ Concern / πŸ’‘ Suggestion / πŸ“ Nit). Only `APPROVE`s when zero issues and every test in Phases 3–5 passes β€” it doesn't approve out of politeness. Read the agent file for the full workflow before relying on its output.

## 15. Quick repo map

```
ts/src/                 source TS β€” REST exchanges, base, REST tests
ts/src/pro/             source TS β€” WS exchanges, WS tests
ts/src/prediction/      source TS β€” prediction-market exchanges (extend PredictionExchange)
ts/src/base/PredictionExchange.ts  prediction base (outcome loading/caching β€” see .claude/rules/prediction-outcomes.md)
ts/src/abstract/        AUTO-GENERATED API method signatures
ts/src/base/Exchange.ts master base (partly transpiled into all langs)
ts/src/base/ws/         WS base (Client, Cache, OrderBook, Future)
ts/src/test/            REST tests (unified methods, base, static fixtures)
ts/src/pro/test/        WS tests
build/                  transpilers + build scripts (transpile.ts = regex)
js/                     GENERATED β€” tsc output
python/ccxt/            GENERATED + hand-written base/
python/ccxt/async_support/  GENERATED + hand-written base/ + ws/
php/                    GENERATED + hand-written Exchange.php top + errors
php/async/, php/pro/    GENERATED + hand-written ReactPHP plumbing
cs/ccxt/                GENERATED + hand-written base/ (except BaseMethods.cs)
go/v4/                  GENERATED Go (every file is transpiled)
wiki/                   docs (Manual.md = authoritative API spec)
examples/               per-language end-user examples
.claude/skills/         per-language usage skills (/ccxt-python, /ccxt-typescript, …)
                        β€” public API reference for callers, NOT for editing CCXT
.claude/rules/          topic-scoped contributor rules (auto-load via `paths:` frontmatter);
                        prediction-outcomes.md = the outcome loading/caching pattern
```