-
Notifications
You must be signed in to change notification settings - Fork 202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: new tx history - cache tx details #1371
Changes from 87 commits
be0e9e0
64369cf
2c4ad7b
c4a6df9
188be70
e73ba69
f2225bf
080affe
67f4c5d
6da99ec
8543bef
5278afc
47c0ba9
3228452
43fb44e
6c46790
2f72b6c
f2dc99e
99ec664
316391b
8ae47c2
52b9d25
070df16
4379fa4
19decb3
08f40da
672e31c
7ccac83
b4dbcb2
d2772c0
fdd545b
ebe5c86
cd06852
809ddb2
51a7794
68a6fba
5e8bd99
97360ab
785ce95
7df1356
c44f665
58c06c7
95bb24d
036fa5a
03047bb
065f498
71a5416
6b13f36
6070b51
2c6244a
bd9a7c2
ee0fad1
3c69e43
d4fe7b7
e2be948
952bb2e
7809856
ceb555b
b350668
4133320
5ca9b1f
d20fe79
e2cec54
f6bf775
fb93b23
237455d
1307f5f
40e554e
3e57e91
9d1d5de
842b5dc
24fb120
589da16
b254b73
f910b8c
162a420
11812a9
6fb25d8
56de752
e30ce4d
dd28b66
bdb7746
0b6a2cd
c95a523
09e3e80
8a2b2e1
fb6347e
1e5c043
85e745a
43ca07a
ffedd37
3875249
b8903d2
9acba66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,14 +15,22 @@ import { | |
WithdrawalStatus | ||
} from '../../state/app/state' | ||
import { ChainId, getBlockTime, isNetwork, rpcURLs } from '../../util/networks' | ||
import { isCctpTransfer } from '../../hooks/useTransactionHistory' | ||
import { | ||
ChainPair, | ||
Deposit, | ||
isCctpTransfer | ||
} from '../../hooks/useTransactionHistory' | ||
import { getWagmiChain } from '../../util/wagmi/getWagmiChain' | ||
import { getL1ToL2MessageDataFromL1TxHash } from '../../util/deposits/helpers' | ||
import { AssetType } from '../../hooks/arbTokenBridge.types' | ||
import { getDepositStatus } from '../../state/app/utils' | ||
import { getBlockBeforeConfirmation } from '../../state/cctpState' | ||
import { getAttestationHashAndMessageFromReceipt } from '../../util/cctp/getAttestationHashAndMessageFromReceipt' | ||
|
||
const CLAIM_PARENT_CHAIN_DETAILS_LOCAL_STORAGE_KEY = | ||
'arbitrum:bridge:claim:parent:tx:details' | ||
const DEPOSITS_LOCAL_STORAGE_KEY = 'arbitrum:bridge:deposits' | ||
|
||
export enum StatusLabel { | ||
PENDING = 'Pending', | ||
CLAIMABLE = 'Claimable', | ||
|
@@ -101,13 +109,21 @@ export function getProvider(chainId: ChainId) { | |
} | ||
|
||
export function isSameTransaction( | ||
tx_1: MergedTransaction, | ||
tx_2: MergedTransaction | ||
txDetails_1: { | ||
txId: string | ||
parentChainId: ChainId | ||
childChainId: ChainId | ||
}, | ||
txDetails_2: { | ||
txId: string | ||
parentChainId: ChainId | ||
childChainId: ChainId | ||
} | ||
) { | ||
return ( | ||
tx_1.txId === tx_2.txId && | ||
tx_1.parentChainId === tx_2.parentChainId && | ||
tx_1.childChainId === tx_2.childChainId | ||
txDetails_1.txId === txDetails_2.txId && | ||
txDetails_1.parentChainId === txDetails_2.parentChainId && | ||
txDetails_1.childChainId === txDetails_2.childChainId | ||
) | ||
} | ||
|
||
|
@@ -133,6 +149,99 @@ function getWithdrawalStatusFromReceipt( | |
} | ||
} | ||
|
||
export function getDepositsWithoutStatusesFromCache(): Deposit[] { | ||
return JSON.parse( | ||
localStorage.getItem(DEPOSITS_LOCAL_STORAGE_KEY) ?? '[]' | ||
) as Deposit[] | ||
} | ||
|
||
/** | ||
* Cache deposits from event logs. We don't fetch these so we need to store them locally. | ||
* | ||
* @param {MergedTransaction} tx - Deposit from event logs to be cached. | ||
*/ | ||
export function addDepositToCache(tx: Deposit) { | ||
if (tx.direction !== 'deposit') { | ||
return | ||
} | ||
|
||
const cachedDeposits = getDepositsWithoutStatusesFromCache() | ||
|
||
const foundInCache = cachedDeposits.find(cachedTx => | ||
isSameTransaction( | ||
{ ...cachedTx, txId: cachedTx.txID }, | ||
{ ...tx, txId: tx.txID } | ||
) | ||
) | ||
|
||
if (foundInCache) { | ||
return | ||
} | ||
|
||
const newCachedDeposits = [tx, ...cachedDeposits] | ||
|
||
localStorage.setItem( | ||
DEPOSITS_LOCAL_STORAGE_KEY, | ||
JSON.stringify(newCachedDeposits) | ||
) | ||
} | ||
|
||
/** | ||
* Cache parent chain tx details when claiming. This is the chain the funds were claimed on. We store locally because we don't have access to this tx from the child chain tx data. | ||
* | ||
* @param {MergedTransaction} tx - Transaction that initiated the withdrawal (child chain transaction). | ||
* @param {string} parentChainTxId - Transaction ID of the claim transaction (parent chain transaction ID). | ||
*/ | ||
export function setWithdrawalClaimParentChainTxDetails( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated |
||
tx: MergedTransaction, | ||
parentChainTxId: string | ||
) { | ||
const key = `${tx.parentChainId}-${tx.childChainId}-${tx.txId}` | ||
|
||
const cachedClaimParentChainTxId = JSON.parse( | ||
localStorage.getItem(CLAIM_PARENT_CHAIN_DETAILS_LOCAL_STORAGE_KEY) ?? '{}' | ||
) | ||
|
||
if (key in cachedClaimParentChainTxId) { | ||
// already set | ||
return | ||
} | ||
|
||
localStorage.setItem( | ||
CLAIM_PARENT_CHAIN_DETAILS_LOCAL_STORAGE_KEY, | ||
JSON.stringify({ | ||
...cachedClaimParentChainTxId, | ||
[key]: { | ||
txId: parentChainTxId, | ||
timestamp: dayjs().valueOf() | ||
} | ||
}) | ||
) | ||
} | ||
|
||
export function getWithdrawalClaimParentChainTxDetails( | ||
tx: MergedTransaction | ||
): { txId: string; timestamp: number } | undefined { | ||
if (!tx.isWithdrawal || tx.isCctp) { | ||
return undefined | ||
} | ||
|
||
const key = `${tx.parentChainId}-${tx.childChainId}-${tx.txId}` | ||
|
||
const cachedClaimParentChainTxDetails = ( | ||
JSON.parse( | ||
localStorage.getItem(CLAIM_PARENT_CHAIN_DETAILS_LOCAL_STORAGE_KEY) ?? '{}' | ||
) as { | ||
[key in string]: { | ||
txId: string | ||
timestamp: number | ||
} | ||
} | ||
)[key] | ||
|
||
return cachedClaimParentChainTxDetails | ||
} | ||
|
||
export async function getUpdatedEthDeposit( | ||
tx: MergedTransaction | ||
): Promise<MergedTransaction> { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,7 @@ import { useUpdateUSDCBalances } from './CCTP/useUpdateUSDCBalances' | |
import { useNativeCurrency } from './useNativeCurrency' | ||
import { useTransactionHistory } from './useTransactionHistory' | ||
import { DepositStatus, WithdrawalStatus } from '../state/app/state' | ||
import { addDepositToCache } from '../components/TransactionHistory/helpers' | ||
|
||
export const wait = (ms = 0) => { | ||
return new Promise(res => setTimeout(res, ms)) | ||
|
@@ -188,6 +189,9 @@ export const useArbTokenBridge = ( | |
} | ||
|
||
const ethBridger = await EthBridger.fromProvider(l2.provider) | ||
const parentChainBlockTimestamp = Math.floor( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm I was getting a decimal value for some reason before but might have been a mistake on my part, removing math floor |
||
(await l1.provider.getBlock('latest')).timestamp | ||
) | ||
|
||
let tx: L1EthDepositTransaction | ||
|
||
|
@@ -212,7 +216,7 @@ export const useArbTokenBridge = ( | |
destination: walletAddress, | ||
direction: 'deposit-l1', | ||
status: 'pending', | ||
createdAt: dayjs().valueOf(), | ||
createdAt: parentChainBlockTimestamp * 1_000, | ||
resolvedAt: null, | ||
txId: tx.hash, | ||
asset: nativeCurrency.symbol, | ||
|
@@ -227,6 +231,25 @@ export const useArbTokenBridge = ( | |
childChainId: Number(l2NetworkID) | ||
}) | ||
|
||
addDepositToCache({ | ||
sender: walletAddress, | ||
destination: walletAddress, | ||
status: 'pending', | ||
txID: tx.hash, | ||
assetName: nativeCurrency.symbol, | ||
assetType: AssetType.ETH, | ||
l1NetworkID, | ||
l2NetworkID, | ||
value: utils.formatUnits(amount, nativeCurrency.decimals), | ||
parentChainId: Number(l1NetworkID), | ||
childChainId: Number(l2NetworkID), | ||
direction: 'deposit', | ||
type: 'deposit-l1', | ||
source: 'local_storage_cache', | ||
timestampCreated: String(parentChainBlockTimestamp), | ||
nonce: tx.nonce | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Requested by @fionnachan, adding nonce so that we can fix #986 in the future |
||
}) | ||
|
||
const receipt = await tx.wait() | ||
|
||
if (txLifecycle?.onTxConfirm) { | ||
|
@@ -398,6 +421,9 @@ export const useArbTokenBridge = ( | |
return | ||
} | ||
const erc20Bridger = await Erc20Bridger.fromProvider(l2.provider) | ||
const parentChainBlockTimestamp = Math.floor( | ||
(await l1.provider.getBlock('latest')).timestamp | ||
) | ||
|
||
try { | ||
const { symbol, decimals } = await fetchErc20Data({ | ||
|
@@ -428,7 +454,7 @@ export const useArbTokenBridge = ( | |
destination: destinationAddress ?? walletAddress, | ||
direction: 'deposit-l1', | ||
status: 'pending', | ||
createdAt: dayjs().valueOf(), | ||
createdAt: parentChainBlockTimestamp * 1_000, | ||
resolvedAt: null, | ||
txId: tx.hash, | ||
asset: symbol, | ||
|
@@ -443,6 +469,25 @@ export const useArbTokenBridge = ( | |
childChainId: Number(l2NetworkID) | ||
}) | ||
|
||
addDepositToCache({ | ||
sender: walletAddress, | ||
destination: destinationAddress ?? walletAddress, | ||
status: 'pending', | ||
txID: tx.hash, | ||
assetName: symbol, | ||
assetType: AssetType.ERC20, | ||
l1NetworkID, | ||
l2NetworkID, | ||
fionnachan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
value: utils.formatUnits(amount, decimals), | ||
parentChainId: Number(l1NetworkID), | ||
childChainId: Number(l2NetworkID), | ||
direction: 'deposit', | ||
type: 'deposit-l1', | ||
source: 'local_storage_cache', | ||
timestampCreated: String(parentChainBlockTimestamp), | ||
nonce: tx.nonce | ||
}) | ||
|
||
const receipt = await tx.wait() | ||
|
||
if (txLifecycle?.onTxConfirm) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,10 @@ import { MergedTransaction, WithdrawalStatus } from '../state/app/state' | |
import { isUserRejectedError } from '../util/isUserRejectedError' | ||
import { errorToast } from '../components/common/atoms/Toast' | ||
import { AssetType, L2ToL1EventResultPlus } from './arbTokenBridge.types' | ||
import { getProvider } from '../components/TransactionHistory/helpers' | ||
import { | ||
getProvider, | ||
setWithdrawalClaimParentChainTxDetails | ||
} from '../components/TransactionHistory/helpers' | ||
import { L2TransactionReceipt } from '@arbitrum/sdk' | ||
import { ContractReceipt, utils } from 'ethers' | ||
import { useTransactionHistory } from './useTransactionHistory' | ||
|
@@ -109,12 +112,17 @@ export function useClaimWithdrawal(): UseClaimWithdrawalResult { | |
} | ||
|
||
const isSuccess = (res as ContractReceipt).status === 1 | ||
const txHash = (res as ContractReceipt).transactionHash | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Read timestamp from here |
||
|
||
updatePendingTransaction({ | ||
...tx, | ||
status: isSuccess ? WithdrawalStatus.EXECUTED : WithdrawalStatus.FAILURE, | ||
resolvedAt: isSuccess ? dayjs().valueOf() : null | ||
}) | ||
|
||
if (isSuccess) { | ||
setWithdrawalClaimParentChainTxDetails(tx, txHash) | ||
} | ||
} | ||
|
||
return { claim, isClaiming } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Can we name it better? Wasn't clear at first what it meant 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like
PARENT_CHAIN_TX_DETAILS_OF_CLAIM_TX
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated