
Company News
Socket Named Top Sales Organization by RepVue
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.
@flarenetwork/multichain-wallet-connector
Advanced tools
A multichain wallet connector library with core and React bindings
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.
eip155:14, xrpl:0)npm install @flarenetwork/multichain-wallet-connector
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.
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.
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>
);
}
| Wallet | Chains | Connection Mode |
|---|---|---|
| MetaMask | EVM | Single chain |
| WalletConnect | EVM + XRPL | Multiple chains at once |
| Ledger | EVM + XRPL | Single chain |
| Xaman | XRPL | No chain selection needed |
| D'CENT | XRPL | No chain selection needed (in-app browser only, silently skipped elsewhere) |
| Chain | CAIP-2 ID |
|---|---|
| Ethereum Mainnet | eip155:1 |
| Ethereum Sepolia | eip155:11155111 |
| Flare Mainnet | eip155:14 |
| Flare Testnet Coston | eip155:16 |
| Flare Testnet Coston2 | eip155:114 |
| Songbird Canary-Network | eip155:19 |
| XRP Ledger Mainnet | xrpl:0 |
| XRP Ledger Testnet | xrpl:1 |
| Wallet | EVM (eip155:*) | XRPL (xrpl:*) |
|---|---|---|
| MetaMask | Yes | — |
| WalletConnect | Yes | Yes |
| Ledger | Yes | Yes |
| Xaman | — | Yes |
| D'CENT | — | Yes |
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" });
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);
},
});
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");
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"
The library provides headless state management for common UI patterns. You build the UI; the library manages the state and actions.
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>;
| Mode | Wallet Selection | Chain Selection |
|---|---|---|
single-wallet:single-chain | One wallet | Predetermined |
single-wallet:multi-chain | One wallet | User selects |
multi-wallet:single-chain | User selects | Predetermined |
multi-wallet:multi-chain | User selects | User selects |
For *:single-chain modes, pass caip2 in the config to fix the chain.
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;
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"));
}
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 },
});
| Standard | Path Pattern | Description |
|---|---|---|
bip44 | m/44'/coin'/0'/0/index | Classic BIP44 derivation |
ledgerLive | m/44'/60'/index'/0/0 | Ledger Live derivation (EVM only) |
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"
}
}
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);
| Range | Category |
|---|---|
| 4001–4010 | Common (all wallets) |
| 4100–4102 | MetaMask |
| 4200–4205 | Ledger |
| 4300–4303 | WalletConnect |
| 4400–4403 | Xaman |
| 4500–4501 | D'CENT |
pnpm install
pnpm build # one-time build
pnpm build:watch # watch mode
pnpm typecheck
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.
pnpm lint:check
pnpm lint:fix
MIT
FAQs
A multichain wallet connector library with core and React bindings
We found that @flarenetwork/multichain-wallet-connector demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 9 open source maintainers collaborating on the project.
Did you know?

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.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.

Company News
/Security News
Socket is an initial recipient of OpenAI's Cybersecurity Grant Program, which commits $10M in API credits to defenders securing open source software.