chore(walletui): update dependencies - #1564
Conversation
WalkthroughThe PR introduces TypeScript/Vite path aliases, updates React Query usage to v5-style APIs, adjusts related UI code (isLoading→isPending), and bumps frontend dependencies. It also adds a concrete NFT faucet action in ClaimNftsButton, tweaks WebAuthn typing, updates tsconfig moduleResolution to Bundler, and configures aliases in Vite. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as ClaimNftsButton
participant Store as accountStore
participant API as claimTestnetFaucetNfts (json_rpc)
participant Backend as Walletd API
participant QC as queryClient
User->>UI: Click "Claim Testnet NFTs"
UI->>Store: Read current account
alt No account
UI-->>User: Render nothing
else Account present
UI->>API: claimTestnetFaucetNfts({account, numberToMint:5, mutableData, maxFee})
API->>Backend: POST mint request
Backend-->>API: Response (success/error)
API-->>UI: Promise resolved/rejected
opt On success
UI->>QC: invalidateQueries(predicate: NFTs keys)
QC-->>UI: Trigger refetch of NFT lists
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (31)
applications/tari_walletd/web_ui/package.json (1)
6-11: AddpackageManagerfield and enforce pnpm usage. Inapplications/tari_walletd/web_ui/package.json(and other workspace packages at lines 45–52), add a top-level"packageManager": "pnpm@<your-pnpm-version>"so Corepack and IDEs pick pnpm automatically, and update CI and local scripts to run
pnpm installinstead ofnpm install. (stackoverflow.com, truecoderguru.com)applications/tari_walletd/web_ui/src/utils/helpers.tsx (1)
307-309: Bug: timezone detection regex appends a second “Z”.Strings already ending with “Z” don’t match the current regex and get “ZZ”, yielding Invalid Date in some browsers.
Apply this diff:
- if (!/[Z+\-]\d{2}:?\d{2}$/.test(formatted)) { + // Accepts ...Z or ...+hh:mm / +hhmm + if (!/(Z|[+\-]\d{2}:?\d{2})$/.test(formatted)) { formatted += "Z"; }applications/tari_walletd/web_ui/src/Components/JsonTooltip.tsx (1)
26-35: Fix crash risk on invalid/missing JSON and correct prop types.
jsonTextis typed as string but checked against null, andJSON.parseisn’t guarded. Also,childrenshould be ReactNode, not string.-export default function JsonTooltip({ jsonText, children }: { jsonText: string; children: string }) { - if (jsonText === null) { - return <>No data</>; - } - return ( - <div className="tooltip"> - {children} - <span className="tooltiptext json">{renderJson(JSON.parse(jsonText))}</span> - </div> - ); -} +export default function JsonTooltip({ + jsonText, + children, +}: { + jsonText?: string | null; + children: React.ReactNode; +}) { + if (!jsonText) { + return <>No data</>; + } + let parsed: unknown; + try { + parsed = JSON.parse(jsonText); + } catch { + return <>Invalid JSON</>; + } + return ( + <div className="tooltip"> + {children} + <span className="tooltiptext json">{renderJson(parsed as any)}</span> + </div> + ); +}applications/tari_walletd/web_ui/src/routes/AssetVault/Components/SelectAccount.tsx (1)
44-44: Fix selection when comparing addresses and remove non-null assertion.Currently you compare objects (
info.account.address === account?.address) which may fail; usesubstateIdToStringconsistently and avoidaccount!.const theme = useTheme(); + + const selectedValue = + account && + dataAccountsList?.accounts?.some( + (info: AccountInfo) => + substateIdToString(info.account.address) === substateIdToString(account.address), + ) + ? substateIdToString(account.address) + : "addAccount"; @@ - value={ - dataAccountsList?.accounts.some((info: AccountInfo) => info.account.address === account?.address) - ? substateIdToString(account!.address) - : "addAccount" - } + value={selectedValue}Also applies to: 70-77
applications/tari_walletd/web_ui/src/routes/Transactions/Events.tsx (1)
52-59: Type guard before passing to CopyAddress
valuemay be non-string; guard to prevent prop type issues.- if (key === "resource" || key === "resource_address") { + if ((key === "resource" || key === "resource_address") && typeof value === "string") { return ( <Box sx={{ display: "flex", alignItems: "center", gap: 1 }}> <Typography variant="body2" color="text.secondary">Resource:</Typography> <CopyAddress address={value} /> </Box> ); }applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/steps/FormStep.tsx (1)
95-109: Duplicate id between InputLabel and Select; use labelIdCurrent code assigns the same id to both elements. Fix for a11y and DOM validity.
- <InputLabel id="select-payer-account">Account (to pay fees)</InputLabel> + <InputLabel id="select-payer-account-label">Account (to pay fees)</InputLabel> <Select - id="select-payer-account" + id="select-payer-account" + labelId="select-payer-account-label" name="payerAccount" disabled={disabled} displayEmpty value={ transferFormState.payerAccount || substateIdToString(accounts.find((a) => a.account.is_default)?.account.address) || "" }applications/tari_walletd/web_ui/src/routes/Auth/Auth.tsx (1)
35-39: Move setAuthToken calls into an effect; don’t set state during render.setAuthToken() inside render paths (“none”/“webauthn”) can cause render loops. Do the updates in an effect and keep render pure.
@@ useEffect(() => { if (!authMethodsIsError && authMethod) { setCurrAuthMethod(authMethod.method); } if (authMethodsError) { console.error(authMethodsError); } - }, [authMethod, authMethodsIsError]); + }, [authMethod, authMethodsIsError, authMethodsError]); + + // Apply auth token side-effects outside of render + useEffect(() => { + if (currAuthMethod === "none") { + setAuthToken(AUTH_TOKEN_FOR_NONE_AUTH); + } + if (currAuthMethod === "webauthn" && authToken === AUTH_TOKEN_FOR_NONE_AUTH) { + setAuthToken(""); + } + }, [currAuthMethod, authToken, setAuthToken]); @@ if (currAuthMethod === "none") { console.log("no auth"); - setAuthToken(AUTH_TOKEN_FOR_NONE_AUTH); return <Navigate replace to={redirect} />; } @@ if (currAuthMethod === "webauthn") { - if (authToken === AUTH_TOKEN_FOR_NONE_AUTH) { - setAuthToken(""); - } return <Navigate replace to={"/auth/webauthn?redirect=" + redirect} />; }Also applies to: 41-46, 25-33
applications/tari_walletd/web_ui/src/routes/Transactions/TransactionDetails.tsx (1)
53-54: React Query v5: use isPending (not isLoading).In v5, isLoading was split/repurposed; recommended gate is isPending. Update selector and usages for correct initial-state handling.
- const { data, isLoading, isError, error } = useTransactionDetails(transactionId); + const { data, isPending, isError, error } = useTransactionDetails(transactionId); @@ - if (isLoading) { + if (isPending) { return <Loading />; } @@ - <Fade in={!isLoading}> + <Fade in={!isPending}>References: TanStack Query v5 migration guide and queries docs. (tanstack.com)
Also applies to: 92-99, 136-136
applications/tari_walletd/web_ui/src/utils/json_rpc.ts (1)
164-165: Bug: outerAddress ignored after first resolution.When outerAddress is set, code returns DEFAULT_WALLET_ADDRESS instead of reusing outerAddress. This can regress to localhost even after discovering a remote address.
- const getAddress = !outerAddress ? getClientAddress() : Promise.resolve(DEFAULT_WALLET_ADDRESS); + const getAddress = !outerAddress ? getClientAddress() : Promise.resolve(outerAddress!); ... - const getAddress = !outerAddress ? getClientAddress() : Promise.resolve(DEFAULT_WALLET_ADDRESS); + const getAddress = !outerAddress ? getClientAddress() : Promise.resolve(outerAddress!)Also applies to: 187-189
applications/tari_walletd/web_ui/src/routes/Transactions/Instructions.tsx (1)
35-41: RowData receives an unused second param; keys become "undefined-*" and may collide.React only passes a single props object to function components, so
indexhere is always undefined. Remove the second param and the inner TableRow keys (the parent already keys ), or passindexvia props.-function RowData({ title, data }: { title: string; data: Instruction }, index: number) { +function RowData({ title, data }: { title: string; data: Instruction }) { const [open, setOpen] = useState(false); const theme = useTheme(); return ( <> - <TableRow key={`${index}-1`}> + <TableRow> <DataTableCell sx={{ borderTop: 1, borderTopColor: "divider", borderBottom: "none" }}>{title}</DataTableCell> <DataTableCell width={90} sx={{ borderTop: 1, borderTopColor: "divider", borderBottom: "none", textAlign: "center" }} > ... - <TableRow key={`${index}-2`}> + <TableRow>Also applies to: 57-58
applications/tari_walletd/web_ui/src/routes/AssetVault/Components/TransferNft.tsx (3)
271-279: Bug: Effect depends on undefinedopenand default payer-account condition inverted.
openisn’t in scope in this component (should beprops.open), and you only set the default payer account when it’s already non-empty.- useEffect(() => { - if (transferFormState.payerAccount != "") { - setTransferFormState({ - ...transferFormState, - payerAccount: substateIdToString(account.address), - }); - } - }, [open]); + useEffect(() => { + if (transferFormState.payerAccount == "") { + setTransferFormState({ + ...transferFormState, + payerAccount: substateIdToString(account.address), + }); + } + }, [props.open]);
142-151: Unbounded refetch on every render.
refetchNfts()is invoked at render-time, causing continuous fetching/re-renders. Move it into an effect keyed by dialog open state.- refetchNfts().catch(console.error); + useEffect(() => { + if (props.open) { + refetchNfts().catch(console.error); + } + }, [props.open, refetchNfts]);
310-318: Type the Select change event correctly for single-select.The payer account Select isn’t
multiple, so useSelectChangeEvent<string>to avoid incorrect runtime guards.- const handlePayerAccountChange = (event: SelectChangeEvent<string[]>) => { - if (typeof event.target.value != "string") { - return; - } + const handlePayerAccountChange = (event: SelectChangeEvent<string>) => { const payerAccountSelected = { ComponentAddress: event.target.value, };applications/tari_walletd/web_ui/src/routes/AssetVault/Components/PublishTemplate.tsx (1)
200-207: Fix account selection/value handling and maxFee typingCurrent Select value can be an object or account name (not address), which breaks MUI equality checks and produces wrong fee_account. Also, maxFee is typed number but stored as string. Align on address strings and parse numbers.
@@ - function setFormValue(e: React.ChangeEvent<HTMLInputElement>) { - setFormState({ - ...formState, - [e.target.name]: e.target.value, - }); - if (validity[e.target.name as keyof object] !== undefined) { - setValidity({ - ...validity, - [e.target.name]: e.target.validity.valid, - }); - } - } + function setFormValue(e: React.ChangeEvent<HTMLInputElement>) { + const { name, value, validity: vld } = e.target; + setFormState({ + ...formState, + [name]: + name === "maxFee" + ? (value === "" ? null : Number(value)) + : (value as unknown as string), + }); + if ((validity as Record<string, boolean>)[name] !== undefined) { + setValidity({ + ...validity, + [name]: vld.valid, + } as typeof validity); + } + } @@ - function setSelectFormValue(e: SelectChangeEvent<unknown>) { + function setSelectFormValue(e: SelectChangeEvent<string>) { setFormState({ ...formState, - [e.target.name]: e.target.value, + [e.target.name]: e.target.value as string, }); } @@ - const onSubmit = async (e: FormEvent) => { + const onSubmit = async (e: FormEvent) => { e.preventDefault(); - if (!account) { + // Allow submit if either a selected account or a store account is present + if (!formState.account && !account) { return; } setDisabled(true); const isDryRun = !formState.maxFee; - publishTemplate({ + publishTemplate({ fee_account: { ComponentAddress: formState.account || substateIdToString(account.address) }, binary: base64FromArrayBuffer(formState.binary!), max_fee: isDryRun ? 1_000_000 : Number(formState.maxFee) || 0, detect_inputs: true, dry_run: isDryRun, }) @@ - useEffect(() => { - let account = accounts?.find((a) => a.account.is_default)?.account.name || null; - if (account) { - setFormState({ ...INITIAL_VALUES, account }); - setValidity({ ...validity, account: true }); - } - }, [accounts]); + useEffect(() => { + const addr = accounts?.find((a) => a.account.is_default)?.account.address; + if (addr) { + setFormState((s) => ({ ...s, account: substateIdToString(addr) })); + setValidity((v) => ({ ...v, account: true }) as typeof validity); + } + }, [accounts]); @@ - <InputLabel id="select-account">Account</InputLabel> + <InputLabel id="select-account">Account</InputLabel> <Select - id="select-account" + id="select-account" + labelId="select-account" name="account" disabled={disabled} displayEmpty - value={formState.account || accounts.find((a) => a.account.is_default) || ""} + value={ + formState.account || + (accounts?.find((a) => a.account.is_default) + ? substateIdToString(accounts.find((a) => a.account.is_default)!.account.address) + : "") + } onChange={setSelectFormValue} variant="outlined" > @@ <TextField name="maxFee" label="Fee" type="number" - value={formState.maxFee} + value={formState.maxFee ?? ""} placeholder="Enter max fee" onChange={setFormValue} disabled={disabled} style={{ flexGrow: 1 }} />Also applies to: 213-231, 104-115, 117-123, 124-133, 256-265
applications/tari_walletd/web_ui/src/api/hooks/useTemplatesAuthored.tsx (1)
14-18: RemovenotifyOnChangePropsfrom all hooks and reviewretryOnMountunder React Query v5
- Delete the
notifyOnChangePropsline from everyuseQuerycall in:
useWebauthn.tsx(L14)useTemplatesAuthored.tsx(L15)useTemplate.tsx(L34)useAccounts.ts(L258)- Replace any memoization needs with the
selectoption.- Comment out or remove
retryOnMountin each hook and verify whether it’s still required, since its semantics have changed in v5.Example diff for useTemplatesAuthored.tsx:
return useQuery({ queryKey: ["templates_list_authored", request], queryFn: () => templatesListAuthored(request), - refetchInterval: false, - notifyOnChangeProps: ["data", "error"], - retryOnMount: false, + refetchInterval: false, + // notifyOnChangeProps removed in v5 + // retryOnMount: false, // verify necessity under v5 semantics retry: false, });Use this command to locate all instances before applying changes:
rg -n 'notifyOnChangeProps|retryOnMount' -g '*.ts' -g '*.tsx'applications/tari_walletd/web_ui/src/routes/AccountDetails/AccountDetails.tsx (1)
171-177: Bug: “Public key” column renders the account address instead of the public key.User-facing data is incorrect.
- <DataTableCell> - {accountsData?.public_key && <CopyAddress address={accountsData?.account.address!} />} - </DataTableCell> + <DataTableCell> + {accountsData?.public_key && <CopyAddress address={accountsData.public_key!} />} + </DataTableCell>applications/tari_walletd/web_ui/src/routes/WebauthnRegistration/Components/Registration.tsx (1)
88-99: Decode challenge as base64url and add a guard (Buffer may be unnecessary in browsers).The challenge from WebAuthn servers is commonly base64url-encoded. Using Buffer.from(..., "base64") can fail on '-'/'_' and relies on polyfills. Prefer a base64url→Uint8Array helper and guard for missing challenge.
- const challenge = Buffer.from((startRegisterResponse.public_key as any).challenge, "base64"); + const pk = startRegisterResponse.public_key as any; + const challengeB64url = pk?.challenge; + if (typeof challengeB64url !== "string" || challengeB64url.length === 0) { + throw new Error("Failed to start registration: missing challenge"); + } + const challenge = base64urlToUint8Array(challengeB64url);Add once in this module (or a shared util):
function base64urlToUint8Array(input: string): Uint8Array { const b64 = input.replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, ""); const bin = atob(b64); const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); return bytes; }applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/SendNft.tsx (1)
151-155: Do not call refetch during render. Move to an effect.Calling refetchNfts() unconditionally in render can cause repeated fetches and render loops. Trigger refetch on account changes instead.
- // Only refetch NFTs when account is available - if (account) { - refetchNfts().catch(console.error); - } + // Only refetch NFTs when account becomes available or changes + useEffect(() => { + if (account) { + refetchNfts().catch(console.error); + } + }, [account, refetchNfts]);applications/tari_walletd/web_ui/src/api/hooks/useNfts.tsx (1)
31-39: Page-through with consistent page size for better performance.Subsequent requests use limit: 1. Use the same limit for all pages and break when the last page has < limit items.
- while (nfts.nfts.length > 0) { + while (nfts.nfts.length > 0) { offset += limit; - nfts = await nftList({ + nfts = await nftList({ account: request.account, - limit: 1, + limit, offset: offset, }); result = result.concat(nfts.nfts); + if (nfts.nfts.length < limit) break; }applications/tari_walletd/web_ui/src/Components/ConnectorLink/ConnectorLink.tsx (1)
57-66: Fix connector-link regex and hard JSON.parse to avoid crashes on valid links.
([^\\]*)excludes backslashes, not slashes; names with “/” fail.- Greedy
(.*)can over-capture.JSON.parsecan throw, leaving the dialog open in a bad state.Apply safer parsing and error handling:
- const setLink = (value: string) => { - const re = /tari:\/\/([^\\]*)\/([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)\/(.*)\/(.*)/i; - let groups; - if ((groups = re.exec(value))) { - setName(decodeURIComponent(groups[1])); - setSignalingServerJWT(groups[2]); - setPermissions(JSON.parse(groups[3]).map((permission: any) => parse(permission))); - setOptionalPermissions(JSON.parse(groups[4]).map((permission: any) => parse(permission))); - } - _setLink(value); - }; + const setLink = (value: string) => { + const re = /^tari:\/\/([^/]+)\/([A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+)\/([^/]+)\/([^/]+)/i; + const groups = re.exec(value); + if (groups) { + try { + setName(decodeURIComponent(groups[1])); + setSignalingServerJWT(groups[2]); + const required = JSON.parse(groups[3]); + const optional = JSON.parse(groups[4]); + setPermissions(required.map((p: any) => parse(p))); + setOptionalPermissions(optional.map((p: any) => parse(p))); + setLinkDetected(true); + } catch (e) { + console.error("Invalid connector link JSON:", e); + setLinkDetected(false); + } + } + _setLink(value); + };applications/tari_walletd/web_ui/src/api/hooks/useTransactions.tsx (1)
37-44: Bug: transaction_details queryKey must include hashWithout hashing in the key, different transactions will share cache entries.
return useQuery({ - queryKey: ["transaction_details"], + queryKey: ["transaction_details", hash], queryFn: () => { return transactionsGet({ transaction_id: hash }); }, });applications/tari_walletd/web_ui/src/api/hooks/useAccounts.ts (3)
104-156: Fix fee fallback, prefer const, and invalidate balances/transactions using variables in onSettled
- Use ?? for max_fee to avoid overriding 0.
- Use const for request objects.
- Invalidate related queries after transfer: accounts, transactions, balances for the source account (via onSettled variables).
Apply:
return useMutation({ mutationFn: (params: TransferParams) => { - const account = { ComponentAddress: params.account }; - const max_fee = params.max_fee || DEFAULT_MAX_FEE; + const account = { ComponentAddress: params.account }; + const max_fee = params.max_fee ?? DEFAULT_MAX_FEE; if (params.resourceType === "Confidential") { - let transferRequest = { + const transferRequest = { account, amount: params.amount, resource_address: params.resource_address, destination_public_key: params.destination_public_key, max_fee, proof_from_badge_resource: params.badge, input_selection: params.input_selection, output_to_revealed: params.output_to_revealed, dry_run: params.dry_run, }; return accountsConfidentialTransfer(transferRequest); } else if (params.resourceType === "Stealth") { - let transferRequest = { + const transferRequest = { owner_account: account, input_selection: params.input_selection, resource_address: params.resource_address, destination_public_key: params.destination_public_key, max_fee, blinded_output_amount: params.output_to_revealed ? 0 : params.amount, revealed_output_amount: params.output_to_revealed ? params.amount : 0, dry_run: params.dry_run, }; return accountsStealthTransfer(transferRequest); } else { // Fungible and NFTs - let transferRequest = { + const transferRequest = { account, amount: params.amount, resource_address: params.resource_address, destination_public_key: params.destination_public_key, max_fee, proof_from_badge_resource: params.badge, input_selection: params.input_selection, output_to_revealed: params.output_to_revealed, dry_run: params.dry_run, }; return accountsTransfer(transferRequest); } }, - onError: (error: ApiError) => { - error; - }, - onSettled: () => { - queryClient.invalidateQueries({ queryKey: ["accounts"] }); - }, + onError: (_error: ApiError) => {}, + onSettled: (_data, _error, vars) => { + queryClient.invalidateQueries({ queryKey: ["accounts"] }); + queryClient.invalidateQueries({ queryKey: ["transactions"] }); + if (vars?.account) { + queryClient.invalidateQueries({ queryKey: ["accounts_balances_" + vars.account] }); + } + }, });
218-224: Query key must include pagination parametersThe query function depends on offset and limit; include them in the key to avoid cache collisions and stale data. (tanstack.dev)
Apply:
return useQuery({ - queryKey: ["accounts"], + queryKey: ["accounts", offset, limit], queryFn: () => accountsList({ offset, limit }), enabled, });
277-282: Include parameters in validator_fees query keyDifferent inputs should produce distinct cache entries; otherwise you’ll serve wrong data across calls. (tanstack.dev)
Apply:
export const useValidatorFees = (accountOrKeyIndex: AccountOrKeyIndex, shardGroup = null) => { return useQuery({ - queryKey: ["validator_fees"], + queryKey: ["validator_fees", accountOrKeyIndex, shardGroup], queryFn: () => validatorsGetFees({ account_or_key: accountOrKeyIndex, shard_group: shardGroup }), }); };applications/tari_walletd/web_ui/src/App.tsx (2)
139-155: Remove ts-ignore; type GuardedRoute and encode redirect paramImprove typing and avoid constructing an unencoded query param.
-interface GuardedRouteProps { - component: React.ComponentType<any>; - redirect?: string; - isAuthenticated: boolean; - - [key: string]: any; -} - -// @ts-ignore -const GuardedRoute = ({ - component: Component, - redirect = "/", - isAuthenticated = false, - ...rest -}: GuardedRouteProps) => { - return isAuthenticated ? <Component {...rest} /> : <Navigate replace to={"/auth?redirect=" + redirect} />; -}; +interface GuardedRouteProps<P extends Record<string, unknown> = Record<string, unknown>> { + component: React.ComponentType<P>; + redirect?: string; + isAuthenticated: boolean; + [key: string]: unknown; +} +const GuardedRoute = <P extends Record<string, unknown>>({ + component: Component, + redirect = "/", + isAuthenticated = false, + ...rest +}: GuardedRouteProps<P>) => { + return isAuthenticated ? ( + <Component {...(rest as P)} /> + ) : ( + <Navigate replace to={`/auth?redirect=${encodeURIComponent(redirect)}`} /> + ); +};
179-193: IncludeauthMethodsErrorin the effect’s dependency array or explicitly disable the lint ruleThe
useEffectat applications/tari_walletd/web_ui/src/App.tsx lines 179–193 readsauthMethodsErrorbut only lists[authMethod, authMethodsIsError]in its deps. AddauthMethodsErrorto the array or precede the effect with a// eslint-disable-next-line react-hooks/exhaustive-depscomment if this omission is intentional.applications/tari_walletd/web_ui/src/Components/WalletConnectLink/WalletConnectLink.tsx (5)
195-201: Manual “Connect” path never pairs the wallet (broken flow)Typing a link and pressing Connect advances to page 2 without creating/pairing the wallet, leaving proposal undefined.
- const handleConnect = () => { - linkRef.current && setLink(linkRef.current.value); - setPage(page + 1); - }; + const handleConnect = async () => { + if (linkRef.current) { + setLink(linkRef.current.value.trim()); + } + await handleConnectWithLink(); + };
266-268: Fix events array syntax; it currently creates a single malformed stringThis likely breaks WalletConnect event subscriptions.
- events: ['chainChanged", "accountsChanged'], + events: ["chainChanged", "accountsChanged"],
253-265: Include tari_getAccountByAddress in supported methodsexecuteMethod supports it but it’s missing here; keep these in sync to avoid proposal rejection.
methods: [ "tari_getSubstate", "tari_getDefaultAccount", + "tari_getAccountByAddress", "tari_getAccountBalances", "tari_submitTransaction", "tari_getTransactionResult", "tari_getTemplate", "tari_createKey", "tari_viewConfidentialVaultBalance", "tari_createFreeTestCoins", "tari_listSubstates", "tari_getNftsList", ],
157-160: Default branch should throw to fail fastReturning undefined on unsupported methods hides errors and returns a 200 with empty result.
- default: - setError(`Unsupported method ${method}`); + default: + const msg = `Unsupported method ${method}`; + setError(msg); + throw new Error(msg);
63-64: Remove hardcoded WalletConnect projectId fallbackShipping a shared fallback can leak usage to a public ID and bypasses your “feature disabled when not set” guard below.
-const projectId: string = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID || "78f3485d08b9640a087cbcea000e1f8b"; +const projectId: string | undefined = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID;
| "react": "^19.1.1", | ||
| "react-dom": "^19.1.1", |
There was a problem hiding this comment.
Align React types with React 19 (likely TS errors if left at 18.x).
You’re on react/react-dom 19.1.1 but still using @types/react* 18.x, which often causes duplicate/conflicting types and broken refs/JSX typings in TS. Update both to 19.x to match React 19. (npmjs.com, react.dev)
Apply this diff:
- "@types/react": "^18.3.24",
- "@types/react-dom": "^18.3.7",
+ "@types/react": "^19.1.1",
+ "@types/react-dom": "^19.1.1",Also applies to: 46-47
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/package.json around lines 34-35 and also
lines 46-47, the project uses react and react-dom 19.1.1 but still has @types
packages pinned to 18.x causing TypeScript type conflicts; update the @types
packages to match React 19 (bump @types/react and @types/react-dom to the
corresponding 19.x versions, and any other React-related @types (e.g.,
@types/react-test-renderer) to 19.x if present) in package.json and run your
package manager (npm/yarn/pnpm) to install the matching types so JSX/refs
typings align with React 19.
| "zustand": "^5.0.8", | ||
| "zustand-persist": "^0.4.0" |
There was a problem hiding this comment.
💡 Verification agent
❓ Verification inconclusive
Reassess ‘zustand-persist’ with Zustand v5.
Zustand v5 includes behavioral changes in persist and drops older patterns. The third‑party “zustand-persist” package is effectively unmaintained (last release years ago), so compatibility is uncertain. Prefer the official persist from zustand/middleware (or purpose‑built alternatives) to avoid subtle hydration bugs. (github.com, socket.dev, security.snyk.io)
If you’re ready to switch, I can propose a minimal diff replacing “zustand-persist” with persist from zustand/middleware and createJSONStorage. Want me to draft it?
Replace zustand-persist with official persist middleware
In applications/tari_walletd/web_ui/package.json remove "zustand-persist" (v0.4.0, last published 4 years ago) to avoid compatibility issues (npmjs.com) and refactor persistence to use:
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'This leverages the built-in v5 persist middleware and storage helpers (zustand.docs.pmnd.rs). Let me know if you’d like a minimal diff.
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/package.json around lines 40-41, remove the
"zustand-persist": "^0.4.0" dependency and replace persistence usage with the
official zustand v5 middleware: refactor any store files to import persist and
createJSONStorage from 'zustand/middleware' and wrap create with persist (using
createJSONStorage for custom storage when needed), update imports/usages
accordingly, and run npm/yarn install to update lockfile; ensure no lingering
references to "zustand-persist" remain in code or package.json.
| return useMutation({ | ||
| mutationFn: (params: ClaimBurnRequest) => accountsClaimBurn(params), | ||
| onError: (error: ApiError) => { | ||
| error; | ||
| }, | ||
| onSettled: () => { | ||
| queryClient.invalidateQueries(["accounts"]); | ||
| queryClient.invalidateQueries({ queryKey: ["accounts"] }); | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Drop no-op onError and also invalidate transactions after ClaimBurn
- onError just references the variable; make it a no-op param or remove it.
- Claiming burns will also affect the transactions list; invalidate it too.
Apply:
return useMutation({
mutationFn: (params: ClaimBurnRequest) => accountsClaimBurn(params),
- onError: (error: ApiError) => {
- error;
- },
+ onError: (_error: ApiError) => {},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["accounts"] });
+ queryClient.invalidateQueries({ queryKey: ["transactions"] });
},
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return useMutation({ | |
| mutationFn: (params: ClaimBurnRequest) => accountsClaimBurn(params), | |
| onError: (error: ApiError) => { | |
| error; | |
| }, | |
| onSettled: () => { | |
| queryClient.invalidateQueries(["accounts"]); | |
| queryClient.invalidateQueries({ queryKey: ["accounts"] }); | |
| }, | |
| }); | |
| return useMutation({ | |
| mutationFn: (params: ClaimBurnRequest) => accountsClaimBurn(params), | |
| onError: (_error: ApiError) => {}, | |
| onSettled: () => { | |
| queryClient.invalidateQueries({ queryKey: ["accounts"] }); | |
| queryClient.invalidateQueries({ queryKey: ["transactions"] }); | |
| }, | |
| }); |
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/src/api/hooks/useAccounts.ts around lines 55
to 63, the mutation's onError handler is a no-op that only references the error
variable and should be removed or replaced with an explicit no-op param, and the
onSettled invalidation should also include the transactions query because
ClaimBurn affects both accounts and transactions; update onError to either be
removed or written as onError: (_: ApiError) => {}, and change the
invalidateQueries call to invalidate both ["accounts"] and ["transactions"] (or
call invalidateQueries twice) so both caches are refreshed after the mutation.
| return useMutation({ | ||
| mutationFn: async (req: AccountsCreateMutate) => { | ||
| return await accountsCreate({ | ||
| account_name: req.accountName || "", | ||
| is_default: req.isDefault || null, | ||
| key_id: req.keyId || null, | ||
| }); | ||
| }, | ||
| { | ||
| onError: (error: ApiError) => { | ||
| error; | ||
| }, | ||
| onSettled: () => { | ||
| queryClient.invalidateQueries(["accounts"]); | ||
| }, | ||
| onError: (error: ApiError) => { | ||
| error; | ||
| }, | ||
| ); | ||
| onSettled: () => { | ||
| queryClient.invalidateQueries({ queryKey: ["accounts"] }); | ||
| }, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Use nullish coalescing for booleans/numbers; remove no-op onError
Using || will coerce false/0 to null unintentionally. Prefer ?? to preserve explicit false/0. Also, make onError a no-op.
Apply:
return useMutation({
mutationFn: async (req: AccountsCreateMutate) => {
return await accountsCreate({
- account_name: req.accountName || "",
- is_default: req.isDefault || null,
- key_id: req.keyId || null,
+ account_name: req.accountName ?? "",
+ is_default: req.isDefault ?? null,
+ key_id: req.keyId ?? null,
});
},
- onError: (error: ApiError) => {
- error;
- },
+ onError: (_error: ApiError) => {},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["accounts"] });
},
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return useMutation({ | |
| mutationFn: async (req: AccountsCreateMutate) => { | |
| return await accountsCreate({ | |
| account_name: req.accountName || "", | |
| is_default: req.isDefault || null, | |
| key_id: req.keyId || null, | |
| }); | |
| }, | |
| { | |
| onError: (error: ApiError) => { | |
| error; | |
| }, | |
| onSettled: () => { | |
| queryClient.invalidateQueries(["accounts"]); | |
| }, | |
| onError: (error: ApiError) => { | |
| error; | |
| }, | |
| ); | |
| onSettled: () => { | |
| queryClient.invalidateQueries({ queryKey: ["accounts"] }); | |
| }, | |
| }); | |
| }; | |
| return useMutation({ | |
| mutationFn: async (req: AccountsCreateMutate) => { | |
| return await accountsCreate({ | |
| account_name: req.accountName ?? "", | |
| is_default: req.isDefault ?? null, | |
| key_id: req.keyId ?? null, | |
| }); | |
| }, | |
| onError: (_error: ApiError) => {}, | |
| onSettled: () => { | |
| queryClient.invalidateQueries({ queryKey: ["accounts"] }); | |
| }, | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/src/api/hooks/useAccounts.ts around lines
73-88, the mutation currently uses || which coerces false/0 to null and includes
a pointless onError handler; change the ternary defaults to use nullish
coalescing (??) for is_default and key_id so falsy but valid values like false
or 0 are preserved (keep account_name default "" as-is), and remove the no-op
onError block entirely (or omit onError from the useMutation options).
| return useQuery({ | ||
| queryKey: ["transactions", req.status], | ||
| queryFn: () => transactionsGetAll(req), | ||
| onError: (error: ApiError) => { | ||
| error; | ||
| }, | ||
| refetchInterval: 5000, | ||
| keepPreviousData: true, | ||
| placeholderData: (previousData) => previousData, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Key queries by full request, not just status
Prevents cache collisions across pages/filters. Keeping placeholderData is good to emulate keepPreviousData.
return useQuery({
- queryKey: ["transactions", req.status],
+ queryKey: ["transactions", req],
queryFn: () => transactionsGetAll(req),
refetchInterval: 5000,
placeholderData: (previousData) => previousData,
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return useQuery({ | |
| queryKey: ["transactions", req.status], | |
| queryFn: () => transactionsGetAll(req), | |
| onError: (error: ApiError) => { | |
| error; | |
| }, | |
| refetchInterval: 5000, | |
| keepPreviousData: true, | |
| placeholderData: (previousData) => previousData, | |
| }); | |
| }; | |
| return useQuery({ | |
| queryKey: ["transactions", req], | |
| queryFn: () => transactionsGetAll(req), | |
| refetchInterval: 5000, | |
| placeholderData: (previousData) => previousData, | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/src/api/hooks/useTransactions.tsx around
lines 47 to 53, the queryKey currently only uses req.status which can cause
cache collisions across different pages/filters; change the queryKey to uniquely
represent the full request (e.g. include all relevant fields or a stable
serialization like JSON.stringify(req) or a tuple of req.page, req.status,
req.filters, etc.) so each distinct request has its own cache entry, and keep
the existing placeholderData to emulate keepPreviousData behavior.
| import Loading from "@components/Loading"; | ||
| import Error from "@components/Error"; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Rename imported Error to avoid shadowing the global Error
Biome flags this as an error. Rename the import and usage to prevent confusion and lint failures.
-import Loading from "@components/Loading";
-import Error from "@components/Error";
+import Loading from "@components/Loading";
+import ErrorView from "@components/Error";
@@
- if (isError) {
- return <Error message={errorMessage} />;
- }
+ if (isError) {
+ return <ErrorView message={errorMessage} />;
+ }Also applies to: 37-39
🧰 Tools
🪛 Biome (2.1.2)
[error] 24-24: Do not shadow the global "Error" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/src/Components/FetchStatusCheck.tsx around
lines 23-24 (and similarly at lines 37-39), the import named Error shadows the
global Error and triggers a lint/biome error; rename the import to a
non-conflicting identifier (e.g., ErrorComponent or FetchError), update all
usages in this file to that new name (including JSX and any references), and
ensure the import path remains the same so functionality is unchanged.
| import Loading from "../../Components/Loading"; | ||
| import Error from "../../Components/Error"; | ||
| import Loading from "@components/Loading"; | ||
| import Error from "@components/Error"; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Rename imported Error to avoid shadowing the global Error.
Biome flags this (noShadowRestrictedNames). Rename the import and make message extraction safe.
-import Error from "@components/Error";
+import ErrorView from "@components/Error";
@@
- return <Error message={error.message} />;
+ return <ErrorView message={error instanceof Error ? error.message : String(error)} />;Also applies to: 97-99
🧰 Tools
🪛 Biome (2.1.2)
[error] 44-44: Do not shadow the global "Error" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
🤖 Prompt for AI Agents
In
applications/tari_walletd/web_ui/src/routes/Transactions/TransactionDetails.tsx
around lines 44 and 97-99, the import "Error" shadows the global Error and
message extraction is unsafe; rename the import (e.g., ErrorComponent or
ErrorAlert) and update all usages accordingly, and when extracting the message
from an error value use a safe expression or type guard (for example use
optional chaining and fallback like error?.message ?? String(error) or check
typeof) before passing to the component so no runtime exceptions occur.
| import AddIcon from "@mui/icons-material/Add"; | ||
| import Fade from "@mui/material/Fade"; | ||
| import { Form } from "react-router-dom"; | ||
| import Button from "@mui/material/Button/Button"; |
There was a problem hiding this comment.
Fix MUI Button import path.
@mui/material/Button/Button is incorrect and brittle. Use the public entrypoint.
-import Button from "@mui/material/Button/Button";
+import Button from "@mui/material/Button";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import Button from "@mui/material/Button/Button"; | |
| import Button from "@mui/material/Button"; |
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/src/routes/Wallet/Components/Keys.tsx around
line 35, the import uses the internal path "@mui/material/Button/Button" which
is brittle and incorrect; replace it with the public entrypoint
"@mui/material/Button" to import Button from the library's supported API
surface.
| import { create } from "zustand"; | ||
| import { persist } from "zustand/middleware"; | ||
| import { AUTH_TOKEN_FOR_NONE_AUTH } from "../routes/Auth/Auth"; | ||
| import { AUTH_TOKEN_FOR_NONE_AUTH } from "@routes/Auth/Auth"; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Avoid store → route dependency; extract shared constant to break potential cycles.
Importing from @routes/Auth/Auth inside the store can create circular deps if that route imports the store. Move AUTH_TOKEN_FOR_NONE_AUTH to a shared module (e.g., @constants/auth) or define/export it here, and have the route import from there.
Check for a cycle and single source of truth:
Proposed change after extracting the constant:
-import { AUTH_TOKEN_FOR_NONE_AUTH } from "@routes/Auth/Auth";
+import { AUTH_TOKEN_FOR_NONE_AUTH } from "@constants/auth";🏁 Script executed:
#!/bin/bash
rg -n 'AUTH_TOKEN_FOR_NONE_AUTH'
rg -n 'from "@store/authStore"' applications/tari_walletd/web_ui/src/routes/Auth/Auth.tsxLength of output: 1621
Extract AUTH_TOKEN_FOR_NONE_AUTH to a shared module
A circular dependency exists: authStore.ts → Auth.tsx → authStore.ts. Move AUTH_TOKEN_FOR_NONE_AUTH into a new src/constants/auth.ts, then update all imports in:
applications/tari_walletd/web_ui/src/routes/Auth/Auth.tsxapplications/tari_walletd/web_ui/src/store/authStore.tsapplications/tari_walletd/web_ui/src/App.tsxapplications/tari_walletd/web_ui/src/utils/json_rpc.ts
- import { AUTH_TOKEN_FOR_NONE_AUTH } from "@routes/Auth/Auth";
+ import { AUTH_TOKEN_FOR_NONE_AUTH } from "@constants/auth";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { AUTH_TOKEN_FOR_NONE_AUTH } from "@routes/Auth/Auth"; | |
| import { AUTH_TOKEN_FOR_NONE_AUTH } from "@constants/auth"; |
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/src/store/authStore.ts around line 6, the
import of AUTH_TOKEN_FOR_NONE_AUTH from @routes/Auth/Auth creates a circular
dependency via Auth.tsx; extract the constant into a new file
applications/tari_walletd/web_ui/src/constants/auth.ts exporting
AUTH_TOKEN_FOR_NONE_AUTH, then replace the current import in this file to import
from src/constants/auth.ts and update the same import in
applications/tari_walletd/web_ui/src/routes/Auth/Auth.tsx,
applications/tari_walletd/web_ui/src/App.tsx, and
applications/tari_walletd/web_ui/src/utils/json_rpc.ts so all four files import
the constant from the new shared module, run a typecheck/build to ensure no
remaining circular imports.
Test Results (CI)419 tests 413 ✅ 1h 23m 4s ⏱️ For more details on these failures, see this check. Results for commit 764c95f. |
Description
Update wallet ui dependencies
Updated React Query syntax throughout the site, as there were breaking changes going from v4 to v5
Added alias imports
Motivation and Context
How Has This Been Tested?
Manually
What process can a PR reviewer use to test or verify this change?
Breaking Changes
x
Summary by CodeRabbit