For the complete documentation index, see llms.txt. This page is also available as Markdown.

Integration Guide

This guide walks through integrating the COTI Wallet Plugin into a React/wagmi dApp.

Principle: The provider owns the unlock flow. App pages call commands. Do not build custom unlock routing.

1. Wrap your app once

Mount WagmiRainbowKitProvider and PrivacyBridgeProvider near the root of your app:

import {
  PrivacyBridgeProvider,
  WagmiRainbowKitProvider,
  type OnboardModalTheme,
} from '@coti-io/coti-wallet-plugin';

const onboardTheme: OnboardModalTheme = {
  modal: { backgroundColor: '#ffffff', color: '#0f172a' },
  title: { color: '#0f172a' },
  description: { color: '#64748b' },
  primaryButton: { backgroundColor: '#1E29F6', color: '#ffffff' },
  saveOptionTitle: { color: '#0f172a' },
  saveOptionDescription: { color: '#64748b' },
  tooltipButton: { color: '#64748b' },
};

export function Root() {
  return (
    <WagmiRainbowKitProvider walletConnectProjectId={walletConnectProjectId}>
      <PrivacyBridgeProvider
        privateUnlock={{
          theme: onboardTheme,
          warning:
            'This dApp never stores or receives the AES key. Unlock stays inside the plugin.',
          onRestoreCancelled: () => {
            // Optional: show "User canceled" toast.
          },
        }}
      >
        <App />
      </PrivacyBridgeProvider>
    </WagmiRainbowKitProvider>
  );
}

Do not render <OnboardModal /> yourself. PrivacyBridgeProvider mounts the modal once internally via PrivateUnlockProvider.

Optional: network guard

Wrap page content with NetworkGuard to show a fallback UI when the wallet is on an unsupported chain:

2. Configure the plugin (before rendering hooks)

Call configureCotiPlugin() once at app startup, before any plugin hooks run:

See Configuration for all options.

3. Add an unlock control

Use usePrivateUnlock() for unlock orchestration in your UI:

usePrivateUnlock() API

Method / property
Description

isUnlocked

true when private balances are visible

isUnlocking

true while unlock/onboarding is in progress

unlock()

Start the unlock flow (succeeds only after private balances refresh)

lock()

Hide private balances

toggleLock()

Toggle between locked and unlocked

requireUnlock(action)

Run action after ensuring unlock; returns true if unlocked

reset()

Reset unlock UI state

4. Guard private actions

Use requireUnlock(action) for any operation that needs private balance or key access. It tries the cached session key first, then backup/Snap restore, then the onboarding modal only if needed.

5. Display token balances

Use the bounded context hooks to read wallet and token state:

Private balances appear only after unlock. Public balances are always readable on-chain.

6. Bridge public ↔ private tokens

Use the swap context for portal operations:

On COTI chains, bridging uses the native COTI bridge. On Sepolia and Avalanche Fuji, bridging routes through the PoD Privacy Portal.

Lock semantics

Understanding lock behavior is important for both UX and security:

Contract onboarding normally ends on the plugin success screen. The user can reveal/copy the raw AES key, then click Done. Any pending action passed to requireUnlock runs after Done.

Non-Snap flows show a Save Locally switch for optional encrypted backup. Snap onboarding hides that switch and still shows the persist step because the Snap always stores the key. If the user rejects the manual backup signature while Save Locally is on, the plugin skips the success screen and completes unlock when balance refresh succeeds. See AES Key Onboarding.

Onboarding routes

Wallet
First route
Fallback

MetaMask with Snap

Retrieve AES key from Snap

Contract onboarding if Snap is empty

Other wallets / MetaMask without Snap

Encrypted backup restore

Contract onboarding, then manual AES key input

See Configuration for encrypted backup and grant service setup, and AES Key Onboarding for the full contract onboarding flow.

Do

  • Use PrivacyBridgeProvider privateUnlock={...} once near app root.

  • Use usePrivateUnlock() for unlock orchestration: unlock(), lock(), toggleLock(), requireUnlock(action).

  • Use usePrivacyBridgeUnlock() inside or after that guard for private operations: sendPrivateToken, encryptPrivateValue, decryptPrivateValue.

  • Let the plugin own OnboardModal, Snap install, restore-only flow, contract onboarding, and the AES key success screen.

Do not

  • Do not render OnboardModal for unlock in app pages.

  • Do not call refreshPrivateBalances({ restoreOnly: true }) as a custom unlock flow.

  • Do not infer key existence from isPrivateUnlocked; it only means private balances are visible.

  • Do not delete Snap-stored keys or encrypted backups on lock. Lock only clears plaintext session state.

Error handling

The plugin throws typed CotiPluginError instances with structured CotiErrorCode values. See API Reference — Error codes for the full list.

Last updated

Was this helpful?