Last active
August 22, 2024 03:03
-
-
Save Yuripetusko/68d9f328a4cd5c85fc881ffc731db47d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Processor } from './helpers'; | |
| import * as rmrkEquippable from '../abi/rmrkEquippable'; | |
| import { | |
| getCollectionDatabaseId, | |
| getEquippableGroupDatabaseId, | |
| getTokenDatabaseId, | |
| } from '../model/helpers'; | |
| import { | |
| CollectionValidParentEquippableGroupIdSetEventPayload, | |
| EquippableGroup, | |
| EquippedEventPayload, | |
| Event, | |
| EventType, | |
| NftAsset, | |
| } from '../model'; | |
| import { BigNumber } from 'ethers'; | |
| const getEquippedEventPayload = (decodedEvent: { | |
| tokenId: BigNumber; | |
| assetId: BigNumber; | |
| slotPartId: BigNumber; | |
| childId: BigNumber; | |
| childAddress: string; | |
| childAssetId: BigNumber; | |
| }) => { | |
| return new EquippedEventPayload({ | |
| tokenId: decodedEvent.tokenId.toBigInt(), | |
| assetId: decodedEvent.assetId.toBigInt(), | |
| slotPartId: decodedEvent.slotPartId.toBigInt(), | |
| childId: decodedEvent.childId.toBigInt(), | |
| childAddress: decodedEvent.childAddress, | |
| childAssetId: decodedEvent.childAssetId.toBigInt(), | |
| }); | |
| }; | |
| /** | |
| * Process ChildAssetEquipped event. This event is emitted when a child asset is equipped to a token | |
| * @param decodedEvent | |
| * @param originalEvent | |
| * @param ctx | |
| */ | |
| export const processChildAssetEquipped: Processor< | |
| ReturnType<(typeof rmrkEquippable.events)['ChildAssetEquipped']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const cache = ctx.batchState.cachedState; | |
| const token = await cache.tokens.get( | |
| getTokenDatabaseId(collectionId, decodedEvent.tokenId), | |
| { collection: true } | |
| ); | |
| if (token) { | |
| token.equipmentUpdatedAt = ctx.dbBlock.timestamp; | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| eventType: EventType.ChildAssetEquipped, | |
| token, | |
| payload: getEquippedEventPayload(decodedEvent), | |
| }) | |
| ); | |
| } | |
| }; | |
| /** | |
| * Process ChildAssetUnequipped event. This event is emitted when a child asset is unequipped from a token. | |
| * @param decodedEvent | |
| * @param originalEvent | |
| * @param ctx | |
| */ | |
| export const processChildAssetUnequipped: Processor< | |
| ReturnType<(typeof rmrkEquippable.events)['ChildAssetUnequipped']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const cache = ctx.batchState.cachedState; | |
| const token = await cache.tokens.get( | |
| getTokenDatabaseId(collectionId, decodedEvent.tokenId), | |
| { collection: true } | |
| ); | |
| if (token) { | |
| token.equipmentUpdatedAt = ctx.dbBlock.timestamp; | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| eventType: EventType.ChildAssetUnequipped, | |
| token, | |
| payload: getEquippedEventPayload(decodedEvent), | |
| }) | |
| ); | |
| } | |
| }; | |
| /** | |
| * Process ValidParentEquippableGroupIdSet event. This event is emitted mapping of equippableGroupId to parent contract address and slot id is set | |
| * @param decodedEvent | |
| * @param originalEvent | |
| * @param ctx | |
| */ | |
| export const processValidParentEquippableGroupIdSet: Processor< | |
| ReturnType< | |
| (typeof rmrkEquippable.events)['ValidParentEquippableGroupIdSet']['decode'] | |
| > | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const { equippableGroupId, slotPartId, parentAddress } = decodedEvent; | |
| const cache = ctx.batchState.cachedState; | |
| const collection = await cache.collections.getOrThrow(collectionId); | |
| const equippableGroup = await cache.equippableGroups.getOrCreate( | |
| getEquippableGroupDatabaseId(collectionId, equippableGroupId), | |
| () => | |
| new EquippableGroup({ | |
| collection, | |
| parentCollectionId: getCollectionDatabaseId(parentAddress), | |
| slotPartId: slotPartId.toBigInt(), | |
| }) | |
| ); | |
| //TODO: ? Since equippableGroup id consists of child collectionId + slotId, if child collecton gets new equippableGroup with the same slotId, it will not update parentCollectionId in it | |
| // Let's overwrite previous group id for now. This means that if collection want to target same slot ids but different parent collection, only the last group will be saved. | |
| // We might want to make id of equippagbleFGrop more unique, possibly include parent contract address there too | |
| equippableGroup.parentCollectionId = getCollectionDatabaseId(parentAddress); | |
| // FIXME: This sucks as we are fetching all assets twice, once in prefetcher, and once here, because there is no way to get entities by where clause from our cache EntityManager | |
| const tokenAssets = await ctx.ctx.store.find(NftAsset, { | |
| where: { | |
| equippableGroupId: getEquippableGroupDatabaseId( | |
| originalEvent.address, | |
| decodedEvent.equippableGroupId | |
| ), | |
| }, | |
| }); | |
| for (const tokenAsset of tokenAssets) { | |
| const tokenAssetFromCache = | |
| await ctx.batchState.cachedState.tokenAssets.get(tokenAsset.id); | |
| if (tokenAssetFromCache) { | |
| tokenAssetFromCache.equippableGroupEntity = equippableGroup; | |
| } | |
| } | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| eventType: EventType.ValidParentEquippableGroupIdSet, | |
| collection, | |
| payload: new CollectionValidParentEquippableGroupIdSetEventPayload({ | |
| slotPartId: slotPartId.toBigInt(), | |
| parentAddress, | |
| equippableGroupId: equippableGroupId.toBigInt(), | |
| }), | |
| }) | |
| ); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| Asset, | |
| AssetAcceptedEventPayload, | |
| AssetAddedToTokensEventPayload, | |
| AssetPrioritySetEventPayload, | |
| BatchMetadataUpdateEventPayload, | |
| BurnEventPayload, | |
| Event, | |
| EventType, | |
| MetadataUpdateEventPayload, | |
| MintEventPayload, | |
| NftAsset, | |
| OwnershipTransferredEventPayload, | |
| TransferEventPayload, | |
| } from '../model'; | |
| import { | |
| getAssetDatabaseId, | |
| getCollectionDatabaseId, | |
| getTokenAssetId, | |
| getTokenDatabaseId, | |
| } from '../model/helpers'; | |
| import { Processor } from './helpers'; | |
| import * as rmrkMultiAsset from '../abi/rmrkMultiAsset'; | |
| import * as erc4906 from '../abi/ERC4906MetadataUpdate'; | |
| import * as rmrkCollectionUtils from '../abi/RMRKCollectionUtils'; | |
| import { BigNumber, ethers } from 'ethers'; | |
| import { getOrCreateTokenEntityForTransferEvent } from './common'; | |
| import { NotificationType } from '../services/NotificationsManager'; | |
| export const processTokenTransfer: Processor< | |
| ReturnType<(typeof rmrkMultiAsset.events)['Transfer']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const cache = ctx.batchState.cachedState; | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const tokenId = getTokenDatabaseId(collectionId, decodedEvent.tokenId); | |
| const isMintEvent = decodedEvent.from === ethers.constants.AddressZero; | |
| const isBurnEvent = decodedEvent.to === ethers.constants.AddressZero; | |
| const token = await getOrCreateTokenEntityForTransferEvent( | |
| tokenId, | |
| collectionId, | |
| isMintEvent, | |
| cache | |
| ); | |
| token.owner = decodedEvent.to; | |
| // Mint | |
| if (isMintEvent) { | |
| token.equipmentUpdatedAt = ctx.dbBlock.timestamp; | |
| ctx.batchState.multicalls.tokenMintedMulticallEntities.set(token.id, token); | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| eventType: EventType.TokenMint, | |
| token, | |
| payload: new MintEventPayload({ | |
| from: decodedEvent.from, | |
| to: decodedEvent.to, | |
| tokenId: decodedEvent.tokenId.toBigInt(), | |
| }), | |
| }) | |
| ); | |
| return; | |
| } | |
| // Burn | |
| if (isBurnEvent) { | |
| token.burned = true; | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| eventType: EventType.TokenBurn, | |
| token, | |
| payload: new BurnEventPayload({ | |
| from: decodedEvent.from, | |
| to: decodedEvent.to, | |
| tokenId: decodedEvent.tokenId.toBigInt(), | |
| }), | |
| }) | |
| ); | |
| return; | |
| } | |
| // Transfer | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| eventType: EventType.TokenTransfer, | |
| token: token, | |
| payload: new TransferEventPayload({ | |
| from: decodedEvent.from, | |
| to: decodedEvent.to, | |
| tokenId: decodedEvent.tokenId.toBigInt(), | |
| }), | |
| }) | |
| ); | |
| }; | |
| export const processOwnershipTransferred: Processor< | |
| ReturnType<(typeof rmrkMultiAsset.events)['OwnershipTransferred']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| // FIXME: As we start listening to catalogs also, OwnershipTransferred event can be emitted on catalog contract, not on collection contract, so we might need to stop throwing error here if collection is not found | |
| const collection = await ctx.batchState.cachedState.collections.getOrThrow( | |
| collectionId | |
| ); | |
| collection.owner = decodedEvent.newOwner; | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| eventType: EventType.CollectionOwnershipTransfer, | |
| collection, | |
| payload: new OwnershipTransferredEventPayload({ | |
| newOwner: decodedEvent.newOwner, | |
| previousOwner: decodedEvent.previousOwner, | |
| }), | |
| }) | |
| ); | |
| }; | |
| export const processAssetSet: Processor< | |
| ReturnType<(typeof rmrkMultiAsset.events)['AssetSet']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const emCtx = ctx.batchState.cachedState; | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const id = getAssetDatabaseId(collectionId, decodedEvent.assetId); | |
| const collection = await emCtx.collections.get(collectionId); | |
| if (!collection) { | |
| ctx.ctx.log.error({ collectionId }, 'No collection found'); | |
| return; | |
| } | |
| const asset = new Asset({ | |
| id: id, | |
| collection: collection, | |
| }); | |
| ctx.batchState.cachedState.assets.add(asset); | |
| }; | |
| export const processAssetAddedToTokens: Processor< | |
| ReturnType<(typeof rmrkMultiAsset.events)['AssetAddedToTokens']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const notificationsManager = ctx.state.notificationManager; | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const collection = await ctx.batchState.cachedState.collections.get( | |
| collectionId | |
| ); | |
| const assetId = getAssetDatabaseId(collectionId, decodedEvent.assetId); | |
| const asset = await ctx.batchState.cachedState.assets.get(assetId); | |
| if (!asset) { | |
| ctx.ctx.log.error( | |
| { event: 'AssetAddedToTokens', assetId }, | |
| 'No asset found' | |
| ); | |
| return; | |
| } | |
| await Promise.all([ | |
| ...decodedEvent.tokenIds.map(async (tokenId) => { | |
| const nftAssetId = getTokenAssetId( | |
| collectionId, | |
| tokenId, | |
| decodedEvent.assetId | |
| ); | |
| const token = await ctx.batchState.cachedState.tokens.get( | |
| getTokenDatabaseId(collectionId, tokenId), | |
| { collection: true } | |
| ); | |
| if (!token) { | |
| ctx.ctx.log.error( | |
| { | |
| tokenId, | |
| databaseTokenId: getTokenDatabaseId(collectionId, tokenId), | |
| }, | |
| 'Unexpected state met for AssetAddedToTokens' | |
| ); | |
| return; | |
| } | |
| const nftAsset = await ctx.batchState.cachedState.tokenAssets.getOrCreate( | |
| nftAssetId, | |
| () => | |
| new NftAsset({ | |
| nft: token, | |
| asset, | |
| pending: true, | |
| }) | |
| ); | |
| ctx.batchState.multicalls.getAssetMetadataMulticallEntities.set( | |
| nftAsset.id, | |
| nftAsset | |
| ); | |
| // #### Notifications logic #### | |
| if (collection?.owner !== token.owner) { | |
| notificationsManager.notify(NotificationType.ASSET_ADDED_TO_TOKEN, { | |
| tokenId: token.id, | |
| subscriberId: token.owner, | |
| timestamp: ctx.dbBlock.timestamp.getTime(), | |
| }); | |
| } | |
| // #### End of notification logic #### | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| token, | |
| eventType: EventType.AssetAddedToTokens, | |
| payload: new AssetAddedToTokensEventPayload({ | |
| tokenId: tokenId.toBigInt(), | |
| assetId: decodedEvent.assetId.toBigInt(), | |
| replacesId: decodedEvent.replacesId.toBigInt(), | |
| }), | |
| }) | |
| ); | |
| }), | |
| ]); | |
| }; | |
| export const processAssetAccepted: Processor< | |
| ReturnType<(typeof rmrkMultiAsset.events)['AssetAccepted']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const nftId = getTokenDatabaseId(collectionId, decodedEvent.tokenId); | |
| const nftAssetId = getTokenAssetId( | |
| collectionId, | |
| decodedEvent.tokenId, | |
| decodedEvent.assetId | |
| ); | |
| const nftAsset = await ctx.batchState.cachedState.tokenAssets.get(nftAssetId); | |
| if (!nftAsset) { | |
| ctx.ctx.log.error({ nftAssetId }, 'Unexpected state met'); | |
| return; | |
| } | |
| nftAsset.pending = false; | |
| const token = await ctx.batchState.cachedState.tokens.get(nftId, { | |
| collection: true, | |
| }); | |
| if (!token) { | |
| ctx.ctx.log.warn( | |
| { nftId, nftAssetId }, | |
| 'AssetAccepted: token not found in the store' | |
| ); | |
| } | |
| if (token) { | |
| // Contracts where tokenURI points to an asset, need to try to fetch tokenURI again once the first asset is added | |
| if (token.activeAssetIds?.length == 0) { | |
| ctx.batchState.multicalls.tokenMintedMulticallEntities.set( | |
| token.id, | |
| token | |
| ); | |
| } | |
| // If replacesId is not zero, we can be sure that Token Asset was replaced with this new one, so we need to remove the old Asset from token relation | |
| const isReplacement = !decodedEvent.replacesId.isZero(); | |
| if (isReplacement) { | |
| const replacedNftAssetId = getTokenAssetId( | |
| collectionId, | |
| decodedEvent.tokenId, | |
| decodedEvent.replacesId | |
| ); | |
| // Remove replaced token asset from cache/database | |
| await ctx.batchState.cachedState.tokenAssets.remove(replacedNftAssetId); | |
| // Remove replaced token asset from multicall array | |
| ctx.batchState.multicalls.getAssetMetadataMulticallEntities.delete( | |
| replacedNftAssetId | |
| ); | |
| // When asset is replaced, it's better to re-fetch tokenURI in case it was using asset's metadata | |
| ctx.batchState.multicalls.tokenMintedMulticallEntities.set( | |
| token.id, | |
| token | |
| ); | |
| // Remove replaced token asset from active asset ids array | |
| const replacedActiveAssetIdIndex = token.activeAssetIds?.findIndex( | |
| (activeAssetId) => activeAssetId === decodedEvent.replacesId.toNumber() | |
| ); | |
| if ( | |
| replacedActiveAssetIdIndex !== undefined && | |
| replacedActiveAssetIdIndex > -1 | |
| ) { | |
| token.activeAssetIds?.splice( | |
| replacedActiveAssetIdIndex, | |
| 1, | |
| decodedEvent.assetId.toNumber() | |
| ); | |
| } | |
| } else { | |
| // Only push new asset id to the end of activeAssetIds array if it's not a replacement | |
| // We need to keep activeAssetIds in database in the same order as it is on chain, to be able to accurately sort resources by priority | |
| if (!token.activeAssetIds?.includes(decodedEvent.assetId.toNumber())) { | |
| token.activeAssetIds = [ | |
| ...(token.activeAssetIds || []), | |
| decodedEvent.assetId.toNumber(), | |
| ]; | |
| } | |
| } | |
| } | |
| if (token) { | |
| ctx.batchState.multicalls.getActiveAssetPrioritiesMulticallEntities.set( | |
| token.id, | |
| token | |
| ); | |
| } | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| token, | |
| eventType: EventType.AssetAccepted, | |
| payload: new AssetAcceptedEventPayload({ | |
| tokenId: decodedEvent.tokenId.toBigInt(), | |
| assetId: decodedEvent.assetId.toBigInt(), | |
| replacesId: decodedEvent.replacesId.toBigInt(), | |
| }), | |
| }) | |
| ); | |
| }; | |
| export const processAssetPrioritySet: Processor< | |
| ReturnType<(typeof rmrkMultiAsset.events)['AssetPrioritySet']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const tokenId = getTokenDatabaseId(collectionId, decodedEvent.tokenId); | |
| const token = await ctx.batchState.cachedState.tokens.get(tokenId, { | |
| collection: true, | |
| }); | |
| if (token) { | |
| ctx.batchState.multicalls.getActiveAssetPrioritiesMulticallEntities.set( | |
| token.id, | |
| token | |
| ); | |
| } else { | |
| ctx.ctx.log.warn(`AssetPrioritySet: token ${tokenId} not found in store`); | |
| } | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| token, | |
| eventType: EventType.AssetPrioritySet, | |
| payload: new AssetPrioritySetEventPayload({ | |
| tokenId: decodedEvent.tokenId.toBigInt(), | |
| }), | |
| }) | |
| ); | |
| }; | |
| export const processAssetRejected: Processor< | |
| ReturnType<(typeof rmrkMultiAsset.events)['AssetRejected']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| await ctx.batchState.cachedState.tokenAssets.remove( | |
| getTokenAssetId(collectionId, decodedEvent.tokenId, decodedEvent.assetId) | |
| ); | |
| }; | |
| export const processMetadataUpdate: Processor< | |
| ReturnType<(typeof erc4906.events)['MetadataUpdate']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| const nftId = getTokenDatabaseId(collectionId, decodedEvent._tokenId); | |
| const token = await ctx.batchState.cachedState.tokens.get(nftId); | |
| if (token) { | |
| ctx.batchState.multicalls.tokensMetadataUpdatedMulticallEntities.set( | |
| token.id, | |
| token | |
| ); | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| token, | |
| eventType: EventType.MetadataUpdate, | |
| payload: new MetadataUpdateEventPayload({ | |
| tokenId: decodedEvent._tokenId.toBigInt(), | |
| collectionId: originalEvent.address, | |
| }), | |
| }) | |
| ); | |
| } | |
| }; | |
| export const processBatchMetadataUpdate: Processor< | |
| ReturnType<(typeof erc4906.events)['BatchMetadataUpdate']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(originalEvent.address); | |
| if (decodedEvent._fromTokenId.lt(BigNumber.from(0))) { | |
| ctx.ctx.log.error( | |
| `BatchMetadataUpdate: _fromTokenId is less than 0: ${decodedEvent._fromTokenId.toString()}` | |
| ); | |
| return; | |
| } | |
| if ( | |
| decodedEvent._toTokenId | |
| .sub(decodedEvent._fromTokenId) | |
| .gt(BigNumber.from(10_000)) | |
| ) { | |
| ctx.ctx.log.error( | |
| `BatchMetadataUpdate: Number of token is too large (more than 10_000), we don't want to process so many refreshes: from: ${decodedEvent._fromTokenId.toString()} to: ${decodedEvent._toTokenId.toString()}` | |
| ); | |
| return; | |
| } | |
| if (decodedEvent._fromTokenId.gte(decodedEvent._toTokenId)) { | |
| ctx.ctx.log.error( | |
| `BatchMetadataUpdate: toTokenId cannot be less than or equal to fromTokenId: ${decodedEvent._fromTokenId.toString()} to: ${decodedEvent._toTokenId.toString()}` | |
| ); | |
| return; | |
| } | |
| const collection = await ctx.batchState.cachedState.collections.get( | |
| collectionId | |
| ); | |
| let nextTokenIdInBatch = decodedEvent._fromTokenId; | |
| do { | |
| const nftId = getTokenDatabaseId(collectionId, nextTokenIdInBatch); | |
| const token = await ctx.batchState.cachedState.tokens.get(nftId); | |
| if (token) { | |
| ctx.batchState.multicalls.tokensMetadataUpdatedMulticallEntities.set( | |
| token.id, | |
| token | |
| ); | |
| } | |
| nextTokenIdInBatch = nextTokenIdInBatch.add(BigNumber.from(1)); | |
| } while (nextTokenIdInBatch.lte(decodedEvent._toTokenId)); | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| collection, | |
| eventType: EventType.BatchMetadataUpdate, | |
| payload: new BatchMetadataUpdateEventPayload({ | |
| fromTokenId: decodedEvent._fromTokenId.toBigInt(), | |
| toTokenId: decodedEvent._toTokenId.toBigInt(), | |
| collectionId: originalEvent.address, | |
| }), | |
| }) | |
| ); | |
| }; | |
| export const processCollectionUtilsMetadataUpdate: Processor< | |
| ReturnType<(typeof rmrkCollectionUtils.events)['MetadataUpdate']['decode']> | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(decodedEvent.collection); | |
| const nftId = getTokenDatabaseId(collectionId, decodedEvent.tokenId); | |
| const token = await ctx.batchState.cachedState.tokens.get(nftId); | |
| if (token) { | |
| ctx.batchState.multicalls.tokensMetadataUpdatedMulticallEntities.set( | |
| token.id, | |
| token | |
| ); | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| token, | |
| eventType: EventType.MetadataUpdate, | |
| payload: new MetadataUpdateEventPayload({ | |
| tokenId: decodedEvent.tokenId.toBigInt(), | |
| collectionId: decodedEvent.collection, | |
| }), | |
| }) | |
| ); | |
| } | |
| }; | |
| export const processCollectionUtilsBatchMetadataUpdate: Processor< | |
| ReturnType< | |
| (typeof rmrkCollectionUtils.events)['BatchMetadataUpdate']['decode'] | |
| > | |
| > = async (decodedEvent, originalEvent, ctx) => { | |
| const collectionId = getCollectionDatabaseId(decodedEvent.collection); | |
| if (decodedEvent.fromTokenId.lt(BigNumber.from(0))) { | |
| ctx.ctx.log.error( | |
| `BatchMetadataUpdate: fromTokenId is less than 0: ${decodedEvent.fromTokenId.toString()}` | |
| ); | |
| return; | |
| } | |
| if ( | |
| decodedEvent.toTokenId | |
| .sub(decodedEvent.toTokenId) | |
| .gt(BigNumber.from(10_000)) | |
| ) { | |
| ctx.ctx.log.error( | |
| `BatchMetadataUpdate: Number of token is too large (more than 10_000), we don't want to process so many refreshes: from: ${decodedEvent.fromTokenId.toString()} to: ${decodedEvent.toTokenId.toString()}` | |
| ); | |
| return; | |
| } | |
| if (decodedEvent.fromTokenId.gte(decodedEvent.toTokenId)) { | |
| ctx.ctx.log.error( | |
| `BatchMetadataUpdate: toTokenId cannot be less than or equal to fromTokenId: ${decodedEvent.fromTokenId.toString()} to: ${decodedEvent.toTokenId.toString()}` | |
| ); | |
| return; | |
| } | |
| ctx.ctx.log.info( | |
| { | |
| fromTokenId: decodedEvent.fromTokenId.toString(), | |
| toTokenId: decodedEvent.toTokenId.toString(), | |
| }, | |
| 'BatchMetadataUpdate' | |
| ); | |
| const collection = await ctx.batchState.cachedState.collections.get( | |
| collectionId | |
| ); | |
| let nextTokenIdInBatch = decodedEvent.fromTokenId; | |
| do { | |
| const nftId = getTokenDatabaseId(collectionId, nextTokenIdInBatch); | |
| const token = await ctx.batchState.cachedState.tokens.get(nftId); | |
| if (token) { | |
| ctx.batchState.multicalls.tokensMetadataUpdatedMulticallEntities.set( | |
| token.id, | |
| token | |
| ); | |
| } else { | |
| ctx.ctx.log.warn( | |
| { | |
| nftId, | |
| }, | |
| 'BatchMetadataUpdate: Token not found' | |
| ); | |
| } | |
| nextTokenIdInBatch = nextTokenIdInBatch.add(BigNumber.from(1)); | |
| } while (nextTokenIdInBatch.lte(decodedEvent.toTokenId)); | |
| ctx.eventFacade.addEvent( | |
| new Event({ | |
| collection, | |
| eventType: EventType.BatchMetadataUpdate, | |
| payload: new BatchMetadataUpdateEventPayload({ | |
| fromTokenId: decodedEvent.fromTokenId.toBigInt(), | |
| toTokenId: decodedEvent.toTokenId.toBigInt(), | |
| collectionId: decodedEvent.collection, | |
| }), | |
| }) | |
| ); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| type SimbleBlock { | |
| id: ID! | |
| hash: String! | |
| height: Int! | |
| parentHash: String! | |
| timestamp: Int! | |
| } | |
| enum SyncState { | |
| Synced | |
| Failed | |
| OutOfSync | |
| } | |
| type RawLog @entity @index(fields: ["blockNumber", "network"]) { | |
| id: ID! | |
| logIndex: Int! | |
| transactionIndex: Int! | |
| data: String! | |
| transactionHash: String! | |
| address: String! | |
| topics: [String!]! | |
| blockNumber: Int @index | |
| syncState: SyncState | |
| block: SimbleBlock! | |
| network: Network! | |
| } | |
| type Block @entity { | |
| id: ID! | |
| number: Int! @index | |
| timestamp: DateTime! | |
| network: Network! | |
| events: [Event]! @derivedFrom(field: "block") | |
| } | |
| type Event @entity { | |
| id: ID! | |
| block: Block! | |
| transactionHash: String! | |
| eventType: EventType @index | |
| marketplaceEventMetadata: MarketplaceEventMetadata | |
| token: Nft | |
| collection: Collection | |
| catalog: Catalog | |
| from: String | |
| payload: EventPayloads! | |
| } | |
| enum EventType { | |
| TokenNestTransfer | |
| TokenTransfer | |
| TokenMint | |
| TokenBurn | |
| AssetAddedToTokens | |
| AssetPrioritySet | |
| AssetAccepted | |
| ChildAssetEquipped | |
| ChildAssetUnequipped | |
| ChildAccepted | |
| ChildRejected | |
| ChildAbandoned | |
| CollectionAdded | |
| CollectionOwnershipTransfer | |
| CollectionRemoved | |
| AuctionClosed | |
| NewBid | |
| ListingAdded | |
| ListingRemoved | |
| ListingUpdated | |
| NewOffer | |
| NewSale | |
| OfferCancelled | |
| OwnershipTransferred | |
| RoyaltiesPaid | |
| IncentivesPaid | |
| ValidParentEquippableGroupIdSet | |
| AddedEquippables | |
| AddedPart | |
| SetEquippables | |
| SetEquippableToAll | |
| MetadataUpdate | |
| BatchMetadataUpdate | |
| ReceivedERC20 | |
| TransferredERC20 | |
| } | |
| union EventPayloads = | |
| NestTransferEventPayload | |
| | OwnershipTransferredEventPayload | |
| | TransferEventPayload | |
| | ContractEventPaused | |
| | BurnEventPayload | |
| | MintEventPayload | |
| | AssetAddedToTokensEventPayload | |
| | AssetAcceptedEventPayload | |
| | AssetPrioritySetEventPayload | |
| | CollectionAddedEventPayload | |
| | CollectionRemovedEventPayload | |
| | ContractEventAuctionClosed | |
| | ContractEventNewBid | |
| | ContractEventListingAdded | |
| | ContractEventListingRemoved | |
| | ContractEventListingUpdated | |
| | ContractEventNewOffer | |
| | ContractEventNewSale | |
| | ContractEventOfferCancelled | |
| | ContractEventOwnershipTransferred | |
| | ContractEventRoyaltiesPaid | |
| | ContractEventIncentivesPaid | |
| | EquippedEventPayload | |
| | ChildAcceptedEventPayload | |
| | ChildRejectedEventPayload | |
| | ChildAbandonedEventPayload | |
| | CollectionValidParentEquippableGroupIdSetEventPayload | |
| | CatalogAddedEquippablesEventPayload | |
| | CatalogAddedPartEventPayload | |
| | CatalogSetEquippableToAllEventPayload | |
| | CatalogSetEquippablesEventPayload | |
| | MetadataUpdateEventPayload | |
| | BatchMetadataUpdateEventPayload | |
| | ReceivedERC20EventPayload | |
| | TransferredERC20EventPayload | |
| type NestTransferEventPayload { | |
| from: String! | |
| to: String! | |
| fromTokenId: BigInt! | |
| toTokenId: BigInt! | |
| tokenId: BigInt! | |
| } | |
| type ChildAcceptedEventPayload { | |
| childIndex: BigInt! | |
| tokenId: BigInt! | |
| childAddress: String! | |
| childId: BigInt! | |
| } | |
| type ChildRejectedEventPayload { | |
| tokenId: BigInt! | |
| childAddress: String! | |
| childId: BigInt! | |
| toZero: Boolean! | |
| fromPending: Boolean! | |
| } | |
| type ChildAbandonedEventPayload { | |
| tokenId: BigInt! | |
| childAddress: String! | |
| childId: BigInt! | |
| toZero: Boolean! | |
| fromPending: Boolean! | |
| } | |
| type OwnershipTransferredEventPayload { | |
| previousOwner: String! | |
| newOwner: String! | |
| } | |
| type TransferEventPayload { | |
| from: String! | |
| to: String! | |
| tokenId: BigInt! | |
| } | |
| type BurnEventPayload { | |
| from: String! | |
| to: String! | |
| tokenId: BigInt! | |
| } | |
| type MintEventPayload { | |
| from: String! | |
| to: String! | |
| fromTokenId: BigInt | |
| toTokenId: BigInt | |
| tokenId: BigInt! | |
| } | |
| type AssetAddedToTokensEventPayload { | |
| tokenId: BigInt! | |
| assetId: BigInt! | |
| replacesId: BigInt! | |
| } | |
| type AssetPrioritySetEventPayload { | |
| tokenId: BigInt! | |
| } | |
| type AssetAcceptedEventPayload { | |
| tokenId: BigInt! | |
| assetId: BigInt! | |
| replacesId: BigInt! | |
| } | |
| type CollectionAddedEventPayload { | |
| collection: String! | |
| deployer: String! | |
| name: String! | |
| symbol: String! | |
| maxSupply: Int! | |
| collectionMetadata: String! | |
| legoCombination: LegoCombination! | |
| mintingType: MintingType! | |
| registryIsSoulbound: Boolean! | |
| registryConfig: RegistryConfig! | |
| } | |
| type ReceivedERC20EventPayload { | |
| erc20Contract: String! | |
| toTokenId: BigInt! | |
| from: String! | |
| amount: BigInt! | |
| } | |
| type TransferredERC20EventPayload { | |
| erc20Contract: String! | |
| fromTokenId: BigInt! | |
| to: String! | |
| amount: BigInt! | |
| } | |
| type CollectionValidParentEquippableGroupIdSetEventPayload { | |
| equippableGroupId: BigInt! | |
| slotPartId: BigInt! | |
| parentAddress: String! | |
| } | |
| type CatalogAddedEquippablesEventPayload { | |
| partId: BigInt! | |
| equippableAddresses: [String]! | |
| } | |
| type CatalogAddedPartEventPayload { | |
| partId: BigInt! | |
| itemType: ItemType! | |
| z: Int! | |
| equippable: [String]! | |
| metadataUri: String! | |
| } | |
| type CatalogSetEquippableToAllEventPayload { | |
| partId: BigInt! | |
| } | |
| type CatalogSetEquippablesEventPayload { | |
| partId: BigInt! | |
| equippableAddresses: [String]! | |
| } | |
| type CollectionRemovedEventPayload { | |
| collectionId: String! | |
| } | |
| type MetadataUpdateEventPayload { | |
| collectionId: String! | |
| tokenId: BigInt! | |
| } | |
| type BatchMetadataUpdateEventPayload { | |
| collectionId: String! | |
| fromTokenId: BigInt! | |
| toTokenId: BigInt! | |
| } | |
| enum CollectionType { | |
| MultiAssetPreMint | |
| MultiAssetPreMintSoulbound | |
| Nestable | |
| NestablePreMint | |
| NestableMultiAssetPreMint | |
| NestableMultiAssetPreMintSoulbound | |
| EquippablePreMint | |
| EquippablePreMintSoulbound | |
| EquippableErc20Pay | |
| EquippableErc20PaySoulbound | |
| ERC721 | |
| ERC1155 | |
| } | |
| type ContractEventAuctionClosed { | |
| listingId: BigInt! | |
| closer: String! | |
| cancelled: Boolean! | |
| auctionCreator: String! | |
| winningBidder: String! | |
| totalPricePaid: BigInt | |
| currency: String | |
| } | |
| type ContractEventListingAdded { | |
| listingId: BigInt | |
| buyoutPricePerToken: BigInt | |
| saleType: Int! | |
| paymentCurrency: String! | |
| quantity: BigInt | |
| startTime: DateTime | |
| endTime: DateTime | |
| listingCreator: String | |
| tokenType: Int | |
| tokenId: BigInt! | |
| tokenAddress: String! | |
| gbm: Gbm | |
| } | |
| type ContractEventListingRemoved { | |
| listingId: BigInt! | |
| listingCreator: String! | |
| } | |
| type ContractEventListingUpdated { | |
| listingId: BigInt! | |
| buyoutPricePerToken: BigInt | |
| saleType: Int! | |
| paymentCurrency: String! | |
| quantity: BigInt | |
| startTime: DateTime | |
| endTime: DateTime | |
| listingCreator: String | |
| tokenType: Int | |
| tokenId: BigInt! | |
| tokenAddress: String! | |
| gbm: Gbm | |
| } | |
| type ContractEventNewOffer { | |
| offerId: BigInt! | |
| tokenAddress: String! | |
| tokenId: BigInt! | |
| buyer: String! | |
| saleType: Int! | |
| quantityWanted: BigInt! | |
| totalDirectOfferAmount: BigInt! | |
| currency: String! | |
| expirationTimestamp: DateTime | |
| expectedChildren: [ExpectedChildren]! | |
| } | |
| type ContractEventNewBid { | |
| listingId: BigInt! | |
| buyer: String! | |
| quantityWanted: BigInt! | |
| totalDirectOfferAmount: BigInt! | |
| currency: String! | |
| incentiveIfOutbid: BigInt | |
| outbidBidId: String | |
| } | |
| type ContractEventNewSale { | |
| listingId: BigInt! | |
| tokenAddress: String! | |
| tokenId: BigInt! | |
| seller: String! | |
| buyer: String! | |
| quantityBought: BigInt! | |
| totalPricePaid: BigInt | |
| currency: String | |
| } | |
| type ContractEventOfferCancelled { | |
| offerId: BigInt! | |
| } | |
| type ContractEventOwnershipTransferred { | |
| previousOwner: String! | |
| newOwner: String! | |
| } | |
| type ContractEventPaused { | |
| account: String! | |
| pauseState: Boolean! | |
| } | |
| type ContractEventRoyaltiesPaid { | |
| listingId: BigInt! | |
| tokenAddress: String! | |
| tokenId: BigInt! | |
| recipient: String! | |
| royaltyAmount: BigInt! | |
| offerId: BigInt | |
| } | |
| type ContractEventIncentivesPaid { | |
| listingId: BigInt! | |
| earner: String! | |
| payer: String! | |
| incentivePaid: BigInt! | |
| } | |
| type EquippedEventPayload { | |
| tokenId: BigInt! | |
| assetId: BigInt! | |
| slotPartId: BigInt! | |
| childId: BigInt! | |
| childAddress: String! | |
| childAssetId: BigInt! | |
| } | |
| enum ChainName { | |
| MoonbaseAlpha | |
| Moonbeam | |
| sepolia | |
| ethereum | |
| polygon | |
| base | |
| baseSepolia | |
| astar | |
| bsc | |
| astarZkEvm | |
| bob | |
| } | |
| enum LegoCombination { | |
| None, | |
| MultiAsset, | |
| Nestable, | |
| NestableMultiAsset, | |
| Equippable, | |
| ERC721, | |
| ERC1155, | |
| Custom | |
| } | |
| enum MintingType { | |
| None, | |
| RMRKPreMint, | |
| RMRKLazyMintNativeToken, | |
| RMRKLazyMintERC20, | |
| Custom | |
| } | |
| type RegistryConfig { | |
| usesOwnable: Boolean | |
| usesAccessControl: Boolean | |
| hasStandardAssetManagement: Boolean | |
| hasStandardMinting: Boolean | |
| hasStandardNestMinting: Boolean | |
| autoAcceptsFirstAsset: Boolean | |
| adminRole: String | |
| customLegoCombination: Int | |
| customMintingType: Int | |
| usesRMRKContributor: Boolean | |
| usesRMRKMintingUtils: Boolean | |
| usesRMRKLockable: Boolean | |
| } | |
| type Network @entity { | |
| id: ID! | |
| } | |
| type Collection @entity { | |
| id: ID! | |
| contractAddress: String! | |
| metadata: String! | |
| name: String! | |
| symbol: String! | |
| royaltyRecipient: String | |
| royaltyPercentageBps: Int | |
| legoCombination: LegoCombination! | |
| mintingType: MintingType! | |
| registryIsSoulbound: Boolean! | |
| registryConfig: RegistryConfig! | |
| maxSupply: Int | |
| deployer: String! | |
| owner: String! | |
| hasSoulBoundInterface: Boolean! | |
| hasEquippableInterface: Boolean! | |
| hasTokenHolderInterface: Boolean | |
| chainName: ChainName! | |
| network: Network! | |
| events: [Event] @derivedFrom(field: "collection") | |
| nfts: [Nft] @derivedFrom(field: "collection") | |
| assets: [Asset] @derivedFrom(field: "collection") | |
| equippableGroups: [EquippableGroup] @derivedFrom(field: "collection") | |
| offers: [CollectionOffer] @derivedFrom(field: "collection") | |
| deletedAt: DateTime @index | |
| createdAt: DateTime @index | |
| } | |
| type Nft @entity { | |
| id: ID! | |
| collection: Collection! | |
| priorities: [Int] | |
| owner: String! | |
| burned: Boolean! | |
| metadata: String | |
| # activeAssetIds in database is in the same order as it is on chain, and used to accurately sort resources by priority | |
| activeAssetIds: [Int!] | |
| events: [Event]! @derivedFrom(field: "token") | |
| # MultiAsset properties | |
| assets: [NftAsset] @derivedFrom(field: "nft") | |
| # Nestable properties | |
| pending: Boolean | |
| parent: Nft | |
| children: [Nft] @derivedFrom(field: "parent") | |
| # Equippable properties | |
| equipmentUpdatedAt: DateTime | |
| offers: [UnlistedOffer] @derivedFrom(field: "token") | |
| listing: Listing @derivedFrom(field: "token") | |
| isTransferable: Boolean! | |
| lastTransferabilityCheckAt: DateTime | |
| erc20TokenHolderTokens: [Erc20TokenHolderToken] @derivedFrom(field: "token") | |
| } | |
| type Asset @entity { | |
| id: ID! | |
| collection: Collection! | |
| } | |
| type NftAsset @entity { | |
| id: ID! | |
| nft: Nft! | |
| asset: Asset! | |
| pending: Boolean! | |
| metadata: String | |
| parts: [String!] | |
| equippableGroupId: String @index | |
| equippableGroupEntity: EquippableGroup | |
| catalogAddress: String | |
| } | |
| enum GbmPreset { | |
| None | |
| Low | |
| Medium | |
| High | |
| Degen | |
| } | |
| type Gbm { | |
| preset: GbmPreset! | |
| auctionDebt: BigInt! | |
| lastBidDueIncentives: BigInt! | |
| minBidPerToken: BigInt! | |
| } | |
| enum ListingType { | |
| Direct | |
| GBMAuction | |
| CollectionOffer | |
| UnlistedOffer | |
| } | |
| type MarketplaceEventMetadata { | |
| tokenId: String | |
| listingId: String | |
| buyoutPricePerToken: BigInt | |
| saleType: ListingType! | |
| paymentCurrency: String! | |
| quantity: BigInt | |
| startTime: DateTime | |
| endTime: DateTime | |
| endTimeOriginal: DateTime | |
| listingCreator: String | |
| tokenType: Int | |
| gbm: Gbm | |
| } | |
| type Listing @entity { | |
| id: ID! | |
| buyoutPricePerToken: BigInt | |
| saleType: ListingType! | |
| paymentCurrency: String! | |
| quantity: BigInt | |
| startTime: DateTime | |
| endTime: DateTime | |
| endTimeOriginal: DateTime | |
| listingCreator: String | |
| tokenType: Int | |
| token: Nft! @unique | |
| gbm: Gbm | |
| bids: [Bid] @derivedFrom(field: "listing") | |
| } | |
| type Bid @entity { | |
| id: ID! | |
| buyer: String! | |
| quantityWanted: BigInt! | |
| totalDirectOfferAmount: BigInt! | |
| currency: String! | |
| listing: Listing! | |
| block: Block! | |
| incentiveIfOutbid: BigInt | |
| } | |
| type CollectionOffer @entity { | |
| id: ID! | |
| block: Block | |
| collection: Collection! | |
| buyer: String! | |
| quantityWanted: BigInt! | |
| totalDirectOfferAmount: BigInt | |
| currency: String! | |
| expirationTimestamp: DateTime | |
| expectedChildren: [ExpectedChildren]! | |
| } | |
| type ExpectedChildren { | |
| tokenId: BigInt! | |
| contractAddress: String! | |
| } | |
| type UnlistedOffer @entity { | |
| id: ID! | |
| token: Nft! | |
| buyer: String! | |
| quantityWanted: BigInt | |
| totalDirectOfferAmount: BigInt | |
| currency: String! | |
| expirationTimestamp: DateTime | |
| expectedChildren: [ExpectedChildren]! | |
| block: Block! | |
| } | |
| type AuctionConfig @entity { | |
| id: ID! | |
| timeBuffer: BigInt! | |
| auctionCancellationPeriod: BigInt! | |
| } | |
| type EquippableGroup @entity { | |
| id: ID! | |
| # It would be nice to make a relation to parentCollection, but we cannot guarantee that parent collection was added to registry | |
| #parentCollection: Collection! | |
| parentCollectionId: String! | |
| slotPartId: BigInt! | |
| collection: Collection! | |
| nftAssets: [NftAsset] @derivedFrom(field: "equippableGroupEntity") | |
| } | |
| enum ItemType { | |
| None, | |
| Slot, | |
| Fixed | |
| } | |
| type CatalogPart @entity { | |
| id: ID! | |
| itemType: ItemType! | |
| z: Int! | |
| equippable: [String] | |
| metadataUri: String | |
| isEquippableToAll: Boolean | |
| catalog: Catalog! | |
| } | |
| type Catalog @entity { | |
| id: ID! | |
| owner: String | |
| metadataUri: String | |
| type: String | |
| parts: [CatalogPart] @derivedFrom(field: "catalog") | |
| events: [Event] @derivedFrom(field: "catalog") | |
| network: Network! | |
| createdAt: DateTime @index | |
| updatedAt: DateTime @index | |
| } | |
| type Erc20TokenHolderToken @entity { | |
| id: ID! | |
| token: Nft! | |
| amount: BigInt! | |
| contractAddress: String! | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment