Skip to content

Menese Protocol Integration


OhShii Launcher is an allowlisted partner on Menese’s Sovereign Send canister. This means:

  • Free address derivation — the launcher’s vouched users don’t pay 45B cycles per getMyAddress()
  • No subscription required — the launcher uses the per-tx fee model, not the MeneseSDK subscription
  • 20% revenue share — of the 0.1% protocol fee on sendSol/signSend operations
  • Backend canister allowlisted — can call registerPartnerUsers() to vouch for users
Partner (OhShii)Non-partner
getMyAddress() costFree (vouched users)45B cycles (caller pays)
Subscription neededNoYes ($20-$323/mo) or cycles
Revenue share20% of 0.1% protocol feeNone
User registrationBackend calls registerPartnerUsers()Must use registerSession(developerKey)
depositSol() fee0% (pool’s own fee applies)0% (same)
sendSol() fee0.1% atomic0.1% atomic
  • Allowlisted backend canister: vil43-piaaa-aaaal-qsibq-cai (prod)
  • Partner fee-share address: BuqZagKKiRFUfxNFRKK7w3JymQgxuCy2rxFBdQTysMW4 (assigned by Menese)

CanisterIDRole
Sovereign Sendfxjsq-raaaa-aaaab-agdaa-caiSigning canister — 0.1% fee model, no subscription
ICP-SOL Swap Pool (chain-key)w2vjc-2yaaa-aaaab-ae6zq-caiSOL↔ICP swaps at minimal slippage. NOT an oracle — legacy CROSSCHAIN_ORACLE_* code symbols are kept for API stability
ckSOL mintercrmds-kqaaa-aaaaf-qf5aq-caimSOL minting/redemption
mSOL ledger (ICRC-2)2ykjj-eyaaa-aaaae-af4ma-caimSOL token
MeneseSDK (legacy)urs2a-ziaaa-aaaad-aembq-caiOld subscription model — not used by OhShii’s current flows
ICP Ledgerryjl3-tyaaa-aaaaa-aaaba-caiICP token

signDeposit

swapIcpToSol

registerPartnerUsers

Browser (Frontend)

Sovereign Send (fxjsq)

ICP-SOL Swap Pool (w2vjc)

OhShii Backend (prod: vil43)

Solana Network (threshold Schnorr)


Flow 1: SOL → ICP (via Sovereign Send — sign-only path)

Section titled “Flow 1: SOL → ICP (via Sovereign Send — sign-only path)”

Status: Implemented — uses sign-only for reliable broadcasting. ICP arrives automatically in ~30-60s after Solana confirms.

ICP-SOL Pool (w2vjc)Solana RPCSovereign Send (fxjsq)OhShii FrontendUser Wallet (Phantom)ICP-SOL Pool (w2vjc)Solana RPCSovereign Send (fxjsq)OhShii FrontendUser Wallet (Phantom)1. Send SOL to derived address2. fetchBlockhash3. signDeposit(pool, lamports, blockhash)signedTxBase644. broadcastSolTx(signedTxBase64)5. Pool receives SOL, credits ICP6. ICP arrives at user principal (~30-60s)

Frontend code path: sovereignSend.js → depositSolForIcp(lamports, identityOrAgent)

Why sign-only instead of autonomous depositSol:

  • The autonomous path (depositSol) uses canister HTTP outcalls to broadcast — these can fail silently due to stale blockhash or HTTP errors, returning ok while the SOL doesn’t actually move.
  • The sign-only path (signDeposit) lets the launcher frontend fetch a fresh blockhash and broadcast directly via Solana RPC, which is faster (~0 cycles) and more reliable.

Broadcast honesty: broadcastSolTx tries the RPCs sequentially (first acceptance wins) and then polls getSignatureStatuses until the transaction is confirmed/finalized — RPC acceptance alone is never reported as success (with skipPreflight, a stale-blockhash tx is accepted and then silently dropped). The tx signature is always surfaced with a Solana explorer link, on failures too.

