Headless Core β
@xrpl-wallet-kit/core is the foundation of the kit β no DOM, no UI, no framework. Use it when you want to build your own UI from scratch, integrate with a design system, or use XRPL Wallet Kit in a non-browser environment.
When to use the headless core β
- Building a custom connect button or modal in your own design system
- Integrating with Radix UI, shadcn/ui, Headless UI, or similar libraries
- Server-side utilities β nonce generation, message validation, session parsing
- React Native or Electron apps where the DOM-based UI doesn't apply
- Testing or scripting β automating wallet interactions without UI
Install β
sh
npm install @xrpl-wallet-kit/core
npm install @xrpl-wallet-kit/adapter-gemwallet # add only the adapters you needBasic setup β
ts
import { WalletManager } from "@xrpl-wallet-kit/core";
import { createGemWalletAdapter } from "@xrpl-wallet-kit/adapter-gemwallet";
import { createXamanAdapter } from "@xrpl-wallet-kit/adapter-xaman";
const manager = new WalletManager({
adapters: [
createGemWalletAdapter(),
createXamanAdapter({ apiKey: import.meta.env.VITE_XAMAN_CLIENT_ID }),
],
network: {
id: "mainnet",
networkType: "MAINNET",
url: "wss://xrplcluster.com",
nativeAsset: "XRP",
nativeAssetDecimals: 6,
},
});That's it. No root element, no stylesheet, no framework.
Connecting a wallet β
Connect using a specific adapter ID, or let the user choose with your own UI:
ts
// List available adapters to build your own picker UI
const adapters = manager.getAdapters();
// [{ id: "gemwallet", name: "GemWallet", icon: "...", isAvailable: true }, ...]
// Connect to a specific adapter
const result = await manager.connect("gemwallet");
console.log(result.account.address); // rN7n3473...
// Or connect by adapter ID from user input
await manager.connect(selectedAdapterId);Session and state β
ts
// Get the current session (null if not connected)
const session = manager.getSession();
if (session) {
const { address, publicKey } = session.account;
const adapterId = session.adapterId;
}
// Get current connection status
const status = manager.getStatus();
// "idle" | "connecting" | "connected" | "disconnecting" | "error"Subscribing to events β
All UI should be driven by events β never poll.
ts
// Connection lifecycle
manager.on("connecting", () => { /* show loading state */ });
manager.on("connected", ({ session }) => { /* update UI with session */ });
manager.on("disconnected", () => { /* clear user state */ });
// Transaction lifecycle
manager.on("tx_submitted", ({ hash }) => { /* show pending */ });
manager.on("tx_confirmed", ({ hash, result }) => { /* show success */ });
manager.on("tx_failed", ({ hash, error }) => { /* show error */ });
// Remove a specific listener
const off = manager.on("connected", handler);
off(); // remove
// Remove all listeners for an event
manager.off("connected");Signing transactions β
ts
// Sign only β returns the signed blob, does not submit
const { signedTxBlob } = await manager.signTransaction({
txJson: {
TransactionType: "Payment",
Account: session.account.address,
Destination: "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
Amount: "1000000", // 1 XRP in drops
Fee: "12",
Sequence: 1,
},
});
// Sign and submit β returns hash + result
const { hash, result } = await manager.signAndSubmit({
txJson: { /* ... */ },
});Building a custom connect UI β
Here's a minimal example of a custom adapter picker + connect flow:
ts
class WalletPickerUI {
private manager: WalletManager;
private root: HTMLElement;
constructor(manager: WalletManager, root: HTMLElement) {
this.manager = manager;
this.root = root;
this.subscribeToManager();
this.render();
}
private subscribeToManager() {
this.manager.on("connecting", () => this.render());
this.manager.on("connected", () => this.render());
this.manager.on("disconnected", () => this.render());
this.manager.on("error", () => this.render());
}
private render() {
const status = this.manager.getStatus();
const session = this.manager.getSession();
if (status === "connected" && session) {
this.root.innerHTML = `
<div class="wallet-info">
<span>${session.account.address.slice(0, 10)}β¦</span>
<button id="wk-disconnect">Disconnect</button>
</div>`;
this.root.querySelector("#wk-disconnect")
?.addEventListener("click", () => this.manager.disconnect());
return;
}
if (status === "connecting") {
this.root.innerHTML = `<div class="wallet-loading">Connectingβ¦</div>`;
return;
}
// Show adapter list
const adapters = this.manager.getAdapters();
this.root.innerHTML = `
<div class="wallet-picker">
<h3>Connect Wallet</h3>
${adapters.map(a => `
<button class="adapter-btn" data-id="${a.id}" ${!a.isAvailable ? "disabled" : ""}>
<img src="${a.icon}" width="24" height="24" alt="" />
${a.name}
${!a.isAvailable ? " (not installed)" : ""}
</button>
`).join("")}
</div>`;
this.root.querySelectorAll<HTMLButtonElement>(".adapter-btn").forEach(btn => {
btn.addEventListener("click", () => {
this.manager.connect(btn.dataset.id!).catch(console.error);
});
});
}
destroy() {
this.manager.off("connecting");
this.manager.off("connected");
this.manager.off("disconnected");
this.manager.off("error");
this.root.innerHTML = "";
}
}Session restore on startup β
Always call recoverSession() once on app load to restore the previous session without prompting the user:
ts
const restored = await manager.recoverSession();
if (restored) {
console.log("Session restored:", restored.account.address);
}React without the React package β
If you're in React but don't want @xrpl-wallet-kit/react, you can use a useSyncExternalStore-based hook:
ts
import { useSyncExternalStore, useEffect, useRef } from "react";
import type { WalletManager, WalletSession } from "@xrpl-wallet-kit/core";
export function useWalletSession(manager: WalletManager): WalletSession | null {
const subscribe = useRef((cb: () => void) => {
const off1 = manager.on("connected", cb);
const off2 = manager.on("disconnected", cb);
const off3 = manager.on("session_restored", cb);
return () => { off1(); off2(); off3(); };
}).current;
return useSyncExternalStore(
subscribe,
() => manager.getSession(),
() => null // SSR snapshot
);
}Destroying the manager β
Clean up event listeners and connections when the app unmounts:
ts
manager.destroy();See also β
- WalletManager API β full method reference
- Events & Hooks β all 17 events with payloads
- React integration β if you want the React package
- Writing a Custom Adapter