From bdbd9a8e426b28fc8143867659ba779db167c056 Mon Sep 17 00:00:00 2001 From: Rodrigo Manuel Navarro Lajous <40175251+rlajous@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:51:13 -0300 Subject: [PATCH] PLT-1397 Added Mint POAP on POAP package (#60) * PLT-1397 Added provider * PLT-1397 Added intreface in POAP Client to claim sync * PLT-1397 Updated yarn * Added different ways to claim poaps * Eslint fix * Changed Camel Case to pascalCase * Changed pascalCase to CamelCase * Rename Status to MintingStatus * Eslint fix * Eslint fix * Eslint fix * Eslint fix * Eslint fix * PLT-1397 changed how to create poapTokenApi * PLT-1397 Some documentation and variable refactor * PLT-1397 Refactor code * Changed camel case * Updated yarn * Updated test * Changed name * Changed name * Changed variable name * Changed variable name * Changed variable name * Changed variable name * Changed variable name * Changed variable name * Changed variable name * Changed variable name * Changed names * Changed version --- examples/drops/backend/.env.template | 4 + examples/drops/backend/package.json | 1 + examples/drops/backend/src/index.ts | 9 +- .../drops/backend/src/methods/create_drop.ts | 18 +- .../src/methods/fetch_multiple_drops.ts | 4 +- .../backend/src/methods/fetch_single_drop.ts | 4 +- .../src/methods/get_required_env_var.ts | 7 + examples/poaps/backend/.env.template | 5 + examples/poaps/backend/package.json | 1 + examples/poaps/backend/src/index.ts | 37 ++- .../src/methods/email_reservation_poap.ts | 17 ++ .../src/methods/fetch_multiple_poaps.ts | 4 +- .../fetch_multiple_poaps_by_collector.ts | 6 +- .../fetch_multiple_poaps_by_drop_id.ts | 8 +- .../backend/src/methods/fetch_single_poap.ts | 4 +- .../src/methods/get_required_env_var.ts | 7 + .../backend/src/methods/mint_async_poap.ts | 44 ++++ .../backend/src/methods/mint_sync_poap.ts | 14 ++ .../poaps/backend/src/utils/handleError.ts | 35 +++ packages/drops/package.json | 6 +- packages/drops/src/DropsClient.ts | 105 +++++--- packages/drops/src/domain/Drop.ts | 129 ++++++---- packages/drops/src/index.ts | 2 +- packages/drops/src/types/input.ts | 24 +- packages/moments/package.json | 6 +- packages/moments/src/types/index.ts | 1 - packages/performance/package.json | 2 +- packages/poaps/package.json | 6 +- packages/poaps/src/PoapsClient.ts | 236 +++++++++++++++--- packages/poaps/src/domain/POAPReservation.ts | 36 +++ packages/poaps/src/domain/Poap.ts | 42 ++-- .../src/errors/CodeAlreadyMintedError.ts | 5 + packages/poaps/src/errors/CodeExpiredError.ts | 5 + .../poaps/src/errors/FinishedWithError.ts | 7 + packages/poaps/src/index.ts | 5 + packages/poaps/src/types/index.ts | 1 + packages/poaps/src/types/input.ts | 63 ++++- packages/poaps/src/types/response.ts | 6 + packages/poaps/src/utils/MintChecker.ts | 87 +++++++ packages/poaps/src/utils/PoapIndexed.ts | 46 ++++ packages/poaps/src/utils/RetryableTask.ts | 47 ++++ packages/providers/package.json | 5 +- .../AuthenticationProviderHttp.ts | 4 +- .../src/core/PoapCompass/PoapCompass.ts | 29 ++- .../src/core/PoapDropApi/PoapDropApi.ts | 37 ++- .../src/core/PoapMomentsApi/PoapMomentsApi.ts | 4 +- .../src/core/PoapTokenApi/PoapTokenApi.ts | 151 +++++++++++ packages/providers/src/core/index.ts | 1 + .../MissingAuthenticationProviderError.ts | 5 + .../TokensApiProvider/TokensApiProvider.ts | 17 ++ .../ports/TokensApiProvider/Types/index.ts | 2 + .../ports/TokensApiProvider/Types/input.ts | 6 + .../ports/TokensApiProvider/Types/response.ts | 66 +++++ .../src/ports/TokensApiProvider/index.ts | 7 + packages/providers/src/ports/index.ts | 8 + packages/providers/test/PoapCompass.spec.ts | 2 +- packages/providers/test/PoapDropApi.spec.ts | 2 +- packages/utils/package.json | 5 +- packages/utils/src/index.ts | 3 +- packages/utils/src/queries/utils.ts | 2 +- packages/utils/src/types/mintingStatus.ts | 6 + yarn.lock | 19 +- 62 files changed, 1239 insertions(+), 238 deletions(-) create mode 100644 examples/drops/backend/.env.template create mode 100644 examples/drops/backend/src/methods/get_required_env_var.ts create mode 100644 examples/poaps/backend/.env.template create mode 100644 examples/poaps/backend/src/methods/email_reservation_poap.ts create mode 100644 examples/poaps/backend/src/methods/get_required_env_var.ts create mode 100644 examples/poaps/backend/src/methods/mint_async_poap.ts create mode 100644 examples/poaps/backend/src/methods/mint_sync_poap.ts create mode 100644 examples/poaps/backend/src/utils/handleError.ts delete mode 100644 packages/moments/src/types/index.ts create mode 100644 packages/poaps/src/domain/POAPReservation.ts create mode 100644 packages/poaps/src/errors/CodeAlreadyMintedError.ts create mode 100644 packages/poaps/src/errors/CodeExpiredError.ts create mode 100644 packages/poaps/src/errors/FinishedWithError.ts create mode 100644 packages/poaps/src/types/response.ts create mode 100644 packages/poaps/src/utils/MintChecker.ts create mode 100644 packages/poaps/src/utils/PoapIndexed.ts create mode 100644 packages/poaps/src/utils/RetryableTask.ts create mode 100644 packages/providers/src/core/PoapTokenApi/PoapTokenApi.ts create mode 100644 packages/providers/src/ports/AuthenticationProvider/errors/MissingAuthenticationProviderError.ts create mode 100644 packages/providers/src/ports/TokensApiProvider/TokensApiProvider.ts create mode 100644 packages/providers/src/ports/TokensApiProvider/Types/index.ts create mode 100644 packages/providers/src/ports/TokensApiProvider/Types/input.ts create mode 100644 packages/providers/src/ports/TokensApiProvider/Types/response.ts create mode 100644 packages/providers/src/ports/TokensApiProvider/index.ts create mode 100644 packages/utils/src/types/mintingStatus.ts diff --git a/examples/drops/backend/.env.template b/examples/drops/backend/.env.template new file mode 100644 index 00000000..97dffa51 --- /dev/null +++ b/examples/drops/backend/.env.template @@ -0,0 +1,4 @@ +CLIENT_ID= +CLIENT_SECRET= +API_KEY= +OAUTH_SERVER_DOMAIN= diff --git a/examples/drops/backend/package.json b/examples/drops/backend/package.json index 0afb2db9..7ef444f5 100644 --- a/examples/drops/backend/package.json +++ b/examples/drops/backend/package.json @@ -14,6 +14,7 @@ "@poap-xyz/utils": "*", "@types/node": "^18.16.0", "axios": "^1.3.5", + "dotenv": "^16.0.3", "form-data": "^4.0.0", "stream": "^0.0.2" }, diff --git a/examples/drops/backend/src/index.ts b/examples/drops/backend/src/index.ts index 46238cf3..fb4c5e84 100644 --- a/examples/drops/backend/src/index.ts +++ b/examples/drops/backend/src/index.ts @@ -4,12 +4,17 @@ import { PoapCompass, PoapDropApi } from '@poap-xyz/providers'; import { fetch_multiple_drops } from './methods/fetch_multiple_drops'; import { fetch_single_drop } from './methods/fetch_single_drop'; import { create_drop } from './methods/create_drop'; +import { getRequiredEnvVar } from './methods/get_required_env_var'; + +import dotenv from 'dotenv'; + +dotenv.config(); async function main(): Promise { // Use your library here const client = new DropsClient( - new PoapCompass('you_api_key'), - new PoapDropApi('your_api_key'), + new PoapCompass({ apiKey: getRequiredEnvVar('API_KEY') }), + new PoapDropApi({ apiKey: getRequiredEnvVar('API_KEY') }), ); // Multiple Drops await measurePerformance( diff --git a/examples/drops/backend/src/methods/create_drop.ts b/examples/drops/backend/src/methods/create_drop.ts index 0a690123..d76d330d 100644 --- a/examples/drops/backend/src/methods/create_drop.ts +++ b/examples/drops/backend/src/methods/create_drop.ts @@ -30,19 +30,19 @@ export const create_drop = async (client: DropsClient): Promise => { description: 'Description', city: 'Buenos Aires', country: 'Argentina', - start_date: toPOAPdate(today), - end_date: toPOAPdate(oneMonthFromToday), - expiry_date: toPOAPdate(twoMonthsFromToday), - event_url: 'https://poap.xyz/', - virtual_event: true, - secret_code: '123456', + startDate: toPOAPdate(today), + endDate: toPOAPdate(oneMonthFromToday), + expiryDate: toPOAPdate(twoMonthsFromToday), + eventUrl: 'https://poap.xyz/', + virtualEvent: true, + secretCode: '123456', image: await fs.promises.readFile('src/assets/poap.png'), filename: 'file.png', contentType: 'image/png', - event_template_id: 1, + eventTemplateId: 1, email: 'your_email@poap.io', - requested_codes: 10, - private_event: true, + requestedCodes: 10, + privateEvent: true, }; try { diff --git a/examples/drops/backend/src/methods/fetch_multiple_drops.ts b/examples/drops/backend/src/methods/fetch_multiple_drops.ts index c6456099..a393b316 100644 --- a/examples/drops/backend/src/methods/fetch_multiple_drops.ts +++ b/examples/drops/backend/src/methods/fetch_multiple_drops.ts @@ -6,8 +6,8 @@ export const fetch_multiple_drops = async ( ): Promise => { try { const data: PaginatedResult = await client.fetch({ - sort_field: DropsSortFields.Id, - sort_dir: Order.ASC, + sortField: DropsSortFields.Id, + sortDir: Order.ASC, limit: 10, offset: 1, }); diff --git a/examples/drops/backend/src/methods/fetch_single_drop.ts b/examples/drops/backend/src/methods/fetch_single_drop.ts index f4889232..a004af33 100644 --- a/examples/drops/backend/src/methods/fetch_single_drop.ts +++ b/examples/drops/backend/src/methods/fetch_single_drop.ts @@ -4,8 +4,8 @@ import { Order, PaginatedResult } from '@poap-xyz/utils'; export const fetch_single_drop = async (client: DropsClient): Promise => { try { const data: PaginatedResult = await client.fetch({ - sort_field: DropsSortFields.Id, - sort_dir: Order.ASC, + sortField: DropsSortFields.Id, + sortDir: Order.ASC, limit: 10, offset: 0, ids: [1], diff --git a/examples/drops/backend/src/methods/get_required_env_var.ts b/examples/drops/backend/src/methods/get_required_env_var.ts new file mode 100644 index 00000000..63322d09 --- /dev/null +++ b/examples/drops/backend/src/methods/get_required_env_var.ts @@ -0,0 +1,7 @@ +export const getRequiredEnvVar = (envVarName: string): string => { + const envVar = process.env[envVarName]; + if (envVar === undefined) { + throw new Error(`Environment variable ${envVarName} is required`); + } + return envVar; +}; diff --git a/examples/poaps/backend/.env.template b/examples/poaps/backend/.env.template new file mode 100644 index 00000000..7c00d81b --- /dev/null +++ b/examples/poaps/backend/.env.template @@ -0,0 +1,5 @@ +CLIENT_ID= +CLIENT_SECRET= +API_KEY= +OAUTH_SERVER_DOMAIN= +POAP_TOKEN_BASE_URL=https://api.poap.tech diff --git a/examples/poaps/backend/package.json b/examples/poaps/backend/package.json index c8457566..945824ac 100644 --- a/examples/poaps/backend/package.json +++ b/examples/poaps/backend/package.json @@ -14,6 +14,7 @@ "@poap-xyz/utils": "*", "@types/node": "^18.16.0", "axios": "^1.3.5", + "dotenv": "^16.0.3", "form-data": "^4.0.0", "perf_hooks": "^0.0.1", "stream": "^0.0.2" diff --git a/examples/poaps/backend/src/index.ts b/examples/poaps/backend/src/index.ts index a1aa71ba..54f004a5 100644 --- a/examples/poaps/backend/src/index.ts +++ b/examples/poaps/backend/src/index.ts @@ -1,14 +1,38 @@ import { measurePerformance } from '@poap-xyz/performance'; import { PoapsClient } from '@poap-xyz/poaps'; -import { PoapCompass } from '@poap-xyz/providers'; +import { + AuthenticationProviderHttp, + PoapCompass, + PoapTokenApi, +} from '@poap-xyz/providers'; import { fetch_multiple_poaps } from './methods/fetch_multiple_poaps'; import { fetch_single_poap } from './methods/fetch_single_poap'; import { fetch_multiple_poaps_by_collector } from './methods/fetch_multiple_poaps_by_collector'; import { fetch_multiple_poaps_by_drop_id } from './methods/fetch_multiple_poaps_by_drop_id'; +import { mint_sync_poap } from './methods/mint_sync_poap'; +import { getRequiredEnvVar } from './methods/get_required_env_var'; +import { mint_async_poap } from './methods/mint_async_poap'; +import { email_reservation_poap } from './methods/email_reservation_poap'; +import dotenv from 'dotenv'; + +dotenv.config(); async function main(): Promise { // Use your library here - const client = new PoapsClient(new PoapCompass('you_api_key')); + const client = new PoapsClient( + new PoapCompass({ + apiKey: getRequiredEnvVar('API_KEY'), + }), + new PoapTokenApi({ + apiKey: getRequiredEnvVar('API_KEY'), + baseUrl: getRequiredEnvVar('POAP_TOKEN_BASE_URL'), + authenticationProvider: new AuthenticationProviderHttp( + getRequiredEnvVar('CLIENT_ID'), + getRequiredEnvVar('CLIENT_SECRET'), + getRequiredEnvVar('OAUTH_SERVER_DOMAIN'), + ), + }), + ); // Multiple Poaps await measurePerformance( () => fetch_multiple_poaps(client), @@ -29,6 +53,15 @@ async function main(): Promise { () => fetch_multiple_poaps_by_drop_id(client), 'fetch_multiple_poaps_by_drop_id', ); + // mint Sync Poap + await measurePerformance(() => mint_sync_poap(client), 'mint_sync_poap'); + // mint Async Poap + await measurePerformance(() => mint_async_poap(client), 'mint_async_poap'); + // Email Reservation Poap + await measurePerformance( + () => email_reservation_poap(client), + 'email_reservation_poap', + ); } main().catch((error) => { diff --git a/examples/poaps/backend/src/methods/email_reservation_poap.ts b/examples/poaps/backend/src/methods/email_reservation_poap.ts new file mode 100644 index 00000000..cc1c6497 --- /dev/null +++ b/examples/poaps/backend/src/methods/email_reservation_poap.ts @@ -0,0 +1,17 @@ +import { PoapsClient, POAPReservation } from '@poap-xyz/poaps'; +import { handleError } from '../utils/handleError'; + +export const email_reservation_poap = async ( + client: PoapsClient, +): Promise => { + try { + const data: POAPReservation = await client.emailReservation({ + mintCode: 'your_poap_code', + email: 'your@email.io', + sendEmail: true, + }); + console.log(data); + } catch (error) { + handleError(error); + } +}; diff --git a/examples/poaps/backend/src/methods/fetch_multiple_poaps.ts b/examples/poaps/backend/src/methods/fetch_multiple_poaps.ts index 158eebfe..a0f512a2 100644 --- a/examples/poaps/backend/src/methods/fetch_multiple_poaps.ts +++ b/examples/poaps/backend/src/methods/fetch_multiple_poaps.ts @@ -6,8 +6,8 @@ export const fetch_multiple_poaps = async ( ): Promise => { try { const data: PaginatedResult = await client.fetch({ - sort_field: PoapsSortFields.MintedOn, - sort_dir: Order.ASC, + sortField: PoapsSortFields.MintedOn, + sortDir: Order.ASC, limit: 10, offset: 0, }); diff --git a/examples/poaps/backend/src/methods/fetch_multiple_poaps_by_collector.ts b/examples/poaps/backend/src/methods/fetch_multiple_poaps_by_collector.ts index ac2f39c3..6d102895 100644 --- a/examples/poaps/backend/src/methods/fetch_multiple_poaps_by_collector.ts +++ b/examples/poaps/backend/src/methods/fetch_multiple_poaps_by_collector.ts @@ -6,11 +6,11 @@ export const fetch_multiple_poaps_by_collector = async ( ): Promise => { try { const data: PaginatedResult = await client.fetch({ - sort_field: PoapsSortFields.MintedOn, - sort_dir: Order.ASC, + sortField: PoapsSortFields.MintedOn, + sortDir: Order.ASC, limit: 10, offset: 0, - collector_address: '0xf6B6F07862A02C85628B3A9688beae07fEA9C863', + collectorAddress: '0xf6B6F07862A02C85628B3A9688beae07fEA9C863', }); console.log(data); console.log( diff --git a/examples/poaps/backend/src/methods/fetch_multiple_poaps_by_drop_id.ts b/examples/poaps/backend/src/methods/fetch_multiple_poaps_by_drop_id.ts index 76121630..ddf0c41b 100644 --- a/examples/poaps/backend/src/methods/fetch_multiple_poaps_by_drop_id.ts +++ b/examples/poaps/backend/src/methods/fetch_multiple_poaps_by_drop_id.ts @@ -6,12 +6,12 @@ export const fetch_multiple_poaps_by_drop_id = async ( ): Promise => { try { const data: PaginatedResult = await client.fetch({ - sort_field: PoapsSortFields.MintedOn, - sort_dir: Order.DESC, + sortField: PoapsSortFields.MintedOn, + sortDir: Order.DESC, limit: 10, offset: 0, - drop_id: 3, - filter_by_zero_address: true, + dropId: 3, + filterByZeroAddress: true, }); console.log(data); console.log('The first 10 POAP tokens minted for the drop 14.'); diff --git a/examples/poaps/backend/src/methods/fetch_single_poap.ts b/examples/poaps/backend/src/methods/fetch_single_poap.ts index ae9b343f..9675302f 100644 --- a/examples/poaps/backend/src/methods/fetch_single_poap.ts +++ b/examples/poaps/backend/src/methods/fetch_single_poap.ts @@ -4,8 +4,8 @@ import { PaginatedResult, Order } from '@poap-xyz/utils'; export const fetch_single_poap = async (client: PoapsClient): Promise => { try { const data: PaginatedResult = await client.fetch({ - sort_field: PoapsSortFields.MintedOn, - sort_dir: Order.ASC, + sortField: PoapsSortFields.MintedOn, + sortDir: Order.ASC, limit: 10, offset: 0, ids: [1], diff --git a/examples/poaps/backend/src/methods/get_required_env_var.ts b/examples/poaps/backend/src/methods/get_required_env_var.ts new file mode 100644 index 00000000..63322d09 --- /dev/null +++ b/examples/poaps/backend/src/methods/get_required_env_var.ts @@ -0,0 +1,7 @@ +export const getRequiredEnvVar = (envVarName: string): string => { + const envVar = process.env[envVarName]; + if (envVar === undefined) { + throw new Error(`Environment variable ${envVarName} is required`); + } + return envVar; +}; diff --git a/examples/poaps/backend/src/methods/mint_async_poap.ts b/examples/poaps/backend/src/methods/mint_async_poap.ts new file mode 100644 index 00000000..55f74483 --- /dev/null +++ b/examples/poaps/backend/src/methods/mint_async_poap.ts @@ -0,0 +1,44 @@ +import { PoapsClient } from '@poap-xyz/poaps'; +import { handleError } from '../utils/handleError'; + +/** + * Attempts to mint a POAP (Proof of Attendance Protocol) token asynchronously based on a predefined QR hash and address. + * After successfully minting, the function fetches and logs the details of the minted POAP. + * In the event of an error during the process, the error is captured and managed by a separate utility function. + * + * Note: Replace 'your_mint_code' and 'your_address' placeholders with appropriate values. + * + * @async + * @function + * @param {PoapsClient} client - An instance of the PoapsClient to interface with the POAP service. + * @returns {Promise} Resolves when the operation completes, either with a minted POAP or an error. + */ +export const mint_async_poap = async (client: PoapsClient): Promise => { + try { + // Initiate the asynchronous mint process + const queueUid: string = await client.mintAsync({ + mintCode: 'your_mint_code', + address: 'your_address', + }); + + // Wait for the mint's status to transition from 'IN_PROCESS' or 'PENDING' states + await client.waitMintStatus(queueUid, 'your_mint_code'); + + // Wait for the minted POAP to be indexed and fetch the mint code information related to the QR hash + const getMintCodeResponse = await client.waitPoapIndexed('your_mint_code'); + + // Retrieve and log the specifics of the minted POAP + console.log( + ( + await client.fetch({ + limit: 1, + offset: 0, + ids: [getMintCodeResponse.poapId], + }) + ).items[0], + ); + } catch (error) { + // Address any errors using the designated utility function + handleError(error); + } +}; diff --git a/examples/poaps/backend/src/methods/mint_sync_poap.ts b/examples/poaps/backend/src/methods/mint_sync_poap.ts new file mode 100644 index 00000000..8decac76 --- /dev/null +++ b/examples/poaps/backend/src/methods/mint_sync_poap.ts @@ -0,0 +1,14 @@ +import { POAP, PoapsClient } from '@poap-xyz/poaps'; +import { handleError } from '../utils/handleError'; + +export const mint_sync_poap = async (client: PoapsClient): Promise => { + try { + const data: POAP = await client.mintSync({ + mintCode: 'your_poap_code', + address: 'your_address', + }); + console.log(data); + } catch (error) { + handleError(error); + } +}; diff --git a/examples/poaps/backend/src/utils/handleError.ts b/examples/poaps/backend/src/utils/handleError.ts new file mode 100644 index 00000000..3222c06c --- /dev/null +++ b/examples/poaps/backend/src/utils/handleError.ts @@ -0,0 +1,35 @@ +import { + CodeAlreadyMintedError, + CodeExpiredError, + FinishedWithError, +} from '@poap-xyz/poaps'; + +/** + * Handles specific POAP-related errors by logging them. + * + * Errors handled: + * - CodeAlreadyMintedError: Thrown when a POAP mint code has already been minted. + * - CodeExpiredError: Thrown when a POAP mint code has expired and is no longer valid for minting. + * - FinishedWithError: Thrown when the POAP mint process completes but encounters an error. + * + * @param {unknown} error - The error object to be checked and handled. + */ +export const handleError = (error: unknown): void => { + if ( + // Checks if the error is an instance of CodeAlreadyClaimedError. + // This error occurs when a user attempts to mint a POAP that has already been minted by someone else. + error instanceof CodeAlreadyMintedError || + // Checks if the error is an instance of CodeExpiredError. + // This error is thrown when the mint code for a POAP has expired. + error instanceof CodeExpiredError || + // Checks if the error is an instance of FinishedWithError. + // This error indicates that the POAP mint process finished but encountered an unexpected error. + error instanceof FinishedWithError + ) { + // Logs the specific error message. + console.error(error); + } else { + // Logs the generic error message. + console.error('An unexpected error occurred:', error); + } +}; diff --git a/packages/drops/package.json b/packages/drops/package.json index 5d1a8edc..62f5077d 100644 --- a/packages/drops/package.json +++ b/packages/drops/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/drops", - "version": "0.0.38", + "version": "0.0.39", "description": "Drops module for the poap.js library", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -26,7 +26,7 @@ "build": "rollup -c --bundleConfigAsCjs" }, "dependencies": { - "@poap-xyz/providers": "0.0.38", - "@poap-xyz/utils": "0.0.38" + "@poap-xyz/providers": "0.0.39", + "@poap-xyz/utils": "0.0.39" } } diff --git a/packages/drops/src/DropsClient.ts b/packages/drops/src/DropsClient.ts index 8a0b6246..b2ea3844 100644 --- a/packages/drops/src/DropsClient.ts +++ b/packages/drops/src/DropsClient.ts @@ -10,7 +10,7 @@ import { PaginatedResult, nextCursor, creatPrivateFilter, - creatUndefinedOrder, + createUndefinedOrder, createBetweenFilter, createFilter, createInFilter, @@ -30,8 +30,8 @@ export class DropsClient { * @param {DropApiProvider} DropApiProvider - The provider for the POAP drop API. */ constructor( - private CompassProvider: CompassProvider, - private DropApiProvider: DropApiProvider, + private compassProvider: CompassProvider, + private dropApiProvider: DropApiProvider, ) {} /** @@ -47,27 +47,27 @@ export class DropsClient { limit, offset, name, - sort_field, - sort_dir, + sortField, + sortDir, from, to, ids, - is_private, + isPrivate, } = input; const variables = { limit, offset, - orderBy: creatUndefinedOrder(sort_field, sort_dir), + orderBy: createUndefinedOrder(sortField, sortDir), where: { - ...creatPrivateFilter('private', is_private), + ...creatPrivateFilter('private', isPrivate), ...createFilter('name', name), ...createBetweenFilter('created_date', from, to), ...createInFilter('id', ids), }, }; - const { data } = await this.CompassProvider.request( + const { data } = await this.compassProvider.request( PAGINATED_DROPS_QUERY, variables, ); @@ -75,22 +75,34 @@ export class DropsClient { const drops = data.drops.map( (drop) => new Drop({ - ...drop, id: Number(drop.id), + fancyId: drop.fancy_id, + name: drop.name, + description: drop.description, + city: drop.city, + country: drop.country, + channel: drop.channel, + platform: drop.platform, + locationType: drop.location_type, + dropUrl: drop.drop_url, + imageUrl: drop.image_url, + animationUrl: drop.animation_url, year: Number(drop.year), - poap_count: drop.stats_by_chain_aggregate.aggregate.sum + startDate: new Date(drop.start_date), + timezone: drop.timezone, + private: drop.private, + createdDate: new Date(drop.created_date), + poapCount: drop.stats_by_chain_aggregate.aggregate.sum ? Number(drop.stats_by_chain_aggregate.aggregate.sum.poap_count) : 0, - transfer_count: drop.stats_by_chain_aggregate.aggregate.sum + transferCount: drop.stats_by_chain_aggregate.aggregate.sum ? Number(drop.stats_by_chain_aggregate.aggregate.sum.transfer_count) : 0, - email_claim: drop.email_claims_stats + emailReservationCount: drop.email_claims_stats ? Number(drop.email_claims_stats.total) : 0, - start_date: new Date(drop.start_date), - end_date: new Date(drop.end_date), - created_date: new Date(drop.created_date), - expiry_date: new Date(drop.expiry_date), + expiryDate: new Date(drop.expiry_date), + endDate: new Date(drop.end_date), }), ); @@ -109,7 +121,25 @@ export class DropsClient { * @returns {Promise} The newly created drop. */ async create(input: CreateDropsInput): Promise { - const repsonse = await this.DropApiProvider.createDrop(input); + const repsonse = await this.dropApiProvider.createDrop({ + name: input.name, + description: input.description, + city: input.city, + country: input.country, + start_date: input.startDate, + end_date: input.endDate, + expiry_date: input.expiryDate, + event_url: input.eventUrl, + virtual_event: input.virtualEvent, + image: input.image, + filename: input.filename, + contentType: input.contentType, + secret_code: input.secretCode, + event_template_id: input.eventTemplateId, + email: input.email, + requested_codes: input.requestedCodes, + private_event: input.privateEvent, + }); return this.formatDrop(repsonse); } @@ -122,34 +152,47 @@ export class DropsClient { * @returns {Promise} The updated drop. */ async update(input: UpdateDropsInput): Promise { - const repsonse = await this.DropApiProvider.updateDrop(input); + const repsonse = await this.dropApiProvider.updateDrop({ + name: input.name, + description: input.description, + country: input.country, + city: input.city, + start_date: input.startDate, + end_date: input.endDate, + expiry_date: input.expiryDate, + event_url: input.eventUrl, + virtual_event: input.virtualEvent, + private_event: input.privateEvent, + event_template_id: input.eventTemplateId, + secret_code: input.secretCode, + }); return this.formatDrop(repsonse); } private formatDrop(drop: DropResponse): Drop { return new Drop({ id: drop.id, - fancy_id: drop.fancy_id, + fancyId: drop.fancy_id, name: drop.name, description: drop.description, city: drop.city, country: drop.country, channel: drop.channel, platform: drop.platform, - location_type: drop.location_type, - drop_url: drop.event_url, - image_url: drop.image_url, - animation_url: drop.animation_url, + locationType: drop.location_type, + dropUrl: drop.event_url, + imageUrl: drop.image_url, + animationUrl: drop.animation_url, year: drop.year, - start_date: new Date(drop.start_date), + startDate: new Date(drop.start_date), timezone: drop.timezone, private: drop.private_event, - created_date: new Date(drop.created_date), - expiry_date: new Date(drop.expiry_date), - end_date: new Date(drop.end_date), - transfer_count: 0, - poap_count: 0, - email_claim: 0, + createdDate: new Date(drop.created_date), + expiryDate: new Date(drop.expiry_date), + endDate: new Date(drop.end_date), + transferCount: 0, + poapCount: 0, + emailReservationCount: 0, }); } } diff --git a/packages/drops/src/domain/Drop.ts b/packages/drops/src/domain/Drop.ts index 27feefcf..0129130b 100644 --- a/packages/drops/src/domain/Drop.ts +++ b/packages/drops/src/domain/Drop.ts @@ -1,106 +1,131 @@ /* eslint-disable max-statements */ export class Drop { id: number; - fancy_id: string; + fancyId: string; name: string; description: string; city: string; country: string; channel: string; platform: string; - location_type: string; - drop_url: string; - image_url: string; - animation_url: string; + locationType: string; + dropUrl: string; + imageUrl: string; + animationUrl: string; year: number; timezone: string; private: boolean; - start_date: Date; - created_date: Date; - expiry_date: Date; - end_date: Date; - poap_count: number; - transfer_count: number; - email_claim: number; + startDate: Date; + createdDate: Date; + expiryDate: Date; + endDate: Date; + poapCount: number; + transferCount: number; + emailReservationCount: number; constructor(properties: DropProperties) { this.id = properties.id; - this.fancy_id = properties.fancy_id; + this.fancyId = properties.fancyId; this.name = properties.name; this.description = properties.description; this.city = properties.city; this.country = properties.country; this.channel = properties.channel; this.platform = properties.platform; - this.location_type = properties.location_type; - this.drop_url = properties.drop_url; - this.image_url = properties.image_url; - this.animation_url = properties.animation_url; + this.locationType = properties.locationType; + this.dropUrl = properties.dropUrl; + this.imageUrl = properties.imageUrl; + this.animationUrl = properties.animationUrl; this.year = properties.year; - this.start_date = properties.start_date; + this.startDate = properties.startDate; this.timezone = properties.timezone; this.private = properties.private; - this.created_date = properties.created_date; - this.poap_count = properties.poap_count; - this.transfer_count = properties.transfer_count; - this.email_claim = properties.email_claim; - this.expiry_date = properties.expiry_date; - this.end_date = properties.end_date; + this.createdDate = properties.createdDate; + this.poapCount = properties.poapCount; + this.transferCount = properties.transferCount; + this.emailReservationCount = properties.emailReservationCount; + this.expiryDate = properties.expiryDate; + this.endDate = properties.endDate; } public getTotalMinted(): number { - return this.poap_count + this.email_claim; + return this.poapCount + this.emailReservationCount; } - public toSerializableObject(): any { + public toSerializableObject(): SerializableDrop { return { id: this.id, - fancy_id: this.fancy_id, + fancyId: this.fancyId, name: this.name, description: this.description, city: this.city, country: this.country, channel: this.channel, platform: this.platform, - location_type: this.location_type, - drop_url: this.drop_url, - image_url: this.image_url, - animation_url: this.animation_url, + locationType: this.locationType, + dropUrl: this.dropUrl, + imageUrl: this.imageUrl, + animationUrl: this.animationUrl, year: this.year, timezone: this.timezone, private: this.private, - start_date: this.start_date.toISOString(), - created_date: this.created_date.toISOString(), - poap_count: this.poap_count, - transfer_count: this.transfer_count, - email_claim: this.email_claim, - expiry_date: this.expiry_date.toISOString(), - end_date: this.end_date.toISOString() + startDate: this.startDate.toISOString(), + createdDate: this.createdDate.toISOString(), + poapCount: this.poapCount, + transferCount: this.transferCount, + emailReservationCount: this.emailReservationCount, + expiryDate: this.expiryDate.toISOString(), + endDate: this.endDate.toISOString(), }; - } + } +} + +export interface SerializableDrop { + id: number; + fancyId: string; + name: string; + description: string; + city: string; + country: string; + channel: string; + platform: string; + locationType: string; + dropUrl: string; + imageUrl: string; + animationUrl: string; + year: number; + timezone: string; + private: boolean; + startDate: string; // ISO String representation of Date + createdDate: string; // ISO String representation of Date + poapCount: number; + transferCount: number; + emailReservationCount: number; + expiryDate: string; // ISO String representation of Date + endDate: string; // ISO String representation of Date } export interface DropProperties { id: number; - fancy_id: string; + fancyId: string; name: string; description: string; city: string; country: string; channel: string; platform: string; - location_type: string; - drop_url: string; - image_url: string; - animation_url: string; + locationType: string; + dropUrl: string; + imageUrl: string; + animationUrl: string; year: number; timezone: string; private: boolean; - created_date: Date; - start_date: Date; - expiry_date: Date; - end_date: Date; - poap_count: number; - transfer_count: number; - email_claim: number; -} \ No newline at end of file + createdDate: Date; + startDate: Date; + expiryDate: Date; + endDate: Date; + poapCount: number; + transferCount: number; + emailReservationCount: number; +} diff --git a/packages/drops/src/index.ts b/packages/drops/src/index.ts index 8925e278..5fd44bb1 100644 --- a/packages/drops/src/index.ts +++ b/packages/drops/src/index.ts @@ -1,5 +1,5 @@ export { DropsClient } from './DropsClient'; -export { Drop, DropProperties } from './domain/Drop'; +export { Drop, DropProperties, SerializableDrop } from './domain/Drop'; export { FetchDropsInput, CreateDropsInput, diff --git a/packages/drops/src/types/input.ts b/packages/drops/src/types/input.ts index 93e1d332..5caab795 100644 --- a/packages/drops/src/types/input.ts +++ b/packages/drops/src/types/input.ts @@ -8,12 +8,12 @@ export enum DropsSortFields { export interface FetchDropsInput extends PaginationInput { name?: string; - sort_field?: DropsSortFields; - sort_dir?: Order; + sortField?: DropsSortFields; + sortDir?: Order; from?: string; to?: string; ids?: number[]; - is_private?: boolean; + isPrivate?: boolean; } export interface CreateDropsInput { @@ -21,19 +21,19 @@ export interface CreateDropsInput { description: string; city: string; country: string; - start_date: string; - end_date: string; - expiry_date: string; - event_url: string; - virtual_event: boolean; + startDate: string; + endDate: string; + expiryDate: string; + eventUrl: string; + virtualEvent: boolean; image: Buffer; filename: string; contentType: string; - secret_code: string; - event_template_id?: number | null; + secretCode: string; + eventTemplateId?: number | null; email: string; - requested_codes?: number; - private_event?: boolean; + requestedCodes?: number; + privateEvent?: boolean; } export type UpdateDropsInput = CreateDropsInput; diff --git a/packages/moments/package.json b/packages/moments/package.json index 33d57d92..8f66cc67 100644 --- a/packages/moments/package.json +++ b/packages/moments/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/moments", - "version": "0.0.38", + "version": "0.0.39", "description": "Moments module for the poap.js library", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -26,8 +26,8 @@ "build": "rollup -c --bundleConfigAsCjs" }, "dependencies": { - "@poap-xyz/providers": "0.0.38", - "@poap-xyz/utils": "0.0.38", + "@poap-xyz/providers": "0.0.39", + "@poap-xyz/utils": "0.0.39", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/packages/moments/src/types/index.ts b/packages/moments/src/types/index.ts deleted file mode 100644 index e3365cb9..00000000 --- a/packages/moments/src/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './input'; diff --git a/packages/performance/package.json b/packages/performance/package.json index 84ff9bdf..f4a399c4 100644 --- a/packages/performance/package.json +++ b/packages/performance/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/performance", - "version": "0.0.38", + "version": "0.0.39", "description": "Performance module for the poap.js library", "type": "module", "main": "dist/cjs/index.cjs", diff --git a/packages/poaps/package.json b/packages/poaps/package.json index 59994ab1..c791bb46 100644 --- a/packages/poaps/package.json +++ b/packages/poaps/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/poaps", - "version": "0.0.38", + "version": "0.0.39", "description": "Poaps module for the poap.js library", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -26,7 +26,7 @@ "build": "rollup -c --bundleConfigAsCjs" }, "dependencies": { - "@poap-xyz/providers": "0.0.38", - "@poap-xyz/utils": "0.0.38" + "@poap-xyz/providers": "0.0.39", + "@poap-xyz/utils": "0.0.39" } } diff --git a/packages/poaps/src/PoapsClient.ts b/packages/poaps/src/PoapsClient.ts index 04cbd19c..697cdf4a 100644 --- a/packages/poaps/src/PoapsClient.ts +++ b/packages/poaps/src/PoapsClient.ts @@ -1,87 +1,102 @@ -import { CompassProvider } from '@poap-xyz/providers'; +import { CompassProvider, TokensApiProvider } from '@poap-xyz/providers'; import { POAP } from './domain/Poap'; +import { POAPReservation } from './domain/POAPReservation'; import { PaginatedPoapsResponse, PAGINATED_POAPS_QUERY } from './queries'; -import { FetchPoapsInput } from './types'; +import { + FetchPoapsInput, + EmailReservationInput, + WalletMintInput, +} from './types'; import { PaginatedResult, nextCursor, createBetweenFilter, creatEqFilter, createInFilter, - creatUndefinedOrder, + createUndefinedOrder, creatAddressFilter, + MintingStatus, } from '@poap-xyz/utils'; +import { CodeAlreadyMintedError } from './errors/CodeAlreadyMintedError'; +import { CodeExpiredError } from './errors/CodeExpiredError'; +import { MintChecker } from './utils/MintChecker'; +import { PoapIndexed } from './utils/PoapIndexed'; +import { PoapMintStatus } from './types/response'; /** - * Represents a client for working with POAP drops. - * - * @class DropsClient + * Represents a client for interacting with POAPs (Proof of Attendance Protocol tokens). + * @class */ export class PoapsClient { /** - * Creates a new DropsClient object. - * - * @constructor - * @param {CompassProvider} CompassProvider - The provider for the POAP compass API. + * Initializes a new instance of the PoapsClient. + * @param {CompassProvider} compassProvider - The provider for the POAP compass API. + * @param {TokensApiProvider} tokensApiProvider - The provider for the Tokens API. */ - constructor(private CompassProvider: CompassProvider) {} + constructor( + private compassProvider: CompassProvider, + private tokensApiProvider: TokensApiProvider, + ) {} /** - * Fetches drops based on the specified input. - * + * Fetches a list of POAP tokens based on the given input criteria. * @async - * @method - * @param {FetchPoapsInput} input - The input for fetching drops. - * @returns {Promise>} A paginated result of drops. + * @param {FetchPoapsInput} input - Criteria for fetching POAP tokens. + * @returns {Promise>} A paginated list of POAP tokens. */ async fetch(input: FetchPoapsInput): Promise> { const { limit, offset, chain, - collector_address, - minted_date_from, - minted_date_to, + collectorAddress, + mintedDateFrom, + mintedDateTo, ids, - drop_id, - sort_field, - sort_dir, - filter_by_zero_address = true, + dropId, + sortField, + sortDir, + filterByZeroAddress = true, } = input; const variables = { limit, offset, - orderBy: creatUndefinedOrder(sort_field, sort_dir), + orderBy: createUndefinedOrder(sortField, sortDir), where: { ...creatAddressFilter( 'collector_address', - filter_by_zero_address, - collector_address, + filterByZeroAddress, + collectorAddress, ), ...creatEqFilter('chain', chain), - ...creatEqFilter('drop_id', drop_id), - ...createBetweenFilter('minted_on', minted_date_from, minted_date_to), + ...creatEqFilter('drop_id', dropId), + ...createBetweenFilter('minted_on', mintedDateFrom, mintedDateTo), ...createInFilter('id', ids), }, }; - const { data } = await this.CompassProvider.request( + const { data } = await this.compassProvider.request( PAGINATED_POAPS_QUERY, variables, ); const poaps = data.poaps.map((poap) => { const { drop } = poap; - const minted_on = new Date(0); - minted_on.setUTCSeconds(poap.minted_on); + const mintedOn = new Date(0); + mintedOn.setUTCSeconds(poap.minted_on); return new POAP({ - ...poap, - ...drop, id: Number(poap.id), - minted_on, - drop_id: Number(poap.drop_id), - start_date: new Date(drop.start_date), - end_date: new Date(drop.end_date), + collectorAddress: poap.collector_address, + transferCount: poap.transfer_count, + mintedOn, + dropId: Number(poap.drop_id), + imageUrl: drop.image_url, + city: drop.city, + country: drop.country, + description: drop.description, + startDate: new Date(drop.start_date), + name: drop.name, + endDate: new Date(drop.end_date), }); }); @@ -90,4 +105,151 @@ export class PoapsClient { nextCursor(poaps.length, limit, offset), ); } + + /** + * Retrieves the secret code associated with a POAP code. + * @async + * @param {string} mintCode - The POAP code for which to get the secret. + * @returns {Promise} The associated secret code. + * @throws {CodeAlreadyMintedError} Thrown when the POAP code has already been minted. + * @throws {CodeExpiredError} Thrown when the POAP code is expired. + */ + private async getSecretCode(mintCode: string): Promise { + const getCodeResponse = await this.getMintCode(mintCode); + + if (getCodeResponse.minted == true) { + throw new CodeAlreadyMintedError(mintCode); + } + if (getCodeResponse.isActive == false) { + throw new CodeExpiredError(mintCode); + } + + return getCodeResponse.secretCode; + } + + /** + * Retrieves mint code details for a specific QR hash. + * @async + * @param {string} mintCode - The QR hash for which to get the mint code. + * @returns {Promise} The mint code details. + */ + async getMintCode(mintCode: string): Promise { + const getMintCodeRaw = await this.tokensApiProvider.getMintCode(mintCode); + return { + minted: getMintCodeRaw.claimed, + isActive: getMintCodeRaw.is_active, + secretCode: getMintCodeRaw.secret, + poapId: getMintCodeRaw.result?.token, + }; + } + + /** + * Fetches the current status of a mint based on its unique ID. + * @async + * @param {string} queueUid - The unique ID of the mint. + * @returns {Promise} The current status of the mint. + */ + async getMintStatus(queueUid: string): Promise { + const mintStatusResponse = await this.tokensApiProvider.mintStatus( + queueUid, + ); + return mintStatusResponse.status; + } + + /** + * Awaits until the mint's status changes from 'IN_PROCESS' or 'PENDING'. + * @async + * @param {string} queueUid - The unique ID of the mint. + * @returns {Promise} + */ + async waitMintStatus(queueUid: string, mintCode: string): Promise { + const checker = new MintChecker(queueUid, this.tokensApiProvider, mintCode); + await checker.checkMintStatus(); + } + + /** + * Awaits until a specific POAP, identified by its QR hash, is indexed. + * @async + * @param {string} mintCode - The QR hash identifying the POAP to be indexed. + * @returns {Promise} Details of the indexed POAP. + */ + async waitPoapIndexed(mintCode: string): Promise { + const checker = new PoapIndexed(mintCode, this.tokensApiProvider); + return await checker.waitPoapIndexed(); + } + + /** + * Begins an asynchronous mint process and provides a unique queue ID in return. + * @async + * @param {WalletMintInput} input - Details required for the mint. + * @returns {Promise} A unique queue ID for the initiated mint. + */ + async mintAsync(input: WalletMintInput): Promise { + const secretCode = await this.getSecretCode(input.mintCode); + + const response = await this.tokensApiProvider.postMintCode({ + address: input.address, + qr_hash: input.mintCode, + secret: secretCode, + sendEmail: false, + }); + + return response.queue_uid; + } + + /** + * Starts a synchronous mint process. The method waits for the mint to be processed and then + * fetches the associated POAP. It combines the asynchronous mint and subsequent status checking + * into a synchronous process for ease of use. + * @async + * @param {WalletMintInput} input - Details needed for the mint. + * @returns {Promise} The associated POAP upon successful mint completion. + * @throws {FinishedWithError} If there's an error concluding the mint process. + */ + async mintSync(input: WalletMintInput): Promise { + const queueUid = await this.mintAsync(input); + + await this.waitMintStatus(queueUid, input.mintCode); + + const getCodeResponse = await this.waitPoapIndexed(input.mintCode); + + return ( + await this.fetch({ + limit: 1, + offset: 0, + ids: [getCodeResponse.poapId], + }) + ).items[0]; + } + + /** + * Reserves a POAP against an email address and provides reservation details. + * @async + * @param {EmailReservationInput} input - Information for the reservation. + * @returns {Promise} The reservation details of the POAP. + */ + async emailReservation( + input: EmailReservationInput, + ): Promise { + const secretCode = await this.getSecretCode(input.mintCode); + + const response = await this.tokensApiProvider.postMintCode({ + address: input.email, + qr_hash: input.mintCode, + secret: secretCode, + sendEmail: input.sendEmail || true, + }); + + return new POAPReservation({ + email: input.email, + dropId: response.event.id, + imageUrl: response.event.image_url, + city: response.event.city, + country: response.event.country, + description: response.event.description, + startDate: new Date(response.event.start_date), + endDate: new Date(response.event.end_date), + name: response.event.name, + }); + } } diff --git a/packages/poaps/src/domain/POAPReservation.ts b/packages/poaps/src/domain/POAPReservation.ts new file mode 100644 index 00000000..ac9e082a --- /dev/null +++ b/packages/poaps/src/domain/POAPReservation.ts @@ -0,0 +1,36 @@ +/* eslint-disable max-statements */ +export class POAPReservation { + email: string; + dropId: number; + imageUrl: string; + city: string; + country: string; + description: string; + startDate: Date; + endDate: Date; + name: string; + + constructor(properties: POAPReservationProperties) { + this.email = properties.email; + this.dropId = properties.dropId; + this.imageUrl = properties.imageUrl; + this.city = properties.city; + this.country = properties.country; + this.description = properties.description; + this.startDate = properties.startDate; + this.endDate = properties.endDate; + this.name = properties.name; + } +} + +export interface POAPReservationProperties { + email: string; + dropId: number; + imageUrl: string; + city: string; + country: string; + description: string; + startDate: Date; + name: string; + endDate: Date; +} diff --git a/packages/poaps/src/domain/Poap.ts b/packages/poaps/src/domain/Poap.ts index f3ba8446..a0baea33 100644 --- a/packages/poaps/src/domain/Poap.ts +++ b/packages/poaps/src/domain/Poap.ts @@ -1,45 +1,45 @@ /* eslint-disable max-statements */ export class POAP { id: number; - collector_address: string; - transfer_count: number; - minted_on: Date; - drop_id: number; - image_url: string; + collectorAddress: string; + transferCount: number; + mintedOn: Date; + dropId: number; + imageUrl: string; city: string; country: string; description: string; - start_date: Date; - end_date: Date; + startDate: Date; + endDate: Date; name: string; constructor(properties: PoapProperties) { this.id = properties.id; - this.collector_address = properties.collector_address; - this.minted_on = properties.minted_on; - this.drop_id = properties.drop_id; - this.transfer_count = properties.transfer_count; - this.image_url = properties.image_url; + this.collectorAddress = properties.collectorAddress; + this.mintedOn = properties.mintedOn; + this.dropId = properties.dropId; + this.transferCount = properties.transferCount; + this.imageUrl = properties.imageUrl; this.city = properties.city; this.country = properties.country; this.description = properties.description; - this.start_date = properties.start_date; - this.end_date = properties.end_date; + this.startDate = properties.startDate; + this.endDate = properties.endDate; this.name = properties.name; } } export interface PoapProperties { id: number; - collector_address: string; - transfer_count: number; - minted_on: Date; - drop_id: number; - image_url: string; + collectorAddress: string; + transferCount: number; + mintedOn: Date; + dropId: number; + imageUrl: string; city: string; country: string; description: string; - start_date: Date; + startDate: Date; name: string; - end_date: Date; + endDate: Date; } diff --git a/packages/poaps/src/errors/CodeAlreadyMintedError.ts b/packages/poaps/src/errors/CodeAlreadyMintedError.ts new file mode 100644 index 00000000..c7122884 --- /dev/null +++ b/packages/poaps/src/errors/CodeAlreadyMintedError.ts @@ -0,0 +1,5 @@ +export class CodeAlreadyMintedError extends Error { + constructor(code: string) { + super(`Code: '${code}' already minted `); + } +} diff --git a/packages/poaps/src/errors/CodeExpiredError.ts b/packages/poaps/src/errors/CodeExpiredError.ts new file mode 100644 index 00000000..91d931de --- /dev/null +++ b/packages/poaps/src/errors/CodeExpiredError.ts @@ -0,0 +1,5 @@ +export class CodeExpiredError extends Error { + constructor(code: string) { + super(`Code: '${code}', has been expired`); + } +} diff --git a/packages/poaps/src/errors/FinishedWithError.ts b/packages/poaps/src/errors/FinishedWithError.ts new file mode 100644 index 00000000..062b824b --- /dev/null +++ b/packages/poaps/src/errors/FinishedWithError.ts @@ -0,0 +1,7 @@ +export class FinishedWithError extends Error { + constructor(error: string, code: string) { + super( + `Code: '${code}', finished with error: '${error}', please try again later `, + ); + } +} diff --git a/packages/poaps/src/index.ts b/packages/poaps/src/index.ts index 7cbe4ac3..faf13e1c 100644 --- a/packages/poaps/src/index.ts +++ b/packages/poaps/src/index.ts @@ -1,3 +1,8 @@ export { PoapsSortFields, FetchPoapsInput } from './types/input'; +export { PoapMintStatus } from './types/response'; export { PoapsClient } from './PoapsClient'; export { POAP } from './domain/Poap'; +export { POAPReservation } from './domain/POAPReservation'; +export { FinishedWithError } from './errors/FinishedWithError'; +export { CodeAlreadyMintedError } from './errors/CodeAlreadyMintedError'; +export { CodeExpiredError } from './errors/CodeExpiredError'; diff --git a/packages/poaps/src/types/index.ts b/packages/poaps/src/types/index.ts index e3365cb9..eccece42 100644 --- a/packages/poaps/src/types/index.ts +++ b/packages/poaps/src/types/index.ts @@ -1 +1,2 @@ export * from './input'; +export * from './response'; diff --git a/packages/poaps/src/types/input.ts b/packages/poaps/src/types/input.ts index fbb0a4e6..c4096b85 100644 --- a/packages/poaps/src/types/input.ts +++ b/packages/poaps/src/types/input.ts @@ -1,19 +1,68 @@ import { Order, Chain, PaginationInput } from '@poap-xyz/utils'; +/** + * Enum to define available fields for sorting Poaps. + * + * @export + * @enum {string} + */ export enum PoapsSortFields { + /** Represents sorting by the date when a Poap was minted. */ MintedOn = 'minted_on', + /** Represents sorting by the ID of a Poap. */ Id = 'id', } +/** + * Represents the input fields for fetching Poaps. + * This interface extends `PaginationInput` to provide pagination capability. + * + * @export + * @interface FetchPoapsInput + * @extends {PaginationInput} + */ export interface FetchPoapsInput extends PaginationInput { + /** Optional filter for the name of a Poap. */ name?: string; + /** Optional filter for the blockchain chain of a Poap. */ chain?: Chain; - minted_date_from?: string; - minted_date_to?: string; + /** Optional filter for the start date when a Poap was minted. */ + mintedDateFrom?: string; + /** Optional filter for the end date when a Poap was minted. */ + mintedDateTo?: string; + /** Optional filter for specific Poap IDs. */ ids?: number[]; - collector_address?: string; - drop_id?: number; - sort_field?: PoapsSortFields; - sort_dir?: Order; - filter_by_zero_address?: boolean; + /** Optional filter for the collector's address. */ + collectorAddress?: string; + /** Optional filter for a specific drop ID. */ + dropId?: number; + /** Field by which to sort the results. */ + sortField?: PoapsSortFields; + /** Direction in which to sort the results. */ + sortDir?: Order; + /** Filter to include/exclude Poaps with zero addresses. */ + filterByZeroAddress?: boolean; +} + +/** + * Represents the input fields required to mint a Poap for a wallet. + * + * @export + * @interface WalletMintInput + */ +export interface WalletMintInput { + mintCode: string; + address: string; +} + +/** + * Represents the input fields required to reserve a Poap via email. + * + * @export + * @interface EmailReservationInput + */ +export interface EmailReservationInput { + mintCode: string; + email: string; + sendEmail?: boolean; } diff --git a/packages/poaps/src/types/response.ts b/packages/poaps/src/types/response.ts new file mode 100644 index 00000000..893ce4d2 --- /dev/null +++ b/packages/poaps/src/types/response.ts @@ -0,0 +1,6 @@ +export interface PoapMintStatus { + minted: boolean; + isActive: boolean; + secretCode: string; + poapId: number; +} diff --git a/packages/poaps/src/utils/MintChecker.ts b/packages/poaps/src/utils/MintChecker.ts new file mode 100644 index 00000000..a779c505 --- /dev/null +++ b/packages/poaps/src/utils/MintChecker.ts @@ -0,0 +1,87 @@ +import { MintingStatus } from '@poap-xyz/utils'; +import { TokensApiProvider, MintStatusResponse } from '@poap-xyz/providers'; +import { FinishedWithError } from '../errors/FinishedWithError'; +import { RetryableTask } from './RetryableTask'; + +/** + * A utility class designed to continually check the status of a Poap token mint. + * If a mint is still pending or in process, it implements a backoff retry mechanism. + */ +export class MintChecker extends RetryableTask { + private queueUid: string; + private mintCode: string; + + /** + * Constructs a new instance of the MintChecker class. + * + * @param {string} queueUid - The unique identifier for the token mint. + * @param {string} mintCode - The unique code for the token mint. + * @param {TokensApiProvider} tokensApiProvider - The provider to fetch the mint status. + */ + constructor( + queueUid: string, + tokensApiProvider: TokensApiProvider, + mintCode: string, + ) { + super(tokensApiProvider); + this.queueUid = queueUid; + this.mintCode = mintCode; + } + + /** + * Determines if a retry should be performed based on the provided minting status. + * + * @private + * @param {MintingStatus} status - The current minting status. + * @returns {boolean} Returns true if a retry should be performed, otherwise false. + */ + private shouldRetry(status: MintingStatus): boolean { + return ( + status === MintingStatus.IN_PROCESS || status === MintingStatus.PENDING + ); + } + + /** + * Handles any error statuses from the mint status response. + * If the minting process finishes with an error, an exception will be thrown. + * + * @private + * @param {MintStatusResponse} mintStatusResponse - The response from the mint status check. + * @throws {FinishedWithError} Throws an error if the minting process finished with an error. + */ + private handleErrorStatus( + mintStatusResponse: MintStatusResponse, + mintCode: string, + ): void { + if ( + mintStatusResponse.status === MintingStatus.FINISH_WITH_ERROR && + mintStatusResponse.result?.error + ) { + throw new FinishedWithError(mintStatusResponse.result?.error, mintCode); + } + } + + /** + * Checks the current status of a token mint. + * If the mint is still pending or in process, it will retry the check with an increased delay. + * + * @public + * @returns {Promise} A promise that resolves once the status has been checked. + * @throws {FinishedWithError} Throws an error if the minting process finished with an error. + */ + public async checkMintStatus(): Promise { + try { + const mintStatusResponse = await this.tokensApiProvider.mintStatus( + this.queueUid, + ); + + if (this.shouldRetry(mintStatusResponse.status)) { + await this.backoffAndRetry(() => this.checkMintStatus()); + } else { + this.handleErrorStatus(mintStatusResponse, this.mintCode); + } + } catch (e) { + await this.backoffAndRetry(() => this.checkMintStatus()); + } + } +} diff --git a/packages/poaps/src/utils/PoapIndexed.ts b/packages/poaps/src/utils/PoapIndexed.ts new file mode 100644 index 00000000..6db58535 --- /dev/null +++ b/packages/poaps/src/utils/PoapIndexed.ts @@ -0,0 +1,46 @@ +import { TokensApiProvider } from '@poap-xyz/providers'; +import { RetryableTask } from './RetryableTask'; +import { PoapMintStatus } from '../types'; + +/** + * @class PoapIndexed + * @extends {RetryableTask} + * + * Represents a utility class designed to periodically check if a POAP (Proof of Attendance Protocol) token is indexed. + * This class extends `RetryableTask` to utilize its backoff retry mechanism in case the token hasn't been indexed yet. + */ +export class PoapIndexed extends RetryableTask { + private mintCode: string; + + /** + * Creates an instance of the PoapIndexed class. + * + * @param {string} mintCode - A unique QR hash representing the token. + * @param {TokensApiProvider} tokensApiProvider - An instance of the TokensApiProvider used to check the indexing status of the token. + */ + constructor(mintCode: string, tokensApiProvider: TokensApiProvider) { + super(tokensApiProvider); + this.mintCode = mintCode; + } + + /** + * Periodically checks if the POAP token, represented by its QR hash, is indexed. + * This method will continue retrying with an increasing delay until either the token is indexed or it reaches the maximum allowed retries. + * + * @returns {Promise} A promise that either resolves with the indexed token's mint code response or rejects due to reaching the max retry limit. + */ + public async waitPoapIndexed(): Promise { + let response = await this.tokensApiProvider.getMintCode(this.mintCode); + while (response.result == null) { + response = await this.backoffAndRetry(() => + this.tokensApiProvider.getMintCode(this.mintCode), + ); + } + return { + minted: response.claimed, + isActive: response.is_active, + secretCode: response.secret, + poapId: response.result?.token, + }; + } +} diff --git a/packages/poaps/src/utils/RetryableTask.ts b/packages/poaps/src/utils/RetryableTask.ts new file mode 100644 index 00000000..657c7cec --- /dev/null +++ b/packages/poaps/src/utils/RetryableTask.ts @@ -0,0 +1,47 @@ +import { TokensApiProvider } from '@poap-xyz/providers'; + +const MAX_RETRIES = 20; +const INITIAL_DELAY = 1000; +const BACKOFF_FACTOR = 1.2; + +/** + * Abstract class representing a task that can be retried with an increasing delay. + */ +export abstract class RetryableTask { + protected retries = 0; + protected delay: number = INITIAL_DELAY; + protected tokensApiProvider: TokensApiProvider; + + /** + * Constructs a new RetryableTask instance. + * + * @param {TokensApiProvider} tokensApiProvider - The provider used to perform operations that might be retried. + */ + constructor(tokensApiProvider: TokensApiProvider) { + this.tokensApiProvider = tokensApiProvider; + } + + /** + * Attempts to perform a given task. If the task fails, it retries with an increasing delay until + * maximum retries are reached. + * + * @protected + * @template T - The type of value that the callback returns. + * @param {() => Promise} callback - The asynchronous function representing the task to be retried. + * @returns {Promise} A promise that resolves to the result of the task or rejects with an error. + * @throws {Error} Throws an error if maximum retries are reached. + */ + protected backoffAndRetry(callback: () => Promise): Promise { + if (this.retries >= MAX_RETRIES) { + throw new Error('Max retries reached'); + } + this.retries++; + this.delay *= BACKOFF_FACTOR; + + return new Promise((resolve, reject) => { + setTimeout(() => { + callback().then(resolve, reject); + }, this.delay); + }); + } +} diff --git a/packages/providers/package.json b/packages/providers/package.json index fd081013..4fa4fecb 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/providers", - "version": "0.0.38", + "version": "0.0.39", "description": "Providers module for the poap.js library", "main": "dist/cjs/index.cjs", "module": "dist/esm/index.mjs", @@ -24,6 +24,9 @@ "scripts": { "build": "rollup -c --bundleConfigAsCjs" }, + "dependencies": { + "@poap-xyz/utils": "0.0.39" + }, "devDependencies": { "axios-mock-adapter": "^1.21.4" }, diff --git a/packages/providers/src/core/AuthenticationProviderHttp/AuthenticationProviderHttp.ts b/packages/providers/src/core/AuthenticationProviderHttp/AuthenticationProviderHttp.ts index 6e16de3f..2c51159c 100644 --- a/packages/providers/src/core/AuthenticationProviderHttp/AuthenticationProviderHttp.ts +++ b/packages/providers/src/core/AuthenticationProviderHttp/AuthenticationProviderHttp.ts @@ -1,7 +1,7 @@ import { AuthenticationProvider } from '../../ports'; import axios from 'axios'; -const OAUTH_SERVER = 'auth.accounts.poap.xyz'; +const DEFAULT_OAUTH_SERVER = 'auth.accounts.poap.xyz'; export class AuthenticationProviderHttp implements AuthenticationProvider { private readonly oAuthServerDomain: string; @@ -16,7 +16,7 @@ export class AuthenticationProviderHttp implements AuthenticationProvider { ) { this.clientId = clientId; this.clientSecret = clientSecret; - this.oAuthServerDomain = oAuthServerDomain || OAUTH_SERVER; + this.oAuthServerDomain = oAuthServerDomain || DEFAULT_OAUTH_SERVER; } public async getJWT(audience: string): Promise { diff --git a/packages/providers/src/core/PoapCompass/PoapCompass.ts b/packages/providers/src/core/PoapCompass/PoapCompass.ts index ccc1d87e..6a2305d4 100644 --- a/packages/providers/src/core/PoapCompass/PoapCompass.ts +++ b/packages/providers/src/core/PoapCompass/PoapCompass.ts @@ -4,26 +4,26 @@ import { CompassProvider } from '../../ports/CompassProvider/CompassProvider'; import axios from 'axios'; // TODO: Change variable type any to a more specific type -const COMPASS_BASE_URL = 'https://compass.poap.tech/v1/graphql'; +const DEFAULT_COMPASS_BASE_URL = 'https://public.compass.poap.tech/v1/graphql'; /** * A class that implements the `CompassProvider` interface for fetching data from the Poap API. - * * @class * @implements {CompassProvider} */ export class PoapCompass implements CompassProvider { + private apiKey: string; + private baseUrl: string; + /** * Creates a new instance of the `PoapCompass` class. - * * @constructor - * @param {string} apiKey - The API key to use for requests. - * @param {HttpProvider} HttpProvider - An instance of the `HttpProvider` class for making HTTP requests. + * @param {PoapCompassConfig} config - Configuration object containing the API key and optional base URL. */ - constructor( - private apiKey: string, - private baseUrl: string = COMPASS_BASE_URL, - ) {} + constructor(config: PoapCompassConfig) { + this.apiKey = config.apiKey; + this.baseUrl = config.baseUrl || DEFAULT_COMPASS_BASE_URL; + } /** * Fetches data from the Poap GraphQL API. @@ -90,3 +90,14 @@ export class PoapCompass implements CompassProvider { } } } + +/** + * Configuration interface for the PoapCompass class. + * @interface + * @property {string} apiKey - The API key to use for requests to the Poap API. + * @property {string} [baseUrl] - Optional base URL for the Poap API. If not provided, a default will be used. + */ +export interface PoapCompassConfig { + apiKey: string; + baseUrl?: string; +} diff --git a/packages/providers/src/core/PoapDropApi/PoapDropApi.ts b/packages/providers/src/core/PoapDropApi/PoapDropApi.ts index 9e9001ce..ba5d0487 100644 --- a/packages/providers/src/core/PoapDropApi/PoapDropApi.ts +++ b/packages/providers/src/core/PoapDropApi/PoapDropApi.ts @@ -8,9 +8,9 @@ import { UpdateDropInput, } from '../../ports/DropApiProvider/Types'; import FormData from 'form-data'; -import axios from 'axios'; +import axios, { AxiosInstance } from 'axios'; -const DROP_BASE_URL = 'https://api.poap.tech'; +const DEFAULT_DROP_BASE_URL = 'https://api.poap.tech'; /** * A class that implements the `DropApiProvider` interface for interacting with the Poap Drop API. @@ -19,17 +19,23 @@ const DROP_BASE_URL = 'https://api.poap.tech'; * @implements {DropApiProvider} */ export class PoapDropApi implements DropApiProvider { + private apiKey: string; + private baseUrl: string; + private poapApi: AxiosInstance; + /** * Creates a new instance of the `PoapDropApi` class. * * @constructor - * @param {string} apiKey - The API key to use for requests. - * @param {HttpProvider} HttpProvider - An instance of the `HttpProvider` class for making HTTP requests. + * @param {PoapDropApiConfig} config - Configuration object containing the API key and optional base URL. */ - constructor( - private apiKey: string, - private baseUrl: string = DROP_BASE_URL, - ) {} + constructor(config: PoapDropApiConfig) { + this.apiKey = config.apiKey; + this.baseUrl = config.baseUrl || DEFAULT_DROP_BASE_URL; + this.poapApi = axios.create({ + timeout: 10000, // 10 seconds + }); + } /** * Creates a new drop on the Poap Drop API. @@ -62,6 +68,7 @@ export class PoapDropApi implements DropApiProvider { }, }); } + /** * Updates an existing drop on the Poap Drop API. * @@ -98,7 +105,7 @@ export class PoapDropApi implements DropApiProvider { }; return ( - await axios(url, { + await this.poapApi(url, { method: options.method, data: options.body, headers: headersWithApiKey, @@ -106,3 +113,15 @@ export class PoapDropApi implements DropApiProvider { ).data; } } + +/** + * Configuration interface for the PoapDropApi class. + * + * @interface + * @property {string} apiKey - The API key to use for requests. + * @property {string} [baseUrl] - Optional base URL to override the default one. + */ +export interface PoapDropApiConfig { + apiKey: string; + baseUrl?: string; +} diff --git a/packages/providers/src/core/PoapMomentsApi/PoapMomentsApi.ts b/packages/providers/src/core/PoapMomentsApi/PoapMomentsApi.ts index 6d5a02a6..8837a417 100644 --- a/packages/providers/src/core/PoapMomentsApi/PoapMomentsApi.ts +++ b/packages/providers/src/core/PoapMomentsApi/PoapMomentsApi.ts @@ -6,7 +6,7 @@ import { AuthenticationProvider } from '../../ports'; import axios, { AxiosError } from 'axios'; import { MediaStatus } from './Types/MediaStatus'; -const MOMENTS_BASE_URL = 'https://moments.poap.tech'; +const DEFAULT_MOMENTS_BASE_URL = 'https://moments.poap.tech'; /** * PoapMomentsApi class provides methods to interact with the POAP Moments API @@ -26,7 +26,7 @@ export class PoapMomentsApi implements MomentsApiProvider { baseUrl?: string; authenticationProvider?: AuthenticationProvider; }) { - this.baseUrl = params.baseUrl ?? MOMENTS_BASE_URL; + this.baseUrl = params.baseUrl ?? DEFAULT_MOMENTS_BASE_URL; this.authenticationProvider = params.authenticationProvider; } diff --git a/packages/providers/src/core/PoapTokenApi/PoapTokenApi.ts b/packages/providers/src/core/PoapTokenApi/PoapTokenApi.ts new file mode 100644 index 00000000..d0747817 --- /dev/null +++ b/packages/providers/src/core/PoapTokenApi/PoapTokenApi.ts @@ -0,0 +1,151 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { MissingAuthenticationProviderError } from './../../ports/AuthenticationProvider/errors/MissingAuthenticationProviderError'; +import { + PostMintCodeResponse, + MintStatusResponse, + GetMintCodeResponse, +} from './../../ports/TokensApiProvider/Types/response'; +import { MintCodeInput } from './../../ports/TokensApiProvider/Types/input'; +import { AuthenticationProvider } from './../../ports/AuthenticationProvider/AuthenticationProvider'; +import { TokensApiProvider } from './../../ports/TokensApiProvider/TokensApiProvider'; +import axios, { AxiosInstance } from 'axios'; + +const DEFAULT_DROP_BASE_URL = 'https://api.poap.tech'; + +/** + * Represents the main interface to interact with the Poap Drop API. + * + * @export + * @class PoapTokenApi + * @implements {TokensApiProvider} + */ +export class PoapTokenApi implements TokensApiProvider { + private apiKey: string; + private baseUrl: string; + private authenticationProvider?: AuthenticationProvider; + private poapApi: AxiosInstance; + /** + * Constructs a new instance of the `PoapTokenApi` class. + * + * @constructor + * @param {PoapTokenApiOptions} options - Configuration options for the API. + * @param {string} options.apiKey - The API key for authenticating requests. + * @param {string} [options.baseUrl=DEFAULT_DROP_BASE_URL] - The base URL for the API. + * @param {AuthenticationProvider} [options.authenticationProvider] - Optional provider for JWT authentication. + */ + constructor({ + apiKey, + baseUrl = DEFAULT_DROP_BASE_URL, + authenticationProvider, + }: PoapTokenApiOptions) { + this.apiKey = apiKey; + this.baseUrl = baseUrl; + this.authenticationProvider = authenticationProvider; + this.poapApi = axios.create({ + timeout: 10000, // 10 seconds + }); + } + + /** + * Retrieves the mint code details. + * + * @param {string} code - The unique QR hash for the mint. + * @returns {Promise} Details of the mint code. + */ + async getMintCode(code: string): Promise { + return await this.secureFetch( + `${this.baseUrl}/actions/claim-qr?qr_hash=${code}`, + { + method: 'GET', + headers: {}, + }, + ); + } + + /** + * Posts a new mint code to the API. + * + * @param {MintCodeInput} input - The input data for the mint code. + * @returns {Promise} Response from the mint code creation. + */ + async postMintCode(input: MintCodeInput): Promise { + return await this.secureFetch( + `${this.baseUrl}/actions/claim-qr`, + { + method: 'POST', + body: input, + headers: {}, + }, + ); + } + + /** + * Checks the status of a mint by its unique identifier. + * + * @param {string} uid - The unique identifier for the mint. + * @returns {Promise} Status details of the mint. + */ + async mintStatus(uid: string): Promise { + return await this.secureFetch( + `${this.baseUrl}/queue-message/${uid}`, + { + method: 'GET', + headers: {}, + }, + ); + } + + /** + * Sends a secure HTTP request to the Poap API with proper headers. + * + * @private + * @template R - Type of the expected response data. + * @param {string} url - The complete URL for the HTTP request. + * @param {any} options - Configuration options for the HTTP request. + * @returns {Promise} A promise that resolves with the parsed API response. + */ + private async secureFetch(url: string, options: any): Promise { + const headersWithApiKey = { + ...options.headers, + 'x-api-key': this.apiKey, + Authorization: await this.getAuthorizationToken(), + }; + + return ( + await this.poapApi(url, { + method: options.method, + data: options.body, + headers: headersWithApiKey, + }) + ).data; + } + + /** + * Retrieves the authorization token for making authenticated requests. + * + * @private + * @throws {MissingAuthenticationProviderError} If no authentication provider is provided. + * @returns {Promise} The bearer token for authentication. + */ + private async getAuthorizationToken(): Promise { + if (!this.authenticationProvider) { + throw new MissingAuthenticationProviderError(); + } + return `Bearer ${await this.authenticationProvider.getJWT(this.baseUrl)}`; + } +} + +/** + * Represents the configuration options required when instantiating the `PoapTokenApi` class. + * + * @export + * @interface PoapTokenApiOptions + * @property {string} apiKey - The API key to use for authenticating requests. + * @property {string} [baseUrl] - The base URL for the API. Defaults to 'https://api.poap.tech'. + * @property {AuthenticationProvider} [authenticationProvider] - Optional provider for JWT authentication. + */ +export interface PoapTokenApiOptions { + apiKey: string; + baseUrl?: string; + authenticationProvider?: AuthenticationProvider; +} diff --git a/packages/providers/src/core/index.ts b/packages/providers/src/core/index.ts index e4f610db..7613b3fb 100644 --- a/packages/providers/src/core/index.ts +++ b/packages/providers/src/core/index.ts @@ -2,4 +2,5 @@ export { PoapMomentsApi } from './PoapMomentsApi/PoapMomentsApi'; export { PoapDropApi } from './PoapDropApi/PoapDropApi'; export { PoapCompass } from './PoapCompass/PoapCompass'; export { AuthenticationProviderHttp } from './AuthenticationProviderHttp/AuthenticationProviderHttp'; +export { PoapTokenApi, PoapTokenApiOptions } from './PoapTokenApi/PoapTokenApi'; export { InvalidMediaError } from './PoapMomentsApi/errors/InvalidMediaError'; diff --git a/packages/providers/src/ports/AuthenticationProvider/errors/MissingAuthenticationProviderError.ts b/packages/providers/src/ports/AuthenticationProvider/errors/MissingAuthenticationProviderError.ts new file mode 100644 index 00000000..61190bee --- /dev/null +++ b/packages/providers/src/ports/AuthenticationProvider/errors/MissingAuthenticationProviderError.ts @@ -0,0 +1,5 @@ +export class MissingAuthenticationProviderError extends Error { + constructor() { + super(`An AuthenticationProvider is required for write operations`); + } +} diff --git a/packages/providers/src/ports/TokensApiProvider/TokensApiProvider.ts b/packages/providers/src/ports/TokensApiProvider/TokensApiProvider.ts new file mode 100644 index 00000000..25605fcb --- /dev/null +++ b/packages/providers/src/ports/TokensApiProvider/TokensApiProvider.ts @@ -0,0 +1,17 @@ +import { + GetMintCodeResponse, + PostMintCodeResponse, + MintCodeInput, + MintStatusResponse, +} from './Types'; + +/** + * Provides methods for interacting with a Tokens API. + * + * @interface TokensApiProvider + */ +export interface TokensApiProvider { + getMintCode(code: string): Promise; + postMintCode(input: MintCodeInput): Promise; + mintStatus(uid: string): Promise; +} diff --git a/packages/providers/src/ports/TokensApiProvider/Types/index.ts b/packages/providers/src/ports/TokensApiProvider/Types/index.ts new file mode 100644 index 00000000..eccece42 --- /dev/null +++ b/packages/providers/src/ports/TokensApiProvider/Types/index.ts @@ -0,0 +1,2 @@ +export * from './input'; +export * from './response'; diff --git a/packages/providers/src/ports/TokensApiProvider/Types/input.ts b/packages/providers/src/ports/TokensApiProvider/Types/input.ts new file mode 100644 index 00000000..a7c2bfb5 --- /dev/null +++ b/packages/providers/src/ports/TokensApiProvider/Types/input.ts @@ -0,0 +1,6 @@ +export interface MintCodeInput { + address: string; + qr_hash: string; + secret: string; + sendEmail: boolean; +} diff --git a/packages/providers/src/ports/TokensApiProvider/Types/response.ts b/packages/providers/src/ports/TokensApiProvider/Types/response.ts new file mode 100644 index 00000000..f99ce711 --- /dev/null +++ b/packages/providers/src/ports/TokensApiProvider/Types/response.ts @@ -0,0 +1,66 @@ +import { MintingStatus } from '@poap-xyz/utils'; + +export interface GetMintCodeResponse { + id: number; + qr_hash: string; + tx_hash: string; + event_id: number; + beneficiary: string; + user_input: string; + signer: string; + claimed: boolean; + claimed_date: string; + created_date: string; + is_active: boolean; + secret: string; + event: Drop; + tx_status: string; + result: { + token: number; + }; +} + +export interface PostMintCodeResponse { + id: number; + qr_hash: string; + queue_uid: string; + event_id: number; + beneficiary: string; + user_input: string; + signer: string; + claimed: boolean; + claimed_date: string; + created_date: string; + is_active: boolean; + event: Drop; +} + +export interface MintStatusResponse { + uid: number; + operation: string; + status: MintingStatus; + result: { + tx_hash: string; + error: string; + } | null; +} + +interface Drop { + id: number; + fancy_id: string; + name: string; + description: string; + city: string; + country: string; + event_url: string; + image_url: string; + animation_url: string; + year: number; + start_date: string; + end_date: string; + expiry_date: string; + from_admin: boolean; + virtual_event: boolean; + event_template_id?: number | null; + private_event: boolean; +} diff --git a/packages/providers/src/ports/TokensApiProvider/index.ts b/packages/providers/src/ports/TokensApiProvider/index.ts new file mode 100644 index 00000000..350af322 --- /dev/null +++ b/packages/providers/src/ports/TokensApiProvider/index.ts @@ -0,0 +1,7 @@ +export { TokensApiProvider } from './TokensApiProvider'; +export { + GetMintCodeResponse, + PostMintCodeResponse, + MintCodeInput, + MintStatusResponse, +} from './Types'; diff --git a/packages/providers/src/ports/index.ts b/packages/providers/src/ports/index.ts index f82dfd2e..422f5555 100644 --- a/packages/providers/src/ports/index.ts +++ b/packages/providers/src/ports/index.ts @@ -1,5 +1,13 @@ export * from './MomentsApiProvider/MomentsApiProvider'; export * from './DropApiProvider/DropApiProvider'; +export { + TokensApiProvider, + GetMintCodeResponse, + PostMintCodeResponse, + MintCodeInput, + MintStatusResponse, +} from './TokensApiProvider'; + export { HttpProvider } from './HttpProvider/HttpProvider'; export { CompassProvider } from './CompassProvider/CompassProvider'; export { AuthenticationProvider } from './AuthenticationProvider/AuthenticationProvider'; diff --git a/packages/providers/test/PoapCompass.spec.ts b/packages/providers/test/PoapCompass.spec.ts index 29b97c08..e84ace7e 100644 --- a/packages/providers/test/PoapCompass.spec.ts +++ b/packages/providers/test/PoapCompass.spec.ts @@ -22,7 +22,7 @@ describe('PoapCompass', () => { mockAxios.onPost().reply(200, responseData); - const poapCompass = new PoapCompass(apiKey); + const poapCompass = new PoapCompass({ apiKey }); const result = await poapCompass.request(query, variables); expect(result).toEqual(responseData); diff --git a/packages/providers/test/PoapDropApi.spec.ts b/packages/providers/test/PoapDropApi.spec.ts index a6b5f0e4..d2530c0c 100644 --- a/packages/providers/test/PoapDropApi.spec.ts +++ b/packages/providers/test/PoapDropApi.spec.ts @@ -17,7 +17,7 @@ describe('PoapDropApi', () => { mock = new MockAdapter(axios); apiKey = 'test-api-key'; baseUrl = 'https://api.poap.test'; - api = new PoapDropApi(apiKey, baseUrl); + api = new PoapDropApi({ apiKey, baseUrl }); }); afterEach(() => { diff --git a/packages/utils/package.json b/packages/utils/package.json index 39f95831..d0bd9a6a 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/utils", - "version": "0.0.38", + "version": "0.0.39", "description": "Utils module for the poap.js library", "type": "module", "main": "dist/cjs/index.cjs", @@ -24,6 +24,5 @@ "homepage": "https://github.com/poap-xyz/poap.js#readme", "scripts": { "build": "rollup -c --bundleConfigAsCjs" - }, - "stableVersion": "0.0.38" + } } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 60c4e04b..adf3092c 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,3 +1,4 @@ +export { MintingStatus } from './types/mintingStatus'; export { Chain } from './types/chain'; export { Order } from './types/filter'; export { PaginationInput } from './types/input'; @@ -6,7 +7,7 @@ export { PaginatedResult } from './types/pagination'; export { nextCursor } from './functions/nextCursor'; export { filterUndefinedProperties, - creatUndefinedOrder, + createUndefinedOrder, createFilter, creatEqFilter, creatNeqFilter, diff --git a/packages/utils/src/queries/utils.ts b/packages/utils/src/queries/utils.ts index 4ed553ef..fb9502cd 100644 --- a/packages/utils/src/queries/utils.ts +++ b/packages/utils/src/queries/utils.ts @@ -11,7 +11,7 @@ export function filterUndefinedProperties>( return filteredObj; } -export function creatUndefinedOrder( +export function createUndefinedOrder( key: string | undefined, value?: string | undefined, ): Record { diff --git a/packages/utils/src/types/mintingStatus.ts b/packages/utils/src/types/mintingStatus.ts new file mode 100644 index 00000000..434ab8d4 --- /dev/null +++ b/packages/utils/src/types/mintingStatus.ts @@ -0,0 +1,6 @@ +export enum MintingStatus { + PENDING = 'PENDING', + IN_PROCESS = 'IN_PROCESS', + FINISH = 'FINISH', + FINISH_WITH_ERROR = 'FINISH_WITH_ERROR', +} diff --git a/yarn.lock b/yarn.lock index eb02cb38..d26fdd90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -864,8 +864,8 @@ __metadata: version: 0.0.0-use.local resolution: "@poap-xyz/drops@workspace:packages/drops" dependencies: - "@poap-xyz/providers": 0.0.38 - "@poap-xyz/utils": 0.0.38 + "@poap-xyz/providers": 0.0.39 + "@poap-xyz/utils": 0.0.39 languageName: unknown linkType: soft @@ -873,8 +873,8 @@ __metadata: version: 0.0.0-use.local resolution: "@poap-xyz/moments@workspace:packages/moments" dependencies: - "@poap-xyz/providers": 0.0.38 - "@poap-xyz/utils": 0.0.38 + "@poap-xyz/providers": 0.0.39 + "@poap-xyz/utils": 0.0.39 "@types/uuid": ^9.0.2 uuid: ^9.0.0 languageName: unknown @@ -890,15 +890,16 @@ __metadata: version: 0.0.0-use.local resolution: "@poap-xyz/poaps@workspace:packages/poaps" dependencies: - "@poap-xyz/providers": 0.0.38 - "@poap-xyz/utils": 0.0.38 + "@poap-xyz/providers": 0.0.39 + "@poap-xyz/utils": 0.0.39 languageName: unknown linkType: soft -"@poap-xyz/providers@*, @poap-xyz/providers@0.0.38, @poap-xyz/providers@workspace:packages/providers": +"@poap-xyz/providers@*, @poap-xyz/providers@0.0.39, @poap-xyz/providers@workspace:packages/providers": version: 0.0.0-use.local resolution: "@poap-xyz/providers@workspace:packages/providers" dependencies: + "@poap-xyz/utils": 0.0.39 axios-mock-adapter: ^1.21.4 peerDependencies: axios: ^1.3.5 @@ -906,7 +907,7 @@ __metadata: languageName: unknown linkType: soft -"@poap-xyz/utils@*, @poap-xyz/utils@0.0.38, @poap-xyz/utils@workspace:packages/utils": +"@poap-xyz/utils@*, @poap-xyz/utils@0.0.39, @poap-xyz/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@poap-xyz/utils@workspace:packages/utils" languageName: unknown @@ -1783,6 +1784,7 @@ __metadata: "@types/node": ^18.16.0 "@types/node-fetch": ^2.6.3 axios: ^1.3.5 + dotenv: ^16.0.3 form-data: ^4.0.0 stream: ^0.0.2 ts-node: ^10.4.0 @@ -1821,6 +1823,7 @@ __metadata: "@poap-xyz/utils": "*" "@types/node": ^18.16.0 axios: ^1.3.5 + dotenv: ^16.0.3 form-data: ^4.0.0 perf_hooks: ^0.0.1 stream: ^0.0.2