From 1fff06739dd5194c5db8cc631aa4519789bfcceb Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Mon, 17 Aug 2020 13:51:56 +0100 Subject: [PATCH] Replace GraphQL subscriptions (WS) by queries with polling (HTTP) (#203) The Graph is having issues with GraphQL subscriptions, so we are temporarily moving to queries with polling for the time being. Once subscriptions get enabled again on The Graph, we will move back to GraphQL subscriptions. This commit also adds the possibility to consume the organization connector from any app connector, allowing to retrieve its options for example. This was done so that app connectors using TheGraph can inherit from the pollInterval defined in the main connector. This commit also updates the nodejs/ demos. --- examples/nodejs/src/describe-script.ts | 7 +- examples/nodejs/src/organization.ts | 4 +- examples/nodejs/src/subscriptions-org.ts | 2 +- examples/nodejs/src/subscriptions-tokens.ts | 19 ++-- examples/nodejs/src/subscriptions-voting.ts | 24 ++++-- examples/nodejs/src/tokens.ts | 20 ++--- examples/nodejs/src/voting.ts | 23 ++--- examples/nodejs/tsconfig.json | 4 +- .../src/__test__/connector/agreement.test.ts | 4 +- .../src/__test__/connector/signers.test.ts | 4 +- .../src/__test__/connector/versions.test.ts | 4 +- .../src/__test__/entities/agreement.test.ts | 4 +- packages/connect-agreement/src/connect.ts | 25 ++++-- .../src/thegraph/connector.ts | 15 +++- .../src/connections/ConnectorJson.ts | 10 ++- .../src/connections/IOrganizationConnector.ts | 1 + .../connect-core/src/utils/app-connectors.ts | 9 +- packages/connect-ethereum/src/index.ts | 4 +- .../src/__test__/token-balance.test.ts | 4 +- .../src/__test__/transactions.test.ts | 4 +- packages/connect-finance/src/connect.ts | 31 ++++--- .../connect-finance/src/thegraph/connector.ts | 15 +++- packages/connect-thegraph/src/connector.ts | 10 ++- .../src/core/GraphQLWrapper.ts | 86 +++++++++++-------- packages/connect-tokens/src/connect.ts | 28 ++++-- .../connect-tokens/src/thegraph/connector.ts | 26 ++++-- .../src/__test__/models/votes.test.ts | 34 ++++++-- .../thegraph/disputableVoting.test.ts | 15 +++- .../src/__test__/thegraph/settings.test.ts | 7 +- .../src/__test__/thegraph/voters.test.ts | 11 ++- .../src/__test__/thegraph/votes.test.ts | 24 ++++-- .../connect-voting-disputable/src/connect.ts | 33 +++++-- .../src/thegraph/connector.ts | 82 ++++++++++++++---- .../connect-voting/src/__test__/casts.test.ts | 4 +- .../connect-voting/src/__test__/votes.test.ts | 4 +- packages/connect-voting/src/connect.ts | 28 ++++-- .../connect-voting/src/thegraph/connector.ts | 15 +++- packages/connect/src/connect.ts | 5 +- 38 files changed, 438 insertions(+), 211 deletions(-) diff --git a/examples/nodejs/src/describe-script.ts b/examples/nodejs/src/describe-script.ts index 94c13f9e..5df75ad5 100644 --- a/examples/nodejs/src/describe-script.ts +++ b/examples/nodejs/src/describe-script.ts @@ -1,5 +1,5 @@ import { connect, describeScript, App } from '@aragon/connect' -import { Vote, Voting } from '@aragon/connect-voting' +import connectVoting, { Vote } from '@aragon/connect-voting' const VOTING_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-mainnet' @@ -14,10 +14,7 @@ function voteId(vote: Vote) { async function main() { const org = await connect('beehive.aragonid.eth', 'thegraph') const apps = await org.apps() - const votingApp = apps.find((app: App) => app.name === 'voting')! - - const voting = new Voting(votingApp.address, VOTING_SUBGRAPH_URL) - + const voting = await connectVoting(org.app('voting')) const votes = await voting.votes() const { script } = votes.find((vote) => voteId(vote) === '#54')! diff --git a/examples/nodejs/src/organization.ts b/examples/nodejs/src/organization.ts index 172b08ec..a3a4f587 100644 --- a/examples/nodejs/src/organization.ts +++ b/examples/nodejs/src/organization.ts @@ -10,7 +10,7 @@ async function main() { permissions.map(console.log) console.log('\nA role from a permission:') - const role = await permissions[4].getRole() + const role = await permissions[4].role() console.log(role) console.log('\nApps:') @@ -34,7 +34,7 @@ async function main() { console.log(appByAddress) console.log('\nAn app from a permission:') - const appFromPermission = await permissions[1].getApp() + const appFromPermission = await permissions[1].app() console.log(appFromPermission) } diff --git a/examples/nodejs/src/subscriptions-org.ts b/examples/nodejs/src/subscriptions-org.ts index bc1b6c3b..c2ab188b 100644 --- a/examples/nodejs/src/subscriptions-org.ts +++ b/examples/nodejs/src/subscriptions-org.ts @@ -5,7 +5,7 @@ const ORG_ADDRESS = '0xd697d1f417c621a6a54d9a7fa2805596ca393222' async function main() { const org = (await connect(ORG_ADDRESS, 'thegraph', { - chainId: 4, + network: 4, })) as Organization const subscription = org.onPermissions((permissions: Permission[]) => { diff --git a/examples/nodejs/src/subscriptions-tokens.ts b/examples/nodejs/src/subscriptions-tokens.ts index e58d3df3..05b01230 100644 --- a/examples/nodejs/src/subscriptions-tokens.ts +++ b/examples/nodejs/src/subscriptions-tokens.ts @@ -1,25 +1,24 @@ -import { TokenManager, TokenHolder } from '@aragon/connect-tokens' +import connect from '@aragon/connect' +import connectTokens, { TokenHolder } from '@aragon/connect-tokens' import { keepRunning } from './helpers' +const ORG_ADDRESS = '0x7318f8bc0a9f0044d5c77a6aca9c73c1da49c51e' const TOKENS_APP_ADDRESS = '0xb27004bf714ce2aa38f14647b38836f26df86cbf' -const ALL_TOKEN_MANAGER_SUBGRAPH_URL = - 'https://api.thegraph.com/subgraphs/name/aragon/aragon-tokens-mainnet' async function main() { - const tokenManager = new TokenManager( - TOKENS_APP_ADDRESS, - ALL_TOKEN_MANAGER_SUBGRAPH_URL - ) + const org = await connect(ORG_ADDRESS, 'thegraph') + const tokens = await connectTokens(org.app(TOKENS_APP_ADDRESS)) + const token = await tokens.token() - const token = await tokenManager.token() + console.log(`\nToken:`) console.log(token) - const subscription = token.onHolders((holders: TokenHolder[]) => { + const subscription = tokens.onHolders((holders: TokenHolder[]) => { console.log(`\nHolders:`) holders.map(console.log) }) - await keepRunning() + // await keepRunning() // Simply to illustrate how to close a subscription subscription.unsubscribe() diff --git a/examples/nodejs/src/subscriptions-voting.ts b/examples/nodejs/src/subscriptions-voting.ts index 65cf1530..e8f3f338 100644 --- a/examples/nodejs/src/subscriptions-voting.ts +++ b/examples/nodejs/src/subscriptions-voting.ts @@ -1,17 +1,25 @@ -import { Voting, Vote, Cast } from '@aragon/connect-voting' +import connect from '@aragon/connect' +import connectVoting, { Voting, Vote, Cast } from '@aragon/connect-voting' import { keepRunning } from './helpers' const ORG_ADDRESS = '0x7cee20f778a53403d4fc8596e88deb694bc91c98' const VOTING_APP_ADDRESS = '0xf7f9a33ed13b01324884bd87137609251b5f7c88' -const ALL_VOTING_SUBGRAPH_URL = - 'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-rinkeby' async function main() { - const voting = new Voting(VOTING_APP_ADDRESS, ALL_VOTING_SUBGRAPH_URL) + const org = await connect(ORG_ADDRESS, 'thegraph', { network: 4 }) + const voting = await connectVoting(org.app(VOTING_APP_ADDRESS)) - const votesSubscription = voting.onVotes((votes: Vote[]) => { + const votesSubscription = voting.onVotes({}, (votes: Vote[]) => { console.log('\nVotes: ') - votes.map(console.log) + votes + .map((vote, index) => ({ + index, + title: vote.metadata, + id: vote.id, + })) + .forEach((vote) => { + console.log(vote) + }) console.log( `\nTry creating a new vote at https://rinkeby.aragon.org/#/${ORG_ADDRESS}/${VOTING_APP_ADDRESS}/\n` ) @@ -23,7 +31,9 @@ async function main() { const castsSubscription = vote1.onCasts((casts: Cast[]) => { console.log(`\nCasts:`) - casts.map(console.log) + casts.forEach((cast) => { + console.log(cast) + }) console.log( `\nTry casting a vote on https://rinkeby.aragon.org/#/${ORG_ADDRESS}/${VOTING_APP_ADDRESS}/vote/1 (You must first mint yourself a token in the Token Manager)\n` ) diff --git a/examples/nodejs/src/tokens.ts b/examples/nodejs/src/tokens.ts index f3799060..d6554d86 100644 --- a/examples/nodejs/src/tokens.ts +++ b/examples/nodejs/src/tokens.ts @@ -1,23 +1,19 @@ -import { TokenManager } from '@aragon/connect-tokens' +import connect from '@aragon/connect' +import connectTokens from '@aragon/connect-tokens' +const ORG_ADDRESS = '0x7318f8bc0a9f0044d5c77a6aca9c73c1da49c51e' const TOKENS_APP_ADDRESS = '0xb27004bf714ce2aa38f14647b38836f26df86cbf' -const ALL_TOKEN_MANAGER_SUBGRAPH_URL = - 'https://api.thegraph.com/subgraphs/name/aragon/aragon-tokens-mainnet' async function main() { - const tokenManager = new TokenManager( - TOKENS_APP_ADDRESS, - ALL_TOKEN_MANAGER_SUBGRAPH_URL, - true - ) + const org = await connect(ORG_ADDRESS, 'thegraph', { verbose: true }) + const tokens = await connectTokens(org.app(TOKENS_APP_ADDRESS)) - console.log(tokenManager) - - const token = await tokenManager.token() + const token = await tokens.token() console.log(token) console.log('\nHolders:') - const holders = await token.holders() + const holders = await tokens.holders() + holders.map(console.log) } diff --git a/examples/nodejs/src/voting.ts b/examples/nodejs/src/voting.ts index 871fe31a..fa47e962 100644 --- a/examples/nodejs/src/voting.ts +++ b/examples/nodejs/src/voting.ts @@ -1,18 +1,18 @@ import { connect } from '@aragon/connect' -import { Cast, Vote, Voting } from '@aragon/connect-voting' +import connectVoting, { Cast, Vote, Voting } from '@aragon/connect-voting' -type Env = { chainId: number; org: string; votingSubgraphUrl: string } +type Env = { network: number; org: string; votingSubgraphUrl: string } const envs = new Map( Object.entries({ rinkeby: { - chainId: 4, + network: 4, org: 'gardens.aragonid.eth', votingSubgraphUrl: 'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-rinkeby', }, mainnet: { - chainId: 1, + network: 1, org: 'governance.aragonproject.eth', votingSubgraphUrl: 'https://api.thegraph.com/subgraphs/name/aragon/aragon-voting-mainnet', @@ -41,20 +41,11 @@ function voteId(vote: Vote) { } async function main() { - const org = await connect(env.org, 'thegraph', { chainId: env.chainId }) - const apps = await org.apps() - const votingApp = apps.find((app) => app.appName === 'voting.aragonpm.eth') + const org = await connect(env.org, 'thegraph', { network: env.network }) + const voting = await connectVoting(org.app('voting')) console.log('\nOrganization:', org.location, `(${org.address})`) - - if (!votingApp?.address) { - console.log('\nNo voting app found in this organization') - return - } - - console.log(`\nVoting app: ${votingApp.address}`) - - const voting = new Voting(votingApp.address, env.votingSubgraphUrl) + console.log(`\nVoting app: ${voting.address}`) console.log(`\nVotes:`) const votes = await voting.votes() diff --git a/examples/nodejs/tsconfig.json b/examples/nodejs/tsconfig.json index e5a06980..05186c23 100644 --- a/examples/nodejs/tsconfig.json +++ b/examples/nodejs/tsconfig.json @@ -8,7 +8,7 @@ "references": [ { "path": "../../packages/connect" }, { "path": "../../packages/connect-thegraph" }, - { "path": "../../packages/connect-thegraph-tokens" }, - { "path": "../../packages/connect-thegraph-voting" } + { "path": "../../packages/connect-tokens" }, + { "path": "../../packages/connect-voting" } ] } diff --git a/packages/connect-agreement/src/__test__/connector/agreement.test.ts b/packages/connect-agreement/src/__test__/connector/agreement.test.ts index ba260526..6b006c9f 100644 --- a/packages/connect-agreement/src/__test__/connector/agreement.test.ts +++ b/packages/connect-agreement/src/__test__/connector/agreement.test.ts @@ -9,7 +9,9 @@ describe('Agreement', () => { let connector: AgreementConnectorTheGraph beforeAll(() => { - connector = new AgreementConnectorTheGraph(AGREEMENT_SUBGRAPH_URL) + connector = new AgreementConnectorTheGraph({ + subgraphUrl: AGREEMENT_SUBGRAPH_URL, + }) }) afterAll(async () => { diff --git a/packages/connect-agreement/src/__test__/connector/signers.test.ts b/packages/connect-agreement/src/__test__/connector/signers.test.ts index 8bdc894f..52e3451c 100644 --- a/packages/connect-agreement/src/__test__/connector/signers.test.ts +++ b/packages/connect-agreement/src/__test__/connector/signers.test.ts @@ -9,7 +9,9 @@ describe('Agreement signers', () => { let connector: AgreementConnectorTheGraph beforeAll(() => { - connector = new AgreementConnectorTheGraph(AGREEMENT_SUBGRAPH_URL) + connector = new AgreementConnectorTheGraph({ + subgraphUrl: AGREEMENT_SUBGRAPH_URL, + }) }) afterAll(async () => { diff --git a/packages/connect-agreement/src/__test__/connector/versions.test.ts b/packages/connect-agreement/src/__test__/connector/versions.test.ts index 66287ec8..3e3fd52e 100644 --- a/packages/connect-agreement/src/__test__/connector/versions.test.ts +++ b/packages/connect-agreement/src/__test__/connector/versions.test.ts @@ -8,7 +8,9 @@ describe('Agreement versions', () => { let connector: AgreementConnectorTheGraph beforeAll(() => { - connector = new AgreementConnectorTheGraph(AGREEMENT_SUBGRAPH_URL) + connector = new AgreementConnectorTheGraph({ + subgraphUrl: AGREEMENT_SUBGRAPH_URL, + }) }) afterAll(async () => { diff --git a/packages/connect-agreement/src/__test__/entities/agreement.test.ts b/packages/connect-agreement/src/__test__/entities/agreement.test.ts index eb2c814d..8a3ff37d 100644 --- a/packages/connect-agreement/src/__test__/entities/agreement.test.ts +++ b/packages/connect-agreement/src/__test__/entities/agreement.test.ts @@ -8,7 +8,9 @@ describe('Agreement', () => { let agreement: Agreement beforeAll(() => { - const connector = new AgreementConnectorTheGraph(AGREEMENT_SUBGRAPH_URL) + const connector = new AgreementConnectorTheGraph({ + subgraphUrl: AGREEMENT_SUBGRAPH_URL, + }) agreement = new Agreement(connector, AGREEMENT_APP_ADDRESS) }) diff --git a/packages/connect-agreement/src/connect.ts b/packages/connect-agreement/src/connect.ts index 2438d8e7..00eb5a7f 100644 --- a/packages/connect-agreement/src/connect.ts +++ b/packages/connect-agreement/src/connect.ts @@ -1,16 +1,16 @@ import { createAppConnector } from '@aragon/connect-core' - import Agreement from './models/Agreement' import AgreementConnectorTheGraph, { subgraphUrlFromChainId, } from './thegraph/connector' type Config = { - subgraphUrl: string + pollInterval?: number + subgraphUrl?: string } export default createAppConnector( - ({ app, config, connector, network, verbose }) => { + ({ app, config, connector, network, orgConnector, verbose }) => { if (connector !== 'thegraph') { console.warn( `Connector unsupported: ${connector}. Using "thegraph" instead.` @@ -18,11 +18,20 @@ export default createAppConnector( } const subgraphUrl = - config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId) - const agreementConnector = new AgreementConnectorTheGraph( + config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId) ?? undefined + + let pollInterval + if (orgConnector.name === 'thegraph') { + pollInterval = + config?.pollInterval ?? orgConnector.config?.pollInterval ?? undefined + } + + const connectorTheGraph = new AgreementConnectorTheGraph({ + pollInterval, subgraphUrl, - verbose - ) - return new Agreement(agreementConnector, app.address) + verbose, + }) + + return new Agreement(connectorTheGraph, app.address) } ) diff --git a/packages/connect-agreement/src/thegraph/connector.ts b/packages/connect-agreement/src/thegraph/connector.ts index a34f681b..770db5f3 100644 --- a/packages/connect-agreement/src/thegraph/connector.ts +++ b/packages/connect-agreement/src/thegraph/connector.ts @@ -28,16 +28,25 @@ export function subgraphUrlFromChainId(chainId: number) { return null } +type AgreementConnectorTheGraphConfig = { + pollInterval?: number + subgraphUrl?: string + verbose?: boolean +} + export default class AgreementConnectorTheGraph implements IAgreementConnector { #gql: GraphQLWrapper - constructor(subgraphUrl: string, verbose: boolean = false) { - if (!subgraphUrl) { + constructor(config: AgreementConnectorTheGraphConfig) { + if (!config.subgraphUrl) { throw new Error( 'AgreementConnectorTheGraph requires subgraphUrl to be passed.' ) } - this.#gql = new GraphQLWrapper(subgraphUrl, verbose) + this.#gql = new GraphQLWrapper(config.subgraphUrl, { + pollInterval: config.pollInterval, + verbose: config.verbose, + }) } async disconnect() { diff --git a/packages/connect-core/src/connections/ConnectorJson.ts b/packages/connect-core/src/connections/ConnectorJson.ts index 95ff5a04..5043c482 100644 --- a/packages/connect-core/src/connections/ConnectorJson.ts +++ b/packages/connect-core/src/connections/ConnectorJson.ts @@ -19,13 +19,15 @@ export type ConnectorJsonConfig = { class ConnectorJson implements IOrganizationConnector { #permissions: Permission[] + connection?: ConnectionContext + readonly config: ConnectorJsonConfig readonly name = 'json' readonly network: Network - connection?: ConnectionContext - constructor({ permissions, network }: ConnectorJsonConfig) { - this.#permissions = permissions - this.network = network + constructor(config: ConnectorJsonConfig) { + this.config = config + this.network = config.network + this.#permissions = config.permissions } async connect(connection: ConnectionContext) { diff --git a/packages/connect-core/src/connections/IOrganizationConnector.ts b/packages/connect-core/src/connections/IOrganizationConnector.ts index 6342e594..ec2deda5 100644 --- a/packages/connect-core/src/connections/IOrganizationConnector.ts +++ b/packages/connect-core/src/connections/IOrganizationConnector.ts @@ -9,6 +9,7 @@ import Role from '../entities/Role' export default interface IOrganizationConnector { readonly name: string readonly network: Network + readonly config: { [key: string]: any } appByAddress(organization: Organization, appAddress: string): Promise appForOrg(organization: Organization, filters?: AppFilters): Promise appsForOrg(organization: Organization, filters?: AppFilters): Promise diff --git a/packages/connect-core/src/utils/app-connectors.ts b/packages/connect-core/src/utils/app-connectors.ts index 087e310d..b0696231 100644 --- a/packages/connect-core/src/utils/app-connectors.ts +++ b/packages/connect-core/src/utils/app-connectors.ts @@ -4,10 +4,11 @@ import App from '../entities/App' type AppConnectContext = { app: App - connector: string config: object + connector: string ipfs: ConnectionContext['ipfs'] network: Network + orgConnector: ConnectionContext['orgConnector'] verbose: boolean } @@ -56,7 +57,12 @@ export function createAppConnector< const { connection } = app.organization const { orgConnector } = connection + + // App connector config. const [connectorName, connectorConfig] = normalizeConnectorConfig( + // Contrary to the main connect() function, app connectors don’t require + // the connector to be passed. In this case, the name of the org + // connector (e.g. `name`) is used instead. connector || orgConnector.name ) @@ -66,6 +72,7 @@ export function createAppConnector< connector: connectorName, ipfs: connection.ipfs, network: orgConnector.network, + orgConnector, verbose: connection.verbose, }) diff --git a/packages/connect-ethereum/src/index.ts b/packages/connect-ethereum/src/index.ts index 0e8e0457..42f81528 100644 --- a/packages/connect-ethereum/src/index.ts +++ b/packages/connect-ethereum/src/index.ts @@ -19,11 +19,13 @@ export type ConnectorEthereumConfig = { } class ConnectorEthereum implements IOrganizationConnector { + connection?: ConnectionContext + readonly config: ConnectorEthereumConfig readonly name = 'ethereum' readonly network: Network - connection?: ConnectionContext constructor(config: ConnectorEthereumConfig) { + this.config = config this.network = config.network } diff --git a/packages/connect-finance/src/__test__/token-balance.test.ts b/packages/connect-finance/src/__test__/token-balance.test.ts index 0710fdc6..a618f81e 100644 --- a/packages/connect-finance/src/__test__/token-balance.test.ts +++ b/packages/connect-finance/src/__test__/token-balance.test.ts @@ -11,7 +11,9 @@ describe('when connecting to a finance app', () => { let connector: FinanceConnectorTheGraph beforeAll(() => { - connector = new FinanceConnectorTheGraph(FINANCE_SUBGRAPH_URL) + connector = new FinanceConnectorTheGraph({ + subgraphUrl: FINANCE_SUBGRAPH_URL, + }) }) afterAll(async () => { diff --git a/packages/connect-finance/src/__test__/transactions.test.ts b/packages/connect-finance/src/__test__/transactions.test.ts index ec991745..c864f7ce 100644 --- a/packages/connect-finance/src/__test__/transactions.test.ts +++ b/packages/connect-finance/src/__test__/transactions.test.ts @@ -9,7 +9,9 @@ describe('when connecting to a finance app', () => { let connector: FinanceConnectorTheGraph beforeAll(() => { - connector = new FinanceConnectorTheGraph(FINANCE_SUBGRAPH_URL) + connector = new FinanceConnectorTheGraph({ + subgraphUrl: FINANCE_SUBGRAPH_URL, + }) }) afterAll(async () => { diff --git a/packages/connect-finance/src/connect.ts b/packages/connect-finance/src/connect.ts index 1dc1457a..efb391f8 100644 --- a/packages/connect-finance/src/connect.ts +++ b/packages/connect-finance/src/connect.ts @@ -1,28 +1,37 @@ -import { createAppConnector, Network } from '@aragon/connect-core' -import { IFinanceConnector } from './types' +import { createAppConnector } from '@aragon/connect-core' import Finance from './models/Finance' import FinanceConnectorTheGraph, { subgraphUrlFromChainId, } from './thegraph/connector' type Config = { - subgraphUrl: string + pollInterval?: number + subgraphUrl?: string } export default createAppConnector( - async ({ app, config, connector, network, verbose }) => { + ({ app, config, connector, network, orgConnector, verbose }) => { if (connector !== 'thegraph') { console.warn( `Connector unsupported: ${connector}. Using "thegraph" instead.` ) } - return new Finance( - new FinanceConnectorTheGraph( - config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId), - verbose - ), - app.address - ) + const subgraphUrl = + config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId) ?? undefined + + let pollInterval + if (orgConnector.name === 'thegraph') { + pollInterval = + config?.pollInterval ?? orgConnector.config?.pollInterval ?? undefined + } + + const connectorTheGraph = new FinanceConnectorTheGraph({ + pollInterval, + subgraphUrl, + verbose, + }) + + return new Finance(connectorTheGraph, app.address) } ) diff --git a/packages/connect-finance/src/thegraph/connector.ts b/packages/connect-finance/src/thegraph/connector.ts index 7a526b3f..a3504e8a 100644 --- a/packages/connect-finance/src/thegraph/connector.ts +++ b/packages/connect-finance/src/thegraph/connector.ts @@ -19,16 +19,25 @@ export function subgraphUrlFromChainId(chainId: number) { return null } +type FinanceConnectorTheGraphConfig = { + pollInterval?: number + subgraphUrl?: string + verbose?: boolean +} + export default class FinanceConnectorTheGraph implements IFinanceConnector { #gql: GraphQLWrapper - constructor(subgraphUrl: string, verbose = false) { - if (!subgraphUrl) { + constructor(config: FinanceConnectorTheGraphConfig) { + if (!config.subgraphUrl) { throw new Error( 'FinanceConnectorTheGraph requires subgraphUrl to be passed.' ) } - this.#gql = new GraphQLWrapper(subgraphUrl, verbose) + this.#gql = new GraphQLWrapper(config.subgraphUrl, { + pollInterval: config.pollInterval, + verbose: config.verbose, + }) } async disconnect() { diff --git a/packages/connect-thegraph/src/connector.ts b/packages/connect-thegraph/src/connector.ts index 9f8e38cc..39001dca 100644 --- a/packages/connect-thegraph/src/connector.ts +++ b/packages/connect-thegraph/src/connector.ts @@ -28,6 +28,7 @@ import { export type ConnectorTheGraphConfig = { network: Networkish orgSubgraphUrl?: string + pollInterval?: number verbose?: boolean } @@ -62,11 +63,13 @@ function appFiltersToQueryFilter(appFilters: AppFilters) { class ConnectorTheGraph implements IOrganizationConnector { #gql: GraphQLWrapper + connection?: ConnectionContext + readonly config: ConnectorTheGraphConfig readonly name = 'thegraph' readonly network: Network - connection?: ConnectionContext constructor(config: ConnectorTheGraphConfig) { + this.config = config this.network = toNetwork(config.network) const orgSubgraphUrl = @@ -78,7 +81,10 @@ class ConnectorTheGraph implements IOrganizationConnector { ) } - this.#gql = new GraphQLWrapper(orgSubgraphUrl, config.verbose) + this.#gql = new GraphQLWrapper(orgSubgraphUrl, { + pollInterval: config.pollInterval, + verbose: config.verbose, + }) } async connect(connection: ConnectionContext) { diff --git a/packages/connect-thegraph/src/core/GraphQLWrapper.ts b/packages/connect-thegraph/src/core/GraphQLWrapper.ts index 68ff0b56..39ca5b38 100644 --- a/packages/connect-thegraph/src/core/GraphQLWrapper.ts +++ b/packages/connect-thegraph/src/core/GraphQLWrapper.ts @@ -1,59 +1,66 @@ import fetch from 'isomorphic-unfetch' -import ws from 'isomorphic-ws' import { Client, - defaultExchanges, - subscriptionExchange, - createRequest, + GraphQLRequest, + createRequest as createRequestUrql, } from '@urql/core' -import { SubscriptionClient } from 'subscriptions-transport-ws' import { DocumentNode } from 'graphql' import { pipe, subscribe } from 'wonka' import { SubscriptionHandler } from '@aragon/connect-types' -import { ParseFunction, QueryResult, SubscriptionOperation } from '../types' - -const AUTO_RECONNECT = true -const CONNECTION_TIMEOUT = 20 * 1000 - -function filterSubgraphUrl(url: string): [string, string] { - if (!/^(?:https|wss):\/\//.test(url)) { - throw new Error('Please provide a valid subgraph URL') +import { ParseFunction, QueryResult } from '../types' + +// Average block time is about 13 seconds on the 2020-08-14 +// See https://etherscan.io/chart/blocktime +const POLL_INTERVAL_DEFAULT = 13 * 1000 + +function createRequest(query: DocumentNode, args: object): GraphQLRequest { + // Make every operation type a query, until GraphQL subscriptions get added again. + if (query.definitions) { + query = { + ...query, + definitions: query.definitions.map((definition) => ({ + ...definition, + operation: 'query', + })), + } } - return [url.replace(/^wss/, 'https'), url.replace(/^https/, 'wss')] + return createRequestUrql(query, args) +} + +type GraphQLWrapperOptions = { + pollInterval?: number + verbose?: boolean } export default class GraphQLWrapper { #client: Client + #pollInterval: number #verbose: boolean - close: () => void - constructor(subgraphUrl: string, verbose = false) { - const [urlHttp, urlWs] = filterSubgraphUrl(subgraphUrl) + constructor( + subgraphUrl: string, + options: GraphQLWrapperOptions | boolean = {} + ) { + if (typeof options === 'boolean') { + console.warn( + 'GraphQLWrapper: please use `new GraphQLWrapper(url, { verbose })` rather than `new GraphQLWrapper(url, verbose)`.' + ) + options = { verbose: options } + } + options = options as GraphQLWrapperOptions - const subscriptionClient = new SubscriptionClient( - urlWs, - { reconnect: AUTO_RECONNECT, timeout: CONNECTION_TIMEOUT }, - ws - ) + this.#verbose = options.verbose ?? false + this.#pollInterval = options.pollInterval ?? POLL_INTERVAL_DEFAULT - this.#client = new Client({ - maskTypename: true, - url: urlHttp, - fetch, - exchanges: [ - ...defaultExchanges, - subscriptionExchange({ - forwardSubscription: (operation: SubscriptionOperation) => - subscriptionClient.request(operation), - }), - ], - }) + this.#client = new Client({ maskTypename: true, url: subgraphUrl, fetch }) + } - this.#verbose = verbose - this.close = () => subscriptionClient.close() + close(): void { + // Do nothing for now. + // Will be used when GraphQL subscriptions will be added again. } - subscribeToQuery( + subscribeToQuery( query: DocumentNode, args: any = {}, callback: Function @@ -61,7 +68,10 @@ export default class GraphQLWrapper { const request = createRequest(query, args) return pipe( - this.#client.executeSubscription(request), + this.#client.executeQuery(request, { + pollInterval: this.#pollInterval, + requestPolicy: 'cache-and-network', + }), subscribe((result: QueryResult) => { if (this.#verbose) { console.log(this.describeQueryResult(result)) diff --git a/packages/connect-tokens/src/connect.ts b/packages/connect-tokens/src/connect.ts index f0997a01..af68bddd 100644 --- a/packages/connect-tokens/src/connect.ts +++ b/packages/connect-tokens/src/connect.ts @@ -1,28 +1,38 @@ -import { createAppConnector, Network } from '@aragon/connect-core' -import { ITokensConnector } from './types' +import { createAppConnector } from '@aragon/connect-core' import Tokens from './models/Tokens' import TokensConnectorTheGraph, { subgraphUrlFromChainId, } from './thegraph/connector' type Config = { + pollInterval?: number subgraphUrl?: string } export default createAppConnector( - async ({ app, config, connector, network, verbose }) => { + async ({ app, config, connector, network, orgConnector, verbose }) => { if (connector !== 'thegraph') { console.warn( `Connector unsupported: ${connector}. Using "thegraph" instead.` ) } - const tokensConnector = await TokensConnectorTheGraph.create( - config.subgraphUrl ?? (subgraphUrlFromChainId(network.chainId) || ''), - app.address, - verbose - ) + const subgraphUrl = + config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId) ?? undefined - return new Tokens(tokensConnector, app.address) + let pollInterval + if (orgConnector.name === 'thegraph') { + pollInterval = + config?.pollInterval ?? orgConnector.config?.pollInterval ?? undefined + } + + const connectorTheGraph = await TokensConnectorTheGraph.create({ + appAddress: app.address, + pollInterval, + subgraphUrl, + verbose, + }) + + return new Tokens(connectorTheGraph, app.address) } ) diff --git a/packages/connect-tokens/src/thegraph/connector.ts b/packages/connect-tokens/src/thegraph/connector.ts index a5cabeb3..93c8b949 100644 --- a/packages/connect-tokens/src/thegraph/connector.ts +++ b/packages/connect-tokens/src/thegraph/connector.ts @@ -19,6 +19,13 @@ export function subgraphUrlFromChainId(chainId: number) { return null } +type TokensConnectorTheGraphConfig = { + appAddress?: Address + pollInterval?: number + subgraphUrl?: string + verbose?: boolean +} + export default class TokensConnectorTheGraph implements ITokensConnector { #gql: GraphQLWrapper #token: Token @@ -29,21 +36,28 @@ export default class TokensConnectorTheGraph implements ITokensConnector { } static async create( - subgraphUrl: string, - appAddress: Address, - verbose: boolean = false + config: TokensConnectorTheGraphConfig ): Promise { - if (!subgraphUrl) { + if (!config.subgraphUrl) { throw new Error( 'TokensConnectorTheGraph requires subgraphUrl to be passed.' ) } - const gql = new GraphQLWrapper(subgraphUrl, verbose) + if (!config.appAddress) { + throw new Error( + 'TokensConnectorTheGraph requires appAddress to be passed.' + ) + } + + const gql = new GraphQLWrapper(config.subgraphUrl, { + pollInterval: config.pollInterval, + verbose: config.verbose, + }) const token = await gql.performQueryWithParser( queries.TOKEN('query'), - { tokenManagerAddress: appAddress }, + { tokenManagerAddress: config.appAddress }, (result) => parseToken(result) ) diff --git a/packages/connect-voting-disputable/src/__test__/models/votes.test.ts b/packages/connect-voting-disputable/src/__test__/models/votes.test.ts index 2f0ea2dc..13b40beb 100644 --- a/packages/connect-voting-disputable/src/__test__/models/votes.test.ts +++ b/packages/connect-voting-disputable/src/__test__/models/votes.test.ts @@ -1,13 +1,21 @@ -import { DisputableVoting, Vote, CastVote, DisputableVotingConnectorTheGraph } from '../../../src' +import { + DisputableVoting, + Vote, + CastVote, + DisputableVotingConnectorTheGraph, +} from '../../../src' const VOTING_APP_ADDRESS = '0x26e14ed789b51b5b226d69a5d40f72dc2d0180fe' -const VOTING_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' +const VOTING_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' describe('DisputableVoting', () => { let voting: DisputableVoting beforeAll(() => { - const connector = new DisputableVotingConnectorTheGraph(VOTING_SUBGRAPH_URL) + const connector = new DisputableVotingConnectorTheGraph({ + subgraphUrl: VOTING_SUBGRAPH_URL, + }) voting = new DisputableVoting(connector, VOTING_APP_ADDRESS) }) @@ -18,11 +26,17 @@ describe('DisputableVoting', () => { describe('end date', () => { test('computes the end date properly', async () => { const scheduledVote = await voting.vote(`${VOTING_APP_ADDRESS}-vote-0`) - const expectedScheduledVoteEndDate = parseInt(scheduledVote.startDate) + parseInt(scheduledVote.duration) - expect(scheduledVote.endDate).toBe(expectedScheduledVoteEndDate.toString()) + const expectedScheduledVoteEndDate = + parseInt(scheduledVote.startDate) + parseInt(scheduledVote.duration) + expect(scheduledVote.endDate).toBe( + expectedScheduledVoteEndDate.toString() + ) const settledVote = await voting.vote(`${VOTING_APP_ADDRESS}-vote-2`) - const expectedSettledVoteEndDate = parseInt(settledVote.startDate) + parseInt(settledVote.duration) + parseInt(settledVote.pauseDuration) + const expectedSettledVoteEndDate = + parseInt(settledVote.startDate) + + parseInt(settledVote.duration) + + parseInt(settledVote.pauseDuration) expect(settledVote.endDate).toBe(expectedSettledVoteEndDate.toString()) }) }) @@ -67,8 +81,8 @@ describe('DisputableVoting', () => { test('fetches the cast vote info', async () => { expect(castVote.id).toBe(`${VOTE_ID}-cast-${VOTER_ADDRESS}`) expect(castVote.supports).toBe(false) - expect(castVote.stake).toBe("1000000000000000000") - expect(castVote.createdAt).toBe("1596394229") + expect(castVote.stake).toBe('1000000000000000000') + expect(castVote.createdAt).toBe('1596394229') expect(castVote.caster).toBe(VOTER_ADDRESS) }) @@ -87,7 +101,9 @@ describe('DisputableVoting', () => { const collateralRequirement = await vote.collateralRequirement() expect(collateralRequirement.id).toBe(voteId) - expect(collateralRequirement.token).toBe('0x3af6b2f907f0c55f279e0ed65751984e6cdc4a42') + expect(collateralRequirement.token).toBe( + '0x3af6b2f907f0c55f279e0ed65751984e6cdc4a42' + ) expect(collateralRequirement.actionAmount).toBe('0') expect(collateralRequirement.challengeAmount).toBe('0') expect(collateralRequirement.challengeDuration).toBe('259200') diff --git a/packages/connect-voting-disputable/src/__test__/thegraph/disputableVoting.test.ts b/packages/connect-voting-disputable/src/__test__/thegraph/disputableVoting.test.ts index 8ec9633c..76c444c0 100644 --- a/packages/connect-voting-disputable/src/__test__/thegraph/disputableVoting.test.ts +++ b/packages/connect-voting-disputable/src/__test__/thegraph/disputableVoting.test.ts @@ -2,13 +2,16 @@ import { DisputableVotingData } from '../../types' import { DisputableVotingConnectorTheGraph } from '../../../src' const VOTING_APP_ADDRESS = '0x26e14ed789b51b5b226d69a5d40f72dc2d0180fe' -const VOTING_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' +const VOTING_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' describe('DisputableVoting', () => { let connector: DisputableVotingConnectorTheGraph beforeAll(() => { - connector = new DisputableVotingConnectorTheGraph(VOTING_SUBGRAPH_URL) + connector = new DisputableVotingConnectorTheGraph({ + subgraphUrl: VOTING_SUBGRAPH_URL, + }) }) afterAll(async () => { @@ -24,8 +27,12 @@ describe('DisputableVoting', () => { test('returns the disputable voting data', () => { expect(disputableVoting.id).toBe(VOTING_APP_ADDRESS) - expect(disputableVoting.dao).toBe('0xa6e4b08981ae324f16d6be39362f6de2da22882a') - expect(disputableVoting.token).toBe('0x991f49aad101db17ff02d8d867a880703bface62') + expect(disputableVoting.dao).toBe( + '0xa6e4b08981ae324f16d6be39362f6de2da22882a' + ) + expect(disputableVoting.token).toBe( + '0x991f49aad101db17ff02d8d867a880703bface62' + ) }) }) }) diff --git a/packages/connect-voting-disputable/src/__test__/thegraph/settings.test.ts b/packages/connect-voting-disputable/src/__test__/thegraph/settings.test.ts index 36d9c71f..9f5753fd 100644 --- a/packages/connect-voting-disputable/src/__test__/thegraph/settings.test.ts +++ b/packages/connect-voting-disputable/src/__test__/thegraph/settings.test.ts @@ -1,13 +1,16 @@ import { DisputableVotingConnectorTheGraph, Setting } from '../../../src' const VOTING_APP_ADDRESS = '0x26e14ed789b51b5b226d69a5d40f72dc2d0180fe' -const VOTING_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' +const VOTING_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' describe('DisputableVoting settings', () => { let connector: DisputableVotingConnectorTheGraph beforeAll(() => { - connector = new DisputableVotingConnectorTheGraph(VOTING_SUBGRAPH_URL) + connector = new DisputableVotingConnectorTheGraph({ + subgraphUrl: VOTING_SUBGRAPH_URL, + }) }) afterAll(async () => { diff --git a/packages/connect-voting-disputable/src/__test__/thegraph/voters.test.ts b/packages/connect-voting-disputable/src/__test__/thegraph/voters.test.ts index e58c6034..22d3d9f1 100644 --- a/packages/connect-voting-disputable/src/__test__/thegraph/voters.test.ts +++ b/packages/connect-voting-disputable/src/__test__/thegraph/voters.test.ts @@ -2,13 +2,16 @@ import { DisputableVotingConnectorTheGraph, Voter } from '../../../src' const VOTER_ADDRESS = '0x0090aed150056316e37fe6dfa10dc63e79d173b6' const VOTING_APP_ADDRESS = '0x26e14ed789b51b5b226d69a5d40f72dc2d0180fe' -const VOTING_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' +const VOTING_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' describe('DisputableVoting voters', () => { let connector: DisputableVotingConnectorTheGraph beforeAll(() => { - connector = new DisputableVotingConnectorTheGraph(VOTING_SUBGRAPH_URL) + connector = new DisputableVotingConnectorTheGraph({ + subgraphUrl: VOTING_SUBGRAPH_URL, + }) }) afterAll(async () => { @@ -19,7 +22,9 @@ describe('DisputableVoting voters', () => { let voter: Voter beforeAll(async () => { - voter = await connector.voter(`${VOTING_APP_ADDRESS}-voter-${VOTER_ADDRESS}`) + voter = await connector.voter( + `${VOTING_APP_ADDRESS}-voter-${VOTER_ADDRESS}` + ) }) test('allows fetching voter information', async () => { diff --git a/packages/connect-voting-disputable/src/__test__/thegraph/votes.test.ts b/packages/connect-voting-disputable/src/__test__/thegraph/votes.test.ts index fb7c15a3..803f763a 100644 --- a/packages/connect-voting-disputable/src/__test__/thegraph/votes.test.ts +++ b/packages/connect-voting-disputable/src/__test__/thegraph/votes.test.ts @@ -1,13 +1,16 @@ import { DisputableVotingConnectorTheGraph, Vote, CastVote } from '../../../src' const VOTING_APP_ADDRESS = '0x26e14ed789b51b5b226d69a5d40f72dc2d0180fe' -const VOTING_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' +const VOTING_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/facuspagnuolo/aragon-dvoting-rinkeby-staging' describe('DisputableVoting votes', () => { let connector: DisputableVotingConnectorTheGraph beforeAll(() => { - connector = new DisputableVotingConnectorTheGraph(VOTING_SUBGRAPH_URL) + connector = new DisputableVotingConnectorTheGraph({ + subgraphUrl: VOTING_SUBGRAPH_URL, + }) }) afterAll(async () => { @@ -47,15 +50,23 @@ describe('DisputableVoting votes', () => { expect(castVotes.length).toBeGreaterThan(1) const firstCastVote = castVotes[0] - expect(firstCastVote.id).toBe(`${VOTING_APP_ADDRESS}-vote-4-cast-0x0090aed150056316e37fe6dfa10dc63e79d173b6`) - expect(firstCastVote.caster).toBe('0x0090aed150056316e37fe6dfa10dc63e79d173b6') + expect(firstCastVote.id).toBe( + `${VOTING_APP_ADDRESS}-vote-4-cast-0x0090aed150056316e37fe6dfa10dc63e79d173b6` + ) + expect(firstCastVote.caster).toBe( + '0x0090aed150056316e37fe6dfa10dc63e79d173b6' + ) expect(firstCastVote.createdAt).toEqual('1596383834') expect(firstCastVote.stake).toBe('1000000000000000000') expect(firstCastVote.supports).toBe(true) const secondCastVote = castVotes[1] - expect(secondCastVote.id).toBe(`${VOTING_APP_ADDRESS}-vote-4-cast-0xa9ac50dce74c46025dc9dceafb4fa21f0dc142ea`) - expect(secondCastVote.caster).toBe('0xa9ac50dce74c46025dc9dceafb4fa21f0dc142ea') + expect(secondCastVote.id).toBe( + `${VOTING_APP_ADDRESS}-vote-4-cast-0xa9ac50dce74c46025dc9dceafb4fa21f0dc142ea` + ) + expect(secondCastVote.caster).toBe( + '0xa9ac50dce74c46025dc9dceafb4fa21f0dc142ea' + ) expect(secondCastVote.createdAt).toEqual('1596394454') expect(secondCastVote.stake).toBe('1000000000000000000') expect(secondCastVote.supports).toBe(false) @@ -97,4 +108,3 @@ describe('DisputableVoting votes', () => { }) }) }) - diff --git a/packages/connect-voting-disputable/src/connect.ts b/packages/connect-voting-disputable/src/connect.ts index f14484a4..ee51e033 100644 --- a/packages/connect-voting-disputable/src/connect.ts +++ b/packages/connect-voting-disputable/src/connect.ts @@ -1,20 +1,37 @@ import { createAppConnector } from '@aragon/connect-core' - import DisputableVoting from './models/DisputableVoting' -import DisputableVotingConnectorTheGraph, { subgraphUrlFromChainId } from './thegraph/connector' +import DisputableVotingConnectorTheGraph, { + subgraphUrlFromChainId, +} from './thegraph/connector' type Config = { - subgraphUrl: string + pollInterval?: number + subgraphUrl?: string } export default createAppConnector( - ({ app, config, connector, network, verbose }) => { + ({ app, config, connector, network, orgConnector, verbose }) => { if (connector !== 'thegraph') { - console.warn(`Connector unsupported: ${connector}. Using "thegraph" instead.`) + console.warn( + `Connector unsupported: ${connector}. Using "thegraph" instead.` + ) } - const subgraphUrl = config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId) - const votingConnector = new DisputableVotingConnectorTheGraph(subgraphUrl, verbose) - return new DisputableVoting(votingConnector, app.address) + const subgraphUrl = + config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId) ?? undefined + + let pollInterval + if (orgConnector.name === 'thegraph') { + pollInterval = + config?.pollInterval ?? orgConnector.config?.pollInterval ?? undefined + } + + const connectorTheGraph = new DisputableVotingConnectorTheGraph({ + pollInterval, + subgraphUrl, + verbose, + }) + + return new DisputableVoting(connectorTheGraph, app.address) } ) diff --git a/packages/connect-voting-disputable/src/thegraph/connector.ts b/packages/connect-voting-disputable/src/thegraph/connector.ts index 05039eca..12f3acb0 100644 --- a/packages/connect-voting-disputable/src/thegraph/connector.ts +++ b/packages/connect-voting-disputable/src/thegraph/connector.ts @@ -18,7 +18,7 @@ import { parseVotes, parseCastVote, parseCastVotes, - parseCollateralRequirement + parseCollateralRequirement, } from './parsers' export function subgraphUrlFromChainId(chainId: number) { @@ -34,21 +34,35 @@ export function subgraphUrlFromChainId(chainId: number) { return null } -export default class DisputableVotingConnectorTheGraph implements IDisputableVotingConnector { +type DisputableVotingConnectorTheGraphConfig = { + pollInterval?: number + subgraphUrl?: string + verbose?: boolean +} + +export default class DisputableVotingConnectorTheGraph + implements IDisputableVotingConnector { #gql: GraphQLWrapper - constructor(subgraphUrl: string, verbose: boolean = false) { - if (!subgraphUrl) { - throw new Error('DisputableVotingConnectorTheGraph requires subgraphUrl to be passed.') + constructor(config: DisputableVotingConnectorTheGraphConfig) { + if (!config.subgraphUrl) { + throw new Error( + 'DisputableVotingConnectorTheGraph requires subgraphUrl to be passed.' + ) } - this.#gql = new GraphQLWrapper(subgraphUrl, verbose) + this.#gql = new GraphQLWrapper(config.subgraphUrl, { + pollInterval: config.pollInterval, + verbose: config.verbose, + }) } async disconnect() { this.#gql.close() } - async disputableVoting(disputableVoting: string): Promise { + async disputableVoting( + disputableVoting: string + ): Promise { return this.#gql.performQueryWithParser( queries.GET_DISPUTABLE_VOTING('query'), { disputableVoting }, @@ -56,7 +70,10 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - onDisputableVoting(disputableVoting: string, callback: Function): SubscriptionHandler { + onDisputableVoting( + disputableVoting: string, + callback: Function + ): SubscriptionHandler { return this.#gql.subscribeToQueryWithParser( queries.GET_DISPUTABLE_VOTING('subscription'), { disputableVoting }, @@ -73,7 +90,10 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - onCurrentSetting(disputableVoting: string, callback: Function): SubscriptionHandler { + onCurrentSetting( + disputableVoting: string, + callback: Function + ): SubscriptionHandler { return this.#gql.subscribeToQueryWithParser( queries.GET_CURRENT_SETTING('subscription'), { disputableVoting }, @@ -99,7 +119,11 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - async settings(disputableVoting: string, first: number, skip: number): Promise { + async settings( + disputableVoting: string, + first: number, + skip: number + ): Promise { return this.#gql.performQueryWithParser( queries.ALL_SETTINGS('query'), { disputableVoting, first, skip }, @@ -107,7 +131,12 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - onSettings(disputableVoting: string, first: number, skip: number, callback: Function): SubscriptionHandler { + onSettings( + disputableVoting: string, + first: number, + skip: number, + callback: Function + ): SubscriptionHandler { return this.#gql.subscribeToQueryWithParser( queries.ALL_SETTINGS('subscription'), { disputableVoting, first, skip }, @@ -133,7 +162,11 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - async votes(disputableVoting: string, first: number, skip: number): Promise { + async votes( + disputableVoting: string, + first: number, + skip: number + ): Promise { return this.#gql.performQueryWithParser( queries.ALL_VOTES('query'), { disputableVoting, first, skip }, @@ -141,7 +174,12 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - onVotes(disputableVoting: string, first: number, skip: number, callback: Function): SubscriptionHandler { + onVotes( + disputableVoting: string, + first: number, + skip: number, + callback: Function + ): SubscriptionHandler { return this.#gql.subscribeToQueryWithParser( queries.ALL_VOTES('subscription'), { disputableVoting, first, skip }, @@ -167,7 +205,11 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - async castVotes(voteId: string, first: number, skip: number): Promise { + async castVotes( + voteId: string, + first: number, + skip: number + ): Promise { return this.#gql.performQueryWithParser( queries.ALL_CAST_VOTES('query'), { voteId, first, skip }, @@ -175,7 +217,12 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - onCastVotes(voteId: string, first: number, skip: number, callback: Function): SubscriptionHandler { + onCastVotes( + voteId: string, + first: number, + skip: number, + callback: Function + ): SubscriptionHandler { return this.#gql.subscribeToQueryWithParser( queries.ALL_CAST_VOTES('subscription'), { voteId, first, skip }, @@ -209,7 +256,10 @@ export default class DisputableVotingConnectorTheGraph implements IDisputableVot ) } - onCollateralRequirement(voteId: string, callback: Function): SubscriptionHandler { + onCollateralRequirement( + voteId: string, + callback: Function + ): SubscriptionHandler { return this.#gql.subscribeToQueryWithParser( queries.GET_COLLATERAL_REQUIREMENT('subscription'), { voteId }, diff --git a/packages/connect-voting/src/__test__/casts.test.ts b/packages/connect-voting/src/__test__/casts.test.ts index 3e21b3e0..2e101cae 100644 --- a/packages/connect-voting/src/__test__/casts.test.ts +++ b/packages/connect-voting/src/__test__/casts.test.ts @@ -8,7 +8,9 @@ describe('when connecting to a voting app', () => { let connector: VotingConnectorTheGraph beforeAll(() => { - connector = new VotingConnectorTheGraph(VOTING_SUBGRAPH_URL) + connector = new VotingConnectorTheGraph({ + subgraphUrl: VOTING_SUBGRAPH_URL, + }) }) afterAll(async () => { diff --git a/packages/connect-voting/src/__test__/votes.test.ts b/packages/connect-voting/src/__test__/votes.test.ts index 6cf98d72..739e9341 100644 --- a/packages/connect-voting/src/__test__/votes.test.ts +++ b/packages/connect-voting/src/__test__/votes.test.ts @@ -8,7 +8,9 @@ describe('when connecting to a voting app', () => { let connector: VotingConnectorTheGraph beforeAll(() => { - connector = new VotingConnectorTheGraph(VOTING_SUBGRAPH_URL) + connector = new VotingConnectorTheGraph({ + subgraphUrl: VOTING_SUBGRAPH_URL, + }) }) afterAll(async () => { diff --git a/packages/connect-voting/src/connect.ts b/packages/connect-voting/src/connect.ts index 48cbb094..eb1d33cf 100644 --- a/packages/connect-voting/src/connect.ts +++ b/packages/connect-voting/src/connect.ts @@ -5,23 +5,33 @@ import VotingConnectorTheGraph, { } from './thegraph/connector' type Config = { - subgraphUrl: string + pollInterval?: number + subgraphUrl?: string } export default createAppConnector( - ({ app, config, connector, network, verbose }) => { + ({ app, config, connector, network, orgConnector, verbose }) => { if (connector !== 'thegraph') { console.warn( `Connector unsupported: ${connector}. Using "thegraph" instead.` ) } - return new Voting( - new VotingConnectorTheGraph( - config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId), - verbose - ), - app.address - ) + const subgraphUrl = + config.subgraphUrl ?? subgraphUrlFromChainId(network.chainId) ?? undefined + + let pollInterval + if (orgConnector.name === 'thegraph') { + pollInterval = + config?.pollInterval ?? orgConnector.config?.pollInterval ?? undefined + } + + const connectorTheGraph = new VotingConnectorTheGraph({ + pollInterval, + subgraphUrl, + verbose, + }) + + return new Voting(connectorTheGraph, app.address) } ) diff --git a/packages/connect-voting/src/thegraph/connector.ts b/packages/connect-voting/src/thegraph/connector.ts index c63dbff6..d4b49af5 100644 --- a/packages/connect-voting/src/thegraph/connector.ts +++ b/packages/connect-voting/src/thegraph/connector.ts @@ -19,16 +19,25 @@ export function subgraphUrlFromChainId(chainId: number) { return null } +type VotingConnectorTheGraphConfig = { + pollInterval?: number + subgraphUrl?: string + verbose?: boolean +} + export default class VotingConnectorTheGraph implements IVotingConnector { #gql: GraphQLWrapper - constructor(subgraphUrl: string, verbose: boolean = false) { - if (!subgraphUrl) { + constructor(config: VotingConnectorTheGraphConfig) { + if (!config.subgraphUrl) { throw new Error( 'VotingConnectorTheGraph requires subgraphUrl to be passed.' ) } - this.#gql = new GraphQLWrapper(subgraphUrl, verbose) + this.#gql = new GraphQLWrapper(config.subgraphUrl, { + pollInterval: config.pollInterval, + verbose: config.verbose, + }) } async disconnect() { diff --git a/packages/connect/src/connect.ts b/packages/connect/src/connect.ts index 0160b406..73d3c07c 100644 --- a/packages/connect/src/connect.ts +++ b/packages/connect/src/connect.ts @@ -28,7 +28,10 @@ export type ConnectOptions = { export type ConnectorDeclaration = | IOrganizationConnector - | [string, object | undefined] + | ['ethereum', ConnectorEthereumConfig | undefined] + | ['json', ConnectorJsonConfig | undefined] + | ['thegraph', ConnectorTheGraphConfig | undefined] + | [string, any] | string type IpfsResolver = (cid: string, path?: string) => string