Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit 7981d2d

Browse files
committed
added escrow milestones view for proposal
1 parent 92f6531 commit 7981d2d

File tree

5 files changed

+562
-138
lines changed

5 files changed

+562
-138
lines changed

apps/web/src/modules/proposal/components/ProposalDescription/DecodedTransactions.tsx

Lines changed: 105 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,133 @@
11
import { Box, Flex, Stack, Text, atoms } from '@zoralabs/zord'
2-
import axios from 'axios'
2+
import { toLower } from 'lodash'
3+
import { get } from 'lodash'
34
import React, { Fragment } from 'react'
4-
import useSWR from 'swr'
5-
import { formatEther } from 'viem'
5+
import { decodeAbiParameters, formatEther, parseAbiParameters } from 'viem'
66

77
import { ETHERSCAN_BASE_URL } from 'src/constants/etherscan'
8-
import SWR_KEYS from 'src/constants/swrKeys'
8+
import { getEscrowBundler } from 'src/modules/create-proposal/components/TransactionForm/Escrow/EscrowUtils'
99
import { useChainStore } from 'src/stores/useChainStore'
10-
import { CHAIN_ID } from 'src/typings'
1110
import { walletSnippet } from 'src/utils/helpers'
11+
import { DecodedTransaction } from './useDecodedTransactions'
1212

