Skip to content

Commit e632ef5

Browse files
committed
feat: add candidate creation flow
1 parent e91af00 commit e632ef5

92 files changed

Lines changed: 4593 additions & 657 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/subgraph/schema.graphql

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,16 @@ type Snapshot @entity {
104104
proposalCount: Int!
105105
}
106106

107-
type Proposal @entity {
107+
interface ProposalMetadataEntity {
108+
id: ID!
109+
title: String
110+
description: String
111+
metadata: String
112+
representedAddress: String
113+
discussionUrl: String
114+
}
115+
116+
type Proposal implements ProposalMetadataEntity @entity {
108117
id: ID!
109118
proposalId: Bytes!
110119
proposalNumber: Int!
@@ -192,7 +201,7 @@ type ProposalCandidateGroup @entity {
192201
currentAbstainCount: BigInt!
193202
}
194203

195-
type ProposalCandidateVersion @entity {
204+
type ProposalCandidateVersion implements ProposalMetadataEntity @entity {
196205
id: ID!
197206
group: ProposalCandidateGroup!
198207
candidateId: Bytes!
@@ -202,12 +211,13 @@ type ProposalCandidateVersion @entity {
202211
targets: [Bytes!]!
203212
values: [BigInt!]!
204213
calldatas: [Bytes!]!
205-
description: String!
214+
title: String
215+
description: String
216+
metadata: String
217+
representedAddress: String
218+
discussionUrl: String
206219
proposalId: Bytes!
207220
createdAt: BigInt!
208-
title: String!
209-
summary: String!
210-
discussionUrl: String
211221
signatures: [CandidateSponsorSignature!]! @derivedFrom(field: "version")
212222
signatureCount: BigInt!
213223
totalVoteWeight: BigInt!

apps/subgraph/src/eas.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Address, BigInt, Bytes, json, JSONValueKind } from '@graphprotocol/graph-ts'
1+
import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts'
22

33
import {
44
Attested as AttestedEvent,
@@ -32,6 +32,7 @@ import {
3232
PROPOSAL_CANDIDATE_SCHEMA_UID,
3333
TREASURY_ASSET_PIN_SCHEMA_UID,
3434
} from './utils/eas'
35+
import { parseProposalMetadata } from './utils/proposalMetadata'
3536

3637
const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'
3738

@@ -206,23 +207,11 @@ function loadOrCreateCandidateGroup(
206207
group.currentAgainstCount = BigInt.fromI32(0)
207208
group.currentAbstainCount = BigInt.fromI32(0)
208209
group.leadingVersion = null
210+
group.save()
209211
}
210212
return group
211213
}
212214

213-
function getDescriptionField(description: string, key: string): string {
214-
let parsedResult = json.try_fromString(description)
215-
if (parsedResult.isError || parsedResult.value.kind != JSONValueKind.OBJECT) {
216-
return ''
217-
}
218-
let obj = parsedResult.value.toObject()
219-
let value = obj.get(key)
220-
if (!value || value.kind != JSONValueKind.STRING) {
221-
return ''
222-
}
223-
return value.toString()
224-
}
225-
226215
function recomputeVersionSignatureAggregates(versionId: string): void {
227216
let version = ProposalCandidateVersion.load(versionId)
228217
if (!version) return
@@ -397,15 +386,24 @@ function handleProposalCandidateAttestation(event: AttestedEvent): void {
397386
version.targets = targets
398387
version.values = decoded.values
399388
version.calldatas = decoded.calldatas
400-
version.description = decoded.description
389+
version.metadata = decoded.description
401390
version.proposalId = decoded.proposalId
402391
version.createdAt = event.block.timestamp
403-
let title = getDescriptionField(decoded.description, 'title')
404-
let summary = getDescriptionField(decoded.description, 'description')
405-
let discussionUrl = getDescriptionField(decoded.description, 'discussionUrl')
406-
version.title = title.length > 0 ? title : ''
407-
version.summary = summary.length > 0 ? summary : ''
408-
version.discussionUrl = discussionUrl.length > 0 ? discussionUrl : null
392+
let parsedMetadata = parseProposalMetadata(decoded.description)
393+
version.title =
394+
parsedMetadata && parsedMetadata.title.length > 0 ? parsedMetadata.title : null
395+
version.description =
396+
parsedMetadata && parsedMetadata.description.length > 0
397+
? parsedMetadata.description
398+
: null
399+
version.representedAddress =
400+
parsedMetadata && parsedMetadata.representedAddress.length > 0
401+
? parsedMetadata.representedAddress
402+
: null
403+
version.discussionUrl =
404+
parsedMetadata && parsedMetadata.discussionUrl.length > 0
405+
? parsedMetadata.discussionUrl
406+
: null
409407
version.save()
410408
recomputeGroupVersionAggregates(group.id)
411409
recomputeGroupLeadingVersion(group.id)

apps/subgraph/src/governor.ts

Lines changed: 13 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import {
55
Bytes,
66
crypto,
77
dataSource,
8-
json,
9-
JSONValueKind,
108
log,
119
} from '@graphprotocol/graph-ts'
1210

@@ -33,49 +31,26 @@ import {
3331
} from '../generated/templates/Governor/Governor'
3432
import { Token as TokenContract } from '../generated/templates/Governor/Token'
3533
import { Treasury as TreasuryContract } from '../generated/templates/Governor/Treasury'
34+
import { parseProposalMetadata } from './utils/proposalMetadata'
3635

3736
function parseDescriptionFields(descriptionMetadata: string): string[] {
3837
let title: string | null = null
3938
let description: string | null = null
4039
let representedAddress: string | null = null
4140
let discussionUrl: string | null = null
4241

43-
let parsedDescriptionResult = json.try_fromString(descriptionMetadata)
44-
45-
if (
46-
!parsedDescriptionResult.isError &&
47-
parsedDescriptionResult.value.kind == JSONValueKind.OBJECT
48-
) {
49-
let parsedDescription = parsedDescriptionResult.value.toObject()
50-
51-
let parsedTitle = parsedDescription.get('title')
52-
if (parsedTitle && parsedTitle.kind == JSONValueKind.STRING) {
53-
let parsedTitleValue = parsedTitle.toString()
54-
title = parsedTitleValue.length > 0 ? parsedTitleValue : null
55-
}
56-
57-
let parsedBody = parsedDescription.get('description')
58-
if (parsedBody && parsedBody.kind == JSONValueKind.STRING) {
59-
let parsedBodyValue = parsedBody.toString()
60-
description = parsedBodyValue.length > 0 ? parsedBodyValue : null
61-
}
62-
63-
let parsedRepresentedAddress = parsedDescription.get('representedAddress')
64-
if (
65-
parsedRepresentedAddress &&
66-
parsedRepresentedAddress.kind == JSONValueKind.STRING
67-
) {
68-
let parsedRepresentedAddressValue = parsedRepresentedAddress.toString()
69-
representedAddress =
70-
parsedRepresentedAddressValue.length > 0 ? parsedRepresentedAddressValue : null
71-
}
72-
73-
let parsedDiscussionUrl = parsedDescription.get('discussionUrl')
74-
if (parsedDiscussionUrl && parsedDiscussionUrl.kind == JSONValueKind.STRING) {
75-
let parsedDiscussionUrlValue = parsedDiscussionUrl.toString()
76-
discussionUrl =
77-
parsedDiscussionUrlValue.length > 0 ? parsedDiscussionUrlValue : null
78-
}
42+
let parsedDescription = parseProposalMetadata(descriptionMetadata)
43+
44+
if (parsedDescription) {
45+
title = parsedDescription.title.length > 0 ? parsedDescription.title : null
46+
description =
47+
parsedDescription.description.length > 0 ? parsedDescription.description : null
48+
representedAddress =
49+
parsedDescription.representedAddress.length > 0
50+
? parsedDescription.representedAddress
51+
: null
52+
discussionUrl =
53+
parsedDescription.discussionUrl.length > 0 ? parsedDescription.discussionUrl : null
7954
} else {
8055
let split = descriptionMetadata.split('&&')
8156
title = split.length > 0 && split[0].length > 0 ? split[0] : null
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { json, JSONValue, JSONValueKind, TypedMap } from '@graphprotocol/graph-ts'
2+
3+
export class ParsedProposalMetadata {
4+
title: string
5+
description: string
6+
representedAddress: string
7+
discussionUrl: string
8+
9+
constructor(
10+
title: string = '',
11+
description: string = '',
12+
representedAddress: string = '',
13+
discussionUrl: string = ''
14+
) {
15+
this.title = title
16+
this.description = description
17+
this.representedAddress = representedAddress
18+
this.discussionUrl = discussionUrl
19+
}
20+
}
21+
22+
function getStringField(obj: TypedMap<string, JSONValue>, key: string): string {
23+
let value = obj.get(key)
24+
if (!value || value.kind != JSONValueKind.STRING) {
25+
return ''
26+
}
27+
28+
return value.toString()
29+
}
30+
31+
export function parseProposalMetadata(metadata: string): ParsedProposalMetadata | null {
32+
let parsedResult = json.try_fromString(metadata)
33+
if (parsedResult.isError || parsedResult.value.kind != JSONValueKind.OBJECT) {
34+
return null
35+
}
36+
37+
let obj = parsedResult.value.toObject()
38+
return new ParsedProposalMetadata(
39+
getStringField(obj, 'title'),
40+
getStringField(obj, 'description'),
41+
getStringField(obj, 'representedAddress'),
42+
getStringField(obj, 'discussionUrl')
43+
)
44+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { assert, describe, test } from 'matchstick-as'
2+
3+
import { parseProposalMetadata } from '../src/utils/proposalMetadata'
4+
5+
describe('Proposal metadata parser', () => {
6+
test('parses canonical proposal metadata JSON', () => {
7+
const parsed = parseProposalMetadata(
8+
'{"version":1,"title":"JSON title","description":"JSON body","representedAddress":"0x1234","discussionUrl":"https://example.com/discussion","transactionBundles":[{"type":"transfer","callCount":1}]}'
9+
)
10+
11+
assert.assertNotNull(parsed)
12+
if (!parsed) return
13+
14+
assert.stringEquals(parsed.title, 'JSON title')
15+
assert.stringEquals(parsed.description, 'JSON body')
16+
assert.stringEquals(parsed.representedAddress, '0x1234')
17+
assert.stringEquals(parsed.discussionUrl, 'https://example.com/discussion')
18+
})
19+
20+
test('returns null for non-json metadata', () => {
21+
const parsed = parseProposalMetadata('Legacy title&&Legacy body')
22+
assert.assertTrue(parsed == null)
23+
})
24+
})

apps/web/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const basicConfig = {
3737
'@buildeross/stores',
3838
'@buildeross/auction-ui',
3939
'@buildeross/proposal-ui',
40+
'@buildeross/candidate-ui',
4041
'@buildeross/dao-ui',
4142
'@buildeross/feed-ui',
4243
'@buildeross/create-proposal-ui',

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"@buildeross/analytics": "workspace:*",
77
"@buildeross/auction-ui": "workspace:*",
88
"@buildeross/blocklist": "workspace:*",
9+
"@buildeross/candidate-ui": "workspace:*",
910
"@buildeross/constants": "workspace:*",
1011
"@buildeross/create-dao-ui": "workspace:*",
1112
"@buildeross/create-proposal-ui": "workspace:*",

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { CACHE_TIMES } from '@buildeross/constants/cacheTimes'
22
import { PUBLIC_DEFAULT_CHAINS } from '@buildeross/constants/chains'
33
import type { CandidateGroup } from '@buildeross/sdk'
44
import { getCandidateGroup } from '@buildeross/sdk'
5+
import { getDAOAddresses } from '@buildeross/sdk/contract'
56
import { useChainStore } from '@buildeross/stores'
6-
import { CHAIN_ID } from '@buildeross/types'
7+
import { AddressType, CHAIN_ID } from '@buildeross/types'
78
import { Box, Button, Flex, Stack, Text } from '@buildeross/zord'
89
import { GetServerSideProps } from 'next'
910
import { useRouter } from 'next/router'
@@ -130,9 +131,9 @@ const CandidateDetailPage: NextPageWithLayout<CandidateDetailPageProps> = ({
130131
</Box>
131132
<Box>
132133
<Text fontWeight="label" fontSize={14} mb="x1">
133-
Summary
134+
Description
134135
</Text>
135-
<Text>{candidate.leadingVersion.summary}</Text>
136+
<Text>{candidate.leadingVersion.description}</Text>
136137
</Box>
137138
{candidate.leadingVersion.discussionUrl && (
138139
<Box>
@@ -201,7 +202,7 @@ CandidateDetailPage.getLayout = getDaoLayout
201202

202203
export const getServerSideProps: GetServerSideProps = async (context) => {
203204
const network = context.params?.network as string
204-
// const token = context.params?.token as string
205+
const token = context.params?.token as AddressType
205206
const candidateId = context.params?.candidateId as string
206207

207208
// Validate network
@@ -213,6 +214,12 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
213214
return { notFound: true }
214215
}
215216

217+
const addresses = await getDAOAddresses(validChain.id, token)
218+
219+
if (!addresses) {
220+
return { notFound: true }
221+
}
222+
216223
// Fetch candidate data server-side
217224
let initialData: CandidateGroup | undefined
218225
try {
@@ -233,8 +240,10 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
233240

234241
return {
235242
props: {
243+
addresses,
236244
candidateId,
237245
initialData: initialData || null,
246+
chainId: validChain.id,
238247
},
239248
}
240249
}

0 commit comments

Comments
 (0)