Extension Wallet
Integration
Cosmos Chains
Wallet

Wallet

The interface of each Cosmos wallet is different, and in order to solve the problem of having to manually attach each wallet to the Dapp, it was developed to automatically add the same interface and wallet.

Installation

npm install @cosmostation/wallets

Or if you're using yarn:

yarn add @cosmostation/wallets

Add Wallet

To detect a wallet, it is ideal to inject it directly from the extension, but it is also possible to manually inject a wallet that has not been injected into a dapp.

Example

import { registerCosmosWallet, CosmosRegisterWallet } from '@cosmostation/wallets';
 
<button
  onClick={() => {
    const wallet: CosmosRegisterWallet = {
      // ...
    };
 
    registerCosmosWallet(wallet);
  }}
>
  Register Wallet
</button>;
Registered Wallets

go to test

Example

This is an example of adding keplr and leap wallet.

Keplr

import { registerCosmosWallet, CosmosRegisterWallet } from '@cosmostation/wallets';
 
<button
  onClick={() => {
    if (!window.keplr) {
      alert('Keplr extension is not installed');
      return;
    }
 
    const wallet: CosmosRegisterWallet = {
      name: 'Keplr',
      logo: 'https://wallet.keplr.app/keplr-brand-assets/keplr-logo.svg',
      events: {
        on(type, listener) {
          if (type === 'AccountChanged') {
            window.addEventListener('keplr_keystorechange', listener);
          }
        },
        off(type, listener) {
          if (type === 'AccountChanged') {
            window.removeEventListener('keplr_keystorechange', listener);
          }
        },
      },
      methods: {
        getSupportedChainIds: async () => {
          return ['cosmoshub-4'];
        },
        connect: async (chainIds) => {
          const cIds = typeof chainIds === 'string' ? [chainIds] : chainIds;
          const supportedChainIds = await wallet.methods.getSupportedChainIds();
 
          if (!cIds.every((cId) => supportedChainIds.includes(cId))) {
            throw new Error('Unsupported chainId is exist');
          }
 
          await window.keplr.enable(chainIds);
        },
        getAccount: async (chainId) => {
          const response = await window.keplr.getKey(chainId);
 
          return {
            address: response.bech32Address,
            name: response.name,
            public_key: {
              type: response.algo,
              value: Buffer.from(response.pubKey).toString('base64'),
            },
            is_ledger: response.isNanoLedger,
          };
        },
        signAmino: async (chainId, document, options) => {
          if (typeof options?.edit_mode?.fee === 'boolean') {
            window.keplr.defaultOptions.sign.preferNoSetFee = options.edit_mode.fee;
          }
 
          if (typeof options?.edit_mode?.memo === 'boolean') {
            window.keplr.defaultOptions.sign.preferNoSetMemo = options.edit_mode.memo;
          }
 
          if (typeof options?.is_check_balance === 'boolean') {
            window.keplr.defaultOptions.sign.disableBalanceCheck = options.is_check_balance;
          }
 
          const signer = options?.signer || (await wallet.methods.getAccount(chainId)).address;
 
          const response = await window.keplr.signAmino(chainId, signer, document);
 
          return {
            signature: response.signature.signature,
            signed_doc: response.signed,
          };
        },
        signDirect: async (chainId, document, options) => {
          if (typeof options?.edit_mode?.fee === 'boolean') {
            window.keplr.defaultOptions.sign.preferNoSetFee = options.edit_mode.fee;
          }
 
          if (typeof options?.edit_mode?.memo === 'boolean') {
            window.keplr.defaultOptions.sign.preferNoSetMemo = options.edit_mode.memo;
          }
 
          if (typeof options?.is_check_balance === 'boolean') {
            window.keplr.defaultOptions.sign.disableBalanceCheck = !options.is_check_balance;
          }
 
          const account = await wallet.methods.getAccount(chainId);
 
          if (account.is_ledger) {
            throw new Error('Ledger is not supported');
          }
 
          const signer = options?.signer || account.address;
 
          const signingDoc = {
            accountNumber: document.account_number,
            authInfoBytes: document.auth_info_bytes,
            chainId: document.chain_id,
            bodyBytes: document.body_bytes,
          };
 
          const response = await window.keplr.signDirect(chainId, signer, signingDoc);
 
          return {
            signature: response.signature.signature,
            signed_doc: {
              auth_info_bytes: response.signed.authInfoBytes,
              body_bytes: response.signed.bodyBytes,
            },
          };
        },
        sendTransaction: async (chainId, tx_bytes, mode) => {
          const broadcastMode =
            mode === 1 ? 'block' : mode === 2 ? 'sync' : mode === 3 ? 'async' : 'sync';
 
          const txBytes =
            typeof tx_bytes === 'string'
              ? new Uint8Array(Buffer.from(tx_bytes, 'base64'))
              : tx_bytes;
 
          const response = await window.keplr.sendTx(chainId, txBytes, broadcastMode);
 
          const txHash = Buffer.from(response).toString('hex').toUpperCase();
 
          return txHash;
        },
        addChain: async (chain) => {
          const coinType = chain.coin_type ? Number(chain.coin_type.replaceAll("'", '')) : 118;
 
          await window.keplr.experimentalSuggestChain({
            chainId: chain.chain_id,
            chainName: chain.chain_name,
            rpc: chain.lcd_url,
            rest: chain.lcd_url,
            bip44: {
              coinType,
            },
            bech32Config: {
              bech32PrefixAccAddr: chain.address_prefix,
              bech32PrefixAccPub: chain.address_prefix + 'pub',
              bech32PrefixValAddr: chain.address_prefix + 'valoper',
              bech32PrefixValPub: chain.address_prefix + 'valoperpub',
              bech32PrefixConsAddr: chain.address_prefix + 'valcons',
              bech32PrefixConsPub: chain.address_prefix + 'valconspub',
            },
            currencies: [
              {
                coinDenom: chain.display_denom,
                coinMinimalDenom: chain.base_denom,
                coinDecimals: chain.decimals || 6,
                coinGeckoId: chain.coingecko_id || 'unknown',
              },
            ],
            feeCurrencies: [
              {
                coinDenom: chain.display_denom,
                coinMinimalDenom: chain.base_denom,
                coinDecimals: chain.decimals || 6,
                coinGeckoId: chain.coingecko_id || 'unknown',
                gasPriceStep: {
                  low: chain?.gas_rate?.tiny ? Number(chain?.gas_rate?.tiny) : 0.01,
                  average: chain?.gas_rate?.low ? Number(chain?.gas_rate?.low) : 0.025,
                  high: chain?.gas_rate?.average ? Number(chain?.gas_rate?.average) : 0.04,
                },
              },
            ],
            stakeCurrency: {
              coinDenom: chain.display_denom,
              coinMinimalDenom: chain.base_denom,
              coinDecimals: chain.decimals || 6,
              coinGeckoId: chain.coingecko_id || 'unknown',
            },
          });
        },
      },
    };
 
    registerCosmosWallet(wallet);
  }}
