Skip to content

Commit 1af3d15

Browse files
feat: WalletConnect integration, part 9, getAccounts
1 parent 188d44e commit 1af3d15

File tree

4 files changed

+192
-6
lines changed

4 files changed

+192
-6
lines changed

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

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { SigningType } from "@airgap/beacon-wallet";
2+
import { useToast } from "@chakra-ui/react";
23
import { useDynamicModalContext } from "@umami/components";
3-
import { type ImplicitAccount, estimate, toAccountOperations } from "@umami/core";
4+
import {
5+
type ImplicitAccount,
6+
estimate,
7+
getPublicKeyAndCurve,
8+
toAccountOperations,
9+
} from "@umami/core";
410
import {
511
useAsyncActionHandler,
612
useFindNetwork,
@@ -9,7 +15,7 @@ import {
915
walletKit,
1016
} from "@umami/state";
1117
import { WalletConnectError } from "@umami/utils";
12-
import { formatJsonRpcError } from "@walletconnect/jsonrpc-utils";
18+
import { formatJsonRpcError, formatJsonRpcResult } from "@walletconnect/jsonrpc-utils";
1319
import { type SessionTypes, type SignClientTypes, type Verify } from "@walletconnect/types";
1420
import { type SdkErrorKey, getSdkError } from "@walletconnect/utils";
1521

@@ -21,7 +27,6 @@ import {
2127
type SignHeaderProps,
2228
type SignPayloadProps,
2329
} from "../SendFlow/utils";
24-
2530
/**
2631
* @returns a function that handles a beacon message and opens a modal with the appropriate content
2732
*
@@ -34,6 +39,7 @@ export const useHandleWcRequest = () => {
3439
const getAccount = useGetOwnedAccountSafe();
3540
const getImplicitAccount = useGetImplicitAccount();
3641
const findNetwork = useFindNetwork();
42+
const toast = useToast();
3743

3844
return async (
3945
event: {
@@ -57,11 +63,42 @@ export const useHandleWcRequest = () => {
5763

5864
switch (request.method) {
5965
case "tezos_getAccounts": {
60-
throw new WalletConnectError(
61-
"Getting accounts is not supported yet",
62-
"WC_METHOD_UNSUPPORTED",
66+
const wcPeers = walletKit.getActiveSessions();
67+
if (!(topic in wcPeers)) {
68+
throw new WalletConnectError(`Unknown session ${topic}`, "UNAUTHORIZED_EVENT", null);
69+
}
70+
const session = wcPeers[topic];
71+
const accountPkh = session.namespaces.tezos.accounts[0].split(":")[2];
72+
const signer = getImplicitAccount(accountPkh);
73+
const networkName = session.namespaces.tezos.chains?.[0].split(":")[1];
74+
const network = networkName ? findNetwork(networkName) : null;
75+
if (!network) {
76+
throw new WalletConnectError(
77+
`Unsupported network ${networkName}`,
78+
"UNSUPPORTED_CHAINS",
79+
session
80+
);
81+
}
82+
const { publicKey, curve } = await getPublicKeyAndCurve(
83+
accountPkh,
84+
signer,
85+
network,
6386
session
6487
);
88+
const response = formatJsonRpcResult(id, [
89+
{
90+
algo: curve,
91+
address: accountPkh,
92+
pubkey: publicKey,
93+
},
94+
]);
95+
await walletKit.respondSessionRequest({ topic, response });
96+
97+
toast({
98+
description: "Successfully provided the requested account data",
99+
status: "success",
100+
});
101+
return;
65102
}
66103

67104
case "tezos_sign": {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { makeToolkit } from "@umami/tezos";
2+
import { WalletConnectError } from "@umami/utils";
3+
4+
import { getPublicKeyAndCurve } from "./getPublicKeyAndCurve";
5+
6+
jest.mock("@umami/tezos", () => ({
7+
...jest.requireActual("@umami/tezos"),
8+
makeToolkit: jest.fn(),
9+
}));
10+
const mockGetManagerKey = jest.fn();
11+
12+
describe("getPublicKeyAndCurve", () => {
13+
beforeEach(() => {
14+
jest.mocked(makeToolkit).mockImplementation(
15+
() =>
16+
({
17+
rpc: {
18+
getManagerKey: mockGetManagerKey,
19+
},
20+
}) as any
21+
);
22+
jest.clearAllMocks();
23+
});
24+
25+
const mockSigner = { address: "tz1..." } as any;
26+
const mockNetwork = { name: "mainnet" } as any;
27+
const mockAddress = "tz1...";
28+
29+
it("returns the public key and curve for ed25519", async () => {
30+
mockGetManagerKey.mockResolvedValue("edpk123456789");
31+
32+
const result = await getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork);
33+
34+
expect(result).toEqual({
35+
publicKey: "edpk123456789",
36+
curve: "ed25519",
37+
});
38+
});
39+
40+
it("returns the public key and curve for secp256k1", async () => {
41+
mockGetManagerKey.mockResolvedValue("sppk123456789");
42+
43+
const result = await getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork);
44+
45+
expect(result).toEqual({
46+
publicKey: "sppk123456789",
47+
curve: "secp256k1",
48+
});
49+
});
50+
51+
it("returns the public key and curve for p-256", async () => {
52+
mockGetManagerKey.mockResolvedValue("p2pk123456789");
53+
54+
const result = await getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork);
55+
56+
expect(result).toEqual({
57+
publicKey: "p2pk123456789",
58+
curve: "p-256",
59+
});
60+
});
61+
62+
it("throws an error if the public key has an unknown prefix", async () => {
63+
mockGetManagerKey.mockResolvedValue("unknown123456789");
64+
65+
await expect(getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork)).rejects.toThrow(
66+
WalletConnectError
67+
);
68+
69+
await expect(getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork)).rejects.toThrow(
70+
"Unknown curve for the public key: unknown123456789"
71+
);
72+
});
73+
74+
it("throws an error if the account is not revealed", async () => {
75+
mockGetManagerKey.mockResolvedValue(null);
76+
77+
await expect(getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork)).rejects.toThrow(
78+
WalletConnectError
79+
);
80+
81+
await expect(getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork)).rejects.toThrow(
82+
`Signer address is not revealed on the ${mockNetwork.name}`
83+
);
84+
});
85+
86+
it("handles the case where the managerKeyResponse is an object with a key field", async () => {
87+
mockGetManagerKey.mockResolvedValue({ key: "edpk987654321" });
88+
89+
const result = await getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork);
90+
91+
expect(result).toEqual({
92+
publicKey: "edpk987654321",
93+
curve: "ed25519",
94+
});
95+
});
96+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
"UNSUPPORTED_ACCOUNTS",
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+
"UNSUPPORTED_ACCOUNTS",
48+
session || null
49+
);
50+
}
51+
return { publicKey, curve };
52+
};

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from "./decodeBeaconPayload";
55
export * from "./Delegate";
66
export * from "./estimate";
77
export * from "./execute";
8+
export * from "./getPublicKeyAndCurve";
89
export * from "./helpers";
910
export * from "./Operation";
1011
export * from "./testUtils";

0 commit comments

Comments
 (0)