DEV Community

estell
estell

Posted on

Never Get Trapped by Wallet Vendors

TL:DR

Avoid wallet vendor lock-in by designing for portability from day one: use open client interfaces (EIP-1193 + EIP-6963 + WalletConnect), verify signatures in a vendor-agnostic way (ERC-1271 + ERC-6492), choose accounts you can re-key without changing address (ERC-4337 or EIP-7702), and keep keys portable/self-hostable with auditable key management.
Migration checklist at the end.

Why wallet vendor lock-in happens (and how to beat it)

Lock-in shows up when your app depends on a vendor’s SDK, key storage, or account model so tightly that switching breaks UX or forces new addresses. The antidote is to architect for exit: open standards, separable components, and accounts you can re-key.

1) Use open client interfaces (so any wallet works)

  • EIP-1193 — a minimal, event-driven provider API. Code to this instead of a vendor SDK.
  • EIP-6963 — discover all injected providers (multiple extensions/bridges) and let users pick.
  • WalletConnect v2 — open, multi-chain connection protocol instead of proprietary bridges.

Drop-in snippet (TypeScript/React) — discover providers via EIP-6963 and use EIP-1193:

// EIP-6963: collect all injected providers
type ProviderDetail = { info: { uuid: string; name: string }; provider: any };
const discovered = new Map<string, ProviderDetail>();

window.addEventListener('eip6963:announceProvider', (event: any) => {
  const detail = event.detail as ProviderDetail;
  discovered.set(detail.info.uuid, detail);
});

// Ask wallets to announce themselves
window.dispatchEvent(new Event('eip6963:requestProvider'));

// Connect with the chosen provider (UI for selection omitted)
export async function connect(uuid: string) {
  const chosen = discovered.get(uuid)?.provider;
  if (!chosen) throw new Error('Provider not found');
  const accounts = await chosen.request({ method: 'eth_requestAccounts' });
  return { provider: chosen, account: accounts[0] as string };
}

Enter fullscreen mode Exit fullscreen mode

This keeps you off vendor-specific SDKs and future-proofs your connection layer.

2) Verify signatures in a vendor-agnostic way

Your app should accept signatures from EOAs and smart accounts without caring which wallet produced them.

  • ERC-1271 — standard isValidSignature for contract wallets.
  • OpenZeppelin SignatureChecker — one helper that supports both ECDSA (EOA) and ERC-1271.
  • ERC-6492 — validates counterfactual (pre-deploy) smart-account signatures in off-chain checks.

Drop-in snippet (Solidity 0.8+) — robust signature verification:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";

library SigLib {
    function verify(address signer, bytes32 digest, bytes memory sig) internal view returns (bool) {
        // Works for EOAs (ECDSA) and ERC-1271 smart accounts
        return SignatureChecker.isValidSignatureNow(signer, digest, sig);
    }
}

Enter fullscreen mode Exit fullscreen mode

3) Re-key without changing address (4337 or 7702)

The most painful lock-in is when a vendor change means a new address.

  • ERC-4337 smart accounts: assets live in a contract you can update (rotate signers/modules) while the address and history stay the same. Any compliant bundler/AA stack can run underneath.
  • EIP-7702 “smart EOAs”: temporarily delegate execution to a contract while keeping your same EOA address. Great for gradual upgrades and revocation-friendly safety.

When to use which?

  • Need plugins/guardians/recovery now? → 4337
  • Already have a large EOA base and want a light upgrade path? → 7702
  • Want a hybrid? Start with 7702, migrate heavy users to 4337 later.

4) Keep keys portable (self-host > black box)

If users’ keys or recovery factors live in a vendor black box, you’re stuck. Prefer transparent, self-hostable key management you can audit and move.

  • Openfort’s OpenSigner is open-source and self-hostable; it splits sensitive material across components so you can own the critical pieces and still offer smooth UX.

Want a concise overview of how to design this?

👉 How to Avoid Wallet Vendor Dependency

👉 Deep dive on security & key architecture:

👉 EOA vs Smart Wallet trade-offs (when to re-key vs upgrade):

## Copy-paste patterns

A) SIWE without vendor coupling (1193 signer)

import { SiweMessage } from 'siwe';

export async function siweLogin(provider: any, domain: string, uri: string) {
  const [address] = await provider.request({ method: 'eth_requestAccounts' });
  const message = new SiweMessage({
    domain, address, uri,
    version: '1',
    nonce: crypto.randomUUID(),
    chainId: 1
  });
  const signature = await provider.request({
    method: 'personal_sign',
    params: [message.prepareMessage(), address]
  });
  // send { message: message.prepareMessage(), signature } to your backend for verification
  return { address, signature, message: message.prepareMessage() };
}

Enter fullscreen mode Exit fullscreen mode

Backends should verify ECDSA or ERC-1271, and support ERC-6492 if you accept counterfactual signatures.

Migration & RFP checklist (paste into your doc)

  1. Client standards
    • [ ] EIP-1193 only (no hard vendor SDK dependency)
    • [ ] EIP-6963 provider discovery (multi-injected support)
    • [ ] WalletConnect v2 supported (multi-chain)
  2. Signature compatibility
    • [ ] ERC-1271 accepted wherever you verify signatures
    • [ ] ERC-6492 supported for counterfactual signatures (off-chain flows)
  3. Account model
    • [ ] ERC-4337 or EIP-7702 so you can rotate signers without changing address
  4. Key management
    • [ ] Keys/recovery are exportable and portable
    • [ ] Open-source, auditable key infra (self-hostable path)
    • [ ] Clear data-residency & incident-response docs
  5. Exit plan
    • [ ] Written runbook (rotate signers, swap connection layer, decommission SDK)
    • [ ] Contractual guarantees (data export SLA, non-punitive rate limits)

### Common gotchas to avoid

  • Hard-coding window.ethereum instead of using EIP-6963 discovery.
  • Storing the wallet vendor name in auth sessions instead of checking capabilities.
  • Verifying signatures EOA-only and breaking smart-account users.
  • Gating features on a single wallet extension (“works best with …”).
  • Migrating users to a new address instead of re-keying the existing one.
  • Treating key management as a black box you can’t audit or move.

### Conclusion

Wallet vendor lock-in isn’t destiny. It’s just a set of choices. If you adopt open interfaces, vendor-agnostic signature checks, re-keyable accounts, and portable keys, you can change providers without breaking UX or addresses.

What would you add to the migration checklist? Any war stories from switching vendors?

Top comments (0)