Governance and Security Model

Acta separates cold governance from hot operations. The cold authority controls protocol configuration and the timelock; the hot wallet runs routine market and oracle operations within those limits. The guardian is an emergency canceller/pause key.

Roles

Cold authority (ACTA_ADMIN)

The cold authority is the hardcoded public key CLSYf1AL9rXYjGbSjvexMRegriqpLR37kmLkcqvAgBnN. In production this is intended to be a Squads v4 vault PDA. Acta authorization is a pure signer check: if the transaction signer equals ACTA_ADMIN, the cold action is authorized. Squads supplies that signer via its own M-of-N approval flow.

Cold authority operations:

  • initialize the global config
  • initialize, update, and close premium configs through the timelock
  • register makers through the timelock
  • create and update oracle sources through the timelock
  • raise or keep the global timelock delay immediately
  • lower the global timelock delay only through the timelock
  • rotate the hot wallet immediately
  • set, rotate, or clear the guardian immediately
  • set, rotate, or revoke the settlement attestor immediately
  • toggle pause immediately
  • queue and execute pending actions
  • cancel pending actions

The cold authority is separate from the program upgrade authority.

Hot wallet (GlobalConfig.hot_authority)

The hot wallet is the online key used by backend services. It is stored in GlobalConfig.hot_authority and can be rotated by the cold authority without a redeploy.

Hot wallet operations:

  • create, finalize, and close markets
  • create and close concrete oracle accounts from already-approved oracle sources
  • publish a post-expiry scalar settlement price to an oracle account. UpdateOraclePrice checks the live hot_authority, and works for the hot wallet only while no settlement attestor is configured: once GlobalConfig.settlement_attestor is set, direct hot publication is rejected on-chain (SettlementAttestationRequired, 1090) and settlement requires UpdateOraclePriceAttested with an Ed25519 attestation from that key — a 2-of-2 of hot key and attestor key. The cold authority retains a break-glass direct path
  • co-sign OpenPosition
  • withdraw collected protocol fees

The hot wallet cannot modify cold configuration, rotate itself, bypass maker quote signatures, or bypass taker transaction signatures.

Guardian (GlobalConfig.guardian)

The guardian is an optional emergency key. It defaults to unset; the default public key does not authorize anyone. The cold authority sets, rotates, or clears it with SetGuardian.

Guardian operations:

  • cancel any queued pending action before execution
  • engage the emergency pause (releasing it / unpause is cold-only)

The guardian cannot queue or execute pending actions, rotate itself, rotate the hot wallet, unpause, or perform hot/cold configuration changes. A guardian is a single trusted key: if compromised it can only grief (veto-spam queued actions, re-engage the pause) and the cold authority recovers by rotating it with SetGuardian (immediate).

Permissionless exits

Settlement and liquidation are permissionless. Any signer may settle an expired position or liquidate an ITM unfunded position. Pause does not block exits.

Authority Matrix

ActionAuthorityTiming
Program upgradeDedicated program-upgrade Squads 3-of-4 vaultSquads delay ≥24h on mainnet
Initialize global configColdImmediate
Rotate hot walletColdImmediate
Set / rotate / clear guardian (SetGuardian, op 27)ColdImmediate
Engage emergency pause (SetPause, op 28)Cold or guardianImmediate
Release emergency pause / unpause (SetPause, op 28)ColdImmediate
Queue pending action (QueuePendingAction, op 23)ColdImmediate queue
Execute pending action (ExecutePendingAction, op 25)ColdAfter delay
Cancel pending action (CancelPendingAction, op 24)Cold or guardianBefore execute
Raise / keep timelock delay (UpdateActionTimelock, op 26)ColdImmediate
Lower timelock delay (UpdateActionTimelock, op 26)ColdTimelocked by current delay
Initialize / update / close premium configColdTimelocked
Register makerColdTimelocked
Create / update oracle sourceColdTimelocked
Create / finalize / close marketHot walletImmediate
Create / close concrete oracleHot walletImmediate
Publish settlement price (UpdateOraclePrice, op 16)Hot wallet (only while no attestor is set) or coldImmediate after expiry
Publish attested settlement price (UpdateOraclePriceAttested, op 31)Hot wallet + attestor Ed25519 proof (2-of-2)After expiry, attestor set
Set / rotate settlement attestor (SetSettlementAttestor, op 29)ColdImmediate
Revoke settlement attestor (RevokeSettlementAttestor, op 30)ColdImmediate
Withdraw protocol feesHot walletImmediate
Open positionHot wallet + taker tx signer + maker Ed25519 quoteImmediate
Settle positionPermissionlessAfter market finalization
Liquidate positionPermissionlessITM + unfunded

