diff --git a/package.json b/package.json index 76af0bc..850a9d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/poap-family", - "version": "1.19.0", + "version": "1.20.0", "author": { "name": "POAP", "url": "https://poap.xyz" diff --git a/src/hooks/useAddressTokens.ts b/src/hooks/useAddressTokens.ts index 122e8d8..918db75 100644 --- a/src/hooks/useAddressTokens.ts +++ b/src/hooks/useAddressTokens.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react' -import { scanAddress } from 'loaders/poap' +import { fetchCollectorPOAPs } from 'loaders/collector' import { POAP } from 'models/poap' function useAddressTokens(address: string): { @@ -16,7 +16,7 @@ function useAddressTokens(address: string): { () => { const controller = new AbortController() setLoading(true) - scanAddress(address, controller.signal).then( + fetchCollectorPOAPs(address, controller.signal).then( (foundPOAPs) => { setLoading(false) setPOAPs(foundPOAPs) diff --git a/src/hooks/useEventInCommon.ts b/src/hooks/useEventInCommon.ts index 99554b6..6337469 100644 --- a/src/hooks/useEventInCommon.ts +++ b/src/hooks/useEventInCommon.ts @@ -1,12 +1,11 @@ import { useCallback, useEffect, useState } from 'react' import { AbortedError } from 'models/error' import { Drop } from 'models/drop' -import { POAP } from 'models/poap' import { CountProgress, DownloadProgress } from 'models/http' import { InCommon } from 'models/api' import { filterInCommon } from 'models/in-common' import { getInCommonEventsWithEvents, getInCommonEventsWithProgress } from 'loaders/api' -import { scanAddress } from 'loaders/poap' +import { fetchCollectorDrops } from 'loaders/collector' function useEventInCommon( eventId: number, @@ -82,38 +81,34 @@ function useEventInCommon( const fetchAddressInCommon = useCallback( async (address: string, abortSignal: AbortSignal) => { - let tokens: POAP[] + let addressDrops: Drop[] try { - tokens = await scanAddress(address, abortSignal) + addressDrops = await fetchCollectorDrops(address, abortSignal) } catch (err: unknown) { addError(address, err) return } - for (const token of tokens) { - const event = token.event - if (event != null) { - const eventId = event.id - setInCommon((prevInCommon) => { - if (prevInCommon == null) { - return { - [eventId]: [address], - } + for (const addressDrop of addressDrops) { + const addressDropId = addressDrop.id + setInCommon((prevInCommon) => { + if (prevInCommon == null) { + return { + [addressDropId]: [address], } - if (eventId in prevInCommon) { - if (!prevInCommon[eventId].includes(address)) { - prevInCommon[eventId].push(address) - } - } else { - prevInCommon[eventId] = [address] + } + if (addressDropId in prevInCommon) { + if (!prevInCommon[addressDropId].includes(address)) { + prevInCommon[addressDropId].push(address) } - return prevInCommon - }) - setEvents((prevEvents) => ({ ...prevEvents, [eventId]: event })) - } else { - const error = new Error(`Could not find POAP ${token.id}`) - addError(address, error) - return - } + } else { + prevInCommon[addressDropId] = [address] + } + return prevInCommon + }) + setEvents((prevEvents) => ({ + ...prevEvents, + [addressDropId]: addressDrop, + })) } setLoadedOwners((prevLoadedCount) => prevLoadedCount + 1) }, diff --git a/src/hooks/useEventsInCommon.ts b/src/hooks/useEventsInCommon.ts index 2529334..8cfe3df 100644 --- a/src/hooks/useEventsInCommon.ts +++ b/src/hooks/useEventsInCommon.ts @@ -1,8 +1,7 @@ import { useCallback, useEffect, useState } from 'react' import { getInCommonEventsWithEvents, getInCommonEventsWithProgress } from 'loaders/api' -import { scanAddress } from 'loaders/poap' +import { fetchCollectorDrops } from 'loaders/collector' import { AbortedError } from 'models/error' -import { POAP } from 'models/poap' import { Drop } from 'models/drop' import { CountProgress, DownloadProgress } from 'models/http' import { EventsInCommon, InCommon } from 'models/api' @@ -414,9 +413,9 @@ function useEventsInCommon( const processEventAddress = useCallback( async (eventId: number, address: string, abortSignal: AbortSignal) => { removeError(eventId, address) - let ownerTokens: POAP[] + let ownerDrops: Drop[] try { - ownerTokens = await scanAddress(address, abortSignal) + ownerDrops = await fetchCollectorDrops(address, abortSignal) } catch (err: unknown) { if (!(err instanceof AbortedError)) { addError( @@ -429,19 +428,10 @@ function useEventsInCommon( } return } - for (const ownerToken of ownerTokens) { - if (ownerToken.event) { - updateInCommonEvent(eventId, address, ownerToken.event) - } else { - addError( - eventId, - address, - new Error(`Missing token drop for ${address}`) - ) - return - } - } incrLoadedCount(eventId) + for (const ownerDrop of ownerDrops) { + updateInCommonEvent(eventId, address, ownerDrop) + } }, [] ) diff --git a/src/hooks/useEventsOwnersAndMetrics.ts b/src/hooks/useEventsOwnersAndMetrics.ts index 7c819a1..69a4102 100644 --- a/src/hooks/useEventsOwnersAndMetrics.ts +++ b/src/hooks/useEventsOwnersAndMetrics.ts @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react' import { filterInvalidOwners } from 'models/address' import { AbortedError } from 'models/error' import { EventAndOwners, InCommon } from 'models/api' -import { fetchPOAPs } from 'loaders/poap' +import { fetchDropCollectors } from 'loaders/collector' import { getEventAndOwners, getEventMetrics, @@ -156,11 +156,11 @@ function useEventsOwnersAndMetrics(eventIds: number[], expiryDates: Record { removeError(eventId) addLoading(eventId) - let eventOwnerTokensResult - let eventMetricsResult + let eventCollectorsResult: PromiseSettledResult>> + let eventMetricsResult: PromiseSettledResult>> try { - [eventOwnerTokensResult, eventMetricsResult] = await Promise.allSettled([ - fetchPOAPs(eventId, abortSignal), + [eventCollectorsResult, eventMetricsResult] = await Promise.allSettled([ + fetchDropCollectors([eventId], abortSignal), getEventMetrics(eventId, abortSignal, force), ]) } catch (err: unknown) { @@ -176,18 +176,18 @@ function useEventsOwnersAndMetrics(eventIds: number[], expiryDates: Record token.owner) + eventCollectorsResult.value ) } else { - if (!(eventOwnerTokensResult.reason instanceof AbortedError)) { - console.error(eventOwnerTokensResult.reason) + if (!(eventCollectorsResult.reason instanceof AbortedError)) { + console.error(eventCollectorsResult.reason) addError( eventId, - new Error(`Tokens for drop '${eventId}' failed to fetch`, { - cause: eventOwnerTokensResult.reason, + new Error(`Collectors for drop '${eventId}' failed to fetch`, { + cause: eventCollectorsResult.reason, }) ) } diff --git a/src/loaders/collection.ts b/src/loaders/collection.ts index 408c052..4daeddf 100644 --- a/src/loaders/collection.ts +++ b/src/loaders/collection.ts @@ -57,7 +57,6 @@ export async function findEventsCollections( }, 'offset', limit, - undefined, abortSignal ), eventIds.length < 2 ? Promise.resolve([]) : queryAllCompass( @@ -94,7 +93,6 @@ export async function findEventsCollections( }, 'offset', limit, - undefined, abortSignal ) ]) diff --git a/src/loaders/collector.ts b/src/loaders/collector.ts new file mode 100644 index 0000000..624d849 --- /dev/null +++ b/src/loaders/collector.ts @@ -0,0 +1,153 @@ +import { IGNORED_OWNERS } from 'models/address' +import { DEFAULT_COLLECTOR_LIMIT, parseColectorDrop, parseCollector } from 'models/collector' +import { DEFAULT_DROP_LIMIT } from 'models/event' +import { DEFAULT_COMPASS_LIMIT } from 'models/compass' +import { DEFAULT_POAP_LIMIT, parsePOAP, POAP } from 'models/poap' +import { Drop } from 'models/drop' +import { queryAllCompass } from 'loaders/compass' + +export async function fetchDropCollectors( + dropIds: number[], + abortSignal?: AbortSignal, + limit = Math.min(DEFAULT_COLLECTOR_LIMIT, DEFAULT_COMPASS_LIMIT), +): Promise { + return await queryAllCompass( + `poaps`, + parseCollector, + ` + query FetchDropCollectors( + $dropIds: [bigint!] + $offset: Int! + $limit: Int! + ) { + poaps( + where: { + drop_id: { _in: $dropIds } + collector_address: { + _nin: ["${IGNORED_OWNERS.join('", "')}"] + } + } + distinct_on: collector_address + offset: $offset + limit: $limit + ) { + collector_address + } + } + `, + { + dropIds, + limit, + }, + 'offset', + limit, + abortSignal + ) +} + +export async function fetchCollectorDrops( + address: string, + abortSignal?: AbortSignal, + limit = Math.min(DEFAULT_DROP_LIMIT, DEFAULT_COMPASS_LIMIT), +): Promise { + return await queryAllCompass( + `poaps`, + (data: unknown) => parseColectorDrop(data, /*includeDescription*/false), + ` + query FetchCollectorDrops( + $address: String! + $offset: Int! + $limit: Int! + ) { + poaps( + where: { + collector_address: { _eq: $address } + } + distinct_on: drop_id + offset: $offset + limit: $limit + ) { + drop { + id + name + description + image_url + city + country + start_date + end_date + expiry_date + drop_image { + gateways { + type + url + } + } + } + } + } + + `, + { + address: address.toLowerCase(), + limit, + }, + 'offset', + limit, + abortSignal, + ) +} + +export async function fetchCollectorPOAPs( + address: string, + abortSignal?: AbortSignal, + limit = Math.min(DEFAULT_POAP_LIMIT, DEFAULT_COMPASS_LIMIT), +): Promise { + return await queryAllCompass( + 'poaps', + parsePOAP, + ` + query FetchCollectorPOAPs( + $address: String! + $offset: Int! + $limit: Int! + ) { + poaps( + where: { + collector_address: { _eq: $address } + } + offset: $offset + limit: $limit + ) { + id + collector_address + minted_on + drop { + id + name + description + image_url + city + country + start_date + end_date + expiry_date + drop_image { + gateways { + type + url + } + } + } + } + } + `, + { + address: address.toLowerCase(), + limit, + }, + 'offset', + limit, + abortSignal + ) +} diff --git a/src/loaders/compass.ts b/src/loaders/compass.ts index 31d15d2..a7a9ae6 100644 --- a/src/loaders/compass.ts +++ b/src/loaders/compass.ts @@ -185,7 +185,6 @@ export async function queryAllCompass( variables: Record, offsetKey: string, limit: number, - total: number, abortSignal?: AbortSignal ): Promise { let results: T[] = [] @@ -216,9 +215,7 @@ export async function queryAllCompass( results = [...results, ...pageResults] pageCount = pageResults.length - } while ( - (total && pageCount >= total) || (!total && pageCount > 0) - ) + } while (pageCount > 0) return results } diff --git a/src/loaders/drop.ts b/src/loaders/drop.ts index 199e4ef..f441cb9 100644 --- a/src/loaders/drop.ts +++ b/src/loaders/drop.ts @@ -1,8 +1,12 @@ import { DEFAULT_COMPASS_LIMIT } from 'models/compass' import { DEFAULT_DROP_LIMIT, DEFAULT_SEARCH_LIMIT } from 'models/event' import { Drop, parseDrop } from 'models/drop' -import { queryAggregateCountCompass, queryFirstCompass, queryManyCompass } from 'loaders/compass' import { HttpError } from 'models/error' +import { + queryAggregateCountCompass, + queryFirstCompass, + queryManyCompass, +} from 'loaders/compass' export async function searchDrops( query: string, diff --git a/src/loaders/event.ts b/src/loaders/event.ts index 0c0e923..be9ccef 100644 --- a/src/loaders/event.ts +++ b/src/loaders/event.ts @@ -1,10 +1,9 @@ -import { filterInvalidOwners } from 'models/address' import { parseEventIds } from 'models/event' import { Drop } from 'models/drop' import { HttpError } from 'models/error' import { getEventAndOwners, getEventMetrics, getEvents } from 'loaders/api' -import { fetchPOAPs } from 'loaders/poap' import { fetchDrop, fetchDropsOrErrors } from 'loaders/drop' +import { fetchDropCollectors } from 'loaders/collector' export async function eventLoader({ params, request }) { const force = new URL(request.url).searchParams.get('force') === 'true' @@ -38,22 +37,20 @@ export async function eventLoader({ params, request }) { }) } - const [tokensSettled, metricsSettled] = await Promise.allSettled([ - fetchPOAPs(params.eventId), + const [collectorsSettled, metricsSettled] = await Promise.allSettled([ + fetchDropCollectors([params.eventId]), getEventMetrics(params.eventId, null, /*refresh*/force), ]) - if (tokensSettled.status === 'rejected') { + if (collectorsSettled.status === 'rejected') { throw new Response('', { status: 503, - statusText: 'Drop could not be fetch from POAP API', + statusText: + `Drop collectors could not be fetched: ${collectorsSettled.reason}`, }) } - const tokens = tokensSettled.value - const owners = filterInvalidOwners( - tokens.map((token) => token.owner) - ) + const owners = collectorsSettled.value return { event, diff --git a/src/loaders/poap.ts b/src/loaders/poap.ts deleted file mode 100644 index 2e9afcf..0000000 --- a/src/loaders/poap.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { AbortedError, HttpError } from 'models/error' -import { - parsePOAP, - POAP_API_URL, - POAP_API_KEY, - POAP_FETCH_RETRIES, - POAP, -} from 'models/poap' - -/** - * Fetch all POAP for given drop. - */ -export async function fetchPOAPs(eventId: number, abortSignal?: AbortSignal, limit: number = 100): Promise { - let tokens = [] - let results = { total: 0, offset: 0, limit, tokens: [] } - let retries = 0 - - do { - let response: Response - try { - response = await fetch( - `${POAP_API_URL}/event/${eventId}/poaps` + - `?offset=${results.offset}` + - `&limit=${results.limit}`, - { - signal: abortSignal instanceof AbortSignal ? abortSignal : null, - headers: { - 'x-api-key': POAP_API_KEY, - }, - } - ) - } catch (err: unknown) { - if (err instanceof Error && err.name === 'AbortError') { - throw new AbortedError( - `Fetch POAPs for '${eventId}' from ${results.offset} aborted` - ) - } - - console.error(err) - - if (retries >= POAP_FETCH_RETRIES) { - throw new Error( - `Fetch POAPs for '${eventId}' from ${results.offset} ` + - `failed: ${err}` - ) - } - - retries++ - continue - } - - if (response.status === 404) { - return tokens - } - - if (response.status !== 200) { - if (retries >= POAP_FETCH_RETRIES) { - let message: string | undefined - try { - const data = await response.json() - if (typeof data === 'object' && 'message' in data) { - message = data.message - } - } catch (err: unknown) { - console.error(err) - } - if (message) { - throw new HttpError( - `Fetch POAPs for '${eventId}' from ${results.offset} ` + - `failed (status ${response.status}): ${message}`, - { status: response.status } - ) - } - throw new HttpError( - `Fetch POAPs for '${eventId}' from ${results.offset} ` + - `failed (status ${response.status})`, - { status: response.status } - ) - } - - retries++ - continue - } - - retries = 0 - results = await response.json() - - if ( - results == null || - typeof results !== 'object' || - !('tokens' in results) || - !Array.isArray(results.tokens) - ) { - throw new Error( - `Invalid POAP response for drop '${eventId}' from ${results.offset}` - ) - } - - tokens = [ - ...tokens, - ...results.tokens.map((token) => parsePOAP(token)), - ] - - // Finish on empty page. - if (results.tokens.length === 0) { - break - } - - // Go to next page. - results.offset += results.limit - } while ( - tokens.length < results.total || - results.total === 0 - ) - - return tokens -} - -/** - * Fetch all POAP for given address. - */ -export async function scanAddress(address: string, abortSignal: AbortSignal): Promise { - let retries = 0 - let message: string | undefined - - do { - let response - try { - response = await fetch(`${POAP_API_URL}/actions/scan/${address}`, { - signal: abortSignal instanceof AbortSignal ? abortSignal : null, - headers: { - 'x-api-key': POAP_API_KEY, - }, - }) - } catch (err: unknown) { - if (err instanceof Error && err.name === 'AbortError') { - throw new AbortedError(`Scan address aborted`) - } - - console.error(err) - - if (retries >= POAP_FETCH_RETRIES) { - message = String(err) - throw new Error(`Scan address failed: ${message}`) - } - - retries++ - message = undefined - continue - } - - if (response.status !== 200) { - if (retries >= POAP_FETCH_RETRIES) { - try { - const data = await response.json() - if (typeof data === 'object' && 'message' in data) { - message = data.message - } - } catch (err: unknown) { - console.error(err) - } - - if (message) { - throw new HttpError( - `Scan address failed (status ${response.status}): ${message}`, - { status: response.status } - ) - } - - throw new HttpError( - `Scan address failed (status ${response.status})`, - { status: response.status } - ) - } - - retries++ - message = undefined - continue - } - - const tokens: unknown = await response.json() - - if (!tokens || !Array.isArray(tokens)) { - throw new Error(`Invalid POAP response for scan`) - } - - return tokens.map((token) => parsePOAP(token)) - } while ( - retries <= POAP_FETCH_RETRIES - ) - - // eslint-disable-next-line no-unreachable - throw new Error( - `Scan address failed (${retries} retries)${message ? `: ${message}` : ''}` - ) -} diff --git a/src/models/collector.ts b/src/models/collector.ts new file mode 100644 index 0000000..75e42e5 --- /dev/null +++ b/src/models/collector.ts @@ -0,0 +1,34 @@ +import { Drop, parseDrop } from 'models/drop' + +export const DEFAULT_COLLECTOR_LIMIT = 100 + +export function parseCollector(data: unknown): string { + if ( + !data || + typeof data !== 'object' || + !('collector_address' in data) || + !data.collector_address || + typeof data.collector_address !== 'string' + ) { + throw new Error('Invalid collector') + } + + return data.collector_address +} + +export function parseColectorDrop( + data: unknown, + includeDescription: boolean, +): Drop { + if ( + !data || + typeof data !== 'object' || + !('drop' in data) || + !data.drop || + typeof data.drop !== 'object' + ) { + throw new Error('Invalid collector drop') + } + + return parseDrop(data.drop, includeDescription) +} diff --git a/src/models/poap.ts b/src/models/poap.ts index e8be971..453792b 100644 --- a/src/models/poap.ts +++ b/src/models/poap.ts @@ -1,5 +1,6 @@ import { Drop, parseDrop } from 'models/drop' import { getRandomInt } from 'utils/number' +import { getAddress } from 'utils/ethereum' export const POAP_API_URL = process.env.REACT_APP_POAP_API_URL ?? 'https://api.poap.tech' export const POAP_API_KEY = process.env.REACT_APP_POAP_API_KEY @@ -12,10 +13,12 @@ export const POAP_COLLECTIONS_URL = 'https://collections.poap.xyz' export const POAP_FETCH_RETRIES = 5 export const POAP_PROFILE_LIMIT = 20 +export const DEFAULT_POAP_LIMIT = 100 + export interface POAP { id: string owner: string - created?: Date + created: Date event?: Drop } @@ -49,7 +52,7 @@ export function parsePOAP(token: unknown): POAP { throw new Error('Invalid POAP ID') } - let owner: string | undefined + let collectorAddress: string | undefined if ( 'owner' in token && token.owner != null @@ -60,32 +63,59 @@ export function parsePOAP(token: unknown): POAP { token.owner.id != null && typeof token.owner.id === 'string' ) { - owner = token.owner.id + collectorAddress = getAddress(token.owner.id) } else if (typeof token.owner === 'string') { - owner = token.owner + collectorAddress = getAddress(token.owner) } + } else if ( + 'collector_address' in token && + token.collector_address != null && + typeof token.collector_address === 'string' + ) { + collectorAddress = getAddress(token.collector_address) } - if (owner == null) { - throw new Error('Invalid POAP owner') + if (collectorAddress == null) { + throw new Error('Invalid POAP collector') + } + + let mintedOn: Date | undefined + if ( + 'created' in token && + token.created != null && ( + typeof token.created === 'number' || + typeof token.created === 'string' + ) + ) { + mintedOn = new Date(token.created) + } else if ( + 'minted_on' in token && + token.minted_on != null && + typeof token.minted_on === 'number' + ) { + mintedOn = new Date(token.minted_on * 1000) + } + if (mintedOn == null) { + throw new Error('Invalid POAP minted date') + } + + let drop: Drop | undefined + if ( + 'event' in token && + token.event != null + ) { + drop = parseDrop(token.event, /*includeDescription*/false) + } else if ( + 'drop' in token && + token.drop != null + ) { + drop = parseDrop(token.drop, /*includeDescription*/false) } return { id: tokenId, - owner, - created: - 'created' in token && - token.created != null && - ( - typeof token.created === 'number' || - typeof token.created === 'string' - ) - ? new Date(token.created) - : undefined, - event: - 'event' in token && - token.event != null - ? parseDrop(token.event, /*includeDescription*/false) - : undefined, + owner: collectorAddress, + created: mintedOn, + event: drop, } } diff --git a/src/pages/Addresses.tsx b/src/pages/Addresses.tsx index 48f9a7b..2a5a811 100644 --- a/src/pages/Addresses.tsx +++ b/src/pages/Addresses.tsx @@ -9,9 +9,8 @@ import { InCommon } from 'models/api' import { EnsByAddress } from 'models/ethereum' import { HTMLContext } from 'stores/html' import { ResolverEnsContext, ReverseEnsContext } from 'stores/ethereum' -import { fetchPOAPs, scanAddress } from 'loaders/poap' import { getEventsOwners } from 'loaders/api' -import { uniq } from 'utils/array' +import { fetchCollectorDrops, fetchDropCollectors } from 'loaders/collector' import AddressesForm from 'components/AddressesForm' import Card from 'components/Card' import CenterPage from 'components/CenterPage' @@ -188,19 +187,11 @@ function Addresses() { async (address: string, controller: AbortController) => { enableLoadingByAddress(address) try { - const tokens = await scanAddress(address, controller.signal) + const addressDrops = await fetchCollectorDrops(address, controller.signal) incrLoadedCount() - setPower(address, tokens.length) - for (const token of tokens) { - const event = token.event - if (event == null) { - setErrorByAddress( - address, - new Error(`Could not find POAP ${token.id}`) - ) - continue - } - updateAddressEvent(address, event) + setPower(address, addressDrops.length) + for (const addressDrop of addressDrops) { + updateAddressEvent(address, addressDrop) } disableLoadingByAddress(address) } catch (err: unknown) { @@ -329,29 +320,35 @@ function Addresses() { } else if (searchEvents.length > 0) { setLoadingEventsOwners(true) if (force) { - let promise = new Promise((r) => r(undefined)) - for (const searchEventId of searchEvents) { - const controller = new AbortController() - promise = promise.then(() => { - fetchPOAPs(searchEventId, controller.signal).then( - (tokens) => { - setLoadingEventsOwners(false) - const owners = uniq(tokens.map((token) => token.owner)) - const addresses = owners.map((owner) => parseAddress(owner)) - updateAddresses(addresses) - }, - (err) => { - setLoadingEventsOwners(false) - addError( - new Error(`Cannot load drop ${searchEventId}`, { - cause: err, - }) - ) - } + const controller = new AbortController() + fetchDropCollectors(searchEvents, controller.signal).then( + (collectors) => { + let addresses: ParsedAddress[] | undefined + try { + addresses = collectors.map((owner) => parseAddress(owner)) + } catch (err: unknown) { + setLoadingEventsOwners(false) + addError( + new Error('Cannot parse collectors', { + cause: err, + }) + ) + return + } + + setLoadingEventsOwners(false) + updateAddresses(addresses) + }, + (err) => { + setLoadingEventsOwners(false) + addError( + new Error(`Cannot load drops ${searchEvents.join(', ')}`, { + cause: err, + }) ) - }) - controllers.push(controller) - } + } + ) + controllers.push(controller) } else { const controller = new AbortController() getEventsOwners(searchEvents, controller.signal).then( diff --git a/src/utils/ethereum.ts b/src/utils/ethereum.ts index ebccd91..7bafc9e 100644 --- a/src/utils/ethereum.ts +++ b/src/utils/ethereum.ts @@ -1,4 +1,4 @@ -import { getAddress } from '@ethersproject/address' +import { getAddress as ethereumGetAddress } from '@ethersproject/address' export function normalizeAddress(address: string): string | null { try { @@ -7,3 +7,7 @@ export function normalizeAddress(address: string): string | null { return null } } + +export function getAddress(address: string): string { + return ethereumGetAddress(address) +}