Skip to content

Architecture

This document describes the canister layout and the distinction between OHSHII Governance (ONS) and LGE Governance (SONS).

OhShii spans two separate codebases — the Launcher (this repo) and the Locker — plus the OHSHII token suite (the governance token’s own ledger and index). Each set is listed below with its mainnet canister ID and on-chain role.

OhShii Launcher (dev-ohshii-launcher, this project)

Section titled “OhShii Launcher (dev-ohshii-launcher, this project)”
CanisterMainnet canister IDRole
ohshii_launcher_backendvil43-piaaa-aaaal-qsibq-caiCore orchestrator: LGE/token creation, per-campaign deployment of ledger/index/sons_governance, and the housekeeping timer. Holds per-contribution escrow custody, and its MAIN account is the platform / DAO ICP treasury (receives the 5% platform fee and post-window residual sweeps).
dao_storageuqcjy-biaaa-aaaal-qsifq-caiPersistent storage for campaign records, contributions, sponsor/referral data, the per-campaign governance_canister_id mapping, and the DAO-transparency event log. Only the backend writes to it.
pool_managerhymny-gyaaa-aaaal-qsm7a-caiCreates each campaign’s ICPSwap pool and seeds its initial liquidity; at finalization hands the LP position to the campaign’s sons_governance before that DAO becomes autonomous. Also holds the OHSHII ecosystem LP position (under ONS governance), collects trading fees through governance proposals (during the pre-DAO bootstrap phase the same levers also accept the authorized caller set; collected fees go to the treasury either way), runs ICP→OHSHII treasury buybacks, and refunds held ICP from the campaign and referral subaccounts on failure. See pool_manager responsibilities.
ohshii_governancexnsdw-yaaaa-aaaal-qsqua-caiONS governance — the OhShii ecosystem’s single platform-wide DAO. Members lock OHSHII for Voting Power and vote (quadratic) on proposals, with liquid-democracy delegation and an identity-verification gate with two strength levels (Google is Standard; Telegram, World ID and DecideID are Strong). Through executed proposals ONS upgrades and administers the launcher and locker canisters and the OHSHII token suite — canister / asset / governance upgrades (incl. ledger-suite and dapp batches), ExecuteAdminMethod (call_raw to launcher/locker/token methods), config changes, guardian management, snapshot and controller operations, and voting-parameter changes — and manages the OHSHII treasury and its liquidity (the LP position pool_manager holds; it can also mint/hold its own custom ICPSwap pools). It also serves the cached cycle-balance / canister-status reads used by System Status. Full proposal inventory: see Governance scope and GOVERNANCE.md.
ohshii_launcher_frontendv5mnw-oaaaa-aaaal-qsica-caiAsset canister serving the launcher web UI.

Note: sons_governance is not in dfx.json. One sons_governance canister is created per LGE campaign at runtime by the backend (Cycles-Ledger create_canister, then the bundled sons_governance.wasm.gz is uploaded in chunks into that canister’s own chunk store and installed from them); each instance governs a single campaign. See Governance scope.

OhShii Locker (dev-ohshii-locker, separate project)

Section titled “OhShii Locker (dev-ohshii-locker, separate project)”
CanisterMainnet canister IDRole
ohshii_locker_backendidxmu-dqaaa-aaaah-aq7da-caiLock creation and validation, timer-driven unlock processing, and custody orchestration via proxy.
lock_storagegq7kz-gyaaa-aaaah-ard5q-caiCanonical lock records and indices in stable memory, including each lock’s custody subaccount.
proxyop74x-uyaaa-aaaah-aq7uq-caiSole entrypoint for locker fund movement (execute_transfer, gated to the locker backend only — ONS governance cannot call it directly). Wraps the call to strongbox in a per-canister circuit breaker and keeps its own transfer-status ledger for reconciliation and public transparency.
strongboxoi62d-zaaaa-aaaah-aq7ua-caiExecutes the ICRC-1 transfer and holds custody. Its authorization list also grants the locker and launcher backends standing access, but in current code every transfer is routed through proxy.
ohshii_locker_frontendiewka-oiaaa-aaaah-aq7dq-caiAsset canister serving the locker web UI.

Launcher and Locker are two separate codebases and deploy pipelines. They share Internet Identity (and, on mainnet, alternative origins for the same principal). The launcher backend calls the locker only to forward the lock-creation and lock-sale feature switches; ONS governance calls it through approved proposals (lock administration). LGE vesting locks are always kept in the campaign’s own governance canister (SONS), never in the Locker; for SONS vesting vs Locker locks and the integration flows, see OhShii Locker integration. For why the locker splits fund movement across proxy and strongbox instead of a single canister, see the “Why proxy is a separate canister” section of the OhShii Locker repository’s own README.md (a separate codebase — not part of this repo).

