Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Centrifuge App: Only show pools with permissions in Issuer menu #1564

Merged
merged 4 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading