diff --git a/src/backends/zdk-alpha/ZDKAlphaDataSource.ts b/src/backends/zdk-alpha/ZDKAlphaDataSource.ts index 5146351..b9077dc 100644 --- a/src/backends/zdk-alpha/ZDKAlphaDataSource.ts +++ b/src/backends/zdk-alpha/ZDKAlphaDataSource.ts @@ -6,40 +6,18 @@ import { MarketType as ZDKMarketType, Network, SortDirection as ZDKSortDirection, - V1MarketEntityStatus, - V3AskStatus, - V2AuctionStatus, - PriceSummaryFragment, - TokenSortKey, TokensQueryInput, TokensQueryFilter, TokenSortInput, - MarketDetailsFragment, - MarketInfoFragment, - EventType, - EventInfoFragment, - V1MarketEventType, - V2AuctionEventType, - V3AskEventType, } from '@zoralabs/zdk-alpha/dist/queries/queries-sdk'; -import { MarketType, NFTQuery, SortDirection, SortField } from '../../types/NFTQuery'; -import { - AUCTION_EVENT_TYPES, - AUCTION_SOURCE_TYPES, - FIXED_PRICE_EVENT_TYPES, - FIXED_PRICE_MARKET_SOURCES, - FIXED_SIDE_TYPES, - MarketModule, - MARKET_INFO_STATUSES, - MARKET_TYPES, - MEDIA_SOURCES, - NFTIdentifier, - NormalizedEvent, - TOKEN_TRANSFER_EVENT_CONTEXT_TYPES, - TOKEN_TRANSFER_EVENT_TYPES, -} from '../../types'; -import { ZERO_ADDRESS } from '../../constants/addresses'; +import { MarketType, NFTQuery, SortDirection } from '../../types/NFTQuery'; +import { MEDIA_SOURCES, NFTIdentifier } from '../../types'; import { NotFoundError } from '../../fetcher/ErrorUtils'; +import { resolveSortKey } from './utils/resolveSortKey'; +import { dateToISO } from './utils/dateToISO'; +import { processEvents } from './transformUtils/processEvents'; +import { processMarkets } from './transformUtils/processMarkets'; +import { getChainFromNetwork } from './utils/getChainFromNetwork'; // this enums are params for useNFTQuery() export { @@ -50,437 +28,6 @@ export { NFTQuery, } from '../../types/NFTQuery'; -function dateToISO(date: string) { - if (!date.endsWith('Z')) { - date += 'Z'; - } - - return new Date(date).toISOString(); -} - -function getChainFromNetwork(network: NetworkIDs) { - switch (network) { - case '1': - return Chain.Mainnet; - // case '3': - // return Chain.Ropsten; - // case '4': - // return Chain.Rinkeby; - default: - throw new Error('Chain not supported'); - } -} - -const resolveSortKey = (sortField: SortField) => { - if (sortField === SortField.MINTED) { - return TokenSortKey.Minted; - } - if (sortField === SortField.ACTIVE) { - return TokenSortKey.Transferred; - } - if (sortField === SortField.ANY_PRICE) { - throw new Error('not supported'); - } - if (sortField === SortField.TOKEN_ID) { - return TokenSortKey.TokenId; - } - throw new Error('not supported'); -}; - -function getEvents(events: EventInfoFragment[]): NormalizedEvent[] { - const eventsList: NormalizedEvent[] = []; - - events.forEach((tokenEvent) => { - const common = { - at: { - timestamp: dateToISO(tokenEvent.transactionInfo.blockTimestamp), - blockNumber: tokenEvent.transactionInfo.blockNumber, - transactionHash: tokenEvent.transactionInfo.transactionHash || undefined, - }, - }; - - if ( - tokenEvent.eventType === EventType.MintEvent && - tokenEvent.properties.__typename === 'MintEvent' - ) { - eventsList.push({ - ...common, - from: ZERO_ADDRESS, - to: tokenEvent.properties.toAddress, - type: TOKEN_TRANSFER_EVENT_TYPES.MINT, - eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_TRANSFER_EVENT, - raw: { - source: MEDIA_SOURCES.ZORA, - data: tokenEvent, - }, - }); - } - - if ( - tokenEvent.eventType === EventType.TransferEvent && - tokenEvent.properties.__typename === 'TransferEvent' - ) { - eventsList.push({ - ...common, - from: tokenEvent.properties.fromAddress, - to: tokenEvent.properties.toAddress, - eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_TRANSFER_EVENT, - type: - tokenEvent.properties.toAddress === ZERO_ADDRESS - ? TOKEN_TRANSFER_EVENT_TYPES.BURN - : TOKEN_TRANSFER_EVENT_TYPES.TRANSFER, - raw: { - source: MEDIA_SOURCES.ZORA, - data: tokenEvent, - }, - }); - } - - if ( - tokenEvent.eventType === EventType.V1MarketEvent && - tokenEvent.properties.__typename === 'V1MarketEvent' - ) { - // FIXME: what to do when unable to map to FIXED_PRICE_EVENT_TYPES? - let event: FIXED_PRICE_EVENT_TYPES | undefined = undefined; - - switch (tokenEvent.properties.marketEventType) { - case V1MarketEventType.V1MarketAskCreated: - event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CREATED; - break; - - case V1MarketEventType.V1MarketAskRemoved: - event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CANCELLED; - break; - - case V1MarketEventType.V1MarketAskRemoved: - event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CANCELLED; - break; - } - - if (!event) { - return; - } - - eventsList.push({ - ...common, - // FIXME: address === sender??? - sender: tokenEvent.properties.address, - marketAddress: tokenEvent.properties.collectionAddress, - blockInfo: { - timestamp: tokenEvent.transactionInfo.blockTimestamp, - blockNumber: tokenEvent.transactionInfo.blockNumber, - }, - event, - eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_MARKET_EVENT, - side: FIXED_SIDE_TYPES.ASK, - raw: { - source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V1, - data: tokenEvent, - }, - }); - } - - if ( - tokenEvent.eventType === EventType.V2AuctionEvent && - tokenEvent.properties.__typename === 'V2AuctionEvent' - ) { - let event: AUCTION_EVENT_TYPES | undefined = undefined; - - switch (tokenEvent.properties.auctionEventType) { - case V2AuctionEventType.V2AuctionCreated: - event = AUCTION_EVENT_TYPES.AUCTION_CREATED; - break; - - case V2AuctionEventType.V2AuctionCanceled: - event = AUCTION_EVENT_TYPES.AUCTION_CANCELLED; - break; - - case V2AuctionEventType.V2AuctionBid: - event = AUCTION_EVENT_TYPES.AUCTION_BID; - break; - - case V2AuctionEventType.V2AuctionApprovalUpdated: - event = AUCTION_EVENT_TYPES.AUCTION_APPROVED; - break; - - // Not necessarily useful to display - // case V2AuctionEventType.V2AuctionDurationExtended: - // event = AUCTION_EVENT_TYPES.AUCTION_UPDATED; - // break; - - // case V2AuctionEventType.V2AuctionReservePriceUpdated: - // event = AUCTION_EVENT_TYPES.AUCTION_UPDATED; - // break; - - case V2AuctionEventType.V2AuctionEnded: - event = AUCTION_EVENT_TYPES.AUCTION_ENDED; - break; - } - - if (!event) { - return; - } - - eventsList.push({ - ...common, - event: event, - sender: tokenEvent.properties.address, - marketAddress: tokenEvent.properties.collectionAddress, - eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_MARKET_EVENT, - blockInfo: { - timestamp: tokenEvent.transactionInfo.blockTimestamp, - blockNumber: tokenEvent.transactionInfo.blockNumber, - }, - raw: { - source: AUCTION_SOURCE_TYPES.ZORA_RESERVE_V2, - raw: tokenEvent, - }, - }); - } - - if ( - tokenEvent.eventType === EventType.V3AskEvent && - tokenEvent.properties.__typename === 'V3AskEvent' - ) { - let event: FIXED_PRICE_EVENT_TYPES | undefined = undefined; - switch (tokenEvent.properties.v3AskEventType) { - case V3AskEventType.V3AskCanceled: - event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CANCELLED; - break; - case V3AskEventType.V3AskCreated: - event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CREATED; - break; - case V3AskEventType.V3AskFilled: - event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_FILLED; - break; - case V3AskEventType.V3AskPriceUpdated: - event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_UPDATED; - break; - } - - const filledAskFields = - tokenEvent.properties?.properties?.__typename === 'V3AskFilledEventProperties' - ? { - buyer: tokenEvent.properties.properties.buyer, - seller: tokenEvent.properties.properties.seller, - } - : {}; - - const fixedPriceFields = - tokenEvent.properties?.properties?.__typename === 'V3AskCreatedEventProperties' || - tokenEvent.properties?.properties?.__typename === - 'V3AskPriceUpdatedEventProperties' - ? { - seller: tokenEvent.properties.properties.seller, - } - : {}; - - eventsList.push({ - ...common, - ...filledAskFields, - ...fixedPriceFields, - sender: tokenEvent.properties.address, - marketAddress: tokenEvent.properties.collectionAddress, - blockInfo: { - timestamp: tokenEvent.transactionInfo.blockTimestamp, - blockNumber: tokenEvent.transactionInfo.blockNumber, - }, - event, - eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_MARKET_EVENT, - side: FIXED_SIDE_TYPES.ASK, - raw: { - source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V3, - data: tokenEvent, - }, - }); - } - }); - - return eventsList; -} - -const getStandardMarketData = ( - market: MarketResponseFragmentItem, - amount: PriceSummaryFragment -) => ({ - createdAt: { - timestamp: dateToISO(market.transactionInfo.blockTimestamp), - blockNumber: market.transactionInfo.blockNumber || undefined, - transactionHash: market.transactionInfo.transactionHash || undefined, - }, - amount: { - usd: amount!.usdcPrice - ? { - value: amount!.usdcPrice?.decimal, - raw: amount!.usdcPrice?.raw, - } - : undefined, - eth: amount!.ethPrice - ? { - value: amount!.ethPrice?.decimal, - raw: amount!.ethPrice?.raw, - } - : undefined, - symbol: amount!.nativePrice.currency.name, - decimals: amount!.nativePrice.currency.decimals, - address: amount!.nativePrice.currency.address, - amount: { - raw: amount!.nativePrice.raw, - value: amount!.nativePrice.decimal, - }, - }, - raw: market, -}); - -// This shows the return type for a market item with both details and info -type MarketResponseFragmentItem = MarketDetailsFragment & MarketInfoFragment; - -function getMarkets(markets: MarketResponseFragmentItem[]) { - const getReserveAuctionStatus = (status: V2AuctionStatus) => { - if (status === V2AuctionStatus.Active) { - return MARKET_INFO_STATUSES.ACTIVE; - } - if (status === V2AuctionStatus.Canceled) { - return MARKET_INFO_STATUSES.CANCELED; - } - if (status === V2AuctionStatus.Completed) { - return MARKET_INFO_STATUSES.COMPLETE; - } - return MARKET_INFO_STATUSES.UNKNOWN; - }; - const getV1MarketFixedPriceStatus = (status: V1MarketEntityStatus) => { - if (status === V1MarketEntityStatus.Active) { - return MARKET_INFO_STATUSES.ACTIVE; - } - if (status === V1MarketEntityStatus.Canceled) { - return MARKET_INFO_STATUSES.CANCELED; - } - if (status === V1MarketEntityStatus.Completed) { - return MARKET_INFO_STATUSES.COMPLETE; - } - return MARKET_INFO_STATUSES.UNKNOWN; - }; - const getV3AskStatus = (status: V3AskStatus) => { - if (status === V3AskStatus.Active) { - return MARKET_INFO_STATUSES.ACTIVE; - } - if (status === V3AskStatus.Canceled) { - return MARKET_INFO_STATUSES.CANCELED; - } - if (status === V3AskStatus.Completed) { - return MARKET_INFO_STATUSES.COMPLETE; - } - return MARKET_INFO_STATUSES.UNKNOWN; - }; - - const marketResponse: MarketModule[] = []; - markets.forEach((market) => { - if ( - market.marketType === ZDKMarketType.V1Ask && - market.properties.__typename === 'V1Ask' - ) { - marketResponse.push({ - type: MARKET_TYPES.FIXED_PRICE, - source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V1, - side: FIXED_SIDE_TYPES.ASK, - // TODO(iain): fix naming - status: getV1MarketFixedPriceStatus(market.properties.offerStatus), - ...getStandardMarketData(market, market.properties.amount), - }); - } - if ( - market.marketType === ZDKMarketType.V1Offer && - market.properties.__typename === 'V1Offer' - ) { - marketResponse.push({ - type: MARKET_TYPES.FIXED_PRICE, - source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V1, - side: FIXED_SIDE_TYPES.OFFER, - status: getV1MarketFixedPriceStatus(market.properties.offerStatus), - ...getStandardMarketData(market, market.properties.amount), - }); - } - if ( - market.marketType === ZDKMarketType.V2Auction && - market.properties.__typename === 'V2Auction' - ) { - const expiresAt = market.properties.estimatedExpirationTime; - - marketResponse.push({ - type: MARKET_TYPES.AUCTION, - source: AUCTION_SOURCE_TYPES.ZORA_RESERVE_V2, - status: getReserveAuctionStatus(market.properties.auctionStatus), - auctionId: market.properties.auctionId, - // Duration shouldn't be able to overflow - duration: parseInt(market.properties.duration, 10), - startedAt: market.properties.firstBidTime - ? { - timestamp: market.properties.firstBidTime, - } - : undefined, - bids: [], - endsAt: market.properties.firstBidTime - ? { - timestamp: dateToISO(expiresAt), - } - : undefined, - currentBid: - market.properties.highestBidder && market.properties.highestBidPrice - ? { - creator: market.properties.highestBidder, - created: { - timestamp: dateToISO(market.transactionInfo.blockTimestamp), - }, - amount: { - usd: market.properties.highestBidPrice.usdcPrice - ? { - value: market.properties.highestBidPrice.usdcPrice?.decimal, - raw: market.properties.highestBidPrice.usdcPrice?.raw, - decimals: 18, - } - : undefined, - eth: market.properties.highestBidPrice.ethPrice - ? { - value: market.properties.highestBidPrice.ethPrice.decimal, - raw: market.properties.highestBidPrice.ethPrice.raw, - decimals: 18, - } - : undefined, - amount: { - raw: market.properties.highestBidPrice.nativePrice.raw, - value: market.properties.highestBidPrice.nativePrice.decimal, - decimals: - market.properties.highestBidPrice.nativePrice.currency.decimals || - undefined, - }, - symbol: market.properties.highestBidPrice.nativePrice.currency.name, - name: market.properties.highestBidPrice.nativePrice.currency.name, - address: market.properties.highestBidPrice.nativePrice.currency.address, - }, - } - : undefined, - ...getStandardMarketData( - market, - market.properties.reservePrice || market.properties.highestBidPrice - ), - }); - } - if ( - market.marketType === ZDKMarketType.V3Ask && - market.properties.__typename === 'V3Ask' - ) { - marketResponse.push({ - type: MARKET_TYPES.FIXED_PRICE, - source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V3, - side: FIXED_SIDE_TYPES.ASK, - status: getV3AskStatus(market.properties.askStatus), - ...getStandardMarketData(market, market.properties.askPrice), - }); - } - }); - return marketResponse; -} - export function transformNFTZDKAlpha( tokenResponse: SharedTokenResponse, object?: NFTObject @@ -517,13 +64,13 @@ export function transformNFTZDKAlpha( // Response of token query if (tokenResponse.__typename === 'TokenWithFullMarketHistory') { - object.markets = getMarkets(tokenResponse.markets); - object.events = getEvents(tokenResponse.events); + object.markets = processMarkets(tokenResponse.markets); + object.events = processEvents(tokenResponse.events); } // Response of tokens (plural) query if (tokenResponse.__typename === 'TokenWithMarketsSummary') { - object.markets = getMarkets(tokenResponse.marketsSummary); + object.markets = processMarkets(tokenResponse.marketsSummary); } object.metadata = { diff --git a/src/backends/zdk-alpha/transformUtils/processEvents.ts b/src/backends/zdk-alpha/transformUtils/processEvents.ts new file mode 100644 index 0000000..41d0c00 --- /dev/null +++ b/src/backends/zdk-alpha/transformUtils/processEvents.ts @@ -0,0 +1,199 @@ +import { + EventType, + EventInfoFragment, + V2AuctionEventType, + V3AskEventType, +} from '@zoralabs/zdk-alpha/dist/queries/queries-sdk'; +import { + AUCTION_EVENT_TYPES, + AUCTION_SOURCE_TYPES, + FIXED_PRICE_EVENT_TYPES, + FIXED_PRICE_MARKET_SOURCES, + FIXED_SIDE_TYPES, + MEDIA_SOURCES, + NormalizedEvent, + TOKEN_TRANSFER_EVENT_CONTEXT_TYPES, + TOKEN_TRANSFER_EVENT_TYPES, +} from '../../../types'; +import { ZERO_ADDRESS } from '../../../constants/addresses'; +import { dateToISO } from '../utils/dateToISO'; +import { processMarketEvent } from './processMarketEvent'; + +export function processEvents(events: EventInfoFragment[]): NormalizedEvent[] { + const eventsList: NormalizedEvent[] = []; + + events.forEach((tokenEvent) => { + const common = { + at: { + timestamp: dateToISO(tokenEvent.transactionInfo.blockTimestamp), + blockNumber: tokenEvent.transactionInfo.blockNumber, + transactionHash: tokenEvent.transactionInfo.transactionHash || undefined, + }, + }; + + if ( + tokenEvent.eventType === EventType.MintEvent && + tokenEvent.properties.__typename === 'MintEvent' + ) { + eventsList.push({ + ...common, + from: ZERO_ADDRESS, + to: tokenEvent.properties.toAddress, + type: TOKEN_TRANSFER_EVENT_TYPES.MINT, + eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_TRANSFER_EVENT, + raw: { + source: MEDIA_SOURCES.ZORA, + data: tokenEvent, + }, + }); + } + + if ( + tokenEvent.eventType === EventType.TransferEvent && + tokenEvent.properties.__typename === 'TransferEvent' + ) { + eventsList.push({ + ...common, + from: tokenEvent.properties.fromAddress, + to: tokenEvent.properties.toAddress, + eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_TRANSFER_EVENT, + type: + tokenEvent.properties.toAddress === ZERO_ADDRESS + ? TOKEN_TRANSFER_EVENT_TYPES.BURN + : TOKEN_TRANSFER_EVENT_TYPES.TRANSFER, + raw: { + source: MEDIA_SOURCES.ZORA, + data: tokenEvent, + }, + }); + } + + if ( + tokenEvent.eventType === EventType.V1MarketEvent && + tokenEvent.properties.__typename === 'V1MarketEvent' + ) { + const e = processMarketEvent(tokenEvent); + + if (e) { + eventsList.push(e); + } + } + + if ( + tokenEvent.eventType === EventType.V2AuctionEvent && + tokenEvent.properties.__typename === 'V2AuctionEvent' + ) { + let event: AUCTION_EVENT_TYPES | undefined = undefined; + + switch (tokenEvent.properties.auctionEventType) { + case V2AuctionEventType.V2AuctionCreated: + event = AUCTION_EVENT_TYPES.AUCTION_CREATED; + break; + + case V2AuctionEventType.V2AuctionCanceled: + event = AUCTION_EVENT_TYPES.AUCTION_CANCELLED; + break; + + case V2AuctionEventType.V2AuctionBid: + event = AUCTION_EVENT_TYPES.AUCTION_BID; + break; + + case V2AuctionEventType.V2AuctionApprovalUpdated: + event = AUCTION_EVENT_TYPES.AUCTION_APPROVED; + break; + + // Not necessarily useful to display + // case V2AuctionEventType.V2AuctionDurationExtended: + // event = AUCTION_EVENT_TYPES.AUCTION_UPDATED; + // break; + + // case V2AuctionEventType.V2AuctionReservePriceUpdated: + // event = AUCTION_EVENT_TYPES.AUCTION_UPDATED; + // break; + + case V2AuctionEventType.V2AuctionEnded: + event = AUCTION_EVENT_TYPES.AUCTION_ENDED; + break; + } + + if (!event) { + return; + } + + eventsList.push({ + ...common, + event: event, + sender: tokenEvent.properties.address, + marketAddress: tokenEvent.properties.collectionAddress, + eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_MARKET_EVENT, + blockInfo: { + timestamp: tokenEvent.transactionInfo.blockTimestamp, + blockNumber: tokenEvent.transactionInfo.blockNumber, + }, + raw: { + source: AUCTION_SOURCE_TYPES.ZORA_RESERVE_V2, + raw: tokenEvent, + }, + }); + } + + if ( + tokenEvent.eventType === EventType.V3AskEvent && + tokenEvent.properties.__typename === 'V3AskEvent' + ) { + let event: FIXED_PRICE_EVENT_TYPES | undefined = undefined; + switch (tokenEvent.properties.v3AskEventType) { + case V3AskEventType.V3AskCanceled: + event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CANCELLED; + break; + case V3AskEventType.V3AskCreated: + event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CREATED; + break; + case V3AskEventType.V3AskFilled: + event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_FILLED; + break; + case V3AskEventType.V3AskPriceUpdated: + event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_UPDATED; + break; + } + + const filledAskFields = + tokenEvent.properties?.properties?.__typename === 'V3AskFilledEventProperties' + ? { + buyer: tokenEvent.properties.properties.buyer, + seller: tokenEvent.properties.properties.seller, + } + : {}; + + const fixedPriceFields = + tokenEvent.properties?.properties?.__typename === 'V3AskCreatedEventProperties' || + tokenEvent.properties?.properties?.__typename === + 'V3AskPriceUpdatedEventProperties' + ? { + seller: tokenEvent.properties.properties.seller, + } + : {}; + + eventsList.push({ + ...common, + ...filledAskFields, + ...fixedPriceFields, + sender: tokenEvent.properties.address, + marketAddress: tokenEvent.properties.collectionAddress, + blockInfo: { + timestamp: tokenEvent.transactionInfo.blockTimestamp, + blockNumber: tokenEvent.transactionInfo.blockNumber, + }, + event, + eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_MARKET_EVENT, + side: FIXED_SIDE_TYPES.ASK, + raw: { + source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V3, + data: tokenEvent, + }, + }); + } + }); + + return eventsList; +} diff --git a/src/backends/zdk-alpha/transformUtils/processMarketEvent.ts b/src/backends/zdk-alpha/transformUtils/processMarketEvent.ts new file mode 100644 index 0000000..d505ccf --- /dev/null +++ b/src/backends/zdk-alpha/transformUtils/processMarketEvent.ts @@ -0,0 +1,69 @@ +import { + EventInfoFragment, + EventType, + V1MarketEventType, +} from '@zoralabs/zdk-alpha/dist/queries/queries-sdk'; +import { extractPrice } from '../utils/extractPrice'; +import { + FIXED_PRICE_EVENT_TYPES, + FIXED_PRICE_MARKET_SOURCES, + FIXED_SIDE_TYPES, + NormalizedEvent, + TOKEN_TRANSFER_EVENT_CONTEXT_TYPES, +} from '../../../types'; +import { dateToISO } from '../utils/dateToISO'; + +export function processMarketEvent(e: EventInfoFragment): NormalizedEvent | undefined { + if ( + !( + e.eventType === EventType.V1MarketEvent && + e.properties.__typename === 'V1MarketEvent' + ) + ) { + return undefined; + } + + // FIXME: what to do when unable to map to FIXED_PRICE_EVENT_TYPES? + let event: FIXED_PRICE_EVENT_TYPES | undefined = undefined; + + switch (e.properties.marketEventType) { + case V1MarketEventType.V1MarketAskCreated: + event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CREATED; + break; + + case V1MarketEventType.V1MarketAskRemoved: + event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CANCELLED; + break; + + case V1MarketEventType.V1MarketAskRemoved: + event = FIXED_PRICE_EVENT_TYPES.FIXED_PRICE_CANCELLED; + break; + } + + if (!event) { + return; + } + + return { + // FIXME: address === sender??? + sender: e.properties.address, + ...extractPrice(e), + marketAddress: e.properties.collectionAddress, + event, + eventType: TOKEN_TRANSFER_EVENT_CONTEXT_TYPES.TOKEN_MARKET_EVENT, + side: FIXED_SIDE_TYPES.ASK, + blockInfo: { + timestamp: e.transactionInfo.blockTimestamp, + blockNumber: e.transactionInfo.blockNumber, + }, + at: { + timestamp: dateToISO(e.transactionInfo.blockTimestamp), + blockNumber: e.transactionInfo.blockNumber, + transactionHash: e.transactionInfo.transactionHash || undefined, + }, + raw: { + source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V1, + data: e, + }, + }; +} diff --git a/src/backends/zdk-alpha/transformUtils/processMarkets.ts b/src/backends/zdk-alpha/transformUtils/processMarkets.ts new file mode 100644 index 0000000..0559135 --- /dev/null +++ b/src/backends/zdk-alpha/transformUtils/processMarkets.ts @@ -0,0 +1,165 @@ +import { + MarketType as ZDKMarketType, + V1MarketEntityStatus, + V3AskStatus, + V2AuctionStatus, +} from '@zoralabs/zdk-alpha/dist/queries/queries-sdk'; +import { + AUCTION_SOURCE_TYPES, + FIXED_PRICE_MARKET_SOURCES, + FIXED_SIDE_TYPES, + MarketModule, + MARKET_INFO_STATUSES, + MARKET_TYPES, +} from '../../../types'; +import { dateToISO } from '../utils/dateToISO'; +import { + getStandardMarketData, + MarketResponseFragmentItem, +} from '../utils/getStandardMarketData'; + +export function processMarkets(markets: MarketResponseFragmentItem[]) { + const getReserveAuctionStatus = (status: V2AuctionStatus) => { + if (status === V2AuctionStatus.Active) { + return MARKET_INFO_STATUSES.ACTIVE; + } + if (status === V2AuctionStatus.Canceled) { + return MARKET_INFO_STATUSES.CANCELED; + } + if (status === V2AuctionStatus.Completed) { + return MARKET_INFO_STATUSES.COMPLETE; + } + return MARKET_INFO_STATUSES.UNKNOWN; + }; + const getV1MarketFixedPriceStatus = (status: V1MarketEntityStatus) => { + if (status === V1MarketEntityStatus.Active) { + return MARKET_INFO_STATUSES.ACTIVE; + } + if (status === V1MarketEntityStatus.Canceled) { + return MARKET_INFO_STATUSES.CANCELED; + } + if (status === V1MarketEntityStatus.Completed) { + return MARKET_INFO_STATUSES.COMPLETE; + } + return MARKET_INFO_STATUSES.UNKNOWN; + }; + const getV3AskStatus = (status: V3AskStatus) => { + if (status === V3AskStatus.Active) { + return MARKET_INFO_STATUSES.ACTIVE; + } + if (status === V3AskStatus.Canceled) { + return MARKET_INFO_STATUSES.CANCELED; + } + if (status === V3AskStatus.Completed) { + return MARKET_INFO_STATUSES.COMPLETE; + } + return MARKET_INFO_STATUSES.UNKNOWN; + }; + + const marketResponse: MarketModule[] = []; + markets.forEach((market) => { + if ( + market.marketType === ZDKMarketType.V1Ask && + market.properties.__typename === 'V1Ask' + ) { + marketResponse.push({ + type: MARKET_TYPES.FIXED_PRICE, + source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V1, + side: FIXED_SIDE_TYPES.ASK, + // TODO(iain): fix naming + status: getV1MarketFixedPriceStatus(market.properties.offerStatus), + ...getStandardMarketData({ market, amount: market.properties.amount }), + }); + } + if ( + market.marketType === ZDKMarketType.V1Offer && + market.properties.__typename === 'V1Offer' + ) { + marketResponse.push({ + type: MARKET_TYPES.FIXED_PRICE, + source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V1, + side: FIXED_SIDE_TYPES.OFFER, + status: getV1MarketFixedPriceStatus(market.properties.offerStatus), + ...getStandardMarketData({ market, amount: market.properties.amount }), + }); + } + if ( + market.marketType === ZDKMarketType.V2Auction && + market.properties.__typename === 'V2Auction' + ) { + const expiresAt = market.properties.estimatedExpirationTime; + + marketResponse.push({ + type: MARKET_TYPES.AUCTION, + source: AUCTION_SOURCE_TYPES.ZORA_RESERVE_V2, + status: getReserveAuctionStatus(market.properties.auctionStatus), + auctionId: market.properties.auctionId, + // Duration shouldn't be able to overflow + duration: parseInt(market.properties.duration, 10), + startedAt: market.properties.firstBidTime + ? { + timestamp: market.properties.firstBidTime, + } + : undefined, + bids: [], + endsAt: market.properties.firstBidTime + ? { + timestamp: dateToISO(expiresAt), + } + : undefined, + currentBid: + market.properties.highestBidder && market.properties.highestBidPrice + ? { + creator: market.properties.highestBidder, + created: { + timestamp: dateToISO(market.transactionInfo.blockTimestamp), + }, + amount: { + usd: market.properties.highestBidPrice.usdcPrice + ? { + value: market.properties.highestBidPrice.usdcPrice?.decimal, + raw: market.properties.highestBidPrice.usdcPrice?.raw, + decimals: 18, + } + : undefined, + eth: market.properties.highestBidPrice.ethPrice + ? { + value: market.properties.highestBidPrice.ethPrice.decimal, + raw: market.properties.highestBidPrice.ethPrice.raw, + decimals: 18, + } + : undefined, + amount: { + raw: market.properties.highestBidPrice.nativePrice.raw, + value: market.properties.highestBidPrice.nativePrice.decimal, + decimals: + market.properties.highestBidPrice.nativePrice.currency.decimals || + undefined, + }, + symbol: market.properties.highestBidPrice.nativePrice.currency.name, + name: market.properties.highestBidPrice.nativePrice.currency.name, + address: market.properties.highestBidPrice.nativePrice.currency.address, + }, + } + : undefined, + ...getStandardMarketData({ + market, + amount: market.properties.reservePrice || market.properties.highestBidPrice, + }), + }); + } + if ( + market.marketType === ZDKMarketType.V3Ask && + market.properties.__typename === 'V3Ask' + ) { + marketResponse.push({ + type: MARKET_TYPES.FIXED_PRICE, + source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V3, + side: FIXED_SIDE_TYPES.ASK, + status: getV3AskStatus(market.properties.askStatus), + ...getStandardMarketData({ market, amount: market.properties.askPrice }), + }); + } + }); + return marketResponse; +} diff --git a/src/backends/zdk-alpha/utils/dateToISO.ts b/src/backends/zdk-alpha/utils/dateToISO.ts new file mode 100644 index 0000000..ab859bd --- /dev/null +++ b/src/backends/zdk-alpha/utils/dateToISO.ts @@ -0,0 +1,7 @@ +export function dateToISO(date: string) { + if (!date.endsWith('Z')) { + date += 'Z'; + } + + return new Date(date).toISOString(); +} diff --git a/src/backends/zdk-alpha/utils/extractPrice.ts b/src/backends/zdk-alpha/utils/extractPrice.ts new file mode 100644 index 0000000..0ed201c --- /dev/null +++ b/src/backends/zdk-alpha/utils/extractPrice.ts @@ -0,0 +1,65 @@ +import { EventInfoFragment } from '@zoralabs/zdk-alpha/dist/queries/queries-sdk'; + +export function extractPrice(e: EventInfoFragment) { + if ( + e.properties.__typename === 'V1MarketEvent' && + (e.properties.properties.__typename === 'V1MarketAskCreatedEventProperties' || + e.properties.properties.__typename === 'V1MarketOfferFinalizedEventProperties' || + e.properties.properties.__typename === 'V1MarketAskRemovedEventProperties') + ) { + const p = e.properties.properties; + return { + price: p.price.usdcPrice + ? { + symbol: p.price.nativePrice.currency.name, + amount: p.price.nativePrice.decimal, + usdcPrice: p.price.usdcPrice, + nativePrice: p.price.nativePrice, + } + : undefined, + }; + } + + if ( + e.properties.__typename === 'V2AuctionEvent' && + (e.properties.properties.__typename === 'V2AuctionBidEventProperties' || + e.properties.properties.__typename === + 'V2AuctionReservePriceUpdatedEventProperties' || + e.properties.properties.__typename === 'V2AuctionCreatedEventProperties') + ) { + const p = e.properties.properties; + return { + price: p.price.usdcPrice + ? { + symbol: p.price.nativePrice.currency.name, + amount: p.price.nativePrice.decimal, + usdcPrice: p.price.usdcPrice, + nativePrice: p.price.nativePrice, + } + : undefined, + }; + } + + if ( + e.properties.__typename === 'V3AskEvent' && + (e.properties.properties.__typename === 'V3AskCreatedEventProperties' || + e.properties.properties.__typename === 'V3AskCanceledEventProperties' || + e.properties.properties.__typename === 'V3AskFilledEventProperties' || + e.properties.properties.__typename === 'V3AskPriceUpdatedEventProperties') + ) { + const p = e.properties.properties.price; + + return { + price: p.usdcPrice + ? { + symbol: p.nativePrice.currency.name, + amount: p.nativePrice.decimal, + usdcPrice: p.usdcPrice, + nativePrice: p.nativePrice, + } + : undefined, + }; + } + + return { price: undefined }; +} diff --git a/src/backends/zdk-alpha/utils/getChainFromNetwork.ts b/src/backends/zdk-alpha/utils/getChainFromNetwork.ts new file mode 100644 index 0000000..7497606 --- /dev/null +++ b/src/backends/zdk-alpha/utils/getChainFromNetwork.ts @@ -0,0 +1,15 @@ +import { Chain } from '@zoralabs/zdk-alpha/dist/queries/queries-sdk'; +import { NetworkIDs } from 'src/constants/networks'; + +export function getChainFromNetwork(network: NetworkIDs) { + switch (network) { + case '1': + return Chain.Mainnet; + // case '3': + // return Chain.Ropsten; + // case '4': + // return Chain.Rinkeby; + default: + throw new Error('Chain not supported'); + } +} diff --git a/src/backends/zdk-alpha/utils/getStandardMarketData.ts b/src/backends/zdk-alpha/utils/getStandardMarketData.ts new file mode 100644 index 0000000..75cd91b --- /dev/null +++ b/src/backends/zdk-alpha/utils/getStandardMarketData.ts @@ -0,0 +1,63 @@ +import { + MarketDetailsFragment, + MarketInfoFragment, + PriceSummaryFragment, +} from '@zoralabs/zdk-alpha/dist/queries/queries-sdk'; +import { ZERO_ADDRESS } from 'src/constants/addresses'; +import { dateToISO } from './dateToISO'; + +// This shows the return type for a market item with both details and info +export type MarketResponseFragmentItem = MarketDetailsFragment & MarketInfoFragment; + +export function getStandardMarketData({ + market, + amount, +}: { + market: MarketResponseFragmentItem; + amount: PriceSummaryFragment; +}) { + return { + createdAt: { + timestamp: dateToISO(market.transactionInfo.blockTimestamp), + blockNumber: market.transactionInfo.blockNumber || undefined, + transactionHash: market.transactionInfo.transactionHash || undefined, + }, + amount: { + usd: amount!.usdcPrice + ? { + raw: amount!.usdcPrice.raw, + value: amount!.usdcPrice?.decimal, + currency: { + value: amount!.nativePrice.decimal, + raw: amount!.nativePrice.raw, + }, + } + : undefined, + eth: amount!.ethPrice + ? { + raw: amount!.ethPrice?.raw, + value: amount!.ethPrice.decimal, + currency: { + address: ZERO_ADDRESS, + decimals: 18, + name: 'ETH', + }, + } + : undefined, + symbol: amount!.nativePrice.currency.name, + decimals: amount!.nativePrice.currency.decimals, + address: amount!.nativePrice.currency.address, + amount: { + raw: amount!.nativePrice.raw, + decimal: amount!.nativePrice.decimal, + currency: amount!.nativePrice.currency, + value: amount!.nativePrice.decimal, + amount: { + raw: amount!.nativePrice.raw, + value: amount!.nativePrice.decimal, + }, + }, + }, + raw: market, + }; +} diff --git a/src/backends/zdk-alpha/utils/resolveSortKey.ts b/src/backends/zdk-alpha/utils/resolveSortKey.ts new file mode 100644 index 0000000..cd33d30 --- /dev/null +++ b/src/backends/zdk-alpha/utils/resolveSortKey.ts @@ -0,0 +1,18 @@ +import { TokenSortKey } from '@zoralabs/zdk-alpha/dist/queries/queries-sdk'; +import { SortField } from 'src/types/NFTQuery'; + +export function resolveSortKey(sortField: SortField) { + if (sortField === SortField.MINTED) { + return TokenSortKey.Minted; + } + if (sortField === SortField.ACTIVE) { + return TokenSortKey.Transferred; + } + if (sortField === SortField.ANY_PRICE) { + throw new Error('not supported'); + } + if (sortField === SortField.TOKEN_ID) { + return TokenSortKey.TokenId; + } + throw new Error('not supported'); +} diff --git a/src/types/NFTInterface.ts b/src/types/NFTInterface.ts index 805db8c..dd361d3 100644 --- a/src/types/NFTInterface.ts +++ b/src/types/NFTInterface.ts @@ -80,6 +80,21 @@ export type CurrencyValue = { address: ETHAddress; }; +export type MarketInfo = { + raw: any; + amount?: CurrencyValue; + // pending - inactive pending some event + // active - can be filled / auction is ongoing + // completed - auction end fill complete + // canceled - user cancels at some point + status: MARKET_INFO_STATUSES; + createdAt: TimedAction; + createdBy?: string; + finishedAt?: TimedAction; + canceledAt?: TimedAction; + marketContract?: ETHAddress; +}; + export type TimedAction = { timestamp: string; blockNumber?: number; @@ -127,21 +142,6 @@ export type EditionLike = { purchases: readonly EditionPurchaseEvent[]; } & MarketInfo; -export type MarketInfo = { - raw: any; - amount?: CurrencyValue; - // pending - inactive pending some event - // active - can be filled / auction is ongoing - // completed - auction end fill complete - // canceled - user cancels at some point - status: MARKET_INFO_STATUSES; - createdAt: TimedAction; - createdBy?: string; - finishedAt?: TimedAction; - canceledAt?: TimedAction; - marketContract?: ETHAddress; -}; - export type MarketModule = AuctionLike | FixedPriceLike | EditionLike; export enum AUCTION_EVENT_TYPES { @@ -207,6 +207,12 @@ type MarketFixedPriceEvent = SharedMarketEventData & { side: FIXED_SIDE_TYPES; buyer?: ETHAddress; seller?: ETHAddress; + price?: { + symbol: string; + amount: number; + usdcPrice?: Pick; + nativePrice?: Pick; + }; raw: | { source: FIXED_PRICE_MARKET_SOURCES.ZORA_ASK_V3;