> For the complete documentation index, see [llms.txt](https://docs.bitsafe.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bitsafe.finance/decentralization-manager/operations-and-reference/reward-automation.md).

# Reward automation

Assign the CIP-104 reward coupons of a Decentralized Party to a governance-approved split, with one vote and no vote per round.

[Governance lifecycle](/decentralization-manager/concepts-and-development/governance-lifecycle.md) · [Security architecture](/decentralization-manager/operations-and-reference/security-architecture.md)

## What it does

Under CIP-104, an application provider earns traffic-based rewards as `RewardCouponV2` coupons. When a Decentralized Party is the provider, each coupon names the party as `provider` and has no beneficiary. A coupon expires after about 36 hours. A Decentralized Party has no wallet automation. Its coupons therefore expire when nobody uses them in time.

Reward automation solves this in two parts:

1. The members vote once to create a `CouponReassignmentDelegation` contract. The contract holds a fixed beneficiary split and a list of assigners.
2. Each Decentralization Manager node runs a background loop. On each sweep, an assigner node assigns every unassigned coupon to the split. The assignment needs no vote.

After the assignment, each beneficiary holds its own coupons. Each beneficiary must mint its coupons before they expire. Decentralization Manager does not mint for the beneficiaries.

{% hint style="info" %}
The `governance-rewards-automation-v1` package also has a minting mode with the actions **Setup Minting Delegation** and **Accept External Party Setup**. In that mode, a validator mints the coupons for the Decentralized Party. Do not use both modes for one Decentralized Party, because they compete for the same coupons.
{% endhint %}

## Before you start

* The Decentralized Party has an active `GovernanceRules` contract.
* Every participant has the `governance-rewards-automation-v1` DAR, uploaded and vetted. The repository publishes it as `releases/v1/governance-rewards-automation-v1-0.1.0.dar`. [Package and contract operations](/decentralization-manager/operations-and-reference/package-and-contract-operations.md) describes the upload.
* Every participant has the Splice packages that the DAR depends on, including `splice-amulet` and `splice-api-reward-assignment-v1`.
* The participant of each assigner hosts the Decentralized Party. Otherwise the assigner cannot read the coupons.
* Each assigner node has party credentials for the Decentralized Party. [Authentication and operator roles](/decentralization-manager/operations-and-reference/authentication-and-operator-roles.md) describes the credentials.
* Each beneficiary has a wallet or an agent that mints its coupons.

## Set up the delegation

Create the delegation with a governance proposal. [Complete your first governance action](/decentralization-manager/get-started/complete-your-first-governance-action.md) describes the propose, confirm and execute steps.

{% stepper %}
{% step %}

### Open the proposal form

In **Parties**, select the Decentralized Party. Then select **New Proposal**.
{% endstep %}

{% step %}

### Select the action

In **Proposal Type**, under **Rewards**, select **Setup Coupon Reassignment Delegation**.
{% endstep %}

{% step %}

### Enter the delegation

* **DSO Party:** the form fills in the DSO party of the network. Check the value.
* **Replaces Delegation:** leave it empty for the first delegation.
* **Assigners:** add each member party that can run the assignment with **Add Assigner**. Any one assigner is enough.
* **Beneficiary split:** add each beneficiary with **Add Beneficiary**. Enter a whole-number **Weight** for each beneficiary.
  {% endstep %}

{% step %}

### Submit and execute

Select **Submit Proposal**. The members confirm the proposal in **Approvals**. A member then executes it.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
Check the **DSO Party** value before you submit. Any party can create a coupon that names itself as the DSO. The automation therefore ignores coupons from every other DSO. A wrong value assigns nothing and reports no error.
{% endhint %}

The API equivalent is `POST /governance/propose`. `GET /network-info` returns the DSO party of the network as `dso_party_id`.

```bash
curl -X POST "https://<decentralization-manager-host>/governance/propose" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "party_id": "<decentralized-party-id>",
    "rules_contract_id": "<governance-rules-contract-id>",
    "proposal": {
      "type": "setup_coupon_reassignment_delegation",
      "dso": "<dso-party-id>",
      "assigners": ["<member-party-id-1>", "<member-party-id-2>"],
      "new_beneficiaries": [
        {"beneficiary": "<beneficiary-party-id-1>", "percentage": "0.8"},
        {"beneficiary": "<beneficiary-party-id-2>", "percentage": "0.2"}
      ],
      "prior_delegation": null
    }
  }'
```

| Field               | Meaning                                                                             |
| ------------------- | ----------------------------------------------------------------------------------- |
| `dso`               | The DSO party whose coupons the delegation can assign.                              |
| `assigners`         | The member parties that can run the assignment. Any one of them is enough.          |
| `new_beneficiaries` | The fixed split. Each entry names a party and its `percentage` as a decimal string. |
| `prior_delegation`  | The contract ID of the delegation to replace, or `null` for the first delegation.   |

### Split rules

Decentralization Manager checks these rules when you propose. The Daml action checks them again at execution.

* The split has at least 1 and at most 20 beneficiaries.
* Each percentage is greater than 0 and at most 1.
* No beneficiary appears twice.
* The percentages sum to exactly 1.0. The ledger compares exact decimals. Three shares of `0.3333333333` therefore fail. Balance the last share by hand, for example `0.3333333334`.
* The Decentralized Party keeps no share by default. To keep a share, add the Decentralized Party as a beneficiary.
* The assigner list has at least one party, and no party appears twice.

The web form takes whole-number weights. It converts the weights to exact percentages and gives the rounding remainder to the largest weight. A confirmer can then reproduce the split from the weights.

### Change or revoke the delegation

* **Change the split or the assigners:** propose **Setup Coupon Reassignment Delegation** again, and select the active delegation in **Replaces Delegation**. The execution archives the old delegation and creates the new one in one transaction.
* **Stop the automation:** propose **Revoke Coupon Reassignment Delegation**, and select the delegation in **Delegation Contract ID**. The API type is `revoke_coupon_reassignment_delegation` with the field `delegation`.

Only a governance vote can replace or revoke a delegation. A proposal with an empty **Replaces Delegation** fails with HTTP 409 while a delegation is active. The same proposal fails with HTTP 503 if the node cannot read the active delegations.

## How the automation works

Every Decentralization Manager node runs the reward loop. No variable turns it on or off. An active delegation on the ledger turns it on for a Decentralized Party.

1. The loop wakes every 60 seconds, or more often if a reward interval is shorter. Each wake counts one heartbeat.
2. Every `DECPM_REWARD_AUTOMATION_INTERVAL_SECS`, the loop sweeps each Decentralized Party that the node has party credentials for.
3. The loop reads the active delegation of the party. It skips a party with no delegation.
4. The loop checks that the delegation names the member party of this node as an assigner. If not, the node reads the backlog for the expiry gauge only. It also logs a warning.
5. The loop reads the unassigned coupons of the party. It skips the coupons that expire within `DECPM_REWARD_MIN_EXPIRY_MARGIN_SECS`. It sorts the rest by expiry, soonest first.
6. The loop assigns all of these coupons with `Delegation_Assign` transactions. Each transaction carries at most `DECPM_REWARD_MAX_CREATES` divided by the beneficiary count, rounded down, with a minimum of one coupon.

Between sweeps, the node reads the backlog every `DECPM_REWARD_EXPIRY_READ_INTERVAL_SECS`. This read refreshes the expiry gauge and assigns nothing.

The loop handles failures in two ways:

* **Contention or a network timeout:** the node ends the sweep. The next sweep reads the ledger again. Several assigners often compete for the same coupons. This case is therefore normal.
* **Any other ledger rejection:** the node sends the coupons of that transaction again, one coupon at a time. It skips each coupon that the ledger still rejects and logs it at the `ERROR` level. The next sweep tries the skipped coupons again.

If the participant has no rewards package, the automation stays off for that participant. It logs this state at the `TRACE` level only.

## Identity checks

Any one assigner can reassign all unassigned coupons of the party at any time. The split in the delegation is the only possible destination. These checks keep the assignment on the correct contracts:

| Check                                                                                                                                                                                                                                       | Where                                                  | Result of a failed check                                                                      |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| The delegation names this Decentralized Party in its `decparty` field. If several delegations are active, the newest one applies.                                                                                                           | Decentralization Manager, when it reads the delegation | The node ignores the other delegations and logs a warning when several are active.            |
| The member party of this node is in `assigners`. The comparison uses the full party ID, so a party with the same name or namespace does not match.                                                                                          | Decentralization Manager                               | The node reports the backlog and assigns nothing.                                             |
| The delegation template is `Governance.Rewards.CouponReassignmentDelegation:CouponReassignmentDelegation` in the configured `governance-rewards-automation-v1` package. The node reads the template from the ledger before each assignment. | Decentralization Manager                               | The node refuses the delegation and ends the sweep. The log shows `refusing delegation from`. |
| Each coupon comes from `Splice.Amulet:RewardCouponV2`. It names the Decentralized Party as `provider` and the delegation DSO as `dso`, and it has no beneficiary.                                                                           | Decentralization Manager, when it reads the coupons    | The node does not assign the coupon.                                                          |
| The `assigner` in the choice is in `assigners`.                                                                                                                                                                                             | `Delegation_Assign` in Daml                            | The ledger rejects the transaction.                                                           |
| Each coupon is a `RewardCouponV2` with the DSO of the delegation.                                                                                                                                                                           | `Delegation_Assign` in Daml                            | The ledger rejects the transaction.                                                           |
| The new beneficiaries are the split in the contract. The caller cannot supply them.                                                                                                                                                         | `Delegation_Assign` in Daml                            | Not applicable. The choice has no beneficiary argument.                                       |

## Configuration

These variables control the timing and the size of the assignments. The [Configuration reference](/decentralization-manager/operations-and-reference/configuration-reference.md) lists every `DECPM_*` variable.

| Variable                                 | Default | Purpose                                                                                                                                                                                                   |
| ---------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DECPM_REWARD_AUTOMATION_INTERVAL_SECS`  | `300`   | Seconds between sweeps. This value sets the cadence only. A sweep assigns every assignable coupon, so the interval changes latency, not throughput. A value of `0` runs as `1` with a warning.            |
| `DECPM_REWARD_EXPIRY_READ_INTERVAL_SECS` | `3600`  | Seconds between backlog reads that only refresh `decman_reward_oldest_unassigned_expires_in_seconds`. The gauge refreshes at the shorter of the two intervals. A value of `0` runs as `1` with a warning. |
| `DECPM_REWARD_MAX_CREATES`               | `100`   | Maximum output contracts that one `Delegation_Assign` creates. Lower it if assignments start to fail.                                                                                                     |
| `DECPM_REWARD_MIN_EXPIRY_MARGIN_SECS`    | `120`   | Seconds that a coupon must have left before expiry to be assigned. The margin keeps a coupon from expiring during the submission.                                                                         |

The metrics in the next section use the metrics port `DECPM_METRICS_PORT`, `9464` by default. The metrics listener has no authentication and binds to `DECPM_HOST`.

## Verify that it works

1. Check the delegation. The first entry is the delegation that the automation uses.

   ```bash
   curl -s -H "Authorization: Bearer <token>" \
     "https://<decentralization-manager-host>/coupon-reassignment-delegation?party_id=<decentralized-party-id>"
   ```

   The response lists `cid`, `dso`, `assigners` and `beneficiary_count` for each active delegation. It normally holds one entry.
2. Check the reward metrics on each assigner node.

   ```bash
   curl -s "http://<node-address>:9464/metrics" | grep '^decman_reward_'
   ```
3. Confirm that `decman_reward_heartbeat_total` increases and that `decman_reward_sweep_total` increases once per sweep interval.
4. Confirm that `decman_reward_coupons_assigned_total` increases on at least one assigner node when the party earns coupons. The log shows `reassigned coupon batch` with the count.
5. Check that `decman_reward_oldest_unassigned_expires_in_seconds` stays well above zero. The series disappears when the party has no unassigned coupons.

### Reward metrics

Each metric except the heartbeat has the label `decparty`.

| Metric                                               | Meaning                                                                                 |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `decman_reward_heartbeat_total`                      | Wakes of the reward loop.                                                               |
| `decman_reward_sweep_total`                          | Sweeps started for the party.                                                           |
| `decman_reward_coupons_assigned_total`               | Coupons that this node assigned.                                                        |
| `decman_reward_coupons_skipped_total`                | Coupons that the ledger rejected one at a time. The next sweep tries them again.        |
| `decman_reward_sweep_zero_assigned_total`            | Sweeps that found assignable coupons and assigned none.                                 |
| `decman_reward_sweep_failed_total`                   | Sweeps that ended in an error.                                                          |
| `decman_reward_expiry_read_total`                    | Backlog reads that only refreshed the expiry gauge.                                     |
| `decman_reward_credentials_unavailable_total`        | Passes in which the token request failed for a party whose delegation names this node.  |
| `decman_reward_auth_unregistered_total`              | Passes that found no authentication entry for a party whose delegation names this node. |
| `decman_reward_oldest_unassigned_expires_in_seconds` | Seconds until the oldest unassigned coupon expires, from the last read.                 |

Useful alerts include a heartbeat that does not increase for 15 minutes, and an expiry gauge below a margin that you choose. [Monitoring and logging](/decentralization-manager/operations-and-reference/monitoring-and-logging.md) describes the metrics setup.

### Troubleshooting

| Symptom                                                                                            | Cause and action                                                                                                                                            |
| -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No `decman_reward_sweep_total` series for the party                                                | The node has no party credentials for the party. Configure the credentials.                                                                                 |
| Sweeps run, and the log shows `the delegation does not name this node as an assigner`              | The member party of this node is not an assigner. Replace the delegation with a corrected assigner list.                                                    |
| Sweeps run without errors, and no coupon is ever assigned                                          | No delegation is active, the DSO party in the delegation is wrong, or the participant has no rewards package. Check the delegation and the vetted packages. |
| `decman_reward_sweep_zero_assigned_total` increases                                                | The node found coupons and assigned none. Look for `assign chunk failed` or `coupon rejected on its own` in the log.                                        |
| `decman_reward_coupons_skipped_total` increases                                                    | The ledger rejected some coupons. The `ERROR` log lines name each coupon and the error ID.                                                                  |
| `decman_reward_credentials_unavailable_total` or `decman_reward_auth_unregistered_total` increases | The node cannot get a token for the party. Fix the party credentials.                                                                                       |
| The log shows `refusing delegation from`                                                           | The active delegation does not come from the configured package. Replace it through governance.                                                             |
| The proposal fails with HTTP 409                                                                   | A delegation is already active. Select it in **Replaces Delegation**.                                                                                       |

## Limits

* The automation sees only the coupons that list the Decentralized Party as an observer. The DSO decides this when it creates a coupon. An invisible coupon looks the same as a coupon that the party never earned.
* A split has at most 20 beneficiaries.
* Canton cannot enforce one delegation for each party. If several delegations are active, the automation uses the newest one.
* The automation does not mint. Each beneficiary must mint its coupons before they expire.

## Technical sources

* [Rewards automation package](https://github.com/DLC-link/decentralization-manager/blob/main/daml/governance-rewards-automation/README.md)
* [`CouponReassignmentDelegation` template](https://github.com/DLC-link/decentralization-manager/blob/main/daml/governance-rewards-automation/daml/Governance/Rewards/CouponReassignmentDelegation.daml)
* [`SetupCouponReassignmentDelegation` action](https://github.com/DLC-link/decentralization-manager/blob/main/daml/governance-rewards-automation/daml/Governance/Rewards/SetupCouponReassignmentDelegation.daml)
* [Reward automation loop](https://github.com/DLC-link/decentralization-manager/blob/main/crates/decman/src/server/reward_automation.rs)
* [Proposal validation](https://github.com/DLC-link/decentralization-manager/blob/main/crates/decman-lib/src/framework/validate.rs)
* [Environment variables](https://github.com/DLC-link/decentralization-manager/blob/main/README.md#environment-variables)

## Next step

👉 **Watch the automation:** [Monitoring and logging](/decentralization-manager/operations-and-reference/monitoring-and-logging.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.bitsafe.finance/decentralization-manager/operations-and-reference/reward-automation.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
