Skip to main content

Overview

This is an introductory guide to take you through the entire process of how to send notifications from a Subgraph using Push.

caution

Subgraph notifications are currenly supported on staging environtment from Push dApp

Introduction: The Graph Protocol & Subgraphs

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.

A Subgraph defines which data The Graph will index from a blockchain, and how it will store it. Once deployed, it will form a part of a global graph of blockchain data which you can retrieve using GraphQL.

Currently, Push only supports the subgraphs deployed on Hosted Service of The Graph Protocol. Providing support to Subgraph Studio would be part of the next iteration.

For more information on how to deploy a subgraph on the hosted service for your smart contract or dApp, check out this documentation.

Notifications from Subgraphs 💡

Subgraphs retrieve and store data from the blockchain for a particular smart contract. This data can be used to analyze a variety of things related to the smart contract.

For example, the Uniswap Subgraph stores data related to the total volume across all trading pairs, volume data per trading pair, and even data for a particular token.

What if you intelligently fetch the data from a Subgraph and generate useful alerts 🤔? This will be extremely helpful for the end-users of your dApp and entities connected to your smart contract making the user experience smoother.

Sending Notifications using Push

Push protocol has developed an in-house helper function specifically for The Graph Protocol which allows you to read events from the Subgraph and define notifications accordingly. Once defined, they will be stored on the Subgraph in a Long String format.

Push Nodes can, later on, fetch the notifications defined on a Subgraph and push them accordingly to Subscribers of the Channel.

Notifications via The Graph architecture

Push X Graph Integration Example

Integrate Push Protocol with an ERC-20 contract's subgraph to send out notifications whenever a Transfer happens.

Prerequisites

  1. Have a Push Notification Channel ready - see the docs here to create a channel.
  2. Install the graph CLI.
npm install -g @graphprotocol/graph-cli
# OR
yarn global add @graphprotocol/graph-cli
  1. Link your Github to The Graph's hosted service.

  2. Add a subgraph named MySubgraphXYZ (or a name of your choice) from your Hosted Service Dashboard.

Contract deployment

note

Skip this step if you a have deployed contract already that you want to query

  1. Copy and Deploy the sample ERC-20 contract using Remix IDE
pragma solidity ^0.4.24;

//Safe Math Interface
contract SafeMath {

function safeAdd(uint a, uint b) public pure returns (uint c) {
c = a + b;
require(c >= a);
}

function safeSub(uint a, uint b) public pure returns (uint c) {
require(b <= a);
c = a - b;
}

function safeMul(uint a, uint b) public pure returns (uint c) {
c = a * b;
require(a == 0 || c / a == b);
}

function safeDiv(uint a, uint b) public pure returns (uint c) {
require(b > 0);
c = a / b;
}
}

// ERC Token Standard #20 Interface
contract ERC20Interface {
function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant returns (uint balance);
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);

event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

// Contract function to receive approval and execute function in one call
contract ApproveAndCallFallBack {
function receiveApproval(address from, uint256 tokens, address token, bytes data) public;
}

//Actual token contract
contract PushToken is ERC20Interface, SafeMath {
string public symbol;
string public name;
uint8 public decimals;
uint public _totalSupply;

mapping(address => uint) balances;
mapping(address => mapping(address => uint)) allowed;

constructor() public {
symbol = "PUSH";
name = "Push Token";
decimals = 2;
_totalSupply = 100000;
balances[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}

function totalSupply() public constant returns (uint) {
return _totalSupply - balances[address(0)];
}

function balanceOf(address tokenOwner) public constant returns (uint balance) {
return balances[tokenOwner];
}

function transfer(address to, uint tokens) public returns (bool success) {
balances[msg.sender] = safeSub(balances[msg.sender], tokens);
balances[to] = safeAdd(balances[to], tokens);
emit Transfer(msg.sender, to, tokens);
return true;
}

function approve(address spender, uint tokens) public returns (bool success) {
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
return true;
}

function transferFrom(address from, address to, uint tokens) public returns (bool success) {
balances[from] = safeSub(balances[from], tokens);
allowed[from][msg.sender] = safeSub(allowed[from][msg.sender], tokens);
balances[to] = safeAdd(balances[to], tokens);
emit Transfer(from, to, tokens);
return true;
}

function allowance(address tokenOwner, address spender) public constant returns (uint remaining) {
return allowed[tokenOwner][spender];
}

function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) {
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data);
return true;
}

function () public payable {
revert();
}
}

Subgraph deployment

note

Skip this step if you already have a deployed subgraph

  1. Navigate to the Subgraph directory and you’ll find schema.graphql file. Open in an editor of your choice and include the following Push Schema —
type EpnsNotificationCounter @entity {
id: ID!
totalCount: BigInt!
}

type EpnsPushNotification @entity {
id: ID!
notificationNumber: BigInt!
recipient: String!
notification: String!
}
  1. In the mappings file under src/ of your subgraph, export the subgraph ID —
// Note: Push Protocol supports only The Graph Hosted Service at present

export const subgraphID = '<GITHUB_USERNAME>/<SUBGRAPH_NAME>';

// example
// export const subgraphID = "aiswaryawalter/push-graph-test"
note

