Skip to content

Commit 3c2c490

Browse files
feat: WalletConnect: error handling
1 parent e17bef5 commit 3c2c490

File tree

8 files changed

+273
-67
lines changed

8 files changed

+273
-67
lines changed

apps/web/src/components/Menu/ErrorLogsMenu/ErrorLogsMenu.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Box, Button, Divider, Flex, Heading, Link, Text, VStack } from "@chakra-ui/react";
22
import { errorsActions, useAppDispatch, useAppSelector } from "@umami/state";
3-
import { handleTezError } from "@umami/utils";
43

54
import { useColor } from "../../../styles/useColor";
65
import { EmptyMessage } from "../../EmptyMessage";
@@ -61,8 +60,7 @@ export const ErrorLogsMenu = () => {
6160
</Text>
6261
{errorLog.technicalDetails && (
6362
<Text marginTop="12px" color={color("700")} size="sm">
64-
{handleTezError({ name: "unknown", message: errorLog.technicalDetails }) ??
65-
""}
63+
{JSON.stringify(errorLog.technicalDetails)}
6664
</Text>
6765
)}
6866
</Flex>

apps/web/src/components/WalletConnect/WalletConnectProvider.tsx

+15-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type EventEmitter from "events";
33
import { type NetworkType } from "@airgap/beacon-wallet";
44
import { useToast } from "@chakra-ui/react";
55
import { type WalletKitTypes } from "@reown/walletkit";
6+
import { TezosOperationError } from "@taquito/taquito";
67
import { useDynamicModalContext } from "@umami/components";
78
import {
89
createWalletKit,
@@ -12,10 +13,10 @@ import {
1213
walletKit,
1314
} from "@umami/state";
1415
import { type Network } from "@umami/tezos";
15-
import { CustomError, WalletConnectError } from "@umami/utils";
16+
import { CustomError, WalletConnectError, type WcErrorKey, getWcErrorResponse } from "@umami/utils";
1617
import { formatJsonRpcError } from "@walletconnect/jsonrpc-utils";
1718
import { type SessionTypes } from "@walletconnect/types";
18-
import { type SdkErrorKey, getSdkError } from "@walletconnect/utils";
19+
import { getSdkError } from "@walletconnect/utils";
1920
import { type PropsWithChildren, useCallback, useEffect, useRef } from "react";
2021

2122
import { SessionProposalModal } from "./SessionProposalModal";
@@ -94,7 +95,7 @@ export const WalletConnectProvider = ({ children }: PropsWithChildren) => {
9495
handleAsyncActionUnsafe(async () => {
9596
const activeSessions: Record<string, SessionTypes.Struct> = walletKit.getActiveSessions();
9697
if (!(event.topic in activeSessions)) {
97-
throw new WalletConnectError("Session not found", "INVALID_EVENT", null);
98+
throw new WalletConnectError("Session not found", "SESSION_NOT_FOUND", null);
9899
}
99100

100101
const session = activeSessions[event.topic];
@@ -105,19 +106,20 @@ export const WalletConnectProvider = ({ children }: PropsWithChildren) => {
105106
await handleWcRequest(event, session);
106107
}).catch(async error => {
107108
const { id, topic } = event;
108-
let sdkErrorKey: SdkErrorKey =
109-
error instanceof WalletConnectError ? error.sdkError : "SESSION_SETTLEMENT_FAILED";
110-
if (sdkErrorKey === "USER_REJECTED") {
111-
console.info("WC request rejected", sdkErrorKey, event, error);
109+
let wcErrorKey: WcErrorKey = "UNKNOWN_ERROR";
110+
111+
if (error instanceof WalletConnectError) {
112+
wcErrorKey = error.wcError;
113+
} else if (error instanceof TezosOperationError) {
114+
wcErrorKey = "REJECTED_BY_CHAIN";
115+
}
116+
const response = formatJsonRpcError(id, getWcErrorResponse(error));
117+
if (wcErrorKey === "USER_REJECTED") {
118+
console.info("WC request rejected", wcErrorKey, event, error);
112119
} else {
113-
if (error.message.includes("delegate.unchanged")) {
114-
sdkErrorKey = "INVALID_EVENT";
115-
}
116-
console.warn("WC request failed", sdkErrorKey, event, error);
120+
console.warn("WC request failed", wcErrorKey, event, error, response);
117121
}
118122
// dApp is waiting so we need to notify it
119-
const sdkErrorMessage = getSdkError(sdkErrorKey).message;
120-
const response = formatJsonRpcError(id, sdkErrorMessage);
121123
await walletKit.respondSessionRequest({ topic, response });
122124
}),
123125
[handleAsyncActionUnsafe, handleWcRequest, toast]

apps/web/src/components/WalletConnect/useHandleWcRequest.tsx

+27-15
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ import {
1414
useGetOwnedAccountSafe,
1515
walletKit,
1616
} from "@umami/state";
17-
import { WalletConnectError } from "@umami/utils";
17+
import { WC_ERRORS, WalletConnectError } from "@umami/utils";
1818
import { formatJsonRpcError, formatJsonRpcResult } from "@walletconnect/jsonrpc-utils";
1919
import { type SessionTypes, type SignClientTypes, type Verify } from "@walletconnect/types";
20-
import { type SdkErrorKey, getSdkError } from "@walletconnect/utils";
2120

2221
import { SignPayloadRequestModal } from "../common/SignPayloadRequestModal";
2322
import { BatchSignPage } from "../SendFlow/common/BatchSignPage";
@@ -61,11 +60,18 @@ export const useHandleWcRequest = () => {
6160
let modal;
6261
let onClose;
6362

63+
const handleUserRejected = () => {
64+
// dApp is waiting so we need to notify it
65+
const response = formatJsonRpcError(id, WC_ERRORS.USER_REJECTED);
66+
console.info("WC request rejected by user", event, response);
67+
void walletKit.respondSessionRequest({ topic, response });
68+
};
69+
6470
switch (request.method) {
6571
case "tezos_getAccounts": {
6672
const wcPeers = walletKit.getActiveSessions();
6773
if (!(topic in wcPeers)) {
68-
throw new WalletConnectError(`Unknown session ${topic}`, "UNAUTHORIZED_EVENT", null);
74+
throw new WalletConnectError(`Unknown session ${topic}`, "SESSION_NOT_FOUND", null);
6975
}
7076
const session = wcPeers[topic];
7177
const accountPkh = session.namespaces.tezos.accounts[0].split(":")[2];
@@ -89,15 +95,20 @@ export const useHandleWcRequest = () => {
8995

9096
case "tezos_sign": {
9197
if (!request.params.account) {
92-
throw new WalletConnectError("Missing account in request", "INVALID_EVENT", session);
98+
throw new WalletConnectError(
99+
"Missing account in request",
100+
"MISSING_ACCOUNT_IN_REQUEST",
101+
session
102+
);
93103
}
94104
const signer = getImplicitAccount(request.params.account);
95105
const network = findNetwork(chainId.split(":")[1]);
96106
if (!network) {
97107
throw new WalletConnectError(
98108
`Unsupported network ${chainId}`,
99109
"UNSUPPORTED_CHAINS",
100-
session
110+
session,
111+
chainId
101112
);
102113
}
103114

@@ -115,24 +126,24 @@ export const useHandleWcRequest = () => {
115126

116127
modal = <SignPayloadRequestModal opts={signPayloadProps} />;
117128
onClose = () => {
118-
const sdkErrorKey: SdkErrorKey = "USER_REJECTED";
119-
console.info("WC request rejected by user", sdkErrorKey, event);
120-
// dApp is waiting so we need to notify it
121-
const response = formatJsonRpcError(id, getSdkError(sdkErrorKey).message);
122-
void walletKit.respondSessionRequest({ topic, response });
129+
handleUserRejected();
123130
};
124131
return openWith(modal, { onClose });
125132
}
126133

127134
case "tezos_send": {
128135
if (!request.params.account) {
129-
throw new WalletConnectError("Missing account in request", "INVALID_EVENT", session);
136+
throw new WalletConnectError(
137+
"Missing account in request",
138+
"MISSING_ACCOUNT_IN_REQUEST",
139+
session
140+
);
130141
}
131142
const signer = getAccount(request.params.account);
132143
if (!signer) {
133144
throw new WalletConnectError(
134145
`Unknown account, no signer: ${request.params.account}`,
135-
"UNAUTHORIZED_EVENT",
146+
"INTERNAL_SIGNER_IS_MISSING",
136147
session
137148
);
138149
}
@@ -168,16 +179,17 @@ export const useHandleWcRequest = () => {
168179
modal = <BatchSignPage {...signProps} {...event.params.request.params} />;
169180
}
170181
onClose = () => {
171-
throw new WalletConnectError("Rejected by user", "USER_REJECTED", session);
182+
handleUserRejected();
172183
};
173184

174185
return openWith(modal, { onClose });
175186
}
176187
default:
177188
throw new WalletConnectError(
178189
`Unsupported method ${request.method}`,
179-
"WC_METHOD_UNSUPPORTED",
180-
session
190+
"METHOD_UNSUPPORTED",
191+
session,
192+
request.method
181193
);
182194
}
183195
});
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { type ManagerKeyResponse } from "@taquito/rpc";
2+
import { type ImplicitAccount } from "@umami/core";
3+
import { type Network, type RawPkh, makeToolkit } from "@umami/tezos";
4+
import { WalletConnectError } from "@umami/utils";
5+
import { type SessionTypes } from "@walletconnect/types";
6+
7+
/**
8+
* Fetches the public key and curve of a given tz1 address.
9+
*
10+
* @param address - tz1 address of the account
11+
* @param signer - Implicit account
12+
* @param network - network
13+
* @param session - WalletConnect session
14+
* @returns the public key if revelead
15+
* Throws an error if the account is not revealed
16+
*/
17+
export const getPublicKeyAndCurve = async (
18+
address: RawPkh,
19+
signer: ImplicitAccount,
20+
network: Network,
21+
session?: SessionTypes.Struct | null
22+
): Promise<{ publicKey: string; curve: string }> => {
23+
const tezosToolkit = await makeToolkit({
24+
type: "fake",
25+
signer: signer,
26+
network,
27+
});
28+
const keyResponse: ManagerKeyResponse | null = await tezosToolkit.rpc.getManagerKey(address);
29+
if (!keyResponse) {
30+
throw new WalletConnectError(
31+
`Signer address is not revealed on the ${network.name}. To reveal it, send any amount, e.g. 0.000001ꜩ, from that address to yourself. Wait several minutes and try again.`,
32+
"SIGNER_ADDRESS_NOT_REVEALED",
33+
session || null
34+
);
35+
}
36+
const publicKey = typeof keyResponse === "string" ? keyResponse : keyResponse.key;
37+
const curve = publicKey.startsWith("edpk")
38+
? "ed25519"
39+
: publicKey.startsWith("sppk")
40+
? "secp256k1"
41+
: publicKey.startsWith("p2pk")
42+
? "p-256"
43+
: null;
44+
if (!curve) {
45+
throw new WalletConnectError(
46+
`Unknown curve for the public key: ${publicKey}`,
47+
"UNKNOWN_CURVE_FOR_PUBLIC_KEY",
48+
session || null,
49+
publicKey
50+
);
51+
}
52+
return { publicKey, curve };
53+
};

packages/state/src/slices/errors.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe("Errors reducer", () => {
2929
description: `error ${i}`,
3030
stacktrace: "stacktrace",
3131
technicalDetails: "technicalDetails",
32+
code: i,
3233
})
3334
);
3435
}

packages/test-utils/src/errorContext.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ export const errorContext1 = {
22
timestamp: "2023-08-03T19:27:43.735Z",
33
description: "error1",
44
stacktrace: "stacktrace",
5+
code: 100,
56
technicalDetails: "technicalDetails",
67
};
78

89
export const errorContext2 = {
910
timestamp: "2023-08-03T20:21:58.395Z",
1011
description: "error1",
1112
stacktrace: "stacktrace",
13+
code: 200,
1214
technicalDetails: "technicalDetails",
1315
};

0 commit comments

Comments
 (0)