1313
interface DecodedTransactionProps {
14-
targets: string[]
15-
calldatas: string[]
16-
values: string[]
14+
decodedTransactions: DecodedTransaction[] | undefined
1715
}
16+
1817
export const DecodedTransactions: React.FC<DecodedTransactionProps> = ({
19-
targets,
20-
calldatas,
21-
values,
18+
decodedTransactions,
2219
}) => {
2320
const chain = useChainStore((x) => x.chain)
2421

25-
/*
26-
27-
format in shape defined in ethers actor
28-
29-
*/
30-
const formatSendEth = (value: string) => {
31-
const amount = formatEther(BigInt(value))
32-
return {
33-
functionName: 'Transfer',
34-
name: 'Transfer',
35-
args: {
36-
['Transfer']: { name: `value`, value: `${amount} ETH` },
37-
},
38-
}
39-
}
40-
41-
const decodeTransaction = async (
42-
chainId: CHAIN_ID,
43-
target: string,
44-
calldata: string,
45-
value: string
46-
) => {
47-
/* if calldata is '0x' */
48-
const isTransfer = calldata === '0x'
22+
const renderArgument = (arg: any) => {
23+
if (arg?.name === 'escrowData') {
24+
// Decode escrow data
25+
const decodedAbiData = decodeAbiParameters(
26+
parseAbiParameters([
27+
'address client',
28+
'address resolver',
29+
'uint8 resolverType',
30+
'address token',
31+
'uint256 terminationTime',
32+
'bytes32 details',
33+
'address provider',
34+
'address providerReceiver',
35+
'bool requireVerification',
36+
'bytes32 escrowType',
37+
]),
38+
arg.value
39+
)
4940

50-
if (isTransfer) {
51-
return formatSendEth(value)
41+
return (
42+
<Stack pl={'x2'} gap={'x1'}>
43+
<Flex>client/signer: {get(decodedAbiData, '[0]', '')}</Flex>
44+
<Flex>resolver: {get(decodedAbiData, '[1]', '')}</Flex>
45+
<Flex>
46+
Safety Valve Date:{' '}
47+
{new Date(Number(get(decodedAbiData, '[4]', 0)) * 1000).toLocaleString()}
48+
</Flex>
49+
<Flex>Service Provider: {get(decodedAbiData, '[6]', '')}</Flex>
50+
</Stack>
51+
)
5252
}
5353

54-
try {
55-
const decoded = await axios.post('/api/decode', {
56-
calldata: calldata,
57-
contract: target,
58-
chain: chainId,
59-
})
60-
61-
if (decoded?.data?.statusCode) throw new Error('Decode failed')
62-
63-
return decoded.data
64-
} catch (err) {
65-
console.log('err', err)
66-
67-
// if this tx has value display it as a send eth tx
68-
if (value.length && parseInt(value)) return formatSendEth(value)
69-
70-
// if no value return original calldata
71-
return calldata
72-
}
54+
return (
55+
<Flex key={arg?.name}>
56+
{arg?.name}:{' '}
57+
{arg?.name === '_milestoneAmounts'
58+
? arg.value
59+
.split(',')
60+
.map((amt: string) => `${formatEther(BigInt(amt))} ETH`)
61+
.join(', ')
62+
: arg?.name === '_fundAmount'
63+
? formatEther(BigInt(arg?.value)) + ' ETH'
64+
: arg?.value}
65+
</Flex>
66+
)
7367
}
7468

75-
const { data: decodedTransactions } = useSWR(
76-
targets && calldatas && values
77-
? [SWR_KEYS.PROPOSALS_TRANSACTIONS, targets, calldatas, values]
78-
: null,
79-
async (_, targets, calldatas, values) => {
80-
return await Promise.all(
81-
targets.map(async (target, i) => {
82-
const transaction = await decodeTransaction(
83-
chain.id,
84-
target,
85-
calldatas[i],
86-
values[i]
87-
)
88-
89-
return {
90-
target,
91-
transaction,
92-
isNotDecoded: transaction === calldatas[i],
93-
}
94-
})
95-
)
96-
},
97-
{ revalidateOnFocus: false }
98-
)
99-
10069
return (
10170
<Stack style={{ maxWidth: 600, wordBreak: 'break-word' }}>
10271
<ol>
103-
{decodedTransactions?.map((decoded, i) => (
104-
<Fragment key={`${decoded.target}-${i}`}>
105-
{decoded.isNotDecoded ? (
106-
<li className={atoms({ paddingBottom: 'x4' })}>{decoded.transaction}</li>
107-
) : (
108-
<li className={atoms({ paddingBottom: 'x4' })}>
109-
<Stack>
110-
<Stack gap={'x1'}>
111-
<Box
112-
color={'secondary'}
113-
fontWeight={'heading'}
114-
className={atoms({ textDecoration: 'underline' })}
115-
>
116-
<a
117-
href={`${ETHERSCAN_BASE_URL[chain.id]}/address/${
118-
decoded?.target
119-
}`}
120-
target="_blank"
121-
rel="noreferrer"
72+
{decodedTransactions?.map((decoded, i) => {
73+
const isEscrow = toLower(decoded.target).includes(toLower(getEscrowBundler(chain.id)))
74+
75+
return (
76+
<Fragment key={`${decoded.target}-${i}`}>
77+
{decoded.isNotDecoded ? (
78+
<li className={atoms({ paddingBottom: 'x4' })}>{decoded.transaction.toString()}</li>
79+
) : (
80+
<li className={atoms({ paddingBottom: 'x4' })}>
81+
<Stack>
82+
<Stack gap={'x1'}>
83+
<Box
84+
color={'secondary'}
85+
fontWeight={'heading'}
86+
className={atoms({ textDecoration: 'underline' })}
12287
>
123-
<Text display={{ '@initial': 'flex', '@768': 'none' }}>
124-
{walletSnippet(decoded?.target)}
125-
</Text>
126-
<Text display={{ '@initial': 'none', '@768': 'flex' }}>
127-
{decoded?.target}
128-
</Text>
129-
</a>
130-
</Box>
131-
<Flex pl={'x2'}>
132-
{`.${decoded?.transaction?.functionName}(`}
133-
{!decoded?.transaction?.args &&
134-
!decoded.transaction.decoded.length &&
88+
<a
89+
href={`${ETHERSCAN_BASE_URL[chain.id]}/address/${decoded?.target
90+
}`}
91+
target="_blank"
92+
rel="noreferrer"
93+
>
94+
<Text display={{ '@initial': 'flex', '@768': 'none' }}>
95+
{walletSnippet(decoded?.target)}
96+
</Text>
97+
<Text display={{ '@initial': 'none', '@768': 'flex' }}>
98+
{decoded?.target}
99+
</Text>
100+
</a>
101+
</Box>
102+
<Flex pl={'x2'}>
103+
{`.${!isEscrow ? decoded?.transaction?.functionName : 'deployEscrow'
104+
}(`}
105+
{!decoded?.transaction?.args &&
106+
!decoded.transaction?.decoded?.length &&
107+
`)`}
108+
</Flex>
109+
110+
<Stack pl={'x4'} gap={'x1'}>
111+
{(decoded?.transaction?.args &&
112+
Object?.values(decoded?.transaction?.args).map((arg: any) =>
113+
renderArgument(arg)
114+
)) ||
115+
(decoded?.transaction?.decoded &&
116+
decoded?.transaction?.decoded?.map((arg: any) => (
117+
<Flex key={arg}>{arg}</Flex>
118+
)))}
119+
</Stack>
120+
121+
{(!!decoded?.transaction?.args ||
122+
!!decoded?.transaction?.decoded?.length) &&
135123
`)`}
136-
</Flex>
137-
<Stack pl={'x4'} gap={'x1'}>
138-
{(decoded?.transaction?.args &&
139-
Object?.values(decoded?.transaction?.args).map((arg: any) => (
140-
// if verified contract and arguments object {name, value}
141-
<Flex key={arg?.name}>
142-
{arg?.name}: {arg?.value}
143-
</Flex>
144-
))) ||
145-
// if unverified contract and arguments array [value]
146-
(decoded?.transaction?.decoded &&
147-
decoded?.transaction?.decoded?.map((arg: any) => (
148-
<Flex key={arg}>{arg}</Flex>
149-
)))}
150124
</Stack>
151-
{(!!decoded?.transaction?.args ||
152-
!!decoded?.transaction.decoded.length) &&
153-
`)`}
154125
</Stack>
155-
</Stack>
156-
</li>
157-
)}
158-
</Fragment>
159-
))}
126+
</li>
127+
)}
128+
</Fragment>
129+
);
130+
})}
160131
</ol>
161132
</Stack>
162133
)

0 commit comments

Comments
 (0)