diff --git a/packages/example/components/test-plan/connect-wallet.tsx b/packages/example/components/test-plan/connect-wallet.tsx
new file mode 100644
index 00000000..7af4d62e
--- /dev/null
+++ b/packages/example/components/test-plan/connect-wallet.tsx
@@ -0,0 +1,42 @@
+import { Mainnet, useCelo } from '@celo/react-celo';
+import React, { useEffect } from 'react';
+
+import { SuccessIcon } from './success-icon';
+import { Result, TestBlock } from './ui';
+import { Status, useTestStatus } from './useTestStatus';
+
+export function ConnectWalletCheck() {
+ const { connect, address, updateNetwork } = useCelo();
+ const { status, errorMessage, wrapActionWithStatus, setStatus } =
+ useTestStatus();
+
+ const onConnectWallet = wrapActionWithStatus(async () => {
+ await updateNetwork(Mainnet);
+ await connect();
+ });
+
+ useEffect(() => {
+ if (address) {
+ setStatus.success();
+ }
+ }, [address, setStatus]);
+
+ return (
+
+
+
+ Press the button above to choose a wallet to connect to.
+
+
+ Connected to {address}
+
+ {errorMessage}
+
+
+ );
+}
diff --git a/packages/example/components/test-plan/error-icon.tsx b/packages/example/components/test-plan/error-icon.tsx
new file mode 100644
index 00000000..dc450068
--- /dev/null
+++ b/packages/example/components/test-plan/error-icon.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+
+export function ErrorIcon() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/example/components/test-plan/send-transaction.tsx b/packages/example/components/test-plan/send-transaction.tsx
new file mode 100644
index 00000000..003eda1f
--- /dev/null
+++ b/packages/example/components/test-plan/send-transaction.tsx
@@ -0,0 +1,38 @@
+import { useCelo } from '@celo/react-celo';
+
+import { sendTestTransaction } from '../../utils/send-test-transaction';
+import { SuccessIcon } from './success-icon';
+import { Result, TestBlock } from './ui';
+import { useDisabledTest } from './useDisabledTest';
+import { useTestStatus } from './useTestStatus';
+
+export function SendTransaction() {
+ const { performActions } = useCelo();
+ const { status, errorMessage, wrapActionWithStatus } = useTestStatus();
+ const [disabled, setDisabled] = useDisabledTest();
+
+ const onRunTest = wrapActionWithStatus(async () => {
+ setDisabled(true);
+ await sendTestTransaction(performActions);
+ });
+
+ return (
+
+
+ This sends a very small transaction to impact market contract.
+
+ You'll need to approve the transaction in the wallet.
+
+
+ Transaction sent
+
+ {errorMessage}
+
+
+ );
+}
diff --git a/packages/example/components/test-plan/success-icon.tsx b/packages/example/components/test-plan/success-icon.tsx
new file mode 100644
index 00000000..2cfb48e1
--- /dev/null
+++ b/packages/example/components/test-plan/success-icon.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+
+export function SuccessIcon() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/example/components/test-plan/switch-networks.tsx b/packages/example/components/test-plan/switch-networks.tsx
new file mode 100644
index 00000000..65e10ad6
--- /dev/null
+++ b/packages/example/components/test-plan/switch-networks.tsx
@@ -0,0 +1,47 @@
+import { Alfajores, useCelo } from '@celo/react-celo';
+import { useEffect, useState } from 'react';
+
+import { SuccessIcon } from './success-icon';
+import { Result, TestBlock } from './ui';
+import { useDisabledTest } from './useDisabledTest';
+import { Status, useTestStatus } from './useTestStatus';
+
+export function SwitchNetwork() {
+ const { updateNetwork, network } = useCelo();
+ const { status, errorMessage, wrapActionWithStatus, setStatus } =
+ useTestStatus();
+ const [disabledTest, setDisabledTest] = useDisabledTest();
+ const [connectedNetwork, setConnectedNetwork] = useState('');
+
+ const onSwitchNetworks = wrapActionWithStatus(async () => {
+ setDisabledTest(true);
+ await updateNetwork(Alfajores);
+ });
+
+ useEffect(() => {
+ setConnectedNetwork(network.name);
+ if (status === Status.NotStarted && network.name === Alfajores.name) {
+ setStatus.error('Already set to Alfajores');
+ setDisabledTest(true);
+ }
+ }, [network.name, setStatus, status, setDisabledTest]);
+
+ return (
+
+
+
+ Currently connected to {connectedNetwork}.
+
+
+ Switched to Alfajores
+
+ {errorMessage}
+
+
+ );
+}
diff --git a/packages/example/components/test-plan/ui.tsx b/packages/example/components/test-plan/ui.tsx
new file mode 100644
index 00000000..05af33a8
--- /dev/null
+++ b/packages/example/components/test-plan/ui.tsx
@@ -0,0 +1,102 @@
+import React from 'react';
+import { SecondaryButton } from '../buttons';
+
+import { ErrorIcon } from './error-icon';
+import { Status } from './useTestStatus';
+
+export function TestTag({ type }: { type: Status }) {
+ const getText = (type: Status) => {
+ return type.replace('-', ' ');
+ };
+
+ return {getText(type)} ;
+}
+
+export function TestBlock({
+ status,
+ title,
+ onRunTest,
+ disabledTest,
+ children,
+}: React.PropsWithChildren<{
+ title: string;
+ status: Status;
+ disabledTest: boolean;
+ onRunTest: () => void;
+}>) {
+ return (
+
+
+
+
+
+
+
+
+ Run
+
+
+ {children}
+
+
+ );
+}
+
+export const Header: React.FC = (props) => (
+
+);
+
+export const Text: React.FC = (props) => (
+
+);
+
+const ResultContext = React.createContext('');
+
+function useResultContext() {
+ const context = React.useContext(ResultContext);
+ if (!context) {
+ throw new Error('Cannot use this element outside the Result component');
+ }
+ return context;
+}
+
+export function Result(props: React.PropsWithChildren<{ status: Status }>) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+export const Success: React.FC = (props) => {
+ const context = useResultContext();
+ return context === Status.Success ? (
+
+ {props.children}
+
+ ) : null;
+};
+
+export const ErrorText: React.FC = (props) => {
+ const context = useResultContext();
+ return context === Status.Error ? (
+
+ {props.children}
+
+ ) : null;
+};
+
+export const Default: React.FC = (props) => {
+ const context = useResultContext();
+ return context === Status.NotStarted || context === Status.Pending ? (
+ <>{props.children}>
+ ) : null;
+};
+
+Result.Success = Success;
+Result.Error = ErrorText;
+Result.Default = Default;
diff --git a/packages/example/components/test-plan/update-fee-currency.tsx b/packages/example/components/test-plan/update-fee-currency.tsx
new file mode 100644
index 00000000..9087c216
--- /dev/null
+++ b/packages/example/components/test-plan/update-fee-currency.tsx
@@ -0,0 +1,53 @@
+import { CeloContract } from '@celo/contractkit';
+import { useCelo } from '@celo/react-celo';
+import { useEffect } from 'react';
+
+import { SuccessIcon } from './success-icon';
+import { Result, TestBlock } from './ui';
+import { useDisabledTest } from './useDisabledTest';
+import { useTestStatus } from './useTestStatus';
+
+export function UpdateFeeCurrency() {
+ const { updateFeeCurrency, feeCurrency, supportsFeeCurrency, address } =
+ useCelo();
+ const [disabledTest, setDisabledTest] = useDisabledTest();
+ const { status, errorMessage, wrapActionWithStatus, setStatus } =
+ useTestStatus();
+
+ useEffect(() => {
+ if (address && supportsFeeCurrency !== undefined && !supportsFeeCurrency) {
+ setDisabledTest(true);
+ setStatus.error('Wallet does not support updating fee currency.');
+ }
+ }, [address, supportsFeeCurrency, setStatus, setDisabledTest]);
+
+ const onUpdateCurrency = wrapActionWithStatus(async () => {
+ setDisabledTest(true);
+ await updateFeeCurrency(CeloContract.StableTokenBRL);
+ if (feeCurrency !== CeloContract.StableTokenBRL) {
+ throw new Error('Fee currency did not update.');
+ }
+ });
+
+ return (
+
+
+ Fee currency used: {feeCurrency}
+
+ <>
+ Change the currency used in transactions.
+ >
+
+
+ Fee currency {feeCurrency}
+
+ {errorMessage}
+
+
+ );
+}
diff --git a/packages/example/components/test-plan/useDisabledTest.tsx b/packages/example/components/test-plan/useDisabledTest.tsx
new file mode 100644
index 00000000..9ef7d516
--- /dev/null
+++ b/packages/example/components/test-plan/useDisabledTest.tsx
@@ -0,0 +1,15 @@
+import { useCelo } from '@celo/react-celo';
+import { Dispatch, SetStateAction, useEffect, useState } from 'react';
+
+export const useDisabledTest = (): [
+ boolean,
+ Dispatch>
+] => {
+ const { address } = useCelo();
+ const [disabled, setDisabled] = useState(true);
+ useEffect(() => {
+ setDisabled(!address);
+ }, [address]);
+
+ return [disabled, setDisabled];
+};
diff --git a/packages/example/components/test-plan/useTestStatus.tsx b/packages/example/components/test-plan/useTestStatus.tsx
new file mode 100644
index 00000000..9a0c15b9
--- /dev/null
+++ b/packages/example/components/test-plan/useTestStatus.tsx
@@ -0,0 +1,73 @@
+import { useCelo } from '@celo/react-celo';
+import { useEffect, useMemo, useState } from 'react';
+
+export enum Status {
+ NotStarted = 'not-started',
+ Success = 'success',
+ Error = 'error',
+ Pending = 'pending',
+}
+
+function hasMessage(error: unknown): error is { message: string } {
+ if (!error || typeof error !== 'object') {
+ return false;
+ }
+
+ return 'message' in error;
+}
+
+export const useTestStatus = () => {
+ const { address } = useCelo();
+
+ const [status, setStatus] = useState(Status.NotStarted);
+
+ const set = useMemo(() => {
+ return {
+ success: () => {
+ setStatus(Status.Success);
+ },
+ error: (error: unknown) => {
+ setStatus(Status.Error);
+
+ if (hasMessage(error)) {
+ setErrorMessage(error.message);
+ } else if (typeof error === 'string') {
+ setErrorMessage(error);
+ } else {
+ setErrorMessage(JSON.stringify(error));
+ }
+ },
+ pending: () => setStatus(Status.Pending),
+ notStarted: () => setStatus(Status.NotStarted),
+ };
+ }, []);
+
+ useEffect(() => {
+ if (!address) {
+ set.notStarted();
+ }
+
+ return () => {
+ set.notStarted();
+ };
+ }, [address, set]);
+
+ const [errorMessage, setErrorMessage] = useState('');
+
+ const wrapActionWithStatus = (action: () => Promise) => async () => {
+ set.pending();
+ try {
+ await action();
+ set.success();
+ } catch (error) {
+ set.error(error);
+ }
+ };
+
+ return {
+ status,
+ errorMessage,
+ setStatus: set,
+ wrapActionWithStatus,
+ };
+};
diff --git a/packages/example/pages/_app.tsx b/packages/example/pages/_app.tsx
index 736720ee..c02709a6 100644
--- a/packages/example/pages/_app.tsx
+++ b/packages/example/pages/_app.tsx
@@ -32,7 +32,7 @@ function MyApp({ Component, pageProps, router }: AppProps): React.ReactElement {
},
}}
/>
-
+
diff --git a/packages/example/pages/index.tsx b/packages/example/pages/index.tsx
index 49df7259..2c26615f 100644
--- a/packages/example/pages/index.tsx
+++ b/packages/example/pages/index.tsx
@@ -213,7 +213,7 @@ export default function Home(): React.ReactElement {
-
+
react-celo
A{' '}
diff --git a/packages/example/pages/wallet-test-plan.tsx b/packages/example/pages/wallet-test-plan.tsx
new file mode 100644
index 00000000..32a50ed5
--- /dev/null
+++ b/packages/example/pages/wallet-test-plan.tsx
@@ -0,0 +1,48 @@
+import { CeloProvider, Mainnet, useCelo } from '@celo/react-celo';
+import React from 'react';
+
+import { ConnectWalletCheck } from '../components/test-plan/connect-wallet';
+import { SendTransaction } from '../components/test-plan/send-transaction';
+import { SwitchNetwork } from '../components/test-plan/switch-networks';
+import { UpdateFeeCurrency } from '../components/test-plan/update-fee-currency';
+
+export default function WalletTestPlan(): React.ReactElement {
+ const { destroy } = useCelo();
+ return (
+
+
+
Wallet Test Plan
+
+ A set of steps to help verify how well a given wallet interacts with
+ react-celo.
+
+ You will need to connect a wallet before being able to run any other
+ test.
+
+
+
+ Disconnect wallet
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/example/pages/wallet.tsx b/packages/example/pages/wallet.tsx
index 87eaf9ec..882b490b 100644
--- a/packages/example/pages/wallet.tsx
+++ b/packages/example/pages/wallet.tsx
@@ -443,7 +443,7 @@ export default function Wallet(): React.ReactElement {
-
+
react-celo wallet
{
+ const celo = await k.contracts.getGoldToken();
+ await celo
+ .transfer(
+ // impact market contract
+ '0x73D20479390E1acdB243570b5B739655989412f5',
+ Web3.utils.toWei('0.00000001', 'ether')
+ )
+ .sendAndWaitForReceipt({
+ from: k.connection.defaultAccount,
+ gasPrice: k.connection.defaultGasPrice,
+ });
+ });
+}