feat:re-verify (#243)

* feat: implements verify context badge

* feat: implements verify cases in example dapp - valid/invalid/unknown

* fix: styled props

---------

Co-authored-by: Gancho Radkov <ganchoradkov@gmail.com>
This commit is contained in:
Gancho Radkov 2023-07-18 17:12:25 +03:00 committed by GitHub
parent 65a3a7bef1
commit 2143840806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 184 additions and 48 deletions

View File

@ -0,0 +1,56 @@
import * as React from "react";
import { ORIGIN_OPTIONS } from "../constants/default";
import styled from "styled-components";
interface OriginSimulationProps {
origin: string;
show: boolean;
}
const SelectContainer = styled.select`
width: 150px;
background: transparent;
color: black;
height: 30px;
border-radius: 4px;
padding: 2px;
font-size: "1.25em";
bottom: 40px;
left: 50px;
direction: ltr;
unicode-bidi: embed;
margin: 5px;
`;
const SelectOption = styled.option`
font-size: "1.25em";
`;
const OriginSimulationDropdown = (props: OriginSimulationProps) => {
const { origin, show } = props;
const setOrigin = React.useCallback((origin: string) => {
localStorage.setItem("wallet_connect_dapp_origin", origin);
location.reload();
}, []);
return (
<div>
{show && (
<SelectContainer
value={origin}
onChange={(e) => setOrigin(e?.target?.value)}
>
<option disabled>Origin Url:</option>
{ORIGIN_OPTIONS.map((e, i) => {
return (
<SelectOption key={i} value={e.value}>
{e.label}
</SelectOption>
);
})}
</SelectContainer>
)}
</div>
);
};
export default OriginSimulationDropdown;

View File

@ -4,9 +4,10 @@ import styled from "styled-components";
import Icon from "./Icon"; import Icon from "./Icon";
import { useState } from "react"; import { useState } from "react";
interface DropdownProps { interface RelayRegionDropdownProps {
relayerRegion: string; relayerRegion: string;
setRelayerRegion?: (relayer: string) => void; setRelayerRegion?: (relayer: string) => void;
show: boolean;
} }
const SelectContainer = styled.select` const SelectContainer = styled.select`
@ -17,47 +18,27 @@ const SelectContainer = styled.select`
border-radius: 4px; border-radius: 4px;
padding: 2px; padding: 2px;
font-size: "1.25em"; font-size: "1.25em";
position: absolute;
bottom: 40px; bottom: 40px;
left: 50px; left: 50px;
direction: ltr; direction: ltr;
unicode-bidi: embed; unicode-bidi: embed;
margin: 5px;
`; `;
const SelectOption = styled.option` const SelectOption = styled.option`
font-size: "1.25em"; font-size: "1.25em";
`; `;
const Dropdown = (props: DropdownProps) => { const RelayRegionDropdown = (props: RelayRegionDropdownProps) => {
const { relayerRegion, setRelayerRegion } = props; const { relayerRegion, setRelayerRegion, show } = props;
const [openSelect, setOpenSelect] = useState(false);
const selectRef = React.createRef();
const openDropdown = () => {
setOpenSelect(!openSelect);
};
return ( return (
<div <div>
style={{ {show && (
paddingTop: 72,
width: 250,
display: "flex",
justifyContent: "center",
alignItems: "flex-end",
}}
>
<button onClick={openDropdown} style={{ background: "transparent" }}>
<Icon size={30} src={"/assets/settings.svg"} />
</button>
{openSelect && (
<SelectContainer <SelectContainer
value={relayerRegion} value={relayerRegion}
onChange={(e) => setRelayerRegion?.(e?.target?.value)} onChange={(e) => setRelayerRegion?.(e?.target?.value)}
> >
<option selected disabled> <option disabled>Relayer Region:</option>
Relayer Region:
</option>
{REGIONALIZED_RELAYER_ENDPOINTS.map((e, i) => { {REGIONALIZED_RELAYER_ENDPOINTS.map((e, i) => {
return ( return (
<SelectOption key={i} value={e.value}> <SelectOption key={i} value={e.value}>
@ -71,4 +52,4 @@ const Dropdown = (props: DropdownProps) => {
); );
}; };
export default Dropdown; export default RelayRegionDropdown;

View File

@ -53,6 +53,13 @@ export const SToggleContainer = styled.div`
} }
`; `;
export const SDropDownContainer = styled.div`
padding-top: 20px;
display: flex;
justify-content: center;
align-items: flex-end;
`;
export const SFullWidthContainer = styled.div` export const SFullWidthContainer = styled.div`
width: 100%; width: 100%;
display: flex; display: flex;

View File

@ -1,3 +1,5 @@
import { getAppMetadata } from "@walletconnect/utils";
if (!process.env.NEXT_PUBLIC_PROJECT_ID) if (!process.env.NEXT_PUBLIC_PROJECT_ID)
throw new Error("`NEXT_PUBLIC_PROJECT_ID` env variable is missing."); throw new Error("`NEXT_PUBLIC_PROJECT_ID` env variable is missing.");
@ -48,6 +50,7 @@ export const DEFAULT_APP_METADATA = {
description: "React App for WalletConnect", description: "React App for WalletConnect",
url: "https://walletconnect.com/", url: "https://walletconnect.com/",
icons: ["https://avatars.githubusercontent.com/u/37784886"], icons: ["https://avatars.githubusercontent.com/u/37784886"],
verifyUrl: "https://verify.walletconnect.com",
}; };
/** /**
@ -185,3 +188,18 @@ export const REGIONALIZED_RELAYER_ENDPOINTS: RelayerType[] = [
label: "Asia Pacific", label: "Asia Pacific",
}, },
]; ];
export const ORIGIN_OPTIONS = [
{
value: getAppMetadata().url,
label: "VALID",
},
{
value: "https://invalid.origin",
label: "INVALID",
},
{
value: "unknown",
label: "UNKNOWN",
},
];

View File

@ -46,6 +46,7 @@ interface IContext {
isFetchingBalances: boolean; isFetchingBalances: boolean;
setChains: any; setChains: any;
setRelayerRegion: any; setRelayerRegion: any;
origin: string;
} }
/** /**
@ -86,7 +87,7 @@ export function ClientContextProvider({
const [relayerRegion, setRelayerRegion] = useState<string>( const [relayerRegion, setRelayerRegion] = useState<string>(
DEFAULT_RELAY_URL! DEFAULT_RELAY_URL!
); );
const [origin, setOrigin] = useState<string>(getAppMetadata().url);
const reset = () => { const reset = () => {
setSession(undefined); setSession(undefined);
setBalances({}); setBalances({});
@ -284,15 +285,24 @@ export function ClientContextProvider({
const createClient = useCallback(async () => { const createClient = useCallback(async () => {
try { try {
setIsInitializing(true); setIsInitializing(true);
const claimedOrigin =
localStorage.getItem("wallet_connect_dapp_origin") || origin;
const _client = await Client.init({ const _client = await Client.init({
logger: DEFAULT_LOGGER, logger: DEFAULT_LOGGER,
relayUrl: relayerRegion, relayUrl: relayerRegion,
projectId: DEFAULT_PROJECT_ID, projectId: DEFAULT_PROJECT_ID,
metadata: getAppMetadata() || DEFAULT_APP_METADATA, metadata: {
...(getAppMetadata() || DEFAULT_APP_METADATA),
url: claimedOrigin,
verifyUrl:
claimedOrigin === "unknown"
? "http://non-existent-url"
: DEFAULT_APP_METADATA.verifyUrl, // simulates `UNKNOWN` verify context
},
}); });
setClient(_client); setClient(_client);
setOrigin(_client.metadata.url);
prevRelayerValue.current = relayerRegion; prevRelayerValue.current = relayerRegion;
await _subscribeToEvents(_client); await _subscribeToEvents(_client);
await _checkPersistedState(_client); await _checkPersistedState(_client);
@ -302,7 +312,13 @@ export function ClientContextProvider({
} finally { } finally {
setIsInitializing(false); setIsInitializing(false);
} }
}, [_checkPersistedState, _subscribeToEvents, _logClientId, relayerRegion]); }, [
_checkPersistedState,
_subscribeToEvents,
_logClientId,
relayerRegion,
origin,
]);
useEffect(() => { useEffect(() => {
if (!client) { if (!client) {
@ -329,6 +345,7 @@ export function ClientContextProvider({
disconnect, disconnect,
setChains, setChains,
setRelayerRegion, setRelayerRegion,
origin,
}), }),
[ [
pairings, pairings,
@ -345,6 +362,7 @@ export function ClientContextProvider({
disconnect, disconnect,
setChains, setChains,
setRelayerRegion, setRelayerRegion,
origin,
] ]
); );

View File

@ -4,7 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
import Banner from "../components/Banner"; import Banner from "../components/Banner";
import Blockchain from "../components/Blockchain"; import Blockchain from "../components/Blockchain";
import Column from "../components/Column"; import Column from "../components/Column";
import Dropdown from "../components/Dropdown"; import RelayRegionDropdown from "../components/RelayRegionDropdown";
import Header from "../components/Header"; import Header from "../components/Header";
import Modal from "../components/Modal"; import Modal from "../components/Modal";
import { import {
@ -32,6 +32,7 @@ import {
SButtonContainer, SButtonContainer,
SConnectButton, SConnectButton,
SContent, SContent,
SDropDownContainer,
SLanding, SLanding,
SLayout, SLayout,
SToggleContainer, SToggleContainer,
@ -39,6 +40,8 @@ import {
import { useWalletConnectClient } from "../contexts/ClientContext"; import { useWalletConnectClient } from "../contexts/ClientContext";
import { useJsonRpc } from "../contexts/JsonRpcContext"; import { useJsonRpc } from "../contexts/JsonRpcContext";
import { useChainData } from "../contexts/ChainDataContext"; import { useChainData } from "../contexts/ChainDataContext";
import Icon from "../components/Icon";
import OriginSimulationDropdown from "../components/OriginSimulationDropdown";
// Normal import does not work here // Normal import does not work here
const { version } = require("@walletconnect/sign-client/package.json"); const { version } = require("@walletconnect/sign-client/package.json");
@ -66,6 +69,7 @@ const Home: NextPage = () => {
isInitializing, isInitializing,
setChains, setChains,
setRelayerRegion, setRelayerRegion,
origin,
} = useWalletConnectClient(); } = useWalletConnectClient();
// Use `JsonRpcContext` to provide us with relevant RPC methods and states. // Use `JsonRpcContext` to provide us with relevant RPC methods and states.
@ -444,6 +448,12 @@ const Home: NextPage = () => {
} }
}; };
const [openSelect, setOpenSelect] = useState(false);
const openDropdown = () => {
setOpenSelect(!openSelect);
};
const renderContent = () => { const renderContent = () => {
const chainOptions = isTestnet ? DEFAULT_TEST_CHAINS : DEFAULT_MAIN_CHAINS; const chainOptions = isTestnet ? DEFAULT_TEST_CHAINS : DEFAULT_MAIN_CHAINS;
@ -469,10 +479,17 @@ const Home: NextPage = () => {
<SConnectButton left onClick={onConnect} disabled={!chains.length}> <SConnectButton left onClick={onConnect} disabled={!chains.length}>
Connect Connect
</SConnectButton> </SConnectButton>
<Dropdown <SDropDownContainer>
relayerRegion={relayerRegion} <RelayRegionDropdown
setRelayerRegion={setRelayerRegion} relayerRegion={relayerRegion}
/> setRelayerRegion={setRelayerRegion}
show={openSelect}
/>
<OriginSimulationDropdown origin={origin} show={openSelect} />
</SDropDownContainer>
<button onClick={openDropdown} style={{ background: "transparent" }}>
<Icon size={30} src={"/assets/settings.svg"} />
</button>
</SButtonContainer> </SButtonContainer>
</SLanding> </SLanding>
) : ( ) : (

View File

@ -1,3 +1,8 @@
import { useMemo } from 'react'
import { useSnapshot } from 'valtio'
import SettingsStore from '@/store/SettingsStore'
import { getVerifyStatus } from '@/utils/HelperUtil'
import { Avatar, Col, Link, Row, Text } from '@nextui-org/react' import { Avatar, Col, Link, Row, Text } from '@nextui-org/react'
import { SignClientTypes } from '@walletconnect/types' import { SignClientTypes } from '@walletconnect/types'
@ -12,17 +17,26 @@ interface IProps {
* Components * Components
*/ */
export default function ProjectInfoCard({ metadata }: IProps) { export default function ProjectInfoCard({ metadata }: IProps) {
const { currentRequestVerifyContext } = useSnapshot(SettingsStore.state)
const { icons, name, url } = metadata const { icons, name, url } = metadata
const validation = useMemo(
() => getVerifyStatus(currentRequestVerifyContext),
[currentRequestVerifyContext]
)
return ( return (
<Row align="center"> <>
<Col span={3}> <Row align="center">
<Avatar src={icons[0]} /> <Col span={3}>
</Col> <Avatar src={icons[0]} />
<Col span={14}> </Col>
<Text h5>{name}</Text> <Col span={15}>
<Link href={url}>{url}</Link> <Text h5>{name}</Text>
</Col> <Row>
</Row> <Link href={url}>{url}</Link>
<Text style={{ marginLeft: '5px' }}>{validation}</Text>
</Row>
</Col>
</Row>
</>
) )
} }

