Skip to content

Commit

Permalink
feat: Beacon responds with Error to dApp
Browse files Browse the repository at this point in the history
  • Loading branch information
dianasavvatina committed Jan 22, 2025
1 parent 0a3987e commit eb62201
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 53 deletions.
19 changes: 15 additions & 4 deletions apps/web/src/components/SendFlow/Beacon/useSignWithBeacon.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { BeaconMessageType, type OperationResponseInput } from "@airgap/beacon-wallet";
import {
BeaconErrorType,
BeaconMessageType,
type OperationResponseInput,
} from "@airgap/beacon-wallet";
import { type TezosToolkit } from "@taquito/taquito";
import { useDynamicModalContext } from "@umami/components";
import { executeOperations, totalFee } from "@umami/core";
import { WalletClient, useAsyncActionHandler } from "@umami/state";
import { getErrorContext } from "@umami/utils";
import { useForm } from "react-hook-form";

import { SuccessStep } from "../SuccessStep";
Expand Down Expand Up @@ -34,9 +39,15 @@ export const useSignWithBeacon = ({

return openWith(<SuccessStep hash={opHash} />);
},
(error: { message: any }) => ({
description: `Failed to confirm Beacon operation: ${error.message}`,
})
(error: any) => {
const context = getErrorContext(error);
void WalletClient.respond({
id: headerProps.requestId.id.toString(),
type: BeaconMessageType.Error,
errorType: BeaconErrorType.UNKNOWN_ERROR,
});
return { description: `Failed to confirm Beacon operation: ${context.description}` };
}
);

return {
Expand Down
70 changes: 28 additions & 42 deletions apps/web/src/components/beacon/useHandleBeaconMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
useRemoveBeaconPeerBySenderId,
} from "@umami/state";
import { type Network } from "@umami/tezos";
import { CustomError } from "@umami/utils";
import { BeaconError, CustomError, getErrorContext } from "@umami/utils";

