Skip to content

Commit

Permalink
deploy pools
Browse files Browse the repository at this point in the history
  • Loading branch information
onnovisser committed Sep 29, 2023
1 parent 3a2ce95 commit f857b6c
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 107 deletions.
43 changes: 43 additions & 0 deletions centrifuge-app/src/components/PoolChangesBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Banner, Text } from '@centrifuge/fabric'
import * as React from 'react'
import { useLoanChanges, usePoolChanges } from '../utils/usePools'
import { RouterTextLink } from './TextLink'

export type PoolChangesBannerProps = {
poolId: string
}
const STORAGE_KEY = 'poolChangesBannerDismissedAt'

export function PoolChangesBanner({ poolId }: PoolChangesBannerProps) {
const changes = usePoolChanges(poolId)
const loanChanges = useLoanChanges(poolId)
const [isOpen, setIsOpen] = React.useState(false)

React.useEffect(() => {
const dismissedAt = new Date(localStorage.getItem(STORAGE_KEY) ?? 0)
if (
(changes && new Date(changes.submittedAt) > dismissedAt) ||
(loanChanges?.length && new Date(loanChanges.at(-1)!.submittedAt) > dismissedAt)
) {
setIsOpen(true)
}
}, [changes, loanChanges])

function onClose() {
localStorage.setItem(STORAGE_KEY, new Date(Date.now()).toISOString())
setIsOpen(false)
}

return (
<Banner
isOpen={isOpen}
onClose={onClose}
title={
<Text as="h3" color="textInverted" variant="heading5">
There are pending pool changes that can now be enabled{' '}
<RouterTextLink to={`/issuer/${poolId}/configuration`}>here</RouterTextLink>
</Text>
}
/>
)
}
126 changes: 126 additions & 0 deletions centrifuge-app/src/components/Swaps/Swap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { CurrencyBalance, CurrencyKey, findBalance, findCurrency, Rate } from '@centrifuge/centrifuge-js'
import { useBalances, useCentrifugeApi, useCentrifugeTransaction } from '@centrifuge/centrifuge-react'
import { CentrifugeTransactionOptions } from '@centrifuge/centrifuge-react/dist/hooks/useCentrifugeTransaction'
import { Button, Card, CurrencyInput, SelectInner, Stack } from '@centrifuge/fabric'
import Decimal from 'decimal.js-light'
import * as React from 'react'
import { Dec } from '../../utils/Decimal'
import { formatBalance, roundDown } from '../../utils/formatting'
import { useCurrencies } from '../../utils/useCurrencies'
import { useSuitableAccountPicker } from '../../utils/usePermissions'

type SwapProps = {
defaultBuy?: CurrencyKey
defaultSell?: CurrencyKey
}

