npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@rialo/frost

v0.1.1

Published

React wallet integration library for Rialo DApps

Readme

Rialo Frost 🧊

The wallet integration toolkit for Rialo dApps — Connect wallets, sign transactions, and build Web3 apps in minutes.

pnpm add @rialo/frost

30-Second Setup

// 1. Create config (do this once, outside components)
import { createConfig, getDefaultRialoClientConfig } from "@rialo/frost";

export const config = createConfig({
  clientConfig: getDefaultRialoClientConfig("devnet"),
});

// 2. Wrap your app
import { FrostProvider, ConnectButton } from "@rialo/frost";

function App() {
  return (
    <FrostProvider config={config}>
      <ConnectButton />
    </FrostProvider>
  );
}

That's it. You now have a working wallet connection button.


Table of Contents


New to Web3?

What You Need to Know

Wallet = A browser extension (like MetaMask, but for Rialo) that stores the user's private keys and lets them approve transactions.

Account/Address = A public identifier (like 7xKXtg2CW87d97...) that represents a user on the blockchain. One wallet can have multiple accounts.

Signing = When a user cryptographically approves something using their wallet. This proves they own the account without exposing their private key.

Transaction = An operation that changes state on the blockchain (sending tokens, interacting with smart contracts, etc.).

How Frost Fits In

┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│   Your App   │ ───► │    Frost     │ ───► │   Wallet     │
│              │      │              │      │  Extension   │
│  - UI        │      │  - Discovers │      │              │
│  - Logic     │      │    wallets   │      │  - Stores    │
│  - Hooks     │      │  - Manages   │      │    keys      │
│              │      │    state     │      │  - Signs     │
└──────────────┘      └──────────────┘      └──────────────┘

Frost handles all the complex wallet communication so you can focus on building your app.


Installation

Requirements

  • Node.js 18+
  • React 18 or 19
  • A Rialo wallet extension (for testing)

Install

# pnpm (recommended)
pnpm add @rialo/frost

# npm
npm install @rialo/frost

# yarn
yarn add @rialo/frost

For Non-React Apps

Use the framework-agnostic core package:

pnpm add @rialo/frost-core

Step-by-Step Guide

Step 1: Create Your Config

Create a file called frost.config.ts in your src folder:

// src/frost.config.ts
import { createConfig, getDefaultRialoClientConfig } from "@rialo/frost";

export const frostConfig = createConfig({
  clientConfig: getDefaultRialoClientConfig("devnet"), // Use "mainnet" for production
  autoConnect: true, // Reconnect automatically on page refresh
});

⚠️ Important: Create the config outside of React components. This prevents it from being recreated on every render.

Step 2: Add the Provider

Wrap your app with FrostProvider:

// src/App.tsx
import { FrostProvider } from "@rialo/frost";
import { frostConfig } from "./frost.config";

function App() {
  return (
    <FrostProvider config={frostConfig}>
      <YourApp />
    </FrostProvider>
  );
}

Step 3: Add a Connect Button

Option A: Use the built-in component

import { ConnectButton } from "@rialo/frost";

function Header() {
  return (
    <nav>
      <h1>My dApp</h1>
      <ConnectButton />
    </nav>
  );
}

Option B: Build your own

import {
  useWallets,
  useConnectWallet,
  useDisconnectWallet,
  useIsConnected,
  useActiveAccount,
} from "@rialo/frost";

function CustomConnectButton() {
  const wallets = useWallets();
  const { mutate: connect, isPending } = useConnectWallet();
  const { mutate: disconnect } = useDisconnectWallet();
  const isConnected = useIsConnected();
  const account = useActiveAccount();

  if (isConnected && account) {
    return (
      <div>
        <span>{account.address.slice(0, 6)}...{account.address.slice(-4)}</span>
        <button onClick={() => disconnect()}>Disconnect</button>
      </div>
    );
  }

  return (
    <div>
      {wallets.map((wallet) => (
        <button
          key={wallet.name}
          onClick={() => connect({ walletName: wallet.name })}
          disabled={isPending}
        >
          {wallet.icon && <img src={wallet.icon} alt="" width={20} />}
          Connect {wallet.name}
        </button>
      ))}
    </div>
  );
}

Step 4: Display Balance

import { useNativeBalance, useIsConnected } from "@rialo/frost";

function Balance() {
  const isConnected = useIsConnected();
  const { formatted, isLoading, refetch } = useNativeBalance();

  if (!isConnected) return null;

  return (
    <div>
      {isLoading ? "Loading..." : `${formatted} RIA`}
      <button onClick={() => refetch()}>↻</button>
    </div>
  );
}

Step 5: Sign Messages

import { useSignMessage, useIsConnected } from "@rialo/frost";

function SignDemo() {
  const isConnected = useIsConnected();
  const { mutate: signMessage, isPending } = useSignMessage({
    onSuccess: (result) => {
      console.log("Signature:", result.signature);
    },
  });

  if (!isConnected) return <p>Connect wallet first</p>;

  return (
    <button
      onClick={() => signMessage({ message: "Hello from my dApp!" })}
      disabled={isPending}
    >
      {isPending ? "Signing..." : "Sign Message"}
    </button>
  );
}

Step 6: Send Transactions

import { useSendTransaction } from "@rialo/frost";

function SendTx({ transaction }: { transaction: Uint8Array }) {
  const { mutate: send, isPending, isSuccess, data } = useSendTransaction({
    onSuccess: (result) => {
      console.log("Transaction sent:", result.signature);
    },
    onError: (error) => {
      console.error("Failed:", error.message);
    },
  });

  return (
    <div>
      <button onClick={() => send({ transaction })} disabled={isPending}>
        {isPending ? "Sending..." : "Send Transaction"}
      </button>
      {isSuccess && <p>✓ Signature: {data.signature}</p>}
    </div>
  );
}

Hooks Reference

Connection

| Hook | Returns | Description | |------|---------|-------------| | useWallets() | WalletEntity[] | All discovered wallets | | useWalletsReady() | boolean | True when wallet discovery is complete | | useConnectWallet() | Mutation | Connect to a wallet | | useDisconnectWallet() | Mutation | Disconnect current wallet | | useConnectionStatus() | ConnectionStatus | "disconnected" | "connecting" | "connected" | "reconnecting" | | useIsConnected() | boolean | True if connected |

Account & Wallet

| Hook | Returns | Description | |------|---------|-------------| | useActiveWallet() | WalletEntity \| null | Currently connected wallet | | useActiveAccount() | AccountEntity \| null | Currently active account | | useAccounts() | AccountEntity[] | All accounts from connected wallet |

Chain & Client

| Hook | Returns | Description | |------|---------|-------------| | useChainId() | string | Current chain ID (e.g., "rialo:devnet") | | useSwitchChain() | Mutation | Switch to a different network | | useClient() | RialoClient | Direct RPC client access |

Balance

| Hook | Returns | Description | |------|---------|-------------| | useNativeBalance() | { formatted, balance, isLoading, refetch } | Native token balance |

Signing & Transactions

| Hook | Description | |------|-------------| | useSignMessage() | Sign arbitrary messages | | useSignTransaction() | Sign transaction without sending | | useSendTransaction() | Sign and send via wallet | | useSignAndSendTransaction() | Sign via wallet, send via Frost's RPC |


Async/Await with mutateAsync

All mutation hooks (useConnectWallet, useSignMessage, useSignTransaction, useSendTransaction, useSignAndSendTransaction, useDisconnectWallet, useSwitchChain) return both mutate and mutateAsync.

Callback Pattern (mutate)

The mutate function is fire-and-forget with callbacks:

const { mutate: send } = useSendTransaction({
  onSuccess: (result) => console.log("Sent:", result.signature),
  onError: (error) => console.error("Failed:", error),
});

// Fire and forget - no await, no return value
send({ transaction });

Async/Await Pattern (mutateAsync)

The mutateAsync function returns a Promise you can await:

const { mutateAsync: sendAsync } = useSendTransaction();

async function handleSend() {
  try {
    const result = await sendAsync({ transaction });
    console.log("Transaction signature:", result.signature);
    // Continue with next steps...
  } catch (error) {
    console.error("Transaction failed:", error);
  }
}

When to Use Each

| Pattern | Use When | |---------|----------| | mutate | Simple fire-and-forget, UI updates via hook state | | mutateAsync | Sequential operations, custom error handling, chaining actions |

Real-World Example: Sign Then Send

function TransactionFlow() {
  const { mutateAsync: signAsync } = useSignTransaction();
  const { mutateAsync: sendAsync } = useSendTransaction();
  const [status, setStatus] = useState<string>("");

  async function handleTransaction(transaction: Uint8Array) {
    try {
      setStatus("Signing...");
      const { signedTransaction } = await signAsync({ transaction });

      setStatus("Sending...");
      const { signature } = await sendAsync({ transaction: signedTransaction });

      setStatus(`Success! Signature: ${signature}`);
    } catch (error) {
      setStatus(`Error: ${error.message}`);
    }
  }

  return (
    <div>
      <button onClick={() => handleTransaction(myTx)}>Execute</button>
      <p>{status}</p>
    </div>
  );
}

All Mutation Hooks Support Both Patterns

// Connection
const { mutateAsync: connectAsync } = useConnectWallet();
const result = await connectAsync({ walletName: "Rialo" });

