Skip to content

Commit

Permalink
Ledger support (#54)
Browse files Browse the repository at this point in the history
* feat: add https for local development

* feat: add support for ledger

* feat: add ledger support

* feat: add ledger support

* feat: add ledger support

* feat: add ledger support

* feat: ledger support

* feat: add leadger support

* feat: ledger support

* Some updates

* Reset back to master

* Revert login page logic

* Fix various things

* working

* some updates

* Fix unlock error silent failing

* dapp signatures working

* touch up

Co-authored-by: Bartosz Lipinski <[email protected]>
  • Loading branch information
nathanielparke and bartosz-lipinski authored Dec 2, 2020
1 parent 32358af commit f22090c
Show file tree
Hide file tree
Showing 20 changed files with 604 additions and 123 deletions.
27 changes: 27 additions & 0 deletions .cert/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEozCCAwugAwIBAgIRALVgQ4iLzJxtipCRuRZ9FWMwDQYJKoZIhvcNAQELBQAw
gbkxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTFHMEUGA1UECww+YmFy
dG9zei5saXBpbnNraUBCYXJ0b3N6cy1NYWNCb29rLVByby5sb2NhbCAoQmFydG9z
eiBMaXBpbnNraSkxTjBMBgNVBAMMRW1rY2VydCBiYXJ0b3N6LmxpcGluc2tpQEJh
cnRvc3pzLU1hY0Jvb2stUHJvLmxvY2FsIChCYXJ0b3N6IExpcGluc2tpKTAeFw0x
OTA2MDEwMDAwMDBaFw0zMDEwMTgwMjU2MzhaMHIxJzAlBgNVBAoTHm1rY2VydCBk
ZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTFHMEUGA1UECww+YmFydG9zei5saXBpbnNr
aUBCYXJ0b3N6cy1NYWNCb29rLVByby5sb2NhbCAoQmFydG9zeiBMaXBpbnNraSkw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOwT5doIalTreoX71W6TLr
V2tijuSHLRmIDcDutPM/cPYNUHgkGthZveMdrcoaDqveHZdGjWY39U+8kzrdcbV2
7oXNd4ivC5acS8DIJNCO3G1JcNSYnxmZXEaPAHXaVke+SMXVTWbUvA8Rkyor9hPe
KW8gtFqm2IT/klRBfWuYLO24dILrCfYkqJkZ6g++X7pBp1R/8h9SYdHWbHxIDk2d
CWaHNA7v8g1bMw2ZmxICwgbsARplLgIU/ZWRKQik2axOIeHDpoeV9/hj4SXvs1bA
yvO8oNMjYuekkAs117NAfJb9oR6p/iet39IfzY4zQmBqwFDuAu7+nZq1NfFLajor
AgMBAAGjbDBqMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAM
BgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFLRNFQImfwsJQApnvaPukIsWedjpMBQG
A1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAYEAgm1tjv0cRoBR
lBzIajECMHVK1dHYARaaFrG4ll1DpWS38Cyn2FN/YIIdTrzh8vFIkV/Leeozcjp8
nE84zziw9nYvX86SfKtv8uVaVLPoNm2hl9JQS/19dMrB25vpeDGJqmnF3/n7oVEC
SY6fY2xLMx47tpnT5P9NuXgP6Zz6KVQp+CPEfoIkTo+dU0Kk67K5Q9OR42SyiRG2
JxSBymbPV/mHwOxAS2M6QMODPt+FpVYeiz+iM6d1lL2NGs2CnyBFaFLNlmxij9yL
rZoU+Om5LrhgY5CL7/DVkU6xZC0VI9AZvV3eV5ouPv4ofH47BSOkxwLj5V4xFGTf
+1YRuvqE12EecBpz5g/33LzrkipA7G6P1Oca38g7Xkv6+XKALXIZ2dtcXUp7s7Ty
Cf6Nydlgeqe6Ik6+OKgWIwCahr2cWWVKC/JFMqOYugD4dYcyvbv+V06YEQvYJP5B
yhVdn4uVAfrGGFUCRm9ZM3EhSPZlNwVcG6l5jaV/48L+Uy32VGvJ
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions .cert/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDOwT5doIalTreo
X71W6TLrV2tijuSHLRmIDcDutPM/cPYNUHgkGthZveMdrcoaDqveHZdGjWY39U+8
kzrdcbV27oXNd4ivC5acS8DIJNCO3G1JcNSYnxmZXEaPAHXaVke+SMXVTWbUvA8R
kyor9hPeKW8gtFqm2IT/klRBfWuYLO24dILrCfYkqJkZ6g++X7pBp1R/8h9SYdHW
bHxIDk2dCWaHNA7v8g1bMw2ZmxICwgbsARplLgIU/ZWRKQik2axOIeHDpoeV9/hj
4SXvs1bAyvO8oNMjYuekkAs117NAfJb9oR6p/iet39IfzY4zQmBqwFDuAu7+nZq1
NfFLajorAgMBAAECggEBAMuSWeW1+N0q9IpEOhko44n1OTaBm2G9djYP1Lc0U41T
m/DgGmryQ7OY09aVFzkw2OiKGjjNYKgYUbpK/Nqs6w9/Kx9zYpF3x4N80wQ9u1vu
jWySO8FKZdoqkQ6cVW31Jg6leKTc4TL1N6EGVa+TS1yjT1fUPK2q4skBOxSAeUAK
t5OvL5eeuumu+Ya3nrwQ1wp1ZBWhkeDIGjED8SDPfT0kU4v8dKTJcXk7ooF5s1OZ
H6HSzEej8eZSfBLDTwPsHylaGTcTi9Tgi8CnjAVdBLaFJlS+bkvv7m5/jnuEpofX
HkyAW5LDnIvHLycE3Ql9BQsR/DF9uzQvn9VRwPGQm4ECgYEA6/eyWhQfl4rDfjQi
fzQgjEYnTbE69R3LUUKulUQQeBYcZO2JAAGZ45G6lZ1zEWFmXuj0aQRYpj798cTg
0ZyH7Dllae8GPZlQ0u/Pxp03DzrvNGbHiTEBYqyVB+gJFn9Robm1eG+kP3QVgaIq
iOXwnUGL74MaJKdwi9ruhzLaQwUCgYEA4E6qJwkBRkGfpjSc0stwTEd+04nSPU1u
V3jFkxS/MNHCF/FUOGBKvrEbbedZ4DRiHjmQ6+zsvl+5jW75bw2rrxxxjkvpNjZe
BOKhac7c+jHXEAm4xuP6lUnPOenBhy/AvOJMm+6wzxdvTzp2gfnfIUOld24bdSXP
29yOzOpYb28CgYAlDn0f0FE1x0D0LNPODi2eWdYKSW7s14T6efJY1puPgEltQDBn
o9i6+EPJAzTy4czl0sevRlN1qCbRNQ3pXR+rZUgb3sGoIs+ikK6cjkv7RFIUdJ+Z
V+zTxi6RU0s6ETyMnVF2XHH61Qwbk5ACd7nVuFl1f603XGQ8UmFrMf080QKBgQDQ
a2eg87YScOGGDvb0ywFib0BCEJqgSXVQo7B5lNp94zmFA8EszRRGkcwZ19DkCeht
izHEdhYYYlvINihg7wPqpvRAsvpUXDoKMganiQY9F9hsV4wwih8JXlbFyhT/pvhg
yalDbostMepEZN8+sE2K3A9ApLewp1y3Pv4VG17m0wKBgB7GIrI2Jx5CG4yFoM8y
kB5rbvNlGAER58AMhzMebs7e2pQg0GxMpaNwqSZyVG0kLq7ayXn9ExA+uQpAG6cR
fnBWVflYf0hKIC1M6U9kOle6lZ/Nbdo80wGa5z3Ieup6w9WB0pbtifvbsnv6RLeA
S7MNQDcNXJxQulqppa+1BOTl
-----END PRIVATE KEY-----
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
insert_final_newline = true

[*.{js}]
charset = utf-8

[src/**.js]
indent_style = space
indent_size = 2

[{package.json,.travis.yml}]
indent_style = space
indent_size = 2
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
HTTPS=true
SSL_CRT_FILE=./.cert/cert.pem SSL_KEY_FILE=./.cert/key.pem
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ledgerhq/hw-transport-webusb": "^5.34.0",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@project-serum/serum": "^0.13.11",
Expand Down
9 changes: 5 additions & 4 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,17 @@ export default function App() {
<Suspense fallback={<LoadingIndicator />}>
<ThemeProvider theme={theme}>
<CssBaseline />

<ConnectionProvider>
<WalletProvider>
<SnackbarProvider maxSnack={5} autoHideDuration={8000}>
<SnackbarProvider maxSnack={5} autoHideDuration={8000}>
<WalletProvider>
<NavigationFrame>
<Suspense fallback={<LoadingIndicator />}>
<PageContents />
</Suspense>
</NavigationFrame>
</SnackbarProvider>
</WalletProvider>
</WalletProvider>
</SnackbarProvider>
</ConnectionProvider>
</ThemeProvider>
</Suspense>
Expand Down
82 changes: 82 additions & 0 deletions src/components/AddHarwareWalletDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, {useEffect, useState} from 'react';
import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogForm from './DialogForm';
import {LedgerWalletProvider} from "../utils/walletProvider/ledger";
import CircularProgress from "@material-ui/core/CircularProgress";
import {useSnackbar} from "notistack";

export default function AddHardwareWalletDialog({ open, onAdd, onClose }) {
const [pubKey, setPubKey] = useState();
const { enqueueSnackbar } = useSnackbar();

useEffect(() => {( async () => {
if (open) {
try {
const provider = new LedgerWalletProvider();
await provider.init();
setPubKey(provider.publicKey);
} catch (err) {
console.log(`received error when attempting to connect ledger: ${err}`);
if (err.statusCode === 0x6804) {
enqueueSnackbar('Unlock ledger device', { variant: 'error' })
}
setPubKey(undefined)
onClose();
}
}
})();}, [open, onClose])

return (
<DialogForm
open={open}
onEnter={() => {}}
onClose={() => {
setPubKey(undefined);
onClose();
}}
onSubmit={() => {
setPubKey(undefined);
onAdd(pubKey);
onClose();
}}
fullWidth
>
<DialogTitle>Add hardware wallet</DialogTitle>
<DialogContent style={{ paddingTop: 16 }}>
<div
style={{
display: 'flex',
flexDirection: 'column',
}}
>
{pubKey
? (
<>
<b>Hardware wallet detected:</b>
<div>{pubKey.toString()}</div>
</>
)
: (
<>
<b>Connect your ledger and open the Solana application</b>
<CircularProgress />
</>
)
}
</div>
</DialogContent>
<DialogActions>
<Button onClick={() => {
setPubKey(undefined);
onClose();
}}>Close</Button>
<Button type="submit" color="primary" disabled={!pubKey}>
Add
</Button>
</DialogActions>
</DialogForm>
);
}
13 changes: 8 additions & 5 deletions src/components/BalancesList.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ function BalanceListItemDetails({ publicKey, balanceInfo }) {
closeTokenAccountDialogOpen,
setCloseTokenAccountDialogOpen,
] = useState(false);
const wallet = useWallet()

if (!balanceInfo) {
return <LoadingIndicator delay={0} />;
Expand All @@ -197,10 +198,12 @@ function BalanceListItemDetails({ publicKey, balanceInfo }) {

return (
<>
<ExportAccountDialog
onClose={() => setExportAccDialogOpen(false)}
open={exportAccDialogOpen}
/>
{wallet.allowsExport &&
<ExportAccountDialog
onClose={() => setExportAccDialogOpen(false)}
open={exportAccDialogOpen}
/>
}
<div className={classes.itemDetails}>
<div className={classes.buttonContainer}>
{!publicKey.equals(owner) && showTokenInfoDialog ? (
Expand Down Expand Up @@ -270,7 +273,7 @@ function BalanceListItemDetails({ publicKey, balanceInfo }) {
</Link>
</Typography>
</div>
{exportNeedsDisplay && (
{exportNeedsDisplay && wallet.allowsExport && (
<div>
<Typography variant="body2">
<Link href={'#'} onClick={(e) => setExportAccDialogOpen(true)}>
Expand Down
8 changes: 4 additions & 4 deletions src/components/DebugButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function DebugButtons() {
const wallet = useWallet();
const updateTokenName = useUpdateTokenName();
const { endpoint } = useConnectionConfig();
const balanceInfo = useBalanceInfo(wallet.account.publicKey);
const balanceInfo = useBalanceInfo(wallet.publicKey);
const [sendTransaction, sending] = useSendTransaction();
const callAsync = useCallAsync();

Expand All @@ -29,13 +29,13 @@ export default function DebugButtons() {
function requestAirdrop() {
callAsync(
wallet.connection.requestAirdrop(
wallet.account.publicKey,
wallet.publicKey,
LAMPORTS_PER_SOL,
),
{
onSuccess: async () => {
await sleep(5000);
refreshAccountInfo(wallet.connection, wallet.account.publicKey);
refreshAccountInfo(wallet.connection, wallet.publicKey);
},
successMessage:
'Success! Please wait up to 30 seconds for the SOL tokens to appear in your wallet.',
Expand All @@ -53,7 +53,7 @@ export default function DebugButtons() {
sendTransaction(
createAndInitializeMint({
connection: wallet.connection,
owner: wallet.account,
owner: wallet,
mint,
amount: 1000,
decimals: 2,
Expand Down
2 changes: 1 addition & 1 deletion src/components/ExportAccountDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function ExportAccountDialog({ open, onClose }) {
type={isHidden && 'password'}
variant="outlined"
margin="normal"
value={bs58.encode(wallet.account.secretKey)}
value={bs58.encode(wallet.provider.account.secretKey)}
/>
<FormControlLabel
control={
Expand Down
24 changes: 24 additions & 0 deletions src/components/NavigationFrame.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import CheckIcon from '@material-ui/icons/Check';
import AddIcon from '@material-ui/icons/Add';
import ExitToApp from '@material-ui/icons/ExitToApp';
import AccountIcon from '@material-ui/icons/AccountCircle';
import UsbIcon from '@material-ui/icons/Usb';
import Divider from '@material-ui/core/Divider';
import Hidden from '@material-ui/core/Hidden';
import IconButton from '@material-ui/core/IconButton';
Expand All @@ -22,6 +23,7 @@ import CodeIcon from '@material-ui/icons/Code';
import Tooltip from '@material-ui/core/Tooltip';
import AddAccountDialog from './AddAccountDialog';
import DeleteAccountDialog from "./DeleteAccountDialog";
import AddHardwareWalletDialog from "./AddHarwareWalletDialog";

const useStyles = makeStyles((theme) => ({
content: {
Expand Down Expand Up @@ -131,6 +133,7 @@ function WalletSelector() {
const { accounts, setWalletSelector, addAccount } = useWalletSelector();
const [anchorEl, setAnchorEl] = useState(null);
const [addAccountOpen, setAddAccountOpen] = useState(false);
const [addHardwareWalletDialogOpen, setAddHardwareWalletDialogOpen] = useState(false);
const [deleteAccountOpen, setDeleteAccountOpen] = useState(false);
const [isDeleteAccountEnabled, setIsDeleteAccountEnabled] = useState(false);
const classes = useStyles();
Expand All @@ -141,6 +144,18 @@ function WalletSelector() {

return (
<>
<AddHardwareWalletDialog
open={addHardwareWalletDialogOpen}
onClose={() => setAddHardwareWalletDialogOpen(false)}
onAdd={(pubKey) => {
addAccount({ name: 'Hardware wallet', importedAccount: pubKey.toString(), ledger: true });
setWalletSelector({
walletIndex: undefined,
importedPubkey: pubKey.toString(),
ledger: true
});
}}
/>
<AddAccountDialog
open={addAccountOpen}
onClose={() => setAddAccountOpen(false)}
Expand All @@ -151,6 +166,7 @@ function WalletSelector() {
importedPubkey: importedAccount
? importedAccount.publicKey.toString()
: undefined,
ledger: false,
});
setAddAccountOpen(false);
}}
Expand Down Expand Up @@ -208,6 +224,14 @@ function WalletSelector() {
</MenuItem>
))}
<Divider />
<MenuItem
onClick={() => setAddHardwareWalletDialogOpen(true)}
>
<ListItemIcon className={classes.menuItemIcon}>
<UsbIcon fontSize="small" />
</ListItemIcon>
Import Hardware Wallet
</MenuItem>
<MenuItem
onClick={() => {
setAnchorEl(null);
Expand Down
12 changes: 5 additions & 7 deletions src/pages/PopupPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default function PopupPage({ opener }) {
useEffect(() => {
if (
connectedAccount &&
!connectedAccount.publicKey.equals(wallet.publicKey)
!connectedAccount.equals(wallet.publicKey)
) {
setConnectedAccount(null);
}
Expand All @@ -95,11 +95,11 @@ export default function PopupPage({ opener }) {

if (
!connectedAccount ||
!connectedAccount.publicKey.equals(wallet.publicKey)
!connectedAccount.equals(wallet.publicKey)
) {
// Approve the parent page to connect to this wallet.
function connect(autoApprove) {
setConnectedAccount(wallet.account);
setConnectedAccount(wallet.publicKey);
postMessage({
method: 'connected',
params: { publicKey: wallet.publicKey.toBase58(), autoApprove },
Expand All @@ -116,13 +116,11 @@ export default function PopupPage({ opener }) {
assert(request.method === 'signTransaction');
const message = bs58.decode(request.params.message);

function sendSignature() {
async function sendSignature() {
setRequests((requests) => requests.slice(1));
postMessage({
result: {
signature: bs58.encode(
nacl.sign.detached(message, wallet.account.secretKey),
),
signature: await wallet.createSignature(message),
publicKey: wallet.publicKey.toBase58(),
},
id: request.id,
Expand Down
3 changes: 2 additions & 1 deletion src/utils/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export function useIsProdNetwork() {
}

export function useSolanaExplorerUrlSuffix() {
const endpoint = useContext(ConnectionContext).endpoint;
const context = useContext(ConnectionContext);
const endpoint = context.endpoint;
if (endpoint === clusterApiUrl('devnet')) {
return '?cluster=devnet';
} else if (endpoint === clusterApiUrl('testnet')) {
Expand Down
Loading

0 comments on commit f22090c

Please sign in to comment.