Skip to content

Commit

Permalink
Use AddressBook record when exists on xsigner owners
Browse files Browse the repository at this point in the history
  • Loading branch information
henrypalacios committed Jan 29, 2024
1 parent f915048 commit c3140ec
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 43 deletions.
78 changes: 56 additions & 22 deletions src/components/Settings/ManageOwners/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BookIcon from "@mui/icons-material/Book";
import CloseIcon from "@mui/icons-material/Close";
import CreateOutlinedIcon from "@mui/icons-material/CreateOutlined";
import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined";
Expand All @@ -8,36 +9,45 @@ import {
Divider,
Modal,
TextField,
Tooltip,
Typography,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
import router from "next/router";
import * as React from "react";
import { ArrayOneOrMore } from "useink/dist/core";

import { useAppNotificationContext } from "@/components/AppToastNotification/AppNotificationsContext";
import { LoadingButton } from "@/components/common/LoadingButton";
import { LoadingSkeleton } from "@/components/common/LoadingSkeleton";
import NetworkBadge from "@/components/NetworkBadge";
import { AccountSigner } from "@/components/StepperSignersAccount/AccountSigner";
import { getChain } from "@/config/chain";
import { Owner, SignatoriesAccount } from "@/domain/SignatoriesAccount";
import { ROUTES } from "@/config/routes";
import {
CrossOwnerWithAddressBook,
Owner,
SignatoriesAccount,
} from "@/domain/SignatoriesAccount";
import { useSetXsignerSelected } from "@/hooks/xsignerSelected/useSetXsignerSelected";

interface Props {
selectedMultisig?: SignatoriesAccount<CrossOwnerWithAddressBook>;
handleAddOwner: () => void;
handleDeleteOwner: (owner: Owner) => void;
isDeletedLoading?: boolean;
}

export default function ManageOwners({
owners,
selectedMultisig,
handleAddOwner,
handleDeleteOwner,
isDeletedLoading = false,
}: {
owners?: ArrayOneOrMore<Owner>;
selectedMultisig?: SignatoriesAccount;
handleAddOwner: () => void;
handleDeleteOwner: (owner: Owner) => void;
isDeletedLoading?: boolean;
}) {
}: Props) {
const theme = useTheme();
const [open, setOpen] = React.useState({ edit: false, delete: false });
const { owners } = selectedMultisig || {};
const [ownersList, setOwnersList] = React.useState<
ArrayOneOrMore<Owner> | undefined
SignatoriesAccount<CrossOwnerWithAddressBook>["owners"] | undefined
>(owners);
const [currentOwner, setCurrentOwner] = React.useState<Owner>({} as Owner);
const { setXsigner } = useSetXsignerSelected();
Expand Down Expand Up @@ -77,10 +87,12 @@ export default function ManageOwners({

await setXsigner({
...selectedMultisig,
owners: newOwners as ArrayOneOrMore<Owner>,
owners: newOwners as SignatoriesAccount["owners"],
});

setOwnersList(newOwners as ArrayOneOrMore<Owner>);
setOwnersList(
newOwners as SignatoriesAccount<CrossOwnerWithAddressBook>["owners"]
);
handleClose();
addNotification({
message: "Owner name updated successfully",
Expand All @@ -92,7 +104,11 @@ export default function ManageOwners({
<Box display="flex">
<Modal
open={open.edit}
sx={{ display: "flex", alignItems: "center", justifyContent: "center" }}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Box
display="flex"
Expand Down Expand Up @@ -180,7 +196,11 @@ export default function ManageOwners({
</Modal>
<Modal
open={open.delete}
sx={{ display: "flex", alignItems: "center", justifyContent: "center" }}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Box
display="flex"
Expand Down Expand Up @@ -254,6 +274,9 @@ export default function ManageOwners({
locally and will never be shared with us or any third parties.
</Typography>
<Box mt={2}>
{ownersList === undefined && (
<LoadingSkeleton count={5} width={"100%"} />
)}
{ownersList?.map((owner) => (
<Box
display="flex"
Expand All @@ -271,10 +294,19 @@ export default function ManageOwners({
truncateAmount={16}
/>
<Box display="flex" gap={0.25}>
<CreateOutlinedIcon
sx={{ cursor: "pointer" }}
onClick={() => handleEdit(owner)}
/>
{owner.inAddressBook ? (
<Tooltip title="Edit on address book">
<BookIcon
sx={{ cursor: "pointer" }}
onClick={() => router.replace(ROUTES.AddressBook)}
/>
</Tooltip>
) : (
<CreateOutlinedIcon
sx={{ cursor: "pointer" }}
onClick={() => handleEdit(owner)}
/>
)}
{isDeletedLoading &&
currentOwner.address === owner.address ? (
<CircularProgress color="secondary" size={20} />
Expand Down Expand Up @@ -304,18 +336,20 @@ export default function ManageOwners({
</Box>
))}
</Box>
<Button
<LoadingButton
variant="text"
sx={{
justifyContent: "flex-start",
width: "150px",
fontSize: 14,
marginTop: "1rem",
}}
onClick={handleAddOwner}
{...(owners === undefined
? { isLoading: true }
: { onClick: handleAddOwner })}
>
+ Add new owner
</Button>
</LoadingButton>
</Box>
</Box>
);
Expand Down
12 changes: 10 additions & 2 deletions src/domain/SignatoriesAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ export type Owner = {
name: string;
};

export interface SignatoriesAccount {
export type CrossOwnerWithAddressBook = {
address: string;
name: string;
inAddressBook: boolean;
};

export interface SignatoriesAccount<
T extends Owner | CrossOwnerWithAddressBook = Owner
> {
address: string;
name: string;
networkId: Chain["id"];
owners: ArrayOneOrMore<Owner>;
owners: ArrayOneOrMore<T>;
threshold: number;
}
22 changes: 13 additions & 9 deletions src/hooks/addressBook/useAddAddressBook.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { ChangeEvent, useState } from "react";

import { DEFAULT_CHAIN } from "@/config/chain";
import { useLocalDbContext } from "@/context/uselocalDbContext";
import { usePolkadotContext } from "@/context/usePolkadotContext";
import { AddressBook } from "@/domain/AddressBooks";
import { AddressBookEvents } from "@/domain/events/AddressBookEvents";
import { ChainId } from "@/services/useink/types";
import { isValidAddress } from "@/utils/blockchain";
import {
areAddressesEqual,
getHexAddress,
isValidAddress,
} from "@/utils/blockchain";

const VALIDATIONS = {
isInputEmpty: (input: string) => input.trim() === "",
isValidAddress: isValidAddress,
existAddress: (accountAddress: string, data: AddressBook[]): boolean =>
data.some((element) => element.address === accountAddress),
data.some((addressBookItem) =>
areAddressesEqual(addressBookItem.address, accountAddress)
),
};

const ERROR_MESSAGES = {
Expand All @@ -20,8 +27,6 @@ const ERROR_MESSAGES = {
INVALID_ADDRESS: "This is not a valid address",
};

const DEFAULT_CHAIN = "rococo-contracts-testnet";

const initialErrorState = {
name: {
isError: false,
Expand Down Expand Up @@ -109,19 +114,19 @@ export const useAddAddressBook = () => {
return;
}

addAddress(formInput, network);
_addAddress(formInput, network);
setError(initialErrorState);
};

const addAddress = (
const _addAddress = (
addressBook: AddressBook | undefined,
network: ChainId | undefined
) => {
if (!addressBook) return;
const newRegister: AddressBook = {
networkId: network as ChainId,
name: addressBook?.name,
address: addressBook?.address,
name: addressBook.name,
address: getHexAddress(addressBook.address),
};

addressBookRepository.addAddress(newRegister);
Expand All @@ -136,7 +141,6 @@ export const useAddAddressBook = () => {
};

return {
addAddress,
handleChangeInput,
handleClick,
resetState,
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/addressBook/useListAddressBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { usePolkadotContext } from "@/context/usePolkadotContext";
import { AddressBook } from "@/domain/AddressBooks";
import { AddressBookEvents } from "@/domain/events/AddressBookEvents";
import { useEventListenerCallback } from "@/hooks/useEventListenerCallback";
import { formatAddressForNetwork } from "@/utils/blockchain";
import { customReportError } from "@/utils/error";

export function useListAddressBook(networkId: string | undefined) {
Expand All @@ -23,6 +24,7 @@ export function useListAddressBook(networkId: string | undefined) {
if (!result) return [];
const newData = result.map((element) => ({
...element,
address: formatAddressForNetwork(element.address, network),
isEditable: false,
}));
setData(newData);
Expand Down
37 changes: 32 additions & 5 deletions src/hooks/xsignerSelected/useGetXsignerSelected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import { useLocalDbContext } from "@/context/uselocalDbContext";
import { usePolkadotContext } from "@/context/usePolkadotContext";
import { XsignerAccountEvents } from "@/domain/events/XsignerAccountEvents";
import { SignatoriesAccount } from "@/domain/SignatoriesAccount";
import { createArrayOneOrMore } from "@/domain/utilityTsTypes";
import { getHexAddress } from "@/utils/blockchain";

import { useEventListenerCallback } from "../useEventListenerCallback";

export function useGetXsignerSelected() {
const { xsignerSelectedRepository, signatoriesAccountRepository } =
useLocalDbContext();
interface UseGetXsignerSelectedReturn {
xSignerSelected: SignatoriesAccount | null | undefined;
}

export function useGetXsignerSelected(): UseGetXsignerSelectedReturn {
const {
xsignerSelectedRepository,
signatoriesAccountRepository,
addressBookRepository,
} = useLocalDbContext();
const { network } = usePolkadotContext();
const [xSignerSelected, setXsignerSelected] = useState<
SignatoriesAccount | null | undefined
Expand All @@ -26,11 +35,29 @@ export function useGetXsignerSelected() {
));

if (account) {
setXsignerSelected(account);
setXsignerSelected({
...account,
owners: createArrayOneOrMore(
account.owners.map((owner) => {
const inAddressBook = addressBookRepository.getItemByAddress(
getHexAddress(owner.address)
);

if (!inAddressBook) return { ...owner, inAddressBook: false };

return { ...owner, name: inAddressBook.name, inAddressBook: true };
})
),
});
} else {
setXsignerSelected(null);
}
}, [network, signatoriesAccountRepository, xsignerSelectedRepository]);
}, [
addressBookRepository,
network,
signatoriesAccountRepository,
xsignerSelectedRepository,
]);

useEffect(() => {
getAccount();
Expand Down
1 change: 0 additions & 1 deletion src/pages/app/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ export default function SettingsPage() {
<Box mt={2} bgcolor={theme.palette.grey.A100} p={3}>
<ManageOwners
selectedMultisig={selectedMultisig ?? undefined}
owners={selectedMultisig?.owners}
handleAddOwner={handleAddOwner}
isDeletedLoading={isLoading}
handleDeleteOwner={handleDeleteOwner}
Expand Down
1 change: 1 addition & 0 deletions src/services/localDB/AddressBookRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class AddressBookRepository implements IAddressBookRepository {
getItemByAddress(accountAddress: string): AddressBook | undefined {
const data = getData(this.storageKey);
if (!data) return undefined;

const filterElement = Object.values(data).find(
(element) => element.address === accountAddress
);
Expand Down
33 changes: 29 additions & 4 deletions src/utils/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,40 @@ const networkIdToPrefix: Record<string, number> = {
"rococo-contracts-testnet": 42,
};

export const areAddressesEqual = (address1: string, address2: string) => {
const rawAddress1 = ss58.decode(address1).bytes;
const rawAddress2 = ss58.decode(address2).bytes;
export const getHexAddress = (address: string) => ss58.decode(address).bytes;

/**
* Checks if two Polkadot/Kusama addresses are equal.
* This is done by decoding the addresses to their raw byte representation
* and comparing these byte arrays.
*
* @param {string} address1 - The first address to compare.
* @param {string} address2 - The second address to compare.
* @returns {boolean} - Returns true if the addresses are equal, otherwise false.
*/
export const areAddressesEqual = (
address1: string,
address2: string
): boolean => {
const rawAddress1 = getHexAddress(address1);
const rawAddress2 = getHexAddress(address2);

return rawAddress1 === rawAddress2;
};

/**
* Formats a Polkadot/Kusama address for a specific network.
* This is achieved by decoding the address to its raw byte format,
* then re-encoding it with the prefix corresponding to the desired network.
* Each network in the Polkadot ecosystem has a specific prefix which denotes the network.
*
* @param {string} address - The address to format.
* @param {string} networkId - The network identifier for which to format the address.
* @returns {string} - The formatted address for the specified network. If the networkId is unknown,
* it defaults to using a prefix of 42, commonly used for generic or development purposes.
*/
export const formatAddressForNetwork = (address: string, networkId: string) => {
const rawAddress = ss58.decode(address).bytes;
const rawAddress = isHex(address) ? address : ss58.decode(address).bytes;

const prefix =
networkIdToPrefix[networkId] !== undefined
Expand Down

0 comments on commit c3140ec

Please sign in to comment.