NEAR and NEP141 Balance Widget
A React component to display NEAR native and NEP141 tokens balance. Formatted with their corresponding decimals.
Last updated
A React component to display NEAR native and NEP141 tokens balance. Formatted with their corresponding decimals.
Last updated
This widget integrates with the original Wallet Selector.
Getting Started
Follow the Project Setup instructions to start the project in your local machine and try the recipes yourself. You can also clone or navigate the repo to get just what you need.
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
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
.
WalletStateContextController
This context component is used through the useWalletStateContext
hook. It provides the connected wallet address, balance, and actions to sign in and out:
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 { 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>;
};
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>;
};
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}
</>
);
};
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>
);
};