Key facts:

  • signDeposit has 0% Sovereign Send fee — the pool applies its own 0.15% LP fee
  • Min deposit: 0.05 SOL
  • Always reserve 935,880 lamports for Solana (890,880 rent-exempt minimum + 45,000 priority fee) via maxSendLamports() — a transfer that would drop the derived account below the rent-exempt minimum is rejected.
  • No second call needed after broadcast — ICP release is automatic
  • Cached users can deposit even if registerPartnerUsers is temporarily failing

Flow 2: ICP → SOL (via the chain-key swap pool)

Section titled “Flow 2: ICP → SOL (via the chain-key swap pool)”

Status: Implemented and working.

ICP-SOL Swap Pool (w2vjc)OhShii FrontendUserICP-SOL Swap Pool (w2vjc)OhShii FrontendUser1. Gateway fee (0.3% to backend)2. icrc2_approve (ICP_LEDGER, spender=pool)4. Pool pulls ICP, signs SOL tx, broadcasts to Solana3. swapIcpToSol(e8s, solAddress)5. SOL arrives at Solana addr

Frontend code path: solSwap.js → swapIcpToSol(e8s, solAddress, identityOrAgent)

Key facts:

  • The swap pool pulls ICP via ICRC-2 transfer_from after the user’s icrc2_approve
  • OhShii charges a governance-configurable gateway fee (default 30 bps = 0.3%) in ICP. The fee is read live from the backend (get_solana_gateway_fee_config; the frontend falls back to 30 bps if the query fails) and set via admin_set_solana_gateway_fee through an approved ONS proposal (hard cap 1000 bps = 10%). It is collected on the backend canister’s default account (the DAO fee treasury)
  • The fee is charged only after the swap returns ok — a user whose swap fails never loses the fee. If the fee transfer itself fails after a successful swap, the swap still succeeds and the uncollected fee is logged (the DAO’s loss, never the user’s)
  • The pool has its own fee (0.15% for LPs + dynamic spread up to 0.3%)
  • The pool reserves are limited, so a large swap can exceed them — the UI reads getCanisterInfo and blocks amounts whose quoted output exceeds the current reserve

Flow 3: SOL → mSOL (via Sovereign Send sign-only + ckSOL minter)

Section titled “Flow 3: SOL → mSOL (via Sovereign Send sign-only + ckSOL minter)”

Status: Not available in the app. The frontend code (depositSolForMsol) signs, broadcasts, confirms the tx on Solana (getSignatureStatuses polling) and then calls expectCkSolDeposit(txHash, lamports) so the minter verifies the deposit on-chain and mints, but no part of the UI exposes this flow.

1. User sends SOL to derived address (same as Flow 1)
2. Frontend: fetchSolanaBlockhash() via RPC
3. Frontend: sovereignSend.signDeposit(CKSOL_MINTER, lamports, blockhash)
4. Frontend: broadcastSolTx(signedTxBase64) via Solana RPC
5. Frontend: cksol.expectCkSolDeposit(txHash, lamports) ← verifies on-chain
6. ckSOL minter mints mSOL to user's principal

Note: Unlike Flow 1, mSOL minting requires a second call (expectCkSolDeposit) to verify the Solana deposit. This is because the ckSOL canister verifies the tx on-chain before minting.

Status: Not available in the app. The frontend code (redeemMsolToSol()) performs the ICRC-2 approve on the mSOL ledger (spender = ckSOL minter, amount + fee headroom) before requestCkSolRedemption, and the broadcast is confirmation-polled, but the UI has no redeem action.

1. Frontend: icrc2_approve(MSOL_LEDGER, spender=CKSOL_MINTER, amount)
2. Frontend: cksol.requestCkSolRedemption(lamports, solAddress, memo)
3. ckSOL burns mSOL, signs SOL transfer, returns signedTxBase64
4. Frontend: broadcasts signedTxBase64 to Solana via RPC
5. User receives SOL at their Solana address

Flow 5: mSOL → ICP (two-step, presented as one action)

Section titled “Flow 5: mSOL → ICP (two-step, presented as one action)”

Status: Not available in the app — phase 1 is redeemMsolToSol() (Flow 4), and like Flow 4 it is not exposed in the UI.

Phase 1 — mSOL → SOL:
1. Approve mSOL for burning
2. Redeem via ckSOL → get signed SOL tx
3. Broadcast to Solana
4. Poll derived SOL address until balance arrives (~15-30s)
Phase 2 — SOL → ICP:
5. depositSol(ICP_SOL_SWAP, balance)
6. ICP arrives automatically (~30-60s)

