New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

@flarenetwork/multichain-wallet-connector

Package Overview
Dependencies
Maintainers
9
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@flarenetwork/multichain-wallet-connector

A multichain wallet connector library with core and React bindings

latest
Source
npmnpm
Version
0.0.1
Version published
Maintainers
9
Created
Source

@flarenetwork/multichain-wallet-connector

WIP — This library is under active development. APIs are subject to change.

A TypeScript library for connecting to multiple blockchain wallets across EVM and non-EVM chains, with first-class React support.

Features

  • Multi-wallet — MetaMask, WalletConnect, Ledger, Xaman, D'CENT
  • Multi-chain — EVM chains (Ethereum, Flare, Songbird) + XRP Ledger
  • React hooks — Type-safe hooks for wallet state, chain clients, and connection actions
  • Headless UI helpers — Wallet selection state and Ledger address selection (bring your own UI)
  • CAIP-2 identifiers — Standard chain addressing (eip155:14, xrpl:0)
  • Discriminated unions — Type-safe connection args per wallet type

Installation

npm install @flarenetwork/multichain-wallet-connector

Prerequisites

  • Node.js 24+
  • pnpm (package manager)
  • React 18 or 19 (peer dependency — 18 is supported because Ledger transport libraries depend on it)

Quick Start

1. Create a MultiChain instance

import { MultiChain } from "@flarenetwork/multichain-wallet-connector";

const multichain = new MultiChain({
  wallets: {
    metamask: {},
    "wallet-connect": {
      projectId: "YOUR_WALLETCONNECT_PROJECT_ID",
      metadata: { name: "My dApp", url: "https://mydapp.com" },
    },
    ledger: {},
    xaman: { apiKey: "YOUR_XAMAN_API_KEY" },
    dcent: {},
  },
});

Only include wallets you need. Each wallet key is optional.

2. Setup React provider

import { MultichainProvider } from "@flarenetwork/multichain-wallet-connector/react";

function App() {
  return (
    <MultichainProvider multichain={multichain}>
      <YourApp />
    </MultichainProvider>
  );
}

MultichainProvider calls multichain.mount() on mount, which initializes connectors and restores previous sessions.

3. Connect a wallet

import { useMultichain, useWallet } from "@flarenetwork/multichain-wallet-connector/react";
import { useMutation } from "@tanstack/react-query";

function ConnectButton() {
  const { connect, disconnect } = useMultichain();
  const { isConnected, state } = useWallet("metamask");

  const connectMutation = useMutation({
    mutationFn: () => connect({ wallet: "metamask", chain: "eip155:14" }),
  });

  const disconnectMutation = useMutation({
    mutationFn: () => disconnect("metamask"),
  });

  if (isConnected) {
    return (
      <div>
        <p>Connected: {state.activeAddress}</p>
        <button onClick={() => disconnectMutation.mutate()}>Disconnect</button>
      </div>
    );
  }

  return (
    <button onClick={() => connectMutation.mutate()} disabled={connectMutation.isPending}>
      {connectMutation.isPending ? "Connecting..." : "Connect to Flare"}
    </button>
  );
}

Supported Wallets & Chains

Wallets

WalletChainsConnection Mode
MetaMaskEVMSingle chain
WalletConnectEVM + XRPLMultiple chains at once
LedgerEVM + XRPLSingle chain
XamanXRPLNo chain selection needed
D'CENTXRPLNo chain selection needed (in-app browser only, silently skipped elsewhere)

Chains (CAIP-2 format)

ChainCAIP-2 ID
Ethereum Mainneteip155:1
Ethereum Sepoliaeip155:11155111
Flare Mainneteip155:14
Flare Testnet Costoneip155:16
Flare Testnet Coston2eip155:114
Songbird Canary-Networkeip155:19
XRP Ledger Mainnetxrpl:0
XRP Ledger Testnetxrpl:1