Timelock

Acta uses one global delay: GlobalConfig.timelock_secs. It starts at 0, should be raised after deployment, and is capped at 7 days. There are no per-kind delay slots and no GlobalConfig.timelock_secs[0..N] array.

The timelock uses a generic store-and-replay model. Queue stores a commitment to the exact wrapped instruction; execute replays the same wrapped instruction after execute_at.

Timelockable wrapped opcodes:

OpcodeInstruction
0InitializeConfig
1UpdateConfig
2RegisterMaker
17CreateOracleSource
18UpdateOracleSource
20CloseConfig
26UpdateActionTimelock

In production, the first six reject direct calls with DirectCallDisabled; they must go through queue -> execute. UpdateActionTimelock is special: raising or keeping the delay is immediate; lowering it must be queued and waited out under the current delay.

Wire Shape

QueuePendingAction data is [23, wrapped_opcode, ...wrapped_args] and accounts are [admin, pending_action_pda, system_program, global_config_pda, ...wrapped_accounts].

ExecutePendingAction data is [25, wrapped_opcode, ...wrapped_args] and accounts are [admin, pending_action_pda, ...wrapped_accounts].

The commitment binds:

sha256(
  "acta:pend.v2"
  || wrapped_opcode
  || ordered wrapped account keys
  || full wrapped instruction data
)

Client note: generated Codama builders for QueuePendingAction and ExecutePendingAction expose only the wrapped opcode, so they are not sufficient for real generic timelock payloads. Use the SDK flow helpers or manually build the full wrapped wire layout above.

Emergency Pause

SetPause sets the PAUSED bit in GlobalConfig.flags. Engaging the pause is immediate for cold or guardian; releasing it (unpause) is cold-only, so a compromised guardian cannot unpause to resume an attack.

While paused, OpenPosition fails with ProtocolPaused. Settlement, liquidation, and other exit paths remain available.

Settlement Attestation

GlobalConfig.settlement_attestor optionally names an independent Ed25519 second signer for hot settlement publication. While it is unset (all-zero), UpdateOraclePrice behaves as described above. Once set (SetSettlementAttestor, cold-only), the hot wallet's direct call fails with SettlementAttestationRequired (1090); settlement then requires UpdateOraclePriceAttested, where an Ed25519 verify instruction signed by the attestor over a domain-separated message must immediately precede the update in the same transaction. The cold authority keeps the direct path as break-glass (Ed25519 precompile instructions cannot be produced through Squads CPI). RevokeSettlementAttestor (cold-only) clears the key and deliberately returns hot settlement to direct mode; the 32-byte replay domain is retained so signatures can never be replayed across deployments.

Squads Integration

In the target deployment, ACTA_ADMIN is a protocol-cold Squads 3-of-4 vault PDA. Squads handles approvals and member rotation; Acta checks the vault signature. The Acta timelock delays sensitive actions, and the guardian can cancel them. A second Squads vault controls program upgrades.

Program Upgrade

The program upgrade authority is a separate Squads 3-of-4 vault. On mainnet it has a delay of at least 24 hours. Upgrades use a verified buffer and Squads Programs. Initial mainnet authority transfer uses Safe Authority Transfer.

Not Implemented

  • No per-kind timelock array.
  • No AddPremiumToken instruction. Supported premium mints are represented by premium config PDAs created through InitializeConfig.
  • No global-config close instruction. CloseConfig closes a premium config.
  • No on-chain Squads threshold introspection. The threshold lives in Squads state.