Skip to content

Commit 53ed2e8

Browse files
authored
Allows converting viewonly accounts to Ledger, add fee selector to ledger send flow (#234)
1 parent 84207c5 commit 53ed2e8

File tree

9 files changed

+139
-74
lines changed

9 files changed

+139
-74
lines changed

main/api/ledger/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { observable } from "@trpc/server/observable";
2+
import { z } from "zod";
23

34
import { ledgerManager, ConnectionStatus } from "./utils/ledger";
45
import { handleSendTransactionInput } from "../transactions/handleSendTransaction";
@@ -25,4 +26,16 @@ export const ledgerRouter = t.router({
2526
.mutation(async (opts) => {
2627
return ledgerManager.submitTransaction(opts.input);
2728
}),
29+
cancelSubmitLedgerTransaction: t.procedure.mutation(async () => {
30+
return ledgerManager.cancelTransaction();
31+
}),
32+
markAccountAsLedger: t.procedure
33+
.input(
34+
z.object({
35+
publicAddress: z.string(),
36+
}),
37+
)
38+
.mutation((opts) => {
39+
return ledgerManager.markAccountAsLedger(opts.input.publicAddress);
40+
}),
2841
});

main/api/ledger/utils/ledger.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import IronfishApp, {
88
ResponseAddress,
99
ResponseViewKey,
1010
ResponseProofGenKey,
11+
ResponseSign,
1112
} from "@zondax/ledger-ironfish";
1213
import { z } from "zod";
1314

@@ -73,6 +74,7 @@ class LedgerManager {
7374
transport: Transport | null = null;
7475
subscribers: Map<string, (status: ConnectionStatus) => void> = new Map();
7576
connectionStatus: ConnectionStatus = { ...EMPTY_CONNECTION_STATUS };
77+
signTransactionPromise: Promise<ResponseSign> | null = null;
7678
taskQueue = new PromiseQueue();
7779

7880
private connect = async (): ManagerResponse<Transport> => {
@@ -344,7 +346,7 @@ class LedgerManager {
344346
name: accountName,
345347
account: encoded,
346348
});
347-
await ledgerStore.setIsLedgerAccount(publicAddress, true);
349+
await this.markAccountAsLedger(publicAddress);
348350

349351
return accountImport;
350352
} catch (err) {
@@ -359,6 +361,10 @@ class LedgerManager {
359361
return returnValue;
360362
};
361363

364+
markAccountAsLedger = async (publicAddress: string) => {
365+
await ledgerStore.setIsLedgerAccount(publicAddress, true);
366+
};
367+
362368
submitTransaction = async ({
363369
fromAccount,
364370
toAccount,
@@ -401,11 +407,18 @@ class LedgerManager {
401407
throw new Error(ironfishAppReponse.error.message);
402408
}
403409

404-
const signResponse = await ironfishAppReponse.data.sign(
410+
this.signTransactionPromise = ironfishAppReponse.data.sign(
405411
DERIVATION_PATH,
406412
unsignedTransactionBuffer,
407413
);
408414

415+
const signResponse = await this.signTransactionPromise;
416+
417+
if (!this.signTransactionPromise) {
418+
logger.info("Transaction was cancelled by the client");
419+
return null;
420+
}
421+
409422
if (!signResponse.signature) {
410423
throw new Error(signResponse.errorMessage || "No signature returned");
411424
}
@@ -435,6 +448,10 @@ class LedgerManager {
435448

436449
return returnValue;
437450
};
451+
452+
cancelTransaction() {
453+
this.signTransactionPromise = null;
454+
}
438455
}
439456

440457
export const ledgerManager = new LedgerManager();

renderer/components/ConnectLedgerModal/ConnectLedgerModal.tsx

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
ModalFooter,
66
ModalBody,
77
} from "@chakra-ui/react";
8-
import { useEffect, useState } from "react";
8+
import { useEffect, useMemo, useState } from "react";
99
import { useIntl, defineMessages } from "react-intl";
1010

1111
import { trpcReact } from "@/providers/TRPCProvider";
@@ -17,21 +17,6 @@ import { StepConnect } from "./Steps/StepConnect";
1717
import { StepError } from "./Steps/StepError";
1818

1919
const messages = defineMessages({
20-
headingConnectLedger: {
21-
defaultMessage: "Connect your Ledger",
22-
},
23-
plugDevice: {
24-
defaultMessage: "Plug your ledger device directly into your computer",
25-
},
26-
unlockLedger: {
27-
defaultMessage: "Unlock your Ledger",
28-
},
29-
openApp: {
30-
defaultMessage: "Open the Iron Fish app",
31-
},
32-
connectAccount: {
33-
defaultMessage: "Connect account",
34-
},
3520
cancel: {
3621
defaultMessage: "Cancel",
3722
},
@@ -102,6 +87,24 @@ export function ConnectLedgerModal({
10287
setStep("ERROR");
10388
},
10489
});
90+
const {
91+
isLoading: isMarkingAsLedger,
92+
error: markAsLedgerError,
93+
mutate: markAccountAsLedger,
94+
} = trpcReact.markAccountAsLedger.useMutation({
95+
onSuccess: () => {
96+
onClose();
97+
},
98+
onError: () => {
99+
setStep("ERROR");
100+
},
101+
});
102+
103+
const { data: userAccounts } = trpcReact.getAccounts.useQuery();
104+
const existingLedgerAccount = useMemo(() => {
105+
return userAccounts?.find((account) => account.address === publicAddress);
106+
}, [publicAddress, userAccounts]);
107+
const isLedgerAccountImportedAndFlagged = !!existingLedgerAccount?.isLedger;
105108

106109
useEffect(() => {
107110
if (
@@ -130,13 +133,15 @@ export function ConnectLedgerModal({
130133
deviceName={deviceName}
131134
isConfirmed={isConfirmed}
132135
onConfirmChange={setIsConfirmed}
136+
isImported={isLedgerAccountImportedAndFlagged}
133137
/>
134138
)}
135139
{step === "APPROVE" && <StepApprove />}
136140
{step === "ERROR" && (
137141
<StepError
138142
errorMessage={
139-
importError?.message ??
143+
importError?.message ||
144+
markAsLedgerError?.message ||
140145
"An unknown error occured. Please try again."
141146
}
142147
/>
@@ -154,7 +159,8 @@ export function ConnectLedgerModal({
154159
!isLedgerUnlocked ||
155160
!isIronfishAppOpen ||
156161
(step === "SELECT_ACCOUNT" && !isConfirmed) ||
157-
isImporting
162+
isImporting ||
163+
isMarkingAsLedger
158164
}
159165
onClick={() => {
160166
if (step === "ERROR") {
@@ -168,8 +174,12 @@ export function ConnectLedgerModal({
168174
}
169175

170176
if (step === "SELECT_ACCOUNT") {
171-
importLedgerAccount();
172-
setStep("APPROVE");
177+
if (existingLedgerAccount) {
178+
markAccountAsLedger({ publicAddress });
179+
} else {
180+
importLedgerAccount();
181+
setStep("APPROVE");
182+
}
173183
}
174184
}}
175185
>

renderer/components/ConnectLedgerModal/Steps/StepAddAccount.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Heading,
88
HStack,
99
Text,
10+
Tooltip,
1011
} from "@chakra-ui/react";
1112
import Image from "next/image";
1213
import { defineMessages, useIntl } from "react-intl";
@@ -24,20 +25,25 @@ const messages = defineMessages({
2425
defaultMessage:
2526
"One last step! Confirm the account you'd like to connect to the Node App.",
2627
},
28+
alreadyImported: {
29+
defaultMessage: "This Ledger account has already been imported",
30+
},
2731
});
2832

2933
type Props = {
3034
deviceName: string;
3135
publicAddress: string;
3236
isConfirmed: boolean;
3337
onConfirmChange: (value: boolean) => void;
38+
isImported: boolean;
3439
};
3540

3641
export function StepAddAccount({
3742
deviceName,
3843
publicAddress,
3944
isConfirmed,
4045
onConfirmChange,
46+
isImported,
4147
}: Props) {
4248
const { formatMessage } = useIntl();
4349

@@ -49,7 +55,7 @@ export function StepAddAccount({
4955
fontSize="medium"
5056
color={COLORS.GRAY_MEDIUM}
5157
_dark={{
52-
color: COLORS.DARK_MODE.GRAY_MEDIUM,
58+
color: COLORS.DARK_MODE.GRAY_LIGHT,
5359
}}
5460
>
5561
{formatMessage(messages.description)}
@@ -61,10 +67,18 @@ export function StepAddAccount({
6167
>
6268
<HStack py={5} px={4} justifyContent="space-between">
6369
<Text fontSize="medium">{deviceName}</Text>
64-
<Checkbox
65-
isChecked={isConfirmed}
66-
onChange={(e) => onConfirmChange(e.target.checked)}
67-
/>
70+
<Tooltip
71+
label={isImported ? formatMessage(messages.alreadyImported) : null}
72+
placement="top"
73+
>
74+
<Box>
75+
<Checkbox
76+
isChecked={isConfirmed}
77+
onChange={(e) => onConfirmChange(e.target.checked)}
78+
disabled={isImported}
79+
/>
80+
</Box>
81+
</Tooltip>
6882
</HStack>
6983

7084
<Box borderBottom="1px solid black" />
@@ -95,7 +109,7 @@ export function StepAddAccount({
95109
wordBreak="break-all"
96110
color={COLORS.GRAY_MEDIUM}
97111
_dark={{
98-
color: COLORS.DARK_MODE.GRAY_MEDIUM,
112+
color: COLORS.DARK_MODE.GRAY_LIGHT,
99113
}}
100114
>
101115
{publicAddress}

renderer/components/ConnectLedgerModal/Steps/StepConnect.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,6 @@ const messages = defineMessages({
2424
openApp: {
2525
defaultMessage: "Open the Iron Fish app",
2626
},
27-
connectAccount: {
28-
defaultMessage: "Connect account",
29-
},
30-
cancel: {
31-
defaultMessage: "Cancel",
32-
},
33-
continue: {
34-
defaultMessage: "Continue",
35-
},
3627
});
3728

3829
type Props = {

renderer/components/SendAssetsForm/ConfirmLedgerModal/ConfirmLedgerModal.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ export function ConfirmLedgerModal({
8383
},
8484
});
8585

86+
useEffect(() => {
87+
if (
88+
!["CONNECT_LEDGER", "ERROR"].includes(step) &&
89+
(!isLedgerConnected || !isLedgerUnlocked || !isIronfishAppOpen)
90+
) {
91+
setStep("CONNECT_LEDGER");
92+
}
93+
}, [step, isLedgerConnected, isLedgerUnlocked, isIronfishAppOpen]);
94+
8695
const {
8796
mutate: submitTransaction,
8897
data: submittedTransactionData,
@@ -98,6 +107,9 @@ export function ConfirmLedgerModal({
98107
},
99108
});
100109

110+
const { mutate: cancelTransaction } =
111+
trpcReact.cancelSubmitLedgerTransaction.useMutation();
112+
101113
useEffect(() => {
102114
if (
103115
step === "CONFIRM_TRANSACTION" &&
@@ -108,9 +120,12 @@ export function ConfirmLedgerModal({
108120
}, [isLedgerConnected, isLedgerUnlocked, isIronfishAppOpen, step]);
109121

110122
const handleClose = useCallback(() => {
123+
if (step === "CONFIRM_TRANSACTION") {
124+
cancelTransaction();
125+
}
111126
reset();
112127
onCancel();
113-
}, [onCancel, reset]);
128+
}, [onCancel, reset, step, cancelTransaction]);
114129

115130
return (
116131
<Modal isOpen={isOpen} onClose={handleClose}>

0 commit comments

Comments
 (0)