Skip to content

Commit

Permalink
feat: add ark endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
MartianGreed committed Aug 18, 2024
1 parent f45ac5f commit d366fa6
Show file tree
Hide file tree
Showing 26 changed files with 628 additions and 34 deletions.
7 changes: 5 additions & 2 deletions apps/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"format": "prettier --check \"**/*.{js,cjs,mjs,ts,tsx,md,json}\"",
"start": "bun with-env next start",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
"with-env": "dotenv -e ../../.env --",
"test": "NODE_ENV=test vitest --config vitest.config.ts"
},
"dependencies": {
"@ark-project/react": "1.0.2",
Expand Down Expand Up @@ -86,7 +87,9 @@
"eslint": "^9.6.0",
"prettier": "^3.3.2",
"tailwindcss": "3.4.4",
"typescript": "^5.5.3"
"typescript": "^5.5.3",
"vitest": "^2.0.5",
"vitest-tsconfig-paths": "^3.4.1"
},
"overrides": {
"react-is": "^19.0.0-beta-26f2496093-20240514"
Expand Down
4 changes: 4 additions & 0 deletions apps/nextjs/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const env = createEnv({
NEXT_PUBLIC_ALCHEMY_API: z.string(),
NEXT_PUBLIC_ETHPLORER_APIKEY: z.string(),
// NEXT_PUBLIC_CLIENTVAR: z.string(),
NEXT_PUBLIC_ARK_MARKETPLACE_API: z.string().url(),
NEXT_PUBLIC_ARK_ORDERBOOK_API: z.string().url(),
},
/**
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
Expand All @@ -63,6 +65,8 @@ export const env = createEnv({
NEXT_PUBLIC_ETHPLORER_APIKEY: process.env.NEXT_PUBLIC_ETHPLORER_APIKEY,
NEXT_PUBLIC_RESERVOIR_API_KEY: process.env.NEXT_PUBLIC_RESERVOIR_API_KEY,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
NEXT_PUBLIC_ARK_MARKETPLACE_API: process.env.NEXT_PUBLIC_ARK_MARKETPLACE_API,
NEXT_PUBLIC_ARK_ORDERBOOK_API: process.env.NEXT_PUBLIC_ARK_ORDERBOOK_API,
},
skipValidation:
!!process.env.CI ||
Expand Down
105 changes: 103 additions & 2 deletions apps/nextjs/src/lib/ark/ark-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@ import { getCollections } from "./getCollection";
import type { Fetcher } from "./client";
import { ArkClient } from "./client";
import { getCollectionActivity } from "./getCollectionActivity";
import { getCollectionSearch } from "./getCollectionSearch";
import { getCollectionTokens } from "./getCollectionTokens";
import { getPortfolioActivity } from "./getPortfolioActivity";
import { getPortfolioCollections } from "./getPortfolioCollections";
import { getPortfolioTokens } from "./getPortfolioTokens";
import { getSystemStatus } from "./getSystemStatus";
import { getToken } from "./getToken";
import { getTokenMarketdata } from "./getTokenMarketdata";
import { getTokenActivity } from "./getTokenActivity";
import { getTokenOffers } from "./getTokenOffers";

// import { getPrices } from "./getPrices";
// import { Mobula } from "mobula-sdk";

// vi.spyOn(Mobula, 'Mobula').mockImplementation(() => ({
// multiDataResponse: {
// data: {
// ethereum: {
// price: 0.001,
// },
// starknet: {
// price: 0.002,
// }
// },
// },
// })
// );

describe('ArkApi', () => {
let fetchMock: Fetcher<any>;
Expand All @@ -15,16 +42,90 @@ describe('ArkApi', () => {
});

it('should get collections', async () => {
const collectionAddress = erc721Tokens.beasts.contractAddresses.L2[ChainType.L2.MAIN] as string
const collectionAddress = erc721Tokens.beasts.contractAddresses.L2[ChainType.L2.MAIN]!
const _ = await getCollections({ client, collectionAddress })

expect(fetchMock.mock.calls.length).toBe(1)
});

it('should get collections activity', async () => {
const collectionAddress = erc721Tokens.beasts.contractAddresses.L2[ChainType.L2.MAIN] as string
const collectionAddress = erc721Tokens.beasts.contractAddresses.L2[ChainType.L2.MAIN]!
const _ = await getCollectionActivity({ client, collectionAddress, page: 10 })

expect(fetchMock.mock.calls.length).toBe(1)
});

it('should search collections', async () => {
const _ = await getCollectionSearch({ client, query: "Beast" })

expect(fetchMock.mock.calls.length).toBe(1)
});

it('should get collection tokens', async () => {
const collectionAddress = erc721Tokens.beasts.contractAddresses.L2[ChainType.L2.MAIN]!
const _ = await getCollectionTokens({ client, collectionAddress })

expect(fetchMock.mock.calls.length).toBe(1)
});

it('should get portfolio activity', async () => {
const _ = await getPortfolioActivity({ client, walletAddress: "0x1234567890123456789012345678901234567890" })

expect(fetchMock.mock.calls.length).toBe(1)
});

it('should get portfolio collections', async () => {
const _ = await getPortfolioCollections({ client, walletAddress: "0x1234567890123456789012345678901234567890" })

expect(fetchMock.mock.calls.length).toBe(1)
});

it('should get portfolio tokens', async () => {
const collectionAddress = erc721Tokens.beasts.contractAddresses.L2[ChainType.L2.MAIN]!
const _ = await getPortfolioTokens({ client, walletAddress: "0x1234567890123456789012345678901234567890", collectionAddress })

expect(fetchMock.mock.calls.length).toBe(1)
});

// it('should get prices', async () => {
// const prices = await getPrices()
//
// expect(prices).toBe({
// ethereum: { price: 0.001 },
// starknet: { price: 0.002 },
// })
// });

it('should get system status', async () => {
const _ = await getSystemStatus({ client })

expect(fetchMock.mock.calls.length).toBe(1)
});

it('should get token', async () => {
const _ = await getToken({ client, contractAddress: "0x1234567890123456789012345678901234567890", tokenId: 0 })

expect(fetchMock.mock.calls.length).toBe(1)
});


it('should get token activity', async () => {
const _ = await getTokenActivity({ client, contractAddress: "0x1234567890123456789012345678901234567890", tokenId: 0 })

expect(fetchMock.mock.calls.length).toBe(1)
});


it('should get token marketdata', async () => {
const _ = await getTokenMarketdata({ client, contractAddress: "0x1234567890123456789012345678901234567890", tokenId: 0 })

expect(fetchMock.mock.calls.length).toBe(1)
});

it('should get token offers', async () => {
const _ = await getTokenOffers({ client, contractAddress: "0x1234567890123456789012345678901234567890", tokenId: 0 })

expect(fetchMock.mock.calls.length).toBe(1)
});
});

2 changes: 1 addition & 1 deletion apps/nextjs/src/lib/ark/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ArkClient } from "./client";

describe('ArkClient', () => {
it('should work', async () => {
const client = new ArkClient(() => ({ json: () => Promise.resolve({}) }));
const client = new ArkClient(() => ({ json: () => Promise.resolve({}) }), 'http://localhost:9999');
expect(await client.fetch('shouldwork')).toEqual({});
});
});
11 changes: 8 additions & 3 deletions apps/nextjs/src/lib/ark/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { env } from "@/env";
export type Fetcher<T> = (url: string, options?: FetcherOpts) => Promise<T>
export interface FetcherOpts {
headers?: Record<string, string>
Expand All @@ -6,12 +7,15 @@ export interface FetcherOpts {
// ArkClient aims to ease the testing phase of api endpoints
export class ArkClient {
fetcher: Fetcher<any>
constructor(fetcher: Fetcher<any>) {
source: string

constructor(fetcher: Fetcher<any>, source: string) {
this.fetcher = fetcher
this.source = source
}

public async fetch<T>(url: string, options: FetcherOpts = {}): Promise<T> {
const res = await this.fetcher(url, {
const res = await this.fetcher(this.source + url, {
...options,
headers: {
...options.headers,
Expand All @@ -22,4 +26,5 @@ export class ArkClient {
}
}

export const ArkClientFetch = new ArkClient(fetch);
export const ArkMarketplaceClientFetch = new ArkClient(fetch, env.NEXT_PUBLIC_ARK_MARKETPLACE_API);
export const ArkOrderbookFetch = new ArkClient(fetch, env.NEXT_PUBLIC_ARK_ORDERBOOK_API);
9 changes: 7 additions & 2 deletions apps/nextjs/src/lib/ark/getCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ export interface CollectionApiResponse {
data: Collection;
}

export async function getCollections({ client, collectionAddress }: { client: ArkClient, collectionAddress: string }) {
interface GetCollectionParams {
client: ArkClient
collectionAddress: string
}

export async function getCollections({ client, collectionAddress }: GetCollectionParams): Promise<CollectionApiResponse> {
try {
return await client.fetch(`/collections/${collectionAddress}/0x534e5f4d41494e`)
} catch (error) {
throw new Error("failed to fetch collection data : " + error)
throw new Error("failed to fetch collection data : " + (error as string))
}
}
12 changes: 4 additions & 8 deletions apps/nextjs/src/lib/ark/getCollectionActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,17 @@ export interface CollectionActivityApiResponse {
interface GetCollectionActivityParams {
client: ArkClient
collectionAddress: string
page: number
page?: number
itemsPerPage?: number
}

export async function getCollectionActivity({ client, page, collectionAddress, itemsPerPage = 10 }: GetCollectionActivityParams) {
export async function getCollectionActivity({ client, collectionAddress, page = 1, itemsPerPage = 10 }: GetCollectionActivityParams): Promise<CollectionActivityApiResponse> {
try {
const queryParams = [`items_per_page=${itemsPerPage}`];

if (page !== undefined) {
queryParams.push(`page=${page}`);
}
const queryParams = [`items_per_page=${itemsPerPage}`, `page=${page}`];

return await client.fetch(`/collections/${collectionAddress}/activity?${queryParams.join("&")}`);

} catch (error) {
throw new Error("failed to fetch collection activity : " + error)
throw new Error("failed to fetch collection activity : " + (error as string))
}
}
26 changes: 26 additions & 0 deletions apps/nextjs/src/lib/ark/getCollectionSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { AccountSearchResult, CollectionSearchResult } from "@/types/ark";
import type { ArkClient } from "./client";

interface CollectionSearchApiResponse {
data: {
collections: CollectionSearchResult[];
accounts: AccountSearchResult[];
};
}
interface GetCollectionSearchParams {
client: ArkClient;
query: string;
}

export async function getCollectionSearch({ client, query }: GetCollectionSearchParams): Promise<CollectionSearchApiResponse> {
const queryParams = [`q=${query}`];

try {
return await client.fetch(`/collections/search?${queryParams.join("&")}`);
} catch (error) {
console.error(error)
return {
data: { collections: [], accounts: [] },
} as CollectionSearchApiResponse;
}
}
53 changes: 53 additions & 0 deletions apps/nextjs/src/lib/ark/getCollectionTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { CollectionToken } from "@/types/ark"
import type { ArkClient } from "./client";

import { createSearchParamsCache, parseAsStringLiteral } from "nuqs/server";

export const collectionSortDirectionKey = "direction";
export const collectionSortDirectionsValues = ["asc", "desc"] as const;
export const collectionSortDirectionsParser = parseAsStringLiteral(
collectionSortDirectionsValues,
).withDefault("asc");
export type CollectionSortDirection =
(typeof collectionSortDirectionsValues)[number];

export const collectionSortByKey = "sort";
export const collectionSortByValues = ["price"] as const;
export const collectionSortByParser = parseAsStringLiteral(
collectionSortByValues,
).withDefault("price");
export type CollectionSortBy = (typeof collectionSortByValues)[number];

export const collectionPageSearchParamsCache = createSearchParamsCache({
[collectionSortDirectionKey]: collectionSortDirectionsParser,
[collectionSortByKey]: collectionSortByParser,
});

interface CollectionTokensApiResponse {
data: CollectionToken[];
next_page: number | null;
token_count: number;
}
interface GetCollectionTokensParams {
client: ArkClient;
collectionAddress: string;
page?: number;
itemsPerPage?: number;
sortBy?: string;
sortDirection?: string;
}

export async function getCollectionTokens({ client, collectionAddress, page = 1, itemsPerPage = 50, sortBy = "price", sortDirection = "asc" }: GetCollectionTokensParams): Promise<CollectionTokensApiResponse> {
const queryParams = [`items_per_page=${itemsPerPage}`, `sort=${sortBy}`, `direction=${sortDirection}`, `page=${page}`];

try {
return await client.fetch(`/collections/${collectionAddress}/0x534e5f4d41494e/tokens?${queryParams.join("&")}`);
} catch (error) {
console.error(error)
return {
data: [],
next_page: null,
token_count: 0,
} as CollectionTokensApiResponse;
}
}
26 changes: 26 additions & 0 deletions apps/nextjs/src/lib/ark/getPortfolioActivity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { PortfolioActivity } from "@/types/ark";
import type { ArkClient } from "./client"


export interface PortfolioActivityApiResponse {
count: number;
data: PortfolioActivity[];
next_page: number;
}

interface GetPortfolioActivityParams {
client: ArkClient
walletAddress: string
page?: number
itemsPerPage?: number
}

export async function getPortfolioActivity({ client, walletAddress, page = 1, itemsPerPage = 10 }: GetPortfolioActivityParams): Promise<PortfolioActivityApiResponse> {
const queryParams = [`items_per_page=${itemsPerPage}`, `page=${page}`];

try {
return await client.fetch(`/portfolio/${walletAddress}/activity?${queryParams.join("&")}`);
} catch (error) {
throw new Error("failed to fetch portfolio activity : " + (error as string))
}
}
33 changes: 33 additions & 0 deletions apps/nextjs/src/lib/ark/getPortfolioCollections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { PortfolioCollection } from "@/types/ark";
import type { ArkClient } from "./client"


export interface PortfolioCollectionApiResponse {
collection_count: number;
token_count: number;
data: PortfolioCollection[];
next_page: number | null;

}

interface GetPortfolioActivityParams {
client: ArkClient
walletAddress: string
page?: number
itemsPerPage?: number
}

export async function getPortfolioCollections({ client, walletAddress, page = 1, itemsPerPage = 10 }: GetPortfolioActivityParams): Promise<PortfolioCollectionApiResponse> {
const queryParams = [`items_per_page=${itemsPerPage}`, `page=${page}`];

try {
return await client.fetch(`/portfolio/${walletAddress}/collections?${queryParams.join("&")}`);
} catch (_) {
return {
data: [],
next_page: null,
collection_count: 0,
token_count: 0,
};
}
}
Loading

0 comments on commit d366fa6

Please sign in to comment.