Skip to content

Commit

Permalink
Add generic error message for send operation (#2185)
Browse files Browse the repository at this point in the history
* Remove unused components SendTokensFormPage and RecipientsPage

* Add generic message for errors during sign operation

* Update tests

* Move error handling logic to @umami/utils; Use CustomError for custom error message

* Fix pnpm package issue

* Remove error stacktrace for web errormodels

---------

Co-authored-by: Ajinkya Rajandekar <[email protected]>
  • Loading branch information
OKendigelyan and ajinkyaraj-23 authored Dec 6, 2024
1 parent 7bf8c68 commit b3ef307
Show file tree
Hide file tree
Showing 82 changed files with 517 additions and 304 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ docs/

storybook-static

# IDE
.idea
1 change: 1 addition & 0 deletions apps/desktop-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@umami/tezos": "workspace:^",
"@umami/typescript-config": "workspace:^",
"@umami/tzkt": "workspace:^",
"@umami/utils": "workspace:^",
"date-fns": "^4.1.0",
"lodash": "^4.17.21",
"react": "^18.3.1",
Expand Down
11 changes: 6 additions & 5 deletions apps/desktop-e2e/src/helpers/AccountGroup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InMemorySigner } from "@taquito/signer";
import { type RawPkh, derivePublicKeyPair, makeDerivationPath } from "@umami/tezos";
import { CustomError } from "@umami/utils";
import lodash from "lodash";

