Logging System
Architecture
Section titled “Architecture”| Canister | MemId | Log type | Capacity / rotation | Size |
|---|---|---|---|---|
| Backend | 8,9 | CriticalLogEntry | 50K max (MAX_CRITICAL_LOGS), FIFO | ~10-25 MB |
| Proposal Mgr | 7 | EventRecord | 10K max, prune | ~2-5 MB |
| LGE Gov | 4,5 | EventRecord | 10K max, prune | ~2-5 MB |
| Pool Mgr | 18,19 | PoolEventRecord | 10K max, FIFO | ~2-5 MB |
| DAO Storage | 18,19 | StorageEventRecord | 10K max, FIFO | ~2-5 MB |
Total stable log overhead: ~18-45 MB across all 5 canister types (under 0.01% of the 500 GiB stable memory limit of any one canister).
Three-tier logging across all canisters:
| Tier | Persistence | Capacity | Purpose |
|---|---|---|---|
| IC system log | Kept by the network; survives upgrades, deleted on reinstall or uninstall | log_memory_limit per canister (network default 4096 bytes; oldest lines dropped first) | Diagnostics: every println! and every trap message, read through the management canister fetch_canister_logs |
| Heap Logs | Lost on upgrade | Variable per canister | Real-time campaign/operation tracking |
| Stable Memory Logs | Persistent across upgrades | 10,000 entries FIFO per canister (50,000 on the backend) | Audit trail, monitoring, DAO transparency |
All stable log query endpoints are public (no caller restriction on query calls) for DAO transparency. The IC system log of every canister listed here has its log_visibility set to public, so anyone can read it through the IC management canister; see The IC system log below.
The IC system log
Section titled “The IC system log”The IC system log (also called the canister log, or “Native IC Logs” on the status pages) is kept by the Internet Computer itself, outside the canister’s own memory. Its rules come from the network, not from OhShii code:
- What goes in: every
ic_cdk::println!line and every trap message. A trap is recorded as[TRAP]: <message>; a Rustpanic!leaves two lines,Panicked at …and[TRAP]: Panicked at …. - What does not go in: anything printed while a method runs in non-replicated mode — a plain query
call, a composite query and its callbacks,
inspect_message, and cleanup callbacks. Aprintln!inside a query is recorded only when that query runs replicated (called as an update, or by another canister). A call refused ininspect_messagetherefore leaves no line in any log. - Writes from non-replicated code are discarded: a query or
inspect_messagecannot change canister state, so a stable log row or a counter written from one of them is dropped with the rest of that message’s state. - Size: set per canister by the
log_memory_limitsetting (0, or 4096 bytes up to 2 MiB). The network default is 4096 bytes and the oldest lines are dropped first, so at the default a busy canister keeps only minutes of lines and a quiet one keeps days. The memory reserved for the log counts toward the canister’s memory usage. - Lifetime: the log survives upgrades; it is deleted when the canister is reinstalled or uninstalled (a canister that runs out of cycles is uninstalled).
- Visibility: the
log_visibilitysetting iscontrollers(the network default),public, or a list of up to 10 allowed viewers. The launcher canisters (backend, OHSHII Governance, Pool Manager, DAO Storage) have it set topublicin their canister settings, and each LGE Governance canister sets its own log topublicafter every upgrade. Because these logs are public, the text a canister prints or traps with is public text. - Reading: the management canister’s
fetch_canister_logsquery (filterable by index or by timestamp), for exampledfx canister logs <canister-id> --network ic --identity anonymousfor a public log. Each line carries a monotonic index that keeps growing across upgrades. The status pages show the same lines under “Native IC Logs”.
What each launcher canister writes there: the backend prints every message it also stores through
log_and_store (the stable critical log below), plus diagnostic lines of its own; OHSHII Governance,
LGE Governance, the Pool Manager and DAO Storage print diagnostic lines tagged with the canister or
function name. Because the log is public, it carries only real events, refusals, errors and trap
messages: no debug lines, no line of their own from queries or inspect_message,
and no line for a timer tick that found no work, except a skip caused by a real condition (low cycles,
the previous tick still running). A new token’s ledger, index and LGE Governance
(SONS) ids are withheld from the public records from creation or relaunch until the ICPSwap pool is created (the SONS id
too, because its public configuration query returns the ledger id). In that window the lines the creation flow writes in
the backend, the Pool Manager, DAO Storage and OHSHII Governance carry no such id: where a line names one of these canisters (the owners of the new token’s initial balances, the archive controllers), it shows only the shortened <ledger-redacted:…> form. The IC system log is a
diagnostic window, not a record: the stable
logs below and the canisters’ own records are the durable trail.
Replicated query refusal: every public query, including the log readers on this
page, answers query calls from anyone. When another canister calls it from an update method or a timer (a replicated
call), it answers only the OhShii canisters that read it; any other caller is refused with a trap whose message starts with
OHSHII_QUERY_REPLICATED_REFUSED canister=<id> method=<name> caller=<principal>, so the refusal is
visible in the IC system log of the refusing canister. An ingress update call naming a query
method is refused earlier, in inspect_message, and leaves no line.
Stable Memory Logs — Per Canister
Section titled “Stable Memory Logs — Per Canister”Every canister records each of its upgrades in the stable log the System Status page shows: the launcher backend, ONS, pool_manager and dao_storage (like the Locker’s canisters) as a row UPGRADE v=<canister_version>, and each campaign governance canister through its own SystemLifecycle event with event_type = "upgraded" (this event carries no version number and an empty campaign_id). canister_version is the system counter of the canister: it rises with each upgrade, but also with other changes to the canister such as its settings or controllers, so the number is not a count of upgrades.
Backend (CriticalLogEntry)
Section titled “Backend (CriticalLogEntry)”-
Storage:
StableBTreeMap(MemoryId 8), counter inStableCell(MemoryId 9) -
Capacity: 50,000 entries max (
MAX_CRITICAL_LOGS) -
Rotation: once the ring reaches 50,000 rows, the next insert removes the oldest rows above the cap plus 100 more
-
Safety: Skips the stable write (the line still goes to the IC system log) when the canister’s stable memory exceeds 95% of 4 GiB (about 3.8 GiB) — a fixed threshold, not a fraction of the 500 GiB limit
-
Timer: 300s (5 min) housekeeping cycle
-
Fields:
timestamp,payment_memo,campaign_id,user_principal,message,error_code,log_level -
Levels:
error,warn,success,info(auto-inferred from message keywords bylog_and_store). Call sites that pass the level explicitly (log_and_store_with_level,log_escrow_latch) can also writecritical(money in an ambiguous state, or a broken invariant that needs attention) and, at some older sites,warning(read aswarn) -
Error codes: Auto-extracted from message patterns (
GOVERNANCE_CREATION_FAILED,TRANSFER_FAILED,SECURITY_*, etc.). The purchase flow emits one explicit forensic code:Code Written by Meaning Action ICP_RECEIVEDpurchase_tokens_icrc2aftericrc2_transfer_fromOk and BEFOREdao_storage.add_contribution_atomic(src/backend/src/purchase.rs)ICP has landed in the backend canister. Message body carries block=N amount_e8s=X contribution_id=Y. Thecontribution_idis deterministic on(user, campaign, amount, client_nonce)and matchesdao_storage::Contribution.contribution_idonce applied.None on the happy path — the entry is observability only. Recovery is fully on-chain (see below); there is no off-chain reconciler. Orphan recovery is on-chain. Purchases use per-contribution escrow custody, so a deposit that landed but never became a contribution is recoverable on-chain without any external job:
finalizereconciles unforwarded escrows into the campaign sub-account, and a deposit still held in its escrow is returned to the buyer bysweep_escrow_refund(see WORKFLOWS.md). TheICP_RECEIVEDlog is purely an audit/observability aid — anyone can match ablock/contribution_idagainstdao_storage.lookup_contribution(contribution_id)(a free query) — it is not consumed by any off-chain server. On-chain escrow state is the source of truth; there is no off-chain reconciler (see WORKFLOWS.md and ARCHITECTURE.md). -
Upgrade row: every upgrade writes one
inforowUPGRADE v=<canister_version>into the critical log the System Status page shows (get_public_logs) -
Key functions (in
lib.rs/memory.rs):log_and_store(msg)— println + infer level + store stable + route to heaplog_and_store_with_level(msg, level)— same with explicit levelstore_critical_log(entry)— direct stable insert (used from modules outside lib.rs, e.g. security.rs)log_security_event(type, details)— security audit withSECURITY_*error codes, persists to stableroute_to_heap_logs(msg, campaign_id)— routes to ICO_LOGS or RECENT_LOGS based on message prefix
API endpoints:
| Method | Signature | Access | Notes |
|---|---|---|---|
get_critical_logs | (limit: opt nat64) -> vec CriticalLogEntry | Public | Most recent N (at most 3000), oldest first; no limit (null) returns the newest 3000 (older rows: get_public_logs_paginated) |
get_critical_log_count | () -> nat64 | Public | Total stable log count |
get_public_logs | (limit: opt nat64) -> vec CriticalLogEntry | Public | Most recent N (default 50, max 3000) |
get_public_log_count | () -> nat64 | Public | Same as get_critical_log_count |
get_public_logs_paginated | (start: nat64, limit: nat64) -> (vec CriticalLogEntry, nat64) | Public | Paginated (max 3000/page), returns (entries, total) |
get_campaign_finalize_logs | (campaign_id: text, limit: opt nat64) -> vec CriticalLogEntry | Public | Newest-first finalization timeline for one campaign (filters the stable store to the [finalize_step] / [finalize_ico] / [post_finalization] tags; default 50, max 200) |
get_rate_limit_events | (limit: nat64) -> RateLimitEventPage | Public | Heap storage, newest N (max 500), plus total_count (rows the ring holds), truncated and ring_capacity |
get_campaign_logs | (campaign_id: text, user: opt principal) -> vec IcoLogEntry | Public | Heap storage: the campaign’s bucket (at most 100 rows, kept for one hour), optionally filtered by user |
Finalization step telemetry ([finalize_step]): every LGE finalization pass writes one stable entry per pipeline milestone — state flip to Finalizing, escrow reconcile, existing-position pre-gate, pool verify-or-create (the pool itself is born at LGE creation; at finalization the pool_create milestone reuses it and runs the liquidity leg — pool_manager deposits the raise from the campaign subaccount inside this step; there is no separate drain step) / add-liquidity on an existing pool, pool_id persistence, position verification, post-finalization (vesting locks, position transfer, governance handoff), terminal Completed commit, rollback to Frozen, and the Failed flip — successes AND failures. Message format: [finalize_step] campaign_id: <id> step=<step> target=<canister> outcome=<ok|err> <detail>; campaign_id is set explicitly on the entry, and failures carry an error_code of FINALIZE_STEP_<STEP>. The frontend “LGE Finalization Status” box on the campaign page consumes this feed via get_campaign_finalize_logs.
Read budget on the event readers. The event readers of OHSHII Governance, LGE Governance, the Pool Manager and DAO Storage walk the store by key and decode only the rows they return. A read that would not fit in one query (a budget of 3 billion instructions) stops at the budget instead of failing: a “recent” reader returns the NEWEST rows that fit, in the same order, and
get_proposal_eventsstops its newest-first scan at the same budget; a paginated reader (get_events,get_pool_events,get_storage_events_query) returns the rows from itsstartposition that fit. So an answer can hold fewer rows than the limit asked for; ask again with a smaller limit, or continue a paginated read from the next position.
OHSHII Governance (EventRecord + GovernanceEvent)
Section titled “OHSHII Governance (EventRecord + GovernanceEvent)”- Storage:
StableBTreeMap(MemoryId 7), counter inStableCell - Capacity: 10,000 events max
- Rotation: once the ring holds more than 10,000 events, the next insert removes the oldest events above the cap plus 100 more
- Timer: 300s (5 min) for proposal expiry, execution, refunds
- Event types — 54 variants. The groups below are illustrative, not exhaustive; the
authoritative enumeration is the
GovernanceEventtype in the canister’s.did.- Proposal lifecycle:
ProposalCreated,VoteCast,ProposalApproved,ProposalExpired,ProposalRejected,ProposalExecutionStarted,ProposalExecutionFailed - Execution results:
ChunksTransferredToStore,UpgradeCompleted,AssetUpgradeCompleted,AdminMethodExecuted,FeeCollected,CustomPoolCreated - Moderation:
GuardianVeto,ProposalDescriptionModerationUpdated - Upload:
UploadStarted,UploadCompleted,UploadSessionExpired,AssetLeaseTakenOver,FeeForfeited - Operational:
CanisterManagementAction,ControllerChanged,TimerCycleSummary,TransferResult,SystemLifecycle
- Proposal lifecycle:
- Upgrade row: every upgrade writes one
SystemLifecycleevent (event_type = "UPGRADE",details = "v=<canister_version>"), which the System Status page renders asUPGRADE v=<canister_version> - Key function:
log_event(event)inmemory.rs
API endpoints:
| Method | Signature | Access | Notes |
|---|---|---|---|
get_events | (start: nat64, limit: nat64) -> vec EventRecord | Public | Paginated from the OLDEST stored event, max 3000/page |
get_recent_events | (limit: opt nat64) -> vec EventRecord | Public | The newest N (default 100, max 3000), returned oldest first |
get_events_count | () -> nat64 | Public | Number of events currently stored |
get_proposal_events | (proposal_id: nat64) -> vec EventRecord | Public | Events for one proposal, max 500 |
LGE Governance (EventRecord + GovernanceEvent)
Section titled “LGE Governance (EventRecord + GovernanceEvent)”One instance per LGE campaign (dynamic canister, not in dfx.json).
- Storage:
StableBTreeMap(MemoryId 4), counter inStableCell(MemoryId 5) - Capacity: 10,000 events max
- Rotation: once the ring holds more than 10,000 events, the next insert removes the oldest events above the cap plus 100 more
- Timer: 300s (5 min) for lock unlocks and proposal checks (proposals are checked on every tick)
- Event types — 50 variants. The groups below are illustrative, not exhaustive; the
authoritative enumeration is the
GovernanceEventtype in that campaign’s canonical.did.- Proposal lifecycle:
ProposalCreated,VoteCast,ProposalApproved,ProposalExpired,ProposalRejected,ProposalExecutionStarted,ProposalExecuted,ProposalExecutionFailed - Config:
ConfigUpdatedByAdmin,ConfigUpdated,AdminAdded,AdminRemoved,GovernanceFinalized - Upload:
UploadStarted,UploadCompleted,UploadSessionExpired,AssetLeaseTakenOver,FeeForfeited - Execution results:
UpgradeCompleted,ChunksTransferredToStore,AssetUpgradeCompleted - Operational:
LockStateChanged,TreasuryOperation,TimerCycleSummary,ErrorOccurred,SystemLifecycle,CanisterManagementAction
- Proposal lifecycle:
- Upgrade row: every upgrade writes one
SystemLifecycleevent withevent_type = "upgraded"and an emptycampaign_id; it carries no version number - Key function:
add_event(event)inmemory.rs
API endpoints:
| Method | Signature | Access | Notes |
|---|---|---|---|
get_recent_events | (limit: nat64) -> vec EventRecord | Public | Most recent N (max 1000), returned newest first |
get_events_count | () -> nat64 | Public | Number of events ever written (the id counter), not the number currently stored |
get_proposal_events | (proposal_id: nat64) -> vec EventRecord | Public | Events for one proposal, max 500 |
Pool Manager (PoolEventRecord + PoolEvent)
Section titled “Pool Manager (PoolEventRecord + PoolEvent)”- Storage:
StableBTreeMap(MemoryId 18), counter inStableCell(MemoryId 19) - Capacity: 10,000 events max (
MAX_POOL_EVENTS) - Rotation: FIFO — once the ring holds more than 10,000 events, the next insert removes the oldest events above the cap plus 100 more
- Timer: None (event-driven only — logs on pool creation, fee collection, buyback, etc.)
- Event types — 21 variants:
PoolCreated,FeesCollected,BuybackExecuted,FeeCollectionFailed,ProposalExecuted,LiquidityAdded,TransferCompleted,OhshiiSwapExecuted,AdminBuybackExecuted,CustomPoolCreated,CustomPoolLiquidityAdded,PoolStateResetForRelaunch,LedgerRelinked,PoolRedeployed,OperationStep,RateLimitSaturated,TreasuryLiquidityAdded,TreasuryLiquidityRemoved,CampaignPoolReconciled,PoolUnusedBalanceSwept,Error - Key function:
log_pool_event(event)inmemory.rs - Migration log: Separate
StableBTreeMap(MemoryId 14) for upgrade migration messages. It has no row cap; its readerget_migration_statusreturns the first (oldest) 1,000 entries - Upgrade row: every upgrade writes one row
UPGRADE v=<canister_version>(anupgradeoperation step) into the pool event log the System Status page shows
API endpoints:
| Method | Signature | Access | Notes |
|---|---|---|---|
get_pool_events | (start: nat64, limit: nat64) -> vec PoolEventRecord | Public | Paginated, max 3000/page |
get_recent_pool_events | (limit: opt nat64) -> vec PoolEventRecord | Public | Most recent N (default 50, max 3000) |
get_pool_events_count | () -> nat64 | Public | Number of events currently stored |
get_logs | (campaign_id: opt text) -> vec PoolCreationLog | Public | Heap (lost on upgrade), pool creation logs, at most 1,000 |
get_operation_logs | (campaign_id: text) -> vec OperationLog | Public | Heap (lost on upgrade), max 100/campaign — do not rely on it for durable history; use get_pool_events instead |
clear_logs | () -> Result | Authorized | Update call, clears heap logs |
DAO Storage (StorageEventRecord + StorageEvent)
Section titled “DAO Storage (StorageEventRecord + StorageEvent)”- Storage:
StableBTreeMap(MemoryId 18), counter inStableCell(MemoryId 19) - Capacity: 10,000 events max (
MAX_STORAGE_EVENTS) - Rotation: FIFO — once the ring holds more than 10,000 events, the next insert removes the oldest events above the cap plus 100 more
- Timer: None (event-driven only — logs on guarded state changes, contribution modifications, etc.)
- Event types — 9 variants:
CampaignStateChanged,CampaignParamsUpdated,ContributionModified,CreatorUpdated,SponsorModified,TokenModified,RateLimitSaturated,Error,SystemLifecycle - Upgrade row: every upgrade writes one
SystemLifecycleevent (event_type = "UPGRADE",details = "v=<canister_version>"), which the System Status page renders asUPGRADE v=<canister_version> - Caller tracking: the state-change and modification events include an
admin: Stringfield capturing the caller principal - Key function:
log_storage_event(event)inmemory.rs
API endpoints:
| Method | Signature | Access | Notes |
|---|---|---|---|
get_storage_events_query | (start: nat64, limit: nat64) -> vec StorageEventRecord | Public | Paginated, max 3000/page |
get_recent_storage_events_query | (limit: opt nat64) -> vec StorageEventRecord | Public | Most recent N (default 100, max 3000) |
get_storage_events_count_query | () -> nat64 | Public | Number of events currently stored |
Heap Logs (lost on upgrade)
Section titled “Heap Logs (lost on upgrade)”Backend
Section titled “Backend”| Store | Capacity | TTL | Purpose | Read by |
|---|---|---|---|---|
ICO_LOGS | 100 entries/campaign, 200 campaign buckets max | 1 hour | Campaign operations (purchase, finalize, refund, relaunch); levels info / success / warning / error | get_campaign_logs, get_ico_creation_logs, get_imported_dao_creation_logs, get_relaunch_logs, get_ico_logs |
RECENT_LOGS | 50 entries | None | Treasury/conversion operations | get_recent_conversion_logs |
TOKEN_CREATION_LOGS | 100 entries/user, 500 users max (LRU eviction) | None | Token creation progress | get_token_creation_progress (the caller’s own rows) |
RATE_LIMIT_EVENTS | 1,000 entries | None | DoS/cycle-drain monitoring | get_rate_limit_events |
RECENT_SHIELD_EVENTS | 100 entries | None | Shield refusals and state changes | get_shield_rate_limit_status |
Routing: route_to_heap_logs(msg, campaign_id) checks the message to decide which heap store receives the entry. RECENT_LOGS receives messages containing FUNDS_ALLOCATION, TREASURY_TRANSFER, CMC_TRANSFER or CYCLES_CONVERSION, or starting with [SECURITY]. ICO_LOGS receives messages starting with [create_ico_campaign], [finalize_ico], [check_expired_icos], [refund_campaign], [purchase_tokens_icrc2], [ICP_RECEIVED], [ESCROW_, [REFUND_, [REFERRAL_, [admin_upgrade_index, [admin_create_governance, [post_upgrade], [relaunch_ico], [admin_clear_stuck_relaunch], [admin_retry_relaunch_step] or [internal_create_ledger_and_index].
Pool Manager
Section titled “Pool Manager”| Store | Capacity | TTL | Purpose |
|---|---|---|---|
PoolCreationLog (in State) | 1,000 entries | None | Pool creation success/failure records |
OperationLog (per campaign) | 100 per campaign | None | Pool operation logs (fee collection, buyback, etc.) |
Log Level Strategy
Section titled “Log Level Strategy”Only significant events go to stable memory. This prevents filling the stable rings (10K entries, 50K on the backend) with routine noise.
| Level | What gets persisted | Examples |
|---|---|---|
| error | Always | Transfer failures, governance creation failures, inter-canister call errors |
| warn | Always | Low cycles, expired campaigns updated, security events, lock frozen |
| success | Operation milestones | Finalize completed, upgrade completed, lock unlocked, transfer sent |
| info | Non-repetitive milestones only | Guarded actions, lifecycle events (init, post_upgrade), config changes |
| critical | Always (backend, explicit level only) | A payment debit whose outcome is ambiguous, a broken invariant the flow refuses to pass |
The level is a field only in the backend critical log. OHSHII Governance, LGE Governance, the Pool Manager and DAO Storage store typed events with no level field; the status pages derive a level from the event type.
What does NOT go to stable memory (IC system log only):
- Progress lines of a flow that did work (for example a refund pass that processed refunds, an unlock pass that unlocked)
- Timer skips caused by a real condition (for example the previous tick still running, or LGE Governance’s low-cycles
skip; the backend’s low-cycles skip is also stored in the critical log as a
warn) - Error lines that the flow also reports to its caller
A timer tick that finds no work writes no line at all, not even to the IC system log, and debug diagnostics are not printed.
Scalability & Log Duration Estimates
Section titled “Scalability & Log Duration Estimates”Rotation limits
Section titled “Rotation limits”| Canister | Max entries | Rotation strategy |
|---|---|---|
| Backend | 50,000 | At the cap, removes the oldest rows above the cap plus 100 |
| OHSHII Governance | 10,000 | Above the cap, removes the oldest rows above the cap plus 100 |
| LGE Governance | 10,000 | Above the cap, removes the oldest rows above the cap plus 100 |
| Pool Manager | 10,000 | Above the cap, removes the oldest rows above the cap plus 100 |
| DAO Storage | 10,000 | Above the cap, removes the oldest rows above the cap plus 100 |
Pessimistic daily volume estimates
Section titled “Pessimistic daily volume estimates”Assumes a busy day with active LGE campaigns, frequent proposals, pool operations, and guarded recovery actions.
| Canister | Pessimistic daily entries | Days until full | Reasoning |
|---|---|---|---|
| Backend | ~300-500 | 100-166 days (50K ring) | Multiple active campaigns with purchases + finalization + security events + privileged operations + hourly housekeeping cleanup events |
| OHSHII Governance | ~50-150 | 66-200 days | Active proposal creation/voting/execution + canister management + timer summaries. No per-tick logs. |
| LGE Governance (per instance) | ~100-300 | 33-100 days | Active lock unlock processing (up to 30 locks per 5-minute cycle, logged only on an actual unlock) + proposal lifecycle + treasury ops. Worst case: 300 locks unlocking over a few days. |
| Pool Manager | ~20-80 | 125-500 days | Event-driven only (no timer). Pool creation, fee collection, buyback — all infrequent. |
| DAO Storage | ~10-50 | 200-1000 days | Event-driven only. Campaign state changes, contribution modifications — mostly during active LGE periods. |
Worst case (all canisters under sustained heavy load simultaneously): the backend fills its 50K ring in ~100 days and LGE Governance its 10K ring in ~33 days; from then on FIFO rotation keeps the most recent entries, a rolling window of the last few weeks (10K rings) to months (backend) of significant operations.
Memory overhead per canister
Section titled “Memory overhead per canister”Each stable log entry is ~200-500 bytes serialized. At 10,000 entries:
- ~2-5 MB per canister for the log StableBTreeMap
- Well within the 500 GiB stable memory limit (typically <0.001% of capacity)
IC memory limits (per the canonical IC resource limits):
Resource Limit Notes Stable memory per canister 500 GiB Per-canister cap; the actually-usable amount is also bounded by the subnet’s shared memory capacity (see “Subnet capacity” below) Wasm heap (Wasm32) 4 GiB Hard limit of the 32-bit address space. OhShii canisters build to wasm32-unknown-unknown, so this 4 GiB heap ceiling is the one that applies to them.Wasm heap (Wasm64) 6 GiB The opt-in wasm64-unknown-unknowntarget (not used by OhShii); an ICP platform limit, expected to grow toward stable-memory capacity over timePer-message stable memory access 2 GiB read / 2 GiB write Per replicated message; upgrades: 8 GiB read/write Subnet capacity 2 TiB Shared across all canisters on the subnet The 10K-entry log buffers use ~2-5 MB each — negligible relative to any of these limits.
Sources: Canister Resource Limits, Canister Storage
Frontend Monitoring
Section titled “Frontend Monitoring”SystemStatusPage (/system-status)
Section titled “SystemStatusPage (/system-status)”Public dashboard with tabs for:
- Backend Stable Memory Logs: Paginated
CriticalLogEntrylist with level filtering (error/warn/success/info; rows with thecriticallevel are listed under All) - Pool Manager Events: Paginated
PoolEventRecordlist - OHSHII Governance Events: Paginated
EventRecordlist with event type formatting - DAO Storage: Summary + paginated
StorageEventRecordlist - Rate Limit Events: Heap-based DoS monitoring events
The Backend, Pool Manager, OHSHII Governance and DAO Storage tabs also show Native IC Logs: the canister’s IC system log, read through the IC management canister fetch_canister_logs (see The IC system log).
OnsVotingPage Governance Logs
Section titled “OnsVotingPage Governance Logs”Expandable “Governance Canister Status & Logs” section with three sub-tabs:
- Summary: Canister ID, total events count, loaded events count
- Stable Events: Paginated event list with level filtering (error/warn/success/info), formatted per GovernanceEvent variant
- Native IC Logs: the raw IC system log from the management canister, sorted by timestamp descending
See also
Section titled “See also”- ARCHITECTURE.md — Canister map and governance scope table.
- WORKFLOWS.md — Create LGE, token, vote, create/execute proposal, backend vs frontend upgrade, self-snapshot flows.
- GOVERNANCE.md — ONS vs SONS, proposal categories, snapshot/restore.
- OHSSHII_LOCKER_INTEGRATION.md — OhShii Locker token list and lock management via ONS proposals, SONS vesting vs Locker.
- GOOGLE_VERIFICATION.md — Google verification (Standard): the Internet-Identity attribute-bundle mechanism, one Google account per wallet, the address-normalisation rules, privacy model, governance controls and escape hatches.
- TELEGRAM_VERIFICATION.md — Telegram verification (Strong): the flow, the privacy model, and the veto-override attestation it now carries.
- WORLD_ID_VERIFICATION.md — World ID voter verification (Strong, legacy): flow, privacy model, configuration states including the
accepts_new_proofsretirement flag, threshold-ECDSA RP signatures, rate limits and governance controls. - DECIDEID_VERIFICATION.md — DecideID voter verification (Strong, legacy): configuration states including the
accepts_new_proofsretirement flag, the VC and OIDC paths, privacy model, rate limits and pause levers. - FRONTEND.md — Frontend architecture: React + JSX + JSDoc
@ts-checktype-checking, the canister-types.js + utils/canister.js bridges, Candid wire conventions, auth providers, and the TypeScript version policy. - Deploy/VOTER_VERIFICATION_GUIDE.md — Verifying WASM hashes for DAO proposals.