Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
81 changes: 81 additions & 0 deletions build-on-coti/tools/coti-wallet-plugin/README.md
Original file line number Diff line number Diff line change
@@ -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) |


96 changes: 96 additions & 0 deletions build-on-coti/tools/coti-wallet-plugin/api-reference.md
Original file line number Diff line number Diff line change
@@ -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<void>`): Opens the wallet connection flow.
* **`disconnect()`** (`() => Promise<void>`): 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<boolean>`): 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<string | null>`): Retrieves the AES key (routes to Snap for MetaMask, or Onboarding Contract for others).
* **`unlockPrivateBalances()`** (`() => Promise<boolean>`): 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<string>
```

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<void>
```

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<string>`): 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.
147 changes: 147 additions & 0 deletions build-on-coti/tools/coti-wallet-plugin/cross-chain-bridge.md
Original file line number Diff line number Diff line change
@@ -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<void>
```

Sends native token to the configured bridge recipient address.

```typescript
function bridgeERC20(amount: bigint, tokenId: string, tokenAddress: `0x${string}`): Promise<void>
```

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<void>`): 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 (
<div>
<button onClick={handleBridgeNative} disabled={isLoading}>
{isLoading ? 'Bridging...' : 'Bridge 10 COTI to Ethereum'}
</button>
{error && <p>Error: {error.message}</p>}
{tracking.currentStep && <p>Step {tracking.currentStep} of 4</p>}
{tracking.destinationHash && <p>Done! Dest tx: {tracking.destinationHash}</p>}
</div>
);
}
```
Loading