From 8cefe4358af45fda5952e88f7307dbc69d92a917 Mon Sep 17 00:00:00 2001 From: Onno Visser Date: Fri, 2 Aug 2024 10:13:36 +0200 Subject: [PATCH] Finance form: Withdraw address select (#2319) --- centrifuge-app/src/config.ts | 6 +- centrifuge-app/src/pages/Loan/FinanceForm.tsx | 159 ++++++++++++------ fabric/src/components/Select/index.tsx | 33 ++-- 3 files changed, 134 insertions(+), 64 deletions(-) diff --git a/centrifuge-app/src/config.ts b/centrifuge-app/src/config.ts index f726439d19..2aa155576f 100644 --- a/centrifuge-app/src/config.ts +++ b/centrifuge-app/src/config.ts @@ -151,11 +151,13 @@ export const ethConfig = { export const config = import.meta.env.REACT_APP_NETWORK === 'altair' ? ALTAIR : CENTRIFUGE +const assetHubChainId = import.meta.env.REACT_APP_IS_DEMO ? 1001 : 1000 + export const parachainNames: Record = { - 1000: 'Asset Hub', + [assetHubChainId]: 'Asset Hub', } export const parachainIcons: Record = { - 1000: assetHubLogo, + [assetHubChainId]: assetHubLogo, } const infuraKey = import.meta.env.REACT_APP_INFURA_KEY diff --git a/centrifuge-app/src/pages/Loan/FinanceForm.tsx b/centrifuge-app/src/pages/Loan/FinanceForm.tsx index ed24db9c0b..70b820f174 100644 --- a/centrifuge-app/src/pages/Loan/FinanceForm.tsx +++ b/centrifuge-app/src/pages/Loan/FinanceForm.tsx @@ -25,10 +25,12 @@ import { Button, Card, CurrencyInput, + Flex, Grid, GridRow, InlineFeedback, Select, + SelectInner, Shelf, Stack, Text, @@ -51,7 +53,7 @@ import { isExternalLoan } from './utils' const TOKENMUX_PALLET_ACCOUNTID = '0x6d6f646c6366672f746d75780000000000000000000000000000000000000000' -type Key = `${'parachain' | 'evm'}:${number}` +type Key = `${'parachain' | 'evm'}:${number}` | 'centrifuge' type FinanceValues = { amount: number | '' | Decimal withdraw: undefined | WithdrawAddress @@ -226,18 +228,19 @@ function WithdrawSelect({ withdrawAddresses }: { withdrawAddresses: WithdrawAddr } function Mux({ - withdrawAddressesByDomain, withdrawAmounts, + selectedAddressIndexByCurrency, + setSelectedAddressIndex, }: { amount: Decimal total: Decimal - withdrawAddressesByDomain: Record withdrawAmounts: WithdrawBucket[] + selectedAddressIndexByCurrency: Record + setSelectedAddressIndex: (currency: string, index: number) => void }) { const utils = useCentrifugeUtils() const getName = useGetNetworkName() const getIcon = useGetNetworkIcon() - return ( Transactions per network @@ -252,32 +255,51 @@ function Mux({ No suitable withdraw addresses )} - {withdrawAmounts.map(({ currency, amount, locationKey }) => { - const address = withdrawAddressesByDomain[locationKey][0] + {withdrawAmounts.map(({ currency, amount, addresses, currencyKey }) => { + const index = selectedAddressIndexByCurrency[currencyKey] ?? 0 + const address = addresses.at(index >>> 0) // undefined when index is -1 return ( {formatBalance(amount, currency.symbol)} - {truncateAddress(utils.formatAddress(address.address))} - - + ({ + label: truncateAddress(utils.formatAddress(addr.address)), + value: index.toString(), + })), + ]} + value={index.toString()} + onChange={(event) => setSelectedAddressIndex(currencyKey, parseInt(event.target.value))} + small /> - {typeof address.location === 'string' - ? getName(address.location as any) - : 'parachain' in address.location - ? parachainNames[address.location.parachain] - : getName(address.location.evm)} - + + + + {address && ( + + + {typeof address.location === 'string' + ? getName(address.location as any) + : 'parachain' in address.location + ? parachainNames[address.location.parachain] + : getName(address.location.evm)} + + )} ) @@ -294,6 +316,7 @@ export function useWithdraw(poolId: string, borrower: CombinedSubstrateAccount, const muxBalances = useBalances(TOKENMUX_PALLET_ACCOUNTID) const cent: Centrifuge = useCentrifuge() const api = useCentrifugeApi() + const [selectedAddressIndexByCurrency, setSelectedAddressIndexByCurrency] = React.useState>({}) const ao = access.assetOriginators.find((a) => a.address === borrower.actingAddress) const withdrawAddresses = ao?.transferAllowlist ?? [] @@ -330,25 +353,26 @@ export function useWithdraw(poolId: string, borrower: CombinedSubstrateAccount, } const sortedBalances = sortBalances(muxBalances?.currencies || [], pool.currency) - const withdrawAmounts = muxBalances?.currencies - ? divideBetweenCurrencies(amount, sortedBalances, withdrawAddresses) - : [] - const totalAvailable = withdrawAmounts.reduce((acc, cur) => acc.add(cur.amount), Dec(0)) - const withdrawAddressesByDomain: Record = {} - withdrawAddresses.forEach((addr) => { - const key = locationToKey(addr.location) - if (withdrawAddressesByDomain[key]) { - withdrawAddressesByDomain[key].push(addr) - } else { - withdrawAddressesByDomain[key] = [addr] - } + const ignoredCurrencies = Object.entries(selectedAddressIndexByCurrency).flatMap(([key, index]) => { + return index === -1 ? [key] : [] }) + const { buckets: withdrawAmounts } = muxBalances?.currencies + ? divideBetweenCurrencies(amount, sortedBalances, withdrawAddresses, ignoredCurrencies) + : { buckets: [] } + + const totalAvailable = withdrawAmounts.reduce((acc, cur) => acc.add(cur.amount), Dec(0)) return { render: () => ( { + setSelectedAddressIndexByCurrency((prev) => ({ + ...prev, + [currencyToString(currencyKey)]: index, + })) + }} total={totalAvailable} amount={amount} /> @@ -357,8 +381,8 @@ export function useWithdraw(poolId: string, borrower: CombinedSubstrateAccount, getBatch: () => { return combineLatest( withdrawAmounts.flatMap((bucket) => { - // TODO: Select specific withdraw address for a domain if there's multiple - const withdraw = withdrawAddressesByDomain[bucket.locationKey][0] + const index = selectedAddressIndexByCurrency[bucket.currencyKey] ?? 0 + const withdraw = bucket.addresses[index] if (bucket.amount.isZero()) return [] return [ of( @@ -394,6 +418,8 @@ export function useWithdraw(poolId: string, borrower: CombinedSubstrateAccount, const order: Record = { 'evm:8453': 5, 'evm:84531': 5, + 'parachain:1000': 4, + 'parachain:1001': 4, 'parachain:2000': 4, 'evm:42220': 3, 'evm:44787': 3, @@ -418,37 +444,70 @@ function sortBalances(balances: AccountCurrencyBalance[], localPoolCurrency: Cur } function locationToKey(location: WithdrawAddress['location']) { - return Object.entries(location)[0].join(':') as Key + return typeof location === 'string' ? location : (Object.entries(location)[0].join(':') as Key) } -type WithdrawBucket = { currency: CurrencyMetadata; amount: Decimal; locationKey: Key } +function currencyToString(currencyKey: CurrencyMetadata['key']) { + return JSON.stringify(currencyKey).replace(/"/g, '') +} + +type WithdrawBucket = { + currency: CurrencyMetadata + amount: Decimal + locationKey: Key + currencyKey: string + addresses: WithdrawAddress[] +} function divideBetweenCurrencies( amount: Decimal, balances: AccountCurrencyBalance[], withdrawAddresses: WithdrawAddress[], + ignoredCurrencies: string[], result: WithdrawBucket[] = [] ) { const [next, ...rest] = balances - if (!next) return result + if (!next) { + return { + buckets: result, + remainder: amount, + } + } - const hasAddress = !!withdrawAddresses.find( - (addr) => locationToKey(addr.location) === locationToKey(getCurrencyLocation(next.currency)) + const addresses = withdrawAddresses.filter((addr) => + [locationToKey(getCurrencyLocation(next.currency)), 'centrifuge'].includes(locationToKey(addr.location)) ) const key = locationToKey(getCurrencyLocation(next.currency)) let combinedResult = [...result] let remainder = amount - if (hasAddress) { + if (addresses.length) { const balanceDec = next.balance.toDecimal() - if (remainder.lte(balanceDec)) { - combinedResult.push({ amount: remainder, currency: next.currency, locationKey: key }) + let obj = { + currency: next.currency, + locationKey: key, + addresses, + currencyKey: currencyToString(next.currency.key), + } + if (ignoredCurrencies.includes(obj.currencyKey)) { + combinedResult.push({ + amount: Dec(0), + ...obj, + }) + } else if (remainder.lte(balanceDec)) { + combinedResult.push({ + amount: remainder, + ...obj, + }) remainder = Dec(0) } else { remainder = remainder.sub(balanceDec) - combinedResult.push({ amount: balanceDec, currency: next.currency, locationKey: key }) + combinedResult.push({ + amount: balanceDec, + ...obj, + }) } } - return divideBetweenCurrencies(remainder, rest, withdrawAddresses, combinedResult) + return divideBetweenCurrencies(remainder, rest, withdrawAddresses, ignoredCurrencies, combinedResult) } diff --git a/fabric/src/components/Select/index.tsx b/fabric/src/components/Select/index.tsx index d5cc088833..38fa43e539 100644 --- a/fabric/src/components/Select/index.tsx +++ b/fabric/src/components/Select/index.tsx @@ -16,6 +16,7 @@ export type SelectProps = React.SelectHTMLAttributes & { label?: string | React.ReactElement placeholder?: string errorMessage?: string + small?: boolean } const StyledSelect = styled.select` @@ -40,20 +41,28 @@ const StyledSelect = styled.select` } ` -const Chevron = styled(IconChevronDown)` - position: absolute; - top: 0; - right: 0; - bottom: 0; - margin-top: auto; - margin-bottom: auto; - pointer-events: none; -` - -export function SelectInner({ options, placeholder, disabled, ...rest }: Omit) { +export function SelectInner({ + options, + placeholder, + disabled, + small, + ...rest +}: Omit) { return ( - + {placeholder && (