Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions applications/tari_walletd/web_ui/src/components/AccountName.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2025. The Tari Project
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
// disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
// products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import React, { useState } from "react";
import { Box, TextField, IconButton } from "@mui/material";
import { IoCheckmark, IoClose } from "react-icons/io5";
import { ComponentAddress } from "@tari-project/typescript-bindings";
import { useAccountsRename } from "../services/api/hooks/useAccounts";
import { LuPencilLine } from "react-icons/lu";
import { useTheme } from "@mui/material/styles";

export interface AccountNameProps {
accountAddress: ComponentAddress;
currentName?: string | null;
showRenameButton?: boolean;
onRenameSuccess?: (newName: string) => void;
onRenameError?: (error: any) => void;
}

const AccountName: React.FC<AccountNameProps> = ({
accountAddress,
currentName,
showRenameButton = true,
onRenameSuccess,
onRenameError,
}) => {
const [isEditingName, setIsEditingName] = useState(false);
const [newName, setNewName] = useState("");

const renameAccountMutation = useAccountsRename();

const theme = useTheme();

const handleStartEdit = () => {
setIsEditingName(true);
setNewName(currentName || "");
};

const handleCancelEdit = () => {
setIsEditingName(false);
setNewName("");
};

const handleSaveRename = () => {
if (newName.trim() && newName !== currentName) {
renameAccountMutation.mutate(
{
account: accountAddress,
newName: newName.trim(),
},
{
onSuccess: () => {
const trimmedName = newName.trim();
setIsEditingName(false);
setNewName("");
onRenameSuccess?.(trimmedName);
},
onError: (error: any) => {
console.error("Error renaming account:", error);
onRenameError?.(error);
},
},
);
} else {
handleCancelEdit();
}
};

const handleKeyPress = (event: React.KeyboardEvent) => {
if (event.key === "Enter") {
handleSaveRename();
} else if (event.key === "Escape") {
handleCancelEdit();
}
};

return (
<Box display="flex" alignItems="center" gap={1}>
{isEditingName ? (
<TextField
value={newName}
onChange={(e) => setNewName(e.target.value)}
onKeyDown={handleKeyPress}
size="small"
autoFocus
disabled={renameAccountMutation.isPending}
placeholder="Account name"
/>
) : (
<span>{currentName || "<No Name>"}</span>
)}
{showRenameButton && (
<>
{isEditingName ? (
<Box display="flex" gap={0.5}>
<IconButton
size="small"
onClick={handleSaveRename}
disabled={renameAccountMutation.isPending}
title="Save"
>
<IoCheckmark />
</IconButton>
<IconButton
size="small"
onClick={handleCancelEdit}
disabled={renameAccountMutation.isPending}
title="Cancel"
>
<IoClose />
</IconButton>
</Box>
) : (
<IconButton size="small" onClick={handleStartEdit} title="Rename account">
<LuPencilLine
style={{
color: theme.palette.primary.main,
}}
/>
</IconButton>
)}
</>
)}
</Box>
);
};

export default AccountName;
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,20 @@
import { useState } from "react";
import PageHeading from "@components/PageHeading";
import Grid from "@mui/material/Grid";
import { StyledPaper } from "@components/StyledComponents";
import { StyledPaper, InnerHeading } from "@components/StyledComponents";
import TableContainer from "@mui/material/TableContainer";
import Table from "@mui/material/Table";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import TableCell from "@mui/material/TableCell";
import TableBody from "@mui/material/TableBody";
import { useAccountsGetBalances, useAccountsGet } from "@api/hooks/useAccounts";
import { useAccountsGetBalances, useAccountsGet } from "../../services/api/hooks/useAccounts";
import AccountName from "@components/AccountName";
import { useNFTsList } from "@api/hooks/useNfts";
import { ApiError } from "@api/helpers/types";
import { DataTableCell } from "@components/StyledComponents";
import FetchStatusCheck from "@components/FetchStatusCheck";
import {
BalanceEntry,
decodeOotleAddress,
decodeOotleAddressOrNull,
substateIdToString,
} from "@tari-project/typescript-bindings";
import { BalanceEntry, decodeOotleAddressOrNull, substateIdToString } from "@tari-project/typescript-bindings";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Likely wrong imports from types-only package.

substateIdToString is a local util, not from @tari-project/typescript-bindings. Also verify decodeOotleAddressOrNull is coming from a runtime module.

Based on learnings
Suggested fix for substateIdToString:

-import { BalanceEntry, decodeOotleAddressOrNull, substateIdToString } from "@tari-project/typescript-bindings";
+import type { BalanceEntry } from "@tari-project/typescript-bindings";
+import { substateIdToString } from "@utils/helpers";
+import { decodeOotleAddressOrNull } from "@utils/helpers"; // update to the actual runtime module that provides this function

🏁 Script executed:

