diff --git a/centrifuge-app/src/components/Menu-deprecated/index.tsx b/centrifuge-app/src/components/Menu-deprecated/index.tsx
index 83e5d3d7e1..6723461169 100644
--- a/centrifuge-app/src/components/Menu-deprecated/index.tsx
+++ b/centrifuge-app/src/components/Menu-deprecated/index.tsx
@@ -2,7 +2,7 @@ import { Box, IconInvestments, IconNft, Menu as Panel, MenuItemGroup, Shelf, Sta
import { config } from '../../config'
import { useAddress } from '../../utils/useAddress'
import { useIsAboveBreakpoint } from '../../utils/useIsAboveBreakpoint'
-import { usePools } from '../../utils/usePools'
+import { usePoolsThatAnyConnectedAddressHasPermissionsFor } from '../../utils/usePermissions'
import { RouterLinkButton } from '../RouterLinkButton'
import { GovernanceMenu } from './GovernanceMenu'
import { IssuerMenu } from './IssuerMenu'
@@ -10,8 +10,7 @@ import { PageLink } from './PageLink'
import { PoolLink } from './PoolLink'
export function Menu() {
- // const pools = usePoolsThatAnyConnectedAddressHasPermissionsFor() || []
- const pools = usePools() || []
+ const pools = usePoolsThatAnyConnectedAddressHasPermissionsFor() || []
const isXLarge = useIsAboveBreakpoint('XL')
const address = useAddress('substrate')
@@ -38,7 +37,7 @@ export function Menu() {
{(pools.length > 0 || config.poolCreationType === 'immediate') && (
- id)}>
+
{isXLarge ? (
{!!pools.length &&
diff --git a/centrifuge-app/src/components/Menu/IssuerMenu.tsx b/centrifuge-app/src/components/Menu/IssuerMenu.tsx
index 58ed994907..85cc76b7bc 100644
--- a/centrifuge-app/src/components/Menu/IssuerMenu.tsx
+++ b/centrifuge-app/src/components/Menu/IssuerMenu.tsx
@@ -6,14 +6,13 @@ import { Toggle } from './Toggle'
type IssuerMenuProps = {
defaultOpen?: boolean
- poolIds?: string[]
stacked?: boolean
children?: React.ReactNode
}
-export function IssuerMenu({ defaultOpen = false, poolIds = [], stacked, children }: IssuerMenuProps) {
+export function IssuerMenu({ defaultOpen = false, stacked, children }: IssuerMenuProps) {
const match = useRouteMatch<{ pid: string }>('/issuer/:pid')
- const isActive = match && poolIds.includes(match.params.pid)
+ const isActive = !!match
const [open, setOpen] = React.useState(defaultOpen)
const { space } = useTheme()
const fullWidth = `calc(100vw - 2 * ${space[1]}px)`
diff --git a/centrifuge-app/src/components/Menu/index.tsx b/centrifuge-app/src/components/Menu/index.tsx
index e8479b6928..cf9e1e4d79 100644
--- a/centrifuge-app/src/components/Menu/index.tsx
+++ b/centrifuge-app/src/components/Menu/index.tsx
@@ -2,7 +2,7 @@ import { Box, IconInvestments, IconNft, Menu as Panel, MenuItemGroup, Shelf, Sta
import { config } from '../../config'
import { useAddress } from '../../utils/useAddress'
import { useIsAboveBreakpoint } from '../../utils/useIsAboveBreakpoint'
-import { usePools } from '../../utils/usePools'
+import { usePoolsThatAnyConnectedAddressHasPermissionsFor } from '../../utils/usePermissions'
import { RouterLinkButton } from '../RouterLinkButton'
import { GovernanceMenu } from './GovernanceMenu'
import { IssuerMenu } from './IssuerMenu'
@@ -10,8 +10,7 @@ import { PageLink } from './PageLink'
import { PoolLink } from './PoolLink'
export function Menu() {
- // const pools = usePoolsThatAnyConnectedAddressHasPermissionsFor() || []
- const pools = usePools() || []
+ const pools = usePoolsThatAnyConnectedAddressHasPermissionsFor() || []
const isLarge = useIsAboveBreakpoint('L')
const address = useAddress('substrate')
@@ -38,15 +37,14 @@ export function Menu() {
{(pools.length > 0 || config.poolCreationType === 'immediate') && (
- id)}>
+
{isLarge ? (
- {!!pools.length &&
- pools.map((pool) => (
-
-
-
- ))}
+ {pools.map((pool) => (
+
+
+
+ ))}
{address && config.poolCreationType === 'immediate' && (
diff --git a/centrifuge-app/src/utils/usePermissions.ts b/centrifuge-app/src/utils/usePermissions.ts
index 9783859ef2..2fbf758210 100644
--- a/centrifuge-app/src/utils/usePermissions.ts
+++ b/centrifuge-app/src/utils/usePermissions.ts
@@ -12,7 +12,7 @@ import { combineLatest, filter, map, repeatWhen, switchMap } from 'rxjs'
import { diffPermissions } from '../pages/IssuerPool/Configuration/Admins'
import { useCollections } from './useCollections'
import { useLoan } from './useLoans'
-import { usePool, usePoolMetadata } from './usePools'
+import { usePool, usePoolMetadata, usePools } from './usePools'
export function usePermissions(address?: string) {
const [result] = useCentrifugeQuery(['permissions', address], (cent) => cent.pools.getUserPermissions([address!]), {
@@ -29,38 +29,38 @@ export function usePoolPermissions(poolId?: string) {
return result
}
-// export function useUserPermissionsMulti(addresses: string[]) {
-// const [results] = useCentrifugeQueries(
-// addresses.map((address) => ({
-// queryKey: ['permissions', address],
-// queryCallback: (cent) => cent.pools.getUserPermissions([address!]),
-// }))
-// )
-
-// return results
-// }
+export function useUserPermissionsMulti(addresses: string[], options?: { enabled?: boolean }) {
+ const [result] = useCentrifugeQuery(
+ ['permissions', ...addresses],
+ (cent) => cent.pools.getUserPermissions([addresses]),
+ {
+ enabled: !!addresses.length && options?.enabled !== false,
+ }
+ )
+ return result
+}
-// // Better name welcomed lol
-// export function usePoolsThatAnyConnectedAddressHasPermissionsFor() {
-// const {
-// substrate: { combinedAccounts },
-// } = useWallet()
-// const actingAddresses = [...new Set(combinedAccounts?.map((acc) => acc.actingAddress))]
-// const permissionResults = useUserPermissionsMulti(actingAddresses)
+// Better name welcomed lol
+export function usePoolsThatAnyConnectedAddressHasPermissionsFor() {
+ const {
+ substrate: { combinedAccounts, proxiesAreLoading },
+ } = useWallet()
+ const actingAddresses = [...new Set(combinedAccounts?.map((acc) => acc.actingAddress))]
+ const permissionsResult = useUserPermissionsMulti(actingAddresses, { enabled: !proxiesAreLoading })
-// const poolIds = new Set(
-// permissionResults
-// .map((permissions) =>
-// Object.entries(permissions?.pools || {}).map(([poolId, roles]) => (roles.roles.length ? poolId : []))
-// )
-// .flat(2)
-// )
+ const poolIds = new Set(
+ permissionsResult
+ ?.map((permissions) =>
+ Object.entries(permissions?.pools || {}).map(([poolId, roles]) => (roles.roles.length ? poolId : []))
+ )
+ .flat(2)
+ )
-// const pools = usePools(false)
-// const filtered = pools?.filter((p) => poolIds.has(p.id))
+ const pools = usePools(false)
+ const filtered = pools?.filter((p) => poolIds.has(p.id))
-// return filtered
-// }
+ return filtered
+}
// Returns whether the connected address can borrow from a pool in principle
export function useCanBorrow(poolId: string) {
diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts
index 5b2bb11352..661086e9a7 100644
--- a/centrifuge-js/src/modules/pools.ts
+++ b/centrifuge-js/src/modules/pools.ts
@@ -681,6 +681,18 @@ type BorrowerTransaction = {
amount: CurrencyBalance | undefined
}
+export type Permissions = {
+ pools: {
+ [poolId: string]: PoolRoles
+ }
+ currencies: {
+ [currency: string]: {
+ roles: CurrencyRole[]
+ holder: boolean
+ }
+ }
+}
+
const formatPoolKey = (keys: StorageKey<[u32]>) => (keys.toHuman() as string[])[0].replace(/\D/g, '')
const formatLoanKey = (keys: StorageKey<[u32, u32]>) => (keys.toHuman() as string[])[1].replace(/\D/g, '')
@@ -1124,8 +1136,14 @@ export function getPoolsModule(inst: Centrifuge) {
)
}
- function getUserPermissions(args: [address: Account]) {
- const [address] = args
+ function getUserPermissions(
+ args: [address: T]
+ ): T extends Array ? Observable : Observable {
+ const [maybeArray] = args
+ const addresses = (Array.isArray(maybeArray) ? (maybeArray as Account[]) : [maybeArray as Account]).map(
+ (addr) => addressToHex(addr) as string
+ )
+ const addressSet = new Set(addresses)
const $api = inst.getApi()
const $events = inst.getEvents().pipe(
@@ -1135,59 +1153,76 @@ export function getPoolsModule(inst: Centrifuge) {
)
if (!event) return false
- const [accountId] = (event.toJSON() as any).event.data
- return isSameAddress(address, accountId)
+ const [accountId] = (event.toHuman() as any).event.data
+ return addressSet.has(addressToHex(accountId))
})
)
return $api.pipe(
- switchMap((api) => api.query.permissions.permission.entries(address)),
- map((permissionsData) => {
- const roles: {
- pools: {
- [poolId: string]: PoolRoles
- }
- currencies: {
- [currency: string]: {
- roles: CurrencyRole[]
- holder: boolean
- }
- }
- } = {
- pools: {},
- currencies: {},
- }
-
- permissionsData.forEach(([keys, value]) => {
- const key = (keys.toHuman() as any)[1] as { Pool: string } | { Currency: any }
- if ('Pool' in key) {
- const poolId = key.Pool.replace(/\D/g, '')
- const permissions = value.toJSON() as any
- roles.pools[poolId] = {
- roles: (
- [
- 'PoolAdmin',
- 'Borrower',
- 'PricingAdmin',
- 'LiquidityAdmin',
- 'InvestorAdmin',
- 'LoanAdmin',
- 'PODReadAccess',
+ switchMap((api) =>
+ api.query.permissions.permission.keys().pipe(
+ switchMap((keys) => {
+ const userKeys = keys
+ .map((key) => {
+ const [account, scope] = key.toHuman() as any as [string, { Pool: string } | { Currency: any }]
+ return [
+ addressToHex(account),
+ 'Pool' in scope ? { Pool: scope.Pool.replace(/\D/g, '') } : scope,
] as const
- ).filter((role) => AdminRoleBits[role] & permissions.poolAdmin.bits),
- tranches: {},
- }
- permissions.trancheInvestor.info
- .filter((info: any) => info.permissionedTill * 1000 > Date.now())
- .forEach((info: any) => {
- roles.pools[poolId].tranches[info.trancheId] = new Date(info.permissionedTill * 1000).toISOString()
})
- }
- })
- return roles
- }),
+ .filter(([account, scope]) => {
+ return 'Pool' in scope && addressSet.has(account)
+ })
+ return api.query.permissions.permission.multi(userKeys).pipe(
+ map((permissionsData) => {
+ const permissionsByAddressIndex: Permissions[] = []
+
+ function setPoolRoles(user: string, poolId: string, roles: PoolRoles) {
+ const i = addresses.indexOf(user)
+ const obj = permissionsByAddressIndex[i] ?? {
+ pools: {},
+ currencies: {},
+ }
+ obj.pools[poolId] = roles
+ permissionsByAddressIndex[i] = obj
+ }
+ permissionsData.forEach((value, i) => {
+ const [account, scope] = userKeys[i]
+ if ('Pool' in scope) {
+ const poolId = scope.Pool.replace(/\D/g, '')
+ const permissions = value.toJSON() as any
+ const roles: PoolRoles = {
+ roles: (
+ [
+ 'PoolAdmin',
+ 'Borrower',
+ 'PricingAdmin',
+ 'LiquidityAdmin',
+ 'InvestorAdmin',
+ 'LoanAdmin',
+ 'PODReadAccess',
+ ] as const
+ ).filter((role) => AdminRoleBits[role] & permissions.poolAdmin.bits),
+ tranches: {},
+ }
+ permissions.trancheInvestor.info
+ .filter((info: any) => info.permissionedTill * 1000 > Date.now())
+ .forEach((info: any) => {
+ roles.tranches[info.trancheId] = new Date(info.permissionedTill * 1000).toISOString()
+ })
+
+ setPoolRoles(account, poolId, roles)
+ }
+ })
+ return Array.isArray(maybeArray) ? permissionsByAddressIndex : permissionsByAddressIndex[0]
+ })
+ )
+ })
+ )
+ ),
+
repeatWhen(() => $events)
- )
+ ) as any
}
function getPoolPermissions(args: [poolId: string]) {
diff --git a/centrifuge-js/src/utils/index.ts b/centrifuge-js/src/utils/index.ts
index 8b5f3d027c..2d826349a2 100644
--- a/centrifuge-js/src/utils/index.ts
+++ b/centrifuge-js/src/utils/index.ts
@@ -114,7 +114,7 @@ export function getDateMonthsFromNow(month: number) {
return new Date(date.setMonth(date.getMonth() + month))
}
-export function addressToHex(addr: string) {
+export function addressToHex(addr: string | Uint8Array) {
return u8aToHex(decodeAddress(addr))
}
diff --git a/centrifuge-react/src/components/WalletProvider/WalletProvider.tsx b/centrifuge-react/src/components/WalletProvider/WalletProvider.tsx
index b6c8376f51..4097aa261c 100644
--- a/centrifuge-react/src/components/WalletProvider/WalletProvider.tsx
+++ b/centrifuge-react/src/components/WalletProvider/WalletProvider.tsx
@@ -41,6 +41,7 @@ export type WalletContextType = {
evmChainId?: number
accounts: SubstrateAccount[] | null
proxies: Record | undefined
+ proxiesAreLoading: boolean
multisigs: ComputedMultisig[]
combinedAccounts: CombinedSubstrateAccount[] | null
selectedAccount: SubstrateAccount | null
@@ -210,7 +211,7 @@ export function WalletProvider({
wallet: state.evm.selectedWallet as any,
}))
: null
- const { data: proxies } = useQuery(
+ const { data: proxies, isLoading: proxiesAreLoading } = useQuery(
[
'proxies',
state.substrate.accounts?.map((acc) => acc.address),
@@ -232,7 +233,7 @@ export function WalletProvider({
)
const delegatees = [...new Set(Object.values(proxies ?? {})?.flatMap((p) => p.map((d) => d.delegator)))]
- const { data: nestedProxies } = useQuery(
+ const { data: nestedProxies, isLoading: nestedProxiesAreLoading } = useQuery(
['nestedProxies', delegatees],
() => firstValueFrom(cent.proxies.getMultiUserProxies([delegatees])),
{
@@ -454,6 +455,7 @@ export function WalletProvider({
selectedProxies: selectedCombinedAccount?.proxies || null,
selectedMultisig: selectedCombinedAccount?.multisig || null,
proxies: combinedProxies,
+ proxiesAreLoading: nestedProxiesAreLoading || proxiesAreLoading,
subscanUrl,
},
evm: {
diff --git a/centrifuge-react/src/hooks/useCentrifugeQueries.ts b/centrifuge-react/src/hooks/useCentrifugeQueries.ts
deleted file mode 100644
index 59f5612e1d..0000000000
--- a/centrifuge-react/src/hooks/useCentrifugeQueries.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import Centrifuge from '@centrifuge/centrifuge-js'
-import * as React from 'react'
-import { useQueries, useQueryClient } from 'react-query'
-import { firstValueFrom, Observable } from 'rxjs'
-import { useCentrifuge } from '../components/CentrifugeProvider'
-import { useCentrifugeKey } from '../components/CentrifugeProvider/CentrifugeProvider'
-import { CentrifugeQueryOptions, getQuerySource } from './useCentrifugeQuery'
-
-type MultiQueryOptions = ({
- queryKey: readonly unknown[]
- queryCallback: (cent: Centrifuge) => Observable
-} & CentrifugeQueryOptions)[]
-
-// TODO: Fix infinite loop when receiving new data sometimes
-export function useCentrifugeQueries(
- queries: readonly [...MultiQueryOptions]
-): readonly [(T | null | undefined)[], (Observable | undefined)[]] {
- const cent = useCentrifuge()
- const centKey = useCentrifugeKey()
- const queryClient = useQueryClient()
-
- // Using react-query to cache the observables to ensure that all consumers subscribe to the same multicasted observable
- const sourceResults = useQueries(
- queries.map((query) => {
- const { queryKey, queryCallback, ...options } = query
- const { suspense, enabled = true } = options || {}
- return {
- queryKey: ['querySource', centKey, ...queryKey],
- queryFn: () => getQuerySource(cent, queryKey, queryCallback, options),
- suspense,
- staleTime: Infinity,
- enabled,
- }
- })
- )
-
- const dataResults = useQueries(
- queries.map((query, i) => {
- const { queryKey, queryCallback, ...options } = query
- const { suspense, enabled = true } = options || {}
- const $source = sourceResults[i].data
- return {
- queryKey: ['queryData', centKey, ...queryKey, !!$source],
- queryFn: () => ($source ? firstValueFrom($source) : null),
- suspense,
- // Infinite staleTime as useQueries here is only used to populate the cache initially and
- // to handle suspending the component when the suspense option is enabled.
- // Further data is subscribed to, and added to the cache, after the component has mounted.
- staleTime: Infinity,
- enabled: $source && enabled,
- retry: false,
- }
- })
- )
-
- React.useEffect(() => {
- const subs = sourceResults.map((r, i) =>
- r.data?.subscribe({
- next: (data) => {
- if (data) {
- const cached = queryClient.getQueryData(['queryData', centKey, ...queries[i].queryKey, true])
- if (cached !== data) {
- queryClient.setQueryData(['queryData', centKey, ...queries[i].queryKey, true], data)
- }
- }
- },
- })
- )
- return () => {
- subs.forEach((sub) => sub?.unsubscribe())
- }
- }, [sourceResults])
-
- return [dataResults.map((r) => r.data), sourceResults.map((r) => r.data)] as const
-}
diff --git a/centrifuge-react/src/index.ts b/centrifuge-react/src/index.ts
index 0dc9c137b2..668d1a01ee 100644
--- a/centrifuge-react/src/index.ts
+++ b/centrifuge-react/src/index.ts
@@ -6,7 +6,6 @@ export * from './components/WalletMenu'
export * from './components/WalletProvider'
export { useAsyncCallback } from './hooks/useAsyncCallback'
export { useBalances } from './hooks/useBalances'
-export * from './hooks/useCentrifugeQueries'
export * from './hooks/useCentrifugeQuery'
export { useCentrifugeTransaction } from './hooks/useCentrifugeTransaction'
export { useEns } from './hooks/useEns'