Progress indicator: Approving → Redeeming → Broadcasting → Waiting for SOL → Converting to ICP → Complete

mSOL is a separate Menese product, not an intermediate step in SOL↔ICP, and the OhShii app does not currently expose it. The flows above would let a user:

  • Convert SOL to mSOL
  • Convert mSOL back to SOL when they want native SOL
  • Convert mSOL to ICP via the two-step flow (mSOL → SOL → ICP)
  • Convert SOL directly to ICP without touching mSOL at all

Registration is lazy: it runs only when the user explicitly enters a SOL→ICP flow (the SOL→ICP mode on the Solana tab, or the Solana participation box on a DAO page) and the gateway is verifiably healthy — never eagerly on tab mount. This stops the partner map from growing with visitors who never deposit and keeps a disabled/paused gateway from generating any Menese traffic.

1. Frontend (on SOL→ICP intent): backend.register_menese_user()
2. Backend: sovereignSend.registerPartnerUsers([caller_principal])
3. User is now vouched — free getMyAddress() and signDeposit()
4. Frontend: sovereignSend.getMyAddress() → user's derived SOL address

The backend rate-limits registrations (Shield: per-caller + global caps, PoW under Defcon) and records each vouched principal in a stable working set.

admin_remove_menese_user(opt vec principal), invoked by the authorized caller (an approved ONS proposal, “Revoke Menese Vouched Users”), calls Sovereign Send’s removePartnerUsers. Passing null/empty revokes every principal in the stable vouched working set in one call; passing a list revokes exactly those principals (the explicit path forwards them verbatim, including principals the working set does not record). The backend also self-reports its partner state via the anonymous get_menese_partner_status query (last registration outcome, vouched-set size, cached fee-treasury address).

Graceful degradation when registration is unavailable

Section titled “Graceful degradation when registration is unavailable”

If registerPartnerUsers fails (allowlist reset, Sovereign Send maintenance, etc.):

  • Cached users (anyone who previously called getMyAddress): can still derive addresses and sign deposits. cachedUsers survives allowlist resets. These users are NOT blocked.
  • New users (never used the Solana tab before): cannot get a derived address. The UI shows a warning that the Solana Gateway is temporarily unavailable for new users.
  • ICP → SOL (swap-pool path): unaffected — does not use Sovereign Send.
  • The deposit button remains enabled for users who have an address loaded.
  • Registration is retried on next page load.

OperationFeePaid to
getMyAddress()Free (partner)—
depositSol()0% Sovereign Send + pool’s 0.15% LP feePool LPs
sendSol()0.1% atomicMenese protocol
swapIcpToSol()0.15% LP fee + dynamic spread (max 0.3%)Pool LPs + swap pool
OperationFeeCollected by
ICP → SOLGovernance-configurable, default 0.3% (30 bps); hard cap 10%Backend canister (DAO fee treasury) — only after the swap succeeds
SOL → ICPNo OhShii fee—

The fee is stored on-chain (SolanaGatewayFeeConfig, get_solana_gateway_fee_config query) and set by admin_set_solana_gateway_fee through the ONS “Set Solana Gateway Fee” proposal. The frontend reads it live and falls back to 30 bps if the query fails (it never silently drops to 0).

The gateway fee is a separate ICP transfer from the user’s wallet to the backend, made by the app after the swap succeeds; the swap pool itself does not collect it.

  • 20% of the 0.1% protocol fee on sendSol/signSend operations
  • Distributed in SOL on Solana, 7-day epochs, minimum 0.05 SOL
  • Partner fee-share address: BuqZagKKiRFUfxNFRKK7w3JymQgxuCy2rxFBdQTysMW4
  • Applies to sendSol/signSend only. OhShii’s conversion flows use fee-free signDeposit deposits and the ICP↔SOL swap pool — neither carries the 0.1% protocol fee — so OhShii usage generates no material fee-share. Consistent with this, the address holds only about the Solana rent-exempt minimum.

