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 .
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();
}