Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wallet connect #195

Merged
merged 13 commits into from
Jun 27, 2023
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
"@talismn/connect-wallets": "^1.2.3",
"@tanstack/react-query": "^4.24.10",
"@tanstack/react-table": "^8.7.9",
"@walletconnect/modal": "^2.4.7",
"@walletconnect/universal-provider": "^2.8.1",
"big.js": "^6.2.1",
"bn.js": "^5.2.1",
"bs58": "^5.0.0",
Expand Down Expand Up @@ -105,4 +107,4 @@
"npm": "please-use-yarn",
"yarn": ">=1.22.19"
}
}
}
98 changes: 49 additions & 49 deletions src/GlobalStateProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,86 @@
import { WalletAccount, getWalletBySource } from '@talismn/connect-wallets';
import { createContext } from 'preact';
import { StateUpdater, useCallback, useContext, useEffect, useMemo, useState } from 'preact/compat';
import { useLocation } from 'react-router-dom';
import { useCallback, useContext, useEffect, useMemo } from 'preact/compat';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { config } from './config';
import { storageKeys } from './constants/localStorage';
import { useLocalStorage } from './hooks/useLocalStorage';
import { TenantName } from './models/Tenant';
import { ThemeName } from './models/Theme';
import { storageService } from './services/storage/local';

