From 8ecc65b4be303f0e605b5856eeae861a0b0a3434 Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Thu, 20 Jul 2023 08:16:01 -0400 Subject: [PATCH 01/18] Implement ledger integration improvements Consolidate approval views for all tx types Hook up Ledger transfer Remove unused address from messages Consolidate tx build calls Clean up Message and component props --- apps/extension/src/Approvals/Approvals.tsx | 58 +++---- .../src/Approvals/ApproveBond/ConfirmBond.tsx | 109 ------------ .../src/Approvals/ApproveBond/index.ts | 3 - .../ApproveTransfer/ApproveTransfer.tsx | 89 ---------- .../ApproveTransfer/ConfirmLedgerTransfer.tsx | 103 ------------ .../ApproveTransfer/ConfirmTransfer.tsx | 122 -------------- .../src/Approvals/ApproveTransfer/index.ts | 3 - .../ApproveTx.tsx} | 60 ++++--- .../ConfirmLedgerTx.tsx} | 112 +++++++++---- .../src/Approvals/ApproveTx/ConfirmTx.tsx | 157 ++++++++++++++++++ .../src/Approvals/ApproveTx/index.ts | 3 + apps/extension/src/Approvals/types.ts | 25 ++- .../src/background/approvals/handler.ts | 4 +- .../src/background/approvals/messages.ts | 27 +-- .../src/background/approvals/service.ts | 47 ++++-- .../src/background/ledger/service.ts | 8 +- apps/extension/src/provider/Namada.ts | 6 +- apps/extension/src/provider/messages.ts | 3 +- apps/namada-interface/.prettierrc | 3 +- apps/namada-interface/src/schema/index.ts | 18 -- .../src/types/environment.d.ts | 29 ++-- packages/shared/lib/src/sdk/mod.rs | 135 ++++++++++----- packages/shared/lib/src/sdk/tx.rs | 7 + 23 files changed, 463 insertions(+), 668 deletions(-) delete mode 100644 apps/extension/src/Approvals/ApproveBond/ConfirmBond.tsx delete mode 100644 apps/extension/src/Approvals/ApproveBond/index.ts delete mode 100644 apps/extension/src/Approvals/ApproveTransfer/ApproveTransfer.tsx delete mode 100644 apps/extension/src/Approvals/ApproveTransfer/ConfirmLedgerTransfer.tsx delete mode 100644 apps/extension/src/Approvals/ApproveTransfer/ConfirmTransfer.tsx delete mode 100644 apps/extension/src/Approvals/ApproveTransfer/index.ts rename apps/extension/src/Approvals/{ApproveBond/ApproveBond.tsx => ApproveTx/ApproveTx.tsx} (62%) rename apps/extension/src/Approvals/{ApproveBond/ConfirmLedgerBond.tsx => ApproveTx/ConfirmLedgerTx.tsx} (63%) create mode 100644 apps/extension/src/Approvals/ApproveTx/ConfirmTx.tsx create mode 100644 apps/extension/src/Approvals/ApproveTx/index.ts delete mode 100644 apps/namada-interface/src/schema/index.ts diff --git a/apps/extension/src/Approvals/Approvals.tsx b/apps/extension/src/Approvals/Approvals.tsx index c39210bde..ec4ca67c7 100644 --- a/apps/extension/src/Approvals/Approvals.tsx +++ b/apps/extension/src/Approvals/Approvals.tsx @@ -3,6 +3,7 @@ import { ThemeProvider } from "styled-components"; import { Routes, Route } from "react-router-dom"; import { getTheme } from "@namada/utils"; +import { TxType } from "@namada/shared"; import { AppContainer, @@ -11,11 +12,11 @@ import { TopSection, Heading, } from "./Approvals.components"; -import { ApproveTransfer, ConfirmTransfer } from "./ApproveTransfer"; import { ApproveConnection } from "./ApproveConnection"; import { TopLevelRoute } from "Approvals/types"; -import { ConfirmLedgerTransfer } from "./ApproveTransfer/ConfirmLedgerTransfer"; -import { ApproveBond, ConfirmBond, ConfirmLedgerBond } from "./ApproveBond"; +import { ConfirmLedgerTx } from "./ApproveTx/ConfirmLedgerTx"; +import { ConfirmTx } from "./ApproveTx/ConfirmTx"; +import { ApproveTx } from "./ApproveTx/ApproveTx"; export enum Status { Completed, @@ -23,11 +24,17 @@ export enum Status { Failed, } +export type ApprovalDetails = { + source: string; + msgId: string; + txType: TxType; + publicKey?: string; + target?: string; +}; + export const Approvals: React.FC = () => { const theme = getTheme("dark"); - const [msgId, setMsgId] = useState(""); - const [address, setAddress] = useState(""); - const [publicKey, setPublicKey] = useState(""); + const [details, setDetails] = useState(); return ( @@ -39,44 +46,17 @@ export const Approvals: React.FC = () => { - } + path={`${TopLevelRoute.ApproveTx}/:type`} + element={} /> } + path={TopLevelRoute.ConfirmTx} + element={} /> } + path={TopLevelRoute.ConfirmLedgerTx} + element={} /> - - } - /> - } - /> - - } - /> - } diff --git a/apps/extension/src/Approvals/ApproveBond/ConfirmBond.tsx b/apps/extension/src/Approvals/ApproveBond/ConfirmBond.tsx deleted file mode 100644 index 88b44713e..000000000 --- a/apps/extension/src/Approvals/ApproveBond/ConfirmBond.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; - -import { - Button, - ButtonVariant, - Input, - InputVariants, -} from "@namada/components"; -import { shortenAddress } from "@namada/utils"; - -import { Status } from "Approvals/Approvals"; -import { - ApprovalContainer, - ButtonContainer, - InfoHeader, - InfoLoader, -} from "Approvals/Approvals.components"; -import { Ports } from "router"; -import { useRequester } from "hooks/useRequester"; -import { SubmitApprovedBondMsg } from "background/approvals"; -import { Address } from "App/Accounts/AccountListing.components"; -import { closeCurrentTab } from "utils"; - -type Props = { - msgId: string; - address: string; -}; - -export const ConfirmBond: React.FC = ({ msgId, address }) => { - const navigate = useNavigate(); - const requester = useRequester(); - const [password, setPassword] = useState(""); - const [error, setError] = useState(); - const [status, setStatus] = useState(); - const [statusInfo, setStatusInfo] = useState(""); - - const handleApproveBond = async (): Promise => { - setStatus(Status.Pending); - try { - // TODO: use executeUntil here! - setStatusInfo("Decrypting keys and submitting transfer..."); - await requester.sendMessage( - Ports.Background, - new SubmitApprovedBondMsg(msgId, address, password) - ); - setStatus(Status.Completed); - } catch (e) { - setError("Unable to authenticate Tx!"); - setStatus(Status.Failed); - } - setStatusInfo(""); - setStatus(Status.Completed); - return; - }; - - useEffect(() => { - (async () => { - if (status === Status.Completed) { - await closeCurrentTab(); - } - })(); - }, [status]); - - return ( - - {status === Status.Pending && ( - - -

{statusInfo}

-
- )} - {status === Status.Failed && ( -

- {error} -
- Try again -

- )} - {status !== (Status.Pending || Status.Completed) && ( - <> -
- Decrypt keys for
{shortenAddress(address)}
-
- setPassword(e.target.value)} - /> - - - - - - )} -
- ); -}; diff --git a/apps/extension/src/Approvals/ApproveBond/index.ts b/apps/extension/src/Approvals/ApproveBond/index.ts deleted file mode 100644 index e25c4c155..000000000 --- a/apps/extension/src/Approvals/ApproveBond/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./ApproveBond"; -export * from "./ConfirmBond"; -export * from "./ConfirmLedgerBond"; diff --git a/apps/extension/src/Approvals/ApproveTransfer/ApproveTransfer.tsx b/apps/extension/src/Approvals/ApproveTransfer/ApproveTransfer.tsx deleted file mode 100644 index 792fe5492..000000000 --- a/apps/extension/src/Approvals/ApproveTransfer/ApproveTransfer.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useNavigate } from "react-router-dom"; -import { useEffect } from "react"; - -import { Button, ButtonVariant } from "@namada/components"; -import { shortenAddress } from "@namada/utils"; -import { AccountType, Tokens } from "@namada/types"; - -import { useQuery } from "hooks"; -import { Address } from "App/Accounts/AccountListing.components"; -import { - ApprovalContainer, - ButtonContainer, -} from "Approvals/Approvals.components"; -import { TopLevelRoute } from "Approvals/types"; -import { Ports } from "router"; -import { RejectTxMsg } from "background/approvals"; -import { useRequester } from "hooks/useRequester"; -import { closeCurrentTab } from "utils"; - -type Props = { - setMsgId: (msgId: string) => void; - setAddress: (address: string) => void; -}; - -export const ApproveTransfer: React.FC = ({ setAddress, setMsgId }) => { - const navigate = useNavigate(); - const requester = useRequester(); - - const query = useQuery(); - // TODO: Get current parent account alias to display to user - const type = query.get("type") || ""; - const id = query.get("id") || ""; - const amount = query.get("amount") || ""; - const source = query.get("source") || ""; - const target = query.get("target") || ""; - const tokenAddress = query.get("token") || ""; - const tokenType = - Object.values(Tokens).find((token) => token.address === tokenAddress) - ?.symbol || ""; - - useEffect(() => { - if (source) { - setAddress(source); - } - }, [source]); - - const handleApproveClick = (): void => { - setMsgId(id); - if (type === AccountType.Ledger) { - return navigate(`${TopLevelRoute.ConfirmLedgerTransfer}`); - } - navigate(TopLevelRoute.ConfirmTransfer); - }; - - const handleReject = async (): Promise => { - try { - // TODO: use executeUntil here! - await requester.sendMessage(Ports.Background, new RejectTxMsg(id)); - - // Close tab - await closeCurrentTab(); - } catch (e) { - console.warn(e); - } - return; - }; - - return ( - -

Approve this Transaction?

-

Target: 

-
{shortenAddress(target)}
-

Source: 

-
{shortenAddress(source)}
-

- Amount: {amount} {tokenType} -

