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 23, 2024
1 parent aaecb51 commit 762de48
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 7 deletions.
50 changes: 43 additions & 7 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,41 @@ export const useHandleWcRequest = () => {

switch (request.method) {
case "tezos_getAccounts": {
throw new WalletConnectError(
"Getting accounts is not supported yet",
"WC_METHOD_UNSUPPORTED",
session
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
);
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
99 changes: 99 additions & 0 deletions packages/core/src/getPublicKeyAndCurve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { getPublicKeyAndCurve } from "./getPublicKeyAndCurve";

Check warning on line 1 in packages/core/src/getPublicKeyAndCurve.test.ts

View workflow job for this annotation

GitHub Actions / test

There should be at least one empty line between import groups
import { WalletConnectError } from "@umami/utils";

Check warning on line 2 in packages/core/src/getPublicKeyAndCurve.test.ts

View workflow job for this annotation

GitHub Actions / test

`@umami/utils` import should occur before import of `./getPublicKeyAndCurve`
import { makeToolkit } from "@umami/tezos";

Check warning on line 3 in packages/core/src/getPublicKeyAndCurve.test.ts

View workflow job for this annotation

GitHub Actions / test

`@umami/tezos` import should occur before import of `./getPublicKeyAndCurve`
import { TezosToolkit } from "@taquito/taquito";

Check warning on line 4 in packages/core/src/getPublicKeyAndCurve.test.ts

View workflow job for this annotation

GitHub Actions / test

`@taquito/taquito` import should occur before import of `./getPublicKeyAndCurve`

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

describe("getPublicKeyAndCurve", () => {

beforeEach(() => {
const testToolkit = new TezosToolkit("test-tezos-toolkit");

Check warning on line 15 in packages/core/src/getPublicKeyAndCurve.test.ts

View workflow job for this annotation

GitHub Actions / test

'testToolkit' is assigned a value but never used. Allowed unused vars must match /^_/u

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("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",
});
});

it("throws an error if the managerKeyResponse object does not have a key", async () => {
mockGetManagerKey.mockResolvedValue({});

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}`
);
});
});
53 changes: 53 additions & 0 deletions packages/core/src/getPublicKeyAndCurve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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";

/**
* Estimates (and simulates the execution of) the operations.
*
* @param address - tz1 address of the account
* @param signer - Implicit account
* @param network - network
* @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
): Promise<{ publicKey: string; curve: string }> => {
const tezosToolkit = await makeToolkit({
type: "fake",
signer: signer,
network,
});
const managerKeyResponse: ManagerKeyResponse = await tezosToolkit.rpc.getManagerKey(address);
let publicKey = "";
if (typeof managerKeyResponse === "string") {
publicKey = managerKeyResponse;
} else if (managerKeyResponse.key) {
publicKey = managerKeyResponse.key;
} else {
throw new WalletConnectError(
`Signer address is not revealed on the ${network.name}`,
"UNSUPPORTED_ACCOUNTS",
null
);
}
let curve = "unknown";
if (publicKey.startsWith("edpk")) {
curve = "ed25519";
} else if (publicKey.startsWith("sppk")) {
curve = "secp256k1";
} else if (publicKey.startsWith("p2pk")) {
curve = "p-256";
} else {
throw new WalletConnectError(
`Unknown curve for the public key: ${publicKey}`,
"UNSUPPORTED_ACCOUNTS",
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

0 comments on commit 762de48

Please sign in to comment.