Skip to content

Commit

Permalink
feat: WalletConnect integration, part 9, getAccounts
Browse files Browse the repository at this point in the history
  • Loading branch information
dianasavvatina committed Dec 27, 2024
1 parent aaecb51 commit f153824
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 6 deletions.
49 changes: 43 additions & 6 deletions apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { SigningType } from "@airgap/beacon-wallet";
import { useToast } from "@chakra-ui/react";
import { useDynamicModalContext } from "@umami/components";
import { type ImplicitAccount, estimate, toAccountOperations } from "@umami/core";
import {
type ImplicitAccount,
estimate,
getPublicKeyAndCurve,
toAccountOperations,
} from "@umami/core";
import {
useAsyncActionHandler,
useFindNetwork,
Expand All @@ -9,7 +15,7 @@ import {
walletKit,
} from "@umami/state";
import { WalletConnectError } from "@umami/utils";
import { formatJsonRpcError } from "@walletconnect/jsonrpc-utils";
import { formatJsonRpcError, formatJsonRpcResult } from "@walletconnect/jsonrpc-utils";
import { type SessionTypes, type SignClientTypes, type Verify } from "@walletconnect/types";
import { type SdkErrorKey, getSdkError } from "@walletconnect/utils";

Expand All @@ -21,7 +27,6 @@ import {
type SignHeaderProps,
type SignPayloadProps,
} from "../SendFlow/utils";

/**
* @returns a function that handles a beacon message and opens a modal with the appropriate content
*
Expand All @@ -34,6 +39,7 @@ export const useHandleWcRequest = () => {
const getAccount = useGetOwnedAccountSafe();
const getImplicitAccount = useGetImplicitAccount();
const findNetwork = useFindNetwork();
const toast = useToast();

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

switch (request.method) {
case "tezos_getAccounts": {
throw new WalletConnectError(
"Getting accounts is not supported yet",
"WC_METHOD_UNSUPPORTED",
const wcPeers = walletKit.getActiveSessions();
if (!(topic in wcPeers)) {
throw new WalletConnectError(`Unknown session ${topic}`, "UNAUTHORIZED_EVENT", null);
}
const session = wcPeers[topic];
const accountPkh = session.namespaces.tezos.accounts[0].split(":")[2];
const signer = getImplicitAccount(accountPkh);
const networkName = session.namespaces.tezos.chains?.[0].split(":")[1];
const network = networkName ? findNetwork(networkName) : null;
if (!network) {
throw new WalletConnectError(
`Unsupported network ${networkName}`,
"UNSUPPORTED_CHAINS",
session
);
}
const { publicKey, curve } = await getPublicKeyAndCurve(
accountPkh,
signer,
network,
session
);
const response = formatJsonRpcResult(id, [
{
algo: curve,
address: accountPkh,
pubkey: publicKey,
},
]);
await walletKit.respondSessionRequest({ topic, response });

toast({
description: "Successfully provided the requested account data",
status: "success",
});
return;
}

case "tezos_sign": {
Expand Down
96 changes: 96 additions & 0 deletions packages/core/src/getPublicKeyAndCurve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { makeToolkit } from "@umami/tezos";
import { WalletConnectError } from "@umami/utils";

import { getPublicKeyAndCurve } from "./getPublicKeyAndCurve";

jest.mock("@umami/tezos", () => ({
...jest.requireActual("@umami/tezos"),
makeToolkit: jest.fn(),
}));
const mockGetManagerKey = jest.fn();

describe("getPublicKeyAndCurve", () => {
beforeEach(() => {
jest.mocked(makeToolkit).mockImplementation(
() =>
({
rpc: {
getManagerKey: mockGetManagerKey,
},
}) as any
);
jest.clearAllMocks();
});

const mockSigner = { address: "tz1..." } as any;
const mockNetwork = { name: "mainnet" } as any;
const mockAddress = "tz1...";

it("returns the public key and curve for ed25519", async () => {
mockGetManagerKey.mockResolvedValue("edpk123456789");

const result = await getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork);

expect(result).toEqual({
publicKey: "edpk123456789",
curve: "ed25519",
});
});

it("returns the public key and curve for secp256k1", async () => {
mockGetManagerKey.mockResolvedValue("sppk123456789");

const result = await getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork);

expect(result).toEqual({
publicKey: "sppk123456789",
curve: "secp256k1",
});
});

it("returns the public key and curve for p-256", async () => {
mockGetManagerKey.mockResolvedValue("p2pk123456789");

const result = await getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork);

expect(result).toEqual({
publicKey: "p2pk123456789",
curve: "p-256",
});
});

it("throws an error if the public key has an unknown prefix", async () => {
mockGetManagerKey.mockResolvedValue("unknown123456789");

await expect(getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork)).rejects.toThrow(
WalletConnectError
);

await expect(getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork)).rejects.toThrow(
"Unknown curve for the public key: unknown123456789"
);
});

it("throws an error if the account is not revealed", async () => {
mockGetManagerKey.mockResolvedValue(null);

await expect(getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork)).rejects.toThrow(
WalletConnectError
);

await expect(getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork)).rejects.toThrow(
`Signer address is not revealed on the ${mockNetwork.name}`
);
});

it("handles the case where the managerKeyResponse is an object with a key field", async () => {
mockGetManagerKey.mockResolvedValue({ key: "edpk987654321" });

const result = await getPublicKeyAndCurve(mockAddress, mockSigner, mockNetwork);

expect(result).toEqual({
publicKey: "edpk987654321",
curve: "ed25519",
});
});
});
54 changes: 54 additions & 0 deletions packages/core/src/getPublicKeyAndCurve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { type ManagerKeyResponse } from "@taquito/rpc";
import { type ImplicitAccount } from "@umami/core";
import { type Network, type RawPkh, makeToolkit } from "@umami/tezos";
import { WalletConnectError } from "@umami/utils";
import { type SessionTypes } from "@walletconnect/types";

/**
* Estimates (and simulates the execution of) the operations.
*
* @param address - tz1 address of the account
* @param signer - Implicit account
* @param network - network
* @param session - WalletConnect session
* @returns the public key if revelead
* Throws an error if the account is not revelead
*/
export const getPublicKeyAndCurve = async (
address: RawPkh,
signer: ImplicitAccount,
network: Network,
session?: SessionTypes.Struct | null
): Promise<{ publicKey: string; curve: string }> => {
const tezosToolkit = await makeToolkit({
type: "fake",
signer: signer,
network,
});
const keyResponse: ManagerKeyResponse | null = await tezosToolkit.rpc.getManagerKey(address);
console.log("keyResponse", keyResponse);
if (!keyResponse) {
throw new WalletConnectError(
`Signer address is not revealed on the ${network.name}`,
"UNSUPPORTED_ACCOUNTS",
session || null
);
}
const publicKey = typeof keyResponse === "string" ? keyResponse : keyResponse.key;
console.log("publicKey", publicKey);
const curve = publicKey.startsWith("edpk")
? "ed25519"
: publicKey.startsWith("sppk")
? "secp256k1"
: publicKey.startsWith("p2pk")
? "p-256"
: null;
if (!curve) {
throw new WalletConnectError(
`Unknown curve for the public key: ${publicKey}`,
"UNSUPPORTED_ACCOUNTS",
session || null
);
}
return { publicKey, curve };
};
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "./decodeBeaconPayload";
export * from "./Delegate";
export * from "./estimate";
export * from "./execute";
export * from "./getPublicKeyAndCurve";
export * from "./helpers";
export * from "./Operation";
export * from "./testUtils";
Expand Down

1 comment on commit f153824

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title Lines Statements Branches Functions
apps/desktop Coverage: 83%
83.81% (1786/2131) 79.58% (850/1068) 78.27% (454/580)
apps/web Coverage: 83%
83.81% (1786/2131) 79.58% (850/1068) 78.27% (454/580)
packages/components Coverage: 97%
97.51% (196/201) 95.91% (94/98) 88.13% (52/59)
packages/core Coverage: 82%
83.26% (219/263) 75.91% (104/137) 81.66% (49/60)
packages/crypto Coverage: 100%
100% (43/43) 90.9% (10/11) 100% (7/7)
packages/data-polling Coverage: 97%
95.27% (141/148) 87.5% (21/24) 92.85% (39/42)
packages/multisig Coverage: 98%
98.47% (129/131) 85.71% (18/21) 100% (36/36)
packages/social-auth Coverage: 100%
100% (21/21) 100% (11/11) 100% (3/3)
packages/state Coverage: 85%
84.79% (820/967) 81.03% (188/232) 78.59% (301/383)
packages/tezos Coverage: 89%
88.72% (118/133) 94.59% (35/37) 86.84% (33/38)
packages/tzkt Coverage: 89%
87.32% (62/71) 87.5% (14/16) 80.48% (33/41)

Please sign in to comment.