- - - - - -
- ); -}; diff --git a/apps/extension/src/Approvals/ApproveTransfer/ConfirmLedgerTransfer.tsx b/apps/extension/src/Approvals/ApproveTransfer/ConfirmLedgerTransfer.tsx deleted file mode 100644 index 9877b3f30..000000000 --- a/apps/extension/src/Approvals/ApproveTransfer/ConfirmLedgerTransfer.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { useCallback, useState } from "react"; -import { LedgerError } from "@namada/ledger-namada"; -import { toBase64 } from "@cosmjs/encoding"; - -import { Button, ButtonVariant } from "@namada/components"; - -import { Ledger } from "background/ledger"; -import { - GetTransferBytesMsg, - SubmitSignedTransferMsg, -} from "background/ledger/messages"; -import { Ports } from "router"; -import { closeCurrentTab } from "utils"; -import { useRequester } from "hooks/useRequester"; -import { Status } from "Approvals/Approvals"; -import { - ApprovalContainer, - ButtonContainer, -} from "Approvals/Approvals.components"; - -type Props = { - msgId: string; -}; - -export const ConfirmLedgerTransfer: React.FC = ({ msgId }) => { - const requester = useRequester(); - const [error, setError] = useState(); - const [status, setStatus] = useState(); - - const submitTransfer = async (): Promise => { - setStatus(Status.Pending); - const ledger = await Ledger.init(); - - try { - // Constuct tx bytes from SDK - const { bytes, path } = await requester.sendMessage( - Ports.Background, - new GetTransferBytesMsg(msgId) - ); - - // Sign with Ledger - const signatures = await ledger.sign(bytes, path); - const { errorMessage, returnCode } = signatures; - - // Close transport so that it may be re-opened on a subsequent attempt (due to error) - await ledger.closeTransport(); - - if (returnCode !== LedgerError.NoErrors) { - setError(errorMessage); - return setStatus(Status.Failed); - } - - // Submit signatures for tx - await requester.sendMessage( - Ports.Background, - new SubmitSignedTransferMsg(msgId, toBase64(bytes), signatures) - ); - setStatus(Status.Completed); - } catch (e) { - console.warn(e); - const ledgerErrors = await ledger.queryErrors(); - setError(ledgerErrors); - setStatus(Status.Failed); - } - }; - - const handleCloseTab = useCallback(async (): Promise => { - await closeCurrentTab(); - }, []); - - return ( - - {status === Status.Failed && ( -

- {error} -
- Try again -

- )} - {status === Status.Pending &&

Submitting transfer...

} - {status !== Status.Pending && status !== Status.Completed && ( - <> -

Make sure your Ledger is unlocked, and click "Submit"

- - - - - )} - {status === Status.Completed && ( - <> -

Success! You may close this window.

- - - - - )} -
- ); -}; diff --git a/apps/extension/src/Approvals/ApproveTransfer/ConfirmTransfer.tsx b/apps/extension/src/Approvals/ApproveTransfer/ConfirmTransfer.tsx deleted file mode 100644 index ca9c92de1..000000000 --- a/apps/extension/src/Approvals/ApproveTransfer/ConfirmTransfer.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; - -import { Button, ButtonVariant, Input, InputVariants } from "@namada/components"; -import { shortenAddress } from "@namada/utils"; - -import { Status } from "Approvals/Approvals"; -import { - ApprovalContainer, - ButtonContainer, - InfoHeader, - InfoLoader, -} from "Approvals/Approvals.components"; -import { Ports } from "router"; -import { useRequester } from "hooks/useRequester"; -import { SubmitApprovedTransferMsg } from "background/approvals"; -import { Address } from "App/Accounts/AccountListing.components"; -import { closeCurrentTab } from "utils"; -import { FetchAndStoreMaspParamsMsg, HasMaspParamsMsg } from "provider"; - -type Props = { - msgId: string; - address: string; -}; - -export const ConfirmTransfer: React.FC = ({ msgId, address }) => { - const navigate = useNavigate(); - const requester = useRequester(); - const [password, setPassword] = useState(""); - const [error, setError] = useState(); - const [status, setStatus] = useState(); - const [statusInfo, setStatusInfo] = useState(""); - - const handleApproveTransfer = async (): Promise => { - setStatus(Status.Pending); - const hasMaspParams = await requester.sendMessage( - Ports.Background, - new HasMaspParamsMsg() - ); - - if (!hasMaspParams) { - setStatusInfo("Fetching MASP parameters..."); - try { - await requester.sendMessage( - Ports.Background, - new FetchAndStoreMaspParamsMsg() - ); - } catch (e) { - setError(`Fetching MASP parameters failed: ${e}`); - setStatus(Status.Failed); - } - } - try { - // TODO: use executeUntil here! - setStatusInfo("Decrypting keys and submitting transfer..."); - await requester.sendMessage( - Ports.Background, - new SubmitApprovedTransferMsg(msgId, address, password) - ); - setStatus(Status.Completed); - } catch (e) { - setError("Unable to authenticate Tx!"); - setStatus(Status.Failed); - } - setStatusInfo(""); - setStatus(Status.Completed); - return; - }; - - useEffect(() => { - (async () => { - if (status === Status.Completed) { - await closeCurrentTab(); - } - })(); - }, [status]); - - return ( - - {status === Status.Pending && ( - - -

{statusInfo}

-
- )} - {status === Status.Failed && ( -

- {error} -
- Try again -

- )} - {status !== (Status.Pending || Status.Completed) && ( - <> -
- Decrypt keys for
{shortenAddress(address)}
-
- setPassword(e.target.value)} - /> - - - - - - )} -
- ); -}; diff --git a/apps/extension/src/Approvals/ApproveTransfer/index.ts b/apps/extension/src/Approvals/ApproveTransfer/index.ts deleted file mode 100644 index 591a51410..000000000 --- a/apps/extension/src/Approvals/ApproveTransfer/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./ApproveTransfer"; -export * from "./ConfirmTransfer"; -export * from "./ConfirmLedgerTransfer"; diff --git a/apps/extension/src/Approvals/ApproveBond/ApproveBond.tsx b/apps/extension/src/Approvals/ApproveTx/ApproveTx.tsx similarity index 62% rename from apps/extension/src/Approvals/ApproveBond/ApproveBond.tsx rename to apps/extension/src/Approvals/ApproveTx/ApproveTx.tsx index d7dd0c9e8..5890d7ea4 100644 --- a/apps/extension/src/Approvals/ApproveBond/ApproveBond.tsx +++ b/apps/extension/src/Approvals/ApproveTx/ApproveTx.tsx @@ -4,6 +4,7 @@ import { useCallback, useEffect } from "react"; import { Button, ButtonVariant } from "@namada/components"; import { shortenAddress } from "@namada/utils"; import { AccountType, Tokens } from "@namada/types"; +import { TxType } from "@namada/shared"; import { useQuery } from "hooks"; import { Address } from "App/Accounts/AccountListing.components"; @@ -11,31 +12,31 @@ import { ApprovalContainer, ButtonContainer, } from "Approvals/Approvals.components"; -import { TopLevelRoute } from "Approvals/types"; +import { TopLevelRoute, TxTypeLabel } from "Approvals/types"; import { Ports } from "router"; import { RejectTxMsg } from "background/approvals"; import { useRequester } from "hooks/useRequester"; import { closeCurrentTab } from "utils"; +import { useSanitizedParams } from "@namada/hooks"; +import { ApprovalDetails } from "Approvals/Approvals"; type Props = { - setAddress: (address: string) => void; - setMsgId: (msgId: string) => void; - setPublicKey: (publicKey: string) => void; + setDetails: (details: ApprovalDetails) => void; }; -export const ApproveBond: React.FC = ({ - setAddress, - setMsgId, - setPublicKey, -}) => { +export const ApproveTx: React.FC = ({ setDetails }) => { const navigate = useNavigate(); const requester = useRequester(); + const params = useSanitizedParams(); + const txType = parseInt(params?.type || "0"); + const query = useQuery(); - const type = query.get("type") || ""; - const id = query.get("id") || ""; + const accountType = query.get("accountType") || ""; + const msgId = query.get("id") || ""; const amount = query.get("amount") || ""; const source = query.get("source") || ""; + const target = query.get("target") || ""; const tokenAddress = query.get("token") || ""; const tokenType = Object.values(Tokens).find((token) => token.address === tokenAddress) @@ -43,26 +44,26 @@ export const ApproveBond: React.FC = ({ const publicKey = query.get("publicKey") || ""; useEffect(() => { - if (source) { - setAddress(source); - } - if (publicKey) { - setPublicKey(publicKey); - } - }, [source, publicKey]); + setDetails({ + source, + txType, + msgId, + publicKey, + target, + }); + }, [source, publicKey, txType, target, msgId]); const handleApproveClick = (): void => { - setMsgId(id); - if (type === AccountType.Ledger) { - return navigate(`${TopLevelRoute.ConfirmLedgerBond}`); + if (accountType === AccountType.Ledger) { + return navigate(`${TopLevelRoute.ConfirmLedgerTx}`); } - navigate(TopLevelRoute.ConfirmBond); + navigate(TopLevelRoute.ConfirmTx); }; const handleReject = useCallback(async (): Promise => { try { // TODO: use executeUntil here! - await requester.sendMessage(Ports.Background, new RejectTxMsg(id)); + await requester.sendMessage(Ports.Background, new RejectTxMsg(msgId)); // Close tab await closeCurrentTab(); @@ -70,13 +71,22 @@ export const ApproveBond: React.FC = ({ console.warn(e); } return; - }, [id]); + }, [msgId]); return ( -

Approve this Bond?

+

+ Approve this {TxTypeLabel[txType as TxType]} + transaction? +

Source: 

{shortenAddress(source)}
+ {target && ( + <> +

Target: 

+
{shortenAddress(target)}
+ + )}

Amount: {amount} {tokenType}

