A covenant rarely has one thing to do. A vault might allow deposit, withdraw, and recover; a token needs a leader path and a delegate path. All of them live in one program R, because R is what the P2SH output committed to.
So the program needs a way to be told which branch to run, and the caller needs a way to say it. That is dispatch.
Entrypoints
An entrypoint is a named, externally invocable branch of a covenant program with an ordered list of argument types. The Program ABI identifies it by two things, both of which are part of its identity:
- its case-sensitive name
- its ordered argument types
An invocation is the entrypoint arguments, the optional dispatch tag, and the covenant program, all supplied in the spending input’s signature script.
Dispatch tags
KCC1 derives a four-byte tag from the entrypoint’s name and signature:
FunctionSignature = UTF8("{name}({comma-separated canonical type names})")
dispatch_tag = Hash(FunctionSignature)[0:4] Hash is BLAKE3 with 32-byte output, and the tag is its first four bytes. The rules on the preimage are strict:
- Types are comma-separated inside parentheses.
- No whitespace, no length field, no terminator.
- A zero-argument entrypoint ends with
().
The type names must be the canonical spellings from The Value ABI. byte[] is not bytes; byte[32] is not byte[ 32 ].
FunctionSignature = UTF8("step(int,byte[4],bool,byte)")
dispatch_tag = 2c49ed65 Some more, computed the same way:
| FunctionSignature | dispatch_tag |
|---|---|
step() | 4cc16552 |
reset() | d1830c7b |
step(int,byte[4],bool,byte) | 2c49ed65 |
transfer(State[],sig[],byte[]) | 283386b0 |
Note the first two: same program, different names, completely unrelated tags. And note the third — step() and step(int,byte[4],bool,byte) share a name but are different entrypoints with different tags. The signature is the identity, not the name.
Uniqueness is the producer’s job
All dispatch tags in a multi-entrypoint program must be distinct, and a program producer must reject a program whose entrypoints collide. This covers both cases:
- two different signatures whose BLAKE3 prefixes happen to collide, and
- two entrypoints that produce the identical preimage (same name, same types).
Four bytes is short enough that this is a real check, not a formality. It is enforced at build time because at spend time a colliding tag is simply ambiguous — the script would run whichever branch it tested first.
The invocation layout
From the envelope page, the signature script must be push-only and shaped:
PushArguments(arguments)
[OP_DATA_4 dispatch_tag]
PushMinimal(R) The tag element follows one rule with no exceptions:
| Entrypoints in the program | Dispatch tag |
|---|---|
| exactly 1 | must be omitted |
| 2 or more | must be present, as OP_DATA_4 ‖ 4 tag bytes |
Omitting it for a single-entrypoint program is not an optimization you may skip — it is required. There is nothing to select, so the tag would be dead weight that the program would have to drop.
Because PushMinimal(R) is last, it is the first thing the P2SH hash check consumes — which leaves the arguments and the tag sitting on the stack exactly where the program expects them.
What this looks like compiled
KCC1 says what the caller supplies; it does not dictate how the program branches internally. Here is how the SilverScript compiler does it, from a real compile of a two-entrypoint counter:
Two design points are worth extracting, because they generalize beyond this compiler:
The state pushes run on every path. They are not inside any branch. That is what lets the state occupy one contiguous, predictable byte range regardless of which entrypoint is invoked — the property the templates page depends on.
Dispatch bytecode may live in the template prefix. KCC1 explicitly permits this: a producer may place dispatch-preservation bytecode before or after the state range, provided the declared range and stack behaviour stay correct. Here OpToAltStack is that bytecode, and it is why the state starts at offset 1 instead of 0.
Compare with the single-entrypoint build of the same contract, where there is no selector to preserve and the state starts at offset 0:
Current compiler divergence
The SilverScript compiler pushes a numeric branch index (0, 1, 2 …) as the selector, not a KCC1 four-byte dispatch tag. In the artifact above, branch 0 is step and branch 1 is reset, selected with OP_0 and OP_1.
KCC1 specifies OP_DATA_4 followed by Hash(FunctionSignature)[0:4].
Both are still Draft, and the two schemes trade off differently:
| Numeric selector | KCC1 dispatch tag | |
|---|---|---|
| Size | 1 byte | 5 bytes |
| Stable across edits | No — reordering entrypoints changes the index | Yes — derived from name and types |
| Meaningful without the ABI | No | Somewhat, given a signature guess |
If you are writing tooling today, read the selector convention from the compiled artifact rather than assuming either scheme.
Next
State, Templates & Continuations — how that contiguous state range is defined, hashed, and rewritten to build a successor.