# Rialo Frost Core 🧊

**Framework-agnostic wallet integration library for Rialo dApps** — The foundation that powers `@rialo/frost` React bindings. Use this package directly when building with Vue, Svelte, Angular, or vanilla JavaScript.

```bash
pnpm add @rialo/frost-core
```

## Why Frost Core?

Frost Core provides the low-level wallet integration primitives without any framework dependencies:

- **Zero Framework Lock-in**: Works with any JavaScript framework or vanilla JS
- **Wallet Standard Compliant**: Built on [@wallet-standard](https://github.com/wallet-standard/wallet-standard)
- **Reactive State Management**: Powered by [@tanstack/store](https://tanstack.com/store) for efficient updates
- **Session Persistence**: Automatic session management with configurable TTL
- **Type-Safe**: Full TypeScript support with comprehensive type definitions
- **Tree-Shakeable**: Only import what you need

---

## Table of Contents

- [Architecture Overview](#architecture-overview)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Actions Reference](#actions-reference)
- [State Management](#state-management)
- [Error Handling](#error-handling)
- [Testing](#testing)
- [Advanced Usage](#advanced-usage)
- [Troubleshooting](#troubleshooting)
- [API Reference](#api-reference)

---

## Architecture Overview

```
┌──────────────────────────────────────────────────────────────┐
│                       Your Application                        │
├──────────────────────────────────────────────────────────────┤
│                        Frost Core                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐   │
│  │   Actions   │  │    Store    │  │    WalletRegistry   │   │
│  │  connect()  │  │  TanStack   │  │  Auto-discovery     │   │
│  │  sign*()    │◄─┤   Store     │◄─┤  Event handling     │   │
│  │  send*()    │  │  Reactive   │  │  Wallet-standard    │   │
│  └─────────────┘  └─────────────┘  └─────────────────────┘   │
├──────────────────────────────────────────────────────────────┤
│                    @rialo/wallet-standard                     │
├──────────────────────────────────────────────────────────────┤
│                      Wallet Extensions                        │
└──────────────────────────────────────────────────────────────┘
```

**Key Components:**

- **Actions**: Pure functions for wallet operations (`connect`, `signMessage`, `sendTransaction`, etc.)
- **Store**: Reactive state container with automatic persistence
- **WalletRegistry**: Discovers and tracks available Rialo-compatible wallets
- **WalletEventBridge**: Handles wallet events (account changes, disconnections)

---

## Installation

### Requirements

- Node.js 18+
- A Rialo wallet extension (for testing)

### Install

```bash
# pnpm (recommended)
pnpm add @rialo/frost-core

# npm
npm install @rialo/frost-core

# yarn
yarn add @rialo/frost-core
```

---

## Quick Start

```typescript
import {
  createConfig,
  getDefaultRialoClientConfig,
  connect,
  disconnect,
  signMessage,
  sendTransaction,
  WalletRegistry,
  WalletEventBridge,
  initializeConfig,
} from "@rialo/frost-core";

// 1. Create configuration
const config = createConfig({
  clientConfig: getDefaultRialoClientConfig("devnet"),
  autoConnect: true,
});

// 2. Initialize wallet discovery (required for wallet operations)
const registry = new WalletRegistry(config);
const bridge = new WalletEventBridge(config);
initializeConfig(config, registry, bridge);

// 3. Wait for wallet discovery
await registry.ready;

// 4. Connect to a wallet
const { walletName, accountAddress } = await connect(config, {
  walletName: "Rialo",
});
console.log(`Connected: ${accountAddress}`);

// 5. Sign a message
const { signature, signedMessage } = await signMessage(config, {
  message: "Hello from my dApp!",
});

// 6. Send a transaction
const { signature: txSignature } = await sendTransaction(config, {
  transaction: myTransactionBytes,
});

// 7. Cleanup when done
config.destroy();
```

---

## Configuration

### createConfig(options)

Creates a Frost configuration object. **Important**: Create once and reuse throughout your application.

```typescript
import { createConfig, getDefaultRialoClientConfig } from "@rialo/frost-core";

const config = createConfig({
  // Required: RPC client configuration
  clientConfig: getDefaultRialoClientConfig("devnet"),

  // Optional settings (shown with defaults)
  autoConnect: true,                    // Reconnect on page load
  sessionTTL: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds
  storageKey: "rialo-frost",            // localStorage key prefix
  storage: localStorage,                // Storage implementation (null to disable)
});
```

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `clientConfig` | `RialoClientConfig` | **Required** | RPC client configuration |
| `autoConnect` | `boolean` | `true` | Auto-reconnect on page load |
| `sessionTTL` | `number` | `604800000` (7 days) | Session expiration in milliseconds |
| `storageKey` | `string` | `"rialo-frost"` | Storage key prefix |
| `storage` | `Storage \| null` | `localStorage` | Storage implementation |

### Custom RPC URL

```typescript
const config = createConfig({
  clientConfig: {
    chain: {
      id: "rialo:mainnet",
      name: "Mainnet",
      rpcUrl: "https://my-custom-rpc.example.com",
    },
    transport: { timeout: 30000 },
  },
});
```

### Networks

| Network | Chain ID | Getter |
|---------|----------|--------|
| Devnet | `rialo:devnet` | `getDefaultRialoClientConfig("devnet")` |
| Testnet | `rialo:testnet` | `getDefaultRialoClientConfig("testnet")` |
| Mainnet | `rialo:mainnet` | `getDefaultRialoClientConfig("mainnet")` |
| Localnet | `rialo:localnet` | `getDefaultRialoClientConfig("localnet")` |

---

## Actions Reference

All actions are pure functions that take `config` as their first argument.

### Connection Actions

#### connect(config, options)

Connects to a wallet.

```typescript
interface ConnectOptions {
  walletName: string;  // Name of the wallet to connect
  silent?: boolean;    // Attempt without popup (for reconnection)
}

interface ConnectResult {
  walletName: string;
  accountAddress: string;
}

const result = await connect(config, { walletName: "Rialo" });
```

**Throws:**
- `WalletNotFoundError` — Wallet not discovered
- `UnsupportedFeatureError` — Wallet doesn't support connect
- `ConnectionFailedError` — User rejected or wallet error

#### disconnect(config)

Disconnects from the current wallet. Sets `userDisconnected` flag to prevent auto-reconnect.

```typescript
await disconnect(config);
```

#### reconnect(config)

Attempts silent reconnection to previously connected wallet.

```typescript
const result = await reconnect(config);
// Returns ConnectResult | null
```

Returns `null` if:
- No previous connection exists
- User explicitly disconnected
- Session expired

#### getActiveConnection(config)

Gets current connection state synchronously.

```typescript
interface ActiveConnection {
  status: ConnectionStatus;
  chainId: string;
  walletName: string | null;
  accountAddress: string | null;
  connectedAt: number | null;
  lastUsedAt: number;
}

const connection = getActiveConnection(config);
```

### Wallet Actions

#### getWallets(config)

Gets all discovered wallets.

```typescript
const wallets: WalletEntity[] = getWallets(config);
```

#### getSortedWallets(config)

Gets wallets sorted by priority (higher first), then by last connected time.

```typescript
const wallets: WalletEntity[] = getSortedWallets(config);
```

#### getWallet(config, walletName)

Gets a specific wallet by name.

```typescript
const wallet: WalletEntity | undefined = getWallet(config, "Rialo");
```

#### hasFeature(config, walletName, feature)

Checks if a wallet supports a specific feature.

```typescript
const canSign = hasFeature(config, "Rialo", "rialo:signMessage");
```

#### supportsChain(config, walletName)

Checks if a wallet supports the currently configured chain.

```typescript
const supported = supportsChain(config, "Rialo");
```

### Account Actions

#### getAccounts(config)

Gets all known accounts.

```typescript
const accounts: AccountEntity[] = getAccounts(config);
```

#### getActiveAccount(config)

Gets the currently active (connected) account.

```typescript
const account: AccountEntity | null = getActiveAccount(config);
```

#### getAccount(config, address)

Gets a specific account by address.

```typescript
const account: AccountEntity | undefined = getAccount(config, "7xKXtg...");
```

### Message Signing

#### signMessage(config, options)

Signs an arbitrary message.

```typescript
interface SignMessageOptions {
  message: string | Uint8Array;  // String will be UTF-8 encoded
}

interface SignMessageResult {
  signature: Uint8Array;
  signedMessage: Uint8Array;
}

// Sign a string
const result = await signMessage(config, {
  message: "Hello World",
});

// Sign raw bytes
const result = await signMessage(config, {
  message: new Uint8Array([1, 2, 3]),
});
```

**Throws:**
- `WalletDisconnectedError` — No wallet connected
- `UnsupportedFeatureError` — Wallet doesn't support signing
- `WalletError` — Wallet operation failed

### Transaction Operations

#### signTransaction(config, options)

Signs a transaction without submitting it.

```typescript
interface SignTransactionOptions {
  transaction: Uint8Array | { serialize(): Uint8Array };
}

interface SignTransactionResult {
  signedTransaction: Uint8Array;
}

const { signedTransaction } = await signTransaction(config, {
  transaction: myTransaction,
});
```

#### sendTransaction(config, options)

Signs and sends a transaction using the wallet's built-in send feature.

```typescript
interface SendTransactionType {
  transaction: Uint8Array | { serialize(): Uint8Array };
}

interface SendTransactionResult {
  signature: Uint8Array;
}

const { signature } = await sendTransaction(config, {
  transaction: myTransaction,
});
```

#### signAndSendTransaction(config, options)

Signs via wallet, sends via Frost's RPC client with confirmation. Useful when you want:
- Custom RPC URL
- More control over retry logic
- To not trust the wallet's send implementation

```typescript
interface SignAndSendTransactionOptions {
  transaction: Uint8Array | { serialize(): Uint8Array };
  sendOptions?: {
    skipPreflight?: boolean;
    maxRetries?: number;
    confirmMaxRetries?: number;
    confirmRetryDelayMs?: number;
  };
}

interface SignAndSendTransactionResult {
  signature: string;  // Base58 encoded
}

const { signature } = await signAndSendTransaction(config, {
  transaction: myTransaction,
  sendOptions: { maxRetries: 3 },
});
```

**Throws:**
- `WalletDisconnectedError` — No wallet connected
- `UnsupportedFeatureError` — Wallet doesn't support signing
- `TransactionFailedError` — Transaction confirmed but execution failed on-chain

---

## State Management

Frost Core uses [@tanstack/store](https://tanstack.com/store) for reactive state management.

### FrostAppState

```typescript
interface FrostAppState {
  status: ConnectionStatus;           // "disconnected" | "connecting" | "connected" | "reconnecting"
  chainId: string;                    // Current chain ID
  walletName: string | null;          // Connected wallet name
  accountAddress: string | null;      // Active account address
  connectedAt: number | null;         // Connection timestamp
  sessionExpiresAt: number | null;    // Session expiration timestamp
  lastUsedAt: number;                 // Last activity timestamp
  userDisconnected: boolean;          // Prevents auto-reconnect
  wallets: Map<string, WalletEntity>; // Discovered wallets
  accounts: Map<string, AccountEntity>; // Known accounts
}
```

### Subscribing to State Changes

```typescript
// Subscribe to all state changes
const unsubscribe = config.store.subscribe(({ currentVal, prevVal }) => {
  if (currentVal.status !== prevVal.status) {
    console.log(`Status changed: ${prevVal.status} -> ${currentVal.status}`);
  }
});

// Read current state
const state = config.store.state;

// Unsubscribe when done
unsubscribe();
```

### Entity Types

#### WalletEntity

```typescript
interface WalletEntity {
  name: string;                        // Display name
  icon?: string;                       // Base64 data URI
  chains: readonly string[];           // Supported chains
  features: readonly string[];         // Feature identifiers
  installedVersion?: string;           // Wallet version
  priority?: number;                   // UI sort priority
  lastConnectedAt?: number;            // Last successful connection
}
```

#### AccountEntity

```typescript
interface AccountEntity {
  address: string;                     // Base58-encoded address
  publicKey: Uint8Array;               // Raw public key bytes
  walletName: string;                  // Parent wallet name
  chains: readonly string[];           // Valid chains
  label?: string;                      // Optional user label
}
```

---

## Error Handling

Frost provides typed errors for precise handling.

### Error Type Guard

```typescript
import { isFrostError, type FrostError } from "@rialo/frost-core";

try {
  await connect(config, { walletName: "Unknown" });
} catch (error) {
  if (isFrostError(error)) {
    handleFrostError(error);
  } else {
    console.error("Unknown error:", error);
  }
}
```

### Error Types

| Code | Class | When |
|------|-------|------|
| `WALLET_NOT_FOUND` | `WalletNotFoundError` | Wallet extension not discovered |
| `WALLET_DISCONNECTED` | `WalletDisconnectedError` | Operation requires connected wallet |
| `UNSUPPORTED_CHAIN` | `UnsupportedChainError` | Wallet doesn't support current chain |
| `UNSUPPORTED_FEATURE` | `UnsupportedFeatureError` | Missing wallet capability |
| `CONNECTION_FAILED` | `ConnectionFailedError` | User rejected or wallet error |
| `SESSION_EXPIRED` | `SessionExpiredError` | Persisted session too old |
| `TRANSACTION_FAILED` | `TransactionFailedError` | Transaction confirmed but failed on-chain |
| `WALLET_ERROR` | `WalletError` | Generic wallet error wrapper |

### Error Properties

All errors extend `FrostError` and include:

```typescript
class FrostError extends Error {
  code: string;           // Error code (e.g., "WALLET_NOT_FOUND")
  walletName?: string;    // Associated wallet (if applicable)
}
```

Specific errors have additional properties:

```typescript
// UnsupportedChainError
error.chainId;           // Requested chain
error.supportedChains;   // Array of supported chains

// UnsupportedFeatureError
error.feature;           // Required feature identifier

// ConnectionFailedError
error.reason;            // Failure reason string

// SessionExpiredError
error.expiredAt;         // Expiration timestamp

// TransactionFailedError
error.signature;         // Transaction signature
error.reason;            // On-chain failure reason

// WalletError
error.originalError;     // Original error from wallet
```

### Handling Pattern

```typescript
import {
  isFrostError,
  WalletNotFoundError,
  WalletDisconnectedError,
  UnsupportedChainError,
  ConnectionFailedError,
} from "@rialo/frost-core";

function handleError(error: unknown) {
  if (!isFrostError(error)) {
    console.error("Unknown error:", error);
    return;
  }

  switch (error.code) {
    case "WALLET_NOT_FOUND":
      alert(`Install ${error.walletName} to continue`);
      break;
    case "WALLET_DISCONNECTED":
      alert("Please connect your wallet first");
      break;
    case "UNSUPPORTED_CHAIN":
      const chainError = error as UnsupportedChainError;
      alert(`Switch to: ${chainError.supportedChains.join(", ")}`);
      break;
    case "CONNECTION_FAILED":
      alert("Connection rejected or failed");
      break;
    case "TRANSACTION_FAILED":
      alert(`Transaction failed: ${error.message}`);
      break;
    default:
      alert(error.message);
  }
}
```

---

## Testing

Frost Core includes comprehensive testing utilities via the `/testing` export.

### Installation

```typescript
import {
  createMockConfig,
  createMockWallet,
  createMockAccount,
  createMockClient,
  createMockStorage,
  waitFor,
  waitForState,
} from "@rialo/frost-core/testing";
```

### Creating a Mock Config

```typescript
const { config, mockClient, mockStorage } = createMockConfig({
  chainId: "rialo:devnet",
  autoConnect: false,
  initialState: {
    status: "disconnected",
  },
});
```

### Creating Mock Wallets

```typescript
const mockWallet = createMockWallet({
  name: "TestWallet",
  autoApprove: true,           // Auto-approve connections
  connectDelay: 100,           // Simulate network delay
  failConnect: false,          // Simulate connection failure
  supportsEvents: true,        // Include standard:events
  supportsSignMessage: true,   // Include rialo:signMessage
  supportsSignTransaction: true,
  supportsSignAndSend: true,
});

// Register the mock wallet
config.store.setState(state => ({
  ...state,
  wallets: new Map([[mockWallet.name, createMockWalletEntity({ name: mockWallet.name })]]),
  _walletRefs: new Map([[mockWallet.name, mockWallet]]),
}));
```

### Mock Wallet Test Helpers

```typescript
// Trigger account change event
mockWallet._emitAccountChange([newAccount]);

// Check call counts
console.log(mockWallet._connectCallCount);
console.log(mockWallet._signMessageCallCount);

// Access last operations
console.log(mockWallet._lastSignedMessage);
console.log(mockWallet._lastSignedTransaction);

// Reset state between tests
mockWallet._reset();
```

### Mock RPC Client

```typescript
const mockClient = createMockClient({
  defaultBalance: 1_000_000_000n,  // 1 RLO equivalent
  requestDelay: 50,                 // Simulate latency
  failRequests: false,              // Simulate RPC errors
});

// Set specific balances
mockClient._setBalance("7xKXtg...", 5_000_000_000n);

// Simulate transaction failures
mockClient._setTransactionFails(true, "Insufficient funds");

// Check operations
console.log(mockClient._sendTransactionCallCount);
console.log(mockClient._lastSentTransaction);
```

### Wait Utilities

```typescript
// Wait for a condition
await waitFor(() => config.store.state.status === "connected", {
  timeout: 5000,
  interval: 50,
});

// Wait for specific state
const state = await waitForState(
  config,
  (s) => s.wallets.size > 0,
  { timeout: 3000 }
);
```

### Example Test

```typescript
import { describe, it, expect, beforeEach } from "vitest";
import { connect, disconnect } from "@rialo/frost-core";
import {
  createMockConfig,
  createMockWallet,
  createMockWalletEntity,
  waitForState,
} from "@rialo/frost-core/testing";

describe("connect", () => {
  let config;
  let mockWallet;

  beforeEach(() => {
    const { config: c } = createMockConfig();
    config = c;
    mockWallet = createMockWallet({ name: "TestWallet" });

    // Register mock wallet
    config.store.setState(state => ({
      ...state,
      wallets: new Map([["TestWallet", createMockWalletEntity({ name: "TestWallet" })]]),
      _walletRefs: new Map([["TestWallet", mockWallet]]),
    }));
  });

  it("should connect to wallet", async () => {
    const result = await connect(config, { walletName: "TestWallet" });

    expect(result.walletName).toBe("TestWallet");
    expect(config.store.state.status).toBe("connected");
    expect(mockWallet._connectCallCount).toBe(1);
  });

  it("should throw on unknown wallet", async () => {
    await expect(
      connect(config, { walletName: "Unknown" })
    ).rejects.toThrow("WALLET_NOT_FOUND");
  });
});
```

---

## Advanced Usage

### Initializing Without Auto-Discovery

For custom wallet registration or SSR environments:

```typescript
import {
  createConfig,
  getDefaultRialoClientConfig,
  WalletRegistry,
  WalletEventBridge,
  initializeConfig,
} from "@rialo/frost-core";

const config = createConfig({
  clientConfig: getDefaultRialoClientConfig("devnet"),
  storage: null,  // Disable persistence for SSR
});

// Initialize only in browser
if (typeof window !== "undefined") {
  const registry = new WalletRegistry(config);
  const bridge = new WalletEventBridge(config);
  initializeConfig(config, registry, bridge);
}
```

### Switching Chains

```typescript
import { getDefaultRialoClientConfig } from "@rialo/frost-core";

// Switch to mainnet
config.switchChain(getDefaultRialoClientConfig("mainnet"));

// Get current chain
const chainId = config.getChainId(); // "rialo:mainnet"
```

### Direct Store Access

```typescript
// Read state
const { status, walletName, accountAddress } = config.store.state;

// Subscribe to changes
const unsubscribe = config.store.subscribe(({ currentVal }) => {
  console.log("New status:", currentVal.status);
});

// Update state (internal use only)
config.store.setState(state => ({
  ...state,
  lastUsedAt: Date.now(),
}));
```

### Custom Persistence

```typescript
// In-memory storage
const memoryStorage = new Map<string, string>();

const customStorage: Storage = {
  getItem: (key) => memoryStorage.get(key) ?? null,
  setItem: (key, value) => memoryStorage.set(key, value),
  removeItem: (key) => memoryStorage.delete(key),
  clear: () => memoryStorage.clear(),
  get length() { return memoryStorage.size; },
  key: (index) => Array.from(memoryStorage.keys())[index] ?? null,
};

const config = createConfig({
  clientConfig: getDefaultRialoClientConfig("devnet"),
  storage: customStorage,
});
```

### Direct RPC Client Access

```typescript
// Access the RialoClient for direct RPC calls
const client = config.client;

// Example: Get balance
const balance = await client.getBalance(publicKey);

// Example: Get latest blockhash
const { blockhash, lastValidBlockHeight } = await client.getLatestBlockhash();
```

### Cleanup

Always destroy the config when unmounting your app:

```typescript
// Cleanup all subscriptions and resources
config.destroy();
```

---

## Troubleshooting

### No wallets found

1. Install a Rialo wallet extension
2. Make sure the extension is enabled for your site
3. Wait for `registry.ready` before checking wallets
4. Refresh the page

### Connection keeps failing

1. Unlock your wallet
2. Check if the wallet supports your network (devnet/mainnet)
3. Try disabling and re-enabling the extension
4. Clear localStorage and refresh

### Session expired on page load

Expected behavior. The default session TTL is 7 days. Configure with `sessionTTL` option:

```typescript
const config = createConfig({
  clientConfig: getDefaultRialoClientConfig("devnet"),
  sessionTTL: 30 * 24 * 60 * 60 * 1000, // 30 days
});
```

### State not updating

Ensure you're subscribing to the store correctly:

```typescript
// Wrong: Reading state once
const status = config.store.state.status;

// Right: Subscribing to changes
config.store.subscribe(({ currentVal }) => {
  updateUI(currentVal.status);
});
```

### Auto-reconnect not working

Check if `userDisconnected` flag is set:

```typescript
const { userDisconnected } = config.store.state;
if (userDisconnected) {
  // User explicitly disconnected, won't auto-reconnect
  // Must call connect() again
}
```

---

## API Reference

### Exports

```typescript
// Configuration
export { createConfig, initializeConfig } from "./config";
export type { CreateConfigOptions, FrostConfig } from "./config";

// Chain definitions (re-exported from @rialo/ts-cdk)
export {
  getDefaultRialoClientConfig,
  RIALO_DEVNET_CHAIN,
  RIALO_TESTNET_CHAIN,
  RIALO_MAINNET_CHAIN,
  RIALO_LOCALNET_CHAIN,
} from "@rialo/ts-cdk";

// Actions
export {
  connect,
  disconnect,
  reconnect,
  getActiveConnection,
  getWallets,
  getSortedWallets,
  getWallet,
  hasFeature,
  supportsChain,
  getAccounts,
  getActiveAccount,
  getAccount,
  signMessage,
  signTransaction,
  sendTransaction,
  signAndSendTransaction,
} from "./actions";

// Types
export type {
  ConnectionStatus,
  FrostAppState,
  WalletEntity,
  AccountEntity,
  ConnectOptions,
  ConnectResult,
  ActiveConnection,
  SignMessageOptions,
  SignMessageResult,
  SignTransactionOptions,
  SignTransactionResult,
  SendTransactionType,
  SendTransactionResult,
  SignAndSendTransactionOptions,
  SignAndSendTransactionResult,
} from "./types";

// Errors
export {
  FrostError,
  isFrostError,
  WalletNotFoundError,
  WalletDisconnectedError,
  UnsupportedChainError,
  UnsupportedFeatureError,
  ConnectionFailedError,
  SessionExpiredError,
  TransactionFailedError,
  WalletError,
} from "./errors";

// Internal (for framework bindings)
export { WalletRegistry } from "./registry/WalletRegistry";
export { WalletEventBridge } from "./bridge/WalletEventBridge";
```

### Testing Exports (`@rialo/frost-core/testing`)

```typescript
export {
  createMockConfig,
  createMockWallet,
  createMockAccount,
  createMockClient,
  createMockStorage,
  createMockWalletEntity,
  createMockAccountEntity,
  waitFor,
  waitForState,
} from "./testing";
```

---

## License

Apache-2.0
