# Mirror tokens

Source: https://docs.settlemint.com/docs/concepts/records-and-evidence/mirror-tokens
A standard token that stands for a holding kept off chain, minted and burned one to one with the custodian's or registrar's transaction id.

A mirror token is a standard DALP token that stands for a security held off chain at a custodian or registrar. The custodian mints and burns it one to one against that holding, the collateral rule caps its supply, and every mint and burn carries the custodian's or registrar's transaction id. Once minted, the token trades and settles like any other token.

## What makes a token a mirror token [#what-makes-a-token-a-mirror-token]

A token is a mirror token when its metadata has a `heldAt` entry that names where the legal holding sits. An optional `heldAtAccount` entry names the account there. Add both as metadata fields on the token's instrument template.

A mirror token also needs:

- the supply delegate token feature, with the custodian's wallet added as a delegate that can mint and burn
- the supply management role on that same wallet
- the collateral rule at a ratio of 100%

Without the supply delegate feature, or without the collateral rule at 100% for a mint, a mint or burn is refused with [DALP-200385](https://docs.settlemint.com/docs/api-reference/errors/platform-api-error-reference#client-request-errors) and the reason `setup-incomplete`.

## Minting and burning [#minting-and-burning]

Post the new attested holding as a collateral claim first, through `PATCH /api/v2/tokens/{assetId}/collateral`. A mint above the claim is refused by the collateral rule.

Send the mint or burn with the custodian's or registrar's transaction id in `registerReference`. DALP puts a keccak-256 hash of the id on chain, through the token's supply delegate feature, and keeps the text off chain.

| Refusal `reason` | What it means |
| --- | --- |
| `external-reference-required` | The mint or burn has no `registerReference`. |
| `single-movement-per-reference` | The call names more than one recipient or holder. |
| `burn-own-units-only` | The burn names another holder. The holder transfers to the custodian's wallet first. |
| `caller-not-delegate` | The wallet is not a supply delegate with that permission. |
| `setup-incomplete` | The supply delegate feature or the collateral rule is missing. |

API v1, batch runs, sale funding and agent mandates cannot carry a reference, so a mirror token refuses them.

In the Console, the Mint and Burn sheets on a mirror token show where the holding sits and require the external reference. The token page shows a Held at card.

## Reading the reference back [#reading-the-reference-back]

The reference appears on the movement it belongs to:

- [Webhooks](https://docs.settlemint.com/docs/api-reference/webhooks/events): `token.transfer.*`, `asset.issuance.*` and `asset.burn.final` carry `externalReference`.
- Activity: `GET /api/v2/tokens/{assetId}/events` and its export add an `externalReference` value to the supply delegate mint or burn, for a caller with a role on the token. Other callers see the movement without it.

Investors see the location as text: the holding page in the Portal shows "Held at", and the positions and portfolio tables of a PDF statement name it on a full-width line under the instrument.

## Corporate actions wait for reconciliation [#corporate-actions-wait-for-reconciliation]

A corporate action pays or allots the holders it counts on its record date. On a mirror token those holders must match the custodian's or registrar's record. A reconciliation break on the token, first seen on or before the record date, that is still open or under investigation, therefore holds the corporate action's snapshot: preview is refused with `reconciliation-break-open` and nothing is stored. A corporate action without a record date counts the day preview runs.

The snapshot goes ahead once every such break is resolved or written off. Either change goes through the break's own approval, so a second operator checks it when your organisation requires maker-checker. A break first seen after the record date does not hold the snapshot. A token that is not a mirror token is never held.

## Position statements [#position-statements]

The custodian's or registrar's position statement is the record a mirror token is compared with. Send it to DALP once per business date.

`PUT /api/v2/reporting/mirror-reconciliations/{tokenAddress}/configuration` names the statement's layout and the cut-off of the day. The layout is a named CSV profile: its delimiter (comma, semicolon or tab) and the headers of the columns naming the wallet, the quantity and, when the file has one, the ISIN. The cut-off is a local time with an IANA timezone. A token nobody configured reads the default profile `dalp-registrar-v1`, with the columns `holderWallet` and `quantity`, and has no cut-off. Only the organisation owner or a platform administrator can change the configuration.

`POST /api/v2/reporting/mirror-reconciliations/{tokenAddress}/statements` stores the statement of a date, in the CSV text your profile describes. Each changed text of a date becomes that date's next numbered version, and every version keeps the profile it was read with. Sending the text of the date's latest version again adds nothing and answers that version, so a retried upload is safe.

A file the profile cannot read stores nothing. The answer is `Rejected`, with the refusal and the line:

| Refusal | What it means |
| --- | --- |
| `empty-file` | The file has no header row. |
| `missing-column` | The file lacks a column the profile names. `column` says which. |
| `invalid-wallet` | A row's wallet is not a readable address. |
| `invalid-quantity` | A row's quantity is not a number the security's decimals can hold. |
| `duplicate-wallet` | A wallet appears on two rows. |
| `isin-mismatch` | A row names another ISIN than the one recorded on the token. |
| `malformed-quote` | A quote character sits inside an unquoted cell. |
| `unterminated-quote` | A quoted cell is never closed. |

`GET /api/v2/reporting/mirror-reconciliations/{tokenAddress}/statements` lists the stored versions, newest date and version first.

## Daily reconciliation [#daily-reconciliation]

Once a mirror token has a cut-off, DALP reconciles it every day. At the cut-off it reads the token's holders at the last final block, the latest statement version of that date, the supply and the trusted collateral claim, and compares them:

- each investor's quantity on chain with the quantity on the statement, line by line.
- the statement total with the supply and with the collateral claim.

If a daylight-saving change repeats the local cut-off time, DALP uses its second occurrence. If the change skips that time, DALP moves the cut-off forward by the clock change, so a 02:30 cut-off in Brussels becomes 03:30. The clock change does not skip the day's reconciliation.

The run of the date is `clear` when everything matches, `break` when anything does not, and `no-statement` when no statement of the date was stored by the cut-off. A `no-statement` run is never clear. It raises a case for your operations team.

Each investor line that does not match opens a break in the reconciliation break register, with its ageing, under the asset key `registrar:<token address>`. A statement total that differs from the supply opens a break on `supply:<token address>`, and one that differs from the collateral claim opens a break on `collateral-claim:<token address>`, under the same asset key. A later run where the line or the total matches clears its break. The first run of a date reads only the statement versions stored by the cut-off, so a statement that arrives late still leaves a `no-statement` run. A statement stored after the run, or a new version of it, gives the date a new numbered run.

`GET /api/v2/reporting/mirror-reconciliations/{tokenAddress}/runs` lists the runs with their outcome and number of breaks. `GET .../runs/{runDate}` returns the date's latest run with its totals and every line that does not match, and `GET .../runs/{runDate}/file` exports those lines as CSV. Both take `?version=` to read an earlier run of the date.

In the Console, the holder register's export sheet has a **Reconciliation** tab on a mirror token, with the runs, the lines of each run and a CSV export. A reporting obligation can include the `mirror-reconciliation` report, which lists each run in its period with every line that does not match.

## Settlement feed [#settlement-feed]

DALP sends the custodian or registrar every settled movement on a mirror token: each venue fill, the bust of a settled trade, each mint and burn, and each corporate-action movement. A movement's value date is the date of its settlement block in the token's cut-off timezone.

Each record carries the movement, the trade for a fill or a bust, the settlement transaction, the parties, the quantity, the unit price for a fill or a bust, and the value date. The parties are the wallets' OnchainID addresses. The feed never carries national ID numbers or other personal data.

Once a value date has closed in the token's timezone and its last block is final, DALP writes the day's file and sends one `reporting.mirror-movement.final` webhook per movement. The file lists the movements in settlement order, so the same movements always give the same bytes. A changed day becomes the date's next file version. A movement keeps the same `movementId`, so a repeated delivery of its webhook is the same movement.

`GET /api/v2/reporting/mirror-reconciliations/{tokenAddress}/settlement-feeds` lists the files. `GET .../settlement-feeds/{valueDate}/file` downloads one, with `?version=` for an earlier version. `POST .../settlement-feeds/{valueDate}/replays` rebuilds the day from the chain and says whether it equals the stored file. It stores nothing.
