Skip to content

Reference ​

Status: testnet only, not audited yet.

You are about to call Knot from your own contract or tool. Call the API. The book is storage, and it only accepts the API that Atlas resolves for that service name.

This table lists the methods that work on Dusk today.

Live ids: Deployments. Versions and redeploys: versioning.md.

Deploy the book and the API, then the owner calls init_data with the book id. The same id may be retried. A different book panics. That call is the owner key itself: a contract inside the owner's transaction cannot make it.

Registry ​

The registry answers one question: did enough current members sign this message? It does not hold funds.

MethodWhoReturnsEffect
init_dataOwner, direct, oncePoints the API at its book
create_accountAnyonegroup idNew group
account / next_account_idAnyonethe group, or the next idRead
verify_quorumAnyoneboolChecks each signature
verify_quorum_aggregateAnyoneboolChecks one combined signature
change_accountQuorum of current membersNew members and threshold
set_timelockQuorum of current membersThis group's delay
cancel_pendingQuorumDrops a scheduled change now
execute_pendingAnyone, after the waitApplies the scheduled change

diagnose_quorum is not on chain. It lives in the Lab.

EventWhen
data_setFirst init_data
account_createdcreate_account
pending_scheduledA delayed membership or timelock change
account_changedThe new members are stored
timelock_setThe new delay is stored
pending_cancelledcancel_pending

Delay 0 emits the applied event only, not also pending_scheduled.

Proposals ​

A proposal is one call: target, function, arguments, deadline. The API asks the registry who the members are.

After init_data, the owner calls init_registry with the registry API, then set_authorized_account with the one group that may propose, finalize, or execute. Each set_authorized_account bumps auth_generation and retires open and queued proposals of the old binding.

MethodWhoEffect
init_dataOwner, direct, oncePoints the API at its book
init_registryOwner, directRegistry API. Bumps epoch. Clears the bound group
set_authorized_accountOwner, directThe one group that may propose, finalize, or execute
set_proposal_ttl / set_tombstoneOwner, directConfig. proposal_ttl is from 1 to 100,000 blocks
proposeAnyone, for the bound groupOthers are rejected
approveA memberStores one signature. Does not check generation
finalizeAnyone, once threshold is metRuns now, or queues
executeAnyone, after execute_atRuns a queued proposal
cancelCurrent members, quorumDrops a queued proposal now. Open proposals expire instead
pruneAnyoneprune(0) removes nothing. Each call reads a bounded page
proposal / status / epochAnyoneRead

Finalize refuses its own contract and its book as a target. If your contract is the target, check the caller. The snippet is on Integrate.

EventCarries
proposal_createdId, digest, account, deadline, target, function, arguments
proposal_approvedId, digest, signer, signature
proposal_finalized / proposal_queuedThe call, and execute_at when queued
proposal_cancelledId, digest, account
authorized_account_setAccount and the new generation
prunedProposal ids removed, and digest keys removed

prune still returns how many proposals were removed. A later call can remove expired digests and emit pruned with a return count of 0.

Limits ​

  • 1 to 16 members in a group.
  • Function name at most 64 bytes. Arguments at most 4096 bytes.
  • proposal_ttl at most 100,000 blocks.

Call types ​

knot-encoding features: call-types for the argument structs, contract-events for event payloads, data-driver for serde on those structs. The two you need first are SignatureEntry and VerifyQuorumArgs. Also CreateAccountArgs, MultisigAccountView, and VerifyQuorumAggregateArgs.

Signed bytes ​

The Lab and both APIs hash the same bytes. The digest is Keccak-256 of a flat preimage. Full 32 bytes, never truncated.

A proposal (proposal_digest_v3), little-endian lengths and integers:

text
domain          nocturne.knot.multisig.proposal.v3
chain_id        u64
self_id         32 bytes     the proposals API
epoch           u64
committee_id    u64
nonce           u64
target          32 bytes
fn_len          u32
function_name   bytes
args_len        u32
call_args       bytes
deadline        u64

Changing members (change_account_digest_v3):

text
domain          nocturne.knot.multisig-registry.change_account.v3
chain_id        u64
self_id         32 bytes     the registry API
account_id      u64
nonce           u64
member_count    u32
member_pks      96 bytes each, in order
threshold       u32

Set-timelock and cancel layouts use these domains. The byte layout is the same length-prefixed shape as the others, in knot-encoding.

ActionDomain
Proposalnocturne.knot.multisig.proposal.v3
Change membersnocturne.knot.multisig-registry.change_account.v3
Set the account delaynocturne.knot.multisig-registry.set_timelock.v1
Cancel a pending membership changenocturne.knot.multisig-registry.cancel_pending.v1
Cancel a queued proposalnocturne.knot.multisig.proposal.cancel.v1

Member order is part of the change_account digest. auth_generation is not inside the digest. Finalize reads it from the book and compares it to the generation stored when the proposal was opened.

Warden signs nothing. See Warden.