Tomo Connect SDK Lite

The Tomo Connect SDK Lite aims to define an easy-to-integrate and highly compatible Blockchain Provider API for dApps. This API allows dApps to easily connect to all kinds of wallets (including extension wallets, hardware wallets, and mobile wallets), retrieve account asset data, and sign messages and transactions (especially Babylon staking related transactions).

Integration

First, you need to add Tomo Connect SDK in to your project dependency:

npm install @tomo-inc/wallet-connect-sdk

Then initialize the UI view and context provider

import {
  TomoContextProvider,
  useTomoModalControl,
  useTomoWalletConnect
} from '@tomo-inc/wallet-connect-sdk'

export default function Demo() {
  return (
    <TomoContextProvider
      // optional
      style={{
        rounded: 'medium',
        theme: 'light',
        primaryColor: '#FF7C2A'
      }}
    >
      <ChildComponent />
    </TomoContextProvider>
  )
}

type ChildProps = {
  style: TomoProviderSetting['style']
  setStyle: (v: TomoProviderSetting['style']) => void
}
export function ChildComponent(props: ChildProps) {
  const tomoModal = useTomoModalControl()
  const tomoWalletConnect = useTomoWalletConnect()

  return (
    <div style={{ textAlign: 'right' }}>
      <button
        onClick={async () => {
          await tomoModal.open('connect')
        }}
      >
        Connect Wallet
      </button>
      <button
        onClick={async () => {
          await tomoWalletConnect.disconnect()
        }}
      >
        Disconnect Wallet
      </button>
    </div>
  )
}

Using the above code in your dApp, you can easily open Tomo BTC connect modal and interact with the supported BTC wallet.

Using the Provider API

Once you connect the wallet, you can use the following way to interact with the connect wallet:

import {
  useTomoProviders,
  useTomoModalControl,
  useTomoWalletConnect,
  useTomoWalletState
} from '@tomo-inc/wallet-connect-sdk'

// Open BTC connect modal
const tomoModal = useTomoModalControl();
tomoModal.open("connect");

// Check wallet connection status
const tomowalletState = useTomoWalletState();
const connected = tomowalletState.isConnected;

// Get provider
const providers = useTomoProviders();
const provider = providers.bitcoinProvider;

// Disconnect
const tomoWalletConnect = useTomoWalletConnect();
tomoWalletConnect.disconnect();

All the provider APIs are as follows:

export type Fees = {
  // fee for inclusion in the next block
  fastestFee: number
  // fee for inclusion in a block in 30 mins
  halfHourFee: number
  // fee for inclusion in a block in 1 hour
  hourFee: number
  // economy fee: inclusion not guaranteed
  economyFee: number
  // minimum fee: the minimum fee of the network
  minimumFee: number
}

// UTXO is a structure defining attributes for a UTXO
export interface UTXO {
  // hash of transaction that holds the UTXO
  txid: string
  // index of the output in the transaction
  vout: number
  // amount of satoshis the UTXO holds
  value: number
  // the script that the UTXO contains
  scriptPubKey: string
}

// supported networks
export enum Network {
  MAINNET = 'mainnet',
  TESTNET = 'testnet',
  SIGNET = 'signet'
}

export interface InscriptionResult {
  list: Inscription[]
  total: number
}

export interface Inscription {
  output: string
  inscriptionId: string
  address: string
  offset: number
  outputValue: number
  location: string
  contentType: string
  contentLength: number
  inscriptionNumber: number
  timestamp: number
  genesisTransaction: string
}

export abstract class WalletProvider {
  abstract connectWallet(): Promise<this>
  abstract getWalletProviderName(): Promise<string>
  abstract getAddress(): Promise<string>
  abstract getPublicKeyHex(): Promise<string>
  abstract signPsbt(psbtHex: string): Promise<string>
  abstract signPsbts(psbtsHexes: string[]): Promise<string[]>
  abstract getNetwork(): Promise<Network>
  abstract signMessageBIP322(message: string): Promise<string>
  abstract on(eventName: string, callBack: () => void): void
  abstract off(eventName: string, callBack: () => void): void
  abstract switchNetwork(network: Network): Promise<void>
  abstract sendBitcoin(to: string, satAmount: number): Promise<string>
  abstract getNetworkFees(): Promise<Fees>
  abstract pushTx(txHex: string): Promise<string>
  abstract getUtxos(address: string, amount?: number): Promise<UTXO[]>
  abstract getBTCTipHeight(): Promise<number>
  abstract getBalance(): Promise<number>
  abstract getInscriptions(cursor?: number, size?: number): Promise<InscriptionResult>
}

Wallet Customization