The OHSHII governance token has its own ICRC token suite — one ledger plus its index:

CanisterMainnet canister IDRole
OHSHII Ledger7rrmx-tyaaa-aaaal-qsraq-caiICRC-1 ledger for the OHSHII token (proposal-fee currency, Voting-Power / lock source, treasury asset).
OHSHII Index7yshl-fqaaa-aaaal-qsrba-caiTransaction index for the OHSHII ledger (history and holder lookups).

The OHSHII ICPSwap pool (yvg67-gyaaa-aaaar-qbseq-cai) is the ICP/OHSHII DEX pool — infrastructure around the token (used for treasury buybacks and the price/swap UI), not part of the token suite itself.

The launcher frontend’s System Status page (SystemStatusPage.jsx) monitors the launcher canisters and this OHSHII token infrastructure only. OhShii Locker has its own canister set and its own System Status page in the locker project.

pool_manager is the launcher’s ICPSwap-liquidity and campaign-custody canister. Its role is broader than the “pool creation and fee collection” summary suggests:

  • Pool creation + initial liquidity — for each campaign it creates the ICPSwap V3 pool (factory createPool via an ICPSwap passcode) and mints the initial LP position. Liquidity is two-phase: the pool is created at LGE start, and the ICP + token liquidity is minted at finalization. Finalize retries are idempotent, so a Frozen→Finalizing retry never orphans a position.
  • LP-position handoff to the SONS DAO — at finalization it executes the one-way ICPSwap transferPosition that moves LP-position ownership from pool_manager to the campaign’s sons_governance. The backend orchestrates this at finalization STEP 2, before the step that seals SONS into autonomy (which removes the backend from the SONS admin set) — so the position belongs to the DAO before the DAO becomes self-governing.
  • Fee collection (governance-gated) — it claims ICPSwap trading fees and keeps per-campaign fee bookkeeping. Direct/creator fee collection is disabled. The governance path is an executed proposal: ONS for a position the pool manager owns (collect-to-treasury, collect-and-buyback), and a campaign’s own SONS for the position that DAO owns.
  • Holds the OHSHII ecosystem LP position — pool_manager is the on-chain owner of the OHSHII/ICP ICPSwap liquidity position (the full-range position on the yvg67… pool). That position’s trading fees are collected by an approved ONS proposal, which either forwards them to the treasury or swaps the collected ICP for a token the proposal names. It acts under ONS command: only ohshii_governance may invoke proposal_transfer_pool_position to move that position, and ONS is an admin of pool_manager. (Separately, ohshii_governance can also mint and hold new custom ICPSwap pools directly via its own proposal_create_custom_pool / proposal_transfer_custom_pool_position self-methods — but the live OHSHII position is held by pool_manager, not governance.)
  • Refund custody — on a failed campaign it returns held ICP to contributors from the campaign subaccount and the per-campaign referral subaccount. Refunds draw only from these held subaccounts — never the treasury.

All of the above is driven by the backend (during finalization and refunds) or by executed ONS/SONS governance proposals; pool_manager treats ohshii_governance as an admin so proposal execution can reach its governance-only methods.

Who can modify campaign state and OhShii Locker state

