A standardized way to discover Cosmos wallet extensions in the browser, inspired by EIP-6963 for Ethereum.
The Cosmos ecosystem lacks a standardized way to discover wallet extensions. Currently, dApps need to:
- Check for specific global variables (
window.keplr,window.leap, etc.) - Hardcode wallet detection logic for each wallet
- Deal with race conditions when wallets inject at different times
- Maintain a growing list of wallet-specific detection code
CIPD solves this by providing an event-based discovery protocol that:
- Works with any wallet that implements the protocol
- Handles race conditions automatically
- Provides a consistent API for wallet discovery
- Includes a polyfill for existing wallets that don't yet support CIPD natively
CIPD defines two custom events:
cosmos:requestProvider- Dispatched by dApps to request wallet announcementscosmos:announceProvider- Dispatched by wallets to announce their presence
Each wallet announcement includes:
interfaceCosmosProviderDetail{info: {uuid: string// Unique identifier for this provider instancename: string// Human-readable wallet name (e.g., "Keplr")icon: string// Data URL of the wallet iconrdns: string// Reverse domain name ID (e.g., "app.keplr")}provider: CosmosProvider// The wallet's Cosmos provider interface}npm install @luknyb/cipd
# or
pnpm add @luknyb/cipd
# or
yarn add @luknyb/cipdimport{createStore}from'@luknyb/cipd'// Create a store to manage discovered walletsconststore=createStore()// Subscribe to wallet changesstore.subscribe((providers)=>{console.log('Available wallets:',providers.map(p=>p.info.name))})// Get all currently discovered walletsconstwallets=store.getProviders()// Find a specific wallet by RDNSconstkeplr=store.findProvider('app.keplr')if(keplr){awaitkeplr.provider.enable('cosmoshub-4')constkey=awaitkeplr.provider.getKey('cosmoshub-4')console.log('Address:',key.bech32Address)}<scriptlang="ts">import { onMount } from'svelte'import { createStore, typeCosmosProviderDetail } from'@luknyb/cipd'let wallets =$state<CosmosProviderDetail[]>([])onMount(() => {const store =createStore()returnstore.subscribe((providers) => {wallets= [...providers] }, { emitImmediately: true })})</script>
{#eachwalletsaswallet}
<buttononclick={() =>connect(wallet)}>
<imgsrc={wallet.info.icon} alt={wallet.info.name} />
{wallet.info.name}
</button>
{/each}import{useEffect,useState}from'react'import{createStore,typeCosmosProviderDetail}from'@luknyb/cipd'functionWalletList(){const[wallets,setWallets]=useState<CosmosProviderDetail[]>([])useEffect(()=>{conststore=createStore()returnstore.subscribe((providers)=>{setWallets([...providers])},{emitImmediately: true})},[])return(<div>{wallets.map((wallet)=>(<buttonkey={wallet.info.uuid}onClick={()=>connect(wallet)}><imgsrc={wallet.info.icon}alt={wallet.info.name}/>{wallet.info.name}</button>))}</div>)}<script setup lang="ts">import { ref, onMounted, onUnmounted } from'vue'import { createStore, typeCosmosProviderDetail } from'@luknyb/cipd'const wallets =ref<CosmosProviderDetail[]>([])let unsubscribe: (() =>void) |undefinedonMounted(() => {const store =createStore()unsubscribe=store.subscribe((providers) => {wallets.value= [...providers] }, { emitImmediately: true })})onUnmounted(() =>unsubscribe?.())</script>
<template>
<buttonv-for="wallet in wallets":key="wallet.info.uuid"@click="connect(wallet)">
<img:src="wallet.info.icon":alt="wallet.info.name" />
{{ wallet.info.name }}
</button>
</template>Creates a store for managing discovered wallet providers.
interfaceCreateStoreOptions{polyfill?: boolean// Whether to detect existing wallets (default: true)}conststore=createStore({polyfill: true})| Method | Description |
|---|---|
subscribe(listener, options?) | Subscribe to provider changes. Returns unsubscribe function. |
getProviders() | Get all currently discovered providers. |
findProvider(rdns) | Find a provider by its RDNS identifier. |
reset() | Clear and re-request providers. |
clear() | Clear all providers from the store. |
destroy() | Destroy the store and remove all listeners. |
Manually request all wallets to announce themselves.
import{requestProviders}from'@luknyb/cipd'requestProviders()Manually announce a wallet provider (useful for wallet developers).
import{announceProvider}from'@luknyb/cipd'announceProvider({info: {uuid: crypto.randomUUID(),name: 'My Wallet',icon: 'data:image/svg+xml;base64,...',rdns: 'com.mywallet',},provider: myCosmosProvider,})The polyfill automatically detects these wallets:
| Wallet | RDNS |
|---|---|
| Keplr | app.keplr |
| Leap | io.leapwallet |
| Cosmostation | io.cosmostation |
| XDEFI | io.xdefi |
| Falcon | io.falconwallet |
| Coin98 | io.coin98 |
To natively support CIPD in your wallet extension:
// Listen for discovery requestswindow.addEventListener('cosmos:requestProvider',()=>{announceWallet()})// Announce on loadannounceWallet()functionannounceWallet(){window.dispatchEvent(newCustomEvent('cosmos:announceProvider',{detail: {info: {uuid: crypto.randomUUID(),name: 'My Wallet',icon: 'data:image/svg+xml;base64,...',rdns: 'com.mywallet',},provider: window.myWallet,},}))}Full TypeScript support with exported types:
importtype{CosmosProvider,CosmosProviderDetail,CosmosProviderInfo,Key,OfflineSigner,ChainInfo,Store,}from'@luknyb/cipd'MIT