Wallet Integration

Guide to integrating WalletPair into your wallet application.

Create a Session

typescript
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_sendTransaction',
      'wallet_signMessage',
      'wallet_signTypedData',
      'wallet_switchChain',
    ],
    events: ['accountsChanged', 'chainChanged'],
    chains: ['eip155:1', 'eip155:137'],
  },
  meta: {
    name: 'My Wallet',
    description: 'A non-custodial wallet',
    url: 'https://wallet.example',
    icon: 'https://wallet.example/icon.png',
  },
});

The capabilities object declares what your wallet supports. Only include methods your wallet can actually handle. wallet_sendTransaction is optional — cold wallets and hardware signers that cannot broadcast should omit it.

Join a Pairing

typescript
// Parse the QR code / pairing URI
await session.prepareJoin(pairingUri);

// Display session fingerprint to the user for verification
console.log('Session fingerprint:', session.sessionFingerprint);

// Confirm the join (dApp auto-accepts after sealed_join verification)
await session.confirmJoin();

The two-step join (prepareJoin + confirmJoin) lets you display the session fingerprint before completing the handshake. The dApp auto-accepts once it verifies the sealed join payload.

Handle Requests

typescript
session.on('request', async (req) => {
  console.log('Method:', req.method);
  console.log('Params:', req.params);

  switch (req.method) {
    case 'wallet_getAccounts':
      await session.approve(req.id, {
        accounts: [{ address: '0x...', chains: ['eip155:1', 'eip155:137'] }],
      });
      break;

    case 'wallet_signMessage':
      // Show confirmation UI to user
      const signature = await sign(req.params.message);
      await session.approve(req.id, { signature });
      break;

    default:
      await session.reject(req.id, {
        code: 'unsupported_method',
        message: `Method ${req.method} not supported`,
      });
  }
});

Every request must be either approved or rejected. The wallet must display a confirmation UI for signing and transaction methods — never blind-sign.

Push Events

typescript
// Notify the dApp when accounts change
session.pushEvent('accountsChanged', {
  accounts: [{ address: '0xNew...', chains: ['eip155:1'] }],
});

// Notify when chain changes
session.pushEvent('chainChanged', {
  chain: 'eip155:137',
});

Push events proactively to keep the dApp in sync. Always emit chainChanged after a wallet_switchChain approval.

Persistence

typescript
const session = new WalletSession({
  transport,
  capabilities: { /* ... */ },
  persistence: {
    save: (snapshot) => secureStore.set('walletpair.session', snapshot),
    load: () => secureStore.get('walletpair.session'),
    clear: () => secureStore.delete('walletpair.session'),
  },
});

Use a secure storage backend (e.g., Expo SecureStore on mobile, encrypted IndexedDB on web). Session snapshots contain cryptographic keys and must be stored securely.

Security Requirements

  • All signing methods must show a user confirmation UI
  • Never blind-sign transactions or EIP-712 data
  • Warn users that EIP-191 signatures are not chain-bound
  • Detect and warn on Permit/spending-allowance patterns in EIP-712
  • Verify that chain param matches tx.chainId when both are present