Packages
dp_exchange_core
0.1.21
0.3.48
0.3.47
0.3.46
0.3.45
0.3.44
0.3.43
0.3.42
0.3.41
0.3.40
0.3.39
0.3.38
0.3.37
0.3.36
0.3.35
0.3.34
0.3.33
0.3.32
0.3.31
0.3.30
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.1.79
0.1.78
0.1.77
0.1.76
0.1.75
0.1.74
0.1.73
0.1.72
0.1.71
0.1.70
0.1.69
0.1.68
0.1.67
0.1.66
0.1.65
0.1.64
0.1.63
0.1.62
0.1.61
0.1.60
0.1.59
0.1.58
0.1.57
0.1.56
0.1.55
0.1.54
0.1.53
0.1.52
0.1.51
0.1.50
0.1.49
0.1.48
0.1.47
0.1.46
0.1.45
0.1.44
0.1.43
0.1.42
0.1.41
0.1.40
0.1.39
0.1.38
0.1.37
0.1.36
0.1.35
0.1.34
0.1.33
0.1.32
0.1.31
0.1.30
0.1.29
0.1.28
0.1.27
0.1.26
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
EXPERIMENTAL — Shared contract for the DpExchange family of exchange adapters: behaviours, value types, canonical-pair normalisation and a conformance suite.
Current section
Files
Jump to
Current section
Files
usage-rules/adapter.md
# Implementing a venue package
## The shape
```elixir
defmodule DpExchange.YourVenue do
@behaviour DpExchange.Core.Venue
end
```
That module is the **entire public API of your package**. Everything else — transport,
signing, session handling, supervision — is internal, and the conformance suite asserts it.
Declare the behaviour. The compiler's missing-callback check is the cheapest assertion in
the whole contract and it runs before any test does.
## Do not add functions to the facade
A venue does not add functions; it declares which ones it answers. A public function only
your venue has is a function a caller must know which venue it is holding to call — which
is the coupling the family exists to remove.
This is not hypothetical. One venue shipped `get_staking_balances/2` as a public
venue-specific function. It does not cross the facade, and the consumer loses that call at
migration.
If a capability is genuinely missing from the facade, that is a Core change with a
deliberate release behind it, not a local addition.
## `capabilities/0` is a claim about a real venue
```elixir
def capabilities do
DpExchange.Core.Capabilities.new(
endpoints: %{
{:get_price, 2} => :experimental,
{:get_transfers, 2} => :unsupported
},
supported_quotes: ~w(USD USDC),
historical_timeframes: ~w(1m 1h 1d),
credential_benefit: :higher_ceiling,
public_ceiling: %{limit: 10, per_ms: 1_000},
authenticated_ceiling: %{limit: 100, per_ms: 1_000},
measured_at: ~D[2026-08-27],
measured_against: "GET /api/v3/exchangeInfo"
)
end
```
**Build it with `new/1`.** Assembling `%Capabilities{}` directly skips every validation,
and the validations are the point.
### Declare what you measured, not what you assume
If a value was measured, say when and against what. If it was read from documentation and
never probed, say that instead. **An unlabelled number is worse than a missing one.**
This is not fussiness. A state table drafted by people who knew the system was wrong in
**7 of 21 rows**; the measured version replaced it.
### The declaration and the behaviour may not disagree
- `:proven` or `:experimental` → the function **works**. It may not answer
`{:error, :not_supported}`.
- `:unsupported` → the function **exists and returns `{:error, :not_supported}`**. Not a
raise, not undefined, not degraded data.
Over-declaring fails in your caller's hands at runtime. Under-declaring hides working
functionality. The suite checks both directions because checking one leaves the other open.
### Never declare transport
There is no `has_websocket` and there must never be one. Both endpoints exist on every
venue. What a caller legitimately needs is *which kinds of data* stream — `streamable:
[:quotes, :order_book]` — not which channels carry them. `"level2"` is your venue's word;
`:order_book` is everyone's.
## Fail closed; never substitute
The recurring failure in this family is **a nearby substitute where there should be an
error**. A missing granularity becoming the closest one. A missing endpoint becoming
synthetic data. Every value stays plausible and only the meaning is wrong, which is why it
does not surface as a failure.
If asked for a timeframe you do not serve, return an error. Do not serve the nearest width.
## Timestamps are the venue's own
Use what the venue gave you, unchanged. Where it gave nothing, `nil` is the honest answer —
a substituted local clock is a plausible value with the wrong meaning.
`Balance` is the one exception, and it is stated: its timestamp is **when you asked**,
because a balance has no venue event time and its freshness is the only thing a caller can
reason about.
## Carry the incident, not just the code
Where a moduledoc explains *why* a guard exists, that explanation is the most valuable
thing in the file. Carry it when the code moves or is copied. A guard without its reason
reads as defensive padding, and the next person tidying up deletes it.