Skip to content

Commit

Permalink
Add pool structure
Browse files Browse the repository at this point in the history
  • Loading branch information
kattylucy committed Nov 12, 2024
1 parent 073733d commit 5b0b723
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 30 deletions.
12 changes: 12 additions & 0 deletions centrifuge-app/src/components/Tooltips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,18 @@ export const tooltipText = {
label: 'Total NAV',
body: 'Total nav minus accrued fees',
},
oneTranche: {
label: '',
body: 'This pool will only have one investment class where all investors share the same level of risk and return.',
},
twoTranches: {
label: '',
body: 'This pool will have two classes. Senior tranche which has priority in receiving returns. And Junior tranche which is the last to receive returns (after Senior tranche obligations are met) but receives higher yield as compensation for the higher risk.',
},
threeTranches: {
label: '',
body: 'This pool will have three classes. Senior tranche is the safest tranche with priority in repayment. Mezzanine tranche has intermediate risk and receives payment after Senior tranche obligations are met. Junior tranche which only receives returns after both Senior and Mezzanine tranches are paid.',
},
}

export type TooltipsProps = {
Expand Down
206 changes: 186 additions & 20 deletions centrifuge-app/src/pages/IssuerCreatePool/PoolStructureSection.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
import { Box, Checkbox, Grid, IconHelpCircle, Text } from '@centrifuge/fabric'
import { Field, FieldProps } from 'formik'
import { useTheme } from 'styled-components'
import { PoolMetadataInput } from '@centrifuge/centrifuge-js'
import { Box, Checkbox, CurrencyInput, Grid, IconHelpCircle, Select, Text, TextInput } from '@centrifuge/fabric'
import { Field, FieldProps, useFormikContext } from 'formik'
import styled, { useTheme } from 'styled-components'
import { Tooltips, tooltipText } from '../../../src/components/Tooltips'
import { config } from '../../config'
import { validate } from './validate'

export const StyledGrid = styled(Grid)`
background-color: ${({ theme }) => theme.colors.backgroundSecondary};
padding: 40px;
border: ${({ theme }) => `1px solid ${theme.colors.borderPrimary}`};
border-radius: 8px;
`

export const StyledBox = styled(StyledGrid)``

const ASSET_CLASSES = Object.keys(config.assetClasses).map((key) => ({
label: key,
value: key,
}))

const CheckboxOption = ({
name,
label,
value,
disabled = false,
icon,
sublabel,
id,
}: {
name: string
label: string
sublabel?: string
value: string | number
disabled?: boolean
icon?: React.ReactNode
id?: keyof typeof tooltipText
}) => {
const theme = useTheme()
return (
Expand Down Expand Up @@ -47,7 +66,7 @@ const CheckboxOption = ({
/>
)}
</Field>
{icon && <Box ml={3}>{icon}</Box>}
{icon && <Tooltips type={id} label={<Box ml={3}>{icon}</Box>} />}
{sublabel && (
<Text variant="body2" color="textSecondary" style={{ marginLeft: 26, lineHeight: 'normal' }}>
{sublabel}
Expand All @@ -59,31 +78,51 @@ const CheckboxOption = ({

export const PoolStructureSection = () => {
const theme = useTheme()
const form = useFormikContext<PoolMetadataInput>()
const { values } = form

console.log(values)

const subAssetClasses =
config.assetClasses[form.values.assetClass]?.map((label) => ({
label,
value: label,
})) ?? []

const getTrancheName = (index: number) => {
switch (index) {
case 0:
return 'Junior'
case 1:
return values.tranches.length === 2 ? 'Senior' : 'Mezzanine'
case 2:
return 'Senior'
default:
return ''
}
}

const handleTrancheNameChange = (e: React.ChangeEvent<HTMLInputElement>, index: number, form: any) => {
const newValue = e.target.value
const poolName = values.poolName
const suffix = newValue.startsWith(poolName) ? newValue.substring(poolName.length).trim() : newValue
form.setFieldValue(`tranches.${index}.tokenName`, `${poolName} ${suffix}`)
}

return (
<div>
<Text variant="heading2" fontWeight={700}>
Pool Structure
</Text>
<Grid
backgroundColor={theme.colors.backgroundSecondary}
padding={4}
border={`1px solid ${theme.colors.borderPrimary}`}
mt={24}
borderRadius={8}
gridTemplateColumns={['1fr', '1fr 1fr']}
gap={3}
>
<StyledGrid gridTemplateColumns={['1fr', '1fr 1fr']} gap={3} mt={2}>
<Box>
<Text variant="body2">Pool type *</Text>

<CheckboxOption
name="poolStructure"
label="Revolving pool"
value="revolving"
sublabel="Dynamic and flexible pools that allow continuous inflows and outflows of assets. Investors can add or withdraw funds at any time, and the pool remains active indefinitely."
/>

<CheckboxOption
name="poolStructure"
label="Static pool (coming soon)"
Expand All @@ -92,32 +131,159 @@ export const PoolStructureSection = () => {
sublabel="Fixed pool of assets where funds remain locked. There are no continuous inflows or outflows during the investment period, and the pool has a defined maturity date."
/>
</Box>

<Box>
<Text variant="body2">Define tranche structure *</Text>

<CheckboxOption
name="trancheStructure"
label="Single tranche"
id="oneTranche"
value={1}
icon={<IconHelpCircle size="iconSmall" color={theme.colors.textSecondary} />}
/>

<CheckboxOption
name="trancheStructure"
label="Two tranches"
id="oneTranche"
value={2}
icon={<IconHelpCircle size="iconSmall" color={theme.colors.textSecondary} />}
/>

<CheckboxOption
name="trancheStructure"
label="Three tranches"
id="oneTranche"
value={3}
icon={<IconHelpCircle size="iconSmall" color={theme.colors.textSecondary} />}
/>
</Box>
</Grid>
</StyledGrid>
<Box mt={4} mb={3}>
<Text>Asset setup</Text>
<StyledGrid gridTemplateColumns={['1fr', '1fr 1fr']} gap={3} mt={3}>
<Box>
<Field name="assetClass" validate={validate.assetClass}>
{({ field, meta, form }: FieldProps) => (
<Select
name="assetClass"
label={
<Tooltips
type="assetClass"
label={<Text variant="heading4">Primary asset class*</Text>}
size="sm"
/>
}
onChange={(event) => {
form.setFieldValue('assetClass', event.target.value)
form.setFieldValue('subAssetClass', '', false)
}}
onBlur={field.onBlur}
errorMessage={meta.touched && meta.error ? meta.error : undefined}
value={field.value}
options={ASSET_CLASSES}
placeholder="Please select..."
/>
)}
</Field>
<Box mt={3}>
<Field name="assetDenomination" validate={validate.currency}>
{({ field, form, meta }: FieldProps) => {
return (
<Select
name="assetDenomination"
label={<Tooltips type="currency" label={<Text variant="heading4">Asset denomination*</Text>} />}
onChange={(event) => form.setFieldValue('assetDenomination', event.target.value)}
onBlur={field.onBlur}
errorMessage={meta.touched && meta.error ? meta.error : undefined}
value={field.value}
options={[
{ value: 'usdc', label: 'USDC' },
{ value: 'usdt', label: 'USDT (coming soon)', disabled: true },
{ value: 'dai', label: 'DAI (coming soon)', disabled: true },
]}
placeholder="Select..."
/>
)
}}
</Field>
</Box>
</Box>
<Box>
<Field name="subAssetClass" validate={validate.subAssetClass}>
{({ field, meta, form }: FieldProps) => (
<Select
name="subAssetClass"
label="Secondary asset class"
onChange={(event) => form.setFieldValue('subAssetClass', event.target.value)}
onBlur={field.onBlur}
errorMessage={meta.touched && meta.error ? meta.error : undefined}
value={field.value}
options={subAssetClasses}
placeholder="Select..."
/>
)}
</Field>
</Box>
</StyledGrid>
</Box>
<Box mt={4} mb={3}>
<Text>Tranches</Text>
{Array.from({ length: values.trancheStructure }).map((_, index) => (
<StyledBox key={index} mt={3}>
<Text variant="heading3">Tranche {index + 1}</Text>
<Box border={`.5px solid ${theme.colors.borderPrimary}`} width="100%" mt={2} mb={2} />
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gap={3}>
<Box>
<Field name={`tranches.${index}.tokenName`}>
{({ field, form }: FieldProps) => (
<TextInput
{...field}
label="Token name"
placeholder={getTrancheName(index)}
maxLength={30}
name={`tranches.${index}.tokenName`}
disabled={values.tranches.length === 1}
value={getTrancheName(index)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleTrancheNameChange(e, index, form)}
/>
)}
</Field>
<Box mt={3}>
<Field name={`tranches.${index}.minInvestment`} validate={validate.minInvestment}>
{({ field, form, meta }: FieldProps) => (
<CurrencyInput
{...field}
label={
<Tooltips type="minimumInvestment" label={<Text variant="heading4">Min. investment*</Text>} />
}
placeholder="0.00"
currency={values.currency}
errorMessage={meta.touched ? meta.error : undefined}
onChange={(value) => form.setFieldValue(field.name, value)}
onBlur={() => form.setFieldTouched(field.name, true)}
/>
)}
</Field>
</Box>
</Box>
<Box>
<Field name={`tranches.${index}.symbolName`} validate={validate.symbolName}>
{({ field, form, meta }: FieldProps) => (
<TextInput
{...field}
onChange={(e) => form.setFieldValue(field.name, e.target.value)}
errorMessage={meta.touched ? meta.error : undefined}
label={<Tooltips type="tokenSymbol" label={<Text variant="heading4">Token symbol*</Text>} />}
placeholder="4-12 characters"
minLength={4}
maxLength={12}
// disabled={isUpdating}
/>
)}
</Field>
</Box>
</Grid>
</StyledBox>
))}
</Box>
</div>
)
}
19 changes: 14 additions & 5 deletions centrifuge-app/src/pages/IssuerCreatePool/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export type CreatePoolValues = Omit<
PoolMetadataInput,
'poolIcon' | 'issuerLogo' | 'executiveSummary' | 'adminMultisig' | 'poolFees' | 'poolReport' | 'poolRatings'
> & {
trancheStructure: 1 | 2 | 3
poolStructure: string
assetDenomination: string

poolIcon: File | null
issuerLogo: File | null
executiveSummary: File | null
Expand Down Expand Up @@ -54,21 +58,27 @@ export type CreatePoolValues = Omit<
reportUrl?: string
reportFile?: File | null
}[]
poolStructure: string
}

export const initialValues: CreatePoolValues = {
poolIcon: null,
poolName: '',
// pool structure
poolStructure: '',
trancheStructure: 1,
assetClass: 'Private credit',
assetDenomination: '',
subAssetClass: '',

// pool structure -> tranches
tranches: [createEmptyTranche('')],

poolIcon: null,
poolName: '',
currency: isTestEnv ? 'USDC' : 'Native USDC',
maxReserve: 1000000,
epochHours: 23, // in hours
epochMinutes: 50, // in minutes
listed: !import.meta.env.REACT_APP_DEFAULT_UNLIST_POOLS,
investorType: '',
poolStructure: '',
issuerName: '',
issuerRepName: '',
issuerLogo: null,
Expand All @@ -88,7 +98,6 @@ export const initialValues: CreatePoolValues = {

poolRatings: [],

tranches: [createEmptyTranche('')],
adminMultisig: {
signers: [],
threshold: 1,
Expand Down
12 changes: 9 additions & 3 deletions centrifuge-js/src/modules/pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,18 +665,24 @@ export type PoolReport = {
}

export interface PoolMetadataInput {
// pool structure
poolStructure: string
trancheStructure: number
assetClass: 'Public credit' | 'Private credit'
subAssetClass: string
assetDenomination: string

// pool structure -> tranches

// details
poolIcon: FileType | null
poolName: string
assetClass: 'Public credit' | 'Private credit'
subAssetClass: string
currency: string
maxReserve: number | ''
epochHours: number | ''
epochMinutes: number | ''
listed?: boolean
investorType: string
poolStructure: string

// issuer
issuerName: string
Expand Down
2 changes: 1 addition & 1 deletion fabric/src/theme/tokens/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const grayScale = {
10: '#cfcfcf33',
50: '#F6F6F6',
100: '#E7E7E7',
300: '#6A7280',
300: '#CFCFCF',
500: '#91969B',
600: '#667085',
800: '#252B34',
Expand Down
2 changes: 1 addition & 1 deletion fabric/src/theme/tokens/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const statusPromoteBg = '#f8107114'
const colors = {
textPrimary: grayScale[800],
textSecondary: grayScale[500],
textDisabled: grayScale[300],
textDisabled: '#6A7280',
textInverted: 'white',
textGold: gold,

Expand Down

0 comments on commit 5b0b723

Please sign in to comment.