diff --git a/apps/extension/src/Approvals/ApproveBond/ConfirmLedgerBond.tsx b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx similarity index 63% rename from apps/extension/src/Approvals/ApproveBond/ConfirmLedgerBond.tsx rename to apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx index d1581f714..b431e95aa 100644 --- a/apps/extension/src/Approvals/ApproveBond/ConfirmLedgerBond.tsx +++ b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx @@ -2,49 +2,49 @@ import { useCallback, useState } from "react"; import { toBase64 } from "@cosmjs/encoding"; import BigNumber from "bignumber.js"; -import { LedgerError } from "@namada/ledger-namada"; +import { LedgerError, ResponseSign } from "@namada/ledger-namada"; import { Button, ButtonVariant } from "@namada/components"; import { defaultChainId as chainId } from "@namada/chains"; +import { TxType } from "@namada/shared"; +import { + Message, + RevealPKProps, + SubmitRevealPKMsgValue, + Tokens, +} from "@namada/types"; import { Ledger } from "background/ledger"; import { GetBondBytesMsg, + GetTransferBytesMsg, GetRevealPKBytesMsg, SubmitSignedBondMsg, SubmitSignedRevealPKMsg, + SubmitSignedTransferMsg, } from "background/ledger/messages"; import { Ports } from "router"; import { closeCurrentTab } from "utils"; import { useRequester } from "hooks/useRequester"; -import { Status } from "Approvals/Approvals"; +import { ApprovalDetails, Status } from "Approvals/Approvals"; import { ApprovalContainer, ButtonContainer, } from "Approvals/Approvals.components"; import { InfoHeader, InfoLoader } from "Approvals/Approvals.components"; -import { - Message, - RevealPKProps, - SubmitRevealPKMsgValue, - Tokens, -} from "@namada/types"; + import { QueryPublicKeyMsg } from "background/keyring"; +import { TxTypeLabel } from "Approvals/types"; type Props = { - address: string; - msgId: string; - publicKey: string; + details?: ApprovalDetails; }; -export const ConfirmLedgerBond: React.FC = ({ - address, - msgId, - publicKey, -}) => { +export const ConfirmLedgerTx: React.FC = ({ details }) => { const requester = useRequester(); const [error, setError] = useState(); const [status, setStatus] = useState(); const [statusInfo, setStatusInfo] = useState(""); + const { source, msgId = "", publicKey, txType } = details || {}; const revealPk = async (publicKey: string): Promise => { const revealPKArgs: RevealPKProps = { @@ -101,36 +101,81 @@ export const ConfirmLedgerBond: React.FC = ({ ); }; - const submitBond = async (): Promise => { + const handleSubmitTx = useCallback(async (): Promise => { setStatus(Status.Pending); setStatusInfo("Querying for public key on chain..."); - const pk = await queryPublicKey(address); + if (source && publicKey) { + const pk = await queryPublicKey(source); - if (!pk) { - setStatusInfo( - "Public key not found! Review and approve reveal pk on your Ledger" - ); - await revealPk(publicKey); + if (!pk) { + setStatusInfo( + "Public key not found! Review and approve reveal pk on your Ledger" + ); + await revealPk(publicKey); + } + + return await submitTx(); } + }, [source, publicKey]); + + const getBytesByType = async ( + type?: TxType + ): Promise<{ bytes: Uint8Array; path: string }> => { + switch (type) { + case TxType.Bond: + return await requester.sendMessage( + Ports.Background, + new GetBondBytesMsg(msgId) + ); + case TxType.Transfer: + return await requester.sendMessage( + Ports.Background, + new GetTransferBytesMsg(msgId) + ); + default: + throw new Error("Invalid transaction type!"); + } + }; + + // TODO: This will not be necessary when `submit_signed_tx` is implemented! + const submitByType = async ( + bytes: Uint8Array, + signatures: ResponseSign, + type?: TxType + ): Promise => { + switch (type) { + case TxType.Bond: + return await requester.sendMessage( + Ports.Background, + new SubmitSignedBondMsg(msgId, toBase64(bytes), signatures) + ); + case TxType.Transfer: + return await requester.sendMessage( + Ports.Background, + new SubmitSignedTransferMsg(msgId, toBase64(bytes), signatures) + ); + default: + throw new Error("Invalid transaction type!"); + } + }; + const submitTx = async (): Promise => { // Open ledger transport const ledger = await Ledger.init(); + const txLabel = TxTypeLabel[txType as TxType]; try { // Constuct tx bytes from SDK - const { bytes, path } = await requester.sendMessage( - Ports.Background, - new GetBondBytesMsg(msgId) - ); + const { bytes, path } = await getBytesByType(txType); + setStatusInfo(`Review and approve ${txLabel} transaction on your Ledger`); - setStatusInfo("Review and approve bond transaction on your Ledger"); // Sign with Ledger const signatures = await ledger.sign(bytes, path); const { errorMessage, returnCode } = signatures; if (returnCode !== LedgerError.NoErrors) { - console.warn("Bond sign errors encountered, exiting: ", { + console.warn(`${txLabel} signing errors encountered, exiting: `, { returnCode, errorMessage, }); @@ -139,11 +184,8 @@ export const ConfirmLedgerBond: React.FC = ({ } // Submit signatures for tx - setStatusInfo("Submitting bond transaction..."); - await requester.sendMessage( - Ports.Background, - new SubmitSignedBondMsg(msgId, toBase64(bytes), signatures) - ); + setStatusInfo(`Submitting ${txLabel} transaction...`); + await submitByType(bytes, signatures, txType); setStatus(Status.Completed); } catch (e) { console.warn(e); @@ -175,7 +217,7 @@ export const ConfirmLedgerBond: React.FC = ({ <>

Make sure your Ledger is unlocked, and click "Submit"

- diff --git a/apps/extension/src/Approvals/ApproveTx/ConfirmTx.tsx b/apps/extension/src/Approvals/ApproveTx/ConfirmTx.tsx new file mode 100644 index 000000000..811515e85 --- /dev/null +++ b/apps/extension/src/Approvals/ApproveTx/ConfirmTx.tsx @@ -0,0 +1,157 @@ +import { useCallback, useContext, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import { + Button, + ButtonVariant, + Input, + InputVariants, +} from "@namada/components"; +import { shortenAddress } from "@namada/utils"; +import { TxType } from "@namada/shared"; + +import { ApprovalDetails, Status } from "Approvals/Approvals"; +import { + ApprovalContainer, + ButtonContainer, + InfoHeader, + InfoLoader, +} from "Approvals/Approvals.components"; +import { Ports } from "router"; +import { useRequester } from "hooks/useRequester"; +import { + SubmitApprovedBondMsg, + SubmitApprovedTransferMsg, + SubmitApprovedUnbondMsg, +} from "background/approvals"; +import { Address } from "App/Accounts/AccountListing.components"; +import { closeCurrentTab } from "utils"; +import { FetchAndStoreMaspParamsMsg, HasMaspParamsMsg } from "provider"; +import { TxTypeLabel } from "Approvals/types"; + +type Props = { + details?: ApprovalDetails; +}; + +export const ConfirmTx: React.FC = ({ details }) => { + const { source = "", msgId = "", txType } = details || {}; + const navigate = useNavigate(); + const requester = useRequester(); + const [password, setPassword] = useState(""); + const [error, setError] = useState(); + const [status, setStatus] = useState(); + const [statusInfo, setStatusInfo] = useState(""); + + const handleApproveTx = useCallback(async (): Promise => { + setStatus(Status.Pending); + setStatusInfo( + `Decrypting keys and submitting ${TxTypeLabel[txType as TxType]}...` + ); + + switch (txType) { + case TxType.Bond: { + await requester.sendMessage( + Ports.Background, + new SubmitApprovedBondMsg(msgId, password) + ); + setStatusInfo(""); + setStatus(Status.Completed); + break; + } + case TxType.Transfer: { + const hasMaspParams = await requester.sendMessage( + Ports.Background, + new HasMaspParamsMsg() + ); + + if (!hasMaspParams) { + setStatusInfo("Fetching MASP parameters..."); + try { + await requester.sendMessage( + Ports.Background, + new FetchAndStoreMaspParamsMsg() + ); + } catch (e) { + setError(`Fetching MASP parameters failed: ${e}`); + setStatus(Status.Failed); + } + } + try { + await requester.sendMessage( + Ports.Background, + new SubmitApprovedTransferMsg(msgId, password) + ); + setStatusInfo(""); + setStatus(Status.Completed); + } catch (e) { + console.info(e); + setError(`${e}`); + setStatus(Status.Failed); + } + break; + } + case TxType.Unbond: { + await requester.sendMessage( + Ports.Background, + new SubmitApprovedUnbondMsg(msgId, password) + ); + setStatusInfo(""); + setStatus(Status.Completed); + break; + } + } + }, [password]); + + useEffect(() => { + (async () => { + if (status === Status.Completed) { + await closeCurrentTab(); + } + })(); + }, [status]); + + return ( + + {status === Status.Pending && ( + + +

{statusInfo}

+
+ )} + {status === Status.Failed && ( +

+ {error} +
+ Try again +

+ )} + {status !== (Status.Pending || Status.Completed) && ( + <> +
+ Decrypt keys for
{shortenAddress(source)}
+
+ setPassword(e.target.value)} + /> + + + + + + )} +
+ ); +}; diff --git a/apps/extension/src/Approvals/ApproveTx/index.ts b/apps/extension/src/Approvals/ApproveTx/index.ts new file mode 100644 index 000000000..b5936e7f7 --- /dev/null +++ b/apps/extension/src/Approvals/ApproveTx/index.ts @@ -0,0 +1,3 @@ +export * from "./ApproveTx"; +export * from "./ConfirmTx"; +export * from "./ConfirmLedgerTx"; diff --git a/apps/extension/src/Approvals/types.ts b/apps/extension/src/Approvals/types.ts index 700fb022a..1c2313334 100644 --- a/apps/extension/src/Approvals/types.ts +++ b/apps/extension/src/Approvals/types.ts @@ -1,14 +1,21 @@ +import { TxType } from "@namada/shared"; + export enum TopLevelRoute { Default = "/", - ApproveConnection = "/approve-connection", - // Transfer - ApproveTransfer = "/approve-transfer", - ConfirmTransfer = "/confirm-transfer", - ConfirmLedgerTransfer = "/confirm-ledger-transfer", + // Connection approval + ApproveConnection = "/approve-connection", - // Bond - ApproveBond = "/approve-bond", - ConfirmBond = "/confirm-bond", - ConfirmLedgerBond = "/confirm-ledger-bond", + // Transaction approval + ApproveTx = "/approve-tx", + ConfirmTx = "/confirm-tx", + ConfirmLedgerTx = "/confirm-ledger-tx", } + +export const TxTypeLabel: Record = { + [TxType.Bond]: "bond", + [TxType.Unbond]: "unbond", + [TxType.Transfer]: "transfer", + [TxType.Withdraw]: "withdraw", + [TxType.RevealPK]: "reveal-pk", +}; diff --git a/apps/extension/src/background/approvals/handler.ts b/apps/extension/src/background/approvals/handler.ts index 653d92b81..3e8220881 100644 --- a/apps/extension/src/background/approvals/handler.ts +++ b/apps/extension/src/background/approvals/handler.ts @@ -71,8 +71,8 @@ const handleSubmitApprovedTransferMsg: ( const handleApproveBondMsg: ( service: ApprovalsService ) => InternalHandler = (service) => { - return async (_, { txMsg, accountType, publicKey }) => { - return await service.approveBond(txMsg, accountType, publicKey); + return async (_, { txMsg, accountType }) => { + return await service.approveBond(txMsg, accountType); }; }; diff --git a/apps/extension/src/background/approvals/messages.ts b/apps/extension/src/background/approvals/messages.ts index 43549e9b4..0f7e359a5 100644 --- a/apps/extension/src/background/approvals/messages.ts +++ b/apps/extension/src/background/approvals/messages.ts @@ -38,11 +38,7 @@ export class SubmitApprovedTransferMsg extends Message { return MessageType.SubmitApprovedTransfer; } - constructor( - public readonly msgId: string, - public readonly address: string, - public readonly password: string - ) { + constructor(public readonly msgId: string, public readonly password: string) { super(); } @@ -50,9 +46,6 @@ export class SubmitApprovedTransferMsg extends Message { if (!this.msgId) { throw new Error("msgId must not be empty!"); } - if (!this.address) { - throw new Error("address must not be empty!"); - } if (!this.password) { throw new Error( "Password is required to submitTx for this type of account!" @@ -76,11 +69,7 @@ export class SubmitApprovedBondMsg extends Message { return MessageType.SubmitApprovedBond; } - constructor( - public readonly msgId: string, - public readonly address: string, - public readonly password: string - ) { + constructor(public readonly msgId: string, public readonly password: string) { super(); } @@ -88,9 +77,6 @@ export class SubmitApprovedBondMsg extends Message { if (!this.msgId) { throw new Error("msgId must not be empty!"); } - if (!this.address) { - throw new Error("address must not be empty!"); - } if (!this.password) { throw new Error("Password is required to submit bond tx!"); } @@ -112,11 +98,7 @@ export class SubmitApprovedUnbondMsg extends Message { return MessageType.SubmitApprovedUnbond; } - constructor( - public readonly msgId: string, - public readonly address: string, - public readonly password: string - ) { + constructor(public readonly msgId: string, public readonly password: string) { super(); } @@ -124,9 +106,6 @@ export class SubmitApprovedUnbondMsg extends Message { if (!this.msgId) { throw new Error("msgId must not be empty!"); } - if (!this.address) { - throw new Error("address must not be empty!"); - } if (!this.password) { throw new Error("Password is required to submit unbond tx!"); } diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index 43d1050ca..b2fe3b9d1 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -9,6 +9,7 @@ import { SubmitBondMsgValue, TransferMsgValue, } from "@namada/types"; +import { TxType } from "@namada/shared"; import { KVStore } from "@namada/storage"; import { KeyRingService, TabStore } from "background/keyring"; @@ -21,7 +22,7 @@ export class ApprovalsService { protected readonly connectedTabsStore: KVStore, protected readonly keyRingService: KeyRingService, protected readonly ledgerService: LedgerService - ) { } + ) {} // Deserialize transfer details and prompt user async approveTransfer(txMsg: string, type?: AccountType): Promise { @@ -31,11 +32,17 @@ export class ApprovalsService { // Decode tx details and launch approval screen const txDetails = deserialize(txMsgBuffer, TransferMsgValue); - const { source, target, token, amount: amountBN } = txDetails; + const { + source, + target, + token, + amount: amountBN, + tx: { publicKey = "" }, + } = txDetails; const amount = new BigNumber(amountBN.toString()); - const baseUrl = `${browser.runtime.getURL( - "approvals.html" - )}#/approve-transfer`; + const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ + TxType.Transfer + }`; const url = paramsToUrl(baseUrl, { id, @@ -43,18 +50,15 @@ export class ApprovalsService { target, token, amount: amount.toString(), - type: type as string, + accountType: type as string, + publicKey, }); this._launchApprovalWindow(url); } // Deserialize bond details and prompt user - async approveBond( - txMsg: string, - type: AccountType, - publicKey?: string - ): Promise { + async approveBond(txMsg: string, type: AccountType): Promise { const txMsgBuffer = Buffer.from(fromBase64(txMsg)); const id = uuid(); await this.txStore.set(id, txMsg); @@ -62,17 +66,24 @@ export class ApprovalsService { // Decode tx details and launch approval screen const txDetails = deserialize(txMsgBuffer, SubmitBondMsgValue); - const { source, nativeToken: token, amount: amountBN } = txDetails; + const { + source, + nativeToken: token, + amount: amountBN, + tx: { publicKey = "" }, + } = txDetails; const amount = new BigNumber(amountBN.toString()); - const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-bond`; + const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ + TxType.Bond + }`; const url = paramsToUrl(baseUrl, { id, source, token, amount: amount.toString(), - publicKey: publicKey || "", - type: type as string, + publicKey, + accountType: type as string, }); this._launchApprovalWindow(url); @@ -91,14 +102,16 @@ export class ApprovalsService { const { source, nativeToken, amount: amountBN } = txDetails; const amount = new BigNumber(amountBN.toString()); // TODO: This query should include perhaps a "type" indicating whether it's a bond or unbond tx: - const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-bond`; + const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ + TxType.Unbond + }`; const url = paramsToUrl(baseUrl, { id, source, token: nativeToken, amount: amount.toString(), - type: type as string, + accountType: type as string, }); this._launchApprovalWindow(url); diff --git a/apps/extension/src/background/ledger/service.ts b/apps/extension/src/background/ledger/service.ts index ebb62fd82..1c3ac16ed 100644 --- a/apps/extension/src/background/ledger/service.ts +++ b/apps/extension/src/background/ledger/service.ts @@ -9,7 +9,7 @@ import { TransferMsgValue, } from "@namada/types"; import { ResponseSign } from "@namada/ledger-namada"; -import { Sdk } from "@namada/shared"; +import { Sdk, TxType } from "@namada/shared"; import { IStore, KVStore, Store } from "@namada/storage"; import { chains } from "@namada/chains"; import { makeBip44Path } from "@namada/utils"; @@ -62,7 +62,7 @@ export class LedgerService { throw new Error(`Ledger account not found for ${publicKey}`); } - const bytes = await this.sdk.build_reveal_pk(fromBase64(txMsg)); + const bytes = await this.sdk.build_tx(TxType.RevealPK, fromBase64(txMsg)); const path = makeBip44Path(coinType, account.path); return { bytes, path }; @@ -119,7 +119,7 @@ export class LedgerService { throw new Error(`Ledger account not found for ${source}`); } - const bytes = await this.sdk.build_transfer(fromBase64(txMsg)); + const bytes = await this.sdk.build_tx(TxType.Transfer, fromBase64(txMsg)); const path = makeBip44Path(coinType, account.path); return { bytes, path }; @@ -187,7 +187,7 @@ export class LedgerService { throw new Error(`Ledger account not found for ${source}`); } - const bytes = await this.sdk.build_bond(fromBase64(txMsg)); + const bytes = await this.sdk.build_tx(TxType.Bond, fromBase64(txMsg)); const path = makeBip44Path(coinType, account.path); return { bytes, path }; diff --git a/apps/extension/src/provider/Namada.ts b/apps/extension/src/provider/Namada.ts index 2222d6f87..123c5dc7b 100644 --- a/apps/extension/src/provider/Namada.ts +++ b/apps/extension/src/provider/Namada.ts @@ -27,7 +27,7 @@ export class Namada implements INamada { constructor( private readonly _version: string, protected readonly requester?: MessageRequester - ) { } + ) {} public async connect(chainId: string): Promise { return await this.requester?.sendMessage( @@ -94,10 +94,10 @@ export class Namada implements INamada { type: AccountType; publicKey?: string; }): Promise { - const { txMsg, type, publicKey } = props; + const { txMsg, type } = props; return await this.requester?.sendMessage( Ports.Background, - new ApproveBondMsg(txMsg, type, publicKey) + new ApproveBondMsg(txMsg, type) ); } diff --git a/apps/extension/src/provider/messages.ts b/apps/extension/src/provider/messages.ts index 43ce2f412..e295af2d0 100644 --- a/apps/extension/src/provider/messages.ts +++ b/apps/extension/src/provider/messages.ts @@ -295,8 +295,7 @@ export class ApproveBondMsg extends Message { constructor( public readonly txMsg: string, - public readonly accountType: AccountType, - public readonly publicKey?: string + public readonly accountType: AccountType ) { super(); } diff --git a/apps/namada-interface/.prettierrc b/apps/namada-interface/.prettierrc index f0eb61e0f..193626a11 100644 --- a/apps/namada-interface/.prettierrc +++ b/apps/namada-interface/.prettierrc @@ -2,5 +2,6 @@ "trailingComma": "es5", "tabWidth": 2, "semi": true, - "singleQuote": false + "singleQuote": false, + "bracketSpacing": false } diff --git a/apps/namada-interface/src/schema/index.ts b/apps/namada-interface/src/schema/index.ts deleted file mode 100644 index 6259ebc1b..000000000 --- a/apps/namada-interface/src/schema/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import BN from "bn.js"; - -export class TokenAmount { - micro: BN; - constructor(properties: { micro: BN }) { - this.micro = new BN(properties.micro); - } -} - -export const schemaAmount = new Map([ - [ - TokenAmount, - { - kind: "struct", - fields: [["micro", "u64"]], - }, - ], -]); diff --git a/apps/namada-interface/src/types/environment.d.ts b/apps/namada-interface/src/types/environment.d.ts index 36537b5a3..ec60be9bf 100644 --- a/apps/namada-interface/src/types/environment.d.ts +++ b/apps/namada-interface/src/types/environment.d.ts @@ -4,27 +4,18 @@ declare global { NODE_ENV: "development" | "production"; REACT_APP_LOCAL?: "true" | "false"; - // Default ledger - REACT_APP_LEDGER_URL?: string; - REACT_APP_LEDGER_PORT?: string; - REACT_APP_CHAIN_ID?: string; - REACT_APP_FAUCET?: string; + REACT_APP_NAMADA_ALIAS?: string; + REACT_APP_NAMADA_CHAIN_ID?: string; + REACT_APP_NAMADA_URL?: string; + REACT_APP_NAMADA_BECH32_PREFIX?: string; - // IBC Chain A - REACT_APP_CHAIN_A_ALIAS?: string; - REACT_APP_CHAIN_A_ID?: string; - REACT_APP_CHAIN_A_URL?: string; - REACT_APP_CHAIN_A_PORT?: string; - REACT_APP_CHAIN_A_FAUCET?: string; - REACT_APP_CHAIN_A_PORT_ID?: string; + REACT_APP_COSMOS_ALIAS?: string; + REACT_APP_COSMOS_CHAIN_ID?: string; + REACT_APP_COSMOS_CHAIN_URL?: string; - // IBC Chain B - REACT_APP_CHAIN_B_ALIAS?: string; - REACT_APP_CHAIN_B_ID?: string; - REACT_APP_CHAIN_B_URL?: string; - REACT_APP_CHAIN_B_PORT?: string; - REACT_APP_CHAIN_B_FAUCET?: string; - REACT_APP_CHAIN_B_PORT_ID?: string; + REACT_APP_OSMOSIS_ALIAS?: string; + REACT_APP_OSMOSIS_CHAIN_ID?: string; + REACT_APP_OSMOSIS_URL?: string; // CoinGecko REACT_APP_API_URL?: string; diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 1004271ed..1cb61de3f 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -22,6 +22,16 @@ mod signature; mod tx; mod wallet; +#[wasm_bindgen] +#[derive(Copy, Clone, Debug)] +pub enum TxType { + Bond = 0, + Unbond = 1, + Withdraw = 2, + Transfer = 3, + RevealPK = 4, +} + // Require that a public key is present fn validate_pk(pk: Option) -> Result { match pk { @@ -197,48 +207,68 @@ impl Sdk { Ok(()) } - /// Contruct transfer data for external signers, returns byte array - pub async fn build_transfer(&mut self, tx_msg: &[u8]) -> Result { - let args = tx::transfer_tx_args(tx_msg, None, None)?; - - let transfer = namada::ledger::tx::build_transfer( - &self.client, - &mut self.wallet, - &mut self.shielded_ctx, - args.clone(), - ) - .await - .map_err(JsError::from)?; - - let bytes = transfer.0.try_to_vec().map_err(JsError::from)?; - - to_js_result(bytes) - } - - /// Contruct bond data for external signers, returns byte array - pub async fn build_bond(&mut self, tx_msg: &[u8]) -> Result { - let args = tx::bond_tx_args(tx_msg, None)?; - - let bond = namada::ledger::tx::build_bond(&self.client, &mut self.wallet, args.clone()) - .await - .map_err(JsError::from)?; - - let bytes = bond.0.try_to_vec().map_err(JsError::from)?; - - to_js_result(bytes) - } - - /// Contruct unbond data for external signers, returns byte array - pub async fn build_unbond(&mut self, tx_msg: &[u8]) -> Result { - let args = tx::unbond_tx_args(tx_msg, None)?; - - let unbond = namada::ledger::tx::build_unbond(&self.client, &mut self.wallet, args.clone()) - .await - .map_err(JsError::from)?; - - let bytes = unbond.0.try_to_vec().map_err(JsError::from)?; + /// Build transaction for specified type, return bytes to client + pub async fn build_tx(&mut self, tx_type: TxType, tx_msg: &[u8]) -> Result { + let tx = match tx_type { + TxType::Bond => { + let args = tx::bond_tx_args(tx_msg, None)?; + let bond = + namada::ledger::tx::build_bond(&self.client, &mut self.wallet, args.clone()) + .await + .map_err(JsError::from)?; + bond.0 + } + TxType::RevealPK => { + let args = tx::reveal_pk_tx_args(tx_msg)?; + + let reveal_pk = namada::ledger::tx::build_reveal_pk( + &self.client, + &mut self.wallet, + args::RevealPk { + tx: args.tx.clone(), + public_key: args.public_key.clone(), + }, + ) + .await?; + + match reveal_pk { + Some(v) => v.0, + None => { + return Err(JsError::new( + "Attempted to build reveal pk for existing public key!", + )) + } + } + } + TxType::Transfer => { + let args = tx::transfer_tx_args(tx_msg, None, None)?; + let transfer = namada::ledger::tx::build_transfer( + &self.client, + &mut self.wallet, + &mut self.shielded_ctx, + args.clone(), + ) + .await + .map_err(JsError::from)?; + transfer.0 + } + TxType::Unbond => { + let args = tx::unbond_tx_args(tx_msg, None)?; + let unbond = + namada::ledger::tx::build_unbond(&self.client, &mut self.wallet, args.clone()) + .await + .map_err(JsError::from)?; + unbond.0 + } + _ => { + return Err(JsError::new(&format!( + "TxType \"{:?}\" not implemented!", + tx_type, + ))) + } + }; - to_js_result(bytes) + to_js_result(tx.try_to_vec().map_err(JsError::from)?) } // Append signatures and return tx bytes @@ -284,6 +314,29 @@ impl Sdk { Ok(()) } + /// Submit signed tx + pub async fn submit_signed_tx( + &mut self, + tx_msg: &[u8], + tx_bytes: &[u8], + raw_sig_bytes: &[u8], + wrapper_sig_bytes: &[u8], + ) -> Result<(), JsError> { + let transfer_tx = self.sign_tx(tx_bytes, raw_sig_bytes, wrapper_sig_bytes)?; + let args = tx::tx_args_from_slice(tx_msg)?; + let verification_key = args.verification_key.clone(); + let pk = validate_pk(verification_key)?; + + self.submit_reveal_pk(&args, transfer_tx.clone(), &pk) + .await?; + + namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args, transfer_tx) + .await + .map_err(JsError::from)?; + + Ok(()) + } + pub async fn submit_transfer( &mut self, tx_msg: &[u8], diff --git a/packages/shared/lib/src/sdk/tx.rs b/packages/shared/lib/src/sdk/tx.rs index 522f5c265..515b16217 100644 --- a/packages/shared/lib/src/sdk/tx.rs +++ b/packages/shared/lib/src/sdk/tx.rs @@ -336,6 +336,13 @@ pub fn ibc_transfer_tx_args( Ok(args) } +pub fn tx_args_from_slice(tx_msg_bytes: &[u8]) -> Result { + let tx_msg = TxMsg::try_from_slice(tx_msg_bytes).map_err(JsError::from)?; + let args = tx_msg_into_args(tx_msg, None)?; + + Ok(args) +} + /// Maps serialized tx_msg into Tx args. /// This is common for all tx types. /// From c81f1bf41c3ad7c11d02d746adb55c733722064f Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Tue, 25 Jul 2023 05:16:41 -0400 Subject: [PATCH 02/18] remove reveal pk schema, re-use TxMsgValue instead --- .../Approvals/ApproveTx/ConfirmLedgerTx.tsx | 26 +++++------- .../src/background/ledger/service.ts | 4 +- packages/shared/lib/src/sdk/mod.rs | 41 +++++++------------ packages/shared/lib/src/sdk/tx.rs | 28 ++----------- packages/types/src/tx/schema/index.ts | 7 ++-- packages/types/src/tx/schema/revealPK.ts | 17 -------- 6 files changed, 33 insertions(+), 90 deletions(-) delete mode 100644 packages/types/src/tx/schema/revealPK.ts diff --git a/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx index b431e95aa..40cd61c92 100644 --- a/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx +++ b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx @@ -6,12 +6,7 @@ import { LedgerError, ResponseSign } from "@namada/ledger-namada"; import { Button, ButtonVariant } from "@namada/components"; import { defaultChainId as chainId } from "@namada/chains"; import { TxType } from "@namada/shared"; -import { - Message, - RevealPKProps, - SubmitRevealPKMsgValue, - Tokens, -} from "@namada/types"; +import { Message, Tokens, TxProps } from "@namada/types"; import { Ledger } from "background/ledger"; import { @@ -34,6 +29,7 @@ import { InfoHeader, InfoLoader } from "Approvals/Approvals.components"; import { QueryPublicKeyMsg } from "background/keyring"; import { TxTypeLabel } from "Approvals/types"; +import { TxMsgValue } from "@namada/types/src/tx/schema/tx"; type Props = { details?: ApprovalDetails; @@ -47,19 +43,16 @@ export const ConfirmLedgerTx: React.FC = ({ details }) => { const { source, msgId = "", publicKey, txType } = details || {}; const revealPk = async (publicKey: string): Promise => { - const revealPKArgs: RevealPKProps = { - tx: { - token: Tokens.NAM.address || "", - feeAmount: new BigNumber(0), - gasLimit: new BigNumber(0), - chainId, - publicKey, - }, + const txArgs: TxProps = { + token: Tokens.NAM.address || "", + feeAmount: new BigNumber(0), + gasLimit: new BigNumber(0), + chainId, publicKey, }; - const msgValue = new SubmitRevealPKMsgValue(revealPKArgs); - const msg = new Message(); + const msgValue = new TxMsgValue(txArgs); + const msg = new Message(); const encoded = msg.encode(msgValue); // Open Ledger transport @@ -86,6 +79,7 @@ export const ConfirmLedgerTx: React.FC = ({ details }) => { ); } catch (e) { console.warn("An error occured: ", e); + await ledger.closeTransport(); throw new Error(`${e}`); } finally { await ledger.closeTransport(); diff --git a/apps/extension/src/background/ledger/service.ts b/apps/extension/src/background/ledger/service.ts index 1c3ac16ed..4fc73584c 100644 --- a/apps/extension/src/background/ledger/service.ts +++ b/apps/extension/src/background/ledger/service.ts @@ -5,7 +5,6 @@ import { AccountType, Bip44Path, SubmitBondMsgValue, - SubmitRevealPKMsgValue, TransferMsgValue, } from "@namada/types"; import { ResponseSign } from "@namada/ledger-namada"; @@ -24,6 +23,7 @@ import { encodeSignature, generateId } from "utils"; import { ExtensionRequester } from "extension"; import { Ports } from "router"; import { UpdatedStakingEventMsg } from "content/events"; +import { TxMsgValue } from "@namada/types/src/tx/schema/tx"; export const LEDGERSTORE_KEY = "ledger-store"; const UUID_NAMESPACE = "be9fdaee-ffa2-11ed-8ef1-325096b39f47"; @@ -52,7 +52,7 @@ export class LedgerService { // Deserialize txMsg to retrieve source const { publicKey } = deserialize( Buffer.from(fromBase64(txMsg)), - SubmitRevealPKMsgValue + TxMsgValue ); // Query account from Ledger storage to determine path for signer diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 1cb61de3f..f85c5b37b 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -167,28 +167,6 @@ impl Sdk { Ok(()) } - /// Contruct reveal pk data for external signers, returns byte array - pub async fn build_reveal_pk(&mut self, tx_msg: &[u8]) -> Result { - let args = tx::reveal_pk_tx_args(tx_msg)?; - - let reveal_pk = namada::ledger::tx::build_reveal_pk( - &self.client, - &mut self.wallet, - args::RevealPk { - tx: args.tx.clone(), - public_key: args.public_key.clone(), - }, - ) - .await?; - - let bytes = match reveal_pk { - Some(v) => v.0.try_to_vec().map_err(JsError::from)?, - None => vec![], - }; - - to_js_result(bytes) - } - /// Submit signed reveal pk tx pub async fn submit_signed_reveal_pk( &mut self, @@ -198,9 +176,9 @@ impl Sdk { wrapper_sig_bytes: &[u8], ) -> Result<(), JsError> { let reveal_pk_tx = self.sign_tx(tx_bytes, raw_sig_bytes, wrapper_sig_bytes)?; - let args = tx::reveal_pk_tx_args(tx_msg).map_err(JsError::from)?; + let args = tx::tx_args_from_slice(&tx_msg).map_err(JsError::from)?; - namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args.tx, reveal_pk_tx) + namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args, reveal_pk_tx) .await .map_err(JsError::from)?; @@ -219,14 +197,23 @@ impl Sdk { bond.0 } TxType::RevealPK => { - let args = tx::reveal_pk_tx_args(tx_msg)?; + let args = tx::tx_args_from_slice(tx_msg)?; + + let public_key = match args.verification_key.clone() { + Some(v) => PublicKey::from(v), + _ => { + return Err(JsError::new( + "verification_key is required in this context!", + )) + } + }; let reveal_pk = namada::ledger::tx::build_reveal_pk( &self.client, &mut self.wallet, args::RevealPk { - tx: args.tx.clone(), - public_key: args.public_key.clone(), + tx: args.clone(), + public_key, }, ) .await?; diff --git a/packages/shared/lib/src/sdk/tx.rs b/packages/shared/lib/src/sdk/tx.rs index 515b16217..74c544b90 100644 --- a/packages/shared/lib/src/sdk/tx.rs +++ b/packages/shared/lib/src/sdk/tx.rs @@ -40,29 +40,6 @@ pub struct SubmitRevealPKMsg { public_key: String, } -/// Maps serialized tx_msg into RevealPk args -/// -/// # Arguments -/// -/// * `tx_msg` - Borsh serialized tx_msg. -/// -/// # Errors -/// -/// Returns JsError if the tx_msg can't be deserialized or -/// Rust structs can't be created. -pub fn reveal_pk_tx_args(tx_msg: &[u8]) -> Result { - let tx_msg = SubmitRevealPKMsg::try_from_slice(tx_msg)?; - let SubmitRevealPKMsg { tx, public_key } = tx_msg; - let public_key = PK::Ed25519(PublicKey::from_str(&public_key).map_err(JsError::from)?); - - let args = args::RevealPk { - tx: tx_msg_into_args(tx, None)?, - public_key, - }; - - Ok(args) -} - /// Maps serialized tx_msg into BondTx args. /// /// # Arguments @@ -166,7 +143,10 @@ pub struct SubmitWithdrawMsg { /// /// Returns JsError if the tx_msg can't be deserialized or /// Rust structs can't be created. -pub fn withdraw_tx_args(tx_msg: &[u8], password: Option) -> Result { +pub fn withdraw_tx_args( + tx_msg: &[u8], + password: Option, +) -> Result { let tx_msg = SubmitWithdrawMsg::try_from_slice(tx_msg)?; let SubmitWithdrawMsg { diff --git a/packages/types/src/tx/schema/index.ts b/packages/types/src/tx/schema/index.ts index 3e37ff695..15d3bbcfb 100644 --- a/packages/types/src/tx/schema/index.ts +++ b/packages/types/src/tx/schema/index.ts @@ -3,7 +3,6 @@ export * from "./ibcTransfer"; export * from "./transfer"; export * from "./bond"; export * from "./signature"; -export * from "./revealPK"; export * from "./unbond"; export * from "./withdraw"; @@ -13,8 +12,8 @@ import { TransferMsgValue } from "./transfer"; import { SubmitBondMsgValue } from "./bond"; import { SubmitUnbondMsgValue } from "./unbond"; import { SubmitWithdrawMsgValue } from "./withdraw"; -import { SubmitRevealPKMsgValue } from "./revealPK"; import { SignatureMsgValue } from "./signature"; +import { TxMsgValue } from "./tx"; export type Schema = | AccountMsgValue @@ -23,5 +22,5 @@ export type Schema = | SubmitBondMsgValue | SubmitUnbondMsgValue | SubmitWithdrawMsgValue - | SubmitRevealPKMsgValue - | SignatureMsgValue; + | SignatureMsgValue + | TxMsgValue; diff --git a/packages/types/src/tx/schema/revealPK.ts b/packages/types/src/tx/schema/revealPK.ts deleted file mode 100644 index 902d9b7c0..000000000 --- a/packages/types/src/tx/schema/revealPK.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { field } from "@dao-xyz/borsh"; -import { TxMsgValue } from "./tx"; -import { RevealPKProps } from "../types"; - -export class SubmitRevealPKMsgValue { - @field({ type: TxMsgValue }) - tx!: InstanceType; - - @field({ type: "string" }) - publicKey!: string; - - constructor(data: RevealPKProps) { - Object.assign(this, data); - this.tx = new TxMsgValue(data.tx); - } -} From e478e7697307e4372026c0b085b20f2861dc8d6d Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Tue, 25 Jul 2023 06:03:55 -0400 Subject: [PATCH 03/18] Further clean up, consolidate all getBytes msg --- .../Approvals/ApproveTx/ConfirmLedgerTx.tsx | 35 ++++------- .../src/background/ledger/handler.ts | 46 +++++--------- apps/extension/src/background/ledger/init.ts | 6 +- .../src/background/ledger/messages.ts | 60 +++++++------------ .../src/background/ledger/service.ts | 19 +++--- packages/shared/lib/src/sdk/mod.rs | 10 ++-- 6 files changed, 64 insertions(+), 112 deletions(-) diff --git a/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx index 40cd61c92..55c21f550 100644 --- a/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx +++ b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx @@ -10,9 +10,8 @@ import { Message, Tokens, TxProps } from "@namada/types"; import { Ledger } from "background/ledger"; import { - GetBondBytesMsg, - GetTransferBytesMsg, GetRevealPKBytesMsg, + GetTxBytesMsg, SubmitSignedBondMsg, SubmitSignedRevealPKMsg, SubmitSignedTransferMsg, @@ -113,25 +112,6 @@ export const ConfirmLedgerTx: React.FC = ({ details }) => { } }, [source, publicKey]); - const getBytesByType = async ( - type?: TxType - ): Promise<{ bytes: Uint8Array; path: string }> => { - switch (type) { - case TxType.Bond: - return await requester.sendMessage( - Ports.Background, - new GetBondBytesMsg(msgId) - ); - case TxType.Transfer: - return await requester.sendMessage( - Ports.Background, - new GetTransferBytesMsg(msgId) - ); - default: - throw new Error("Invalid transaction type!"); - } - }; - // TODO: This will not be necessary when `submit_signed_tx` is implemented! const submitByType = async ( bytes: Uint8Array, @@ -161,7 +141,18 @@ export const ConfirmLedgerTx: React.FC = ({ details }) => { try { // Constuct tx bytes from SDK - const { bytes, path } = await getBytesByType(txType); + if (!txType) { + throw new Error("txType was not provided!"); + } + if (!source) { + throw new Error("source was not provided!"); + } + + const { bytes, path } = await requester.sendMessage( + Ports.Background, + new GetTxBytesMsg(txType, msgId, source) + ); + setStatusInfo(`Review and approve ${txLabel} transaction on your Ledger`); // Sign with Ledger diff --git a/apps/extension/src/background/ledger/handler.ts b/apps/extension/src/background/ledger/handler.ts index 75d153522..b03793873 100644 --- a/apps/extension/src/background/ledger/handler.ts +++ b/apps/extension/src/background/ledger/handler.ts @@ -2,12 +2,11 @@ import { Handler, Env, Message, InternalHandler } from "router"; import { LedgerService } from "./service"; import { AddLedgerAccountMsg, - GetBondBytesMsg, - GetRevealPKBytesMsg, - GetTransferBytesMsg, + GetTxBytesMsg, SubmitSignedTransferMsg, SubmitSignedBondMsg, SubmitSignedRevealPKMsg, + GetRevealPKBytesMsg, } from "./messages"; export const getHandler: (service: LedgerService) => Handler = (service) => { @@ -18,17 +17,11 @@ export const getHandler: (service: LedgerService) => Handler = (service) => { env, msg as AddLedgerAccountMsg ); - case GetRevealPKBytesMsg: - return handleGetRevealPKBytesMsg(service)( - env, - msg as GetRevealPKBytesMsg - ); + case GetTxBytesMsg: + return handleGetTxBytesMsg(service)(env, msg as GetTxBytesMsg); - case GetTransferBytesMsg: - return handleGetTransferBytesMsg(service)( - env, - msg as GetTransferBytesMsg - ); + case GetRevealPKBytesMsg: + return handleGetRevealPKBytesMsg(service)(env, msg as GetTxBytesMsg); case SubmitSignedRevealPKMsg: return handleSubmitSignedRevealPKMsg(service)( env, @@ -39,8 +32,6 @@ export const getHandler: (service: LedgerService) => Handler = (service) => { env, msg as SubmitSignedTransferMsg ); - case GetBondBytesMsg: - return handleGetBondBytesMsg(service)(env, msg as GetTransferBytesMsg); case SubmitSignedBondMsg: return handleSubmitSignedBondMsg(service)( env, @@ -62,15 +53,6 @@ const handleAddLedgerAccountMsg: ( }; }; -const handleGetTransferBytesMsg: ( - service: LedgerService -) => InternalHandler = (service) => { - return async (_, msg) => { - const { msgId } = msg; - return await service.getTransferBytes(msgId); - }; -}; - const handleSubmitSignedTransferMsg: ( service: LedgerService ) => InternalHandler = (service) => { @@ -80,21 +62,21 @@ const handleSubmitSignedTransferMsg: ( }; }; -const handleGetBondBytesMsg: ( +const handleSubmitSignedBondMsg: ( service: LedgerService -) => InternalHandler = (service) => { +) => InternalHandler = (service) => { return async (_, msg) => { - const { msgId } = msg; - return await service.getBondBytes(msgId); + const { bytes, msgId, signatures } = msg; + return await service.submitBond(msgId, bytes, signatures); }; }; -const handleSubmitSignedBondMsg: ( +const handleGetTxBytesMsg: ( service: LedgerService -) => InternalHandler = (service) => { +) => InternalHandler = (service) => { return async (_, msg) => { - const { bytes, msgId, signatures } = msg; - return await service.submitBond(msgId, bytes, signatures); + const { address, txType, txMsg } = msg; + return await service.getTxBytes(txType, txMsg, address); }; }; diff --git a/apps/extension/src/background/ledger/init.ts b/apps/extension/src/background/ledger/init.ts index 12eaba6f2..b1d69dc9a 100644 --- a/apps/extension/src/background/ledger/init.ts +++ b/apps/extension/src/background/ledger/init.ts @@ -2,9 +2,8 @@ import { Router } from "router"; import { ROUTE } from "./constants"; import { AddLedgerAccountMsg, - GetBondBytesMsg, + GetTxBytesMsg, GetRevealPKBytesMsg, - GetTransferBytesMsg, SubmitSignedBondMsg, SubmitSignedRevealPKMsg, SubmitSignedUnbondMsg, @@ -15,9 +14,8 @@ import { LedgerService } from "./service"; export function init(router: Router, service: LedgerService): void { router.registerMessage(AddLedgerAccountMsg); - router.registerMessage(GetBondBytesMsg); + router.registerMessage(GetTxBytesMsg); router.registerMessage(GetRevealPKBytesMsg); - router.registerMessage(GetTransferBytesMsg); router.registerMessage(SubmitSignedBondMsg); router.registerMessage(SubmitSignedRevealPKMsg); router.registerMessage(SubmitSignedUnbondMsg); diff --git a/apps/extension/src/background/ledger/messages.ts b/apps/extension/src/background/ledger/messages.ts index 7b485755b..613054875 100644 --- a/apps/extension/src/background/ledger/messages.ts +++ b/apps/extension/src/background/ledger/messages.ts @@ -3,20 +3,21 @@ import { Bip44Path } from "@namada/types"; import { Message } from "router"; import { ROUTE } from "./constants"; +import { TxType } from "@namada/shared"; enum MessageType { AddLedgerAccount = "add-ledger-account", + GetTxBytes = "get-tx-bytes", + // Reveal PK GetRevealPKBytes = "get-reveal-pk-bytes", SubmitSignedRevealPK = "submit-signed-reveal-pk", // Transfers - GetTransferBytes = "get-transfer-bytes", SubmitSignedTransfer = "submit-signed-transfer", // Bonds - GetBondBytes = "get-bond-bytes", SubmitSignedBond = "submit-signed-bond", SubmitSignedUnbond = "submit-signed-unbond", } @@ -62,48 +63,31 @@ export class AddLedgerAccountMsg extends Message { } } -export class GetRevealPKBytesMsg extends Message<{ +export class GetTxBytesMsg extends Message<{ bytes: Uint8Array; path: string; }> { public static type(): MessageType { - return MessageType.GetRevealPKBytes; + return MessageType.GetTxBytes; } - constructor(public readonly txMsg: string) { + constructor( + public readonly txType: TxType, + public readonly txMsg: string, + public readonly address: string + ) { super(); } validate(): void { + if (!this.txType) { + throw new Error("txType is required!"); + } if (!this.txMsg) { throw new Error("txMsg was not provided!"); } - } - - route(): string { - return ROUTE; - } - - type(): string { - return GetRevealPKBytesMsg.type(); - } -} - -export class GetTransferBytesMsg extends Message<{ - bytes: Uint8Array; - path: string; -}> { - public static type(): MessageType { - return MessageType.GetTransferBytes; - } - - constructor(public readonly msgId: string) { - super(); - } - - validate(): void { - if (!this.msgId) { - throw new Error("Transfer Tx msgId was not provided!"); + if (!this.address) { + throw new Error("address was not provided!"); } } @@ -112,25 +96,25 @@ export class GetTransferBytesMsg extends Message<{ } type(): string { - return GetTransferBytesMsg.type(); + return GetTxBytesMsg.type(); } } -export class GetBondBytesMsg extends Message<{ +export class GetRevealPKBytesMsg extends Message<{ bytes: Uint8Array; path: string; }> { public static type(): MessageType { - return MessageType.GetBondBytes; + return MessageType.GetRevealPKBytes; } - constructor(public readonly msgId: string) { + constructor(public readonly txMsg: string) { super(); } validate(): void { - if (!this.msgId) { - throw new Error("Bond Tx msgId was not provided!"); + if (!this.txMsg) { + throw new Error("txMsg was not provided!"); } } @@ -139,7 +123,7 @@ export class GetBondBytesMsg extends Message<{ } type(): string { - return GetBondBytesMsg.type(); + return GetRevealPKBytesMsg.type(); } } diff --git a/apps/extension/src/background/ledger/service.ts b/apps/extension/src/background/ledger/service.ts index 4fc73584c..3d08adea0 100644 --- a/apps/extension/src/background/ledger/service.ts +++ b/apps/extension/src/background/ledger/service.ts @@ -4,6 +4,7 @@ import { deserialize } from "@dao-xyz/borsh"; import { AccountType, Bip44Path, + Message, SubmitBondMsgValue, TransferMsgValue, } from "@namada/types"; @@ -161,8 +162,10 @@ export class LedgerService { } } - async getBondBytes( - msgId: string + async getTxBytes( + txType: TxType, + msgId: string, + address: string ): Promise<{ bytes: Uint8Array; path: string }> { const txMsg = await this.txStore.get(msgId); @@ -174,20 +177,14 @@ export class LedgerService { const { coinType } = chains[this.chainId].bip44; try { - // Deserialize txMsg to retrieve source - const { source } = deserialize( - Buffer.from(fromBase64(txMsg)), - SubmitBondMsgValue - ); - // Query account from Ledger storage to determine path for signer - const account = await this._ledgerStore.getRecord("address", source); + const account = await this._ledgerStore.getRecord("address", address); if (!account) { - throw new Error(`Ledger account not found for ${source}`); + throw new Error(`Ledger account not found for ${address}`); } - const bytes = await this.sdk.build_tx(TxType.Bond, fromBase64(txMsg)); + const bytes = await this.sdk.build_tx(txType, fromBase64(txMsg)); const path = makeBip44Path(coinType, account.path); return { bytes, path }; diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index f85c5b37b..3b300ff08 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -25,11 +25,11 @@ mod wallet; #[wasm_bindgen] #[derive(Copy, Clone, Debug)] pub enum TxType { - Bond = 0, - Unbond = 1, - Withdraw = 2, - Transfer = 3, - RevealPK = 4, + Bond = 1, + Unbond = 2, + Withdraw = 3, + Transfer = 4, + RevealPK = 5, } // Require that a public key is present From 8f7a0ce17b107f18f08b28b2b1a20c25445d464f Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Tue, 25 Jul 2023 06:04:51 -0400 Subject: [PATCH 04/18] Lint --- apps/extension/src/background/ledger/service.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/extension/src/background/ledger/service.ts b/apps/extension/src/background/ledger/service.ts index 3d08adea0..8a8b666de 100644 --- a/apps/extension/src/background/ledger/service.ts +++ b/apps/extension/src/background/ledger/service.ts @@ -1,13 +1,7 @@ import { fromBase64 } from "@cosmjs/encoding"; import { deserialize } from "@dao-xyz/borsh"; -import { - AccountType, - Bip44Path, - Message, - SubmitBondMsgValue, - TransferMsgValue, -} from "@namada/types"; +import { AccountType, Bip44Path, TransferMsgValue } from "@namada/types"; import { ResponseSign } from "@namada/ledger-namada"; import { Sdk, TxType } from "@namada/shared"; import { IStore, KVStore, Store } from "@namada/storage"; From 0de3b86fcabd64a33162dd61092bd70b60ec8f8c Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Tue, 25 Jul 2023 09:01:03 -0400 Subject: [PATCH 05/18] Consolidate submit-signed-tx related methods --- apps/extension/.prettierrc | 3 +- .../src/background/approvals/service.ts | 30 +++----- apps/extension/src/background/keyring/init.ts | 2 - .../src/background/ledger/messages.ts | 12 ++- .../src/background/ledger/service.ts | 22 ++++-- apps/extension/src/provider/messages.ts | 25 ------- apps/extension/src/utils/index.ts | 17 ++++- .../slices/StakingAndGovernance/actions.ts | 74 +++++++++++-------- packages/shared/lib/src/sdk/mod.rs | 64 ---------------- packages/shared/lib/src/sdk/tx.rs | 6 -- packages/types/src/signer.ts | 6 +- 11 files changed, 95 insertions(+), 166 deletions(-) diff --git a/apps/extension/.prettierrc b/apps/extension/.prettierrc index f0eb61e0f..193626a11 100644 --- a/apps/extension/.prettierrc +++ b/apps/extension/.prettierrc @@ -2,5 +2,6 @@ "trailingComma": "es5", "tabWidth": 2, "semi": true, - "singleQuote": false + "singleQuote": false, + "bracketSpacing": false } diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index b2fe3b9d1..00ac2d347 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -1,20 +1,16 @@ import browser from "webextension-polyfill"; -import { fromBase64 } from "@cosmjs/encoding"; -import { v4 as uuid } from "uuid"; +import {fromBase64} from "@cosmjs/encoding"; +import {v4 as uuid} from "uuid"; import BigNumber from "bignumber.js"; -import { deserialize } from "@dao-xyz/borsh"; +import {deserialize} from "@dao-xyz/borsh"; -import { - AccountType, - SubmitBondMsgValue, - TransferMsgValue, -} from "@namada/types"; -import { TxType } from "@namada/shared"; -import { KVStore } from "@namada/storage"; +import {AccountType, SubmitBondMsgValue, TransferMsgValue} from "@namada/types"; +import {TxType} from "@namada/shared"; +import {KVStore} from "@namada/storage"; -import { KeyRingService, TabStore } from "background/keyring"; -import { LedgerService } from "background/ledger"; -import { paramsToUrl } from "@namada/utils"; +import {KeyRingService, TabStore} from "background/keyring"; +import {LedgerService} from "background/ledger"; +import {paramsToUrl} from "@namada/utils"; export class ApprovalsService { constructor( @@ -37,7 +33,7 @@ export class ApprovalsService { target, token, amount: amountBN, - tx: { publicKey = "" }, + tx: {publicKey = ""}, } = txDetails; const amount = new BigNumber(amountBN.toString()); const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ @@ -70,7 +66,7 @@ export class ApprovalsService { source, nativeToken: token, amount: amountBN, - tx: { publicKey = "" }, + tx: {publicKey = ""}, } = txDetails; const amount = new BigNumber(amountBN.toString()); const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ @@ -90,7 +86,6 @@ export class ApprovalsService { } // Deserialize bond details and prompt user - // TODO: Finish implementing! async approveUnbond(txMsg: string, type?: AccountType): Promise { const txMsgBuffer = Buffer.from(fromBase64(txMsg)); const id = uuid(); @@ -99,9 +94,8 @@ export class ApprovalsService { // Decode tx details and launch approval screen const txDetails = deserialize(txMsgBuffer, SubmitBondMsgValue); - const { source, nativeToken, amount: amountBN } = txDetails; + const {source, nativeToken, amount: amountBN} = txDetails; const amount = new BigNumber(amountBN.toString()); - // TODO: This query should include perhaps a "type" indicating whether it's a bond or unbond tx: const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ TxType.Unbond }`; diff --git a/apps/extension/src/background/keyring/init.ts b/apps/extension/src/background/keyring/init.ts index 101294a17..80f7362cb 100644 --- a/apps/extension/src/background/keyring/init.ts +++ b/apps/extension/src/background/keyring/init.ts @@ -23,7 +23,6 @@ import { EncodeInitAccountMsg, QueryAccountsMsg, QueryBalancesMsg, - EncodeRevealPkMsg, SubmitIbcTransferMsg, FetchAndStoreMaspParamsMsg, HasMaspParamsMsg, @@ -40,7 +39,6 @@ export function init(router: Router, service: KeyRingService): void { router.registerMessage(ConnectInterfaceMsg); router.registerMessage(DeriveAccountMsg); router.registerMessage(EncodeInitAccountMsg); - router.registerMessage(EncodeRevealPkMsg); router.registerMessage(GenerateMnemonicMsg); router.registerMessage(GetActiveAccountMsg); router.registerMessage(LockKeyRingMsg); diff --git a/apps/extension/src/background/ledger/messages.ts b/apps/extension/src/background/ledger/messages.ts index 613054875..c0427fabe 100644 --- a/apps/extension/src/background/ledger/messages.ts +++ b/apps/extension/src/background/ledger/messages.ts @@ -7,17 +7,15 @@ import { TxType } from "@namada/shared"; enum MessageType { AddLedgerAccount = "add-ledger-account", - GetTxBytes = "get-tx-bytes", - - // Reveal PK GetRevealPKBytes = "get-reveal-pk-bytes", - SubmitSignedRevealPK = "submit-signed-reveal-pk", - // Transfers - SubmitSignedTransfer = "submit-signed-transfer", + // TODO: - Implement for single SubmitSignedTx + SubmitSignedTx = "submit-signed-tx", - // Bonds + // TODO: Remove: + SubmitSignedRevealPK = "submit-signed-reveal-pk", + SubmitSignedTransfer = "submit-signed-transfer", SubmitSignedBond = "submit-signed-bond", SubmitSignedUnbond = "submit-signed-unbond", } diff --git a/apps/extension/src/background/ledger/service.ts b/apps/extension/src/background/ledger/service.ts index 8a8b666de..d16cc431b 100644 --- a/apps/extension/src/background/ledger/service.ts +++ b/apps/extension/src/background/ledger/service.ts @@ -1,7 +1,12 @@ import { fromBase64 } from "@cosmjs/encoding"; import { deserialize } from "@dao-xyz/borsh"; -import { AccountType, Bip44Path, TransferMsgValue } from "@namada/types"; +import { + AccountType, + Bip44Path, + SubmitBondMsgValue, + TransferMsgValue, +} from "@namada/types"; import { ResponseSign } from "@namada/ledger-namada"; import { Sdk, TxType } from "@namada/shared"; import { IStore, KVStore, Store } from "@namada/storage"; @@ -14,7 +19,7 @@ import { TabStore, syncTabs, } from "background/keyring"; -import { encodeSignature, generateId } from "utils"; +import { encodeSignature, encodeTx, generateId } from "utils"; import { ExtensionRequester } from "extension"; import { Ports } from "router"; import { UpdatedStakingEventMsg } from "content/events"; @@ -134,6 +139,8 @@ export class LedgerService { if (!txMsg) { throw new Error(`Transaction ${msgId} not found!`); } + const { tx } = deserialize(fromBase64(txMsg), TransferMsgValue); + const encodedTx = encodeTx(tx); const { wrapperSignature, rawSignature } = signatures; @@ -142,8 +149,8 @@ export class LedgerService { const wrapperSig = encodeSignature(wrapperSignature); try { - await this.sdk.submit_signed_transfer( - fromBase64(txMsg), + await this.sdk.submit_signed_tx( + encodedTx, fromBase64(bytes), rawSig, wrapperSig @@ -200,13 +207,16 @@ export class LedgerService { throw new Error(`Bond Transaction ${msgId} not found!`); } + const { tx } = deserialize(fromBase64(txMsg), SubmitBondMsgValue); + const encodedTx = encodeTx(tx); + const { rawSignature, wrapperSignature } = signatures; try { const rawSig = encodeSignature(rawSignature); const wrapperSig = encodeSignature(wrapperSignature); - await this.sdk.submit_signed_bond( - fromBase64(txMsg), + await this.sdk.submit_signed_tx( + encodedTx, fromBase64(bytes), rawSig, wrapperSig diff --git a/apps/extension/src/provider/messages.ts b/apps/extension/src/provider/messages.ts index e295af2d0..2e7fd9cea 100644 --- a/apps/extension/src/provider/messages.ts +++ b/apps/extension/src/provider/messages.ts @@ -235,31 +235,6 @@ export class EncodeInitAccountMsg extends Message { } } -export class EncodeRevealPkMsg extends Message { - public static type(): MessageType { - return MessageType.EncodeRevealPublicKey; - } - - constructor(public readonly signer: string) { - super(); - } - - validate(): void { - if (!this.signer) { - throw new Error("An signer is required!"); - } - return; - } - - route(): string { - return Route.KeyRing; - } - - type(): string { - return EncodeRevealPkMsg.type(); - } -} - export class ApproveTransferMsg extends Message { public static type(): MessageType { return MessageType.ApproveTransfer; diff --git a/apps/extension/src/utils/index.ts b/apps/extension/src/utils/index.ts index bee4ad30e..7169c6e04 100644 --- a/apps/extension/src/utils/index.ts +++ b/apps/extension/src/utils/index.ts @@ -1,10 +1,16 @@ import browser from "webextension-polyfill"; import { v5 as uuid } from "uuid"; -import { DerivedAccount, Message, SignatureMsgValue } from "@namada/types"; +import { + DerivedAccount, + Message, + SignatureMsgValue, + TxProps, +} from "@namada/types"; import { pick } from "@namada/utils"; import { AccountStore } from "background/keyring"; import { ISignature } from "@namada/ledger-namada"; +import { TxMsgValue } from "@namada/types/src/tx/schema/tx"; /** * Query the current extension tab and close it @@ -72,3 +78,12 @@ export const encodeSignature = (sig: ISignature): Uint8Array => { const msg = new Message(); return msg.encode(value); }; + +/** + * Helper to encode Tx given TxProps + */ +export const encodeTx = (tx: TxProps): Uint8Array => { + const txMsgValue = new TxMsgValue(tx); + const msg = new Message(); + return msg.encode(txMsgValue); +}; diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts b/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts index 47eea84e5..e597d5294 100644 --- a/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts +++ b/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts @@ -23,13 +23,12 @@ import { import { RootState } from "store"; import { Account } from "slices/accounts"; -const toValidator = ( - [address, stake]: [string, string | null] -): Validator => ({ +const toValidator = ([address, stake]: [string, string | null]): Validator => ({ uuid: address, name: address, // TODO: get votes per token from Namada - votingPower: stake === null ? undefined : new BigNumber(stake).multipliedBy(1_000_000), + votingPower: + stake === null ? undefined : new BigNumber(stake).multipliedBy(1_000_000), homepageUrl: "http://namada.net", commission: new BigNumber(0), // TODO: implement commission description: "TBD", @@ -37,7 +36,13 @@ const toValidator = ( const toMyValidators = ( acc: MyValidators[], - [_, validator, stake, unbonded, withdrawable]: [string, string, string, string, string] + [_, validator, stake, unbonded, withdrawable]: [ + string, + string, + string, + string, + string + ] ): MyValidators[] => { const index = acc.findIndex((myValidator) => myValidator.uuid === validator); const v = acc[index]; @@ -49,15 +54,17 @@ const toMyValidators = ( ...arr.slice(idx + 1), ]; - const stakedAmount = new BigNumber(stake) - .plus(new BigNumber(v?.stakedAmount || 0)); - - const unbondedAmount = - (new BigNumber(unbonded)).plus(new BigNumber(v?.unbondedAmount || 0)); + const stakedAmount = new BigNumber(stake).plus( + new BigNumber(v?.stakedAmount || 0) + ); - const withdrawableAmount = - (new BigNumber(withdrawable)).plus(new BigNumber(v?.withdrawableAmount || 0)); + const unbondedAmount = new BigNumber(unbonded).plus( + new BigNumber(v?.unbondedAmount || 0) + ); + const withdrawableAmount = new BigNumber(withdrawable).plus( + new BigNumber(v?.withdrawableAmount || 0) + ); return [ ...sliceFn(acc, index), @@ -72,10 +79,13 @@ const toMyValidators = ( ]; }; -const toBond = ([owner, validator, amount, startEpoch]: - [string, string, string, string]): StakingPosition => { - - return { +const toBond = ([owner, validator, amount, startEpoch]: [ + string, + string, + string, + string +]): StakingPosition => { + return { uuid: owner + validator + startEpoch, bonded: true, stakedAmount: new BigNumber(amount), @@ -83,19 +93,23 @@ const toBond = ([owner, validator, amount, startEpoch]: validatorId: validator, totalRewards: "TBD", }; -} - -const toUnbond = ([owner, validator, amount, startEpoch, withdrawableEpoch]: - [string, string, string, string, string]): StakingPosition => { +}; +const toUnbond = ([owner, validator, amount, startEpoch, withdrawableEpoch]: [ + string, + string, + string, + string, + string +]): StakingPosition => { const bond = toBond([owner, validator, amount, startEpoch]); return { ...bond, bonded: false, withdrawableEpoch: new BigNumber(withdrawableEpoch), - } -} + }; +}; export const fetchValidators = createAsyncThunk< { allValidators: Validator[] }, @@ -106,8 +120,10 @@ export const fetchValidators = createAsyncThunk< const { rpc } = chains[chainId]; const query = new Query(rpc); - const queryResult = - (await query.query_all_validators()) as [string, string | null][]; + const queryResult = (await query.query_all_validators()) as [ + string, + string | null + ][]; const allValidators = queryResult.map(toValidator); thunkApi.dispatch(fetchMyValidators(allValidators)); @@ -183,10 +199,7 @@ export const fetchMyStakingPositions = createAsyncThunk< const [bonds, unbonds] = await query.query_staking_positions(addresses); return Promise.resolve({ - myStakingPositions: [ - ...bonds.map(toBond), - ...unbonds.map(toUnbond), - ] + myStakingPositions: [...bonds.map(toBond), ...unbonds.map(toUnbond)], }); } catch (error) { console.warn(`error: ${error}`); @@ -241,8 +254,7 @@ export const postNewBonding = createAsyncThunk< publicKey, }, }, - type, - publicKey + type ); }); @@ -275,7 +287,7 @@ export const postNewUnbonding = createAsyncThunk< export const postNewWithdraw = createAsyncThunk< void, - { owner: string, validatorId: string }, + { owner: string; validatorId: string }, { state: RootState } >(POST_UNSTAKING, async ({ owner, validatorId }, thunkApi) => { const { chainId } = thunkApi.getState().settings; diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 3b300ff08..705365dc1 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -278,29 +278,6 @@ impl Sdk { Ok(tx) } - /// Submit signed transfer tx - pub async fn submit_signed_transfer( - &mut self, - tx_msg: &[u8], - tx_bytes: &[u8], - raw_sig_bytes: &[u8], - wrapper_sig_bytes: &[u8], - ) -> Result<(), JsError> { - let transfer_tx = self.sign_tx(tx_bytes, raw_sig_bytes, wrapper_sig_bytes)?; - let args = tx::transfer_tx_args(tx_msg, None, None).map_err(JsError::from)?; - let verification_key = args.tx.verification_key.clone(); - let pk = validate_pk(verification_key)?; - - self.submit_reveal_pk(&args.tx, transfer_tx.clone(), &pk) - .await?; - - namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args.tx, transfer_tx) - .await - .map_err(JsError::from)?; - - Ok(()) - } - /// Submit signed tx pub async fn submit_signed_tx( &mut self, @@ -379,24 +356,6 @@ impl Sdk { Ok(()) } - /// Submit signed bond - pub async fn submit_signed_bond( - &mut self, - tx_msg: &[u8], - tx_bytes: &[u8], - raw_sig_bytes: &[u8], - wrapper_sig_bytes: &[u8], - ) -> Result<(), JsError> { - let bond_tx = self.sign_tx(tx_bytes, raw_sig_bytes, wrapper_sig_bytes)?; - let args = tx::bond_tx_args(tx_msg, None).map_err(JsError::from)?; - - namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args.tx, bond_tx) - .await - .map_err(JsError::from)?; - - Ok(()) - } - /// Submit unbond pub async fn submit_unbond( &mut self, @@ -415,29 +374,6 @@ impl Sdk { Ok(()) } - /// Submit signed unbond tx - pub async fn submit_signed_unbond( - &mut self, - tx_msg: &[u8], - tx_bytes: &[u8], - raw_sig_bytes: &[u8], - wrapper_sig_bytes: &[u8], - ) -> Result<(), JsError> { - let bond_tx = self.sign_tx(tx_bytes, raw_sig_bytes, wrapper_sig_bytes)?; - let args = tx::unbond_tx_args(tx_msg, None).map_err(JsError::from)?; - let verification_key = args.tx.verification_key.clone(); - let pk = validate_pk(verification_key)?; - - self.submit_reveal_pk(&args.tx, bond_tx.clone(), &pk) - .await?; - - namada::ledger::tx::process_tx(&self.client, &mut self.wallet, &args.tx, bond_tx) - .await - .map_err(JsError::from)?; - - Ok(()) - } - pub async fn submit_withdraw( &mut self, tx_msg: &[u8], diff --git a/packages/shared/lib/src/sdk/tx.rs b/packages/shared/lib/src/sdk/tx.rs index 74c544b90..1b83723fe 100644 --- a/packages/shared/lib/src/sdk/tx.rs +++ b/packages/shared/lib/src/sdk/tx.rs @@ -34,12 +34,6 @@ pub struct SubmitBondMsg { tx: TxMsg, } -#[derive(BorshSerialize, BorshDeserialize)] -pub struct SubmitRevealPKMsg { - tx: TxMsg, - public_key: String, -} - /// Maps serialized tx_msg into BondTx args. /// /// # Arguments diff --git a/packages/types/src/signer.ts b/packages/types/src/signer.ts index 7fdb71bfb..b6c86fb4d 100644 --- a/packages/types/src/signer.ts +++ b/packages/types/src/signer.ts @@ -10,11 +10,7 @@ import { export interface Signer { accounts: () => Promise; - submitBond( - args: SubmitBondProps, - type: AccountType, - publicKey?: string - ): Promise; + submitBond(args: SubmitBondProps, type: AccountType): Promise; submitUnbond(args: SubmitUnbondProps): Promise; submitWithdraw(args: SubmitWithdrawProps): Promise; submitTransfer(args: TransferProps, type: AccountType): Promise; From a2fdefa57af31987fe0e2b42ffbebf103b03d70c Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Tue, 25 Jul 2023 10:52:35 -0400 Subject: [PATCH 06/18] Hook up unbond to Approvals and Ledger --- apps/extension/.prettierrc | 3 +- .../Approvals/ApproveTx/ConfirmLedgerTx.tsx | 6 +++ .../src/background/approvals/init.ts | 11 +++- .../src/background/approvals/messages.ts | 30 +++++++++++ .../src/background/approvals/service.ts | 41 ++++++++------ .../src/background/ledger/handler.ts | 16 +++++- .../src/background/ledger/messages.ts | 7 +-- .../src/background/ledger/service.ts | 39 ++++++++++++++ apps/extension/src/provider/InjectedNamada.ts | 50 +++++++++-------- apps/extension/src/provider/Namada.ts | 17 ++++-- apps/extension/src/provider/Signer.ts | 30 +++++++---- apps/extension/src/provider/messages.ts | 15 +++++- .../slices/StakingAndGovernance/actions.ts | 54 ++++++++++++------- packages/types/src/namada.ts | 17 +++--- packages/types/src/signer.ts | 4 +- 15 files changed, 238 insertions(+), 102 deletions(-) diff --git a/apps/extension/.prettierrc b/apps/extension/.prettierrc index 193626a11..f0eb61e0f 100644 --- a/apps/extension/.prettierrc +++ b/apps/extension/.prettierrc @@ -2,6 +2,5 @@ "trailingComma": "es5", "tabWidth": 2, "semi": true, - "singleQuote": false, - "bracketSpacing": false + "singleQuote": false } diff --git a/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx index 55c21f550..364172e18 100644 --- a/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx +++ b/apps/extension/src/Approvals/ApproveTx/ConfirmLedgerTx.tsx @@ -15,6 +15,7 @@ import { SubmitSignedBondMsg, SubmitSignedRevealPKMsg, SubmitSignedTransferMsg, + SubmitSignedUnbondMsg, } from "background/ledger/messages"; import { Ports } from "router"; import { closeCurrentTab } from "utils"; @@ -124,6 +125,11 @@ export const ConfirmLedgerTx: React.FC = ({ details }) => { Ports.Background, new SubmitSignedBondMsg(msgId, toBase64(bytes), signatures) ); + case TxType.Unbond: + return await requester.sendMessage( + Ports.Background, + new SubmitSignedUnbondMsg(msgId, toBase64(bytes), signatures) + ); case TxType.Transfer: return await requester.sendMessage( Ports.Background, diff --git a/apps/extension/src/background/approvals/init.ts b/apps/extension/src/background/approvals/init.ts index 60bc02c6a..2fbc98a2e 100644 --- a/apps/extension/src/background/approvals/init.ts +++ b/apps/extension/src/background/approvals/init.ts @@ -1,10 +1,16 @@ import { Router } from "router"; -import { ApproveBondMsg, ApproveTransferMsg } from "provider"; +import { + ApproveBondMsg, + ApproveTransferMsg, + ApproveUnbondMsg, + ApproveWithdrawMsg, +} from "provider"; import { RejectTxMsg, SubmitApprovedBondMsg, SubmitApprovedUnbondMsg, SubmitApprovedTransferMsg, + SubmitApprovedWithdrawMsg, } from "./messages"; import { ROUTE } from "./constants"; @@ -14,10 +20,13 @@ import { getHandler } from "./handler"; export function init(router: Router, service: ApprovalsService): void { router.registerMessage(ApproveBondMsg); router.registerMessage(ApproveTransferMsg); + router.registerMessage(ApproveUnbondMsg); + router.registerMessage(ApproveWithdrawMsg); router.registerMessage(RejectTxMsg); router.registerMessage(SubmitApprovedBondMsg); router.registerMessage(SubmitApprovedUnbondMsg); router.registerMessage(SubmitApprovedTransferMsg); + router.registerMessage(SubmitApprovedWithdrawMsg); router.addHandler(ROUTE, getHandler(service)); } diff --git a/apps/extension/src/background/approvals/messages.ts b/apps/extension/src/background/approvals/messages.ts index 0f7e359a5..fa142a43c 100644 --- a/apps/extension/src/background/approvals/messages.ts +++ b/apps/extension/src/background/approvals/messages.ts @@ -6,6 +6,7 @@ enum MessageType { SubmitApprovedTransfer = "submit-approved-transfer", SubmitApprovedBond = "submit-approved-bond", SubmitApprovedUnbond = "submit-approved-unbond", + SubmitApprovedWithdraw = "submit-approved-withdraw", } export class RejectTxMsg extends Message { @@ -121,3 +122,32 @@ export class SubmitApprovedUnbondMsg extends Message { return SubmitApprovedUnbondMsg.type(); } } + +export class SubmitApprovedWithdrawMsg extends Message { + public static type(): MessageType { + return MessageType.SubmitApprovedWithdraw; + } + + constructor(public readonly msgId: string, public readonly password: string) { + super(); + } + + validate(): void { + if (!this.msgId) { + throw new Error("msgId must not be empty!"); + } + if (!this.password) { + throw new Error("Password is required to submit unbond tx!"); + } + + return; + } + + route(): string { + return ROUTE; + } + + type(): string { + return SubmitApprovedWithdrawMsg.type(); + } +} diff --git a/apps/extension/src/background/approvals/service.ts b/apps/extension/src/background/approvals/service.ts index 00ac2d347..d3df98425 100644 --- a/apps/extension/src/background/approvals/service.ts +++ b/apps/extension/src/background/approvals/service.ts @@ -1,16 +1,21 @@ import browser from "webextension-polyfill"; -import {fromBase64} from "@cosmjs/encoding"; -import {v4 as uuid} from "uuid"; +import { fromBase64 } from "@cosmjs/encoding"; +import { v4 as uuid } from "uuid"; import BigNumber from "bignumber.js"; -import {deserialize} from "@dao-xyz/borsh"; +import { deserialize } from "@dao-xyz/borsh"; -import {AccountType, SubmitBondMsgValue, TransferMsgValue} from "@namada/types"; -import {TxType} from "@namada/shared"; -import {KVStore} from "@namada/storage"; +import { + AccountType, + SubmitBondMsgValue, + SubmitUnbondMsgValue, + TransferMsgValue, +} from "@namada/types"; +import { TxType } from "@namada/shared"; +import { KVStore } from "@namada/storage"; -import {KeyRingService, TabStore} from "background/keyring"; -import {LedgerService} from "background/ledger"; -import {paramsToUrl} from "@namada/utils"; +import { KeyRingService, TabStore } from "background/keyring"; +import { LedgerService } from "background/ledger"; +import { paramsToUrl } from "@namada/utils"; export class ApprovalsService { constructor( @@ -33,7 +38,7 @@ export class ApprovalsService { target, token, amount: amountBN, - tx: {publicKey = ""}, + tx: { publicKey = "" }, } = txDetails; const amount = new BigNumber(amountBN.toString()); const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ @@ -66,7 +71,7 @@ export class ApprovalsService { source, nativeToken: token, amount: amountBN, - tx: {publicKey = ""}, + tx: { publicKey = "" }, } = txDetails; const amount = new BigNumber(amountBN.toString()); const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ @@ -85,16 +90,20 @@ export class ApprovalsService { this._launchApprovalWindow(url); } - // Deserialize bond details and prompt user - async approveUnbond(txMsg: string, type?: AccountType): Promise { + // Deserialize unbond details and prompt user + async approveUnbond(txMsg: string, type: AccountType): Promise { const txMsgBuffer = Buffer.from(fromBase64(txMsg)); const id = uuid(); await this.txStore.set(id, txMsg); // Decode tx details and launch approval screen - const txDetails = deserialize(txMsgBuffer, SubmitBondMsgValue); + const txDetails = deserialize(txMsgBuffer, SubmitUnbondMsgValue); - const {source, nativeToken, amount: amountBN} = txDetails; + const { + source, + amount: amountBN, + tx: { publicKey = "" }, + } = txDetails; const amount = new BigNumber(amountBN.toString()); const baseUrl = `${browser.runtime.getURL("approvals.html")}#/approve-tx/${ TxType.Unbond @@ -103,9 +112,9 @@ export class ApprovalsService { const url = paramsToUrl(baseUrl, { id, source, - token: nativeToken, amount: amount.toString(), accountType: type as string, + publicKey, }); this._launchApprovalWindow(url); diff --git a/apps/extension/src/background/ledger/handler.ts b/apps/extension/src/background/ledger/handler.ts index b03793873..30f19082a 100644 --- a/apps/extension/src/background/ledger/handler.ts +++ b/apps/extension/src/background/ledger/handler.ts @@ -7,6 +7,7 @@ import { SubmitSignedBondMsg, SubmitSignedRevealPKMsg, GetRevealPKBytesMsg, + SubmitSignedUnbondMsg, } from "./messages"; export const getHandler: (service: LedgerService) => Handler = (service) => { @@ -37,7 +38,11 @@ export const getHandler: (service: LedgerService) => Handler = (service) => { env, msg as SubmitSignedBondMsg ); - + case SubmitSignedUnbondMsg: + return handleSubmitSignedUnbondMsg(service)( + env, + msg as SubmitSignedBondMsg + ); default: throw new Error("Unknown msg type"); } @@ -71,6 +76,15 @@ const handleSubmitSignedBondMsg: ( }; }; +const handleSubmitSignedUnbondMsg: ( + service: LedgerService +) => InternalHandler = (service) => { + return async (_, msg) => { + const { bytes, msgId, signatures } = msg; + return await service.submitUnbond(msgId, bytes, signatures); + }; +}; +``; const handleGetTxBytesMsg: ( service: LedgerService ) => InternalHandler = (service) => { diff --git a/apps/extension/src/background/ledger/messages.ts b/apps/extension/src/background/ledger/messages.ts index c0427fabe..00e6d6c69 100644 --- a/apps/extension/src/background/ledger/messages.ts +++ b/apps/extension/src/background/ledger/messages.ts @@ -241,8 +241,7 @@ export class SubmitSignedUnbondMsg extends Message { constructor( public readonly msgId: string, public readonly bytes: string, - public readonly signatures: ResponseSign, - public readonly publicKey: string + public readonly signatures: ResponseSign ) { super(); } @@ -259,10 +258,6 @@ export class SubmitSignedUnbondMsg extends Message { if (!this.signatures) { throw new Error("No signatures were provided!"); } - - if (!this.publicKey) { - throw new Error("No publicKey provided!"); - } } route(): string { diff --git a/apps/extension/src/background/ledger/service.ts b/apps/extension/src/background/ledger/service.ts index d16cc431b..bb19930e9 100644 --- a/apps/extension/src/background/ledger/service.ts +++ b/apps/extension/src/background/ledger/service.ts @@ -5,6 +5,7 @@ import { AccountType, Bip44Path, SubmitBondMsgValue, + SubmitUnbondMsgValue, TransferMsgValue, } from "@namada/types"; import { ResponseSign } from "@namada/ledger-namada"; @@ -233,6 +234,44 @@ export class LedgerService { await this.keyring.broadcastUpdateBalance(); } + /* Submit a bond with provided signatures */ + async submitUnbond( + msgId: string, + bytes: string, + signatures: ResponseSign + ): Promise { + const txMsg = await this.txStore.get(msgId); + + if (!txMsg) { + throw new Error(`Bond Transaction ${msgId} not found!`); + } + + const { tx } = deserialize(fromBase64(txMsg), SubmitUnbondMsgValue); + const encodedTx = encodeTx(tx); + + const { rawSignature, wrapperSignature } = signatures; + + try { + const rawSig = encodeSignature(rawSignature); + const wrapperSig = encodeSignature(wrapperSignature); + await this.sdk.submit_signed_tx( + encodedTx, + fromBase64(bytes), + rawSig, + wrapperSig + ); + + await this.broadcastUpdateStaking(); + + // Clear pending tx if successful + await this.txStore.set(msgId, null); + } catch (e) { + console.warn(e); + } + + await this.keyring.broadcastUpdateBalance(); + } + /** * Append a new address record for use with Ledger */ diff --git a/apps/extension/src/provider/InjectedNamada.ts b/apps/extension/src/provider/InjectedNamada.ts index b566377a0..f2fc5b07c 100644 --- a/apps/extension/src/provider/InjectedNamada.ts +++ b/apps/extension/src/provider/InjectedNamada.ts @@ -4,12 +4,13 @@ import { DerivedAccount, Namada as INamada, Signer as ISigner, + TxMsgProps, } from "@namada/types"; -import { InjectedProxy } from "./InjectedProxy"; -import { Signer } from "./Signer"; +import {InjectedProxy} from "./InjectedProxy"; +import {Signer} from "./Signer"; export class InjectedNamada implements INamada { - constructor(private readonly _version: string) { } + constructor(private readonly _version: string) {} public async connect(chainId: string): Promise { return await InjectedProxy.requestMethod("connect", chainId); @@ -39,10 +40,10 @@ export class InjectedNamada implements INamada { public async balances( owner: string - ): Promise<{ token: string; amount: string }[]> { + ): Promise<{token: string; amount: string}[]> { return await InjectedProxy.requestMethod< string, - { token: string; amount: string }[] + {token: string; amount: string}[] >("balances", owner); } @@ -50,43 +51,40 @@ export class InjectedNamada implements INamada { return new Signer(chainId, this); } - public async submitBond(props: { - txMsg: string; - type: AccountType; - publicKey?: string; - }): Promise { - const { txMsg, type, publicKey } = props; + public async submitBond(props: TxMsgProps): Promise { + const {txMsg, type} = props; return await InjectedProxy.requestMethod< - { txMsg: string; type: AccountType; publicKey?: string }, + {txMsg: string; type: AccountType}, void >("submitBond", { txMsg, type, - publicKey, }); } - public async submitUnbond(txMsg: string): Promise { - return await InjectedProxy.requestMethod( - "submitUnbond", - txMsg - ); + public async submitUnbond(props: TxMsgProps): Promise { + const {txMsg, type} = props; + return await InjectedProxy.requestMethod< + {txMsg: string; type: AccountType}, + void + >("submitUnbond", {txMsg, type}); } - public async submitWithdraw(txMsg: string): Promise { - return await InjectedProxy.requestMethod( - "submitWithdraw", - txMsg - ); + public async submitWithdraw(props: TxMsgProps): Promise { + const {txMsg, type} = props; + return await InjectedProxy.requestMethod< + {txMsg: string; type: AccountType}, + void + >("submitWithdraw", {txMsg, type}); } public async submitTransfer(props: { txMsg: string; type: AccountType; }): Promise { - const { txMsg, type } = props; + const {txMsg, type} = props; return await InjectedProxy.requestMethod< - { txMsg: string; type: AccountType }, + {txMsg: string; type: AccountType}, void >("submitTransfer", { txMsg, @@ -106,7 +104,7 @@ export class InjectedNamada implements INamada { address: string; }): Promise { return await InjectedProxy.requestMethod< - { txMsg: string; address: string }, + {txMsg: string; address: string}, string >("encodeInitAccount", props); } diff --git a/apps/extension/src/provider/Namada.ts b/apps/extension/src/provider/Namada.ts index 123c5dc7b..d57b93eac 100644 --- a/apps/extension/src/provider/Namada.ts +++ b/apps/extension/src/provider/Namada.ts @@ -92,7 +92,6 @@ export class Namada implements INamada { public async submitBond(props: { txMsg: string; type: AccountType; - publicKey?: string; }): Promise { const { txMsg, type } = props; return await this.requester?.sendMessage( @@ -101,17 +100,25 @@ export class Namada implements INamada { ); } - public async submitUnbond(txMsg: string): Promise { + public async submitUnbond(props: { + txMsg: string; + type: AccountType; + }): Promise { + const { txMsg, type } = props; return await this.requester?.sendMessage( Ports.Background, - new ApproveUnbondMsg(txMsg) + new ApproveUnbondMsg(txMsg, type) ); } - public async submitWithdraw(txMsg: string): Promise { + public async submitWithdraw(props: { + txMsg: string; + type: AccountType; + }): Promise { + const { txMsg, type } = props; return await this.requester?.sendMessage( Ports.Background, - new ApproveWithdrawMsg(txMsg) + new ApproveWithdrawMsg(txMsg, type) ); } diff --git a/apps/extension/src/provider/Signer.ts b/apps/extension/src/provider/Signer.ts index 95875d944..fb642ef4e 100644 --- a/apps/extension/src/provider/Signer.ts +++ b/apps/extension/src/provider/Signer.ts @@ -16,12 +16,13 @@ import { SubmitUnbondMsgValue, SubmitWithdrawMsgValue, } from "@namada/types"; +import { ApproveWithdrawMsg } from "./messages"; export class Signer implements ISigner { constructor( protected readonly chainId: string, private readonly _namada: Namada - ) { } + ) {} public async accounts(): Promise { return (await this._namada.accounts(this.chainId))?.map( @@ -41,8 +42,7 @@ export class Signer implements ISigner { */ public async submitBond( args: SubmitBondProps, - type: AccountType, - publicKey?: string + type: AccountType ): Promise { const msgValue = new SubmitBondMsgValue(args); const msg = new Message(); @@ -51,32 +51,38 @@ export class Signer implements ISigner { return await this._namada.submitBond({ txMsg: toBase64(encoded), type, - publicKey, }); } /** * Submit unbond transaction */ - public async submitUnbond(args: SubmitBondProps): Promise { + public async submitUnbond( + args: SubmitBondProps, + type: AccountType + ): Promise { const msgValue = new SubmitUnbondMsgValue(args); - const msg = new Message(); const encoded = msg.encode(msgValue); - return await this._namada.submitUnbond(toBase64(encoded)); + return await this._namada.submitUnbond({ txMsg: toBase64(encoded), type }); } /** * Submit withdraw transaction */ - public async submitWithdraw(args: SubmitBondProps): Promise { + public async submitWithdraw( + args: SubmitBondProps, + type: AccountType + ): Promise { const msgValue = new SubmitWithdrawMsgValue(args); - const msg = new Message(); const encoded = msg.encode(msgValue); - return await this._namada.submitWithdraw(toBase64(encoded)); + return await this._namada.submitWithdraw({ + txMsg: toBase64(encoded), + type, + }); } /** @@ -105,7 +111,9 @@ export class Signer implements ISigner { const serializedIbcTransfer = ibcTransferMessage.encode(ibcTransferMsgValue); - return await this._namada.submitIbcTransfer(toBase64(serializedIbcTransfer)); + return await this._namada.submitIbcTransfer( + toBase64(serializedIbcTransfer) + ); } /** diff --git a/apps/extension/src/provider/messages.ts b/apps/extension/src/provider/messages.ts index 2e7fd9cea..9fccdcafe 100644 --- a/apps/extension/src/provider/messages.ts +++ b/apps/extension/src/provider/messages.ts @@ -301,7 +301,7 @@ export class ApproveUnbondMsg extends Message { constructor( public readonly txMsg: string, - public readonly accountType?: AccountType + public readonly accountType: AccountType ) { super(); } @@ -310,6 +310,10 @@ export class ApproveUnbondMsg extends Message { if (!this.txMsg) { throw new Error("txMsg was not provided!"); } + if (!this.accountType) { + throw new Error("accountType was not provided!"); + } + return; } @@ -327,7 +331,10 @@ export class ApproveWithdrawMsg extends Message { return MessageType.ApproveWithdraw; } - constructor(public readonly txMsg: string) { + constructor( + public readonly txMsg: string, + public readonly accountType: AccountType + ) { super(); } @@ -335,6 +342,10 @@ export class ApproveWithdrawMsg extends Message { if (!this.txMsg) { throw new Error("An encoded txMsg is required!"); } + if (!this.accountType) { + throw new Error("accountType was not provided!"); + } + return; } diff --git a/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts b/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts index e597d5294..6cb6493db 100644 --- a/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts +++ b/apps/namada-interface/src/slices/StakingAndGovernance/actions.ts @@ -269,20 +269,29 @@ export const postNewUnbonding = createAsyncThunk< { state: RootState } >(POST_UNSTAKING, async (change, thunkApi) => { const { chainId } = thunkApi.getState().settings; + const { derived } = thunkApi.getState().accounts; const integration = getIntegration(chainId); const signer = integration.signer() as Signer; + const { owner } = change; + const { + details: { type, publicKey }, + } = derived[chainId][owner]; - await signer.submitUnbond({ - source: change.owner, - validator: change.validatorId, - amount: change.amount, - tx: { - token: Tokens.NAM.address || "", - feeAmount: new BigNumber(0), - gasLimit: new BigNumber(0), - chainId, + await signer.submitUnbond( + { + source: change.owner, + validator: change.validatorId, + amount: change.amount, + tx: { + token: Tokens.NAM.address || "", + feeAmount: new BigNumber(0), + gasLimit: new BigNumber(0), + chainId, + publicKey, + }, }, - }); + type + ); }); export const postNewWithdraw = createAsyncThunk< @@ -291,17 +300,24 @@ export const postNewWithdraw = createAsyncThunk< { state: RootState } >(POST_UNSTAKING, async ({ owner, validatorId }, thunkApi) => { const { chainId } = thunkApi.getState().settings; + const { derived } = thunkApi.getState().accounts; const integration = getIntegration(chainId); const signer = integration.signer() as Signer; + const { + details: { type }, + } = derived[chainId][owner]; - await signer.submitWithdraw({ - source: owner, - validator: validatorId, - tx: { - token: Tokens.NAM.address || "", - feeAmount: new BigNumber(0), - gasLimit: new BigNumber(0), - chainId, + await signer.submitWithdraw( + { + source: owner, + validator: validatorId, + tx: { + token: Tokens.NAM.address || "", + feeAmount: new BigNumber(0), + gasLimit: new BigNumber(0), + chainId, + }, }, - }); + type + ); }); diff --git a/packages/types/src/namada.ts b/packages/types/src/namada.ts index a4385bccd..7f9365a20 100644 --- a/packages/types/src/namada.ts +++ b/packages/types/src/namada.ts @@ -2,6 +2,8 @@ import { AccountType, DerivedAccount } from "./account"; import { Chain } from "./chain"; import { Signer } from "./signer"; +export type TxMsgProps = { txMsg: string; type: AccountType }; + export interface Namada { connect(chainId: string): Promise; accounts(chainId: string): Promise; @@ -11,17 +13,10 @@ export interface Namada { suggestChain(chainConfig: Chain): Promise; chain: (chainId: string) => Promise; chains: () => Promise; - submitBond: (props: { - txMsg: string; - type: AccountType; - publicKey?: string; - }) => Promise; - submitUnbond: (txMsg: string) => Promise; - submitWithdraw: (txMsg: string) => Promise; - submitTransfer: (props: { - txMsg: string; - type: AccountType; - }) => Promise; + submitBond: (props: TxMsgProps) => Promise; + submitUnbond: (props: TxMsgProps) => Promise; + submitWithdraw: (props: TxMsgProps) => Promise; + submitTransfer: (props: TxMsgProps) => Promise; submitIbcTransfer: (txMsg: string) => Promise; encodeInitAccount: (props: { txMsg: string; diff --git a/packages/types/src/signer.ts b/packages/types/src/signer.ts index b6c86fb4d..0ce0afb61 100644 --- a/packages/types/src/signer.ts +++ b/packages/types/src/signer.ts @@ -11,8 +11,8 @@ import { export interface Signer { accounts: () => Promise; submitBond(args: SubmitBondProps, type: AccountType): Promise; - submitUnbond(args: SubmitUnbondProps): Promise; - submitWithdraw(args: SubmitWithdrawProps): Promise; + submitUnbond(args: SubmitUnbondProps, type: AccountType): Promise; + submitWithdraw(args: SubmitWithdrawProps, type: AccountType): Promise; submitTransfer(args: TransferProps, type: AccountType): Promise; submitIbcTransfer(args: IbcTransferProps): Promise; encodeInitAccount( From 3eaeda815b874d290ecb3e428101bdd1d6ed2aee Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Tue, 25 Jul 2023 11:05:21 -0400 Subject: [PATCH 07/18] Lint --- apps/extension/src/provider/Signer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/extension/src/provider/Signer.ts b/apps/extension/src/provider/Signer.ts index fb642ef4e..68d03de12 100644 --- a/apps/extension/src/provider/Signer.ts +++ b/apps/extension/src/provider/Signer.ts @@ -16,13 +16,12 @@ import { SubmitUnbondMsgValue, SubmitWithdrawMsgValue, } from "@namada/types"; -import { ApproveWithdrawMsg } from "./messages"; export class Signer implements ISigner { constructor( protected readonly chainId: string, private readonly _namada: Namada - ) {} + ) { } public async accounts(): Promise { return (await this._namada.accounts(this.chainId))?.map( From 5f1ae75350dfd6e48d020f05d2101ce22f7eea13 Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Tue, 25 Jul 2023 11:27:19 -0400 Subject: [PATCH 08/18] Clean up ConfirmTx --- .../src/Approvals/ApproveTx/ApproveTx.tsx | 3 +- .../Approvals/ApproveTx/ConfirmLedgerTx.tsx | 3 +- .../src/Approvals/ApproveTx/ConfirmTx.tsx | 82 +++++++++---------- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/apps/extension/src/Approvals/ApproveTx/ApproveTx.tsx b/apps/extension/src/Approvals/ApproveTx/ApproveTx.tsx index 5890d7ea4..9b9d158e6 100644 --- a/apps/extension/src/Approvals/ApproveTx/ApproveTx.tsx +++ b/apps/extension/src/Approvals/ApproveTx/ApproveTx.tsx @@ -76,7 +76,7 @@ export const ApproveTx: React.FC = ({ setDetails }) => { return (

- Approve this {TxTypeLabel[txType as TxType]} + Approve this {TxTypeLabel[txType as TxType]}{" "} transaction?

Source: 

@@ -90,7 +90,6 @@ export const ApproveTx: React.FC = ({ setDetails }) => {

Amount: {amount} {tokenType}

-