Section titled “Who can modify campaign state and OhShii Locker state”
  • Campaigns (including non-completed)
    Stored in dao_storage. Only the backend can write (it calls update_campaign and admin_update_campaign_status on storage). So campaign state is changed in two ways:

    • Backend (self-flow): methods such as admin_update_campaign_status / admin_update_ico_info are called by the backend as part of its own flows; the backend then calls dao_storage. In production these operations run through ohshii_governance via an approved (ONS) proposal where implemented, or remain unavailable if no proposal path exists.
    • OHSHII Governance (via proposals): When an ONS proposal is approved and executed, ohshii_governance calls the backend via call_raw (e.g. admin_update_campaign_status, admin_update_ico_info); the backend performs the update and calls dao_storage.
      So campaign state is modified by the backend (its own flows) and by OHSHII governance (through executed ONS proposals).
  • Campaign states and refunds
    Campaign state can be Active, Finalizing, Completed, Failed, or Frozen. Finalizing is the provisional phase after the target is reached while pool/liquidity, vesting locks, LP position transfer, governance sync, SONS finalization, and controller cleanup are still running. Completed is terminal and is written only after those critical finalization steps succeed. A campaign becomes Failed when it expires without selling out (housekeeping) or when finalization is run on a non–sold-out campaign. For Failed campaigns, contributors can request a refund within 7 days of expiry. For escrow-funded campaigns the refund is 95% of the contribution, tier-independent: the net 90% held in the campaign subaccount plus the held 5% referral held in the per-campaign referral subaccount (creator still nets ~70% of their original purchase); legacy pure pre-escrow campaigns refund the historical ~90% of gross. Refunds run two ways: a contributor calls request_refund (per-user, within the 7-day window) and the bulk refund_campaign path handles emergency batch processing, invoked by the authorized caller. In both cases the backend marks the contributions as refunded in dao_storage and the pool_manager transfers ICP from the campaign subaccount and the referral subaccount (held funds only — never the treasury) to the user. Each refund leg is net of its own ledger fee, which the user bears. After the 7-day refund window closes, a housekeeping task sweeps any un-refunded residual ICP (campaign subaccount + referral subaccount) to the DAO treasury on-chain — balance-driven and at-most-once, covering both Failed and Completed campaigns. The sweep skips the reserved all-zero subaccount, which is pool_manager’s own main account rather than a campaign account (see WORKFLOWS.md §0 for the derivations and the reserved value). See WORKFLOWS.md (Campaign failure and refunds) for the full per-leg math.

  • Closed historic campaigns
    On mainnet, the index canisters of eight early campaigns (dao-0000000000, dao-0000000002, dao-0000000003, dao-0000000004, dao-0000000005, dao-0000000007, dao-0000000009 and dao-0000000010) and the token ledger of dao-0000000002 no longer hold a module (uninstalled when their cycles ran out). These campaigns are closed: their records stay readable in dao_storage, and the canisters are not reinstalled. Where the ledger is still installed (for example the Completed dao-0000000005 and dao-0000000007), the token keeps working on its ledger, but there is no index canister serving its transaction history.

  • OhShii Locker (token list and locks)
    The locker backend (ohshii_locker_backend) accepts calls from its authorized callers — principally ohshii_governance (see locker is_admin() which includes OHSHII_GOVERNANCE_ID) and the launcher backend. The launcher backend also calls the locker: it forwards the lock-creation and lock-sale feature switches (admin_set_lock_creation_status, admin_set_lock_sale_status). It no longer creates locks: delayed-unlock vesting is held by each campaign’s own governance canister, and the locker’s former on-behalf endpoint (create_lock_on_behalf_of_v2) and pool_manager’s approve_locker_for_lock were removed. So:

    • OHSHII Governance (via proposals): Executes ONS proposals that target the locker (e.g. admin_update_token, remove_supported_token, admin_update_lock_status, admin_delete_lock, admin_transfer_lock_recipient). Listing a NEW token is disabled: the supported-token set is frozen and ONS refuses admin_list_token proposals at creation.
    • Backend: Can set the locker’s lock-creation and lock-sale switches.
      So the OHSHII governance (via proposals) modifies locker state (token list and locks); the backend only switches public lock creation and lock sales on or off.

LGE contribution custody is per-contribution and fully on-chain — there is no off-chain reconciler. Each purchase routes the buyer’s ICP through a backend escrow subaccount derived per contribution (a single-purchase compartment), then forwards three clean legs out of that escrow:

  • Net 90% → the campaign’s pool_manager subaccount — the LGE proceeds that fund liquidity at finalization.
  • 5% platform → the DAO treasury — sent to the backend canister’s MAIN account (vil43-piaaa-aaaal-qsibq-cai).
  • Held 5% referral → the per-campaign referral subaccount — retained until the LGE outcome (paid out on success, or returned to contributors on failure as part of the 95% refund above).

The backend canister’s MAIN account (vil43-piaaa-aaaal-qsibq-cai) IS the platform / DAO ICP treasury: it receives the 5% platform fee on every purchase and the 7-day residual sweeps after refund windows close. On campaign failure, refunds are paid ONLY from held funds (campaign subaccount + referral subaccount); the treasury is never used as a refund source. Finalization reconciles any un-forwarded escrow stragglers before pool_manager deposits the campaign subaccount into the pool (the raise never transits pool_manager’s main account), so no contribution is left in limbo without an off-chain process. See WORKFLOWS.md for the authoritative per-leg fee/refund math — it is the single source of truth for the numbers; this section only summarises where the funds sit.


Two different governance systems exist; they must not be confused.

