Adapters: rules and the path to a new venue¶
An adapter translates one venue's API into adapter-api and nothing more. Everything a venue does not
decide (journal, idempotency, recovery from fills, kill switch, quote refresh) is the host's, written
once. The interface and its rules are in design.md §3; this page is how an adapter is built,
tested and merged. It is strict on purpose: an adapter that passes it works with qkt as it is.
For a walk-through with code, see writing-an-adapter.md.
The branch¶
- One venue, one branch:
adapter/<venue>, lowercase letters and digits only (adapter/bybit). - The branch carries the whole adapter, nothing else. It may change only:
adapter-<venue>/**,settings.gradle.kts,app/build.gradle.kts,README.md,docs/adapters.mdand.github/workflows/adapter.yml(to pass the venue's secrets). It is cut fromdevand merges intodev. A host or API change it needs is its ownfeat/…PR, merged first. - CI enforces both (
prworkflow). Fixes to an existing adapter use the same branch name.
The module¶
adapter-<venue>/
build.gradle.kts api(project(":adapter-api")), testImplementation(project(":adapter-testkit"))
README.md setup, connect qkt, settings, venue behaviour, contract suite
CLAUDE.md @README.md and @../docs/adapters.md, nothing else
src/main/kotlin/com/qkt/venuegateway/<venue>/
client/ the venue's protocol in its own words; no adapter-api types
<Venue>Mapping*.kt the only place venue words become adapter-api types
<Venue>Adapter.kt implements VenueAdapter
<Venue>AdapterFactory.kt type = "<venue>"; reads settings and credentials, fails naming the key
<Venue>Settings.kt every setting, its default, or why it has none
src/main/resources/META-INF/services/com.qkt.venuegateway.adapter.VenueAdapterFactory
src/test/kotlin/… offline tests and <Venue>ContractTest
src/test/resources/fixtures/ responses recorded from the venue, account ids removed
- Depends on
adapter-apionly (another adapter'sclientpackage at most, aspaperreuses Deribit's public client). Neverhost,vgp-wireorapp. - Settings are
GATEWAY_SETTING_<KEY>at run time; credentials areGATEWAY_LOGIN/GATEWAY_SECRET. A setting that chooses real money over a test environment has no default. - No threads, clocks or env reads of its own beyond what
AdapterContexthands in.
Checkpoints, in order¶
Each checkpoint is one or more commits on the branch; do not start the next with the last one red.
- Probe. Call the venue's test environment by hand; record every response the adapter will read
into
fixtures/. Write the facts you measured (limits, id formats, how long closed orders stay queryable, rounding, error codes) into the module README. - Client. The venue protocol, tested offline against the fixtures (and
MockWebServerfor sockets). Noadapter-apiimport inclient/. - Mapping. Venue →
adapter-apitypes, every field from a fixture. Money isBigDecimalfrom the raw JSON text. Unknown enum values fail loudly. - Adapter and factory. Registered under
META-INF/services; refuses bad settings naming the key. - Contract.
<Venue>ContractTest extends AdapterContractTestpasses against the test environment. It reads its key from<VENUE>_CLIENT_ID/<VENUE>_CLIENT_SECRETand skips locally without them. - Wire-up.
settings.gradle.ktsincludes the module;app/build.gradle.ktsbundles it; the root README's Adapters table has a row linking to the module README;adapter.ymlpasses its secrets. - End to end. qkt trades through a gateway running the adapter on the test environment: an order placed, filled, seen on the stream, and closed. Note the run in the PR.
Tests the adapter must have¶
| Test | Runs | Proves |
|---|---|---|
| client tests | offline, fixtures | requests are built and answers read as the venue sends them |
| mapping tests | offline, fixtures | every adapter-api field, decimals exact, unknown values refused |
| settings tests | offline | defaults, required keys, bad values refused by name |
<Venue>ContractTest |
test environment | the behaviour the host relies on (adapter-testkit) |
CI¶
The adapter workflow runs for every PR touching an adapter, and nightly for all of them:
scripts/check-adapter.sh <venue>: the layout above is complete (module, README, CLAUDE.md, service registration, contract test, fixtures, settings and app inclusion, README row)../gradlew :adapter-<venue>:testwith the venue's secrets, then a check that the contract suite ran: when the venue's secrets are configured (repository secrets<VENUE>_CLIENT_IDand<VENUE>_CLIENT_SECRET, test-environment keys with trade scope only), a skipped contract test is a failure. Without them it is a warning, and the PR records a local contract run against the test environment (command and result) instead.- Nightly, the same against
main, so a venue that changes under us turns red before qkt notices.
Done means¶
All checkpoints green, the adapter workflow green, the contract suite run on the test environment (in CI, or locally and noted in the PR), the end-to-end run noted in the PR, and nothing in the diff outside the branch scope.