Currently Tomo Connect SDK supports the following BTC wallet:

  • OKX Bitcoin

  • Unisat

  • Tomo Bitcoin

  • OneKey Bitcoin

  • Bitget Bitcoin

  • Keystone Bitcoin

  • imToken Mobile Wallet

  • Binance Web3 Wallet

It is very easy to sort the wallets supported, and you can sort the wallet list, enable or disable it by the following config, customized wallet could be added to the list as well:

<TomoContextProvider
  indexWallets={[
    'bitcoin_okx',
    'bitcoin_unisat',
    'bitcoin_tomo',
    'bitcoin_onekey',
    'bitcoin_bitget',
    'bitcoin_keystone',
    'bitcoin_imtoken',
    'bitcoin_binance',
    'xyz'
  ]}
  additionalWallets={[
    {
      id: 'xyz',
      name: 'XYZ BTC Wallet',
      chainType: 'bitcoin',
      connectProvider: XYZWallet,
      type: 'extension',
      img: 'https://your wallet logo.svg'
    }
  ]}
  >
</TomoContextProvider>

When wallet is selected, connection hints will pop up, and the content of the hints could be customized by option 'connectionHints', it could be used as below:

<TomoContextProvider
  connectionHints={[
    {
      text: 'Subject to Developer’s compliance with the terms and conditions of this Agreement',
      logo: (
        <img className={'tm-size-5'} src={'https://tomo.inc/favicon.ico'} />
      )
    },
    {
      text: 'I certify that there are no Bitcoin inscriptions tokens in my wallet.'
    },
    {
      isRequired: true,
      text: (
        <span>
          I certify that I have read and accept the updated{' '}
          <a className={'tm-text-primary'}>Terms of Use</a> and{' '}
          <a className={'tm-text-primary'}>Privacy Policy</a>.
        </span>
      )
    }
  ]}
>
  {children}
</TomoContextProvider>

If you want to support your own Bitcoin wallet into the wallet list, there are two ways:

Submit Wallet PR

Submit a PR to https://github.com/UnyxTech/tomo-wallet-provider

The example PR can be found here

Implement Your BTC Provider

import {
  Network,
  WalletInfo,
  WalletProvider
} from '@tomo-inc/tomo-wallet-provider'
import { parseUnits } from '@tomo-inc/tomo-wallet-provider'

export const xyzProvider = 'xyz'

export class XYZWallet extends WalletProvider {
  private xyzWalletInfo: WalletInfo | undefined
  private bitcoinNetworkProvider: any

  constructor() {
    super()
    if (!window[xyzProvider]) {
      throw new Error('XYZ Wallet extension not found')
    }

    this.bitcoinNetworkProvider = window[xyzProvider]
  }

  connectWallet = async (): Promise<this> => {
    const workingVersion = '1.0.0'
    if (!this.bitcoinNetworkProvider) {
      throw new Error('XYZ Wallet extension not found')
    }
    if (this.bitcoinNetworkProvider.getVersion) {
      const version = await this.bitcoinNetworkProvider.getVersion()
      if (version < workingVersion) {
        throw new Error('Please update XYZ Wallet to the latest version')
      }
    }

    let addresses = null
    let pubKey = null
    try {
      addresses = await this.bitcoinNetworkProvider.connectWallet()
      pubKey = await this.bitcoinNetworkProvider.getPublicKey()
      if (!addresses || addresses.length === 0 || !pubKey) {
        throw new Error('BTC is not enabled in XYZ Wallet')
      }
    } catch (error) {
      throw new Error('BTC is not enabled in XYZ Wallet')
    }
    
    this.xyzWalletInfo = {
      publicKeyHex: pubKey,
      address: addresses[0]
    }
    return this
  }

  getWalletProviderName = async (): Promise<string> => {
    return 'XYZ'
  }

  getAddress = async (): Promise<string> => {
    return (await this.bitcoinNetworkProvider.getAccounts())[0]
  }

  getPublicKeyHex = async (): Promise<string> => {
    if (!this.xyzWalletInfo) {
      throw new Error('XYZ Wallet not connected')
    }
    return this.xyzWalletInfo.publicKeyHex
  }

  signPsbt = async (psbtHex: string): Promise<string> => {
    if (!this.xyzWalletInfo) {
      throw new Error('XYZ Wallet not connected')
    }
    return await this.bitcoinNetworkProvider.signPsbt(psbtHex)
  }

  signPsbts = async (psbtsHexes: string[]): Promise<string[]> => {
    if (!this.xyzWalletInfo) {
      throw new Error('XYZ Wallet not connected')
    }
    return await this.bitcoinNetworkProvider.signPsbts(psbtsHexes)
  }

