> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trdrs.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Provider self-check

> Prove your public provider follows the provider contract before anyone connects a real account to it.

Prove your provider handles identity, ordering and evidence correctly before a single real account
depends on it. A public provider is the code that lets trdrs reach a market it doesn't run itself,
written against the published provider contract. The provider self-check runs fourteen rules
against it and tells you, rule by rule, what held and what didn't. Once your provider passes, a
venue can plug it in from its Providers list.

Use this page if you're building a provider. It's a different check from the
[integration self-check](/providers/requirements), which measures software that calls the trdrs API;
this one measures software that trdrs calls. By the end you have an evidence file listing a result
for every rule.

You can run it before you have a vendor account, a certificate or a network connection. The
reference provider in the `@trdrs/reference-provider` package follows the contract exactly and
passes every rule, so running the suite against your own provider shows you the differences, and
those are your work list.

For a real exchange's test environment, trdrs also runs a testnet provider for the exchanges
[Run a testnet provider](/guides/run-a-testnet-provider) names, and that page shows how to qualify
it.

## What it checks

Each rule exists because getting it wrong loses or duplicates someone's trade. The reason comes back
with every result, because a provider that only learns what failed tends to make the check pass
rather than make the behavior right.

| Rule | What it requires |
| - | - |
| `manifest` | The manifest parses under the contract, and doesn't claim it can take orders without session fencing. |
| `grant_echoes_authority` | A session grant repeats the connection, environment, scope and account it was asked for, so it can't be replayed against another. |
| `acquisition_idempotent` | The same request for a session returns the same grant, so a caller that retries never holds two identities for one intent. |
| `fencing_revokes_older` | A newer session generation revokes the older session. The older holder still believes its grant is valid, so the provider is the only place the truth lives. |
| `fencing_refuses_older_acquire` | A generation below the highest can't start a session. Otherwise a restarted reader quietly takes an account back. |
| `payload_hash_verified` | A command whose hash doesn't match its body is refused, because the bytes and the stated intent disagree. |
| `command_idempotent` | The same command id and body return the same receipt, so a retry after a lost answer recovers the first outcome instead of trading again. |
| `command_conflict` | The same command id with a different body is refused. Two intents can't share one name; the first one stands. |
| `absence_is_not_rejection` | A command you have no record of reads as "no evidence", not "rejected". A caller that reads absence as refusal sends the order again. |
| `events_ordered` | Event cursors and account sequence numbers always increase. |
| `events_resume` | Resuming after a cursor returns exactly the events after it, with none missing and none repeated. |
| `unknown_cursor_declared` | A cursor you don't recognize is reported as unknown, never replayed from the start. Starting over quietly is how a consumer applies a day of trading twice. |
| `coverage_only_when_complete` | A snapshot page claims to cover the account only when it's the complete account. A partial page that claims coverage makes an unfinished read look like proof the account is flat. |
| `account_isolation` | A session for one account can't read another. An account id isn't a credential. |

## Run it

Call `runProviderSelfCheck` with a function that sends each of the suite's requests to your provider
and returns what came back, plus the identities to test with:

```ts theme={null}
import { runProviderSelfCheck } from '@trdrs/reference-provider'

const evidence = await runProviderSelfCheck(
  async ({ method, path, query, body }) => {
    const url = new URL(path, 'https://your-provider.example.com')
    for (const [key, value] of Object.entries(query ?? {})) url.searchParams.set(key, value)
    const response = await fetch(url, { method, headers: { 'content-type': 'application/json' }, body: body ? JSON.stringify(body) : undefined })
    const text = await response.text()
    return response.headers.get('content-type')?.includes('event-stream')
      ? { status: response.status, stream: text.split('\n\n').filter(Boolean).map(frame => JSON.parse(frame.replace(/^data: /, ''))) }
      : { status: response.status, body: text ? JSON.parse(text) : undefined }
  },
  {
    connectionId: 'your-connection',
    environment: 'sandbox',
    authorizationRef: 'your-authorization',
    tradableAccountId: 'ACCOUNT-1',
    otherAccountId: 'ACCOUNT-2',
    order: { instrumentId: 'ES-MAR26', providerSymbol: 'ESH6', quantity: '1' },
  },
)
```

`tradableAccountId` and `order` let the suite place one order to test the command rules, and
`otherAccountId` lets it test that one account can't read another. Leave them out and the rules that
need them are skipped, not passed.

## Read the evidence

`runProviderSelfCheck` returns the evidence directly; write it to a file and keep it. It never
contains your `authorizationRef`.

```json theme={null}
{
  "suite": "provider-contract-v1",
  "providerVersion": "your-provider-v1",
  "passed": false,
  "skipped": ["coverage_only_when_complete"],
  "results": [
    { "id": "payload_hash_verified", "outcome": "fail", "detail": "a forged payload hash was accepted with 200", "why": "..." }
  ]
}
```

Each rule has one of three outcomes:

| Outcome | What it means |
| - | - |
| `pass` | The rule was seen to hold. |
| `fail` | The rule was seen to break. `detail` says what was seen. |
| `skipped` | The rule couldn't be tried, for example because no tradable account was given, or the account fit on one page so there was no partial page to check. |

`passed` is `true` only when every rule passed. A skipped rule doesn't count, because approving a
provider for behavior nobody saw is worse than reporting nothing.

The suite runs every rule even when the first one fails, and a provider it can't reach at all comes
back as failures rather than a crash. One run gives you the whole list.

## What it doesn't check

It isn't a certification, and it isn't a market. The reference provider has no order book, no
slippage and no latency, so passing every rule says nothing about how your market fills under load.
It says your provider follows the contract's rules on identity, ordering and evidence: the part
that, when it's wrong, loses money without anyone noticing.

## Next steps

<CardGroup cols={2}>
  <Card title="Run a testnet provider" icon="flask" href="/guides/run-a-testnet-provider">
    Qualify a real exchange's test environment behind the contract.
  </Card>

  <Card title="Run a venue" icon="building" href="/guides/run-a-venue">
    See how a venue plugs a provider into its routes.
  </Card>
</CardGroup>
