Safety & owner model
Kairence is non-custodial smart contracts plus an off-chain backend for operational convenience. The custodial layer is the smart contracts — your DIEM, DD, PT20, PT721, and KAI all live on-chain.
This page documents what the protocol's owner can and cannot do, where the 7-day timelock applies, and what happens in failure modes.
Owner role
The deployer initially owns every admin-bearing contract. Pre-mainnet the owner is a single deployer EOA; before launch, ownership is transferred to a Gnosis Safe multisig via TransferOwnership.s.sol.
The owner can:
| Action | Constraint | Risk if compromised |
|---|---|---|
Rotate Locker reference in Vault | 7-day timelock + cross-check | Old Locker breaks; user funds safe |
Rotate UnstakeQueue reference in Vault | 7-day timelock + cross-check | Old queue breaks; user funds safe |
Add/remove Locker in kDIEM allowlist | 7-day timelock + cross-check | New Locker can mint kDIEM (and ultimately drain DIEM through PT20.deposit + maturity); 7d gives users time to exit |
Add/remove Redeemer in kDIEM allowlist | 7-day timelock + cross-check | Limited — kDIEM.burn requires from == msg.sender, so a hostile redeemer can only burn its own balance |
Add/remove Minter in DD allowlist | 7-day timelock | New minter can inflate DD; 7d gives users time to exit |
Rotate Redeemer in Queue | 7-day timelock + cross-check | New redeemer can request fake unstakes; 7d window for users to exit |
Rotate Locker in DDMinter | 7-day timelock | Old DDMinter stops being callable by new Locker |
Rotate Locker in PT20Factory | 7-day timelock | Only affects PTs deployed AFTER rotation; old PTs unchanged |
Rotate Locker in PT721 | 7-day timelock | New Locker can mint new NFTs; old NFTs unaffected |
| Pause DD minting | Instant | Stops new locks from minting DD; transfers/burns unaffected |
| Pause Locker | Instant | Stops new locks; existing positions unaffected |
| Pause Redeemer | Instant | Stops new redeems; existing pending claims unaffected |
| Register / deregister a platform in DailyAuction | Instant | Affects new bids only |
Set backend address in DailyAuction | Instant | Can change who calls markKeyIssued; doesn't affect bid funds |
Set kaiPerDD[platform] (leftover KAI fee rate) | Instant, capped at MAX_KAI_PER_DD = 1 KAI/DD | Operational fee. Worst case: owner raises fee to the cap, making buyLeftover expensive; bid auction unaffected |
armEmergency(recipient) | 7-day wait before executeEmergencyInitiate | Owner can eventually drain all DIEM to recipient |
The owner cannot:
- Withdraw user PT20/PT721 balances directly.
- Mint DD without going through DDMinter (which requires being on DD's allowlist).
- Mint kDIEM without being on kDIEM's locker allowlist.
- Force-claim someone else's queued DIEM.
- Override an active timelocked proposal without going through cancel + re-propose (which restarts the 7-day clock).
The 7-day timelock pattern
Every wiring change uses the same shape:
// Day 0
owner.proposeXxx(newAddress);
// → emits Proposed event, records effectiveAt = now + 7 days
// Days 0..6
// Users see the event in indexers, can decide whether to exit.
// Owner can owner.cancelXxxChange() during this window.
// Day 7+
anyone.activateXxx();
// → applies the change, clears pendingThis means:
- A compromised owner cannot drain instantly. Worst case, they propose changes; you have 7 days to exit any way you can (DEX sell of PT20, redeem at maturity if applicable, etc.).
- Activation is permissionless. Once the 7 days pass, anyone can call activate. If the owner is gone, the change still happens.
- Cancellation is owner-only. Useful for "oops, wrong address" situations.
Wiring cross-checks
Every propose* and setXxxInitial does a sanity check against the target's own state:
Vault.setLockerInitial(L)requiresLocker(L).vault() == address(this).Vault.setUnstakeQueueInitial(Q)requiresQueue(Q).vault() == address(this).kDIEM.addLockerInitial(L)requiresLocker(L).kDiem() == address(this).kDIEM.addRedeemerInitial(R)requiresRedeemer(R).kDiem() == address(this).Queue.setRedeemerInitial(R)requiresRedeemer(R).unstakeQueue() == address(this).- (Plus
proposeXxxvariants do the same check.)
Cross-checks do not protect against a malicious owner — they protect against deployment typos. A malicious owner can simply deploy a new contract with correct getters and propose it.
Emergency drain
Three-step procedure, total minimum elapsed time 7 days + Venice cooldown (≈1 day) = ~8 days:
1. owner.armEmergency(recipient)
→ records emergencyArmedAt = now
→ emits EmergencyArmed event with effectiveAt = now + 7 days
2. wait 7 days
(users see the event, can exit through normal flows)
3. owner.executeEmergencyInitiate()
→ flips emergencyInitiated = true
→ blocks all normal flows (stake, initiateUnstake, claimUnstake, transferOut)
→ calls Venice initiateUnstake on the full staked balance
4. wait Venice cooldown (~1 day)
5. owner.executeEmergencyDrain()
→ calls Venice unstake (drains staked DIEM into Vault)
→ transfers FULL balance (staked-drained + any free DIEM) to recipientcancelEmergency() is callable by owner during step 2 only.
This exists as a true last resort — e.g., a critical bug is discovered that puts user funds at risk and the protocol needs to be evacuated to a recovery address. It is owner-centralization by design; it is the strongest argument for the owner being a multisig with diverse signers.
Backend trust model
The backend operator (separate from contract owner) has exactly one on-chain power: auction.markKeyIssued(platform, day, bidIndex). This sets bid.keyIssuedAt so winners and indexers know the Venice API key was issued.
A compromised backend:
- Can: Falsely mark keys as issued without actually issuing a Venice key. Winners burn DD/KAI without getting inference. This is operational loss for winners on the affected day.
- Cannot: Drain anyone's DIEM, mint DD or KAI, override settlement, or affect refunds.
Mitigations:
- HSM/KMS storage of the backend private key.
- Owner can rotate the backend EOA instantly via
setBackend(newBackend). - Monitoring + alerts on
markKeyIssuedevents to detect inconsistencies.
Pause semantics summary
| Contract | What pause blocks | What it does NOT block |
|---|---|---|
| DD | mint() | transfer, burn, approve |
| Locker | lockPT20(), lockPT721() | n/a |
| Redeemer | redeemAndRequestUnstake(), redeemPT721AndRequestUnstake() | n/a |
Existing positions remain transferable and burnable during a pause — users can always exit even while issuance is frozen.
Defense-in-depth: ReentrancyGuard
All state-mutating external functions in Vault, Queue, and Auction are nonReentrant. DIEM and DD/KAI tokens don't have hooks today, but the guard closes a whole class of issues if a future integration ever introduces a token with callback behavior.
Audits and tests
- Unit tests: 157 tests in
test/unit/+ 22 intest/core/, all green. - Foundry invariants: 5 invariants × 128 000 random calls each — zero failures.
- Echidna fuzz: 50 100 calls × 2 property checks — zero failures.
- Slither static analysis: 45 findings, all false positives or by design.
- Aderyn static analysis: 2 High + 7 Low, all false positives or by design.
- External audit: Pending pre-mainnet engagement.
- Bug bounty: Will be opened on Immunefi at mainnet launch.
See the contracts overview for per-contract notes.