ScopeCanister(s)What it governsToken used for votingDeploy model
OHSHII Governance (ONS)Single canister: ohshii_governance (e.g. xnsdw-yaaaa-aaaal-qsqua-cai)OhShii Launcher canisters (backend, storage, pool_manager), OhShii Locker (token list, etc.), and the OHSHII token suite (ledger, index, archives). Also the OHSHII token’s DEX liquidity positions (held by pool_manager, moved only by ONS proposal) and the OhShii treasury.OHSHII token (quadratic voting on locked tokens)In dfx.json; deployed with the launcher.
LGE Governance (SONS)One per LGE/campaign: sons_governance (dynamic ID per campaign)Only that LGE: treasury of the campaign token, governance WASM, asset canister, pool/params for that campaign.The LGE campaign tokenDynamic: created by the backend during create_ico_campaign (see Backend: governance creation). Not in dfx.json.

LGE Governance SONS

OHSHII Governance ONS

ohshii_governance

Launcher canisters

OhShii Locker

OHSHII token suite ledger/index/archive

OHSHII DEX liquidity positions

sons_governance A

sons_governance B

Treasury + WASM + asset

Treasury + WASM + asset

Vote with OHSHII

Vote with LGE token

Each LGE campaign gets its own governance canister. The backend:

  1. Creates an empty canister via Cycles Ledger (create_empty_governance_canister).
  2. Creates ledger and index canisters with that governance as controller.
  3. Installs the governance WASM (GOVERNANCE_WASM = sons_governance.wasm.gz) into the empty canister, in chunks: the module is uploaded piece by piece into the target canister’s own chunk store and then installed from those pieces in one atomic step, which the platform completes only if the pieces reassemble to exactly the declared module. The chunk store is cleared afterwards.

Why chunks. A single install message carries the module and its initialization argument together, under a 2 MiB platform limit. Chunked installation removes that ceiling, so the size of the governance module does not limit campaign creation. The bytes installed are unchanged, so the module hash a created governance canister reports is the same value published for the bundled module.

Relevant code: src/backend/src/lib.rs — create_empty_governance_canister, create_governance_canister_internal, GOVERNANCE_WASM — and src/backend/src/sons_install.rs, which owns the chunking, the resume of an interrupted upload and the cleanup. The same WASM binary is used for every SONS governance canister; each instance has its own state (proposals, treasury, config).


OhShii Locker

OHSHII token

OhShii Launcher

writes campaign state

feature switches

proposals call backend

proposals call directly

proposals call directly

upgrade/govern

upgrade/govern

upgrade/govern

creates per LGE

governs own

ohshii_launcher_backend

dao_storage

pool_manager

ohshii_governance

ohshii_launcher_frontend

ohshii ledger

ohshii index

ohshii pool

ohshii_locker_backend

lock_storage

proxy

strongbox

ohshii_locker_frontend

sons_governance

Treasury + WASM + asset

Campaign state and locker state are modified by both the backend (direct backend or vesting flows) and the OHSHII governance (via executed ONS proposals), as described in Who can modify campaign state and OhShii Locker state.


All five launcher canisters persist their state directly in stable memory with the ic-stable-structures crate — there is no manual pre_upgrade/post_upgrade serialization step. Each canister has exactly one MemoryManager, which partitions stable memory into VirtualMemory regions (one per MemoryId); each region backs a StableBTreeMap (collections / indices) or a StableCell (single values, config, counters). State therefore survives canister upgrades automatically.

What each canister persists (representative structures):

CanisterPersists in stable memory
ohshii_launcher_backendLaunched-token records + counters, feature-toggle status cells (LGE / token / index creation, the Bitcoin & Solana gateways), critical audit logs, relaunch replay-protection, the Shield used-PoW-nonce ledger, OHSHII guest-fee custody markers, and the creation-payment custody journal with its settlement work list
dao_storageCampaigns, contributions, created tokens, sponsor/referral data, secondary indices, and the DAO-transparency event log
ohshii_governance (ONS)Proposals, executed proposals, votes & voter history, voting-parameter config, identity-verification state (one verified set and one anti-replay set per provider, plus the derivable union), liquid-democracy delegation maps, and an event log
sons_governance (SONS)One instance per campaign — that campaign’s proposals, votes, config, vesting-lock records, delegation maps, and verification state
pool_managerLiquidity-pool registry and per-campaign pool/fee bookkeeping (pool / position / ledger ids, per-campaign sub-accounts, fee totals & distributions) and a pool event log

Creation-payment custody: the journal and its work list

