diff --git a/src/common/persistence/persistence.service.ts b/src/common/persistence/persistence.service.ts index ebb4d27c2..5f2cbe69d 100644 --- a/src/common/persistence/persistence.service.ts +++ b/src/common/persistence/persistence.service.ts @@ -768,6 +768,15 @@ export class PersistenceService { ); } + async getLastAuctionIdForMarketplace( + marketplaceKey: string, + ): Promise { + return await this.execute( + this.getLastAuctionIdForMarketplace.name, + this.auctionsRepository.getLastAuctionIdForMarketplace(marketplaceKey), + ); + } + async getBulkAuctions(auctionsIds: number[]): Promise { return await this.execute( this.getBulkAuctions.name, @@ -785,6 +794,19 @@ export class PersistenceService { ); } + async getAuctionByIdentifierAndMarketplace( + identifier: string, + marketplaceKey: string, + ): Promise { + return await this.execute( + this.getAuctionByIdentifierAndMarketplace.name, + this.auctionsRepository.getAuctionByIdentifierAndMarketplace( + identifier, + marketplaceKey, + ), + ); + } + async getAuctionCountForIdentifiers( identifiers: string[], ): Promise { diff --git a/src/db/auctions/auctions.repository.ts b/src/db/auctions/auctions.repository.ts index e4db8dfd5..f66c93b7f 100644 --- a/src/db/auctions/auctions.repository.ts +++ b/src/db/auctions/auctions.repository.ts @@ -408,6 +408,18 @@ export class AuctionsRepository { return null; } + async getLastAuctionIdForMarketplace( + marketplaceKey: string, + ): Promise { + const { marketplaceAuctionId } = await this.auctionsRepository + .createQueryBuilder('a') + .select('a.marketplaceAuctionId as marketplaceAuctionId') + .where({ marketplaceKey: marketplaceKey }) + .orderBy('id', 'DESC') + .getRawOne(); + return marketplaceAuctionId; + } + async getBulkAuctions(auctionsIds: number[]): Promise { return await this.auctionsRepository .createQueryBuilder('auctions') @@ -429,6 +441,21 @@ export class AuctionsRepository { return null; } + async getAuctionByIdentifierAndMarketplace( + identifier: string, + marketplaceKey: string, + ): Promise { + return await this.auctionsRepository.findOne({ + where: [ + { + identifier: identifier, + marketplaceKey: marketplaceKey, + status: AuctionStatusEnum.Running, + }, + ], + }); + } + async getAuctionCountForIdentifiers( identifiers: string[], ): Promise { @@ -527,40 +554,6 @@ export class AuctionsRepository { await this.rollbackCreateAuction(auctions); } - private getDefaultQueryRequest( - startDate: number, - endDate: number = null, - ): QueryRequest { - if (endDate) { - return new QueryRequest({ - filters: { - childExpressions: undefined, - filters: [ - { field: 'status', values: ['Running'], op: Operation.EQ }, - { field: 'startDate', values: [`${startDate}`], op: Operation.LE }, - { - field: 'endDate', - values: [`${endDate ? endDate : startDate}`], - op: Operation.LE, - }, - { field: 'endDate', values: ['1'], op: Operation.GE }, - ], - operator: Operator.AND, - }, - }); - } - return new QueryRequest({ - filters: { - childExpressions: undefined, - filters: [ - { field: 'status', values: ['Running'], op: Operation.EQ }, - { field: 'startDate', values: [`${startDate}`], op: Operation.LE }, - ], - operator: Operator.AND, - }, - }); - } - private async getAuctionsForIdentifierSortByPrice( queryRequest: QueryRequest, identifier: string, diff --git a/src/modules/assets/models/AuctionEvent.enum.ts b/src/modules/assets/models/AuctionEvent.enum.ts index f75b7b00d..1f9f73922 100644 --- a/src/modules/assets/models/AuctionEvent.enum.ts +++ b/src/modules/assets/models/AuctionEvent.enum.ts @@ -8,6 +8,7 @@ export enum AuctionEventEnum { export enum ExternalAuctionEventEnum { Listing = 'listing', + ListNftOnMarketplace = 'listNftOnMarketplace', Buy = 'buy', BuyNft = 'buyNft', BulkBuy = 'bulkBuy', @@ -16,6 +17,7 @@ export enum ExternalAuctionEventEnum { AcceptOffer = 'acceptOffer', UpdateOffer = 'update_offer_event', AcceptGlobalOffer = 'acceptGlobalOffer', + ClaimBackNft = 'claimBackNft', } export enum ElrondNftsSwapAuctionEventEnum { diff --git a/src/modules/auctions/auctions-getter.service.ts b/src/modules/auctions/auctions-getter.service.ts index 5b78ccd9a..3c55c79bd 100644 --- a/src/modules/auctions/auctions-getter.service.ts +++ b/src/modules/auctions/auctions-getter.service.ts @@ -135,6 +135,14 @@ export class AuctionsGetterService { return await this.persistenceService.getAuction(id); } + async getLastAuctionIdForMarketplace( + marketplaceKey: string, + ): Promise { + return await this.persistenceService.getLastAuctionIdForMarketplace( + marketplaceKey, + ); + } + async getAuctionByIdAndMarketplace( id: number, marketplaceKey: string, @@ -145,6 +153,16 @@ export class AuctionsGetterService { ); } + async getAuctionByIdentifierAndMarketplace( + identifier: string, + marketplaceKey: string, + ): Promise { + return await this.persistenceService.getAuctionByIdentifierAndMarketplace( + identifier, + marketplaceKey, + ); + } + async getAvailableTokens(id: number): Promise { return await this.persistenceService.getAvailableTokensByAuctionId(id); } diff --git a/src/modules/rabbitmq/blockchain-events/handlers/buy-event.handler.ts b/src/modules/rabbitmq/blockchain-events/handlers/buy-event.handler.ts index e0b1d8d80..a2b8699e9 100644 --- a/src/modules/rabbitmq/blockchain-events/handlers/buy-event.handler.ts +++ b/src/modules/rabbitmq/blockchain-events/handlers/buy-event.handler.ts @@ -1,16 +1,20 @@ import { Injectable, Logger } from '@nestjs/common'; -import { ElrondNftsSwapAuctionEventEnum } from 'src/modules/assets/models'; +import { AuctionEntity } from 'src/db/auctions'; +import { + ElrondNftsSwapAuctionEventEnum, + ExternalAuctionEventEnum, +} from 'src/modules/assets/models'; import { AuctionsGetterService, AuctionsSetterService, } from 'src/modules/auctions'; import { AuctionStatusEnum } from 'src/modules/auctions/models'; import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; -import { Marketplace } from 'src/modules/marketplaces/models'; import { MarketplaceTypeEnum } from 'src/modules/marketplaces/models/MarketplaceType.enum'; import { CreateOrderArgs, OrderStatusEnum } from 'src/modules/orders/models'; import { OrdersService } from 'src/modules/orders/order.service'; import { BuySftEvent } from '../../entities/auction'; +import { ClaimEvent } from '../../entities/auction/claim.event'; import { ElrondSwapBuyEvent } from '../../entities/auction/elrondnftswap/elrondswap-buy.event'; import { FeedEventsSenderService } from '../feed-events.service'; @@ -27,6 +31,7 @@ export class BuyEventHandler { async handle(event: any, hash: string, marketplaceType: MarketplaceTypeEnum) { const { buySftEvent, topics } = this.getEventAndTopics(event, hash); + let auction: AuctionEntity; const marketplace = await this.marketplaceService.getMarketplaceByType( buySftEvent.getAddress(), @@ -38,11 +43,20 @@ export class BuyEventHandler { this.logger.log( `Buy event detected for hash '${hash}' and marketplace '${marketplace?.name}'`, ); - const auction = - await this.auctionsGetterService.getAuctionByIdAndMarketplace( + + if (topics.auctionId) { + auction = await this.auctionsGetterService.getAuctionByIdAndMarketplace( parseInt(topics.auctionId, 16), marketplace.key, ); + } else { + const auctionIdentifier = `${topics.collection}-${topics.nonce}`; + auction = + await this.auctionsGetterService.getAuctionByIdentifierAndMarketplace( + auctionIdentifier, + marketplace.key, + ); + } if (!auction) return; const result = await this.auctionsGetterService.getAvailableTokens( @@ -64,7 +78,7 @@ export class BuyEventHandler { ownerAddress: topics.currentWinner, auctionId: auction.id, priceToken: auction.paymentToken, - priceAmount: topics.bid, + priceAmount: auction.minBid, priceNonce: auction.paymentNonce, blockHash: hash, status: OrderStatusEnum.Bought, @@ -74,7 +88,7 @@ export class BuyEventHandler { ); await this.feedEventsSenderService.sendBuyEvent( topics.currentWinner, - topics.bid, + auction.minBid, topics.boughtTokens, orderSft, auction, @@ -97,6 +111,12 @@ export class BuyEventHandler { const topics = buySftEvent.getTopics(); return { buySftEvent, topics }; } + + if (event.identifier === ExternalAuctionEventEnum.BuyNft) { + const buySftEvent = new ClaimEvent(event); + const topics = buySftEvent.getTopics(); + return { buySftEvent, topics }; + } const buySftEvent = new BuySftEvent(event); const topics = buySftEvent.getTopics(); return { buySftEvent, topics }; diff --git a/src/modules/rabbitmq/blockchain-events/handlers/startAuction-event.handler.ts b/src/modules/rabbitmq/blockchain-events/handlers/startAuction-event.handler.ts index 6a7426e55..7ff4619da 100644 --- a/src/modules/rabbitmq/blockchain-events/handlers/startAuction-event.handler.ts +++ b/src/modules/rabbitmq/blockchain-events/handlers/startAuction-event.handler.ts @@ -1,23 +1,32 @@ import { Injectable, Logger } from '@nestjs/common'; +import { elrondConfig } from 'src/config'; import { AuctionEntity } from 'src/db/auctions'; import { AssetByIdentifierService } from 'src/modules/assets'; -import { ElrondNftsSwapAuctionEventEnum } from 'src/modules/assets/models'; -import { AuctionsSetterService } from 'src/modules/auctions'; +import { + ElrondNftsSwapAuctionEventEnum, + ExternalAuctionEventEnum, +} from 'src/modules/assets/models'; +import { + AuctionsGetterService, + AuctionsSetterService, +} from 'src/modules/auctions'; import { ElrondSwapAuctionTypeEnum } from 'src/modules/auctions/models'; import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; import { Marketplace } from 'src/modules/marketplaces/models'; import { MarketplaceTypeEnum } from 'src/modules/marketplaces/models/MarketplaceType.enum'; import { UsdPriceService } from 'src/modules/usdPrice/usd-price.service'; -import { ELRONDNFTSWAP_KEY } from 'src/utils/constants'; +import { ELRONDNFTSWAP_KEY, ENEFTOR_KEY } from 'src/utils/constants'; import { AuctionTokenEvent } from '../../entities/auction'; import { ElrondSwapAuctionEvent } from '../../entities/auction/elrondnftswap/elrondswap-auction.event'; +import { ListNftEvent } from '../../entities/auction/listNft.event'; import { FeedEventsSenderService } from '../feed-events.service'; @Injectable() export class StartAuctionEventHandler { private readonly logger = new Logger(StartAuctionEventHandler.name); constructor( - private auctionsService: AuctionsSetterService, + private auctionsSetterService: AuctionsSetterService, + private auctionsGetterService: AuctionsGetterService, private feedEventsSenderService: FeedEventsSenderService, private assetByIdentifierService: AssetByIdentifierService, private usdPriceService: UsdPriceService, @@ -28,7 +37,6 @@ export class StartAuctionEventHandler { const { auctionTokenEvent, topics } = this.getEventAndTopics(event); if (!auctionTokenEvent && !topics) return; - const auctionIdentifier = `${topics.collection}-${topics.nonce}`; const marketplace = await this.marketplaceService.getMarketplaceByType( auctionTokenEvent.getAddress(), marketplaceType, @@ -39,12 +47,7 @@ export class StartAuctionEventHandler { this.logger.log( `Auction listing event detected for hash '${hash}' and marketplace '${marketplace?.name}'`, ); - const auction = await this.saveAuction( - topics, - auctionIdentifier, - marketplace, - hash, - ); + const auction = await this.saveAuction(topics, marketplace, hash); if (!auction) return; @@ -57,19 +60,30 @@ export class StartAuctionEventHandler { private async saveAuction( topics: any, - auctionIdentifier: string, marketplace: Marketplace, hash: string, ) { - if (marketplace.key === ELRONDNFTSWAP_KEY) { - return await this.handleElrondSwapAuction( + const auctionIdentifier = `${topics.collection}-${topics.nonce}`; + if ( + marketplace.key === ELRONDNFTSWAP_KEY || + marketplace.key === ENEFTOR_KEY + ) { + if (topics.auctionId === '0') { + let auctionId = + await this.auctionsGetterService.getLastAuctionIdForMarketplace( + marketplace.key, + ); + topics.auctionId = auctionId && auctionId > 0 ? auctionId++ : 1; + } + return await this.handleSaveAuctionFromTopics( auctionIdentifier, topics, hash, marketplace, ); } - return await this.auctionsService.saveAuction( + + return await this.auctionsSetterService.saveAuction( parseInt(topics.auctionId, 16), auctionIdentifier, marketplace, @@ -77,26 +91,29 @@ export class StartAuctionEventHandler { ); } - private async handleElrondSwapAuction( + private async handleSaveAuctionFromTopics( auctionIdentifier: string, topics: any, hash: string, auctionTokenEventMarketplace: Marketplace, ) { + let decimals = elrondConfig.decimals; const asset = await this.assetByIdentifierService.getAsset( auctionIdentifier, ); - - const paymentToken = await this.usdPriceService.getToken( - topics.paymentToken, - ); - return await this.auctionsService.saveAuctionEntity( + if (topics.paymentToken !== elrondConfig.egld) { + const paymentToken = await this.usdPriceService.getToken( + topics.paymentToken, + ); + decimals = paymentToken.decimals; + } + return await this.auctionsSetterService.saveAuctionEntity( AuctionEntity.fromWithdrawTopics( topics, asset.tags?.toString(), hash, auctionTokenEventMarketplace.key, - paymentToken?.decimals, + decimals, ), asset.tags, ); @@ -111,6 +128,12 @@ export class StartAuctionEventHandler { } return { auctionTokenEvent, topics }; } + + if (event.identifier === ExternalAuctionEventEnum.ListNftOnMarketplace) { + const auctionTokenEvent = new ListNftEvent(event); + const topics = auctionTokenEvent.getTopics(); + return { auctionTokenEvent, topics }; + } const auctionTokenEvent = new AuctionTokenEvent(event); const topics = auctionTokenEvent.getTopics(); return { auctionTokenEvent, topics }; diff --git a/src/modules/rabbitmq/blockchain-events/handlers/withdrawAuction-event.handler.ts b/src/modules/rabbitmq/blockchain-events/handlers/withdrawAuction-event.handler.ts index b8909ee80..0bc9072a0 100644 --- a/src/modules/rabbitmq/blockchain-events/handlers/withdrawAuction-event.handler.ts +++ b/src/modules/rabbitmq/blockchain-events/handlers/withdrawAuction-event.handler.ts @@ -1,7 +1,9 @@ import { Injectable, Logger } from '@nestjs/common'; +import { AuctionEntity } from 'src/db/auctions'; import { AuctionEventEnum, ElrondNftsSwapAuctionEventEnum, + ExternalAuctionEventEnum, } from 'src/modules/assets/models'; import { AuctionsGetterService, @@ -9,9 +11,9 @@ import { } from 'src/modules/auctions'; import { AuctionStatusEnum } from 'src/modules/auctions/models'; import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; -import { Marketplace } from 'src/modules/marketplaces/models'; import { MarketplaceTypeEnum } from 'src/modules/marketplaces/models/MarketplaceType.enum'; import { WithdrawEvent } from '../../entities/auction'; +import { ClaimEvent } from '../../entities/auction/claim.event'; import { ElrondSwapWithdrawEvent } from '../../entities/auction/elrondnftswap/elrondswap-withdraw.event'; @Injectable() @@ -25,6 +27,7 @@ export class WithdrawAuctionEventHandler { async handle(event: any, hash: string, marketplaceType: MarketplaceTypeEnum) { const { withdraw, topics } = this.getEventAndTopics(event); + let auction: AuctionEntity; const marketplace = await this.marketplaceService.getMarketplaceByType( withdraw.getAddress(), marketplaceType, @@ -35,11 +38,19 @@ export class WithdrawAuctionEventHandler { this.logger.log( `Withdraw event detected for hash '${hash}' and marketplace '${marketplace?.name}'`, ); - const auction = - await this.auctionsGetterService.getAuctionByIdAndMarketplace( + if (topics.auctionId) { + auction = await this.auctionsGetterService.getAuctionByIdAndMarketplace( parseInt(topics.auctionId, 16), marketplace.key, ); + } else { + const auctionIdentifier = `${topics.collection}-${topics.nonce}`; + auction = + await this.auctionsGetterService.getAuctionByIdentifierAndMarketplace( + auctionIdentifier, + marketplace.key, + ); + } if (!auction) return; @@ -57,6 +68,13 @@ export class WithdrawAuctionEventHandler { const topics = withdraw.getTopics(); return { withdraw, topics }; } + + if (event.identifier === ExternalAuctionEventEnum.ClaimBackNft) { + const withdraw = new ClaimEvent(event); + const topics = withdraw.getTopics(); + return { withdraw, topics }; + } + const withdraw = new WithdrawEvent(event); const topics = withdraw.getTopics(); return { withdraw, topics }; diff --git a/src/modules/rabbitmq/blockchain-events/marketplace-events.service.ts b/src/modules/rabbitmq/blockchain-events/marketplace-events.service.ts index 3edbce8bb..14e999a57 100644 --- a/src/modules/rabbitmq/blockchain-events/marketplace-events.service.ts +++ b/src/modules/rabbitmq/blockchain-events/marketplace-events.service.ts @@ -47,6 +47,7 @@ export class MarketplaceEventsService { case AuctionEventEnum.BuySftEvent: case ExternalAuctionEventEnum.Buy: case ExternalAuctionEventEnum.BulkBuy: + case ExternalAuctionEventEnum.BuyNft: case ElrondNftsSwapAuctionEventEnum.Purchase: const eventName = Buffer.from(event.topics[0], 'base64').toString(); if ( @@ -62,6 +63,7 @@ export class MarketplaceEventsService { break; case AuctionEventEnum.WithdrawEvent: case ElrondNftsSwapAuctionEventEnum.WithdrawSwap: + case ExternalAuctionEventEnum.ClaimBackNft: if ( Buffer.from(event.topics[0], 'base64').toString() === ExternalAuctionEventEnum.UpdateOffer @@ -86,6 +88,7 @@ export class MarketplaceEventsService { break; case AuctionEventEnum.AuctionTokenEvent: case ExternalAuctionEventEnum.Listing: + case ExternalAuctionEventEnum.ListNftOnMarketplace: case ElrondNftsSwapAuctionEventEnum.NftSwap: await this.startAuctionEventHandler.handle( event, diff --git a/src/modules/rabbitmq/entities/auction/claim.event.topics.ts b/src/modules/rabbitmq/entities/auction/claim.event.topics.ts new file mode 100644 index 000000000..3f40e212d --- /dev/null +++ b/src/modules/rabbitmq/entities/auction/claim.event.topics.ts @@ -0,0 +1,25 @@ +import { Address } from '@elrondnetwork/erdjs'; + +export class ClaimEventsTopics { + private currentWinner: Address; + private collection: string; + private nonce: string; + private auctionId: string; + private boughtTokens: string = '1'; + + constructor(rawTopics: string[]) { + this.currentWinner = new Address(Buffer.from(rawTopics[1], 'base64')); + this.collection = Buffer.from(rawTopics[2], 'base64').toString(); + this.nonce = Buffer.from(rawTopics[3], 'base64').toString('hex'); + } + + toPlainObject() { + return { + currentWinner: this.currentWinner.bech32(), + collection: this.collection, + nonce: this.nonce, + auctionId: this.auctionId, + boughtTokens: this.boughtTokens, + }; + } +} diff --git a/src/modules/rabbitmq/entities/auction/claim.event.ts b/src/modules/rabbitmq/entities/auction/claim.event.ts new file mode 100644 index 000000000..45e67713b --- /dev/null +++ b/src/modules/rabbitmq/entities/auction/claim.event.ts @@ -0,0 +1,15 @@ +import { GenericEvent } from '../generic.event'; +import { ClaimEventsTopics } from './claim.event.topics'; + +export class ClaimEvent extends GenericEvent { + private decodedTopics: ClaimEventsTopics; + + constructor(init?: Partial) { + super(init); + this.decodedTopics = new ClaimEventsTopics(this.topics); + } + + getTopics() { + return this.decodedTopics.toPlainObject(); + } +} diff --git a/src/modules/rabbitmq/entities/auction/listNft.event.topics.ts b/src/modules/rabbitmq/entities/auction/listNft.event.topics.ts new file mode 100644 index 000000000..9ff02cf24 --- /dev/null +++ b/src/modules/rabbitmq/entities/auction/listNft.event.topics.ts @@ -0,0 +1,39 @@ +import { Address } from '@elrondnetwork/erdjs/out'; +import { elrondConfig } from 'src/config'; + +export class ListNftEventsTopics { + private collection: string; + private nonce: string; + private auctionId: string = '0'; + private nrAuctionTokens: string = '1'; + private originalOwner: Address; + private price: string; + private paymentToken: string = elrondConfig.egld; + private paymentTokenNonce: string = '0'; + private auctionType: string = ''; + private deadline: number = 0; + + constructor(rawTopics: string[]) { + this.originalOwner = new Address(Buffer.from(rawTopics[1], 'base64')); + this.collection = Buffer.from(rawTopics[2], 'base64').toString(); + this.nonce = Buffer.from(rawTopics[3], 'base64').toString('hex'); + this.price = Buffer.from(rawTopics[4], 'base64') + .toString('hex') + .hexBigNumberToString(); + } + + toPlainObject() { + return { + originalOwner: this.originalOwner.bech32(), + collection: this.collection, + nonce: this.nonce, + auctionId: this.auctionId, + nrAuctionTokens: this.nrAuctionTokens, + price: this.price, + paymentToken: this.paymentToken, + paymentTokenNonce: this.paymentTokenNonce, + auctionType: this.auctionType, + deadline: this.deadline, + }; + } +} diff --git a/src/modules/rabbitmq/entities/auction/listNft.event.ts b/src/modules/rabbitmq/entities/auction/listNft.event.ts new file mode 100644 index 000000000..b0eb3dfb2 --- /dev/null +++ b/src/modules/rabbitmq/entities/auction/listNft.event.ts @@ -0,0 +1,15 @@ +import { GenericEvent } from '../generic.event'; +import { ListNftEventsTopics } from './listNft.event.topics'; + +export class ListNftEvent extends GenericEvent { + private decodedTopics: ListNftEventsTopics; + + constructor(init?: Partial) { + super(init); + this.decodedTopics = new ListNftEventsTopics(this.topics); + } + + getTopics() { + return this.decodedTopics.toPlainObject(); + } +} diff --git a/src/utils/constants/index.ts b/src/utils/constants/index.ts index 2718c0f87..5b7c4a6f4 100644 --- a/src/utils/constants/index.ts +++ b/src/utils/constants/index.ts @@ -19,4 +19,5 @@ export const XOXNO_MINTING_MANAGER = 'erd1qqqqqqqqqqqqqpgqg9fa0dmpn8fu3fnleeqn5zt8rl8mdqjkys5s2gtas7'; export const DEADRARE_KEY = 'deadrare'; export const ELRONDNFTSWAP_KEY = 'elrondnftswap'; +export const ENEFTOR_KEY = 'eneftor'; export const FRAMEIT_KEY = 'frameit';