Wallet-Chain Support Matrix

WalletEVM (eip155:*)XRPL (xrpl:*)
MetaMaskYes
WalletConnectYesYes
LedgerYesYes
XamanYes
D'CENTYes

React Hooks

All hooks are imported from @flarenetwork/multichain-wallet-connector/react.

useMultichain()

Connection actions — connect, disconnect, and low-level connector access.

const { connect, disconnect, disconnectAll, getConnector } = useMultichain();

// MetaMask — single EVM chain
await connect({ wallet: "metamask", chain: "eip155:14" });

// WalletConnect — multiple chains at once
await connect({ wallet: "wallet-connect", chains: ["eip155:14", "xrpl:0"] });

// Ledger — single chain with optional BIP44 options
await connect({ wallet: "ledger", chain: "eip155:14", options: { bip44, ledgerHDStandard: "bip44" } });

// Xaman — no chain selection
await connect({ wallet: "xaman" });

// D'CENT — no chain selection (detected from wallet, in-app browser only)
await connect({ wallet: "dcent" });

// Disconnect
await disconnect("metamask");
await disconnectAll();

useWallet(walletType)

Reactive wallet state and typed connector for a specific wallet.

const { state, connector, isConnected, isConnecting } = useWallet("metamask");

if (isConnected) {
  console.log(state.activeAddress); // "0x..."
  console.log(state.caip2); // "eip155:14"
  console.log(state.availableAddresses); // [{ address, caip2, isEvm, type }]
}

useWallets()

All configured wallets with their current states.

const wallets = useWallets();
// [{ type: 'metamask', state: WalletState }, { type: 'wallet-connect', state: WalletState }, ...]

useChain(caip2, options?)

Get the connected wallet for a specific chain. Returns a discriminated union with a type-safe client.

// EVM chain — client is EvmClient (viem-based)
const evm = useChain("eip155:14");
if (evm.exists) {
  console.log(evm.address); // "0x..." — resolved, no assertion needed
  console.log(evm.walletType); // "metamask" | "wallet-connect" | "ledger"
  const balance = await evm.client.getBalance(evm.address);
}

// XRPL chain — client is XrpClient
const xrpl = useChain("xrpl:0");
if (xrpl.exists) {
  console.log(xrpl.address); // "r..." — resolved, no assertion needed
  await xrpl.client.sendTx({ TransactionType: "Payment" /* ... */ });
}

// Prefer a specific wallet when multiple could serve the same chain
const flare = useChain("eip155:14", { wallet: "metamask" });

Contract Interaction (EVM)

The EVM client exposes viem's PublicClient and WalletClient for contract reads and writes.

import { useChain } from "@flarenetwork/multichain-wallet-connector/react";
import { useMutation } from "@tanstack/react-query";
import { erc20Abi, parseUnits } from "viem";

function TokenBalance({ chainId }: { chainId: Eip155Caip2 }) {
  const chain = useChain(chainId, { wallet: "metamask" });

  const balance = useMutation({
    mutationFn: async () => {
      if (!chain.exists) throw new Error("Not connected");
      const publicClient = chain.client.getPublicClient();
      return publicClient.readContract({
        address: "0xTokenAddress...",
        abi: erc20Abi,
        functionName: "balanceOf",
        args: ["0xAccountAddress..."],
      });
    },
  });

  // ...
}
function TokenTransfer({ chainId }: { chainId: Eip155Caip2 }) {
  const chain = useChain(chainId, { wallet: "metamask" });

  const transfer = useMutation({
    mutationFn: async () => {
      if (!chain.exists) throw new Error("Not connected");
      const walletClient = chain.client.getWalletClient();
      const [account] = await walletClient.getAddresses();
      return walletClient.writeContract({
        account,
        chain: null,
        address: "0xTokenAddress...",
        abi: erc20Abi,
        functionName: "transfer",
        args: ["0xRecipient...", parseUnits("10", 18)],
      });
    },
  });

  // ...
}