import { PermissionRequestModal } from "./PermissionRequestModal";
import { SignPayloadRequestModal } from "../common/SignPayloadRequestModal";
Expand All @@ -41,9 +41,22 @@ export const useHandleBeaconMessage = () => {
const findNetwork = useFindNetwork();
const removePeer = useRemoveBeaconPeerBySenderId();

// Beacon SDK expects errorData for TRANSACTION_INVALID_ERROR only and as an array of RPC errors
const respondWithError = async (
messageId: string,
errorType: BeaconErrorType,
errorData?: any
) => {
await WalletClient.respond({
id: messageId,
type: BeaconMessageType.Error,
errorType,
errorData,
});
};

// we should confirm that we support the network that the beacon request is coming from
const checkNetwork = ({
id: messageId,
network: beaconNetwork,
}: {
id: string;
Expand All @@ -52,15 +65,10 @@ export const useHandleBeaconMessage = () => {
const network = findNetwork(beaconNetwork.type);

if (!network) {
void WalletClient.respond({
id: messageId,
type: BeaconMessageType.Error,
errorType: BeaconErrorType.NETWORK_NOT_SUPPORTED,
});
throw new CustomError(
throw new BeaconError(
`Got Beacon request from an unknown network: ${JSON.stringify(
beaconNetwork
)}. Please add it to the networks list and retry.`
)}. Please add it to the networks list and retry.`, BeaconErrorType.NETWORK_NOT_SUPPORTED
);
}

Expand All @@ -79,11 +87,7 @@ export const useHandleBeaconMessage = () => {
checkNetwork(message);
modal = <PermissionRequestModal request={message} />;
onClose = async () => {
await WalletClient.respond({
id: message.id,
type: BeaconMessageType.Error,
errorType: BeaconErrorType.NOT_GRANTED_ERROR,
});
await respondWithError(message.id, BeaconErrorType.NOT_GRANTED_ERROR);
await removePeer(message.senderId);
};
break;
Expand All @@ -100,24 +104,15 @@ export const useHandleBeaconMessage = () => {
};
modal = <SignPayloadRequestModal opts={signPayloadProps} />;
onClose = async () => {
await WalletClient.respond({
id: message.id,
type: BeaconMessageType.Error,
errorType: BeaconErrorType.ABORTED_ERROR,
});
await respondWithError(message.id, BeaconErrorType.ABORTED_ERROR);
};
break;
}
case BeaconMessageType.OperationRequest: {
const network = checkNetwork(message);
const signer = getAccount(message.sourceAddress);
if (!signer) {
void WalletClient.respond({
id: message.id,
type: BeaconMessageType.Error,
errorType: BeaconErrorType.NO_PRIVATE_KEY_FOUND_ERROR,
});
throw new CustomError(`Unknown account: ${message.sourceAddress}`);
throw new BeaconError(`Unknown account: ${message.sourceAddress}`, BeaconErrorType.NO_PRIVATE_KEY_FOUND_ERROR);
}

const operation = toAccountOperations(
Expand All @@ -141,33 +136,24 @@ export const useHandleBeaconMessage = () => {
} else {
modal = <BatchSignPage {...signProps} {...message.operationDetails} />;
}
onClose = () =>
WalletClient.respond({
id: message.id,
type: BeaconMessageType.Error,
errorType: BeaconErrorType.ABORTED_ERROR,
});
onClose = () => respondWithError(message.id, BeaconErrorType.ABORTED_ERROR);

break;
}
default: {
// TODO: Open a modal with an unknown operation instead

void WalletClient.respond({
id: message.id,
type: BeaconMessageType.Error,
errorType: BeaconErrorType.UNKNOWN_ERROR,
});

throw new CustomError(`Unknown Beacon message type: ${message.type}`);
throw new BeaconError(`Unknown Beacon message type: ${message.type}`, BeaconErrorType.UNKNOWN_ERROR);
}
}

return openWith(modal, { onClose });
},
(error: { message: any }) => ({
description: `Error while processing Beacon request: ${error.message}`,
})
(error: any) => {
const context = getErrorContext(error);
const errorType = error.errorType || BeaconErrorType.UNKNOWN_ERROR;
void respondWithError(message.id, errorType);
return {description: `Error while processing Beacon request: ${context.description}`};
}
);
};
};
2 changes: 1 addition & 1 deletion packages/core/src/AccountOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BigNumber } from "bignumber.js";

import { type Account, type ImplicitAccount, type MultisigAccount } from "./Account";
import { type Operation } from "./Operation";
import { CustomError } from "../../utils/src/ErrorContext";
import { CustomError } from "@umami/utils";

type ProposalOperations = {
type: "proposal";
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MANAGER_LAMBDA } from "@taquito/taquito";
import { type Address, type ContractAddress, type ImplicitAddress } from "@umami/tezos";
import { isEqual } from "lodash";

import { CustomError } from "../../utils/src/ErrorContext";
import { CustomError } from "@umami/utils";

export type TezTransfer = {
type: "tez";
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/beaconUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isValidImplicitPkh, parseImplicitPkh, parsePkh } from "@umami/tezos";
import { type ImplicitAccount } from "./Account";
import { type ImplicitOperations } from "./AccountOperations";
import { type ContractOrigination, type Operation } from "./Operation";
import { CustomError } from "../../utils/src/ErrorContext";
import { CustomError } from "@umami/utils";

/**
* takes a list of {@link PartialTezosOperation} which come from Beacon
Expand Down
24 changes: 20 additions & 4 deletions packages/utils/src/ErrorContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { TezosOperationError, type TezosOperationErrorWithMessage } from "@taqui
import { type ErrorResponse } from "@walletconnect/jsonrpc-utils";
import { type SessionTypes } from "@walletconnect/types";
import sanitizeHtml from "sanitize-html";
import {

Check failure on line 6 in packages/utils/src/ErrorContext.ts

View workflow job for this annotation

GitHub Actions / test

Imports "BeaconErrorType" are only used as type

Check warning on line 6 in packages/utils/src/ErrorContext.ts

View workflow job for this annotation

GitHub Actions / test

`@airgap/beacon-wallet` import should occur before import of `@taquito/rpc`
BeaconErrorType,
BeaconMessageType,

Check warning on line 8 in packages/utils/src/ErrorContext.ts

View workflow job for this annotation

GitHub Actions / test

'BeaconMessageType' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 8 in packages/utils/src/ErrorContext.ts

View workflow job for this annotation

GitHub Actions / test

'BeaconMessageType' is defined but never used
type Network as BeaconNetwork,

Check warning on line 9 in packages/utils/src/ErrorContext.ts

View workflow job for this annotation

GitHub Actions / test

'BeaconNetwork' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 9 in packages/utils/src/ErrorContext.ts

View workflow job for this annotation

GitHub Actions / test

'BeaconNetwork' is defined but never used
type BeaconRequestOutputMessage,

Check warning on line 10 in packages/utils/src/ErrorContext.ts

View workflow job for this annotation

GitHub Actions / test

'BeaconRequestOutputMessage' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 10 in packages/utils/src/ErrorContext.ts

View workflow job for this annotation

GitHub Actions / test

'BeaconRequestOutputMessage' is defined but never used
} from "@airgap/beacon-wallet";

import { TezosRpcErrors } from "./TezosRpcErrors";

Expand All @@ -22,6 +28,15 @@ export class CustomError extends Error {
}
}

export class BeaconError extends CustomError {
errorType: BeaconErrorType;
constructor(message: string, errorType: BeaconErrorType) {
super(message);
this.name = "BeaconError";
this.errorType = errorType;
}
}

export class WalletConnectError extends CustomError {
code: WcErrorCode;
context?: string | number;
Expand Down Expand Up @@ -132,7 +147,7 @@ export const getErrorContext = (error: any, silent: boolean = false): ErrorConte
"Something went wrong. Please try again. Contact support if the issue persists.";
let description = defaultDescription;
let technicalDetails: any = undefined;
let code: number = WcErrorCode.INTERNAL_ERROR;
let code: WcErrorCode | number = WcErrorCode.INTERNAL_ERROR;
const errorMessage = typeof error === "string" ? error : error.message;

let stacktrace = "";
Expand All @@ -142,12 +157,14 @@ export const getErrorContext = (error: any, silent: boolean = false): ErrorConte
technicalDetails = error;
}

if (error instanceof CustomError) {
if (error instanceof BeaconError) {
description = errorMessage;
} else if (error instanceof WalletConnectError) {
code = error.code;
description = errorMessage;
technicalDetails = error.context;
} else if (error instanceof CustomError) {
description = errorMessage;
} else if (error instanceof TezosOperationError) {
code = WcErrorCode.REJECTED_BY_CHAIN;
const lastError = error.lastError;
Expand Down Expand Up @@ -175,7 +192,6 @@ export const getErrorContext = (error: any, silent: boolean = false): ErrorConte
const plainMessage = stripHtmlTags(error.message);
description = `HTTP request failed for ${error.url} (${error.status}) ${httpError}`;
code = error.status;
console.log("HTTP ERROR", error);
if (code === 500) {
description = `${description}\nDetails: ${plainMessage}`;
}
Expand All @@ -186,7 +202,7 @@ export const getErrorContext = (error: any, silent: boolean = false): ErrorConte
}

if (!silent) {
console.warn("Request failed", code, description, technicalDetails, error);
console.error("Request failed", code, description, technicalDetails, error);
}

return {
Expand Down

0 comments on commit eb62201

Please sign in to comment.