feat: 接入公开币种目录并统一多币种金额与语言初始化处理

This commit is contained in:
2026-05-21 15:14:00 +08:00
parent 626914feb6
commit 6b18e25766
27 changed files with 277 additions and 94 deletions

View File

@@ -21,11 +21,14 @@ import {
ticketNumberSpec,
} from "@/features/hall/hall-bet-rules";
import type { HallDrawLiveSnapshot } from "@/features/hall/use-hall-draw-live";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { triggerWalletPollingAfterBet } from "@/hooks/use-wallet-polling";
import { getLotteryEcho } from "@/lib/lottery-echo";
import { getLotteryRequestLocale } from "@/lib/lottery-locale";
import { formatMinorAsCurrency, parseDecimalInputToMinor } from "@/lib/money";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { cn } from "@/lib/utils";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import { LotteryApiBizError } from "@/types/api/errors";
import type { PlayEffectivePayload, PlayEffectivePlayRow } from "@/types/api/play-effective";
import type { TicketLineInput, TicketPlaceData, TicketPreviewData } from "@/types/api/ticket";
@@ -391,6 +394,7 @@ function quickFillKeys(category: HallCategory): { favorites: string; history: st
export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }) {
const { display, isBettable, reload: reloadDraw } = drawLive;
const { t } = useTranslation("player");
const profile = usePlayerSessionStore((s) => s.profile);
const [activeCategory, setActiveCategory] = useState<HallCategory>("D2");
const [rows, setRows] = useState<DraftRow[]>(() => [newDraftRow()]);
@@ -414,18 +418,14 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
number: null,
longPress: false,
});
useCurrencyCatalog();
const currencyParam = useMemo(() => {
const fromEnv = process.env.NEXT_PUBLIC_LOTTERY_PLAY_CURRENCY?.trim();
return fromEnv !== undefined && fromEnv !== "" ? fromEnv : undefined;
}, []);
const currencyParam = useMemo(() => resolvePlayerCurrency(profile), [profile]);
const loadCatalog = useCallback(async () => {
setCatalogState((s) => (s.kind === "ok" ? s : { kind: "loading" }));
try {
const data = await getPlayEffective(
currencyParam !== undefined ? { currency: currencyParam } : undefined,
);
const data = await getPlayEffective({ currency: currencyParam });
setCatalogState({ kind: "ok", data });
} catch (e) {
const msg = e instanceof LotteryApiBizError ? e.message : t("hall.loadingError");
@@ -435,9 +435,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
const refreshWallet = useCallback(async () => {
try {
const wallet = await getWalletBalance(
currencyParam !== undefined ? { currency: currencyParam } : undefined,
);
const wallet = await getWalletBalance({ currency: currencyParam });
setAvailableMinor(Number(wallet.available_balance ?? 0));
} catch {
setAvailableMinor(0);
@@ -470,7 +468,8 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
);
}, [activeCategory, catalogState]);
const currencyCode = catalogState.kind === "ok" ? catalogState.data.currency_code : "NPR";
const currencyCode =
catalogState.kind === "ok" ? catalogState.data.currency_code : currencyParam;
const categoryPlays = useMemo(() => {
if (catalogState.kind !== "ok") return [];
@@ -656,7 +655,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
const entries: DraftEntry[] = [];
rows.forEach((row, rowIndex) => {
playColumns.forEach((column) => {
const amount = parseDecimalInputToMinor(row.amounts[column.key] ?? "");
const amount = parseDecimalInputToMinor(row.amounts[column.key] ?? "", currencyCode);
if (amount === null || amount <= 0) return;
const line = lineForPlay(activeCategory, column.play, row.number, amount, column.digitSlot);
if (!line) return;
@@ -673,7 +672,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
});
});
return entries;
}, [activeCategory, playColumns, rows]);
}, [activeCategory, currencyCode, playColumns, rows]);
const draftEntries = collectEntries();
const draftSummary = useMemo(() => {

View File

@@ -22,7 +22,9 @@ import {
TableRow,
} from "@/components/ui/table";
import { getLotteryRequestLocale } from "@/lib/lottery-locale";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { cn } from "@/lib/utils";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import { LotteryApiBizError } from "@/types/api/errors";
import type {
PlayEffectivePayload,
@@ -77,19 +79,15 @@ function formatMoneyAmount(n: number): string {
}
export function HallPlayCatalogPanel() {
const profile = usePlayerSessionStore((s) => s.profile);
const { t } = useTranslation("player");
const [state, setState] = useState<LoadState>({ kind: "loading" });
const currencyParam = useMemo(() => {
const fromEnv = process.env.NEXT_PUBLIC_LOTTERY_PLAY_CURRENCY?.trim();
return fromEnv !== undefined && fromEnv !== "" ? fromEnv : undefined;
}, []);
const currencyParam = useMemo(() => resolvePlayerCurrency(profile), [profile]);
const load = useCallback(async () => {
setState((s) => (s.kind === "ok" ? s : { kind: "loading" }));
try {
const data = await getPlayEffective(
currencyParam !== undefined ? { currency: currencyParam } : undefined,
);
const data = await getPlayEffective({ currency: currencyParam });
setState({ kind: "ok", data });
} catch (e) {
if (e instanceof LotteryApiBizError && e.code === 9004) {

View File

@@ -12,6 +12,7 @@ import {
TransferOutDialog,
} from "@/features/wallet/wallet-transfer-dialogs";
import { formatMinorAsCurrency } from "@/lib/money";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { cn } from "@/lib/utils";
import { useNetworkConnectionStore } from "@/stores/network-connection-store";
import { usePlayerSessionStore } from "@/stores/player-session-store";
@@ -26,9 +27,8 @@ export function HallWalletStrip() {
const degradedWalletPollRef = useRef<number | null>(null);
const currency = useMemo(
() =>
(balance?.currency_code ?? profile?.default_currency ?? "NPR").toUpperCase(),
[balance?.currency_code, profile?.default_currency],
() => resolvePlayerCurrency(profile, balance),
[balance, profile],
);
const refresh = useCallback(async () => {

View File

@@ -16,12 +16,15 @@ import {
} from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
import { norm4d } from "@/lib/norm-4d";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { playLabel } from "@/lib/play-labels";
import { cn } from "@/lib/utils";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { TicketItemDetailPayload } from "@/types/api/ticket-items";
type OddsSnapRow = { prize_scope?: string; odds_value?: number };
@@ -66,6 +69,8 @@ type TicketItemDetailWithExtras = TicketItemDetailPayload & {
/** 界面文档 §4.8 注单详情 */
export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
const { t } = useTranslation("player");
const profile = usePlayerSessionStore((state) => state.profile);
useCurrencyCatalog();
const [data, setData] = useState<TicketItemDetailPayload | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
@@ -131,7 +136,7 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
);
}
const cur = data.currency_code ?? "NPR";
const cur = data.currency_code ?? resolvePlayerCurrency(profile);
const st = ticketStatusDisplay(data.status, data.win_amount, data.jackpot_win_amount, t);
const totalWin = data.win_amount + data.jackpot_win_amount;
const pub = data.published_draw_results;

View File

@@ -15,11 +15,14 @@ import { PlayerPanel } from "@/components/layout/player-panel";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Skeleton } from "@/components/ui/skeleton";
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { useIsMobile } from "@/hooks/use-mobile";
import { formatMinorAsCurrency } from "@/lib/money";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { playLabel } from "@/lib/play-labels";
import { cn } from "@/lib/utils";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { TicketItemListRow } from "@/types/api/ticket-items";
const ORDERS_PAGE_SIZE = 20;
@@ -42,6 +45,8 @@ function formatYmd(value: Date): string {
export function TicketOrdersListScreen() {
const searchParams = useSearchParams();
const { t } = useTranslation("player");
const profile = usePlayerSessionStore((state) => state.profile);
useCurrencyCatalog();
const drawNoFilter = useMemo(() => (searchParams.get("draw_no") ?? "").trim(), [searchParams]);
const statusFilter = useMemo(
() => searchParams.getAll("status").map((s) => s.trim()).filter(Boolean),
@@ -353,7 +358,7 @@ export function TicketOrdersListScreen() {
<>
<div className="space-y-3">
{items.map((row) => {
const cur = row.currency_code ?? "NPR";
const cur = row.currency_code ?? resolvePlayerCurrency(profile);
const st = ticketStatusDisplay(row.status, row.win_amount, row.jackpot_win_amount, t);
const totalWin = row.win_amount + row.jackpot_win_amount;
return (

View File

@@ -15,6 +15,7 @@ import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { getPublicCurrencies } from "@/api/currency";
import { getPlayerMe, getPlayerPing } from "@/api/player";
import { LanguageSwitcher } from "@/components/language-switcher";
import { Button, buttonVariants } from "@/components/ui/button";
@@ -93,7 +94,7 @@ export function EntryGate() {
const tokenFromUrl = searchParams.get("token") ?? "";
const { bearerToken, setBearerToken, setProfile, clearBearerToken } =
const { bearerToken, setBearerToken, setProfile, setCurrencies, clearBearerToken } =
usePlayerSessionStore();
const [phase, setPhase] = useState<Phase>("loading");
@@ -141,6 +142,13 @@ export function EntryGate() {
try {
const [me] = await Promise.all([getPlayerMe(), sleep(300)]);
try {
const currencies = await getPublicCurrencies();
setCurrencies(currencies.items);
} catch {
// 不阻断进场主流程;金额精度会退回默认 2 位兜底。
}
updateStep("token", "done");
updateStep("account", "done");
@@ -216,6 +224,7 @@ export function EntryGate() {
tokenFromUrl,
setBearerToken,
setProfile,
setCurrencies,
clearBearerToken,
router,
updateStep,

View File

@@ -2,6 +2,7 @@
import { useEffect } from "react";
import { getPublicCurrencies } from "@/api/currency";
import { getPlayerMe } from "@/api/player";
import { usePlayerSessionStore } from "@/stores/player-session-store";
@@ -14,20 +15,31 @@ export function HydratePlayerAuth(): null {
(state) => state.restoreBearerToken,
);
const setProfile = usePlayerSessionStore((state) => state.setProfile);
const setCurrencies = usePlayerSessionStore((state) => state.setCurrencies);
useEffect(() => {
const token = restoreBearerToken();
if (!token) return;
if (usePlayerSessionStore.getState().profile !== null) return;
void (async () => {
try {
if (usePlayerSessionStore.getState().currencies.length === 0) {
try {
const currencies = await getPublicCurrencies();
setCurrencies(currencies.items);
} catch {
// 币种元数据失败时不影响登录态恢复。
}
}
if (!token) return;
if (usePlayerSessionStore.getState().profile !== null) return;
const me = await getPlayerMe();
setProfile(me);
} catch {
/* 401 由 lottery-http 拦截跳转 */
}
})();
}, [restoreBearerToken, setProfile]);
}, [restoreBearerToken, setCurrencies, setProfile]);
return null;
}

View File

@@ -17,9 +17,12 @@ import {
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { PlayerPanel } from "@/components/layout/player-panel";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { formatMinorAsCurrency } from "@/lib/money";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { playLabel } from "@/lib/play-labels";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { DrawResultListItem } from "@/types/api/draw-results";
import type { TicketDrawMyMatchPayload, TicketItemListRow } from "@/types/api/ticket-items";
@@ -31,6 +34,7 @@ type WinningCheckResult = {
export function CheckWinningScreen() {
const { t } = useTranslation("player");
useCurrencyCatalog();
const [ticketNo, setTicketNo] = useState("");
const [latestDraw, setLatestDraw] = useState<DrawResultListItem | null>(null);
const [recent, setRecent] = useState<string[]>([]);
@@ -197,9 +201,11 @@ function WinningResultDialog({
onCheckAnother: () => void;
}) {
const { t } = useTranslation("player");
const profile = usePlayerSessionStore((state) => state.profile);
const totalWin = (data?.match.total_win_minor ?? 0) + (data?.match.total_jackpot_win_minor ?? 0);
const isWon = totalWin > 0 || (data?.match.winning_ticket_count ?? 0) > 0;
const firstTicket = useMemo(() => data?.tickets[0] ?? null, [data]);
const currency = firstTicket?.currency_code ?? resolvePlayerCurrency(profile);
if (!data) return null;
@@ -250,7 +256,7 @@ function WinningResultDialog({
{t("results.check.amount")}
</p>
<p className="mt-2 font-mono text-lg font-black text-[#0a8f3e]">
{formatMinorAsCurrency(totalWin, firstTicket?.currency_code ?? "NPR")}
{formatMinorAsCurrency(totalWin, currency)}
</p>
</div>
</div>

View File

@@ -18,11 +18,14 @@ import {
import { Skeleton } from "@/components/ui/skeleton";
import { JackpotResultsStrip } from "@/features/results/jackpot-results-strip";
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { getPlayerBearerTokenPayload } from "@/lib/lottery-auth";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
import { norm4d } from "@/lib/norm-4d";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { cn } from "@/lib/utils";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { DrawResultDetailPayload } from "@/types/api/draw-results";
type DrawResultDetailScreenProps = {
@@ -32,6 +35,8 @@ type DrawResultDetailScreenProps = {
/** §4.6 开奖结果详情23 分区 + [< >] 切换 + 本人命中高亮 + Jackpot */
export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps) {
const { t } = useTranslation("player");
const profile = usePlayerSessionStore((state) => state.profile);
useCurrencyCatalog();
const [data, setData] = useState<DrawResultDetailPayload | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
@@ -135,7 +140,7 @@ export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps)
);
}
const currency = "NPR";
const currency = data.jackpot?.currency_code ?? resolvePlayerCurrency(profile);
const showMyPayout =
myTotals &&
myTotals.hasBets &&

View File

@@ -20,7 +20,10 @@ import {
import { PlayerPanel } from "@/components/layout/player-panel";
import { JackpotResultsStrip } from "@/features/results/jackpot-results-strip";
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { DrawResultListItem } from "@/types/api/draw-results";
const RESULTS_PAGE_SIZE = 10;
@@ -32,6 +35,8 @@ const MONTH_OPTIONS = Array.from({ length: 12 }, (_, value) => ({
export function DrawResultsListScreen() {
const { t } = useTranslation("player");
const profile = usePlayerSessionStore((state) => state.profile);
useCurrencyCatalog();
const [items, setItems] = useState<DrawResultListItem[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [date, setDate] = useState("");
@@ -46,6 +51,7 @@ export function DrawResultsListScreen() {
const businessDate = /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : undefined;
const quickYears = useMemo(() => buildYearOptions(calendarMonth), [calendarMonth]);
const featured = items?.[0] ?? null;
const jackpotCurrency = featured?.jackpot?.currency_code ?? resolvePlayerCurrency(profile);
const fetchList = useCallback(async (targetPage = 1, append = false) => {
setError(null);
@@ -104,7 +110,7 @@ export function DrawResultsListScreen() {
return (
<PlayerPanel title={t("results.title")} subtitle={t("results.subtitle")} eyebrow={t("brand.name")}>
<div className="space-y-4">
<JackpotResultsStrip currencyCode="NPR" />
<JackpotResultsStrip currencyCode={jackpotCurrency} />
<div className="rounded-xl border border-[#e6edf8] bg-[#f8fbff] p-3">
<p className="mb-2 text-xs font-bold text-[#32518d]">

View File

@@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { getWalletBalance } from "@/api/wallet";
import { TransferInPage } from "@/features/wallet/wallet-transfer-forms";
import { Skeleton } from "@/components/ui/skeleton";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
@@ -17,9 +18,8 @@ export function TransferInScreen() {
const [loading, setLoading] = useState(true);
const currency = useMemo(
() =>
(balance?.currency_code ?? profile?.default_currency ?? "NPR").toUpperCase(),
[balance?.currency_code, profile?.default_currency],
() => resolvePlayerCurrency(profile, balance),
[balance, profile],
);
const load = useCallback(async () => {

View File

@@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { getWalletBalance } from "@/api/wallet";
import { TransferOutPage } from "@/features/wallet/wallet-transfer-forms";
import { Skeleton } from "@/components/ui/skeleton";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
@@ -17,9 +18,8 @@ export function TransferOutScreen() {
const [loading, setLoading] = useState(true);
const currency = useMemo(
() =>
(balance?.currency_code ?? profile?.default_currency ?? "NPR").toUpperCase(),
[balance?.currency_code, profile?.default_currency],
() => resolvePlayerCurrency(profile, balance),
[balance, profile],
);
const load = useCallback(async () => {

View File

@@ -7,6 +7,7 @@ import { getWalletLogs } from "@/api/wallet";
import { Button } from "@/components/ui/button";
import { PlayerPanel } from "@/components/layout/player-panel";
import { WalletLogsBlock } from "@/features/wallet/wallet-logs-block";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { formatWalletClientError } from "@/lib/wallet-api-error";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import { getWalletLogsLastPage, type WalletLogsData } from "@/types/api/wallet-logs";
@@ -25,8 +26,8 @@ export function WalletLogsScreen() {
const loadMoreRef = useRef<HTMLDivElement | null>(null);
const currency = useMemo(
() => (profile?.default_currency ?? "NPR").toUpperCase(),
[profile?.default_currency],
() => resolvePlayerCurrency(profile),
[profile],
);
const fetchPassRef = useRef(true);

View File

@@ -15,6 +15,7 @@ import {
} from "@/features/wallet/wallet-transfer-dialogs";
import { WalletLogsBlock } from "@/features/wallet/wallet-logs-block";
import { formatMinorAsCurrency } from "@/lib/money";
import { resolvePlayerCurrency } from "@/lib/player-currency";
import { formatWalletClientError } from "@/lib/wallet-api-error";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
@@ -34,13 +35,10 @@ export function WalletScreen() {
const [error, setError] = useState<string | null>(null);
const loadMoreRef = useRef<HTMLDivElement | null>(null);
const currency = useMemo(() => {
return (
balance?.currency_code ??
profile?.default_currency ??
"NPR"
).toUpperCase();
}, [balance?.currency_code, profile?.default_currency]);
const currency = useMemo(
() => resolvePlayerCurrency(profile, balance),
[balance, profile],
);
const fetchPassRef = useRef(true);

View File

@@ -18,7 +18,12 @@ import {
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PlayerPanel } from "@/components/layout/player-panel";
import { formatMinorAsCurrency, parseDecimalInputToMinor } from "@/lib/money";
import {
formatMinorAsCurrency,
getCurrencyDecimalPlaces,
parseDecimalInputToMinor,
} from "@/lib/money";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { formatWalletClientError } from "@/lib/wallet-api-error";
import { LotteryApiBizError } from "@/types/api/errors";
@@ -64,14 +69,15 @@ export function TransferInPanel({
variant?: PanelVariant;
}) {
const { t } = useTranslation("player");
useCurrencyCatalog();
const [amountText, setAmountText] = useState("");
const [submitting, setSubmitting] = useState(false);
const [localError, setLocalError] = useState<string | null>(null);
const tid = `${idPrefix}in-amount`;
const parsedMinor = useMemo(
() => parseDecimalInputToMinor(amountText),
[amountText],
() => parseDecimalInputToMinor(amountText, currency),
[amountText, currency],
);
const previewAfter =
parsedMinor != null ? lotteryMinor + parsedMinor : lotteryMinor;
@@ -204,14 +210,15 @@ export function TransferOutPanel({
variant?: PanelVariant;
}) {
const { t } = useTranslation("player");
useCurrencyCatalog();
const [amountText, setAmountText] = useState("");
const [submitting, setSubmitting] = useState(false);
const [localError, setLocalError] = useState<string | null>(null);
const tid = `${idPrefix}out-amount`;
const parsedMinor = useMemo(
() => parseDecimalInputToMinor(amountText),
[amountText],
() => parseDecimalInputToMinor(amountText, currency),
[amountText, currency],
);
const previewAfter =
parsedMinor != null
@@ -219,8 +226,9 @@ export function TransferOutPanel({
: availableMinor;
const fillAll = () => {
const major = availableMinor / 100;
setAmountText(major.toFixed(2));
const decimals = getCurrencyDecimalPlaces(currency);
const major = availableMinor / 10 ** decimals;
setAmountText(major.toFixed(decimals));
};
const submit = async () => {