Table of contents generated with markdown-toc
This project does the following:
-
Actively monitors an Ethereum contract for any Transfer Events
-
Captures past Transfer Events on the specified Ethereum contract.
- You must specify the genesis, start, and end block (please look at the arguments section).
-
Put the data in a Postgres database.
-
Spins up an instance of graphQL to query the database.
-
This project was developed to work with Bored Apes. New adoptions might require changes.
-
There might be errors when increasing the block ranges.
This project is built inside a docker container. The recommended setup is as follows. VScode has instructions on opening Github repositories in a VScode container. But the TLDR is as follows:
-
Open the repository in VScode.
-
Click the green icon on the bottom right of your screen
-
Click
Rebuild Container
The database used in this container is not persistent. A persistent DB made testing difficult. Please keep this in mind.
At a high level, the application does the following:
-
Setups the Postgres DB and starts up graphQL.
-
The schema will be implemented on the Postgres DB.
-
The graphQL server will start.
-
-
The application to listen for new
Transfer
events on the contract address.- It adds new events to the DB.
-
The application will query past
Transfer
events on the contract address.- It will add all the past events to the DB.
This application uses a configuration file. You can also pass in all the parameters via the command line if you choose. An example config file:
{
"infrastructure": {
"infuraKey": "xxxxxxxx",
"etherScanApiKey": "aaaaaaa"
},
"contract": {
"contractAddress": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
"includePastTransactions": true,
"contractStartBlock": 12287507,
"startBlock": 14145035,
"endBlock": 14146039,
"pastQueryDelimeter": 10000
},
"dbConnection": {
"newEventsConnection": "newEventsConnection",
"pastEventsConnection": "pastEventsConnection",
"graphqlPort": 4001,
"checkDuplications": true
}
}
Most parameters are straightforward. The parameters users might want to update are as follows:
-
contractAddress
- Update the contract address to watch. -
includePastTransactions
- Indicate if they want past transactions or not. -
contractStartBlock
- What is the contract creation block number.- This is used if the
startBlock
is set toearliest
.
- This is used if the
-
startBlock
- The block to start at.- A number or
earliest
is acceptable.
- A number or
-
endBlock
- The block to end at.-
A number or
latest
is acceptable. -
If
latest
is used - The application must process a “live” transfer to determine thelatest
block is. The application will get all the past transactions after thelatest
block number has been determined.
-
-
pastQueryDelimeter
- The application queries the past events in “chunks.” Infura has a query limit of10000
, therefore it is recommended that users stick to this number or lower. -
graphqlPort
- The port graphQL will expose. -
checkDuplications
- Should the application check for duplicate events based on thetransactionHash
before inserting them into the DB?
To start the application, do the following:
-
Create the
config.json
file.-
Rename the
ContractWatcher/src
/example.config.json
toContractWatcher/src/config.json
-
Update the
infuraKey
andetherScanApiKey
-
-
Start the application
cd ContractWatcher/
npm start
Open up the application on the port you configured. Currently, the application runs on localhost
. Please view the upcoming sections to view the queries and mutations.
This query will provide you with all the events in the database
query Query {
events {
contractAddress
transactionHash
blockNumber
fromAddress
toAddress
tokenId
isRead
}
}
This query will filter for your desired transactionHash
query Query($transactionHash: String!) {
transactionHash(transactionHash: "0x81095d5ee8183eec43ff775c13968571e06d1323fe6d9a8bdbc8c57ecc398f40") {
contractAddress
transactionHash
blockNumber
fromAddress
toAddress
tokenId
isRead
}
}
This query will filter for the specified contract address.
query Query($contractAddress: String!) {
contracts(contractAddress: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D") {
contractAddress
transactionHash
blockNumber
fromAddress
toAddress
tokenId
isRead
}
}
This will return all events for which the following statement is true: isRead === false
query Query {
allUnread {
contractAddress
transactionHash
blockNumber
fromAddress
toAddress
tokenId
isRead
}
}
You can query the tokenId
with a variety of addresses. The below captures the type of address you can use.
query Query($contractAddress: String!, $tokenId: Float!) {
allContractAddressAndTokenId(contractAddress: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", tokenId: 4049) {
contractAddress
transactionHash
blockNumber
fromAddress
toAddress
tokenId
isRead
}
}
query Query($tokenId: Float!, $fromAddress: String!) {
allFromAddressAndTokenId(tokenId: 9149, fromAddress: "0xC310e760778ECBca4C65B6C559874757A4c4Ece0") {
contractAddress
transactionHash
blockNumber
fromAddress
toAddress
tokenId
isRead
}
}
query Query($allToAddressAndTokenIdTokenId2: Float!, $toAddress: String!) {
allToAddressAndTokenId(tokenId: 9149, toAddress: "0x5d23B4ea39E274b23fdEa9bFDb57b032AcEd47d4") {
contractAddress
transactionHash
blockNumber
fromAddress
toAddress
tokenId
isRead
}
}
The parameter to mark an event read or unread is isRead - (Bool)
.
-
If
isRead == true
, the event has been read. -
If
isRead == false
, the event has not been read.
You can mark isRead
true or false. You must query events by the contractAddress
and tokenId
mutation Mutation {
markReadOrUnread (
contractAddress: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
tokenId: 4049,
isRead: true
) {
transactionHash
isRead
}
}
-
Improving Past Events - Currently, the application can view past events, but not all of them. The application runs into various bugs if the block range is too large. I haven’t had the time to debug them all. Therefore, it is recommended to stick to the current range. Feel free to use a larger block range, but keep in mind that the application might fail.
-
Improving New Events - The listener will close after each call, therefore I use the
setTimeout
function to repeatedly call the listener. This isn’t the best implementation, but it works for the current use case. -
Making Some Functions More Robust - Primarily the database functions. They have been written to handle a specific read/write use case for the application. But with a simple refactor, users can specify the
where
andinsert
queries to make the functions more robust. -
Use Cloud Infrastructure - Currently, the application only runs locally; using cloud infrastructure would make the application more robust.
-
Add Testing - Currently, the application is tested manually; adding some automated tests would be great.