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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@solutiofi/connectorkit-svelte

v0.7.1

Published

Solana wallet connection library for Svelte 5, powered by @solana/connector headless API

Readme

@solutiofi/connectorkit-svelte

The ultimate Solana wallet connection library for Svelte 5. Powered by @solana/connector headless API and built with Svelte Runes.

npm version License: MIT

Features

  • Zero-Config Polyfills — Works with Vite/SvelteKit out of the box (no vite-plugin-node-polyfills needed)
  • 🎨 Fully Themeable — Customize every color with CSS variables; optional shadcn-token shim for drop-in integration with shadcn-styled apps
  • 📱 Mobile-First — Deep links to wallet apps, MWA on Android (Seeker / Seed Vault), WalletConnect QR
  • 🔁 Cross-tab Deep-Link Returns — Phantom/Solflare/Backpack often redirect back into a new Chrome tab; we propagate the connection back to the original tab via BroadcastChannel so the user doesn't notice
  • 🔧 Svelte 5 Runes — Built from the ground up for the new reactivity model; SSR-safe
  • 🌐 Wallet Standard — Supports all modern Solana wallets (Phantom, Solflare, Backpack, Jupiter, Magic Eden, …) + your own via additionalWallets
  • 🔐 Remote Signer — Drop-in /remote adapter for custodial backends (Fireblocks, Privy, Turnkey, your own KMS) — the signing key stays on your server
  • ✍️ Signing — Sign messages and transactions with full TypeScript support
  • 💰 Balances — Auto-fetching for SOL and SPL tokens, with token logo proxy support
  • 🎯 Filtering — Precise control over which wallets are shown, hidden, or featured; "Recent" badge for the last-used wallet
  • 👥 Multi-Account — Seamless switching between accounts; standalone <AccountSwitcher /> component
  • 🛟 Cancel Anywhere — Tap the connect button again while it's connecting to abort cleanly (essential for Seeker MWA where the wallet emits no cancellation signal)
  • 🪪 Hardened Sessions — Deep-link ephemeral keypairs expire after 24h (configurable) instead of living forever in localStorage

Installation

npm install @solutiofi/connectorkit-svelte @solana/web3.js
# or
pnpm add @solutiofi/connectorkit-svelte @solana/web3.js

@solana/web3.js is a peer dependency — install it alongside the library so your bundler resolves a single version.

Quick Start

  1. Wrap your application with SvelteConnectorProvider. The getDefaultConfig() helper fills in sensible defaults (clusters, autoConnect, WalletConnect metadata) so you only specify what's unique to your app:
<!-- +layout.svelte -->
<script lang="ts">
  import {
    SvelteConnectorProvider,
    WalletButton,
    getDefaultConfig,
  } from '@solutiofi/connectorkit-svelte';
  import '@solutiofi/connectorkit-svelte/theme.css';

  const config = getDefaultConfig({
    appName: 'My Solana App',
    network: 'mainnet',
    walletConnect: true, // auto-reads PUBLIC_WALLETCONNECT_PROJECT_ID / VITE_WALLETCONNECT_PROJECT_ID / NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID
  });
</script>

<SvelteConnectorProvider {config}>
  <nav>
    <WalletButton />
  </nav>
  <slot />
</SvelteConnectorProvider>

Prefer to hand-roll the config object instead? You can — getDefaultConfig() returns a plain SvelteConnectorConfig you can spread or replace. See Configuration below.

  1. Use the context in valid child components:
<!-- +page.svelte -->
<script lang="ts">
  import { getConnectorContext, TokenList } from '@solutiofi/connectorkit-svelte';

  const ctx = getConnectorContext();
</script>

{#if ctx.state.isConnected}
  <h1>Welcome, {ctx.state.publicKey}</h1>
  <TokenList />
{/if}

Configuration

Detailed configuration reference for SvelteConnectorConfig:

interface SvelteConnectorConfig {
  // --- Basic ---
  appName: string;              // Name shown in wallet connection prompts
  appUrl?: string;              // URL of your app (for wallet metadata)
  appIcon?: string;             // Icon URL — forwarded to MWA `appIdentity.icon`
                                // and WalletConnect metadata. Defaults to `/favicon.svg`.
  network?: 'mainnet' | 'devnet' | 'testnet' | 'localnet'
          | 'solana:mainnet' | 'solana:devnet' | 'solana:testnet' | 'solana:localnet';
                                // Default: 'mainnet'. Both the legacy short form
                                // and the CAIP-2 chain-id form are accepted —
                                // pick whichever matches the docs you're copying
                                // from. See "Cluster IDs" below.

  // --- Connection ---
  autoConnect?: boolean;        // Attempt to auto-connect on load (default: false).
                                // When enabled, restores the LAST-connected wallet
                                // (not the first in the list).
  rpcUrl?: string;              // Custom RPC URL (defaults to public Alchemy endpoint)
  debug?: boolean;              // Enable verbose logging (gates internal console.log)

  // --- UI/UX ---
  walletOrder?: string[];       // Sort wallets by name: ['Phantom', 'Backpack']
  walletLimit?: number;         // Max wallets to show before "More" (default: 4)
  imageProxy?: string;          // Proxy URL for wallet icons + token logos
                                // (CORS / privacy). See "Image Proxy" below.

  // --- Filtering ---
  wallets?: {
    allowList?: string[];       // Strict list: ONLY show these wallets
    denyList?: string[];        // Blacklist: NEVER show these wallets
    featured?: string[];        // Highlight these wallets at the top with a badge
  };

  // --- Custom / Embedded Wallets ---
  additionalWallets?: Wallet[]; // Wallet Standard wallets registered alongside
                                // the auto-discovered ones. Useful for embedded
                                // signers, dev/test wallets, or remote-signer
                                // adapters. Each must implement
                                // `@wallet-standard/base`'s `Wallet` interface.

  // --- Network Switching ---
  clusters?: {                  // Custom networks for ClusterSelector
    name: string;               // Display name (shown in ClusterSelector)
    id: string;                 // Cluster id — accepts both 'mainnet' / 'devnet' /
                                // 'testnet' / 'localnet' AND the CAIP-2 form
                                // 'solana:mainnet' / etc. Custom ids pass through
                                // unchanged.
    rpcUrl: string;
    wsUrl?: string;
  }[];

  // --- WalletConnect (Optional) ---
  walletConnect?: {
    projectId: string;          // Get from https://cloud.walletconnect.com
    metadata?: {
      name: string;
      description: string;
      url: string;
      icons: string[];
    };
  };
}

getDefaultConfig({ ... })

A small factory that fills in the boilerplate so you can write:

const config = getDefaultConfig({
  appName: 'My App',
  walletConnect: true, // shorthand — auto-detects PROJECT_ID env var
});

| Option | Type | Default | |---|---|---| | appName | string | required | | appUrl | string | undefined (provider falls back to window.location.origin at runtime) | | appIcon | string | undefined | | network | 'mainnet' \| 'devnet' \| 'testnet' \| 'localnet' | 'mainnet' | | clusters | ClusterConfig[] | DEFAULT_CLUSTERS (Mainnet/Devnet/Testnet) | | autoConnect | boolean | true | | walletConnect | true \| { projectId, metadata? } | not enabled | | additionalWallets | Wallet[] | none | | debug | boolean | false |

When walletConnect: true, the project ID is auto-detected (first non-empty wins):

  1. PUBLIC_WALLETCONNECT_PROJECT_ID (SvelteKit $env/static/public)
  2. VITE_WALLETCONNECT_PROJECT_ID (plain Vite)
  3. NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID (cross-framework convention)
  4. globalThis.__CONNECTORKIT_ENV__.<KEY> (escape hatch for non-bundler environments)

If none is found, WC is silently skipped — the rest of the connector still works. Pass an explicit walletConnect: { projectId: '...' } to override.

DEFAULT_CLUSTERS is also exported in case you want to extend rather than replace.

Cluster IDs

@solutiofi/connectorkit-svelte accepts cluster ids in both the legacy short form and the CAIP-2 chain-id form — the library normalizes between them internally:

| Network | Legacy form | CAIP-2 form (canonical) | |---|---|---| | Mainnet | mainnet | solana:mainnet | | Devnet | devnet | solana:devnet | | Testnet | testnet | solana:testnet | | Localnet | localnet | solana:localnet |

Both work as input to selectCluster(id), config.network, and clusters[].id. Custom cluster ids (anything not in the table above) pass through unchanged. Snippets copy-pasted from upstream Solana docs that use the CAIP-2 form work without translation.

A handful of helpers are exported for callers that need to interop with both forms:

import {
  toCaip2ClusterId,    // 'mainnet' → 'solana:mainnet' (unknown ids pass through)
  toLegacyClusterId,   // 'solana:devnet' → 'devnet'
  isCaip2ClusterId,    // true for 'solana:*'
  clusterIdsMatch,     // form-agnostic equality: ('mainnet', 'solana:mainnet') → true
} from '@solutiofi/connectorkit-svelte';

ctx.currentCluster.id preserves whichever form was originally configured (no surprise rewrites). Use toCaip2ClusterId(ctx.currentCluster.id) if you need the canonical form regardless.

Component Reference

<WalletButton />

The primary connect button. Automatically handles "Connect", "Disconnect", "Copy Address", and "Change Wallet".

| Prop | Type | Default | Description | |------|------|---------|-------------| | size | 'sm' \| 'md' \| 'lg' | 'md' | Scaling of the button | | showIcon | boolean | true | Show wallet icon when connected | | class | string | '' | Custom classes |

<WalletModal />

A standalone modal component. Use this if you want to create your own trigger button.

<script>
  let isOpen = $state(false);
</script>
<button onclick={() => isOpen = true}>Connect Custom</button>
<WalletModal bind:open={isOpen} />

| Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | false | Control modal visibility | | title | string | 'Connect Wallet' | Modal title text | | onclose | () => void | - | Called when modal closes | | footer | Snippet | - | Custom footer content (see below) |

The modal ships with:

  • Real focus trap — Tab cycles inside, Shift+Tab cycles back, ESC closes, focus returns to the invoking element on close
  • Body scroll lock while open
  • role="alert" error banner so screen readers announce connection failures immediately
  • "Recent" badge on the last-connected wallet — it's also floated to the top of the picker
  • "Don't have a wallet yet?" install-link section when none of Phantom / Solflare / Backpack / Glow are detected (desktop only — mobile uses deep-link wallets for the same purpose)
  • Reactive Android layout — MWA + WalletConnect promoted; deep-link wallets in a collapsible "All Wallets" section

Custom Footer Slot

You can pass a custom footer to add additional functionality like Ledger or hardware wallet connections:

<WalletModal bind:open={isOpen}>
  {#snippet footer()}
    <button onclick={handleLedgerConnect}>
      🔒 Connect Ledger
    </button>
  {/snippet}
</WalletModal>

<ClusterSelector />

A dropdown to switch between networks (Mainnet/Devnet/Testnet) or custom clusters. ESC closes the dropdown via a <svelte:window> keydown handler.

Multi-Account Support

If a connected wallet provides multiple accounts, @solutiofi/connectorkit-svelte automatically handles them. The <WalletButton /> and <WalletDropdown /> include a built-in UI for switching accounts.

The selected account is persisted per-wallet, so users don't need to re-select when they reconnect.

<AccountSwitcher />

Standalone account picker — use it in your app header or any spot where the in-dropdown switcher isn't enough. Reads from ctx.state.allAccounts and calls ctx.selectAccount(address) on click.

<script>
  import { AccountSwitcher } from '@solutiofi/connectorkit-svelte';
</script>

<AccountSwitcher />

| Prop | Type | Default | Description | |---|---|---|---| | class | string | '' | Extra class names on the root | | hideWhenSingle | boolean | true | Hide entirely if the wallet only exposes one account |

<TokenList />

Displays a list of SPL tokens held by the connected wallet.

  • Auto-fetches from RPC; refetches when the cluster's RPC URL changes
  • Shows token icons, symbols, and formatted balances
  • Filters out zero-balance tokens
  • Token logos go through imageProxy if configured

| Prop | Type | Default | Description | |---|---|---|---| | class | string | '' | Extra class names on the root | | maxHeight | string | '300px' | Scroll-area max-height (any CSS length) |

<TransactionHistory />

Recent on-chain activity for the connected wallet, classified per-row (sent / received / swap / nft / stake / program / tokenAccountClosed). Drop it into a sidebar / drawer.

  • Auto-fetches via getSignaturesForAddress + getParsedTransaction (in batches) when the wallet connects or the active cluster changes
  • Classifier inspects parsed instructions + pre/post token balances — see Transaction classifier below
  • Each row links to Solana Explorer on the active cluster (mainnet / devnet / testnet / localnet)
  • Token enrichment (symbol / logo / decimals) via optional lookup — wire to your <TokenList> cache
  • Pagination via signature cursor (Load more button)
<TransactionHistory pageSize={20} lookup={(mint) => tokenStore.tokens.find(t => t.mint === mint)} />

| Prop | Type | Default | Description | |---|---|---|---| | class | string | '' | Extra class names on the root | | maxHeight | string | '400px' | Scroll-area max-height (any CSS length) | | pageSize | number | 20 | Signatures pulled per page | | lookup | (mint) => { symbol?, logo?, decimals? } \| undefined | none | Mint → token info resolver for enrichment | | hideHeader | boolean | false | Hide the title bar (useful inside a drawer with its own header) |

The headless <TransactionHistoryElement> variant ships in the same module — see below.

<WalletConnectQR />

Rendered by the modal automatically when the user picks WalletConnect, but also exported for standalone use. The canvas redraws when the uri prop changes (WC v2 rotates URIs on session expiry), has aria-label for screen readers, and includes a "Copy link instead" button for users who can't scan (CLI wallets, mobile-only-paired wallets, etc.).

<WalletConnectQR uri={ctx.state.walletConnectUri} />

<AddressDisplay />

Displays a truncated wallet address with click-to-copy.

| Prop | Type | Default | Description | |------|------|---------|-------------| | address | string | required | The address to display | | chars | number | 4 | Chars to show (e.g. ABCD...WXYZ) | | copyable | boolean | false | Enable copy-to-clipboard |

Headless Element composables

The pre-styled components above are built on top of headless Element counterparts. Pass a children snippet to render with your own markup; receive the slice + actions as the snippet's args. Use these when you want the library's state machine but completely custom styling — drop them anywhere <SvelteConnectorProvider> is mounted.

<script>
  import { BalanceElement, AccountElement, ClusterElement, DisconnectElement } from '@solutiofi/connectorkit-svelte';
</script>

<BalanceElement precision={4}>
  {#snippet children({ formattedBalance, isLoading, refetch })}
    <button onclick={refetch}>
      {isLoading ? '…' : `${formattedBalance} SOL`}
    </button>
  {/snippet}
</BalanceElement>

<AccountElement addressChars={6}>
  {#snippet children({ formattedAddress, walletName, walletIcon, copy, copied, disconnect, isConnected })}
    {#if isConnected}
      <div class="my-pill">
        <img src={walletIcon} alt="" />
        <span>{walletName}</span>
        <button onclick={copy}>{copied ? 'Copied' : formattedAddress}</button>
        <button onclick={disconnect} aria-label="Sign out">×</button>
      </div>
    {/if}
  {/snippet}
</AccountElement>

<ClusterElement>
  {#snippet children({ current, clusters, selectCluster, isMainnet })}
    <select value={current?.id} onchange={(e) => selectCluster(e.currentTarget.value)}>
      {#each clusters as c}<option value={c.id}>{c.name}</option>{/each}
    </select>
    {#if !isMainnet}<span class="warn">⚠ Test cluster</span>{/if}
  {/snippet}
</ClusterElement>

<DisconnectElement>
  {#snippet children({ disconnect, isConnected, walletName })}
    {#if isConnected}
      <button onclick={disconnect}>Sign out of {walletName}</button>
    {/if}
  {/snippet}
</DisconnectElement>

| Element | Snippet args | |---|---| | <BalanceElement> | { balance, formattedBalance, symbol, isLoading, error, lastFetched, refetch } + prop precision?: number | | <AccountElement> | { address, formattedAddress, walletName, walletIcon, isConnected, isConnecting, allAccounts, selectAccount, copy, copied, disconnect } + prop addressChars?: number | | <ClusterElement> | { current, clusters, selectCluster, isMainnet, currentRpcUrl } | | <DisconnectElement> | { disconnect, isConnected, walletName } | | <TransactionHistoryElement> | { transactions, isLoading, hasMore, error, lastFetched, refetch, loadMore, explorerUrlFor } + props pageSize?: number, lookup?: TokenInfoLookup, commitment?: 'processed' \| 'confirmed' \| 'finalized' |

Transaction classifier

The classifier feeding <TransactionHistory> (classifyTransaction from _utils/tx-classifier.ts) maps a ParsedTransactionWithMeta to a ClassifiedTransaction { type, amount?, symbol?, swapFrom?, swapTo?, counterparty?, programName?, status, fee, ... }. Priority-ordered rules (earlier wins): swap → tokenAccountClosed → token transfer → SOL transfer → NFT → stake → program fallback. Known DEX / NFT / stake program IDs live in _utils/programs.ts — add to that registry to get programName on additional protocols. Use the headless <TransactionHistoryElement> to plug in custom row markup while keeping classification + pagination logic.

Element components render null when no children snippet is provided — they hold no state of their own.

Hooks & API

Access the connector state reactively anywhere in your app:

import { getConnectorContext } from '@solutiofi/connectorkit-svelte';

const ctx = getConnectorContext();

State Properties

  • ctx.state.isConnected: boolean
  • ctx.state.publicKey: string | null
  • ctx.state.account: WalletAccount | null (currently selected account)
  • ctx.state.allAccounts: WalletAccount[] (All available accounts)
  • ctx.state.isConnecting: boolean
  • ctx.state.connectingWalletId: string | null (which wallet ID is mid-connect)
  • ctx.state.connectors: WalletConnector[] (every detected + registered wallet)
  • ctx.state.walletConnectUri: string | null (WC QR URI when active)
  • ctx.state.error: WalletError | null
  • ctx.currentCluster: ClusterConfig used for network switching
  • ctx.currentRpcUrl: string current RPC endpoint
  • ctx.clusters: ClusterConfig[] available clusters
  • ctx.config: SvelteConnectorConfig the provider was constructed with (read-only)

Methods

  • ctx.connect(walletId): Connect to a specific wallet
  • ctx.cancelConnect(): Abort an in-flight connect attempt. No-op when nothing is connecting. Required for MWA on Seeker, where the SDK emits no signal when the user dismisses the wallet's authorization popup — surface a "Cancel" button in your UI while isConnecting is true (<WalletButton /> does this automatically).
  • ctx.disconnect(): Disconnect current wallet
  • ctx.clearError(): Clear state.error
  • ctx.openWalletConnect(): Open the WC flow directly
  • ctx.signMessage(message): Sign a byte array
  • ctx.signTransaction(tx): Sign a serialized transaction
  • ctx.signAndSendTransaction(tx, options): Sign and submit to network
  • ctx.selectCluster(id): Switch network
  • ctx.selectAccount(address): Switch active account
  • ctx.balanceStore: shared SOL balance store ({ balance, isLoading, fetchBalance, refresh, formatBalance, ... })

getConnectorContext() is strict — it throws if called outside a <SvelteConnectorProvider>. Pair it with hasConnectorContext() (also exported) for components that need to render with or without the provider.

@solana/kit signer

For consumers on the modern @solana/kit SDK (formerly gill), getKitSigner(ctx?) returns a TransactionPartialSigner backed by the connected wallet. Forwards through the same ctx.signTransaction() pipeline (so MWA / DeepLink / Wallet-Standard wallets all work uniformly).

import { getConnectorContext, getKitSigner } from '@solutiofi/connectorkit-svelte';
import {
  pipe,
  setTransactionMessageFeePayerSigner,
  signTransaction
} from '@solana/kit';

const signer = getKitSigner(); // throws if no wallet connected

const signedTx = await pipe(
  message,
  (m) => setTransactionMessageFeePayerSigner(signer, m),
  (m) => signTransaction(m, [signer])
);

@solana/kit is an optional peer dependency (peerDependenciesMeta.optional: true). Importing getKitSigner alone doesn't pull kit at runtime — its types are import type only. Consumers who don't use kit pay nothing.

Theming

All styles are defined as CSS custom properties. Light mode is the default; add .dark class — or set data-theme="dark" on <html> — to activate dark mode. Both selectors are supported for compatibility with Tailwind v4's darkMode: 'class' setup.

Two import paths

// Default — defines `--sck-*` tokens
import '@solutiofi/connectorkit-svelte/theme.css';

// OR: shadcn-compatible shim that maps shadcn's standard tokens
// (`--background`, `--foreground`, `--primary`, `--border`, `--radius`,
// etc.) onto the library's `--sck-*` tokens. Drop-in for any app already
// running a shadcn-styled palette + dark-mode toggle.
import '@solutiofi/connectorkit-svelte/theme.shadcn.css';

The shadcn shim falls back to the library's own defaults when a shadcn token is undefined, so partial themes still render correctly.

Complete CSS Variable Reference

| Variable | Light Mode | Dark Mode | Description | |----------|------------|-----------|-------------| | Brand Colors |||| | --sck-primary | #008AFF | #3DA6FF | Main action color | | --sck-primary-hover | #0075D9 | #008AFF | Hover state | | Backgrounds |||| | --sck-bg | #FBFCFD | #191C21 | Modal/dropdown background | | --sck-bg-secondary | #F4F5F7 | #272C33 | Card backgrounds | | --sck-bg-hover | #E6E7EB | #30363F | Hover backgrounds | | Text |||| | --sck-text | #1F2329 | #FBFCFD | Primary text | | --sck-text-muted | #5B6778 | #939EAE | Muted text | | Borders |||| | --sck-border | #CBD1D8 | #383F4A | Border color | | Status Colors |||| | --sck-success | #248C3E | #34D058 | Success states | | --sck-error | #E42E2C | #F85149 | Error states | | --sck-warning | #FFA500 | #FFCC00 | Warning states |

Enabling Dark Mode

Add the .dark class to the body element to activate dark mode:

<script>
  import { onMount } from 'svelte';
  let isDark = $state(true);

  onMount(() => {
    // Restore from localStorage or use system preference
    const saved = localStorage.getItem('theme');
    isDark = saved ? saved === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches;
    applyTheme();
  });

  function toggleTheme() {
    isDark = !isDark;
    localStorage.setItem('theme', isDark ? 'dark' : 'light');
    applyTheme();
  }

  function applyTheme() {
    document.body.classList.toggle('dark', isDark);
  }
</script>

<button onclick={toggleTheme}>
  {isDark ? '☀️' : '🌙'}
</button>

Image Proxy

Token logos + wallet icons frequently live on third-party CDNs that throttle anonymous traffic or block CORS. Pass imageProxy on your config to route them through your own server (or an image-CDN like imgproxy / wsrv.nl / Cloudinary):

getDefaultConfig({
  appName: 'My App',
  // Append form (default):
  imageProxy: 'https://images.weserv.nl/?url=',
  // …or placeholder form:
  // imageProxy: 'https://my.cdn.example/img?src={url}&w=128',
});
  • Source URL is URL-encoded and appended as ?url=... by default
  • If {url} placeholder is present, it's substituted in-place
  • data: and blob: URIs bypass the proxy
  • Applied to both <WalletIcon /> (wallet logos) and <TokenList /> (token logos)

Adding Custom Wallets

Pass additionalWallets to register a Wallet-Standard wallet alongside auto-discovered ones. Useful for embedded signers, dev/test wallets, or remote-signer adapters:

import type { Wallet } from '@wallet-standard/base';
import { myEmbeddedWallet } from './my-embedded-wallet';

const config = getDefaultConfig({
  appName: 'My App',
  additionalWallets: [myEmbeddedWallet], // Any object implementing the Wallet interface
});

The wallet is forwarded straight into the wallet-standard registry; discovery / dedupe / filtering downstream is identical to auto-discovered wallets.

Remote Signer (@solutiofi/connectorkit-svelte/remote)

For custodial / server-side signing setups — Fireblocks, Privy, Turnkey, or your own KMS — createRemoteSignerWallet returns a Wallet-Standard wallet that delegates signing to an HTTP endpoint. The signing key lives on your server; the browser never holds it.

import { getDefaultConfig } from '@solutiofi/connectorkit-svelte';
import { createRemoteSignerWallet } from '@solutiofi/connectorkit-svelte/remote';

const remoteWallet = createRemoteSignerWallet({
  name: 'My Custodial Wallet',
  address: 'HeLpfu1Vau1tHpZmvbsCRYrSk9oHHFGuesxBdxYHvLW', // base58 pubkey
  endpoint: '/api/connector-signer',
  // Async headers — recompute per request for short-lived bearer tokens
  headers: async () => ({ authorization: `Bearer ${await getSessionToken()}` }),
});

const config = getDefaultConfig({
  appName: 'My App',
  additionalWallets: [remoteWallet],
});

Server contract

Three JSON-over-POST handlers. Defaults are /sign-transaction, /sign-and-send-transaction, /sign-message (override via paths).

POST /sign-transaction
  { account, transaction (base64), chain ('solana:mainnet'|...) }
→ { signedTransaction (base64) }

POST /sign-and-send-transaction
  { account, transaction (base64), chain, options? }
→ { signature (base58) }

POST /sign-message
  { account, message (base64) }
→ { signature (base64) }

Non-2xx responses surface as a RemoteSignerError with .status and .bodyText populated, so app code can react to e.g. 403 (token expired) or 503 (signer locked) distinctly from network failures.

Options

| Option | Type | Default | |---|---|---| | name | string | required — shown in the wallet picker | | address | string | required — base58 pubkey the signer represents | | endpoint | string | required — base URL, e.g. '/api/signer' or 'https://signer.example.com' | | icon | WalletIcon | generic placeholder | | headers | Record<string,string> | () => Promise<Record<string,string>> | none | | paths | Partial<RemoteSignerPaths> | /sign-transaction, /sign-and-send-transaction, /sign-message | | chains | readonly ('solana:mainnet'\|'solana:devnet'\|'solana:testnet'\|'solana:localnet')[] | all four | | fetch | typeof fetch | global fetch |

The full wire types (SignTransactionRequest, SignAndSendTransactionResponse, etc.) are exported from the same subpath if you want to share them between client and server route handlers.

Troubleshooting & FAQ

"Global is not defined" or "Buffer is not defined"?

You generally do not need to configure this manually. @solutiofi/connectorkit-svelte automatically polyfills global, Buffer, and process in the browser environment when the provider mounts.

Does this work with SvelteKit SSR?

Yes — <WalletButton>, <WalletModal>, <WalletDropdown> and the rest of the surface are SSR-safe. All localStorage / sessionStorage access goes through internal safeLocalStorage / safeSessionStorage wrappers that return null server-side instead of throwing. SSR-relevant derived state (showAndroidLayout, recentConnectorId, etc.) is wrapped in $derived so it re-evaluates after hydration without mismatch.

My wallet isn't showing up?

  1. Ensure the wallet is installed and supports the Wallet Standard (most do).
  2. Check if you have wallets.allowList configured and the wallet is missing from it.
  3. Check wallets.denyList.
  4. On desktop, if no wallets are detected the modal shows an "Install …" section with links for Phantom / Solflare / Backpack / Glow — the user may need to install one first.

autoConnect is reconnecting to the wrong wallet on refresh

As of 0.3.1 the auto-connect logic prefers the last-connected wallet (read from localStorage) and only falls back to the first rehydrated wallet in the list. Earlier versions silently picked whichever wallet wallet-standard listed first — Solflare always beat Phantom if both had cached accounts. Update to ≥ 0.3.1.

Mobile wallets aren't detected?

On mobile Safari/Chrome, wallet extensions can't inject — wallets are standalone apps. @solutiofi/connectorkit-svelte handles this automatically:

Native Mobile App Support (Automatic):

  • On mobile devices, wallets like Phantom, Solflare, Backpack, Jupiter, and Magic Eden are auto-detected and added to the wallet list
  • They appear as standard wallets in the connect modal
  • Tapping them initiates an encrypted (NaCl box / X25519 + XSalsa20-Poly1305) deep-link connection — redirects to the wallet app
  • After approval, the wallet redirects back to your dApp and the connection is auto-established

On Android (MWA):

  • Solana Mobile Wallet Adapter is registered as an additional connector ("Solana Mobile")
  • On Seeker / Seed Vault, this is the native path; the SDK is dynamic-imported so desktop bundles don't carry the MWA dep tree
  • cluster, appIcon, appName, appUrl are all threaded through from SvelteConnectorConfig — no silent fallback to mainnet-beta or /favicon.svg

WalletConnect (recommended as cross-platform fallback): Configure WalletConnect for universal mobile support. The provider package is dynamic-imported so consumers without a projectId pay no runtime cost.

const config = getDefaultConfig({
  appName: 'My App',
  network: 'mainnet',
  walletConnect: { projectId: 'YOUR_PROJECT_ID' }, // or `true` to auto-detect
});

Mobile connection flow:

  1. User opens your dApp in mobile Safari/Chrome
  2. Taps "Connect Wallet"
  3. Sees Phantom, Solflare, etc. in the standard wallet list
  4. Taps "Phantom" → redirects to Phantom app → user approves
  5. Wallet redirects back to your dApp → connection surfaces in your UI

Cross-tab deep-link returns: Phantom/Solflare/Backpack universal-link returns frequently open a new Chrome tab. We use BroadcastChannel('connectorkit-svelte:deeplink') to propagate the connection from the new tab back to your original tab — so ctx.state.isConnected flips in the tab the user was already on, no manual tab-switching needed.

Deep-link session storage: ephemeral encryption keypairs are stored in localStorage with a configurable 24h TTL (sessionTtlMs). Sessions older than the TTL are discarded on load so XSS-extractable keys don't live forever.

Cancelling MWA / a wallet app: tap the "Connecting…" button to abort. On Seeker the SDK emits no signal when the user dismisses the wallet popup, so we detect the cancellation via window.blur/window.focus transitions and accept an explicit ctx.cancelConnect() call as a hard fallback. <WalletButton> wires this up automatically.

Pro tip: If users open your dApp from within a wallet's in-app browser (e.g., Phantom's browser), the wallet injects itself via Wallet Standard — no deep links needed.

Dev / debug

Set debug: true on your config (or use getDefaultConfig({ ..., debug: true })) to enable verbose internal logging. The library uses a gated logger (logger.log); logger.warn / logger.error always emit. No console.log ships unguarded.

License

MIT © 2026 Connectorkit Authors