export interface GlobalStateValues {
export interface GlobalState {
dAppName: string;
tenantName: TenantName;
tenantRPC?: string;
wallet?: WalletAccount;
}

export interface GlobalState {
state: Partial<GlobalStateValues>;
setState: StateUpdater<Partial<GlobalStateValues>>;
walletAccount: WalletAccount | undefined;
walletAccount?: WalletAccount;
setWalletAccount: (data: WalletAccount) => void;
removeWalletAccount: () => void;
getThemeName: () => ThemeName;
dAppName: string;
}

export const defaultState: GlobalStateValues = {
tenantName: TenantName.Amplitude,
tenantRPC: undefined,
};
export const defaultTenant = TenantName.Pendulum;

const GlobalStateContext = createContext<GlobalState | undefined>(undefined);

const GlobalStateProvider = ({
children,
value = defaultState,
}: {
children: ReactNode;
value?: Partial<GlobalStateValues>;
}) => {
const { pathname } = useLocation();
const [state, setState] = useState(() => {
if (value) return value;
if (pathname) {
const GlobalStateProvider = ({ children }: { children: ReactNode }) => {
const params = useParams();
const [walletAccount, setWallet] = useState<WalletAccount | undefined>(undefined);
//const { pathname } = useLocation();

const tenantName = useMemo(() => {
return params.network && Object.values<string>(TenantName).includes(params.network)
? (params.network as TenantName)
: defaultTenant;
/* if (pathname) {
const [network] = pathname.split('/').filter(Boolean);
const tenantName = Object.values<string>(TenantName).includes(network)
? (network as TenantName)
: TenantName.Pendulum;

if (tenantName) {
return {
tenantName,
tenantRPC: config.tenants[tenantName].rpc,
};
}
}
return defaultState;
});
const dAppName = state.tenantName || TenantName.Amplitude;
} */
}, [params.network]);

const dAppName = tenantName;

const getThemeName = useCallback(
() => (state.tenantName ? config.tenants[state.tenantName]?.theme || ThemeName.Amplitude : ThemeName.Amplitude),
[state?.tenantName],
() => (tenantName ? config.tenants[tenantName]?.theme || ThemeName.Amplitude : ThemeName.Amplitude),
[tenantName],
);

const {
state: account,
set,
clear,
} = useLocalStorage<string | undefined>({
key: `${storageKeys.ACCOUNT}-${state.tenantName}`,
key: `${storageKeys.ACCOUNT}-${tenantName}`,
});

const removeWalletAccount = useCallback(() => {
clear();
setWallet(undefined);
}, [clear]);

const setWalletAccount = useCallback(
(wallet: WalletAccount | undefined) => {
set(wallet?.address);
setWallet(wallet);
},
[set],
);

useEffect(() => {
// ! TODO: do this also for wallet connect (https://github.com/WalletConnect/web-examples/blob/3c8ebfe96af617697916f99dcc8a3ab843970d1c/dapps/react-dapp-v2-with-web3js/src/contexts/ClientContext.tsx#L311)
const run = async () => {
storageService.removeExpired();
if (!account) return;
if (!account) removeWalletAccount();
const name = storageService.get('@talisman-connect/selected-wallet-name');
if (!name) return;
const wallet = getWalletBySource(name);
Expand All @@ -83,28 +89,22 @@ const GlobalStateProvider = ({
await wallet.enable(dAppName || TenantName.Amplitude);
const selectedWallet = (await wallet.getAccounts()).find((a) => a.address === account);
if (!selectedWallet) return;
setState((prev) => ({ ...prev, wallet: selectedWallet }));
setWallet(selectedWallet);
};
run();
}, [account, dAppName, state.tenantName]);
}, [account, removeWalletAccount, dAppName]);

const providerValue = useMemo(
const providerValue = useMemo<GlobalState>(
() => ({
state,
setState,
walletAccount: state.wallet,
setWalletAccount: (wallet: WalletAccount | undefined) => {
set(wallet?.address);
setState((prev) => ({ ...prev, wallet }));
},
removeWalletAccount: () => {
clear();
setState((prev) => ({ ...prev, wallet: undefined }));
},
walletAccount,
tenantName: tenantName,
tenantRPC: config.tenants[tenantName].rpc,
setWalletAccount,
removeWalletAccount,
getThemeName,
dAppName,
}),
[clear, dAppName, getThemeName, set, state],
[dAppName, getThemeName, removeWalletAccount, setWalletAccount, tenantName, walletAccount],
);

return <GlobalStateContext.Provider value={providerValue}>{children}</GlobalStateContext.Provider>;
Expand Down
2 changes: 1 addition & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Navigate, Route, Routes } from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import TermsAndConditions from './TermsAndConditions';
import Layout from './components/Layout';
import { defaultPageLoader } from './components/Loader/Page';
import { NotFound } from './components/NotFound';
import { SuspenseLoad } from './components/Suspense';
import { config } from './config';
import TermsAndConditions from './TermsAndConditions';

/**
* Components need to be default exports inside the file for suspense loading to work properly
Expand Down
1 change: 1 addition & 0 deletions src/assets/wallet-connect.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions src/components/ChainSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ const ChainSelector = ({ tenantName }: { tenantName: TenantName | undefined }):
<Button
size="sm"
color="ghost"
className="text-sm border-base-300 bg-base-200 min-h-[2.25rem] h-auto px-2 sm:px-3"
className="text-sm border-base-300 bg-base-200 min-h-[2.1rem] h-auto px-2 sm:px-3"
title={tenantName}
>
{tenantName === TenantName.Pendulum ? (
<PendulumLogo className="w-5 h-6 mr-1" />
<PendulumLogo className="w-4 h-5 mr-1" />
) : (
<AmplitudeLogo className="w-5 h-5 mr-1 " />
<AmplitudeLogo className="w-4 h-4 mr-1 " />
)}
<span className="text-sm mr-1 sm:mr-3">{tenantName ? toTitle(tenantName) : ''}</span>
<ChevronDownIcon className="w-3 h-3" stroke-width="2" />
<span className="text-sm mr-1 sm:mr-2">{tenantName ? toTitle(tenantName) : ''}</span>
<ChevronDownIcon className="w-4 h-4" stroke-width="2" />
</Button>
<Dropdown.Menu className="w-30 mt-1.5 p-1 text-sm border-base-300 border bg-base-200 rounded-xl shadow-none">
{options.map((option, i) => (
Expand Down
4 changes: 2 additions & 2 deletions src/components/Layout/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const CollapseMenu = ({
children: JSX.Element | null;
}) => {
const { pathname } = useLocation();
const { tenantName } = useGlobalState().state;
const { tenantName } = useGlobalState();
const isPendulum = tenantName === TenantName.Pendulum;

const isActive = useMemo(() => {
Expand Down Expand Up @@ -69,7 +69,7 @@ export type NavProps = {
};

const Nav = memo(({ onClick }: NavProps) => {
const { state } = useGlobalState();
const state = useGlobalState();

return (
<nav>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Layout/NetworkId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useNodeInfoState } from '../../NodeInfoProvider';

const NetworkId: FC = memo(() => {
const lastBlockNumber = useNodeInfoState().state.bestNumberFinalize;
const { tenantRPC } = useGlobalState().state;
const { tenantRPC } = useGlobalState();
const encodedRPC = tenantRPC ? encodeURI(tenantRPC) : '';

return (
Expand Down
35 changes: 8 additions & 27 deletions src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Bars3Icon } from '@heroicons/react/20/solid';
import { memo, useEffect, useMemo, useState } from 'preact/compat';
import { Outlet, useParams } from 'react-router-dom';
import { memo, useState } from 'preact/compat';
import { Outlet } from 'react-router-dom';
import { useGlobalState } from '../../GlobalStateProvider';
import AmplitudeLogo from '../../assets/amplitud-logo.svg';
import PendulumLogo from '../../assets/pendulum-logo.png';
import { config } from '../../config';
import { TenantName } from '../../models/Tenant';
import ChainSelector from '../ChainSelector';
import OpenWallet from '../OpenWallet';
import OpenWallet from '../Wallet';
import Nav from './Nav';
import NetworkId from './NetworkId';
import SocialAndTermLinks from './SocialAndTermLinks';
Expand All @@ -16,31 +15,13 @@ import './styles.sass';

export default function Layout(): JSX.Element | null {
const [visible, setVisible] = useState(false);
const params = useParams();
const { state, setState, dAppName } = useGlobalState();
const isPendulum = state.tenantName === TenantName.Pendulum;
const isTestnet = state.tenantName === TenantName.Foucoco;
const { tenantName, dAppName } = useGlobalState();
const isPendulum = tenantName === TenantName.Pendulum;
const isTestnet = tenantName === TenantName.Foucoco;
const sideBarLogo = isPendulum ? PendulumLogo : AmplitudeLogo;
const chevronColor = isPendulum ? 'white' : 'grey ';
const bgColor = isPendulum ? 'bg-white' : 'bg-black';

const network: TenantName = useMemo(() => {
return params.network && Object.values<string>(TenantName).includes(params.network)
? (params.network as TenantName)
: TenantName.Pendulum;
}, [params.network]);

useEffect(() => {
// Only change state if network is different
const n =
network && Object.values<string>(TenantName).includes(network) ? (network as TenantName) : TenantName.Pendulum;
setState((prevState) => ({
...prevState,
tenantName: n,
tenantRPC: config.tenants[n].rpc,
}));
}, [network, setState]);

const FooterLink = memo(() => {
return isPendulum ? (
<span onClick={() => (window.location.href = '/amplitude')}>Amplitude</span>
Expand Down Expand Up @@ -72,7 +53,7 @@ export default function Layout(): JSX.Element | null {
{isTestnet && <div className="foucoco-tag">Foucoco testnet</div>}
<Nav onClick={() => setVisible(false)} />
<div className="sidebar-footer">
<Versions tenantName={state.tenantName} />
<Versions tenantName={tenantName} />
<NetworkId />
<SocialAndTermLinks />
</div>
Expand All @@ -87,7 +68,7 @@ export default function Layout(): JSX.Element | null {
</button>
</div>
<OpenWallet dAppName={dAppName} />
<ChainSelector tenantName={state.tenantName} />
<ChainSelector tenantName={tenantName} />
<div className="dropdown dropdown-end mr-2 hidden">
<button className="flex space-x-2 items-center px-4 py-2 btn no-animation">
<span className={`${isPendulum ? 'text-white' : ''} text-md`}>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Layout/links.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChevronRightIcon } from '@heroicons/react/20/solid';
import { ComponentChildren } from 'preact';
import { HTMLAttributes } from 'preact/compat';
import { GlobalStateValues } from '../../GlobalStateProvider';
import { GlobalState } from '../../GlobalStateProvider';
import BridgeIcon from '../../assets/bridge';
import CollatorsIcon from '../../assets/collators';
import DashboardIcon from '../../assets/dashboard';
Expand All @@ -24,7 +24,7 @@ export type BaseLinkItem = {
export type LinkItem = BaseLinkItem & {
submenu?: BaseLinkItem[];
};
export type Links = (state: Partial<GlobalStateValues>) => LinkItem[];
export type Links = (state: Partial<GlobalState>) => LinkItem[];

const arrow = <ChevronRightIcon className="nav-arrow w-5 h-5" />;

Expand Down
1 change: 0 additions & 1 deletion src/components/Swap/useSwapComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export interface UseSwapComponentProps {
export const defaults = config.swap.defaults;

export const useSwapComponent = ({ from, to, onChange }: UseSwapComponentProps) => {
console.log(from);
const { walletAccount } = useGlobalState();
const {
state: { api },
Expand Down
Loading