Skip to content

Commit

Permalink
feat/677 - Support querying pending Tx within extension (#736)
Browse files Browse the repository at this point in the history
* feat: add msg support for querying pending tx storage

* feat: use approval details from requester query

* feat: update pending tx storage and props for approvals/submit

* feat: hooking up multiple tx to approvals views

* feat: signer should accept either single or multiple Tx

* fix: formatting
  • Loading branch information
jurevans authored Apr 22, 2024
1 parent 28841b9 commit b9bf688
Show file tree
Hide file tree
Showing 22 changed files with 391 additions and 326 deletions.
10 changes: 4 additions & 6 deletions apps/extension/src/Approvals/Approvals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Container } from "@namada/components";

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";
Expand All @@ -20,12 +21,9 @@ export enum Status {
}

export type ApprovalDetails = {
source: string;
msgId: string;
txType: TxType;
publicKey?: string;
target?: string;
nativeToken?: string;
tx: PendingTxDetails[];
};

export type SignatureDetails = {
Expand All @@ -50,8 +48,8 @@ export const Approvals: React.FC = () => {
>
<Routes>
<Route
path={`${TopLevelRoute.ApproveTx}/:type`}
element={<ApproveTx setDetails={setDetails} />}
path={`${TopLevelRoute.ApproveTx}/:msgId/:type/:accountType`}
element={<ApproveTx setDetails={setDetails} details={details} />}
/>
<Route
path={TopLevelRoute.ConfirmTx}
Expand Down
128 changes: 78 additions & 50 deletions apps/extension/src/Approvals/ApproveTx/ApproveTx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,60 @@ import { AccountType, Tokens } from "@namada/types";
import { shortenAddress } from "@namada/utils";
import { ApprovalDetails } from "Approvals/Approvals";
import { TopLevelRoute } from "Approvals/types";
import { RejectTxMsg } from "background/approvals";
import { useQuery } from "hooks";
import {
PendingTxDetails,
QueryPendingTxMsg,
RejectTxMsg,
} from "background/approvals";
import { ExtensionRequester } from "extension";
import { useRequester } from "hooks/useRequester";
import { Ports } from "router";
import { closeCurrentTab } from "utils";

type Props = {
setDetails: (details: ApprovalDetails) => void;
details?: ApprovalDetails;
};

export const ApproveTx: React.FC<Props> = ({ setDetails }) => {
const fetchPendingTxDetails = async (
requester: ExtensionRequester,
msgId: string
): Promise<PendingTxDetails[] | void> => {
return await requester.sendMessage(
Ports.Background,
new QueryPendingTxMsg(msgId)
);
};

export const ApproveTx: React.FC<Props> = ({ details, setDetails }) => {
const navigate = useNavigate();
const requester = useRequester();

// Parse URL params
const params = useSanitizedParams();
const txType = parseInt(params?.type || "0");

const query = useQuery();
const {
accountType,
msgId,
amount,
source,
target,
validator,
tokenAddress,
publicKey,
nativeToken,
} = query.getAll();

const tokenType =
Object.values(Tokens).find((token) => token.address === tokenAddress)
?.symbol || "NAM";
const accountType =
(params?.accountType as AccountType) || AccountType.PrivateKey;
const msgId = params?.msgId || "0";

useEffect(() => {
if (source && txType && msgId) {
setDetails({
source,
txType,
msgId,
publicKey,
target,
nativeToken,
fetchPendingTxDetails(requester, msgId)
.then((details) => {
if (!details) {
throw new Error(
`Failed to fetch Tx details - no transactions exists for ${msgId}`
);
}
// TODO: Handle array of approval details
setDetails({
txType,
msgId,
tx: details,
});
})
.catch((e) => {
console.error(`Could not fetch pending Tx with msgId = ${msgId}: ${e}`);
});
}
}, [source, publicKey, txType, target, msgId]);
}, [txType, msgId]);

const handleApproveClick = useCallback((): void => {
if (accountType === AccountType.Ledger) {
Expand Down Expand Up @@ -83,25 +91,45 @@ export const ApproveTx: React.FC<Props> = ({ setDetails }) => {
<strong>{TxTypeLabel[txType as TxType]}</strong> transaction?
</Alert>
<Stack gap={2}>
{source && (
<p className="text-xs">
Source: <strong>{shortenAddress(source)}</strong>
</p>
)}
{target && (
<p className="text-xs">
Target:
<strong>{shortenAddress(target)}</strong>
</p>
)}
{amount && (
<p className="text-xs">
Amount: {amount} {tokenType}
</p>
)}
{validator && (
<p className="text-xs">Validator: {shortenAddress(validator)}</p>
)}
{details?.tx.map((txDetails, i) => {
const { amount, source, target, publicKey, tokenAddress, validator } =
txDetails || {};
const tokenType =
Object.values(Tokens).find(
(token) => token.address === tokenAddress
)?.symbol || "NAM";

return (
<div key={i}>
{source && (
<p className="text-xs">
Source: <strong>{shortenAddress(source)}</strong>
</p>
)}
{target && (
<p className="text-xs">
Target:
<strong>{shortenAddress(target)}</strong>
</p>
)}
{amount && (
<p className="text-xs">
Amount: {amount} {tokenType}
</p>
)}
{publicKey && (
<p className="text-xs">
Public key: {shortenAddress(publicKey)}
</p>
)}
{validator && (
<p className="text-xs">
Validator: {shortenAddress(validator)}
</p>
)}
</div>
);
})}
</Stack>
<Stack gap={3} direction="horizontal">
<ActionButton onClick={handleApproveClick}>Approve</ActionButton>
Expand Down
15 changes: 9 additions & 6 deletions apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export const ConfirmLedgerTx: React.FC<Props> = ({ details }) => {
const [error, setError] = useState<string>();
const [status, setStatus] = useState<Status>();
const [statusInfo, setStatusInfo] = useState("");
const { source, msgId, publicKey, txType, nativeToken } = details || {};
const { msgId, txType } = details || {};
const { source, publicKey, nativeToken } = details?.tx[0] || {};

useEffect(() => {
if (status === Status.Completed) {
Expand Down Expand Up @@ -135,11 +136,13 @@ export const ConfirmLedgerTx: React.FC<Props> = ({ details }) => {
throw new Error("msgId was not provided!");
}

const { bytes, path } = await requester
.sendMessage(Ports.Background, new GetTxBytesMsg(txType, msgId, source))
.catch((e) => {
throw new Error(`Requester error: ${e}`);
});
const { bytes, path } = (
await requester
.sendMessage(Ports.Background, new GetTxBytesMsg(txType, msgId, source))
.catch((e) => {
throw new Error(`Requester error: ${e}`);
})
)[0];

setStatusInfo(`Review and approve ${txLabel} transaction on your Ledger`);

Expand Down
20 changes: 9 additions & 11 deletions apps/extension/src/Approvals/ApproveTx/ConfirmTx.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

import { SupportedTx, TxType, TxTypeLabel } from "@heliax/namada-sdk/web";
import { SupportedTx, TxTypeLabel } from "@heliax/namada-sdk/web";
import { ActionButton, Alert, Input, Stack } from "@namada/components";
import { shortenAddress } from "@namada/utils";
import { ApprovalDetails, Status } from "Approvals/Approvals";
import { SubmitApprovedTxMsg } from "background/approvals";
import { UnlockVaultMsg } from "background/vault";
Expand All @@ -17,7 +16,7 @@ type Props = {
};

export const ConfirmTx: React.FC<Props> = ({ details }) => {
const { source, msgId, txType } = details || {};
const { msgId, txType } = details || {};

const navigate = useNavigate();
const requester = useRequester();
Expand All @@ -27,10 +26,12 @@ export const ConfirmTx: React.FC<Props> = ({ details }) => {
const [statusInfo, setStatusInfo] = useState("");

const handleApproveTx = useCallback(async (): Promise<void> => {
if (!txType) {
// TODO: What would be a better handling of this? txType should be defined
throw new Error("txType should be defined");
}
setStatus(Status.Pending);
setStatusInfo(
`Decrypting keys and submitting ${TxTypeLabel[txType as TxType]}...`
);
setStatusInfo(`Decrypting keys and submitting ${TxTypeLabel[txType]}...`);

try {
if (!msgId) {
Expand Down Expand Up @@ -96,12 +97,9 @@ export const ConfirmTx: React.FC<Props> = ({ details }) => {
Try again
</Alert>
)}
{status !== (Status.Pending || Status.Completed) && source && (
{status !== (Status.Pending || Status.Completed) && (
<>
<Alert type="warning">
Decrypt keys for{" "}
<strong className="text-xs">{shortenAddress(source)}</strong>
</Alert>
<Alert type="warning">Verify your password to continue</Alert>
<Input
variant="Password"
label={"Password"}
Expand Down
8 changes: 6 additions & 2 deletions apps/extension/src/background/approvals/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ describe("approvals handler", () => {

const approveTxMsg = new ApproveTxMsg(
TxType.Bond,
"txMsg",
"specificMsg",
[
{
txMsg: "txMsg",
specificMsg: "specificMsg",
},
],
AccountType.Mnemonic
);
handler(env, approveTxMsg);
Expand Down
15 changes: 13 additions & 2 deletions apps/extension/src/background/approvals/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { Env, Handler, InternalHandler, Message } from "router";
import {
ConnectInterfaceResponseMsg,
QueryPendingTxMsg,
RejectSignatureMsg,
RejectTxMsg,
RevokeConnectionMsg,
Expand All @@ -27,6 +28,8 @@ export const getHandler: (service: ApprovalsService) => Handler = (service) => {
env,
msg as SubmitApprovedTxMsg
);
case QueryPendingTxMsg:
return handleQueryPendingTxMsg(service)(env, msg as QueryPendingTxMsg);
case IsConnectionApprovedMsg:
return handleIsConnectionApprovedMsg(service)(
env,
Expand Down Expand Up @@ -72,8 +75,8 @@ export const getHandler: (service: ApprovalsService) => Handler = (service) => {
const handleApproveTxMsg: (
service: ApprovalsService
) => InternalHandler<ApproveTxMsg> = (service) => {
return async (_, { txType, specificMsg, txMsg, accountType }) => {
return await service.approveTx(txType, specificMsg, txMsg, accountType);
return async (_, { txType, tx, accountType }) => {
return await service.approveTx(txType, tx, accountType);
};
};

Expand All @@ -93,6 +96,14 @@ const handleSubmitApprovedTxMsg: (
};
};

const handleQueryPendingTxMsg: (
service: ApprovalsService
) => InternalHandler<QueryPendingTxMsg> = (service) => {
return async (_, { msgId }) => {
return await service.queryPendingTx(msgId);
};
};

const handleIsConnectionApprovedMsg: (
service: ApprovalsService
) => InternalHandler<IsConnectionApprovedMsg> = (service) => {
Expand Down
2 changes: 2 additions & 0 deletions apps/extension/src/background/approvals/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { Router } from "router";
import {
ConnectInterfaceResponseMsg,
QueryPendingTxMsg,
RejectSignatureMsg,
RejectTxMsg,
RevokeConnectionMsg,
Expand All @@ -21,6 +22,7 @@ import { ApprovalsService } from "./service";
export function init(router: Router, service: ApprovalsService): void {
router.registerMessage(ApproveTxMsg);
router.registerMessage(RejectTxMsg);
router.registerMessage(QueryPendingTxMsg);
router.registerMessage(SubmitApprovedTxMsg);
router.registerMessage(ApproveSignArbitraryMsg);
router.registerMessage(RejectSignatureMsg);
Expand Down
Loading

0 comments on commit b9bf688

Please sign in to comment.