NEAR and NEP141 Balance Widget
A React component to display NEAR native and NEP141 tokens balance. Formatted with their corresponding decimals.
This widget integrates with the original Wallet Selector.

The WalletSelector Component
This React component may be inserted as a child of a NavBar component. You may need only the logic for your project, so follow along to learn how to initialize the NEAR wallet and integrate it with @near-wallet-selector/core
import clsx from "clsx";
import { useEffect, useState } from "react";
import { useTranslation } from "next-i18next";
import { Button } from "../button/Button";
import { Typography } from "ui/typography/Typography";
import { Icon } from "ui/icon/Icon";
import { useNearWalletSelectorContext } from "context/near/wallet-selector/useNearWalletSelectorContext";
import near from "providers/near";
import { useWalletStateContext } from "context/wallet/state/useWalletStateContext";
import pulse from "providers/pulse";
import { CollateralTokenBalance } from "ui/pulse/market-card/collateral-token-balance/CollateralTokenBalance";
import { useNearPromptWarsMarketContractContext } from "context/near/prompt-wars-market-contract/useNearPromptWarsMarketContractContext";
import { WalletSelectorProps } from "./WalletSelector.types";
import styles from "./WalletSelector.module.scss";
import "@near-wallet-selector/modal-ui/styles.css";
export const WalletSelector: React.FC<WalletSelectorProps> = ({ className }) => {
const [isWidgetVisible, setIsWidgetVisible] = useState(false);
const { marketId } = useNearPromptWarsMarketContractContext();
const nearWalletSelectorContext = useNearWalletSelectorContext();
const wallet = useWalletStateContext();
useEffect(() => {
if (!nearWalletSelectorContext.selector) {
return;
}
nearWalletSelectorContext.initModal(near.getConfig().factoryWalletId);
}, [nearWalletSelectorContext.selector]);
const handleOnConnectWalletClick = () => {
nearWalletSelectorContext.signOut();
setIsWidgetVisible(false);
};
const handleOnDisplayWidgetClick = () => {
if (wallet.isConnected) {
setIsWidgetVisible(!isWidgetVisible);
} else {
nearWalletSelectorContext.modal?.show();
}
};
const { t } = useTranslation(["prompt-wars"]);
return (
<div className={clsx(styles["wallet-selector"], className)}>
<Button
color={wallet.actions.isGettingGuestWallet ? "success" : "primary"}
variant="outlined"
onClick={handleOnDisplayWidgetClick}
rightIcon={<Icon name={wallet.address ? "icon-power" : "icon-power-crossed"} />}
className={styles["wallet-selector__button"]}
animate={wallet.actions.isGettingGuestWallet ? "pulse" : undefined}
size="s"
>
{wallet.isConnected ? (
<Typography.Text inline truncate flat>
{wallet.address}
</Typography.Text>
) : (
<>
{wallet.actions.isGettingGuestWallet
? t("promptWars.walletSelector.isSettingGuestWallet")
: t("promptWars.connectWallet")}
</>
)}
</Button>
{isWidgetVisible && (
<>
<div
className={styles["wallet-selector__widget--backdrop"]}
onClick={() => setIsWidgetVisible(!isWidgetVisible)}
role="presentation"
/>
<div className={styles["wallet-selector__widget"]}>
<div className={styles["wallet-selector__balance"]}>
<Typography.Description>{t("promptWars.walletSelector.nativeBalance")}</Typography.Description>
<Typography.Text>{wallet.balance}</Typography.Text>
<Typography.Description>
{t("promptWars.balance")} <code>@{pulse.getDefaultCollateralToken().accountId}</code>
</Typography.Description>
<Typography.Text>
<CollateralTokenBalance
accountId={wallet.address || marketId}
contractAddress={pulse.getDefaultCollateralToken().accountId}
/>
</Typography.Text>
<Typography.MiniDescription flat>
<Typography.Anchor href={`${wallet.explorer}/accounts/${wallet.address}`} target="_blank">
{wallet.isConnected && wallet.address}
</Typography.Anchor>
</Typography.MiniDescription>
</div>
<div className={styles["wallet-selector__connect"]}>
<Button size="xs" color="primary" onClick={handleOnConnectWalletClick}>
{wallet.isConnected ? "Disconnect" : "Connect"}
</Button>
</div>
</div>
</>
)}
</div>
);
};
The NearWalletSelectorContextController
NearWalletSelectorContextController
This component is designed to be a React context state and action manager. It should initialize the NEAR wallets modules on the first load. It will also set a selector
state const
so that you can load the wallets modal within UI components.
This context component works together with WalletStateContextController
.
import React, { useEffect, useState } from "react";
import { BrowserWallet, NetworkId, setupWalletSelector, WalletSelector } from "@near-wallet-selector/core";
import { setupModal, WalletSelectorModal } from "@near-wallet-selector/modal-ui";
import { setupNearWallet } from "@near-wallet-selector/near-wallet";
import { setupNearFi } from "@near-wallet-selector/nearfi";
import { setupHereWallet } from "@near-wallet-selector/here-wallet";
import { setupMathWallet } from "@near-wallet-selector/math-wallet";
import { setupSender } from "@near-wallet-selector/sender";
import { setupNightly } from "@near-wallet-selector/nightly";
import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet";
import { setupLedger } from "@near-wallet-selector/ledger";
import { setupCoin98Wallet } from "@near-wallet-selector/coin98-wallet";
import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet";
import near from "providers/near";
import { useWalletStateContext } from "context/wallet/state/useWalletStateContext";
import { WalletSelectorChain } from "context/wallet-selector/WalletSelectorContext.types";
import { useRoutes } from "hooks/useRoutes/useRoutes";
import { setupGuestWallet } from "providers/near/wallet-selector/setupGuestWallet";
import { useLocalStorage } from "hooks/useLocalStorage/useLocalStorage";
import { NearWalletSelectorContextControllerProps } from "./NearWalletSelectorContext.types";
import { NearWalletSelectorContext } from "./NearWalletSelectorContext";
export const NearWalletSelectorContextController = ({ children }: NearWalletSelectorContextControllerProps) => {
const [selector, setSelector] = useState<WalletSelector>();
const [modal, setModal] = useState<WalletSelectorModal>();
const walletStateContext = useWalletStateContext();
const routes = useRoutes();
const ls = useLocalStorage();
const initGuestConnection = async (accountId: string) => {
console.log(`initGuestConnection: ${accountId}`);
try {
const connection = await near.initWalletConnection();
const { near: nearAPI, wallet: nearWalletConnection } = connection;
const wallet = await selector?.wallet("guest-wallet");
await (wallet as BrowserWallet)?.signIn({ contractId: near.getConfig().factoryWalletId });
walletStateContext.setContext({
wallet,
connection: nearWalletConnection,
provider: nearAPI,
guest: {
address: accountId,
},
});
walletStateContext.setIsConnected(true);
walletStateContext.setAddress(accountId);
walletStateContext.setNetwork(near.getConfig().networkId as NetworkId);
walletStateContext.setChain(WalletSelectorChain.near);
walletStateContext.setExplorer(near.getConfig().explorerUrl);
const accountBalance = await near.getAccountBalance(nearAPI, accountId);
walletStateContext.setBalance(near.formatAccountBalance(accountBalance.available, 8));
} catch (error) {
console.log(error);
}
walletStateContext.setActions((prev) => ({
...prev,
isGettingGuestWallet: false,
}));
};
const onSignedIn = async (s: WalletSelector) => {
try {
const connection = await near.initWalletConnection();
const { near: nearAPI, wallet: nearWalletConnection } = connection;
walletStateContext.setContext({
connection: nearWalletConnection,
provider: nearAPI,
guest: {
address: near.getConfig().guestWalletId,
},
});
const wallet = await s?.wallet();
if (!wallet) {
return;
}
const [account] = await wallet.getAccounts();
walletStateContext.setContext({
wallet,
connection: nearWalletConnection,
provider: nearAPI,
guest: {
address: account.accountId,
},
});
walletStateContext.setIsConnected(s.isSignedIn());
walletStateContext.setAddress(account.accountId);
walletStateContext.setNetwork(near.getConfig().networkId as NetworkId);
walletStateContext.setChain(WalletSelectorChain.near);
walletStateContext.setExplorer(near.getConfig().explorerUrl);
const accountBalance = await near.getAccountBalance(nearAPI, account.accountId);
walletStateContext.setBalance(near.formatAccountBalance(accountBalance.available, 8));
} catch (error) {
console.log(error);
}
};
const signOut = async () => {
try {
const wallet = await selector?.wallet()!;
await wallet.signOut();
} catch (error) {
console.log(error);
}
walletStateContext.reset();
};
useEffect(() => {
(async () => {
try {
const network = near.getConfig().networkId as NetworkId;
const walletSelector = await setupWalletSelector({
network,
modules: [
setupGuestWallet(),
setupNearWallet(),
setupMyNearWallet(),
setupNearFi(),
setupHereWallet(),
setupMathWallet(),
setupSender(),
setupNightly(),
setupMeteorWallet(),
setupLedger(),
setupCoin98Wallet(),
],
});
const onSignedInSub = walletSelector.on("signedIn", () => onSignedIn(walletSelector));
setSelector(walletSelector);
return () => {
onSignedInSub.remove();
};
} catch (error) {
console.log(error);
}
return undefined;
})();
}, []);
useEffect(() => {
if (!selector) {
return;
}
(async () => {
try {
const wallet = await selector?.wallet();
if (!wallet) {
return;
}
onSignedIn(selector!);
} catch (error) {
console.log(error);
}
})();
}, [selector]);
useEffect(() => {
if (!selector) {
return;
}
(async () => {
try {
if (ls.get("near_app_wallet_auth_key") !== null) {
console.log(`existing non-guest wallet connected`);
return;
}
walletStateContext.setActions((prev) => ({
...prev,
isGettingGuestWallet: true,
}));
const walletAuthKey = ls.get<{
accountId: string;
allKeys: Array<string>;
}>("promptwars_wallet_auth_key");
if (walletAuthKey !== null && /^guest-/i.test(walletAuthKey?.accountId)) {
initGuestConnection(walletAuthKey.accountId);
return;
}
const response = await fetch(routes.api.promptWars.createGuestAccount());
const result = await response.json();
ls.set("near-wallet-selector:selectedWalletId", JSON.stringify("guest-wallet"));
ls.set("near-wallet-selector:recentlySignedInWallets", JSON.stringify(["guest-wallet"]));
ls.set(Object.keys(result)[0], result[Object.keys(result)[0]]);
ls.set(Object.keys(result)[1], result[Object.keys(result)[1]]);
ls.set(Object.keys(result)[2], result[Object.keys(result)[2]]);
console.log(result);
const { accountId } = result.promptwars_wallet_auth_key;
initGuestConnection(accountId);
} catch (error) {
console.log(error);
walletStateContext.setActions((prev) => ({
...prev,
isGettingGuestWallet: false,
}));
}
})();
}, [selector]);
const initModal = (contractId: string) => {
setModal(
setupModal(selector!, {
contractId,
}),
);
};
const props = {
selector,
modal,
initModal,
signOut,
};
return <NearWalletSelectorContext.Provider value={props}>{children}</NearWalletSelectorContext.Provider>;
};
WalletStateContextController
WalletStateContextController
This context component is used through the useWalletStateContext
hook. It provides the connected wallet address, balance, and actions to sign in and out:
import { ReactNode, useState } from "react";
import { NetworkId } from "@near-wallet-selector/core";
import { Address, Balance, Chain, Context, Explorer, IsConnected, StateActions } from "./WalletStateContext.types";
import { WalletStateContext } from "./WalletStateContext";
export const WalletStateContextController = ({ children }: { children: ReactNode }) => {
const [network, setNetwork] = useState<NetworkId | undefined>("testnet");
const [explorer, setExplorer] = useState<Explorer>(undefined);
const [chain, setChain] = useState<Chain>(undefined);
const [address, setAddress] = useState<Address>(undefined);
const [balance, setBalance] = useState<Balance>("0.00");
const [isConnected, setIsConnected] = useState<IsConnected>(false);
const [context, setContext] = useState<Context>({
connection: undefined,
provider: undefined,
guest: {
address: "",
},
});
const [actions, setActions] = useState<StateActions>({
isGettingGuestWallet: false,
});
const reset = () => {
setNetwork(undefined);
setExplorer(undefined);
setChain(undefined);
setAddress(undefined);
setBalance("0.00");
setIsConnected(false);
setActions({ isGettingGuestWallet: false });
};
const props = {
setNetwork,
setAddress,
setExplorer,
setChain,
setBalance,
setIsConnected,
setContext,
setActions,
network,
address,
explorer,
chain,
balance,
isConnected,
reset,
context,
actions,
};
return <WalletStateContext.Provider value={props}>{children}</WalletStateContext.Provider>;
};
CollateralTokenBalance
CollateralTokenBalance
You may also want to look at this component to understand how it fetches the balance of a given NEP141 token. Also displayed in the wallet widget:
import { useEffect } from "react";
import useNearFungibleTokenContract from "providers/near/contracts/fungible-token/useNearFungibleTokenContract";
import pulse from "providers/pulse";
import { CollateralTokenBalanceProps } from "./CollateralTokenBalance.types";
export const CollateralTokenBalance: React.FC<CollateralTokenBalanceProps> = ({ contractAddress, accountId }) => {
const { getBalanceOf, balanceOf } = useNearFungibleTokenContract({
contractAddress,
});
useEffect(() => {
if (!accountId) {
return;
}
getBalanceOf(accountId);
}, [accountId, contractAddress]);
const collateralToken = pulse.getCollateralTokenByAccountId(contractAddress);
return (
<>
{collateralToken.symbol} {balanceOf}
</>
);
};
Last updated