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

# Pause / Unpause

> Guide to pausing and unpausing an instrument in the Registry App.

> [!NOTE]
>
> This feature is introduced with version `0.15` of the Registry App.
>
> Version `0.9.0` of the utility-registry-v0 package added the underlying `isPaused` field to the
> `InstrumentConfiguration`.
>
> A paused instrument cannot be interacted with by non-registrar parties, with narrow exceptions
> where the registrar itself is the counterparty. The registrar can still administer the
> instrument configuration (including unrelated fields) while it is paused.

## Overview

Each `InstrumentConfiguration` carries an `isPaused : Optional Paused` field. `Some Paused` marks
the instrument paused; `None` marks it unpaused (the default for newly created instruments).

```mermaid theme={null}
stateDiagram-v2
  [*] --> Unpaused
  Unpaused --> Paused: Registrar archives and recreates<br/>with isPaused = Some Paused
  Paused --> Unpaused: Registrar archives and recreates<br/>with isPaused = None
  Paused --> Paused: Registrar modifies unrelated fields
```

There is no dedicated pause/unpause choice. Instead, the registrar toggles `isPaused` through the
same `RegistrarService_ArchiveAndCreateInstrumentConfiguration` choice used for any other
configuration change — archiving the current `InstrumentConfiguration` and creating a replacement
with the new `isPaused` value. Every other field (identifiers, credential requirements, block list
setting, provider app reward beneficiaries) except the force transfer setting can be changed in the
same call, so pausing an instrument doesn't prevent the registrar from administering it.

> [!NOTE]
>
> Pausing is scoped to a single instrument. Other instruments under the same registrar are
> unaffected.

## Pausing and unpausing

> [tab] dApp UI
>
> **1. Open instrument management**
>
> As the registrar, navigate to **Registry → Management → Instrument Management** and
> select the instrument.
>
> **2. Open the modify dialog**
>
> Click **Modify** on the instrument's details.
>
> **3. Toggle Pause Instrument**
>
> Check (or uncheck) **Pause Instrument**. Other fields on the same form can be changed at
> the same time.
>
> **4. Submit**
>
> Click **Modify** to submit. On success, the UI shows "Successfully modified instrument
> configuration" and the instrument's new pause state is reflected immediately.

