diff --git a/apps/web/src/components/AccountCard/AccountBalance.test.tsx b/apps/web/src/components/AccountCard/AccountBalance.test.tsx
index 3a0252f491..cce60fe3df 100644
--- a/apps/web/src/components/AccountCard/AccountBalance.test.tsx
+++ b/apps/web/src/components/AccountCard/AccountBalance.test.tsx
@@ -5,7 +5,9 @@ import {
addTestAccount,
assetsActions,
makeStore,
+ networksActions,
} from "@umami/state";
+import { GHOSTNET, MAINNET } from "@umami/tezos";
import { AccountBalance } from "./AccountBalance";
import { act, render, screen, userEvent, waitFor, within } from "../../testUtils";
@@ -20,7 +22,9 @@ beforeEach(() => {
});
describe("", () => {
- it("renders a buy tez link", () => {
+ it("renders a buy tez link for mainnet", () => {
+ store.dispatch(networksActions.setCurrent(MAINNET));
+
render(, { store });
const link = screen.getByRole("link", { name: "Buy" });
@@ -31,6 +35,16 @@ describe("", () => {
);
});
+ it("renders a buy tez link for ghostnet", () => {
+ store.dispatch(networksActions.setCurrent(GHOSTNET));
+
+ render(, { store });
+
+ const link = screen.getByRole("link", { name: "Buy" });
+ expect(link).toBeVisible();
+ expect(link).toHaveAttribute("href", "https://faucet.ghostnet.teztnets.com/");
+ });
+
it("renders a receive button", () => {
render(, { store });
diff --git a/apps/web/src/components/AccountCard/AccountBalance.tsx b/apps/web/src/components/AccountCard/AccountBalance.tsx
index 25e4f5bef2..ed079eac9c 100644
--- a/apps/web/src/components/AccountCard/AccountBalance.tsx
+++ b/apps/web/src/components/AccountCard/AccountBalance.tsx
@@ -1,6 +1,11 @@
import { Box, Flex, Link, Text } from "@chakra-ui/react";
import { useDynamicModalContext } from "@umami/components";
-import { useCurrentAccount, useGetAccountBalance, useGetDollarBalance } from "@umami/state";
+import {
+ useBuyTezUrl,
+ useCurrentAccount,
+ useGetAccountBalance,
+ useGetDollarBalance,
+} from "@umami/state";
import { TEZ, prettyTezAmount } from "@umami/tezos";
import { SendTezButton } from "./SendTezButton";
@@ -19,7 +24,7 @@ export const AccountBalance = () => {
const usdBalance = useGetDollarBalance()(address);
const isVerified = useIsAccountVerified();
- const buyTezUrl = `https://widget.wert.io/default/widget/?commodity=XTZ&address=${address}&network=tezos&commodity_id=xtz.simple.tezos`;
+ const buyTezUrl = useBuyTezUrl(address);
const getUsdBalance = () => {
if (balance === undefined) {
diff --git a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
index c5186cd0ff..8ccc3389b0 100644
--- a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
+++ b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx
@@ -1,4 +1,5 @@
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 {
@@ -9,7 +10,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";
@@ -21,7 +22,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
*
@@ -34,6 +34,7 @@ export const useHandleWcRequest = () => {
const getAccount = useGetOwnedAccountSafe();
const getImplicitAccount = useGetImplicitAccount();
const findNetwork = useFindNetwork();
+ const toast = useToast();
return async (
event: {
@@ -57,11 +58,28 @@ 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 publicKey = signer.pk;
+ const response = formatJsonRpcResult(id, [
+ {
+ algo: "ed25519", // the only supported curve
+ address: accountPkh,
+ pubkey: publicKey,
+ },
+ ]);
+ await walletKit.respondSessionRequest({ topic, response });
+
+ toast({
+ description: "Successfully provided the requested account data",
+ status: "success",
+ });
+ return;
}
case "tezos_sign": {
diff --git a/apps/web/src/views/Activity/Activity.test.tsx b/apps/web/src/views/Activity/Activity.test.tsx
index 73fd07bd39..e0a544ad4c 100644
--- a/apps/web/src/views/Activity/Activity.test.tsx
+++ b/apps/web/src/views/Activity/Activity.test.tsx
@@ -6,7 +6,7 @@ import {
makeStore,
networksActions,
} from "@umami/state";
-import { MAINNET } from "@umami/tezos";
+import { GHOSTNET, MAINNET } from "@umami/tezos";
import {
type TzktCombinedOperation,
getCombinedOperations,
@@ -45,7 +45,7 @@ describe("", () => {
jest.mocked(getRelatedTokenTransfers).mockResolvedValue([]);
});
- it("displays an empty state ", async () => {
+ it("displays an empty state", async () => {
render(, { store });
await waitFor(() => {
@@ -57,6 +57,32 @@ describe("", () => {
).toBeVisible();
expect(screen.queryByTestId("view-all-operations-button")).not.toBeInTheDocument();
});
+
+ it("has correct mainnet Buy Tez link", async () => {
+ store.dispatch(networksActions.setCurrent(MAINNET));
+ render(, { store });
+
+ await waitFor(() => {
+ expect(screen.getByTestId("empty-state-message")).toBeVisible();
+ });
+ const link = screen.getByRole("link", { name: "Buy Tez Now" });
+ expect(link).toHaveAttribute(
+ "href",
+ `https://widget.wert.io/default/widget/?commodity=XTZ&address=${account.address.pkh}&network=tezos&commodity_id=xtz.simple.tezos`
+ );
+ });
+
+ it("has correct ghostnet Buy Tez link", async () => {
+ store.dispatch(networksActions.setCurrent(GHOSTNET));
+ render(, { store });
+
+ await waitFor(() => {
+ expect(screen.getByTestId("empty-state-message")).toBeVisible();
+ });
+ const link = screen.getByRole("link", { name: "Buy Tez Now" });
+ expect(link).toBeVisible();
+ expect(link).toHaveAttribute("href", "https://faucet.ghostnet.teztnets.com/");
+ });
});
describe("with operations", () => {
diff --git a/apps/web/src/views/Activity/Activity.tsx b/apps/web/src/views/Activity/Activity.tsx
index ba6824010d..ccf4b39d88 100644
--- a/apps/web/src/views/Activity/Activity.tsx
+++ b/apps/web/src/views/Activity/Activity.tsx
@@ -1,7 +1,7 @@
import { Box, Center, Divider, Flex, Image, Spinner } from "@chakra-ui/react";
import { type Account } from "@umami/core";
import { useGetOperations } from "@umami/data-polling";
-import { useCurrentAccount } from "@umami/state";
+import { useBuyTezUrl, useCurrentAccount } from "@umami/state";
import loadingDots from "../../assets/loading-dots.gif";
import { EmptyMessage } from "../../components/EmptyMessage";
@@ -21,7 +21,7 @@ export const Activity = () => {
isVerified
);
- const buyTezUrl = `https://widget.wert.io/default/widget/?commodity=XTZ&address=${currentAccount?.address.pkh}&network=tezos&commodity_id=xtz.simple.tezos`;
+ const buyTezUrl = useBuyTezUrl(currentAccount?.address.pkh);
const isEmpty = operations.length === 0 && !isLoading;
diff --git a/apps/web/src/views/Earn/Earn.tsx b/apps/web/src/views/Earn/Earn.tsx
index 4a47046f11..d4fff578ab 100644
--- a/apps/web/src/views/Earn/Earn.tsx
+++ b/apps/web/src/views/Earn/Earn.tsx
@@ -8,7 +8,7 @@ import { ViewOverlay } from "../../components/ViewOverlay/ViewOverlay";
export const Earn = () => {
const isVerified = useIsAccountVerified();
- const buyTezUrl = "https://stake.tezos.com/";
+ const stakeTezosUrl = "https://stake.tezos.com/";
return (
<>
@@ -16,7 +16,7 @@ export const Earn = () => {
{isVerified ? (
diff --git a/apps/web/src/views/Tokens/Token.tsx b/apps/web/src/views/Tokens/Token.tsx
index f9ff8e6382..ab21853452 100644
--- a/apps/web/src/views/Tokens/Token.tsx
+++ b/apps/web/src/views/Tokens/Token.tsx
@@ -43,6 +43,7 @@ export const Token = ({ token }: TokenProps) => {
_notLast={{
borderBottom: `1px solid ${color("100")}`,
}}
+ data-testid="token-card"
paddingY={{ base: "18px", md: "30px" }}
>
diff --git a/apps/web/src/views/Tokens/Tokens.test.tsx b/apps/web/src/views/Tokens/Tokens.test.tsx
new file mode 100644
index 0000000000..adb1241d9c
--- /dev/null
+++ b/apps/web/src/views/Tokens/Tokens.test.tsx
@@ -0,0 +1,72 @@
+import { mockImplicitAccount, mockMnemonicAccount } from "@umami/core";
+import {
+ type UmamiStore,
+ accountsActions,
+ addTestAccount,
+ makeStore,
+ networksActions,
+} from "@umami/state";
+import { GHOSTNET, MAINNET } from "@umami/tezos";
+
+import { Tokens } from "./Tokens";
+import { render, screen, waitFor } from "../../testUtils";
+
+let store: UmamiStore;
+const account = mockImplicitAccount(0);
+
+beforeEach(() => {
+ store = makeStore();
+ addTestAccount(store, account);
+ store.dispatch(accountsActions.setCurrent(account.address.pkh));
+});
+
+describe("", () => {
+ beforeEach(() => {
+ addTestAccount(store, mockMnemonicAccount(1));
+ addTestAccount(store, mockMnemonicAccount(2));
+ store.dispatch(networksActions.setCurrent(MAINNET));
+ });
+
+ describe("without tokens", () => {
+ it("displays an empty state", async () => {
+ render(, { store });
+
+ await waitFor(() => {
+ expect(screen.getByTestId("empty-state-message")).toBeVisible();
+ });
+
+ expect(screen.getByText("Get Started with Tokens")).toBeVisible();
+ expect(
+ screen.getByText("You need Tez to take part in any activity. Buy some to get started.")
+ ).toBeVisible();
+ expect(screen.getByText("Buy Tez Now")).toBeVisible();
+ expect(screen.queryByTestId("token-card")).not.toBeInTheDocument();
+ });
+
+ it("has correct mainnet Buy Tez link", async () => {
+ store.dispatch(networksActions.setCurrent(MAINNET));
+ render(, { store });
+
+ await waitFor(() => {
+ expect(screen.getByTestId("empty-state-message")).toBeVisible();
+ });
+ const link = screen.getByRole("link", { name: "Buy Tez Now" });
+ expect(link).toHaveAttribute(
+ "href",
+ `https://widget.wert.io/default/widget/?commodity=XTZ&address=${account.address.pkh}&network=tezos&commodity_id=xtz.simple.tezos`
+ );
+ });
+
+ it("has correct ghostnet Buy Tez link", async () => {
+ store.dispatch(networksActions.setCurrent(GHOSTNET));
+ render(, { store });
+
+ await waitFor(() => {
+ expect(screen.getByTestId("empty-state-message")).toBeVisible();
+ });
+ const link = screen.getByRole("link", { name: "Buy Tez Now" });
+ expect(link).toBeVisible();
+ expect(link).toHaveAttribute("href", "https://faucet.ghostnet.teztnets.com/");
+ });
+ });
+});
diff --git a/apps/web/src/views/Tokens/Tokens.tsx b/apps/web/src/views/Tokens/Tokens.tsx
index 3baa203218..96d0702bb5 100644
--- a/apps/web/src/views/Tokens/Tokens.tsx
+++ b/apps/web/src/views/Tokens/Tokens.tsx
@@ -1,6 +1,6 @@
import { Flex, VStack } from "@chakra-ui/react";
import { fullId } from "@umami/core";
-import { useCurrentAccount, useGetAccountAllTokens } from "@umami/state";
+import { useBuyTezUrl, useCurrentAccount, useGetAccountAllTokens } from "@umami/state";
import { Token } from "./Token";
import { EmptyMessage } from "../../components/EmptyMessage";
@@ -13,7 +13,7 @@ export const Tokens = () => {
const currentAccount = useCurrentAccount()!;
const availableTokens = useGetAccountAllTokens()(currentAccount.address.pkh);
- const buyTezUrl = `https://widget.wert.io/default/widget/?commodity=XTZ&address=${currentAccount.address.pkh}&network=tezos&commodity_id=xtz.simple.tezos`;
+ const buyTezUrl = useBuyTezUrl(currentAccount.address.pkh);
return (
<>
diff --git a/packages/state/src/hooks/network.test.ts b/packages/state/src/hooks/network.test.ts
index 1d9ecdb119..f905f2553e 100644
--- a/packages/state/src/hooks/network.test.ts
+++ b/packages/state/src/hooks/network.test.ts
@@ -3,6 +3,7 @@ import { GHOSTNET, MAINNET, mockImplicitAddress } from "@umami/tezos";
import {
useAvailableNetworks,
+ useBuyTezUrl,
useFindNetwork,
useSelectNetwork,
useSelectedNetwork,
@@ -35,6 +36,28 @@ describe("networkHooks", () => {
});
});
+ describe("useBuyTezUrl", () => {
+ it("for mainnet - returns customized mainnet url", () => {
+ store.dispatch(networksActions.setCurrent(MAINNET));
+
+ const {
+ result: { current },
+ } = renderHook(() => useBuyTezUrl("pkh123"), { store });
+ expect(current).toEqual(
+ `${MAINNET.buyTezUrl}/default/widget/?commodity=XTZ&address=pkh123&network=tezos&commodity_id=xtz.simple.tezos`
+ );
+ });
+
+ it("for others - returns url from network setting", () => {
+ store.dispatch(networksActions.setCurrent(GHOSTNET));
+
+ const {
+ result: { current },
+ } = renderHook(() => useBuyTezUrl("pkh123"), { store });
+ expect(current).toEqual(GHOSTNET.buyTezUrl);
+ });
+ });
+
describe("useAvailableNetworks", () => {
it("returns default networks by default", () => {
const {
diff --git a/packages/state/src/hooks/network.ts b/packages/state/src/hooks/network.ts
index 232542ea97..78070cac65 100644
--- a/packages/state/src/hooks/network.ts
+++ b/packages/state/src/hooks/network.ts
@@ -1,3 +1,4 @@
+import { MAINNET } from "@umami/tezos";
import { useDispatch } from "react-redux";
import { useAppSelector } from "./useAppSelector";
@@ -5,6 +6,17 @@ import { assetsActions, networksActions } from "../slices";
export const useSelectedNetwork = () => useAppSelector(s => s.networks.current);
+export const useBuyTezUrl = (pkh?: string) => {
+ const network = useSelectedNetwork();
+ let buyTezUrl = network.buyTezUrl;
+
+ if (buyTezUrl && network === MAINNET) {
+ buyTezUrl += `/default/widget/?commodity=XTZ&address=${pkh}&network=tezos&commodity_id=xtz.simple.tezos`;
+ }
+
+ return buyTezUrl;
+};
+
export const useAvailableNetworks = () => useAppSelector(s => s.networks.available);
export const useFindNetwork = () => {