diff --git a/landing/siwe.react.js b/landing/siwe.react.js
index 0bc2ed01c..19575b529 100644
--- a/landing/siwe.react.js
+++ b/landing/siwe.react.js
@@ -1,203 +1,206 @@
// @flow
import {
useConnectModal,
RainbowKitProvider,
darkTheme,
useModalState,
connectorsForWallets,
} from '@rainbow-me/rainbowkit';
import '@rainbow-me/rainbowkit/styles.css';
import {
injectedWallet,
rainbowWallet,
metaMaskWallet,
walletConnectWallet,
// eslint-disable-next-line import/extensions
} from '@rainbow-me/rainbowkit/wallets';
import invariant from 'invariant';
import _merge from 'lodash/fp/merge.js';
import * as React from 'react';
import { useAccount, useSigner, WagmiConfig } from 'wagmi';
import ConnectedWalletInfo from 'lib/components/connected-wallet-info.react.js';
import type { SIWEWebViewMessage } from 'lib/types/siwe-types.js';
import {
getSIWEStatementForPublicKey,
- siweStatementWithoutPublicKey,
siweMessageSigningExplanationStatements,
createSIWEMessage,
} from 'lib/utils/siwe-utils.js';
import {
WagmiENSCacheProvider,
configureWagmiChains,
createWagmiClient,
} from 'lib/utils/wagmi-utils.js';
import { SIWEContext } from './siwe-context.js';
import css from './siwe.css';
const { chains, provider } = configureWagmiChains(process.env.COMM_ALCHEMY_KEY);
const connectors = connectorsForWallets([
{
groupName: 'Recommended',
wallets: [
injectedWallet({ chains }),
rainbowWallet({ chains }),
metaMaskWallet({ chains }),
walletConnectWallet({ chains }),
],
},
]);
const wagmiClient = createWagmiClient({ connectors, provider });
function postMessageToNativeWebView(message: SIWEWebViewMessage) {
window.ReactNativeWebView?.postMessage?.(JSON.stringify(message));
}
async function signInWithEthereum(
address: string,
signer,
nonce: string,
statement: string,
) {
invariant(nonce, 'nonce must be present in signInWithEthereum');
const message = createSIWEMessage(address, statement, nonce);
const signature = await signer.signMessage(message);
postMessageToNativeWebView({
type: 'siwe_success',
address,
message,
signature,
});
}
function SIWE(): React.Node {
const { address } = useAccount();
const { data: signer } = useSigner();
const { siweNonce, siwePrimaryIdentityPublicKey } =
React.useContext(SIWEContext);
const onClick = React.useCallback(() => {
invariant(siweNonce, 'nonce must be present during SIWE attempt');
- const statement = siwePrimaryIdentityPublicKey
- ? getSIWEStatementForPublicKey(siwePrimaryIdentityPublicKey)
- : siweStatementWithoutPublicKey;
+ invariant(
+ siwePrimaryIdentityPublicKey,
+ 'primaryIdentityPublicKey must be present during SIWE attempt',
+ );
+ const statement = getSIWEStatementForPublicKey(
+ siwePrimaryIdentityPublicKey,
+ );
signInWithEthereum(address, signer, siweNonce, statement);
}, [address, signer, siweNonce, siwePrimaryIdentityPublicKey]);
const { openConnectModal } = useConnectModal();
const hasNonce = siweNonce !== null && siweNonce !== undefined;
React.useEffect(() => {
if (hasNonce && openConnectModal) {
openConnectModal();
}
}, [hasNonce, openConnectModal]);
const prevConnectModalOpen = React.useRef(false);
const modalState = useModalState();
const closeTimeoutRef = React.useRef();
const { connectModalOpen } = modalState;
React.useEffect(() => {
if (!connectModalOpen && prevConnectModalOpen.current && !signer) {
closeTimeoutRef.current = setTimeout(
() => postMessageToNativeWebView({ type: 'siwe_closed' }),
50,
);
} else if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = undefined;
}
prevConnectModalOpen.current = connectModalOpen;
}, [connectModalOpen, signer]);
const newModalAppeared = React.useCallback(mutationList => {
for (const mutation of mutationList) {
for (const addedNode of mutation.addedNodes) {
if (
addedNode instanceof HTMLElement &&
addedNode.id === 'walletconnect-wrapper'
) {
postMessageToNativeWebView({
type: 'walletconnect_modal_update',
state: 'open',
});
}
}
for (const addedNode of mutation.removedNodes) {
if (
addedNode instanceof HTMLElement &&
addedNode.id === 'walletconnect-wrapper'
) {
postMessageToNativeWebView({
type: 'walletconnect_modal_update',
state: 'closed',
});
}
}
}
}, []);
React.useEffect(() => {
const observer = new MutationObserver(newModalAppeared);
invariant(document.body, 'document.body should be set');
observer.observe(document.body, { childList: true });
return () => {
observer.disconnect();
};
}, [newModalAppeared]);
if (!hasNonce) {
return (
Unable to proceed: nonce not found.
);
} else if (!signer) {
return null;
} else {
return (
Wallet Connected
{siweMessageSigningExplanationStatements}
By signing up, you agree to our{' '}
Terms of Use &{' '}
Privacy Policy.
Sign in using this wallet
);
}
}
function SIWEWrapper(): React.Node {
const theme = React.useMemo(() => {
return _merge(darkTheme())({
radii: {
modal: 0,
modalMobile: 0,
},
colors: {
modalBackdrop: '#242529',
},
});
}, []);
return (
);
}
export default SIWEWrapper;
diff --git a/lib/utils/siwe-utils.js b/lib/utils/siwe-utils.js
index 5e41037a4..ec538c34b 100644
--- a/lib/utils/siwe-utils.js
+++ b/lib/utils/siwe-utils.js
@@ -1,124 +1,122 @@
// @flow
import invariant from 'invariant';
import { SiweMessage } from 'siwe';
import { isDev } from './dev-utils.js';
import type { SIWEMessage } from '../types/siwe-types.js';
const siweNonceRegex: RegExp = /^[a-zA-Z0-9]{17}$/;
function isValidSIWENonce(candidate: string): boolean {
return siweNonceRegex.test(candidate);
}
const ethereumAddressRegex: RegExp = /^0x[a-fA-F0-9]{40}$/;
function isValidEthereumAddress(candidate: string): boolean {
return ethereumAddressRegex.test(candidate);
}
const primaryIdentityPublicKeyRegex: RegExp = /^[a-zA-Z0-9+/]{43}$/;
function isValidPrimaryIdentityPublicKey(candidate: string): boolean {
return primaryIdentityPublicKeyRegex.test(candidate);
}
-const siweStatementWithoutPublicKey: string =
+const siweStatementLegalAgreement: string =
'By continuing, I accept the Comm Terms of Service: https://comm.app/terms';
function createSIWEMessage(
address: string,
statement: string,
nonce: string,
): string {
invariant(nonce, 'nonce must be present in createSiweMessage');
const domain = window.location.host;
const origin = window.location.origin;
const message = new SiweMessage({
domain,
address,
statement,
uri: origin,
version: '1',
chainId: '1',
nonce,
});
return message.prepareMessage();
}
function isValidSIWEDomain(candidate: string): boolean {
return isDev
? candidate === 'localhost:3000'
: candidate === 'comm.app' || candidate === 'web.comm.app';
}
function isValidSIWEURI(candidate: string): boolean {
return isDev
? candidate === 'http://localhost:3000'
: candidate === 'https://comm.app' || candidate === 'https://web.comm.app';
}
// Verify that the SIWEMessage is a well formed Comm SIWE Auth message.
function isValidSIWEMessage(candidate: SIWEMessage): boolean {
return (
- (candidate.statement === siweStatementWithoutPublicKey ||
- (candidate.statement !== null &&
- candidate.statement !== undefined &&
- isValidSIWEStatementWithPublicKey(candidate.statement))) &&
+ candidate.statement !== null &&
+ candidate.statement !== undefined &&
+ isValidSIWEStatementWithPublicKey(candidate.statement) &&
candidate.version === '1' &&
candidate.chainId === 1 &&
isValidSIWEDomain(candidate.domain) &&
isValidSIWEURI(candidate.uri) &&
isValidSIWENonce(candidate.nonce) &&
isValidEthereumAddress(candidate.address)
);
}
function getSIWEStatementForPublicKey(publicKey: string): string {
invariant(
isValidPrimaryIdentityPublicKey(publicKey),
'publicKey must be well formed in getSIWEStatementForPublicKey',
);
- return `Device IdPubKey: ${publicKey} ${siweStatementWithoutPublicKey}`;
+ return `Device IdPubKey: ${publicKey} ${siweStatementLegalAgreement}`;
}
const siweStatementWithPublicKeyRegex =
/^Device IdPubKey: [a-zA-Z0-9+/]{43} By continuing, I accept the Comm Terms of Service: https:\/\/comm.app\/terms$/;
function isValidSIWEStatementWithPublicKey(candidate: string): boolean {
return siweStatementWithPublicKeyRegex.test(candidate);
}
const publicKeyFromSIWEStatementRegex: RegExp = /[a-zA-Z0-9+/]{43}/;
function getPublicKeyFromSIWEStatement(statement: string): string {
invariant(
isValidSIWEStatementWithPublicKey(statement),
'candidate must be well formed SIWE statement with public key',
);
const publicKeyMatchArray = statement.match(publicKeyFromSIWEStatementRegex);
invariant(
publicKeyMatchArray !== null &&
publicKeyMatchArray !== undefined &&
publicKeyMatchArray.length === 1,
'publicKeyMatchArray should have one and only one element',
);
return publicKeyMatchArray[0];
}
// These are shown in the `SIWE` components on both `landing` and `web`.
const siweMessageSigningExplanationStatements: string =
`To complete the login process, you’ll now be ` +
`asked to sign a message using your wallet. ` +
`This signature will attest that your Ethereum ` +
`identity is represented by your new Comm identity.`;
export {
- siweStatementWithoutPublicKey,
isValidSIWENonce,
isValidEthereumAddress,
primaryIdentityPublicKeyRegex,
isValidPrimaryIdentityPublicKey,
createSIWEMessage,
isValidSIWEMessage,
getSIWEStatementForPublicKey,
isValidSIWEStatementWithPublicKey,
getPublicKeyFromSIWEStatement,
siweMessageSigningExplanationStatements,
};