Skip to content

Commit

Permalink
handle-safeTx (#2562)
Browse files Browse the repository at this point in the history
  • Loading branch information
tienkane authored Nov 29, 2024
1 parent e2a7eec commit f02bca1
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 114 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@reach/dialog": "^0.17.0",
"@reach/portal": "^0.17.0",
"@reduxjs/toolkit": "1.9.3",
"@safe-global/safe-apps-sdk": "^9.1.0",
"@sentry/react": "^8.34.0",
"@tanstack/react-query": "^5.52.1",
"@use-gesture/react": "^10.2.27",
Expand Down
265 changes: 151 additions & 114 deletions src/state/transactions/updater.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { BigNumber } from '@ethersproject/bignumber'
import { ChainId } from '@kyberswap/ks-sdk-core'
import SafeAppsSDK, { TransactionStatus as SafeTransactionStatus } from '@safe-global/safe-apps-sdk'
import { ethers } from 'ethers'
import { findReplacementTx } from 'find-replacement-tx'
import { useEffect, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'

import { NotificationType } from 'components/Announcement/type'
import { useActiveWeb3React } from 'hooks'
import { CONNECTION } from 'components/Web3Provider'
import { useActiveWeb3React, useWeb3React } from 'hooks'
import useMixpanel, { MIXPANEL_TYPE, NEED_CHECK_SUBGRAPH_TRANSACTION_TYPES } from 'hooks/useMixpanel'
import { useBlockNumber, useKyberSwapConfig, useTransactionNotify } from 'state/application/hooks'
import { AppDispatch, AppState } from 'state/index'
Expand All @@ -16,6 +18,8 @@ import { findTx } from 'utils'
import { checkedTransaction, finalizeTransaction, modifyTransaction, removeTx, replaceTx } from './actions'
import { SerializableTransactionReceipt, TRANSACTION_TYPE, TransactionDetails } from './type'

const appsSdk = new SafeAppsSDK()

function shouldCheck(
lastBlockNumber: number,
tx?: { addedTime: number; receipt?: SerializableTransactionReceipt; lastCheckedBlockNumber?: number },
Expand All @@ -40,6 +44,7 @@ function shouldCheck(

export default function Updater(): null {
const { chainId, account } = useActiveWeb3React()
const { connector } = useWeb3React()
const { readProvider } = useKyberSwapConfig(chainId)

const lastBlockNumber = useBlockNumber()
Expand All @@ -55,6 +60,122 @@ export default function Updater(): null {
const { mixpanelHandler, subgraphMixpanelHandler } = useMixpanel()
const transactionNotify = useTransactionNotify()

const handleTransactionReceipt = (hash: string, receipt: any) => {
if (!lastBlockNumber) return
if (
!receipt ||
receipt?.txStatus === SafeTransactionStatus.AWAITING_EXECUTION ||
receipt?.txStatus === SafeTransactionStatus.AWAITING_CONFIRMATIONS
) {
dispatch(checkedTransaction({ chainId, hash, blockNumber: lastBlockNumber }))
return
}

const transaction = findTx(transactions, receipt.transactionHash)
if (!transaction) return
dispatch(
finalizeTransaction({
chainId,
hash: receipt.transactionHash,
receipt: {
blockHash: receipt.blockHash,
status: receipt.status,
},
needCheckSubgraph: NEED_CHECK_SUBGRAPH_TRANSACTION_TYPES.includes(transaction.type),
}),
)

transactionNotify({
hash: receipt.transactionHash,
type: receipt.status === 1 ? NotificationType.SUCCESS : NotificationType.ERROR,
account: account ?? '',
})
if (receipt.status === 1) {
// Swapped (address sender, address srcToken, address dstToken, address dstReceiver, uint256 spentAmount, uint256 returnAmount)
const swapEventTopic = ethers.utils.id('Swapped(address,address,address,address,uint256,uint256)')
const swapLogs = receipt.logs.filter((log: any) => log.topics[0] === swapEventTopic)
// take the last swap event
const lastSwapEvent = swapLogs.slice(-1)[0]

// decode the data
const swapInterface = new ethers.utils.Interface([
'event Swapped (address sender, address srcToken, address dstToken, address dstReceiver, uint256 spentAmount, uint256 returnAmount)',
])
const parsed = swapInterface.parseLog(lastSwapEvent)

if (
(transaction.extraInfo as any)?.tokenAmountOut &&
transaction.extraInfo?.arbitrary?.outputDecimals !== undefined
) {
const extraInfo = { ...transaction.extraInfo }
;(extraInfo as any).tokenAmountOut = ethers.utils.formatUnits(
parsed.args.returnAmount.toString(),
transaction.extraInfo?.arbitrary?.outputDecimals,
)
dispatch(
modifyTransaction({
chainId: transaction.chainId,
hash: transaction.hash,
extraInfo,
}),
)
}

const arbitrary = transaction.extraInfo?.arbitrary
switch (transaction.type) {
case TRANSACTION_TYPE.SWAP: {
if (!arbitrary) return
if (account && arbitrary.isPermitSwap) {
dispatch(revokePermit({ chainId, address: arbitrary.inputAddress, account }))
}
mixpanelHandler(MIXPANEL_TYPE.SWAP_COMPLETED, {
arbitrary,
actual_gas: receipt.gasUsed || BigNumber.from(0),
gas_price: receipt.effectiveGasPrice || BigNumber.from(0),
tx_hash: receipt.transactionHash,
feeInfo: arbitrary.feeInfo,
})
break
}
case TRANSACTION_TYPE.BRIDGE: {
if (arbitrary) {
mixpanelHandler(MIXPANEL_TYPE.BRIDGE_TRANSACTION_SUBMIT, {
...arbitrary,
tx_hash: receipt.transactionHash,
})
}
break
}
case TRANSACTION_TYPE.ELASTIC_COLLECT_FEE: {
if (arbitrary) {
mixpanelHandler(MIXPANEL_TYPE.ELASTIC_COLLECT_FEES_COMPLETED, arbitrary)
}
break
}
case TRANSACTION_TYPE.ELASTIC_INCREASE_LIQUIDITY: {
if (arbitrary) {
mixpanelHandler(MIXPANEL_TYPE.ELASTIC_INCREASE_LIQUIDITY_COMPLETED, {
...arbitrary,
tx_hash: receipt.transactionHash,
})
}
break
}
case TRANSACTION_TYPE.CANCEL_LIMIT_ORDER: {
if (arbitrary) {
mixpanelHandler(MIXPANEL_TYPE.LO_CANCEL_ORDER_SUBMITTED, {
...arbitrary,
tx_hash: receipt.transactionHash,
})
}
break
}
default:
break
}
}
}

useEffect(() => {
if (!readProvider || !lastBlockNumber) return
const uniqueTransactions = [
Expand Down Expand Up @@ -111,122 +232,38 @@ export default function Updater(): null {
}
})
.catch(console.warn)
readProvider
.getTransactionReceipt(hash)
.then(receipt => {
if (!receipt) {
dispatch(checkedTransaction({ chainId, hash, blockNumber: lastBlockNumber }))
return
}

const transaction = findTx(transactions, receipt.transactionHash)
if (!transaction) return
dispatch(
finalizeTransaction({
chainId,
hash: receipt.transactionHash,
receipt: {
blockHash: receipt.blockHash,
status: receipt.status,
},
needCheckSubgraph: NEED_CHECK_SUBGRAPH_TRANSACTION_TYPES.includes(transaction.type),
}),
)

transactionNotify({
hash: receipt.transactionHash,
type: receipt.status === 1 ? NotificationType.SUCCESS : NotificationType.ERROR,
account: account ?? '',
if (connector?.id === CONNECTION.SAFE_CONNECTOR_ID) {
appsSdk.txs
.getBySafeTxHash(hash)
.then(receipt => {
handleTransactionReceipt(
hash,
receipt
? {
...receipt,
transactionHash: hash,
blockHash: '',
status: receipt.txStatus === SafeTransactionStatus.SUCCESS ? 1 : 0,
}
: undefined,
)
})
if (receipt.status === 1) {
// Swapped (address sender, address srcToken, address dstToken, address dstReceiver, uint256 spentAmount, uint256 returnAmount)
const swapEventTopic = ethers.utils.id('Swapped(address,address,address,address,uint256,uint256)')
const swapLogs = receipt.logs.filter(log => log.topics[0] === swapEventTopic)
// take the last swap event
const lastSwapEvent = swapLogs.slice(-1)[0]

// decode the data
const swapInterface = new ethers.utils.Interface([
'event Swapped (address sender, address srcToken, address dstToken, address dstReceiver, uint256 spentAmount, uint256 returnAmount)',
])
const parsed = swapInterface.parseLog(lastSwapEvent)

if (
(transaction.extraInfo as any)?.tokenAmountOut &&
transaction.extraInfo?.arbitrary?.outputDecimals !== undefined
) {
const extraInfo = { ...transaction.extraInfo }
;(extraInfo as any).tokenAmountOut = ethers.utils.formatUnits(
parsed.args.returnAmount.toString(),
transaction.extraInfo?.arbitrary?.outputDecimals,
)
dispatch(
modifyTransaction({
chainId: transaction.chainId,
hash: transaction.hash,
extraInfo,
}),
)
}

const arbitrary = transaction.extraInfo?.arbitrary
switch (transaction.type) {
case TRANSACTION_TYPE.SWAP: {
if (!arbitrary) return
if (account && arbitrary.isPermitSwap) {
dispatch(revokePermit({ chainId, address: arbitrary.inputAddress, account }))
}
mixpanelHandler(MIXPANEL_TYPE.SWAP_COMPLETED, {
arbitrary,
actual_gas: receipt.gasUsed || BigNumber.from(0),
gas_price: receipt.effectiveGasPrice || BigNumber.from(0),
tx_hash: receipt.transactionHash,
feeInfo: arbitrary.feeInfo,
})
break
}
case TRANSACTION_TYPE.BRIDGE: {
if (arbitrary) {
mixpanelHandler(MIXPANEL_TYPE.BRIDGE_TRANSACTION_SUBMIT, {
...arbitrary,
tx_hash: receipt.transactionHash,
})
}
break
}
case TRANSACTION_TYPE.ELASTIC_COLLECT_FEE: {
if (arbitrary) {
mixpanelHandler(MIXPANEL_TYPE.ELASTIC_COLLECT_FEES_COMPLETED, arbitrary)
}
break
}
case TRANSACTION_TYPE.ELASTIC_INCREASE_LIQUIDITY: {
if (arbitrary) {
mixpanelHandler(MIXPANEL_TYPE.ELASTIC_INCREASE_LIQUIDITY_COMPLETED, {
...arbitrary,
tx_hash: receipt.transactionHash,
})
}
break
}
case TRANSACTION_TYPE.CANCEL_LIMIT_ORDER: {
if (arbitrary) {
mixpanelHandler(MIXPANEL_TYPE.LO_CANCEL_ORDER_SUBMITTED, {
...arbitrary,
tx_hash: receipt.transactionHash,
})
}
break
}
default:
break
}
}
})
.catch((error: any) => {
console.error(`failed to check transaction hash: ${hash}`, error)
})
.catch((error: any) => {
console.error(`failed to check transaction hash: ${hash}`, error)
})
} else {
readProvider
.getTransactionReceipt(hash)
.then(receipt => {
handleTransactionReceipt(hash, receipt)
})
.catch((error: any) => {
console.error(`failed to check transaction hash: ${hash}`, error)
})
}
})

uniqueTransactions
.filter(hash => findTx(transactions, hash)?.extraInfo?.needCheckSubgraph)
.forEach(async (hash: string) => {
Expand Down

0 comments on commit f02bca1

Please sign in to comment.