useChainSupport(filter?)

Get chain configuration, optionally filtered.

// All configured chains
const allChains = useChainSupport();

// Only EVM chains
const evmChains = useChainSupport({ chainType: "evm" });

// Only XRPL chains
const xrplChains = useChainSupport({ chainType: "xrpl" });

// Chains supported by a specific wallet
const ledgerChains = useChainSupport({ wallet: "ledger" });

// Each chain has: { caip2, name, nativeCurrency, rpcUrls, ... }

useWalletSupport(filter?)

Get configured wallets with their supported chains and connection mode.

const wallets = useWalletSupport();
// [{ type: 'metamask', name: 'MetaMask', chains: ['eip155:14', ...], singleChain: true }, ...]

// Exclude specific wallets
const filtered = useWalletSupport({ exclude: ["ledger"] });

useMultichainEvents(handlers)

Subscribe to global wallet events. Handlers are stable — the hook manages subscriptions internally.

useMultichainEvents({
  onConnect: ({ walletType, caip2, address, availableAddresses, isReconnect }) => {
    console.log(`${walletType} connected to ${caip2} with address ${address}`);
  },
  onDisconnect: ({ walletType }) => {
    console.log(`${walletType} disconnected`);
  },
  onChainChanged: ({ walletType, caip2 }) => {
    console.log(`${walletType} switched to ${caip2}`);
  },
  onError: ({ walletType, error }) => {
    console.error(`${walletType} error:`, error);
  },
});

MultiChain Instance Methods

Beyond the constructor and mount(), the MultiChain instance provides imperative methods for use outside of React components (e.g., in event callbacks or initialization logic).

const multichain = new MultiChain({
  /* ... */
});

// Check if any wallet is connected to a chain
if (multichain.isConnectedByCaip2("xrpl:0")) {
  await multichain.disconnectAll();
}

// Get all configured wallet types
const types = multichain.getWalletTypes(); // ["metamask", "wallet-connect", ...]

// Get a specific connector
const connector = multichain.getConnector("metamask");

Utility Functions

Type-safe wrappers for common Object methods.

import { objectKeys, objectValues, objectEntries, shortenAddress } from "@flarenetwork/multichain-wallet-connector";

objectKeys({ a: 1, b: 2 }); // Array<"a" | "b">
objectValues({ a: 1, b: 2 }); // Array<number>
objectEntries({ a: 1, b: 2 }); // Array<["a" | "b", number]>

shortenAddress("0x1234567890abcdef1234567890abcdef12345678");
// "0x1234...5678"

UI Helpers (Headless)

The library provides headless state management for common UI patterns. You build the UI; the library manages the state and actions.

Modal System

A headless selection API for wallet and chain selection. The library manages selection and connect-args derivation; your app manages modal visibility and screen/view routing.

import { MultichainSelectionProvider, useMultichainSelection } from "@flarenetwork/multichain-wallet-connector/react";

<MultichainSelectionProvider
  config={{
    mode: "multi-wallet:multi-chain",
    wallets: {
      metamask: "all",
      "wallet-connect": ["eip155:14", "xrpl:0"],
      xaman: "all",
    },
  }}
>
  <WalletModal />
</MultichainSelectionProvider>;

Selection Modes

ModeWallet SelectionChain Selection
single-wallet:single-chainOne walletPredetermined
single-wallet:multi-chainOne walletUser selects
multi-wallet:single-chainUser selectsPredetermined
multi-wallet:multi-chainUser selectsUser selects

For *:single-chain modes, pass caip2 in the config to fix the chain.

Using useMultichainSelection()

const selection = useMultichainSelection();

const args = selection.selectWallet("metamask");
selection.toggleCaip2("eip155:14");
selection.clearWallet();
selection.reset();

if (args) {
  await connect(args);
}

if (selection.canConnect) {
  await connect(selection.connectArgs);
}

selection.selectedWalletType;
selection.selectedWallet;
selection.selectedCaip2s;
selection.availableCaip2s;
selection.wallets;

Example A: Generic Connect Modal (app-owned view)

import {
  MultichainSelectionProvider,
  useMultichainSelection,
  useMultichain,
} from "@flarenetwork/multichain-wallet-connector/react";
import type { ConnectWalletArgs } from "@flarenetwork/multichain-wallet-connector/react";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";

function App() {
  return (
    <MultichainSelectionProvider
      config={{
        mode: "multi-wallet:multi-chain",
        wallets: {
          metamask: "all",
          "wallet-connect": "all",
          xaman: "all",
          dcent: "all",
        },
      }}
    >
      <WalletModal />
    </MultichainSelectionProvider>
  );
}

function useConnectMutation() {
  const { connect } = useMultichain();
  return useMutation({
    mutationFn: (args: ConnectWalletArgs) => connect(args),
  });
}

function WalletModal() {
  const selection = useMultichainSelection();
  const connectMutation = useConnectMutation();
  const [open, setOpen] = useState(false);

  const view = deriveView(selection);

  function deriveView(selection: ReturnType<typeof useMultichainSelection>) {
    if (!selection.selectedWalletType) return "wallet-list";
    if (selection.selectedWallet?.isConnected) return "success";

    const shouldSkipChainSelect =
      selection.fixedCaip2 !== null ||
      selection.selectedWalletType === "xaman" ||
      selection.selectedWalletType === "dcent";

    return shouldSkipChainSelect ? "connecting" : "chain-select";
  }

  if (!open) return <button onClick={() => setOpen(true)}>Connect Wallet</button>;

  return (
    <dialog open>
      <button
        onClick={() => {
          setOpen(false);
          selection.reset();
        }}
      >
        Close
      </button>

      {view === "wallet-list" && (
        <div>
          <h2>Select Wallet</h2>
          {selection.wallets.map((wallet) => (
            <button key={wallet.type} onClick={() => selection.selectWallet(wallet.type)}>
              {wallet.name}
              {wallet.isConnected && " (connected)"}
            </button>
          ))}
        </div>
      )}

      {view === "chain-select" && selection.selectedWallet && (
        <div>
          <h2>Select Chain</h2>
          <button onClick={selection.clearWallet}>Back</button>

          {selection.selectedWallet.caip2s.map((caip2) => (
            <button
              key={caip2}
              onClick={() => selection.toggleCaip2(caip2)}
              style={{ fontWeight: selection.selectedCaip2s.includes(caip2) ? "bold" : "normal" }}
            >
              {caip2} {selection.selectedCaip2s.includes(caip2) && "(selected)"}
            </button>
          ))}

          {connectMutation.error && <p style={{ color: "red" }}>{connectMutation.error.message}</p>}

          <button
            disabled={!selection.canConnect || connectMutation.isPending}
            onClick={() => selection.canConnect && connectMutation.mutate(selection.connectArgs)}
          >
            {connectMutation.isPending ? "Connecting..." : "Connect"}
          </button>
        </div>
      )}

      {view === "connecting" && <p>Connecting to {selection.selectedWallet?.name}...</p>}
      {view === "success" && <p>Connected to {selection.selectedWallet?.name}!</p>}
    </dialog>
  );
}

D'CENT only works inside its in-app browser. The connector initializes silently when unavailable (no errors or console warnings) — it only throws DCENT_NOT_IN_BROWSER when connect() is explicitly called. Use isAvailable() to check and redirect:

import { DcentConnector } from "@flarenetwork/multichain-wallet-connector";

if (!DcentConnector.isAvailable()) {
  // Pass the XRPL CAIP-2 identifier: "xrpl:0" (mainnet) or "xrpl:1" (testnet)
  window.location.assign(DcentConnector.getDynamicLink(window.location.href, "xrpl:0"));
}