>
  Register Keplr Wallet
</button>;

Leap

import { registerCosmosWallet, CosmosRegisterWallet } from '@cosmostation/wallets';
 
<button
  onClick={() => {
    if (!window.leap) {
      alert('Leap extension is not installed');
      return;
    }
 
    const wallet: CosmosRegisterWallet = {
      name: 'Leap',
      logo: 'https://miro.medium.com/v2/resize:fill:176:176/1*2jNLyjIPuU8HBbayPapwcQ.png',
      events: {
        on(type, listener) {
          if (type === 'AccountChanged') {
            window.addEventListener('leap_keystorechange', listener);
          }
        },
        off(type, listener) {
          if (type === 'AccountChanged') {
            window.removeEventListener('leap_keystorechange', listener);
          }
        },
      },
      methods: {
        getSupportedChainIds: async () => {
          return ['cosmoshub-4'];
        },
        connect: async (chainIds) => {
          const cIds = typeof chainIds === 'string' ? [chainIds] : chainIds;
          const supportedChainIds = await wallet.methods.getSupportedChainIds();
 
          if (!cIds.every((cId) => supportedChainIds.includes(cId))) {
            throw new Error('Unsupported chainId is exist');
          }
 
          await window.leap.enable(chainIds);
        },
        getAccount: async (chainId) => {
          const response = await window.leap.getKey(chainId);
 
          return {
            address: response.bech32Address,
            name: response.name,
            public_key: {
              type: response.algo,
              value: Buffer.from(response.pubKey).toString('base64'),
            },
            is_ledger: response.isNanoLedger,
          };
        },
        signAmino: async (chainId, document, options) => {
          if (typeof options?.edit_mode?.fee === 'boolean') {
            window.leap.defaultOptions.sign.preferNoSetFee = options.edit_mode.fee;
          }
 
          if (typeof options?.edit_mode?.memo === 'boolean') {
            window.leap.defaultOptions.sign.preferNoSetMemo = options.edit_mode.memo;
          }
 
          if (typeof options?.is_check_balance === 'boolean') {
            window.leap.defaultOptions.sign.disableBalanceCheck = options.is_check_balance;
          }
 
          const signer = options?.signer || (await wallet.methods.getAccount(chainId)).address;
 
          const response = await window.leap.signAmino(chainId, signer, document);
 
          return {
            signature: response.signature.signature,
            signed_doc: response.signed,
          };
        },
        signDirect: async (chainId, document, options) => {
          if (typeof options?.edit_mode?.fee === 'boolean') {
            window.leap.defaultOptions.sign.preferNoSetFee = options.edit_mode.fee;
          }
 
          if (typeof options?.edit_mode?.memo === 'boolean') {
            window.leap.defaultOptions.sign.preferNoSetMemo = options.edit_mode.memo;
          }
 
          if (typeof options?.is_check_balance === 'boolean') {
            window.leap.defaultOptions.sign.disableBalanceCheck = !options.is_check_balance;
          }
 
          const account = await wallet.methods.getAccount(chainId);
 
          if (account.is_ledger) {
            throw new Error('Ledger is not supported');
          }
 
          const signer = options?.signer || account.address;
 
          const signingDoc = {
            accountNumber: document.account_number,
            authInfoBytes: document.auth_info_bytes,
            chainId: document.chain_id,
            bodyBytes: document.body_bytes,
          };
 
          const response = await window.leap.signDirect(chainId, signer, signingDoc);
 
          return {
            signature: response.signature.signature,
            signed_doc: {
              auth_info_bytes: response.signed.authInfoBytes,
              body_bytes: response.signed.bodyBytes,
            },
          };
        },
        sendTransaction: async (chainId, tx_bytes, mode) => {
          const broadcastMode =
            mode === 1 ? 'block' : mode === 2 ? 'sync' : mode === 3 ? 'async' : 'sync';
 
          const txBytes =
            typeof tx_bytes === 'string'
              ? new Uint8Array(Buffer.from(tx_bytes, 'base64'))
              : tx_bytes;
 
          const response = await window.leap.sendTx(chainId, txBytes, broadcastMode);
 
          const txHash = Buffer.from(response).toString('hex').toUpperCase();
 
          return txHash;
        },
        addChain: async (chain) => {
          const coinType = chain.coin_type ? Number(chain.coin_type.replaceAll("'", '')) : 118;
 
          await window.leap.experimentalSuggestChain({
            chainId: chain.chain_id,
            chainName: chain.chain_name,
            rpc: chain.lcd_url,
            rest: chain.lcd_url,
            bip44: {
              coinType,
            },
            bech32Config: {
              bech32PrefixAccAddr: chain.address_prefix,
              bech32PrefixAccPub: chain.address_prefix + 'pub',
              bech32PrefixValAddr: chain.address_prefix + 'valoper',
              bech32PrefixValPub: chain.address_prefix + 'valoperpub',
              bech32PrefixConsAddr: chain.address_prefix + 'valcons',
              bech32PrefixConsPub: chain.address_prefix + 'valconspub',
            },
            currencies: [
              {
                coinDenom: chain.display_denom,
                coinMinimalDenom: chain.base_denom,
                coinDecimals: chain.decimals || 6,
                coinGeckoId: chain.coingecko_id || 'unknown',
              },
            ],
            feeCurrencies: [
              {
                coinDenom: chain.display_denom,
                coinMinimalDenom: chain.base_denom,
                coinDecimals: chain.decimals || 6,
                coinGeckoId: chain.coingecko_id || 'unknown',
                gasPriceStep: {
                  low: chain?.gas_rate?.tiny ? Number(chain?.gas_rate?.tiny) : 0.01,
                  average: chain?.gas_rate?.low ? Number(chain?.gas_rate?.low) : 0.025,
                  high: chain?.gas_rate?.average ? Number(chain?.gas_rate?.average) : 0.04,
                },
              },
            ],
            stakeCurrency: {
              coinDenom: chain.display_denom,
              coinMinimalDenom: chain.base_denom,
              coinDecimals: chain.decimals || 6,
              coinGeckoId: chain.coingecko_id || 'unknown',
            },
          });
        },
      },
    };
 
    registerCosmosWallet(wallet);
  }}
>
  Register Leap Wallet
</button>;