View File

@ -5,6 +5,8 @@ import { POLKADOT_SIGNING_METHODS } from '@/data/PolkadotData'
import { MULTIVERSX_SIGNING_METHODS } from '@/data/MultiversxData' import { MULTIVERSX_SIGNING_METHODS } from '@/data/MultiversxData'
import { TRON_SIGNING_METHODS } from '@/data/TronData' import { TRON_SIGNING_METHODS } from '@/data/TronData'
import ModalStore from '@/store/ModalStore' import ModalStore from '@/store/ModalStore'
import SettingsStore from '@/store/SettingsStore'
import { useSnapshot } from 'valtio'
import { signClient } from '@/utils/WalletConnectUtil' import { signClient } from '@/utils/WalletConnectUtil'
import { SignClientTypes } from '@walletconnect/types' import { SignClientTypes } from '@walletconnect/types'
import { useCallback, useEffect } from 'react' import { useCallback, useEffect } from 'react'
@ -19,6 +21,8 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
*****************************************************************************/ *****************************************************************************/
const onSessionProposal = useCallback( const onSessionProposal = useCallback(
(proposal: SignClientTypes.EventArguments['session_proposal']) => { (proposal: SignClientTypes.EventArguments['session_proposal']) => {
// set the verify context so it can be displayed in the projectInfoCard
SettingsStore.setCurrentRequestVerifyContext(proposal.verifyContext)
ModalStore.open('SessionProposalModal', { proposal }) ModalStore.open('SessionProposalModal', { proposal })
}, },
[] []
@ -30,9 +34,11 @@ export default function useWalletConnectEventsManager(initialized: boolean) {
const onSessionRequest = useCallback( const onSessionRequest = useCallback(
async (requestEvent: SignClientTypes.EventArguments['session_request']) => { async (requestEvent: SignClientTypes.EventArguments['session_request']) => {
console.log('session_request', requestEvent) console.log('session_request', requestEvent)
const { topic, params } = requestEvent const { topic, params, verifyContext } = requestEvent
const { request } = params const { request } = params
const requestSession = signClient.session.get(topic) const requestSession = signClient.session.get(topic)
// set the verify context so it can be displayed in the projectInfoCard
SettingsStore.setCurrentRequestVerifyContext(verifyContext)
switch (request.method) { switch (request.method) {
case EIP155_SIGNING_METHODS.ETH_SIGN: case EIP155_SIGNING_METHODS.ETH_SIGN:

View File

@ -1,3 +1,4 @@
import { Verify } from '@walletconnect/types'
import { proxy } from 'valtio' import { proxy } from 'valtio'
/** /**
@ -17,6 +18,7 @@ interface State {
kadenaAddress: string kadenaAddress: string
relayerRegionURL: string relayerRegionURL: string
activeChainId: string activeChainId: string
currentRequestVerifyContext?: Verify.Context
} }
/** /**
@ -89,6 +91,10 @@ const SettingsStore = {
state.activeChainId = value state.activeChainId = value
}, },
setCurrentRequestVerifyContext(context: Verify.Context) {
state.currentRequestVerifyContext = context
},
toggleTestNets() { toggleTestNets() {
state.testNets = !state.testNets state.testNets = !state.testNets
if (state.testNets) { if (state.testNets) {

View File

@ -9,6 +9,7 @@ import { TRON_CHAINS, TTronChain } from '@/data/TronData'
import { KADENA_CHAINS, TKadenaChain } from '@/data/KadenaData' import { KADENA_CHAINS, TKadenaChain } from '@/data/KadenaData'
import { utils } from 'ethers' import { utils } from 'ethers'
import { Verify } from '@walletconnect/types'
/** /**
* Truncates string (in the middle) via given lenght value * Truncates string (in the middle) via given lenght value
@ -160,3 +161,15 @@ export function formatChainName(chainId: string) {
chainId chainId
) )
} }
export function getVerifyStatus(context?: Verify.Context) {
if (!context) return ''
switch (context.verified.validation) {
case 'VALID':
return '✅ Verified'
case 'INVALID':
return '❌ Origin does not match'
case 'UNKNOWN':
return '❓ Unknown'
}
}