Ledger Address Selection

Manage BIP44 derivation paths and address pagination for Ledger hardware wallets.

import { useLedgerSelection, useWallet, Bip44 } from "@flarenetwork/multichain-wallet-connector/react";
import type { Caip2 } from "@flarenetwork/multichain-wallet-connector/react";

function LedgerAddressPicker({ caip2 }: { caip2: Caip2 }) {
  const { connector } = useWallet("ledger");
  const { selection, selections } = useLedgerSelection(caip2);

  // Generate paginated BIP44 paths
  const page = Bip44.getPaginatedBip44s({
    from: 0,
    to: 5,
    ledgerHDStandard: selection.hdStandard,
    caip2,
  });

  // Fetch addresses from the device
  const addresses = await connector.fetchAddressesForSelection(caip2, page.paths);

  // User selects an address
  selection.selectAddress(addresses[0]);

  // Switch HD standard
  selection.setHdStandard("ledgerLive");

  // Access all selections across chains
  console.log(selections); // { 'eip155:14': { address, bip44 }, ... }
}

Options are optional — when omitted, defaults are resolved from the active Ledger session (HD standard and selected account). You can override with explicit values:

const { selection } = useLedgerSelection(caip2, {
  initialHdStandard: "ledgerLive",
  initialSelections: { "eip155:14": someAddress },
});

HD Standards

StandardPath PatternDescription
bip44m/44'/coin'/0'/0/indexClassic BIP44 derivation
ledgerLivem/44'/60'/index'/0/0Ledger Live derivation (EVM only)

Error Handling

All wallet errors use the WalletError class with typed error codes. Each wallet has a subclass (LedgerError, MetaMaskError, WalletConnectError, XamanError, DcentError) that maps raw SDK errors to typed codes via fromError().

WalletErrorHelper provides utilities for type-checking and resolving raw errors:

import { WalletErrorHelper, ErrorCode } from "@flarenetwork/multichain-wallet-connector/react";

try {
  await connect({ wallet: "metamask", chain: "eip155:14" });
} catch (error) {
  if (WalletErrorHelper.isWalletError(error)) {
    switch (error.codeKey) {
      case "COMMON_USER_REJECTED":
        // User rejected the connection request
        break;
      case "METAMASK_NOT_INSTALLED":
        // MetaMask extension not found
        break;
      case "LEDGER_WRONG_APP":
        // Wrong app open on Ledger device
        break;
      case "DCENT_NOT_IN_BROWSER":
        // Attempted to connect D'CENT outside its in-app browser
        break;
    }

    console.log(error.code); // 4001
    console.log(error.codeKey); // "COMMON_USER_REJECTED"
    console.log(error.walletType); // "metamask"
  }
}

Resolving raw errors by wallet type

When a raw (non-WalletError) error bubbles up from a client, use WalletErrorHelper.from() to map it to the correct subclass:

import { WalletErrorHelper } from "@flarenetwork/multichain-wallet-connector";

// Returns the error as-is if already a WalletError,
// otherwise dispatches to the subclass fromError() by wallet type.
// Returns null if the error can't be mapped.
const walletError = WalletErrorHelper.from(error, walletType);

Error Code Ranges

RangeCategory
4001–4010Common (all wallets)
4100–4102MetaMask
4200–4205Ledger
4300–4303WalletConnect
4400–4403Xaman
4500–4501D'CENT

Development

Building the library

pnpm install
pnpm build       # one-time build
pnpm build:watch # watch mode

Type checking

pnpm typecheck

Running the playground

The playground is a React app in playground/ that exercises all library features.

pnpm dev

This starts the Vite dev server from playground/. The playground imports the library from the parent directory source, so changes to the library are reflected immediately.

Linting

pnpm lint:check
pnpm lint:fix

License

MIT

Keywords

blockchain

FAQs

Package last updated on 05 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts