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

walletpair-sdk

v1.1.0

Published

TypeScript SDK for the [WalletPair Protocol](https://github.com/atshelchin/walletpair/blob/main/walletpair-protocol-v1.md) -- connect dApps and wallets with end-to-end encrypted, relay-based or BLE communication.

Downloads

1,645

Readme

walletpair-sdk

TypeScript SDK for the WalletPair Protocol -- connect dApps and wallets with end-to-end encrypted, relay-based or BLE communication.

Features

  • Chain-agnostic core -- uses CAIP-2 chain IDs (eip155:1, solana:mainnet), ready for multi-chain
  • EVM support -- EIP-1193 provider + wagmi connector (under walletpair-sdk/evm)
  • Transport-agnostic -- WebSocket relay and Web Bluetooth (BLE) transports included, pluggable Transport interface for custom transports
  • End-to-end encrypted -- X25519 key exchange + ChaCha20-Poly1305 AEAD, relay never sees payload content
  • Session snapshots -- serialize() / restore() for controlled reconnect flows; production crash recovery requires write-ahead counter persistence
  • Zero native dependencies -- pure JS crypto via noble libraries

Architecture

walletpair-sdk
├── Core (chain-agnostic)
│   ├── crypto.ts          X25519, HKDF, ChaCha20-Poly1305, seal/unseal
│   ├── types.ts           Transport interface, CAIP-2 helpers, protocol messages
│   ├── emitter.ts         Typed event emitter
│   ├── ws-transport.ts    WebSocket transport (browser/Node/Deno/Bun)
│   ├── dapp-session.ts    DApp-side session state machine
│   └── wallet-session.ts  Wallet-side session state machine
├── BLE (walletpair-sdk/ble)
│   ├── framing.ts         BLE message fragmentation/reassembly (Section 19.5)
│   └── web-ble-transport.ts  Web Bluetooth Central transport (runtime detection)
└── EVM (walletpair-sdk/evm)
    ├── eip1193.ts         EIP-1193 provider (maps eth_ methods to WalletPair)
    └── wagmi.ts           wagmi connector factory

Data Flow

┌──────────┐                              ┌──────────┐
│   dApp   │                              │  Wallet  │
│          │                              │          │
│ DAppSession                        WalletSession  │
│     │    │                              │    │     │
│     ▼    │     ┌──────────────┐         │    ▼     │
│ Transport├────►│ Relay / BLE  │◄────────┤Transport │
│          │     └──────────────┘         │          │
└──────────┘     (sees only routing       └──────────┘
                  metadata -- payloads
                  are E2E encrypted)

Install

npm install walletpair-sdk

Quick Start

DApp Side (Vanilla JS/TS)

import { DAppSession, WebSocketTransport } from 'walletpair-sdk'

const transport = new WebSocketTransport('wss://relay.walletpair.org/v1')
const session = new DAppSession({
  transport,
  meta: { name: 'My dApp', description: 'Example dApp', url: 'https://example.com', icon: 'https://example.com/icon.png' },
})

// 1. Create pairing -- display the URI as a QR code
const uri = await session.createPairing()
console.log('Scan this:', uri)

// 2. When wallet joins, show session fingerprint for visual verification
//    (DApp auto-accepts after sealed_join verification)
session.on('sessionFingerprint', (fingerprint) => {
  console.log('Session fingerprint:', fingerprint)
  // Display to user so they can verify it matches wallet display
})

// 3. Once connected, send requests
session.on('phase', async (phase) => {
  if (phase === 'connected') {
    const accounts = await session.request('wallet_getAccounts')
    console.log('Accounts:', accounts)
  }
})

// 4. Listen for wallet events
session.on('event', ({ event, data }) => {
  console.log(`Event: ${event}`, data)
})

Wallet Side (JS/TS / React Native)

import { WalletSession, WebSocketTransport } from 'walletpair-sdk'

const transport = new WebSocketTransport('wss://relay.walletpair.org/v1')
const session = new WalletSession({
  transport,
  capabilities: {
    methods: ['wallet_getAccounts', 'wallet_signMessage'],
    events: ['accountsChanged', 'chainChanged'],
    chains: ['eip155:1', 'eip155:137'],
  },
  meta: { name: 'My Wallet', description: 'Example Wallet', url: 'https://mywallet.app', icon: 'https://mywallet.app/icon.png' },
})

// 1. Join from pairing URI (scanned from QR code)
const fingerprint = await session.joinFromUri(uri)
console.log('Session fingerprint:', fingerprint) // show to user for visual verification

// 2. Handle incoming requests
session.on('request', ({ id, method, params }) => {
  switch (method) {
    case 'wallet_getAccounts':
      session.approve(id, ['0xYourAddress'])
      break
    case 'wallet_signMessage':
      // Sign and return, or reject
      session.approve(id, { signature: '0x...' })
      // session.reject(id, 'user_rejected', 'User declined')
      break
  }
})

// 3. Push events to dApp
session.pushEvent('accountsChanged', { accounts: ['0xNewAddress'] })

EVM dApp with EIP-1193 Provider

import { DAppSession, WebSocketTransport } from 'walletpair-sdk'
import { WalletPairProvider } from 'walletpair-sdk/evm'

const transport = new WebSocketTransport('wss://relay.walletpair.org/v1')
const session = new DAppSession({
  transport,
  meta: { name: 'My dApp', description: 'Example dApp', url: 'https://example.com', icon: 'https://example.com/icon.png' },
})
const provider = new WalletPairProvider({ session, chainId: 1 })

// Use like any EIP-1193 provider
const accounts = await provider.request({ method: 'eth_requestAccounts' })
const chainId = await provider.request({ method: 'eth_chainId' })

// Standard EIP-1193 events
provider.on('accountsChanged', (accounts) => { /* ... */ })
provider.on('chainChanged', (chainId) => { /* ... */ })
provider.on('disconnect', (error) => { /* ... */ })

EVM dApp with wagmi

import { walletPair } from 'walletpair-sdk/evm/wagmi'
import { createConfig, http } from 'wagmi'
import { mainnet, polygon } from 'wagmi/chains'

const config = createConfig({
  chains: [mainnet, polygon],
  connectors: [
    walletPair({
      relayUrl: 'wss://relay.walletpair.org/v1',
      meta: { name: 'My dApp', description: 'Example dApp', url: 'https://example.com', icon: 'https://example.com/icon.png' },
      onPairingUri: (uri) => {
        // Display QR code with this URI
        showQrCode(uri)
      },
      onSessionFingerprint: (fingerprint) => {
        // Display session fingerprint for user visual verification
        showSessionFingerprint(fingerprint)
      },
    }),
  ],
  transports: {
    [mainnet.id]: http(),
    [polygon.id]: http(),
  },
})

BLE Transport (Web Bluetooth)

import { DAppSession } from 'walletpair-sdk'
import { WebBleCentralTransport, isWebBleSupported } from 'walletpair-sdk/ble'

if (isWebBleSupported()) {
  const transport = new WebBleCentralTransport()
  const session = new DAppSession({
    transport,
    meta: { name: 'My dApp', description: 'Example dApp', url: 'https://example.com', icon: 'https://example.com/icon.png' },
  })

  // BLE pairing URI has no relay parameter
  const uri = await session.createPairing()
  // uri = "walletpair:?ch=...&pubkey=..."
}

Session Snapshots

serialize() and restore() can be used in controlled reconnect flows, but they are not enough for production crash recovery by themselves. The protocol requires sequence counters to be persisted before every encrypted send. A crash after sending but before saving a new snapshot can roll back a counter and cause nonce reuse with the same traffic key.

For production, persist { traffic_keys, sendSeq, recvSeq } with a write-ahead store before each send, or disable reconnect after process/page termination and require fresh pairing.

Demo-only page reload snapshot:

// Save before unload
window.addEventListener('beforeunload', () => {
  sessionStorage.setItem('wp', session.serialize())
})

// Restore on load
const saved = sessionStorage.getItem('wp')
if (saved && session.restore(saved)) {
  await session.reconnect()
}

API Reference

Core

DAppSession

new DAppSession({ transport, meta: { name, description, url, icon }, requestTimeout?, autoAccept? })

| Method | Description | |--------|-------------| | createPairing(): Promise<string> | Create channel, returns pairing URI for QR display | | acceptWallet() | Accept wallet (called automatically after sealed_join verification) | | rejectWallet() | Reject wallet pairing | | request<T>(method, params?): Promise<T> | Send encrypted request, returns decrypted response | | ping() | Send heartbeat ping | | close() | Gracefully close session | | destroy() | Close + remove all event listeners | | serialize(): string | Serialize a session snapshot | | restore(json): boolean | Restore a session snapshot | | reconnect(): Promise<void> | Reconnect after restore |

Events:

| Event | Payload | Description | |-------|---------|-------------| | phase | DAppPhase | State machine transition | | pairingUri | string | Pairing URI generated | | sessionFingerprint | string | Session fingerprint for visual verification | | walletJoined | { pubkey, capabilities?, meta } | Wallet joined the channel | | response | { id, ok, data } | Response received | | event | { event, data } | Wallet pushed an event | | error | Error | Error occurred |

Phases: idle -> waiting -> pending_accept -> connected -> closed

WalletSession

new WalletSession({ transport, capabilities, meta: { name, description, url, icon } })

| Method | Description | |--------|-------------| | joinFromUri(uri): Promise<string> | Join channel, returns session fingerprint | | approve(requestId, result) | Approve request with encrypted result | | reject(requestId, code?, message?) | Reject request with error | | pushEvent(event, data) | Push event to dApp | | ping() | Send heartbeat ping | | close() | Gracefully close session | | destroy() | Close + remove all event listeners | | serialize() / restore(json) | Session snapshot/restore |

Events:

| Event | Payload | Description | |-------|---------|-------------| | phase | WalletPhase | State machine transition | | sessionFingerprint | string | Session fingerprint for visual verification | | request | { id, method, params } | Incoming request from dApp | | error | Error | Error occurred |

Phases: idle -> waiting -> connected -> closed

WebSocketTransport

new WebSocketTransport(url: string)
new WebSocketTransport({ url: string, protocols?: string[] })

Transport Interface

Implement this to create custom transports (e.g., for React Native BLE peripheral):

interface Transport {
  readonly state: 'disconnected' | 'connecting' | 'connected'
  send(msg: ProtocolMessage): void
  connect(): Promise<void>
  disconnect(): void
  onMessage(handler: (msg: ProtocolMessage) => void): void
  onClose(handler: () => void): void
  onOpen(handler: () => void): void
}

CAIP-2 Chain ID Helpers

import { parseChainId, formatChainId, evmChainId, evmNumericChainId } from 'walletpair-sdk'

parseChainId('eip155:1')           // { namespace: 'eip155', reference: '1' }
formatChainId('eip155', '137')     // 'eip155:137'
evmChainId(1)                      // 'eip155:1'
evmNumericChainId('eip155:1')      // 1
evmNumericChainId('solana:mainnet') // null

EVM

WalletPairProvider (EIP-1193)

import { WalletPairProvider } from 'walletpair-sdk/evm'

new WalletPairProvider({ session, chainId?, mapper? })

Default method mapping:

| EIP-1193 Method | WalletPair Method | |----------------|-------------------| | eth_requestAccounts | wallet_getAccounts | | eth_accounts | wallet_getAccounts | | personal_sign | wallet_signMessage | | eth_signTypedData_v4 | wallet_signTypedData | | eth_sendTransaction | wallet_signTransaction | | wallet_switchEthereumChain | wallet_switchChain | | wallet_addEthereumChain | wallet_addChain | | eth_chainId | Handled locally (returns cached chain ID) | | net_version | Handled locally (returns cached chain ID) | | Others | Passed through as-is |

Override with a custom MethodMapper for specialized behavior.

walletPair() (wagmi connector)

import { walletPair } from 'walletpair-sdk/evm/wagmi'

walletPair({
  relayUrl: string,          // WebSocket relay URL
  meta: { name, description, url, icon }, // DApp metadata
  requestTimeout?: number,   // Request timeout in ms
  onPairingUri?: (uri) => void,            // QR code display callback
  onSessionFingerprint?: (fingerprint) => void, // Session fingerprint display callback
})

BLE

import {
  WebBleCentralTransport,  // Web Bluetooth Central (dApp side)
  isWebBleSupported,        // Runtime availability check
  frameMessage,             // Low-level: split JSON into BLE frames
  Defragmenter,             // Low-level: reassemble BLE frames
  BLE_SERVICE_UUID,         // WalletPair BLE service UUID
  BLE_WRITE_CHAR_UUID,      // DApp -> Wallet characteristic
  BLE_NOTIFY_CHAR_UUID,     // Wallet -> DApp characteristic
} from 'walletpair-sdk/ble'

Extending for New Chains

To add support for a new chain (e.g., Solana):

  1. Create src/solana/ directory
  2. Implement a provider that maps Solana RPC methods to WalletPair requests
  3. Add subpath export in package.json:
    { "./solana": "./src/solana/index.ts" }
  4. Wallet side: declare Solana chains in capabilities:
    capabilities: {
      methods: ['wallet_signTransaction', 'wallet_signMessage'],
      chains: ['solana:mainnet', 'solana:devnet'],
    }

The core protocol is chain-agnostic -- DAppSession.request() and WalletSession.approve() work with any method/params structure.

Publishing

# 1. Create a changeset (select patch/minor/major)
npx changeset

# 2. Apply changeset and bump version
npx changeset version

# 3. Build and publish to npm
npm run changeset:publish

Security

  • E2E Encryption: X25519 ECDH -> HKDF-SHA256 -> ChaCha20-Poly1305 AEAD
  • MITM Protection: Session fingerprint derived from SHA256(prefix || channel_id || dapp_pubkey) for visual verification on both devices
  • Replay Protection: Sequence-number-based nonces, monotonically increasing
  • Channel Isolation: 256-bit random channel IDs
  • Zero Trust Relay: Relay sees routing metadata only, never plaintext payloads

License

MIT