#!/bin/bash
# Confirm where substateIdToString and decodeOotleAddressOrNull are defined in the repo
rg -nP -C2 '\bexport\s+(function|const)\s+substateIdToString\b'
rg -nP -C2 '\bexport\s+(function|const)\s+decodeOotleAddressOrNull\b'
# Locate existing imports of these to see the correct module
rg -nP -C2 'substateIdToString.*from'
rg -nP -C2 'decodeOotleAddressOrNull.*from'

Length of output: 18528


🏁 Script executed:

#!/bin/bash
echo "Inspecting bindings entrypoint for decodeOotleAddressOrNull exports"
rg -nP 'decodeOotleAddressOrNull' -C1 bindings/src/index.ts || echo "decodeOotleAddressOrNull not found in index.ts"
echo
echo "Checking for re-export of ootleAddress module"
rg -nP 'export.*helpers/ootleAddress' -C1 bindings/src/index.ts || echo "No ootleAddress re-export in index.ts"

Length of output: 291


Correct imports in AccountDetails.tsx

-import { BalanceEntry, decodeOotleAddressOrNull, substateIdToString } from "@tari-project/typescript-bindings";
+import type { BalanceEntry } from "@tari-project/typescript-bindings";
+import { decodeOotleAddressOrNull } from "@tari-project/typescript-bindings";
+import { substateIdToString } from "@utils/helpers";
📝 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.

Suggested change
import { BalanceEntry, decodeOotleAddressOrNull, substateIdToString } from "@tari-project/typescript-bindings";
import type { BalanceEntry } from "@tari-project/typescript-bindings";
import { decodeOotleAddressOrNull } from "@tari-project/typescript-bindings";
import { substateIdToString } from "@utils/helpers";
🤖 Prompt for AI Agents
In applications/tari_walletd/web_ui/src/routes/AccountDetails/AccountDetails.tsx
around line 39, the import line currently references symbols that are not
correctly imported from the package; update this import to use the actual
exported identifiers used in the file: remove or correct the non-exported
decodeOotleAddressOrNull (either import it from its proper module or drop it)
and ensure BalanceEntry and substateIdToString are imported exactly as exported
by @tari-project/typescript-bindings; then run the TypeScript compiler to
confirm there are no unresolved import errors and adjust the import path/names
accordingly.

import NftList from "@routes/AssetVault/NFTs/NFTList";
import CopyAddress from "@components/CopyAddress";
import { Form, useParams } from "react-router-dom";
Expand All @@ -49,6 +45,7 @@ import Loading from "@components/Loading";
import { IoAdd } from "react-icons/io5";
import { Box, Fade, TextField, Button } from "@mui/material";
import { handleChangePage, handleChangeRowsPerPage } from "@utils/helpers";
import { formatCurrency } from "@utils/helpers";

