Kaspalytics Learn is a work in progress.
Updated: Aug 2, 2026

Leaders & Delegators

How KCC1 coordinates a transition when several covenant instances participate — choosing the right output context, assigning leader and delegator roles by input position, and why delegation is contract-wide.

Covenant IDs give a lineage an identity that consensus tracks. The moment more than one instance of that lineage is spent in a single transaction, a coordination problem appears that consensus does not solve for you: every input runs its own script, and each one is asked to approve the same transition.

Left alone, that goes wrong in two directions. Every input redundantly re-validating everything is expensive and easy to get subtly inconsistent. Every input validating only its own slice is worse — it can be individually correct and collectively broken.

KCC1 §9 settles both with a convention.

Picking the right context

The two scopes consensus exposes — local authorized-output, and shared Covenant ID — are not interchangeable. The rule KCC1 states:

When validation is scoped to one authorizing input, the covenant must use that input’s authorized-output context. When validation is scoped to all participants sharing a Covenant ID, it must use the shared Covenant ID output context.

The failure mode is asymmetric, which is why it matters. A covenant that validates only its local authorized outputs, while the protocol’s rule is actually about the whole covenant group, will pass each input individually and still let the transaction as a whole break the invariant. Three inputs each correctly checking their own slice can collectively mint tokens if nobody checks the total.

The reverse mistake — using the shared context for a genuinely local rule — is merely wasteful rather than unsound, so in practice the dangerous direction is only one.

Roles by position

Rather than have every participant re-derive who is responsible for what, KCC1 assigns roles by position:

In the shared context for the active Covenant ID, the first input is the leader and every later input is a delegator. Since the context is ordered by input index, the leader is the lowest-indexed input carrying that Covenant ID. A one-input group has a leader and no delegators.

Inputs (covenant A)
Input 0 — LEADER amount 400
lowest index carrying covenant A
validates the entire transition
Input 1 — delegator amount 600
joins the leader’s transition
checks only that it is not input 0
shared transition
Outputs (covenant A)
Output 0 amount 1000
covenant binding → A
validated by the leader

Every participating input takes the role fixed by its position. Each path must reject the opposite position.

A merge: two token UTXOs consolidated into one. Only input 0 does the real work.

The three rules:

  1. Position fixes the role. The first input takes the leader path; every later input takes a delegator path. Each path must reject the opposite position — a leader entrypoint running at input 3 must fail, and a delegator entrypoint running at input 0 must fail.
  2. The leader validates the complete shared transition — participant and continuation cardinality, coverage, and authentication of every program, template, or state that validation relies on.
  3. Each delegator validates any protocol-specific local conditions needed to rely safely on the leader.

Membership needs no separate check when the active Covenant ID comes from the active input: the shared context includes that input by definition.

What the checks look like

Here is the shape the SilverScript compiler generates for a covenant-bound declaration. The leader:

entry __leader_transition(State[] new_states, sig leader_sig) {
    byte[32] cov_id = OpInputCovenantId(this.activeInputIndex);

    int in_count  = OpCovInputCount(cov_id);
    int out_count = OpCovOutputCount(cov_id);
    require(out_count == new_states.length);

    // k = 0 must be this input, or we are not the leader
    require(OpCovInputIdx(cov_id, 0) == this.activeInputIndex);

    // gather every participating input's prior state
    State[] prev_states = [];
    for (k, 0, in_count, max_ins) {
        int in_idx = OpCovInputIdx(cov_id, k);
        prev_states = prev_states.append(readInputState(in_idx));
    }

    __policy(prev_states, new_states, leader_sig);

    // validate every continuation output
    for (k, 0, out_count, max_outs) {
        validateOutputState(OpCovOutputIdx(cov_id, k), new_states[k]);
    }
}

And the delegator, which is almost nothing:

entry __delegate_transition() {
    byte[32] cov_id = OpInputCovenantId(this.activeInputIndex);
    // must NOT be the leader position
    require(OpCovInputIdx(cov_id, 0) != this.activeInputIndex);
}
Executes
input 0 runs __leader_transition
reads all inputs, runs policy, validates all outputs
complete transition validated
input 1 runs __delegate_transition
confirms it is not at covenant-input position 0
deferred to leader
consensus
both inputs must succeed for the tx to be valid
transition accepted
The delegator's single check is what makes the division safe: it guarantees a leader exists at position 0 and that this input is not it.

The delegator looks almost vacuous, and that is the point worth sitting with. It does not verify the policy — it verifies that somebody at position 0 was obliged to. Since every input’s script must succeed for the transaction to be valid, and input 0 can only succeed by running the full leader validation, the delegator’s one-line check is enough.

Why delegation is contract-wide

There is a trap here that KCC1 does not spell out but SilverScript’s design notes do, and it is worth understanding because it explains an otherwise puzzling compiler restriction.

A generated delegate authenticates the contract at input 0, but it cannot tell which entrypoint that input selected. If the same contract also exposed an authorized-output-bound entrypoint, that entrypoint could occupy input 0 — satisfying every delegator’s check — while never validating the complete covenant group.

The delegators would all defer to a leader that was not actually acting as one.

SilverScript therefore rejects contracts that mix authorized-output-bound and covenant-bound declarations: any contract with a covenant-bound declaration is a leader contract, and all its covenant declarations must be covenant-bound. A hand-written entrypoint in a leader contract must explicitly pick one of three roles:

  1. reject shared execution entirely, by requiring OpCovInputCount(cov_id) == 1;
  2. act as a delegate, by rejecting covenant input 0; or
  3. act as a leader, by requiring covenant input 0 and validating the complete group.

The compiler cannot verify hand-written covenant-group logic, so it requires an explicit acknowledgment attribute instead of silently trusting it.

Single-group enforcement

One more check worth knowing. A covenant using the local authorized-output context can additionally require that its Covenant ID has exactly one continuation group in the transaction:

byte[32] cov_id = OpInputCovenantId(this.activeInputIndex);
require(OpCovOutputCount(cov_id) == OpAuthOutputCount(this.activeInputIndex));

This says: every output carrying my Covenant ID is authorized by me. Without it, a transaction could contain several independent authorization groups for the same covenant, each locally valid.

No separate validity check on cov_id is needed — OpCovOutputCount fails if the value is not valid covenant-id data.

Next

Control Principals — KCC2, and how an ABI declares who controls a covenant.

References

Kaspalytics strives to provide accurate data - our highest level of effort is given to data validation and maintenance. However, we cannot guarantee 100% accuracy. Data is subject to inaccuracies and change.

Contact | © 2026 Kaspalytics