Links

Catching events from promises

How does the frontend listen for contract events?
When reloading, or after a custom interaction from the user, the application performs queries to The Graph, and retrieves data from active node operators listening and indexing our selected contract events (Learn more).
The GraphiQL
The API to make queries to.
The subgraph, deployed on The Graph Network, is available for queries on the Hosted Service. It contains:
  • subgraph.yaml: a manifest that describes the data it's interested in ;
  • schema.graphql: a schema that defines the data entities, and how the queries should be performed ;
  • promise-factory.ts: a mapping that handles the custom actions, by translating the data it receives into understandable entities we defined in the schema.

Manifest

subgraph.yaml
specVersion: 0.0.4
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: PromiseFactory
network: mumbai
source:
# The current PromiseFactory address
address: '0xa288Da44e534Ebed813D7ea8aEc7A86A50a878B9'
abi: PromiseFactory
# The block it should start indexing at
startBlock: 29217396
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- ActivePromise
- PromiseContractCreated
- ParticipantAdded
- TwitterVerifiedUser
- TwitterAddVerifiedSuccessful
abis:
- name: PromiseFactory
file: ./abis/PromiseFactory.json
eventHandlers:
# A new promise creation event
- event: PromiseContractCreated(indexed address,indexed
address,string,string,string,string,string[],string[],address[])
# How to handle it in the mapping
handler: handlePromiseContractCreated
# A new participant added to a promise event
- event: ParticipantAdded(indexed address,string,string,address)
handler: handleParticipantAdded
# A successful Twitter verification event
- event: TwitterAddVerifiedSuccessful(indexed address,string)
handler: handleTwitterAddVerifiedSuccessful
file: ./src/promise-factory.ts

Schema

schema.graphql
# Updated at PromiseContractCreated and ParticipantAdded
type ActivePromise @entity {
# A unique ID created in the mapping
id: ID!
# The creator of the promise
owner: Bytes!
# The address of the promise
contractAddress: Bytes!
# The name, IPFS CID and Arweave ID of the promise
promiseName: String!
ipfsCid: String!
arweaveId: String!
# The participants informations
partyNames: [String!]!
partyTwitterHandles: [String!]!
partyAddresses: [Bytes!]!
# The date of the promise creation - base on the block timestamp
# of PromiseContractCreated
createdAt: BigInt
# The date of the last modification - base on the block timestamp
# of ParticipantAdded
updatedAt: BigInt
}
# Fired when a promise is created
type PromiseContractCreated @entity {
id: ID!
owner: Bytes!
contractAddress: Bytes!
promiseName: String!
ipfsCid: String!
arweaveId: String!
partyNames: [String!]!
partyTwitterHandles: [String!]!
partyAddresses: [Bytes!]!
blockTimestamp: BigInt
}
# Fired when a participant is added to a promise
type ParticipantAdded @entity {
id: ID!
contractAddress: Bytes! # address
participantName: String! # string
participantTwitterHandle: String! # string
participantAddress: Bytes! # address
}
# Updated at TwitterAddVerifiedSuccessful
type TwitterVerifiedUser @entity {
id: ID!
address: Bytes!
# All unique handles verified for this address
twitterHandles: [String!]!
# The timestamp of the verification
verifiedAt: BigInt
}
# Fired when a participant gets a Twitter account verified
type TwitterAddVerifiedSuccessful @entity {
id: ID!
address: Bytes!
twitterHandle: String!
}

Mapping

Handling PromiseContractCreated

promise-factory.ts
export function handlePromiseContractCreated(
event: PromiseContractCreatedEvent,
): void {
// It should never happen that the same contract is created twice
// But we can't ever be sure enough, so we check if the entity already exists anyway
let activePromise = ActivePromise.load(
getIdFromEventParams(event.params._contractAddress),
);
// If this ActivePromise doesn't exist, create it
if (!activePromise) {
activePromise = new ActivePromise(
getIdFromEventParams(event.params._contractAddress),
);
}
// Grab the data from the event parameters
// and associate it to this entity
// From the contract...
activePromise.owner = event.params._owner;
activePromise.contractAddress = event.params._contractAddress;
activePromise.promiseName = event.params._promiseName;
activePromise.ipfsCid = event.params._ipfsCid;
activePromise.arweaveId = event.params._arweaveId;
activePromise.partyNames = event.params._partyNames;
activePromise.partyTwitterHandles = event.params._partyTwitterHandles;
activePromise.partyAddresses = event.params._partyAddresses.map<Bytes>(
(e: Bytes) => e,
);
// From the block...
activePromise.createdAt = event.block.timestamp;
activePromise.updatedAt = event.block.timestamp;
// Save the entity
activePromise.save();
}

Handling ParticipantAdded

promise-factory.ts
export function handleParticipantAdded(event: ParticipantAddedEvent): void {
// Grab the entity (created when the promise was created)
// We won't need to create it here, it should not be possible to add
// a participant to a promise that doesn't exist
let activePromise = ActivePromise.load(
getIdFromEventParams(event.params._contractAddress),
);
// We can't use the .push method here because it's not supported by AssemblyScript
// So we have to do it 'manually'
// Create an new array from the old one along with the new parameter
const newNamesArray = activePromise!.partyNames.concat([
event.params._participantName,
]);
const newTwitterHandlesArray = activePromise!.partyTwitterHandles.concat([
event.params._participantTwitterHandle,
]);
const newAddressesArray = activePromise!.partyAddresses.concat([
event.params._participantAddress,
]);
// Set the promise new parameter with the new array
activePromise!.partyNames = newNamesArray;
activePromise!.partyTwitterHandles = newTwitterHandlesArray;
activePromise!.partyAddresses = newAddressesArray;
activePromise!.updatedAt = event.block.timestamp;
activePromise!.save();
}

Handling TwitterAddVerifiedSuccessful

promise-factory.ts
export function handleTwitterAddVerifiedSuccessful(
event: TwitterAddVerifiedSuccessfulEvent,
): void {
// Load the user entity, if they already have verified Twitter accounts
let twitterVerifiedUser = TwitterVerifiedUser.load(
getIdFromEventParams(event.params._owner),
);
// We prefer not interacting directly with twitterHandles that could be null
// Create a new array
let twitterHandlesArray: string[] = [];
// If the user has no verified account (so no entity) yet...
if (!twitterVerifiedUser) {
// Create an entity...
twitterVerifiedUser = new TwitterVerifiedUser(
getIdFromEventParams(event.params._owner),
);
// ... and just add the handle to a new array
twitterHandlesArray = new Array<string>().concat([
event.params._twitterHandle,
]);
} else {
// If the user has been verified before, get the array from the entity
twitterHandlesArray = twitterVerifiedUser.twitterHandles;
// Add the new handle to the array
twitterHandlesArray = twitterHandlesArray.concat([
event.params._twitterHandle,
]);
// Remove duplicates from the array (if the same handle has been verified)
twitterHandlesArray = twitterHandlesArray.filter(
(value, index, self) => self.indexOf(value) === index,
);
}
twitterVerifiedUser.address = event.params._owner;
// Set the twitterHandles without ever checking the content of the entity
twitterVerifiedUser.twitterHandles = twitterHandlesArray;
twitterVerifiedUser.verifiedAt = event.block.timestamp;
twitterVerifiedUser.save();
}

Resources