The SystemStatus page’s “Menese / Solana Gateway” section shows the SOL balance of both surfaced addresses (public keyless RPC getBalance) with explorer links. There is no movement/sweep tooling because OhShii’s flows do not accrue a fee-share (see below) — there is nothing to move.

  • Partner fee-share address BuqZagKKiRFUfxNFRKK7w3JymQgxuCy2rxFBdQTysMW4 — the address Menese assigns for the 20% share on sendSol/signSend. Read-only.
  • Menese protocol fee treasury — getFeeTreasuryAddress() on Sovereign Send returns a single global address (the sink for the 0.1% protocol fee), identical for every caller. The backend fetches and caches it (admin_fetch_menese_treasury_address, invoked by the authorized caller) so the status page can read it via the anonymous get_menese_partner_status query and show its balance. It is Menese-controlled, not derived from the OhShii backend’s principal.

The 20% partner fee-share is 20% of the 0.1% protocol fee on sendSol/signSend (peer-to-peer SOL transfers). OhShii’s Gateway does not use those operations: SOL→ICP goes through fee-free signDeposit, and ICP→SOL goes through the ICP↔SOL swap pool (the pool’s own LP fee, not the Sovereign Send protocol fee). OhShii usage therefore generates no material fee-share — consistent with the partner fee-share address holding only the rent-exempt minimum. Any share Menese does attribute is distributed Menese-side (SOL on Solana, 7-day epochs, min 0.05 SOL).


Partner status — how to verify per environment

Section titled “Partner status — how to verify per environment”

The live Sovereign Send canister exposes getPartnerUserCount : (principal) -> (nat) query (a public query). To confirm the currently-deployed backend is a working partner, query getPartnerUserCount(<active backend principal>) and treat > 0 as registered. It reflects whichever backend is actually deployed. getShieldStatus() returns only aggregate counts and cannot prove membership of a specific principal.

This check is wired into the product in two places:

  • SystemStatus page — the “Menese / Solana Gateway” section resolves the active backend from the deployed environment, queries getPartnerUserCount(activeBackend) anonymously, and renders GREEN only when membership is proven AND the shield resolved AND the circuit breaker is off AND cycles are above the reserve AND the pool is unpaused (reachable-but-unproven renders amber, failures render red).
  • Backend self-report — the anonymous get_menese_partner_status query returns the last registration outcome, the stable vouched-set size, and the cached Menese protocol fee-treasury address as the cheap complement to the authoritative live query.

Derivation Shield v3 — Cycle Drain Protection

Section titled “Derivation Shield v3 — Cycle Drain Protection”

Ed25519 (Solana) doesn’t support hierarchical public key derivation like secp256k1/BIP-32. Unlike ckBTC (which derives child keys locally from a cached master public key), each Solana address derivation requires a threshold Schnorr signing call (~30B cycles).

Sovereign Send (operated by Menese) protects itself against cycle drain with a circuit breaker on its own cycle balance, a global throttle on new derivations, and a caller allowlist for partner canisters. Cached users keep working when the circuit breaker is engaged.

  • Unknown callers: attach 45B cycles per derivation
  • Cached users (anyone who derived before): free, bypass all layers
  • Allowlisted partners + vouched users (the launcher): free derivation

MethodTypeFeeDescription
getMyAddress()updatefree (partner)Get user’s derived SOL address
signSend(to, lamports, blockhash)update0.1% atomicSign SOL transfer — caller broadcasts
sendSol(to, lamports)update0.1% atomicAutonomous send — not used by OhShii (see Flow 1 for why the sign-only path is preferred)
signDeposit(target, lamports, blockhash, borrowParams?)update0%Sign-only deposit — RECOMMENDED
depositSol(target, lamports, borrowParams?)update0%Autonomous deposit — not used by OhShii (see Flow 1)

Partner management (called by allowlisted backend)

Section titled “Partner management (called by allowlisted backend)”
MethodDescription
registerPartnerUsers(Principal[])Vouch for users — max 50K per partner
removePartnerUsers(Principal[])Remove previously vouched users

Registered treasury targets for depositSol