Make sure the above step is complete, as Subgraph ID will be imported in the next step!

  1. Create a file named PushNotification.ts in the src/ folder of your subgraph. We’ll call this our Helper File. Now, copy the below-provided TypeScript code and paste it into the newly created Helper file —
import { BigInt, log } from '@graphprotocol/graph-ts';
import {
EpnsNotificationCounter,
EpnsPushNotification,
} from '../generated/schema';
import { subgraphID } from './push-token';

export function sendPushNotification(
recipient: string,
notification: string
): void {
let id1 = subgraphID;
log.info('New id of EpnsNotificationCounter is: {}', [id1]);
let epnsNotificationCounter = EpnsNotificationCounter.load(id1);
if (epnsNotificationCounter == null) {
epnsNotificationCounter = new EpnsNotificationCounter(id1);
epnsNotificationCounter.totalCount = BigInt.fromI32(0);
}
epnsNotificationCounter.totalCount = epnsNotificationCounter.totalCount.plus(
BigInt.fromI32(1)
);

let count = epnsNotificationCounter.totalCount.toHexString();
let id2 = `${subgraphID}+${count}`;
log.info('New id of EpnsPushNotification is: {}', [id2]);
let epnsPushNotification = EpnsPushNotification.load(id2);
if (epnsPushNotification == null) {
epnsPushNotification = new EpnsPushNotification(id2);
}
epnsPushNotification.recipient = recipient;
epnsPushNotification.notification = notification;
epnsPushNotification.notificationNumber = epnsNotificationCounter.totalCount;
epnsPushNotification.save();
epnsNotificationCounter.save();
}
  1. Import the helper function in the mappings file
import { sendPushNotification } from './PushNotification';
note

Follow steps 5, 6 and 7 within the respective handler functions from which the notification needs to be sent 👇🏼

  1. Define Notification Payload Items: In the event handler mapping from which you need to send the notification, define the notification payload items such as recipient of the notification, type, title, message, etc. These variables will be further used to define the notification variable.

It’s highly recommended to take a look at types of notifications to understand which one you want to send.

For a quick reference, the recipient differs with the payload type. For example, broadcast (type = 1) and special multi-payload notifications have the channel address as the recipient.

let recipient = event.params.to.toHexString(),
type = '3',
title = 'PUSH Received',
body = `Received ${event.params.tokens.div(
power
)} PUSH from ${event.params.from.toHexString()}`,
subject = 'PUSH Received',
message = `Received ${event.params.tokens.div(
power
)} PUSH from ${event.params.from.toHexString()}`,
image =
'https://play-lh.googleusercontent.com/i911_wMmFilaAAOTLvlQJZMXoxBF34BMSzRmascHezvurtslYUgOHamxgEnMXTklsF-S',
secret = 'null',
cta = 'https://push.org/';
  1. Define Notification: The notification variable is defined in the given below format 👇🏼

Format: {"field1" : "value1", "field2" : "value2" }

let notification = `{\"type\": \"${type}\", \"title\": \"${title}\", \"body\": \"${body}\", \"subject\": \"${subject}\", \"message\": \"${message}\", \"image\": \"${image}\", \"secret\": \"${secret}\", \"cta\": \"${cta}\"}`;
  1. Call the Push Helper Function: Once the above steps are complete, we need to invoke the Push helper function and send the response. To call the Push Notification helper function, use the below script —
sendPushNotification(recipient, notification);

Redploy your subgraph

Once you have made changes to your subgraph to include Push related logic, you now need to redploy for the subgraph network to sync the newly added logic —

  1. Generate code
graph codegen
  1. Get the access token from the Graph dashboard & authenticate
graph auth --product hosted-service <ACCESS_TOKEN>
  1. Deploy the subgraph
graph deploy --product hosted-service <GITHUB_USER>/<SUBGRAPH NAME>
  1. Test that the logic is synced by going to the subgraph playground and pasting the below query to show the notification payloads
{
epnsPushNotifications(first: 20) {
id
notificationNumber
recipient
notification
}
}

Here is the final Subgraph with Push integration.

Attach subgraph to your channel

Once you have set up Push integration into your subgraph, you must add the subgraph to its notification channel in order to deliver notifications. You will require the Subgraph ID for this purpose.

It is a slug usually present at the end of the subgraph URL 😉, for example —

https://thegraph.com/hosted-service/subgraph/aiswaryawalter/push-graph-test

If you have already created your channel, you can follow the below steps to enable notifications from your subgraph by following these steps —

  1. Go to Push staging dApp → Channel Dashboard → Add Subgraph Details

  2. Enter your Subgraph ID and Poll Interval

Poll Interval (in seconds) defines the time period at which Push Nodes shall ping the subgraph for fetching the latest notifications.

caution

This is an on-chain transaction that stores the above data to Push Core Contract. So it requires $ETH for gas fees.

note

If you don’t have a channel yet, you can easily create one by following this guide here

Push dApp subgraph option reference

Testing notification

  1. Opt-in to the newly created channel.

  2. Initiate a Transfer from your deployed contract.

  3. Wait for the notification to appear on the recipient's wallet via Push Metamask snap, Push dApp, Push extension, Unstoppable mobile app, Push mobile app or any of the other supported interfaces.

🎉 Congratulations on successfully integrating Push Helper Function into your Subgraph, and also adding Subgraph details into your channel.