Skip to content

Instantly share code, notes, and snippets.

@Yuripetusko
Last active August 22, 2024 03:03
Show Gist options
  • Select an option

  • Save Yuripetusko/68d9f328a4cd5c85fc881ffc731db47d to your computer and use it in GitHub Desktop.

Select an option

Save Yuripetusko/68d9f328a4cd5c85fc881ffc731db47d to your computer and use it in GitHub Desktop.
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(),
}),
})
);
};
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,
}),
})
);
};
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