Skip to content

Commit

Permalink
Centrifuge App: Only show pools with permissions in Issuer menu (#1564)
Browse files Browse the repository at this point in the history
  • Loading branch information
onnovisser authored Sep 6, 2023
1 parent 2f6f481 commit 88eae03
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 173 deletions.
7 changes: 3 additions & 4 deletions centrifuge-app/src/components/Menu-deprecated/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ 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'
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')

Expand All @@ -38,7 +37,7 @@ export function Menu() {
<GovernanceMenu />

{(pools.length > 0 || config.poolCreationType === 'immediate') && (
<IssuerMenu defaultOpen={isXLarge} stacked={!isXLarge} poolIds={pools.map(({ id }) => id)}>
<IssuerMenu defaultOpen={isXLarge} stacked={!isXLarge}>
{isXLarge ? (
<Stack as="ul" gap={1}>
{!!pools.length &&
Expand Down
5 changes: 2 additions & 3 deletions centrifuge-app/src/components/Menu/IssuerMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand Down
18 changes: 8 additions & 10 deletions centrifuge-app/src/components/Menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ 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'
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')

Expand All @@ -38,15 +37,14 @@ export function Menu() {
<GovernanceMenu />

{(pools.length > 0 || config.poolCreationType === 'immediate') && (
<IssuerMenu defaultOpen={isLarge} stacked={!isLarge} poolIds={pools.map(({ id }) => id)}>
<IssuerMenu defaultOpen={isLarge} stacked={!isLarge}>
{isLarge ? (
<Stack as="ul" gap={1}>
{!!pools.length &&
pools.map((pool) => (
<Box key={pool.id} as="li" pl={4}>
<PoolLink pool={pool} />
</Box>
))}
{pools.map((pool) => (
<Box key={pool.id} as="li" pl={4}>
<PoolLink pool={pool} />
</Box>
))}
{address && config.poolCreationType === 'immediate' && (
<Shelf justifyContent="center" as="li" mt={1}>
<CreatePool />
Expand Down
58 changes: 29 additions & 29 deletions centrifuge-app/src/utils/usePermissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!]), {
Expand All @@ -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) {
Expand Down
131 changes: 83 additions & 48 deletions centrifuge-js/src/modules/pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, '')

Expand Down Expand Up @@ -1124,8 +1136,14 @@ export function getPoolsModule(inst: Centrifuge) {
)
}

function getUserPermissions(args: [address: Account]) {
const [address] = args
function getUserPermissions<T extends Account | Account[]>(
args: [address: T]
): T extends Array<Account> ? Observable<Permissions[]> : Observable<Permissions> {
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(
Expand All @@ -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]) {
Expand Down
2 changes: 1 addition & 1 deletion centrifuge-js/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type WalletContextType = {
evmChainId?: number
accounts: SubstrateAccount[] | null
proxies: Record<string, Proxy[]> | undefined
proxiesAreLoading: boolean
multisigs: ComputedMultisig[]
combinedAccounts: CombinedSubstrateAccount[] | null
selectedAccount: SubstrateAccount | null
Expand Down Expand Up @@ -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),
Expand All @@ -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])),
{
Expand Down Expand Up @@ -454,6 +455,7 @@ export function WalletProvider({
selectedProxies: selectedCombinedAccount?.proxies || null,
selectedMultisig: selectedCombinedAccount?.multisig || null,
proxies: combinedProxies,
proxiesAreLoading: nestedProxiesAreLoading || proxiesAreLoading,
subscanUrl,
},
evm: {
Expand Down
Loading

0 comments on commit 88eae03

Please sign in to comment.