export function Swap({ defaultBuy, defaultSell }: SwapProps) {
const [account, accountPicker] = useSuitableAccountPicker({})
const [buyCurrencyKey, setBuyCurrency] = React.useState(defaultBuy)
const [sellCurrencyKey, setSellCurrency] = React.useState(defaultSell)
const [buy, setBuy] = React.useState(0)
const [sell, setSell] = React.useState<number | Decimal>(0)
const [price, setPrice] = React.useState(1)
const [lastChanged, setLastChanged] = React.useState<'buy' | 'sell'>('buy')
const api = useCentrifugeApi()
const balances = useBalances(account?.actingAddress)

const sellBalance =
(balances && sellCurrencyKey && findBalance(balances.currencies, sellCurrencyKey)?.balance.toDecimal()) || Dec(0)

const allCurrencies = useCurrencies()
const currencies = allCurrencies?.filter((cur) => typeof cur.key === 'object' && 'ForeignAsset' in cur.key)

const { execute, isLoading } = useCentrifugeTransaction(
'Place order',
(cent) => (_: [], options?: CentrifugeTransactionOptions) => {
const buyDec = lastChanged === 'buy' ? buy : Dec(sell).div(price)
const buyAmount = CurrencyBalance.fromFloat(buyDec, buyCurrency!.decimals)
const buyPrice = Rate.fromFloat(price)
console.log('buy', buy, buyAmount, buyCurrencyKey, sellCurrencyKey)
return cent.wrapSignAndSend(
api,
api.tx.orderBook.createOrderV1(buyCurrencyKey, sellCurrencyKey, buyAmount, buyPrice),
options
)
}
)

const buyCurrency = buyCurrencyKey ? findCurrency(currencies ?? [], buyCurrencyKey) : undefined
const sellCurrency = sellCurrencyKey ? findCurrency(currencies ?? [], sellCurrencyKey) : undefined

const buyAmount = lastChanged === 'buy' ? buy : toNumber(sell) / price
const sellAmount = lastChanged === 'sell' ? toNumber(sell) : buy * price

return (
<Card p={2}>
<Stack gap={1}>
{accountPicker}
<CurrencyInput
label="Buy"
value={buyAmount}
onChange={(val) => {
setBuy(val)
setSell(val * price)
setLastChanged('buy')
}}
currency={
<SelectInner
value={buyCurrency?.symbol ?? ''}
options={[
{ label: 'Select', value: '', disabled: true },
...(currencies?.map((cur) => ({ label: cur.symbol, value: cur.symbol })) ?? []),
]}
onChange={(e) => {
const key = currencies?.find((cur) => cur.symbol === e.target.value)?.key
if (key) setBuyCurrency(key)
}}
style={{ textAlign: 'right' }}
/>
}
/>
<CurrencyInput
label="Price"
value={price}
onChange={(val) => {
setPrice(val)
}}
currency={sellCurrency?.symbol}
/>
<CurrencyInput
label="Sell"
value={sellAmount}
onChange={(val) => {
setSell(val)
setLastChanged('sell')
}}
secondaryLabel={sellCurrency && `${formatBalance(roundDown(sellBalance), sellCurrency, 2)} available`}
onSetMax={() => {
setSell(sellBalance)
setLastChanged('sell')
}}
currency={
<SelectInner
value={sellCurrency?.symbol ?? ''}
options={[
{ label: 'Select', value: '' },
...(currencies?.map((cur) => ({ label: cur.symbol, value: cur.symbol })) ?? []),
]}
onChange={(e) => {
const key = currencies?.find((cur) => cur.symbol === e.target.value)?.key
if (key) setSellCurrency(key)
}}
style={{ textAlign: 'right' }}
/>
}
/>
<Button loading={isLoading} onClick={() => execute([], { account })}>
Submit
</Button>
</Stack>
</Card>
)
}
function toNumber(n: '' | number | Decimal) {
return n instanceof Decimal ? n.toNumber() : Number(n)
}
171 changes: 98 additions & 73 deletions centrifuge-app/src/pages/IssuerPool/Investors/LiquidityPools.tsx
Original file line number Diff line number Diff line change
@@ -1,106 +1,131 @@
import {
useCentrifuge,
useCentrifugeTransaction,
useGetNetworkName,
useNetworkName,
useWallet,
} from '@centrifuge/centrifuge-react'
import { Button, Text } from '@centrifuge/fabric'
import { useCentrifugeTransaction, useGetNetworkName, useNetworkName } from '@centrifuge/centrifuge-react'
import { Accordion, Button, Stack } from '@centrifuge/fabric'
import React from 'react'
import { useQuery } from 'react-query'
import { useParams } from 'react-router'
import { DataTable } from '../../../components/DataTable'
import { PageSection } from '../../../components/PageSection'
import { useActiveDomains, useDomainRouters } from '../../../utils/useLiquidityPools'
import { useEvmTransaction } from '../../../utils/tinlake/useEvmTransaction'
import { Domain, useActiveDomains } from '../../../utils/useLiquidityPools'
import { useSuitableAccounts } from '../../../utils/usePermissions'
import { usePool } from '../../../utils/usePools'

type Row = { chainId: number }
function getDomainStatus(domain: Domain) {
if (!domain.isActive) {
return 'inactive'
}
if (Object.values(domain.liquidityPools).every((t) => Object.values(t).every((p) => !!p))) {
return 'deployed'
}
return 'deploying'
}

export function LiquidityPools() {
const { pid: poolId } = useParams<{ pid: string }>()
const pool = usePool(poolId)
const domains = useDomainRouters()
const { data: domains } = useActiveDomains(poolId)
const getName = useGetNetworkName()

const titles = {
inactive: 'Not active',
deploying: 'Action needed',
deployed: 'Active',
}

return (
<PageSection
title="Connected blockchains"
subtitle="View liquidity on all blockchains that this pool is connected to, and enable investments on new blockchains."
>
{pool?.tranches && domains && (
<DataTable
data={domains}
columns={[
{
align: 'left',
header: 'Blockchain',
cell: (row: Row) => <Text variant="body2">{getName(row.chainId)}</Text>,
flex: '4',
},
{
align: 'center',
header: '',
cell: (row: Row) => <EnableButton poolId={poolId} chainId={row.chainId} />,
flex: '1',
},
]}
/>
)}
<Accordion
items={
domains?.map((domain) => ({
title: (
<>
{getName(domain.chainId)} {titles[getDomainStatus(domain)]}
</>
),
body: <PoolDomain poolId={poolId} domain={domain} />,
})) ?? []
}
/>
</PageSection>
)
}

