diff --git a/apps/portal/app/components/atom-search-combobox-extended.tsx b/apps/portal/app/components/atom-search-combobox-extended.tsx
new file mode 100644
index 000000000..bf0e04678
--- /dev/null
+++ b/apps/portal/app/components/atom-search-combobox-extended.tsx
@@ -0,0 +1,352 @@
+import * as React from 'react'
+
+import {
+ Button,
+ ButtonSize,
+ ButtonVariant,
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+ CommandShortcut,
+ EmptyStateCard,
+ Icon,
+ IconName,
+ Tag,
+ TagSize,
+} from '@0xintuition/1ui'
+import { GetAtomQuery, useGetAtomsQuery } from '@0xintuition/graphql'
+
+import { useDebounce } from '@lib/hooks/useDebounce'
+import { formatEther } from 'viem'
+
+import { AtomType, AtomTypeSelect } from './atom-type-select'
+
+interface AtomDetailsProps {
+ atom: GetAtomQuery['atom']
+}
+
+const AtomDetails = React.memo(({ atom }: AtomDetailsProps) => {
+ if (!atom) {
+ return
+ }
+
+ return (
+
+ {atom.image ? (
+
+ ) : (
+
+ )}
+
+
{atom.label}
+
{atom.type}
+
+ ID: {atom.vaultId}
+
+
+
+
+
+
TVL
+
+ {(
+ parseFloat(formatEther(BigInt(atom.vault?.totalShares || 0))) *
+ parseFloat(
+ formatEther(BigInt(atom.vault?.currentSharePrice || 0)),
+ )
+ ).toFixed(4)}{' '}
+ ETH
+
+
+
+
+
Attestors
+
+ {atom.vault?.positionCount}
+
+
+
+
+ )
+})
+
+AtomDetails.displayName = 'AtomDetails'
+
+interface AtomSearchComboboxItemProps {
+ atom: NonNullable
+ isSelected?: boolean
+ onSelect: () => void
+ onMouseEnter: () => void
+ onMouseLeave: () => void
+}
+
+const LazyImage = React.memo(
+ ({
+ src,
+ alt,
+ className,
+ }: {
+ src: string
+ alt: string
+ className: string
+ }) => {
+ const [isLoading, setIsLoading] = React.useState(true)
+ const [error, setError] = React.useState(false)
+
+ return (
+ <>
+ {isLoading && (
+
+ )}
+ setIsLoading(false)}
+ onError={() => {
+ setIsLoading(false)
+ setError(true)
+ }}
+ />
+ {error && (
+
+
+
+ )}
+ >
+ )
+ },
+)
+
+LazyImage.displayName = 'LazyImage'
+
+const AtomSearchComboboxItem = React.memo(
+ ({
+ atom,
+ isSelected,
+ onSelect,
+ onMouseEnter,
+ onMouseLeave,
+ }: AtomSearchComboboxItemProps) => {
+ return (
+
+
+
+ {atom?.image ? (
+
+ ) : (
+
+ )}
+
+
{atom?.label}
+
+
+ #{atom?.vaultId}
+
+
+
+
+
+
+
+ {atom?.vault?.currentSharePrice
+ ? (
+ parseFloat(
+ formatEther(BigInt(atom.vault?.totalShares || 0)),
+ ) *
+ parseFloat(
+ formatEther(BigInt(atom.vault?.currentSharePrice || 0)),
+ )
+ ).toFixed(3)
+ : undefined}
+
+
+
+
+ {atom?.vault?.positionCount}
+
+
+
+
+ )
+ },
+ (prevProps, nextProps) => {
+ return (
+ prevProps.isSelected === nextProps.isSelected &&
+ prevProps.atom?.vaultId === nextProps.atom?.vaultId
+ )
+ },
+)
+
+AtomSearchComboboxItem.displayName = 'AtomSearchComboboxItem'
+
+export interface AtomSearchComboboxExtendedProps
+ extends React.HTMLAttributes {
+ onAtomSelect?: (atom: GetAtomQuery['atom']) => void
+ initialValue?: string
+ placeholder?: string
+}
+
+export function AtomSearchComboboxExtended({
+ onAtomSelect = () => {},
+ initialValue = '',
+ placeholder = 'Search for an atom...',
+ ...props
+}: AtomSearchComboboxExtendedProps) {
+ const [searchValue, setSearchValue] = React.useState(initialValue)
+ const debouncedSearch = useDebounce(searchValue, 300)
+ const [isOpen, setIsOpen] = React.useState(false)
+ const [searchResults, setSearchResults] = React.useState<
+ Omit<
+ NonNullable,
+ 'asSubject' | 'asPredicate' | 'asObject'
+ >[]
+ >([])
+ const [selectedAtom, setSelectedAtom] = React.useState()
+ const [hoveredAtom, setHoveredAtom] = React.useState()
+ const [lastHoveredAtom, setLastHoveredAtom] =
+ React.useState()
+ const [selectedType, setSelectedType] = React.useState('All')
+
+ const { data: atomsData, isLoading } = useGetAtomsQuery(
+ {
+ where: {
+ label: { _ilike: `%${debouncedSearch}%` },
+ ...(selectedType !== 'All' && { type: { _eq: selectedType } }),
+ },
+ limit: 25,
+ },
+ {
+ queryKey: ['atoms', debouncedSearch, selectedType],
+ enabled: isOpen,
+ },
+ )
+
+ React.useEffect(() => {
+ setSearchResults(atomsData?.atoms ?? [])
+ }, [atomsData])
+
+ const handleAtomSelect = (
+ atom: Omit,
+ ) => {
+ onAtomSelect(atom as GetAtomQuery['atom'])
+ setSelectedAtom(atom as GetAtomQuery['atom'])
+ setIsOpen(false)
+ setSearchValue('')
+ setSearchResults([])
+ }
+
+ const displayedAtom = (hoveredAtom ||
+ lastHoveredAtom ||
+ selectedAtom ||
+ searchResults[0]) as NonNullable
+
+ const handleMouseEnter = (atom: NonNullable) => {
+ setHoveredAtom(atom)
+ setLastHoveredAtom(atom)
+ }
+
+ const handleMouseLeave = () => {
+ setHoveredAtom(undefined)
+ }
+
+ return (
+
+
+
+
+ {
+ setSearchValue(value)
+ setIsOpen(true)
+ }}
+ onFocus={() => setIsOpen(true)}
+ className="border-0 px-3 "
+ />
+
+
+
+
+
+
+
+
+ {searchResults.map((atom, index) => (
+ }
+ isSelected={selectedAtom?.vaultId === atom.vaultId}
+ onSelect={() => handleAtomSelect(atom)}
+ onMouseEnter={() =>
+ handleMouseEnter(atom as NonNullable)
+ }
+ onMouseLeave={handleMouseLeave}
+ />
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/portal/app/components/atom-search-combobox.tsx b/apps/portal/app/components/atom-search-combobox.tsx
new file mode 100644
index 000000000..9223ddfda
--- /dev/null
+++ b/apps/portal/app/components/atom-search-combobox.tsx
@@ -0,0 +1,189 @@
+import * as React from 'react'
+
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ EmptyStateCard,
+ Icon,
+ IconName,
+} from '@0xintuition/1ui'
+import { GetAtomQuery, useGetAtomsQuery } from '@0xintuition/graphql'
+
+import { useDebounce } from '@lib/hooks/useDebounce'
+import { formatEther } from 'viem'
+
+interface AtomSearchComboboxItemProps {
+ id: string | number
+ name: string
+ avatarSrc?: string
+ value?: number
+ variant?: 'user' | 'non-user'
+ onClick?: () => void
+ onSelect?: () => void
+ attestors?: number
+}
+
+const AtomSearchComboboxItem = ({
+ id,
+ name,
+ avatarSrc,
+ value,
+ onClick,
+ onSelect,
+ attestors,
+}: AtomSearchComboboxItemProps) => {
+ return (
+
+
+
+ {avatarSrc ? (
+
+ ) : (
+
+ )}
+
+
+ {name}
+
+ {id !== undefined && (
+
+ )}
+
+
+
+
+
+ {value?.toFixed(3)}
+
+
+
+
+ {attestors}
+
+
+
+
+ )
+}
+
+export interface AtomSearchComboboxProps
+ extends React.HTMLAttributes {
+ onAtomSelect?: (atom: GetAtomQuery['atom']) => void
+ onCreateAtomClick?: () => void
+ initialValue?: string
+ placeholder?: string
+}
+
+export function AtomSearchCombobox({
+ onAtomSelect = () => {},
+ initialValue = '',
+ placeholder = 'Search for an atom...',
+ ...props
+}: AtomSearchComboboxProps) {
+ const [searchValue, setSearchValue] = React.useState(initialValue)
+ const debouncedSearch = useDebounce(searchValue, 300)
+ const [isOpen, setIsOpen] = React.useState(false)
+ const [searchResults, setSearchResults] = React.useState<
+ Omit<
+ NonNullable,
+ 'asSubject' | 'asPredicate' | 'asObject'
+ >[]
+ >([])
+
+ const { data: atomsData, isLoading } = useGetAtomsQuery(
+ {
+ where: {
+ label: { _ilike: `%${debouncedSearch}%` },
+ },
+ limit: 25,
+ },
+ {
+ queryKey: ['atoms', debouncedSearch],
+ enabled: isOpen,
+ },
+ )
+
+ React.useEffect(() => {
+ setSearchResults(atomsData?.atoms ?? [])
+ }, [atomsData])
+
+ const handleAtomSelect = (
+ atom: Omit,
+ ) => {
+ onAtomSelect(atom as GetAtomQuery['atom'])
+ setIsOpen(false)
+ setSearchValue('')
+ setSearchResults([])
+ }
+
+ React.useEffect(() => {
+ console.log('Atom Data:', atomsData?.atoms)
+ setSearchResults(atomsData?.atoms ?? [])
+ }, [atomsData])
+
+ return (
+
+
+ {
+ setSearchValue(value)
+ setIsOpen(true)
+ }}
+ onFocus={() => setIsOpen(true)}
+ className="text-base h-12"
+ />
+
+
+
+
+
+ {searchResults.map((atom, index) => (
+ handleAtomSelect(atom)}
+ onSelect={() => handleAtomSelect(atom)}
+ />
+ ))}
+
+
+
+
+ )
+}
diff --git a/apps/portal/app/components/atom-type-select.tsx b/apps/portal/app/components/atom-type-select.tsx
new file mode 100644
index 000000000..e486eeccc
--- /dev/null
+++ b/apps/portal/app/components/atom-type-select.tsx
@@ -0,0 +1,49 @@
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@0xintuition/1ui'
+
+export const ATOM_TYPES = [
+ 'All',
+ 'Thing',
+ 'Person',
+ 'Account',
+ 'Organization',
+ 'Book',
+] as const
+export type AtomType = (typeof ATOM_TYPES)[number]
+
+interface AtomTypeSelectProps {
+ value: AtomType
+ onValueChange: (value: AtomType) => void
+}
+
+export function AtomTypeSelect({ value, onValueChange }: AtomTypeSelectProps) {
+ return (
+
+ )
+}
diff --git a/apps/portal/app/components/create-claim/create-claim-form.tsx b/apps/portal/app/components/create-claim/create-claim-form.tsx
index d525c8b94..096ac3e97 100644
--- a/apps/portal/app/components/create-claim/create-claim-form.tsx
+++ b/apps/portal/app/components/create-claim/create-claim-form.tsx
@@ -11,8 +11,8 @@ import {
Text,
toast,
} from '@0xintuition/1ui'
-import { ClaimPresenter, IdentityPresenter } from '@0xintuition/api'
-import { useGetTripleQuery } from '@0xintuition/graphql'
+import { ClaimPresenter } from '@0xintuition/api'
+import { GetAtomQuery, useGetTripleQuery } from '@0xintuition/graphql'
import { IdentityPopover } from '@components/create-claim/create-claim-popovers'
import CreateClaimReview from '@components/create-claim/create-claim-review'
@@ -23,7 +23,6 @@ import { useCheckClaim } from '@lib/hooks/useCheckClaim'
import { useCreateClaimConfig } from '@lib/hooks/useCreateClaimConfig'
import { useCreateTriple } from '@lib/hooks/useCreateTriple'
import { useGetWalletBalance } from '@lib/hooks/useGetWalletBalance'
-import { useIdentityServerSearch } from '@lib/hooks/useIdentityServerSearch'
import {
initialTransactionState,
transactionReducer,
@@ -139,11 +138,10 @@ function CreateClaimForm({
const [lastTxHash, setLastTxHash] = useState(undefined)
const [initialDeposit, setInitialDeposit] = useState('0')
const [vaultId, setVaultId] = useState(undefined)
- // const [searchQuery, setSearchQuery] = useState('')
const [selectedIdentities, setSelectedIdentities] = useState<{
- subject: IdentityPresenter | null
- predicate: IdentityPresenter | null
- object: IdentityPresenter | null
+ subject: GetAtomQuery['atom'] | null
+ predicate: GetAtomQuery['atom'] | null
+ object: GetAtomQuery['atom'] | null
}>({
subject: subject ?? null,
predicate: predicate ?? null,
@@ -156,9 +154,9 @@ function CreateClaimForm({
const { fees } = configData ?? {}
const { data: claimCheckData, refetch: refetchClaimCheck } = useCheckClaim({
- subjectId: selectedIdentities.subject?.vault_id,
- predicateId: selectedIdentities.predicate?.vault_id,
- objectId: selectedIdentities.object?.vault_id,
+ subjectId: selectedIdentities.subject?.vaultId,
+ predicateId: selectedIdentities.predicate?.vaultId,
+ objectId: selectedIdentities.object?.vaultId,
})
const { data: claimData, refetch: refetchClaim } = useGetTripleQuery(
@@ -179,8 +177,6 @@ function CreateClaimForm({
const navigate = useNavigate()
- const { setSearchQuery, identities, handleInput } = useIdentityServerSearch()
-
const publicClient = usePublicClient()
const { address, chain } = useAccount()
@@ -303,9 +299,9 @@ function CreateClaimForm({
selectedIdentities.object !== null
) {
handleOnChainCreateTriple({
- subjectVaultId: selectedIdentities.subject.vault_id,
- predicateVaultId: selectedIdentities.predicate.vault_id,
- objectVaultId: selectedIdentities.object.vault_id,
+ subjectVaultId: selectedIdentities.subject?.vaultId ?? '',
+ predicateVaultId: selectedIdentities.predicate?.vaultId ?? '',
+ objectVaultId: selectedIdentities.object?.vaultId ?? '',
})
}
} catch (error: unknown) {
@@ -314,35 +310,23 @@ function CreateClaimForm({
}
const handleIdentitySelection = (
- identityType: ClaimElementType,
- identity: IdentityPresenter,
+ type: ClaimElementType,
+ identity: GetAtomQuery['atom'],
) => {
- setSelectedIdentities((prevState) => ({
- ...prevState,
- [identityType]: identity,
+ setSelectedIdentities((prev) => ({
+ ...prev,
+ [type]: identity,
}))
- setSearchQuery('')
- // setIdentities([])
- if (identityType === ClaimElement.Subject) {
+
+ if (type === 'subject') {
setIsSubjectPopoverOpen(false)
- } else if (identityType === ClaimElement.Predicate) {
+ } else if (type === 'predicate') {
setIsPredicatePopoverOpen(false)
- } else if (identityType === ClaimElement.Object) {
+ } else if (type === 'object') {
setIsObjectPopoverOpen(false)
}
}
- useEffect(() => {
- if (
- !isSubjectPopoverOpen &&
- !isPredicatePopoverOpen &&
- !isObjectPopoverOpen
- ) {
- setSearchQuery('')
- // setIdentities([])
- }
- }, [isSubjectPopoverOpen, isPredicatePopoverOpen, isObjectPopoverOpen])
-
const walletBalance = useGetWalletBalance(
address ?? (wallet as `0x${string}`),
)
@@ -386,13 +370,10 @@ function CreateClaimForm({
isObjectPopoverOpen={isSubjectPopoverOpen}
setIsObjectPopoverOpen={setIsSubjectPopoverOpen}
selectedIdentity={selectedIdentities.subject}
- identities={identities}
handleIdentitySelection={(
identityType: ClaimElementType,
- identity: IdentityPresenter,
+ identity: GetAtomQuery['atom'],
) => handleIdentitySelection(identityType, identity)}
- setSearchQuery={setSearchQuery}
- handleInput={handleInput}
/>
handleIdentitySelection(identityType, identity)}
- setSearchQuery={setSearchQuery}
- handleInput={handleInput}
/>
handleIdentitySelection(identityType, identity)}
- setSearchQuery={setSearchQuery}
- handleInput={handleInput}
/>
diff --git a/apps/portal/app/components/create-claim/create-claim-popovers.tsx b/apps/portal/app/components/create-claim/create-claim-popovers.tsx
index 973e5a7c6..ccbe1d718 100644
--- a/apps/portal/app/components/create-claim/create-claim-popovers.tsx
+++ b/apps/portal/app/components/create-claim/create-claim-popovers.tsx
@@ -12,33 +12,28 @@ import {
Trunctacular,
useSidebarLayoutContext,
} from '@0xintuition/1ui'
-import { IdentityPresenter } from '@0xintuition/api'
+import { GetAtomQuery } from '@0xintuition/graphql'
-import { IdentitySearchCombobox } from '@components/identity/identity-search-combo-box'
+import { AtomSearchComboboxExtended } from '@components/atom-search-combobox-extended'
import { InfoTooltip } from '@components/info-tooltip'
-import { globalCreateIdentityModalAtom } from '@lib/state/store'
import {
- getAtomDescription,
- getAtomImage,
- getAtomIpfsLink,
- getAtomLabel,
- sliceString,
+ getAtomDescriptionGQL,
+ getAtomIdGQL,
+ getAtomImageGQL,
+ getAtomIpfsLinkGQL,
+ getAtomLabelGQL,
} from '@lib/utils/misc'
import { ClaimElementType } from 'app/types'
-import { useSetAtom } from 'jotai'
interface IdentityPopoverProps {
type: ClaimElementType
isObjectPopoverOpen: boolean
- setIsObjectPopoverOpen: (isOpen: boolean) => void
- selectedIdentity?: IdentityPresenter | null
- identities: IdentityPresenter[]
+ setIsObjectPopoverOpen: (open: boolean) => void
+ selectedIdentity: GetAtomQuery['atom'] | null
handleIdentitySelection: (
- identityType: ClaimElementType,
- identity: IdentityPresenter,
+ type: ClaimElementType,
+ identity: GetAtomQuery['atom'],
) => void
- setSearchQuery: (query: string) => void
- handleInput: (event: React.FormEvent
) => Promise
}
export const IdentityPopover: React.FC = ({
@@ -46,12 +41,8 @@ export const IdentityPopover: React.FC = ({
isObjectPopoverOpen,
setIsObjectPopoverOpen,
selectedIdentity,
- identities,
handleIdentitySelection,
- setSearchQuery,
- handleInput,
}) => {
- const setCreateIdentityModalActive = useSetAtom(globalCreateIdentityModalAtom)
const { isMobileView } = useSidebarLayoutContext()
return (
@@ -88,20 +79,18 @@ export const IdentityPopover: React.FC = ({
@@ -111,30 +100,17 @@ export const IdentityPopover: React.FC = ({
@@ -148,15 +124,11 @@ export const IdentityPopover: React.FC = ({
align="center"
sideOffset={5}
>
- setCreateIdentityModalActive(true)}
- onIdentitySelect={(identity) =>
- handleIdentitySelection(type, identity)
- }
- onValueChange={setSearchQuery}
- onInput={handleInput}
- shouldFilter={false}
+ handleIdentitySelection(type, atom)}
+ placeholder={`Search for ${type}...`}
+ initialValue=""
+ className="w-[600px]"
/>
diff --git a/apps/portal/app/components/create-claim/create-claim-review.tsx b/apps/portal/app/components/create-claim/create-claim-review.tsx
index dc828027c..00c945042 100644
--- a/apps/portal/app/components/create-claim/create-claim-review.tsx
+++ b/apps/portal/app/components/create-claim/create-claim-review.tsx
@@ -9,16 +9,16 @@ import {
Separator,
Text,
} from '@0xintuition/1ui'
-import { IdentityPresenter } from '@0xintuition/api'
+import { GetAtomQuery } from '@0xintuition/graphql'
import { InfoTooltip } from '@components/info-tooltip'
import { StandardFees } from '@components/stake/stake-fee-breakdown'
import {
- getAtomDescription,
- getAtomImage,
- getAtomIpfsLink,
- getAtomLabel,
- getAtomLink,
+ getAtomDescriptionGQL,
+ getAtomImageGQL,
+ getAtomIpfsLinkGQL,
+ getAtomLabelGQL,
+ getAtomLinkGQL,
} from '@lib/utils/misc'
import { CreateClaimFeesType } from '@routes/resources+/create-claim'
import { TransactionActionType } from 'app/types/transaction'
@@ -27,9 +27,9 @@ import { formatUnits } from 'viem'
interface CreateClaimReviewProps {
dispatch: (action: TransactionActionType) => void
selectedIdentities: {
- subject?: IdentityPresenter | null
- predicate?: IdentityPresenter | null
- object?: IdentityPresenter | null
+ subject?: GetAtomQuery['atom'] | null
+ predicate?: GetAtomQuery['atom'] | null
+ object?: GetAtomQuery['atom'] | null
}
initialDeposit: string
fees: CreateClaimFeesType
@@ -203,67 +203,45 @@ const CreateClaimReview: React.FC = ({
(value: T, delay: number): T {
+ const [debouncedValue, setDebouncedValue] = useState(value)
+
+ useEffect(() => {
+ const timer = setTimeout(() => setDebouncedValue(value), delay)
+ return () => clearTimeout(timer)
+ }, [value, delay])
+
+ return debouncedValue
+}
diff --git a/apps/portal/app/lib/state/store.ts b/apps/portal/app/lib/state/store.ts
index 727d86064..843f6a262 100644
--- a/apps/portal/app/lib/state/store.ts
+++ b/apps/portal/app/lib/state/store.ts
@@ -1,4 +1,5 @@
import { ClaimPresenter, IdentityPresenter } from '@0xintuition/api'
+import { GetAtomQuery } from '@0xintuition/graphql'
import { VaultDetailsType } from 'app/types'
import type { WritableAtom } from 'jotai'
@@ -100,9 +101,9 @@ export const imageModalAtom = atom<{
export const createClaimModalAtom = atom<{
isOpen: boolean
- subject?: IdentityPresenter | null
- predicate?: IdentityPresenter | null
- object?: IdentityPresenter | null
+ subject?: GetAtomQuery['atom'] | null
+ predicate?: GetAtomQuery['atom'] | null
+ object?: GetAtomQuery['atom'] | null
}>({
isOpen: false,
subject: null,
diff --git a/apps/portal/app/lib/utils/misc.tsx b/apps/portal/app/lib/utils/misc.tsx
index ca2b85710..d2b3af59e 100644
--- a/apps/portal/app/lib/utils/misc.tsx
+++ b/apps/portal/app/lib/utils/misc.tsx
@@ -2,6 +2,7 @@ import React from 'react'
import { Icon, IconName, Text, Theme } from '@0xintuition/1ui'
import { IdentityPresenter } from '@0xintuition/api'
+import { GetAtomQuery } from '@0xintuition/graphql'
import { SubmitFunction } from '@remix-run/react'
import { BLOCK_EXPLORER_URL, IPFS_GATEWAY_URL, PATHS } from 'app/consts'
@@ -427,6 +428,106 @@ export const getAtomId = (atom: IdentityPresenter) => {
return atom.identity_id
}
+// atom GQL helpers
+export const getAtomImageGQL = (
+ atom: GetAtomQuery['atom'] | null | undefined,
+) => {
+ if (!atom) {
+ return ''
+ }
+ return (
+ atom?.image ??
+ atom?.value?.person?.image ??
+ atom?.value?.thing?.image ??
+ atom?.value?.organization?.image ??
+ ''
+ )
+}
+
+export const getAtomLabelGQL = (
+ atom: GetAtomQuery['atom'] | null | undefined,
+) => {
+ if (!atom) {
+ return '?'
+ }
+ return (
+ atom.label ??
+ atom.value?.person?.name ??
+ atom.value?.thing?.name ??
+ atom.value?.organization?.name ??
+ atom.walletId ??
+ atom.id ??
+ ''
+ )
+}
+
+export const getAtomDescriptionGQL = (
+ atom: GetAtomQuery['atom'] | null | undefined,
+) => {
+ return (
+ atom?.value?.person?.description ??
+ atom?.value?.thing?.description ??
+ atom?.value?.organization?.description ??
+ ''
+ )
+}
+
+export const getAtomIpfsLinkGQL = (
+ atom: GetAtomQuery['atom'] | null | undefined,
+) => {
+ if (!atom) {
+ return ''
+ }
+ if (atom.type === ('Account' || 'Default')) {
+ return `${BLOCK_EXPLORER_URL}/address/${atom.walletId}`
+ }
+ if (atom.data?.startsWith('https')) {
+ return atom.data
+ }
+ if (atom.label?.startsWith('caip10')) {
+ const parts = atom.label.split(':')
+ const chainId = Number(parts[2])
+ const address = parts[3]
+ const chain = extractChain({
+ chains: Object.values(chains),
+ // @ts-ignore Ignoring type since viem doesn't provide proper typings for chain IDs
+ id: chainId,
+ })
+ return chain?.blockExplorers?.default
+ ? `${chain.blockExplorers.default.url}/address/${address}`
+ : ''
+ }
+ return `${IPFS_GATEWAY_URL}/${atom.data?.replace('ipfs://', '')}`
+ return ''
+}
+
+export const getAtomLinkGQL = (
+ atom: GetAtomQuery['atom'] | null | undefined,
+ readOnly: boolean = false,
+) => {
+ if (!atom) {
+ return ''
+ }
+ if (atom.type === ('Account' || 'Default')) {
+ return readOnly
+ ? `${PATHS.READONLY_PROFILE}/${atom.walletId}`
+ : `${PATHS.PROFILE}/${atom.walletId}`
+ }
+ return readOnly
+ ? `${PATHS.READONLY_IDENTITY}/${atom.vaultId}`
+ : `${PATHS.IDENTITY}/${atom.vaultId}`
+}
+
+export const getAtomIdGQL = (atom: GetAtomQuery['atom']) => {
+ if (!atom) {
+ return ''
+ }
+ if (atom.type === ('Account' || 'Default')) {
+ return atom.walletId
+ }
+ return atom.id
+}
+
export const calculatePointsFromFees = (totalProtocolFees: string): number => {
const feesInEth = formatUnits(BigInt(totalProtocolFees), 18)
const pointsPerEth = 10000000
diff --git a/packages/1ui/src/components/Icon/Icon.sprites.svg b/packages/1ui/src/components/Icon/Icon.sprites.svg
index ddd136a9d..380827668 100644
--- a/packages/1ui/src/components/Icon/Icon.sprites.svg
+++ b/packages/1ui/src/components/Icon/Icon.sprites.svg
@@ -1276,5 +1276,11 @@
d="M12 21.75C12.8284 21.75 13.5 21.0784 13.5 20.25C13.5 19.4216 12.8284 18.75 12 18.75C11.1716 18.75 10.5 19.4216 10.5 20.25C10.5 21.0784 11.1716 21.75 12 21.75Z"
fill="currentColor" />
+
+
+
+
diff --git a/packages/1ui/src/components/Icon/Icon.types.ts b/packages/1ui/src/components/Icon/Icon.types.ts
index 1c5e10d9f..dcaab6655 100644
--- a/packages/1ui/src/components/Icon/Icon.types.ts
+++ b/packages/1ui/src/components/Icon/Icon.types.ts
@@ -141,4 +141,5 @@ export const IconName = {
mirror: 'mirror',
warpcast: 'warpcast',
galxe: 'galxe',
+ eth: 'eth',
} as const
diff --git a/packages/graphql/src/fragments/atom.graphql b/packages/graphql/src/fragments/atom.graphql
index 9dd3c1ff3..215f6c763 100644
--- a/packages/graphql/src/fragments/atom.graphql
+++ b/packages/graphql/src/fragments/atom.graphql
@@ -45,6 +45,7 @@ fragment AtomVaultDetails on atoms {
vault {
positionCount
totalShares
+ currentSharePrice
positions_aggregate {
aggregate {
count
diff --git a/packages/graphql/src/generated/index.ts b/packages/graphql/src/generated/index.ts
index 938223fae..6ea2ab76d 100644
--- a/packages/graphql/src/generated/index.ts
+++ b/packages/graphql/src/generated/index.ts
@@ -7510,6 +7510,7 @@ export type AtomVaultDetailsFragment = {
__typename?: 'vaults'
positionCount: number
totalShares: any
+ currentSharePrice: any
positions_aggregate: {
__typename?: 'positions_aggregate'
aggregate?: {
@@ -8669,6 +8670,7 @@ export type GetAccountQuery = {
__typename?: 'vaults'
positionCount: number
totalShares: any
+ currentSharePrice: any
positions_aggregate: {
__typename?: 'positions_aggregate'
aggregate?: {
@@ -9031,6 +9033,7 @@ export type GetAtomsQuery = {
__typename?: 'vaults'
positionCount: number
totalShares: any
+ currentSharePrice: any
positions_aggregate: {
__typename?: 'positions_aggregate'
aggregate?: {
@@ -9114,6 +9117,7 @@ export type GetAtomsWithAggregatesQuery = {
__typename?: 'vaults'
positionCount: number
totalShares: any
+ currentSharePrice: any
positions_aggregate: {
__typename?: 'positions_aggregate'
aggregate?: {
@@ -9208,6 +9212,7 @@ export type GetAtomQuery = {
__typename?: 'vaults'
positionCount: number
totalShares: any
+ currentSharePrice: any
positions_aggregate: {
__typename?: 'positions_aggregate'
aggregate?: {
@@ -12313,6 +12318,7 @@ export const AtomVaultDetailsFragmentDoc = `
vault {
positionCount
totalShares
+ currentSharePrice
positions_aggregate {
aggregate {
count
@@ -17475,6 +17481,10 @@ export const AtomVaultDetails = {
name: { kind: 'Name', value: 'positionCount' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'totalShares' } },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'currentSharePrice' },
+ },
{
kind: 'Field',
name: { kind: 'Name', value: 'positions_aggregate' },
@@ -22356,6 +22366,10 @@ export const GetAccount = {
name: { kind: 'Name', value: 'positionCount' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'totalShares' } },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'currentSharePrice' },
+ },
{
kind: 'Field',
name: { kind: 'Name', value: 'positions_aggregate' },
@@ -24171,6 +24185,10 @@ export const GetAtoms = {
name: { kind: 'Name', value: 'positionCount' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'totalShares' } },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'currentSharePrice' },
+ },
{
kind: 'Field',
name: { kind: 'Name', value: 'positions_aggregate' },
@@ -24543,6 +24561,10 @@ export const GetAtomsWithAggregates = {
name: { kind: 'Name', value: 'positionCount' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'totalShares' } },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'currentSharePrice' },
+ },
{
kind: 'Field',
name: { kind: 'Name', value: 'positions_aggregate' },
@@ -24900,6 +24922,10 @@ export const GetAtom = {
name: { kind: 'Name', value: 'positionCount' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'totalShares' } },
+ {
+ kind: 'Field',
+ name: { kind: 'Name', value: 'currentSharePrice' },
+ },
{
kind: 'Field',
name: { kind: 'Name', value: 'positions_aggregate' },