Skip to content

Commit

Permalink
feat(wallet): keplr connection
Browse files Browse the repository at this point in the history
  • Loading branch information
samsiegart authored and mergify-bot committed Feb 3, 2022
1 parent 0cfddd2 commit 259345e
Show file tree
Hide file tree
Showing 7 changed files with 1,425 additions and 91 deletions.
2 changes: 2 additions & 0 deletions packages/wallet/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
"@endo/init": "^0.5.33",
"@agoric/notifier": "^0.3.33",
"@agoric/ui-components": "^0.2.28",
"@cosmjs/launchpad": "^0.27.1",
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/icons-material": "^5.1.0",
"@mui/lab": "^5.0.0-alpha.67",
"@mui/material": "^5.1.0",
"@mui/styles": "^5.1.0",
"@testing-library/jest-dom": "^5.11.4",
Expand Down
18 changes: 13 additions & 5 deletions packages/wallet/ui/src/components/AppBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Tooltip from '@mui/material/Tooltip';

import WalletConnection from './WalletConnection';
import NavDrawer from './NavDrawer';
import ChainConnector from './ChainConnector';
import { withApplicationContext } from '../contexts/Application';

const logoUrl =
'https://agoric.com/wp-content/themes/agoric_2021_theme/assets/img/logo.svg';
Expand Down Expand Up @@ -44,7 +46,7 @@ const useStyles = makeStyles(theme => ({
},
}));

const AppBar = () => {
const AppBar = ({ useChainBackend }) => {
const theme = useTheme();
const classes = useStyles(theme);

Expand All @@ -62,6 +64,13 @@ const AppBar = () => {
</a>
</div>
<div className={classes.appBarSection}>
<div className={classes.connector}>
{useChainBackend ? (
<ChainConnector></ChainConnector>
) : (
<WalletConnection></WalletConnection>
)}
</div>
<div className={classes.connector}>
<Tooltip title="Help">
<IconButton
Expand All @@ -74,12 +83,11 @@ const AppBar = () => {
</IconButton>
</Tooltip>
</div>
<div className={classes.connector}>
<WalletConnection></WalletConnection>
</div>
</div>
</header>
);
};

export default AppBar;
export default withApplicationContext(AppBar, context => ({
useChainBackend: context.useChainBackend,
}));
122 changes: 122 additions & 0 deletions packages/wallet/ui/src/components/ChainConnector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable react/display-name */
import React, { useEffect, useState } from 'react';
import clsx from 'clsx';
import { makeStyles } from '@mui/styles';
import LoadingButton from '@mui/lab/LoadingButton';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import DialogTitle from '@mui/material/DialogTitle';
import Dialog from '@mui/material/Dialog';
import { NETWORK_CONFIGS, suggestChain } from '../util/SuggestChain';

const useStyles = makeStyles(_ => ({
hidden: {
display: 'none',
},
connector: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
height: '100%',
},
centeredText: {
textAlign: 'center',
},
dialog: {
minWidth: 240,
},
}));

const ChainConnector = () => {
const classes = useStyles();
const [dialogOpened, setDialogOpened] = useState(false);
const [networkConfig, setNetworkConfig] = useState(null);
const [connectionInProgress, setConnectionInProgress] = useState(false);

const handleClose = () => {
setDialogOpened(false);
};

const selectNetworkConfig = nc => {
setNetworkConfig(nc);
setDialogOpened(false);
};

useEffect(() => {
if (!networkConfig) {
return () => {};
}

let networkChanged = false;
setConnectionInProgress(true);

const connect = async () => {
if (!window.getOfflineSigner || !window.keplr) {
setNetworkConfig(null);
alert('Please install the Keplr extension');
} else if (window.keplr.experimentalSuggestChain) {
try {
const cosmJS = await suggestChain(networkConfig[0]);
if (!networkChanged) {
setConnectionInProgress(false);
window.cosmJS = cosmJS;
}
} catch {
if (!networkChanged) {
setConnectionInProgress(false);
setNetworkConfig(null);
}
}
} else {
setNetworkConfig(null);
alert('Please use the most recent version of the Keplr extension');
}
};
connect();

return () => {
networkChanged = true;
};
}, [networkConfig]);

return (
<>
<div className={clsx('Connector', classes.connector)}>
<LoadingButton
loading={connectionInProgress}
color="primary"
variant="outlined"
onClick={() => setDialogOpened(true)}
>
{networkConfig ? 'Connected' : 'Connect Wallet'}
</LoadingButton>
</div>
<Dialog onClose={handleClose} open={dialogOpened}>
<div className={classes.dialog}>
<DialogTitle className={classes.centeredText}>
Select Network
</DialogTitle>
<List sx={{ pt: 0 }}>
{NETWORK_CONFIGS.map(nc => (
<ListItem
button
selected={nc === networkConfig}
onClick={() => selectNetworkConfig(nc)}
key={nc[0]}
>
<ListItemText
className={classes.centeredText}
primary={nc[1]}
/>
</ListItem>
))}
</List>
</div>
</Dialog>
</>
);
};

export default ChainConnector;
8 changes: 8 additions & 0 deletions packages/wallet/ui/src/contexts/Application.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ const Provider = ({ children }) => {
const [issuers, setIssuers] = useReducer(issuersReducer, null);
const [services, setServices] = useState(null);
const [schemaActions, setSchemaActions] = useState(null);
const [useChainBackend, setUseChainBackend] = useState(false);

useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const onChain = urlParams.get('onchain') === 'true';
setUseChainBackend(onChain);
}, []);

