From f53cddb67ae74329dc396e477e368c6fb967afd2 Mon Sep 17 00:00:00 2001 From: Matthew Callens Date: Fri, 10 Mar 2023 16:42:56 -0500 Subject: [PATCH] multiple transaction approval aggregation --- .../Unlocked/Approvals/ApproveTransaction.tsx | 171 ++++++++++++++---- .../recoil/src/hooks/useTransactionData.tsx | 14 +- 2 files changed, 144 insertions(+), 41 deletions(-) diff --git a/packages/app-extension/src/components/Unlocked/Approvals/ApproveTransaction.tsx b/packages/app-extension/src/components/Unlocked/Approvals/ApproveTransaction.tsx index 0b40187010..4d6a927602 100644 --- a/packages/app-extension/src/components/Unlocked/Approvals/ApproveTransaction.tsx +++ b/packages/app-extension/src/components/Unlocked/Approvals/ApproveTransaction.tsx @@ -1,13 +1,16 @@ +import { useEffect, useState } from "react"; import type { Blockchain, FeeConfig } from "@coral-xyz/common"; import { EmptyState, Loading } from "@coral-xyz/react-common"; import { isKeyCold, + type TransactionData as TransactionDataType, + useMultipleTransactionsData, useTransactionData, useWalletBlockchain, } from "@coral-xyz/recoil"; import { styles as makeStyles } from "@coral-xyz/themes"; import { Block as BlockIcon } from "@mui/icons-material"; -import { Typography } from "@mui/material"; +import { type ClassNameMap, Typography } from "@mui/material"; import { BigNumber, ethers } from "ethers"; import { useRecoilValue } from "recoil"; @@ -31,12 +34,12 @@ const useStyles = makeStyles((theme) => ({ fontSize: "14px", marginBottom: "8px", }, - warning: { - color: theme.custom.colors.negative, - fontSize: "14px", - textAlign: "center", - marginTop: "8px", - }, + // warning: { + // color: theme.custom.colors.negative, + // fontSize: "14px", + // textAlign: "center", + // marginTop: "8px", + // }, negative: { color: theme.custom.colors.negative, }, @@ -79,36 +82,6 @@ export function ApproveTransaction({ return ; } - const menuItems = balanceChanges - ? Object.fromEntries( - Object.entries(balanceChanges).map( - ([symbol, { nativeChange, decimals }]) => { - const className = nativeChange.gte(Zero) - ? classes.positive - : classes.negative; - return [ - symbol, - { - onClick: () => {}, - detail: ( - - {ethers.utils.commify( - ethers.utils.formatUnits( - nativeChange, - BigNumber.from(decimals) - ) - )}{" "} - {symbol} - - ), - button: false, - }, - ]; - } - ) - ) - : {}; - const onConfirm = async () => { await onCompletion(transaction, solanaFeeConfig); }; @@ -142,7 +115,7 @@ export function ApproveTransaction({ @@ -191,7 +164,6 @@ export function ApproveAllTransactions({ origin, title, wallet, - // eslint-disable-next-line txs, onCompletion, }: { @@ -202,7 +174,49 @@ export function ApproveAllTransactions({ onCompletion: (confirmed: boolean) => void; }) { const classes = useStyles(); + const blockchain = useWalletBlockchain(wallet); + const transactionsData = useMultipleTransactionsData( + blockchain as Blockchain, + txs + ); const _isKeyCold = useRecoilValue(isKeyCold(wallet)); + const [loading, setLoading] = useState(true); + const [consolidated, setConsolidated] = useState( + null + ); + + useEffect(() => { + if (transactionsData.some((t) => t.loading)) { + return; + } + + const allBalanceChanges: TransactionDataType["balanceChanges"] = {}; + let allNetworkFees = "0"; + + for (const tx of transactionsData) { + if (tx.balanceChanges) { + Object.entries(tx.balanceChanges).forEach(([key, val]) => { + allBalanceChanges[key] = { + nativeChange: ( + allBalanceChanges[key]?.nativeChange ?? BigNumber.from("0") + ).add(val.nativeChange), + decimals: val.decimals, + }; + }); + } + + allNetworkFees = ( + parseFloat(allNetworkFees) + parseFloat(tx.networkFee) + ).toPrecision(2); + } + + setConsolidated({ + ...transactionsData[0], + balanceChanges: allBalanceChanges, + networkFee: allNetworkFees, + }); + setLoading(false); + }, [transactionsData]); const onConfirm = async () => { onCompletion(true); @@ -226,7 +240,84 @@ export function ApproveAllTransactions({ onConfirmLabel="Approve" onDeny={onDeny} > -
Confirming multiple transactions
+ {loading || !consolidated ? ( + + ) : ( +
+ + Transaction Aggregate Details ({transactionsData.length}) + + +
+ )} + {/* {transactionsData.map((tx, i) => + loading ? ( + + ) : ( +
+ + Transaction details #{i + 1} + + +
+ ) + )} */} ); } + +function createTransactionDataMenuItems( + tx: TransactionDataType, + classes: ClassNameMap +): any { + return tx.balanceChanges + ? Object.fromEntries( + Object.entries(tx.balanceChanges).map( + ([symbol, { nativeChange, decimals }]) => { + const className = nativeChange.gte(Zero) + ? classes.positive + : classes.negative; + return [ + symbol, + { + onClick: () => {}, + detail: ( + + {ethers.utils.commify( + ethers.utils.formatUnits( + nativeChange, + BigNumber.from(decimals) + ) + )}{" "} + {symbol} + + ), + button: false, + }, + ]; + } + ) + ) + : {}; +} diff --git a/packages/recoil/src/hooks/useTransactionData.tsx b/packages/recoil/src/hooks/useTransactionData.tsx index d685f97dbc..fe1eadd83a 100644 --- a/packages/recoil/src/hooks/useTransactionData.tsx +++ b/packages/recoil/src/hooks/useTransactionData.tsx @@ -31,7 +31,7 @@ import { const { base58: bs58 } = ethers.utils; const DEFAULT_GAS_LIMIT = BigNumber.from("150000"); -type TransactionData = { +export type TransactionData = { loading: boolean; transaction: string; solanaFeeConfig?: { config: SolanaFeeConfig; disabled: boolean }; @@ -70,6 +70,18 @@ export function useTransactionData( useSolanaTxData(transaction); } +export function useMultipleTransactionsData( + blockchain: Blockchain, + transactions: string[] +): TransactionData[] { + // FIXME: remove lint blocker + return blockchain === Blockchain.ETHEREUM + ? // eslint-disable-next-line react-hooks/rules-of-hooks + transactions.map((tx) => useEthereumTxData(tx)) + : // eslint-disable-next-line react-hooks/rules-of-hooks + transactions.map((tx) => useSolanaTxData(tx)); +} + // // Transaction data for Ethereum //