// Signing
const { mutateAsync: signMessageAsync } = useSignMessage();
const { signature } = await signMessageAsync({ message: "Hello" });

// Transactions
const { mutateAsync: signTxAsync } = useSignTransaction();
const { signedTransaction } = await signTxAsync({ transaction });

const { mutateAsync: sendTxAsync } = useSendTransaction();
const { signature } = await sendTxAsync({ transaction });

const { mutateAsync: signAndSendAsync } = useSignAndSendTransaction();
const { signature } = await signAndSendAsync({ transaction });

// Disconnect
const { mutateAsync: disconnectAsync } = useDisconnectWallet();
await disconnectAsync();

// Switch chain
const { mutateAsync: switchChainAsync } = useSwitchChain();
await switchChainAsync(getDefaultRialoClientConfig("mainnet"));

Hooks in Detail

useConnectWallet

const {
  mutate: connect,  // Call to trigger connection
  isPending,        // True while connecting
  isSuccess,        // True after successful connection
  isError,          // True if connection failed
  error,            // Error object if failed
  data,             // ConnectResult if successful
} = useConnectWallet({
  onSuccess: (result) => {
    console.log("Connected:", result.walletName, result.accountAddress);
  },
  onError: (error) => {
    console.error("Failed:", error.message);
  },
});

// Usage
connect({ walletName: "Rialo" });

useSignMessage

const { mutate: signMessage, isPending } = useSignMessage({
  onSuccess: (result) => {
    // result.signature: Uint8Array
    // result.signedMessage: Uint8Array
  },
});

// Sign a string
signMessage({ message: "Hello World" });

// Sign raw bytes
signMessage({ message: new Uint8Array([1, 2, 3]) });

useNativeBalance

const {
  balance,      // bigint | undefined - Raw balance in lamports
  formatted,    // string | undefined - Human readable (e.g., "1.5")
  isLoading,    // boolean
  isFetching,   // boolean - True during refetch
  isError,      // boolean
  error,        // Error | null
  refetch,      // () => void
} = useNativeBalance({
  address: "optional-address", // Defaults to active account
  decimals: 9,                 // Defaults to 9
});

useSwitchChain

import { getDefaultRialoClientConfig } from "@rialo/frost";

const { mutate: switchChain } = useSwitchChain();

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

// Switch to testnet
switchChain(getDefaultRialoClientConfig("testnet"));

Components

ConnectButton

A complete, styled connect button with dropdown:

<ConnectButton
  label="Connect Wallet"              // Button text when disconnected
  connectingLabel="Connecting..."     // Text while connecting
  connectedLabel={(addr, wallet) =>   // Text when connected
    `${addr.slice(0, 6)}...`
  }
  showDropdown={true}                 // Show dropdown when connected
  onConnect={(wallet, address) => {}} // Connection callback
  onDisconnect={() => {}}             // Disconnect callback
  onError={(error) => {}}             // Error callback
/>

WalletModal

A modal for wallet selection:

const [open, setOpen] = useState(false);

<WalletModal
  open={open}
  onClose={() => setOpen(false)}
  onConnect={(walletName, address) => {
    console.log(`Connected to ${walletName}`);
    setOpen(false);
  }}
  onError={(error, walletName) => {
    console.error(`${walletName} failed:`, error);
  }}
  title="Select a Wallet"
/>

Error Handling

Frost provides typed errors for precise handling:

import { isFrostError, type FrostError } from "@rialo/frost";

function handleError(error: Error) {
  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");
      break;
    case "UNSUPPORTED_CHAIN":
      alert("Switch to a supported network");
      break;
    case "UNSUPPORTED_FEATURE":
      alert("Your wallet doesn't support this feature");
      break;
    case "CONNECTION_FAILED":
      alert("Connection rejected or failed");
      break;
    case "SESSION_EXPIRED":
      alert("Session expired, please reconnect");
      break;
    case "TRANSACTION_FAILED":
      alert(`Transaction failed: ${error.reason}`);
      break;
    case "WALLET_ERROR":
      alert(error.message);
      break;
  }
}

Error Types

| Code | Class | When | |------|-------|------| | WALLET_NOT_FOUND | WalletNotFoundError | Wallet extension not installed | | WALLET_DISCONNECTED | WalletDisconnectedError | No wallet connected | | 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 | Saved session too old | | TRANSACTION_FAILED | TransactionFailedError | Transaction confirmed but execution failed on-chain | | WALLET_ERROR | WalletError | Generic wallet error |


Advanced Usage

Custom RPC URL

import { createConfig } from "@rialo/frost";

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

Disable Session Persistence

const config = createConfig({
  clientConfig: getDefaultRialoClientConfig("devnet"),
  storage: null, // Won't remember connected wallet
});

Share TanStack Query Client

If your app already uses TanStack Query:

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <FrostProvider config={frostConfig} queryClient={queryClient}>
        <YourApp />
      </FrostProvider>
    </QueryClientProvider>
  );
}

Direct Config Access

import { useFrostConfig } from "@rialo/frost";

function Advanced() {
  const config = useFrostConfig();

  // Access RPC client
  const client = config.client;

  // Get chain ID
  const chainId = config.getChainId();

  // Switch chain imperatively
  config.switchChain(getDefaultRialoClientConfig("mainnet"));
}

Non-React Usage

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

// Setup
const config = createConfig({
  clientConfig: getDefaultRialoClientConfig("devnet"),
});

const registry = new WalletRegistry(config);
const bridge = new WalletEventBridge(config);
initializeConfig(config, registry, bridge);

// Connect
const { accountAddress } = await connect(config, { walletName: "Rialo" });

// Sign
const { signedTransaction } = await signTransaction(config, { transaction });

// Disconnect
await disconnect(config);

// Cleanup
config.destroy();

Troubleshooting

"No wallets found"

  1. Install a Rialo wallet extension
  2. Make sure it's enabled for your site
  3. 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

Hooks not updating

Wrong: Creating config inside component

function App() {
  const config = createConfig({ ... }); // ❌ Recreated every render
  return <FrostProvider config={config}>...</FrostProvider>;
}

Right: Creating config outside component

const config = createConfig({ ... }); // ✅ Created once

function App() {
  return <FrostProvider config={config}>...</FrostProvider>;
}

"Session expired" on page load

This is expected behavior. The default session TTL is 7 days. Users just need to reconnect.


Configuration Options

interface CreateConfigOptions {
  /** RPC client configuration (required) */
  clientConfig: RialoClientConfig;

  /** Auto-reconnect on page load (default: true) */
  autoConnect?: boolean;

  /** Session TTL in ms (default: 7 days) */
  sessionTTL?: number;

  /** Storage key prefix (default: "rialo-frost") */
  storageKey?: string;

  /** Custom storage (default: localStorage, null to disable) */
  storage?: Storage | null;
}

Networks

| Network | Chain ID | Use Case | |---------|----------|----------| | Devnet | rialo:devnet | Development (free test tokens) | | Testnet | rialo:testnet | Pre-production testing | | Mainnet | rialo:mainnet | Production |


CSS Custom Properties

Frost components support theming via CSS custom properties. Set these variables to customize the appearance:

Colors

| Variable | Default | Description | |----------|---------|-------------| | --frost-primary | #3b82f6 | Primary brand color (buttons, accents) | | --frost-text-color | #111827 | Main text color | | --frost-text-secondary | #6b7280 | Secondary/muted text | | --frost-border-color | #e5e7eb | Border and divider color | | --frost-danger | #ef4444 | Destructive action color (disconnect) |

Backgrounds

| Variable | Default | Description | |----------|---------|-------------| | --frost-button-text | #ffffff | Button text color | | --frost-button-connected-bg | #f3f4f6 | Connected button background | | --frost-button-connected-text | #111827 | Connected button text | | --frost-dropdown-bg | #ffffff | Dropdown menu background | | --frost-hover-bg | #f3f4f6 | Hover state background | | --frost-modal-bg | #ffffff | Modal dialog background | | --frost-overlay-bg | rgba(0, 0, 0, 0.5) | Modal overlay background | | --frost-icon-bg | #f3f4f6 | Wallet icon background |

Layout

| Variable | Default | Description | |----------|---------|-------------| | --frost-border-radius | 8px | Default border radius | | --frost-border-radius-sm | 6px | Small border radius | | --frost-modal-max-width | 400px | Maximum modal width | | --frost-shadow | 0 4px 16px rgba(0, 0, 0, 0.12) | Box shadow | | --frost-z-index | 9999 | Z-index for overlays |

Example: Dark Theme

:root {
  --frost-primary: #60a5fa;
  --frost-text-color: #f9fafb;
  --frost-text-secondary: #9ca3af;
  --frost-border-color: #374151;
  --frost-button-text: #ffffff;
  --frost-button-connected-bg: #1f2937;
  --frost-button-connected-text: #f9fafb;
  --frost-dropdown-bg: #1f2937;
  --frost-hover-bg: #374151;
  --frost-modal-bg: #111827;
  --frost-overlay-bg: rgba(0, 0, 0, 0.7);
  --frost-icon-bg: #374151;
}

Example: Custom Brand Colors

:root {
  --frost-primary: #8b5cf6;           /* Purple primary */
  --frost-border-radius: 12px;        /* More rounded */
  --frost-shadow: 0 8px 32px rgba(139, 92, 246, 0.2);  /* Purple tinted shadow */
}