Skip to content

Commit

Permalink
feat/583: Refactor extension signing provider (#754)
Browse files Browse the repository at this point in the history
* feat: begin refactor of extension signing provider

* feat: add chain_id to BuiltTx so we may validate

* feat: removed submit functionality

* feat: hooking up balances & transfer for testing signing

* feat: remove tx details from approvals

* feat: update docs, rename TxMsg/Props for clarity

* fix: balances updated for RPC

* feat: hook up signTx functionality

* feat: finish hooking up sign Tx to approvals

* fix: additional fixes to support SDK signing in interface

* fix: changing pending tx storage so sign can be invoked properly

* feat: hook up reveal PK to transfer form

* feat: add signing_data to sign_tx

* fix: clean up shared changes

* feat: remove RPC URL setting, generate docs, clean up

* feat: clean up signing SDK api

* fix: signing client should also accept BuiltTx

* feat: support multiple Tx in approvals

* fix: clean up unused code

* feat: validate chain_id against Tx header if present
  • Loading branch information
jurevans authored May 17, 2024
1 parent 35909e7 commit 18c94e2
Show file tree
Hide file tree
Showing 98 changed files with 1,427 additions and 4,719 deletions.
3 changes: 1 addition & 2 deletions apps/extension/src/App/Accounts/AddAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { LedgerError } from "@zondax/ledger-namada";
import React, { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

import { makeBip44Path } from "@heliax/namada-sdk/web";
import { Ledger, makeBip44Path } from "@heliax/namada-sdk/web";
import { chains } from "@namada/chains";
import { ActionButton, Input, Toggle } from "@namada/components";
import { AccountType, DerivedAccount } from "@namada/types";

import { TopLevelRoute } from "App/types";
import { AddLedgerAccountMsg, DeriveAccountMsg } from "background/keyring";
import { Ledger } from "background/ledger";
import { ExtensionRequester } from "extension";
import { useAuth } from "hooks";
import { isKeyChainLocked, redirectToLogin } from "hooks/useAuth";
Expand Down
52 changes: 1 addition & 51 deletions apps/extension/src/App/AppContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import { DerivedAccount } from "@namada/types";
import { useAccountContext, useVaultContext } from "context";
import { useQuery } from "hooks";
import { useRequester } from "hooks/useRequester";
import {
CheckDurabilityMsg,
FetchAndStoreMaspParamsMsg,
HasMaspParamsMsg,
} from "provider/messages";
import { CheckDurabilityMsg } from "provider/messages";
import { Ports } from "router";
import { openSetupTab } from "utils";
import {
Expand Down Expand Up @@ -39,51 +35,11 @@ export const AppContent = (): JSX.Element => {
const requester = useRequester();

const [isDurable, setIsDurable] = useState<boolean | undefined>();
const [maspStatus, setMaspStatus] = useState<{
status: LoadingStatus;
info: string;
}>({ status: LoadingStatus.Completed, info: "" });

// Fetch Masp params if they don't exist
const fetchMaspParams = async (): Promise<void> => {
const hasMaspParams = await requester.sendMessage(
Ports.Background,
new HasMaspParamsMsg()
);

if (!hasMaspParams) {
setMaspStatus({
status: LoadingStatus.Pending,
info: "Fetching MASP parameters...",
});
try {
await requester.sendMessage(
Ports.Background,
new FetchAndStoreMaspParamsMsg()
);
setMaspStatus({
status: LoadingStatus.Completed,
info: "",
});
} catch (e) {
setMaspStatus({
status: LoadingStatus.Failed,
info: `Fetching MASP parameters failed: ${e}`,
});
//TODO: Notify user in a better way
console.error(e);
}
}
};

const getStartPage = (accounts: DerivedAccount[]): string => {
return accounts.length === 0 ? routes.setup() : routes.viewAccountList();
};

useEffect(() => {
void fetchMaspParams();
}, []);

// Provide a redirect in the case of transaction/connection approvals
useEffect(() => {
if (redirect) {
Expand Down Expand Up @@ -121,12 +77,6 @@ export const AppContent = (): JSX.Element => {
<Alert type="warning">{STORE_DURABILITY_INFO}</Alert>
)}

{maspStatus.status === LoadingStatus.Completed && maspStatus.info && (
<Alert title="MASP Status" type="warning">
{maspStatus.info}
</Alert>
)}

<Routes>
<Route path={"/"} element={<></>} />
<Route path={routes.setup()} element={<Setup />} />
Expand Down
31 changes: 6 additions & 25 deletions apps/extension/src/App/Settings/Network.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
Input,
Stack,
} from "@namada/components";
import { isUrlValid } from "@namada/utils";
import { PageHeader } from "App/Common";
import { UpdateChainMsg } from "background/chains";
import { useRequester } from "hooks/useRequester";
Expand All @@ -25,12 +24,10 @@ enum Status {
export const Network = (): JSX.Element => {
const requester = useRequester();
const [chainId, setChainId] = useState("");
const [url, setUrl] = useState("");
const [status, setStatus] = useState<Status>(Status.Unsubmitted);
const [errorMessage, setErrorMessage] = useState("");

// TODO: Validate URL and sanitize inputs!
const shouldDisableSubmit = status === Status.Pending || !chainId || !url;
const shouldDisableSubmit = status === Status.Pending || !chainId;

useEffect(() => {
void (async () => {
Expand All @@ -42,9 +39,9 @@ export const Network = (): JSX.Element => {
if (!chain) {
throw new Error("Chain not found!");
}
const { chainId, rpc } = chain;
setUrl(rpc);
setChainId(chainId);
const { chainId } = chain;
const santizedChainId = sanitize(chainId);
setChainId(santizedChainId);
} catch (e) {
setErrorMessage(`${e}`);
}
Expand All @@ -57,7 +54,6 @@ export const Network = (): JSX.Element => {
setStatus(Status.Pending);
setErrorMessage("");
const sanitizedChainId = sanitize(chainId);
const sanitizedUrl = sanitize(url);

// Validate sanitized chain ID
if (!sanitizedChainId) {
Expand All @@ -66,26 +62,18 @@ export const Network = (): JSX.Element => {
return;
}

// Validate sanitized URL
const isValidUrl = isUrlValid(sanitizedUrl);
if (!isValidUrl) {
setErrorMessage("Invalid URL!");
setStatus(Status.Failed);
return;
}

try {
await requester.sendMessage(
Ports.Background,
new UpdateChainMsg(sanitizedChainId, sanitizedUrl)
new UpdateChainMsg(sanitizedChainId)
);
setStatus(Status.Complete);
} catch (err) {
setStatus(Status.Failed);
setErrorMessage(`${err}`);
}
},
[chainId, url]
[chainId]
);

return (
Expand All @@ -105,13 +93,6 @@ export const Network = (): JSX.Element => {
onChange={(e) => setChainId(e.target.value)}
error={chainId.length === 0 ? "Chain ID required!" : ""}
/>
<Input
label="URL"
variant="Text"
value={url}
onChange={(e) => setUrl(e.target.value)}
error={url.length === 0 ? "URL is required!" : ""}
/>
{errorMessage && <Alert type="error">{errorMessage}</Alert>}
{status === Status.Complete && (
<Alert type="info">Successfully updated network!</Alert>
Expand Down
44 changes: 24 additions & 20 deletions apps/extension/src/Approvals/Approvals.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React, { useState } from "react";
import { Route, Routes } from "react-router-dom";

import { TxType } from "@heliax/namada-sdk/web";
import { Container } from "@namada/components";
import { AccountType } from "@namada/types";

import { AppHeader } from "App/Common/AppHeader";
import { TopLevelRoute } from "Approvals/types";
import { PendingTxDetails } from "background/approvals";
import { ApproveConnection } from "./ApproveConnection";
import { ApproveSignature } from "./ApproveSignature";
import { ApproveTx } from "./ApproveTx/ApproveTx";
import { ConfirmLedgerTx } from "./ApproveTx/ConfirmLedgerTx";
import { ConfirmTx } from "./ApproveTx/ConfirmTx";
import { ConfirmSignature } from "./ConfirmSignature";
import { ApproveSignArbitrary } from "./ApproveSignArbitrary";
import {
ApproveSignTx,
ConfirmSignLedgerTx,
ConfirmSignTx,
} from "./ApproveSignTx";
import { ConfirmSignature } from "./ConfirmSignArbitrary";

export enum Status {
Completed,
Expand All @@ -21,19 +22,20 @@ export enum Status {
}

export type ApprovalDetails = {
signer: string;
accountType: AccountType;
msgId: string;
txType: TxType;
tx: PendingTxDetails[];
};

export type SignatureDetails = {
export type SignArbitraryDetails = {
msgId: string;
signer: string;
};

export const Approvals: React.FC = () => {
const [details, setDetails] = useState<ApprovalDetails>();
const [signatureDetails, setSignatureDetails] = useState<SignatureDetails>();
const [signArbitraryDetails, setSignArbitraryDetails] =
useState<SignArbitraryDetails>();

return (
<Container
Expand All @@ -48,30 +50,32 @@ export const Approvals: React.FC = () => {
>
<Routes>
<Route
path={`${TopLevelRoute.ApproveTx}/:msgId/:type/:accountType`}
element={<ApproveTx setDetails={setDetails} details={details} />}
path={`${TopLevelRoute.ApproveSignTx}/:msgId/:accountType/:signer`}
element={<ApproveSignTx setDetails={setDetails} />}
/>
<Route
path={TopLevelRoute.ConfirmTx}
element={<ConfirmTx details={details} />}
path={TopLevelRoute.ConfirmSignTx}
element={<ConfirmSignTx details={details} />}
/>
<Route
path={TopLevelRoute.ConfirmLedgerTx}
element={<ConfirmLedgerTx details={details} />}
element={<ConfirmSignLedgerTx details={details} />}
/>
<Route
path={TopLevelRoute.ApproveConnection}
element={<ApproveConnection />}
/>
<Route
path={`${TopLevelRoute.ApproveSignature}/:signer`}
path={`${TopLevelRoute.ApproveSignArbitrary}/:signer`}
element={
<ApproveSignature setSignatureDetails={setSignatureDetails} />
<ApproveSignArbitrary
setSignArbitraryDetails={setSignArbitraryDetails}
/>
}
/>
<Route
path={TopLevelRoute.ConfirmSignature}
element={<ConfirmSignature details={signatureDetails} />}
path={TopLevelRoute.ConfirmSignArbitrary}
element={<ConfirmSignature details={signArbitraryDetails} />}
/>
</Routes>
</Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import { ActionButton, Alert, GapPatterns, Stack } from "@namada/components";
import { useSanitizedParams } from "@namada/hooks";
import { shortenAddress } from "@namada/utils";
import { PageHeader } from "App/Common";
import { SignatureDetails } from "Approvals/Approvals";
import { SignArbitraryDetails } from "Approvals/Approvals";
import { TopLevelRoute } from "Approvals/types";
import { RejectSignatureMsg } from "background/approvals";
import { RejectSignArbitraryMsg } from "background/approvals";
import { useQuery } from "hooks";
import { useRequester } from "hooks/useRequester";
import { Ports } from "router";
import { closeCurrentTab } from "utils";

type Props = {
setSignatureDetails: (details: SignatureDetails) => void;
setSignArbitraryDetails: (details: SignArbitraryDetails) => void;
};

export const ApproveSignature: React.FC<Props> = ({ setSignatureDetails }) => {
export const ApproveSignArbitrary: React.FC<Props> = ({
setSignArbitraryDetails,
}) => {
const navigate = useNavigate();
const params = useSanitizedParams();
const requester = useRequester();
Expand All @@ -27,8 +29,8 @@ export const ApproveSignature: React.FC<Props> = ({ setSignatureDetails }) => {

const handleApproveClick = useCallback((): void => {
if (signer) {
setSignatureDetails({ msgId, signer });
navigate(TopLevelRoute.ConfirmSignature);
setSignArbitraryDetails({ msgId, signer });
navigate(TopLevelRoute.ConfirmSignArbitrary);
}
}, [signer]);

Expand All @@ -37,7 +39,7 @@ export const ApproveSignature: React.FC<Props> = ({ setSignatureDetails }) => {
if (msgId) {
await requester.sendMessage(
Ports.Background,
new RejectSignatureMsg(msgId)
new RejectSignArbitraryMsg(msgId)
);
// Close tab
return await closeCurrentTab();
Expand All @@ -50,7 +52,7 @@ export const ApproveSignature: React.FC<Props> = ({ setSignatureDetails }) => {

return (
<Stack full gap={GapPatterns.TitleContent} className="text-white pt-4 pb-8">
<PageHeader title="Approve Signature Request" />
<PageHeader title="Approve Sign Arbitrary Request" />

{signer && (
<Alert type="warning">
Expand Down
Loading

0 comments on commit 18c94e2

Please sign in to comment.