Kaspalytics Learn is under (heavy) construction. Check back regularly!
Updated: Aug 14, 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. When more than one instance of that lineage is spent in a single transaction, a coordination problem appears that consensus does not solve: every input runs its own script, and each one is asked to approve the same transition.

Without a convention, this goes wrong in two directions. Every input redundantly re-validating everything is expensive and easy to make inconsistent. Every input validating only its own slice is worse - each can be individually correct while the group is 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. A covenant that validates only its local authorized outputs, when the protocol’s rule is about the whole covenant group, passes each input individually while the transaction as a whole breaks the invariant. Three inputs each correctly checking their own slice can collectively mint tokens if none checks the total.

The reverse mistake - using the shared context for a local rule - is wasteful but not unsound.

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

The following is the structure 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]);
    }
}

The delegator is much smaller:

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 does not verify the policy - it verifies that the input 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 check is sufficient.

Why delegation is contract-wide

KCC1 does not spell out the following issue, but SilverScript’s design notes do, and it explains a 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

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 requires that every output carrying the covenant’s ID is authorized by this input. 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