diff --git a/.gitignore b/.gitignore index a6886d9..39a1aa6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ **/ipfs-cache **/data *.json -.env \ No newline at end of file +.env +**/.fastembed_cache \ No newline at end of file diff --git a/README.md b/README.md index fdfeb7e..977910a 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,9 @@ docker run \ ### 2. Compile and run the indexer In a separate terminal, run the following commands: ```bash -CFLAGS='-std=gnu17' cargo run --bin sink -- \ - --reset-db \ +CFLAGS='-std=gnu17' cargo run --release --bin sink -- \ + --no-versioning \ + --no-governance \ --neo4j-uri neo4j://localhost:7687 \ --neo4j-user neo4j \ --neo4j-pass neo4j @@ -36,6 +37,11 @@ CFLAGS='-std=gnu17' cargo run --bin api -- \ --neo4j-user neo4j \ --neo4j-pass neo4j ``` +Schema introspection + +``` +npx get-graphql-schema http://127.0.0.1:8080/graphql > api/schema.graphql +``` ## GRC20 CLI Coming soon™️ \ No newline at end of file diff --git a/api/schema.graphql b/api/schema.graphql index a984491..0df7dde 100644 --- a/api/schema.graphql +++ b/api/schema.graphql @@ -32,6 +32,15 @@ type Entity { """Entity ID""" id: String! + """ + The space ID of the entity (note: the same entity can exist in multiple spaces) + """ + spaceId: String! + createdAt: String! + createdAtBlock: String! + updatedAt: String! + updatedAtBlock: String! + """Entity name (if available)""" name: String @@ -47,15 +56,6 @@ type Entity { """Types of the entity (which are entities themselves)""" types: [Entity!]! - """ - The space ID of the entity (note: the same entity can exist in multiple spaces) - """ - spaceId: String! - createdAt: String! - createdAtBlock: String! - updatedAt: String! - updatedAtBlock: String! - """Attributes of the entity""" attributes(filter: AttributeFilter): [Triple!]! @@ -143,6 +143,50 @@ enum OrderDirection { DESC } +type Property { + """Entity ID""" + id: String! + + """ + The space ID of the entity (note: the same entity can exist in multiple spaces) + """ + spaceId: String! + createdAt: String! + createdAtBlock: String! + updatedAt: String! + updatedAtBlock: String! + + """Entity name (if available)""" + name(strict: Boolean! = true): String + + """Entity description (if available)""" + description: String + + """Entity cover (if available)""" + cover: String + + """Entity blocks (if available)""" + blocks: [Entity!]! + + """Types of the entity (which are entities themselves)""" + types: [Entity!]! + + """Attributes of the entity""" + attributes(filter: AttributeFilter): [Triple!]! + + """Relations outgoing from the entity""" + relations(where: EntityRelationFilter): [Relation!]! + + """Versions of the entity, ordered chronologically""" + versions: [EntityVersion!]! + + """Value type of the property""" + valueType: Entity + + """Value type of the property""" + relationValueType: Entity +} + """Relation object""" type Relation { """Relation ID""" @@ -187,10 +231,10 @@ input RelationFilter { type RootQuery { """Returns a single space by ID""" - space(id: String!): Space + space(id: String!, version: String): Space """Returns multiple spaces according to the provided filter""" - spaces(where: SpaceFilter, first: Int, skip: Int): [Space!]! + spaces(where: SpaceFilter, version: String, first: Int! = 100, skip: Int! = 0): [Space!]! """Returns a single account by ID""" account(id: String!): Account @@ -199,23 +243,72 @@ type RootQuery { accountByAddress(address: String!): Account """Returns multiple accounts according to the provided filter""" - accounts(where: AccountFilter, first: Int, skip: Int): [Account!]! + accounts(where: AccountFilter, first: Int! = 100, skip: Int! = 0): [Account!]! """Returns a single entity identified by its ID and space ID""" - entity(id: String!, spaceId: String!, versionId: String): Entity + entity(id: String!, spaceId: String!, versionId: String, strict: Boolean! = true): Entity """ Returns multiple entities according to the provided space ID and filter """ - entities(spaceId: String!, orderBy: String, orderDirection: OrderDirection, where: EntityFilter, first: Int, skip: Int): [Entity!]! + entities(spaceId: String!, orderBy: String, orderDirection: OrderDirection, where: EntityFilter, first: Int! = 100, skip: Int! = 0, strict: Boolean! = true): [Entity!]! """Returns a single relation identified by its ID and space ID""" - relation(id: String!, spaceId: String!, versionId: String): Relation + relation(id: String!, spaceId: String!, versionId: String, strict: Boolean! = true): Relation """ Returns multiple relations according to the provided space ID and filter """ - relations(spaceId: String!, orderBy: String, orderDirection: OrderDirection, where: RelationFilter, first: Int, skip: Int): [Relation!]! + relations(spaceId: String!, orderBy: String, orderDirection: OrderDirection, where: RelationFilter, first: Int! = 100, skip: Int! = 0, strict: Boolean! = true): [Relation!]! + + """ + Returns a single triple identified by its entity ID, attribute ID, space ID and + optional version ID + """ + triple(entityId: String!, attributeId: String!, spaceId: String!, versionId: String, strict: Boolean! = true): Triple + search(query: String!, first: Int! = 100): [Triple!]! +} + +"""SchemaType object""" +type SchemaType { + """Entity ID""" + id: String! + + """ + The space ID of the entity (note: the same entity can exist in multiple spaces) + """ + spaceId: String! + createdAt: String! + createdAtBlock: String! + updatedAt: String! + updatedAtBlock: String! + + """Entity name (if available)""" + name(strict: Boolean! = true): String + + """Entity description (if available)""" + description: String + + """Entity cover (if available)""" + cover: String + + """Entity blocks (if available)""" + blocks: [Entity!]! + + """Types of the entity (which are entities themselves)""" + types: [Entity!]! + + """Attributes of the entity""" + attributes(filter: AttributeFilter): [Triple!]! + + """Relations outgoing from the entity""" + relations(where: EntityRelationFilter): [Relation!]! + + """Versions of the entity, ordered chronologically""" + versions: [EntityVersion!]! + + """Properties of the Type""" + properties(first: Int! = 100, skip: Int! = 0): [Property!]! } type Space { @@ -244,16 +337,19 @@ type Space { personalSpaceAdminPlugin: String """Members of the space""" - members(first: Int, skip: Int): [Account!]! + members(first: Int! = 100, skip: Int! = 0): [Account!]! """Editors of the space""" - editors(first: Int, skip: Int): [Account!]! + editors(first: Int! = 100, skip: Int! = 0): [Account!]! """Parent spaces of this space""" - parentSpaces(first: Int, skip: Int): [Space!]! + parentSpaces(first: Int! = 100, skip: Int! = 0): [Space!]! """Subspaces of this space""" - subspaces(first: Int, skip: Int): [Space!]! + subspaces(first: Int! = 100, skip: Int! = 0): [Space!]! + types(first: Int! = 100, skip: Int! = 0, strict: Boolean! = true): [SchemaType!]! + type(id: String!, strict: Boolean! = true): SchemaType + entities(orderBy: String, orderDirection: OrderDirection, where: EntityFilter, first: Int! = 100, skip: Int! = 0, strict: Boolean! = true): [Entity!]! } input SpaceFilter { @@ -265,10 +361,10 @@ input SpaceFilter { networkNot: String networkIn: [String!] networkNotIn: [String!] - governanceType: String - governanceTypeNot: String - governanceTypeIn: [String!] - governanceTypeNotIn: [String!] + governanceType: SpaceGovernanceType + governanceTypeNot: SpaceGovernanceType + governanceTypeIn: [SpaceGovernanceType!] + governanceTypeNotIn: [SpaceGovernanceType!] daoContractAddress: String daoContractAddressNot: String daoContractAddressIn: [String!] @@ -314,6 +410,7 @@ type Triple { """Name of the attribute (if available)""" name: String + entity: Entity } enum ValueType { diff --git a/docker/Dockerfile b/docker/Dockerfile index 4d60018..02202b7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -28,6 +28,8 @@ COPY --from=builder /kg-node/target/release/sink . COPY --from=builder /kg-node/geo-substream.spkg . CMD ./sink \ + --no-versioning \ + --no-governance \ --neo4j-uri $neo4j_uri \ --neo4j-user $neo4j_user \ --neo4j-pass $neo4j_pass diff --git a/sink/src/events/edit_published.rs b/sink/src/events/edit_published.rs index 64f19bf..84526d4 100644 --- a/sink/src/events/edit_published.rs +++ b/sink/src/events/edit_published.rs @@ -139,12 +139,19 @@ impl EventHandler { return Ok(()); } - let version_index = mapping::new_version_index(block.block_number, index); - let edit_medatata = - models::Edit::new(edit.name, edit.content_uri, Some(version_index.clone())); - let proposal_id = Proposal::gen_id(&edit.space_plugin_address, &edit.proposal_id); - self.create_edit_relations(block, edit_medatata, &edit.space_id, &proposal_id) - .await?; + let version_index = if self.versioning { + mapping::new_version_index(block.block_number, index) + } else { + "0".to_string() + }; + + if self.governance { + let edit_medatata = + models::Edit::new(edit.name, edit.content_uri, Some(version_index.clone())); + let proposal_id = Proposal::gen_id(&edit.space_plugin_address, &edit.proposal_id); + self.create_edit_relations(block, edit_medatata, &edit.space_id, &proposal_id) + .await?; + } // Group ops by type let num_ops = edit.ops.len(); diff --git a/sink/src/events/handler.rs b/sink/src/events/handler.rs index 796198d..7cfd3d4 100644 --- a/sink/src/events/handler.rs +++ b/sink/src/events/handler.rs @@ -46,6 +46,10 @@ pub struct EventHandler { pub(crate) spaces_blacklist: Vec, pub(crate) embedding_model: fastembed::TextEmbedding, pub(crate) embedding_model_dim: usize, + + // Handler config + pub(crate) versioning: bool, + pub(crate) governance: bool, } impl EventHandler { @@ -103,8 +107,20 @@ impl EventHandler { ) })? .dim, + versioning: false, + governance: false, }) } + + pub fn versioning(mut self, versioning: bool) -> Self { + self.versioning = versioning; + self + } + + pub fn governance(mut self, governance: bool) -> Self { + self.governance = governance; + self + } } fn get_block_metadata(block: &BlockScopedData) -> anyhow::Result { @@ -209,106 +225,112 @@ impl substreams_utils::Sink for EventHandler { .try_collect::>() .await?; - // Handle personal space creation - if !data.personal_plugins_created.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} personal space created events", - data.block.block_number, - data.block.timestamp, - data.personal_plugins_created.len() - ); - } - stream::iter(&data.personal_plugins_created) - .map(Ok) - .try_for_each(|event| async { - self.handle_personal_space_created(event, &data.block).await - }) - .await?; - - // Handle new governance plugin creation - if !data.governance_plugins_created.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} governance plugin created events", - data.block.block_number, - data.block.timestamp, - data.governance_plugins_created.len() - ); - } - stream::iter(&data.governance_plugins_created) - .map(Ok) - .try_for_each(|event| async { - self.handle_governance_plugin_created(event, &data.block) - .await - }) - .await?; - - if !data.initial_editors_added.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} initial editors added events", - data.block.block_number, - data.block.timestamp, - data.initial_editors_added.len() - ); - } - stream::iter(&data.initial_editors_added) - .map(Ok) - .try_for_each(|event| async { - self.handle_initial_space_editors_added(event, &data.block) - .await - }) - .await?; - - if !data.members_added.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} members added events", - data.block.block_number, - data.block.timestamp, - data.members_added.len() - ); - } - stream::iter(&data.members_added) - .map(Ok) - .try_for_each(|event| async { self.handle_member_added(event, &data.block).await }) - .await?; - - if !data.members_removed.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} members removed events", - data.block.block_number, - data.block.timestamp, - data.members_removed.len() - ); + if self.governance { + // Handle personal space creation + if !data.personal_plugins_created.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} personal space created events", + data.block.block_number, + data.block.timestamp, + data.personal_plugins_created.len() + ); + } + stream::iter(&data.personal_plugins_created) + .map(Ok) + .try_for_each(|event| async { + self.handle_personal_space_created(event, &data.block).await + }) + .await?; + + // Handle new governance plugin creation + if !data.governance_plugins_created.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} governance plugin created events", + data.block.block_number, + data.block.timestamp, + data.governance_plugins_created.len() + ); + } + stream::iter(&data.governance_plugins_created) + .map(Ok) + .try_for_each(|event| async { + self.handle_governance_plugin_created(event, &data.block) + .await + }) + .await?; + + if !data.initial_editors_added.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} initial editors added events", + data.block.block_number, + data.block.timestamp, + data.initial_editors_added.len() + ); + } + stream::iter(&data.initial_editors_added) + .map(Ok) + .try_for_each(|event| async { + self.handle_initial_space_editors_added(event, &data.block) + .await + }) + .await?; + + if !data.members_added.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} members added events", + data.block.block_number, + data.block.timestamp, + data.members_added.len() + ); + } + stream::iter(&data.members_added) + .map(Ok) + .try_for_each(|event| async { self.handle_member_added(event, &data.block).await }) + .await?; + + if !data.members_removed.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} members removed events", + data.block.block_number, + data.block.timestamp, + data.members_removed.len() + ); + } + stream::iter(&data.members_removed) + .map(Ok) + .try_for_each(|event| async { + self.handle_member_removed(event, &data.block).await + }) + .await?; + + if !data.editors_added.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} editors added events", + data.block.block_number, + data.block.timestamp, + data.editors_added.len() + ); + } + stream::iter(&data.editors_added) + .map(Ok) + .try_for_each(|event| async { self.handle_editor_added(event, &data.block).await }) + .await?; + + if !data.editors_removed.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} editors removed events", + data.block.block_number, + data.block.timestamp, + data.editors_removed.len() + ); + } + stream::iter(&data.editors_removed) + .map(Ok) + .try_for_each(|event| async { + self.handle_editor_removed(event, &data.block).await + }) + .await?; } - stream::iter(&data.members_removed) - .map(Ok) - .try_for_each(|event| async { self.handle_member_removed(event, &data.block).await }) - .await?; - - if !data.editors_added.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} editors added events", - data.block.block_number, - data.block.timestamp, - data.editors_added.len() - ); - } - stream::iter(&data.editors_added) - .map(Ok) - .try_for_each(|event| async { self.handle_editor_added(event, &data.block).await }) - .await?; - - if !data.editors_removed.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} editors removed events", - data.block.block_number, - data.block.timestamp, - data.editors_removed.len() - ); - } - stream::iter(&data.editors_removed) - .map(Ok) - .try_for_each(|event| async { self.handle_editor_removed(event, &data.block).await }) - .await?; if !data.subspaces_added.is_empty() { tracing::info!( @@ -336,134 +358,136 @@ impl substreams_utils::Sink for EventHandler { .try_for_each(|event| async { self.handle_subspace_removed(event, &data.block).await }) .await?; - if !data.proposed_added_members.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} add member proposal created events", - data.block.block_number, - data.block.timestamp, - data.proposed_added_members.len() - ); + if self.governance { + if !data.proposed_added_members.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} add member proposal created events", + data.block.block_number, + data.block.timestamp, + data.proposed_added_members.len() + ); + } + stream::iter(&data.proposed_added_members) + .map(Ok) + .try_for_each(|event| async { + self.handle_add_member_proposal_created(event, &data.block) + .await + }) + .await?; + + if !data.proposed_removed_members.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} remove member proposal created events", + data.block.block_number, + data.block.timestamp, + data.proposed_removed_members.len() + ); + } + stream::iter(&data.proposed_removed_members) + .map(Ok) + .try_for_each(|event| async { + self.handle_remove_member_proposal_created(event, &data.block) + .await + }) + .await?; + + if !data.proposed_added_editors.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} add editor proposal created events", + data.block.block_number, + data.block.timestamp, + data.proposed_added_editors.len() + ); + } + stream::iter(&data.proposed_added_editors) + .map(Ok) + .try_for_each(|event| async { + self.handle_add_editor_proposal_created(event, &data.block) + .await + }) + .await?; + + if !data.proposed_removed_editors.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} remove editor proposal created events", + data.block.block_number, + data.block.timestamp, + data.proposed_removed_editors.len() + ); + } + stream::iter(&data.proposed_removed_editors) + .map(Ok) + .try_for_each(|event| async { + self.handle_remove_editor_proposal_created(event, &data.block) + .await + }) + .await?; + + // Handle proposed add subspace + if !data.proposed_added_subspaces.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} add subspace proposal created events", + data.block.block_number, + data.block.timestamp, + data.proposed_added_subspaces.len() + ); + } + stream::iter(&data.proposed_added_subspaces) + .map(Ok) + .try_for_each(|event| async { + self.handle_add_subspace_proposal_created(event, &data.block) + .await + }) + .await?; + + // Handle remove subspace proposal created + if !data.proposed_removed_subspaces.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} remove subspace proposal created events", + data.block.block_number, + data.block.timestamp, + data.proposed_removed_subspaces.len() + ); + } + stream::iter(&data.proposed_removed_subspaces) + .map(Ok) + .try_for_each(|event| async { + self.handle_remove_subspace_proposal_created(event, &data.block) + .await + }) + .await?; + + // Handle publish edit proposal created + if !data.edits.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} publish edit proposal created events", + data.block.block_number, + data.block.timestamp, + data.edits.len() + ); + } + stream::iter(&data.edits) + .map(Ok) + .try_for_each(|event| async { + self.handle_publish_edit_proposal_created(event, &data.block) + .await + }) + .await?; + + // Handle vote cast + if !data.votes_cast.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} vote cast events", + data.block.block_number, + data.block.timestamp, + data.votes_cast.len() + ); + } + stream::iter(&data.votes_cast) + .map(Ok) + .try_for_each(|event| async { self.handle_vote_cast(event, &data.block).await }) + .await?; } - stream::iter(&data.proposed_added_members) - .map(Ok) - .try_for_each(|event| async { - self.handle_add_member_proposal_created(event, &data.block) - .await - }) - .await?; - - if !data.proposed_removed_members.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} remove member proposal created events", - data.block.block_number, - data.block.timestamp, - data.proposed_removed_members.len() - ); - } - stream::iter(&data.proposed_removed_members) - .map(Ok) - .try_for_each(|event| async { - self.handle_remove_member_proposal_created(event, &data.block) - .await - }) - .await?; - - if !data.proposed_added_editors.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} add editor proposal created events", - data.block.block_number, - data.block.timestamp, - data.proposed_added_editors.len() - ); - } - stream::iter(&data.proposed_added_editors) - .map(Ok) - .try_for_each(|event| async { - self.handle_add_editor_proposal_created(event, &data.block) - .await - }) - .await?; - - if !data.proposed_removed_editors.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} remove editor proposal created events", - data.block.block_number, - data.block.timestamp, - data.proposed_removed_editors.len() - ); - } - stream::iter(&data.proposed_removed_editors) - .map(Ok) - .try_for_each(|event| async { - self.handle_remove_editor_proposal_created(event, &data.block) - .await - }) - .await?; - - // Handle proposed add subspace - if !data.proposed_added_subspaces.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} add subspace proposal created events", - data.block.block_number, - data.block.timestamp, - data.proposed_added_subspaces.len() - ); - } - stream::iter(&data.proposed_added_subspaces) - .map(Ok) - .try_for_each(|event| async { - self.handle_add_subspace_proposal_created(event, &data.block) - .await - }) - .await?; - - // Handle remove subspace proposal created - if !data.proposed_removed_subspaces.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} remove subspace proposal created events", - data.block.block_number, - data.block.timestamp, - data.proposed_removed_subspaces.len() - ); - } - stream::iter(&data.proposed_removed_subspaces) - .map(Ok) - .try_for_each(|event| async { - self.handle_remove_subspace_proposal_created(event, &data.block) - .await - }) - .await?; - - // Handle publish edit proposal created - if !data.edits.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} publish edit proposal created events", - data.block.block_number, - data.block.timestamp, - data.edits.len() - ); - } - stream::iter(&data.edits) - .map(Ok) - .try_for_each(|event| async { - self.handle_publish_edit_proposal_created(event, &data.block) - .await - }) - .await?; - - // Handle vote cast - if !data.votes_cast.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} vote cast events", - data.block.block_number, - data.block.timestamp, - data.votes_cast.len() - ); - } - stream::iter(&data.votes_cast) - .map(Ok) - .try_for_each(|event| async { self.handle_vote_cast(event, &data.block).await }) - .await?; // Handle edits published if !data.edits_published.is_empty() { @@ -477,23 +501,25 @@ impl substreams_utils::Sink for EventHandler { self.handle_edits_published(data.edits_published, &created_space_ids, &data.block) .await?; - // Handle proposal executed - if !data.executed_proposals.is_empty() { - tracing::info!( - "Block #{} ({}): Processing {} executed proposal events", - data.block.block_number, - data.block.timestamp, - data.executed_proposals.len() - ); + if self.governance { + // Handle proposal executed + if !data.executed_proposals.is_empty() { + tracing::info!( + "Block #{} ({}): Processing {} executed proposal events", + data.block.block_number, + data.block.timestamp, + data.executed_proposals.len() + ); + } + stream::iter(&data.executed_proposals) + .enumerate() + .map(Ok) + .try_for_each(|(idx, event)| { + let block_ref = &data.block; + async move { self.handle_proposal_executed(event, block_ref, idx).await } + }) + .await?; } - stream::iter(&data.executed_proposals) - .enumerate() - .map(Ok) - .try_for_each(|(idx, event)| { - let block_ref = &data.block; - async move { self.handle_proposal_executed(event, block_ref, idx).await } - }) - .await?; // Persist block number and timestamp grc20_core::mapping::triple::insert_many( diff --git a/sink/src/main.rs b/sink/src/main.rs index bddfeb5..31c7a0c 100644 --- a/sink/src/main.rs +++ b/sink/src/main.rs @@ -63,7 +63,9 @@ async fn main() -> Result<(), Error> { None }; - let sink = EventHandler::new(neo4j, cache)?; + let sink = EventHandler::new(neo4j, cache)? + .versioning(!args.no_versioning) + .governance(!args.no_governance); if args.reset_db { reset_db(&sink).await?; @@ -99,10 +101,6 @@ struct AppArgs { #[clap(flatten)] cache_args: CacheArgs, - /// Whether or not to roll up the relations - #[arg(long, default_value_t = true)] - rollup: bool, - /// Whether or not to reset the database #[arg(long)] reset_db: bool, @@ -110,6 +108,14 @@ struct AppArgs { /// Log file path #[arg(long)] log_file: Option, + + /// Whether to enable versioning + #[arg(long, default_value = "false")] + no_versioning: bool, + + /// Whether to index governance events + #[arg(long, default_value = "false")] + no_governance: bool, } #[derive(Debug, Args)]