From 54244a37719f03db957967c1ab6f1b314be7189a Mon Sep 17 00:00:00 2001 From: Q Date: Tue, 5 Sep 2023 22:36:46 +0330 Subject: [PATCH 1/3] feat: tally integration file structure --- .env.example | 3 + .../tally/[dao-slug]/[proposal]/page.tsx | 0 .../tally/[dao-slug]/delegates/page.tsx | 0 .../integration/tally/[dao-slug]/page.tsx | 0 app/(general)/integration/tally/layout.tsx | 58 + .../integration/tally/opengraph-image.tsx | 9 + app/(general)/integration/tally/page.tsx | 15 + .../integration/tally/twitter-image.tsx | 9 + components/shared/example-demos.tsx | 15 + data/turbo-integrations.ts | 9 + env.mjs | 2 + integrations/tally/autogen/schema.ts | 2031 +++++++++++++++++ integrations/tally/components/delegation.tsx | 0 integrations/tally/components/governor.tsx | 1 + integrations/tally/components/proposal.tsx | 0 integrations/tally/components/vote.tsx | 0 integrations/tally/query/query-delegation.ts | 0 integrations/tally/query/query-governor.ts | 44 + integrations/tally/query/query-proposal.ts | 0 integrations/tally/query/query-vote.ts | 0 integrations/tally/query/tally-client.ts | 50 + integrations/tally/tally-provider.tsx | 1 + public/integrations/tally.png | Bin 0 -> 4784 bytes 23 files changed, 2247 insertions(+) create mode 100644 app/(general)/integration/tally/[dao-slug]/[proposal]/page.tsx create mode 100644 app/(general)/integration/tally/[dao-slug]/delegates/page.tsx create mode 100644 app/(general)/integration/tally/[dao-slug]/page.tsx create mode 100644 app/(general)/integration/tally/layout.tsx create mode 100644 app/(general)/integration/tally/opengraph-image.tsx create mode 100644 app/(general)/integration/tally/page.tsx create mode 100644 app/(general)/integration/tally/twitter-image.tsx create mode 100644 integrations/tally/autogen/schema.ts create mode 100644 integrations/tally/components/delegation.tsx create mode 100644 integrations/tally/components/governor.tsx create mode 100644 integrations/tally/components/proposal.tsx create mode 100644 integrations/tally/components/vote.tsx create mode 100644 integrations/tally/query/query-delegation.ts create mode 100644 integrations/tally/query/query-governor.ts create mode 100644 integrations/tally/query/query-proposal.ts create mode 100644 integrations/tally/query/query-vote.ts create mode 100644 integrations/tally/query/tally-client.ts create mode 100644 integrations/tally/tally-provider.tsx create mode 100644 public/integrations/tally.png diff --git a/.env.example b/.env.example index ddbd65ef..fde13952 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,9 @@ NEXT_PUBLIC_ALCHEMY_API_KEY= # Infura: https://www.infura.io NEXT_PUBLIC_INFURA_API_KEY= +# Tally: https://www.tally.xyz/user/settings +NEXT_PUBLIC_TALLY_API_KEY= + # Enables the use of production networks in the development environment NEXT_PUBLIC_PROD_NETWORKS_DEV=false diff --git a/app/(general)/integration/tally/[dao-slug]/[proposal]/page.tsx b/app/(general)/integration/tally/[dao-slug]/[proposal]/page.tsx new file mode 100644 index 00000000..e69de29b diff --git a/app/(general)/integration/tally/[dao-slug]/delegates/page.tsx b/app/(general)/integration/tally/[dao-slug]/delegates/page.tsx new file mode 100644 index 00000000..e69de29b diff --git a/app/(general)/integration/tally/[dao-slug]/page.tsx b/app/(general)/integration/tally/[dao-slug]/page.tsx new file mode 100644 index 00000000..e69de29b diff --git a/app/(general)/integration/tally/layout.tsx b/app/(general)/integration/tally/layout.tsx new file mode 100644 index 00000000..796b3b08 --- /dev/null +++ b/app/(general)/integration/tally/layout.tsx @@ -0,0 +1,58 @@ +"use client" + +import React from "react" +import Link from "next/link" +import { turboIntegrations } from "@/data/turbo-integrations" +import { LuBook } from "react-icons/lu" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" +import { + PageHeader, + PageHeaderCTA, + PageHeaderDescription, + PageHeaderHeading, +} from "@/components/layout/page-header" +import { PageSection } from "@/components/layout/page-section" +import { LightDarkImage } from "@/components/shared/light-dark-image" + +interface LayoutProps { + children?: React.ReactNode +} + +export default function Layout({ children }: LayoutProps) { + return ( +
+ + + {turboIntegrations.tally.name} + + Tally gives users real power in their decentralized organizations. On + Tally, users can delegate voting power, create or pass proposals to + spend DAO funds, manage a protocol, and upgrade smart contracts—all + onchain. Onchain governance is important, no matter the chain. Tally + supports DAOs on Ethereum, Polygon, Arbitrum, Optimism, Avalanche, BNB + Chain, Gnosis, Base, Moonbeam, and Scroll. + + + + + Documentation + + + + {children} +
+ ) +} diff --git a/app/(general)/integration/tally/opengraph-image.tsx b/app/(general)/integration/tally/opengraph-image.tsx new file mode 100644 index 00000000..6b42992a --- /dev/null +++ b/app/(general)/integration/tally/opengraph-image.tsx @@ -0,0 +1,9 @@ +import { IntegrationOgImage } from "@/components/ui/social/og-image-integrations" + +export const runtime = "edge" +export const size = { + width: 1200, + height: 630, +} + +export default IntegrationOgImage("arweave") diff --git a/app/(general)/integration/tally/page.tsx b/app/(general)/integration/tally/page.tsx new file mode 100644 index 00000000..dc3ce9b0 --- /dev/null +++ b/app/(general)/integration/tally/page.tsx @@ -0,0 +1,15 @@ +"use client" + +import { + GovernorSortField, + SortOrder, +} from "@/integrations/tally/autogen/schema" +import { useGovernorsQuery } from "@/integrations/tally/query/query-governor" + +export default function Page() { + const { isLoading, data } = useGovernorsQuery({ + pagination: { limit: 10 }, + }) + console.error(isLoading, data) + return null +} diff --git a/app/(general)/integration/tally/twitter-image.tsx b/app/(general)/integration/tally/twitter-image.tsx new file mode 100644 index 00000000..215a2a59 --- /dev/null +++ b/app/(general)/integration/tally/twitter-image.tsx @@ -0,0 +1,9 @@ +import Image from "./opengraph-image" + +export const runtime = "edge" +export const size = { + width: 1200, + height: 630, +} + +export default Image diff --git a/components/shared/example-demos.tsx b/components/shared/example-demos.tsx index 1ae6b14e..6bdf633a 100644 --- a/components/shared/example-demos.tsx +++ b/components/shared/example-demos.tsx @@ -519,6 +519,21 @@ const demos = [ /> ), + },{ + title: turboIntegrations.tally.name, + description: turboIntegrations.tally.description, + href: turboIntegrations.tally.href, + demo: ( +
+ +
+ ), }, { title: turboIntegrations.starter.name, diff --git a/data/turbo-integrations.ts b/data/turbo-integrations.ts index 4d26d3ff..4422d7c6 100644 --- a/data/turbo-integrations.ts +++ b/data/turbo-integrations.ts @@ -208,6 +208,15 @@ export const turboIntegrations = { category: "services", imgDark: "/integrations/defi-llama.png", }, + tally: { + name: "Tally", + href: "/integration/tally", + url: "https://docs.tally.xyz", + description: "Tally is a frontend for onchain decentralized organizations.", + category: "services", + imgLight: "/integrations/tally.png", + imgDark: "/integrations/tally.png", + }, starter: { name: "Starter Template", href: "/integration/starter", diff --git a/env.mjs b/env.mjs index 775e646d..8949e447 100644 --- a/env.mjs +++ b/env.mjs @@ -29,6 +29,7 @@ export const env = createEnv({ NEXT_PUBLIC_PROD_NETWORKS_DEV: z.enum(["true", "false"]).default("false"), NEXT_PUBLIC_ALCHEMY_API_KEY: z.string().min(1).optional(), NEXT_PUBLIC_INFURA_API_KEY: z.string().min(1).optional(), + NEXT_PUBLIC_TALLY_API_KEY: z.string().min(1).optional(), NEXT_PUBLIC_LIVEPEER_API_KEY: z.string().min(1).optional(), NEXT_PUBLIC_SITE_URL: z.string().url().optional(), }, @@ -50,6 +51,7 @@ export const env = createEnv({ process.env.NEXT_PUBLIC_USE_PUBLIC_PROVIDER, NEXT_PUBLIC_ALCHEMY_API_KEY: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY, NEXT_PUBLIC_INFURA_API_KEY: process.env.NEXT_PUBLIC_INFURA_API_KEY, + NEXT_PUBLIC_TALLY_API_KEY: process.env.NEXT_PUBLIC_TALLY_API_KEY, NEXT_PUBLIC_LIVEPEER_API_KEY: process.env.NEXT_PUBLIC_LIVEPEER_API_KEY, NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL, }, diff --git a/integrations/tally/autogen/schema.ts b/integrations/tally/autogen/schema.ts new file mode 100644 index 00000000..d8480a7e --- /dev/null +++ b/integrations/tally/autogen/schema.ts @@ -0,0 +1,2031 @@ +export type Maybe = T | null +export type InputMaybe = Maybe +export type Exact = { + [K in keyof T]: T[K] +} +export type MakeOptional = Omit & { + [SubKey in K]?: Maybe +} +export type MakeMaybe = Omit & { + [SubKey in K]: Maybe +} +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string + String: string + Boolean: boolean + Int: number + Float: number + AccountID: string + Address: string + AssetID: string + BigInt: string + BlockID: any + Bytes: string + Bytes32: string + ChainID: string + Cursor: any + HashID: any + Long: number + Timestamp: string + Upload: any +} + +/** Key for use with this API. See https://docs.tally.xyz/tally-api/welcome#request-an-api-key for how to request & use! */ +export type ApiKey = { + /** Last four characters of original generated key */ + lastFour: Scalars["String"] + /** User generated name to differentiate keys */ + name: Scalars["String"] +} + +/** A Blockchain `Account` with its associated metadata, participations and activity. */ +export type Account = { + /** AccountActivity (votes, proposals created, etc). Currently only supports on chain governance. */ + activity?: Maybe> + /** EVM Address for this `Account` */ + address: Scalars["Address"] + /** List of APIKeys generated for this account. See https://docs.tally.xyz/tally-api/welcome#request-an-api-key for how to request & use! */ + apiKeys?: Maybe> + /** `Account` bio set on Tally */ + bio: Scalars["String"] + /** `Account` email set on Tally */ + email: Scalars["String"] + /** Ethereum Name Service Name */ + ens?: Maybe + /** Feature flags */ + features?: Maybe> + id: Scalars["ID"] + /** + * Linked Identities: i.e. ENS, Twitter stored in Tally + * @deprecated ens, twitter available on `Account` type + */ + identities?: Maybe + /** `Account` name set on Tally */ + name: Scalars["String"] + /** Organizations the Account is a member of. Can be filtered to get only specific roles. */ + organizations?: Maybe> + otherLinks?: Maybe>> + /** Governances where an `Account` has a token balance or delegations along with `Account` `Participation`: votes, proposals, stats, delegations, etc. */ + participations: Array + /** Picture URL */ + picture?: Maybe + /** Twitter handle */ + twitter?: Maybe +} + +/** A Blockchain `Account` with its associated metadata, participations and activity. */ +export type AccountActivityArgs = { + governanceIds?: InputMaybe> + pagination?: InputMaybe + sort?: InputMaybe +} + +/** A Blockchain `Account` with its associated metadata, participations and activity. */ +export type AccountOrganizationsArgs = { + pagination?: InputMaybe + roles?: InputMaybe> + sort?: InputMaybe +} + +/** A Blockchain `Account` with its associated metadata, participations and activity. */ +export type AccountParticipationsArgs = { + governanceIds?: InputMaybe> + includeInactive?: InputMaybe + pagination?: InputMaybe +} + +export type AccountActivitySort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum AccountActivitySortField { + BlockTimestamp = "BLOCK_TIMESTAMP", +} + +export type ActivityItem = Proposal | Vote + +export type AddAdmin = { + address: Scalars["String"] + role: OrganizationRole +} + +export type AddressInfo = { + accounts: Array + /** Returns a list of `AddressInfo` Activity for a given address across all chains supported by Tally. Currently only supports on chain governances. */ + activity?: Maybe> + address: Scalars["Address"] + /** Account used for SIWE (auth). */ + ethAccount: Account + participations: Array +} + +export type AddressInfoActivityArgs = { + governanceIds?: InputMaybe> + pagination?: InputMaybe + sort?: InputMaybe +} + +export type AddressInfoParticipationsArgs = { + governanceIds?: InputMaybe> + pagination?: InputMaybe +} + +export type Block = { + id: Scalars["BlockID"] + number: Scalars["Long"] + timestamp: Scalars["Timestamp"] +} + +export type BlockIdInput = { + blockNumber: Scalars["Long"] + chain: Scalars["ChainID"] +} + +/** Chain data in the models are only loaded on server startup. If changed please restart the api servers. */ +export type Chain = { + /** API url of the block explorer */ + blockExplorerURL: Scalars["String"] + /** Average block time in seconds. */ + blockTime: Scalars["Float"] + /** Chain as parameter found in the eip. */ + chain: Scalars["String"] + /** The id in eip155:chain_id */ + id: Scalars["ChainID"] + /** Boolean true if it is a testnet, false if it's not. */ + isTestnet: Scalars["Boolean"] + /** If chain is an L2, the L1 id in format eip155:chain_id */ + layer1Id?: Maybe + /** Chain name with removed redundancy and unnecessary words. e.g.: Ethereum Rinkeby */ + mediumName: Scalars["String"] + /** Chain name as found in eip lists. e.g.: Ethereum Testnet Rinkeby */ + name: Scalars["String"] + /** Data from chain native currency. */ + nativeCurrency: NativeCurrency + /** Chain short name as found in eip lists. The Acronym of it. e.g.: rin */ + shortName: Scalars["String"] + /** Icon SVG of the chain logo. */ + svg?: Maybe + /** Boolean true if Tenderly supports simulations. */ + tenderlySupport: Scalars["Boolean"] + /** Boolean true if L2 depends on L1 for voting period, false if it doesn't. */ + useLayer1VotingPeriod: Scalars["Boolean"] +} + +export type Collectible = { + ID: Scalars["ID"] + address: Scalars["String"] + description?: Maybe + imageURI?: Maybe + logoURI: Scalars["String"] + metadata?: Maybe + name?: Maybe + tokenName: Scalars["String"] + tokenSymbol: Scalars["String"] + uri?: Maybe +} + +export type Confirmation = { + owner: Account + signature: Scalars["Bytes"] + signatureType: Scalars["String"] + submissionDate: Scalars["Timestamp"] +} + +export type ContactInformation = { + discord: Scalars["String"] + email: Scalars["String"] + name: Scalars["String"] + twitter: Scalars["String"] +} + +export type ContactInformationInput = { + discord: Scalars["String"] + email: Scalars["String"] + name: Scalars["String"] + twitter: Scalars["String"] +} + +export type Contracts = { + governor: GovernorContract + tokens: Array +} + +/** A single parameter used in a method. */ +export type DecodedParameter = { + /** Decoded calls in the case of a `transactions` parameter on multisend contract or similar. */ + calls?: Maybe> + name: Scalars["String"] + type: Scalars["String"] + value: Scalars["Bytes"] +} + +export type Delegate = { + account: Account + address: Scalars["Address"] + participation: Participation +} + +export type DelegateSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum DelegateSortField { + Created = "CREATED", + Delegations = "DELEGATIONS", + HasDelegateStatement = "HAS_DELEGATE_STATEMENT", + HasEns = "HAS_ENS", + ProposalsCreated = "PROPOSALS_CREATED", + TokensOwned = "TOKENS_OWNED", + Updated = "UPDATED", + VotingWeight = "VOTING_WEIGHT", +} + +export type DelegateStatement = { + dataSource: DelegateStatementSource + dataSourceURL?: Maybe + delegateAddress: Scalars["Address"] + delegateStatement: Scalars["String"] + delegateStatementSummary?: Maybe + discourseProfileLink?: Maybe + discourseUserName?: Maybe + id: Scalars["ID"] + organizationID: Scalars["String"] + seekingDelegations?: Maybe +} + +export enum DelegateStatementSource { + Script = "SCRIPT", + User = "USER", +} + +export type Delegation = { + /** The `Block` when the `Delegation` took place */ + block: Block + /** Actor who is delegating their voting power */ + delegator: Account + /** The `Account` this voting power was delegated to before this `Delegation` event */ + from: Account + /** The `Account` to whom the voting power is not delegated */ + to: Account + /** `Token` contract where this `Delegation` was created */ + token: Token + /** Voting Power delegated at time of delegation */ + votingPower: Scalars["BigInt"] + /** @deprecated renamed for clarity to `votingPower` */ + weight: Scalars["BigInt"] +} + +export type DelegationWeightChange = { + block?: Maybe + delegate: Account + hash?: Maybe + netChange: Scalars["BigInt"] + newBalance: Scalars["BigInt"] + prevBalance: Scalars["BigInt"] + timestamp: Scalars["Timestamp"] + token: Token +} + +export type DelegationWeightChangeSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum DelegationWeightChangeSortField { + Created = "CREATED", + NetChange = "NET_CHANGE", + NewBalance = "NEW_BALANCE", + OldBalance = "OLD_BALANCE", +} + +export type DelegationWeightStats = { + in: Scalars["BigInt"] + out: Scalars["BigInt"] +} + +/** Executable payload of a proposal. This is contains four arrays each of which contain an element for each action included. */ +export type Executable = { + /** Call data sent */ + callDatas: Array + /** Method signatures for the target. Only set in Alpha and Bravo style Governors. */ + signatures: Array + /** Contract targets */ + targets: Array + /** Amount of native asset to be transferred */ + values: Array +} + +/** Describes what happens if a given `Proposal` or `GnosisSafeTransaction` is executed. A call can have an unlimited amount of nested parameters which can have their own calls in the case of a common initial call to a multisend contract. */ +export type ExecutableCall = { + /** Input data that will be sent to the target method. Individual parameters derived from this data are available on the parameters field if decoding succeeds. */ + data?: Maybe + /** Media context i.e. invoice file */ + media?: Maybe + /** Transfer recipe memo */ + memo?: Maybe + /** Method to be called on the target smart contract. */ + method?: Maybe + /** `DecodedParameter`s sent to the method on the target contract. */ + parameters?: Maybe> + /** Tally `Recipe` that was used to create this call. */ + recipe?: Maybe + simulations?: Maybe> + /** `AccountID` of contract that will be called. */ + target: Scalars["AccountID"] + /** Amount of native asset that will be sent to the target contract & method. */ + value?: Maybe +} + +export type Feature = Node & { + id: Scalars["ID"] + name: Scalars["String"] +} + +/** A connection to a list of featues. */ +export type FeatureConnection = { + /** A list of edges. */ + edges?: Maybe>> + /** Information to aid in pagination. */ + pageInfo: PageInfo + /** Identifies the total count of features in the connection. */ + totalCount: Scalars["Int"] +} + +/** An edge in a feature connection. */ +export type FeatureEdge = { + /** A cursor for use in pagination. */ + cursor: Scalars["Cursor"] + /** The item at the end of the edge. */ + node?: Maybe +} + +export type FeatureOrder = { + direction: OrderDirection + field?: InputMaybe +} + +export enum FeatureOrderField { + Name = "NAME", +} + +export type FeatureState = { + account?: Maybe + enabled: Scalars["Boolean"] + governance?: Maybe + name: Scalars["String"] + organization?: Maybe +} + +/** The `File` type, represents the response of uploading a file. */ +export type File = { + contentType: Scalars["String"] + id: Scalars["String"] + metadata: Image + name: Scalars["String"] + url: Scalars["String"] +} + +export type GnosisSafe = { + /** Values of all Tokens in this Gnosis Safe */ + balance?: Maybe + collectibles?: Maybe>> + /** GnosisSafe smart contract AccountID. */ + id: Scalars["AccountID"] + /** GnosisSafe name to help distinguish it. */ + name?: Maybe + /** A counter of the amount of transactions executed on the safe. */ + nonce: Scalars["Int"] + /** A list of owner Accounts. The Account includes participations, but we haven't included gnosis safe owners or signers in the participations yet. */ + owners: Array + /** The amount of confirmations (owner signatures) that are required to execute a transaction. */ + threshold: Scalars["Int"] + /** GnosisSafe smart contract version. */ + version: Scalars["String"] +} + +/** A transaction can be `SUBMITTED` or `EXECUTED`. An `EXECUTED` transaction will include a block and an on chain txHashID. */ +export type GnosisSafeTransaction = { + /** `Block` at which this safe transaction was executed. */ + block?: Maybe + /** Describes what happens if it is executed. This includes a `target` smart contract address as well as the method and input data being used to make the call. A call can have an unlimited amount of nested `parameters` which can have their own calls in the case of a common initial call to a multisend contract. Each call includes a `recipe` placeholder if the call was created on Tally. */ + call?: Maybe + /** All the owners that have signed the transaction. */ + confirmations: Array + /** Chain scoped safeTxHash- https://github.com/safe-global/safe-contracts/blob/da66b45ec87d2fb6da7dfd837b29eacdb9a604c5/contracts/GnosisSafe.sol#L353-L394. */ + id: Scalars["HashID"] + /** Current counter of multisig transactions executed on this safe. No two transactions on this contract will have the same `nonce`. */ + nonce?: Maybe + /** `GnosisSafe` smart contract AccountID. */ + safeID: Scalars["AccountID"] + /** Chain scoped safeTxHash- https://github.com/safe-global/safe-contracts/blob/da66b45ec87d2fb6da7dfd837b29eacdb9a604c5/contracts/GnosisSafe.sol#L353-L394. */ + safeTxHashID?: Maybe + /** Executed transaction verified signatures. */ + signatures?: Maybe + /** A list of all states the transaction has been through with a timestamp. A transaction can be `SUBMITTED` or `EXECUTED`. Similar to a governor proposal. */ + statusChanges: Array + /** Ethereum transaction hash of the executed transaction. */ + txHashID?: Maybe +} + +export type GnosisStatusChange = { + timestamp: Scalars["Timestamp"] + type: GnosisStatusChangeType +} + +export enum GnosisStatusChangeType { + Executed = "EXECUTED", + Submitted = "SUBMITTED", +} + +export type Governance = { + active: Scalars["Boolean"] + balance: Scalars["BigInt"] + chainId: Scalars["ChainID"] + contracts: Contracts + delegatedVotingPower: Scalars["BigInt"] + delegates: Array + features?: Maybe> + id: Scalars["AccountID"] + kind: MultiGovernanceSupport + name: Scalars["String"] + organization: Organization + parameters: GovernanceParameters + proposals: Array + quorum: Scalars["BigInt"] + slug: Scalars["String"] + stats: GovernanceStats + tallyProposals: Array + timelockId?: Maybe + tokens: Array +} + +export type GovernanceBalanceArgs = { + id: Scalars["AccountID"] +} + +export type GovernanceDelegatedVotingPowerArgs = { + id: Scalars["AccountID"] +} + +export type GovernanceDelegatesArgs = { + pagination?: InputMaybe + sort?: InputMaybe +} + +export type GovernanceProposalsArgs = { + pagination?: InputMaybe + proposalIds?: InputMaybe> + proposerIds?: InputMaybe> + proposers?: InputMaybe> + sort?: InputMaybe +} + +export type GovernanceTallyProposalsArgs = { + creatorIds?: InputMaybe> + ids?: InputMaybe> + pagination?: InputMaybe + sort?: InputMaybe +} + +export type GovernanceArgs = { + id: Scalars["AccountID"] + start: Scalars["Long"] + type: GovernanceType +} + +export type GovernanceParameters = + | GovernorAaveParameters + | GovernorAlphaParameters + | GovernorBravoParameters + | OpenZeppelinGovernorParameters + +export type GovernanceSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum GovernanceSortField { + ActiveProposals = "ACTIVE_PROPOSALS", + TotalProposals = "TOTAL_PROPOSALS", +} + +export type GovernanceStats = { + proposals: ProposalStats + tokens: GovernanceTokenStats +} + +export type GovernanceSync = { + id: Scalars["AccountID"] + start: Scalars["Long"] + tokenId: Scalars["AssetID"] + type: GovernanceType +} + +export type GovernanceTokenStats = { + delegatedVotingPower: Scalars["BigInt"] + owners: Scalars["Int"] + supply: Scalars["BigInt"] + voters: Scalars["Int"] +} + +export enum GovernanceType { + Aave = "AAVE", + Governoralpha = "GOVERNORALPHA", + Governorbravo = "GOVERNORBRAVO", + Openzeppelingovernor = "OPENZEPPELINGOVERNOR", +} + +export type GovernanceTypeData = { + name: Scalars["String"] + type: GovernanceType +} + +/** Core type that describes an onchain Governor contract */ +export type Governor = { + /** Current tokens owned by a particular address */ + balance: Scalars["BigInt"] + /** Current voting power of a particular address */ + delegatedVotingPower: Scalars["BigInt"] + /** List of users that can currently create proposals and vote. */ + delegates: Array + id: Scalars["AccountID"] + /** Last block that Tally has indexed. Sometimes our indexer needs to catch up. Our indexer is usually ~1min behind depending on chain so we don't serve data that might later be reorged. */ + lastIndexedBlock: Block + /** Tally name of the governor contract */ + name: Scalars["String"] + parameters: GovernorParameters + /** Counts of total, active, failed, and passed proosals. */ + proposalStats: ProposalStats + /** Proposals created using this Governor contract */ + proposals: Array + /** The minumum amount of votes (total or for depending on type) that are currently required to pass a proposal. */ + quorum: Scalars["BigInt"] + /** Tally slug used for this goverance: tally.xyz/gov/[slug] */ + slug: Scalars["String"] + /** Chain scoped address of the timelock contract for this governor if it exists. */ + timelockId?: Maybe + /** List of related tokens used to operate this contract. Most governors only have one. */ + tokens: Array + /** Governor contract type */ + type: GovernorType +} + +/** Core type that describes an onchain Governor contract */ +export type GovernorBalanceArgs = { + id: Scalars["AccountID"] +} + +/** Core type that describes an onchain Governor contract */ +export type GovernorDelegatedVotingPowerArgs = { + id: Scalars["AccountID"] +} + +/** Core type that describes an onchain Governor contract */ +export type GovernorDelegatesArgs = { + pagination?: InputMaybe + sort?: InputMaybe +} + +/** Core type that describes an onchain Governor contract */ +export type GovernorProposalsArgs = { + pagination?: InputMaybe + proposalIds?: InputMaybe> + proposerIds?: InputMaybe> + proposers?: InputMaybe> + sort?: InputMaybe +} + +export type GovernorAaveParameters = { + proposalThreshold: Scalars["BigInt"] + quorumDenominator?: Maybe + quorumNumerator?: Maybe + quorumVotes?: Maybe + votingDelay: Scalars["BigInt"] + votingPeriod: Scalars["BigInt"] +} + +export type GovernorAlphaParameters = { + /** Amount of votes needed to create a proposal */ + proposalThreshold: Scalars["BigInt"] + /** If the governor supports fractional quorum the denominatior of the quorum fraction */ + quorumDenominator?: Maybe + /** If the governor supports fractional quorum the numerator of the quorum fraction */ + quorumNumerator?: Maybe + /** Amount of votes needed for a proposal to qualify for passing */ + quorumVotes?: Maybe + /** Amount of blocks before a proposal can be voted on */ + votingDelay: Scalars["BigInt"] + /** Amount of blocks a proposal remains active */ + votingPeriod: Scalars["BigInt"] +} + +export type GovernorBravoParameters = { + /** Amount of votes needed to create a proposal */ + proposalThreshold: Scalars["BigInt"] + /** If the governor supports fractional quorum the denominatior of the quorum fraction */ + quorumDenominator?: Maybe + /** If the governor supports fractional quorum the numerator of the quorum fraction */ + quorumNumerator?: Maybe + /** Amount of votes needed for a proposal to qualify for passing */ + quorumVotes?: Maybe + /** Amount of blocks before a proposal can be voted on */ + votingDelay: Scalars["BigInt"] + /** Amount of blocks a proposal remains active */ + votingPeriod: Scalars["BigInt"] +} + +export type GovernorContract = { + address: Scalars["Address"] + lastBlock: Scalars["Long"] + type: GovernanceType +} + +export type GovernorExecutableCallInput = { + /** Input data that will be sent to the target method. Individual parameters derived from this data are available on the parameters field if decoding succeeds. */ + data: Scalars["Bytes"] + /** Media context i.e. invoice file */ + media?: InputMaybe + /** Transfer recipe memo */ + memo?: InputMaybe + /** Method to be called on the target smart contract. */ + method?: InputMaybe + /** Tally `Recipe` that was used to create this call. */ + recipe: Recipe + /** `AccountID` of contract that will be called. */ + target: Scalars["AccountID"] + /** Amount of native asset that will be sent to the target contract & method. */ + value: Scalars["BigInt"] +} + +export type GovernorParameters = + | GovernorAaveParameters + | GovernorAlphaParameters + | GovernorBravoParameters + | OpenZeppelinGovernorParameters + +export type GovernorSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum GovernorSortField { + ActiveProposals = "ACTIVE_PROPOSALS", + TotalProposals = "TOTAL_PROPOSALS", +} + +/** Current token stats */ +export type GovernorTokenStats = { + /** Total delegated voting power from `DelegateVotesChanged` events */ + delegatedVotingPower: Scalars["BigInt"] + /** Number of addresses with non-zero balances of this token derived from `Transfer` events */ + owners: Scalars["Int"] + /** Supply derived from `Transfer` events */ + supply: Scalars["BigInt"] + /** Number of addresses with non-zero voting power of this token derived from `DelegateVotesChanged` events */ + voters: Scalars["Int"] +} + +/** Governor contract type */ +export enum GovernorType { + Aave = "AAVE", + Governoralpha = "GOVERNORALPHA", + Governorbravo = "GOVERNORBRAVO", + Openzeppelingovernor = "OPENZEPPELINGOVERNOR", +} + +/** Identity Providers associated with an `Account`. */ +export type Identities = { + /** Ethereum Name Service */ + ens?: Maybe + twitter?: Maybe +} + +export type IdentitiesInput = { + twitter?: InputMaybe +} + +export type Image = { + thumbnail?: Maybe + url?: Maybe +} + +export type Member = { + account: Account + id: Scalars["ID"] + organization: Organization + /** Number of polls the member has voted on. */ + pollVotesCount: Scalars["Int"] + role: OrganizationRole +} + +export type MemberSort = { + field: MemberSortField + order: SortOrder +} + +export enum MemberSortField { + PollsVoteCount = "POLLS_VOTE_COUNT", +} + +export enum MultiGovernanceSupport { + MultiGovPrimary = "MULTI_GOV_PRIMARY", + MultiGovSecondary = "MULTI_GOV_SECONDARY", + SingleGov = "SINGLE_GOV", +} + +export type Mutation = { + analyticsBackfill: Scalars["Boolean"] + /** Creates an API Key for the logged in User */ + createAPIKey: Scalars["String"] + /** Creates an API User and returns a key that can be used to access the API */ + createAPIUser: Scalars["String"] + createDelegateStatement: DelegateStatement + /** Creates a Governance on Tally associated with an Organization. If the Governance is already indexed it only updates the Organization. */ + createGovernance: Scalars["Boolean"] + /** Creates an organization with a governor and token. */ + createGovernorOrganization: Organization + /** Creates an organization for a group. Adds a demo proposal for that group. */ + createGroupOrganization: Organization + createOrganization: Organization + createPoll: Scalars["String"] + /** Creates a `TallyProposal`. Returns a TallyProposal ID. */ + createProposal: Scalars["ID"] + createProposalActionAttempt: Scalars["Boolean"] + /** Creates a `TallyProposal` with a `Poll`, which immediately begins voting. Returns a Poll ID. */ + createProposalWithPoll: Scalars["ID"] + /** Creates a `TallyProposal` with a `Poll` for a Group. Returns a Poll ID. */ + createProposalWithPollForGroup: Scalars["ID"] + /** Much like governors we can add a safe to an existing DAO. A DAO can have an unlimited amount of `GnosisSafe`s. */ + createSafe: Scalars["Boolean"] + /** Begins indexer sync for the requested token */ + createToken: Scalars["Boolean"] + /** Creates a `VoteAttempt` with the user intended vote and support data */ + createVoteAttempt: Scalars["Boolean"] + deleteSync: Scalars["Boolean"] + /** Adds the authenticated user to the organization. */ + joinOrganization: Scalars["Boolean"] + /** Links a Governance to an Organization if it is unlinked. Fails if Governance doesn't exist or isn't indexed. */ + linkGovernance: Governance + login: Scalars["String"] + logout: Scalars["Boolean"] + runProposalSimulation: Array + /** Unlinks a Safe from it's Organization for linking to other Organizations */ + unlinkGnosisSafe: Scalars["Boolean"] + /** Unlinks a Governance from it's Organization for linking to other Organizations */ + unlinkGovernance: Governance + /** Updates tally stored `Account` metadata (name, bio, picture, email, identity providers, etc) */ + updateAccount: Scalars["Boolean"] + /** Updates an Account for a user via their account id */ + updateAccountByID: Scalars["Boolean"] + updateChain: Chain + updateFeature: FeatureState + /** Updates a Governance data using the given input parameters */ + updateGovernance: Governance + updateOrganization: Organization + /** Updates the admins of organization. `remove` should be a list of member IDs. */ + updateOrganizationAdmins: Scalars["Boolean"] + /** Updates the organization password. */ + updateOrganizationPassword: Scalars["Boolean"] + /** Updates the voting parameters of organization. */ + updateOrganizationVotingParameters: Scalars["Boolean"] + updateParametersOZ: Scalars["Boolean"] + /** Updates a `TallyProposal`. */ + updateProposal: Scalars["Boolean"] + /** We are able to use updateSafe to change a gnosis safe name. */ + updateSafe: Scalars["Boolean"] + upload: File + vote: Scalars["Boolean"] +} + +export type MutationCreateApiKeyArgs = { + name?: InputMaybe +} + +export type MutationCreateApiUserArgs = { + email: Scalars["String"] +} + +export type MutationCreateDelegateStatementArgs = { + dataSource: DelegateStatementSource + dataSourceURL?: InputMaybe + delegateAddress: Scalars["Address"] + delegateStatement: Scalars["String"] + delegateStatementSummary?: InputMaybe + discourseProfileLink?: InputMaybe + discourseUserName?: InputMaybe + organizationID: Scalars["String"] + seekingDelegations?: InputMaybe +} + +export type MutationCreateGovernanceArgs = { + address: Scalars["Address"] + chainId: Scalars["ChainID"] + name?: InputMaybe + organization: Scalars["ID"] + slug?: InputMaybe + start: Scalars["Long"] + tokenId: Scalars["AssetID"] + type: GovernanceType +} + +export type MutationCreateGovernorOrganizationArgs = { + governanceArgs: GovernanceArgs + orgArgs: OrganizationArgs + tokenArgs: TokenArgs +} + +export type MutationCreateGroupOrganizationArgs = { + orgArgs: OrganizationArgs +} + +export type MutationCreateOrganizationArgs = { + color?: InputMaybe + contact?: InputMaybe + description?: InputMaybe + icon?: InputMaybe + name: Scalars["String"] + orgUxVersion: OrgUxVersion + socialProfiles?: InputMaybe + website?: InputMaybe +} + +export type MutationCreatePollArgs = { + proposalId: Scalars["ID"] +} + +export type MutationCreateProposalArgs = { + choices?: InputMaybe> + description: Scalars["String"] + governanceId: Scalars["AccountID"] + governorExecutableCalls: Array + simulationValue?: InputMaybe + title: Scalars["String"] + txHash?: InputMaybe +} + +export type MutationCreateProposalActionAttemptArgs = { + actionType: ProposalActionType + actionUser: Scalars["AccountID"] + governanceId: Scalars["AccountID"] + proposalId: Scalars["ID"] + txID: Scalars["Bytes32"] +} + +export type MutationCreateProposalWithPollArgs = { + choices?: InputMaybe> + description: Scalars["String"] + governanceId: Scalars["AccountID"] + title: Scalars["String"] +} + +export type MutationCreateProposalWithPollForGroupArgs = { + choices?: InputMaybe> + description: Scalars["String"] + organizationId: Scalars["ID"] + title: Scalars["String"] +} + +export type MutationCreateSafeArgs = { + id: Scalars["AccountID"] + name?: InputMaybe + organization: Scalars["ID"] +} + +export type MutationCreateTokenArgs = { + id: Scalars["AssetID"] + start: Scalars["Long"] +} + +export type MutationCreateVoteAttemptArgs = { + governanceId: Scalars["AccountID"] + proposalId: Scalars["ID"] + support: SupportType + txID: Scalars["Bytes32"] + voter: Scalars["AccountID"] +} + +export type MutationDeleteSyncArgs = { + accountID?: InputMaybe +} + +export type MutationJoinOrganizationArgs = { + id: Scalars["ID"] + password?: InputMaybe +} + +export type MutationLinkGovernanceArgs = { + id: Scalars["AccountID"] + organizationId: Scalars["ID"] +} + +export type MutationLoginArgs = { + message: Scalars["String"] + signature: Scalars["String"] +} + +export type MutationRunProposalSimulationArgs = { + proposalID: Scalars["ID"] + value?: InputMaybe +} + +export type MutationUnlinkGnosisSafeArgs = { + id: Scalars["AccountID"] +} + +export type MutationUnlinkGovernanceArgs = { + id: Scalars["AccountID"] +} + +export type MutationUpdateAccountArgs = { + bio?: InputMaybe + email?: InputMaybe + identities?: InputMaybe + name?: InputMaybe + otherLinks?: InputMaybe>> + picture?: InputMaybe +} + +export type MutationUpdateAccountByIdArgs = { + bio?: InputMaybe + email?: InputMaybe + id: Scalars["AccountID"] + identities?: InputMaybe + name?: InputMaybe + otherLinks?: InputMaybe>> + picture?: InputMaybe +} + +export type MutationUpdateChainArgs = { + blockExplorerURL?: InputMaybe + id: Scalars["ChainID"] + rpcURL?: InputMaybe +} + +export type MutationUpdateFeatureArgs = { + accountID?: InputMaybe + governanceID?: InputMaybe + name: Scalars["String"] + organizationID?: InputMaybe + value: Scalars["Boolean"] +} + +export type MutationUpdateGovernanceArgs = { + active?: InputMaybe + governanceId: Scalars["AccountID"] + name?: InputMaybe + slug?: InputMaybe +} + +export type MutationUpdateOrganizationArgs = { + color?: InputMaybe + contact?: InputMaybe + description?: InputMaybe + icon?: InputMaybe + id: Scalars["ID"] + name?: InputMaybe + slug?: InputMaybe + socialProfiles?: InputMaybe + website?: InputMaybe +} + +export type MutationUpdateOrganizationAdminsArgs = { + add?: InputMaybe> + id: Scalars["ID"] + remove?: InputMaybe> +} + +export type MutationUpdateOrganizationPasswordArgs = { + id: Scalars["ID"] + password: Scalars["String"] +} + +export type MutationUpdateOrganizationVotingParametersArgs = { + id: Scalars["ID"] + parameters: UpdateVoteParameters +} + +export type MutationUpdateProposalArgs = { + governanceId: Scalars["AccountID"] + proposalId: Scalars["ID"] + update: UpdateProposalInput +} + +export type MutationUpdateSafeArgs = { + id: Scalars["AccountID"] + name: Scalars["String"] +} + +export type MutationUploadArgs = { + file: UploadFile +} + +export type MutationVoteArgs = { + choice?: InputMaybe + message: Scalars["String"] + pollId: Scalars["ID"] + reason?: InputMaybe + signature: Scalars["String"] + support?: InputMaybe +} + +export type NativeCurrency = { + /** Decimals of the Currency. e.g.: 18 */ + decimals: Scalars["Long"] + /** Name of the Currency. e.g.: Ether */ + name: Scalars["String"] + /** Symbol of the Currency. e.g.: ETH */ + symbol: Scalars["String"] +} + +/** + * An object with an ID. + * Follows the [Relay Global Object Identification Specification](https://relay.dev/graphql/objectidentification.htm) + */ +export type Node = { + /** The id of the object. */ + id: Scalars["ID"] +} + +export type OpenZeppelinGovernorParameters = { + /** Amount of votes needed to create a proposal */ + proposalThreshold: Scalars["BigInt"] + /** If the governor supports fractional quorum the denominatior of the quorum fraction */ + quorumDenominator?: Maybe + /** If the governor supports fractional quorum the numerator of the quorum fraction */ + quorumNumerator?: Maybe + /** Amount of votes needed for a proposal to qualify for passing */ + quorumVotes?: Maybe + /** Amount of blocks before a proposal can be voted on */ + votingDelay: Scalars["BigInt"] + /** Amount of blocks a proposal remains active */ + votingPeriod: Scalars["BigInt"] +} + +export enum OrderDirection { + Asc = "ASC", + Desc = "DESC", +} + +export type OrgMember = { + id: Scalars["AccountID"] + role?: InputMaybe +} + +export enum OrgUxVersion { + Governor = "governor", + Tokenless = "tokenless", +} + +export type Organization = { + /** Can only be accessed by a TallyAdmin or Organization Admin */ + adminData?: Maybe + creator?: Maybe + description?: Maybe + features?: Maybe> + governances: Array + id: Scalars["ID"] + /** Returns members of the organization, optional `roles` filter. */ + members: Array + myRole?: Maybe + name: Scalars["String"] + requiresPasswordToJoin: Scalars["Boolean"] + slug: Scalars["String"] + socialProfiles?: Maybe + /** Organization type, for UX purposes only. */ + uxVersion: OrgUxVersion + visual: Visual + votingParameters?: Maybe + website?: Maybe +} + +export type OrganizationGovernancesArgs = { + chainIds?: InputMaybe> + ids?: InputMaybe> + includeInactive?: InputMaybe + pagination?: InputMaybe + sort?: InputMaybe +} + +export type OrganizationMembersArgs = { + pagination?: InputMaybe + roles?: InputMaybe> + sort?: InputMaybe +} + +export type OrganizationAdminData = { + contact?: Maybe + password?: Maybe +} + +export type OrganizationArgs = { + description?: InputMaybe + name: Scalars["String"] +} + +export enum OrganizationRole { + Admin = "ADMIN", + Member = "MEMBER", + Superadmin = "SUPERADMIN", +} + +export type OrganizationSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum OrganizationSortField { + Id = "ID", + Name = "NAME", +} + +export type OtherLink = { + label: Scalars["String"] + value: Scalars["String"] +} + +export type OtherLinkInput = { + label: Scalars["String"] + value: Scalars["String"] +} + +export type PageInfo = { + /** When paginating forwards, the cursor to continue. */ + endCursor?: Maybe + /** When paginating forwards, are there more items? */ + hasNextPage: Scalars["Boolean"] + /** When paginating backwards, are there more items? */ + hasPreviousPage: Scalars["Boolean"] + /** When paginating backwards, the cursor to continue. */ + startCursor?: Maybe +} + +export type Pagination = { + limit?: InputMaybe + offset?: InputMaybe +} + +/** Convenience type representing the activity a particular `Account` has in a `Governor` contract or it's related `Token` contract. */ +export type Participation = { + account: Account + /** Delegation of voting power of this `account` to another `account`. An `account` can delegate to itself and often that is required in order for voting power to be counted. */ + delegationOut?: Maybe + /** Delegations of voting power made to this `account` */ + delegationsIn: Array + /** @deprecated use `governor` selector instead */ + governance: Governance + governor: Governor + /** Proposals created by this `account` */ + proposals: Array + /** Aggregations of account activity in this governor */ + stats: ParticipationStats + /** Votes made by the `account` on the `governor` */ + votes: Array + /** Query voting power changes for this `account` on this `governor`. You can request all changes or aggregate over an interval using the `interval` parameter. */ + votingPowerChanges: Array + /** @deprecated `votingPowerChanges` is a better name */ + weightChanges: Array +} + +/** Convenience type representing the activity a particular `Account` has in a `Governor` contract or it's related `Token` contract. */ +export type ParticipationDelegationsInArgs = { + pagination?: InputMaybe +} + +/** Convenience type representing the activity a particular `Account` has in a `Governor` contract or it's related `Token` contract. */ +export type ParticipationProposalsArgs = { + pagination?: InputMaybe + sort?: InputMaybe +} + +/** Convenience type representing the activity a particular `Account` has in a `Governor` contract or it's related `Token` contract. */ +export type ParticipationVotesArgs = { + pagination?: InputMaybe + sort?: InputMaybe +} + +/** Convenience type representing the activity a particular `Account` has in a `Governor` contract or it's related `Token` contract. */ +export type ParticipationVotingPowerChangesArgs = { + earliest?: InputMaybe + interval?: InputMaybe + pagination?: InputMaybe + sort?: InputMaybe +} + +/** Convenience type representing the activity a particular `Account` has in a `Governor` contract or it's related `Token` contract. */ +export type ParticipationWeightChangesArgs = { + earliest?: InputMaybe + interval?: InputMaybe + pagination?: InputMaybe + sort?: InputMaybe +} + +export type ParticipationDelegationStats = { + /** Total count of delegations to this `Account` including self-delegation if present */ + total: Scalars["Int"] +} + +export type ParticipationProposalStats = { + /** Number of proposals created by this `Account */ + total: Scalars["Int"] +} + +/** Number of votes on the last 10 proposals if there are at least ten made on this contract. If there are not 10 proposals the amount of proposals is provided as `recentProposalCount`. */ +export type ParticipationRate = { + /** 10 or the number of proposals on this `Governor` if less than 10 */ + recentProposalCount: Scalars["Int"] + /** Number of votes on the last 10 proposals on this `Governor` */ + recentVoteCount: Scalars["Int"] +} + +/** Statistics about an `Account`'s participation in a `Governor` */ +export type ParticipationStats = { + /** Current overall number of delegations that delegate non-zero voting power */ + activeDelegationCount: Scalars["Int"] + /** Number of proposals created by this `Account */ + createdProposalsCount: Scalars["Int"] + /** Current overall number of delegations include those that delegate zero voting power */ + delegationCount: Scalars["Int"] + /** @deprecated use `delegationCount` or `activeDelegationCount` instead. */ + delegations: ParticipationDelegationStats + /** @deprecated use `recentParticipationRate` instead. */ + participationRate: ParticipationRate + /** @deprecated use `createdProposalsCount` instead. */ + proposals: ParticipationProposalStats + /** Number of votes on the last 10 proposals if there are at least ten made on this contract. If there are not at least 10 proposals the amount of proposals is provided as `recentProposalCount`. */ + recentParticipationRate: ParticipationRate + /** Current number of tokens owned by this `Account` */ + tokenBalance: Scalars["BigInt"] + /** Number of votes made by this `Account` */ + voteCount: Scalars["Int"] + /** @deprecated use `voteCount` instead. */ + votes: ParticipationVoteStats + /** Current voting power information including total in & out */ + votingPower: ParticipationVotingPowerStats + /** @deprecated use `votingPower` instead. */ + weight: ParticipationWeightStats +} + +/** Statistics about an `Account`'s participation in a `Governor` */ +export type ParticipationStatsDelegationsArgs = { + excludeZeroWeight?: InputMaybe +} + +export type ParticipationVoteStats = { + /** Number of votes made by this `Account` */ + total: Scalars["Int"] +} + +export type ParticipationVotingPowerStats = { + /** Total current voting power delegated to this `Account` including self-delegation if present */ + in: Scalars["BigInt"] + /** Total current voting power for this `Account` */ + net: Scalars["BigInt"] + /** Total voting power delegated to another `Account` */ + out: Scalars["BigInt"] +} + +export type ParticipationWeightStats = { + /** Total current voting power delegated in/out of this `Account` */ + delegations: DelegationWeightStats + /** Current number of tokens owned by this `Account` */ + owned: Scalars["BigInt"] + /** Total current voting power for this `Account` */ + total: Scalars["BigInt"] +} + +export type Poll = { + author: Account + createdAt: Scalars["Timestamp"] + end: Scalars["BigInt"] + endTs: Scalars["Timestamp"] + id: Scalars["ID"] + myVotingPower: Scalars["BigInt"] + pollVoteStats?: Maybe> + quorum: Scalars["BigInt"] + snapshot: Scalars["BigInt"] + start: Scalars["BigInt"] + startTs: Scalars["Timestamp"] + status: PollStatus + tallyProposal: TallyProposal + voteStats?: Maybe> + votes?: Maybe> +} + +export type PollVotesArgs = { + pagination?: InputMaybe +} + +export enum PollStatus { + Active = "ACTIVE", + Draft = "DRAFT", + Ended = "ENDED", +} + +export type PollVote = { + createdAt: Scalars["Timestamp"] + id: Scalars["ID"] + reason?: Maybe + support: Scalars["String"] + voter: Account + weight: Scalars["BigInt"] +} + +export type PollVoteStat = { + percent: Scalars["Float"] + support: Scalars["String"] + votes: Scalars["BigInt"] + weight: Scalars["BigInt"] +} + +/** Core type that describes a proposal created by an onchain Governor contract */ +export type Proposal = { + /** + * `Block` at proposal creation + * @deprecated selector `createdTransaction` contains the creation block + */ + block: Block + /** `Transaction` that created this proposal */ + createdTransaction: Transaction + /** Proposal description onchain */ + description: Scalars["String"] + /** Last block when you can cast a vote */ + end: Block + /** Time at which a proposal can be executed */ + eta?: Maybe + /** Payload that can be executed after the proposal passes */ + executable: Executable + /** + * Governor contract details + * @deprecated selector `Governor` returns the new type + */ + governance: Governance + /** + * Governor contract `AccountID` + * @deprecated selector `Governor` contains governor contract `id` + */ + governanceId: Scalars["AccountID"] + /** Governor contract details */ + governor: Governor + /** + * Hash of `Transaction` that created this proposal + * @deprecated selector `createdTransaction` contains the creation transaction hash + */ + hash: Scalars["String"] + /** Chain Scoped onchain Proposal ID */ + id: Scalars["ID"] + /** `Account` that created this proposal */ + proposer: Account + /** First block when you can cast a vote, also the time when quorum is established */ + start: Block + /** List of state transitions for this proposal. The last `StatusChange` is the current state. */ + statusChanges?: Maybe> + /** Tally draft if exists */ + tallyProposal?: Maybe + /** Proposal title: usually first line of description */ + title: Scalars["String"] + /** Summary of voting by vote choice */ + voteStats?: Maybe> + /** List of votes on this proposal */ + votes?: Maybe> + /** Voting power of a given address on this proposal */ + votingPower: Scalars["BigInt"] +} + +/** Core type that describes a proposal created by an onchain Governor contract */ +export type ProposalVotesArgs = { + pagination?: InputMaybe + sort?: InputMaybe + voters?: InputMaybe> +} + +/** Core type that describes a proposal created by an onchain Governor contract */ +export type ProposalVotingPowerArgs = { + id: Scalars["AccountID"] +} + +/** The `ProposalActionAttempt` type represents the stored attempt of a user attempting an action on a `Proposal`. */ +export type ProposalActionAttempt = { + actionType: ProposalActionType + actionUser: Account + createdAt: Scalars["Timestamp"] + proposal: Proposal + txID: Scalars["HashID"] +} + +/** The `ProposalActionType` type represents the attempted action on a `Proposal` as part of a `ProposalActionAttempt`. */ +export enum ProposalActionType { + Cancel = "CANCEL", + Execute = "EXECUTE", + Queue = "QUEUE", +} + +export type ProposalSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum ProposalSortField { + CreatedAt = "CREATED_AT", + EndBlock = "END_BLOCK", + ExecutionEta = "EXECUTION_ETA", + StartBlock = "START_BLOCK", +} + +export type ProposalStats = { + /** Total count of active proposals */ + active: Scalars["Int"] + /** Total count of failed proposals including quorum not reached */ + failed: Scalars["Int"] + /** Total count of passed proposals */ + passed: Scalars["Int"] + /** Total count of proposals */ + total: Scalars["Int"] +} + +export enum ProposalStatusType { + /** Voting is in progress. */ + Active = "ACTIVE", + /** Proposal has been canceled. This is a final status. */ + Canceled = "CANCELED", + /** Proposal has been defeated. This proposal cannot be queued or excuted. This is a final status. */ + Defeated = "DEFEATED", + /** Proposal has been executed. This is a final status. */ + Executed = "EXECUTED", + /** Proposal has expired. This is a final status. */ + Expired = "EXPIRED", + /** Proposal has been created, but voting has not started. An address can still accumulate voting power. */ + Pending = "PENDING", + /** Proposal has queued into a timelock. This proposal can be excuted. */ + Queued = "QUEUED", + /** Proposal has succeeded, it can now be queued or executed. */ + Succeeded = "SUCCEEDED", +} + +export type Query = { + /** Returns `Account` given a chain scoped `AccountID`. */ + account: Account + /** Returns `Account` by given an ENS name. */ + accountByEns: Account + accountDelegationsIn?: Maybe> + accounts: Array + address: AddressInfo + /** Returns the `Block` including an actual or estimated timestamp given a `BlockIDInput`. */ + block: Block + chains: Array> + createProposalSimulation: Array + delegateStatement: DelegateStatement + delegationsIn?: Maybe> + features: FeatureConnection + generateAdminToolToken: Scalars["String"] + /** Returns any `GnosisSafe`'s info given a chain scoped `AccountID`. */ + gnosisSafe: GnosisSafe + gnosisSafeTransaction: GnosisSafeTransaction + /** Returns a list of multisig tranasctions given a safe `AccountID`. `Pagniation` defaults to a limit of 20 transactions if no limit is provided. There are a number of filters and ordering settings we can support, please reach out to discuss. */ + gnosisSafeTransactions: Array + /** This will return a list of `GnosisSafe`s related to a DAO along with `GnosisSafe` info similar to the governances query. */ + gnosisSafes: Array + governance: Governance + governanceBySlug: Governance + governanceSyncs?: Maybe> + governanceTypes?: Maybe> + governances: Array + /** Returns a list of governors that match the provided filters. Note: Tally may deactivate governors from time to time. If you wish to include those set `includeInactive` to `true`. */ + governors: Array + /** [Deprecated] Returns `Account` given a legacy indentity id. */ + legacyAccount: Account + me: Account + /** Fetches an object given its ID. */ + node?: Maybe + /** Lookup nodes by a list of IDs. */ + nodes: Array> + organization: Organization + organizationBySlug: Organization + organizationSlugToId: Scalars["ID"] + organizations: Array + poll: Poll + polls: Array + proposal: Proposal + proposalActionAttempt: ProposalActionAttempt + proposals: Array + tallyProposal: TallyProposal + tallyProposals: Array + tokenSyncs?: Maybe> + /** Fetches the last vote attempt from a given user on a proposal. */ + voteAttempt: VoteAttempt + weightChanges?: Maybe> +} + +export type QueryAccountArgs = { + id: Scalars["AccountID"] +} + +export type QueryAccountByEnsArgs = { + ens: Scalars["String"] +} + +export type QueryAccountDelegationsInArgs = { + accountID: Scalars["AccountID"] + governanceId: Scalars["AccountID"] + pagination?: InputMaybe +} + +export type QueryAccountsArgs = { + ids?: InputMaybe> +} + +export type QueryAddressArgs = { + address: Scalars["Address"] +} + +export type QueryBlockArgs = { + id: BlockIdInput +} + +export type QueryCreateProposalSimulationArgs = { + executableCalls: Array + governanceID: Scalars["AccountID"] + value?: InputMaybe +} + +export type QueryDelegateStatementArgs = { + address: Scalars["Address"] + governanceId: Scalars["AccountID"] +} + +export type QueryDelegationsInArgs = { + addresses?: InputMaybe> + pagination?: InputMaybe +} + +export type QueryFeaturesArgs = { + after?: InputMaybe + before?: InputMaybe + first?: InputMaybe + last?: InputMaybe + orderBy?: InputMaybe +} + +export type QueryGnosisSafeArgs = { + id: Scalars["AccountID"] +} + +export type QueryGnosisSafeTransactionArgs = { + safeTxHashID: Scalars["HashID"] +} + +export type QueryGnosisSafeTransactionsArgs = { + gnosisSafeId: Scalars["AccountID"] + pagination?: InputMaybe +} + +export type QueryGnosisSafesArgs = { + organizationIds?: InputMaybe> +} + +export type QueryGovernanceArgs = { + id: Scalars["AccountID"] +} + +export type QueryGovernanceBySlugArgs = { + slug: Scalars["String"] +} + +export type QueryGovernanceSyncsArgs = { + chainIds?: InputMaybe> +} + +export type QueryGovernancesArgs = { + chainIds?: InputMaybe> + ids?: InputMaybe> + includeInactive?: InputMaybe + organizationIds?: InputMaybe> + pagination?: InputMaybe + sort?: InputMaybe +} + +export type QueryGovernorsArgs = { + chainIds?: InputMaybe> + ids?: InputMaybe> + includeInactive?: InputMaybe + organizationIds?: InputMaybe> + pagination?: InputMaybe + sort?: InputMaybe +} + +export type QueryLegacyAccountArgs = { + id: Scalars["String"] +} + +export type QueryNodeArgs = { + id: Scalars["ID"] +} + +export type QueryNodesArgs = { + ids: Array +} + +export type QueryOrganizationArgs = { + id: Scalars["ID"] +} + +export type QueryOrganizationBySlugArgs = { + slug: Scalars["String"] +} + +export type QueryOrganizationSlugToIdArgs = { + slug: Scalars["String"] +} + +export type QueryOrganizationsArgs = { + ids?: InputMaybe> + members?: InputMaybe> + names?: InputMaybe> + pagination?: InputMaybe + sort?: InputMaybe + websites?: InputMaybe> +} + +export type QueryPollArgs = { + id: Scalars["ID"] +} + +export type QueryPollsArgs = { + governanceId?: InputMaybe + organizationId?: InputMaybe + pagination?: InputMaybe +} + +export type QueryProposalArgs = { + governanceId: Scalars["AccountID"] + proposalId: Scalars["ID"] +} + +export type QueryProposalActionAttemptArgs = { + actionType: ProposalActionType + governanceId: Scalars["AccountID"] + proposalId: Scalars["ID"] +} + +export type QueryProposalsArgs = { + chainId: Scalars["ChainID"] + governanceIds?: InputMaybe> + governors?: InputMaybe> + pagination?: InputMaybe + proposalIds?: InputMaybe> + proposerIds?: InputMaybe> + proposers?: InputMaybe> + sort?: InputMaybe +} + +export type QueryTallyProposalArgs = { + id: Scalars["ID"] +} + +export type QueryTallyProposalsArgs = { + creatorIds?: InputMaybe> + governanceIds?: InputMaybe> + ids?: InputMaybe> + organizationIds?: InputMaybe> + pagination?: InputMaybe + sort?: InputMaybe +} + +export type QueryTokenSyncsArgs = { + chainIds?: InputMaybe> +} + +export type QueryVoteAttemptArgs = { + governanceId: Scalars["AccountID"] + proposalId: Scalars["ID"] + voter: Scalars["AccountID"] +} + +export type QueryWeightChangesArgs = { + accoundIDs?: InputMaybe> + pagination?: InputMaybe + sort?: InputMaybe +} + +export enum Recipe { + Custom = "CUSTOM", + Empty = "EMPTY", + OrcaManagePod = "ORCA_MANAGE_POD", + TransferErc_20 = "TRANSFER_ERC_20", + TransferNativeAsset = "TRANSFER_NATIVE_ASSET", +} + +export enum Role { + Admin = "ADMIN", + User = "USER", +} + +export type Simulation = { + executionValue: Scalars["BigInt"] + id: Scalars["ID"] + raw: Scalars["String"] + status: SimulationStatus +} + +export enum SimulationStatus { + Failed = "failed", + Success = "success", +} + +export type SocialProfiles = { + Discord?: Maybe + Others?: Maybe>> + Telegram?: Maybe + Twitter?: Maybe +} + +export type SocialProfilesInput = { + Discord?: InputMaybe + Others?: InputMaybe>> + Telegram?: InputMaybe + Twitter?: InputMaybe +} + +export enum SortOrder { + Asc = "ASC", + Desc = "DESC", +} + +export type StatusChange = { + /** Transaction hash of this state transition if applicable. Computed states do not have an associated transaction. */ + block: Block + /** + * Block Number of this state transition + * @deprecated selector `block` contains the block number + */ + blockNumber: Scalars["Long"] + /** + * Timestamp of this state transition + * @deprecated selector `block` contains the block timestamp + */ + blockTimestamp: Scalars["Timestamp"] + transaction?: Maybe + txHash?: Maybe + /** Proposal State */ + type: ProposalStatusType +} + +/** Vote Choice */ +export enum SupportType { + Abstain = "ABSTAIN", + Against = "AGAINST", + For = "FOR", +} + +export type TallyProposal = { + choices?: Maybe> + createdAt: Scalars["Timestamp"] + creator: Account + description: Scalars["String"] + executableCalls?: Maybe> + governance?: Maybe + governorProposal?: Maybe + hash?: Maybe + id: Scalars["ID"] + onChainId?: Maybe + organization: Organization + poll?: Maybe + status: TallyProposalStatus + title: Scalars["String"] +} + +export type TallyProposalSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum TallyProposalSortField { + CreatedAt = "CREATED_AT", +} + +export enum TallyProposalStatus { + Confirmed = "CONFIRMED", + Draft = "DRAFT", + Failed = "FAILED", + Submitted = "SUBMITTED", +} + +export enum TimeInterval { + All = "ALL", + Day = "DAY", + Hour = "HOUR", + Month = "MONTH", + Quarter = "QUARTER", + Week = "WEEK", + Year = "YEAR", +} + +/** Core type that describes an onchain Token contract */ +export type Token = { + /** + * EVM Address on chain. See `id` for chain id + * @deprecated selector `id` has more context + */ + address: Scalars["Address"] + /** Number of decimal places included in `BigInt` values */ + decimals: Scalars["Int"] + id: Scalars["AssetID"] + /** + * Last block that Tally has indexed. Sometimes our indexer needs to catch up. Our indexer is usually ~1min behind depending on chain so we don't serve data that might later be reorged. + * @deprecated new selector `lastIndexedBlock` has more context + */ + lastBlock: Scalars["Long"] + /** Last block that Tally has indexed. Sometimes our indexer needs to catch up. Our indexer is usually ~1min behind depending on chain so we don't serve data that might later be reorged. */ + lastIndexedBlock: Block + /** Onchain name */ + name: Scalars["String"] + /** Counts of owners, voters as well as total supply and delegated voting power. */ + stats: GovernorTokenStats + /** supply derived from `Transfer` events */ + supply: Scalars["BigInt"] + /** Onchain symbol */ + symbol: Scalars["String"] + /** Token contract type */ + type: TokenType +} + +export type TokenArgs = { + id: Scalars["AssetID"] + start: Scalars["Long"] +} + +export type TokenBalance = { + address?: Maybe + amount: Scalars["String"] + decimals: Scalars["Int"] + fiat: Scalars["String"] + logoURI: Scalars["String"] + name: Scalars["String"] + symbol: Scalars["String"] +} + +export type TokenContract = { + address: Scalars["Address"] + lastBlock: Scalars["Long"] + type: TokenType +} + +export type TokenSync = { + id: Scalars["AssetID"] + start: Scalars["Long"] +} + +export enum TokenType { + Erc20 = "ERC20", + Erc20Aave = "ERC20AAVE", + Erc721 = "ERC721", +} + +export type Transaction = { + block: Block + id: Scalars["HashID"] +} + +export type Treasury = { + tokens: Array + totalUSDValue: Scalars["String"] +} + +export type TwitterIdentity = { + nonce: Scalars["Int"] + url: Scalars["String"] +} + +export type UpdateProposalInput = { + status?: InputMaybe + txHash?: InputMaybe +} + +export type UpdateVoteParameters = { + proposalThreshold?: InputMaybe + quorum?: InputMaybe + role?: InputMaybe + votingPeriod?: InputMaybe +} + +/** The `UploadFile` type, represents the request for uploading a file with a certain payload. */ +export type UploadFile = { + id: Scalars["Int"] + upload: Scalars["Upload"] +} + +export type Visual = { + color?: Maybe + icon?: Maybe +} + +/** Votes cast in a Governor proposal */ +export type Vote = { + /** + * `Block` vote was cast in. + * @deprecated selector `transaction` contains the creation block + */ + block: Block + /** + * Hash of Transaction in which the vote cast. + * @deprecated selector `transaction` contains the creation transaction hash + */ + hash: Scalars["Bytes32"] + /** Proposal and voter concatenated id. */ + id: Scalars["ID"] + /** Proposal on which vote was cast. */ + proposal: Proposal + /** Optional reason for vote choice provided by the voter. */ + reason?: Maybe + /** Vote choice made by voter. */ + support: SupportType + /** `Transaction` vote was cast in. */ + transaction: Transaction + /** Voter that cast the vote. */ + voter: Account + /** Weight of the vote. Typically total delegated voting power of voter at proposal voting `start` block. */ + weight: Scalars["BigInt"] +} + +/** The `VoteAttempt` type represents the stored attempt of a user that tried voting on a given proposal. */ +export type VoteAttempt = { + createdAt: Scalars["Timestamp"] + proposal: Proposal + support: SupportType + txID: Scalars["HashID"] + voter: Account +} + +export type VoteSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum VoteSortField { + Block = "BLOCK", + Created = "CREATED", + Weight = "WEIGHT", +} + +/** Voting Summary per Choice */ +export type VoteStat = { + /** Percent of total weight cast in this `Proposal` */ + percent: Scalars["Float"] + /** Vote Choice */ + support: SupportType + /** Number of distinct votes cast for this Choice/`SupportType` */ + votes: Scalars["BigInt"] + /** Total weight (voting power) for this Choice/`SupportType` */ + weight: Scalars["BigInt"] +} + +export type VotingParameters = { + bigVotingPeriod: Scalars["BigInt"] + proposalThreshold?: Maybe + quorum?: Maybe + /** Role user needs to have to update the voting parameters. */ + requiredRole: OrganizationRole + /** Voting period defined in s, defaults to 172800 (2 days). */ + votingPeriod: Scalars["Long"] +} + +/** Represents a voting power change over an interval or triggered by an event. */ +export type VotingPowerChange = { + /** The `delegate` address whose voting power is changing */ + delegate: Account + /** Net change in voting power caused by this event */ + netChange: Scalars["BigInt"] + /** Voting power after this event or interval */ + newBalance: Scalars["BigInt"] + /** Voting power prior to this event or interval */ + prevBalance: Scalars["BigInt"] + /** Timestamp of event or beginging of the interval this voting power change represents */ + timestamp: Scalars["Timestamp"] + token: Token + /** Transaction that triggered this voting change, unset if this is an interval */ + transaction?: Maybe +} + +export type VotingPowerChangeSort = { + field?: InputMaybe + order?: InputMaybe +} + +export enum VotingPowerChangeSortField { + Created = "CREATED", + NetChange = "NET_CHANGE", + NewBalance = "NEW_BALANCE", + OldBalance = "OLD_BALANCE", +} + +export type GovernorsQuery = { + governors: Array<{ + id: string + name: string + tokens: Array<{ stats: { voters: number } }> + proposalStats: { total: number; active: number } + }> +} + diff --git a/integrations/tally/components/delegation.tsx b/integrations/tally/components/delegation.tsx new file mode 100644 index 00000000..e69de29b diff --git a/integrations/tally/components/governor.tsx b/integrations/tally/components/governor.tsx new file mode 100644 index 00000000..3dbe45e6 --- /dev/null +++ b/integrations/tally/components/governor.tsx @@ -0,0 +1 @@ +export const Governors = () => <> diff --git a/integrations/tally/components/proposal.tsx b/integrations/tally/components/proposal.tsx new file mode 100644 index 00000000..e69de29b diff --git a/integrations/tally/components/vote.tsx b/integrations/tally/components/vote.tsx new file mode 100644 index 00000000..e69de29b diff --git a/integrations/tally/query/query-delegation.ts b/integrations/tally/query/query-delegation.ts new file mode 100644 index 00000000..e69de29b diff --git a/integrations/tally/query/query-governor.ts b/integrations/tally/query/query-governor.ts new file mode 100644 index 00000000..65bb3129 --- /dev/null +++ b/integrations/tally/query/query-governor.ts @@ -0,0 +1,44 @@ +import { QueryGovernancesArgs } from "../autogen/schema" +import { tallyQuery, useTallyQuery } from "./tally-client" + +const governorsDocument = ` + query Governors( + $chainIds: [ChainID!], + $addresses: [Address!], + $ids: [AccountID!], + $includeInactive: Boolean, + $pagination: Pagination, + $sort: GovernorSort, + $includeUnlinked: Boolean + ) { + governors( + chainIds: $chainIds, + addresses: $addresses, + ids: $ids, + includeInactive: $includeInactive, + pagination: $pagination, + sort: $sort, + includeUnlinked: $includeUnlinked + ) { + id + name + tokens { + stats { + voters + } + } + proposalStats { + total + active + } + } + } +` + +// hook (@tanstack/react-query) +export const useGovernorsQuery = (variables: QueryGovernancesArgs) => + useTallyQuery({ query: governorsDocument, variables }) + +// normal function +export const governorsQuery = (variables: QueryGovernancesArgs) => + tallyQuery({ query: governorsDocument, variables }) diff --git a/integrations/tally/query/query-proposal.ts b/integrations/tally/query/query-proposal.ts new file mode 100644 index 00000000..e69de29b diff --git a/integrations/tally/query/query-vote.ts b/integrations/tally/query/query-vote.ts new file mode 100644 index 00000000..e69de29b diff --git a/integrations/tally/query/tally-client.ts b/integrations/tally/query/tally-client.ts new file mode 100644 index 00000000..76d94ecb --- /dev/null +++ b/integrations/tally/query/tally-client.ts @@ -0,0 +1,50 @@ +import { useQuery } from "@tanstack/react-query" + +import { TALLY_API_KEY } from "../tally-provider" + +const TALLY_API_URL = "https://api.tally.xyz/query" + +export type TallyQueryParams = { + query: string + variables: Record +} + +const fetcher = async ({ query, variables }: TallyQueryParams) => { + try { + const response = await fetch(TALLY_API_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Api-Key": TALLY_API_KEY, + }, + body: JSON.stringify({ + query, + variables, + }), + }) + const json = await response.json() + if (json?.errors) { + console.error("error when fetching", json?.errors) + return null + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return json.data + } catch (error) { + console.log("Error when fetching =>", error) + return null + } +} + +// as a function +export const tallyQuery = ({ query, variables }: TallyQueryParams) => { + return fetcher({ query, variables }) +} + +// as a hook +export const useTallyQuery = ({ query, variables }: TallyQueryParams) => { + return useQuery({ + queryKey: [query.slice(0, 10), variables], + queryFn: () => fetcher({ query, variables }), + }) +} diff --git a/integrations/tally/tally-provider.tsx b/integrations/tally/tally-provider.tsx new file mode 100644 index 00000000..7965d243 --- /dev/null +++ b/integrations/tally/tally-provider.tsx @@ -0,0 +1 @@ +export const TALLY_API_KEY = process.env.NEXT_PUBLIC_TALLY_API_KEY as string diff --git a/public/integrations/tally.png b/public/integrations/tally.png new file mode 100644 index 0000000000000000000000000000000000000000..f6d736ebdc4c486282fb5f9a6d6ab9f96f8dd9e8 GIT binary patch literal 4784 zcmb_gXE+=Ap{Ow?-4+KNdpV-`|%wg?!C`_&N=tF_uSu^C}Tq{05uym2?+^6M;mHFTtED0P>~U5UU|6@ z5)yiZ4ph}ND1W~o*vD)>e7G0=wza(6#>I~G7T*vrkW7aqSFEAnb8cs?RGrj}ei-V)}1S z^#^{wpB+d<`>(Z&3);*P?(CAxgQ>q)AAc(hw_Yqh6;G#ZFP?q%p=Vt;1HVsCNsysXQ z3L^-VI`6JBJ#i#y7~lkls_jjiE(7$F5|yo5>AURuXCh9dg$sRjstH!`stxDp#z2T3aGEVuw>Q^ggf6X5X;FuF^gnnQLjleRy-%?paX+7 z4Y@46`v~S25n;4{o=^Ft7u)*h7agjwzc^}epov}Gc02VP7E5**GKohGqiv20Ym<>_ z-*>eFDt0q1pl%y@gaaH(^#zYrI-%7uX{)n35nxNj0I zpPw8ONv;eQxrtkL&G&1C4-pdUtXR|&Fwxk2MUSv(TA$Xct3SD{32)5`c^^h=J^<;s zE*yiC9Xw?vq=O@E+Qc=P^gs~WzwBuiQ9p0fo>WUq%Dt=(Xon?l!}$4WBH%@U#g2tb zu%OV?XMb)YKb~0+Bod3y`RdP>i#L_^jE(d8M{8Nfr^KG3-Tw)2yMMF=)`qI#Y!5dg za(AL$+r=-kjz&Z;cr2VhB!k}bLREqnNxNPh=jEAPG}hlOPaROwPM3HHovh)bXK$O% z<`hFNg8hU#Q7wNYE_dc#AZ}eKSF<~ULdF)!3qhiEu~-$St;ua^iP#h8{$b9E3VBtj zsACUua_UF68PIeI`IMT1d|=r4wy?P^sSehkB3N8b^+GoBUa4-)TT!74-JvaTe-A9Go5@=bL=sWk=z5n-ua;!S-)hr zvxmziu&}A%Uqr{N!Jv#XS-gI%@>UcekJ(p=(oQfnNjQ|IN`{@wR=wvB?USFl6~Gma zi0fh>8i2j0ECrW;bI~T4D}>7R>86zUp$>ReL#iLGsY=DZuO1-hh2V1(R{Mc;9^}K8 zQOO1Sv=02B5A!g%)DVqnC-+#S2z|JN+3Y`tustB`3q$d|at4=8(~a8omBORdv+6Jl z1waD9k+Cf1Gk&Nfh?3r9QZO&s5_nCo2lA8C?WxosHW**K!W14#Sl8fNP&8k8vNaCLtDo6sa?@I}2?-J!aRsj#vgY`(_nDlPz>X^QRVAL8Dphf{ z-!u+JW*pDzv+d3{C}6#$UYKu~XWOd=z&R{Fmef(}xW?P?y2wn%ZUsYVe7k~#LT@V* zab|tUbH~#FIaFO;aLmSCGWD?x6zv(pczA1e|14)In@iTsuzxW&%l{DRrVTdt9vW?d zjBNJ`oGl!mwZNz)4zoZw0b?gqoo87<^I$TNgSMTBmyEzG$Iz1iRR8Z0tcL0M2G#HQ$#C)K)I^tTAD)IS#kdlJ38 z%7`<~d8?+bG{FMj#3?M2lCF71NhY)w)VehRpTPMW7}eC$dr7OQC+A4B-N+)k;_x|- z?`UXlytR);fW(yEsO4w{P|ipt9X+4)N$;M5d)kstsGWz9FOvuk2Em1)Ax02>%ws1G z_Qw~EwJ1q`E8vd6%7|@-KFR{t9wf9pXY~0tfJcn|odXdwoG?3GUioF=eM{1k1Wa0% zE-L!~I+!IJJ!i%znb*i2kOgcFW-lw-@W3VQBHgrTL{g?Ab;dqp0cr6WoU@eo_MOn4TS zU?N65ve=<0^@R`9%?o4SEXlV?Tbsad@ufiY8>7*xIBWx*brtTlA0#w6g$d$*gucn- z_0pY0sQ{S@NYEiV!T5o5dMB~F+@pJLq)&lG)UC-vCdZ2J&0b404Bm;m@QlXr<_XonAgu2^YY1+oj z=(v#VHK)}7*E7nvtL!>gn6=cxhw4gF{|)bpt{TA74q0F1s} z-=<0q98l0MhGT=-Gp4)eYO6pV3o%PGE}s1EL_mRHEnIst@5a_Ks^9MFIsAjF9|g3K zhXuycR@|TO%q+;#1&NAsh$SO|GKjj6H2Mc(7*!3c^n%2|sbI@qoCq;yI2n_1(F;DD z67S9A1Ss zbl9q`Xxez5s0ZvVP~A5#*%6_CLMLEp44^K4`eBJTp>+3q)fHu80;Z@2B)~rtW~@Z& z?X9`x%l4z01D{}2ETK8}cD>KjG(6!wm(vz6zbP>;nCt~xM)pm^h55@J$L<2zV$tkw z(u+@?wA&gTbydJd7w2wK6^T~)f43H^3!x%{Xh<*AHSNXQ)*rF!)-CU`Fh8fQq995G zmI6;Vnkq-Wit2$DdEM0Lc?uB$5WB8x)Z+(@Id;YYP`AziS%Dh@3YVRbc-(n@k$sJI zoHYBCko?p-TTZo?!I)-FMW@+QRgoH+^n zJC77eY!PqJ14}Mx%mGIOUe0Y%&2tPSv^|M8eQ=9ZJ8MQuTpqb^4NzE=059oIb(G{n zPbp!d(vsOFPC{kn!k;UG#3c+tHFX)^NG$^!vujJI#(t<}@2cg!@C?UEKRM$fDJY{~XDKU0-{gnB7vD_=M$!Ffi9CFaGUz_eUM{r&?q5}d?~F*&E4Q6M%TK!WL9fNX<`ri zK6*2|NO0MT%``Nx#%ytCS zwqD{~SnpZE_B?IQR7yykYk%>tPB!}KW6_vTzYesSzHDq1LZRu}YU3XhknW4mzS@zo6pBrQPe<5gN;UkAn9h)%yic1bT0l7{omnFUSM zdwLsOrd{R1M}f?GuUpv}A9%>*g|Qww926CGTy(}@RMlTq+}A=Vm_YV##V!gjY}Kl# z`fX&QMA12a-7A_`h&VgcvSE6vHe`V>?GFmYf=zB_%Jr6co{H zf3}=4f>t?EpT>*+%I{!&k=8cv`*xKRTOR($_tVxzjPAuCg3g3o7fZ9TGIqH<^Yc7{ zwpbxpa6axg-Kgc!IPDc5tG}vQhplsvwojIS5Y=}P7q{Gy(_4+8A^N%2zkp1JhnMdA z%gK3}A&4zoRLWq}m%cCIcb|43AF%H>Tx;Y%`W+=Ncw;agBd!>Y_=10#Jvm*0u=x3U z-}>?jaX8n@RJdaDAoSReRuHZB{9eRac(>nV-#m_)k$}58o3^0sfR|Gcb7Y@NX75YB zNB>^0XE^ran4^D0CiQq1AJFx5@si$1#PHh=UsKj!-}uNe-*xXafsg)^VqE#P<*$e{ zHpfKFx-a3XaFg%Pp_VJ;EbMl5p)EC+)%Tu?n_3x#^ zoCgVbO69@lS_fb4`rg@5$PvFIHcos~RjqOD3OKbl)Y}V+@OaF{L49{vh5L4QbZX?* z{2l&@5=Z$@a4|LvWc#%@TRUW%v=6?@r|UYo_eGQNtfy-;8w)$tsAur*Nhst3t~3OC zA3qevNP$JsciO{lQ=V)N7G9if%zV4Kzx=9%o3-&c9`_sYB=cbaOZ`vPze4Xl0>c;! zyW{5H$I!ff?{Pvr{cpw;-Q_ps5n{GXq++?1uPr!M65jycdzg!Kr)-6K1rVNJBlnrk zj_Tce)Xl~O^|fdsjz7WdkaX8U>BgK~4Ng-lQQX~8h%d~FGgUy*kUIEVKPKe!?MrfI zz5CKa4c@YbVICjIFC*+5N~I)HuCD~P{JK075?G^DE{h+hoO+h;(6H6@2W5eS%?N^+KG0SCfxZg^P z_rBdnGk`6HG;wTgt-uen)3foo&0~Rvj3a3lFPqeH&%A1yBL0NoKkZ`EI!T@ zYH~VJ=G=48&VMTy@>R@1Evo~4`3{b4k0Y7_f4mtt~`;gRKbcY9J~P zU)8Z+d)bgGP*JVs_v!&Vgt^Ry$42zIQ9AXrRL9{=s@p_`;N!GvKIP{3O{Ka- z2+;;60h#yiu(I`qD);}p>pS!N*4b4bTudK98F4JRCg(C>(@lBfJIu|xN<&>qH*ho2 zh}B+bxqh8aukS1QXk=n`;c%sT2m)t(5RXBnJA|Flm~v=sF!yx9fm};h`!#Ff_WD$D jYX6%i&Dk}v`E-3P3+^NDe9`-ZxFONeFof2r*(3i8qU7$r literal 0 HcmV?d00001 From 1d3a532029820807feac28574ff812468fd84ca4 Mon Sep 17 00:00:00 2001 From: Q Date: Sun, 10 Sep 2023 22:21:07 +0330 Subject: [PATCH 2/3] feat: [wip] governors --- app/(general)/integration/tally/layout.tsx | 67 ++++++++-------- app/(general)/integration/tally/page.tsx | 12 +-- integrations/tally/components/governor.tsx | 86 ++++++++++++++++++++- integrations/tally/context/governors.tsx | 88 ++++++++++++++++++++++ integrations/tally/hooks/use-governors.ts | 9 +++ integrations/tally/query/query-governor.ts | 13 ++-- integrations/tally/query/tally-client.ts | 11 ++- 7 files changed, 230 insertions(+), 56 deletions(-) create mode 100644 integrations/tally/context/governors.tsx create mode 100644 integrations/tally/hooks/use-governors.ts diff --git a/app/(general)/integration/tally/layout.tsx b/app/(general)/integration/tally/layout.tsx index 796b3b08..de91d34f 100644 --- a/app/(general)/integration/tally/layout.tsx +++ b/app/(general)/integration/tally/layout.tsx @@ -15,6 +15,7 @@ import { } from "@/components/layout/page-header" import { PageSection } from "@/components/layout/page-section" import { LightDarkImage } from "@/components/shared/light-dark-image" +import { TallyGovernorsProvider } from "@/integrations/tally/context/governors" interface LayoutProps { children?: React.ReactNode @@ -22,37 +23,39 @@ interface LayoutProps { export default function Layout({ children }: LayoutProps) { return ( -
- - - {turboIntegrations.tally.name} - - Tally gives users real power in their decentralized organizations. On - Tally, users can delegate voting power, create or pass proposals to - spend DAO funds, manage a protocol, and upgrade smart contracts—all - onchain. Onchain governance is important, no matter the chain. Tally - supports DAOs on Ethereum, Polygon, Arbitrum, Optimism, Avalanche, BNB - Chain, Gnosis, Base, Moonbeam, and Scroll. - - - - - Documentation - - - - {children} -
+ +
+ + + {turboIntegrations.tally.name} + + Tally gives users real power in their decentralized organizations. + On Tally, users can delegate voting power, create or pass proposals + to spend DAO funds, manage a protocol, and upgrade smart + contracts—all onchain. Onchain governance is important, no matter + the chain. Tally supports DAOs on Ethereum, Polygon, Arbitrum, + Optimism, Avalanche, BNB Chain, Gnosis, Base, Moonbeam, and Scroll. + + + + + Documentation + + + + {children} +
+
) } diff --git a/app/(general)/integration/tally/page.tsx b/app/(general)/integration/tally/page.tsx index dc3ce9b0..97f36c77 100644 --- a/app/(general)/integration/tally/page.tsx +++ b/app/(general)/integration/tally/page.tsx @@ -1,15 +1,7 @@ "use client" -import { - GovernorSortField, - SortOrder, -} from "@/integrations/tally/autogen/schema" -import { useGovernorsQuery } from "@/integrations/tally/query/query-governor" +import { Governors } from "@/integrations/tally/components/governor" export default function Page() { - const { isLoading, data } = useGovernorsQuery({ - pagination: { limit: 10 }, - }) - console.error(isLoading, data) - return null + return } diff --git a/integrations/tally/components/governor.tsx b/integrations/tally/components/governor.tsx index 3dbe45e6..f600f0d1 100644 --- a/integrations/tally/components/governor.tsx +++ b/integrations/tally/components/governor.tsx @@ -1 +1,85 @@ -export const Governors = () => <> +import Link from "next/link" + +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Card, CardTitle } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" + +import { Governor } from "../autogen/schema" +import { useGovernors } from "../hooks/use-governors" + +const GovernorRow = ({ governor }: { governor: Governor | null }) => ( + + +
+ + {governor ? governor.name : } + + {(governor?.proposalStats?.active ?? 0) > 0 && ( + + Active Proposal + + )} + {!governor && ( + + + Active Proposal + + + )} +
+
+
+ Total Proposals: + + {governor ? ( + governor.proposalStats.total + ) : ( + + )} + +
+
+ Active Proposals: + + {governor ? ( + governor.proposalStats.active + ) : ( + + )} + +
+
+
+ +) + +export const Governors = () => { + const { governors, next, hasMore, isLoading } = useGovernors() + return ( +
+

DAOs

+ + {governors.map((governor) => ( + + ))} + {isLoading && + Array(10) + .fill(0) + .map((_, index) => )} + {hasMore && ( + + )} +
+ ) +} diff --git a/integrations/tally/context/governors.tsx b/integrations/tally/context/governors.tsx new file mode 100644 index 00000000..5c3fff57 --- /dev/null +++ b/integrations/tally/context/governors.tsx @@ -0,0 +1,88 @@ +import { createContext, useCallback, useEffect, useMemo, useState } from "react" + +import { + Governor, + GovernorSortField, + InputMaybe, + SortOrder, +} from "../autogen/schema" +import { governorsQuery } from "../query/query-governor" + +export interface ITallyGovernorsContext { + governors: Array + isLoading: boolean + hasMore: boolean + next: () => Promise +} + +export const TallyGovernorsContext = createContext({ + governors: [], + isLoading: false, + hasMore: true, + next: async () => Promise.resolve(), +}) + +export const TallyGovernorsProvider = ({ + children, +}: { + children: React.ReactNode +}) => { + const [governors, setGovernors] = useState>([]) + const [isLoading, setIsLoading] = useState(false) + const [hasMore, setHasMore] = useState(true) + const [lastPage, setLastPage] = useState(null) + + const fetchGovernorsPage = async (page: number) => { + // early return if we're already getting a page + if (isLoading) return + setIsLoading(true) + const pageSize = 250 + const govs = await governorsQuery({ + pagination: { limit: pageSize, offset: page * pageSize }, + sort: { + // @ts-ignore + field: GovernorSortField.ActiveProposals, + order: SortOrder.Desc, + }, + }) + if (govs.result) { + const result: Array = govs.result.governors + if (result.length < pageSize) { + // we have fetched all the governors + setHasMore(false) + } else { + // tally graphql sometimes returns duplicated items. + setGovernors((prev) => Array.from(new Set([...prev, ...result]))) + setLastPage(page) + } + } else { + console.error(JSON.stringify(govs)) + } + setIsLoading(false) + } + + useEffect(() => { + if (governors.length === 0) { + void fetchGovernorsPage(0) + } + }, [governors]) + + const getNextPage = useCallback(async () => { + await fetchGovernorsPage((lastPage ?? 0) + 1) + }, [lastPage]) + + const value: ITallyGovernorsContext = useMemo( + () => ({ + governors, + isLoading, + hasMore, + next: getNextPage, + }), + [governors, isLoading, hasMore] + ) + return ( + + {children} + + ) +} diff --git a/integrations/tally/hooks/use-governors.ts b/integrations/tally/hooks/use-governors.ts new file mode 100644 index 00000000..70aee04e --- /dev/null +++ b/integrations/tally/hooks/use-governors.ts @@ -0,0 +1,9 @@ +"use client" + +import { useContext } from "react" + +import { TallyGovernorsContext } from "../context/governors" + +export const useGovernors = () => { + return useContext(TallyGovernorsContext) +} diff --git a/integrations/tally/query/query-governor.ts b/integrations/tally/query/query-governor.ts index 65bb3129..9886746b 100644 --- a/integrations/tally/query/query-governor.ts +++ b/integrations/tally/query/query-governor.ts @@ -19,18 +19,17 @@ const governorsDocument = ` pagination: $pagination, sort: $sort, includeUnlinked: $includeUnlinked - ) { + ) { id - name - tokens { - stats { - voters - } - } + type proposalStats { total active + failed + passed } + name + slug } } ` diff --git a/integrations/tally/query/tally-client.ts b/integrations/tally/query/tally-client.ts index 76d94ecb..cb9f4c9a 100644 --- a/integrations/tally/query/tally-client.ts +++ b/integrations/tally/query/tally-client.ts @@ -24,15 +24,14 @@ const fetcher = async ({ query, variables }: TallyQueryParams) => { }) const json = await response.json() if (json?.errors) { - console.error("error when fetching", json?.errors) - return null + console.error("error when fetching", json.errors) + return { error: json.errors } } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return json.data + return { result: json.data } } catch (error) { - console.log("Error when fetching =>", error) - return null + console.error("Error when fetching =>", error) + return { error } } } From f335498af900d6f03670e88c9e21afef6679bd69 Mon Sep 17 00:00:00 2001 From: Q Date: Tue, 26 Sep 2023 00:12:30 +0330 Subject: [PATCH 3/3] feat: tally integration --- .env.example | 3 - .../tally/[dao-slug]/[proposal]/page.tsx | 0 .../tally/[dao-slug]/delegates/page.tsx | 0 .../integration/tally/[dao-slug]/page.tsx | 0 .../tally/[daoSlug]/[proposal]/page.tsx | 16 ++ .../tally/[daoSlug]/delegates/page.tsx | 11 + .../integration/tally/[daoSlug]/page.tsx | 11 + .../tally/[daoSlug]/proposals/page.tsx | 11 + .../integration/tally/api-key-form-gate.tsx | 99 +++++++ app/(general)/integration/tally/layout.tsx | 12 +- app/(general)/integration/tally/page.tsx | 4 +- components/shared/example-demos.tsx | 3 +- env.mjs | 2 - integrations/tally/autogen/schema.ts | 1 - .../tally/components/dao-delegations.tsx | 127 +++++++++ .../tally/components/dao-proposals.tsx | 119 ++++++++ integrations/tally/components/delegation.tsx | 62 ++++ .../tally/components/explore-governors.tsx | 267 ++++++++++++++++++ integrations/tally/components/governor.tsx | 266 ++++++++++++----- .../tally/components/proposal-details.tsx | 155 ++++++++++ integrations/tally/components/proposal.tsx | 83 ++++++ integrations/tally/components/vote.tsx | 68 +++++ integrations/tally/context/governors.tsx | 88 ------ integrations/tally/hooks/use-governors.ts | 9 - integrations/tally/hooks/use-tally.ts | 9 + integrations/tally/query/query-account.ts | 34 +++ integrations/tally/query/query-chains.ts | 15 + integrations/tally/query/query-delegation.ts | 56 ++++ integrations/tally/query/query-governor.ts | 170 +++++++++-- integrations/tally/query/query-governors.ts | 41 +++ integrations/tally/query/query-proposal.ts | 76 +++++ integrations/tally/query/query-proposals.ts | 45 +++ integrations/tally/query/query-vote.ts | 0 integrations/tally/query/tally-client.ts | 49 ---- integrations/tally/tally-client.ts | 44 +++ integrations/tally/tally-provider.tsx | 100 ++++++- integrations/tally/utils/index.ts | 10 + 37 files changed, 1809 insertions(+), 257 deletions(-) delete mode 100644 app/(general)/integration/tally/[dao-slug]/[proposal]/page.tsx delete mode 100644 app/(general)/integration/tally/[dao-slug]/delegates/page.tsx delete mode 100644 app/(general)/integration/tally/[dao-slug]/page.tsx create mode 100644 app/(general)/integration/tally/[daoSlug]/[proposal]/page.tsx create mode 100644 app/(general)/integration/tally/[daoSlug]/delegates/page.tsx create mode 100644 app/(general)/integration/tally/[daoSlug]/page.tsx create mode 100644 app/(general)/integration/tally/[daoSlug]/proposals/page.tsx create mode 100644 app/(general)/integration/tally/api-key-form-gate.tsx create mode 100644 integrations/tally/components/dao-delegations.tsx create mode 100644 integrations/tally/components/dao-proposals.tsx create mode 100644 integrations/tally/components/explore-governors.tsx create mode 100644 integrations/tally/components/proposal-details.tsx delete mode 100644 integrations/tally/context/governors.tsx delete mode 100644 integrations/tally/hooks/use-governors.ts create mode 100644 integrations/tally/hooks/use-tally.ts create mode 100644 integrations/tally/query/query-account.ts create mode 100644 integrations/tally/query/query-chains.ts create mode 100644 integrations/tally/query/query-governors.ts create mode 100644 integrations/tally/query/query-proposals.ts delete mode 100644 integrations/tally/query/query-vote.ts delete mode 100644 integrations/tally/query/tally-client.ts create mode 100644 integrations/tally/tally-client.ts create mode 100644 integrations/tally/utils/index.ts diff --git a/.env.example b/.env.example index fde13952..ddbd65ef 100644 --- a/.env.example +++ b/.env.example @@ -10,9 +10,6 @@ NEXT_PUBLIC_ALCHEMY_API_KEY= # Infura: https://www.infura.io NEXT_PUBLIC_INFURA_API_KEY= -# Tally: https://www.tally.xyz/user/settings -NEXT_PUBLIC_TALLY_API_KEY= - # Enables the use of production networks in the development environment NEXT_PUBLIC_PROD_NETWORKS_DEV=false diff --git a/app/(general)/integration/tally/[dao-slug]/[proposal]/page.tsx b/app/(general)/integration/tally/[dao-slug]/[proposal]/page.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/app/(general)/integration/tally/[dao-slug]/delegates/page.tsx b/app/(general)/integration/tally/[dao-slug]/delegates/page.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/app/(general)/integration/tally/[dao-slug]/page.tsx b/app/(general)/integration/tally/[dao-slug]/page.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/app/(general)/integration/tally/[daoSlug]/[proposal]/page.tsx b/app/(general)/integration/tally/[daoSlug]/[proposal]/page.tsx new file mode 100644 index 00000000..7e52eb2f --- /dev/null +++ b/app/(general)/integration/tally/[daoSlug]/[proposal]/page.tsx @@ -0,0 +1,16 @@ +"use client" + +import { ProposalDetails } from "@/integrations/tally/components/proposal-details" + +export default function Page({ + params: { daoSlug, proposal }, +}: { + params: { daoSlug: string; proposal: string } +}) { + return ( + + ) +} diff --git a/app/(general)/integration/tally/[daoSlug]/delegates/page.tsx b/app/(general)/integration/tally/[daoSlug]/delegates/page.tsx new file mode 100644 index 00000000..c9be8c58 --- /dev/null +++ b/app/(general)/integration/tally/[daoSlug]/delegates/page.tsx @@ -0,0 +1,11 @@ +"use client" + +import { DaoDelegations } from "@/integrations/tally/components/dao-delegations" + +export default function Page({ + params: { daoSlug }, +}: { + params: { daoSlug: string } +}) { + return +} diff --git a/app/(general)/integration/tally/[daoSlug]/page.tsx b/app/(general)/integration/tally/[daoSlug]/page.tsx new file mode 100644 index 00000000..cb3393e0 --- /dev/null +++ b/app/(general)/integration/tally/[daoSlug]/page.tsx @@ -0,0 +1,11 @@ +"use client" + +import { Governor } from "@/integrations/tally/components/governor" + +export default function Page({ + params: { daoSlug }, +}: { + params: { daoSlug: string } +}) { + return +} diff --git a/app/(general)/integration/tally/[daoSlug]/proposals/page.tsx b/app/(general)/integration/tally/[daoSlug]/proposals/page.tsx new file mode 100644 index 00000000..53d16c1d --- /dev/null +++ b/app/(general)/integration/tally/[daoSlug]/proposals/page.tsx @@ -0,0 +1,11 @@ +"use client" + +import { DaoProposals } from "@/integrations/tally/components/dao-proposals" + +export default function Page({ + params: { daoSlug }, +}: { + params: { daoSlug: string } +}) { + return +} diff --git a/app/(general)/integration/tally/api-key-form-gate.tsx b/app/(general)/integration/tally/api-key-form-gate.tsx new file mode 100644 index 00000000..9368305f --- /dev/null +++ b/app/(general)/integration/tally/api-key-form-gate.tsx @@ -0,0 +1,99 @@ +"use client" + +import { ReactNode } from "react" +import Link from "next/link" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { z } from "zod" + +import { Button } from "@/components/ui/button" +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { useTally } from "@/integrations/tally/hooks/use-tally" + +export const ApiKeyFormGate = ({ children }: { children: ReactNode }) => { + const { apiKey, setApiKey } = useTally() + const formSchema = z.object({ + apiKey: z.string().min(1), + }) + const form = useForm>({ + resolver: zodResolver(formSchema), + }) + return apiKey ? ( + <> + {children} + + + ) : ( + +
+ ) => + setApiKey(values.apiKey) + )} + > + + Set Tally API Key + + Please enter your Tally API key, if you don't have one, get + one from{" "} + + Tally + + . + + + + ( + + + + + + + )} + /> + + + + +
+ +
+ ) +} diff --git a/app/(general)/integration/tally/layout.tsx b/app/(general)/integration/tally/layout.tsx index de91d34f..46c178eb 100644 --- a/app/(general)/integration/tally/layout.tsx +++ b/app/(general)/integration/tally/layout.tsx @@ -15,7 +15,9 @@ import { } from "@/components/layout/page-header" import { PageSection } from "@/components/layout/page-section" import { LightDarkImage } from "@/components/shared/light-dark-image" -import { TallyGovernorsProvider } from "@/integrations/tally/context/governors" +import { TallyProvider } from "@/integrations/tally/tally-provider" + +import { ApiKeyFormGate } from "./api-key-form-gate" interface LayoutProps { children?: React.ReactNode @@ -23,7 +25,7 @@ interface LayoutProps { export default function Layout({ children }: LayoutProps) { return ( - +
- {children} + + {children} +
-
+ ) } diff --git a/app/(general)/integration/tally/page.tsx b/app/(general)/integration/tally/page.tsx index 97f36c77..48307172 100644 --- a/app/(general)/integration/tally/page.tsx +++ b/app/(general)/integration/tally/page.tsx @@ -1,7 +1,7 @@ "use client" -import { Governors } from "@/integrations/tally/components/governor" +import { ExploreGovernors } from "@/integrations/tally/components/explore-governors" export default function Page() { - return + return } diff --git a/components/shared/example-demos.tsx b/components/shared/example-demos.tsx index 6bdf633a..502620d4 100644 --- a/components/shared/example-demos.tsx +++ b/components/shared/example-demos.tsx @@ -519,7 +519,8 @@ const demos = [ /> ), - },{ + }, + { title: turboIntegrations.tally.name, description: turboIntegrations.tally.description, href: turboIntegrations.tally.href, diff --git a/env.mjs b/env.mjs index 8949e447..775e646d 100644 --- a/env.mjs +++ b/env.mjs @@ -29,7 +29,6 @@ export const env = createEnv({ NEXT_PUBLIC_PROD_NETWORKS_DEV: z.enum(["true", "false"]).default("false"), NEXT_PUBLIC_ALCHEMY_API_KEY: z.string().min(1).optional(), NEXT_PUBLIC_INFURA_API_KEY: z.string().min(1).optional(), - NEXT_PUBLIC_TALLY_API_KEY: z.string().min(1).optional(), NEXT_PUBLIC_LIVEPEER_API_KEY: z.string().min(1).optional(), NEXT_PUBLIC_SITE_URL: z.string().url().optional(), }, @@ -51,7 +50,6 @@ export const env = createEnv({ process.env.NEXT_PUBLIC_USE_PUBLIC_PROVIDER, NEXT_PUBLIC_ALCHEMY_API_KEY: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY, NEXT_PUBLIC_INFURA_API_KEY: process.env.NEXT_PUBLIC_INFURA_API_KEY, - NEXT_PUBLIC_TALLY_API_KEY: process.env.NEXT_PUBLIC_TALLY_API_KEY, NEXT_PUBLIC_LIVEPEER_API_KEY: process.env.NEXT_PUBLIC_LIVEPEER_API_KEY, NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL, }, diff --git a/integrations/tally/autogen/schema.ts b/integrations/tally/autogen/schema.ts index d8480a7e..349abc82 100644 --- a/integrations/tally/autogen/schema.ts +++ b/integrations/tally/autogen/schema.ts @@ -2028,4 +2028,3 @@ export type GovernorsQuery = { proposalStats: { total: number; active: number } }> } - diff --git a/integrations/tally/components/dao-delegations.tsx b/integrations/tally/components/dao-delegations.tsx new file mode 100644 index 00000000..e3964ec6 --- /dev/null +++ b/integrations/tally/components/dao-delegations.tsx @@ -0,0 +1,127 @@ +import { useState } from "react" +import { useInfiniteQuery } from "@tanstack/react-query" +import { BsChevronLeft } from "react-icons/bs" + +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" +import { LinkComponent } from "@/components/shared/link-component" + +import { + DelegateSortField, + Governance, + Governor as GovernorType, + SortOrder, +} from "../autogen/schema" +import { useTally } from "../hooks/use-tally" +import { delegationsQuery } from "../query/query-delegation" +import { Delegation } from "./delegation" + +export const DaoDelegations = ({ slug }: { slug: string }) => { + const { + governors: { data: governors }, + } = useTally() + const governor = governors.find((gov) => gov.slug === slug) + if (!governor) { + return ( + + + + ) + } + return +} + +export const DelegationsDetails = ({ governor }: { governor: Governance }) => { + const [page, setPage] = useState(0) + const [hasMore, setHasMore] = useState(true) + const { apiKey } = useTally() + const fetchDelegations = async ({ pageParam = 0 }) => { + const pageSize = 10 + const response = await delegationsQuery( + { + id: governor.id, + pagination: { limit: pageSize, offset: pageParam * pageSize }, + sort: { field: DelegateSortField.Delegations, order: SortOrder.Desc }, + }, + apiKey! + ) + if (response.error) throw response.error + setPage((page) => page + 1) + const { governors } = response.result + const { delegates } = governors[0] as GovernorType + if (delegates.length < pageSize) setHasMore(false) + return delegates + } + const { + data, + isLoading, + isFetchingNextPage, + isFetching, + error, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: ["proposals", governor.id], + queryFn: fetchDelegations, + retry: false, + getNextPageParam: () => page, + }) + const loadingState = isLoading || isFetchingNextPage || isFetching + return ( +
+ + + {governor.name} + + + + + + + {governor.organization.name[0]} + + +
+ {governor.organization.name} Delegates +
+
+ +
+ {data?.pages.map((page) => + page.map((delegation) => ( + + )) + )} + {loadingState && + Array(10) + .fill(0) + .map((_, index) => ( + + ))} +
+
+ {hasMore && ( + + )} +
+
+
+
+ ) +} diff --git a/integrations/tally/components/dao-proposals.tsx b/integrations/tally/components/dao-proposals.tsx new file mode 100644 index 00000000..0a414e26 --- /dev/null +++ b/integrations/tally/components/dao-proposals.tsx @@ -0,0 +1,119 @@ +import { useState } from "react" +import { useInfiniteQuery } from "@tanstack/react-query" +import { BsChevronLeft } from "react-icons/bs" + +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" +import { LinkComponent } from "@/components/shared/link-component" + +import { Governance, Governor as GovernorType } from "../autogen/schema" +import { useTally } from "../hooks/use-tally" +import { proposalsQuery } from "../query/query-proposals" +import { Proposal } from "./proposal" + +export const DaoProposals = ({ slug }: { slug: string }) => { + const { + governors: { data: governors }, + } = useTally() + const governor = governors.find((gov) => gov.slug === slug) + if (!governor) { + return ( + + + + ) + } + return +} + +export const ProposalsDetails = ({ governor }: { governor: Governance }) => { + const [page, setPage] = useState(0) + const [hasMore, setHasMore] = useState(true) + const { apiKey } = useTally() + const fetchProposals = async ({ pageParam = 0 }) => { + const pageSize = 10 + const response = await proposalsQuery( + { + id: governor.id, + pagination: { limit: pageSize, offset: pageParam * pageSize }, + }, + apiKey! + ) + if (response.error) throw response.error + setPage((page) => page + 1) + const { governors } = response.result + const { proposals } = governors[0] as GovernorType + if (proposals.length < pageSize) setHasMore(false) + return proposals + } + const { + data, + isLoading, + isFetchingNextPage, + isFetching, + error, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: ["proposals", governor.id], + queryFn: fetchProposals, + retry: false, + getNextPageParam: () => page, + }) + const loadingState = isLoading || isFetchingNextPage || isFetching + return ( +
+ + + {governor.name} + + + + + + + {governor.organization.name[0]} + + +
+ {governor.organization.name} Proposals +
+
+ + {data?.pages.map((page) => + page.map((proposal) => ( + + )) + )} + {loadingState && + Array(10) + .fill(0) + .map((_, index) => ( + + ))} +
+ {hasMore && ( + + )} +
+
+
+
+ ) +} diff --git a/integrations/tally/components/delegation.tsx b/integrations/tally/components/delegation.tsx index e69de29b..b61d93b0 100644 --- a/integrations/tally/components/delegation.tsx +++ b/integrations/tally/components/delegation.tsx @@ -0,0 +1,62 @@ +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { Address } from "@/components/blockchain/address" + +import { Governor, Participation } from "../autogen/schema" +import { humanNumber } from "../utils" + +export const Delegation = ({ + delegation, + governor, +}: { + delegation: Participation + governor: Governor +}) => { + const token = governor.tokens[0] + return ( + + + + + + {delegation.account.name[0]} + + +
+ + {delegation.account.name.startsWith("0x") ? ( +
+ ) : ( + delegation.account.name + )} + + + {humanNumber( + delegation.stats.weight.total.substring( + 0, + delegation.stats.weight.total.length - token.decimals + ) + )}{" "} + {token.symbol} + +
+
+ {delegation.account.bio && ( + {delegation.account.bio} + )} +
+ ) +} diff --git a/integrations/tally/components/explore-governors.tsx b/integrations/tally/components/explore-governors.tsx new file mode 100644 index 00000000..0cad6644 --- /dev/null +++ b/integrations/tally/components/explore-governors.tsx @@ -0,0 +1,267 @@ +import { useEffect, useMemo, useRef, useState } from "react" +import Link from "next/link" +import { + BsChevronDoubleLeft, + BsChevronDoubleRight, + BsChevronLeft, + BsChevronRight, +} from "react-icons/bs" + +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { ScrollArea } from "@/components/ui/scroll-area" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Skeleton } from "@/components/ui/skeleton" + +import { useTally } from "../hooks/use-tally" +import { humanNumber } from "../utils" + +const pageSize = 10 +export const ExploreGovernors = () => { + const [page, setPage] = useState(0) + const [selectedChain, setSelectedChain] = useState("all") + const [filterName, setFilterName] = useState(null) + + useEffect(() => { + setPage(0) + }, [selectedChain, filterName]) + const { + governors: { data, isLoading, error }, + chains, + } = useTally() + + const filteredRows = useMemo( + () => + data.filter((gov) => { + if ( + filterName && + !gov.name.toLocaleLowerCase().includes(filterName.toLocaleLowerCase()) + ) + return false + if (selectedChain !== "all" && gov.chainId !== selectedChain) + return false + return true + }), + [selectedChain, filterName] + ) + const totalPages = useMemo( + () => Math.floor(filteredRows.length / pageSize), + [filteredRows] + ) + const rows = useMemo( + () => filteredRows.slice(page * pageSize, (page + 1) * pageSize), + [filteredRows, page] + ) + const clearFilters = () => { + setSelectedChain("all") + setFilterName(null) + } + return ( + + +

DAOs

+
+ setFilterName(e.target.value)} + /> + + +
+
+ {!!error && ( +
+ {String(error)} +
+ )} + + {!isLoading && + rows && + rows.map((gov) => ( + + + +
+ + + + {gov.organization.name[0]} + + + + {gov.organization.name} + {gov.stats.proposals.active > 0 && ( + + Active Proposal + + )} + +
+
+ +
+
+ + {gov.stats.proposals.total} + + Proposals +
+
+ + {humanNumber(gov.stats.tokens.owners)} + + Holders +
+
+ + {humanNumber(gov.stats.tokens.voters)} + + Voters +
+
+
+
+ + ))} + {!isLoading && filteredRows.length === 0 && ( + No DAO found... + )} + {isLoading && + Array(10) + .fill(0) + .map((_, index) => )} +
+
+
+
+ Page {page + 1} of {Math.ceil(filteredRows.length / pageSize)} +
+
+ + + {page + 1} + + +
+
+
+
+
+
+ ) +} + +const GovernorRowSkeleton = () => ( +
+
+ + + +
+
+
+ + + +
+
+ + + +
+
+
+) diff --git a/integrations/tally/components/governor.tsx b/integrations/tally/components/governor.tsx index f600f0d1..ddcbc6a7 100644 --- a/integrations/tally/components/governor.tsx +++ b/integrations/tally/components/governor.tsx @@ -1,85 +1,203 @@ -import Link from "next/link" +import { useQuery } from "@tanstack/react-query" +import { BsChevronLeft } from "react-icons/bs" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { Card, CardTitle } from "@/components/ui/card" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" import { Skeleton } from "@/components/ui/skeleton" +import { LinkComponent } from "@/components/shared/link-component" -import { Governor } from "../autogen/schema" -import { useGovernors } from "../hooks/use-governors" +import { Governance, Governor as GovernorType } from "../autogen/schema" +import { useTally } from "../hooks/use-tally" +import { governorQuery } from "../query/query-governor" +import { humanNumber } from "../utils" +import { Delegation } from "./delegation" +import { Proposal } from "./proposal" -const GovernorRow = ({ governor }: { governor: Governor | null }) => ( - - -
- - {governor ? governor.name : } - - {(governor?.proposalStats?.active ?? 0) > 0 && ( - - Active Proposal - - )} - {!governor && ( - - - Active Proposal - - - )} -
-
-
- Total Proposals: - - {governor ? ( - governor.proposalStats.total - ) : ( - - )} - -
-
- Active Proposals: - - {governor ? ( - governor.proposalStats.active - ) : ( - - )} - -
-
-
- -) +export const Governor = ({ slug }: { slug: string }) => { + const { + governors: { data: governors }, + } = useTally() + const governor = governors.find((gov) => gov.slug === slug) + if (!governor) { + return ( + + + + ) + } + return +} -export const Governors = () => { - const { governors, next, hasMore, isLoading } = useGovernors() +export const GovernorDetails = ({ governor }: { governor: Governance }) => { + const { apiKey } = useTally() + const fetchGovernorInfo = async () => { + const response = await governorQuery( + { + id: governor.id, + }, + apiKey! + ) + if (response.error) throw response.error + const { governors } = response.result + return governors[0] as GovernorType + } + const { data, isLoading, error } = useQuery({ + queryKey: ["governor", governor.id], + queryFn: fetchGovernorInfo, + retry: false, + }) return ( -
-

DAOs

- - {governors.map((governor) => ( - - ))} - {isLoading && - Array(10) - .fill(0) - .map((_, index) => )} - {hasMore && ( - - )} +
+ + + Explore DAOs + + + + + + + {governor.organization.name[0]} + + +
+ {governor.organization.name} + + {governor.organization.description} + +
+
+ + Tokens +
+ {data?.tokens.map((token) => ( +
+
+ {token.name} + + {token.type} + +
+
+ Supply: + {Number( + token.supply.substring( + 0, + token.supply.length - token.decimals + ) + ).toLocaleString()}{" "} + {token.symbol} +
+
+ ))} + {isLoading && } +
+ Stats +
+
+ + {governor.stats.proposals.total} + + Proposals +
+
+ + {humanNumber(governor.stats.tokens.owners)} + + Holders +
+
+ + {humanNumber(governor.stats.tokens.voters)} + + Voters +
+
+ + Proposals +
+
+ {governor.stats.proposals.passed} + + Passed + +
+
+ {governor.stats.proposals.failed} + Failed +
+
+
+
+ {data?.proposals.map((proposal) => ( + + ))} + {isLoading && + Array(4) + .fill(0) + .map((_, index) => ( + + ))} +
+ + + +
+
+ Delegations +
+ {data?.delegates.map((delegation) => ( + + ))} + {isLoading && + Array(3) + .fill(0) + .map((_, index) => ( + + ))} +
+
+ + + +
+
+
) } diff --git a/integrations/tally/components/proposal-details.tsx b/integrations/tally/components/proposal-details.tsx new file mode 100644 index 00000000..77d25b81 --- /dev/null +++ b/integrations/tally/components/proposal-details.tsx @@ -0,0 +1,155 @@ +import { HTMLAttributes } from "react" +import { useQuery } from "@tanstack/react-query" +import moment from "moment" +import { BsChevronLeft } from "react-icons/bs" +import Markdown from "react-markdown" + +import { Badge } from "@/components/ui/badge" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" +import { LinkComponent } from "@/components/shared/link-component" + +import { Governance, Proposal as ProposalType } from "../autogen/schema" +import { useTally } from "../hooks/use-tally" +import { proposalQuery } from "../query/query-proposal" +import { ProposalVoteStats } from "./proposal" +import { Vote } from "./vote" + +export const ProposalDetails = ({ + daoSlug, + proposalId, +}: { + daoSlug: string + proposalId: string +}) => { + const { + governors: { data: governors }, + } = useTally() + const governor = governors.find((gov) => gov.slug === daoSlug) + if (!governor) { + return ( + + + + ) + } + return +} + +const ProposalsStats = ({ + governor, + proposalId, +}: { + governor: Governance + proposalId: string +}) => { + const { apiKey } = useTally() + const fetchProposalDetails = async () => { + const response = await proposalQuery( + { + proposalId, + governanceId: governor.id, + }, + apiKey! + ) + if (response.error) throw response.error + const { proposal } = response.result + return proposal as ProposalType + } + const { data, isLoading, error } = useQuery({ + queryKey: ["proposal", proposalId], + queryFn: fetchProposalDetails, + retry: false, + }) + const lastStatus = + data?.statusChanges?.[data?.statusChanges?.length - 1] ?? null + return ( +
+ + + {governor.name} + + + + + {data ? ( + {data?.description.split("\\n")[0]} + ) : ( + + )} + + + {data?.block?.timestamp && + moment(data.block.timestamp).format("MMM Do, YYYY")} + {isLoading && } + + + + {lastStatus && {lastStatus.type}} + {isLoading && } + {data && ( + ) => ( +

+ ), + a: ({ ...props }: HTMLAttributes) => ( + + ), + + code: ({ ...props }: HTMLAttributes) => ( + + ), + }} + > + {data.description} + + )} + {isLoading && ( + <> + + + + + )} + Votes + {data && } +

