diff --git a/apps/multisig/src/components/AddressInput/AccountDetails.tsx b/apps/multisig/src/components/AddressInput/AccountDetails.tsx index 287d7355..09212876 100644 --- a/apps/multisig/src/components/AddressInput/AccountDetails.tsx +++ b/apps/multisig/src/components/AddressInput/AccountDetails.tsx @@ -19,6 +19,7 @@ type Props = { hideAddress?: boolean identiconSize?: number breakLine?: boolean + isNameLoading?: boolean } export const AccountDetails: React.FC = ({ @@ -33,6 +34,7 @@ export const AccountDetails: React.FC = ({ breakLine, hideIdenticon = false, hideAddress = false, + isNameLoading = false, }) => { const { copy, copied } = useCopied() @@ -54,6 +56,7 @@ export const AccountDetails: React.FC = ({ nameOrAddressOnly={nameOrAddressOnly} breakLine={breakLine} hideAddress={hideAddress} + isNameLoading={isNameLoading} /> {!disableCopy && (
= ({ address, name, chain, nameOrAddressOnly, breakLine, hideAddress }) => { + isNameLoading?: boolean +}> = ({ address, name, chain, nameOrAddressOnly, breakLine, hideAddress, isNameLoading = false }) => { const { resolve } = useAzeroID() const [azeroId, setAzeroId] = useState() const onchainIdentity = useOnchainIdentity(address, chain) @@ -55,12 +57,26 @@ export const NameAndAddress: React.FC<{ return null }, [address, azeroId, chain, name, nameOrAddressOnly, onchainIdentityUi]) - if (!secondaryText) + if (!secondaryText) { return ( -

- {primaryText} -

+
+ {isNameLoading && !name ? ( + <> + + {!nameOrAddressOnly && ( +

+ {primaryText} +

+ )} + + ) : ( +

+ {primaryText} +

+ )} +
) + } return (
void; truncate?: boolean }) => { +const MemberRow = (props: { + member: AugmentedAccount + chain: Chain + onDelete?: () => void + truncate?: boolean + isNameLoading?: boolean +}) => { return (
{props.member.you ? (You) : null}
diff --git a/apps/multisig/src/components/ScanVaults/VaultCard.tsx b/apps/multisig/src/components/ScanVaults/VaultCard.tsx index 14ec2dfe..9139ad0c 100644 --- a/apps/multisig/src/components/ScanVaults/VaultCard.tsx +++ b/apps/multisig/src/components/ScanVaults/VaultCard.tsx @@ -21,7 +21,8 @@ const VaultCard: React.FC<{ vault: ScannedVault; onAdded?: () => void }> = ({ on const { toast } = useToast() const setImportedTeams = useSetRecoilState(importedTeamsState) const navigate = useNavigate() - const { contactByAddress } = useKnownAddresses() + const signerAddresses = vault.multisig.signers.map(s => s.toSs58()) + const { contactByAddress } = useKnownAddresses({ addresses: showMultisig ? signerAddresses : [] }) useEffect(() => { if (add && showMultisig) setShowMultisig(false) }, [add, showMultisig]) diff --git a/apps/multisig/src/components/SubstrateContractAbi/param-input/Address.tsx b/apps/multisig/src/components/SubstrateContractAbi/param-input/Address.tsx index ac9cbe9a..67376d29 100644 --- a/apps/multisig/src/components/SubstrateContractAbi/param-input/Address.tsx +++ b/apps/multisig/src/components/SubstrateContractAbi/param-input/Address.tsx @@ -7,7 +7,7 @@ import { useEffect, useState } from 'react' export const AddressParamInput: ParamInputComponent = ({ chain, onChange, arg }) => { const [query, setQuery] = useState('') const [selectedMultisig] = useSelectedMultisig() - const { addresses } = useKnownAddresses(selectedMultisig.orgId, { includeSelectedMultisig: true }) + const { addresses } = useKnownAddresses({ orgId: selectedMultisig.orgId, includeSelectedMultisig: true }) useEffect(() => { if (!arg) setQuery('') diff --git a/apps/multisig/src/components/TransactionSidesheet/TransactionSidesheetApprovals.tsx b/apps/multisig/src/components/TransactionSidesheet/TransactionSidesheetApprovals.tsx index 76793912..580238ac 100644 --- a/apps/multisig/src/components/TransactionSidesheet/TransactionSidesheetApprovals.tsx +++ b/apps/multisig/src/components/TransactionSidesheet/TransactionSidesheetApprovals.tsx @@ -7,8 +7,16 @@ import { Address } from '@util/addresses' import { useSelectedMultisig } from '@domains/multisig' export const TransactionSidesheetApprovals: React.FC<{ t: Transaction }> = ({ t }) => { - const { contactByAddress } = useKnownAddresses(t.multisig.orgId) const [{ isEthereumAccount }] = useSelectedMultisig() + const approversAddresses = Object.keys(t.approvals).reduce((acc, address) => { + const decodedAddress = isEthereumAccount ? Address.fromSs58(address) : Address.fromPubKey(address) + if (decodedAddress) { + acc.push(decodedAddress.toSs58()) + } + return acc + }, []) + + const { contactByAddress, isLoading } = useKnownAddresses({ orgId: t.multisig.orgId, addresses: approversAddresses }) return (
{Object.entries(t.approvals).map(([address, approval]) => { @@ -24,6 +32,7 @@ export const TransactionSidesheetApprovals: React.FC<{ t: Transaction }> = ({ t
{(t.rawPending || t.executedAt) && ( diff --git a/apps/multisig/src/domains/offchain-data/address-book/hooks/useGetAddressesByOrgIdAndAddress.ts b/apps/multisig/src/domains/offchain-data/address-book/hooks/useGetAddressesByOrgIdAndAddress.ts new file mode 100644 index 00000000..4fd423ef --- /dev/null +++ b/apps/multisig/src/domains/offchain-data/address-book/hooks/useGetAddressesByOrgIdAndAddress.ts @@ -0,0 +1,53 @@ +import { requestSignetBackend } from '@domains/offchain-data/hasura' +import { selectedAccountState } from '@domains/auth' +import { SignedInAccount } from '@domains/auth' +import { useRecoilValue } from 'recoil' +import { useQuery } from '@tanstack/react-query' +import { ADDRESSES_BY_ORG_ID_AND_ADDRESS } from '@domains/offchain-data/address-book/queries/queries' +import { useSelectedMultisig } from '@domains/multisig' +import { Address } from '@util/addresses' +import { Contact } from '../address-book' + +export type ContactAddress = Omit & { team_id?: string; org_id: string } +export type ContactAddressIO = Omit & { address: string } + +export type PaginatedAddresses = { + rows: ContactAddress[] + pageCount: number + rowCount: number +} + +const fetchGraphQLData = async ({ + orgId, + addresses, + selectedAccount, +}: { + orgId: string + addresses: string[] + selectedAccount: SignedInAccount +}): Promise => { + const { data } = await requestSignetBackend( + ADDRESSES_BY_ORG_ID_AND_ADDRESS, + { + orgId, + addresses, + }, + selectedAccount + ) + + return ( + data.address?.map((contact: ContactAddressIO) => ({ ...contact, address: Address.fromSs58(contact.address) })) ?? [] + ) +} + +const useGetAddressesByOrgIdAndAddress = (addresses: string[]) => { + const selectedAccount = useRecoilValue(selectedAccountState) + const [selectedMultisig] = useSelectedMultisig() + return useQuery({ + queryKey: ['addresses', selectedMultisig.id, addresses], + queryFn: () => fetchGraphQLData({ orgId: selectedMultisig.orgId, addresses, selectedAccount: selectedAccount! }), + enabled: !!selectedAccount && addresses.length > 0, + }) +} + +export default useGetAddressesByOrgIdAndAddress diff --git a/apps/multisig/src/domains/offchain-data/address-book/queries/queries.ts b/apps/multisig/src/domains/offchain-data/address-book/queries/queries.ts index 64570328..efbe78a8 100644 --- a/apps/multisig/src/domains/offchain-data/address-book/queries/queries.ts +++ b/apps/multisig/src/domains/offchain-data/address-book/queries/queries.ts @@ -70,6 +70,25 @@ export const PAGINATED_SUB_CATEGORIES_BY_ORG_ID = gql` } ` +export const ADDRESSES_BY_ORG_ID_AND_ADDRESS = gql` + query AddressesByOrgIdAndAddress($orgId: uuid!, $addresses: [String!]!) { + address(where: { org_id: { _eq: $orgId }, address: { _in: $addresses } }) { + id + org_id + name + address + category { + id + name + } + sub_category { + id + name + } + } + } +` + export const UPSERT_ADDRESSES = gql` mutation UpsertAddressesMutation($orgId: String!, $teamId: String!, $addressesInput: [AddressInput!]!) { UpsertAddresses(addressesInput: { addresses: $addressesInput, org_id: $orgId, team_id: $teamId }) { diff --git a/apps/multisig/src/hooks/useKnownAddresses.ts b/apps/multisig/src/hooks/useKnownAddresses.ts index 11e6a991..9baa0747 100644 --- a/apps/multisig/src/hooks/useKnownAddresses.ts +++ b/apps/multisig/src/hooks/useKnownAddresses.ts @@ -6,20 +6,30 @@ import { useMemo } from 'react' import { useSelectedMultisig } from '@domains/multisig' import { useSmartContracts } from '../domains/offchain-data/smart-contract' import { Contact } from '../domains/offchain-data/address-book/address-book' +import useGetAddressesByOrgIdAndAddress from '../domains/offchain-data/address-book/hooks/useGetAddressesByOrgIdAndAddress' type ContactWithNameAndCategory = Partial & AddressWithName -export const useKnownAddresses = ( - teamId?: string, - { - includeSelectedMultisig = false, - includeContracts = false, - }: { includeSelectedMultisig?: boolean; includeContracts?: boolean } = {} -): { addresses: ContactWithNameAndCategory[]; contactByAddress: Record } => { +export const useKnownAddresses = ({ + orgId, + includeSelectedMultisig, + includeContracts, + addresses, +}: { + orgId?: string + includeSelectedMultisig?: boolean + includeContracts?: boolean + addresses?: string[] +} = {}): { + addresses: ContactWithNameAndCategory[] + contactByAddress: Record + isLoading: boolean +} => { const extensionAccounts = useRecoilValue(accountsState) const addressBookByOrgId = useRecoilValue(addressBookByOrgIdState) const [multisig] = useSelectedMultisig() const { contracts } = useSmartContracts() + const { data: addressBookData, isLoading } = useGetAddressesByOrgIdAndAddress(addresses ?? []) const extensionContacts = extensionAccounts.reduce( (acc, { address, meta: { name = '' } = {} }) => { @@ -37,9 +47,9 @@ export const useKnownAddresses = ( ) const addressBookContacts = useMemo(() => { - if (!teamId) return [] + if (!orgId || !addressBookData?.length) return [] - const addresses = addressBookByOrgId[teamId ?? ''] ?? [] + const addresses = [...(addressBookByOrgId[orgId ?? ''] ?? []), ...(addressBookData ?? [])] return addresses.reduce((acc, { address, name, category, sub_category }) => { if (multisig.isEthereumAccount === address.isEthereum) { @@ -54,7 +64,7 @@ export const useKnownAddresses = ( } return acc }, []) - }, [addressBookByOrgId, multisig.isEthereumAccount, teamId]) + }, [addressBookByOrgId, addressBookData, multisig.isEthereumAccount, orgId]) const combinedList = useMemo(() => { let list = extensionContacts @@ -130,5 +140,5 @@ export const useKnownAddresses = ( }, {} as Record) }, [combinedList]) - return { addresses: combinedList, contactByAddress } + return { addresses: combinedList, contactByAddress, isLoading } } diff --git a/apps/multisig/src/layouts/Collaborators/AddCollaboratorModal.tsx b/apps/multisig/src/layouts/Collaborators/AddCollaboratorModal.tsx index 659103c6..c5fdf7b5 100644 --- a/apps/multisig/src/layouts/Collaborators/AddCollaboratorModal.tsx +++ b/apps/multisig/src/layouts/Collaborators/AddCollaboratorModal.tsx @@ -16,7 +16,7 @@ export const AddCollaboratorModal: React.FC = ({ isOpen, onClose }) => { const [selectedMultisig] = useSelectedMultisig() const [address, setAddress] = useState
() const [error, setError] = useState(false) - const { addresses } = useKnownAddresses(selectedMultisig.orgId) + const { addresses } = useKnownAddresses({ orgId: selectedMultisig.orgId }) const { addCollaborator, adding } = useAddOrgCollaborator() const handleAddressChange = (address: Address | undefined) => { diff --git a/apps/multisig/src/layouts/Collaborators/CollaboratorRow.tsx b/apps/multisig/src/layouts/Collaborators/CollaboratorRow.tsx index f3de06fb..a6315476 100644 --- a/apps/multisig/src/layouts/Collaborators/CollaboratorRow.tsx +++ b/apps/multisig/src/layouts/Collaborators/CollaboratorRow.tsx @@ -14,7 +14,11 @@ export const CollaboratorRow: React.FC<{ orgId: string; userId: string; address: userId, address, }) => { - const { contactByAddress } = useKnownAddresses(orgId, { includeContracts: true }) + const { contactByAddress, isLoading } = useKnownAddresses({ + orgId, + includeContracts: true, + addresses: [address.toSs58()], + }) const { deleteCollaborator, deleting } = useDeleteCollaborator() const { isCollaborator } = useUser() @@ -25,7 +29,12 @@ export const CollaboratorRow: React.FC<{ orgId: string; userId: string; address: return (
- +
{!isCollaborator && ( diff --git a/apps/multisig/src/layouts/NewTransaction/Multisend/index.tsx b/apps/multisig/src/layouts/NewTransaction/Multisend/index.tsx index de5a11c6..c78a4502 100644 --- a/apps/multisig/src/layouts/NewTransaction/Multisend/index.tsx +++ b/apps/multisig/src/layouts/NewTransaction/Multisend/index.tsx @@ -31,7 +31,7 @@ const MultiSend = () => { const apiLoadable = useRecoilValueLoadable(pjsApiSelector(multisig.chain.genesisHash)) const { toast } = useToast() const permissions = hasPermission(multisig, 'transfer') - const { addresses } = useKnownAddresses(multisig.orgId) + const { addresses } = useKnownAddresses({ orgId: multisig.orgId }) const newSends = useRecoilValue(multisendSendsAtom) const unit = useRecoilValue(multisendAmountUnitAtom) const token = useRecoilValue(multisendTokenAtom) diff --git a/apps/multisig/src/layouts/NewTransaction/Send/DetailsForm.tsx b/apps/multisig/src/layouts/NewTransaction/Send/DetailsForm.tsx index 906e05bc..5f6a41cb 100644 --- a/apps/multisig/src/layouts/NewTransaction/Send/DetailsForm.tsx +++ b/apps/multisig/src/layouts/NewTransaction/Send/DetailsForm.tsx @@ -69,7 +69,7 @@ export const DetailsForm: React.FC = ({ }) => { const [addressError, setAddressError] = useState(false) const [multisig] = useSelectedMultisig() - const { addresses } = useKnownAddresses(multisig.orgId) + const { addresses } = useKnownAddresses({ orgId: multisig.orgId }) const { hasDelayedPermission, hasNonDelayedPermission } = hasPermission(multisig, 'transfer') const vestingConsts = useRecoilValueLoadable(vestingConstsSelector(multisig.chain.genesisHash)) diff --git a/apps/multisig/src/layouts/Overview/Transactions/TransactionDetailsExpandable.tsx b/apps/multisig/src/layouts/Overview/Transactions/TransactionDetailsExpandable.tsx index a7df1bd2..0188f372 100644 --- a/apps/multisig/src/layouts/Overview/Transactions/TransactionDetailsExpandable.tsx +++ b/apps/multisig/src/layouts/Overview/Transactions/TransactionDetailsExpandable.tsx @@ -31,6 +31,7 @@ import { isExtrinsicProxyWrapped } from '@util/extrinsics' import { CONFIG } from '@lib/config' import { VestingDateRange } from '@components/VestingDateRange' import { Table, TableCell, TableHead, TableHeader, TableRow } from '@components/ui/table' +import { CircularProgressIndicator } from '@talismn/ui' const CopyPasteBox: React.FC<{ content: string; label?: string }> = ({ content, label }) => { const [copied, setCopied] = useState(false) @@ -115,7 +116,7 @@ const MultisigCallDataBox: React.FC<{ calldata: `0x${string}`; genesisHash: stri } const ChangeConfigExpandedDetails = ({ t }: { t: Transaction }) => { - const { contactByAddress } = useKnownAddresses(t.multisig.orgId) + const { contactByAddress } = useKnownAddresses({ orgId: t.multisig.orgId }) return (
@@ -153,14 +154,17 @@ const ChangeConfigExpandedDetails = ({ t }: { t: Transaction }) => { } const MultiSendExpandedDetails = ({ t }: { t: Transaction }) => { - const { contactByAddress } = useKnownAddresses(t.multisig.orgId) - const shouldDisplayCategory = t.decoded?.recipients.some(r => contactByAddress[r.address.toSs58()]?.category) - const shouldDisplaySubcategory = t.decoded?.recipients.some(r => contactByAddress[r.address.toSs58()]?.sub_category) + const recipientAddresses = t.decoded?.recipients.map(r => r.address.toSs58()) + const { contactByAddress, isLoading } = useKnownAddresses({ orgId: t.multisig.orgId, addresses: recipientAddresses }) + const shouldDisplayCategory = + t.decoded?.recipients.some(r => contactByAddress[r.address.toSs58()]?.category) || isLoading + const shouldDisplaySubcategory = + t.decoded?.recipients.some(r => contactByAddress[r.address.toSs58()]?.sub_category) || isLoading const shouldDisplayVesting = t.decoded?.recipients.some(r => r.vestingSchedule) return (
- +
Recipient @@ -172,7 +176,7 @@ const MultiSendExpandedDetails = ({ t }: { t: Transaction }) => { {t.decoded?.recipients.map(({ address, balance, vestingSchedule }, i) => ( - + { identiconSize={28} disableCopy hideIdenticon + isNameLoading={isLoading} /> {shouldDisplayCategory && ( - {contactByAddress[address.toSs58()]?.category?.name} + + {isLoading ? ( +
+ +
+ ) : ( + contactByAddress[address.toSs58()]?.category?.name + )} +
)} {shouldDisplaySubcategory && ( - {contactByAddress[address.toSs58()]?.sub_category?.name} + + {isLoading ? ( +
+ +
+ ) : ( + contactByAddress[address.toSs58()]?.sub_category?.name + )} +
)} {vestingSchedule && ( @@ -301,15 +322,18 @@ function AdvancedExpendedDetails({ } const TransactionDetailsHeaderContent: React.FC<{ t: Transaction }> = ({ t }) => { - const { contactByAddress } = useKnownAddresses(t.multisig.orgId, { + const recipients = t.decoded?.recipients || [] + const [recipient] = t.decoded?.recipients || [] + const recipientAddress = recipient?.address.toSs58() + const { contactByAddress, isLoading } = useKnownAddresses({ + orgId: t.multisig.orgId, includeContracts: true, + addresses: recipients.length === 1 && recipientAddress ? [recipientAddress] : [], }) - const recipients = t.decoded?.recipients || [] if (!t.decoded) return null if (t.decoded.type === TransactionType.Transfer) { - const [recipient] = t.decoded.recipients if (recipient) return (
@@ -321,6 +345,7 @@ const TransactionDetailsHeaderContent: React.FC<{ t: Transaction }> = ({ t }) => nameOrAddressOnly identiconSize={16} disableCopy + isNameLoading={isLoading} />
) diff --git a/apps/multisig/src/layouts/Overview/Transactions/TransactionSummaryRow.tsx b/apps/multisig/src/layouts/Overview/Transactions/TransactionSummaryRow.tsx index d6bbe908..d1d15f19 100644 --- a/apps/multisig/src/layouts/Overview/Transactions/TransactionSummaryRow.tsx +++ b/apps/multisig/src/layouts/Overview/Transactions/TransactionSummaryRow.tsx @@ -38,7 +38,10 @@ const TransactionSummaryRow = ({ showShareButton?: boolean onClick?: () => void }) => { - const { contactByAddress } = useKnownAddresses(t.multisig.orgId) + const { contactByAddress, isLoading } = useKnownAddresses({ + orgId: t.multisig.orgId, + addresses: [t?.draft?.creator.address.toSs58() || ''], + }) const sumOutgoing: Balance[] = useMemo(() => calcSumOutgoing(t), [t]) const combinedView = useRecoilValue(combinedViewState) const tokenPrices = useRecoilValueLoadable(tokenPricesState(sumOutgoing.map(b => b.token))) @@ -157,6 +160,7 @@ const TransactionSummaryRow = ({ withAddressTooltip nameOrAddressOnly disableCopy + isNameLoading={isLoading} /> diff --git a/apps/multisig/src/layouts/Overview/VaultOverview.tsx b/apps/multisig/src/layouts/Overview/VaultOverview.tsx index f6394ee2..d9beb721 100644 --- a/apps/multisig/src/layouts/Overview/VaultOverview.tsx +++ b/apps/multisig/src/layouts/Overview/VaultOverview.tsx @@ -24,8 +24,12 @@ const showMemberState = atom({ export const VaultOverview: React.FC = () => { const [selectedMultisig] = useSelectedMultisig() const [showMembers, setShowMembers] = useRecoilState(showMemberState) - const { contactByAddress } = useKnownAddresses(selectedMultisig.orgId) const { copy, copied } = useCopied() + const signersAddresses = selectedMultisig.signers.map(signer => signer.toSs58()) + const { contactByAddress, isLoading } = useKnownAddresses({ + orgId: selectedMultisig.orgId, + addresses: signersAddresses, + }) return (
@@ -145,6 +149,7 @@ export const VaultOverview: React.FC = () => { identiconSize={20} nameOrAddressOnly withAddressTooltip + isNameLoading={isLoading} /> ))} diff --git a/apps/multisig/src/layouts/Settings/RecoverMultisig.tsx b/apps/multisig/src/layouts/Settings/RecoverMultisig.tsx index 28f3f26d..01d9e51a 100644 --- a/apps/multisig/src/layouts/Settings/RecoverMultisig.tsx +++ b/apps/multisig/src/layouts/Settings/RecoverMultisig.tsx @@ -25,10 +25,11 @@ type Props = { const ScannedMultisigs: React.FC<{ multisigs: { signers: Address[]; threshold: number; address: Address }[] chain: Chain - teamId: string + orgId: string onSelect: (multisig: { signers: Address[]; threshold: number; address: Address }) => void -}> = ({ chain, multisigs, onSelect, teamId }) => { - const { contactByAddress } = useKnownAddresses(teamId) +}> = ({ chain, multisigs, onSelect, orgId }) => { + const signersAddresses = useMemo(() => multisigs.flatMap(m => m.signers.map(signer => signer.toSs58())), [multisigs]) + const { contactByAddress, isLoading } = useKnownAddresses({ orgId, addresses: signersAddresses }) return (
{multisigs.map(multisig => ( @@ -66,6 +67,7 @@ const ScannedMultisigs: React.FC<{ name={contactByAddress[signer.toSs58()]?.name} withAddressTooltip nameOrAddressOnly + isNameLoading={isLoading} />
))} @@ -184,7 +186,7 @@ export const RecoverMultisig: React.FC = ({ multisig }) => { )} ({ address: multisigAddress, signers, diff --git a/apps/multisig/src/layouts/Settings/SignersSettings.tsx b/apps/multisig/src/layouts/Settings/SignersSettings.tsx index 2fae01db..8d38bdf4 100644 --- a/apps/multisig/src/layouts/Settings/SignersSettings.tsx +++ b/apps/multisig/src/layouts/Settings/SignersSettings.tsx @@ -19,7 +19,12 @@ type Props = { } export const SignersSettings: React.FC = ({ capHeight, editable, error, members, multisig, onChange }) => { - const { addresses: knownAddresses, contactByAddress } = useKnownAddresses(multisig.orgId) + const membersAddresses = members.map(m => m.toSs58()) + const { + addresses: knownAddresses, + contactByAddress, + isLoading, + } = useKnownAddresses({ orgId: multisig.orgId, addresses: membersAddresses }) const prevLength = useRef(members.length) const scrollView = useRef(null) @@ -62,6 +67,7 @@ export const SignersSettings: React.FC = ({ capHeight, editable, error, m disableCopy chain={multisig.chain} withAddressTooltip + isNameLoading={isLoading} />