const setBackend = backend => {
setSchemaActions(backend.actions);
Expand Down Expand Up @@ -201,6 +208,7 @@ const Provider = ({ children }) => {
setDeclinedOffers,
closedOffers,
setClosedOffers,
useChainBackend,
};

useDebugLogging(state, [
Expand Down
3 changes: 3 additions & 0 deletions packages/wallet/ui/src/tests/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ jest.mock('../views/Dashboard', () => () => 'Dashboard');
jest.mock('../views/Dapps', () => () => 'Dapps');
jest.mock('../views/Contacts', () => () => 'Contacts');
jest.mock('../views/Issuers', () => () => 'Issuers');
jest.mock('@cosmjs/launchpad', () => () => {
jest.mock();
});

const connectionState = 'connecting';
const withApplicationContext = (Component, _) => ({ ...props }) => {
Expand Down
78 changes: 78 additions & 0 deletions packages/wallet/ui/src/util/SuggestChain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { SigningCosmosClient } from '@cosmjs/launchpad';

export const AGORIC_COIN_TYPE = 564;
export const COSMOS_COIN_TYPE = 118;
export const NETWORK_CONFIGS = [
['https://main.agoric.net/network-config', 'Agoric Mainnet'],
['https://testnet.agoric.net/network-config', 'Agoric Testnet'],
['https://devnet.agoric.net/network-config', 'Agoric Devnet'],
['https://stage.agoric.net/network-config', 'Agoric Stage'],
];

export async function suggestChain(networkConfig, caption = undefined) {
const coinType = Number(
new URL(networkConfig).searchParams.get('coinType') || AGORIC_COIN_TYPE,
);
const res = await fetch(networkConfig);
if (!res.ok) {
throw Error(`Cannot fetch network: ${res.status}`);
}
const { chainName: chainId, rpcAddrs } = await res.json();
const hostname = new URL(networkConfig).hostname;
const network = hostname.split('.')[0];
let rpc;
let api;
if (network !== hostname) {
rpc = `https://${network}.rpc.agoric.net`;
api = `https://${network}.api.agoric.net`;
} else {
rpc = `http://${rpcAddrs[Math.floor(Math.random() * rpcAddrs.length)]}`;
api = rpc.replace(/(:\d+)?$/, ':1317');
}
const stakeCurrency = {
coinDenom: 'BLD',
coinMinimalDenom: 'ubld',
coinDecimals: 6,
coinGeckoId: undefined,
};
const stableCurrency = {
coinDenom: 'RUN',
coinMinimalDenom: 'urun',
coinDecimals: 6,
coinGeckoId: undefined,
};
const chainInfo = {
rpc,
rest: api,
chainId,
chainName: caption || `Agoric ${network}`,
stakeCurrency,
walletUrlForStaking: `https://${network}.staking.agoric.app`,
bip44: {
coinType,
},
bech32Config: {
bech32PrefixAccAddr: 'agoric',
bech32PrefixAccPub: 'agoricpub',
bech32PrefixValAddr: 'agoricvaloper',
bech32PrefixValPub: 'agoricvaloperpub',
bech32PrefixConsAddr: 'agoricvalcons',
bech32PrefixConsPub: 'agoricvalconspub',
},
currencies: [stakeCurrency, stableCurrency],
feeCurrencies: [stableCurrency],
features: ['stargate', 'ibc-transfer'],
};
await window.keplr.experimentalSuggestChain(chainInfo);
await window.keplr.enable(chainId);

const offlineSigner = window.getOfflineSigner(chainId);
const accounts = await offlineSigner.getAccounts();
const cosmJS = new SigningCosmosClient(
'https://node-cosmoshub-3.keplr.app/rest', // TODO: Provide correct rest API
accounts[0].address,
offlineSigner,
);

return cosmJS;
}
Loading

0 comments on commit 259345e

Please sign in to comment.