diff --git a/examples/react-apps/src/pages/metamask-connection/__tests__/connect-wallet.test.tsx b/examples/react-apps/src/pages/metamask-connection/__tests__/connect-wallet.test.tsx
index 171e80d..8e61815 100644
--- a/examples/react-apps/src/pages/metamask-connection/__tests__/connect-wallet.test.tsx
+++ b/examples/react-apps/src/pages/metamask-connection/__tests__/connect-wallet.test.tsx
@@ -2,13 +2,15 @@ import {
act,
render,
screen,
+ waitFor,
waitForElementToBeRemoved,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { generateTestingUtils } from "eth-testing";
+import { ethers } from "ethers";
import WalletConnection from "..";
-describe("Connect wallet", () => {
+describe("Connect wallet [Metamask]", () => {
let originalEthereum: any;
const testingUtils = generateTestingUtils({
providerType: "MetaMask",
@@ -113,4 +115,35 @@ describe("Connect wallet", () => {
await screen.findByText(/chain id: 0x3/i);
expect(screen.getByText(/balance: 5.00/i)).toBeInTheDocument();
});
+ test("User should be able to sign a transaction", async () => {
+ const bobsWallet = ethers.Wallet.createRandom();
+ // Start with a connected bobsWallet
+ testingUtils.mockConnectedWallet([bobsWallet.address]);
+ // mock out a function to call when we request personal sign
+ let signedMessages: string[] = [];
+ testingUtils.lowLevel.mockRequest("personal_sign", async (params: any) => {
+ let statement = (params as string[])[0];
+ const signedMessage = await bobsWallet.signMessage(statement);
+ signedMessages.push(signedMessage);
+ return signedMessage;
+ });
+ // render the wallet
+ render();
+ // connect the wallet
+ const connectButton = screen.getByRole("button", { name: /connect/i });
+ userEvent.click(connectButton);
+ await screen.findByText(`account: ${bobsWallet.address}`, { exact: false });
+ // get the div for the signature result
+ const signatureResult = screen.getByTestId("signatureResult");
+ expect(signatureResult).toHaveTextContent("");
+ // sign the transaction
+ const signButton = screen.getByRole("button", { name: /sign/i });
+ userEvent.click(signButton);
+ // wait for the result to appear
+ await waitFor(() => {
+ expect(signatureResult).not.toHaveTextContent("");
+ });
+ // expect it to be the same as the signed message
+ expect(signatureResult).toHaveTextContent(signedMessages[0]);
+ });
});
diff --git a/examples/react-apps/src/pages/metamask-connection/wallet.tsx b/examples/react-apps/src/pages/metamask-connection/wallet.tsx
index f41f078..bb90a21 100644
--- a/examples/react-apps/src/pages/metamask-connection/wallet.tsx
+++ b/examples/react-apps/src/pages/metamask-connection/wallet.tsx
@@ -149,12 +149,24 @@ function useWallet() {
function Wallet() {
const { account, chainId, balance } = useWallet();
+ const [signatureResult, setSignatureResult] = React.useState("");
+ const personalSign = React.useCallback(async () => {
+ const ethereum = window.ethereum;
+ const message = "Hello, world!";
+ const result = await ethereum.request({
+ method: "personal_sign",
+ params: [message, account, ""],
+ });
+ setSignatureResult(result);
+ }, [account]);
return (
Account: {account}
Chain ID: {chainId}
Balance: {(Number(balance) / 10 ** 18).toFixed(2)}
+
{signatureResult}
+
);
}
diff --git a/examples/react-apps/src/pages/wallet-connect-connection/__test__/connect-wallet.test.tsx b/examples/react-apps/src/pages/wallet-connect-connection/__test__/connect-wallet.test.tsx
index 70da182..6727eed 100644
--- a/examples/react-apps/src/pages/wallet-connect-connection/__test__/connect-wallet.test.tsx
+++ b/examples/react-apps/src/pages/wallet-connect-connection/__test__/connect-wallet.test.tsx
@@ -1,108 +1,144 @@
import {
- act,
- render,
- screen,
- waitForElementToBeRemoved,
- } from "@testing-library/react";
- import userEvent from "@testing-library/user-event";
- import { generateTestingUtils } from "eth-testing";
- import WalletConnection from "..";
- import * as walletConnectProvider from '../provider';
-
- describe("Connect wallet", () => {
- const testingUtils = generateTestingUtils({
- providerType: "WalletConnect",
+ act,
+ render,
+ screen,
+ waitFor,
+ waitForElementToBeRemoved,
+} from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { generateTestingUtils } from "eth-testing";
+import { ethers } from "ethers";
+import WalletConnection from "..";
+import * as walletConnectProvider from "../provider";
+
+describe("Connect wallet [Wallet Connect]", () => {
+ const testingUtils = generateTestingUtils({
+ providerType: "WalletConnect",
+ });
+
+ beforeEach(() => {
+ jest
+ .spyOn(walletConnectProvider, "getWalletConnectProvider")
+ .mockReturnValue(testingUtils.getProvider() as unknown as any);
+ });
+
+ afterEach(() => {
+ testingUtils.clearAllMocks();
+ });
+
+ test("User should be able to connect using Wallet Connect", async () => {
+ // Start with not connected wallet
+ testingUtils.mockNotConnectedWallet();
+
+ testingUtils.mockAccounts(["0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf"]);
+ testingUtils.mockChainId(1);
+ testingUtils.mockBlockNumber(1);
+ testingUtils.mockBalance(
+ "0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf",
+ "0x1bc16d674ec80000"
+ );
+
+ render();
+
+ const connectButton = screen.getByRole("button", { name: /connect/i });
+ userEvent.click(connectButton);
+
+ await waitForElementToBeRemoved(connectButton);
+
+ // Wait for sync
+ await screen.findByText(
+ /account: 0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf/i
+ );
+ // Rest of the state should be present
+ expect(screen.getByText(/chain id: 0x1/i)).toBeInTheDocument();
+ expect(screen.getByText(/balance: 2.00/i)).toBeInTheDocument();
+ });
+
+ test("User should be able to see a changed account or network", async () => {
+ // Start with a connected wallet
+ testingUtils.mockConnectedWallet(
+ ["0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf"],
+ {
+ balance: "0x1bc16d674ec80000",
+ }
+ );
+
+ render();
+
+ const connectButton = screen.getByRole("button", { name: /connect/i });
+ userEvent.click(connectButton);
+
+ await waitForElementToBeRemoved(connectButton);
+
+ // Wait for sync
+ await screen.findByText(
+ /account: 0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf/i
+ );
+
+ // Mock the balance of the new account
+ testingUtils.mockBalance(
+ "0x138071e4e810f34265bd833be9c5dd96f01bd8a5",
+ "0xde0b6b3a7640000"
+ );
+
+ // Simulate a change of account
+ act(() => {
+ testingUtils.mockAccountsChanged([
+ "0x138071e4e810f34265bd833be9c5dd96f01bd8a5",
+ ]);
});
-
- beforeEach(() => {
- jest.spyOn(walletConnectProvider, "getWalletConnectProvider").mockReturnValue(testingUtils.getProvider() as unknown as any)
- })
-
- afterEach(() => {
- testingUtils.clearAllMocks();
+
+ // Wait for sync
+ await screen.findByText(
+ /account: 0x138071e4e810f34265bd833be9c5dd96f01bd8a5/i
+ );
+ expect(screen.getByText(/balance: 1.00/i)).toBeInTheDocument();
+
+ // Mock account balance on the new chain
+ testingUtils.mockBalance(
+ "0x138071e4e810f34265bd833be9c5dd96f01bd8a5",
+ "0x4563918244f40000"
+ );
+
+ // Simulate a change of chain
+ act(() => {
+ testingUtils.mockChainChanged("0x3");
});
-
- test("User should be able to connect using Wallet Connect", async () => {
- // Start with not connected wallet
- testingUtils.mockNotConnectedWallet();
-
- testingUtils.mockAccounts(
- ["0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf"]
- );
- testingUtils.mockChainId(1);
- testingUtils.mockBlockNumber(1);
- testingUtils.mockBalance("0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf", "0x1bc16d674ec80000");
-
- render();
-
- const connectButton = screen.getByRole("button", { name: /connect/i });
- userEvent.click(connectButton);
-
- await waitForElementToBeRemoved(connectButton);
-
- // Wait for sync
- await screen.findByText(
- /account: 0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf/i
- );
- // Rest of the state should be present
- expect(screen.getByText(/chain id: 0x1/i)).toBeInTheDocument();
- expect(screen.getByText(/balance: 2.00/i)).toBeInTheDocument();
+
+ // Wait for sync
+ await screen.findByText(/chain id: 0x3/i);
+ expect(screen.getByText(/balance: 5.00/i)).toBeInTheDocument();
+ });
+
+ test("User should be able to sign a transaction", async () => {
+ const bobsWallet = ethers.Wallet.createRandom();
+ // Start with a connected bobsWallet
+ testingUtils.mockConnectedWallet([bobsWallet.address]);
+ // mock out a function to call when we request personal sign
+ let signedMessages: string[] = [];
+ testingUtils.lowLevel.mockRequest("personal_sign", async (params: any) => {
+ let statement = (params as string[])[0];
+ const signedMessage = await bobsWallet.signMessage(statement);
+ signedMessages.push(signedMessage);
+ return signedMessage;
});
-
- test("User should be able to see a changed account or network", async () => {
- // Start with a connected wallet
- testingUtils.mockConnectedWallet(
- ["0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf"],
- {
- balance: "0x1bc16d674ec80000",
- }
- );
-
- render();
-
- const connectButton = screen.getByRole("button", { name: /connect/i });
- userEvent.click(connectButton);
-
- await waitForElementToBeRemoved(connectButton);
-
- // Wait for sync
- await screen.findByText(
- /account: 0xf61B443A155b07D2b2cAeA2d99715dC84E839EEf/i
- );
-
- // Mock the balance of the new account
- testingUtils.mockBalance(
- "0x138071e4e810f34265bd833be9c5dd96f01bd8a5",
- "0xde0b6b3a7640000"
- );
-
- // Simulate a change of account
- act(() => {
- testingUtils.mockAccountsChanged([
- "0x138071e4e810f34265bd833be9c5dd96f01bd8a5",
- ]);
- });
-
- // Wait for sync
- await screen.findByText(
- /account: 0x138071e4e810f34265bd833be9c5dd96f01bd8a5/i
- );
- expect(screen.getByText(/balance: 1.00/i)).toBeInTheDocument();
-
- // Mock account balance on the new chain
- testingUtils.mockBalance(
- "0x138071e4e810f34265bd833be9c5dd96f01bd8a5",
- "0x4563918244f40000"
- );
-
- // Simulate a change of chain
- act(() => {
- testingUtils.mockChainChanged("0x3");
- });
-
- // Wait for sync
- await screen.findByText(/chain id: 0x3/i);
- expect(screen.getByText(/balance: 5.00/i)).toBeInTheDocument();
+ // render the wallet
+ render();
+ // connect the wallet
+ const connectButton = screen.getByRole("button", { name: /connect/i });
+ userEvent.click(connectButton);
+ await screen.findByText(`account: ${bobsWallet.address}`, { exact: false });
+ // get the div for the signature result
+ const signatureResult = screen.getByTestId("signatureResult");
+ expect(signatureResult).toHaveTextContent("");
+ // sign the transaction
+ const signButton = screen.getByRole("button", { name: /sign/i });
+ userEvent.click(signButton);
+ // wait for the result to appear
+ await waitFor(() => {
+ expect(signatureResult).not.toHaveTextContent("");
});
+ // expect it to be the same as the signed message
+ expect(signatureResult).toHaveTextContent(signedMessages[0]);
});
-
\ No newline at end of file
+});
diff --git a/examples/react-apps/src/pages/wallet-connect-connection/wallet.tsx b/examples/react-apps/src/pages/wallet-connect-connection/wallet.tsx
index d56b3e8..babe186 100644
--- a/examples/react-apps/src/pages/wallet-connect-connection/wallet.tsx
+++ b/examples/react-apps/src/pages/wallet-connect-connection/wallet.tsx
@@ -150,12 +150,25 @@ function useWallet() {
function Wallet() {
const { account, chainId, balance } = useWallet();
+ const [signatureResult, setSignatureResult] = React.useState("");
+
+ const personalSign = React.useCallback(async () => {
+ const provider = getWalletConnectProvider();
+ const message = "Hello, world!";
+ const result = await provider.request({
+ method: "personal_sign",
+ params: [message, account, ""],
+ });
+ setSignatureResult(result);
+ }, [account]);
return (
Account: {account}
Chain ID: {chainId}
Balance: {(Number(balance) / 10 ** 18).toFixed(2)}
+
{signatureResult}
+
);
}
diff --git a/src/mock-manager.ts b/src/mock-manager.ts
index 4769843..d500117 100644
--- a/src/mock-manager.ts
+++ b/src/mock-manager.ts
@@ -23,18 +23,24 @@ export class MockManager {
/**
* Mock a JSON-RPC request
* @param method JSON-RPC method name
- * @param data Data to be resolved, or error to be thrown in case of throw
+ * @param data Data to be resolved, or function to be called, or error to be thrown in case of throw
* @param mockOptions Options of the mock
* @example ```ts
* // Mock one "eth_accounts" request
* mockManager.mockRequest("eth_accounts", ["0x..."]);
* // Persistently mock "eth_chainId" request
* mockManager.mockRequest("eth_chainId", "0x1", { persistent: true });
+ * // Mock with a dynamical value based on params
+ * // "personal_sign" in this case
+ * mockManager.mockRequest("personal_sign", async (params: any) => {
+ * let statement = (params as string[])[0];
+ * return await bobsWallet.signMessage(statement);
+ * });
* ```
*/
public mockRequest(
method: string,
- data: unknown,
+ data: unknown | ((params: unknown[]) => unknown),
mockOptions: MockOptions = {}
) {
const { condition, persistent } = mockOptions;
diff --git a/src/providers/provider.ts b/src/providers/provider.ts
index 4c4d21e..e87098b 100644
--- a/src/providers/provider.ts
+++ b/src/providers/provider.ts
@@ -44,15 +44,24 @@ export class Provider extends EventEmitter {
if (mock.shouldThrow) {
reject(mock.data);
} else {
- resolve({ data: mock.data, callback: mock.triggerCallback });
+ resolve({
+ data: mock.data,
+ callback: mock.triggerCallback,
+ });
}
}, mock.timeout || 0);
});
const { data, callback } = await promise;
+ let returnValue: unknown;
+ if (typeof data === "function") {
+ returnValue = await data(params);
+ } else {
+ returnValue = data;
+ }
if (callback) {
- callback(data, params);
+ callback(returnValue, params);
}
- return data;
+ return returnValue;
}
private findMock(method: string, params: unknown[]) {
diff --git a/src/types.ts b/src/types.ts
index 1b085d7..4189412 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -10,5 +10,5 @@ export type MockOptions = {
export type MockRequest = {
method: string;
- data: unknown;
+ data: unknown | ((params: unknown[]) => unknown);
} & MockOptions;