function BalanceRow(props: BalanceEntry) {
return (
Expand All @@ -57,8 +54,8 @@ function BalanceRow(props: BalanceEntry) {
<CopyAddress address={props.resource_address} display={props.token_symbol || props.resource_address} />
</DataTableCell>
<DataTableCell>{props.resource_type}</DataTableCell>
<DataTableCell>{props.balance}</DataTableCell>
<DataTableCell>{props.confidential_balance}</DataTableCell>
<DataTableCell>{formatCurrency(props.balance)}</DataTableCell>
<DataTableCell>{formatCurrency(props.confidential_balance)}</DataTableCell>
</TableRow>
);
}
Expand Down Expand Up @@ -171,7 +168,9 @@ function AccountDetailsLayout() {
</TableHead>
<TableBody>
<TableRow>
<DataTableCell>{accountsData?.account?.name || "<No Name>"}</DataTableCell>
<DataTableCell>
<AccountName accountAddress={accountAddr!} currentName={accountsData?.account?.name} />
</DataTableCell>
<DataTableCell>
<CopyAddress address={substateIdToString(accountsData?.account.component_address)} />
</DataTableCell>
Expand All @@ -195,7 +194,7 @@ function AccountDetailsLayout() {
</Grid>
<Grid item xs={12} md={12} lg={12}>
<StyledPaper>
Balances
<InnerHeading>Balances</InnerHeading>
<FetchStatusCheck
isError={balancesIsError}
errorMessage={balancesError?.message || "Error fetching data"}
Expand All @@ -219,7 +218,7 @@ function AccountDetailsLayout() {
</Grid>
<Grid item xs={12} md={12} lg={12}>
<StyledPaper>
Account NFTs
<InnerHeading>Account NFTs</InnerHeading>
<NftList
nftsListIsError={nftsListIsError}
nftsListIsFetching={nftsListIsFetching}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import Box from "@mui/material/Box";
import { GridHeadCell, GridDataCell, DataTableCell } from "@components/StyledComponents";
import { styled } from "@mui/material/styles";
import { DataTableCell } from "@components/StyledComponents";
import useAccountStore from "@store/accountStore";
import CopyAddress from "@components/CopyAddress";
import { substateIdToString } from "@tari-project/typescript-bindings";
Expand All @@ -32,13 +30,19 @@ import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import AccountName from "@/components/AccountName";

function AccountDetails() {
const { account, address } = useAccountStore();
const { account, address, setAccount } = useAccountStore();

if (!account) {
return <>Loading...</>;
}

const handleRenameSuccess = (newName: string) => {
setAccount({ ...account, name: newName });
};

return (
<TableContainer>
<Table>
Expand All @@ -51,7 +55,13 @@ function AccountDetails() {
</TableHead>
<TableBody>
<TableRow>
<DataTableCell>{account.name}</DataTableCell>
<DataTableCell>
<AccountName
accountAddress={substateIdToString(account.component_address)}
currentName={account?.name}
onRenameSuccess={handleRenameSuccess}
/>
</DataTableCell>
<DataTableCell>
<CopyAddress address={substateIdToString(account.component_address)} />
</DataTableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
accountsGetBalances,
accountsGetDefault,
accountsList,
accountsRename,
accountsStealthTransfer,
accountsTransfer,
mintFaucetNfts,
Expand Down Expand Up @@ -89,6 +90,30 @@ export const useAccountsCreate = () => {
});
};

export type AccountsRenameMutate = {
account: ComponentAddress;
newName: string;
};

export const useAccountsRename = () => {
return useMutation({
mutationFn: async (req: AccountsRenameMutate) => {
return await accountsRename({
account: { ComponentAddress: req.account },
new_name: req.newName,
});
},
onError: (error: ApiError) => {
error;
},
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({ queryKey: ["accounts"] });
queryClient.invalidateQueries({ queryKey: [`accounts_get_${variables.account}`] });
queryClient.refetchQueries({ queryKey: ["accounts"] });
},
});
};

export interface TransferParams {
account: ComponentAddress;
amount: number;
Expand Down
33 changes: 31 additions & 2 deletions applications/tari_walletd/web_ui/src/utils/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ export function bigintToDecimalString(int: bigint | Amount, decimalPlaces: numbe
return `${wholeValues}.${padding}${fractionalValues}`;
}

export const formatCurrency = (amount: number | bigint): string => {
export const formatCurrency = (amount: number | bigint | Amount): string => {
const { currencySymbol } = useCurrencyStore.getState();

if (typeof amount === "bigint") {
Expand All @@ -272,8 +272,37 @@ export const formatCurrency = (amount: number | bigint): string => {
minimumFractionDigits: 0,
maximumFractionDigits: CURRENCY.DECIMALS,
})} ${currencySymbol}`;
} else if (typeof amount === "string") {
// Handle Amount type
try {
const numericAmount = BigInt(amount);
const divisor = BigInt(CURRENCY.DIVISOR);
const integerPart = numericAmount / divisor;
const remainder = numericAmount % divisor;

const fractionalPart = remainder.toString().padStart(CURRENCY.DECIMALS, "0");

return `${Number(integerPart).toLocaleString("en-US")}.${fractionalPart} ${currencySymbol}`;
} catch (error) {
console.error("Failed to parse Amount:", amount, error);
return `0 ${currencySymbol}`;
}
} else {
return `0 ${currencySymbol}`;
// Handle any other type (object, etc.)
try {
const stringValue = String(amount);
const numericAmount = BigInt(stringValue);
const divisor = BigInt(CURRENCY.DIVISOR);
const integerPart = numericAmount / divisor;
const remainder = numericAmount % divisor;

const fractionalPart = remainder.toString().padStart(CURRENCY.DECIMALS, "0");

return `${Number(integerPart).toLocaleString("en-US")}.${fractionalPart} ${currencySymbol}`;
} catch (error) {
console.error("Failed to parse Amount:", amount, error);
return `0 ${currencySymbol}`;
}
}
};

Expand Down
5 changes: 4 additions & 1 deletion applications/tari_walletd/web_ui/src/utils/json_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import type {
AccountsCreateFreeTestCoinsResponse,
AccountsCreateRequest,
AccountsCreateResponse,
AccountsRenameRequest,
AccountsRenameResponse,
AccountSetDefaultRequest,
AccountSetDefaultResponse,
AccountsGetBalancesRequest,
Expand Down Expand Up @@ -271,7 +273,8 @@ export const accountsClaimBurn = (request: ClaimBurnRequest): Promise<ClaimBurnR
client().then((c) => c.accountsClaimBurn(request));
export const accountsCreate = (request: AccountsCreateRequest): Promise<AccountsCreateResponse> =>
client().then((c) => c.accountsCreate(request));

export const accountsRename = (request: AccountsRenameRequest): Promise<AccountsRenameResponse> =>
client().then((c) => c.accountsRename(request));
export const accountsList = (request: AccountsListRequest): Promise<AccountsListResponse> =>
client().then((c) => c.accountsList(request));
export const accountsGetBalances = (request: AccountsGetBalancesRequest): Promise<AccountsGetBalancesResponse> =>
Expand Down
Loading