Section titled “Registered treasury targets for depositSol”
TargetCanisterWhat you get
ICP-SOL Swapw2vjc-2yaaa-aaaab-ae6zq-caiICP at swap-pool rate
mSOL (ckSOL)crmds-kqaaa-aaaaf-qf5aq-caimSOL
SOL Borrow V3p7teu-wyaaa-aaaab-afnvq-caiUSDC loan against SOL collateral

ComponentStatusNotes
ICP → SOL swapWorkingSwap-pool direct call; governance-configurable gateway fee charged only after a successful swap; reserve-exhaustion guard in the UI
Price/quote queriesWorkingSwap-pool query calls (both directions surfaced in the UI)
User registration (partner)WorkingBackend register_menese_user(), LAZY (on SOL→ICP intent, gateway-health-gated); graceful degradation for cached users
Partner revocationWorkingadmin_remove_menese_user (ONS proposal) → removePartnerUsers, bulk revoke from the stable vouched set
SOL address derivationWorkingSovereign Send getMyAddress() (cached users survive allowlist resets); lazy, gateway-health-gated
SOL balance checkWorkingVia PublicNode/dRPC/Ankr RPCs; visibility-gated 10s poll with failure backoff
SOL → ICP depositWorkingSign-only (signDeposit + frontend broadcast) with on-chain confirmation polling; tx signature + explorer link surfaced
Broadcast confirmationWorkingSequential RPC fallback + getSignatureStatuses polling; success reported only after confirmed/finalized
DAO-page Solana onrampWorkingSolanaDaoParticipationBox (SOL → ICP → LGE) on Active campaigns, fail-closed on gateway/pause/breaker/Guest-cap, hands off to the shared LGE purchase panel
Gateway health surfacingWorkingShared SolanaGatewayStatus (SolanaTab + DAO box) + SystemStatus “Menese / Solana Gateway” section (env-aware getPartnerUserCount; green only when every check is proven)
SOL → mSOL depositNot exposedConfirmed broadcast + expectCkSolDeposit in the frontend code; no UI
mSOL balance displayNot exposedHidden together with the whole mSOL surface
mSOL → SOL redemptionNot exposedICRC-2 approve in the frontend code; no redeem UI
mSOL → ICP conversionNot exposedDepends on the redemption above
Gateway fee (DAO-managed)WorkingSolanaGatewayFeeConfig on-chain, admin_set_solana_gateway_fee + ONS proposal, frontend reads live with a safe 30 bps fallback
Solana treasury visibility (DAO)WorkingSystemStatus section: partner fee-share + Menese protocol fee-treasury balances via public RPC, explorer links
Solana treasury movement / auto-sweepNot applicableOhShii’s flows (signDeposit + swap pool) don’t carry the 0.1% protocol fee, so no fee-share accrues — nothing to sweep
SOL→ICP awaiting-credit verificationWorkingwaitForIcpCredit polls the user’s ICP balance after Solana confirmation; timeout escalates with signature + explorer link + depositId (Menese-side, non-custodial)
ICP→SOL live status pollingWorkingpollSwapUntilSettled surfaces the pool SwapRecord status (Pending/Refunded/Failed/Completed) in the LIVE flow; the error path re-attaches to a swap record the failed call may have created
Fault attributionWorkingEvery gateway error is tagged with one owner (OhShii-config / Menese / Dfinity-minter / ICPSwap / network-user) + the correct next action (utils/crosschainFaults.js)
Swap historyWorkingNewest-first (createdAt desc) + “Load more” pagination in SolanaTab
Dust displayWorkingRent-exempt-only residues render as “≈0 SOL” with tap-to-reveal exact + a precise why-note (0.00089088 rent + 0.000045 fee = 0.00093588 reserved); spendable math untouched
Treasury panel noteWorkingONS treasury panel carries a “Solana (Menese) gateway — visibility only” row: the DAO’s Solana revenue is the ICP gateway fee (already in the ICP row); no SOL is sweepable

The frontend uses an ordered list of Solana RPCs with sequential fallback (first success wins). Source of truth: src/frontend/src/utils/solConstants.js (SOLANA_RPCS).

ProviderURLNotes
Helius (primary, keyed)https://mainnet.helius-rpc.com/?api-key=…Domain-restricted API key (Helius Allowed Domains, Origin/Referer check). Skipped entirely when no key is configured
PublicNode (fallback 1)https://solana-rpc.publicnode.comFree, public, no API key
dRPC (fallback 2)https://solana.drpc.orgFree, public
Ankr (fallback 3)https://rpc.ankr.com/solanaFree, public

The three public RPCs match the Menese SDK order. The Helius key is domain-restricted; on other origins (e.g. local development) the Helius call fails and the loop falls through to the public RPCs.

The frontend’s Content Security Policy (connect-src in src/frontend/.ic-assets.json5) allows these RPC hosts.


Stuck-state observability & recovery boundaries

Section titled “Stuck-state observability & recovery boundaries”

Design invariant: observability is DERIVED from on-chain state the user can already read for their own principal (tx signature, ICP block/balance, pool SwapRecord, getMySwaps, Sovereign-Send depositId) plus client-side localStorage. There is NO per-user on-chain error/status log — that would be a Sybil/DDoS vector.

SOL → ICP (deposit consumed, ICP not credited): after the Solana tx confirms, the UI polls the user’s ICP balance (waitForIcpCredit) and, if no credit is observed in ~4 minutes, escalates with the exact references (tx signature + Solscan link + Sovereign-Send depositId). This state is Menese-side: every recovery method on the swap pool is admin-gated by Menese’s key, and OhShii/DAO/guardian has NO lever to move funds inside the pool. The user’s reference set is what Menese needs to investigate.

ICP → SOL (swap errored or hangs): the pool’s SwapRecord is authoritative. The live flow polls getSwap(swapId) and renders Refunded (ICP returned by the pool), Failed (escalate to Menese with the swap id) or Completed. Funds on the user’s OWN derived SOL address are always re-spendable by retry — only the rent-exempt residue stays.

Fault owners: every error carries exactly one of ohshii-config (DAO toggle — wait), menese (pool/Sovereign Send — retry/escalate with references), dfinity-minter (ckBTC side), icpswap (ckBTC/ICP pool — self-service reclaim), network-user (retry/wait/top-up).

  • User registration goes through the backend’s registerPartnerUsers, not a frontend registerSession(developerKey).
  • Addresses come from Sovereign Send’s getMyAddress(), not MeneseSDK’s getMySolanaAddress().
  • No swapSolToIcp() call follows signDeposit() — ICP release is automatic in the partner flow.
  • Deposits use signDeposit() + frontend broadcast rather than the autonomous depositSol() (see Flow 1).
  • A send never spends the full SOL balance: 935,880 lamports (890,880 rent-exempt + 45,000 fee) stay reserved via maxSendLamports(). The ~0.00089 SOL rent-exempt residue stays on the derived address (Sovereign Send has no close-account method).
  • Cached users can still deposit when registerPartnerUsers fails; the SOL→ICP deposit button is disabled only when the user has no address.

FilePurpose
src/frontend/src/utils/solConstants.jsAll canister IDs, RPC URLs, fee constants, utility functions
src/frontend/src/utils/sovereignSend.jsSOL→ICP, SOL→mSOL, mSOL→SOL, mSOL→ICP, RPC helpers incl. broadcast + confirmation polling
src/frontend/src/utils/solSwap.jsICP→SOL (swap pool), price/quote queries, governance fee read (legacy autonomous-SDK paths removed)
src/frontend/src/hooks/useSolanaGatewayHealth.jsShared live gateway-health snapshot (partner count, shield/breaker, pool pause + reserves); fail-closed contract
src/frontend/src/components/common/SolanaGatewayStatus.jsxShared in-page status surface (SolanaTab + DAO participation box)
src/frontend/src/components/common/SolanaTxProgress.jsxShared tx progress/error callout with Solscan explorer link
src/frontend/src/components/common/MeneseGatewaySection.jsxSystemStatus “Menese / Solana Gateway” section (env-aware partner proof + treasury visibility)
src/frontend/src/components/SolanaDaoParticipationBox.jsxDAO-page SOL → ICP → LGE onramp (sibling of the Bitcoin box)
src/frontend/src/components/TokenManagement/SolanaTab.jsxFull Solana swap UI
src/frontend/src/components/TokenManagement/BridgeTab.jsxCross-Chain Gateway tab wrapper (Bitcoin + Solana); filename kept for API stability