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

Commit f848869

Browse files
wtfsayodan13ram
authored andcommitted
added support for escrow delegate
1 parent aa26c88 commit f848869

File tree

7 files changed

+167
-19
lines changed

7 files changed

+167
-19
lines changed

apps/web/src/data/contract/requests/getDAOAddresses.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AddressType, CHAIN_ID } from 'src/typings'
55
import { unpackOptionalArray } from 'src/utils/helpers'
66

77
import { managerAbi } from '../abis'
8+
import { getEscrowDelegate } from './getEscrowDelegate'
89

910
const getDAOAddresses = async (chainId: CHAIN_ID, tokenAddress: AddressType) => {
1011
const addresses = await readContract({
@@ -17,6 +18,8 @@ const getDAOAddresses = async (chainId: CHAIN_ID, tokenAddress: AddressType) =>
1718

1819
const [metadata, auction, treasury, governor] = unpackOptionalArray(addresses, 4)
1920

21+
const escrowDelegate = await getEscrowDelegate(treasury as AddressType, chainId)
22+
2023
const hasMissingAddresses = Object.values(addresses).includes(NULL_ADDRESS)
2124
if (hasMissingAddresses) return null
2225

@@ -26,6 +29,7 @@ const getDAOAddresses = async (chainId: CHAIN_ID, tokenAddress: AddressType) =>
2629
governor,
2730
metadata,
2831
treasury,
32+
escrowDelegate,
2933
}
3034
}
3135

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import axios from 'axios'
2+
import { checksumAddress, isAddress } from 'viem'
3+
4+
import { CHAIN_ID } from 'src/typings'
5+
6+
interface AttestationResponse {
7+
data: {
8+
attestations: Array<{
9+
attester: string
10+
recipient: string
11+
decodedDataJson: string
12+
}>
13+
}
14+
}
15+
16+
interface DecodedData {
17+
name: string
18+
type: string
19+
value: {
20+
type: string
21+
value: string
22+
}
23+
}
24+
25+
const ATTESTATION_SCHEMA_UID = `0x1289c5f988998891af7416d83820c40ba1c6f5ba31467f2e611172334dc53a0e`
26+
const SMART_INVOICE_MULTISIG = `0x503a5161D1c5D9d82BF35a4c80DA0C3Ad72d9244` // TODO: replace with actual multisig address
27+
const BUILDER_DAO_TREASURY = `0xcf325a4c78912216249b818521b0798a0f904c10`
28+
const BUILDER_DAO_OPS_MULTISIG = `0x58eAEfBEd9EEFbC564E302D0AfAE0B113E42eAb3`
29+
30+
const ATTESTATION_URL: Record<CHAIN_ID, string> = {
31+
[CHAIN_ID.ETHEREUM]: 'https://easscan.org/graphql',
32+
[CHAIN_ID.OPTIMISM]: 'https://optimism.easscan.org/graphql',
33+
[CHAIN_ID.SEPOLIA]: 'https://sepolia.easscan.org/graphql',
34+
[CHAIN_ID.OPTIMISM_SEPOLIA]: 'https://optimism-sepolia.easscan.org/graphql',
35+
[CHAIN_ID.BASE]: 'https://base.easscan.org/graphql',
36+
[CHAIN_ID.BASE_SEPOLIA]: 'https://base-sepolia.easscan.org/graphql',
37+
[CHAIN_ID.ZORA]: '',
38+
[CHAIN_ID.ZORA_SEPOLIA]: '',
39+
[CHAIN_ID.FOUNDRY]: '',
40+
}
41+
42+
export async function getEscrowDelegate(
43+
daoTreasuryAddress: string,
44+
chainId: CHAIN_ID
45+
): Promise<string | null> {
46+
// Input validation
47+
if (!daoTreasuryAddress || !isAddress(daoTreasuryAddress)) {
48+
return null
49+
}
50+
51+
const attestationUrl = ATTESTATION_URL[chainId]
52+
if (!attestationUrl) {
53+
return null
54+
}
55+
56+
const attestationIssuerPriorityOrder = [
57+
checksumAddress(daoTreasuryAddress),
58+
checksumAddress(BUILDER_DAO_TREASURY),
59+
checksumAddress(BUILDER_DAO_OPS_MULTISIG),
60+
checksumAddress(SMART_INVOICE_MULTISIG),
61+
]
62+
63+
const query = `
64+
query Attestations {
65+
attestations(
66+
where: {
67+
schemaId: { equals: "${ATTESTATION_SCHEMA_UID}" }
68+
attester: { in: ["${attestationIssuerPriorityOrder.join('","')}"] }
69+
recipient: { equals: "${checksumAddress(daoTreasuryAddress)}" }
70+
}
71+
) {
72+
attester
73+
recipient
74+
decodedDataJson
75+
}
76+
}
77+
`
78+
79+
try {
80+
const response = await axios.post<AttestationResponse>(
81+
attestationUrl,
82+
{ query },
83+
{
84+
headers: {
85+
'Content-Type': 'application/json',
86+
},
87+
}
88+
)
89+
90+
const attestations = response?.data?.data?.attestations
91+
92+
// Sort attestations based on priority order
93+
const sortedAttestations = attestations.sort((a, b) => {
94+
const indexA = attestationIssuerPriorityOrder.indexOf(a.attester as `0x${string}`)
95+
const indexB = attestationIssuerPriorityOrder.indexOf(b.attester as `0x${string}`)
96+
return indexA - indexB
97+
})
98+
99+
if (!attestations?.length) {
100+
return null
101+
}
102+
103+
try {
104+
// Get the first attestation from priority
105+
const decodedData = JSON.parse(
106+
sortedAttestations[0].decodedDataJson
107+
) as DecodedData[]
108+
109+
const escrowDelegateAddress = decodedData[0]?.value?.value
110+
if (!escrowDelegateAddress || !isAddress(escrowDelegateAddress)) {
111+
return null
112+
}
113+
114+
return escrowDelegateAddress
115+
} catch (parseError) {
116+
console.error('Error parsing attestation data:', parseError)
117+
return null
118+
}
119+
} catch (error) {
120+
console.error('Error fetching attestations:', error)
121+
return null
122+
}
123+
}

apps/web/src/modules/create-proposal/components/TransactionForm/Escrow/EscrowForm.tsx

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ const EscrowForm: React.FC<EscrowFormProps> = ({
119119

120120
const { formValues, setFormValues } = useEscrowFormStore()
121121
const {
122-
addresses: { treasury },
122+
addresses: { escrowDelegate, treasury },
123123
} = useDaoStore()
124124

125125
const handleSubmit = useCallback(
@@ -156,7 +156,7 @@ const EscrowForm: React.FC<EscrowFormProps> = ({
156156
<Formik
157157
initialValues={{
158158
...formValues,
159-
clientAddress: treasury || '',
159+
clientAddress: escrowDelegate || treasury || '',
160160
}}
161161
validationSchema={EscrowFormSchema}
162162
onSubmit={handleSubmit}
@@ -191,23 +191,25 @@ const EscrowForm: React.FC<EscrowFormProps> = ({
191191
'The wallet address to which funds will be released on milestone completions.'
192192
}
193193
/>
194-
<SmartInput
195-
type={TEXT}
196-
formik={formik}
197-
{...formik.getFieldProps('clientAddress')}
198-
id="clientAddress"
199-
inputLabel={'Client'}
200-
placeholder={'0x... or .eth'}
201-
isAddress={true}
202-
errorMessage={
203-
formik.touched.clientAddress && formik.errors.clientAddress
204-
? formik.errors.clientAddress
205-
: undefined
206-
}
207-
helperText={
208-
'This is the wallet address that will control the escrow for releasing funds. This can be DAO Governer Address or multisig of working group within the DAO.'
209-
}
210-
/>
194+
{!escrowDelegate && (
195+
<SmartInput
196+
type={TEXT}
197+
formik={formik}
198+
{...formik.getFieldProps('clientAddress')}
199+
id="clientAddress"
200+
inputLabel={'Client'}
201+
placeholder={'0x... or .eth'}
202+
isAddress={true}
203+
errorMessage={
204+
formik.touched.clientAddress && formik.errors.clientAddress
205+
? formik.errors.clientAddress
206+
: undefined
207+
}
208+
helperText={
209+
'This is the wallet address that will control the escrow for releasing funds. This can be DAO Governer Address or multisig of working group within the DAO.'
210+
}
211+
/>
212+
)}
211213

212214
<DatePicker
213215
{...formik.getFieldProps('safetyValveDate')}

apps/web/src/modules/dao/components/SmartContracts.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ export const SmartContracts = () => {
8989
<ContractLink title="Governor" address={addresses.governor} />
9090
<ContractLink title="Treasury" address={addresses.treasury} />
9191
<ContractLink title="Metadata" address={addresses.metadata} />
92+
{addresses?.escrowDelegate && (
93+
<ContractLink title="Escrow Delegate" address={addresses.escrowDelegate} />
94+
)}
9295
</Flex>
9396
</Flex>
9497
</Box>

apps/web/src/modules/dao/stores/useDaoStore.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface DaoContractAddresses {
1616
auction?: AddressType
1717
treasury?: AddressType
1818
governor?: AddressType
19+
escrowDelegate?: AddressType
1920
}
2021

2122
export interface DaoContracts {
@@ -38,6 +39,7 @@ export const useDaoStore = create<DaoStoreProps>((set) => ({
3839
auction: undefined,
3940
treasury: undefined,
4041
governor: undefined,
42+
escrowDelegate: undefined,
4143
},
4244
setAddresses: (addresses: DaoContractAddresses) => set({ addresses }),
4345
}))

apps/web/src/pages/dao/[network]/[token]/[tokenId].tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CACHE_TIMES } from 'src/constants/cacheTimes'
1111
import { PUBLIC_ALL_CHAINS, PUBLIC_DEFAULT_CHAINS } from 'src/constants/defaultChains'
1212
import { CAST_ENABLED } from 'src/constants/farcasterEnabled'
1313
import { SUCCESS_MESSAGES } from 'src/constants/messages'
14+
import { getEscrowDelegate } from 'src/data/contract/requests/getEscrowDelegate'
1415
import { SDK } from 'src/data/subgraph/client'
1516
import { TokenWithDaoQuery } from 'src/data/subgraph/sdk.generated'
1617
import { useVotes } from 'src/hooks'
@@ -212,12 +213,18 @@ export const getServerSideProps: GetServerSideProps = async ({
212213
auctionAddress,
213214
} = token.dao
214215

216+
const escrowDelegateAddress = (await getEscrowDelegate(
217+
treasuryAddress,
218+
chain.id
219+
)) as AddressType
220+
215221
const addresses: DaoContractAddresses = {
216222
token: collection,
217223
metadata: metadataAddress,
218224
treasury: treasuryAddress,
219225
governor: governorAddress,
220226
auction: auctionAddress,
227+
escrowDelegate: escrowDelegateAddress,
221228
}
222229

223230
const daoOgMetadata: DaoOgMetadata = {

apps/web/src/pages/dao/[network]/[token]/vote/[id].tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Meta } from 'src/components/Meta'
1111
import { CACHE_TIMES } from 'src/constants/cacheTimes'
1212
import { PUBLIC_DEFAULT_CHAINS } from 'src/constants/defaultChains'
1313
import SWR_KEYS from 'src/constants/swrKeys'
14+
import { getEscrowDelegate } from 'src/data/contract/requests/getEscrowDelegate'
1415
import { SDK } from 'src/data/subgraph/client'
1516
import {
1617
formatAndFetchState,
@@ -211,6 +212,11 @@ export const getServerSideProps: GetServerSideProps = async ({ params, req, res
211212
auctionAddress,
212213
} = data.dao
213214

215+
const escrowDelegateAddress = (await getEscrowDelegate(
216+
treasuryAddress,
217+
chain.id
218+
)) as AddressType
219+
214220
const ogMetadata: ProposalOgMetadata = {
215221
proposal: {
216222
proposalNumber: proposal.proposalNumber,
@@ -230,6 +236,7 @@ export const getServerSideProps: GetServerSideProps = async ({ params, req, res
230236
governor: governorAddress,
231237
treasury: treasuryAddress,
232238
auction: auctionAddress,
239+
escrowDelegate: escrowDelegateAddress,
233240
}
234241

235242
const ogImageURL = `${protocol}://${

0 commit comments

Comments
 (0)