diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 1ab5857..e3528cc 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -31,7 +31,7 @@ type NewMint @entity(immutable: true) { color: Int! accessPointAutoApproval: Boolean! triggeredBy: Bytes! # address - tokenOwner: Owner! # address + owner: Owner! # address blockNumber: BigInt! blockTimestamp: BigInt! transactionHash: Bytes! @@ -80,28 +80,19 @@ type Token @entity { accessPoints: [AccessPoint!] @derivedFrom(field: "token") } +# Owner entity for collection, access points, and tokens type Owner @entity { id: Bytes! # address tokens: [Token!] @derivedFrom(field: "owner") accessPoints: [AccessPoint!] @derivedFrom(field: "owner") + collection: Boolean! } +# Controller entity for tokens type Controller @entity { id: Bytes! # address tokens: [Token!] @derivedFrom(field: "controllers") } -type Collection @entity { - id: Bytes! #address - deployer: Bytes! #address - transactionHash: Bytes! #transaction hash - owners: [CollectionOwner!] -} - -type CollectionOwner @entity { - id: Bytes! # address - accessGrantedBy: Bytes! #address - transactionHash: Bytes! -} type GitRepository @entity { id: String! # transaction hash of the first transaction this repository appeared in diff --git a/subgraph/src/fleek-nfa.ts b/subgraph/src/fleek-nfa.ts index fad17d2..80562ba 100644 --- a/subgraph/src/fleek-nfa.ts +++ b/subgraph/src/fleek-nfa.ts @@ -1,11 +1,16 @@ import { Address, Bytes, log, store, ethereum, BigInt } from '@graphprotocol/graph-ts'; + +// Event Imports [based on the yaml config] import { Approval as ApprovalEvent, ApprovalForAll as ApprovalForAllEvent, MetadataUpdate as MetadataUpdateEvent, MetadataUpdate1 as MetadataUpdateEvent1, MetadataUpdate2 as MetadataUpdateEvent2, + TokenRoleChanged as TokenRoleChangedEvent, MetadataUpdate3 as MetadataUpdateEvent3, + CollectionRoleChanged as CollectionRoleChangedEvent, + Initialized as InitializedEvent, Transfer as TransferEvent, NewMint as NewMintEvent, ChangeAccessPointCreationStatus as ChangeAccessPointCreationStatusEvent, @@ -14,21 +19,28 @@ import { ChangeAccessPointNameVerify as ChangeAccessPointNameVerifyEvent, ChangeAccessPointContentVerify as ChangeAccessPointContentVerifyEvent, } from '../generated/FleekNFA/FleekNFA'; + +// Entity Imports [based on the schema] import { AccessPoint, Approval, ApprovalForAll, - Collection, - CollectionOwner, - Controller, + Owner, GitRepository as GitRepositoryEntity, MetadataUpdate, NewMint, - Owner, Token, Transfer, } from '../generated/schema'; +enum CollectionRoles { + Owner, +}; + +enum TokenRoles { + Controller, +}; + export function handleApproval(event: ApprovalEvent): void { let entity = new Approval( event.transaction.hash.concatI32(event.logIndex.toI32()) @@ -87,7 +99,7 @@ export function handleNewMint(event: NewMintEvent): void { newMintEntity.color = color; newMintEntity.accessPointAutoApproval = accessPointAutoApproval; newMintEntity.triggeredBy = event.params.minter; - newMintEntity.tokenOwner = ownerAddress; + newMintEntity.owner = ownerAddress; newMintEntity.blockNumber = event.block.number; newMintEntity.blockTimestamp = event.block.timestamp; newMintEntity.transactionHash = event.transaction.hash; @@ -97,7 +109,6 @@ export function handleNewMint(event: NewMintEvent): void { // Create Token, Owner, and Controller entities let owner = Owner.load(ownerAddress); - let controller = Controller.load(ownerAddress); let gitRepositoryEntity = GitRepositoryEntity.load(gitRepository); let token = new Token(Bytes.fromByteArray(Bytes.fromBigInt(tokenId))); @@ -106,11 +117,6 @@ export function handleNewMint(event: NewMintEvent): void { owner = new Owner(ownerAddress); } - if (!controller) { - // Create a new controller entity - controller = new Controller(ownerAddress); - } - if (!gitRepositoryEntity) { // Create a new gitRepository entity gitRepositoryEntity = new GitRepositoryEntity(gitRepository); @@ -136,7 +142,6 @@ export function handleNewMint(event: NewMintEvent): void { // Save entities owner.save(); - controller.save(); gitRepositoryEntity.save(); token.save(); } @@ -257,6 +262,91 @@ export function handleMetadataUpdateWithIntValue( } } +export function handleInitialized(event: InitializedEvent): void { + // This is the contract creation transaction. + log.warning('This is the contract creation transaction.', []); + if (event.receipt) { + let receipt = event.receipt as ethereum.TransactionReceipt; + log.warning('Contract address is: {}', [ + receipt.contractAddress.toHexString(), + ]); + + // add owner + let owner = new Owner(event.transaction.from); + owner.collection = true; + owner.save(); + } +} + +export function handleCollectionRoleChanged(event: CollectionRoleChangedEvent): void { + let toAddress = event.params.toAddress; + let byAddress = event.params.byAddress; + let role = event.params.role; + let status = event.params.status; + + if (role === CollectionRoles.Owner) { + // Owner role + if (status) { + // granted + let owner = Owner.load(toAddress); + if (!owner) { + owner = new Owner(toAddress); + } + owner.collection = true; + owner.save(); + } else { + // revoked + let owner = Owner.load(toAddress); + if (!owner) { + log.error('Owner entity not found. Role: {}, byAddress: {}, toAddress: {}', [role.toString(), byAddress.toHexString(), toAddress.toHexString()]); + return; + } + owner.collection = false; + owner.save(); + } + } else { + log.error('Role not supported. Role: {}, byAddress: {}, toAddress: {}', [role.toString(), byAddress.toHexString(), toAddress.toHexString()]); + } +} + +export function handleTokenRoleChanged(event: TokenRoleChangedEvent): void { + let tokenId = event.params.tokenId; + let toAddress = event.params.toAddress; + let byAddress = event.params.byAddress; + let role = event.params.role; + let status = event.params.status; + + // load token + let token = Token.load(Bytes.fromByteArray(Bytes.fromBigInt(tokenId))); + if (!token) { + log.error('Token not found. TokenId: {}', [tokenId.toString()]); + return; + } + + if (role === TokenRoles.Controller) { + // Controller role + // get the list of controllers. + let token_controllers = token.controllers; + if (!token_controllers) { + token_controllers = []; + } + if (status) { + // granted + token_controllers.push(toAddress); + } else { + // revoked + // remove address from the controllers list + const index = token_controllers.indexOf(event.params.toAddress, 0); + if (index > -1) { + token_controllers.splice(index, 1); + } + } + token.controllers = token_controllers; + } else { + log.error('Role not supported. Role: {}, byAddress: {}, toAddress: {}', [role.toString(), byAddress.toHexString(), toAddress.toHexString()]); + } +} + export function handleMetadataUpdateWithBooleanValue(event: MetadataUpdateEvent3): void { /** * accessPointAutoApproval @@ -343,7 +433,7 @@ export function handleTransfer(event: TransferEvent): void { accessPointEntity.score = BigInt.fromU32(0); accessPointEntity.contentVerified = false; accessPointEntity.nameVerified = false; - accessPointEntity.status = 'DRAFT'; // Since a `ChangeAccessPointCreationStatus` event is emitted instantly after `NewAccessPoint`, the status will be updated in that handler. + accessPointEntity.creationStatus = 'DRAFT'; // Since a `ChangeAccessPointCreationStatus` event is emitted instantly after `NewAccessPoint`, the status will be updated in that handler. accessPointEntity.owner = event.params.owner; accessPointEntity.token = Bytes.fromByteArray(Bytes.fromBigInt(event.params.tokenId)); @@ -369,18 +459,24 @@ export function handleChangeAccessPointCreationStatus(event: ChangeAccessPointCr let status = event.params.status; if (accessPointEntity) { - if (status == 0) { - accessPointEntity.status = 'DRAFT'; - } else if (status == 1) { - accessPointEntity.status = 'APPROVED'; - } else if (status == 2) { - accessPointEntity.status = 'REJECTED'; - } else if (status == 3) { - accessPointEntity.status = 'REMOVED'; - } else { - // Unknown status - log.error('Unable to handle ChangeAccessPointCreationStatus. Unknown status. Status: {}, AccessPoint: {}', [status.toString(), event.params.apName]); + switch (status) { + case 0: + accessPointEntity.creationStatus = 'DRAFT'; + break; + case 1: + accessPointEntity.creationStatus = 'APPROVED'; + break; + case 2: + accessPointEntity.creationStatus = 'REJECTED'; + break; + case 3: + accessPointEntity.creationStatus = 'REMOVED'; + break; + default: + // Unknown status + log.error('Unable to handle ChangeAccessPointCreationStatus. Unknown status. Status: {}, AccessPoint: {}', [status.toString(), event.params.apName]); } + accessPointEntity.save(); } else { // Unknown access point diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 31c0ee3..18753d4 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -19,9 +19,10 @@ dataSources: - NewMint - Transfer - Token - - Owner - - Controller + - TokenOwner + - TokenController - CollectionOwner + - Collection - GithubRepository - AccessPoint - ChangeAccessPointCreationStatus @@ -49,6 +50,14 @@ dataSources: handler: handleNewMint - event: Transfer(indexed address,indexed address,indexed uint256) handler: handleTransfer + - event: TokenRoleChanged(indexed uint256,indexed uint8,indexed address,bool,address) + handler: handleTokenRoleChanged + - event: TokenRolesCleared(indexed uint256,address) + handler: handleTokenRolesCleared + - event: CollectionRoleChanged(indexed uint8,indexed address,bool,address) + handler: handleCollectionRoleChanged + - event: Initialized(uint8) + handler: handleInitialized - event: ChangeAccessPointContentVerify(string,uint256,indexed bool,indexed address) handler: handleChangeAccessPointContentVerify - event: ChangeAccessPointNameVerify(string,uint256,indexed bool,indexed address)