feat: subgraph setup for the FleekNFA contracts (#72)

* Add verify script and update hardhat config to automate all future code verifications on polygonscan

* Update deployments to match the new deployed instance of the contract on polygon mumbai testnet (verified)

* feat: Generate the base of the subgraph by graph-cli based on the verified deployed instance on mumbai

* bug: fixed type errors in the typescript specification file.

* chore: generate a new build of the subgraph

* feat: write a script to perform multiple types of queries on the subgraph

* docs: Write a README for the query-examples directory to guide users on how they can perform queries

* chore: remove the heavy subgraph generated WASM and add all .WASM files to .gitignore

* chore: apply the requested changes from Zoruka on the verify_polyscan.js file

* docs: write the readme doc for the subgraph

* chore: remove deploy from package.json and add build, abis, and generated to .gitignore

* chore: remove abis, build, and generated from the branch

* chore: move query-examples to examples/query/ and gitignore .graphclient

* docs: update readme (add a section for developing and describe build and generated directories better)

* chore: remove graphql from the root package.json file
This commit is contained in:
Shredder 2023-01-31 18:29:42 +03:30 committed by GitHub
parent 325fdb8361
commit 61aeaaae66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 9381 additions and 81 deletions

View File

@ -6,4 +6,7 @@ API_URL=
PRIVATE_KEY=
# The blocks explorer API (e.g https://mumbai.polygonscan.com/)
POLYSCAN_API=https://mumbai.polygonscan.com/
POLYSCAN_API=https://mumbai.polygonscan.com/
# The address of the deployed contract on the blockchain
CONTRACT_ADDRESS=

7
.gitignore vendored
View File

@ -12,3 +12,10 @@ package-lock.json
# Foundry
out
forge-cache
# Subgraph
*.wasm
subgraph/build
subgraph/generated
subgraph/abis
subgraph/examples/query/.graphclient

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,4 @@
import '@nomiclabs/hardhat-etherscan';
import '@nomiclabs/hardhat-ethers';
import '@nomiclabs/hardhat-web3';
import '@nomicfoundation/hardhat-chai-matchers';
@ -14,6 +15,7 @@ const {
API_URL = 'https://polygon-mainnet.alchemyapi.io/v2/your-api-key',
PRIVATE_KEY,
REPORT_GAS,
POLYGONSCAN_KEY,
} = process.env;
const config: HardhatUserConfig = {
@ -67,6 +69,11 @@ const config: HardhatUserConfig = {
mocha: {
timeout: 200000, // 200 seconds max for running tests
},
etherscan: {
apiKey: {
polygonMumbai: POLYGONSCAN_KEY,
},
},
};
export default config;

View File

@ -27,6 +27,7 @@
},
"homepage": "https://github.com/fleekxyz/non-fungible-apps#readme",
"devDependencies": {
"@graphql-codegen/cli": "^2.16.4",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
"@nomicfoundation/hardhat-network-helpers": "^1.0.7",
"@nomicfoundation/hardhat-toolbox": "^2.0.0",

View File

@ -0,0 +1,21 @@
const { ethers } = require('hardhat');
const { address } = require('../deployments/mumbai/FleekERC721.json');
require('@nomiclabs/hardhat-etherscan');
async function main() {
// Verify the contract after deploying
await hre.run('verify:verify', {
address: address,
constructorArguments: [
'FleekNFAs', // Collection name
'FLKNFA', // Collection symbol
],
});
}
// Call the main function and catch if there is any error
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

91
subgraph/README.md Normal file
View File

@ -0,0 +1,91 @@
# The Graph Integration
This directory contains all documents and files related to the NFA x The Graph integration.
## What is the Graph?
Per the official documentation:
> The Graph is a decentralized protocol for indexing and querying data from blockchains, starting with Ethereum. It makes it possible to query data that is difficult to query directly.
In short, the Graph offers its users a flexible and almost instantaneous way to query data from blockchains with the integration of subgraphs.
Subgraphs are APIs that can be queried with a standard GraphQL API. Each subgraph's manifest describes the data structure of contracts of interest and how they should be indexed. The current directory contains the subgraph implementation for the Fleek NFA contract.
## Directory Structure
All of the files and directories except one are generated by the `graph-cli` package (as you will learn in the next section) based on the current live version of the contract on Polygon's mumbai testnet.
**Directories:**
- abis: contains all ABIs related to all contracts of interest in the subgraph with their JSON format. These ABI files are all generated based on the verified version of the contract on the blockchain.
- build: contains the latest build of the subgraph with its WASM (which is ignored in the repository). This folder is uploaded to IPFS during deployment.
- generated: contains the actual TypeScript implementation of the subgraph with the TS version of the graphql schema. The TS file in the `src` folder uses the TS implementation from here to function. `schema.ts` is the TypeScript version of the graphql schema and the `FleekNFA/FleekNFA.ts` includes the TS wrappers for all entities and events.
- [src](./src/): contains the TypeScript implementation of the handlers of the subgraph for events. These handlers process every new event and store them in the database.
- [tests](./tests/): contains the unit tests for this subgraph. For this purpose, `matchstick` (a unit testing framework by LimeChain) is used.
- [examples](./examples): contains examples for the subgraph.
- [examples/query](./examples/query) is the only directory that is not generated by the `graph-cli` package. You can find a working implementation of the `graphclient` along with a few queries there. For more information please check the [readme](./query-examples/README.md) for the examples.
**Files:**
- [networks.json](./networks.json): stores a list of the live versions of the contracts and their addresses on each chain they are deployed to.
- [package.json](./package.json): a typical package.json file with commands to simplify working with the `graph-cli`.
- [schema.graphql](./schema.graphql): the schema of all events, types, and structs we want to index in our subgraph.
- [subgraph.yaml](./subgraph.yaml): the description of the subgraph with the list of events, paths to ABIs and the schema, information on the network of the subgraph, etc...
- [tsconfig.json](./tsconfig.json): the TypeScript config file in the project.
- [yarn.lock](./yarn.lock): Yarn's auto-generated lock file for keeping track of the exact versions of the packages that were used to run the code.
## Build and deployment
In order to deploy the subgraph, a live deployed instance of the contract is needed. If you are not already familiar with deploying contracts, you can follow the guide [here](https://github.com/fleekxyz/non-fungible-apps/tree/main#-deployment).
When the contract is live, you can use the commands that are described in the `package.json` file to interact with the subgraph (build, deploy, test). But before doing so, please make sure you have the `graph-cli` package installed on your machine:
```bash
# If you want to use yarn:
$ yarn global add @graphprotocol/graph-cli
# If you want to use npm:
$ npm install -g @graphprotocol/graph-cli
```
### Developing the subgraph
If the contract has been through changes and those changes have resulted in a new ABI, the developer should make sure that the subgraph is updated as well to match the new interfaces.
To do so, both `schema.graphql` and `subgraph.yaml` files need to be updated.
The developer should also update the auto-generated TS files by running `yarn codegen` after updating the schema and the subgraph config files. That command changes the TS files in the `generated` directory, and that affects the [fleek-nfa.ts](./src/fleek-nfa.ts) file.
So, the next step is to update the handlers and review them again before doing the final build. Change the code based on the new interfaces and the new requirements as needed.
### Building the subgraph
To build the subgraph, you can simply run the `yarn build` or `npm run build` commands. Under the hood, these commands use the `graph-cli` package to build the new schemas and config files of the subgraph. This is an essential step to do before deploying the subgraph.
### Deploying the subgraph
The Graph offers two hosting services to its clients: The Graph Network, and the Hosted Service.
The Graph Client is only live on Ethereum at the time of writing and although there is a planned sunsetting of the Hosted Service for the Q1 of 2023, it is still our only choice to deploy our subgraph to networks other than Ethereum mainnet. According to the blog post about the sunsetting plan by the Graph, no more deployments are supported after the Q3 of 2022, but at the time of writing it is still possible to deploy subgraphs to the hosted service.
Before using the command line to push and deploy the subgraph to the Hosted Service, you should first create a subgraph on [their website](https://thegraph.com/hosted-service). After creating a subgraph there, you should copy the access token. ___Remember your access token is private and you should never share it with anyone else.___
Now that everything is set, you can simply deploy the subgraph to the Hosted Service by running this command (remember to replace the access token and the github_username/subgraph_name parts):
`graph deploy --product hosted-service --deploy-key YOUR_ACCESS_TOKEN --version-lable v0.0.1 YOUR_GITHUB_USERNAME/SUBGRAPH_NAME_ON_HOSTED_SERVICE`
After the deployment, the Hosted Service will start indexing all data relevant to your subgraph from the genesis block until the latest block on the chain. This will take some time and if your contract already has a lot of transactions and events emitted, it is going to take even more time to index all of those events. You should see the status of your subgraph has changed to `Synced` when all of the blocks are processed.
## Initialization
In order to initialize a new subgraph, you are going to need the `graph-cli` package. If you do not have this package already installed on your machine, please refer to the **Build and deployment** section and after installing it, continue reading this section of the readme.
To create a new subgraph from scratch and initialize it, you should have a deployed live version of your contract(s) on a chain (in this case mumbai).
The `graph-cli` command to init a subgraph is included below, please make sure you change all of the fields that are written in capital. If you do not want to deploy your subgraph to mumbai, change that with the name of the chain you want to deploy to:
`graph init --contract-name CONTRACT_NAME --index-events --studio --from-contract CONTRACT_ADDRESS --abi PATH/TO/ABI/JSON --network mumbai SUBGRAPH_NAME`
## Testing
You can run the unit tests found in `./tests/` with `yarn test` or `npm run test`.

View File

@ -0,0 +1,9 @@
# .graphclientrc.yml
sources:
- name: FleekNFA
handler:
graphql:
endpoint: https://api.thegraph.com/subgraphs/name/emperororokusaki/flk-test-subgraph
documents:
- ./queries.graphql

View File

@ -0,0 +1,35 @@
# Query Examples
This directory contains a TypeScript app that performs queries on the deployed subgraph instance of the Fleek NFA.
## How to run
### Prerequisites
In order to run the app, the following dependencies are required:
- yarn
- NPM
- ts-node
### Running the TypeScript app
To launch the TypeScript app, you need to install all dependencies that come with it: `yarn` or `npm install`
After doing so, you can run the app by executing the following command: `npx ts-node main.ts`
## Tweaking the queries
As previously mentioned, all queries' GraphQL form can be found in the [queries.graphql](./queries.graphql) file.
By following [The Graph's Querying Documentation](https://thegraph.com/docs/en/querying/graphql-api/), you can change the queries or add new ones as you desire.
The next thing you need to do after changing the file, is generating the TypeScript format of them by running the following commands:
```sh
# if you already have graphclient installed, skip the first command.
yarn add -D @graphprotocol/client-cli
yarn graphclient build
```
After generating the new TypeScript query specification files, you can change the [main.ts](./main.ts) script to execute the new queries.

View File

@ -0,0 +1,78 @@
import { ExecutionResult } from 'graphql';
import {
allNameChangesOfTokenZeroDocument,
allNameChangesOfTokenZeroQuery,
allNameChangesOfTokenZeroTriggeredByUserDocument,
allNameChangesOfTokenZeroTriggeredByUserQuery,
allNameChangesOfTokenZeroTriggeredByUserQueryVariables,
execute,
firstFiveMintsByUserDocument,
firstFiveMintsByUserQuery,
lastFiveMintsDocument,
lastFiveMintsQuery,
theSecondAndThirdCollectionRoleGrantedsTriggeredByUserDocument,
theSecondAndThirdCollectionRoleGrantedsTriggeredByUserQuery,
theThreeNewestBuildsThatWereNotTriggeredByUserDocument,
theThreeNewestBuildsThatWereNotTriggeredByUserQuery,
} from './.graphclient';
function main() {
execute(firstFiveMintsByUserDocument, {}).then(
(result: ExecutionResult<firstFiveMintsByUserQuery>) => {
console.log('\nFirst five mints by the user:');
console.log(result.data?.transfers);
}
);
execute(lastFiveMintsDocument, {}).then(
(result: ExecutionResult<lastFiveMintsQuery>) => {
console.log('\nThe last five mints:');
console.log(result.data?.transfers);
}
);
execute(allNameChangesOfTokenZeroDocument, {}).then(
(result: ExecutionResult<allNameChangesOfTokenZeroQuery>) => {
console.log('\nAll name changes of token 0:');
console.log(result.data?.newTokenNames);
}
);
execute(allNameChangesOfTokenZeroTriggeredByUserDocument, {}).then(
(
result: ExecutionResult<allNameChangesOfTokenZeroTriggeredByUserQuery>
) => {
console.log(
'\nAll name changes of token 0 which were triggered by the user:'
);
console.log(result.data?.newTokenNames);
}
);
execute(
theSecondAndThirdCollectionRoleGrantedsTriggeredByUserDocument,
{}
).then(
(
result: ExecutionResult<theSecondAndThirdCollectionRoleGrantedsTriggeredByUserQuery>
) => {
console.log(
'\nThe second and third collection role granted events which were triggered by the user:'
);
console.log(result.data?.collectionRoleGranteds);
}
);
execute(theThreeNewestBuildsThatWereNotTriggeredByUserDocument, {}).then(
(
result: ExecutionResult<theThreeNewestBuildsThatWereNotTriggeredByUserQuery>
) => {
console.log(
'\nThe three newest builds which were not triggered by user:'
);
console.log(result.data?.newBuilds);
}
);
}
main();

View File

@ -0,0 +1,11 @@
{
"name": "query-examples",
"version": "0.0.1",
"description": "Examples that query the Fleek NFA subgraph using the graphql-client package.",
"main": "main.ts",
"author": "Nima Rasooli",
"license": "MIT",
"devDependencies": {
"@graphprotocol/client-cli": "^2.2.16"
}
}

View File

@ -0,0 +1,68 @@
# This file contains all queries that are executed in the main.ts script.
# The "User" who is mentioned in this file is the deployer address of the contract on the mumbai testnet: 0xd4997d0facc83231b9f26a8b2155b4869e99946f
query firstFiveMintsByUser {
transfers(
where: {from: "0x0000000000000000000000000000000000000000", to: "0xd4997d0facc83231b9f26a8b2155b4869e99946f"}
first: 5
) {
id
tokenId
}
}
query lastFiveMints {
transfers(
where: {from: "0x0000000000000000000000000000000000000000"}
first: 5
orderDirection: desc
) {
id
tokenId
to
}
}
query allNameChangesOfTokenZero {
newTokenNames(where: {token: "0"}) {
id
name
triggeredBy
}
}
query allNameChangesOfTokenZeroTriggeredByUser {
newTokenNames(
where: {token: "0", triggeredBy: "0xd4997d0facc83231b9f26a8b2155b4869e99946f"}
) {
id
name
triggeredBy
}
}
query theSecondAndThirdCollectionRoleGrantedsTriggeredByUser {
collectionRoleGranteds(
where: {byAddress: "0xd4997d0facc83231b9f26a8b2155b4869e99946f"}
first: 2
skip: 1
) {
id
role
toAddress
}
}
query theThreeNewestBuildsThatWereNotTriggeredByUser {
newBuilds(
where: {triggeredBy_not: "0xd4997d0facc83231b9f26a8b2155b4869e99946f"}
orderDirection: desc
first: 3
) {
id
commitHash
token
triggeredBy
}
}

File diff suppressed because it is too large Load Diff

7
subgraph/networks.json Normal file
View File

@ -0,0 +1,7 @@
{
"mumbai": {
"FleekNFA": {
"address": "0x34F21E970A7cd383eE429aDB5ed57bbc40ea2B57"
}
}
}

19
subgraph/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "FleekNFA",
"license": "UNLICENSED",
"scripts": {
"codegen": "graph codegen",
"build": "graph build",
"create-local": "graph create --node http://localhost:8020/ FleekNFA",
"remove-local": "graph remove --node http://localhost:8020/ FleekNFA",
"deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 FleekNFA",
"test": "graph test"
},
"dependencies": {
"@graphprotocol/graph-cli": "0.37.2",
"@graphprotocol/graph-ts": "0.29.1"
},
"devDependencies": {
"matchstick-as": "0.5.0"
}
}

131
subgraph/schema.graphql Normal file
View File

@ -0,0 +1,131 @@
type Approval @entity(immutable: true) {
id: Bytes!
owner: Bytes! # address
approved: Bytes! # address
tokenId: BigInt! # uint256
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type ApprovalForAll @entity(immutable: true) {
id: Bytes!
owner: Bytes! # address
operator: Bytes! # address
approved: Boolean! # bool
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type CollectionRoleGranted @entity(immutable: true) {
id: Bytes!
role: Int! # uint8
toAddress: Bytes! # address
byAddress: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type CollectionRoleRevoked @entity(immutable: true) {
id: Bytes!
role: Int! # uint8
toAddress: Bytes! # address
byAddress: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type NewBuild @entity(immutable: true) {
id: Bytes!
token: BigInt! # uint256
commitHash: String! # string
triggeredBy: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type NewTokenDescription @entity(immutable: true) {
id: Bytes!
token: BigInt! # uint256
description: String! # string
triggeredBy: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type NewTokenENS @entity(immutable: true) {
id: Bytes!
token: BigInt! # uint256
ENS: String! # string
triggeredBy: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type NewTokenExternalURL @entity(immutable: true) {
id: Bytes!
token: BigInt! # uint256
externalURL: String! # string
triggeredBy: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type NewTokenImage @entity(immutable: true) {
id: Bytes!
token: BigInt! # uint256
image: String! # string
triggeredBy: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type NewTokenName @entity(immutable: true) {
id: Bytes!
token: BigInt! # uint256
name: String! # string
triggeredBy: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type TokenRoleGranted @entity(immutable: true) {
id: Bytes!
tokenId: BigInt! # uint256
role: Int! # uint8
toAddress: Bytes! # address
byAddress: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type TokenRoleRevoked @entity(immutable: true) {
id: Bytes!
tokenId: BigInt! # uint256
role: Int! # uint8
toAddress: Bytes! # address
byAddress: Bytes! # address
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}
type Transfer @entity(immutable: true) {
id: Bytes!
from: Bytes! # address
to: Bytes! # address
tokenId: BigInt! # uint256
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: Bytes!
}

235
subgraph/src/fleek-nfa.ts Normal file
View File

@ -0,0 +1,235 @@
import {
Approval as ApprovalEvent,
ApprovalForAll as ApprovalForAllEvent,
CollectionRoleGranted as CollectionRoleGrantedEvent,
CollectionRoleRevoked as CollectionRoleRevokedEvent,
NewBuild as NewBuildEvent,
NewTokenDescription as NewTokenDescriptionEvent,
NewTokenENS as NewTokenENSEvent,
NewTokenExternalURL as NewTokenExternalURLEvent,
NewTokenImage as NewTokenImageEvent,
NewTokenName as NewTokenNameEvent,
TokenRoleGranted as TokenRoleGrantedEvent,
TokenRoleRevoked as TokenRoleRevokedEvent,
Transfer as TransferEvent,
} from '../generated/FleekNFA/FleekNFA';
import {
Approval,
ApprovalForAll,
CollectionRoleGranted,
CollectionRoleRevoked,
NewBuild,
NewTokenDescription,
NewTokenENS,
NewTokenExternalURL,
NewTokenImage,
NewTokenName,
TokenRoleGranted,
TokenRoleRevoked,
Transfer,
} from '../generated/schema';
export function handleApproval(event: ApprovalEvent): void {
let entity = new Approval(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.owner = event.params.owner;
entity.approved = event.params.approved;
entity.tokenId = event.params.tokenId;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleApprovalForAll(event: ApprovalForAllEvent): void {
let entity = new ApprovalForAll(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.owner = event.params.owner;
entity.operator = event.params.operator;
entity.approved = event.params.approved;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleCollectionRoleGranted(
event: CollectionRoleGrantedEvent
): void {
let entity = new CollectionRoleGranted(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.role = event.params.role;
entity.toAddress = event.params.toAddress;
entity.byAddress = event.params.byAddress;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleCollectionRoleRevoked(
event: CollectionRoleRevokedEvent
): void {
let entity = new CollectionRoleRevoked(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.role = event.params.role;
entity.toAddress = event.params.toAddress;
entity.byAddress = event.params.byAddress;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleNewBuild(event: NewBuildEvent): void {
let entity = new NewBuild(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.token = event.params.token;
entity.commitHash = event.params.commitHash.toString();
entity.triggeredBy = event.params.triggeredBy;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleNewTokenDescription(
event: NewTokenDescriptionEvent
): void {
let entity = new NewTokenDescription(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.token = event.params.token;
entity.description = event.params.description.toString();
entity.triggeredBy = event.params.triggeredBy;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleNewTokenENS(event: NewTokenENSEvent): void {
let entity = new NewTokenENS(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.token = event.params.token;
entity.ENS = event.params.ENS.toString();
entity.triggeredBy = event.params.triggeredBy;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleNewTokenExternalURL(
event: NewTokenExternalURLEvent
): void {
let entity = new NewTokenExternalURL(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.token = event.params.token;
entity.externalURL = event.params.externalURL.toString();
entity.triggeredBy = event.params.triggeredBy;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleNewTokenImage(event: NewTokenImageEvent): void {
let entity = new NewTokenImage(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.token = event.params.token;
entity.image = event.params.image.toString();
entity.triggeredBy = event.params.triggeredBy;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleNewTokenName(event: NewTokenNameEvent): void {
let entity = new NewTokenName(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.token = event.params.token;
entity.name = event.params.name.toString();
entity.triggeredBy = event.params.triggeredBy;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleTokenRoleGranted(event: TokenRoleGrantedEvent): void {
let entity = new TokenRoleGranted(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.tokenId = event.params.tokenId;
entity.role = event.params.role;
entity.toAddress = event.params.toAddress;
entity.byAddress = event.params.byAddress;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleTokenRoleRevoked(event: TokenRoleRevokedEvent): void {
let entity = new TokenRoleRevoked(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.tokenId = event.params.tokenId;
entity.role = event.params.role;
entity.toAddress = event.params.toAddress;
entity.byAddress = event.params.byAddress;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}
export function handleTransfer(event: TransferEvent): void {
let entity = new Transfer(
event.transaction.hash.concatI32(event.logIndex.toI32())
);
entity.from = event.params.from;
entity.to = event.params.to;
entity.tokenId = event.params.tokenId;
entity.blockNumber = event.block.number;
entity.blockTimestamp = event.block.timestamp;
entity.transactionHash = event.transaction.hash;
entity.save();
}

59
subgraph/subgraph.yaml Normal file
View File

@ -0,0 +1,59 @@
specVersion: 0.0.5
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: FleekNFA
network: mumbai
source:
address: "0x34F21E970A7cd383eE429aDB5ed57bbc40ea2B57"
abi: FleekNFA
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Approval
- ApprovalForAll
- CollectionRoleGranted
- CollectionRoleRevoked
- NewBuild
- NewTokenDescription
- NewTokenENS
- NewTokenExternalURL
- NewTokenImage
- NewTokenName
- TokenRoleGranted
- TokenRoleRevoked
- Transfer
abis:
- name: FleekNFA
file: ./abis/FleekNFA.json
eventHandlers:
- event: Approval(indexed address,indexed address,indexed uint256)
handler: handleApproval
- event: ApprovalForAll(indexed address,indexed address,bool)
handler: handleApprovalForAll
- event: CollectionRoleGranted(indexed uint8,indexed address,address)
handler: handleCollectionRoleGranted
- event: CollectionRoleRevoked(indexed uint8,indexed address,address)
handler: handleCollectionRoleRevoked
- event: NewBuild(indexed uint256,indexed string,indexed address)
handler: handleNewBuild
- event: NewTokenDescription(indexed uint256,indexed string,indexed address)
handler: handleNewTokenDescription
- event: NewTokenENS(indexed uint256,indexed string,indexed address)
handler: handleNewTokenENS
- event: NewTokenExternalURL(indexed uint256,indexed string,indexed address)
handler: handleNewTokenExternalURL
- event: NewTokenImage(indexed uint256,indexed string,indexed address)
handler: handleNewTokenImage
- event: NewTokenName(indexed uint256,indexed string,indexed address)
handler: handleNewTokenName
- event: TokenRoleGranted(indexed uint256,indexed uint8,indexed address,address)
handler: handleTokenRoleGranted
- event: TokenRoleRevoked(indexed uint256,indexed uint8,indexed address,address)
handler: handleTokenRoleRevoked
- event: Transfer(indexed address,indexed address,indexed uint256)
handler: handleTransfer
file: ./src/fleek-nfa.ts

View File

@ -0,0 +1,367 @@
import { newMockEvent } from 'matchstick-as';
import { ethereum, Address, BigInt } from '@graphprotocol/graph-ts';
import {
Approval,
ApprovalForAll,
CollectionRoleGranted,
CollectionRoleRevoked,
NewBuild,
NewTokenDescription,
NewTokenENS,
NewTokenExternalURL,
NewTokenImage,
NewTokenName,
TokenRoleGranted,
TokenRoleRevoked,
Transfer,
} from '../generated/FleekNFA/FleekNFA';
export function createApprovalEvent(
owner: Address,
approved: Address,
tokenId: BigInt
): Approval {
let approvalEvent = changetype<Approval>(newMockEvent());
approvalEvent.parameters = new Array();
approvalEvent.parameters.push(
new ethereum.EventParam('owner', ethereum.Value.fromAddress(owner))
);
approvalEvent.parameters.push(
new ethereum.EventParam('approved', ethereum.Value.fromAddress(approved))
);
approvalEvent.parameters.push(
new ethereum.EventParam(
'tokenId',
ethereum.Value.fromUnsignedBigInt(tokenId)
)
);
return approvalEvent;
}
export function createApprovalForAllEvent(
owner: Address,
operator: Address,
approved: boolean
): ApprovalForAll {
let approvalForAllEvent = changetype<ApprovalForAll>(newMockEvent());
approvalForAllEvent.parameters = new Array();
approvalForAllEvent.parameters.push(
new ethereum.EventParam('owner', ethereum.Value.fromAddress(owner))
);
approvalForAllEvent.parameters.push(
new ethereum.EventParam('operator', ethereum.Value.fromAddress(operator))
);
approvalForAllEvent.parameters.push(
new ethereum.EventParam('approved', ethereum.Value.fromBoolean(approved))
);
return approvalForAllEvent;
}
export function createCollectionRoleGrantedEvent(
role: i32,
toAddress: Address,
byAddress: Address
): CollectionRoleGranted {
let collectionRoleGrantedEvent = changetype<CollectionRoleGranted>(
newMockEvent()
);
collectionRoleGrantedEvent.parameters = new Array();
collectionRoleGrantedEvent.parameters.push(
new ethereum.EventParam(
'role',
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(role))
)
);
collectionRoleGrantedEvent.parameters.push(
new ethereum.EventParam('toAddress', ethereum.Value.fromAddress(toAddress))
);
collectionRoleGrantedEvent.parameters.push(
new ethereum.EventParam('byAddress', ethereum.Value.fromAddress(byAddress))
);
return collectionRoleGrantedEvent;
}
export function createCollectionRoleRevokedEvent(
role: i32,
toAddress: Address,
byAddress: Address
): CollectionRoleRevoked {
let collectionRoleRevokedEvent = changetype<CollectionRoleRevoked>(
newMockEvent()
);
collectionRoleRevokedEvent.parameters = new Array();
collectionRoleRevokedEvent.parameters.push(
new ethereum.EventParam(
'role',
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(role))
)
);
collectionRoleRevokedEvent.parameters.push(
new ethereum.EventParam('toAddress', ethereum.Value.fromAddress(toAddress))
);
collectionRoleRevokedEvent.parameters.push(
new ethereum.EventParam('byAddress', ethereum.Value.fromAddress(byAddress))
);
return collectionRoleRevokedEvent;
}
export function createNewBuildEvent(
token: BigInt,
commitHash: string,
triggeredBy: Address
): NewBuild {
let newBuildEvent = changetype<NewBuild>(newMockEvent());
newBuildEvent.parameters = new Array();
newBuildEvent.parameters.push(
new ethereum.EventParam('token', ethereum.Value.fromUnsignedBigInt(token))
);
newBuildEvent.parameters.push(
new ethereum.EventParam('commitHash', ethereum.Value.fromString(commitHash))
);
newBuildEvent.parameters.push(
new ethereum.EventParam(
'triggeredBy',
ethereum.Value.fromAddress(triggeredBy)
)
);
return newBuildEvent;
}
export function createNewTokenDescriptionEvent(
token: BigInt,
description: string,
triggeredBy: Address
): NewTokenDescription {
let newTokenDescriptionEvent = changetype<NewTokenDescription>(
newMockEvent()
);
newTokenDescriptionEvent.parameters = new Array();
newTokenDescriptionEvent.parameters.push(
new ethereum.EventParam('token', ethereum.Value.fromUnsignedBigInt(token))
);
newTokenDescriptionEvent.parameters.push(
new ethereum.EventParam(
'description',
ethereum.Value.fromString(description)
)
);
newTokenDescriptionEvent.parameters.push(
new ethereum.EventParam(
'triggeredBy',
ethereum.Value.fromAddress(triggeredBy)
)
);
return newTokenDescriptionEvent;
}
export function createNewTokenENSEvent(
token: BigInt,
ENS: string,
triggeredBy: Address
): NewTokenENS {
let newTokenEnsEvent = changetype<NewTokenENS>(newMockEvent());
newTokenEnsEvent.parameters = new Array();
newTokenEnsEvent.parameters.push(
new ethereum.EventParam('token', ethereum.Value.fromUnsignedBigInt(token))
);
newTokenEnsEvent.parameters.push(
new ethereum.EventParam('ENS', ethereum.Value.fromString(ENS))
);
newTokenEnsEvent.parameters.push(
new ethereum.EventParam(
'triggeredBy',
ethereum.Value.fromAddress(triggeredBy)
)
);
return newTokenEnsEvent;
}
export function createNewTokenExternalURLEvent(
token: BigInt,
externalURL: string,
triggeredBy: Address
): NewTokenExternalURL {
let newTokenExternalUrlEvent = changetype<NewTokenExternalURL>(
newMockEvent()
);
newTokenExternalUrlEvent.parameters = new Array();
newTokenExternalUrlEvent.parameters.push(
new ethereum.EventParam('token', ethereum.Value.fromUnsignedBigInt(token))
);
newTokenExternalUrlEvent.parameters.push(
new ethereum.EventParam(
'externalURL',
ethereum.Value.fromString(externalURL)
)
);
newTokenExternalUrlEvent.parameters.push(
new ethereum.EventParam(
'triggeredBy',
ethereum.Value.fromAddress(triggeredBy)
)
);
return newTokenExternalUrlEvent;
}
export function createNewTokenImageEvent(
token: BigInt,
image: string,
triggeredBy: Address
): NewTokenImage {
let newTokenImageEvent = changetype<NewTokenImage>(newMockEvent());
newTokenImageEvent.parameters = new Array();
newTokenImageEvent.parameters.push(
new ethereum.EventParam('token', ethereum.Value.fromUnsignedBigInt(token))
);
newTokenImageEvent.parameters.push(
new ethereum.EventParam('image', ethereum.Value.fromString(image))
);
newTokenImageEvent.parameters.push(
new ethereum.EventParam(
'triggeredBy',
ethereum.Value.fromAddress(triggeredBy)
)
);
return newTokenImageEvent;
}
export function createNewTokenNameEvent(
token: BigInt,
name: string,
triggeredBy: Address
): NewTokenName {
let newTokenNameEvent = changetype<NewTokenName>(newMockEvent());
newTokenNameEvent.parameters = new Array();
newTokenNameEvent.parameters.push(
new ethereum.EventParam('token', ethereum.Value.fromUnsignedBigInt(token))
);
newTokenNameEvent.parameters.push(
new ethereum.EventParam('name', ethereum.Value.fromString(name))
);
newTokenNameEvent.parameters.push(
new ethereum.EventParam(
'triggeredBy',
ethereum.Value.fromAddress(triggeredBy)
)
);
return newTokenNameEvent;
}
export function createTokenRoleGrantedEvent(
tokenId: BigInt,
role: i32,
toAddress: Address,
byAddress: Address
): TokenRoleGranted {
let tokenRoleGrantedEvent = changetype<TokenRoleGranted>(newMockEvent());
tokenRoleGrantedEvent.parameters = new Array();
tokenRoleGrantedEvent.parameters.push(
new ethereum.EventParam(
'tokenId',
ethereum.Value.fromUnsignedBigInt(tokenId)
)
);
tokenRoleGrantedEvent.parameters.push(
new ethereum.EventParam(
'role',
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(role))
)
);
tokenRoleGrantedEvent.parameters.push(
new ethereum.EventParam('toAddress', ethereum.Value.fromAddress(toAddress))
);
tokenRoleGrantedEvent.parameters.push(
new ethereum.EventParam('byAddress', ethereum.Value.fromAddress(byAddress))
);
return tokenRoleGrantedEvent;
}
export function createTokenRoleRevokedEvent(
tokenId: BigInt,
role: i32,
toAddress: Address,
byAddress: Address
): TokenRoleRevoked {
let tokenRoleRevokedEvent = changetype<TokenRoleRevoked>(newMockEvent());
tokenRoleRevokedEvent.parameters = new Array();
tokenRoleRevokedEvent.parameters.push(
new ethereum.EventParam(
'tokenId',
ethereum.Value.fromUnsignedBigInt(tokenId)
)
);
tokenRoleRevokedEvent.parameters.push(
new ethereum.EventParam(
'role',
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(role))
)
);
tokenRoleRevokedEvent.parameters.push(
new ethereum.EventParam('toAddress', ethereum.Value.fromAddress(toAddress))
);
tokenRoleRevokedEvent.parameters.push(
new ethereum.EventParam('byAddress', ethereum.Value.fromAddress(byAddress))
);
return tokenRoleRevokedEvent;
}
export function createTransferEvent(
from: Address,
to: Address,
tokenId: BigInt
): Transfer {
let transferEvent = changetype<Transfer>(newMockEvent());
transferEvent.parameters = new Array();
transferEvent.parameters.push(
new ethereum.EventParam('from', ethereum.Value.fromAddress(from))
);
transferEvent.parameters.push(
new ethereum.EventParam('to', ethereum.Value.fromAddress(to))
);
transferEvent.parameters.push(
new ethereum.EventParam(
'tokenId',
ethereum.Value.fromUnsignedBigInt(tokenId)
)
);
return transferEvent;
}

View File

@ -0,0 +1,64 @@
import {
assert,
describe,
test,
clearStore,
beforeAll,
afterAll,
} from 'matchstick-as/assembly/index';
import { Address, BigInt } from '@graphprotocol/graph-ts';
import { Approval } from '../generated/schema';
import { Approval as ApprovalEvent } from '../generated/FleekNFA/FleekNFA';
import { handleApproval } from '../src/fleek-nfa';
import { createApprovalEvent } from './fleek-nfa-utils';
// Tests structure (matchstick-as >=0.5.0)
// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0
describe('Describe entity assertions', () => {
beforeAll(() => {
let owner = Address.fromString(
'0x0000000000000000000000000000000000000001'
);
let approved = Address.fromString(
'0x0000000000000000000000000000000000000001'
);
let tokenId = BigInt.fromI32(234);
let newApprovalEvent = createApprovalEvent(owner, approved, tokenId);
handleApproval(newApprovalEvent);
});
afterAll(() => {
clearStore();
});
// For more test scenarios, see:
// https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test
test('Approval created and stored', () => {
assert.entityCount('Approval', 1);
// 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function
assert.fieldEquals(
'Approval',
'0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1',
'owner',
'0x0000000000000000000000000000000000000001'
);
assert.fieldEquals(
'Approval',
'0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1',
'approved',
'0x0000000000000000000000000000000000000001'
);
assert.fieldEquals(
'Approval',
'0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1',
'tokenId',
'234'
);
// More assert options:
// https://thegraph.com/docs/en/developer/matchstick/#asserts
});
});

4
subgraph/tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": "@graphprotocol/graph-ts/types/tsconfig.base.json",
"include": ["src"]
}

3503
subgraph/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

1437
yarn.lock

File diff suppressed because it is too large Load Diff