-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3a2ce95
commit f857b6c
Showing
8 changed files
with
381 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
} | ||
/> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
171
centrifuge-app/src/pages/IssuerPool/Investors/LiquidityPools.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
Oops, something went wrong.