Open Source Algorithms
Subgraph Schema
Last Updated: March 3rd, 2025
For suggested improvements or requested changes, email community@aemula.com.
We are in process of setting up the community governance of Aemula's subgraph schema, allowing community members to make pull requests of the source code from Github and propose changes for vote by the community.
Schema
type User @entity {
id: ID!
subscriptionExpiry: BigInt!
isAnnual: Boolean!
timestamp: BigInt!
}
type Article @entity(immutable: true) {
id: ID!
author: ID!
}
type Association @entity(immutable: true) {
id: ID!
user: ID!
article: ID!
type: AssociationType!
timestamp: BigInt!
}
enum AssociationType {
AUTHOR
SUPPORT
NEUTRAL
DISAGREE
REPORT
}
Event Mappings
import {
NewArticle as NewArticleEvent,
NewUser as NewUserEvent,
ReadDisagree as ReadDisagreeEvent,
ReadNeutral as ReadNeutralEvent,
ReadReport as ReadReportEvent,
ReadSupport as ReadSupportEvent,
SubscriptionEnded as SubscriptionEndedEvent,
SubscriptionStarted as SubscriptionStartedEvent
} from "../generated/ERC1967Proxy/AemulaV1"
import {
User,
Article,
Association
} from "../generated/schema"
import { log } from '@graphprotocol/graph-ts';
export function handleNewArticle(event: NewArticleEvent): void {
// pull authorId and articleId from params based on event declarations in Aemula.sol
let authorId = event.params.authorID.toHex();
let user = User.load(authorId);
if (!user) {
log.warning("User {} does not exist. Skipping event processing.", [authorId]);
return;
}
let articleId = event.params.ipfsCID;
let existingArticle = Article.load(articleId);
if (existingArticle) {
log.warning("Article {} already exists. Skipping duplicate creation.", [articleId]);
return;
}
// create a new Article entity
let article = new Article(articleId);
article.author = authorId;
article.save();
// use transaction hash as associationId, adding logIndex to handle multiple events in one transaction
let associationId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
// create a new Association entity to link the author to the article
let association = new Association(associationId);
association.user = authorId;
association.article = articleId;
association.type = "AUTHOR";
association.timestamp = event.block.timestamp;
association.save();
}
export function handleNewUser(event: NewUserEvent): void {
let user = new User(event.params.walletID.toHex());
let expiry = event.params.expiry;
user.subscriptionExpiry = expiry;
user.isAnnual = false;
user.timestamp = event.block.timestamp;
user.save();
}
export function handleReadDisagree(event: ReadDisagreeEvent): void {
let userId = event.params.walletID.toHex();
let user = User.load(userId);
if (!user) {
log.warning("User {} does not exist. Skipping event processing.", [userId]);
return;
}
let articleId = event.params.ipfsCID;
let article = Article.load(articleId);
if (!article) {
log.warning("Article {} does not exist. Skipping event processing.", [articleId]);
return;
}
// use transaction hash as associationId
let associationId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
// create a new Association entity to link user interaction to the article
let association = new Association(associationId);
association.user = userId;
association.article = articleId;
association.type = "DISAGREE";
association.timestamp = event.block.timestamp;
association.save();
}
export function handleReadNeutral(event: ReadNeutralEvent): void {
let userId = event.params.walletID.toHex();
let user = User.load(userId);
if (!user) {
log.warning("User {} does not exist. Skipping event processing.", [userId]);
return;
}
let articleId = event.params.ipfsCID;
let article = Article.load(articleId);
if (!article) {
log.warning("Article {} does not exist. Skipping event processing.", [articleId]);
return;
}
// use transaction hash as associationId
let associationId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
// create a new Association entity to link user interaction to the article
let association = new Association(associationId);
association.user = userId;
association.article = articleId;
association.type = "NEUTRAL";
association.timestamp = event.block.timestamp;
association.save();
}
export function handleReadReport(event: ReadReportEvent): void {
let userId = event.params.walletID.toHex();
let user = User.load(userId);
if (!user) {
log.warning("User {} does not exist. Skipping event processing.", [userId]);
return;
}
let articleId = event.params.ipfsCID;
let article = Article.load(articleId);
if (!article) {
log.warning("Article {} does not exist. Skipping event processing.", [articleId]);
return;
}
// use transaction hash as associationId
let associationId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
// create a new Association entity to link user interaction to the article
let association = new Association(associationId);
association.user = userId;
association.article = articleId;
association.type = "REPORT";
association.timestamp = event.block.timestamp;
association.save();
}
export function handleReadSupport(event: ReadSupportEvent): void {
let userId = event.params.walletID.toHex();
let user = User.load(userId);
if (!user) {
log.warning("User {} does not exist. Skipping event processing.", [userId]);
return;
}
let articleId = event.params.ipfsCID;
let article = Article.load(articleId);
if (!article) {
log.warning("Article {} does not exist. Skipping event processing.", [articleId]);
return;
}
// use transaction hash as associationId
let associationId = event.transaction.hash.toHex() + "-" + event.logIndex.toString();
// create a new Association entity to link user interaction to the article
let association = new Association(associationId);
association.user = userId;
association.article = articleId;
association.type = "SUPPORT";
association.timestamp = event.block.timestamp;
association.save();
}
export function handleSubscriptionEnded(event: SubscriptionEndedEvent): void {
let user = User.load(event.params.walletID.toHex());
// smart contract handles this logic, but need to add the check to resolve subgraph errors
if (user) {
// set subscriptionExpiry to the current block timestamp
user.subscriptionExpiry = event.block.timestamp;
user.save()
} else {
log.warning("User not found with wallet ID: {}", [event.params.walletID.toHex()]);
}
}
export function handleSubscriptionStarted(event: SubscriptionStartedEvent): void {
let user = User.load(event.params.walletID.toHex());
let expiry = event.params.expiry;
let isAnnual = event.params.isAnnual;
if (user) {
// set subscriptionExpiry to the emitted expiry
user.subscriptionExpiry = expiry;
user.isAnnual = isAnnual;
user.timestamp = event.block.timestamp;
user.save()
} else {
log.warning("User not found with wallet ID: {}", [event.params.walletID.toHex()]);
}
}