+
+
+
+ ) +} diff --git a/integrations/tally/components/proposal.tsx b/integrations/tally/components/proposal.tsx index e69de29b..f7bf70b1 100644 --- a/integrations/tally/components/proposal.tsx +++ b/integrations/tally/components/proposal.tsx @@ -0,0 +1,83 @@ +import moment from "moment" + +import { cn } from "@/lib/utils" +import { Badge } from "@/components/ui/badge" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { LinkComponent } from "@/components/shared/link-component" + +import { Proposal as ProposalType } from "../autogen/schema" + +export const Proposal = ({ + proposal, + daoSlug, +}: { + proposal: ProposalType + daoSlug: string +}) => { + const lastStatus = + proposal.statusChanges?.[proposal.statusChanges?.length - 1] ?? null + return ( + +
+
+ {proposal.description} +
+
+ {proposal?.block?.timestamp && ( + + {moment(proposal.block.timestamp).format("MMM Do, YYYY")} + + )} + {lastStatus && {lastStatus.type}} +
+
+ +
+ ) +} + +export const ProposalVoteStats = ({ proposal }: { proposal: ProposalType }) => ( +
+ {proposal.voteStats?.map((voteStat) => ( + + + +
+ + +
+ + {voteStat.support.toLocaleLowerCase()} + + : {voteStat.votes} +
+
+ + + ))} +
+) diff --git a/integrations/tally/components/vote.tsx b/integrations/tally/components/vote.tsx index e69de29b..221aafd4 100644 --- a/integrations/tally/components/vote.tsx +++ b/integrations/tally/components/vote.tsx @@ -0,0 +1,68 @@ +import { cn } from "@/lib/utils" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { Address } from "@/components/blockchain/address" + +import { Token, Vote as VoteType } from "../autogen/schema" +import { humanNumber } from "../utils" + +export const Vote = ({ vote, token }: { vote: VoteType; token: Token }) => ( + +
+ + + + + {vote.voter.name[0]} + + +
+ + {vote.voter.name.startsWith("0x") ? ( +
+ ) : ( + vote.voter.name + )} + + +
+ +
+
+ + {vote.support}{" "} + + {humanNumber( + vote.weight.substring(0, vote.weight.length - token.decimals) + )}{" "} + {token.symbol} + + +
+ {vote.reason && ( + {vote.reason} + )} +
+) diff --git a/integrations/tally/context/governors.tsx b/integrations/tally/context/governors.tsx deleted file mode 100644 index 5c3fff57..00000000 --- a/integrations/tally/context/governors.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { createContext, useCallback, useEffect, useMemo, useState } from "react" - -import { - Governor, - GovernorSortField, - InputMaybe, - SortOrder, -} from "../autogen/schema" -import { governorsQuery } from "../query/query-governor" - -export interface ITallyGovernorsContext { - governors: Array - isLoading: boolean - hasMore: boolean - next: () => Promise -} - -export const TallyGovernorsContext = createContext({ - governors: [], - isLoading: false, - hasMore: true, - next: async () => Promise.resolve(), -}) - -export const TallyGovernorsProvider = ({ - children, -}: { - children: React.ReactNode -}) => { - const [governors, setGovernors] = useState>([]) - const [isLoading, setIsLoading] = useState(false) - const [hasMore, setHasMore] = useState(true) - const [lastPage, setLastPage] = useState(null) - - const fetchGovernorsPage = async (page: number) => { - // early return if we're already getting a page - if (isLoading) return - setIsLoading(true) - const pageSize = 250 - const govs = await governorsQuery({ - pagination: { limit: pageSize, offset: page * pageSize }, - sort: { - // @ts-ignore - field: GovernorSortField.ActiveProposals, - order: SortOrder.Desc, - }, - }) - if (govs.result) { - const result: Array = govs.result.governors - if (result.length < pageSize) { - // we have fetched all the governors - setHasMore(false) - } else { - // tally graphql sometimes returns duplicated items. - setGovernors((prev) => Array.from(new Set([...prev, ...result]))) - setLastPage(page) - } - } else { - console.error(JSON.stringify(govs)) - } - setIsLoading(false) - } - - useEffect(() => { - if (governors.length === 0) { - void fetchGovernorsPage(0) - } - }, [governors]) - - const getNextPage = useCallback(async () => { - await fetchGovernorsPage((lastPage ?? 0) + 1) - }, [lastPage]) - - const value: ITallyGovernorsContext = useMemo( - () => ({ - governors, - isLoading, - hasMore, - next: getNextPage, - }), - [governors, isLoading, hasMore] - ) - return ( - - {children} - - ) -} diff --git a/integrations/tally/hooks/use-governors.ts b/integrations/tally/hooks/use-governors.ts deleted file mode 100644 index 70aee04e..00000000 --- a/integrations/tally/hooks/use-governors.ts +++ /dev/null @@ -1,9 +0,0 @@ -"use client" - -import { useContext } from "react" - -import { TallyGovernorsContext } from "../context/governors" - -export const useGovernors = () => { - return useContext(TallyGovernorsContext) -} diff --git a/integrations/tally/hooks/use-tally.ts b/integrations/tally/hooks/use-tally.ts new file mode 100644 index 00000000..ad61a021 --- /dev/null +++ b/integrations/tally/hooks/use-tally.ts @@ -0,0 +1,9 @@ +"use client" + +import { useContext } from "react" + +import { TallyContext } from "../tally-provider" + +export const useTally = () => { + return useContext(TallyContext) +} diff --git a/integrations/tally/query/query-account.ts b/integrations/tally/query/query-account.ts new file mode 100644 index 00000000..4559a719 --- /dev/null +++ b/integrations/tally/query/query-account.ts @@ -0,0 +1,34 @@ +import { QueryAccountsArgs } from "../autogen/schema" +import { tallyQuery } from "../tally-client" + +const accountDocument = ` + query Accounts( + $ids: [AccountID!], + $addresses: [Address!] + ) { + accounts( + ids: $ids, + addresses: $addresses + ) { + id + address + ens + twitter + name + bio + picture + safes + meta { + name + ens + twitter + bio + picture + type + } + } + } +` + +export const accountQuery = (variables: QueryAccountsArgs, apiKey: string) => + tallyQuery({ query: accountDocument, variables, apiKey }) diff --git a/integrations/tally/query/query-chains.ts b/integrations/tally/query/query-chains.ts new file mode 100644 index 00000000..a61d9530 --- /dev/null +++ b/integrations/tally/query/query-chains.ts @@ -0,0 +1,15 @@ +import { QueryGovernancesArgs } from "../autogen/schema" +import { tallyQuery } from "../tally-client" + +const chainsDocument = ` + query Chains { + chains { + id + name + svg + } + } +` + +export const chainsQuery = (apiKey: string) => + tallyQuery({ query: chainsDocument, variables: {}, apiKey }) diff --git a/integrations/tally/query/query-delegation.ts b/integrations/tally/query/query-delegation.ts index e69de29b..f42ba9f4 100644 --- a/integrations/tally/query/query-delegation.ts +++ b/integrations/tally/query/query-delegation.ts @@ -0,0 +1,56 @@ +import { GovernanceDelegatesArgs } from "../autogen/schema" +import { tallyQuery } from "../tally-client" + +const governorDocument = ` + query Governors( + $ids: [AccountID!], + $pagination: Pagination, + $sort: DelegateSort + ) { + governors( + ids: $ids + ) { + delegates(pagination: $pagination, sort: $sort) { + account { + id + address + ens + twitter + name + bio + picture + safes + } + stats { + delegationCount + votes { + total + } + weight { + owned + total + } + } + governor { + tokens { + symbol + decimals + } + } + } + + } + } +` + +export const delegationsQuery = ( + variables: GovernanceDelegatesArgs & { id: string }, + apiKey: string +) => { + const { id } = variables + return tallyQuery({ + query: governorDocument, + variables: { ...variables, ids: [id] }, + apiKey, + }) +} diff --git a/integrations/tally/query/query-governor.ts b/integrations/tally/query/query-governor.ts index 9886746b..3a89368c 100644 --- a/integrations/tally/query/query-governor.ts +++ b/integrations/tally/query/query-governor.ts @@ -1,27 +1,14 @@ -import { QueryGovernancesArgs } from "../autogen/schema" -import { tallyQuery, useTallyQuery } from "./tally-client" +import { QueryGovernancesArgs, QueryGovernorsArgs } from "../autogen/schema" +import { tallyQuery } from "../tally-client" -const governorsDocument = ` +const governorDocument = ` query Governors( - $chainIds: [ChainID!], - $addresses: [Address!], $ids: [AccountID!], - $includeInactive: Boolean, - $pagination: Pagination, - $sort: GovernorSort, - $includeUnlinked: Boolean ) { governors( - chainIds: $chainIds, - addresses: $addresses, - ids: $ids, - includeInactive: $includeInactive, - pagination: $pagination, - sort: $sort, - includeUnlinked: $includeUnlinked + ids: $ids ) { id - type proposalStats { total active @@ -29,15 +16,152 @@ const governorsDocument = ` passed } name + tokens { + id + type + address + name + symbol + supply + lastBlock + decimals + stats { + voters + supply + delegatedVotingPower + } + } + proposals(pagination: {limit:4, offset:0}, sort: {field: START_BLOCK, order: DESC}) { + id + description + block { + timestamp + } + voteStats { + votes + percent + support + weight + } + statusChanges { + type + } + } + delegates(pagination: {limit:3, offset:0}, sort: { field: VOTING_WEIGHT, order: DESC }) { + account { + id + address + ens + twitter + name + bio + picture + safes + } + stats { + delegationCount + votes { + total + } + weight { + owned + total + } + } + } + name slug } } ` -// hook (@tanstack/react-query) -export const useGovernorsQuery = (variables: QueryGovernancesArgs) => - useTallyQuery({ query: governorsDocument, variables }) +export const governorQuery = ( + variables: QueryGovernorsArgs & { id: string }, + apiKey: string +) => { + const { id } = variables + return tallyQuery({ + query: governorDocument, + variables: { ...variables, ids: [id] }, + apiKey, + }) +} +;` -// normal function -export const governorsQuery = (variables: QueryGovernancesArgs) => - tallyQuery({ query: governorsDocument, variables }) + query GovernanceTrendingDelegates($governanceId: AccountID!, $sort: DelegateSort, $pagination: Pagination) { + governance(id: $governanceId) { + id + chainId + organization { + name + visual { + icon + } + } + contracts { + tokens { + type + address + } + governor { + type + } + } + name + stats { + tokens { + owners + voters + supply + delegatedVotingPower + } + } + quorum + parameters { + ... on GovernorBravoParameters { + proposalThreshold + } + ... on OpenZeppelinGovernorParameters { + proposalThreshold + } + ... on GovernorAlphaParameters { + proposalThreshold + } + ... on GovernorAaveParameters { + proposalThreshold + } + } + tokens { + symbol + decimals + } + delegates(sort: $sort, pagination: $pagination) { + account { + address + name + bio + picture + identities { + ens + twitter + } + } + participation { + stats { + weight { + total + owned + } + votes { + total + } + delegations { + total + } + } + } + } + } +} + +` diff --git a/integrations/tally/query/query-governors.ts b/integrations/tally/query/query-governors.ts new file mode 100644 index 00000000..d3933c32 --- /dev/null +++ b/integrations/tally/query/query-governors.ts @@ -0,0 +1,41 @@ +import { QueryGovernancesArgs } from "../autogen/schema" +import { tallyQuery } from "../tally-client" + +const governorsDocument = ` + query Governances( + $pagination: Pagination, + ) { + governances( + pagination: $pagination, + ) { + id + name + chainId + organization { + visual { + icon + } + name + description + } + stats { + tokens { + voters + owners + } + proposals { + active + total + passed + failed + } + } + slug + } + } +` + +export const governorsQuery = ( + variables: QueryGovernancesArgs, + apiKey: string +) => tallyQuery({ query: governorsDocument, variables, apiKey }) diff --git a/integrations/tally/query/query-proposal.ts b/integrations/tally/query/query-proposal.ts index e69de29b..1d97f47e 100644 --- a/integrations/tally/query/query-proposal.ts +++ b/integrations/tally/query/query-proposal.ts @@ -0,0 +1,76 @@ +import { QueryProposalArgs } from "../autogen/schema" +import { tallyQuery } from "../tally-client" + +const proposalDocument = ` + query ProposalDetails($governanceId: AccountID!, $proposalId: ID!) { + proposal(proposalId: $proposalId, governanceId: $governanceId) { + id + description + voteStats { + votes + percent + support + weight + } + statusChanges { + type + } + votes(sort: {field: WEIGHT, order: DESC}, pagination: {limit: 500}) { + voter { + name + picture + address + identities { + twitter + } + } + reason + support + weight + block { + timestamp + } + } + executable { + values + targets + callDatas + signatures + } + governance { + id + chainId + organization { + description + } + contracts { + governor { + address + type + } + } + timelockId + } + governor { + tokens { + decimals + symbol + } + } + governanceId + block { + timestamp + } + } +} + + +` + +export const proposalQuery = (variables: QueryProposalArgs, apiKey: string) => { + return tallyQuery({ + query: proposalDocument, + variables, + apiKey, + }) +} diff --git a/integrations/tally/query/query-proposals.ts b/integrations/tally/query/query-proposals.ts new file mode 100644 index 00000000..6e0c38eb --- /dev/null +++ b/integrations/tally/query/query-proposals.ts @@ -0,0 +1,45 @@ +import { GovernanceProposalsArgs } from "../autogen/schema" +import { tallyQuery } from "../tally-client" + +const proposalsDocument = ` + query Governors( + $ids: [AccountID!], + $pagination: Pagination + ) { + governors( + ids: $ids + ) { + proposals(pagination: $pagination, sort: {field: START_BLOCK, order: DESC}) { + id + description + block { + timestamp + } + voteStats { + votes + percent + support + weight + } + statusChanges { + type + } + } + name + slug + } + } + +` + +export const proposalsQuery = ( + variables: GovernanceProposalsArgs & { id: string }, + apiKey: string +) => { + const { id } = variables + return tallyQuery({ + query: proposalsDocument, + variables: { ...variables, ids: [id] }, + apiKey, + }) +} diff --git a/integrations/tally/query/query-vote.ts b/integrations/tally/query/query-vote.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/integrations/tally/query/tally-client.ts b/integrations/tally/query/tally-client.ts deleted file mode 100644 index cb9f4c9a..00000000 --- a/integrations/tally/query/tally-client.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { useQuery } from "@tanstack/react-query" - -import { TALLY_API_KEY } from "../tally-provider" - -const TALLY_API_URL = "https://api.tally.xyz/query" - -export type TallyQueryParams = { - query: string - variables: Record -} - -const fetcher = async ({ query, variables }: TallyQueryParams) => { - try { - const response = await fetch(TALLY_API_URL, { - method: "POST", - headers: { - "Content-Type": "application/json", - "Api-Key": TALLY_API_KEY, - }, - body: JSON.stringify({ - query, - variables, - }), - }) - const json = await response.json() - if (json?.errors) { - console.error("error when fetching", json.errors) - return { error: json.errors } - } - - return { result: json.data } - } catch (error) { - console.error("Error when fetching =>", error) - return { error } - } -} - -// as a function -export const tallyQuery = ({ query, variables }: TallyQueryParams) => { - return fetcher({ query, variables }) -} - -// as a hook -export const useTallyQuery = ({ query, variables }: TallyQueryParams) => { - return useQuery({ - queryKey: [query.slice(0, 10), variables], - queryFn: () => fetcher({ query, variables }), - }) -} diff --git a/integrations/tally/tally-client.ts b/integrations/tally/tally-client.ts new file mode 100644 index 00000000..7c39e604 --- /dev/null +++ b/integrations/tally/tally-client.ts @@ -0,0 +1,44 @@ +const TALLY_API_URL = "https://api.tally.xyz/query" + +export type TallyQueryParams = { + query: string + variables: Record + apiKey: string +} + +const fetcher = async ({ query, variables, apiKey }: TallyQueryParams) => { + try { + if (!apiKey) return { error: "Missing tally API Key." } + const data = JSON.stringify({ + query, + variables, + }) + const response = await fetch(TALLY_API_URL, { + method: "POST", + body: data, + headers: { + "Content-Type": "application/json", + "Api-Key": apiKey, + }, + }) + if (response.status === 200) { + const result = await response.json() + if (result?.errors) { + return { error: result.errors } + } + + return { result: result.data } + } else { + const result = await response.json() + if (result?.errors) return { error: result.errors[0].message } + return { error: response.statusText } + } + } catch (error) { + console.error(error) + return { error: String(error) } + } +} + +export const tallyQuery = ({ query, variables, apiKey }: TallyQueryParams) => { + return fetcher({ query, variables, apiKey }) +} diff --git a/integrations/tally/tally-provider.tsx b/integrations/tally/tally-provider.tsx index 7965d243..d7eae414 100644 --- a/integrations/tally/tally-provider.tsx +++ b/integrations/tally/tally-provider.tsx @@ -1 +1,99 @@ -export const TALLY_API_KEY = process.env.NEXT_PUBLIC_TALLY_API_KEY as string +import { createContext, useCallback, useEffect, useMemo, useState } from "react" + +import { + Chain, + Governance, + GovernanceSortField, + SortOrder, +} from "./autogen/schema" +import { chainsQuery } from "./query/query-chains" +import { governorsQuery } from "./query/query-governors" + +export interface ITallyContext { + apiKey: string | null + setApiKey: (apiKey: string | null) => void + governors: { data: Governance[]; isLoading: boolean; error: string | null } + chains: Chain[] +} + +export const TallyContext = createContext({ + apiKey: null, + setApiKey: (apiKey: string | null) => {}, + governors: { data: [], isLoading: true, error: null }, + chains: [], +}) + +export const TallyProvider = ({ children }: { children: React.ReactNode }) => { + const [chains, setChains] = useState>([]) + const [governors, setGovernors] = useState>([]) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + const [apiKey, setApiKey] = useState(null) + const pageSize = 250 + const getGovernorsPage = useCallback( + async (page = 0) => { + const response = await governorsQuery( + { + pagination: { limit: pageSize, offset: page * pageSize }, + sort: { + field: GovernanceSortField.ActiveProposals, + order: SortOrder.Desc, + }, + }, + apiKey! + ) + if (response.error) { + if (page === 0) setError(response.error) + setIsLoading(false) + return + } + const { governances } = response.result + setGovernors((govs) => + Array.from(new Set([...govs, ...(governances as Governance[])])).sort( + (a, b) => + b.stats.proposals.active - a.stats.proposals.active || + b.stats.tokens.voters - a.stats.tokens.voters + ) + ) + if (governances.length < pageSize) { + // we done getting governors info + setIsLoading(false) + } else { + await getGovernorsPage(page + 1) + } + }, + [apiKey] + ) + + const getChains = useCallback(async () => { + const response = await chainsQuery(apiKey!) + if (response.error) { + console.error(response.error) + setIsLoading(false) + return + } + const { chains } = response.result + setChains(chains) + }, [apiKey]) + + useEffect(() => { + if (apiKey) { + setGovernors([]) + setError(null) + setIsLoading(true) + void getChains() + void getGovernorsPage() + } + }, [apiKey]) + + const value: ITallyContext = useMemo( + () => ({ + apiKey, + setApiKey, + chains, + governors: { data: governors, isLoading, error }, + }), + [apiKey, setApiKey, governors, isLoading, error] + ) + return {children} +} diff --git a/integrations/tally/utils/index.ts b/integrations/tally/utils/index.ts new file mode 100644 index 00000000..449e111b --- /dev/null +++ b/integrations/tally/utils/index.ts @@ -0,0 +1,10 @@ +export function humanNumber(num: string | number): string { + if (num == null) { + return "" + } + if (num == 0) { + return "0" + } + const i = Math.floor(Math.log(+num) / Math.log(1000)) + return (+num / Math.pow(1000, i)).toFixed(1) + ["", "K", "M", "B"][i] +}