diff --git a/src/apps/explorer/components/TransanctionBatchGraph/elementsBuilder.tsx b/src/apps/explorer/components/TransanctionBatchGraph/elementsBuilder.tsx index 41610ae46..189f08d3f 100644 --- a/src/apps/explorer/components/TransanctionBatchGraph/elementsBuilder.tsx +++ b/src/apps/explorer/components/TransanctionBatchGraph/elementsBuilder.tsx @@ -26,13 +26,13 @@ export default class ElementsBuilder { this._countEdgeDirection.set(idDirection, count + 1) } - _createNodeElement = (node: Node, parent?: string): ElementDefinition => { + _createNodeElement = (node: Node, parent?: string, hideLabel?: boolean): ElementDefinition => { this._increaseCountNodeType(node.type) return { group: 'nodes', data: { id: `${node.type}:${node.id}`, - label: node.entity.alias, + label: !hideLabel ? node.entity.alias : '', type: node.type, parent: parent ? `${TypeNodeOnTx.NetworkNode}:${parent}` : undefined, }, @@ -45,7 +45,8 @@ export default class ElementsBuilder { } node(node: Node, parent?: string): this { - this._nodes.push(this._createNodeElement(node, parent)) + const GROUP_NODE_NAME = 'group' + this._nodes.push(this._createNodeElement(node, parent, node.id.includes(GROUP_NODE_NAME))) return this } diff --git a/src/apps/explorer/components/TransanctionBatchGraph/index.tsx b/src/apps/explorer/components/TransanctionBatchGraph/index.tsx index 31b17b1a3..0c0c94c17 100644 --- a/src/apps/explorer/components/TransanctionBatchGraph/index.tsx +++ b/src/apps/explorer/components/TransanctionBatchGraph/index.tsx @@ -53,9 +53,9 @@ const WrapperCytoscape = styled(CytoscapeComponent)` ` const iconDice = [faDiceOne, faDiceTwo, faDiceThree, faDiceFour, faDiceFive] -function getTypeNode(account: Account): TypeNodeOnTx { +function getTypeNode(account: Account & { owner?: string }): TypeNodeOnTx { let type = TypeNodeOnTx.Dex - if (account.alias === ALIAS_TRADER_NAME) { + if (account.alias === ALIAS_TRADER_NAME || account.owner) { type = TypeNodeOnTx.Trader } else if (account.alias === PROTOCOL_NAME) { type = TypeNodeOnTx.CowProtocol @@ -98,13 +98,37 @@ function getNodes( const builder = new ElementsBuilder(heightSize) builder.node({ type: TypeNodeOnTx.NetworkNode, entity: networkNode, id: networkNode.alias }) + const groupNodes: string[] = [] for (const key in txSettlement.accounts) { const account = txSettlement.accounts[key] - const parentNodeName = getNetworkParentNode(account, networkNode.alias) + let parentNodeName = getNetworkParentNode(account, networkNode.alias) + + const receiverNode = { alias: `${abbreviateString(account.owner || key, 4, 4)}-group` } + + if (account.owner && account.owner !== key) { + if (!groupNodes.includes(receiverNode.alias)) { + builder.node({ type: TypeNodeOnTx.NetworkNode, entity: receiverNode, id: receiverNode.alias }) + groupNodes.push(receiverNode.alias) + } + parentNodeName = receiverNode.alias + } if (getTypeNode(account) === TypeNodeOnTx.CowProtocol) { builder.center({ type: TypeNodeOnTx.CowProtocol, entity: account, id: key }, parentNodeName) } else { + const receivers = Object.keys(txSettlement.accounts).reduce( + (acc, key) => (txSettlement.accounts?.[key].owner ? [...acc, txSettlement.accounts?.[key].owner] : acc), + [], + ) + + if (receivers.includes(key) && account.owner !== key) { + if (!groupNodes.includes(receiverNode.alias)) { + builder.node({ type: TypeNodeOnTx.NetworkNode, entity: receiverNode, id: receiverNode.alias }) + groupNodes.push(receiverNode.alias) + } + parentNodeName = receiverNode.alias + } + builder.node( { id: key, diff --git a/src/hooks/useTxBatchTrades.tsx b/src/hooks/useTxBatchTrades.tsx index a849d008d..8434a27b7 100644 --- a/src/hooks/useTxBatchTrades.tsx +++ b/src/hooks/useTxBatchTrades.tsx @@ -1,10 +1,11 @@ import { useState, useCallback, useEffect } from 'react' import { Network } from 'types' -import { getTradesAccount, getTradesAndTransfers, Trade, Transfer, Account } from 'api/tenderly' +import { getTradesAccount, getTradesAndTransfers, Trade, Transfer, Account, ALIAS_TRADER_NAME } from 'api/tenderly' import { useMultipleErc20 } from './useErc20' import { SingleErc20State } from 'state/erc20' import { Order } from 'api/operator' +import BigNumber from 'bignumber.js' import { usePrevious } from './usePrevious' interface TxBatchTrades { @@ -14,7 +15,8 @@ interface TxBatchTrades { type Dict = Record -type Accounts = Dict | undefined +type AccountWithReceiver = Account & { owner?: string; uids?: string[] } +type Accounts = Dict | undefined export interface Settlement { tokens: Dict @@ -29,6 +31,26 @@ export type GetTxBatchTradesResult = { isLoading: boolean } +const getGroupedByTransfers = (arr: Transfer[]): Transfer[] => { + return [ + ...arr + .reduce((r, t) => { + const key = `${t.token}-${t.from}-${t.to}` + + const item = + r.get(key) || + Object.assign({}, t, { + value: new BigNumber(0), + }) + + item.value = BigNumber.sum(item.value, new BigNumber(t.value)) + + return r.set(key, item) + }, new Map()) + .values(), + ] +} + export function useTxBatchTrades( networkId: Network | undefined, txHash: string, @@ -38,33 +60,59 @@ export function useTxBatchTrades( const [error, setError] = useState('') const [txBatchTrades, setTxBatchTrades] = useState({ trades: [], transfers: [] }) const [accounts, setAccounts] = useState() - const txOrders = usePrevious(JSON.stringify(orders?.map((o) => ({ owner: o.owner, kind: o.kind })))) // We need to do a deep comparison here to avoid useEffect to be called twice (Orders array is populated partially from different places) + const txOrders = usePrevious(JSON.stringify(orders?.map((o) => ({ owner: o.owner, kind: o.kind, receiver: o.receiver })))) // We need to do a deep comparison here to avoid useEffect to be called twice (Orders array is populated partially from different places) const [erc20Addresses, setErc20Addresses] = useState([]) const { value: valueErc20s, isLoading: areErc20Loading } = useMultipleErc20({ networkId, addresses: erc20Addresses }) const _fetchTxTrades = useCallback(async (network: Network, _txHash: string, orders: Order[]): Promise => { setIsLoading(true) setError('') - try { const { transfers, trades } = await getTradesAndTransfers(network, _txHash) - const _accounts = Object.fromEntries(await getTradesAccount(network, _txHash, trades, transfers)) - const orderIds = orders.map((order) => order.owner) || [] - const transfersWithKind: Transfer[] = transfers.reduce( - (acc, transfer) => - !orderIds.includes(transfer.from) && !orderIds.includes(transfer.to) ? [...acc, transfer] : acc, - [], + const _accounts: Accounts = Object.fromEntries(await getTradesAccount(network, _txHash, trades, transfers)) + const filteredOrders = orders?.filter((order) => _accounts[order.owner]) + const orderOwnersReceivers = [ + ...(filteredOrders?.map((order) => order.owner) || []), + ...(filteredOrders?.map((order) => order.receiver) || []), + ] + const groupedByTransfers = getGroupedByTransfers(transfers) + const transfersWithKind: Transfer[] = groupedByTransfers.filter( + (transfer) => !orderOwnersReceivers.includes(transfer.from) && !orderOwnersReceivers.includes(transfer.to), ) + filteredOrders?.forEach((order) => { + const { owner, kind, receiver } = order + if (!orderOwnersReceivers.includes(owner)) return + transfersWithKind.push( + ...groupedByTransfers + .filter((t) => [t.from, t.to].includes(owner)) + .map((transfer) => ({ ...transfer, kind })), + ) - orders.forEach((order) => { - const { owner, kind } = order transfersWithKind.push( - ...transfers.filter((t) => [t.from, t.to].includes(owner)).map((transfer) => ({ ...transfer, kind })), + ...groupedByTransfers + .filter((t) => [t.from, t.to].includes(receiver)) + .map((transfer) => ({ ...transfer, kind })), ) + orderOwnersReceivers.splice(orderOwnersReceivers.indexOf(owner), 1) + orderOwnersReceivers.splice(orderOwnersReceivers.indexOf(receiver), 1) }) - setErc20Addresses(transfers.map((transfer: Transfer): string => transfer.token)) - setTxBatchTrades({ trades, transfers }) - setAccounts(_accounts) + + const accountsWithReceiver = _accounts + filteredOrders?.forEach((order) => { + if (!(order.receiver in _accounts)) { + accountsWithReceiver[order.receiver] = { + alias: ALIAS_TRADER_NAME, + } + } + accountsWithReceiver[order.receiver] = { + ...accountsWithReceiver[order.receiver], + owner: order.owner, + } + }) + + setErc20Addresses(transfersWithKind.map((transfer: Transfer): string => transfer.token)) + setTxBatchTrades({ trades, transfers: transfersWithKind }) + setAccounts(accountsWithReceiver) } catch (e) { const msg = `Failed to fetch tx batch trades` console.error(msg, e) @@ -72,7 +120,9 @@ export function useTxBatchTrades( } finally { setIsLoading(false) } - }, []) + }, + [orders], + ) useEffect(() => { if (!networkId || !txOrders) {