  signMessageBIP322 = async (message: string): Promise<string> => {
    if (!this.xyzWalletInfo) {
      throw new Error('XYZ Wallet not connected')
    }
    return await this.bitcoinNetworkProvider.signMessage(
      message,
      'bip322-simple'
    )
  }

  getNetwork = async (): Promise<Network> => {
    return await this.bitcoinNetworkProvider.getNetwork()
  }

  on = (eventName: string, callBack: () => void) => {
    return this.bitcoinNetworkProvider.on(eventName, callBack)
  }

  off = (eventName: string, callBack: () => void) => {
    return this.bitcoinNetworkProvider.off(eventName, callBack)
  }

  getBalance = async (): Promise<number> => {
    const result = await this.bitcoinNetworkProvider.getBalance()
    return result
  }

  pushTx = async (txHex: string): Promise<string> => {
    return await this.bitcoinNetworkProvider.pushTx(txHex)
  }

  async switchNetwork(network: Network) {
    return await this.bitcoinNetworkProvider.switchNetwork(network)
  }

  async sendBitcoin(to: string, satAmount: number) {
    const result = await this.bitcoinNetworkProvider.sendBitcoin(
      to,
      Number(parseUnits(satAmount.toString(), 8))
    )
    return result
  }
}

Then you can use it in the wallet context provider

<TomoContextProvider
  additionalWallets={[
    {
      id: 'xyz',
      name: 'XYZ BTC Wallet',
      chainType: 'bitcoin',
      connectProvider: XYZWallet,
      type: 'extension',
      img: 'https://your wallet logo.svg'
    }
  ]}
  // optional
  uiOptions={{
    termsAndServiceUrl: 'https://your wallet terms',
    privacyPolicyUrl: 'https://your wallet privacy'
  }}
>
</TomoContextProvider>

Besides, users can also customize their own wallet provider in an easier way. The WalletProvider class provides default implementations for most interfaces. What users need to do is compare the interfaces of the target wallet with the default implementations of WalletProvider. In an ideal scenario, a customized wallet only needs to implement the constructor and two abstract methods (getWalletProviderName and connectWallet).

import {
  Network,
  WalletInfo,
  WalletProvider
} from '@tomo-inc/tomo-wallet-provider'
import { parseUnits } from '@tomo-inc/tomo-wallet-provider'

export const xyzProvider = 'xyz'

export class XYZWallet extends WalletProvider {
  private xyzWalletInfo: WalletInfo | undefined
  private bitcoinNetworkProvider: any

  constructor() {
    super()
    if (!window[xyzProvider]) {
      throw new Error('XYZ Wallet extension not found')
    }

    this.bitcoinNetworkProvider = window[xyzProvider]
  }
  
  connectWallet = async (): Promise<this> => {
    const workingVersion = '1.0.0'
    if (!this.bitcoinNetworkProvider) {
      throw new Error('XYZ Wallet extension not found')
    }
    if (this.bitcoinNetworkProvider.getVersion) {
      const version = await this.bitcoinNetworkProvider.getVersion()
      if (version < workingVersion) {
        throw new Error('Please update XYZ Wallet to the latest version')
      }
    }

    let addresses = null
    let pubKey = null
    try {
      addresses = await this.bitcoinNetworkProvider.connectWallet()
      pubKey = await this.bitcoinNetworkProvider.getPublicKey()
      if (!addresses || addresses.length === 0 || !pubKey) {
        throw new Error('BTC is not enabled in XYZ Wallet')
      }
    } catch (error) {
      throw new Error('BTC is not enabled in XYZ Wallet')
    }
    
    return this
  }

  getWalletProviderName = async (): Promise<string> => {
    return 'XYZ'
  }
}

Multiple Connection Mode

Activating the 'multiNetworkConnection' feature switches to multi-network mode. In this mode, wallets from various networks are accessible through separate tabs, allowing you to select and establish connections with them. Once connected, providers facilitate interactions with these wallets across different networks. Currently, support is extended to both Bitcoin and Cosmos networks.

<TomoContextProvider
  multiNetworkConnection={true}
  cosmosChains={[
    {
      id: 2,
      name: 'Cosmos',
      type: 'cosmos',
      network: 'cosmoshub-4'
    }
  ]}
  ...
>
  <ChildComponent />
</TomoContextProvider>

// after connected, providers could be used
export function ChildComponent(props: ChildProps) {
  const providers = useTomoProviders()

  const signCosmos = async (address: string, amount: string) => {
    const curChainId = providers.cosmosProvider.getChainId()
    const key = await providers.cosmosProvider.provider.getKey(curChainId)  

    // construct your signDoc
    ...
    
    const { signed, signature } = await providers.cosmosProvider.signAmino(
      key.bech32Address,
      signDoc
    )
  }

  return (
    <div>
      <TomoSocial />
    </div>
  )
}

Last updated