import * as React from "react" ; import styled from "styled-components" ; import WalletConnect from "@walletconnect/client" ; import QRCodeModal from "@walletconnect/qrcode-modal" ; import { convertUtf8ToHex } from "@walletconnect/utils" ; import { IInternalEvent } from "@walletconnect/types" ; import Button from "./components/Button" ; import Column from "./components/Column" ; import Wrapper from "./components/Wrapper" ; import Modal from "./components/Modal" ; import Header from "./components/Header" ; import Loader from "./components/Loader" ; import { fonts } from "./styles" ; import { apiGetAccountAssets , apiGetGasPrices , apiGetAccountNonce } from "./helpers/api" ; import { sanitizeHex , verifySignature , hashTypedDataMessage , hashMessage , } from "./helpers/utilities" ; import { convertAmountToRawNumber , convertStringToHex } from "./helpers/bignumber" ; import { IAssetData } from "./helpers/types" ; import Banner from "./components/Banner" ; import AccountAssets from "./components/AccountAssets" ; import { eip712 } from "./helpers/eip712" ; const SLayout = styled . div `
  position: relative;
  width: 100%;
  /* height: 100%; */
  min-height: 100vh;
  text-align: center;
` ; const SContent = styled ( Wrapper as any ) `
  width: 100%;
  height: 100%;
  padding: 0 16px;
` ; const SLanding = styled ( Column as any ) `
  height: 600px;
` ; const SButtonContainer = styled ( Column as any ) `
  width: 250px;
  margin: 50px 0;
` ; const SConnectButton = styled ( Button as any ) `
  border-radius: 8px;
  font-size: ${ fonts . size . medium };
  height: 44px;
  width: 100%;
  margin: 12px 0;
` ; const SContainer = styled . div `
  height: 100%;
  min-height: 200px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  word-break: break-word;
` ; const SModalContainer = styled . div `
  width: 100%;
  position: relative;
  word-wrap: break-word;
` ; const SModalTitle = styled . div `
  margin: 1em 0;
  font-size: 20px;
  font-weight: 700;
` ; const SModalParagraph = styled . p `
  margin-top: 30px;
` ; const SBalances = styled ( SLanding as any ) `
  height: 100%;
  & h3 {
    padding-top: 30px;
  }
` ; const STable = styled ( SContainer as any ) `
  flex-direction: column;
  text-align: left;
` ; const SRow = styled . div `
  width: 100%;
  display: flex;
  margin: 6px 0;
` ; const SKey = styled . div `
  width: 30%;
  font-weight: 700;
` ; const SValue = styled . div `
  width: 70%;
  font-family: monospace;
` ; const STestButtonContainer = styled . div `
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
` ; const STestButton = styled ( Button as any ) `
  border-radius: 8px;
  font-size: ${ fonts . size . medium };
  height: 44px;
  width: 100%;
  max-width: 175px;
  margin: 12px;
` ; interface IAppState { connector : WalletConnect | null ; fetching : boolean ; connected : boolean ; chainId : number ; showModal : boolean ; pendingRequest : boolean ; uri : string ; accounts : string [ ] ; address : string ; result : any | null ; assets : IAssetData [ ] ; } const INITIAL_STATE = { connector : null , fetching : false , connected : false , chainId : 1 , showModal : false , pendingRequest : false , uri : "" , accounts : [ ] , address : "" , result : null , assets : [ ] , } ; class App extends React . Component < any , any > { public state : IAppState = { ... INITIAL_STATE , } ; public connect = async ( ) => { const bridge = "https://bridge.walletconnect.org" ; const connector = new WalletConnect ( { bridge , qrcodeModal : QRCodeModal } ) ; await this . setState ( { connector } ) ; if ( ! connector . connected ) { await connector . createSession ( ) ; } await this . subscribeToEvents ( ) ; } ; public subscribeToEvents = ( ) => { const { connector } = this . state ; if ( ! connector ) { return ; } connector . on ( "session_update" , async ( error , payload ) => { console . log ( `connector.on("session_update")` ) ; if ( error ) { throw error ; } const { chainId , accounts } = payload . params [ 0 ] ; this . onSessionUpdate ( accounts , chainId ) ; } ) ; connector . on ( "connect" , ( error , payload ) => { console . log ( `connector.on("connect")` ) ; if ( error ) { throw error ; } this . onConnect ( payload ) ; } ) ; connector . on ( "disconnect" , ( error , payload ) => { console . log ( `connector.on("disconnect")` ) ; if ( error ) { throw error ; } this . onDisconnect ( ) ; } ) ; if ( connector . connected ) { const { chainId , accounts } = connector ; const address = accounts [ 0 ] ; this . setState ( { connected : true , chainId , accounts , address , } ) ; this . onSessionUpdate ( accounts , chainId ) ; } this . setState ( { connector } ) ; } ; public killSession = async ( ) => { const { connector } = this . state ; if ( connector ) { connector . killSession ( ) ; } this . resetApp ( ) ; } ; public resetApp = async ( ) => { await this . setState ( { ... INITIAL_STATE } ) ; } ; public onConnect = async ( payload ) => { const { chainId , accounts } = payload . params [ 0 ] ; const address = accounts [ 0 ] ; await this . setState ( { connected : true , chainId , accounts , address , } ) ; this . getAccountAssets ( ) ; } ; public onDisconnect = async ( ) => { this . resetApp ( ) ; } ; public onSessionUpdate = async ( accounts , chainId ) => { const address = accounts [ 0 ] ; await this . setState ( { chainId , accounts , address } ) ; await this . getAccountAssets ( ) ; } ; public getAccountAssets = async ( ) => { const { address , chainId } = this . state ; this . setState ( { fetching : true } ) ; try { const assets = await apiGetAccountAssets ( address , chainId ) ; await this . setState ( { fetching : false , address , assets } ) ; } catch ( error ) { console . error ( error ) ; await this . setState ( { fetching : false } ) ; } } ; public toggleModal = ( ) => this . setState ( { showModal : ! this . state . showModal } ) ; public testSendTransaction = async ( ) => { const { connector , address , chainId } = this . state ; if ( ! connector ) { return ; } const from = address ; const to = address ; const _nonce = await apiGetAccountNonce ( address , chainId ) ; const nonce = sanitizeHex ( convertStringToHex ( _nonce ) ) ; const gasPrices = await apiGetGasPrices ( ) ; const _gasPrice = gasPrices . slow . price ; const gasPrice = sanitizeHex ( convertStringToHex ( convertAmountToRawNumber ( _gasPrice , 9 ) ) ) ; const _gasLimit = 21000 ; const gasLimit = sanitizeHex ( convertStringToHex ( _gasLimit ) ) ; const _value = 0 ; const value = sanitizeHex ( convertStringToHex ( _value ) ) ; const data = "0x" ; const tx = { from , to , nonce , gasPrice , gasLimit , value , data , } ; try { this . toggleModal ( ) ; this . setState ( { pendingRequest : true } ) ; const result = await connector . sendTransaction ( tx ) ; const formattedResult = { method : "eth_sendTransaction" , txHash : result , from : address , to : address , value : "0 ETH" , } ; this . setState ( { connector , pendingRequest : false , result : formattedResult || null , } ) ; } catch ( error ) { console . error ( error ) ; this . setState ( { connector , pendingRequest : false , result : null } ) ; } } ; public testSignMessage = async ( ) => { const { connector , address , chainId } = this . state ; if ( ! connector ) { return ; } const message = `My email is john@doe.com - ${ new Date ( ) . toUTCString ( ) }` ; const hexMsg = convertUtf8ToHex ( message ) ; const msgParams = [ address , hexMsg ] ; try { this . toggleModal ( ) ; this . setState ( { pendingRequest : true } ) ; const result = await connector . signMessage ( msgParams ) ; const hash = hashMessage ( message ) ; const valid = await verifySignature ( address , result , hash , chainId ) ; const formattedResult = { method : "eth_sign" , address , valid , result , } ; this . setState ( { connector , pendingRequest : false , result : formattedResult || null , } ) ; } catch ( error ) { console . error ( error ) ; this . setState ( { connector , pendingRequest : false , result : null } ) ; } } ; public testSignTypedData = async ( ) => { const { connector , address , chainId } = this . state ; if ( ! connector ) { return ; } const message = JSON . stringify ( eip712 . example ) ; const msgParams = [ address , message ] ; try { this . toggleModal ( ) ; this . setState ( { pendingRequest : true } ) ; const result = await connector . signTypedData ( msgParams ) ; const hash = hashTypedDataMessage ( message ) ; const valid = await verifySignature ( address , result , hash , chainId ) ; const formattedResult = { method : "eth_signTypedData" , address , valid , result , } ; this . setState ( { connector , pendingRequest : false , result : formattedResult || null , } ) ; } catch ( error ) { console . error ( error ) ; this . setState ( { connector , pendingRequest : false , result : null } ) ; } } ; public render = ( ) => { const { assets , address , connected , chainId , fetching , showModal , pendingRequest , result , } = this . state ; return ( < SLayout > < Column maxWidth = { 1000 } spanHeight > < Header connected = { connected } address = { address } chainId = { chainId } killSession = { this . killSession } / > < SContent > { ! address && ! assets . length ? ( < SLanding center > < h3 > { `Try out WalletConnect` } < br /> < span > { `v${ process . env . REACT_APP_VERSION }` } < /span> < /h3> < SButtonContainer > < SConnectButton left onClick = { this . connect } fetching = { fetching } > { "Connect to WalletConnect" } < /SConnectButton> < /SButtonContainer> < /SLanding>) : (<SBalances> < Banner / > < h3 > Actions < /h3> < Column center > < STestButtonContainer > < STestButton left onClick = { this . testSendTransaction } > { "eth_sendTransaction" } < /STestButton> < STestButton left onClick = { this . testSignMessage } > { "eth_sign" } < /STestButton> < STestButton left onClick = { this . testSignTypedData } > { "eth_signTypedData" } < /STestButton> < /STestButtonContainer> < /Column> < h3 > Balances < /h3> { ! fetching ? ( < AccountAssets chainId = { chainId } assets = { assets } / > ) : ( < Column center > < SContainer > < Loader /> < /SContainer> < /Column>)} < /SBalances>)} < /SContent> < /Column> < Modal show = { showModal } toggleModal = { this . toggleModal } > { pendingRequest ? ( < SModalContainer > < SModalTitle > { "Pending Call Request" } < /SModalTitle> < SContainer > < Loader /> < SModalParagraph > { "Approve or reject request using your wallet" } < /SModalParagraph> < /SContainer> < /SModalContainer>) : result ? (<SModalContainer> < SModalTitle > { "Call Request Approved" } < /SModalTitle> < STable > { Object . keys ( result ) . map ( key => ( < SRow key = { key } > < SKey > { key } < /SKey> < SValue > { result [ key ] . toString ( ) } < /SValue> < /SRow>))} < /STable> < /SModalContainer>) : (<SModalContainer> < SModalTitle > { "Call Request Rejected" } < /SModalTitle> < /SModalContainer>)} < /Modal> < /SLayout>); } ; } export default App ;