export type AccountGroup = {
Expand Down Expand Up @@ -41,14 +42,14 @@ export class AccountGroupBuilder {

setDerivationPathTemplate(derivationPathTemplate: string): void {
if (this.accountGroup.type !== "mnemonic") {
throw new Error(`Derivation path is not used for ${this.accountGroup.type} accounts}`);
throw new CustomError(`Derivation path is not used for ${this.accountGroup.type} accounts}`);
}
this.derivationPathTemplate = derivationPathTemplate;
}

setSeedPhrase(seedPhrase: string[]): void {
if (this.accountGroup.type !== "mnemonic") {
throw new Error(`Seed phrase is not used for ${this.accountGroup.type} accounts}`);
throw new CustomError(`Seed phrase is not used for ${this.accountGroup.type} accounts}`);
}
this.seedPhrase = seedPhrase;
}
Expand All @@ -57,7 +58,7 @@ export class AccountGroupBuilder {

async setSecretKey(secretKey: string, accountIndex = 0): Promise<void> {
if (this.accountGroup.type !== "secret_key") {
throw new Error(`Secret key is not used for ${this.accountGroup.type} accounts}`);
throw new CustomError(`Secret key is not used for ${this.accountGroup.type} accounts}`);
}
this.accountGroup.accounts[accountIndex].pkh = await (
await InMemorySigner.fromSecretKey(secretKey)
Expand All @@ -84,10 +85,10 @@ export class AccountGroupBuilder {

private async setMnemonicPkhs() {
if (this.seedPhrase.length === 0) {
throw new Error("Seed phrase is not set");
throw new CustomError("Seed phrase is not set");
}
if (this.derivationPathTemplate === "") {
throw new Error("Derivation path is not set");
throw new CustomError("Derivation path is not set");
}
for (let i = 0; i < this.accountGroup.accounts.length; i++) {
const keyPair = await derivePublicKeyPair(
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop-e2e/src/steps/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Given, Then, When } from "@cucumber/cucumber";
import { expect } from "@playwright/test";
import { mnemonic1 as existingSeedphrase } from "@umami/test-utils";
import { DEFAULT_DERIVATION_PATH_TEMPLATE } from "@umami/tezos";
import { CustomError } from "@umami/utils";

import { type CustomWorld } from "./world";
import { type AccountGroup, AccountGroupBuilder } from "../helpers/AccountGroup";
Expand Down Expand Up @@ -119,7 +120,7 @@ Then(
if (backupFileName === "V2Backup.json") {
expectedGroups = await v2BackedupAccountGroups();
} else {
throw new Error(`Unknown backup file: ${backupFileName}`);
throw new CustomError(`Unknown backup file: ${backupFileName}`);
}

// TODO: check for groups amount once all type of groups are supported by the tests
Expand Down
5 changes: 3 additions & 2 deletions apps/desktop-e2e/src/steps/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type Account } from "@umami/core";
import { type AccountsState, makeSecretKeyAccount } from "@umami/state";
import { BLOCK_TIME } from "@umami/tezos";
import { getOperationsByHash } from "@umami/tzkt";
import { CustomError } from "@umami/utils";
import { minutesToMilliseconds } from "date-fns";
import { some } from "lodash";

Expand All @@ -28,7 +29,7 @@ Given(/I have accounts?/, async function (this: CustomWorld, table: DataTable) {
accounts.items.push(account);
accounts.secretKeys[account.address.pkh] = encryptedSecretKey;
} else {
throw new Error(`${data.type} account is not supported yet`);
throw new CustomError(`${data.type} account is not supported yet`);
}
}
this.setReduxState({ accounts });
Expand Down Expand Up @@ -105,7 +106,7 @@ When(
return matches[1];
}
}
throw new Error("TZKT sync last applied block not found");
throw new CustomError("TZKT sync last applied block not found");
};

for (let i = 0; i < 2; i++) {
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"@umami/state": "workspace:^",
"@umami/test-utils": "workspace:^",
"@umami/tezos": "workspace:^",
"@umami/utils": "workspace:^",
"@umami/typescript-config": "workspace:^",
"@umami/tzkt": "workspace:^",
"@vitejs/plugin-react": "^4.3.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Box, Flex, Text } from "@chakra-ui/react";
import { type Operation, tokenNameSafe, tokenPrettyAmount } from "@umami/core";
import { useGetToken } from "@umami/state";
import { prettyTezAmount } from "@umami/tezos";
import { CustomError } from "@umami/utils";

import { OutgoingArrow } from "../../../../assets/icons";
import colors from "../../../../style/colors";
Expand Down Expand Up @@ -45,7 +46,7 @@ export const MultisigDecodedOperation = ({ operation }: { operation: Operation }
case "stake":
case "unstake":
case "finalize_unstake":
throw new Error(`${operation.type} is not supported yet`);
throw new CustomError(`${operation.type} is not supported yet`);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { type MultisigOperation, parseRawMichelson } from "@umami/multisig";
import { useAsyncActionHandler, useGetImplicitAccountSafe, useSelectedNetwork } from "@umami/state";
import { type ImplicitAddress } from "@umami/tezos";
import { CustomError } from "@umami/utils";

import { MultisigActionButton, type MultisigSignerState } from "./MultisigActionButton";
import colors from "../../../../style/colors";
Expand Down Expand Up @@ -40,7 +41,7 @@ export const MultisigSignerTile = ({
const approveOrExecute = () =>
handleAsyncAction(async () => {
if (!signer) {
throw new Error("Can't approve or execute with an account you don't own");
throw new CustomError("Can't approve or execute with an account you don't own");
}

const actionType = operationIsExecutable ? "execute" : "approve";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
useSelectedNetwork,
} from "@umami/state";
import { type RawPkh } from "@umami/tezos";
import { CustomError } from "@umami/utils";
import Papa, { type ParseResult } from "papaparse";
import { FormProvider, useForm } from "react-hook-form";

Expand Down Expand Up @@ -63,7 +64,9 @@ export const CSVFileUploadForm = () => {
Papa.parse(file[0], { skipEmptyLines: true, complete: resolve });
});
if (rows.errors.length > 0) {
throw new Error("Error loading csv file: " + rows.errors.map(e => e.message).join(", "));
throw new CustomError(
"Error loading csv file: " + rows.errors.map(e => e.message).join(", ")
);
}

const operations: Operation[] = [];
Expand All @@ -72,7 +75,7 @@ export const CSVFileUploadForm = () => {
try {
operations.push(parseOperation(senderAccount.address, row, getToken));
} catch (error: any) {
throw new Error(`Error at row #${i + 1}: ${error?.message}`);
throw new CustomError(`Error at row #${i + 1}: ${error?.message}`);
}
}

Expand Down
13 changes: 7 additions & 6 deletions apps/desktop/src/components/CSVFileUploader/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
parsePkh,
tezToMutez,
} from "@umami/tezos";
import { CustomError } from "@umami/utils";

import { validateNonNegativeNumber } from "../../utils/helpers";

Expand All @@ -19,16 +20,16 @@ export const parseOperation = (
const filteredRow = row.filter(col => col.length > 0);
const len = filteredRow.length;
if (len < 2 || 4 < len) {
throw new Error("Invalid csv format");
throw new CustomError("Invalid csv format");
}
const [recipientPkh, prettyAmount, contractPkh] = filteredRow;
if (!isAddressValid(recipientPkh)) {
throw new Error("Invalid csv value: recipient");
throw new CustomError("Invalid csv value: recipient");
}
const recipient = parsePkh(recipientPkh);

if (validateNonNegativeNumber(prettyAmount) === null) {
throw new Error("Invalid csv value: amount");
throw new CustomError("Invalid csv value: amount");
}

if (len === 2) {
Expand All @@ -40,18 +41,18 @@ export const parseOperation = (
}

if (!isValidContractPkh(contractPkh)) {
throw new Error("Invalid csv value: contract address");
throw new CustomError("Invalid csv value: contract address");
}

const contract = parseContractPkh(contractPkh);
const tokenId = filteredRow[3] || "0";
if (validateNonNegativeNumber(tokenId) === null) {
throw new Error("Invalid csv value: tokenId");
throw new CustomError("Invalid csv value: tokenId");
}

const token = getToken(contractPkh, tokenId);
if (!token) {
throw new Error(`Unknown token ${contractPkh} ${tokenId}`);
throw new CustomError(`Unknown token ${contractPkh} ${tokenId}`);
}
const amount = getRealAmount(token, prettyAmount);

Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/components/Onboarding/FakeAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
makeDerivationPath,
parseImplicitPkh,
} from "@umami/tezos";
import { CustomError } from "@umami/utils";
import { useForm } from "react-hook-form";

import { ModalContentWrapper } from "./ModalContentWrapper";
Expand All @@ -26,7 +27,7 @@ export const FakeAccount = ({ onClose }: { onClose: () => void }) => {
const onSubmit = async ({ pkh, name, idp }: { pkh: string; name: string; idp?: IDP }) => {
if (idp && idp.length > 0) {
if (!["google", "facebook", "twitter", "reddit", "email"].includes(idp)) {
throw new Error("Invalid IDP");
throw new CustomError("Invalid IDP");
}
}
const rpc = new RpcClient(GHOSTNET.rpcUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useRestoreFromSecretKey,
useValidateMasterPassword,
} from "@umami/state";
import { CustomError } from "@umami/utils";

import { type MasterPasswordStep } from "../OnboardingStep";
import { EnterAndConfirmPassword } from "./password/EnterAndConfirmPassword";
Expand Down Expand Up @@ -37,7 +38,7 @@ export const MasterPassword = ({
}

if (!account) {
throw new Error("No account data provided.");
throw new CustomError("No account data provided.");
}

switch (account.type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Box, Button, Grid, GridItem, Heading, Select, VStack } from "@chakra-ui
import { MnemonicAutocomplete } from "@umami/components";
import { useAsyncActionHandler } from "@umami/state";
import { mnemonic1 } from "@umami/test-utils";
import { CustomError } from "@umami/utils";
import { validateMnemonic } from "bip39";
import { range } from "lodash";
import { useState } from "react";
Expand Down Expand Up @@ -53,7 +54,7 @@ export const RestoreMnemonic = ({ goToStep }: { goToStep: (step: OnboardingStep)
handleAsyncAction(async () => {
const words = mnemonic.split(" ");
if (!mnemonicSizes.includes(words.length)) {
throw new Error(`the mnemonic must be ${mnemonicSizes.join(", ")} words long`);
throw new CustomError(`the mnemonic must be ${mnemonicSizes.join(", ")} words long`);
}
words.slice(0, mnemonicSize).forEach((word, i) => {
setValue(`word${i}`, word);
Expand All @@ -65,7 +66,7 @@ export const RestoreMnemonic = ({ goToStep }: { goToStep: (step: OnboardingStep)
handleAsyncAction(async () => {
const mnemonic = Object.values(data).join(" ").trim();
if (!validateMnemonic(mnemonic)) {
throw new Error("Invalid Mnemonic");
throw new CustomError("Invalid Mnemonic");
}
goToStep({
type: "nameAccount",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CustomError } from "@umami/utils";

import { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { ContractCallSignPage } from "./ContractCallSignPage";
import { DelegationSignPage } from "./DelegationSignPage";
Expand Down Expand Up @@ -40,6 +42,6 @@ export const BeaconSignPage = ({ operation, message }: BeaconSignPageProps) => {
*/
case "fa1.2":
case "fa2":
throw new Error("Unsupported operation type");
throw new CustomError("Unsupported operation type");
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { type TezosToolkit } from "@taquito/taquito";
import { multisigsActions, useAppDispatch, useAsyncActionHandler } from "@umami/state";
import { parsePkh } from "@umami/tezos";
import { CustomError } from "@umami/utils";
import { FormProvider } from "react-hook-form";

import { type FormValues } from "./FormValues";
Expand Down Expand Up @@ -63,7 +64,7 @@ export const SignTransactionFormPage = (props: SignPageProps<FormValues>) => {
* fetch the contract address, we won't assign the provided label and
* the contract will appear with a default label
*/
throw new Error("An error occurred during contract origination");
throw new CustomError("An error occurred during contract origination");
}

const pkh = (await operation.getOriginatedContractAddresses())[0];
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/components/UpsertContactModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
useValidateNewContactPkh,
} from "@umami/state";
import { isValidContractPkh } from "@umami/tezos";
import { CustomError } from "@umami/utils";
import { type FC, useEffect, useRef } from "react";
import { useForm } from "react-hook-form";

Expand Down Expand Up @@ -52,7 +53,7 @@ export const UpsertContactModal: FC<{
newContact.pkh,
]);
if (!contractsWithNetworks.has(newContact.pkh)) {
throw new Error(`Network not found for contract ${newContact.pkh}`);
throw new CustomError(`Network not found for contract ${newContact.pkh}`);
}
dispatch(
contactsActions.upsert({
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* istanbul ignore file */
import "./index.css";

import { getErrorContext } from "@umami/core";
import { errorsActions } from "@umami/state";
import { getErrorContext } from "@umami/utils";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { ErrorBoundary } from "react-error-boundary";
Expand Down
7 changes: 4 additions & 3 deletions apps/desktop/src/utils/beacon/useHandleBeaconMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
useRemoveBeaconPeerBySenderId,
} from "@umami/state";
import { type Network } from "@umami/tezos";
import { CustomError } from "@umami/utils";

import { PermissionRequestModal } from "./PermissionRequestModal";
import { SignPayloadRequestModal } from "./SignPayloadRequestModal";
Expand Down Expand Up @@ -49,7 +50,7 @@ export const useHandleBeaconMessage = () => {
type: BeaconMessageType.Error,
errorType: BeaconErrorType.NETWORK_NOT_SUPPORTED,
});
throw new Error(
throw new CustomError(
`Got Beacon request from an unknown network: ${JSON.stringify(
beaconNetwork
)}. Please add it to the networks list and retry.`
Expand Down Expand Up @@ -101,7 +102,7 @@ export const useHandleBeaconMessage = () => {
type: BeaconMessageType.Error,
errorType: BeaconErrorType.NO_PRIVATE_KEY_FOUND_ERROR,
});
throw new Error(`Unknown account: ${message.sourceAddress}`);
throw new CustomError(`Unknown account: ${message.sourceAddress}`);
}
const operation = toAccountOperations(
message.operationDetails,
Expand Down Expand Up @@ -132,7 +133,7 @@ export const useHandleBeaconMessage = () => {
errorType: BeaconErrorType.UNKNOWN_ERROR,
});

throw new Error(`Unknown Beacon message type: ${message.type}`);
throw new CustomError(`Unknown Beacon message type: ${message.type}`);
}
}

Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/views/batch/BatchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useSelectedNetwork,
} from "@umami/state";
import { TEZ } from "@umami/tezos";
import { CustomError } from "@umami/utils";
import pluralize from "pluralize";
import { useEffect, useState } from "react";

Expand Down Expand Up @@ -76,7 +77,7 @@ const prettyOperationType = (operation: Operation) => {
return "Finalize Unstake";
case "contract_origination":
case "contract_call":
throw new Error(`${operation.type} is not supported yet`);
throw new CustomError(`${operation.type} is not supported yet`);
}
};

Expand Down
Loading

0 comments on commit b3ef307

Please sign in to comment.