From 93f815e73a8958bdf7a96cacffca6e7234318b01 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Thu, 10 Aug 2023 17:15:44 +0200 Subject: [PATCH 01/16] feat: Portfolio page - add content structure - create AccountTokens - create AccountTransactions - create getAllTransactions related to one address --- .../src/components/AddressTokens.tsx | 15 +++++ .../src/components/AddressTransactions.tsx | 19 ++++++ centrifuge-app/src/components/Root.tsx | 4 ++ centrifuge-app/src/pages/Portfolio.tsx | 59 +++++++++++++++++++ centrifuge-app/src/utils/usePools.ts | 12 ++++ centrifuge-js/src/modules/pools.ts | 54 +++++++++++++++++ 6 files changed, 163 insertions(+) create mode 100644 centrifuge-app/src/components/AddressTokens.tsx create mode 100644 centrifuge-app/src/components/AddressTransactions.tsx create mode 100644 centrifuge-app/src/pages/Portfolio.tsx diff --git a/centrifuge-app/src/components/AddressTokens.tsx b/centrifuge-app/src/components/AddressTokens.tsx new file mode 100644 index 0000000000..38da9d6c01 --- /dev/null +++ b/centrifuge-app/src/components/AddressTokens.tsx @@ -0,0 +1,15 @@ +import { useBalances } from '@centrifuge/centrifuge-react' +import { Box } from '@centrifuge/fabric' +import * as React from 'react' +import { useAddress } from '../utils/useAddress' + +export function AddressTokens() { + const address = useAddress() + const balances = useBalances(address) + + return ( + + Todo: render all tokens + + ) +} diff --git a/centrifuge-app/src/components/AddressTransactions.tsx b/centrifuge-app/src/components/AddressTransactions.tsx new file mode 100644 index 0000000000..46a7df1b54 --- /dev/null +++ b/centrifuge-app/src/components/AddressTransactions.tsx @@ -0,0 +1,19 @@ +import { Box } from '@centrifuge/fabric' +import * as React from 'react' +import { useAddress } from '../utils/useAddress' +import { useAllTransactions } from '../utils/usePools' + +export function AddressTransactions() { + const address = useAddress() + const transactions = useAllTransactions(address) + + return ( + + + Todo: render transactions + + + + + ) +} diff --git a/centrifuge-app/src/components/Root.tsx b/centrifuge-app/src/components/Root.tsx index a84116e5c8..841e4a85a0 100644 --- a/centrifuge-app/src/components/Root.tsx +++ b/centrifuge-app/src/components/Root.tsx @@ -31,6 +31,7 @@ import { EmailVerified } from '../pages/Onboarding/EmailVerified' import { UpdateInvestorStatus } from '../pages/Onboarding/UpdateInvestorStatus' import { PoolDetailPage } from '../pages/Pool' import { PoolsPage } from '../pages/Pools' +import { PortfolioPage } from '../pages/Portfolio' import { TokenOverviewPage } from '../pages/Tokens' import { pinToApi } from '../utils/pinToApi' import { DebugFlags, initialFlagsState } from './DebugFlags' @@ -197,6 +198,9 @@ function Routes() { + + + diff --git a/centrifuge-app/src/pages/Portfolio.tsx b/centrifuge-app/src/pages/Portfolio.tsx new file mode 100644 index 0000000000..a62e4a5dbc --- /dev/null +++ b/centrifuge-app/src/pages/Portfolio.tsx @@ -0,0 +1,59 @@ +import { Box, Card, Grid, Stack, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { AddressTokens } from '../components/AddressTokens' +import { AddressTransactions } from '../components/AddressTransactions' +import { PageWithSideBar } from '../components/PageWithSideBar' +import { useAddress } from '../utils/useAddress' + +export function PortfolioPage() { + return ( + + + + ) +} + +function Portfolio() { + const address = useAddress() + + return ( + + + Your portfolio + Track and manage your portfolio + + + {!!address ? ( + <> + + + Portfolio stats + + + + CFG rewards + + + + + + Allocation + + + + Transaction history + + + + + + Token overview + + + + ) : ( + You need to connect a wallet to see your portfolio + )} + + ) +} diff --git a/centrifuge-app/src/utils/usePools.ts b/centrifuge-app/src/utils/usePools.ts index 5fc8a6ea10..ac3e529202 100644 --- a/centrifuge-app/src/utils/usePools.ts +++ b/centrifuge-app/src/utils/usePools.ts @@ -49,6 +49,18 @@ export function useMonthlyPoolStates(poolId: string, from?: Date, to?: Date) { return result } +export function useAllTransactions(address?: string) { + const [result] = useCentrifugeQuery( + ['all transactions by address', address], + (cent) => cent.pools.getAllTransactions([address!]), + { + enabled: !!address, + } + ) + + return result +} + export function useInvestorTransactions(poolId: string, trancheId?: string, from?: Date, to?: Date) { const [result] = useCentrifugeQuery( ['investorTransactions', poolId, trancheId, from, to], diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index b185d0c543..97cb119931 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -1862,6 +1862,59 @@ export function getPoolsModule(inst: Centrifuge) { ) } + function getAllTransactions(args: [address: string]) { + const [address] = args + + const $query = inst.getSubqueryObservable<{ + investorTransactions: { nodes: SubqueryInvestorTransaction[] } + borrowerTransactions: { nodes: { type: unknown; poolId: string }[] } + }>( + `query($address: String!) { + investorTransactions(filter: { + account: { + id: { + equalTo: $address + } + } + }) { + nodes { + type + poolId + } + } + + borrowerTransactions(filter: { + account: { + id: { + equalTo: $address + } + } + }) { + nodes { + type + poolId + } + } + } + `, + { + address, + } + ) + return $query.pipe( + map((data) => { + if (!data) { + return [] + } + + return { + investorTransactions: data.investorTransactions.nodes.map((entry) => entry), + borrowerTransactions: data.borrowerTransactions.nodes.map((entry) => entry), + } + }) + ) + } + function getInvestorTransactions(args: [poolId: string, trancheId?: string, from?: Date, to?: Date]) { const [poolId, trancheId, from, to] = args const $api = inst.getApi() @@ -2696,6 +2749,7 @@ export function getPoolsModule(inst: Centrifuge) { getNativeCurrency, getCurrencies, getDailyTrancheStates, + getAllTransactions, } } From 0d002b3b3152fc42001a264399515cf390857977 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Fri, 11 Aug 2023 11:15:26 +0200 Subject: [PATCH 02/16] feat: Transactions route --- .../src/components/AddressTransactions.tsx | 14 ++++----- centrifuge-app/src/components/Menu/index.tsx | 16 +++++++++- centrifuge-app/src/components/Root.tsx | 4 +++ .../pages/Portfolio/Transactions/index.tsx | 30 +++++++++++++++++++ .../{Portfolio.tsx => Portfolio/index.tsx} | 14 +++++---- 5 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 centrifuge-app/src/pages/Portfolio/Transactions/index.tsx rename centrifuge-app/src/pages/{Portfolio.tsx => Portfolio/index.tsx} (71%) diff --git a/centrifuge-app/src/components/AddressTransactions.tsx b/centrifuge-app/src/components/AddressTransactions.tsx index 46a7df1b54..803cb20005 100644 --- a/centrifuge-app/src/components/AddressTransactions.tsx +++ b/centrifuge-app/src/components/AddressTransactions.tsx @@ -3,17 +3,17 @@ import * as React from 'react' import { useAddress } from '../utils/useAddress' import { useAllTransactions } from '../utils/usePools' -export function AddressTransactions() { +type AddressTransactionsProps = { + count?: number +} + +export function AddressTransactions({ count }: AddressTransactionsProps) { const address = useAddress() const transactions = useAllTransactions(address) return ( - - - Todo: render transactions - - - + + Todo: render transactions ) } diff --git a/centrifuge-app/src/components/Menu/index.tsx b/centrifuge-app/src/components/Menu/index.tsx index 83e5d3d7e1..4469afe5a8 100644 --- a/centrifuge-app/src/components/Menu/index.tsx +++ b/centrifuge-app/src/components/Menu/index.tsx @@ -1,4 +1,13 @@ -import { Box, IconInvestments, IconNft, Menu as Panel, MenuItemGroup, Shelf, Stack } from '@centrifuge/fabric' +import { + Box, + IconInvestments, + IconNft, + IconPieChart, + Menu as Panel, + MenuItemGroup, + Shelf, + Stack, +} from '@centrifuge/fabric' import { config } from '../../config' import { useAddress } from '../../utils/useAddress' import { useIsAboveBreakpoint } from '../../utils/useIsAboveBreakpoint' @@ -37,6 +46,11 @@ export function Menu() { + + + Portfolio + + {(pools.length > 0 || config.poolCreationType === 'immediate') && ( id)}> {isXLarge ? ( diff --git a/centrifuge-app/src/components/Root.tsx b/centrifuge-app/src/components/Root.tsx index 841e4a85a0..d1d69dc53d 100644 --- a/centrifuge-app/src/components/Root.tsx +++ b/centrifuge-app/src/components/Root.tsx @@ -32,6 +32,7 @@ import { UpdateInvestorStatus } from '../pages/Onboarding/UpdateInvestorStatus' import { PoolDetailPage } from '../pages/Pool' import { PoolsPage } from '../pages/Pools' import { PortfolioPage } from '../pages/Portfolio' +import { TransactionsPage } from '../pages/Portfolio/Transactions' import { TokenOverviewPage } from '../pages/Tokens' import { pinToApi } from '../utils/pinToApi' import { DebugFlags, initialFlagsState } from './DebugFlags' @@ -198,6 +199,9 @@ function Routes() { + + + diff --git a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx new file mode 100644 index 0000000000..7c4154cdf0 --- /dev/null +++ b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx @@ -0,0 +1,30 @@ +import { Box, Stack, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { AddressTransactions } from '../../../components/AddressTransactions' +import { PageWithSideBar } from '../../../components/PageWithSideBar' +import { useAddress } from '../../../utils/useAddress' + +export function TransactionsPage() { + return ( + + + + ) +} + +function Transactions() { + const address = useAddress() + + return ( + + + Transactions + {!!address ? ( + + ) : ( + You need to connect your wallet to see your transactions + )} + + + ) +} diff --git a/centrifuge-app/src/pages/Portfolio.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx similarity index 71% rename from centrifuge-app/src/pages/Portfolio.tsx rename to centrifuge-app/src/pages/Portfolio/index.tsx index a62e4a5dbc..a17083aa02 100644 --- a/centrifuge-app/src/pages/Portfolio.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -1,9 +1,10 @@ import { Box, Card, Grid, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' -import { AddressTokens } from '../components/AddressTokens' -import { AddressTransactions } from '../components/AddressTransactions' -import { PageWithSideBar } from '../components/PageWithSideBar' -import { useAddress } from '../utils/useAddress' +import { Link } from 'react-router-dom' +import { AddressTokens } from '../../components/AddressTokens' +import { AddressTransactions } from '../../components/AddressTransactions' +import { PageWithSideBar } from '../../components/PageWithSideBar' +import { useAddress } from '../../utils/useAddress' export function PortfolioPage() { return ( @@ -42,7 +43,8 @@ function Portfolio() { Transaction history - + + View all @@ -52,7 +54,7 @@ function Portfolio() { ) : ( - You need to connect a wallet to see your portfolio + You need to connect your wallet to see your portfolio )} ) From 7ff2e79e104160b2b7cfd397a688565a02cdfe04 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Thu, 17 Aug 2023 13:07:32 +0200 Subject: [PATCH 03/16] feat: rewards value and styling PortfolioRewards - creates PortfolioRewards - refactor 'useComputeLiquidityRewards' to accept an array of {poolId, trancheId} - refactor 'getBalances' to respect 'reserved' balance --- .../src/components/LiquidityRewards/hooks.ts | 11 +- .../src/components/PortfolioRewards/Coins.tsx | 144 ++++++++++++++++++ .../src/components/PortfolioRewards/index.tsx | 54 +++++++ centrifuge-app/src/pages/Portfolio/index.tsx | 9 +- centrifuge-js/src/modules/pools.ts | 19 ++- centrifuge-js/src/modules/rewards.ts | 22 ++- 6 files changed, 238 insertions(+), 21 deletions(-) create mode 100644 centrifuge-app/src/components/PortfolioRewards/Coins.tsx create mode 100644 centrifuge-app/src/components/PortfolioRewards/index.tsx diff --git a/centrifuge-app/src/components/LiquidityRewards/hooks.ts b/centrifuge-app/src/components/LiquidityRewards/hooks.ts index 9b59234125..44d422a30c 100644 --- a/centrifuge-app/src/components/LiquidityRewards/hooks.ts +++ b/centrifuge-app/src/components/LiquidityRewards/hooks.ts @@ -1,5 +1,6 @@ import { useCentrifugeQuery } from '@centrifuge/centrifuge-react' import * as React from 'react' +import { Dec } from '../../utils/Decimal' export function useAccountStakes(address?: string, poolId?: string, trancheId?: string) { const [result] = useCentrifugeQuery( @@ -46,16 +47,16 @@ export function useRewardCurrencyGroup(poolId?: string, trancheId?: string) { return result } -export function useComputeLiquidityRewards(address?: string, poolId?: string, trancheId?: string) { +export function useComputeLiquidityRewards(address?: string, tranches?: { poolId: string; trancheId: string }[]) { const [result] = useCentrifugeQuery( - ['compute liquidity rewards', address, poolId, trancheId], - (cent) => cent.rewards.computeReward([address!, poolId!, trancheId!, 'Liquidity']), + ['compute liquidity rewards', address, tranches?.map(({ trancheId }) => trancheId).join('')], + (cent) => cent.rewards.computeReward([address!, tranches!, 'Liquidity']), { - enabled: !!address && !!poolId && !!trancheId, + enabled: !!address && !!tranches && tranches.length > 0, } ) - return result + return result ?? Dec(0) } // list of all staked currencyIds diff --git a/centrifuge-app/src/components/PortfolioRewards/Coins.tsx b/centrifuge-app/src/components/PortfolioRewards/Coins.tsx new file mode 100644 index 0000000000..62d50f488b --- /dev/null +++ b/centrifuge-app/src/components/PortfolioRewards/Coins.tsx @@ -0,0 +1,144 @@ +import * as React from 'react' + +export function Coins() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/centrifuge-app/src/components/PortfolioRewards/index.tsx b/centrifuge-app/src/components/PortfolioRewards/index.tsx new file mode 100644 index 0000000000..d64094c10e --- /dev/null +++ b/centrifuge-app/src/components/PortfolioRewards/index.tsx @@ -0,0 +1,54 @@ +import { useAddress, useBalances, useCentrifugeConsts } from '@centrifuge/centrifuge-react' +import { Box, Grid, Stack, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { useTheme } from 'styled-components' +import { formatBalance } from '../../utils/formatting' +import { useComputeLiquidityRewards } from '../LiquidityRewards/hooks' +import { Coins } from './Coins' + +export function PortfolioRewards() { + const { colors } = useTheme() + const consts = useCentrifugeConsts() + const address = useAddress() + const balances = useBalances(address) + + const stakes = balances?.tranches.map(({ poolId, trancheId }) => ({ poolId, trancheId })) ?? [] + const rewards = useComputeLiquidityRewards(address, stakes) + + return ( + + + + CFG rewards + + + + + Claimable + + + + {formatBalance(rewards, consts.chainSymbol, 2)} + + + + + + + + + ) +} diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index a17083aa02..8272c6989a 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom' import { AddressTokens } from '../../components/AddressTokens' import { AddressTransactions } from '../../components/AddressTransactions' import { PageWithSideBar } from '../../components/PageWithSideBar' +import { PortfolioRewards } from '../../components/PortfolioRewards' import { useAddress } from '../../utils/useAddress' export function PortfolioPage() { @@ -26,14 +27,12 @@ function Portfolio() { {!!address ? ( <> - - + + Portfolio stats - - CFG rewards - + diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 97cb119931..60a9c8144e 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -2049,7 +2049,12 @@ export function getPoolsModule(inst: Centrifuge) { rawBalances.forEach(([rawKey, rawValue]) => { const key = parseCurrencyKey((rawKey.toHuman() as any)[1] as CurrencyKey) - const value = rawValue.toJSON() as { free: string | number } + const value = rawValue.toJSON() as { + free: string | number + reserved: string | number + frozen: string | number + } + const currency = findCurrency(currencies, key) if (!currency) return @@ -2057,19 +2062,23 @@ export function getPoolsModule(inst: Centrifuge) { if (typeof key !== 'string' && 'Tranche' in key) { const [pid, trancheId] = key.Tranche const poolId = pid.replace(/\D/g, '') - if (value.free !== 0) { + if (value.free !== 0 || value.reserved !== 0) { + const balance = hexToBN(value.free).add(hexToBN(value.reserved)) + balances.tranches.push({ currency, poolId, trancheId, - balance: new TokenBalance(hexToBN(value.free), currency.decimals), + balance: new TokenBalance(balance, currency.decimals), }) } } else { - if (value.free !== 0) { + if (value.free !== 0 || value.reserved !== 0) { + const balance = hexToBN(value.free).add(hexToBN(value.reserved)) + balances.currencies.push({ currency, - balance: new CurrencyBalance(hexToBN(value.free), currency.decimals), + balance: new CurrencyBalance(balance, currency.decimals), }) } } diff --git a/centrifuge-js/src/modules/rewards.ts b/centrifuge-js/src/modules/rewards.ts index 90e6278fb7..d77dd19d44 100644 --- a/centrifuge-js/src/modules/rewards.ts +++ b/centrifuge-js/src/modules/rewards.ts @@ -1,4 +1,5 @@ import BN from 'bn.js' +import { forkJoin } from 'rxjs' import { combineLatestWith, filter, map, repeat, switchMap } from 'rxjs/operators' import { Centrifuge } from '../Centrifuge' import { RewardDomain } from '../CentrifugeBase' @@ -6,9 +7,10 @@ import { Account, TransactionOptions } from '../types' import { TokenBalance } from '../utils/BN' export function getRewardsModule(inst: Centrifuge) { - function computeReward(args: [address: Account, poolId: string, trancheId: string, rewardDomain: RewardDomain]) { - const [address, poolId, trancheId, rewardDomain] = args - const currencyId = { Tranche: [poolId, trancheId] } + function computeReward( + args: [address: Account, tranches: { poolId: string; trancheId: string }[], rewardDomain: RewardDomain] + ) { + const [address, tranches, rewardDomain] = args const $events = inst.getEvents().pipe( filter(({ api, events }) => { @@ -18,11 +20,19 @@ export function getRewardsModule(inst: Centrifuge) { ) return inst.getApi().pipe( - switchMap((api) => api.call.rewardsApi.computeReward(rewardDomain, currencyId, address)), + switchMap((api) => { + const computeRewardObservables = tranches.map(({ poolId, trancheId }) => + api.call.rewardsApi.computeReward(rewardDomain, { Tranche: [poolId, trancheId] }, address) + ) + return forkJoin(computeRewardObservables) + }), map((data) => { - const reward = data?.toPrimitive() as string + const rewards = data + ?.map((entry) => entry.toPrimitive() as string) + .map((entry) => new BN(entry)) + .reduce((a, b) => a.add(b), new BN(0)) - return reward ? new TokenBalance(reward, 18).toDecimal() : null + return data ? new TokenBalance(rewards, 18).toDecimal() : null }), repeat({ delay: () => $events }) ) From d197261b80abe86bd20e61e2fa46722161efc4b2 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Thu, 17 Aug 2023 15:05:58 +0200 Subject: [PATCH 04/16] feat: claim all rewards - refactor computeReward to recall on all events - refactor claimLiquidityRewards to accept array of tranches --- .../LiquidityRewardsProvider.tsx | 4 +-- .../src/components/PortfolioRewards/index.tsx | 23 +++++++++++-- centrifuge-app/src/pages/Portfolio/index.tsx | 34 ++++++++++--------- centrifuge-js/src/modules/rewards.ts | 24 +++++++------ 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/centrifuge-app/src/components/LiquidityRewards/LiquidityRewardsProvider.tsx b/centrifuge-app/src/components/LiquidityRewards/LiquidityRewardsProvider.tsx index 74ed6db15d..f8be581c1c 100644 --- a/centrifuge-app/src/components/LiquidityRewards/LiquidityRewardsProvider.tsx +++ b/centrifuge-app/src/components/LiquidityRewards/LiquidityRewardsProvider.tsx @@ -20,7 +20,7 @@ function Provider({ poolId, trancheId, children }: LiquidityRewardsProviderProps const address = useAddress() const order = usePendingCollect(poolId, trancheId, address) const stakes = useAccountStakes(address, poolId, trancheId) - const rewards = useComputeLiquidityRewards(address, poolId, trancheId) + const rewards = useComputeLiquidityRewards(address, [{ poolId, trancheId }]) const balances = useBalances(address) const countdown = useClaimCountdown() const rewardCurrencyGroup = useRewardCurrencyGroup(poolId, trancheId) @@ -77,7 +77,7 @@ function Provider({ poolId, trancheId, children }: LiquidityRewardsProviderProps return } - claim.execute([poolId, trancheId]) + claim.execute([[{ poolId, trancheId }]]) }, stake: () => { if (!pool.currency || !order || !order.payoutTokenAmount || !trancheId) { diff --git a/centrifuge-app/src/components/PortfolioRewards/index.tsx b/centrifuge-app/src/components/PortfolioRewards/index.tsx index d64094c10e..337a844d31 100644 --- a/centrifuge-app/src/components/PortfolioRewards/index.tsx +++ b/centrifuge-app/src/components/PortfolioRewards/index.tsx @@ -1,5 +1,5 @@ -import { useAddress, useBalances, useCentrifugeConsts } from '@centrifuge/centrifuge-react' -import { Box, Grid, Stack, Text } from '@centrifuge/fabric' +import { useAddress, useBalances, useCentrifugeConsts, useCentrifugeTransaction } from '@centrifuge/centrifuge-react' +import { Box, Button, Grid, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { useTheme } from 'styled-components' import { formatBalance } from '../../utils/formatting' @@ -15,6 +15,11 @@ export function PortfolioRewards() { const stakes = balances?.tranches.map(({ poolId, trancheId }) => ({ poolId, trancheId })) ?? [] const rewards = useComputeLiquidityRewards(address, stakes) + const { execute, isLoading } = useCentrifugeTransaction( + 'Claim CFG liquidity rewards', + (cent) => cent.rewards.claimLiquidityRewards + ) + return ( + + {!!rewards && rewards.gt(0) && ( + + + + )} diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 8272c6989a..57e5c50103 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -20,10 +20,14 @@ function Portfolio() { return ( - - Your portfolio - Track and manage your portfolio - + + + Your portfolio + + + Track and manage your portfolio + + {!!address ? ( <> @@ -35,22 +39,20 @@ function Portfolio() { - - - Allocation - - - - Transaction history - - View all - - - Token overview + + + Transaction history + + View all + + + + Allocation + ) : ( You need to connect your wallet to see your portfolio diff --git a/centrifuge-js/src/modules/rewards.ts b/centrifuge-js/src/modules/rewards.ts index d77dd19d44..b870b8e9de 100644 --- a/centrifuge-js/src/modules/rewards.ts +++ b/centrifuge-js/src/modules/rewards.ts @@ -1,6 +1,6 @@ import BN from 'bn.js' import { forkJoin } from 'rxjs' -import { combineLatestWith, filter, map, repeat, switchMap } from 'rxjs/operators' +import { combineLatestWith, map, repeat, switchMap } from 'rxjs/operators' import { Centrifuge } from '../Centrifuge' import { RewardDomain } from '../CentrifugeBase' import { Account, TransactionOptions } from '../types' @@ -11,13 +11,7 @@ export function getRewardsModule(inst: Centrifuge) { args: [address: Account, tranches: { poolId: string; trancheId: string }[], rewardDomain: RewardDomain] ) { const [address, tranches, rewardDomain] = args - - const $events = inst.getEvents().pipe( - filter(({ api, events }) => { - const event = events.find(({ event }) => api.events.liquidityRewards.NewEpoch.is(event)) - return !!event - }) - ) + const $events = inst.getEvents() return inst.getApi().pipe( switchMap((api) => { @@ -123,13 +117,21 @@ export function getRewardsModule(inst: Centrifuge) { ) } - function claimLiquidityRewards(args: [poolId: string, trancheId: string], options?: TransactionOptions) { - const [poolId, trancheId] = args + function claimLiquidityRewards( + args: [tranches: { poolId: string; trancheId: string }[]], + options?: TransactionOptions + ) { + const [tranches] = args const $api = inst.getApi() return $api.pipe( switchMap((api) => { - const submittable = api.tx.liquidityRewards.claimReward({ Tranche: [poolId, trancheId] }) + const submittable = api.tx.utility.batchAll( + tranches.flatMap((tranche) => { + return api.tx.liquidityRewards.claimReward({ Tranche: [tranche.poolId, tranche.trancheId] }) + }) + ) + return inst.wrapSignAndSend(api, submittable, options) }) ) From 66c95526f0f7df46cd3f97020e0e384910a65d67 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Thu, 17 Aug 2023 17:05:39 +0200 Subject: [PATCH 05/16] optimize filter and add outstandingOrders --- centrifuge-js/src/modules/pools.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 60a9c8144e..e9ac6d9c99 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -1868,13 +1868,12 @@ export function getPoolsModule(inst: Centrifuge) { const $query = inst.getSubqueryObservable<{ investorTransactions: { nodes: SubqueryInvestorTransaction[] } borrowerTransactions: { nodes: { type: unknown; poolId: string }[] } + outstandingOrders: { nodes: { poolId: string }[] } }>( `query($address: String!) { investorTransactions(filter: { - account: { - id: { - equalTo: $address - } + accountId: { + equalTo: $address } }) { nodes { @@ -1884,10 +1883,8 @@ export function getPoolsModule(inst: Centrifuge) { } borrowerTransactions(filter: { - account: { - id: { - equalTo: $address - } + accountId: { + equalTo: $address } }) { nodes { @@ -1895,6 +1892,16 @@ export function getPoolsModule(inst: Centrifuge) { poolId } } + + outstandingOrders(filter: { + accountId: { + equalTo: $address + } + }) { + nodes { + poolId + } + } } `, { @@ -1910,6 +1917,7 @@ export function getPoolsModule(inst: Centrifuge) { return { investorTransactions: data.investorTransactions.nodes.map((entry) => entry), borrowerTransactions: data.borrowerTransactions.nodes.map((entry) => entry), + outstandingOrders: data.outstandingOrders.nodes.map((entry) => entry), } }) ) From 6b0ec641f926becd756f621173d7124fba8a9e85 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Thu, 17 Aug 2023 18:03:06 +0200 Subject: [PATCH 06/16] feat: get all transactions data filtered by account --- .../src/components/AddressTransactions.tsx | 36 +++++++-- centrifuge-app/src/pages/Portfolio/index.tsx | 2 +- centrifuge-js/src/modules/pools.ts | 75 +++++++++++++++---- 3 files changed, 94 insertions(+), 19 deletions(-) diff --git a/centrifuge-app/src/components/AddressTransactions.tsx b/centrifuge-app/src/components/AddressTransactions.tsx index 803cb20005..cd21258ac2 100644 --- a/centrifuge-app/src/components/AddressTransactions.tsx +++ b/centrifuge-app/src/components/AddressTransactions.tsx @@ -1,19 +1,45 @@ -import { Box } from '@centrifuge/fabric' +import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' +import { Box, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' +import { formatDate } from '../utils/date' import { useAddress } from '../utils/useAddress' -import { useAllTransactions } from '../utils/usePools' +import { useAllTransactions, usePoolMetadata } from '../utils/usePools' type AddressTransactionsProps = { count?: number } export function AddressTransactions({ count }: AddressTransactionsProps) { + const { formatAddress } = useCentrifugeUtils() const address = useAddress() - const transactions = useAllTransactions(address) + const formattedAddress = formatAddress(address || '') + const transactions = useAllTransactions(formattedAddress) + const sorted = [ + ...(transactions?.borrowerTransactions ?? []), + ...(transactions?.investorTransactions ?? []), + ...(transactions?.outstandingOrders ?? []), + ].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) + // console.log('transactions', transactions) + + return !!sorted.length ? ( + + {sorted?.map(({ pool, timestamp }, index) => ( + + + + ))} + + ) : null +} + +function Card({ metadata, date }: { metadata: string; date: string }) { + const { data } = usePoolMetadata({ metadata }) + console.log('iets', data) return ( - - Todo: render transactions + + {data?.pool?.name} + {formatDate(date)} ) } diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 57e5c50103..1a38afd11c 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -44,7 +44,7 @@ function Portfolio() { - + Transaction history View all diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index e9ac6d9c99..707b020b7c 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -1866,9 +1866,42 @@ export function getPoolsModule(inst: Centrifuge) { const [address] = args const $query = inst.getSubqueryObservable<{ - investorTransactions: { nodes: SubqueryInvestorTransaction[] } - borrowerTransactions: { nodes: { type: unknown; poolId: string }[] } - outstandingOrders: { nodes: { poolId: string }[] } + investorTransactions: { + nodes: { + timestamp: string + type: string + pool: { + metadata: string + } + tranche: { + id: string + tokenPrice: string + } + }[] + } + borrowerTransactions: { + nodes: { + timestamp: string + type: unknown + pool: { + metadata: string + } + }[] + } + outstandingOrders: { + nodes: { + timestamp: string + pool: { + metadata: string + } + tranche: { + id: string + tokenPrice: string + } + redeemAmount: string + investAmount: string + }[] + } }>( `query($address: String!) { investorTransactions(filter: { @@ -1877,8 +1910,15 @@ export function getPoolsModule(inst: Centrifuge) { } }) { nodes { + timestamp type - poolId + pool { + metadata + } + tranche { + id + tokenPrice + } } } @@ -1889,7 +1929,11 @@ export function getPoolsModule(inst: Centrifuge) { }) { nodes { type - poolId + pool { + metadata + } + timestamp + amount } } @@ -1899,7 +1943,16 @@ export function getPoolsModule(inst: Centrifuge) { } }) { nodes { - poolId + timestamp + pool { + metadata + } + tranche { + id + tokenPrice + } + redeemAmount + investAmount } } } @@ -1910,14 +1963,10 @@ export function getPoolsModule(inst: Centrifuge) { ) return $query.pipe( map((data) => { - if (!data) { - return [] - } - return { - investorTransactions: data.investorTransactions.nodes.map((entry) => entry), - borrowerTransactions: data.borrowerTransactions.nodes.map((entry) => entry), - outstandingOrders: data.outstandingOrders.nodes.map((entry) => entry), + investorTransactions: data?.investorTransactions.nodes.map((entry) => entry) ?? [], + borrowerTransactions: data?.borrowerTransactions.nodes.map((entry) => entry) ?? [], + outstandingOrders: data?.outstandingOrders.nodes.map((entry) => entry) ?? [], } }) ) From c2abdf0b4cbec314ab92ef006649e6d89db7e648 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Fri, 18 Aug 2023 17:34:43 +0200 Subject: [PATCH 07/16] feat: TransactionCard --- .../src/components/AddressTransactions.tsx | 87 +++++++++++++------ .../src/components/TransactionCard.tsx | 76 ++++++++++++++++ centrifuge-app/src/pages/Portfolio/index.tsx | 8 +- centrifuge-js/src/modules/pools.ts | 57 ++++++------ centrifuge-js/src/types/index.ts | 2 + centrifuge-js/src/types/subquery.ts | 26 +++++- 6 files changed, 194 insertions(+), 62 deletions(-) create mode 100644 centrifuge-app/src/components/TransactionCard.tsx diff --git a/centrifuge-app/src/components/AddressTransactions.tsx b/centrifuge-app/src/components/AddressTransactions.tsx index cd21258ac2..1a118f98ec 100644 --- a/centrifuge-app/src/components/AddressTransactions.tsx +++ b/centrifuge-app/src/components/AddressTransactions.tsx @@ -1,45 +1,80 @@ +import { + SubqueryBorrowerTransaction, + SubqueryInvestorTransaction, + SubqueryOutstandingOrder, +} from '@centrifuge/centrifuge-js' import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' -import { Box, Stack, Text } from '@centrifuge/fabric' +import { Box, Stack } from '@centrifuge/fabric' import * as React from 'react' -import { formatDate } from '../utils/date' import { useAddress } from '../utils/useAddress' -import { useAllTransactions, usePoolMetadata } from '../utils/usePools' +import { useAllTransactions } from '../utils/usePools' +import { TransactionCard, TransactionCardProps } from './TransactionCard' type AddressTransactionsProps = { count?: number } +const formatters = { + investorTransactions: ({ + timestamp, + type, + poolId, + hash, + tokenAmount, + tokenPrice, + }: Pick) => + ({ + date: new Date(timestamp).getTime(), + action: type, + amount: 0, // tokenAmount && tokenPrice + poolId, + hash, + } as TransactionCardProps), + borrowerTransactions: ({ timestamp, type, amount, poolId, hash }: SubqueryBorrowerTransaction) => + ({ + date: new Date(timestamp).getTime(), + action: type, + amount, + poolId, + hash, + } as TransactionCardProps), + outstandingOrders: ({ timestamp, investAmount, redeemAmount, poolId, hash }: SubqueryOutstandingOrder) => + ({ + date: new Date(timestamp).getTime(), + action: 'PENDING_ORDER', + amount: 0, + poolId, + hash, + } as TransactionCardProps), +} + export function AddressTransactions({ count }: AddressTransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const formattedAddress = formatAddress(address || '') const transactions = useAllTransactions(formattedAddress) - const sorted = [ - ...(transactions?.borrowerTransactions ?? []), - ...(transactions?.investorTransactions ?? []), - ...(transactions?.outstandingOrders ?? []), - ].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()) - // console.log('transactions', transactions) + const formattedTransactions: TransactionCardProps[] = [] + + if (transactions) { + const { borrowerTransactions, investorTransactions, outstandingOrders } = transactions + + investorTransactions.forEach((transaction) => + formattedTransactions.push(formatters.investorTransactions(transaction)) + ) + borrowerTransactions.forEach((transaction) => + formattedTransactions.push(formatters.borrowerTransactions(transaction)) + ) + outstandingOrders.forEach((transaction) => formattedTransactions.push(formatters.outstandingOrders(transaction))) + } + // console.log('transactions', transactions?.investorTransactions) - return !!sorted.length ? ( - - {sorted?.map(({ pool, timestamp }, index) => ( - - + return !!formattedTransactions.length ? ( + + {formattedTransactions.map((transaction, index) => ( + + ))} ) : null } - -function Card({ metadata, date }: { metadata: string; date: string }) { - const { data } = usePoolMetadata({ metadata }) - console.log('iets', data) - - return ( - - {data?.pool?.name} - {formatDate(date)} - - ) -} diff --git a/centrifuge-app/src/components/TransactionCard.tsx b/centrifuge-app/src/components/TransactionCard.tsx new file mode 100644 index 0000000000..48b6d96c4e --- /dev/null +++ b/centrifuge-app/src/components/TransactionCard.tsx @@ -0,0 +1,76 @@ +import { BorrowerTransactionType, InvestorTransactionType, Pool } from '@centrifuge/centrifuge-js' +import { Box, Grid, IconExternalLink, Stack, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { formatDate } from '../utils/date' +import { usePool, usePoolMetadata } from '../utils/usePools' + +export type TransactionCardProps = { + date: number + action: InvestorTransactionType | BorrowerTransactionType | 'PENDING_ORDER' + amount: unknown + poolId: string + hash: string + trancheId?: string +} + +export function TransactionCard({ date, action, amount, poolId, hash, trancheId }: TransactionCardProps) { + const pool = usePool(poolId) as Pool + const { data } = usePoolMetadata(pool) + const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined + const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL + // console.log('data', data) + + // console.log('pool', pool) + // console.log('token', token) + + if (!pool || !data) { + return null + } + + return ( + + {action} + + + {formatDate(date, { + day: '2-digit', + month: '2-digit', + year: '2-digit', + })} + + + + + {!!token ? token.currency?.name : data.pool?.name} + + {!!token && ( + + {data?.pool?.name} + + )} + + + + {amount} + + + {!!subScanUrl && !!hash && ( + + + + )} + + ) +} diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 1a38afd11c..7f0eb673de 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -44,11 +44,13 @@ function Portfolio() { - - Transaction history + + + Transaction history + View all - + Allocation diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 707b020b7c..8e2931e92a 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -7,6 +7,7 @@ import { calculateOptimalSolution, SolverResult } from '..' import { Centrifuge } from '../Centrifuge' import { Account, TransactionOptions } from '../types' import { + BorrowerTransactionType, InvestorTransactionType, SubqueryInvestorTransaction, SubqueryPoolSnapshot, @@ -1869,31 +1870,28 @@ export function getPoolsModule(inst: Centrifuge) { investorTransactions: { nodes: { timestamp: string - type: string - pool: { - metadata: string - } - tranche: { - id: string - tokenPrice: string - } + type: InvestorTransactionType + poolId: string + hash: string + tokenAmount: string + tokenPrice: string }[] } borrowerTransactions: { nodes: { timestamp: string - type: unknown - pool: { - metadata: string - } + type: BorrowerTransactionType + poolId: string + amount: string + hash: string }[] } outstandingOrders: { nodes: { timestamp: string - pool: { - metadata: string - } + poolId: string + hash: string + tranche: { id: string tokenPrice: string @@ -1912,13 +1910,11 @@ export function getPoolsModule(inst: Centrifuge) { nodes { timestamp type - pool { - metadata - } - tranche { - id - tokenPrice - } + poolId + hash + + tokenAmount + tokenPrice } } @@ -1928,11 +1924,10 @@ export function getPoolsModule(inst: Centrifuge) { } }) { nodes { - type - pool { - metadata - } timestamp + type + poolId + hash amount } } @@ -1944,15 +1939,15 @@ export function getPoolsModule(inst: Centrifuge) { }) { nodes { timestamp - pool { - metadata - } + redeemAmount + investAmount + poolId + hash + tranche { id tokenPrice } - redeemAmount - investAmount } } } diff --git a/centrifuge-js/src/types/index.ts b/centrifuge-js/src/types/index.ts index 675eb7c119..25ef762cdc 100644 --- a/centrifuge-js/src/types/index.ts +++ b/centrifuge-js/src/types/index.ts @@ -26,3 +26,5 @@ export type Multisig = { } export type ComputedMultisig = Multisig & { address: string } + +export * from './subquery' diff --git a/centrifuge-js/src/types/subquery.ts b/centrifuge-js/src/types/subquery.ts index 6f73171f74..b982d08536 100644 --- a/centrifuge-js/src/types/subquery.ts +++ b/centrifuge-js/src/types/subquery.ts @@ -47,21 +47,43 @@ export type InvestorTransactionType = | 'REDEEM_EXECUTION' | 'TRANSFER_IN' | 'TRANSFER_OUT' + | 'INVEST_COLLECT' + | 'REDEEM_COLLECT' export type SubqueryInvestorTransaction = { __typename?: 'InvestorTransaction' id: string timestamp: string accountId: string + poolId: string trancheId: string epochNumber: number type: InvestorTransactionType + hash: string currencyAmount?: number | null - tokenAmount?: number | null - tokenPrice?: number | null + tokenAmount?: number | string | null + tokenPrice?: number | string | null transactionFee?: number | null } +export type BorrowerTransactionType = 'CREATED' | 'BORROWED' | 'REPAID' | 'CLOSED' + +export type SubqueryBorrowerTransaction = { + timestamp: string + type: BorrowerTransactionType + amount: string + poolId: string + hash: string +} + +export type SubqueryOutstandingOrder = { + timestamp: string + investAmount: string + redeemAmount: string + poolId: string + hash: string +} + export type SubqueryEpoch = { id: string poolId: string From 9f52a21a0a9b634d35577f1a13128641a52e550d Mon Sep 17 00:00:00 2001 From: Hornebom Date: Mon, 21 Aug 2023 12:38:09 +0200 Subject: [PATCH 08/16] wip: trying to calc amount --- .../src/components/AddressTransactions.tsx | 30 ++++++++++++------- .../src/components/TransactionCard.tsx | 9 +++--- centrifuge-js/src/modules/pools.ts | 29 +++++++++++++++++- centrifuge-js/src/types/subquery.ts | 4 +-- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/centrifuge-app/src/components/AddressTransactions.tsx b/centrifuge-app/src/components/AddressTransactions.tsx index 1a118f98ec..5643630e25 100644 --- a/centrifuge-app/src/components/AddressTransactions.tsx +++ b/centrifuge-app/src/components/AddressTransactions.tsx @@ -1,10 +1,7 @@ -import { - SubqueryBorrowerTransaction, - SubqueryInvestorTransaction, - SubqueryOutstandingOrder, -} from '@centrifuge/centrifuge-js' +import { CurrencyBalance, SubqueryBorrowerTransaction, SubqueryOutstandingOrder } from '@centrifuge/centrifuge-js' import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' import { Box, Stack } from '@centrifuge/fabric' +import BN from 'bn.js' import * as React from 'react' import { useAddress } from '../utils/useAddress' import { useAllTransactions } from '../utils/usePools' @@ -14,6 +11,7 @@ type AddressTransactionsProps = { count?: number } +// }: Pick) => { const formatters = { investorTransactions: ({ timestamp, @@ -22,19 +20,29 @@ const formatters = { hash, tokenAmount, tokenPrice, - }: Pick) => - ({ + currencyAmount, + }: { + timestamp: string + type: string + poolId: string + hash: string + tokenAmount: CurrencyBalance + tokenPrice: BN // Price + currencyAmount: CurrencyBalance + }) => { + return { date: new Date(timestamp).getTime(), action: type, - amount: 0, // tokenAmount && tokenPrice + amount: tokenAmount, poolId, hash, - } as TransactionCardProps), + } //as TransactionCardProps + }, borrowerTransactions: ({ timestamp, type, amount, poolId, hash }: SubqueryBorrowerTransaction) => ({ date: new Date(timestamp).getTime(), action: type, - amount, + amount: new CurrencyBalance(0, 0), poolId, hash, } as TransactionCardProps), @@ -42,7 +50,7 @@ const formatters = { ({ date: new Date(timestamp).getTime(), action: 'PENDING_ORDER', - amount: 0, + amount: new CurrencyBalance(0, 0), poolId, hash, } as TransactionCardProps), diff --git a/centrifuge-app/src/components/TransactionCard.tsx b/centrifuge-app/src/components/TransactionCard.tsx index 48b6d96c4e..0f22ac9558 100644 --- a/centrifuge-app/src/components/TransactionCard.tsx +++ b/centrifuge-app/src/components/TransactionCard.tsx @@ -1,13 +1,14 @@ -import { BorrowerTransactionType, InvestorTransactionType, Pool } from '@centrifuge/centrifuge-js' +import { BorrowerTransactionType, CurrencyBalance, InvestorTransactionType, Pool } from '@centrifuge/centrifuge-js' import { Box, Grid, IconExternalLink, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { formatDate } from '../utils/date' +import { formatBalance } from '../utils/formatting' import { usePool, usePoolMetadata } from '../utils/usePools' export type TransactionCardProps = { date: number action: InvestorTransactionType | BorrowerTransactionType | 'PENDING_ORDER' - amount: unknown + amount: CurrencyBalance poolId: string hash: string trancheId?: string @@ -21,7 +22,7 @@ export function TransactionCard({ date, action, amount, poolId, hash, trancheId // console.log('data', data) // console.log('pool', pool) - // console.log('token', token) + // console.log('amount', amount.toString()) if (!pool || !data) { return null @@ -58,7 +59,7 @@ export function TransactionCard({ date, action, amount, poolId, hash, trancheId - {amount} + {formatBalance(amount.toDecimal(), pool.currency)} {!!subScanUrl && !!hash && ( diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index 8e2931e92a..dd537f8d24 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -1875,6 +1875,12 @@ export function getPoolsModule(inst: Centrifuge) { hash: string tokenAmount: string tokenPrice: string + currencyAmount: string + pool: { + currency: { + decimals: number + } + } }[] } borrowerTransactions: { @@ -1915,6 +1921,12 @@ export function getPoolsModule(inst: Centrifuge) { tokenAmount tokenPrice + currencyAmount + pool { + currency { + decimals + } + } } } @@ -1956,10 +1968,25 @@ export function getPoolsModule(inst: Centrifuge) { address, } ) + + // tokenPrice = hexToBN(tokenPrice) return $query.pipe( map((data) => { return { - investorTransactions: data?.investorTransactions.nodes.map((entry) => entry) ?? [], + investorTransactions: + data?.investorTransactions.nodes.map((entry) => ({ + ...entry, + // tokenAmount: new BN(entry.tokenAmount || 0), + // tokenPrice: new BN(entry.tokenPrice || 0), + // currencyAmount: new BN(entry.currencyAmount || 0), + tokenAmount: entry.tokenAmount + ? new CurrencyBalance(entry.tokenAmount, entry.pool.currency.decimals) + : undefined, + tokenPrice: entry.tokenPrice ? new Price(entry.tokenPrice) : undefined, + currencyAmount: entry.currencyAmount + ? new CurrencyBalance(entry.currencyAmount, entry.pool.currency.decimals) + : undefined, + })) ?? [], borrowerTransactions: data?.borrowerTransactions.nodes.map((entry) => entry) ?? [], outstandingOrders: data?.outstandingOrders.nodes.map((entry) => entry) ?? [], } diff --git a/centrifuge-js/src/types/subquery.ts b/centrifuge-js/src/types/subquery.ts index b982d08536..f8c3b95492 100644 --- a/centrifuge-js/src/types/subquery.ts +++ b/centrifuge-js/src/types/subquery.ts @@ -61,8 +61,8 @@ export type SubqueryInvestorTransaction = { type: InvestorTransactionType hash: string currencyAmount?: number | null - tokenAmount?: number | string | null - tokenPrice?: number | string | null + tokenAmount?: number | null + tokenPrice?: number | null transactionFee?: number | null } From 9b4c5a19a2db3d0626c8cefaa54bf23053753850 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Tue, 22 Aug 2023 11:20:17 +0200 Subject: [PATCH 09/16] Transactions list --- .../src/components/AddressTransactions.tsx | 75 ++++++++----- .../src/components/TransactionCard.tsx | 27 +++-- .../src/components/TransactionTypeChip.tsx | 83 ++++++++++++++ .../pages/Portfolio/Transactions/index.tsx | 15 +-- centrifuge-app/src/pages/Portfolio/index.tsx | 2 +- centrifuge-js/src/modules/pools.ts | 102 +++++++++++------- centrifuge-js/src/types/subquery.ts | 15 +-- fabric/src/components/StatusChip/index.tsx | 14 +-- 8 files changed, 237 insertions(+), 96 deletions(-) create mode 100644 centrifuge-app/src/components/TransactionTypeChip.tsx diff --git a/centrifuge-app/src/components/AddressTransactions.tsx b/centrifuge-app/src/components/AddressTransactions.tsx index 5643630e25..f7f6a8a9aa 100644 --- a/centrifuge-app/src/components/AddressTransactions.tsx +++ b/centrifuge-app/src/components/AddressTransactions.tsx @@ -1,17 +1,24 @@ -import { CurrencyBalance, SubqueryBorrowerTransaction, SubqueryOutstandingOrder } from '@centrifuge/centrifuge-js' +import { + SubqueryBorrowerTransaction, + SubqueryInvestorTransaction, + SubqueryOutstandingOrder, +} from '@centrifuge/centrifuge-js' import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' -import { Box, Stack } from '@centrifuge/fabric' -import BN from 'bn.js' +import { Box, Grid, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { useAddress } from '../utils/useAddress' import { useAllTransactions } from '../utils/usePools' -import { TransactionCard, TransactionCardProps } from './TransactionCard' +import { + TransactionCard, + TransactionCardProps, + TRANSACTION_CARD_COLUMNS, + TRANSACTION_CARD_GAP, +} from './TransactionCard' type AddressTransactionsProps = { count?: number } -// }: Pick) => { const formatters = { investorTransactions: ({ timestamp, @@ -21,38 +28,33 @@ const formatters = { tokenAmount, tokenPrice, currencyAmount, - }: { - timestamp: string - type: string - poolId: string - hash: string - tokenAmount: CurrencyBalance - tokenPrice: BN // Price - currencyAmount: CurrencyBalance - }) => { + trancheId, + }: Omit) => { return { date: new Date(timestamp).getTime(), action: type, amount: tokenAmount, poolId, hash, - } //as TransactionCardProps + trancheId, + } as TransactionCardProps }, borrowerTransactions: ({ timestamp, type, amount, poolId, hash }: SubqueryBorrowerTransaction) => ({ date: new Date(timestamp).getTime(), action: type, - amount: new CurrencyBalance(0, 0), + amount, poolId, hash, } as TransactionCardProps), - outstandingOrders: ({ timestamp, investAmount, redeemAmount, poolId, hash }: SubqueryOutstandingOrder) => + outstandingOrders: ({ timestamp, investAmount, redeemAmount, poolId, hash, trancheId }: SubqueryOutstandingOrder) => ({ date: new Date(timestamp).getTime(), action: 'PENDING_ORDER', - amount: new CurrencyBalance(0, 0), + amount: investAmount.add(redeemAmount), poolId, hash, + trancheId, } as TransactionCardProps), } @@ -60,11 +62,11 @@ export function AddressTransactions({ count }: AddressTransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const formattedAddress = formatAddress(address || '') - const transactions = useAllTransactions(formattedAddress) + const allTransactions = useAllTransactions(formattedAddress) const formattedTransactions: TransactionCardProps[] = [] - if (transactions) { - const { borrowerTransactions, investorTransactions, outstandingOrders } = transactions + if (allTransactions) { + const { borrowerTransactions, investorTransactions, outstandingOrders } = allTransactions investorTransactions.forEach((transaction) => formattedTransactions.push(formatters.investorTransactions(transaction)) @@ -74,15 +76,34 @@ export function AddressTransactions({ count }: AddressTransactionsProps) { ) outstandingOrders.forEach((transaction) => formattedTransactions.push(formatters.outstandingOrders(transaction))) } - // console.log('transactions', transactions?.investorTransactions) + + const transactions = formattedTransactions.slice(0, count ?? formattedTransactions.length) return !!formattedTransactions.length ? ( - - {formattedTransactions.map((transaction, index) => ( - - + + + Action + + + Transaction date + + + Token + + + + Amount + - ))} + + + + {transactions.map((transaction, index) => ( + + + + ))} + ) : null } diff --git a/centrifuge-app/src/components/TransactionCard.tsx b/centrifuge-app/src/components/TransactionCard.tsx index 0f22ac9558..c0277a5eb4 100644 --- a/centrifuge-app/src/components/TransactionCard.tsx +++ b/centrifuge-app/src/components/TransactionCard.tsx @@ -4,6 +4,7 @@ import * as React from 'react' import { formatDate } from '../utils/date' import { formatBalance } from '../utils/formatting' import { usePool, usePoolMetadata } from '../utils/usePools' +import { TransactionTypeChip } from './TransactionTypeChip' export type TransactionCardProps = { date: number @@ -14,15 +15,14 @@ export type TransactionCardProps = { trancheId?: string } +export const TRANSACTION_CARD_COLUMNS = `150px 100px 250px 150px 1fr` +export const TRANSACTION_CARD_GAP = 4 + export function TransactionCard({ date, action, amount, poolId, hash, trancheId }: TransactionCardProps) { const pool = usePool(poolId) as Pool const { data } = usePoolMetadata(pool) const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL - // console.log('data', data) - - // console.log('pool', pool) - // console.log('amount', amount.toString()) if (!pool || !data) { return null @@ -30,14 +30,17 @@ export function TransactionCard({ date, action, amount, poolId, hash, trancheId return ( - {action} + + + {formatDate(date, { @@ -58,9 +61,11 @@ export function TransactionCard({ date, action, amount, poolId, hash, trancheId )} - - {formatBalance(amount.toDecimal(), pool.currency)} - + + + {formatBalance(amount, pool.currency)} + + {!!subScanUrl && !!hash && ( - + )} diff --git a/centrifuge-app/src/components/TransactionTypeChip.tsx b/centrifuge-app/src/components/TransactionTypeChip.tsx new file mode 100644 index 0000000000..17b07db7f6 --- /dev/null +++ b/centrifuge-app/src/components/TransactionTypeChip.tsx @@ -0,0 +1,83 @@ +import { StatusChip, StatusChipProps } from '@centrifuge/fabric' +import * as React from 'react' +import { TransactionCardProps } from './TransactionCard' + +type TransactionTypeProps = { + type: TransactionCardProps['action'] +} + +const states: { + [Key in TransactionCardProps['action']]: { + label: string + status: StatusChipProps['status'] + } +} = { + PENDING_ORDER: { + label: 'Pending order', + status: 'default', + }, + INVEST_ORDER_UPDATE: { + label: 'Invest order update', + status: 'default', + }, + REDEEM_ORDER_UPDATE: { + label: 'Redeem order update', + status: 'default', + }, + INVEST_ORDER_CANCEL: { + label: 'Invest order cancel', + status: 'default', + }, + REDEEM_ORDER_CANCEL: { + label: 'Redeem order cancel', + status: 'default', + }, + INVEST_EXECUTION: { + label: 'Invest execution', + status: 'default', + }, + REDEEM_EXECUTION: { + label: 'Redeem execution', + status: 'default', + }, + TRANSFER_IN: { + label: 'Transfer in', + status: 'default', + }, + TRANSFER_OUT: { + label: 'Transfer out', + status: 'default', + }, + INVEST_COLLECT: { + label: 'Invest collect', + status: 'default', + }, + REDEEM_COLLECT: { + label: 'Redeem collect', + status: 'default', + }, + CREATED: { + label: 'Created', + status: 'default', + }, + BORROWED: { + label: 'Borrowed', + status: 'default', + }, + REPAID: { + label: 'Repaid', + status: 'default', + }, + CLOSED: { + label: 'Closed', + status: 'default', + }, +} + +export function TransactionTypeChip({ type }: TransactionTypeProps) { + if (!states[type]) { + return null + } + + return {states[type].label} +} diff --git a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx index 7c4154cdf0..02ad7260ea 100644 --- a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx @@ -18,13 +18,16 @@ function Transactions() { return ( - Transactions - {!!address ? ( - - ) : ( - You need to connect your wallet to see your transactions - )} + + Transaction history + + + {!!address ? ( + + ) : ( + You need to connect your wallet to see your transactions + )} ) } diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 7f0eb673de..64a2aeee54 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -48,7 +48,7 @@ function Portfolio() { Transaction history - + View all diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index dd537f8d24..67750c4a4f 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -1,8 +1,8 @@ import { StorageKey, u32 } from '@polkadot/types' import { Codec } from '@polkadot/types-codec/types' import BN from 'bn.js' -import { combineLatest, EMPTY, expand, firstValueFrom, from, Observable, of, startWith } from 'rxjs' -import { combineLatestWith, filter, map, repeatWhen, switchMap, take } from 'rxjs/operators' +import { combineLatest, EMPTY, expand, firstValueFrom, forkJoin, from, Observable, of, startWith } from 'rxjs' +import { combineLatestWith, filter, map, mergeMap, repeatWhen, switchMap, take, toArray } from 'rxjs/operators' import { calculateOptimalSolution, SolverResult } from '..' import { Centrifuge } from '../Centrifuge' import { Account, TransactionOptions } from '../types' @@ -1872,15 +1872,11 @@ export function getPoolsModule(inst: Centrifuge) { timestamp: string type: InvestorTransactionType poolId: string + trancheId: string hash: string tokenAmount: string tokenPrice: string currencyAmount: string - pool: { - currency: { - decimals: number - } - } }[] } borrowerTransactions: { @@ -1896,14 +1892,13 @@ export function getPoolsModule(inst: Centrifuge) { nodes: { timestamp: string poolId: string + trancheId: string hash: string - + redeemAmount: string + investAmount: string tranche: { - id: string tokenPrice: string } - redeemAmount: string - investAmount: string }[] } }>( @@ -1917,16 +1912,11 @@ export function getPoolsModule(inst: Centrifuge) { timestamp type poolId + trancheId hash - tokenAmount tokenPrice currencyAmount - pool { - currency { - decimals - } - } } } @@ -1954,10 +1944,9 @@ export function getPoolsModule(inst: Centrifuge) { redeemAmount investAmount poolId + trancheId hash - tranche { - id tokenPrice } } @@ -1969,27 +1958,62 @@ export function getPoolsModule(inst: Centrifuge) { } ) - // tokenPrice = hexToBN(tokenPrice) return $query.pipe( - map((data) => { - return { - investorTransactions: - data?.investorTransactions.nodes.map((entry) => ({ - ...entry, - // tokenAmount: new BN(entry.tokenAmount || 0), - // tokenPrice: new BN(entry.tokenPrice || 0), - // currencyAmount: new BN(entry.currencyAmount || 0), - tokenAmount: entry.tokenAmount - ? new CurrencyBalance(entry.tokenAmount, entry.pool.currency.decimals) - : undefined, - tokenPrice: entry.tokenPrice ? new Price(entry.tokenPrice) : undefined, - currencyAmount: entry.currencyAmount - ? new CurrencyBalance(entry.currencyAmount, entry.pool.currency.decimals) - : undefined, - })) ?? [], - borrowerTransactions: data?.borrowerTransactions.nodes.map((entry) => entry) ?? [], - outstandingOrders: data?.outstandingOrders.nodes.map((entry) => entry) ?? [], - } + mergeMap((data) => { + const investorTransactions$ = from(data?.investorTransactions.nodes || []).pipe( + mergeMap((entry) => { + return getPoolCurrency([entry.poolId]).pipe( + map((poolCurrency) => ({ + ...entry, + tokenAmount: new CurrencyBalance(entry.tokenAmount || 0, poolCurrency.decimals), + tokenPrice: new Price(entry.tokenPrice || 0), + currencyAmount: new CurrencyBalance(entry.currencyAmount || 0, poolCurrency.decimals), + trancheId: entry.trancheId.split('-')[1], + })) + ) + }), + toArray() + ) + + const borrowerTransactions$ = from(data?.borrowerTransactions.nodes || []).pipe( + mergeMap((entry) => { + return getPoolCurrency([entry.poolId]).pipe( + map((poolCurrency) => ({ + ...entry, + amount: new CurrencyBalance(entry.amount || 0, poolCurrency.decimals), + })) + ) + }), + toArray() + ) + + const outstandingOrders$ = from(data?.outstandingOrders.nodes || []).pipe( + mergeMap((entry) => { + return getPoolCurrency([entry.poolId]).pipe( + map((poolCurrency) => { + const tokenPrice = new Price(entry.tranche.tokenPrice) + + return { + ...entry, + investAmount: new CurrencyBalance(entry.investAmount || 0, poolCurrency.decimals), + redeemAmount: new CurrencyBalance(entry.redeemAmount || 0, poolCurrency.decimals), + trancheId: entry.trancheId.split('-')[1], + } + }) + ) + }), + toArray() + ) + + return forkJoin([investorTransactions$, borrowerTransactions$, outstandingOrders$]).pipe( + map(([investorTransactions, borrowerTransactions, outstandingOrders]) => { + return { + investorTransactions, + borrowerTransactions, + outstandingOrders, + } + }) + ) }) ) } diff --git a/centrifuge-js/src/types/subquery.ts b/centrifuge-js/src/types/subquery.ts index f8c3b95492..1219ec9718 100644 --- a/centrifuge-js/src/types/subquery.ts +++ b/centrifuge-js/src/types/subquery.ts @@ -1,3 +1,5 @@ +import { CurrencyBalance, Price } from '../utils/BN' + export type SubqueryPoolSnapshot = { __typename?: 'PoolSnapshot' id: string @@ -60,9 +62,9 @@ export type SubqueryInvestorTransaction = { epochNumber: number type: InvestorTransactionType hash: string - currencyAmount?: number | null - tokenAmount?: number | null - tokenPrice?: number | null + currencyAmount?: CurrencyBalance | number | null + tokenAmount?: CurrencyBalance | number | null + tokenPrice?: Price | number | null transactionFee?: number | null } @@ -71,16 +73,17 @@ export type BorrowerTransactionType = 'CREATED' | 'BORROWED' | 'REPAID' | 'CLOSE export type SubqueryBorrowerTransaction = { timestamp: string type: BorrowerTransactionType - amount: string + amount: CurrencyBalance poolId: string hash: string } export type SubqueryOutstandingOrder = { timestamp: string - investAmount: string - redeemAmount: string + investAmount: CurrencyBalance + redeemAmount: CurrencyBalance poolId: string + trancheId: string hash: string } diff --git a/fabric/src/components/StatusChip/index.tsx b/fabric/src/components/StatusChip/index.tsx index 4bad3b604b..be271d6371 100644 --- a/fabric/src/components/StatusChip/index.tsx +++ b/fabric/src/components/StatusChip/index.tsx @@ -15,10 +15,11 @@ const colors = { critical: 'statusCritical', } -const Wrapper = styled.span<{ $color: string }>((props) => +const Chip = styled(Text)<{ $borderColor: string }>((props) => css({ + display: 'inline-block', padding: '0 8px', - borderColor: props.$color, + borderColor: props.$borderColor, borderWidth: '1px', borderStyle: 'solid', borderRadius: '20px', @@ -27,11 +28,10 @@ const Wrapper = styled.span<{ $color: string }>((props) => export const StatusChip: React.FC = ({ status, children }) => { const color = colors[status] + return ( - - - {children} - - + + {children} + ) } From a6e26d91ca469a84c01c6b865dbcdfd03f41d88c Mon Sep 17 00:00:00 2001 From: Hornebom Date: Tue, 22 Aug 2023 13:01:33 +0200 Subject: [PATCH 10/16] feat: token list --- .../src/components/AddressTokens.tsx | 32 ++++++++++-- centrifuge-app/src/components/TokenCard.tsx | 50 +++++++++++++++++++ centrifuge-app/src/pages/Portfolio/index.tsx | 4 +- 3 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 centrifuge-app/src/components/TokenCard.tsx diff --git a/centrifuge-app/src/components/AddressTokens.tsx b/centrifuge-app/src/components/AddressTokens.tsx index 38da9d6c01..093e88a536 100644 --- a/centrifuge-app/src/components/AddressTokens.tsx +++ b/centrifuge-app/src/components/AddressTokens.tsx @@ -1,15 +1,39 @@ import { useBalances } from '@centrifuge/centrifuge-react' -import { Box } from '@centrifuge/fabric' +import { Box, Grid, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { useAddress } from '../utils/useAddress' +import { TokenCard, TOKEN_CARD_COLUMNS, TOKEN_CARD_GAP } from './TokenCard' export function AddressTokens() { const address = useAddress() const balances = useBalances(address) return ( - - Todo: render all tokens - + + + + Token + + + Position + + + Token price + + + Market value + + + + + {!!balances?.tranches && + balances?.tranches.length && + balances.tranches.map((tranche, index) => ( + + + + ))} + + ) } diff --git a/centrifuge-app/src/components/TokenCard.tsx b/centrifuge-app/src/components/TokenCard.tsx new file mode 100644 index 0000000000..666cacf58a --- /dev/null +++ b/centrifuge-app/src/components/TokenCard.tsx @@ -0,0 +1,50 @@ +import { AccountTokenBalance, Pool } from '@centrifuge/centrifuge-js' +import { Grid, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { formatBalance } from '../utils/formatting' +import { usePool } from '../utils/usePools' + +type TokenCardProps = AccountTokenBalance + +export const TOKEN_CARD_COLUMNS = `250px 200px 100px 150px 1FR` +export const TOKEN_CARD_GAP = 4 + +export function TokenCard({ balance, currency, poolId, trancheId }: TokenCardProps) { + const pool = usePool(poolId) as Pool + const isTinlakePool = poolId?.startsWith('0x') + + if (isTinlakePool) { + return null + } + + const tranche = pool.tranches.find(({ id }) => id === trancheId) + + return ( + + + {currency.name} + + + + {formatBalance(balance, currency)} + + + + {tranche?.tokenPrice ? formatBalance(tranche.tokenPrice, tranche.poolCurrency, 4, 2) : '-'} + + + + {tranche?.tokenPrice + ? formatBalance(balance.toDecimal().mul(tranche.tokenPrice.toDecimal()), tranche.poolCurrency, 4, 2) + : '-'} + + + ) +} diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index 64a2aeee54..a497cf3050 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -40,7 +40,9 @@ function Portfolio() { - Token overview + + Token overview + From 73330f72eff4181ed4b9121891e7add673be8933 Mon Sep 17 00:00:00 2001 From: Hornebom Date: Tue, 22 Aug 2023 14:53:29 +0200 Subject: [PATCH 11/16] feat: Allocation --- .../src/components/AddressAllocation.tsx | 24 ++++++++ .../src/components/AddressTokens.tsx | 16 ++--- .../src/components/AddressTransactions.tsx | 6 +- .../src/components/AllocationCard.tsx | 17 ++++++ centrifuge-app/src/pages/Portfolio/index.tsx | 60 ++++++++++++++----- 5 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 centrifuge-app/src/components/AddressAllocation.tsx create mode 100644 centrifuge-app/src/components/AllocationCard.tsx diff --git a/centrifuge-app/src/components/AddressAllocation.tsx b/centrifuge-app/src/components/AddressAllocation.tsx new file mode 100644 index 0000000000..b15291acf0 --- /dev/null +++ b/centrifuge-app/src/components/AddressAllocation.tsx @@ -0,0 +1,24 @@ +import { useBalances } from '@centrifuge/centrifuge-react' +import { Box, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { useAddress } from '../utils/useAddress' +import { AllocationCard } from './AllocationCard' + +export function AddressAllocation() { + const address = useAddress() + const balances = useBalances(address) + + return !!balances?.tranches && !!balances?.tranches.length ? ( + + + {balances?.tranches.map((tranche, index) => ( + + + + ))} + + + ) : ( + No data + ) +} diff --git a/centrifuge-app/src/components/AddressTokens.tsx b/centrifuge-app/src/components/AddressTokens.tsx index 093e88a536..1470c52b82 100644 --- a/centrifuge-app/src/components/AddressTokens.tsx +++ b/centrifuge-app/src/components/AddressTokens.tsx @@ -8,7 +8,7 @@ export function AddressTokens() { const address = useAddress() const balances = useBalances(address) - return ( + return !!balances?.tranches && !!balances?.tranches.length ? ( @@ -26,14 +26,14 @@ export function AddressTokens() { - {!!balances?.tranches && - balances?.tranches.length && - balances.tranches.map((tranche, index) => ( - - - - ))} + {balances.tranches.map((tranche, index) => ( + + + + ))} + ) : ( + No data ) } diff --git a/centrifuge-app/src/components/AddressTransactions.tsx b/centrifuge-app/src/components/AddressTransactions.tsx index f7f6a8a9aa..9757833e66 100644 --- a/centrifuge-app/src/components/AddressTransactions.tsx +++ b/centrifuge-app/src/components/AddressTransactions.tsx @@ -79,7 +79,7 @@ export function AddressTransactions({ count }: AddressTransactionsProps) { const transactions = formattedTransactions.slice(0, count ?? formattedTransactions.length) - return !!formattedTransactions.length ? ( + return !!transactions.length ? ( Action @@ -105,5 +105,7 @@ export function AddressTransactions({ count }: AddressTransactionsProps) { ))} - ) : null + ) : ( + No data + ) } diff --git a/centrifuge-app/src/components/AllocationCard.tsx b/centrifuge-app/src/components/AllocationCard.tsx new file mode 100644 index 0000000000..c9384d1fc7 --- /dev/null +++ b/centrifuge-app/src/components/AllocationCard.tsx @@ -0,0 +1,17 @@ +import { AccountTokenBalance } from '@centrifuge/centrifuge-js' +import { Box, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { usePool, usePoolMetadata } from '../utils/usePools' + +type AlloationCardProps = AccountTokenBalance + +export function AllocationCard({ balance, currency, poolId, trancheId }: AlloationCardProps) { + const pool = usePool(poolId) + const { data: poolMetadata } = usePoolMetadata(pool) + + return ( + + {poolMetadata?.pool?.asset.class}{' '} + + ) +} diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index a497cf3050..bd785d9862 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -1,11 +1,15 @@ +import { useBalances } from '@centrifuge/centrifuge-react' import { Box, Card, Grid, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { Link } from 'react-router-dom' +import { AddressAllocation } from '../../components/AddressAllocation' import { AddressTokens } from '../../components/AddressTokens' import { AddressTransactions } from '../../components/AddressTransactions' import { PageWithSideBar } from '../../components/PageWithSideBar' +import { PoolList } from '../../components/PoolList' import { PortfolioRewards } from '../../components/PortfolioRewards' import { useAddress } from '../../utils/useAddress' +import { useListedPools } from '../../utils/useListedPools' export function PortfolioPage() { return ( @@ -17,6 +21,8 @@ export function PortfolioPage() { function Portfolio() { const address = useAddress() + const balances = useBalances(address) + const showFallback = !balances?.tranches.length && !balances?.currencies.length return ( @@ -39,24 +45,33 @@ function Portfolio() { - - - Token overview - - - + {showFallback ? ( + + ) : ( + <> + + + Token overview + + + - - - Transaction history - - - View all - + + + Transaction history + + + View all + - - Allocation - + + + Allocation + + + + + )} ) : ( You need to connect your wallet to see your portfolio @@ -64,3 +79,16 @@ function Portfolio() { ) } + +function Fallback() { + const [listedPools, , metadataIsLoading] = useListedPools() + + return ( + + + Explore opportunities + + reserve.max.toFloat() > 0)} isLoading={metadataIsLoading} /> + + ) +} From 9bdfaea852b23042026e705f558d1c1a8b8ce5b5 Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 25 Sep 2023 10:28:36 -0400 Subject: [PATCH 12/16] Hide link to portfolio behind debug flag --- centrifuge-app/src/components/Menu/index.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/centrifuge-app/src/components/Menu/index.tsx b/centrifuge-app/src/components/Menu/index.tsx index 7237c6c5eb..9b0a7d0eb3 100644 --- a/centrifuge-app/src/components/Menu/index.tsx +++ b/centrifuge-app/src/components/Menu/index.tsx @@ -12,6 +12,7 @@ import { config } from '../../config' import { useAddress } from '../../utils/useAddress' import { useIsAboveBreakpoint } from '../../utils/useIsAboveBreakpoint' import { usePoolsThatAnyConnectedAddressHasPermissionsFor } from '../../utils/usePermissions' +import { useDebugFlags } from '../DebugFlags' import { RouterLinkButton } from '../RouterLinkButton' import { GovernanceMenu } from './GovernanceMenu' import { IssuerMenu } from './IssuerMenu' @@ -21,7 +22,9 @@ import { PoolLink } from './PoolLink' export function Menu() { const pools = usePoolsThatAnyConnectedAddressHasPermissionsFor() || [] const isLarge = useIsAboveBreakpoint('L') + const isXLarge = useIsAboveBreakpoint('XL') const address = useAddress('substrate') + const { showPortfolio } = useDebugFlags() return ( - - - Portfolio - + {showPortfolio && ( + + + Portfolio + + )} {(pools.length > 0 || config.poolCreationType === 'immediate') && ( From 54e04a9efde119ee9e3f98b2a3378ddf8972d7df Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 25 Sep 2023 12:54:04 -0400 Subject: [PATCH 13/16] Show portfolio link if logged in --- centrifuge-app/src/components/Menu/index.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/centrifuge-app/src/components/Menu/index.tsx b/centrifuge-app/src/components/Menu/index.tsx index 9b0a7d0eb3..8d7a0fdc2d 100644 --- a/centrifuge-app/src/components/Menu/index.tsx +++ b/centrifuge-app/src/components/Menu/index.tsx @@ -22,7 +22,6 @@ import { PoolLink } from './PoolLink' export function Menu() { const pools = usePoolsThatAnyConnectedAddressHasPermissionsFor() || [] const isLarge = useIsAboveBreakpoint('L') - const isXLarge = useIsAboveBreakpoint('XL') const address = useAddress('substrate') const { showPortfolio } = useDebugFlags() @@ -48,8 +47,8 @@ export function Menu() { - {showPortfolio && ( - + {showPortfolio && address && ( + Portfolio From 88a333552f557d47ec6f38b2a6605c1a242d7986 Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 25 Sep 2023 15:56:26 -0400 Subject: [PATCH 14/16] Refactor and prepare layout for shared work --- .../src/components/AddressTokens.tsx | 39 ----- .../src/components/AddressTransactions.tsx | 111 ------------ .../src/components/AllocationCard.tsx | 17 -- .../src/components/LayoutBase/BasePadding.tsx | 14 ++ .../src/components/LayoutBase/BaseSection.tsx | 15 -- .../src/components/LayoutBase/config.ts | 9 - .../src/components/LayoutBase/index.tsx | 10 +- .../src/components/LayoutBase/styles.tsx | 29 ++-- .../src/components/PoolCard/index.tsx | 2 +- centrifuge-app/src/components/PoolList.tsx | 162 +++++++++++++++--- .../src/components/PoolsTokensShared.tsx | 6 +- .../AssetAllocation.tsx} | 18 +- .../{PortfolioRewards => Portfolio}/Coins.tsx | 0 .../components/Portfolio/InvestedTokens.tsx | 44 +++++ .../index.tsx => Portfolio/Rewards.tsx} | 3 +- .../src/components/Portfolio/Transactions.tsx | 110 ++++++++++++ centrifuge-app/src/pages/Pools.tsx | 138 +-------------- .../pages/Portfolio/Transactions/index.tsx | 39 ++--- centrifuge-app/src/pages/Portfolio/index.tsx | 121 ++++++------- 19 files changed, 414 insertions(+), 473 deletions(-) delete mode 100644 centrifuge-app/src/components/AddressTokens.tsx delete mode 100644 centrifuge-app/src/components/AddressTransactions.tsx delete mode 100644 centrifuge-app/src/components/AllocationCard.tsx create mode 100644 centrifuge-app/src/components/LayoutBase/BasePadding.tsx delete mode 100644 centrifuge-app/src/components/LayoutBase/BaseSection.tsx delete mode 100644 centrifuge-app/src/components/LayoutBase/config.ts rename centrifuge-app/src/components/{AddressAllocation.tsx => Portfolio/AssetAllocation.tsx} (63%) rename centrifuge-app/src/components/{PortfolioRewards => Portfolio}/Coins.tsx (100%) create mode 100644 centrifuge-app/src/components/Portfolio/InvestedTokens.tsx rename centrifuge-app/src/components/{PortfolioRewards/index.tsx => Portfolio/Rewards.tsx} (97%) create mode 100644 centrifuge-app/src/components/Portfolio/Transactions.tsx diff --git a/centrifuge-app/src/components/AddressTokens.tsx b/centrifuge-app/src/components/AddressTokens.tsx deleted file mode 100644 index 1470c52b82..0000000000 --- a/centrifuge-app/src/components/AddressTokens.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useBalances } from '@centrifuge/centrifuge-react' -import { Box, Grid, Stack, Text } from '@centrifuge/fabric' -import * as React from 'react' -import { useAddress } from '../utils/useAddress' -import { TokenCard, TOKEN_CARD_COLUMNS, TOKEN_CARD_GAP } from './TokenCard' - -export function AddressTokens() { - const address = useAddress() - const balances = useBalances(address) - - return !!balances?.tranches && !!balances?.tranches.length ? ( - - - - Token - - - Position - - - Token price - - - Market value - - - - - {balances.tranches.map((tranche, index) => ( - - - - ))} - - - ) : ( - No data - ) -} diff --git a/centrifuge-app/src/components/AddressTransactions.tsx b/centrifuge-app/src/components/AddressTransactions.tsx deleted file mode 100644 index 9757833e66..0000000000 --- a/centrifuge-app/src/components/AddressTransactions.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { - SubqueryBorrowerTransaction, - SubqueryInvestorTransaction, - SubqueryOutstandingOrder, -} from '@centrifuge/centrifuge-js' -import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' -import { Box, Grid, Stack, Text } from '@centrifuge/fabric' -import * as React from 'react' -import { useAddress } from '../utils/useAddress' -import { useAllTransactions } from '../utils/usePools' -import { - TransactionCard, - TransactionCardProps, - TRANSACTION_CARD_COLUMNS, - TRANSACTION_CARD_GAP, -} from './TransactionCard' - -type AddressTransactionsProps = { - count?: number -} - -const formatters = { - investorTransactions: ({ - timestamp, - type, - poolId, - hash, - tokenAmount, - tokenPrice, - currencyAmount, - trancheId, - }: Omit) => { - return { - date: new Date(timestamp).getTime(), - action: type, - amount: tokenAmount, - poolId, - hash, - trancheId, - } as TransactionCardProps - }, - borrowerTransactions: ({ timestamp, type, amount, poolId, hash }: SubqueryBorrowerTransaction) => - ({ - date: new Date(timestamp).getTime(), - action: type, - amount, - poolId, - hash, - } as TransactionCardProps), - outstandingOrders: ({ timestamp, investAmount, redeemAmount, poolId, hash, trancheId }: SubqueryOutstandingOrder) => - ({ - date: new Date(timestamp).getTime(), - action: 'PENDING_ORDER', - amount: investAmount.add(redeemAmount), - poolId, - hash, - trancheId, - } as TransactionCardProps), -} - -export function AddressTransactions({ count }: AddressTransactionsProps) { - const { formatAddress } = useCentrifugeUtils() - const address = useAddress() - const formattedAddress = formatAddress(address || '') - const allTransactions = useAllTransactions(formattedAddress) - const formattedTransactions: TransactionCardProps[] = [] - - if (allTransactions) { - const { borrowerTransactions, investorTransactions, outstandingOrders } = allTransactions - - investorTransactions.forEach((transaction) => - formattedTransactions.push(formatters.investorTransactions(transaction)) - ) - borrowerTransactions.forEach((transaction) => - formattedTransactions.push(formatters.borrowerTransactions(transaction)) - ) - outstandingOrders.forEach((transaction) => formattedTransactions.push(formatters.outstandingOrders(transaction))) - } - - const transactions = formattedTransactions.slice(0, count ?? formattedTransactions.length) - - return !!transactions.length ? ( - - - Action - - - Transaction date - - - Token - - - - Amount - - - - - - {transactions.map((transaction, index) => ( - - - - ))} - - - ) : ( - No data - ) -} diff --git a/centrifuge-app/src/components/AllocationCard.tsx b/centrifuge-app/src/components/AllocationCard.tsx deleted file mode 100644 index c9384d1fc7..0000000000 --- a/centrifuge-app/src/components/AllocationCard.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { AccountTokenBalance } from '@centrifuge/centrifuge-js' -import { Box, Text } from '@centrifuge/fabric' -import * as React from 'react' -import { usePool, usePoolMetadata } from '../utils/usePools' - -type AlloationCardProps = AccountTokenBalance - -export function AllocationCard({ balance, currency, poolId, trancheId }: AlloationCardProps) { - const pool = usePool(poolId) - const { data: poolMetadata } = usePoolMetadata(pool) - - return ( - - {poolMetadata?.pool?.asset.class}{' '} - - ) -} diff --git a/centrifuge-app/src/components/LayoutBase/BasePadding.tsx b/centrifuge-app/src/components/LayoutBase/BasePadding.tsx new file mode 100644 index 0000000000..05e1dbe02a --- /dev/null +++ b/centrifuge-app/src/components/LayoutBase/BasePadding.tsx @@ -0,0 +1,14 @@ +import { BoxProps, Stack } from '@centrifuge/fabric' +import * as React from 'react' + +type BaseSectionProps = BoxProps & { + children: React.ReactNode +} + +export function BasePadding({ children, ...boxProps }: BaseSectionProps) { + return ( + + {children} + + ) +} diff --git a/centrifuge-app/src/components/LayoutBase/BaseSection.tsx b/centrifuge-app/src/components/LayoutBase/BaseSection.tsx deleted file mode 100644 index ab523309f7..0000000000 --- a/centrifuge-app/src/components/LayoutBase/BaseSection.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Box, BoxProps } from '@centrifuge/fabric' -import * as React from 'react' -import { config } from './config' - -type BaseSectionProps = BoxProps & { - children: React.ReactNode -} - -export function BaseSection({ children, ...boxProps }: BaseSectionProps) { - return ( - - {children} - - ) -} diff --git a/centrifuge-app/src/components/LayoutBase/config.ts b/centrifuge-app/src/components/LayoutBase/config.ts deleted file mode 100644 index dcb172ee3c..0000000000 --- a/centrifuge-app/src/components/LayoutBase/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const config = { - HEADER_HEIGHT: 60, - TOOLBAR_HEIGHT: 50, - LAYOUT_MAX_WIDTH: 1800, - SIDEBAR_WIDTH: 80, - SIDEBAR_WIDTH_EXTENDED: 220, - PADDING_MAIN: [2, 2, 3, 3, 5], - WALLET_WIDTH: [200, 264], -} diff --git a/centrifuge-app/src/components/LayoutBase/index.tsx b/centrifuge-app/src/components/LayoutBase/index.tsx index 03ca4abf07..742e4dce78 100644 --- a/centrifuge-app/src/components/LayoutBase/index.tsx +++ b/centrifuge-app/src/components/LayoutBase/index.tsx @@ -1,4 +1,5 @@ import { WalletMenu } from '@centrifuge/centrifuge-react' +import { Box } from '@centrifuge/fabric' import * as React from 'react' import { Footer } from '../Footer' import { LoadBoundary } from '../LoadBoundary' @@ -6,7 +7,6 @@ import { LogoLink } from '../LogoLink' import { Menu } from '../Menu' import { OnboardingStatus } from '../OnboardingStatus' import { SideDrawerProps } from '../SideDrawer' -import { config } from './config' import { FooterContainer, HeaderBackground, @@ -35,9 +35,9 @@ export function LayoutBase({ children, sideDrawer }: LayoutBaseProps) { - + - + ]} /> @@ -48,7 +48,9 @@ export function LayoutBase({ children, sideDrawer }: LayoutBaseProps) { - {children} + + {children} + diff --git a/centrifuge-app/src/components/LayoutBase/styles.tsx b/centrifuge-app/src/components/LayoutBase/styles.tsx index ae351ddf12..88ab569c61 100644 --- a/centrifuge-app/src/components/LayoutBase/styles.tsx +++ b/centrifuge-app/src/components/LayoutBase/styles.tsx @@ -1,12 +1,17 @@ import { Box, Grid, Shelf, Stack } from '@centrifuge/fabric' import styled from 'styled-components' -import { config } from './config' // the main breakpoint to switch from stacked to columns layout const BREAK_POINT_COLUMNS = 'M' // breakpoint from minimal to extended left sidebar width const BREAK_POINT_SIDEBAR_EXTENDED = 'L' +const HEADER_HEIGHT = 60 +const TOOLBAR_HEIGHT = 50 +const SIDEBAR_WIDTH = 80 +const SIDEBAR_WIDTH_EXTENDED = 220 +const LAYOUT_MAX_WIDTH = 1800 + export const Root = styled(Box)` position: relative; min-height: 100vh; @@ -29,7 +34,7 @@ export const Inner = styled(Grid)` min-height: 100%; align-items: start; - grid-template-rows: 0px ${config.HEADER_HEIGHT}px 1fr auto ${config.TOOLBAR_HEIGHT}px; + grid-template-rows: 0px ${HEADER_HEIGHT}px 1fr auto ${TOOLBAR_HEIGHT}px; grid-template-columns: auto 1fr; grid-template-areas: 'header-background header-background' @@ -39,8 +44,8 @@ export const Inner = styled(Grid)` 'toolbar toolbar'; @media (min-width: ${({ theme }) => theme.breakpoints[BREAK_POINT_COLUMNS]}) { - grid-template-rows: ${config.HEADER_HEIGHT}px minmax(max-content, 1fr) auto; - grid-template-columns: ${config.SIDEBAR_WIDTH}px 1fr; + grid-template-rows: ${HEADER_HEIGHT}px minmax(max-content, 1fr) auto; + grid-template-columns: ${SIDEBAR_WIDTH}px 1fr; grid-template-areas: 'logo wallet' 'toolbar main' @@ -48,7 +53,7 @@ export const Inner = styled(Grid)` } @media (min-width: ${({ theme }) => theme.breakpoints[BREAK_POINT_SIDEBAR_EXTENDED]}) { - grid-template-columns: ${config.SIDEBAR_WIDTH_EXTENDED}px 1fr; + grid-template-columns: ${SIDEBAR_WIDTH_EXTENDED}px 1fr; } ` @@ -60,7 +65,7 @@ export const HeaderBackground = styled(Box)` grid-area: header-background; width: 100%; - height: ${config.HEADER_HEIGHT}px; + height: ${HEADER_HEIGHT}px; background-color: ${({ theme }) => theme.colors.backgroundPrimary}; border-bottom: ${({ theme }) => `1px solid ${theme.colors.borderSecondary}`}; @@ -76,13 +81,13 @@ export const ToolbarContainer = styled(Box)` position: sticky; left: 0; bottom: 0; - height: ${config.TOOLBAR_HEIGHT}px; + height: ${TOOLBAR_HEIGHT}px; border-top: ${({ theme }) => `1px solid ${theme.colors.borderSecondary}`}; background-color: ${({ theme }) => theme.colors.backgroundPrimary}; @media (min-width: ${({ theme }) => theme.breakpoints[BREAK_POINT_COLUMNS]}) { - top: ${({ theme }) => theme.space[4] + config.HEADER_HEIGHT}px; + top: ${({ theme }) => theme.space[4] + HEADER_HEIGHT}px; bottom: auto; height: auto; @@ -101,7 +106,7 @@ export const LogoContainer = styled(Stack)` top: 0; grid-area: logo; - height: ${config.HEADER_HEIGHT}px; + height: ${HEADER_HEIGHT}px; padding-left: ${({ theme }) => theme.space[2]}px; justify-content: center; @@ -122,13 +127,13 @@ export const WalletContainer = styled(Stack)` ` export const WalletPositioner = styled(Shelf)` - max-width: ${config.LAYOUT_MAX_WIDTH}px; + max-width: ${LAYOUT_MAX_WIDTH}px; justify-content: flex-end; align-items: flex-start; ` export const WalletInner = styled(Stack)` - height: ${config.HEADER_HEIGHT}px; + height: ${HEADER_HEIGHT}px; justify-content: center; pointer-events: auto; // resetting pointer events @@ -142,7 +147,7 @@ export const MainContainer = styled(Box)` align-self: stretch; @media (min-width: ${({ theme }) => theme.breakpoints[BREAK_POINT_COLUMNS]}) { - margin-top: calc(${config.HEADER_HEIGHT}px * -1); + margin-top: calc(${HEADER_HEIGHT}px * -1); border-left: ${({ theme }) => `1px solid ${theme.colors.borderSecondary}`}; } ` diff --git a/centrifuge-app/src/components/PoolCard/index.tsx b/centrifuge-app/src/components/PoolCard/index.tsx index 408a2e4cec..6da4194d7d 100644 --- a/centrifuge-app/src/components/PoolCard/index.tsx +++ b/centrifuge-app/src/components/PoolCard/index.tsx @@ -37,7 +37,7 @@ export function PoolCard({ iconUri, isLoading, }: PoolCardProps) { - const basePath = useRouteMatch(['/pools', '/issuer'])?.path || '' + const basePath = useRouteMatch(['/pools', '/issuer'])?.path || '/pools' const { sizes } = useTheme() return ( diff --git a/centrifuge-app/src/components/PoolList.tsx b/centrifuge-app/src/components/PoolList.tsx index 7a3b7d6b92..7848d7996b 100644 --- a/centrifuge-app/src/components/PoolList.tsx +++ b/centrifuge-app/src/components/PoolList.tsx @@ -1,13 +1,20 @@ -import { Box, Stack } from '@centrifuge/fabric' +import Centrifuge, { Pool, PoolMetadata, Rate } from '@centrifuge/centrifuge-js' +import { useCentrifuge } from '@centrifuge/centrifuge-react' +import { Box, InlineFeedback, Shelf, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' +import { useLocation } from 'react-router' import styled from 'styled-components' +import { getPoolValueLocked } from '../utils/getPoolValueLocked' +import { TinlakePool } from '../utils/tinlake/useTinlakePools' +import { useListedPools } from '../utils/useListedPools' +import { useMetadataMulti } from '../utils/useMetadata' import { PoolCard, PoolCardProps } from './PoolCard' import { PoolStatusKey } from './PoolCard/PoolStatus' +import { PoolFilter } from './PoolFilter' +import { filterPools } from './PoolFilter/utils' -type PoolListProps = { - pools: PoolCardProps[] - isLoading?: boolean -} +export type MetaDataById = Record +export type PoolMetaDataPartial = Partial | undefined const PoolCardBox = styled(Box)` &:hover { @@ -15,22 +22,137 @@ const PoolCardBox = styled(Box)` } ` -export function PoolList({ pools, isLoading }: PoolListProps) { +export function PoolList() { + const cent = useCentrifuge() + const { search } = useLocation() + const [listedPools, , metadataIsLoading] = useListedPools() + + const centPools = listedPools.filter(({ id }) => !id.startsWith('0x')) as Pool[] + const centPoolsMetaData: PoolMetaDataPartial[] = useMetadataMulti( + centPools?.map((p) => p.metadata) ?? [] + ).map((q) => q.data) + const centPoolsMetaDataById = getMetasById(centPools, centPoolsMetaData) + + const upcomingPools = [ + { + apr: Rate.fromApr(0.08), + assetClass: 'Real Estate Bridge Loans', + iconUri: 'https://storage.googleapis.com/tinlake/pool-media/new-silver-2/icon.svg', + name: 'New Silver Series 3', + status: 'Upcoming' as PoolStatusKey, + }, + { + apr: Rate.fromApr(0.15), + assetClass: 'Voluntary Carbon Offsets', + iconUri: 'https://storage.googleapis.com/tinlake/pool-media/flowcarbon-1/FlowcarbonBadge.svg', + name: 'Flowcarbon Nature Offsets Series 2', + status: 'Upcoming' as PoolStatusKey, + }, + ] + + const pools = !!listedPools?.length + ? [ + ...upcomingPools, + ...poolsToPoolCardProps(listedPools, centPoolsMetaDataById, cent).map((pool) => { + if (pool.name?.includes('Anemoy Liquid Treasury Fund')) { + return { + ...pool, + status: 'Upcoming' as PoolStatusKey, + apr: Rate.fromApr(0.05), + } + } + + return pool + }), + ].sort((a, b) => { + if (a.status === 'Upcoming') { + return -1 + } + if (b.status === 'Upcoming') { + return 1 + } + return 0 + }) + : [...upcomingPools] + const filteredPools = !!pools?.length ? filterPools(pools, new URLSearchParams(search)) : [] + + if (!listedPools.length) { + return ( + + + There are no pools yet + + + ) + } + return ( - - {isLoading - ? Array(6) - .fill(true) - .map((_, index) => ( - - - - )) - : pools.map((pool) => ( - - - - ))} + + + + + {!filteredPools.length ? ( + + + No results found with these filters. Try different filters. + + + ) : ( + + {metadataIsLoading + ? Array(6) + .fill(true) + .map((_, index) => ( + + + + )) + : filteredPools.map((pool) => ( + + + + ))} + + )} + ) } + +export function poolsToPoolCardProps( + pools: (Pool | TinlakePool)[], + metaDataById: MetaDataById, + cent: Centrifuge +): PoolCardProps[] { + return pools.map((pool) => { + const tinlakePool = pool.id?.startsWith('0x') && (pool as TinlakePool) + const mostSeniorTranche = pool?.tranches?.slice(1).at(-1) + const metaData = typeof pool.metadata === 'string' ? metaDataById[pool.id] : pool.metadata + + return { + poolId: pool.id, + name: metaData?.pool?.name, + assetClass: metaData?.pool?.asset.class, + valueLocked: getPoolValueLocked(pool), + currencySymbol: pool.currency.symbol, + apr: mostSeniorTranche?.interestRatePerSec, + status: + tinlakePool && tinlakePool.addresses.CLERK !== undefined && tinlakePool.tinlakeMetadata.maker?.ilk + ? 'Maker Pool' + : pool.tranches.at(0)?.capacity.toFloat() // pool is displayed as "open for investments" if the most junior tranche has a capacity + ? 'Open for investments' + : ('Closed' as PoolStatusKey), + iconUri: metaData?.pool?.icon?.uri ? cent.metadata.parseMetadataUrl(metaData?.pool?.icon?.uri) : undefined, + } + }) +} + +function getMetasById(pools: Pool[], poolMetas: PoolMetaDataPartial[]) { + const result: MetaDataById = {} + + pools.forEach(({ id: poolId }, index) => { + result[poolId] = poolMetas[index] + }) + + return result +} diff --git a/centrifuge-app/src/components/PoolsTokensShared.tsx b/centrifuge-app/src/components/PoolsTokensShared.tsx index 896eb090ff..e6af59d340 100644 --- a/centrifuge-app/src/components/PoolsTokensShared.tsx +++ b/centrifuge-app/src/components/PoolsTokensShared.tsx @@ -2,7 +2,7 @@ import { Grid, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { config } from '../config' import { CardTotalValueLocked } from './CardTotalValueLocked' -import { BaseSection } from './LayoutBase/BaseSection' +import { BasePadding } from './LayoutBase/BasePadding' import { LoadBoundary } from './LoadBoundary' import { MenuSwitch } from './MenuSwitch' import { PortfolioCta } from './PortfolioCta' @@ -14,7 +14,7 @@ type PoolsTokensSharedProps = { export function PoolsTokensShared({ title, children }: PoolsTokensSharedProps) { return ( - + @@ -38,6 +38,6 @@ export function PoolsTokensShared({ title, children }: PoolsTokensSharedProps) { {children} - + ) } diff --git a/centrifuge-app/src/components/AddressAllocation.tsx b/centrifuge-app/src/components/Portfolio/AssetAllocation.tsx similarity index 63% rename from centrifuge-app/src/components/AddressAllocation.tsx rename to centrifuge-app/src/components/Portfolio/AssetAllocation.tsx index b15291acf0..53428fd1d0 100644 --- a/centrifuge-app/src/components/AddressAllocation.tsx +++ b/centrifuge-app/src/components/Portfolio/AssetAllocation.tsx @@ -1,24 +1,26 @@ import { useBalances } from '@centrifuge/centrifuge-react' import { Box, Text } from '@centrifuge/fabric' import * as React from 'react' -import { useAddress } from '../utils/useAddress' -import { AllocationCard } from './AllocationCard' +import { useAddress } from '../../utils/useAddress' -export function AddressAllocation() { +export function AssetAllocation() { const address = useAddress() const balances = useBalances(address) return !!balances?.tranches && !!balances?.tranches.length ? ( - + + + Allocation + {balances?.tranches.map((tranche, index) => ( - + + Asset Class{' '} + ))} - ) : ( - No data - ) + ) : null } diff --git a/centrifuge-app/src/components/PortfolioRewards/Coins.tsx b/centrifuge-app/src/components/Portfolio/Coins.tsx similarity index 100% rename from centrifuge-app/src/components/PortfolioRewards/Coins.tsx rename to centrifuge-app/src/components/Portfolio/Coins.tsx diff --git a/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx new file mode 100644 index 0000000000..30166883d3 --- /dev/null +++ b/centrifuge-app/src/components/Portfolio/InvestedTokens.tsx @@ -0,0 +1,44 @@ +import { useBalances } from '@centrifuge/centrifuge-react' +import { Box, Grid, Stack, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { useAddress } from '../../utils/useAddress' +import { TokenCard, TOKEN_CARD_COLUMNS, TOKEN_CARD_GAP } from '../TokenCard' + +export function InvestedTokens() { + const address = useAddress() + const balances = useBalances(address) + + return !!balances?.tranches && !!balances?.tranches.length ? ( + <> + + + Portfolio Composition + + + + + + Token + + + Position + + + Token price + + + Market value + + + + + {balances.tranches.map((tranche, index) => ( + + + + ))} + + + + ) : null +} diff --git a/centrifuge-app/src/components/PortfolioRewards/index.tsx b/centrifuge-app/src/components/Portfolio/Rewards.tsx similarity index 97% rename from centrifuge-app/src/components/PortfolioRewards/index.tsx rename to centrifuge-app/src/components/Portfolio/Rewards.tsx index 337a844d31..f8973fa09d 100644 --- a/centrifuge-app/src/components/PortfolioRewards/index.tsx +++ b/centrifuge-app/src/components/Portfolio/Rewards.tsx @@ -6,7 +6,7 @@ import { formatBalance } from '../../utils/formatting' import { useComputeLiquidityRewards } from '../LiquidityRewards/hooks' import { Coins } from './Coins' -export function PortfolioRewards() { +export function Rewards() { const { colors } = useTheme() const consts = useCentrifugeConsts() const address = useAddress() @@ -34,6 +34,7 @@ export function PortfolioRewards() { style={{ boxShadow: `0px 3px 2px -2px ${colors.borderPrimary}`, }} + bg="white" > diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx new file mode 100644 index 0000000000..1449090584 --- /dev/null +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -0,0 +1,110 @@ +import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' +import { Box, Grid, Stack, Text } from '@centrifuge/fabric' +import * as React from 'react' +import { Link } from 'react-router-dom' +import { useAddress } from '../../utils/useAddress' +import { + TransactionCard, + TransactionCardProps, + TRANSACTION_CARD_COLUMNS, + TRANSACTION_CARD_GAP, +} from '../TransactionCard' + +type AddressTransactionsProps = { + count?: number +} + +// const formatters = { +// investorTransactions: ({ +// timestamp, +// type, +// poolId, +// hash, +// tokenAmount, +// tokenPrice, +// currencyAmount, +// trancheId, +// }: Omit) => { +// return { +// date: new Date(timestamp).getTime(), +// action: type, +// amount: tokenAmount, +// poolId, +// hash, +// trancheId, +// } as TransactionCardProps +// }, +// borrowerTransactions: ({ timestamp, type, amount, poolId, hash }: SubqueryBorrowerTransaction) => +// ({ +// date: new Date(timestamp).getTime(), +// action: type, +// amount, +// poolId, +// hash, +// } as TransactionCardProps), +// outstandingOrders: ({ timestamp, investAmount, redeemAmount, poolId, hash, trancheId }: SubqueryOutstandingOrder) => +// ({ +// date: new Date(timestamp).getTime(), +// action: 'PENDING_ORDER', +// amount: investAmount.add(redeemAmount), +// poolId, +// hash, +// trancheId, +// } as TransactionCardProps), +// } + +export function Transactions({ count }: AddressTransactionsProps) { + const { formatAddress } = useCentrifugeUtils() + const address = useAddress() + const formattedAddress = formatAddress(address || '') + // const allTransactions = useAllTransactions(formattedAddress) + const formattedTransactions: TransactionCardProps[] = [] + + // if (allTransactions) { + // const { borrowerTransactions, investorTransactions, outstandingOrders } = allTransactions + + // investorTransactions.forEach((transaction) => + // formattedTransactions.push(formatters.investorTransactions(transaction)) + // ) + // borrowerTransactions.forEach((transaction) => + // formattedTransactions.push(formatters.borrowerTransactions(transaction)) + // ) + // outstandingOrders.forEach((transaction) => formattedTransactions.push(formatters.outstandingOrders(transaction))) + // } + + const transactions = formattedTransactions.slice(0, count ?? formattedTransactions.length) + + return !!transactions.length ? ( + + + Transaction history + + + + Action + + + Transaction date + + + Token + + + + Amount + + + + + + {transactions.map((transaction, index) => ( + + + + ))} + + + View all + + ) : null +} diff --git a/centrifuge-app/src/pages/Pools.tsx b/centrifuge-app/src/pages/Pools.tsx index e06861c75c..b741308824 100644 --- a/centrifuge-app/src/pages/Pools.tsx +++ b/centrifuge-app/src/pages/Pools.tsx @@ -1,150 +1,14 @@ -import Centrifuge, { Pool, PoolMetadata, Rate } from '@centrifuge/centrifuge-js' -import { useCentrifuge } from '@centrifuge/centrifuge-react' -import { Box, InlineFeedback, Shelf, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' -import { useLocation } from 'react-router-dom' import { LayoutBase } from '../components/LayoutBase' -import { PoolCardProps } from '../components/PoolCard' -import { PoolStatusKey } from '../components/PoolCard/PoolStatus' -import { PoolFilter } from '../components/PoolFilter' -import { filterPools } from '../components/PoolFilter/utils' import { PoolList } from '../components/PoolList' import { PoolsTokensShared } from '../components/PoolsTokensShared' -import { getPoolValueLocked } from '../utils/getPoolValueLocked' -import { TinlakePool } from '../utils/tinlake/useTinlakePools' -import { useListedPools } from '../utils/useListedPools' -import { useMetadataMulti } from '../utils/useMetadata' - -type PoolMetaDataPartial = Partial | undefined -type MetaDataById = Record export function PoolsPage() { return ( - + ) } - -function Pools() { - const cent = useCentrifuge() - const { search } = useLocation() - const [listedPools, , metadataIsLoading] = useListedPools() - - const centPools = listedPools.filter(({ id }) => !id.startsWith('0x')) as Pool[] - const centPoolsMetaData: PoolMetaDataPartial[] = useMetadataMulti( - centPools?.map((p) => p.metadata) ?? [] - ).map((q) => q.data) - const centPoolsMetaDataById = getMetasById(centPools, centPoolsMetaData) - - const upcomingPools = [ - { - apr: Rate.fromApr(0.08), - assetClass: 'Real Estate Bridge Loans', - iconUri: 'https://storage.googleapis.com/tinlake/pool-media/new-silver-2/icon.svg', - name: 'New Silver Series 3', - status: 'Upcoming' as PoolStatusKey, - }, - { - apr: Rate.fromApr(0.15), - assetClass: 'Voluntary Carbon Offsets', - iconUri: 'https://storage.googleapis.com/tinlake/pool-media/flowcarbon-1/FlowcarbonBadge.svg', - name: 'Flowcarbon Nature Offsets Series 2', - status: 'Upcoming' as PoolStatusKey, - }, - ] - - const pools = !!listedPools?.length - ? [ - ...upcomingPools, - ...poolsToPoolCardProps(listedPools, centPoolsMetaDataById, cent).map((pool) => { - if (pool.name?.includes('Anemoy Liquid Treasury Fund')) { - return { - ...pool, - status: 'Upcoming' as PoolStatusKey, - apr: Rate.fromApr(0.05), - } - } - - return pool - }), - ].sort((a, b) => { - if (a.status === 'Upcoming') { - return -1 - } - if (b.status === 'Upcoming') { - return 1 - } - return 0 - }) - : [...upcomingPools] - const filteredPools = !!pools?.length ? filterPools(pools, new URLSearchParams(search)) : [] - - if (!listedPools.length) { - return ( - - - There are no pools yet - - - ) - } - - return ( - - - - - {!filteredPools.length ? ( - - - No results found with these filters. Try different filters. - - - ) : ( - - )} - - - ) -} - -function getMetasById(pools: Pool[], poolMetas: PoolMetaDataPartial[]) { - const result: MetaDataById = {} - - pools.forEach(({ id: poolId }, index) => { - result[poolId] = poolMetas[index] - }) - - return result -} - -function poolsToPoolCardProps( - pools: (Pool | TinlakePool)[], - metaDataById: MetaDataById, - cent: Centrifuge -): PoolCardProps[] { - return pools.map((pool) => { - const tinlakePool = pool.id?.startsWith('0x') && (pool as TinlakePool) - const mostSeniorTranche = pool?.tranches?.slice(1).at(-1) - const metaData = typeof pool.metadata === 'string' ? metaDataById[pool.id] : pool.metadata - - return { - poolId: pool.id, - name: metaData?.pool?.name, - assetClass: metaData?.pool?.asset.class, - valueLocked: getPoolValueLocked(pool), - currencySymbol: pool.currency.symbol, - apr: mostSeniorTranche?.interestRatePerSec, - status: - tinlakePool && tinlakePool.addresses.CLERK !== undefined && tinlakePool.tinlakeMetadata.maker?.ilk - ? 'Maker Pool' - : pool.tranches.at(0)?.capacity.toFloat() // pool is displayed as "open for investments" if the most junior tranche has a capacity - ? 'Open for investments' - : ('Closed' as PoolStatusKey), - iconUri: metaData?.pool?.icon?.uri ? cent.metadata.parseMetadataUrl(metaData?.pool?.icon?.uri) : undefined, - } - }) -} diff --git a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx index 02ad7260ea..dd9389317d 100644 --- a/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/Transactions/index.tsx @@ -1,33 +1,26 @@ import { Box, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' -import { AddressTransactions } from '../../../components/AddressTransactions' -import { PageWithSideBar } from '../../../components/PageWithSideBar' +import { LayoutBase } from '../../../components/LayoutBase' +import { Transactions } from '../../../components/Portfolio/Transactions' import { useAddress } from '../../../utils/useAddress' export function TransactionsPage() { - return ( - - - - ) -} - -function Transactions() { const address = useAddress() - return ( - - - - Transaction history - - + + + + + Transaction history + + - {!!address ? ( - - ) : ( - You need to connect your wallet to see your transactions - )} - + {!!address ? ( + + ) : ( + You need to connect your wallet to see your transactions + )} + + ) } diff --git a/centrifuge-app/src/pages/Portfolio/index.tsx b/centrifuge-app/src/pages/Portfolio/index.tsx index bd785d9862..48c7ae4eb6 100644 --- a/centrifuge-app/src/pages/Portfolio/index.tsx +++ b/centrifuge-app/src/pages/Portfolio/index.tsx @@ -1,94 +1,69 @@ -import { useBalances } from '@centrifuge/centrifuge-react' -import { Box, Card, Grid, Stack, Text } from '@centrifuge/fabric' +import { Card, Grid, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' -import { Link } from 'react-router-dom' -import { AddressAllocation } from '../../components/AddressAllocation' -import { AddressTokens } from '../../components/AddressTokens' -import { AddressTransactions } from '../../components/AddressTransactions' -import { PageWithSideBar } from '../../components/PageWithSideBar' +import { useTheme } from 'styled-components' +import { LayoutBase } from '../../components/LayoutBase' +import { BasePadding } from '../../components/LayoutBase/BasePadding' import { PoolList } from '../../components/PoolList' -import { PortfolioRewards } from '../../components/PortfolioRewards' +import { AssetAllocation } from '../../components/Portfolio/AssetAllocation' +import { InvestedTokens } from '../../components/Portfolio/InvestedTokens' +import { Rewards } from '../../components/Portfolio/Rewards' +import { Transactions } from '../../components/Portfolio/Transactions' import { useAddress } from '../../utils/useAddress' -import { useListedPools } from '../../utils/useListedPools' export function PortfolioPage() { return ( - + - + ) } function Portfolio() { const address = useAddress() - const balances = useBalances(address) - const showFallback = !balances?.tranches.length && !balances?.currencies.length + const theme = useTheme() + if (!address) { + return ( + + You need to connect your wallet to see your portfolio + + ) + } return ( - - - - Your portfolio - - - Track and manage your portfolio - - + <> + + + + + Your portfolio + + + Track and manage your portfolio + + - {!!address ? ( - <> - - Portfolio stats + + Portfolio overview - + - - {showFallback ? ( - - ) : ( - <> - - - Token overview - - - - - - - Transaction history - - - View all - - - - - Allocation - - - - - )} - - ) : ( - You need to connect your wallet to see your portfolio - )} - - ) -} - -function Fallback() { - const [listedPools, , metadataIsLoading] = useListedPools() - - return ( - - - Explore opportunities - - reserve.max.toFloat() > 0)} isLoading={metadataIsLoading} /> - + + + + + + + + + + Explore opportunities + + + + + + ) } From ee8b01277f165bafafb386c2fb62962e996d84d2 Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 25 Sep 2023 16:00:27 -0400 Subject: [PATCH 15/16] Remove unused var --- centrifuge-js/src/modules/pools.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/centrifuge-js/src/modules/pools.ts b/centrifuge-js/src/modules/pools.ts index ab2e3cd55e..e830df38f8 100644 --- a/centrifuge-js/src/modules/pools.ts +++ b/centrifuge-js/src/modules/pools.ts @@ -2143,8 +2143,6 @@ export function getPoolsModule(inst: Centrifuge) { mergeMap((entry) => { return getPoolCurrency([entry.poolId]).pipe( map((poolCurrency) => { - const tokenPrice = new Price(entry.tranche.tokenPrice) - return { ...entry, investAmount: new CurrencyBalance(entry.investAmount || 0, poolCurrency.decimals), From fa0913cfcfe96bcacd39ba4686371fb397dfe3d9 Mon Sep 17 00:00:00 2001 From: sophian Date: Mon, 25 Sep 2023 16:59:09 -0400 Subject: [PATCH 16/16] Clean up --- .../Portfolio/{Coins.tsx => CoinsSvg.tsx} | 2 +- .../components/Portfolio/InvestedTokens.tsx | 53 ++++- .../src/components/Portfolio/Rewards.tsx | 4 +- .../{ => Portfolio}/TransactionTypeChip.tsx | 3 +- .../src/components/Portfolio/Transactions.tsx | 200 +++++++++++++----- centrifuge-app/src/components/TokenCard.tsx | 50 ----- .../src/components/TransactionCard.tsx | 84 -------- 7 files changed, 196 insertions(+), 200 deletions(-) rename centrifuge-app/src/components/Portfolio/{Coins.tsx => CoinsSvg.tsx} (99%) rename centrifuge-app/src/components/{ => Portfolio}/TransactionTypeChip.tsx (95%) delete mode 100644 centrifuge-app/src/components/TokenCard.tsx delete mode 100644 centrifuge-app/src/components/TransactionCard.tsx diff --git a/centrifuge-app/src/components/Portfolio/Coins.tsx b/centrifuge-app/src/components/Portfolio/CoinsSvg.tsx similarity index 99% rename from centrifuge-app/src/components/Portfolio/Coins.tsx rename to centrifuge-app/src/components/Portfolio/CoinsSvg.tsx index 62d50f488b..8cfa68f8e4 100644 --- a/centrifuge-app/src/components/Portfolio/Coins.tsx +++ b/centrifuge-app/src/components/Portfolio/CoinsSvg.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -export function Coins() { +export function CoinsSvg() { return ( - + Token @@ -34,7 +38,7 @@ export function InvestedTokens() { {balances.tranches.map((tranche, index) => ( - + ))} @@ -42,3 +46,44 @@ export function InvestedTokens() { ) : null } + +type TokenCardProps = AccountTokenBalance +export function TokenListItem({ balance, currency, poolId, trancheId }: TokenCardProps) { + const pool = usePool(poolId) as Pool + const isTinlakePool = poolId?.startsWith('0x') + + if (isTinlakePool) { + return null + } + + const tranche = pool.tranches.find(({ id }) => id === trancheId) + + return ( + + + {currency.name} + + + + {formatBalance(balance, tranche?.currency.symbol)} + + + + {tranche?.tokenPrice ? formatBalance(tranche.tokenPrice.toDecimal(), tranche.currency.symbol, 4) : '-'} + + + + {tranche?.tokenPrice + ? formatBalance(balance.toDecimal().mul(tranche.tokenPrice.toDecimal()), tranche.currency.symbol, 4) + : '-'} + + + ) +} diff --git a/centrifuge-app/src/components/Portfolio/Rewards.tsx b/centrifuge-app/src/components/Portfolio/Rewards.tsx index f8973fa09d..94a31c4882 100644 --- a/centrifuge-app/src/components/Portfolio/Rewards.tsx +++ b/centrifuge-app/src/components/Portfolio/Rewards.tsx @@ -4,7 +4,7 @@ import * as React from 'react' import { useTheme } from 'styled-components' import { formatBalance } from '../../utils/formatting' import { useComputeLiquidityRewards } from '../LiquidityRewards/hooks' -import { Coins } from './Coins' +import { CoinsSvg } from './CoinsSvg' export function Rewards() { const { colors } = useTheme() @@ -67,7 +67,7 @@ export function Rewards() { - + ) diff --git a/centrifuge-app/src/components/TransactionTypeChip.tsx b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx similarity index 95% rename from centrifuge-app/src/components/TransactionTypeChip.tsx rename to centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx index 17b07db7f6..e54a3a775d 100644 --- a/centrifuge-app/src/components/TransactionTypeChip.tsx +++ b/centrifuge-app/src/components/Portfolio/TransactionTypeChip.tsx @@ -1,11 +1,12 @@ import { StatusChip, StatusChipProps } from '@centrifuge/fabric' import * as React from 'react' -import { TransactionCardProps } from './TransactionCard' +import { TransactionCardProps } from './Transactions' type TransactionTypeProps = { type: TransactionCardProps['action'] } +// @ts-expect-error const states: { [Key in TransactionCardProps['action']]: { label: string diff --git a/centrifuge-app/src/components/Portfolio/Transactions.tsx b/centrifuge-app/src/components/Portfolio/Transactions.tsx index 1449090584..3f83842cf3 100644 --- a/centrifuge-app/src/components/Portfolio/Transactions.tsx +++ b/centrifuge-app/src/components/Portfolio/Transactions.tsx @@ -1,76 +1,86 @@ -import { useCentrifugeUtils } from '@centrifuge/centrifuge-react' -import { Box, Grid, Stack, Text } from '@centrifuge/fabric' +import { + BorrowerTransactionType, + CurrencyBalance, + InvestorTransactionType, + Pool, + SubqueryInvestorTransaction, +} from '@centrifuge/centrifuge-js' +import { formatBalance, useCentrifugeUtils } from '@centrifuge/centrifuge-react' +import { Box, Grid, IconExternalLink, Stack, Text } from '@centrifuge/fabric' import * as React from 'react' import { Link } from 'react-router-dom' +import { formatDate } from '../../utils/date' import { useAddress } from '../../utils/useAddress' -import { - TransactionCard, - TransactionCardProps, - TRANSACTION_CARD_COLUMNS, - TRANSACTION_CARD_GAP, -} from '../TransactionCard' +import { useAllTransactions, usePool, usePoolMetadata } from '../../utils/usePools' +import { TransactionTypeChip } from './TransactionTypeChip' + +export const TRANSACTION_CARD_COLUMNS = `150px 100px 250px 150px 1fr` +export const TRANSACTION_CARD_GAP = 4 type AddressTransactionsProps = { count?: number } -// const formatters = { -// investorTransactions: ({ -// timestamp, -// type, -// poolId, -// hash, -// tokenAmount, -// tokenPrice, -// currencyAmount, -// trancheId, -// }: Omit) => { -// return { -// date: new Date(timestamp).getTime(), -// action: type, -// amount: tokenAmount, -// poolId, -// hash, -// trancheId, -// } as TransactionCardProps -// }, -// borrowerTransactions: ({ timestamp, type, amount, poolId, hash }: SubqueryBorrowerTransaction) => -// ({ -// date: new Date(timestamp).getTime(), -// action: type, -// amount, -// poolId, -// hash, -// } as TransactionCardProps), -// outstandingOrders: ({ timestamp, investAmount, redeemAmount, poolId, hash, trancheId }: SubqueryOutstandingOrder) => -// ({ -// date: new Date(timestamp).getTime(), -// action: 'PENDING_ORDER', -// amount: investAmount.add(redeemAmount), -// poolId, -// hash, -// trancheId, -// } as TransactionCardProps), -// } +type SubqueryBorrowerTransaction = any +type SubqueryOutstandingOrder = any + +const formatters = { + investorTransactions: ({ + timestamp, + type, + poolId, + hash, + tokenAmount, + tokenPrice, + currencyAmount, + trancheId, + }: Omit) => { + return { + date: new Date(timestamp).getTime(), + action: type, + amount: tokenAmount, + poolId, + hash, + trancheId, + } as TransactionCardProps + }, + borrowerTransactions: ({ timestamp, type, amount, poolId, hash }: SubqueryBorrowerTransaction) => + ({ + date: new Date(timestamp).getTime(), + action: type, + amount, + poolId, + hash, + } as TransactionCardProps), + outstandingOrders: ({ timestamp, investAmount, redeemAmount, poolId, hash, trancheId }: SubqueryOutstandingOrder) => + ({ + date: new Date(timestamp).getTime(), + action: 'PENDING_ORDER', + amount: investAmount.add(redeemAmount), + poolId, + hash, + trancheId, + } as TransactionCardProps), +} export function Transactions({ count }: AddressTransactionsProps) { const { formatAddress } = useCentrifugeUtils() const address = useAddress() const formattedAddress = formatAddress(address || '') - // const allTransactions = useAllTransactions(formattedAddress) + const allTransactions = useAllTransactions(formattedAddress) const formattedTransactions: TransactionCardProps[] = [] - // if (allTransactions) { - // const { borrowerTransactions, investorTransactions, outstandingOrders } = allTransactions + if (allTransactions) { + const { borrowerTransactions, investorTransactions, outstandingOrders } = allTransactions - // investorTransactions.forEach((transaction) => - // formattedTransactions.push(formatters.investorTransactions(transaction)) - // ) - // borrowerTransactions.forEach((transaction) => - // formattedTransactions.push(formatters.borrowerTransactions(transaction)) - // ) - // outstandingOrders.forEach((transaction) => formattedTransactions.push(formatters.outstandingOrders(transaction))) - // } + investorTransactions.forEach((transaction) => + formattedTransactions.push(formatters.investorTransactions(transaction)) + ) + borrowerTransactions.forEach((transaction) => + formattedTransactions.push(formatters.borrowerTransactions(transaction)) + ) + outstandingOrders.forEach((transaction) => formattedTransactions.push(formatters.outstandingOrders(transaction))) + } const transactions = formattedTransactions.slice(0, count ?? formattedTransactions.length) @@ -99,7 +109,7 @@ export function Transactions({ count }: AddressTransactionsProps) { {transactions.map((transaction, index) => ( - + ))} @@ -108,3 +118,77 @@ export function Transactions({ count }: AddressTransactionsProps) { ) : null } + +export type TransactionCardProps = { + date: number + action: InvestorTransactionType | BorrowerTransactionType | 'PENDING_ORDER' + amount: CurrencyBalance + poolId: string + hash: string + trancheId?: string +} + +export function TransactionListItem({ date, action, amount, poolId, hash, trancheId }: TransactionCardProps) { + const pool = usePool(poolId) as Pool + const { data } = usePoolMetadata(pool) + const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined + const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL + + if (!pool || !data) { + return null + } + + return ( + + + + + + + {formatDate(date, { + day: '2-digit', + month: '2-digit', + year: '2-digit', + })} + + + + + {!!token ? token.currency?.name : data.pool?.name} + + {!!token && ( + + {data?.pool?.name} + + )} + + + + + {formatBalance(amount, pool.currency.symbol)} + + + + {!!subScanUrl && !!hash && ( + + + + )} + + ) +} diff --git a/centrifuge-app/src/components/TokenCard.tsx b/centrifuge-app/src/components/TokenCard.tsx deleted file mode 100644 index 666cacf58a..0000000000 --- a/centrifuge-app/src/components/TokenCard.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { AccountTokenBalance, Pool } from '@centrifuge/centrifuge-js' -import { Grid, Text } from '@centrifuge/fabric' -import * as React from 'react' -import { formatBalance } from '../utils/formatting' -import { usePool } from '../utils/usePools' - -type TokenCardProps = AccountTokenBalance - -export const TOKEN_CARD_COLUMNS = `250px 200px 100px 150px 1FR` -export const TOKEN_CARD_GAP = 4 - -export function TokenCard({ balance, currency, poolId, trancheId }: TokenCardProps) { - const pool = usePool(poolId) as Pool - const isTinlakePool = poolId?.startsWith('0x') - - if (isTinlakePool) { - return null - } - - const tranche = pool.tranches.find(({ id }) => id === trancheId) - - return ( - - - {currency.name} - - - - {formatBalance(balance, currency)} - - - - {tranche?.tokenPrice ? formatBalance(tranche.tokenPrice, tranche.poolCurrency, 4, 2) : '-'} - - - - {tranche?.tokenPrice - ? formatBalance(balance.toDecimal().mul(tranche.tokenPrice.toDecimal()), tranche.poolCurrency, 4, 2) - : '-'} - - - ) -} diff --git a/centrifuge-app/src/components/TransactionCard.tsx b/centrifuge-app/src/components/TransactionCard.tsx deleted file mode 100644 index c0277a5eb4..0000000000 --- a/centrifuge-app/src/components/TransactionCard.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { BorrowerTransactionType, CurrencyBalance, InvestorTransactionType, Pool } from '@centrifuge/centrifuge-js' -import { Box, Grid, IconExternalLink, Stack, Text } from '@centrifuge/fabric' -import * as React from 'react' -import { formatDate } from '../utils/date' -import { formatBalance } from '../utils/formatting' -import { usePool, usePoolMetadata } from '../utils/usePools' -import { TransactionTypeChip } from './TransactionTypeChip' - -export type TransactionCardProps = { - date: number - action: InvestorTransactionType | BorrowerTransactionType | 'PENDING_ORDER' - amount: CurrencyBalance - poolId: string - hash: string - trancheId?: string -} - -export const TRANSACTION_CARD_COLUMNS = `150px 100px 250px 150px 1fr` -export const TRANSACTION_CARD_GAP = 4 - -export function TransactionCard({ date, action, amount, poolId, hash, trancheId }: TransactionCardProps) { - const pool = usePool(poolId) as Pool - const { data } = usePoolMetadata(pool) - const token = trancheId ? pool.tranches.find(({ id }) => id === trancheId) : undefined - const subScanUrl = import.meta.env.REACT_APP_SUBSCAN_URL - - if (!pool || !data) { - return null - } - - return ( - - - - - - - {formatDate(date, { - day: '2-digit', - month: '2-digit', - year: '2-digit', - })} - - - - - {!!token ? token.currency?.name : data.pool?.name} - - {!!token && ( - - {data?.pool?.name} - - )} - - - - - {formatBalance(amount, pool.currency)} - - - - {!!subScanUrl && !!hash && ( - - - - )} - - ) -}