From fbaa66520f25c2100dcadb8ce9494525499fd4dc Mon Sep 17 00:00:00 2001 From: Percival Lucena Date: Tue, 26 May 2026 15:49:05 -0300 Subject: [PATCH 1/2] docs: add High-Level DApp Utilities to typescript-sdk documentation --- build-on-coti/tools/typescript-sdk.md | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/build-on-coti/tools/typescript-sdk.md b/build-on-coti/tools/typescript-sdk.md index b05aa0f..1c71a30 100644 --- a/build-on-coti/tools/typescript-sdk.md +++ b/build-on-coti/tools/typescript-sdk.md @@ -152,3 +152,44 @@ Decrypts an AES-encrypted ciphertext representing a string. Generates a random 128-bit AES key. * **Returns:** A string containing the random bytes. + +## High-Level DApp Utilities + +The SDK also includes high-level utilities commonly needed by front-end applications and wallet integrations. + +```typescript +function normalizeAesKey(aesKey: string): string +``` + +Normalizes an AES key string by removing the `0x` prefix, lowering its case, and ensuring it holds valid hexadecimal characters representation (for exactly 128 bits or 256 bits). + +* **Parameters:** + * `aesKey`: The user's AES key as a hex string. +* **Returns:** The normalized AES key. +* **Throws:** An HTTP error or RangeError when the AES key is invalid. + +```typescript +function decryptCtUint64(ciphertext: ctUint, aesKey: string, options?: DecryptionOptions): bigint | null +``` + +A robust wrapper around `decryptUint` that cleanly handles zero balances and applies a sanity check (`isInsaneDecryptedValue`) to prevent garbage output when a wrong AES key is accidentally supplied. + +* **Parameters:** + * `ciphertext`: The encrypted 64-bit ciphertext. + * `aesKey`: The user key for decryption. + * `options`: Optional arguments like `decimals` and `insaneThresholdBase` to tune the sanity boundary limits. +* **Returns:** The decrypted big integer, or `null` if the decryption breached sanity threshold limits. + +```typescript +function buildItSignature(signerAddress: string, contractAddress: string, functionSelector: string, ciphertext: bigint, privateKey: string): string +``` + +Builds the standardized COTI IT signature required by encrypted parameters in COTI L2 smart contracts, computing `solidityPackedKeccak256` and constructing the final properly padded EVM verification signature mapping 27/28 to 0/1. + +* **Parameters:** + * `signerAddress`: The signer's wallet public address. + * `contractAddress`: Target COTI L2 smart-contract address. + * `functionSelector`: The EVM 4-byte selector. + * `ciphertext`: The resulting input-text encoded `bigint`. + * `privateKey`: Signer's private key string. +* **Returns:** A properly normalized 65-byte hex string (including the v-byte mapping padding) ready to be embedded as the IT signature. From 2c59bcb7fafcee01d8fd3114616583096b9646ee Mon Sep 17 00:00:00 2001 From: Percival Lucena Date: Tue, 2 Jun 2026 14:32:09 -0300 Subject: [PATCH 2/2] docs: add COTI Wallet Plugin documentation pages Add multi-page documentation for coti-wallet-plugin under build-on-coti/tools/coti-wallet-plugin/ including: - Overview, installation, and supported networks - Wallet integration guide (detection, adding new wallets) - API reference (useWallet, usePrivateTokenBalance, etc.) - Privacy Bridge hooks and usage - Cross-Chain Bridge hooks, utilities, and examples - Usage examples and example app setup - Security model documentation Update SUMMARY.md navigation to include new pages. --- SUMMARY.md | 7 + .../tools/coti-wallet-plugin/README.md | 81 ++++++++ .../tools/coti-wallet-plugin/api-reference.md | 96 ++++++++++ .../coti-wallet-plugin/cross-chain-bridge.md | 147 +++++++++++++++ .../tools/coti-wallet-plugin/examples.md | 177 ++++++++++++++++++ .../coti-wallet-plugin/privacy-bridge.md | 61 ++++++ .../tools/coti-wallet-plugin/security.md | 35 ++++ .../coti-wallet-plugin/wallet-integration.md | 65 +++++++ 8 files changed, 669 insertions(+) create mode 100644 build-on-coti/tools/coti-wallet-plugin/README.md create mode 100644 build-on-coti/tools/coti-wallet-plugin/api-reference.md create mode 100644 build-on-coti/tools/coti-wallet-plugin/cross-chain-bridge.md create mode 100644 build-on-coti/tools/coti-wallet-plugin/examples.md create mode 100644 build-on-coti/tools/coti-wallet-plugin/privacy-bridge.md create mode 100644 build-on-coti/tools/coti-wallet-plugin/security.md create mode 100644 build-on-coti/tools/coti-wallet-plugin/wallet-integration.md diff --git a/SUMMARY.md b/SUMMARY.md index 9448ef4..9617222 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -62,6 +62,13 @@ * [Remix Plugin](build-on-coti/tools/remix-plugin.md) * [COTI MetaMask Snap](build-on-coti/tools/coti-metamask-snap/README.md) * [Snap Integration](build-on-coti/tools/coti-metamask-snap/snap-integration.md) + * [COTI Wallet Plugin](build-on-coti/tools/coti-wallet-plugin/README.md) + * [Wallet Integration](build-on-coti/tools/coti-wallet-plugin/wallet-integration.md) + * [API Reference](build-on-coti/tools/coti-wallet-plugin/api-reference.md) + * [Privacy Bridge](build-on-coti/tools/coti-wallet-plugin/privacy-bridge.md) + * [Cross-Chain Bridge](build-on-coti/tools/coti-wallet-plugin/cross-chain-bridge.md) + * [Examples](build-on-coti/tools/coti-wallet-plugin/examples.md) + * [Security](build-on-coti/tools/coti-wallet-plugin/security.md) * [Developer Sandbox](build-on-coti/tools/developer-sandbox.md) * [Private Messaging](private-messaging/README.md) * [Quickstart](private-messaging/quickstart.md) diff --git a/build-on-coti/tools/coti-wallet-plugin/README.md b/build-on-coti/tools/coti-wallet-plugin/README.md new file mode 100644 index 0000000..55a7079 --- /dev/null +++ b/build-on-coti/tools/coti-wallet-plugin/README.md @@ -0,0 +1,81 @@ +# COTI Wallet Plugin + +The [**COTI Wallet Plugin**](https://github.com/coti-io/coti-wallet-plugin) is a privacy enhancement layer for existing dApps and wallets. It is not a standalone wallet application — it is designed to be injected into your existing React/wagmi stack to seamlessly add COTI network privacy capabilities to any EIP-1193 compatible wallet. + +## Overview + +By hooking into standard EIP-1193 connections using **wagmi v2 and RainbowKit** as the underlying connection infrastructure, the plugin transparently adds COTI privacy features (AES key derivation, balance decryption, confidential transfers) to whatever wallet the user prefers to use. + +### Key Capabilities + +* **AES Key Management** — Retrieves encryption keys via MetaMask Snap or the COTI Onboarding Contract (multi-wallet support via RainbowKit + wagmi v2) +* **Balance Decryption** — Fetches encrypted on-chain balances and decrypts them client-side +* **Privacy Bridge** — Orchestrates Portal In (deposit) and Portal Out (withdraw) operations with fee estimation +* **Cross-Chain Bridge** — Transfers tokens between COTI and Ethereum networks (native and ERC20) with transaction tracking, limits management, and ongoing transaction monitoring +* **Network Configuration** — COTI Mainnet, Testnet, and Ethereum Mainnet chain definitions ready for wagmi/viem + +### Supported Wallets + +The plugin works with any EIP-1193 browser wallet via RainbowKit, including: + +* MetaMask +* Coinbase Wallet +* Trust Wallet +* Rainbow +* WalletConnect +* Safe +* Argent +* Ledger Live +* Brave Wallet +* Kraken Wallet +* Phantom (EVM) +* OKX Wallet +* Zerion +* TokenPocket +* Bitget Wallet +* Any injected EIP-1193 browser wallet + +### How It Works + +When a user connects through RainbowKit, the plugin detects the wallet type via wagmi's stable `connector.id`. For MetaMask, it routes AES key retrieval through the COTI Snap. For all other wallets, it wraps the wallet's EIP-1193 provider into a `@coti-io/coti-ethers` BrowserProvider, obtains a signer, and calls `generateOrRecoverAes()` on the COTI Onboarding Contract — which prompts the user for a single signature to derive or recover their encryption key. + +This means any wallet that supports standard message signing can participate in COTI's privacy features without needing a custom extension or snap. + +## Installation + +```bash +npm install @coti-io/coti-wallet-plugin +``` + +### Peer Dependencies + +```bash +npm install react ethers viem @coti-io/coti-sdk-typescript @metamask/providers @rainbow-me/rainbowkit wagmi @tanstack/react-query +``` + +### Build Commands + +```bash +npm run build # Produces dist/index.js (CJS) + dist/index.mjs (ESM) + dist/index.d.ts +npm run lint # TypeScript type check (tsc --noEmit) +npm run test # Run test suite (vitest) +npm run clean # Remove dist/ +``` + +## Supported Networks + +| Network | Chain ID | RPC | +| ----------------- | ---------- | -------------------------------------------- | +| COTI Mainnet | 2632500 | https://mainnet.coti.io/rpc | +| COTI Testnet | 7082400 | https://testnet.coti.io/rpc | +| Ethereum Mainnet | 1 | https://eth.llamarpc.com | +| Ethereum Sepolia | 11155111 | https://ethereum-sepolia-rpc.publicnode.com | + +### Cross-Chain Bridge Chain Pairs + +| Environment | COTI Network | Ethereum Network | +| ----------- | ------------------------ | ---------------------- | +| Testnet | COTI Testnet (7082400) | Sepolia (11155111) | +| Mainnet | COTI Mainnet (2632500) | Ethereum Mainnet (1) | + + diff --git a/build-on-coti/tools/coti-wallet-plugin/api-reference.md b/build-on-coti/tools/coti-wallet-plugin/api-reference.md new file mode 100644 index 0000000..1167ac4 --- /dev/null +++ b/build-on-coti/tools/coti-wallet-plugin/api-reference.md @@ -0,0 +1,96 @@ +# API Reference + +## Wallet Operations + +### `useWallet()` + +`useWallet()` is the recommended entry point for all wallet operations. It composes the lower-level hooks internally and manages the full wallet and AES key lifecycle. + +#### Connection + +* **`isConnected`** (`boolean`): Whether a wallet is currently connected. +* **`walletAddress`** (`string`): The connected wallet address. +* **`connect()`** (`() => Promise`): Opens the wallet connection flow. +* **`disconnect()`** (`() => Promise`): Revokes permissions and clears all state/caches. + +#### Network + +* **`networkName`** (`string`): Human-readable network name (e.g. "COTI Mainnet"). +* **`chainId`** (`string | null`): Current chain ID as a decimal string. +* **`switchNetwork(chainId)`** (`(hex: string) => Promise`): Requests the wallet to switch chains. +* **`COTI_MAINNET_ID`** (`string`): `"0x282b34"` +* **`COTI_TESTNET_ID`** (`string`): `"0x6c11a0"` +* **`SEPOLIA_ID`** (`string`): `"0xaa36a7"` + +#### AES Key Lifecycle + +* **`sessionAesKey`** (`string | null`): Current AES key (React state only, never persisted). +* **`isPrivateUnlocked`** (`boolean`): `true` when the session key is set. +* **`getAesKey(address)`** (`(addr: string) => Promise`): Retrieves the AES key (routes to Snap for MetaMask, or Onboarding Contract for others). +* **`unlockPrivateBalances()`** (`() => Promise`): Calls `getAesKey` for the current address and sets the session key. +* **`lockPrivateBalances()`** (`() => void`): Clears the session key and snap cache. +* **`clearKeyCache()`** (`() => void`): Forces a fresh retrieval on the next unlock. + +--- + +### `usePrivateTokenBalance()` + +Provides a unified interface to retrieve and decrypt private balances safely. + +```typescript +function fetchPrivateBalance( + userAddress: string, + aesKey: string, + contractAddress: string, + version: number, + decimals?: number +): Promise +``` + +Fetches and decrypts the balance. Pass `64` for legacy native p.COTI, or `256` for wrapped/bridged private ERC20s. + +* **Parameters:** + * `userAddress`: The wallet address to query. + * `aesKey`: The user's AES key for decryption. + * `contractAddress`: The private token contract address. + * `version`: `64` for legacy native p.COTI, `256` for bridged/private ERC20s. + * `decimals`: Token decimals (optional, defaults to 18). +* **Returns:** The decrypted balance as a formatted string. + +--- + +### `useBalanceUpdater(props)` + +Advanced orchestrator typically used at the Provider level to manage global token states and batch-fetch the entire wallet portfolio in parallel. + +```typescript +function updateAccountState( + account: string, + checkSnap?: boolean, + fetchPrivate?: boolean, + aesKeyOverride?: string, + chainOverride?: number +): Promise +``` + +Triggers a parallelized refresh of all configured COTI/ERC20 and p.ERC20 token balances. + +--- + +### `useAesKeyProvider(walletTypeInfo)` + +Routes AES key retrieval to Snap (MetaMask) or onboard contract (others). + +* **`getAesKey(address)`** (`Promise`): Resolves the AES key for the connected network/wallet type. +* **`isOnboarding`** (`boolean`): Indicates whether the onboarding process is actively running. +* **`onboardingError`** (`string | null`): Catches and exposes onboarding flow errors. + +--- + +## Auxiliary Hooks & Utilities + +* **`useBridgeData()`**: On-chain bridge state (fees, limits, paused status). +* **`useBridgeStatus()`**: Real-time bridge transaction status tracking. +* **`useNetworkEnforcer()`**: Enforces COTI-only networks, prompts chain switch. +* **`useSnap()` / `useMetamask()`**: Standalone hooks for legacy, pure-MetaMask implementations. +* **`formatTokenBalanceDisplay(balance)`**: Standardizes token display with thousand separators. diff --git a/build-on-coti/tools/coti-wallet-plugin/cross-chain-bridge.md b/build-on-coti/tools/coti-wallet-plugin/cross-chain-bridge.md new file mode 100644 index 0000000..61b8ffe --- /dev/null +++ b/build-on-coti/tools/coti-wallet-plugin/cross-chain-bridge.md @@ -0,0 +1,147 @@ +# Cross-Chain Bridge + +These hooks enable token transfers between COTI and Ethereum networks. They are distinct from the [Privacy Bridge](privacy-bridge.md) hooks which handle public ↔ private token movement on the same COTI chain. + +## `useCrossChainBridge()` + +Executes cross-chain bridge transactions with pre-validation (limits, minimums, balance checks). + +### Methods & Properties + +```typescript +function bridgeNative(amount: bigint, tokenId: string): Promise +``` + +Sends native token to the configured bridge recipient address. + +```typescript +function bridgeERC20(amount: bigint, tokenId: string, tokenAddress: `0x${string}`): Promise +``` + +Calls ERC20 `transfer()` to the configured bridge recipient. + +* **`isLoading`** (`boolean`): Whether a bridge transaction is in progress. +* **`error`** (`BridgeError | null`): Typed error with codes: `DAILY_LIMIT_EXCEEDED`, `BELOW_MINIMUM`, `INSUFFICIENT_BALANCE`, `TRANSACTION_FAILED`, `UNSUPPORTED_TOKEN`. +* **`txHash`** (`string | null`): Transaction hash after successful submission. + +--- + +## `useTransactionTracking(txHash, sourceNetworkId, destinationNetworkId)` + +Polls the tracking service for real-time transaction progress. + +* **`currentStep`** (`number | null`): Current stage (COTI-source: 4 steps, Ethereum-source: 3 steps). +* **`destinationHash`** (`string | null`): Destination chain tx hash when completed. +* **`failureReason`** (`string | null`): Reason if the transaction failed. +* **`failedStep`** (`number | null`): Step number where failure occurred. +* **`fee`** (`string | null`): Bridge fee from the tracking service. +* **`isLoading`** / **`error`**: Standard loading and error states. + +--- + +## `useBridgeTransactions(walletAddress, pageSize, pageNumber)` + +Fetches paginated transaction history with 30-second caching. + +* **`transactions`** (`BridgeTransaction[]`): Enriched transaction records with current step, completion status, and destination hash. +* **`totalCount`** (`number`): Total number of transactions for pagination. +* **`isLoading`** / **`error`**: Standard loading and error states. + +--- + +## `useBridgeLimits(walletAddress, tokenId)` + +Polls the Cap Meter API for user and global daily bridge limits (default: every 30 seconds). + +* **`userDailyLimit`** (`string`): User's remaining daily limit in human-readable token units. +* **`globalDailyLimit`** (`string`): Global remaining daily limit. +* **`isLoading`** / **`error`**: Standard loading and error states. + +--- + +## `useWalletStatus()` + +Reports wallet chain validity for cross-chain bridge operations and provides network switching. + +* **`isConnected`** (`boolean`): Connection status. +* **`address`** (`string`): Connected address or empty string. +* **`chainId`** (`number | null`): Current chain ID. +* **`isValidChain`** (`boolean`): Whether the current chain is valid for bridge operations. +* **`switchChain(chainId)`** (`(chainId: number) => Promise`): Switches to a valid chain. +* **`switchError`** (`string | null`): Error from last failed switch attempt. +* **`disconnect()`** (`() => void`): Disconnects the wallet. + +--- + +## `useOngoingTransactions()` + +Monitors all in-progress bridge operations via a module-level registry that persists across component mount/unmount. + +* **`transactions`** (`OngoingTransaction[]`): All in-progress transactions with current step, destination hash, and failure info. + +Use `registerTransaction({ tokenId, sourceChainId, destinationChainId, txHash })` to add a transaction to the registry. + +--- + +## Utility Functions + +| Function | Signature | Description | +| --- | --- | --- | +| `formatTokenAmount` | `(value: bigint, decimals: number) => string` | Formats bigint to decimal string without trailing zeros | +| `parseTokenAmount` | `(value: string, decimals: number) => bigint` | Parses decimal string to bigint (throws on invalid input) | +| `truncateDecimals` | `(value: string, maxDecimals: number) => string` | Truncates without rounding | +| `getActiveChains` | `(connectedChainId?) => ChainPair[]` | Returns valid chain pairs for the current environment | +| `isValidChain` | `(chainId, connectedChainId?) => boolean` | Checks if chain is valid for bridge operations | +| `getActiveChainById` | `(chainId, connectedChainId?) => ChainConfig \| undefined` | Returns chain config for a specific chain | +| `getActiveNetworks` | `(connectedChainId?) => ChainConfig[]` | Returns all active chain configs | +| `getCrossChainTokenConfig` | `(tokenId, chainId) => CrossChainTokenConfig \| undefined` | Returns token configuration for bridge operations | + +--- + +## Usage + +```tsx +import { + useCrossChainBridge, + useTransactionTracking, + useWalletStatus, + registerTransaction, + parseTokenAmount, +} from '@coti-io/coti-wallet-plugin'; + +function CrossChainBridge() { + const { bridgeNative, bridgeERC20, isLoading, error, txHash } = useCrossChainBridge(); + const { isValidChain, switchChain } = useWalletStatus(); + const tracking = useTransactionTracking(txHash, 7082400, 11155111); + + const handleBridgeNative = async () => { + if (!isValidChain) { + await switchChain(7082400); // Switch to COTI Testnet + } + + const amount = parseTokenAmount('10', 18); // 10 COTI + await bridgeNative(amount, 'COTI'); + + // Register for ongoing monitoring + if (txHash) { + registerTransaction({ + tokenId: 'COTI', + sourceChainId: 7082400, + destinationChainId: 11155111, + txHash, + }); + } + }; + + return ( +
+ + {error &&

Error: {error.message}

} + {tracking.currentStep &&

Step {tracking.currentStep} of 4

} + {tracking.destinationHash &&

Done! Dest tx: {tracking.destinationHash}

} +
+ ); +} +``` diff --git a/build-on-coti/tools/coti-wallet-plugin/examples.md b/build-on-coti/tools/coti-wallet-plugin/examples.md new file mode 100644 index 0000000..efa53fa --- /dev/null +++ b/build-on-coti/tools/coti-wallet-plugin/examples.md @@ -0,0 +1,177 @@ +# Usage Examples + +## Basic Setup (MetaMask Only) + +```tsx +import { configureCotiPlugin, PrivacyBridgeProvider } from '@coti-io/coti-wallet-plugin'; + +// Optional — configure before rendering (defaults work for mainnet) +configureCotiPlugin({ + snapId: 'npm:@coti-io/coti-snap', + defaultNetworkId: '0x282b34', // COTI Mainnet (2632500) +}); + +function App() { + return ( + + + + ); +} +``` + +## Any Wallet Setup (RainbowKit + wagmi) + +```tsx +import { WagmiRainbowKitProvider, PrivacyBridgeProvider } from '@coti-io/coti-wallet-plugin'; + +function App() { + return ( + + + + + + ); +} +``` + +## Fetch and Decrypt a Private Balance + +```tsx +import { usePrivateTokenBalance } from '@coti-io/coti-wallet-plugin'; + +function PrivateBalanceViewer({ userAddress, aesKey, tokenAddress }) { + const { fetchPrivateBalance } = usePrivateTokenBalance(); + + const handleFetch = async () => { + try { + // Pass 64 for legacy native p.COTI, or 256 for bridged/private ERC20s + const balance = await fetchPrivateBalance(userAddress, aesKey, tokenAddress, 256, 18); + console.log(`Decrypted Balance: ${balance}`); + } catch (error) { + console.error("Failed to decrypt:", error.message); + } + }; + + return ; +} +``` + +## Use the Privacy Bridge Hook + +```tsx +import { usePrivacyBridge } from '@coti-io/coti-wallet-plugin'; + +function BridgeComponent() { + const { handleSwap, isBridgingLoading } = usePrivacyBridge(); + + const handleDeposit = async () => { + // 100 tokens, sending to private side, token array index 0 + await handleSwap('100', 'to-private', 0); + }; + + return ( + + ); +} +``` + +## Cross-Chain Bridge (COTI ↔ Ethereum) + +```tsx +import { + useCrossChainBridge, + useTransactionTracking, + useWalletStatus, + registerTransaction, + parseTokenAmount, +} from '@coti-io/coti-wallet-plugin'; + +function CrossChainBridge() { + const { bridgeNative, bridgeERC20, isLoading, error, txHash } = useCrossChainBridge(); + const { isValidChain, switchChain } = useWalletStatus(); + const tracking = useTransactionTracking(txHash, 7082400, 11155111); + + const handleBridgeNative = async () => { + if (!isValidChain) { + await switchChain(7082400); // Switch to COTI Testnet + } + + const amount = parseTokenAmount('10', 18); // 10 COTI + await bridgeNative(amount, 'COTI'); + + // Register for ongoing monitoring + if (txHash) { + registerTransaction({ + tokenId: 'COTI', + sourceChainId: 7082400, + destinationChainId: 11155111, + txHash, + }); + } + }; + + return ( +
+ + {error &&

Error: {error.message}

} + {tracking.currentStep &&

Step {tracking.currentStep} of 4

} + {tracking.destinationHash &&

Done! Dest tx: {tracking.destinationHash}

} +
+ ); +} +``` + +## Example App + +A complete working example is available in the [`examples/`](https://github.com/coti-io/coti-wallet-plugin/tree/main/examples) directory. It demonstrates wallet connection, public ERC20 balance reading, and private balance decryption using tokens from the [COTI Token List](https://github.com/coti-io/coti-token-list). + +### Prerequisites + +* Node.js 18+ +* The parent plugin must be built first + +### Setup & Run + +```bash +# 1. Build the plugin (from the repository root) +npm run build + +# 2. Move into the examples directory +cd examples + +# 3. Copy the environment template and add your WalletConnect project ID +cp .env.example .env + +# 4. Install dependencies +npm install + +# 5. Start the dev server +npm run dev +``` + +Opens at http://localhost:5173 + +{% hint style="info" %} +Get a WalletConnect project ID at [https://cloud.walletconnect.com](https://cloud.walletconnect.com) and set it in `examples/.env`: + +``` +VITE_WALLETCONNECT_PROJECT_ID=your_project_id_here +``` +{% endhint %} + +### What the Example App Does + +1. **Connect Wallet** — Click to open the RainbowKit modal (MetaMask, Coinbase, Rabby, WalletConnect, etc.) +2. **Public Balances** — Reads on-chain ERC20 `balanceOf` for all public tokens on COTI Testnet +3. **Native COTI** — Displays native COTI balance via wagmi +4. **Private Balances** — Click "Unlock Private Balances" to derive the AES key (via Snap for MetaMask, or on-chain onboarding contract for other wallets), then decrypted private token balances appear + +### Network + +The example app targets **COTI Testnet** (chain ID 7082400). Ensure your wallet has COTI Testnet funds. On unlock, the app automatically prompts a network switch if needed. diff --git a/build-on-coti/tools/coti-wallet-plugin/privacy-bridge.md b/build-on-coti/tools/coti-wallet-plugin/privacy-bridge.md new file mode 100644 index 0000000..4a4e233 --- /dev/null +++ b/build-on-coti/tools/coti-wallet-plugin/privacy-bridge.md @@ -0,0 +1,61 @@ +# Privacy Bridge + +The Privacy Bridge hooks orchestrate Portal In (deposit) and Portal Out (withdraw) operations — moving tokens between their public and private representations on the same COTI chain. + +## `usePrivacyBridge()` + +Full bridge orchestration — deposit, withdraw, allowance, and fee estimation. + +### Methods & Properties + +```typescript +function handleSwap( + amount?: string, + direction?: 'to-private' | 'to-public', + tokenIndex?: number, + onProgress?: (status: string) => void +): Promise +``` + +Unified method to execute a deposit (`'to-private'`) or withdraw (`'to-public'`). + +* **Parameters:** + * `amount`: The amount of tokens to bridge (as a decimal string). + * `direction`: `'to-private'` for deposit, `'to-public'` for withdraw. + * `tokenIndex`: Index of the selected token in the configured token array. + * `onProgress`: Optional callback for progress status updates. + +--- + +* **`isBridgingLoading`** (`boolean`): Indicates a swap/bridge transaction is in progress. +* **`isApprovalNeeded`** (`boolean`): Indicates whether the selected swap requires an ERC20 allowance approval. + +```typescript +function handleApprove(): Promise +``` + +Execute the approval allowance transaction for a bridge out operation. + +* **`estimatedGasFee`** (`string`): Active gas fee estimation for the selected bridge route. +* **`portalFeeCoti`** (`string`): Portal fee in COTI for the selected bridge route. + +## Usage + +```tsx +import { usePrivacyBridge } from '@coti-io/coti-wallet-plugin'; + +function BridgeComponent() { + const { handleSwap, isBridgingLoading } = usePrivacyBridge(); + + const handleDeposit = async () => { + // 100 tokens, sending to private side, token array index 0 + await handleSwap('100', 'to-private', 0); + }; + + return ( + + ); +} +``` diff --git a/build-on-coti/tools/coti-wallet-plugin/security.md b/build-on-coti/tools/coti-wallet-plugin/security.md new file mode 100644 index 0000000..8ffd25d --- /dev/null +++ b/build-on-coti/tools/coti-wallet-plugin/security.md @@ -0,0 +1,35 @@ +# Security + +The COTI Wallet Plugin is designed with a zero-persistence security model. All sensitive cryptographic material exists only in memory during the active browser session. + +## Memory-Only Keys + +AES keys live exclusively in React state and a module-level singleton cache. They are never written to localStorage, sessionStorage, IndexedDB, or cookies. + +## Ephemeral by Design + +Keys are lost on page refresh. Users must re-authenticate, eliminating persistent attack surface. + +## Singleton Cache + +The internal AES key cache (`globalAESKeyCache`) is a module-scoped variable shared across all hook instances within a single browser tab. This is intentional for performance (avoids re-prompting the user on every component mount) but means the plugin is designed exclusively for **browser SPA environments**. + +{% hint style="warning" %} +The singleton cache is not compatible with SSR or React Server Components. +{% endhint %} + +## Sanity Guards + +Decryption includes threshold checks to detect AES key mismatches before displaying garbage values. + +## No Network Transmission + +AES keys are never sent over the network. All decryption is client-side. + +## Connector Identity + +Wallet type detection uses wagmi's stable `connector.id`, not spoofable `window.ethereum.isMetaMask`. + +## Session Isolation + +`sessionAesKey` is automatically cleared on account change, disconnect, or manual lock. The singleton cache is also cleared on these events via `clearSnapCache()`. diff --git a/build-on-coti/tools/coti-wallet-plugin/wallet-integration.md b/build-on-coti/tools/coti-wallet-plugin/wallet-integration.md new file mode 100644 index 0000000..0d0d0a1 --- /dev/null +++ b/build-on-coti/tools/coti-wallet-plugin/wallet-integration.md @@ -0,0 +1,65 @@ +# Wallet Integration + +The COTI Wallet Plugin supports multiple wallet types through wagmi v2's connector system. Wallet detection determines how AES keys are retrieved — via the COTI Snap for MetaMask or via the Onboarding Contract for all other wallets. + +## Supported Wallet Types + +| WalletType | Connector IDs Matched | AES Key Strategy | +| ------------------ | ---------------------------------------------- | ------------------------ | +| `'metamask'` | `metaMask`, `io.metamask`, `io.metamask.flask` | COTI Snap (if installed) | +| `'coinbase'` | `coinbaseWalletSDK`, `*coinbase*` | Onboarding Contract | +| `'walletconnect'` | `walletConnect`, `*walletconnect*` | Onboarding Contract | +| `'rainbow'` | `rainbow`, `*rainbow*` | Onboarding Contract | +| `'phantom'` | `phantom`, `*phantom*` | Onboarding Contract | +| `'trust'` | `trust-extension`, `trustWallet`, `*trust*` | Onboarding Contract | +| `'rabby'` | `rabby`, `*rabby*` | Onboarding Contract | +| `'ledger'` | `ledger`, `*ledger*` | Onboarding Contract | +| `'unknown'` | Any unrecognized connector ID | Onboarding Contract | + +{% hint style="info" %} +Patterns with `*` indicate case-insensitive partial matching as a fallback (e.g. `*metamask*` matches `io.metamask.flask`). +{% endhint %} + +## How Wallet Detection Works + +1. When a user connects via RainbowKit, wagmi exposes a stable `connector.id` (e.g. `"io.metamask.flask"`, `"coinbaseWalletSDK"`). +2. `mapConnectorIdToWalletType(connectorId)` first attempts an **exact match** against the `CONNECTOR_ID_TO_WALLET_TYPE` map. +3. If no exact match is found, it performs **case-insensitive partial matching** (e.g. any ID containing `"metamask"` resolves to `'metamask'`). +4. If the resolved type is `'metamask'`, an async check via `wallet_getSnaps` determines whether the COTI Snap is installed, setting `isMetaMaskWithSnap`. +5. The `useAesKeyProvider` hook uses `isMetaMaskWithSnap` to route: Snap flow for MetaMask, Onboarding Contract for everything else. + +## Adding a New Wallet + +To add support for a newly recognized `connector.id` or new wallet definition: + +### 1. Add to Type + +Add the new wallet identifier to the `WalletType` union in `src/hooks/useWalletType.ts`: + +```typescript +export type WalletType = 'metamask' | 'coinbase' | ... | 'okx' | 'unknown'; +``` + +### 2. Map Connector ID + +Add wagmi's `connector.id` for that wallet to the `CONNECTOR_ID_TO_WALLET_TYPE` constant: + +```typescript +const CONNECTOR_ID_TO_WALLET_TYPE: Record = { + ... + 'com.okex.wallet': 'okx', + okx: 'okx', +}; +``` + +### 3. Add Partial Match (optional) + +If the wallet has multiple connector ID variants, add a partial match case in `mapConnectorIdToWalletType`. + +### 4. Verify Fallback + +If a connector ID isn't mapped, it automatically falls back to `'unknown'`. The `'unknown'` type uses the standard EIP-1193 Onboarding Contract fallback method by default, meaning most wallets will "just work" even if not explicitly mapped. + +{% hint style="info" %} +Adding a new wallet type is only necessary if you need to implement wallet-specific behavior. For standard EIP-1193 wallets that use the Onboarding Contract flow, no changes are needed — they work automatically via the `'unknown'` fallback. +{% endhint %}