> [tab] API
>
> > [!NOTE]
> >
> > These ledger API calls are intended to be driven by a customer-owned component that pauses
> > and unpauses instruments on a chosen schedule (e.g. to reflect market holidays or
> > non-trading hours), rather than invoked manually.
>
> **1. Look up the current configuration**
>
> As the registrar, fetch the instrument's current contract id and payload from the registry
> backend's instrument configuration endpoint, rather than querying the ledger directly:
>
> ```bash theme={null}
> CONFIG_RESPONSE=$(curl -s -X POST "${REGISTRY_BACKEND_URL}/api/utilities/v0/contract/instrument-configuration" \
>   -H "authorization: Bearer ${JWT_TOKEN}" \
>   -H "content-type: application/json" \
>   --data-raw '{
>     "registrar": "'"${REGISTRAR_PARTY}"'",
>     "instrumentIdentifier": {
>       "source": "'"${REGISTRAR_PARTY}"'",
>       "id": "'"${INSTRUMENT_ID}"'",
>       "scheme": "'"${INSTRUMENT_SCHEME}"'"
>     }
>   }')
> ```
>
> `contract.contractId` is the `InstrumentConfiguration` contract id to pass as `cid` below,
> and `payload` gives you `defaultIdentifier.id`, `additionalIdentifiers`,
> `issuerRequirements`, and `holderRequirements` to carry over unchanged:
>
> ```bash theme={null}
> INSTRUMENT_CONFIGURATION_CID=$(echo "$CONFIG_RESPONSE" | jq -r '.contract.contractId')
> CURRENT_INSTRUMENT_ID=$(echo "$CONFIG_RESPONSE" | jq -r '.payload.defaultIdentifier.id')
> CURRENT_ADDITIONAL_IDENTIFIERS=$(echo "$CONFIG_RESPONSE" | jq -c '.payload.additionalIdentifiers')
> CURRENT_ISSUER_REQUIREMENTS=$(echo "$CONFIG_RESPONSE" | jq -c '.payload.issuerRequirements')
> CURRENT_HOLDER_REQUIREMENTS=$(echo "$CONFIG_RESPONSE" | jq -c '.payload.holderRequirements')
> ```
>
> > [!NOTE]
> >
> > `issuerRequirements` and `holderRequirements` come back from this endpoint with each
> > `requiredClaims` entry shaped as `{"property": ..., "value": ...}` — the backend's own
> > DTO encoding. The raw ledger API command below expects the underlying Daml tuple
> > encoding instead, `{"_1": ..., "_2": ...}`. Convert both before splicing them into the
> > command in the next step:
> >
> > ```bash theme={null}
> > CURRENT_ISSUER_REQUIREMENTS=$(echo "$CURRENT_ISSUER_REQUIREMENTS" | jq -c '
> >   map(.requiredClaims |= map({"_1": .property, "_2": .value}))
> > ')
> > CURRENT_HOLDER_REQUIREMENTS=$(echo "$CURRENT_HOLDER_REQUIREMENTS" | jq -c '
> >   map(.requiredClaims |= map({"_1": .property, "_2": .value}))
> > ')
> > ```
>
> To confirm the instrument's current pause state, query the public token-standard metadata
> endpoint, which exposes it directly as `paused`:
>
> ```bash theme={null}
> curl -s "${REGISTRY_BACKEND_URL}/api/token-standard/v0/registrars/${REGISTRAR_PARTY}/registry/metadata/v1/instruments/${INSTRUMENT_ID}"
> ```
>
> > [!WARNING]
> >
> > Neither endpoint returns `useBlockList` or `forceTransferEnabled` — source their
> > current values separately (e.g. from the dApp UI's instrument details panel) so you
> > don't inadvertently reset them when submitting the choice below.
>
> You'll also need the `RegistrarService` contract id to exercise the choice on. There's no
> dedicated backend endpoint for it, so query the ledger directly: first get the current
> ledger end offset, then look up the registrar's active `RegistrarService` contract as of
> that offset.
>
> ```bash theme={null}
> LEDGER_END=$(curl -s "${JSON_API_URL}/v2/state/ledger-end" \
>   -H "authorization: Bearer ${JWT_TOKEN}" | jq .offset)
>
> REGISTRAR_SERVICE_CID=$(curl -s -X POST "${JSON_API_URL}/v2/state/active-contracts" \
>   -H "authorization: Bearer ${JWT_TOKEN}" \
>   -H "content-type: application/json" \
>   --data-raw '{
>     "eventFormat": {
>       "filtersByParty": {
>         "'"${REGISTRAR_PARTY}"'": {
>           "cumulative": [
>             {
>               "identifierFilter": {
>                 "TemplateFilter": {
>                   "value": {
>                     "templateId": "#utility-registry-app-v0:Utility.Registry.App.V0.Service.Registrar:RegistrarService",
>                     "includeCreatedEventBlob": false
>                   }
>                 }
>               }
>             }
>           ]
>         }
>       },
>       "verbose": false
>     },
>     "activeAtOffset": '"${LEDGER_END}"'
>   }' | jq -r --arg registrar_party "${REGISTRAR_PARTY}" \
>     '.[] | select(.contractEntry.JsActiveContract.createdEvent.createArgument.registrar == $registrar_party)
>       | .contractEntry.JsActiveContract.createdEvent.contractId')
> ```
>
> **2. Exercise RegistrarService\_ArchiveAndCreateInstrumentConfiguration**
>
> As the registrar, exercise the choice on the `RegistrarService` contract, passing the
> current `InstrumentConfiguration` contract id as `cid` and a `payload` that copies every
> field from the existing configuration unchanged, except `isPaused`, which is set to `{}`
> (an empty `Paused` record, i.e. `Some Paused`) to pause, or `null` (`None`) to unpause.
> `forceTransferEnabled` must be passed through exactly as it currently is — the choice
> rejects any attempt to change it in the same call.
>
> > [!NOTE]
> >
> > `CURRENT_FORCE_TRANSFER_ENABLED` should be `null` or `true`, and `CURRENT_USE_BLOCK_LIST`
> > should be `null`, `true`, or `false` — unlike `isPaused`, neither uses `{}`.
>
> > [!NOTE]
> >
> > `commandId` is generated with `uuidgen`, so it must be reachable on your `PATH` (it ships
> > by default on macOS and most Linux distributions; otherwise substitute any unique string).
>
> To **pause** the instrument:
>
> ```bash theme={null}
> RESPONSE=$(curl -s -X POST "${JSON_API_URL}/v2/commands/submit-and-wait-for-transaction" \
>   -H "authorization: Bearer ${JWT_TOKEN}" \
>   -H "content-type: application/json" \
>   --data-raw '{
>     "commands": {
>       "commands": [
>         {
>           "ExerciseCommand": {
>             "templateId": "#utility-registry-app-v0:Utility.Registry.App.V0.Service.Registrar:RegistrarService",
>             "contractId": "'"${REGISTRAR_SERVICE_CID}"'",
>             "choice": "RegistrarService_ArchiveAndCreateInstrumentConfiguration",
>             "choiceArgument": {
>               "cid": "'"${INSTRUMENT_CONFIGURATION_CID}"'",
>               "payload": {
>                 "instrumentId": "'"${CURRENT_INSTRUMENT_ID}"'",
>                 "additionalIdentifiers": '"${CURRENT_ADDITIONAL_IDENTIFIERS}"',
>                 "issuerRequirements": '"${CURRENT_ISSUER_REQUIREMENTS}"',
>                 "holderRequirements": '"${CURRENT_HOLDER_REQUIREMENTS}"',
>                 "useBlockList": '"${CURRENT_USE_BLOCK_LIST}"',
>                 "isPaused": {},
>                 "forceTransferEnabled": '"${CURRENT_FORCE_TRANSFER_ENABLED}"'
>               }
>             }
>           }
>         }
>       ],
>       "workflowId": "",
>       "userId": "'"${USER_ID}"'",
>       "commandId": "'"$(uuidgen)"'",
>       "actAs": ["'"${REGISTRAR_PARTY}"'"],
>       "readAs": []
>     }
>   }')
> ```
>
> To **unpause**, resubmit the same command with `"isPaused": null` in place of
> `"isPaused": {}` — every other field (including `forceTransferEnabled`) stays the same.
>
> **3. Track the replacement contract id**
>
> The choice archives the old `InstrumentConfiguration` and creates a new one. Since
> `submit-and-wait-for-transaction` returns the transaction's event list rather than the
> exercise result directly, pull the new contract id out of the `CreatedEvent` for
> `InstrumentConfiguration`:
>
> ```bash theme={null}
> INSTRUMENT_CONFIGURATION_CID=$(echo "$RESPONSE" | jq -r '
>   .transaction.events[]
>   | .CreatedEvent
>   | select(. != null)
>   | select(.templateId | test(":Utility.Registry.V0.Configuration.Instrument:InstrumentConfiguration$"))
>   | .contractId
> ')
> ```
>
> Use it for subsequent choices, since the old contract id no longer exists.
>
> > [!NOTE]
> >
> > This mirrors what the dApp UI does internally: the **Modify** dialog's "Pause Instrument"
> > checkbox toggles the same `isPaused` field and submits it through the same
> > `RegistrarService_ArchiveAndCreateInstrumentConfiguration` choice, carrying over every other
> > field from the current configuration (see `ModifyInstrumentConfigurationForm` in the
> > registry frontend).

## Enforcement while paused

Pause enforcement is applied at the point where a non-registrar party would act on the instrument.
Requests, offers/acceptances, allocations, and two-step/self transfers all check
`isInstrumentPaused` before proceeding:

```mermaid theme={null}
sequenceDiagram
  participant Holder as Issuer / Holder
  participant Ledger
  participant Config as InstrumentConfiguration

  Holder->>Ledger: Request/offer/accept/transfer
  Ledger->>Config: Fetch instrument configuration
  Config-->>Ledger: isPaused = Some Paused
  Ledger--xHolder: Rejected: "Instrument must not be paused..."
```

| Flow | Choice | Who is checked |
| - | - | - |
| Mint | `acceptMint` (accept request/offer) | Rejected unless `holder == admin` (registrar) |
| Mint | `AllocationFactory_RequestMint` | Rejected unless `holder == registrar` |
| Burn | `validateBurn` (accept request/offer) | Rejected unless `holder == admin` (registrar) |
| Burn | `AllocationFactory_RequestBurn` | Rejected unless `holder == registrar` |
| Burn/Mint | `AllocationFactory_InternalBurnMint` | Checked independently for each burn input owner and each mint output owner; the registrar's own holdings are exempt |
| Allocation (V1) | `AllocationFactory_AllocateInternal` | Rejected unless `sender == registrar` |
| Allocation (V1 accept) | Transfer rule allocation accept | Rejected unless `receiver == registrar`; the registrar as sender does not exempt a non-registrar receiver |
| Two-step / self-transfer | `AllocationFactory` transfer/self-transfer flows | Rejected unless `sender == admin` (registrar) |

> [!WARNING]
>
> The registrar is only exempt where it is itself the party being checked (holder, sender, or
> owner). A non-registrar counterparty is always blocked while the instrument is paused — for
> example, a registrar-initiated transfer offer can still be created while paused, but a
> non-registrar receiver cannot accept a paused instrument's allocation.

## Error messages

| Scenario | Error |
| - | - |
| Requesting a mint while paused | `Instrument must not be paused to request a mint` |
| Accepting a mint while paused | `Instrument must not be paused to accept a mint` |
| Requesting a burn while paused | `Instrument must not be paused to request a burn` |
| Accepting a burn request/offer while paused | `Instrument must not be paused to accept a burn request/offer` |
| Two-step / self-transfer while paused | `Instrument must not be paused in two step and self transfers` |
| Allocating internally while paused | `Instrument must not be paused in allocate internal` |
| Burn-mint internally while paused | `Instrument must not be paused in internal burn mint` |
| Accepting a V1 allocation while paused | `Instrument must not be paused in allocation V1` |

## Backwards compatibility

> [!NOTE]
>
> Parties don't need to be running the latest app version to be protected by a pause. Pausing
> blocks an instrument for everyone, regardless of which package version a counterparty's client
> was built against.

The `isPaused` field and its enforcement were introduced in version `0.10.0` of the
`utility-registry-app-v0` package, but all versions of a package are required to be Smart Contract
Upgrade (SCU) compatible with one another. In practice this means:

* A counterparty submitting commands from an older, pre-`0.10.0` client still has its choices
  (`acceptMint`, `AllocationFactory_RequestBurn`, transfer/self-transfer flows, and so on) run
  against the currently vetted package on the participant, which is the one that contains the
  `isInstrumentPaused` check — not the older package the client code was compiled against.
* There is no old-client code path that bypasses the pause check: the instrument configuration
  payload is read fresh on every choice exercise, so an older client cannot "see" a stale,
  unpaused configuration.
* Older clients that have no concept of `isPaused` simply have no way to unpause an instrument
  themselves — only a registrar exercising
  `RegistrarService_ArchiveAndCreateInstrumentConfiguration` (via a client that supports the
  field) can change the pause state.

## Related

* [Blocklist](https://docs.digitalasset.com/registry/guides/blocklist.md) — another instrument-level restriction enforced the same
  way, via `checkBlockList` on `InstrumentConfiguration`.