Section titled “Creation-payment custody: the journal and its work list”

The journal — MemoryId 48 on the backend. Every flow that takes ICP to build something (standalone token, LGE creation, imported DAO, index canister, cycles top-up, discounted cycles) writes one durable row, keyed by the payment memo, the moment the debit lands and before any other work happens. The row carries who paid, the subaccount the payment sits in, the gross amount, the cycles leg, the state, the ledger blocks for each leg that has landed, and — for an LGE — the four sweep destinations, which are resolved before the debit and written on the row the moment the debit lands, so a retry replays the same amounts. The region is unbounded, and rows are never rotated, evicted or deleted: a dropped terminal row would reopen its memo to a second debit, and a dropped pending row would lose a refund that is owed. Growth is bounded instead by there being one row per real debited payment, and terminal rows older than 30 days are compacted in place under the same key.

The work list — MemoryId 49 on the backend. A bounded map from memo to nothing, holding every row the settlement pass still has to look at: a pending or failed refund, an outstanding sweep, an unresolved cycles leg, and — since every debit latches — every in-flight payment from the moment its ICP moves. An entry is dropped when its row reaches a terminal state. Its cost model is one scan slot per in-flight payment for the duration of that payment’s flow, which is bounded by concurrency rather than by volume.

The settlement pass. The backend’s housekeeping timer runs every five minutes and iterates only the work list, never the journal. It pages a fixed number of entries from a rotating cursor and settles at most a small number of rows per tick, under the same per-payment lock that the payer’s own reclaim endpoint and the guardian lever take, so the three can never interleave on one row. A row whose flow is still executing is scanned and skipped and costs no settlement budget. Once such a row is older than the ledger’s 24-hour deduplication window, no message can still be running it: the pass releases its work-list entry and logs it as an orphaned row, naming the state, the amount and the action that moves it. It logs and does nothing else — the row and the money are left untouched, because the work may have completed with only its marker lost. Recovery from there is guardian_resolve_payment_custody, available to the authorized canister and to the governance-set guardian (limited recovery scope), whose actions can pay only the journal’s own payer or the destinations already frozen on the row.

Upgrade-safe schema evolution (data-integrity model)

Section titled “Upgrade-safe schema evolution (data-integrity model)”

Stable storage is governed by a strict discipline so upgrades never corrupt or silently lose data:

  • New persisted fields are Option<T>, appended last — a missing non-opt field fails Candid decode on upgrade.
  • A MemoryId is never reused once its structure is removed (the MemoryManager never reclaims a bucket; re-binding stale bytes would trap or mis-decode).
  • Value/key types are never tightened or shrunk on a live id.
  • Storable::from_bytes is decode-hardened — a decode failure logs and falls back to a safe default instead of trapping. For the critical settings — the launcher’s feature switches and fee configurations, the ecosystem governance canister’s guardian, voting parameters, verification settings and one-time defaults latch, each campaign governance canister’s configuration, campaign data and pool information, and the Locker’s lock-creation and lock-sale switches — that fallback is never written back: an upgrade whose code cannot read one of them is refused and the previous code keeps the stored values.

Note: OhShii Atlas and OhShii Explorer (analytics tools, not DAO-governed) are powered by a separate off-chain analytics service maintained by a contributor; its internals are out of scope for this on-chain architecture document.


  • ARCHITECTURE.md — Canister map and governance scope table.
  • WORKFLOWS.md — Create LGE, token, vote, create/execute proposal, backend vs frontend upgrade, self-snapshot flows.
  • OHSSHII_LOCKER_INTEGRATION.md — OhShii Locker token list and lock management via ONS proposals, SONS vesting vs Locker.
  • GOVERNANCE.md — ONS vs SONS, proposal categories, snapshot/restore.
  • LIQUID_DEMOCRACY.md — Vote delegation on ONS: follow / unfollow flows, accept-followers toggle, dismiss-follower, snapshot cascade model, four-layer rate limits, cross-canister batched VP fetch contract, event log reference, FAQ.
  • LOGGING_SYSTEM.md — Three-tier logging architecture, stable memory log details, frontend monitoring.
  • 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_proofs retirement flag, threshold-ECDSA RP signatures, rate limits and governance controls.
  • DECIDEID_VERIFICATION.md — DecideID voter verification (Strong, legacy): configuration states including the accepts_new_proofs retirement flag, the VC and OIDC paths, privacy model, rate limits and pause levers.
  • FRONTEND.md — Frontend architecture: React + JSX + JSDoc @ts-check type-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.