diff --git a/README.md b/README.md index 9d97980..810f6fb 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,128 @@ fetchRandomArtworkWithUser().then((result) => { }) ``` +### Fetching random communities without full metadata + +**Signature**: +`fetchRandomCommunities = async (): Promise` + +**Usage**: +```typescript +import { fetchRandomCommunities } from 'verto-cache-interface'; + +fetchRandomCommunities().then((result) => { + const communities = result.entities; + communities.forEach((com) => { + console.log(com.contractId); + console.log(com.type); + console.log(com.lister); + console.log(com.id); + }) +}) +``` + +### Fetching random communities with full metadata + +**Signature**: +`fetchRandomCommunitiesWithMetadata = async (): Promise>` + +**Usage**: +```typescript +import { fetchRandomCommunitiesWithMetadata } from 'verto-cache-interface'; + +fetchRandomCommunitiesWithMetadata().then((result) => { + result.forEach((com) => { + console.log(com.id); + console.log(com.name); + console.log(com.ticker); + console.log(com.logo); + console.log(com.description); + }) +}) +``` + +### Fetching top communities with full metadata + +**Signature**: +`fetchTopCommunities = async (): Promise>` + +**Usage**: +```typescript +import { fetchTopCommunities } from 'verto-cache-interface'; + +fetchTopCommunities().then((result) => { + result.forEach((com) => { + console.log(com.id); + console.log(com.name); + console.log(com.ticker); + console.log(com.logo); + console.log(com.description); + }) +}) +``` + +### Fetching full metadata for given communities + +**Signature**: +`fetchCommunitiesMetadata = async (communityContractIds: Array | string): Promise>` + +**Usage**: +```typescript +import { fetchCommunitiesMetadata } from 'verto-cache-interface'; + +fetchCommunitiesMetadata(["MY-community-id1", "MY-community-id2"]).then((result) => { + result.forEach((com) => { + console.log(com.id); + console.log(com.name); + console.log(com.ticker); + console.log(com.logo); + console.log(com.description); + }) +}) +``` + +### Fetching artwork metadata + +**Signature**: +`fetchArtworkMetadata = async (): Promise` + +**Usage**: +```typescript +import { fetchArtworkMetadata } from 'verto-cache-interface'; + +fetchArtworkMetadata().then((result) => { + console.log(result.id); + console.log(result.name); + console.log(result.lister); // Object (username, name, addresses, bio, links, image) + console.log(result.lister.addresses); + console.log(result.lister.bio); + console.log(result.lister.links); +}) +``` + +### Fetching token by id and optional filtering + +**Signature**: +`fetchTokenById = async (tokenId: string, filter?: (val: CommunityContractToken) => boolean): Promise` + +**Usage**: +```typescript +import { fetchTokenById } from 'verto-cache-interface'; + +fetchTokenById("ABC").then((result) => { + console.log(result.id); + console.log(result.type); + console.log(result.lister); +}); + + +fetchTokenById("ABC", (filterData) => filterData.type === "community").then((result) => { + console.log(result.id); + console.log(result.type); + console.log(result.lister); +}); +``` + ## Hooks Hooks are a way to invoke functions and then invoke certain behaviors inside the cache system. diff --git a/src/calls/fetch-artwork-metadata.ts b/src/calls/fetch-artwork-metadata.ts new file mode 100644 index 0000000..96c2498 --- /dev/null +++ b/src/calls/fetch-artwork-metadata.ts @@ -0,0 +1,26 @@ +import {fetchTokenMetadata} from "./fetch-token-metadata"; +import {fetchUserByUsername} from "./fetch-user-by-username"; +import { fetchContract } from "./fetch-contract"; +import {CollectionState} from "./types/collection-state"; +import {ArtworkMetadata} from "./types/artwork-metadata"; + +/** + * Fetches the metadata for a given artwork + * @param tokenId + */ +export const fetchArtworkMetadata = async (tokenId: string): Promise => { + const fetchMetadata = await fetchTokenMetadata(tokenId, true); + if(!fetchMetadata || fetchMetadata.type.toLowerCase() !== "art") { return undefined; } + + const fetchLister = await fetchUserByUsername(fetchMetadata.lister); + if(!fetchLister) { return undefined; } + + const tokenContract = await fetchContract(fetchMetadata.id!, false, true); + if(!tokenContract) { return undefined; } + + return { + id: tokenId, + name: tokenContract.state.name, + lister: fetchLister + }; +} diff --git a/src/calls/fetch-communities-metadata.ts b/src/calls/fetch-communities-metadata.ts new file mode 100644 index 0000000..4016e20 --- /dev/null +++ b/src/calls/fetch-communities-metadata.ts @@ -0,0 +1,11 @@ +import {fetchCommunityMetadata} from "./fetch-random-communities-with-metadata"; + +/** + * Fetches the metadata for one (string) or more (array) communities. + * This returns an array with (id, name, ticker, logo, description) + * @param communityContractIds Single string or array of string containing the ids of the community as listed in the community contract + */ +export const fetchCommunitiesMetadata = async (communityContractIds: Array | string) => { + const contractIds = Array.isArray(communityContractIds) ? [...communityContractIds] : [communityContractIds]; + return (await fetchCommunityMetadata(contractIds)); +} diff --git a/src/calls/fetch-random-artwork.ts b/src/calls/fetch-random-artwork.ts index 8df05fb..ad20921 100644 --- a/src/calls/fetch-random-artwork.ts +++ b/src/calls/fetch-random-artwork.ts @@ -1,13 +1,13 @@ import {cacheApiBaseRequest} from "./cache-api-base-request"; -import {RandomArtworkResult, TokenMetadata} from "./types/token-metadata"; +import {TokenMetadataLookUp, TokenMetadata} from "./types/token-metadata"; /** * Fetches random art work (tokens with type = 'collection' or 'art'). * @param limit Limit of results to be returned */ export const fetchRandomArtwork = async (limit: number = 4) => { - return (await cacheApiBaseRequest(`token/artwork/random?limit=${limit}`))?.data || { + return (await cacheApiBaseRequest(`token/artwork/random?limit=${limit}`))?.data || { entities: [], resultsStatus: 'NOT_FOUND' - } as RandomArtworkResult; + } as TokenMetadataLookUp; } diff --git a/src/calls/fetch-random-communities-with-metadata.ts b/src/calls/fetch-random-communities-with-metadata.ts new file mode 100644 index 0000000..54cca0e --- /dev/null +++ b/src/calls/fetch-random-communities-with-metadata.ts @@ -0,0 +1,40 @@ +import {RandomCommunities} from "./types/community-contract-state"; +import {fetchRandomCommunities} from "./fetch-random-communities"; +import {fetchContract} from "./fetch-contract"; + +/** + * Fetches random communities with included metadata such as name, ticker, logo and description. + * @param limit + */ +export const fetchRandomCommunitiesWithMetadata = async (limit?: number): Promise> => { + const randomCommunities = await fetchRandomCommunities(limit || 4); + const lookupEntities = randomCommunities.entities; + + if(randomCommunities.resultsStatus === "NOT_FOUND" || lookupEntities?.length <= 0) { + return []; + } else { + const contractIds = lookupEntities.map((item) => item.contractId); + return fetchCommunityMetadata(contractIds); + } +} + +export const fetchCommunityMetadata = async (contractIds: Array) => { + const communities: Array = []; + for (const communitiesKey of contractIds) { + const contract = await fetchContract(communitiesKey, false, true); + if(contract) { + const contractState = contract.state; + const settings: Array = (contractState.settings || []).flat(); + const logoIndex = settings.findIndex(item => item === "communityLogo"); + const descriptionIndex = settings.findIndex(item => item === "communityDescription"); + communities.push({ + id: communitiesKey, + name: contractState.name, + ticker: contractState.ticker, + logo: settings[logoIndex + 1], + description: settings[descriptionIndex + 1], + }) + } + } + return communities; +} diff --git a/src/calls/fetch-random-communities.ts b/src/calls/fetch-random-communities.ts new file mode 100644 index 0000000..39d9fde --- /dev/null +++ b/src/calls/fetch-random-communities.ts @@ -0,0 +1,13 @@ +import {cacheApiBaseRequest} from "./cache-api-base-request"; +import {TokenMetadataLookUp, TokenMetadata} from "./types/token-metadata"; + +/** + * Fetches random communities (tokens with type = 'community'). + * @param limit Limit of results to be returned + */ +export const fetchRandomCommunities = async (limit: number = 4) => { + return (await cacheApiBaseRequest(`token/communities/random?limit=${limit}`))?.data || { + entities: [], + resultsStatus: 'NOT_FOUND' + } as TokenMetadataLookUp; +} diff --git a/src/calls/fetch-token-by-id.ts b/src/calls/fetch-token-by-id.ts new file mode 100644 index 0000000..e53dacf --- /dev/null +++ b/src/calls/fetch-token-by-id.ts @@ -0,0 +1,12 @@ +import {fetchTokens} from "./fetch-tokens"; +import {CommunityContractToken} from "./types/community-contract-state"; + +/** + * Fetch a token given an id an optionally a predicate + * @param tokenId Id for token to look up + * @param filter Predicate for token + */ +export const fetchTokenById = async (tokenId: string, filter?: (val: CommunityContractToken) => boolean): Promise => { + const allTokens = await fetchTokens(); + return allTokens.find((token) => token.id === tokenId && (filter ? filter(token) : true)); +} diff --git a/src/calls/fetch-token-metadata.ts b/src/calls/fetch-token-metadata.ts index 6c39471..fd4dc5d 100644 --- a/src/calls/fetch-token-metadata.ts +++ b/src/calls/fetch-token-metadata.ts @@ -1,6 +1,6 @@ import {cacheApiBaseRequest} from "./cache-api-base-request"; import {TokenMetadata} from "./types/token-metadata"; -import {fetchTokens} from "./fetch-tokens"; +import {fetchTokenById} from "./fetch-token-by-id"; /** * Returns the metadata of a token (contractId, type, lister) @@ -9,8 +9,7 @@ import {fetchTokens} from "./fetch-tokens"; */ export const fetchTokenMetadata = async (tokenId: string, fromContract?: boolean) => { if(fromContract) { - const allTokens = await fetchTokens(); - return allTokens.find((token) => token.id === tokenId); + return fetchTokenById(tokenId); } else { const getTokenMetadata = await cacheApiBaseRequest(`token/metadata/${tokenId}`); diff --git a/src/calls/fetch-top-communities.ts b/src/calls/fetch-top-communities.ts new file mode 100644 index 0000000..80a32b8 --- /dev/null +++ b/src/calls/fetch-top-communities.ts @@ -0,0 +1,16 @@ +import {fetchTokens} from "./fetch-tokens"; +import {cacheApiBaseRequest} from "./cache-api-base-request"; +import {CommunityBalancesRaw} from "./types/community-contract-state"; +import {LookUp} from "./types/lookup"; +import {fetchCommunityMetadata} from "./fetch-random-communities-with-metadata"; + +/** + * Fetches the communities with the top balance amount per contract. + * Returns metadata containing: (id, name, ticker, logo, description) + * @param limit: Max to fetch + */ +export const fetchTopCommunities = async (limit?: number) => { + const cacheRequest = (await (cacheApiBaseRequest>(`token/communities/top?limit=${limit || 4}`)))?.data?.entities || []; + const contractIds = cacheRequest.map((item) => item.contractId); + return (await fetchCommunityMetadata(contractIds)); +} diff --git a/src/calls/types/artwork-metadata.ts b/src/calls/types/artwork-metadata.ts new file mode 100644 index 0000000..c71edb2 --- /dev/null +++ b/src/calls/types/artwork-metadata.ts @@ -0,0 +1,7 @@ +import {CommunityContractPeople} from "./community-contract-state"; + +export interface ArtworkMetadata { + id: string; + name: string; + lister: CommunityContractPeople; +} diff --git a/src/calls/types/community-contract-state.ts b/src/calls/types/community-contract-state.ts index f3625d5..21242c6 100644 --- a/src/calls/types/community-contract-state.ts +++ b/src/calls/types/community-contract-state.ts @@ -19,3 +19,16 @@ export interface CommunityContractState { people: Array tokens: Array; } + +export interface RandomCommunities { + id: string; + name: string; + ticker: string; + logo: string; + description: string; +} + +export interface CommunityBalancesRaw { + contractId: string; + balanceLength: number; +} diff --git a/src/calls/types/lookup.ts b/src/calls/types/lookup.ts new file mode 100644 index 0000000..b37d806 --- /dev/null +++ b/src/calls/types/lookup.ts @@ -0,0 +1,6 @@ +import {TokenMetadata} from "./token-metadata"; + +export interface LookUp { + entities: Array; + resultsStatus: string; +} diff --git a/src/calls/types/token-metadata.ts b/src/calls/types/token-metadata.ts index 2a20280..0dd3540 100644 --- a/src/calls/types/token-metadata.ts +++ b/src/calls/types/token-metadata.ts @@ -1,3 +1,5 @@ +import {LookUp} from "./lookup"; + export interface TokenMetadata { contractId: string; type: string; @@ -5,7 +7,5 @@ export interface TokenMetadata { id?: string; } -export interface RandomArtworkResult { - entities: Array; - resultsStatus: string; +export interface TokenMetadataLookUp extends LookUp { } diff --git a/src/index.ts b/src/index.ts index 909c62b..a7e6c01 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,4 +23,10 @@ export * from './calls/fetch-users'; export * from './calls/fetch-balance-by-username'; export * from './calls/fetch-user-by-username'; export * from './calls/fetch-random-artwork-with-user'; +export * from './calls/fetch-random-communities'; +export * from './calls/fetch-random-communities-with-metadata'; +export * from './calls/fetch-top-communities'; +export * from './calls/fetch-communities-metadata'; +export * from './calls/fetch-artwork-metadata'; +export * from './calls/fetch-token-by-id'; export * from './hooks/cache-contract-hook'; diff --git a/src/tests/api.test.ts b/src/tests/api.test.ts index bf80638..ce5066d 100644 --- a/src/tests/api.test.ts +++ b/src/tests/api.test.ts @@ -13,6 +13,9 @@ import {fetchBalancesForAddress} from "../calls/fetch-balances-for-address"; import {fetchTokenStateMetadata} from "../calls/fetch-token-state-metadata"; import {fetchBalancesByUsername} from "../calls/fetch-balance-by-username"; import {fetchRandomArtworkWithUser} from "../calls/fetch-random-artwork-with-user"; +import {fetchArtworkMetadata} from "../calls/fetch-artwork-metadata"; +import {fetchRandomCommunitiesWithMetadata} from "../calls/fetch-random-communities-with-metadata"; +import {fetchTopCommunities} from "../calls/fetch-top-communities"; describe("API test", () => { test("Fetch Contract", async () => { @@ -145,6 +148,27 @@ describe("API test", () => { } expect(tokenArtwork!.owner.username).not.toBeUndefined(); }); + + test("Fetch artwork metadata", async () => { + const artworkMetadata = await fetchArtworkMetadata("oanaZYYB7DmWFPb2fFOxSJA1Xy7ffu1msId2Ii7olqM"); + expect(artworkMetadata).toStrictEqual({"id":"oanaZYYB7DmWFPb2fFOxSJA1Xy7ffu1msId2Ii7olqM","name":"ArCoNFT-01 Edition 43","lister":{"username":"t8","name":"Tate Berenbaum","addresses":["pvPWBZ8A5HLpGSEfhEmK1A3PfMgB_an8vVS6L14Hsls"],"bio":"Founder of Verto","links":{"twitter":"TateBerenbaum","github":"t8"},"image":"UGu1pI3ObS3wzdQ_GZwOr0DoWShTj4EPFgyHfDaHFgI"}}); + }) + + test("Fetch community metadata", async () => { + const communities = await fetchRandomCommunitiesWithMetadata(); + expect(communities.length).toBeGreaterThanOrEqual(4); + expect(communities[0].id).not.toBeUndefined(); + expect(communities[0].name).not.toBeUndefined(); + expect(communities[0].ticker).not.toBeUndefined(); + }) + + test("Fetch top communities", async () => { + const topComs = await fetchTopCommunities(); + expect(topComs.length).toBeGreaterThanOrEqual(4); + expect(topComs[0].id).not.toBeUndefined(); + expect(topComs[0].name).not.toBeUndefined(); + expect(topComs[0].ticker).not.toBeUndefined(); + }) });