05 · docs/WALLET-INTEGRATION.mdOpenPons manual

Wallet integration

OpenPons is non-custodial. It never asks for, receives, stores or transmits a private key, seed phrase or raw signing key. Every write is signed in the user's own wallet.

Source: src/lib/wallet/eip6963.ts, src/lib/wallet/WalletProvider.tsx, src/components/WalletUI.tsx.

Discovery — EIP-6963

On page load OpenPons:

  1. Adds a listener for eip6963:announceProvider.
  2. Dispatches eip6963:requestProvider.
  3. Stores every announcement as { key, info: { uuid, name, icon, rdns }, provider }. The key is the wallet's rdns (e.g. io.metamask, io.rabby, com.okex.wallet), so the list is not hard-coded — any EIP-6963 wallet appears automatically.
  4. ~350 ms later, checks window.ethereum. If it exists and is not the same object as any announced provider, it is added as a separate Legacy injected entry.

The modal re-dispatches eip6963:requestProvider every time it opens, so wallets that load late still show up.

Wallet icons are only rendered if they are data:image/* URIs (as EIP-6963 requires); anything else is dropped to prevent remote tracking pixels or javascript: URIs.

Provider isolation

  • Each discovered wallet keeps a reference to its own EIP-1193 provider object.
  • After the user picks a wallet, every request (eth_requestAccounts, eth_chainId, wallet_switchEthereumChain, eth_sendTransaction, …) is sent only to that object.
  • window.ethereum is never used as an implicit default. It is only reachable by explicitly selecting the "Legacy injected" entry.
  • accountsChanged / chainChanged / disconnect listeners are attached to the selected provider only and removed when the selection changes.

Persistence

Only the wallet key (rdns) is stored in localStorage (openpons.wallet.v1). After reload, providers are rediscovered and — if the same rdns reappears — OpenPons calls eth_accounts (which never prompts) to restore the session. If the wallet is gone or locked, the user simply sees "Connect wallet".

Robinhood Chain

Field Value
chainId 4663 (0x1237)
Native gas ETH
RPC https://rpc.mainnet.chain.robinhood.com
Explorer https://robinhoodchain.blockscout.com
  • Detection: eth_chainId on connect and on every chainChanged event.
  • Wrong network UI: a banner under the header plus a "Wrong network" wallet button.
  • Switch / add: wallet_switchEthereumChain; on error 4902 (unknown chain) OpenPons calls wallet_addEthereumChain with the parameters above.
  • Reads never go through the wallet. They use OpenPons' own RPC client (official RPC first, then optional OPENPONS_RPC_URLS / NEXT_PUBLIC_OPENPONS_RPC_URLS) with retries and fallback. A wallet on the wrong network can therefore never corrupt displayed data.

The single write path

sendPrepared(tx, onUpdate) is the only function that asks a wallet to sign. It:

  1. Refuses if another transaction is in flight (double-click / duplicate protection).
  2. Refuses if tx.chainId !== 4663, if tx.to is not an address, or if tx.from is set and differs from the connected account.
  3. Immediately before signing, re-reads eth_chainId and eth_accounts from the selected provider and aborts if either changed.
  4. Sends eth_sendTransaction with an explicit chainId: 0x1237 to the selected provider.
  5. Tracks the receipt through OpenPons' RPC (not the wallet), with retries, and reports confirmed or reverted based on receipt.status.

Callers (Trade, Launch, Claim) must additionally re-quote and simulate right before calling sendPrepared — see SECURITY.md.

Manual test matrix

Scenario Expected
Two EIP-6963 wallets installed Both listed; picking one only prompts that one
Only a legacy wallet Listed under "Legacy injected"
No wallet Empty state with install hint
Wallet on another chain Banner + "Wrong network"; trade buttons disabled
Switch account in wallet Address updates; stale prepared txs are rejected (from mismatch)
Reload Same wallet silently restored via eth_accounts
Double-click Buy Second click ignored while the first is in flight
Independent · not affiliated with Pons or Robinhood@openpons