function EnableButton({ poolId, chainId }: { poolId: string; chainId: number }) {
function PoolDomain({ poolId, domain }: { poolId: string; domain: Domain }) {
const pool = usePool(poolId)
const {
evm: { getProvider },
} = useWallet()
const cent = useCentrifuge()
const [account] = useSuitableAccounts({ poolId, poolRole: ['PoolAdmin'] })

const { data: domains } = useActiveDomains(poolId)
const managerAddress = domains?.find((d) => d.chainId === chainId)?.managerAddress
const { data: isEnabled, isLoading: isFetching } = useQuery(
['poolLps', poolId],
async () => {
try {
await Promise.any(
pool.tranches.map((t) =>
cent.liquidityPools
.getLiquidityPools([managerAddress!, poolId, t.id, chainId], {
rpcProvider: getProvider(chainId),
})
.then((r) => {
if (!r.length) throw new Error('tranche not enabled')
})
)
)
return true
} catch {
return false
}
},
{
enabled: !!managerAddress,
staleTime: Infinity,
}
return (
<Stack>
<EnableButton poolId={poolId} domain={domain} />
{pool.tranches.map((t) => (
<>
{domain.undeployedTranches[t.id] && <DeployTrancheButton poolId={poolId} trancheId={t.id} domain={domain} />}
{domain.currencies.map((currency, i) => (
<>
{domain.trancheTokenExists[t.id] && !domain.liquidityPools[t.id][currency.address] && (
<DeployLPButton poolId={poolId} trancheId={t.id} domain={domain} currencyIndex={i} />
)}
</>
))}
</>
))}
</Stack>
)
}

const name = useNetworkName(chainId)
const { execute, isLoading } = useCentrifugeTransaction(
`Enable ${name}`,
(cent) => cent.liquidityPools.enablePoolOnDomain
function DeployTrancheButton({ poolId, trancheId, domain }: { poolId: string; trancheId: string; domain: Domain }) {
const pool = usePool(poolId)

const { execute, isLoading } = useEvmTransaction(`Deploy tranche`, (cent) => cent.liquidityPools.deployTranche)
const tranche = pool.tranches.find((t) => t.id === trancheId)!

return (
<Button loading={isLoading} onClick={() => execute([domain.managerAddress, poolId, trancheId])} small>
Deploy tranche: {tranche.currency.name}
</Button>
)
}

function DeployLPButton({
poolId,
trancheId,
currencyIndex,
domain,
}: {
poolId: string
trancheId: string
domain: Domain
currencyIndex: number
}) {
const pool = usePool(poolId)

const { execute, isLoading } = useEvmTransaction(
`Deploy liquidity pool`,
(cent) => cent.liquidityPools.deployLiquidityPool
)
const tranche = pool.tranches.find((t) => t.id === trancheId)!

return (
<Button
disabled={isEnabled}
loading={isLoading || isFetching}
onClick={() => execute([poolId, chainId], { account })}
loading={isLoading}
onClick={() => execute([domain.managerAddress, poolId, trancheId, domain.currencies[currencyIndex].address])}
small
>
{isEnabled ? 'Enabled' : 'Enable'}
Deploy tranche/currency liquidity pool: {tranche.currency.name} / {domain.currencies[currencyIndex].name}
</Button>
)
}

function EnableButton({ poolId, domain }: { poolId: string; domain: Domain }) {
const [account] = useSuitableAccounts({ poolId, poolRole: ['PoolAdmin'] })
const name = useNetworkName(domain.chainId)
const { execute, isLoading } = useCentrifugeTransaction(
`Enable ${name}`,
(cent) => cent.liquidityPools.enablePoolOnDomain
)

return (
<Button loading={isLoading} onClick={() => execute([poolId, domain.chainId], { account })} small>
Enable
</Button>
)
}
Loading

0 comments on commit f857b6c

Please sign in to comment.