diff --git a/centrifuge-app/src/pages/IssuerCreatePool/useStoredIssuer.ts b/centrifuge-app/src/pages/IssuerCreatePool/useStoredIssuer.ts
index e227208301..2a22860739 100644
--- a/centrifuge-app/src/pages/IssuerCreatePool/useStoredIssuer.ts
+++ b/centrifuge-app/src/pages/IssuerCreatePool/useStoredIssuer.ts
@@ -16,7 +16,7 @@ export function useStoredIssuer() {
if (!allPools || !permissions) {
return []
}
- return allPools.filter(({ id, metadata }) => permissions?.pools[id]?.roles.includes('PoolAdmin') && metadata)
+ return allPools.filter(({ id, metadata }) => permissions?.pools[id]?.roles.includes('InvestorAdmin') && metadata)
}, [allPools, permissions])
const { data, isLoading } = usePoolMetadata(pools[0])
diff --git a/centrifuge-app/src/pages/IssuerPool/Access/PoolManagers.tsx b/centrifuge-app/src/pages/IssuerPool/Access/PoolManagers.tsx
index be478809f2..80ebac3ffd 100644
--- a/centrifuge-app/src/pages/IssuerPool/Access/PoolManagers.tsx
+++ b/centrifuge-app/src/pages/IssuerPool/Access/PoolManagers.tsx
@@ -106,9 +106,9 @@ export function PoolManagers({ poolId }: { poolId: string }) {
storedManagerPermissions,
values.adminMultisig.signers.map((address) => ({
address,
- roles: { MemberListAdmin: true, LiquidityAdmin: true },
+ roles: { InvestorAdmin: true, LiquidityAdmin: true },
})),
- ['LiquidityAdmin', 'MemberListAdmin']
+ ['LiquidityAdmin', 'InvestorAdmin']
),
newPoolMetadata,
],
diff --git a/centrifuge-app/src/pages/IssuerPool/Configuration/Admins.tsx b/centrifuge-app/src/pages/IssuerPool/Configuration/Admins.tsx
index d933ceef00..ef6a72dc4a 100644
--- a/centrifuge-app/src/pages/IssuerPool/Configuration/Admins.tsx
+++ b/centrifuge-app/src/pages/IssuerPool/Configuration/Admins.tsx
@@ -12,7 +12,7 @@ import { Tooltips } from '../../../components/Tooltips'
import { usePoolPermissions, useSuitableAccounts } from '../../../utils/usePermissions'
import { AddAddressInput } from './AddAddressInput'
-type AdminRole = 'PoolAdmin' | 'Borrower' | 'PricingAdmin' | 'LiquidityAdmin' | 'MemberListAdmin' | 'LoanAdmin'
+type AdminRole = 'PoolAdmin' | 'Borrower' | 'PricingAdmin' | 'LiquidityAdmin' | 'InvestorAdmin' | 'LoanAdmin'
type Admin = {
address: string
@@ -157,7 +157,7 @@ export function Admins({ poolId }: { poolId: string }) {
header: ,
cell: (row: Row) => (
[admin.address, admin.roles]))
diff --git a/centrifuge-app/src/pages/IssuerPool/Configuration/ViewLoanTemplate.tsx b/centrifuge-app/src/pages/IssuerPool/Configuration/ViewLoanTemplate.tsx
index e1a5c49e85..7b2d587ad1 100644
--- a/centrifuge-app/src/pages/IssuerPool/Configuration/ViewLoanTemplate.tsx
+++ b/centrifuge-app/src/pages/IssuerPool/Configuration/ViewLoanTemplate.tsx
@@ -18,7 +18,7 @@ export const ViewLoanTemplate: React.FC = () => {
const { pid: poolId, sid: templateId } = useParams<{ pid: string; sid: string }>()
const pool = usePool(poolId)
const { data: poolMetadata } = usePoolMetadata(pool)
- const { data: templateData } = useMetadata(`ipfs://ipfs/${templateId}`)
+ const { data: templateData } = useMetadata(`ipfs://${templateId}`)
return (
<>
diff --git a/centrifuge-app/src/pages/IssuerPool/Investors/InvestorStatus.tsx b/centrifuge-app/src/pages/IssuerPool/Investors/InvestorStatus.tsx
index 112b1e2bb9..b0adecf3d6 100644
--- a/centrifuge-app/src/pages/IssuerPool/Investors/InvestorStatus.tsx
+++ b/centrifuge-app/src/pages/IssuerPool/Investors/InvestorStatus.tsx
@@ -31,7 +31,7 @@ export const InvestorStatus: React.FC = () => {
const permissions = usePermissions(validAddress)
const [pendingTrancheId, setPendingTrancheId] = React.useState('')
- const [account] = useSuitableAccounts({ poolId, poolRole: ['MemberListAdmin'] })
+ const [account] = useSuitableAccounts({ poolId, poolRole: ['InvestorAdmin'] })
const { execute, isLoading: isTransactionPending } = useCentrifugeTransaction(
'Update investor',
diff --git a/centrifuge-app/src/pages/IssuerPool/Investors/OnboardingSettings.tsx b/centrifuge-app/src/pages/IssuerPool/Investors/OnboardingSettings.tsx
index 1016235f3c..6140e8ddc0 100644
--- a/centrifuge-app/src/pages/IssuerPool/Investors/OnboardingSettings.tsx
+++ b/centrifuge-app/src/pages/IssuerPool/Investors/OnboardingSettings.tsx
@@ -205,15 +205,15 @@ export const OnboardingSettings = () => {
},
}
- const memberlistAdmin = import.meta.env.REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY
- const hasMemberlistPermissions = permissions?.[addressToHex(memberlistAdmin)]?.roles.includes('MemberListAdmin')
+ const investorAdmin = import.meta.env.REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY
+ const hasMemberlistPermissions = permissions?.[addressToHex(investorAdmin)]?.roles.includes('InvestorAdmin')
const isAnyTrancheOpen = Object.values(values.openForOnboarding).includes(true)
if (!useExternalUrl && isAnyTrancheOpen && !hasMemberlistPermissions) {
// pool is open for onboarding and onboarding-api proxy is not in pool permissions
- updatePermissionAndConfigTx([[[memberlistAdmin, 'MemberListAdmin']], [], amendedMetadata])
+ updatePermissionAndConfigTx([[[investorAdmin, 'InvestorAdmin']], [], amendedMetadata])
} else if (hasMemberlistPermissions && (useExternalUrl || !isAnyTrancheOpen)) {
// remove onboarding-api proxy from pool permissions
- updatePermissionAndConfigTx([[], [[memberlistAdmin, 'MemberListAdmin']], amendedMetadata])
+ updatePermissionAndConfigTx([[], [[investorAdmin, 'InvestorAdmin']], amendedMetadata])
} else {
updateConfigTx([poolId, amendedMetadata], { account })
}
diff --git a/centrifuge-app/src/pages/IssuerPool/Investors/index.tsx b/centrifuge-app/src/pages/IssuerPool/Investors/index.tsx
index b9536fcb10..0573531a1f 100644
--- a/centrifuge-app/src/pages/IssuerPool/Investors/index.tsx
+++ b/centrifuge-app/src/pages/IssuerPool/Investors/index.tsx
@@ -22,7 +22,7 @@ export function IssuerPoolInvestorsPage() {
function IssuerPoolInvestors() {
const { pid: poolId } = useParams<{ pid: string }>()
- const canEditInvestors = useSuitableAccounts({ poolId, poolRole: ['MemberListAdmin'] }).length > 0
+ const canEditInvestors = useSuitableAccounts({ poolId, poolRole: ['InvestorAdmin'] }).length > 0
const isPoolAdmin = useSuitableAccounts({ poolId, poolRole: ['PoolAdmin'] }).length > 0
return (
diff --git a/centrifuge-app/src/pages/Loan/index.tsx b/centrifuge-app/src/pages/Loan/index.tsx
index adda7d348a..d46db1d9fa 100644
--- a/centrifuge-app/src/pages/Loan/index.tsx
+++ b/centrifuge-app/src/pages/Loan/index.tsx
@@ -74,7 +74,7 @@ const Loan: React.FC = () => {
const imageUrl = nftMetadata?.image ? cent.metadata.parseMetadataUrl(nftMetadata.image) : ''
const { data: templateData } = useMetadata(
- nftMetadata?.properties?._template && `ipfs://ipfs/${nftMetadata?.properties?._template}`
+ nftMetadata?.properties?._template && `ipfs://${nftMetadata?.properties?._template}`
)
const documentId = useNftDocumentId(nft?.collectionId, nft?.id)
diff --git a/centrifuge-app/src/utils/parseMetadataUrl.ts b/centrifuge-app/src/utils/parseMetadataUrl.ts
index d8d8e8c9f1..dcf9c117bd 100644
--- a/centrifuge-app/src/utils/parseMetadataUrl.ts
+++ b/centrifuge-app/src/utils/parseMetadataUrl.ts
@@ -16,8 +16,10 @@ export function parseMetadataUrl(url: string) {
if (!url.includes(':')) {
// string without protocol is assumed to be an IPFS hash
newUrl = new URL(`ipfs/${url}`, IFPS_GATEWAY)
+ } else if (url.startsWith('ipfs://ipfs/')) {
+ newUrl = new URL(url.slice(12), IFPS_GATEWAY)
} else if (url.startsWith('ipfs://')) {
- newUrl = new URL(url.substr(7), IFPS_GATEWAY)
+ newUrl = new URL(url.slice(7), IFPS_GATEWAY)
} else {
newUrl = new URL(url)
}
diff --git a/centrifuge-app/src/utils/usePermissions.ts b/centrifuge-app/src/utils/usePermissions.ts
index aaade41b6b..0fb5226fd3 100644
--- a/centrifuge-app/src/utils/usePermissions.ts
+++ b/centrifuge-app/src/utils/usePermissions.ts
@@ -225,13 +225,13 @@ export function usePoolAccess(poolId: string) {
: []
const missingAdminPermissions = diffPermissions(
[storedAdminRoles],
- [{ address: storedAdminRoles.address, roles: { MemberListAdmin: true } }]
+ [{ address: storedAdminRoles.address, roles: { InvestorAdmin: true } }]
).add
const missingManagerPermissions = diffPermissions(
storedManagerPermissions,
(multisig?.signers || adminDelegates?.map((p) => p.delegatee))?.map((address) => ({
address,
- roles: { MemberListAdmin: true, LiquidityAdmin: true },
+ roles: { InvestorAdmin: true, LiquidityAdmin: true },
})) || []
).add
diff --git a/centrifuge-js/src/modules/metadata.ts b/centrifuge-js/src/modules/metadata.ts
index 4e87e1b5ec..9c8b01f1b4 100644
--- a/centrifuge-js/src/modules/metadata.ts
+++ b/centrifuge-js/src/modules/metadata.ts
@@ -38,8 +38,10 @@ export function getMetadataModule(inst: Centrifuge) {
if (!url.includes(':')) {
// string without protocol is assumed to be an IPFS hash
newUrl = new URL(`ipfs/${url.replace(/\/?(ipfs\/)/, '')}`, inst.config.metadataHost)
+ } else if (url.startsWith('ipfs://ipfs/')) {
+ newUrl = new URL(`ipfs/${url.slice(12)}`, inst.config.metadataHost)
} else if (url.startsWith('ipfs://')) {
- newUrl = new URL(`ipfs/${url.substr(7)}`, inst.config.metadataHost)
+ newUrl = new URL(`ipfs/${url.slice(7)}`, inst.config.metadataHost)
} else {
newUrl = new URL(url)
}
@@ -56,11 +58,8 @@ export function getMetadataModule(inst: Centrifuge) {
const IPFS_HASH_LENGTH = 46
function parseIPFSHash(uri: string) {
- if (uri.includes('ipfs://')) {
- const hash = uri
- .split(/ipfs:\/\/ipfs\//)
- .filter(Boolean)
- .join()
+ if (uri.startsWith('ipfs://')) {
+ const hash = uri.slice(7)
return { uri, ipfsHash: hash }
} else if (!uri.includes('/') && uri.length === IPFS_HASH_LENGTH) {
return { uri: `ipfs://${uri}`, ipfsHash: uri }
diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts
index b418932329..5766f2c335 100644
--- a/centrifuge-js/src/modules/pools.ts
+++ b/centrifuge-js/src/modules/pools.ts
@@ -19,7 +19,7 @@ import { Dec } from '../utils/Decimal'
const PerquintillBN = new BN(10).pow(new BN(18))
const PriceBN = new BN(10).pow(new BN(27))
-type AdminRole = 'PoolAdmin' | 'Borrower' | 'PricingAdmin' | 'LiquidityAdmin' | 'MemberListAdmin' | 'LoanAdmin'
+type AdminRole = 'PoolAdmin' | 'Borrower' | 'PricingAdmin' | 'LiquidityAdmin' | 'InvestorAdmin' | 'LoanAdmin'
type CurrencyRole = 'PermissionedAssetManager' | 'PermissionedAssetIssuer'
@@ -41,7 +41,7 @@ const AdminRoleBits = {
Borrower: 0b00000010,
PricingAdmin: 0b00000100,
LiquidityAdmin: 0b00001000,
- MemberListAdmin: 0b00010000,
+ InvestorAdmin: 0b00010000,
LoanAdmin: 0b00100000,
}
@@ -596,7 +596,7 @@ export function getPoolsModule(inst: Centrifuge) {
) {
if (options?.paymentInfo) {
const hash = '0'.repeat(46)
- return of({ uri: `ipfs://ipfs/${hash}`, ipfsHash: hash })
+ return of({ uri: `ipfs://${hash}`, ipfsHash: hash })
}
const tranchesById: PoolMetadata['tranches'] = {}
@@ -689,7 +689,7 @@ export function getPoolsModule(inst: Centrifuge) {
const submittable = api.tx.utility.batchAll([
...add.map(([addr, role]) =>
api.tx.permissions.add(
- { PoolRole: typeof role === 'string' ? 'PoolAdmin' : 'MemberListAdmin' },
+ { PoolRole: typeof role === 'string' ? 'PoolAdmin' : 'InvestorAdmin' },
addr,
{ Pool: poolId },
{ PoolRole: role }
@@ -697,7 +697,7 @@ export function getPoolsModule(inst: Centrifuge) {
),
...sortedRemove.map(([addr, role]) =>
api.tx.permissions.remove(
- { PoolRole: typeof role === 'string' ? 'PoolAdmin' : 'MemberListAdmin' },
+ { PoolRole: typeof role === 'string' ? 'PoolAdmin' : 'InvestorAdmin' },
addr,
{ Pool: poolId },
{ PoolRole: role }
@@ -973,7 +973,7 @@ export function getPoolsModule(inst: Centrifuge) {
const permissions = value.toJSON() as any
roles.pools[poolId] = {
roles: (
- ['PoolAdmin', 'Borrower', 'PricingAdmin', 'LiquidityAdmin', 'MemberListAdmin', 'LoanAdmin'] as const
+ ['PoolAdmin', 'Borrower', 'PricingAdmin', 'LiquidityAdmin', 'InvestorAdmin', 'LoanAdmin'] as const
).filter((role) => AdminRoleBits[role] & permissions.poolAdmin.bits),
tranches: {},
}
@@ -1028,7 +1028,7 @@ export function getPoolsModule(inst: Centrifuge) {
const permissions = value.toJSON() as any
roles[account] = {
roles: (
- ['PoolAdmin', 'Borrower', 'PricingAdmin', 'LiquidityAdmin', 'MemberListAdmin', 'LoanAdmin'] as const
+ ['PoolAdmin', 'Borrower', 'PricingAdmin', 'LiquidityAdmin', 'InvestorAdmin', 'LoanAdmin'] as const
).filter((role) => AdminRoleBits[role] & permissions.poolAdmin.bits),
tranches: {},
}
@@ -2298,10 +2298,12 @@ export function findBalance>(
}
function parseCurrencyKey(key: CurrencyKey): CurrencyKey {
- if (typeof key === 'string' || 'ForeignAsset' in key) return key
- return {
- Tranche: [key.Tranche[0].replace(/\D/g, ''), key.Tranche[1]],
+ if (typeof key !== 'string' && 'Tranche' in key) {
+ return {
+ Tranche: [key.Tranche[0].replace(/\D/g, ''), key.Tranche[1]],
+ }
}
+ return key
}
function looksLike(a: any, b: any): boolean {
diff --git a/centrifuge-react/src/components/Transactions/TransactionToasts.tsx b/centrifuge-react/src/components/Transactions/TransactionToasts.tsx
index 11778bd3f8..2f2e7be3d8 100644
--- a/centrifuge-react/src/components/Transactions/TransactionToasts.tsx
+++ b/centrifuge-react/src/components/Transactions/TransactionToasts.tsx
@@ -20,6 +20,7 @@ const toastSublabel = {
}
const TOAST_DURATION = 10000
+const ERROR_TOAST_DURATION = 60000
export type TransactionToastsProps = {
positionProps?: {
@@ -44,7 +45,7 @@ export function TransactionToasts({
const explorer = useGetExplorerUrl()
return (
-
+
{transactions
.filter((tx) => !tx.dismissed && !['creating', 'unconfirmed'].includes(tx.status))
.map((tx) => {
@@ -56,8 +57,8 @@ export function TransactionToasts({
status={toastStatus[tx.status]}
onDismiss={dismiss(tx.id)}
onStatusChange={(newStatus) => {
- if (['ok'].includes(newStatus)) {
- setTimeout(dismiss(tx.id), TOAST_DURATION)
+ if (['ok', 'critical'].includes(newStatus)) {
+ setTimeout(dismiss(tx.id), newStatus === 'ok' ? TOAST_DURATION : ERROR_TOAST_DURATION)
}
}}
action={
diff --git a/onboarding-api/README.md b/onboarding-api/README.md
index f3d9b7b706..68f0777508 100644
--- a/onboarding-api/README.md
+++ b/onboarding-api/README.md
@@ -351,7 +351,7 @@ Sets the ultimate beneficial owners for the entity.
Once onboarding is complete a final tx will be signed by the server which will whtielist investors. For this, a pure proxy must be created and sufficiently funded for each chain environment. The pure proxy only has to be created once and can be used for all pools.
-After creating the pure proxy, it must then be given `MemberListAdmin` permissions for each pool by the address with `PoolAdmin` permissions.
+After creating the pure proxy, it must then be given `InvestorAdmin` permissions for each pool by the address with `PoolAdmin` permissions.
## Endpoints
diff --git a/onboarding-api/src/utils/centrifuge.ts b/onboarding-api/src/utils/centrifuge.ts
index 1ea30957c1..0c98e14437 100644
--- a/onboarding-api/src/utils/centrifuge.ts
+++ b/onboarding-api/src/utils/centrifuge.ts
@@ -23,7 +23,7 @@ export const getSigner = async () => {
await cryptoWaitReady()
const keyring = new Keyring({ type: 'sr25519', ss58Format: 2 })
// the pure proxy controller (PURE_PROXY_CONTROLLER_SEED) is the wallet that controls the pure proxy being used to sign the transaction
- // the pure proxy address (MEMBERLIST_ADMIN_PURE_PROXY) has to be given MemberListAdmin permissions on each pool before being able to whitelist investors
+ // the pure proxy address (MEMBERLIST_ADMIN_PURE_PROXY) has to be given InvestorAdmin permissions on each pool before being able to whitelist investors
return keyring.addFromMnemonic(process.env.PURE_PROXY_CONTROLLER_SEED)
}
@@ -48,7 +48,7 @@ export const addCentInvestorToMemberList = async (walletAddress: string, poolId:
api.pipe(
switchMap((api) => {
const submittable = api.tx.permissions.add(
- { PoolRole: 'MemberListAdmin' },
+ { PoolRole: 'InvestorAdmin' },
walletAddress,
{ Pool: poolId },
{ PoolRole: { TrancheInvestor: [trancheId, OneHundredYearsFromNow] } }
@@ -56,7 +56,7 @@ export const addCentInvestorToMemberList = async (walletAddress: string, poolId:
if (metadata?.onboarding?.podReadAccess) {
const address = cent.utils.formatAddress(walletAddress)
const podSubmittable = api.tx.permissions.add(
- { PoolRole: 'MemberListAdmin' },
+ { PoolRole: 'InvestorAdmin' },
address,
{ Pool: poolId },
{ PoolRole: 'PODReadAccess' }