import * as React from "react" ; import gql from "graphql-tag" ; import { Mutation , MutationFunc } from "react-apollo" ; import { Civil } from "@joincivil/core" ; import { EthSignedMessage , EthAddress } from "@joincivil/typescript-types" ; import { Transaction , TransactionButtonNoModal , MetaMaskLogoButton , ManagerSectionHeading , MetaMaskModal , ModalHeading , metaMaskSignImgUrl , } from "../../" ; import { CivilContext , ICivilContext } from "../../context" ; export interface AccountEthAuthProps { civil ? : Civil ; buttonText ? : string ; buttonOnly ? : boolean ; onAuthenticated ? ( address ) : void ; } export interface AccountEthAuthState { errorMessage ? : string ; isWaitingSignatureOpen ? : boolean ; isSignRejectionOpen ? : boolean ; } const setEthAddressMutation = gql `
  mutation($input: UserSignatureInput!) {
    userSetEthAddress(input: $input)
  }
` ; const userEthAddressQuery = gql `
  query {
    currentUser {
      ethAddress
    }
  }
` ; export class AccountEthAuth extends React . Component < AccountEthAuthProps , AccountEthAuthState > { public static contextType = CivilContext ; public context ! : ICivilContext ; private _isMounted ? : boolean ; constructor ( props ) { super ( props ) ; this . state = { } ; } public render ( ) { if ( this . props . buttonOnly ) { return this . renderTransactionUI ( ) ; } return ( < > < ManagerSectionHeading > Log into Civil with your crypto wallet < /ManagerSectionHeading> < p > Almost there ! To set up your Civil account , you need to authenticate your account with a signature . This is similar to signing in with a password . It verifies your account with your crypto wallet . < /p> < div > < p > MetaMask will open a new window , and will require you to sign a message . < /p> { this . renderTransactionUI ( ) } < img src = { metaMaskSignImgUrl } /> < /div> < />); } public componentDidMount ( ) { this . _isMounted = true ; } public componentWillUnmount ( ) { this . _isMounted = false ; } private renderTransactionUI ( ) { return ( < Mutation < any > mutation = { setEthAddressMutation } update = { ( cache , { data : { userSetEthAddress } } ) => { cache . writeQuery ( { query : userEthAddressQuery , data : { currentUser : { ethAddress : userSetEthAddress , __typename : "User" , } , } , } ) ; } } > { userSetEthAddress => ( < > < TransactionButtonNoModal transactions = { this . signTransactions ( userSetEthAddress ) } Button = { props => { return ( < MetaMaskLogoButton onClick = { props . onClick } > { this . props . buttonText || "Open MetaMask" } < /MetaMaskLogoButton>); } } /> { this . renderWaitingSignModal ( ) } { this . renderSignRejectionModal ( userSetEthAddress ) } { this . renderSaveErrorModal ( ) } < />)} < /Mutation>); } private renderWaitingSignModal ( ) { if ( ! this . state . isWaitingSignatureOpen ) { return null ; } return ( < MetaMaskModal waiting = { true } signing = { true } cancelTransaction = { this . cancelTransaction } > < ModalHeading > Please sign the text in MetaMask to authenticate < /ModalHeading> < /MetaMaskModal>); } private renderSignRejectionModal ( userSetEthAddress ) { if ( ! this . state . isSignRejectionOpen ) { return null ; } return ( < MetaMaskModal waiting = { false } denied = { true } denialText = "To authenticate that you own your wallet address, you need to sign the message in your MetaMask wallet." cancelTransaction = { this . cancelTransaction } restartTransactions = { this . signTransactions ( userSetEthAddress ) } > < ModalHeading > Failed to authenticate your wallet address < /ModalHeading> < /MetaMaskModal>); } private renderSaveErrorModal ( ) { if ( ! this . state . errorMessage ) { return null ; } return ( < MetaMaskModal alert = { true } bodyText = { `Something went wrong when authenticating and saving your wallet address (${ this . state . errorMessage }). Please try again later.` } cancelTransaction = { this . cancelTransaction } > < ModalHeading > Failed to save your wallet address < /ModalHeading> < /MetaMaskModal>); } private signTransactions = ( userSetEthAddress ) : Transaction [ ] => { let { civil } = this . context ; if ( this . props . civil ) { civil = this . props . civil ; } return [ { transaction : async ( ) : Promise < EthSignedMessage > => { this . setState ( { isWaitingSignatureOpen : true , isSignRejectionOpen : false , errorMessage : undefined } ) ; const message = "I control this address @ " + new Date ( ) . toISOString ( ) ; return civil ! . signMessage ( message ) ; } , postTransaction : async ( sig ) : Promise < void > => { try { delete sig . rawMessage ; const res = await userSetEthAddress ( { variables : { input : sig , } , } ) ; if ( res && res . data && res . data . userSetEthAddress ) { if ( this . props . onAuthenticated ) { this . props . onAuthenticated ( sig . signer ) ; } if ( this . _isMounted ) { this . setState ( { isWaitingSignatureOpen : false } ) ; } } else { console . error ( "Failed to validate and save ETH address. Response:" , res ) ; throw Error ( "Failed to validate and save ETH address" ) ; } } catch ( err ) { this . setState ( { isWaitingSignatureOpen : false , errorMessage : err , } ) ; } } , handleTransactionError : ( err ) => { this . setState ( { isWaitingSignatureOpen : false } ) ; if ( err . message . indexOf ( "Error: MetaMask Message Signature: User denied message signature." ) !== - 1 ) { this . setState ( { isSignRejectionOpen : true } ) ; } else { console . error ( "Transaction failed:" , err ) ; this . setState ( { errorMessage : "Transaction failed: " + err . message , } ) ; } } , } , ] ; } ; private cancelTransaction = ( ) => { this . setState ( { isWaitingSignatureOpen : false , isSignRejectionOpen : false , errorMessage : undefined , } ) ; } ; }