import { sleep } from '@celo/base' ; import { CeloTx } from '@celo/connect' ; import { RemoteWallet } from '@celo/wallet-remote' ; import WalletConnect , { CLIENT_EVENTS } from '@walletconnect/client' ; import { ClientOptions , ClientTypes , PairingTypes , SessionTypes , } from '@walletconnect/types/dist/cjs' ; import { ERROR } from '@walletconnect/utils' ; import debugConfig from 'debug' ; import { PROJECT_ID , RELAY_URL } from './constants' ; import { SupportedMethods , WalletConnectWalletOptions } from './types' ; import { parseAddress } from './utils' ; import { WalletConnectSigner } from './wc-signer' ; const debug = debugConfig ( 'kit:wallet:wallet-connect-wallet' ) ; /**
 * Session establishment happens out of band so after somehow
 * communicating the connection URI (often via QR code) we can
 * continue with the setup process
 */ async function waitForTruthy ( getValue , attempts = 10 ) { let waitDuration = 500 ; for ( let i = 0 ; i < attempts ; i ++ ) { if ( getValue ( ) ) { return ; } await sleep ( waitDuration ) ; waitDuration = waitDuration * 1.5 ; } throw new Error ( 'Unable to get pairing session, did you lose internet connection?' ) ; } const defaultInitOptions = { relayUrl : RELAY_URL , projectId : PROJECT_ID , logger : 'error' , } ; const defaultConnectOptions = { metadata : { name : 'ContractKit' , description : "Celo's ContractKit is a library to help developers and validators to interact with the celo-blockchain." , url : 'https://github.com/celo-org/celo-monorepo/tree/master/packages/sdk/contractkit' , icons : [ ] , } , permissions : { blockchain : { chains : [ 'eip155:44787' , 'eip155:42220' , 'eip155:62320' , ] , } , jsonrpc : { methods : Object . values ( SupportedMethods ) , } , } , } ; export class WalletConnectWallet extends RemoteWallet < WalletConnectSigner > { private initOptions : ClientOptions ; private connectOptions : ClientTypes . ConnectParams ; private client ? : WalletConnect ; private pairing ? : PairingTypes . Settled ; private pairingProposal ? : PairingTypes . Proposal ; private session ? : SessionTypes . Settled ; constructor ( { init , connect } ) { super ( ) ; this . initOptions = { ... defaultInitOptions , ... init } ; this . connectOptions = { ... defaultConnectOptions , ... connect } ; } /**
     * Pulled out to allow mocking
     */ private getWalletConnectClient ( ) { return WalletConnect . init ( this . initOptions ) ; } /**
     * Get the URI needed for out of band session establishment
     */ public async getUri ( ) { this . client = await this . getWalletConnectClient ( ) ; if ( this . client . session ?. values . length > 0 && this . client . pairing ?. values . length > 0 ) { this . pairing = this . client . pairing . values [ 0 ] ; this . session = this . client . session . values [ 0 ] ; return ; } this . client . on ( CLIENT_EVENTS . session . proposal , this . onSessionProposal ) ; this . client . on ( CLIENT_EVENTS . session . created , this . onSessionCreated ) ; this . client . on ( CLIENT_EVENTS . session . updated , this . onSessionUpdated ) ; this . client . on ( CLIENT_EVENTS . session . deleted , this . onSessionDeleted ) ; this . client . on ( CLIENT_EVENTS . pairing . proposal , this . onPairingProposal ) ; this . client . on ( CLIENT_EVENTS . pairing . created , this . onPairingCreated ) ; this . client . on ( CLIENT_EVENTS . pairing . updated , this . onPairingUpdated ) ; this . client . on ( CLIENT_EVENTS . pairing . deleted , this . onPairingDeleted ) ; this . client . connect ( this . connectOptions ) ?. catch ( ( e ) => { console . error ( `WalletConnect connection failed: ${ e . message }` ) ; } ) ; await waitForTruthy ( ( ) => ! ! this . pairingProposal ) ; return this . pairingProposal ! . signal . params . uri ; } onSessionProposal = ( sessionProposal ) => { debug ( 'onSessionProposal' , sessionProposal ) ; } ; onSessionCreated = ( session ) => { debug ( 'onSessionCreated' , session ) ; this . session = session ; } ; onSessionUpdated = ( session ) => { debug ( 'onSessionUpdated' , session ) ; } ; onSessionDeleted = ( ) => { debug ( 'onSessionDeleted' ) ; this . session = undefined ; } ; onPairingProposal = ( pairingProposal ) => { debug ( 'onPairingProposal' , pairingProposal ) ; this . pairingProposal = pairingProposal ; } ; onPairingCreated = ( pairing ) => { debug ( 'onPairingCreated' , pairing ) ; this . pairing = pairing ; } ; onPairingUpdated = ( pairing ) => { debug ( 'onPairingUpdated' , pairing ) ; if ( ! this . pairing ) { debug ( 'Attempted to update non existant pairing' , pairing ) ; return ; } this . pairing . state . metadata = pairing . state . metadata ; } ; onPairingDeleted = ( ) => { debug ( 'onPairingDeleted' ) ; this . pairing = undefined ; } ; async loadAccountSigners ( ) { /**
         * Session establishment happens out of band so after somehow
         * communicating the connection URI (often via QR code) we can
         * continue with the setup process
         */ await waitForTruthy ( ( ) => ! ! this . session ) ; const addressToSigner = new Map < string , WalletConnectSigner > ( ) ; this . session ! . state . accounts . forEach ( ( addressLike ) => { const { address , networkId } = parseAddress ( addressLike ) ; const signer = new WalletConnectSigner ( this . client ! , this . session ! , address , networkId ) ; addressToSigner . set ( address , signer ) ; } ) ; return addressToSigner ; } param txParams dev async signTransaction ( txParams ) { const fromAddress = txParams . from ! . toString ( ) ; const signer = this . getSigner ( fromAddress ) ; return signer . signRawTransaction ( txParams ) ; } close = async ( ) => { if ( ! this . client ) { throw new Error ( 'Wallet must be initialized before calling close()' ) ; } const reason = ERROR . USER_DISCONNECTED . format ( ) ; if ( this . session ) { await this . client . disconnect ( { topic : this . session . topic , reason , } ) ; } await this . client . pairing . delete ( { topic : this . pairing ! . topic , reason } ) ; } ; }