Menese Protocol Integration
Last updated: 2026-07-11
Repository
Section titled “Repository”- SDK: https://github.com/Menese-Protocol/MeneseSDK-V0
- mSOL/ICP-SOL integration: https://github.com/Menese-Protocol/MeneseSDK-V0/tree/main/msol-icp-sol-integration
- Sovereign Send integration: https://github.com/Menese-Protocol/MeneseSDK-V0/tree/main/sovereign-send-integration
- Billing guide: https://github.com/Menese-Protocol/MeneseSDK-V0/blob/main/msol-icp-sol-integration/developer-billing-integration-guide.md
OhShii is a Partner dApp
Section titled “OhShii is a Partner dApp”OhShii Launcher is an allowlisted partner on Menese’s Sovereign Send canister. This means:
- Free address derivation — our vouched users don’t pay 45B cycles per
getMyAddress() - No subscription required — we use the per-tx fee model, not the MeneseSDK subscription
- 20% revenue share — of the 0.1% protocol fee on
sendSol/signSendoperations - Backend canister allowlisted — can call
registerPartnerUsers()to vouch for users
Partner vs non-partner
Section titled “Partner vs non-partner”| Partner (OhShii) | Non-partner | |
|---|---|---|
getMyAddress() cost | Free (vouched users) | 45B cycles (caller pays) |
| Subscription needed | No | Yes ($20-$323/mo) or cycles |
| Revenue share | 20% of 0.1% protocol fee | None |
| User registration | Backend calls registerPartnerUsers() | Must use registerSession(developerKey) |
depositSol() fee | 0% (pool’s own fee applies) | 0% (same) |
sendSol() fee | 0.1% atomic | 0.1% atomic |
Partner configuration (by Menese team)
Section titled “Partner configuration (by Menese team)”- Allowlisted backend canister:
vil43-piaaa-aaaal-qsibq-cai(prod) - Partner fee-share address:
BuqZagKKiRFUfxNFRKK7w3JymQgxuCy2rxFBdQTysMW4(assigned by Menese)
Canister IDs
Section titled “Canister IDs”| Canister | ID | Role |
|---|---|---|
| Sovereign Send | fxjsq-raaaa-aaaab-agdaa-cai | Signing canister — 0.1% fee model, no subscription |
| ICP-SOL Swap Pool (chain-key) | w2vjc-2yaaa-aaaab-ae6zq-cai | SOL↔ICP swaps at minimal slippage. NOT an oracle — legacy CROSSCHAIN_ORACLE_* code symbols are kept for API stability |
| ckSOL minter | crmds-kqaaa-aaaaf-qf5aq-cai | mSOL minting/redemption |
| mSOL ledger (ICRC-2) | 2ykjj-eyaaa-aaaae-af4ma-cai | mSOL token |
| MeneseSDK (legacy) | urs2a-ziaaa-aaaad-aembq-cai | Old subscription model — used only for legacy recovery |
| ICP Ledger | ryjl3-tyaaa-aaaaa-aaaba-cai | ICP token |
Architecture Overview
Section titled “Architecture Overview”flowchart TB Browser["Browser (Frontend)"] Browser --> Sovereign["Sovereign Send (fxjsq)"] Browser --> Pool["ICP-SOL Swap Pool (w2vjc)"] Browser --> Backend["OhShii Backend (prod: vil43)"] Sovereign -->|signDeposit| Solana["Solana Network (threshold Schnorr)"] Pool -->|swapIcpToSol| Solana Backend -->|registerPartnerUsers| SovereignConversion Flows
Section titled “Conversion Flows”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.
sequenceDiagram participant Wallet as User Wallet (Phantom) participant Frontend as OhShii Frontend participant Sovereign as Sovereign Send (fxjsq) participant RPC as Solana RPC participant Pool as ICP-SOL Pool (w2vjc)
Wallet->>Frontend: 1. Send SOL to derived address Frontend->>RPC: 2. fetchBlockhash Frontend->>Sovereign: 3. signDeposit(pool, lamports, blockhash) Sovereign-->>Frontend: signedTxBase64 Frontend->>RPC: 4. broadcastSolTx(signedTxBase64) RPC->>Pool: 5. Pool receives SOL, credits ICP Pool-->>Frontend: 6. 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, returningokwhile the SOL doesn’t actually move. - The sign-only path (
signDeposit) lets our frontend fetch a fresh blockhash and broadcast directly via Solana RPC, which is faster (~0 cycles) and more reliable. - Multiple partner dApps have experienced the autonomous broadcast returning
okwhile the Solana transaction fails silently.
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:
signDeposithas 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. (NOT 50,000.) - No second call needed after broadcast — ICP release is automatic
- Cached users can deposit even if
registerPartnerUsersis 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.
sequenceDiagram participant User participant Frontend as OhShii Frontend participant Pool as ICP-SOL Swap Pool (w2vjc)
Note over Frontend: 1. Gateway fee (0.3% to backend) Note over Frontend: 2. icrc2_approve (ICP_LEDGER, spender=pool) Frontend->>Pool: 3. swapIcpToSol(e8s, solAddress) Note over Pool: 4. Pool pulls ICP, signs SOL tx, broadcasts to Solana Pool-->>User: 5. SOL arrives at Solana addrFrontend code path: solSwap.js → swapIcpToSol(e8s, solAddress, identityOrAgent)
Key facts:
- The swap pool pulls ICP via ICRC-2
transfer_fromafter the user’sicrc2_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 viaadmin_set_solana_gateway_fee(admin or ONS proposal, 1000 bps hard cap). 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 small (verified 2026-07-11: ~1,124 ICP / ~2.96 SOL); large swaps can hit reserve exhaustion — the UI reads
getCanisterInfoand 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: ⚠️ CODE-COMPLETE, NOT EXPOSED, UNVERIFIED. depositSolForMsol now 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. The flow is deliberately NOT reachable from any UI until the full round-trip (Flow 3 + Flow 4) is verified with a real deposit — whether Menese’s layer would also auto-mint remains an open question with Menese.
1. User sends SOL to derived address (same as Flow 1)2. Frontend: fetchSolanaBlockhash() via RPC3. Frontend: sovereignSend.signDeposit(CKSOL_MINTER, lamports, blockhash)4. Frontend: broadcastSolTx(signedTxBase64) via Solana RPC5. Frontend: cksol.expectCkSolDeposit(txHash, lamports) ← verifies on-chain6. ckSOL minter mints mSOL to user's principalNote: 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.
Flow 4: mSOL → SOL (redemption)
Section titled “Flow 4: mSOL → SOL (redemption)”Status: ⚠️ CODE-COMPLETE, NOT EXPOSED, UNVERIFIED. redeemMsolToSol() now performs the required ICRC-2 approve on the mSOL ledger (spender = ckSOL minter, amount + fee headroom) before requestCkSolRedemption, and the broadcast is confirmation-polled. Still not user-reachable in the UI (no redeem button) and it must stay that way until the full mSOL round-trip is verified end-to-end with a real deposit/redemption.
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 signedTxBase644. Frontend: broadcasts signedTxBase64 to Solana via RPC5. User receives SOL at their Solana addressFlow 5: mSOL → ICP (two-step, presented as one action)
Section titled “Flow 5: mSOL → ICP (two-step, presented as one action)”Status: ⚠️ CODE-COMPLETE, NOT EXPOSED, UNVERIFIED — phase 1 is redeemMsolToSol() (Flow 4, approve now implemented). Inherits Flow 4’s status: not user-reachable and must not be exposed until the round-trip is verified.
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
Summary: Where does mSOL fit?
Section titled “Summary: Where does mSOL fit?”mSOL is a separate product, not an intermediate step in SOL↔ICP. Users can:
- Convert SOL to mSOL to hold yield-bearing SOL (1-2.2% APY)
- 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
User Registration (Partner Flow)
Section titled “User Registration (Partner Flow)”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 addressThe backend rate-limits registrations (Shield: per-caller + global caps, PoW under Defcon) and records each vouched principal in a stable working set.
Revocation (incident response)
Section titled “Revocation (incident response)”admin_remove_menese_user(opt vec principal) (admin or ONS governance; 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 — the incident-time lever for
de-authorizing abusive already-vouched principals without waiting on a
governance vote mid-incident.
Known limitation: the working set only tracks principals vouched after the
upgrade that introduced it (Sovereign Send has no enumeration API), so the
bulk path cannot see pre-upgrade vouches — revoke those by passing the
principals explicitly (the explicit path forwards them verbatim). Full
historical coverage otherwise requires Menese (adminRemoveVouchedUser is
Menese-key-only) — an open item with Menese. 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.cachedUserssurvives 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.
Fee Structure
Section titled “Fee Structure”Fees we pay (to Menese/pool)
Section titled “Fees we pay (to Menese/pool)”| Operation | Fee | Paid to |
|---|---|---|
getMyAddress() | Free (partner) | — |
depositSol() | 0% Sovereign Send + pool’s 0.15% LP fee | Pool LPs |
sendSol() | 0.1% atomic | Menese protocol |
swapIcpToSol() | 0.15% LP fee + dynamic spread (max 0.3%) | Pool LPs + swap pool |
Fees we charge (to users)
Section titled “Fees we charge (to users)”| Operation | Fee | Collected by |
|---|---|---|
| ICP → SOL | Governance-configurable, default 0.3% (30 bps); hard cap 10% | Backend canister (DAO fee treasury) — only after the swap succeeds |
| SOL → ICP | Not yet implemented frontend-side | — |
The fee is stored on-chain (SolanaGatewayFeeConfig, get_solana_gateway_fee_config
query) and set by admin_set_solana_gateway_fee — admin command or 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).
⚠️ Collection is client-enforced and therefore best-effort by design: a user who rejects the fee transfer prompt after a successful swap (or runs a modified client) skips it — the same was true of every prior ordering, since the pool does not enforce our fee. Robust, non-skippable collection would require a pool-side fee split (Menese) or routing swaps through the backend; tracked as a design follow-up.
Revenue share (from Menese)
Section titled “Revenue share (from Menese)”- 20% of the 0.1% protocol fee on
sendSol/signSendoperations - Distributed in SOL on Solana, 7-day epochs, minimum 0.05 SOL
- Partner fee-share address:
BuqZagKKiRFUfxNFRKK7w3JymQgxuCy2rxFBdQTysMW4 - Applies to
sendSol/signSendonly. OhShii’s conversion flows use fee-freesignDepositdeposits 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 the Solana rent-exempt minimum (~0.0009 SOL, as of 2026-07-12).
Solana Treasury (DAO management)
Section titled “Solana Treasury (DAO management)”Status: balance visibility SHIPPED. 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.
Addresses surfaced
Section titled “Addresses surfaced”- Partner fee-share address
BuqZagKKiRFUfxNFRKK7w3JymQgxuCy2rxFBdQTysMW4— the address Menese assigns for the 20% share onsendSol/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 (verified 2026-07-12: anonymous, unrelated principals, and the OhShii backend all receive the same address). The backend fetches and caches it (admin_fetch_menese_treasury_address, admin/ONS-gated) so the status page can read it via the anonymousget_menese_partner_statusquery and show its balance. It is Menese-controlled, not derived from our principal.
Fee-share and OhShii’s flows
Section titled “Fee-share and OhShii’s flows”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. This is environment-aware: it reflects whichever backend is actually deployed, so it is the correct signal after a dev↔prod switch (the switch scripts do not remap Menese IDs — they now print a non-fatal post-switch advisory that runs this exact check against the target backend). 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_statusquery 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).
3-layer defense in Sovereign Send
Section titled “3-layer defense in Sovereign Send”- Circuit breaker — hard stop when canister cycles < 500B. Cached users keep working.
- Global throttle — max 20 new derivations per 60s sliding window.
- Caller allowlist — partner canister bypasses throttle (not circuit breaker).
Cost model
Section titled “Cost model”- Unknown callers: attach 45B cycles per derivation (30B cost + 15B profit)
- Cached users (anyone who derived before): free, bypass all layers
- Allowlisted partners + vouched users (us): free derivation
Sovereign Send API Reference
Section titled “Sovereign Send API Reference”Core operations
Section titled “Core operations”| Method | Type | Fee | Description |
|---|---|---|---|
getMyAddress() | update | free (partner) | Get user’s derived SOL address |
signSend(to, lamports, blockhash) | update | 0.1% atomic | Sign SOL transfer — caller broadcasts |
sendSol(to, lamports) | update | 0.1% atomic | Autonomous — avoid (stale blockhash bug) |
signDeposit(target, lamports, blockhash, borrowParams?) | update | 0% | Sign-only deposit — RECOMMENDED |
depositSol(target, lamports, borrowParams?) | update | 0% | Autonomous deposit — avoid (stale blockhash bug) |
Partner management (called by allowlisted backend)
Section titled “Partner management (called by allowlisted backend)”| Method | Description |
|---|---|
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”| Target | Canister | What you get |
|---|---|---|
| ICP-SOL Swap | w2vjc-2yaaa-aaaab-ae6zq-cai | ICP at swap-pool rate |
| mSOL (ckSOL) | crmds-kqaaa-aaaaf-qf5aq-cai | mSOL (yield-bearing wrapped SOL) |
| SOL Borrow V3 | p7teu-wyaaa-aaaab-afnvq-cai | USDC loan against SOL collateral |
Implementation Status
Section titled “Implementation Status”| Component | Status | Notes |
|---|---|---|
| ICP → SOL swap | Working | Swap-pool direct call; governance-configurable gateway fee charged only after a successful swap; reserve-exhaustion guard in the UI |
| Price/quote queries | Working | Swap-pool query calls (both directions surfaced in the UI) |
| User registration (partner) | Working | Backend register_menese_user(), LAZY (on SOL→ICP intent, gateway-health-gated); graceful degradation for cached users |
| Partner revocation | Working | admin_remove_menese_user (admin/ONS) → removePartnerUsers, bulk revoke from the stable vouched set |
| SOL address derivation | Working | Sovereign Send getMyAddress() (cached users survive allowlist resets); lazy, gateway-health-gated |
| SOL balance check | Working | Via PublicNode/dRPC/Ankr RPCs; visibility-gated 10s poll with failure backoff |
| SOL → ICP deposit | Working | Sign-only (signDeposit + frontend broadcast) with on-chain confirmation polling; tx signature + explorer link surfaced |
| Broadcast confirmation | Working | Sequential RPC fallback + getSignatureStatuses polling; success reported only after confirmed/finalized |
| DAO-page Solana onramp | Working | SolanaDaoParticipationBox (SOL → ICP → LGE) on Active campaigns, fail-closed on gateway/pause/breaker/Guest-cap, hands off to the shared LGE purchase panel |
| Gateway health surfacing | Working | Shared SolanaGatewayStatus (SolanaTab + DAO box) + SystemStatus “Menese / Solana Gateway” section (env-aware getPartnerUserCount, never-green discipline) |
| SOL → mSOL deposit | Code-complete, NOT exposed, unverified | Confirmed broadcast + expectCkSolDeposit wired; must not be exposed until the round-trip is verified (auto-mint question open with Menese) |
| mSOL balance display | Not exposed | Hidden together with the whole mSOL surface until the round-trip is verified |
| mSOL → SOL redemption | Code-complete, NOT exposed, unverified | ICRC-2 approve implemented; no redeem UI until verified end-to-end |
| mSOL → ICP conversion | Code-complete, NOT exposed, unverified | Depends on the redemption above |
| Legacy SOL recovery | Removed | Funds recovered; the dormant urs2a autonomous-SDK code paths were deleted from solSwap.js |
| Gateway fee (DAO-managed) | Working | SolanaGatewayFeeConfig on-chain, admin_set_solana_gateway_fee + ONS proposal, frontend reads live with a safe 30 bps fallback |
| Solana treasury visibility (DAO) | Working | SystemStatus section: partner fee-share + Menese protocol fee-treasury balances via public RPC, explorer links |
| Solana treasury movement / auto-sweep | Not applicable | OhShii’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 verification | Working | waitForIcpCredit polls the user’s ICP balance after Solana confirmation; timeout escalates with signature + explorer link + depositId (Menese-side, non-custodial) |
| ICP→SOL live status polling | Working | pollSwapUntilSettled 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 attribution | Working | Every gateway error is tagged with one owner (OhShii-config / Menese / Dfinity-minter / ICPSwap / network-user) + the correct next action (utils/crosschainFaults.js) |
| Swap history | Working | Newest-first (createdAt desc) + “Load more” pagination in SolanaTab |
| Dust display | Working | Rent-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 note | Working | ONS 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 |
Solana RPC Configuration
Section titled “Solana RPC Configuration”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).
| Provider | URL | Notes |
|---|---|---|
| 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.com | Free, public, no API key |
| dRPC (fallback 2) | https://solana.drpc.org | Free, public |
| Ankr (fallback 3) | https://rpc.ankr.com/solana | Free, public |
The three public RPCs match the Menese SDK order. The Helius key ships in the frontend bundle by design (the app is a fully on-chain static frontend — there is no server to hold it); abuse protection is the domain allow list configured on the key, so requests from other origins are rejected by Helius. On non-allowed origins (e.g. local development) the Helius call fails and the loop falls through to the public RPCs.
RPCs must be added to CSP connect-src in .ic-assets.json5.
Stuck-state observability & recovery boundaries (Phase 2)
Section titled “Stuck-state observability & recovery boundaries (Phase 2)”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. (A partner-callable refund is an open ask with Menese.)
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).
What NOT to do
Section titled “What NOT to do”- Do NOT use
registerSession(developerKey)from frontend — replaced by backendregisterPartnerUsers - Do NOT call
getMySolanaAddress()on MeneseSDK — use Sovereign Send’sgetMyAddress() - Do NOT call
swapSolToIcp()aftersignDeposit()— ICP release is automatic in the partner flow - Do NOT use autonomous
depositSol()— usesignDeposit()+ frontend broadcast (autonomous has stale blockhash bug) - Do NOT send full SOL balance — always reserve 935,880 lamports (890,880 rent-exempt + 45,000 fee) via
maxSendLamports(). ~0.00089 SOL rent-exempt residue stays on the derived address and cannot currently be swept (no close-account method exists on Sovereign Send) - Do NOT block cached users when
registerPartnerUsersfails — they can still deposit - Do NOT block the SOL→ICP deposit button based on registration status — only block if the user has no address
Frontend Code Map
Section titled “Frontend Code Map”| File | Purpose |
|---|---|
src/frontend/src/utils/solConstants.js | All canister IDs, RPC URLs, fee constants, utility functions |
src/frontend/src/utils/sovereignSend.js | SOL→ICP, SOL→mSOL, mSOL→SOL, mSOL→ICP, RPC helpers incl. broadcast + confirmation polling |
src/frontend/src/utils/solSwap.js | ICP→SOL (swap pool), price/quote queries, governance fee read (legacy autonomous-SDK paths removed) |
src/frontend/src/hooks/useSolanaGatewayHealth.js | Shared live gateway-health snapshot (partner count, shield/breaker, pool pause + reserves); fail-closed contract |
src/frontend/src/components/common/SolanaGatewayStatus.jsx | Shared in-page status surface (SolanaTab + DAO participation box) |
src/frontend/src/components/common/SolanaTxProgress.jsx | Shared tx progress/error callout with Solscan explorer link |
src/frontend/src/components/common/MeneseGatewaySection.jsx | SystemStatus “Menese / Solana Gateway” section (env-aware partner proof + treasury visibility) |
src/frontend/src/components/SolanaDaoParticipationBox.jsx | DAO-page SOL → ICP → LGE onramp (sibling of the Bitcoin box) |
src/frontend/src/components/TokenManagement/SolanaTab.jsx | Full Solana swap UI |
src/frontend/src/components/TokenManagement/BridgeTab.jsx | Cross-Chain Gateway tab wrapper (Bitcoin + Solana); filename kept for API stability |