diff --git a/landing/siwe.react.js b/landing/siwe.react.js
index 2b4c72c8a..f6a34736b 100644
--- a/landing/siwe.react.js
+++ b/landing/siwe.react.js
@@ -1,192 +1,168 @@
// @flow
import {
useConnectModal,
getDefaultWallets,
RainbowKitProvider,
darkTheme,
useModalState,
ConnectButton,
} from '@rainbow-me/rainbowkit';
import invariant from 'invariant';
import _merge from 'lodash/fp/merge';
import * as React from 'react';
import '@rainbow-me/rainbowkit/dist/index.css';
-import {
- useAccount,
- useSigner,
- chain,
- configureChains,
- createClient,
- WagmiConfig,
-} from 'wagmi';
-import { alchemyProvider } from 'wagmi/providers/alchemy';
-import { publicProvider } from 'wagmi/providers/public';
+import { useAccount, useSigner, WagmiConfig } from 'wagmi';
import type { SIWEWebViewMessage } from 'lib/types/siwe-types';
import {
getSIWEStatementForPublicKey,
siweStatementWithoutPublicKey,
siweMessageSigningExplanationStatements,
createSIWEMessage,
} from 'lib/utils/siwe-utils.js';
+import { configureWagmiChains, createWagmiClient } from 'lib/utils/wagmi-utils';
import { SIWEContext } from './siwe-context.js';
import css from './siwe.css';
-// details can be found https://0.6.x.wagmi.sh/docs/providers/configuring-chains
-const availableProviders = process.env.COMM_ALCHEMY_KEY
- ? [alchemyProvider({ apiKey: process.env.COMM_ALCHEMY_KEY })]
- : [publicProvider()];
-const { chains, provider } = configureChains(
- [chain.mainnet],
- availableProviders,
-);
-
-const { connectors } = getDefaultWallets({
- appName: 'comm',
- chains,
-});
-
-const wagmiClient = createClient({
- autoConnect: true,
- connectors,
- provider,
-});
+const { chains, provider } = configureWagmiChains(process.env.COMM_ALCHEMY_KEY);
+const { connectors } = getDefaultWallets({ appName: 'comm', 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;
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 { connectModalOpen } = modalState;
React.useEffect(() => {
if (!connectModalOpen && prevConnectModalOpen.current && !signer) {
postMessageToNativeWebView({ type: 'siwe_closed' });
}
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[0]}
{siweMessageSigningExplanationStatements[1]}
Sign in
);
}
}
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/package.json b/lib/package.json
index 744eff150..e27fa6006 100644
--- a/lib/package.json
+++ b/lib/package.json
@@ -1,59 +1,60 @@
{
"name": "lib",
"version": "0.0.1",
"type": "module",
"private": true,
"license": "BSD-3-Clause",
"scripts": {
"clean": "rm -rf node_modules/",
"test": "jest"
},
"devDependencies": {
"@babel/core": "^7.13.14",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
"@babel/plugin-proposal-object-rest-spread": "^7.13.8",
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.12",
"@babel/preset-flow": "^7.13.13",
"@babel/preset-react": "^7.13.13",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"babel-jest": "^26.6.3",
"flow-bin": "^0.182.0",
"flow-typed": "^3.2.1",
"react-refresh": "^0.14.0"
},
"dependencies": {
"dateformat": "^3.0.3",
"emoji-regex": "^9.2.0",
"eth-ens-namehash": "^2.0.8",
"ethers": "^5.7.2",
"fast-json-stable-stringify": "^2.0.0",
"file-type": "^12.3.0",
"invariant": "^2.2.4",
"just-clone": "^3.2.1",
"lodash": "^4.17.21",
"react": "18.1.0",
"react-redux": "^7.1.1",
"reselect": "^4.0.0",
"reselect-map": "^1.0.5",
"simple-markdown": "^0.7.2",
"string-hash": "^1.1.3",
"tcomb": "^3.2.29",
"siwe": "^1.1.6",
"tinycolor2": "^1.4.1",
"tokenize-text": "^1.1.3",
"url-parse-lax": "^3.0.0",
"util-inspect": "^0.1.8",
- "utils-copy-error": "^1.0.1"
+ "utils-copy-error": "^1.0.1",
+ "wagmi": "^0.6.0"
},
"jest": {
"transform": {
"\\.js$": "babel-jest"
},
"transformIgnorePatterns": [
"/node_modules/(?!@babel/runtime)"
]
}
}
diff --git a/lib/utils/wagmi-utils.js b/lib/utils/wagmi-utils.js
new file mode 100644
index 000000000..8effa0a0d
--- /dev/null
+++ b/lib/utils/wagmi-utils.js
@@ -0,0 +1,37 @@
+// @flow
+
+import { chain, configureChains, createClient } from 'wagmi';
+import { alchemyProvider } from 'wagmi/providers/alchemy';
+import { publicProvider } from 'wagmi/providers/public';
+
+// details can be found https://0.6.x.wagmi.sh/docs/providers/configuring-chains
+
+type WagmiChainConfiguration = {
+ +chains: mixed,
+ +provider: mixed,
+};
+function configureWagmiChains(alchemyKey: ?string): WagmiChainConfiguration {
+ const availableProviders = alchemyKey
+ ? [alchemyProvider({ apiKey: alchemyKey })]
+ : [publicProvider()];
+ const { chains, provider } = configureChains(
+ [chain.mainnet],
+ availableProviders,
+ );
+ return { chains, provider };
+}
+
+type WagmiClientInput = {
+ +provider: mixed,
+ +connectors?: mixed,
+};
+function createWagmiClient(input: WagmiClientInput): mixed {
+ const { provider, connectors } = input;
+ return createClient({
+ autoConnect: true,
+ connectors,
+ provider,
+ });
+}
+
+export { configureWagmiChains, createWagmiClient };
diff --git a/web/account/siwe.react.js b/web/account/siwe.react.js
index 2313388cc..c87b7a4f3 100644
--- a/web/account/siwe.react.js
+++ b/web/account/siwe.react.js
@@ -1,226 +1,202 @@
// @flow
import '@rainbow-me/rainbowkit/dist/index.css';
import {
ConnectButton,
darkTheme,
getDefaultWallets,
RainbowKitProvider,
useConnectModal,
} from '@rainbow-me/rainbowkit';
import invariant from 'invariant';
import _merge from 'lodash/fp/merge';
import * as React from 'react';
import { FaEthereum } from 'react-icons/fa';
-import {
- chain,
- configureChains,
- createClient,
- useAccount,
- useSigner,
- WagmiConfig,
-} from 'wagmi';
-import { alchemyProvider } from 'wagmi/providers/alchemy';
-import { publicProvider } from 'wagmi/providers/public';
+import { useAccount, useSigner, WagmiConfig } from 'wagmi';
import {
getSIWENonce,
getSIWENonceActionTypes,
siweAuth,
siweAuthActionTypes,
} from 'lib/actions/siwe-actions';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors';
import type { LogInStartingPayload } from 'lib/types/account-types.js';
import {
useDispatchActionPromise,
useServerCall,
} from 'lib/utils/action-utils';
import {
createSIWEMessage,
siweMessageSigningExplanationStatements,
siweStatementWithoutPublicKey,
} from 'lib/utils/siwe-utils.js';
+import { configureWagmiChains, createWagmiClient } from 'lib/utils/wagmi-utils';
import Button from '../components/button.react';
import LoadingIndicator from '../loading-indicator.react';
import { useSelector } from '../redux/redux-utils';
import { webLogInExtraInfoSelector } from '../selectors/account-selectors.js';
import css from './siwe.css';
-// details can be found https://0.6.x.wagmi.sh/docs/providers/configuring-chains
-const availableProviders = process.env.COMM_ALCHEMY_KEY
- ? [alchemyProvider({ apiKey: process.env.COMM_ALCHEMY_KEY })]
- : [publicProvider()];
-const { chains, provider } = configureChains(
- [chain.mainnet],
- availableProviders,
-);
-
-const { connectors } = getDefaultWallets({
- appName: 'comm',
- chains,
-});
-
-const wagmiClient = createClient({
- autoConnect: true,
- connectors,
- provider,
-});
+const { chains, provider } = configureWagmiChains(process.env.COMM_ALCHEMY_KEY);
+const { connectors } = getDefaultWallets({ appName: 'comm', chains });
+const wagmiClient = createWagmiClient({ connectors, provider });
const getSIWENonceLoadingStatusSelector = createLoadingStatusSelector(
getSIWENonceActionTypes,
);
function SIWE(): React.Node {
const { openConnectModal } = useConnectModal();
const { address } = useAccount();
const { data: signer } = useSigner();
const dispatchActionPromise = useDispatchActionPromise();
const getSIWENonceCall = useServerCall(getSIWENonce);
const getSIWENonceCallLoadingStatus = useSelector(
getSIWENonceLoadingStatusSelector,
);
const siweAuthCall = useServerCall(siweAuth);
const logInExtraInfo = useSelector(webLogInExtraInfoSelector);
const [siweNonce, setSIWENonce] = React.useState(null);
const [
hasSIWEButtonBeenClicked,
setHasSIWEButtonBeenClicked,
] = React.useState(false);
const siweNonceShouldBeFetched =
!siweNonce &&
getSIWENonceCallLoadingStatus !== 'loading' &&
(signer || hasSIWEButtonBeenClicked);
React.useEffect(() => {
if (!siweNonceShouldBeFetched) {
return;
}
dispatchActionPromise(
getSIWENonceActionTypes,
(async () => {
const response = await getSIWENonceCall();
setSIWENonce(response);
})(),
);
}, [dispatchActionPromise, getSIWENonceCall, siweNonceShouldBeFetched]);
const siweButtonColor = React.useMemo(
() => ({ backgroundColor: 'white', color: 'black' }),
[],
);
const callSIWEAuthEndpoint = React.useCallback(
(message: string, signature: string, extraInfo) =>
siweAuthCall({
message,
signature,
...extraInfo,
}),
[siweAuthCall],
);
const attemptSIWEAuth = React.useCallback(
(message: string, signature: string) => {
const extraInfo = logInExtraInfo();
dispatchActionPromise(
siweAuthActionTypes,
callSIWEAuthEndpoint(message, signature, extraInfo),
undefined,
({ calendarQuery: extraInfo.calendarQuery }: LogInStartingPayload),
);
},
[callSIWEAuthEndpoint, dispatchActionPromise, logInExtraInfo],
);
const onSignInButtonClick = React.useCallback(async () => {
invariant(siweNonce, 'nonce must be present during SIWE attempt');
const message = createSIWEMessage(
address,
siweStatementWithoutPublicKey,
siweNonce,
);
const signature = await signer.signMessage(message);
attemptSIWEAuth(message, signature);
}, [address, attemptSIWEAuth, signer, siweNonce]);
let siweLoginForm;
if (signer && !siweNonce) {
siweLoginForm = (
);
} else if (signer) {
siweLoginForm = (
{siweMessageSigningExplanationStatements[0]}
{siweMessageSigningExplanationStatements[1]}
);
}
const onSIWEButtonClick = React.useCallback(() => {
setHasSIWEButtonBeenClicked(true);
openConnectModal && openConnectModal();
}, [openConnectModal]);
let siweButton;
if (openConnectModal) {
siweButton = (
<>
>
);
}
return (
{siweLoginForm}
{siweButton}
);
}
function SIWEWrapper(): React.Node {
const theme = React.useMemo(() => {
return _merge(darkTheme())({
radii: {
modal: 0,
modalMobile: 0,
},
colors: {
modalBackdrop: '#242529',
},
});
}, []);
return (
);
}
export default SIWEWrapper;