From 3d4cf09e2ebf90cb13616ac2a2bf2341318cafa1 Mon Sep 17 00:00:00 2001 From: Marc Velmer Date: Tue, 14 Nov 2023 15:24:26 +0100 Subject: [PATCH] Updated `README` and last minor changes --- README.md | 408 ++++++++++++++++++ src/api/census3/census.ts | 15 +- src/api/census3/strategy.ts | 6 +- src/api/census3/token.ts | 12 +- src/census3.ts | 27 +- test/census3/api/token.test.ts | 6 +- test/census3/integration/census.test.ts | 41 ++ .../{census3.test.ts => service.test.ts} | 0 test/census3/integration/token.test.ts | 4 +- 9 files changed, 477 insertions(+), 42 deletions(-) create mode 100644 test/census3/integration/census.test.ts rename test/census3/integration/{census3.test.ts => service.test.ts} (100%) diff --git a/README.md b/README.md index 68a414d9..0e79b5ec 100644 --- a/README.md +++ b/README.md @@ -562,6 +562,414 @@ const vote = client.cspVote(new Vote([index % 2]), signature); const voteId = await client.submitVote(vote); ~~~ +## Census3 + +### What is Census3? + +Census3 is an API service to create censuses for elections with holders of a single token or a combination of them. +The service creates a list of holder addresses and balances and keeps it updated in real time, for every registered token. +Then, it allows creating a merkle tree census (compatible with [Vocdoni](https://vocdoni.io/)) with those holders, using their balances as vote weights. + +More information about Census3 can be found [here](https://github.com/vocdoni/census3). + +### Using Census3 + +The SDK comes with an implementation of the [Census3 API](https://github.com/vocdoni/census3/blob/main/api/README.md). + +#### Creating a Census3 client + +~~~ts +const client = new VocdoniCensus3Client({ + env: EnvOptions.DEV // dev environment +}) +~~~ + +#### Getting basic service information + +~~~ts +// Get the supported chains +const supportedChains = await client.getSupportedChains(); +// [ +// { +// "chainID": 1, +// "shortName": "eth", +// "name": "Ethereum Mainnet" +// }, +// { +// "chainID": 5, +// "shortName": "gor", +// "name": "Goerli" +// }, +// { +// "chainID": 137, +// "shortName": "matic", +// "name": "Polygon Mainnet" +// }, +// { +// "chainID": 80001, +// "shortName": "maticmum", +// "name": "Mumbai" +// } +// ] +~~~ + +~~~ts +// Get the supported token types +const supportedTypes = await client.getSupportedTypes(); +// ["erc20", "erc777", "poap", "unknown", "erc721burned", "erc1155", "nation3", "want", "erc721"] +~~~ + +#### Getting tokens information and creating them + +~~~ts +// Get the supported tokens +const supportedTokens = await client.getSupportedTokens(); +// [ +// { +// "ID": "0x0AaCfbeC6a24756c20D41914F2caba817C0d8521", +// "type": "erc20", +// "decimals": 18, +// "startBlock": 10886913, +// "symbol": "YAM", +// "totalSupply": "", +// "name": "YAM", +// "status": { +// "atBlock": 18565762, +// "synced": true, +// "progress": 100 +// }, +// "size": 14999, +// "defaultStrategy": 19, +// "chainID": 1, +// "chainAddress": "eth:0x0AaCfbeC6a24756c20D41914F2caba817C0d8521" +// }, +// { +// "ID": "0x0b38210ea11411557c13457D4dA7dC6ea731B88a", +// "type": "erc20", +// "decimals": 18, +// "startBlock": 11203771, +// "symbol": "API3", +// "totalSupply": "", +// "name": "API3", +// "status": { +// "atBlock": 18565763, +// "synced": true, +// "progress": 100 +// }, +// "size": 51178, +// "defaultStrategy": 8, +// "chainID": 1, +// "chainAddress": "eth:0x0b38210ea11411557c13457D4dA7dC6ea731B88a" +// }, +// ... +// ] +~~~ + +~~~ts +// Get a token by its ID (address) and chain identifier +const token = await client.getToken('0x0AaCfbeC6a24756c20D41914F2caba817C0d8521', 1); +// { +// "ID": "0x0AaCfbeC6a24756c20D41914F2caba817C0d8521", +// "type": "erc20", +// "decimals": 18, +// "startBlock": 10886913, +// "symbol": "YAM", +// "totalSupply": "15164231312592159866595366", +// "name": "YAM", +// "status": { +// "atBlock": 18565783, +// "synced": true, +// "progress": 100 +// }, +// "size": 14999, +// "defaultStrategy": 19, +// "chainID": 1, +// "chainAddress": "eth:0x0AaCfbeC6a24756c20D41914F2caba817C0d8521", +// "tags": [] +// } +~~~ + +~~~ts +// Check if a holder is registered for a given token +const token = await client.isHolderInToken( + '0x0AaCfbeC6a24756c20D41914F2caba817C0d8521', + 1, + '0x111000000000000000000000000000000000dEaD' +); +// false +~~~ + +~~~ts +// Creates a new token by passing the address, the type and the chain identifier +const token = await client.createToken('0xa117000000f279d81a1d3cc75430faa017fa5a2e', 'erc20', 1); +~~~ + +#### Getting strategies information and creating them + +~~~ts +// Get the supported strategies +const supportedStrategies = await client.getStrategies(); +// [ +// { +// "ID": 1, +// "alias": "Default strategy for token CRV", +// "predicate": "CRV", +// "uri": "ipfs://bafybeicjqjklqpumewpaue6weg47byz6fwmbg6ozief3w2pgqx7zlwl5ea", +// "tokens": { +// "CRV": { +// "ID": "0xD533a949740bb3306d119CC777fa900bA034cd52", +// "chainID": 1, +// "minBalance": "0", +// "chainAddress": "eth:0xD533a949740bb3306d119CC777fa900bA034cd52" +// } +// } +// }, +// { +// "ID": 2, +// "alias": "Default strategy for token UNI", +// "predicate": "UNI", +// "uri": "ipfs://bafybeiesxbsbvp2agcuolezec6hvimntqdg3w43xs62mecdj2fyeh5anxu", +// "tokens": { +// "UNI": { +// "ID": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", +// "chainID": 1, +// "minBalance": "0", +// "chainAddress": "eth:0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" +// } +// } +// }, +// ... +// ] +~~~ + +~~~ts +// Get the supported strategies by token and chain identifier +const supportedStrategiesByToken = await client.getStrategiesByToken('0x0AaCfbeC6a24756c20D41914F2caba817C0d8521', 1); +// [ +// { +// "ID": 19, +// "alias": "Default strategy for token YAM", +// "predicate": "YAM", +// "uri": "ipfs://bafybeicddxfktpcmbkvrflifbod6eeaizfab7l5ijggswnn5jwu3uhv4i4", +// "tokens": { +// "YAM": { +// "ID": "0x0AaCfbeC6a24756c20D41914F2caba817C0d8521", +// "chainID": 1, +// "minBalance": "0", +// "chainAddress": "eth:0x0AaCfbeC6a24756c20D41914F2caba817C0d8521" +// } +// } +// }, +// { +// "ID": 37, +// "alias": "testStrategy_1699887257144", +// "predicate": "(YAM OR API3) AND 1INCH", +// "uri": "ipfs://bafybeic2gw6nb75ledp3jbz46rmdrnti33hgtlm5icfluxn5ol4enqps7i", +// "tokens": { +// "1INCH": { +// "ID": "0x111111111117dC0aa78b770fA6A738034120C302", +// "chainID": 1, +// "minBalance": "50", +// "chainAddress": "eth:0x111111111117dC0aa78b770fA6A738034120C302" +// }, +// "API3": { +// "ID": "0x0b38210ea11411557c13457D4dA7dC6ea731B88a", +// "chainID": 1, +// "minBalance": "0", +// "chainAddress": "eth:0x0b38210ea11411557c13457D4dA7dC6ea731B88a" +// }, +// "YAM": { +// "ID": "0x0AaCfbeC6a24756c20D41914F2caba817C0d8521", +// "chainID": 1, +// "minBalance": "10000", +// "chainAddress": "eth:0x0AaCfbeC6a24756c20D41914F2caba817C0d8521" +// } +// } +// }, +// ... +// ] +~~~ + +~~~ts +// Get a strategy on a given identifier +const strategy = await client.getStrategy(1); +// { +// "ID": 1, +// "alias": "Default strategy for token CRV", +// "predicate": "CRV", +// "uri": "ipfs://bafybeicjqjklqpumewpaue6weg47byz6fwmbg6ozief3w2pgqx7zlwl5ea", +// "tokens": { +// "CRV": { +// "ID": "0xD533a949740bb3306d119CC777fa900bA034cd52", +// "chainID": 1, +// "minBalance": "0", +// "chainAddress": "eth:0xD533a949740bb3306d119CC777fa900bA034cd52" +// } +// } +// } +~~~ + +~~~ts +// Get strategy size +const size = await client.getStrategySize(1); +// 12455 +~~~ + +~~~ts +// Creates a new strategy by passing the alias, the predicate and the tokens information +const strategyId = await client.createStrategy('test_strategy', '(wANT OR ANT) AND USDC', { + "wANT": { + "ID": "0x1324", + "chainID": 1, + "minBalance": "10000" + }, + "ANT": { + "ID": "0x1324", + "chainID": 5, + }, + "USDC": { + "ID": "0x1324", + "chainID": 1, + "minBalance": "50" + }, +}); +console.log(strategyId) // strategy identifier +~~~ + +~~~ts +// Imports a strategy from IPFS by the given cid +const strategy = await client.importStrategy('bafybeicjqjklqpumewpaue6weg47byz6fwmbg6ozief3w2pgqx7zlwl5ea'); +// { +// "ID": 1, +// "alias": "Default strategy for token CRV", +// "predicate": "CRV", +// "uri": "ipfs://bafybeicjqjklqpumewpaue6weg47byz6fwmbg6ozief3w2pgqx7zlwl5ea", +// "tokens": { +// "CRV": { +// "ID": "0xD533a949740bb3306d119CC777fa900bA034cd52", +// "chainID": 1, +// "minBalance": "0", +// "chainAddress": "eth:0xD533a949740bb3306d119CC777fa900bA034cd52" +// } +// } +// } +~~~ + +~~~ts +// Validates a predicate for a strategy and returns the parsed predicate in JSON +const validatePredicate = await client.validatePredicate('1INCH AND (YAM OR API3)'); +// { +// "result": { +// "childs": { +// "operator": "AND", +// "tokens": [ +// { +// "childs": { +// "operator": "OR", +// "tokens": [ +// { +// "literal": "YAM" +// }, +// { +// "literal": "API3" +// } +// ] +// } +// }, +// { +// "literal": "1INCH" +// } +// ] +// } +// } +// } +~~~ + +~~~ts +// Gets the supported predicate operators +const operators = await client.getSupportedOperators(); +// [ +// { +// "description": "AND logical operator that returns the common token holders between symbols with fixed balance to 1", +// "tag": "AND" +// }, +// { +// "description": "AND:sum logical operator that returns the common token holders between symbols with the sum of their balances on both tokens", +// "tag": "AND:sum" +// }, +// { +// "description": "AND:mul logical operator that returns the common token holders between symbols with the multiplication of their balances on both tokens", +// "tag": "AND:mul" +// }, +// ... +// ] +~~~ + +#### Getting censuses information and creating them + +~~~ts +// Get the supported censuses by strategy identifier +const strategyID = 18; +const censusesByStrategy = await client.getCensuses(strategyID); +// [ +// { +// "ID": 18569955180, +// "strategyID": 18, +// "merkleRoot": "9b1ac0ed374a66b781a22ec5e1b1382324adc0759662e1e6f85fc87f5a23407e", +// "uri": "ipfs://bafybeihwz2mbkkphgs2ni5laymgtfokaskujg2qfqcvoxhkccbdqp6k7ly", +// "size": 14999, +// "weight": "81637958624197446065983341792", +// "anonymous": false +// }, +// { +// "ID": 18569991180, +// "strategyID": 18, +// "merkleRoot": "ab1c003b923c4fec0b24f84893ddda8835fd3990904dc64f06c1fc0eadef402f", +// "uri": "ipfs://bafybeig5jrzw7ayxb442evan4pwa4rfksznh3smyt4exyjkubet2u5ldjm", +// "size": 14999, +// "weight": "653103668993579568527866734336", +// "anonymous": false +// }, +// ... +// ] +~~~ + +~~~ts +// Get a census on a given identifier +const census = await client.getCensus(18569955180); +// { +// "ID": 18569955180, +// "strategyID": 18, +// "merkleRoot": "9b1ac0ed374a66b781a22ec5e1b1382324adc0759662e1e6f85fc87f5a23407e", +// "uri": "ipfs://bafybeihwz2mbkkphgs2ni5laymgtfokaskujg2qfqcvoxhkccbdqp6k7ly", +// "size": 14999, +// "weight": "1514939612264202552941935398517220938016694806267744586724593217517874", +// "anonymous": false +// } +~~~ + +~~~ts +// Creates a new census by passing the strategy identifier +const strategyID = 18; +const census = await client.createCensus(strategyID); +// { +// "ID": 18570184180, +// "strategyID": 18, +// "merkleRoot": "542166dd4757904449e71d5c21058597ab4179f040ee1f9e7dd29eec622ca5ed", +// "uri": "ipfs://bafybeifbhmytl6olebkdoas6uftj3ae5akutmji4io6k37ilzu5uli2nle", +// "size": 14999, +// "weight": "42801802051163230603042274301444096", +// "anonymous": false +// } +~~~ + +~~~ts +// Creates a new census by passing the token address, using the default strategy and returns +// an instance of `TokenCensus` which can be directly used as a census in the Vocdoni chain +const census = await client.createTokenCensus('0x0AaCfbeC6a24756c20D41914F2caba817C0d8521', 1); +console.log(typeof census); // TokenCensus +~~~ + ## Examples You can find a [full featured vite][example-vite] application with all the previous diff --git a/src/api/census3/census.ts b/src/api/census3/census.ts index 68d7ea8f..bdc21f81 100644 --- a/src/api/census3/census.ts +++ b/src/api/census3/census.ts @@ -19,7 +19,7 @@ export interface ICensus3CensusResponse { /** * The identifier of the census */ - censusID: number; + ID: number; /** * The identifier of the strategy of the built census @@ -70,7 +70,7 @@ export interface ICensus3CensusQueueResponse { /** * The string of the error */ - err: string; + error: string; }; /** @@ -130,24 +130,17 @@ export abstract class Census3CensusAPI extends Census3API { } /** - * Requests the creation of a new census with the strategy provided for the blockNumber. + * Requests the creation of a new census with the strategy provided. * * @param {string} url API endpoint URL * @param {number} strategyId The strategy identifier * @param {boolean} anonymous If the census has to be anonymous - * @param {number} blockNumber The number of the block * @returns {Promise} The queue identifier */ - public static create( - url: string, - strategyId: number, - anonymous: boolean = false, - blockNumber?: number - ): Promise { + public static create(url: string, strategyId: number, anonymous: boolean = false): Promise { return axios .post(url + Census3CensusAPIMethods.CREATE, { strategyID: strategyId, - blockNumber, anonymous, }) .then((response) => response.data) diff --git a/src/api/census3/strategy.ts b/src/api/census3/strategy.ts index 49ce533c..11e261bf 100644 --- a/src/api/census3/strategy.ts +++ b/src/api/census3/strategy.ts @@ -10,7 +10,7 @@ enum Census3StrategyAPIMethods { STRATEGY = '/strategies/{id}', SIZE = '/strategies/{id}/size', SIZE_QUEUE = '/strategies/{id}/size/queue/{queueId}', - VALIDATE_PREDICATE = '/strategies/predicate/parse', + VALIDATE_PREDICATE = '/strategies/predicate/validate', OPERATORS = '/strategies/predicate/operators', } @@ -101,7 +101,7 @@ export interface ICensus3StrategySizeQueueResponse { /** * The string of the error */ - err: string; + error: string; }; /** @@ -128,7 +128,7 @@ export interface ICensus3StrategyImportQueueResponse { /** * The string of the error */ - err: string; + error: string; }; /** diff --git a/src/api/census3/token.ts b/src/api/census3/token.ts index 47a4ea88..c893fee8 100644 --- a/src/api/census3/token.ts +++ b/src/api/census3/token.ts @@ -96,16 +96,11 @@ export type Census3Token = { }; }; -export type Census3TokenSummary = Pick< - Census3Token, - 'ID' | 'name' | 'type' | 'startBlock' | 'symbol' | 'tags' | 'chainID' | 'externalID' | 'chainAddress' | 'status' ->; - export interface ICensus3TokenListResponse { /** * The list of the tokens */ - tokens: Array; + tokens: Array; } export interface ICensus3TokenListResponsePaginated extends ICensus3TokenListResponse { @@ -207,13 +202,12 @@ export abstract class Census3TokenAPI extends Census3API { } /** - * Triggers a new scan for the provided token, starting from the defined block. + * Triggers a new scan for the provided token. * * @param {string} url API endpoint URL * @param {string} id The token address * @param {string} type The type of the token * @param {number} chainId The chain id of the token - * @param {number} startBlock The start block * @param {string[]} tags The tags assigned for the token * @param {string} externalId The identifier used by external provider * @returns {Promise} promised IFileCIDResponse @@ -223,7 +217,6 @@ export abstract class Census3TokenAPI extends Census3API { id: string, type: string, chainId: number, - startBlock: number, tags?: string, externalId?: string ): Promise { @@ -232,7 +225,6 @@ export abstract class Census3TokenAPI extends Census3API { ID: id, type, chainID: chainId, - startBlock, tags, externalID: externalId, }) diff --git a/src/census3.ts b/src/census3.ts index 156a90b5..b9abdf06 100644 --- a/src/census3.ts +++ b/src/census3.ts @@ -8,7 +8,6 @@ import { ICensus3CensusResponse, ICensus3SupportedChain, Census3Token, - Census3TokenSummary, Census3Strategy, Census3CreateStrategyToken, ICensus3ValidatePredicateResponse, @@ -20,7 +19,6 @@ import { TokenCensus } from './types'; import { delay } from './util/common'; export type Token = Omit & { tags: string[] }; -export type TokenSummary = Census3TokenSummary; export type Strategy = Census3Strategy; export type StrategyToken = Census3CreateStrategyToken; export type Census3Census = ICensus3CensusResponse; @@ -50,12 +48,18 @@ export class VocdoniCensus3Client { } /** - * Returns a list of summarized tokens supported by the service + * Returns a list of tokens supported by the service * - * @returns {Promise} Token summary list + * @returns {Promise} Token list */ - getSupportedTokens(): Promise { - return Census3TokenAPI.list(this.url, { pageSize: -1 }).then((list) => list.tokens ?? []); + getSupportedTokens(): Promise { + return Census3TokenAPI.list(this.url, { pageSize: -1 }).then( + (list) => + list?.tokens?.map((token) => ({ + ...token, + tags: token.tags?.split(',') ?? [], + })) ?? [] + ); } /** @@ -126,20 +130,18 @@ export class VocdoniCensus3Client { * @param {number} chainId The chain id of the token * @param {string} externalId The identifier used by external provider * @param {string} tags The tag list to associate the token with - * @param {string} startBlock The start block where to start scanning */ createToken( address: string, type: string, chainId: number = 1, externalId: string = '', - tags: string[] = [], - startBlock: number = 0 + tags: string[] = [] ): Promise { invariant(address, 'No token address'); invariant(type, 'No token type'); invariant(isAddress(address), 'Incorrect token address'); - return Census3TokenAPI.create(this.url, address, type, chainId, startBlock, tags?.join(), externalId); + return Census3TokenAPI.create(this.url, address, type, chainId, tags?.join(), externalId); } /** @@ -300,10 +302,9 @@ export class VocdoniCensus3Client { * * @param {number} strategyId The id of the strategy * @param {boolean} anonymous If the census has to be anonymous - * @param {number} blockNumber The block number * @returns {Promise} The census information */ - createCensus(strategyId: number, anonymous: boolean = false, blockNumber?: number): Promise { + createCensus(strategyId: number, anonymous: boolean = false): Promise { invariant(strategyId || strategyId >= 0, 'No strategy id'); const waitForQueue = (queueId: string, wait?: number, attempts?: number): Promise => { @@ -326,7 +327,7 @@ export class VocdoniCensus3Client { }); }; - return Census3CensusAPI.create(this.url, strategyId, anonymous, blockNumber) + return Census3CensusAPI.create(this.url, strategyId, anonymous) .then((createCensus) => createCensus.queueID) .then((queueId) => waitForQueue(queueId)); } diff --git a/test/census3/api/token.test.ts b/test/census3/api/token.test.ts index 79b7a3e6..d87377de 100644 --- a/test/census3/api/token.test.ts +++ b/test/census3/api/token.test.ts @@ -5,15 +5,15 @@ import { Census3TokenAPI, ErrCantGetToken, ErrNotFoundToken, ErrTokenAlreadyExis describe('Census3 token API tests', () => { it('should throw when creating a non existent token', async () => { await expect(async () => { - await Census3TokenAPI.create(URL, '0x0', 'erc20', 1, 0); + await Census3TokenAPI.create(URL, '0x0', 'erc20', 1); }).rejects.toThrow(ErrCantGetToken); }, 5000); it('should throw when creating an already existent token', async () => { try { - await Census3TokenAPI.create(URL, '0xa117000000f279d81a1d3cc75430faa017fa5a2e', 'erc20', 1, 0); + await Census3TokenAPI.create(URL, '0xa117000000f279d81a1d3cc75430faa017fa5a2e', 'erc20', 1); } catch (e) {} await expect(async () => { - await Census3TokenAPI.create(URL, '0xa117000000f279d81a1d3cc75430faa017fa5a2e', 'erc20', 1, 0); + await Census3TokenAPI.create(URL, '0xa117000000f279d81a1d3cc75430faa017fa5a2e', 'erc20', 1); }).rejects.toThrow(ErrTokenAlreadyExists); }, 15000); it('should throw when fetching a non existent token', async () => { diff --git a/test/census3/integration/census.test.ts b/test/census3/integration/census.test.ts new file mode 100644 index 00000000..c1590c2c --- /dev/null +++ b/test/census3/integration/census.test.ts @@ -0,0 +1,41 @@ +import { EnvOptions, VocdoniCensus3Client } from '../../../src'; + +describe('Census3 censuses integration tests', () => { + it('should return the supported censuses information', async () => { + const client = new VocdoniCensus3Client({ env: EnvOptions.DEV }); + const strategies = await client.getStrategies(); + if (strategies.length > 0) { + const censuses = await client.getCensuses(strategies[0].ID); + censuses.forEach((census) => { + expect(census).toMatchObject({ + ID: expect.any(Number), + strategyID: expect.any(Number), + merkleRoot: expect.any(String), + uri: expect.any(String), + size: expect.any(Number), + weight: expect.any(String), + anonymous: expect.any(Boolean), + }); + }); + } + }, 15000); + it('should return the census information', async () => { + const client = new VocdoniCensus3Client({ env: EnvOptions.DEV }); + const strategies = await client.getStrategies(); + if (strategies.length > 0) { + const censuses = await client.getCensuses(strategies[0].ID); + if (censuses.length > 0) { + const census = await client.getCensus(censuses[0].ID); + expect(census).toMatchObject({ + ID: expect.any(Number), + strategyID: expect.any(Number), + merkleRoot: expect.any(String), + uri: expect.any(String), + size: expect.any(Number), + weight: expect.any(String), + anonymous: expect.any(Boolean), + }); + } + } + }, 15000); +}); diff --git a/test/census3/integration/census3.test.ts b/test/census3/integration/service.test.ts similarity index 100% rename from test/census3/integration/census3.test.ts rename to test/census3/integration/service.test.ts diff --git a/test/census3/integration/token.test.ts b/test/census3/integration/token.test.ts index 566063eb..87c33575 100644 --- a/test/census3/integration/token.test.ts +++ b/test/census3/integration/token.test.ts @@ -48,7 +48,7 @@ describe('Census3 token integration tests', () => { it('should create the given token in the census3 service', async () => { const client = new VocdoniCensus3Client({ env: EnvOptions.DEV }); try { - await client.createToken('0xa117000000f279d81a1d3cc75430faa017fa5a2e', 'erc20', 1, null, ['test', 'test2'], 0); + await client.createToken('0xa117000000f279d81a1d3cc75430faa017fa5a2e', 'erc20', 1, null, ['test', 'test2']); } catch (e) {} const token = await client.getToken('0xa117000000f279d81a1d3cc75430faa017fa5a2e', 1); expect(token).toMatchObject({ @@ -66,7 +66,7 @@ describe('Census3 token integration tests', () => { chainAddress: expect.any(String), tags: expect.any(Array), }); - }, 5000); + }, 25000); it('should check if the given holder in a token exists', async () => { const client = new VocdoniCensus3Client({ env: EnvOptions.DEV }); const supportedTokens = await client.getSupportedTokens();