# dev.cocore.compute.exchangePolicy

> Published by [cocore.dev](https://lexicon.garden/identity/did:plc:5quuhkmwe2q4k3azfsgg7kdz)

✓ This is the authoritative definition for this NSID.

## Description

An exchange's published terms of service. Records every parameter that affects how this exchange computes settlements and maintains per-DID token balances: fee schedule, supported currencies, self-loop rules, processor identifiers, and the token-accounting parameters (onboarding grant, balance floor, weekly refresh, monthly patronage distribution, treasury identity). A settlement record strong-refs the active policy so verifiers can re-derive the payout numbers offline. Policies are immutable per-record; the exchange publishes a new record when terms change and updates `active=false` on the prior one.

## Links

- [View on Lexicon Garden](https://lexicon.garden/lexicon/did:plc:5quuhkmwe2q4k3azfsgg7kdz/dev.cocore.compute.exchangePolicy)
- [Documentation](https://lexicon.garden/lexicon/did:plc:5quuhkmwe2q4k3azfsgg7kdz/dev.cocore.compute.exchangePolicy/docs)
- [Examples](https://lexicon.garden/lexicon/did:plc:5quuhkmwe2q4k3azfsgg7kdz/dev.cocore.compute.exchangePolicy/examples)

## Definitions

### `dev.cocore.compute.exchangePolicy`

**Type**: `record`

**Key**: `tid`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `fee` | `ref` → `#feeSchedule` | Yes | Per-receipt fee. `fee.bps` is the fraction of each receipt's token cost routed to the treasury account instead of the provider (conservation 95/5 by default with `bps: 500`). The treasury accumulates these fees and redistributes them as patronage rebates on `patronageDistribution.cadenceDays`. `fee.currency` names the unit the fee schedule is denominated in. |
| `active` | `boolean` | No | Soft-delete: false means a newer policy supersedes this one. Settlements signed against an inactive policy are still valid for receipts that arrived before the policy flipped. |
| `exchange` | `string` (did) | Yes | Exchange DID. MUST equal the repo this record is published in. |
| `selfLoop` | `ref` → `#selfLoopRule` | Yes | How the exchange handles jobs where requester DID == provider DID. |
| `termsUri` | `string` (uri) | No | URL to a human-readable Terms of Service / Privacy Policy for this exchange. Pinned with `termsVersion`; clients prompt the user for re-acceptance whenever the active policy's `termsVersion` no longer matches the version on the user's most recent dev.cocore.compute.termsAcceptance. |
| `createdAt` | `string` (datetime) | Yes |  |
| `processor` | `string` | No | Identifier for the settlement backend the exchange runs (e.g. 'closed-loop', 'usdc-base'). Useful as a hint to clients that need to know whether settlement is internal or external. |
| `tokenRate` | `ref` → `dev.cocore.compute.defs#tokenRate` | No | Uniform per-token rate the exchange asserts for jobs it settles. CANONICAL: providers settling through this exchange MUST price receipts at this rate, ignoring their own `dev.cocore.compute.provider.priceList`. Verifiers MAY reject receipts whose `price.amount` diverges from `tokenRate.inputPricePerMTok * tokens.in / 1e6 + tokenRate.outputPricePerMTok * tokens.out / 1e6` beyond a one-minor-unit floor. The provider's priceList is a denormalization for client display today; a future lexicon revision will introduce a per-receipt provider-set override (with the exchange's permission), at which point priceList becomes authoritative again. Optional only for pre-2026-05 policies, where the provider's priceList was canonical. |
| `tokenFloor` | `integer` | No | Minimum token balance a DID must hold post-dispatch for the exchange to accept a new job. The admission check is `balance - job.priceCeiling.tokensEquivalent >= tokenFloor`. Prevents a user from dispatching jobs that would zero out their balance, which keeps the failure mode 'wait for the next refresh' rather than 'mid-job settlement bounced'. cocore.dev sets this to 100_000. |
| `tokenGrant` | `integer` | No | Tokens granted to a DID on first interaction with this exchange. Idempotent per DID: the exchange MUST issue the grant exactly once and MUST be able to prove which DIDs have already received it (typically via a `dev.cocore.account.tokenGrant` record per recipient). Set to 0 to disable grants. cocore.dev sets this to 1_000_000. |
| `treasuryDid` | `string` (did) | No | The DID whose token balance accumulates the per-receipt fee (5% by default) and from which patronage rebates are distributed. Defaults to the exchange's own DID (`exchange`) when unset, which matches the convention of a cooperative whose treasury IS the exchange's own balance sheet. |
| `termsVersion` | `string` | No | Version string for the terms-of-service text at `termsUri`. Bumping this triggers re-acceptance prompts in clients. Compared as a literal equality string; semantic versioning is a convention, not a requirement. |
| `weeklyRefresh` | `ref` → `#refreshRule` | No | Optional 'use-it-to-keep-it' refresh that lazily issues `amountPerDid` tokens to active DIDs every `cadenceMinutes`. The refresh only fires when the DID touches the network (receipt as either side, balance read, governance act) — dormant DIDs accrue nothing. Sized so a member who actively uses the system stays roughly at the dignity floor; sized so the aggregate mint roughly matches the new-compute capacity coming online. Set to absent to disable. |
| `supportedCurrencies` | `array` | Yes | ISO 4217 (or XBT/XSAT-style) currency codes the exchange will settle in. |
| `patronageDistribution` | `ref` → `#patronageRule` | No | Optional periodic distribution of treasury balance back to active members in proportion to their patronage (consumer spending + provider earnings) during the period. Direct analog of REI's dividend and Rochdale-tradition patronage rebates. Set to absent to disable. |

### `dev.cocore.compute.exchangePolicy#feeSchedule`

**Type**: `object`

Linear fee model: max(amountMinor * bps / 10000, minMinor).

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `bps` | `integer` | Yes | Basis points (1/10000) retained by the exchange. 500 = 5%. |
| `currency` | `string` | Yes | Currency the fee schedule is denominated in. |
| `minMinor` | `integer` | Yes | Floor on the fee in integer minor units. 0 means there is no floor. |

### `dev.cocore.compute.exchangePolicy#refreshRule`

**Type**: `object`

Periodic refresh: amount + cadence.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `amountPerDid` | `integer` | Yes | Tokens credited to a DID per refresh tick. cocore.dev: 70_000 (~7% of the 1M-token onboarding grant). |
| `cadenceMinutes` | `integer` | Yes | Minimum minutes between refreshes for a given DID. cocore.dev: 10_080 (7 days). |

### `dev.cocore.compute.exchangePolicy#selfLoopRule`

**Type**: `object`

What happens when the same DID owns the requester job and the receipt's provider field — e.g., a user running an inference on their own machine via the exchange.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `minMinor` | `integer` | No | Optional facilitation floor — the exchange may still take a small flat amount to cover its operating cost. Ignored when feeWaived is true. |
| `feeWaived` | `boolean` | Yes | If true, the exchange takes no fee on self-loop jobs. The settlement still gets published as an audit trail. |

### `dev.cocore.compute.exchangePolicy#patronageRule`

**Type**: `object`

Periodic distribution of treasury balance to active members in proportion to patronage during the period.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `cadenceDays` | `integer` | Yes | Days between distribution ticks. cocore.dev: 30. |
| `fractionBps` | `integer` | Yes | Basis points of the treasury balance distributed at each tick. Remainder is retained as operating reserve. cocore.dev: 8000 (80% distributed, 20% retained). |

## Raw Schema

```json
{
  "id": "dev.cocore.compute.exchangePolicy",
  "defs": {
    "main": {
      "key": "tid",
      "type": "record",
      "record": {
        "type": "object",
        "required": [
          "exchange",
          "fee",
          "supportedCurrencies",
          "selfLoop",
          "createdAt"
        ],
        "properties": {
          "fee": {
            "ref": "#feeSchedule",
            "type": "ref",
            "description": "Per-receipt fee. `fee.bps` is the fraction of each receipt's token cost routed to the treasury account instead of the provider (conservation 95/5 by default with `bps: 500`). The treasury accumulates these fees and redistributes them as patronage rebates on `patronageDistribution.cadenceDays`. `fee.currency` names the unit the fee schedule is denominated in."
          },
          "active": {
            "type": "boolean",
            "default": true,
            "description": "Soft-delete: false means a newer policy supersedes this one. Settlements signed against an inactive policy are still valid for receipts that arrived before the policy flipped."
          },
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "Exchange DID. MUST equal the repo this record is published in."
          },
          "selfLoop": {
            "ref": "#selfLoopRule",
            "type": "ref",
            "description": "How the exchange handles jobs where requester DID == provider DID."
          },
          "termsUri": {
            "type": "string",
            "format": "uri",
            "description": "URL to a human-readable Terms of Service / Privacy Policy for this exchange. Pinned with `termsVersion`; clients prompt the user for re-acceptance whenever the active policy's `termsVersion` no longer matches the version on the user's most recent dev.cocore.compute.termsAcceptance."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          },
          "processor": {
            "type": "string",
            "maxLength": 64,
            "description": "Identifier for the settlement backend the exchange runs (e.g. 'closed-loop', 'usdc-base'). Useful as a hint to clients that need to know whether settlement is internal or external."
          },
          "tokenRate": {
            "ref": "dev.cocore.compute.defs#tokenRate",
            "type": "ref",
            "description": "Uniform per-token rate the exchange asserts for jobs it settles. CANONICAL: providers settling through this exchange MUST price receipts at this rate, ignoring their own `dev.cocore.compute.provider.priceList`. Verifiers MAY reject receipts whose `price.amount` diverges from `tokenRate.inputPricePerMTok * tokens.in / 1e6 + tokenRate.outputPricePerMTok * tokens.out / 1e6` beyond a one-minor-unit floor. The provider's priceList is a denormalization for client display today; a future lexicon revision will introduce a per-receipt provider-set override (with the exchange's permission), at which point priceList becomes authoritative again. Optional only for pre-2026-05 policies, where the provider's priceList was canonical."
          },
          "tokenFloor": {
            "type": "integer",
            "minimum": 0,
            "description": "Minimum token balance a DID must hold post-dispatch for the exchange to accept a new job. The admission check is `balance - job.priceCeiling.tokensEquivalent >= tokenFloor`. Prevents a user from dispatching jobs that would zero out their balance, which keeps the failure mode 'wait for the next refresh' rather than 'mid-job settlement bounced'. cocore.dev sets this to 100_000."
          },
          "tokenGrant": {
            "type": "integer",
            "minimum": 0,
            "description": "Tokens granted to a DID on first interaction with this exchange. Idempotent per DID: the exchange MUST issue the grant exactly once and MUST be able to prove which DIDs have already received it (typically via a `dev.cocore.account.tokenGrant` record per recipient). Set to 0 to disable grants. cocore.dev sets this to 1_000_000."
          },
          "treasuryDid": {
            "type": "string",
            "format": "did",
            "description": "The DID whose token balance accumulates the per-receipt fee (5% by default) and from which patronage rebates are distributed. Defaults to the exchange's own DID (`exchange`) when unset, which matches the convention of a cooperative whose treasury IS the exchange's own balance sheet."
          },
          "termsVersion": {
            "type": "string",
            "maxLength": 32,
            "description": "Version string for the terms-of-service text at `termsUri`. Bumping this triggers re-acceptance prompts in clients. Compared as a literal equality string; semantic versioning is a convention, not a requirement."
          },
          "weeklyRefresh": {
            "ref": "#refreshRule",
            "type": "ref",
            "description": "Optional 'use-it-to-keep-it' refresh that lazily issues `amountPerDid` tokens to active DIDs every `cadenceMinutes`. The refresh only fires when the DID touches the network (receipt as either side, balance read, governance act) — dormant DIDs accrue nothing. Sized so a member who actively uses the system stays roughly at the dignity floor; sized so the aggregate mint roughly matches the new-compute capacity coming online. Set to absent to disable."
          },
          "supportedCurrencies": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 8,
              "minLength": 3
            },
            "maxLength": 32,
            "minLength": 1,
            "description": "ISO 4217 (or XBT/XSAT-style) currency codes the exchange will settle in."
          },
          "patronageDistribution": {
            "ref": "#patronageRule",
            "type": "ref",
            "description": "Optional periodic distribution of treasury balance back to active members in proportion to their patronage (consumer spending + provider earnings) during the period. Direct analog of REI's dividend and Rochdale-tradition patronage rebates. Set to absent to disable."
          }
        }
      }
    },
    "feeSchedule": {
      "type": "object",
      "required": [
        "bps",
        "minMinor",
        "currency"
      ],
      "properties": {
        "bps": {
          "type": "integer",
          "maximum": 10000,
          "minimum": 0,
          "description": "Basis points (1/10000) retained by the exchange. 500 = 5%."
        },
        "currency": {
          "type": "string",
          "maxLength": 8,
          "minLength": 3,
          "description": "Currency the fee schedule is denominated in."
        },
        "minMinor": {
          "type": "integer",
          "minimum": 0,
          "description": "Floor on the fee in integer minor units. 0 means there is no floor."
        }
      },
      "description": "Linear fee model: max(amountMinor * bps / 10000, minMinor)."
    },
    "refreshRule": {
      "type": "object",
      "required": [
        "amountPerDid",
        "cadenceMinutes"
      ],
      "properties": {
        "amountPerDid": {
          "type": "integer",
          "minimum": 0,
          "description": "Tokens credited to a DID per refresh tick. cocore.dev: 70_000 (~7% of the 1M-token onboarding grant)."
        },
        "cadenceMinutes": {
          "type": "integer",
          "minimum": 60,
          "description": "Minimum minutes between refreshes for a given DID. cocore.dev: 10_080 (7 days)."
        }
      },
      "description": "Periodic refresh: amount + cadence."
    },
    "selfLoopRule": {
      "type": "object",
      "required": [
        "feeWaived"
      ],
      "properties": {
        "minMinor": {
          "type": "integer",
          "minimum": 0,
          "description": "Optional facilitation floor — the exchange may still take a small flat amount to cover its operating cost. Ignored when feeWaived is true."
        },
        "feeWaived": {
          "type": "boolean",
          "description": "If true, the exchange takes no fee on self-loop jobs. The settlement still gets published as an audit trail."
        }
      },
      "description": "What happens when the same DID owns the requester job and the receipt's provider field — e.g., a user running an inference on their own machine via the exchange."
    },
    "patronageRule": {
      "type": "object",
      "required": [
        "fractionBps",
        "cadenceDays"
      ],
      "properties": {
        "cadenceDays": {
          "type": "integer",
          "minimum": 1,
          "description": "Days between distribution ticks. cocore.dev: 30."
        },
        "fractionBps": {
          "type": "integer",
          "maximum": 10000,
          "minimum": 0,
          "description": "Basis points of the treasury balance distributed at each tick. Remainder is retained as operating reserve. cocore.dev: 8000 (80% distributed, 20% retained)."
        }
      },
      "description": "Periodic distribution of treasury balance to active members in proportion to patronage during the period."
    }
  },
  "$type": "com.atproto.lexicon.schema",
  "lexicon": 1,
  "description": "An exchange's published terms of service. Records every parameter that affects how this exchange computes settlements and maintains per-DID token balances: fee schedule, supported currencies, self-loop rules, processor identifiers, and the token-accounting parameters (onboarding grant, balance floor, weekly refresh, monthly patronage distribution, treasury identity). A settlement record strong-refs the active policy so verifiers can re-derive the payout numbers offline. Policies are immutable per-record; the exchange publishes a new record when terms change and updates `active=false` on the prior one."
}
```
