Skip to content

Commit

Permalink
Merge pull request #4 from ElrondDevGuild/feature/hw-provider
Browse files Browse the repository at this point in the history
hw/ledger provider
  • Loading branch information
juliancwirko authored Jun 19, 2022
2 parents 772debc + 589b99f commit fc34df4
Show file tree
Hide file tree
Showing 20 changed files with 3,481 additions and 147 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
### [1.1.0](https://github.com/ElrondDevGuild/nextjs-dapp-template/releases/tag/v1.1.0) (2022-06-19)
- added HW provider

### [1.0.0](https://github.com/ElrondDevGuild/nextjs-dapp-template/releases/tag/v1.0.0) (2022-05-15)
- initial code
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- [elrond-nextjs-dapp.netlify.com](https://elrond-nextjs-dapp.netlify.com)

Simple alternative to the [dapp-core](https://github.com/ElrondNetwork/dapp-core) with React.
Simple alternative to the [dapp-core](https://github.com/ElrondNetwork/dapp-core) with React. Heavily based on [Elven Tools Dapp](https://www.elven.tools/docs/minter-dapp-introduction.html).

The Dapp is built using Nextjs and a couple of helpful tools. More docs soon!
It has straightforward and complete functionality.
Expand Down Expand Up @@ -54,7 +54,7 @@ const NextJSDappTemplate = ({ Component, pageProps }: AppProps) => {

#### LoginModalButton

The component provides the `Connect` button with the modal, which will contain another three buttons for three different authentication possibilities (Maiar Mobile App, Maiar Defi Wallet - browser extension, Elrond Web Wallet). You should be able to use it in any place on the website.
The component provides the `Connect` button with the modal, which will contain another three buttons for four different authentication possibilities (Maiar Mobile App, Maiar Defi Wallet - browser extension, Elrond Web Wallet). You should be able to use it in any place on the website.

```jsx
import { LoginModalButton } from '../tools/LoginModalButton';
Expand Down Expand Up @@ -234,7 +234,6 @@ On each repository code push, the Netlify services will redeploy the app.
Here are other deployment solutions: [NextJS Deployment](https://nextjs.org/docs/deployment).

### Missing for now:
- Ledger auth
- More docs and examples
- More tooling and components
- tests
Expand Down
26 changes: 24 additions & 2 deletions components/demo/SimpleDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import { networkConfig, chainType } from '../../config/network';
import { ActionButton } from '../tools/ActionButton';
import { useLoginInfo } from '../../hooks/auth/useLoginInfo';
import { LoginMethodsEnum } from '../../types/enums';
import { useAccount } from '../../hooks/auth/useAccount';

export const SimpleDemo = () => {
const [result, setResult] = useState<{ type: string; content: string }>();
const [pending, setPending] = useState(false);
const [error, setError] = useState<string>();
const { loginMethod } = useLoginInfo();
const { loginMethod, loginToken } = useLoginInfo();
const { address } = useAccount();

const handleTxCb = useCallback(
({ transaction, pending, error }: TransactionCb) => {
Expand Down Expand Up @@ -103,7 +105,27 @@ export const SimpleDemo = () => {
Confirm it on the Maiar mobile app and wait till it finishes.
</Box>
)}
<Spinner size="xl" mt={6} color="dappTemplate.color2.darker" />
{loginMethod === LoginMethodsEnum.ledger && (
<>
{address && (
<>
<Box fontWeight="bold">Confirm your address on Ledger:</Box>
<Box>{address}</Box>
</>
)}
{loginToken && (
<>
<Box fontWeight="bold">Confirm the auth token on Ledger:</Box>
<Box>{`${loginToken}{}`}</Box>
</>
)}
<Box>
Then wait some time to finish the transaction. You will get the
transaction hash and link at the end.
</Box>
</>
)}
<Spinner mt={6} color="dappTemplate.color2.darker" />
</FlexCardWrapper>
)}
{result?.type && (
Expand Down
6 changes: 3 additions & 3 deletions components/demo/SimpleNftMintDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Box, Link, Text, Spinner, Flex } from '@chakra-ui/react';
import { U32Value } from '@elrondnetwork/erdjs';
import { Link, Text } from '@chakra-ui/react';
import { U32Value, ContractFunction } from '@elrondnetwork/erdjs';
import { useScTransaction } from '../../hooks/core/useScTransaction';
import { useCallback } from 'react';
import { ActionButton } from '../tools/ActionButton';
Expand All @@ -22,7 +22,7 @@ export const SimpleNftMintDemo = ({
const handleSendTx = useCallback(() => {
triggerTx({
smartContractAddress: mintSmartContractAddress,
func: mintFunctionName,
func: new ContractFunction(mintFunctionName),
gasLimit: 14000000,
args: [new U32Value(1)],
value: 0.001,
Expand Down
8 changes: 2 additions & 6 deletions components/tools/Authenticated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,21 @@ export const Authenticated: FC<AuthenticatedProps> = ({
noSpinner = false,
spinnerCentered = false,
}) => {
const { isLoggingIn, isLoggedIn, error } = useLoggingIn();
const { isLoggingIn, isLoggedIn } = useLoggingIn();

if (isLoggingIn)
return noSpinner ? null : (
<Flex justify={spinnerCentered ? 'center' : 'flex-start'}>
<Spinner
thickness="3px"
speed="0.4s"
color="dappTemplate.color2.base"
color="elvenTools.color2.base"
size="md"
mt={3}
/>
</Flex>
);

if (error) {
return <>{error}</>;
}

if (!isLoggedIn) return fallback;

return <>{children}</>;
Expand Down
184 changes: 184 additions & 0 deletions components/tools/LedgerAccountsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { FC, useCallback, useState, useEffect, useRef } from 'react';
import { Box, Text, Flex, Spinner } from '@chakra-ui/react';
import { useRouter } from 'next/router';

import { LoginMethodsEnum } from '../../types/enums';
import { ActionButton } from './ActionButton';
import { shortenHash } from '../../utils/shortenHash';
import { useLoginInfo } from '../../hooks/auth/useLoginInfo';

interface LedgerAccountsListProps {
getHWAccounts: (page?: number, pageSize?: number) => Promise<string[]>;
resetLoginMethod: () => void;
handleLogin: (
type: LoginMethodsEnum,
ledgerAccountsIndex?: number
) => () => void;
}

const ADDRESSES_PER_PAGE = 10;
const LEDGER_NOT_CONNECTED_CODE = 0x6e01;
const LEDGER_DISCONNECTED = 'DisconnectedDeviceDuringOperation';

export const LedgerAccountsList: FC<LedgerAccountsListProps> = ({
getHWAccounts,
resetLoginMethod,
handleLogin,
}) => {
const [accounts, setAccounts] = useState<string[]>();
const [currentPage, setCurrentPage] = useState(0);
const [listPending, setListPending] = useState(true);
const [error, setError] = useState<string>();
const [chosenAddress, setAddress] = useState<string>();

const { loginToken } = useLoginInfo();

const mounted = useRef(false);

const router = useRouter();

useEffect(() => {
mounted.current = true;

const fetch = async () => {
try {
mounted.current && setListPending(true);
const accounts = await getHWAccounts(currentPage, ADDRESSES_PER_PAGE);
if (accounts?.length > 0 && mounted.current) setAccounts(accounts);
} catch (e: any) {
if (
(e.statusCode === LEDGER_NOT_CONNECTED_CODE ||
e.name === LEDGER_DISCONNECTED) &&
mounted.current
) {
setError(
'Not connected, please check the connection and make sure that you have the Elrond app opened on your Ledger device.'
);
}
} finally {
mounted.current && setListPending(false);
}
};
fetch();
return () => {
mounted.current = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPage]);

const handlePrev = useCallback(() => {
setCurrentPage((prevState) => (prevState > 0 ? prevState - 1 : prevState));
}, []);

const handleNext = useCallback(() => {
setCurrentPage((prevState) => prevState + 1);
}, []);

const handleRefresh = useCallback(() => {
router.reload();
}, [router]);

const login = useCallback(
(index, address) => () => {
handleLogin(LoginMethodsEnum.ledger, index)();
setAddress(address);
},
[handleLogin]
);

useEffect(() => {
if (!listPending && !accounts && !error) {
resetLoginMethod();
}
}, [accounts, error, listPending, resetLoginMethod]);

if (listPending) {
return (
<Flex justify="center" align="center" marginTop={6} direction="column">
<Spinner color="elvenTools.color2.base" />
<Box marginTop={3}>Loading addresses, please wait...</Box>
</Flex>
);
}

if (error) {
return (
<Box
textAlign="center"
marginLeft="auto"
marginRight="auto"
marginTop={6}
>
<Text>{error}</Text>
<ActionButton mt={4} onClick={handleRefresh}>
Refresh
</ActionButton>
</Box>
);
}

if (!accounts) return null;

if (chosenAddress)
return (
<Flex justify="center" align="center" marginTop={6} direction="column">
<Spinner color="elvenTools.color2.base" />
<Box marginTop={3}>Confirm on the Ledger device:</Box>
<Box marginTop={3} wordBreak="break-word" textAlign="center">
<Box fontWeight="bold">Address:</Box> {chosenAddress}
</Box>
{loginToken && (
<Box mt={3}>
<Box fontWeight="bold">Auth token:</Box> {loginToken}
{'{}'}
</Box>
)}
</Flex>
);

return (
<Box marginLeft="auto" marginRight="auto" marginTop={6}>
<Text fontWeight="semibold" textAlign="center" mb={2}>
Choose address:
</Text>
{accounts?.map((account: string, index: number) => (
<Box
key={account}
marginBottom={0.5}
cursor="pointer"
border="1px solid transparent"
borderRadius="md"
_hover={{ border: '1px dotted #fff', paddingLeft: 2 }}
transition="padding-left 0.2s"
padding={1}
onClick={login(index, account)}
>
<Box as="span" display="inline-block" textAlign="center" minWidth={4}>
{index + currentPage * ADDRESSES_PER_PAGE}
</Box>
:
<Box
as="span"
display="inline-block"
marginLeft={4}
textAlign="center"
>
{shortenHash(account, 11)}
</Box>
</Box>
))}
<Flex justifyContent="space-between" marginTop={6}>
<Text
onClick={handlePrev}
cursor={currentPage === 0 ? 'not-allowed' : 'pointer'}
opacity={currentPage === 0 ? 0.5 : 1}
>
Prev
</Text>
<Text onClick={handleNext} cursor="pointer">
Next
</Text>
</Flex>
</Box>
);
};
34 changes: 28 additions & 6 deletions components/tools/LoginComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
// Login component wraps all auth services in one place
// You can always use only one of them if needed
import { useCallback, memo } from 'react';
import { useCallback, memo, useState } from 'react';
import { Box, Stack } from '@chakra-ui/react';
import { useLogin } from '../../hooks/auth/useLogin';
import { LoginMethodsEnum } from '../../types/enums';
import { MobileLoginQR } from './MobileLoginQR';
import { ActionButton } from './ActionButton';
import { LedgerAccountsList } from './LedgerAccountsList';

export const LoginComponent = memo(() => {
const { login, isLoggedIn, error, walletConnectUri } = useLogin();
const { login, isLoggedIn, error, walletConnectUri, getHWAccounts } =
useLogin();
const [loginMethod, setLoginMethod] = useState<LoginMethodsEnum>();

const handleLogin = useCallback(
(type: LoginMethodsEnum) => () => {
login(type);
(type: LoginMethodsEnum, ledgerAccountsIndex?: number) => () => {
setLoginMethod(type);
login(type, ledgerAccountsIndex);
},
[login]
);

if (error) return <div>{error}</div>;
const handleLedgerAccountsList = useCallback(() => {
setLoginMethod(LoginMethodsEnum.ledger);
}, []);

const resetLoginMethod = useCallback(() => {
setLoginMethod(undefined);
}, []);

if (error) return <Box textAlign="center">{error}</Box>;

return (
<>
Expand All @@ -42,14 +54,24 @@ export const LoginComponent = memo(() => {
>
Maiar Mobile App
</ActionButton>
<ActionButton isFullWidth onClick={handleLedgerAccountsList}>
Ledger
</ActionButton>
</>
)}
</Stack>
{walletConnectUri && (
{loginMethod === LoginMethodsEnum.walletconnect && walletConnectUri && (
<Box mt={5}>
<MobileLoginQR walletConnectUri={walletConnectUri} />
</Box>
)}
{loginMethod === LoginMethodsEnum.ledger && (
<LedgerAccountsList
getHWAccounts={getHWAccounts}
resetLoginMethod={resetLoginMethod}
handleLogin={handleLogin}
/>
)}
</>
);
});
Expand Down
2 changes: 1 addition & 1 deletion components/ui/SocialMediaIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const SocialMediaIcons = () => {
return (
<Box display="flex" alignItems="center" gap={3}>
<SocialIcon
url="https://www.github.com/ElrondDevGuild"
url="https://github.com/ElrondDevGuild/nextjs-dapp-template"
bgColor="#fff"
style={{ width: 30, height: 30 }}
/>
Expand Down
Loading

0 comments on commit fc34df4

Please sign in to comment.