dApp Notification System via Push Protocol

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
dApp Notification System via Push Protocol
Medium
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Developing a notification system for dApps via Push Protocol

Email notifications in DeFi don't work — users don't have email in the protocol, only a wallet address. Telegram bots require separate subscription. Push Protocol (formerly EPNS) solves this: a decentralized messaging layer where notifications are tied to a wallet address, users subscribe on-chain to channels, and receive notifications in the Push App, browser extension, or directly in the dApp via SDK.

How Push Protocol works

Channel — an address that sends notifications. Created once, requires staking 50 PUSH on Ethereum (or Polygon). The protocol is paid for channel creation, but notification distribution is free.

Subscriber — a user who opts in to a channel. Subscription is an on-chain transaction (or off-chain via a gasless mechanism on Polygon).

Notification — a JSON payload stored in IPFS, indexed by Push Protocol network nodes. Types: Broadcast (all subscribers), Targeted (specific address), Subset (address list).

Creating a channel and sending notifications

npm install @pushprotocol/restapi @pushprotocol/socket ethers

Channel creation happens through the Push dApp (app.push.org). Programmatic notification sending from a server:

import * as PushAPI from '@pushprotocol/restapi'
import { ethers } from 'ethers'

const CHANNEL_PRIVATE_KEY = process.env.PUSH_CHANNEL_PRIVATE_KEY!
const signer = new ethers.Wallet(CHANNEL_PRIVATE_KEY)

async function sendNotification(
  recipientAddress: string,
  title: string,
  body: string,
  cta?: string
) {
  await PushAPI.payloads.sendNotification({
    signer,
    type: 3, // targeted
    identityType: 2, // direct payload
    notification: { title, body },
    payload: {
      title,
      body,
      cta: cta ?? '',
      img: '',
    },
    recipients: `eip155:1:${recipientAddress}`,
    channel: `eip155:1:${CHANNEL_ADDRESS}`,
    env: 'prod',
  })
}

Typical triggers for DeFi notifications

Event Notification type Delay
Liquidation risk (health < 1.2) Targeted, HIGH urgency Real-time
Position liquidated Targeted Real-time
Yield harvest available Targeted Hourly
Governance proposal created Broadcast On-chain event
Voting deadline in 24h Broadcast Scheduled
Large price movement (>10%) Broadcast Price oracle

For real-time triggers, you need an on-chain event listener:

import { createPublicClient, http, parseAbiItem } from 'viem'

const client = createPublicClient({ chain: mainnet, transport: http(RPC_URL) })

// Watch for liquidation events
client.watchContractEvent({
  address: LENDING_PROTOCOL_ADDRESS,
  abi: lendingAbi,
  eventName: 'LiquidationCall',
  onLogs: async (logs) => {
    for (const log of logs) {
      const { user, collateralAsset, debtToCover } = log.args
      await sendNotification(
        user,
        'Position liquidated',
        `Liquidated ${formatUnits(debtToCover, 18)} USDC. Check your positions.`,
        `https://app.protocol.xyz/positions`
      )
    }
  }
})

Displaying notifications in a dApp

Fetch notifications for a user

import * as PushAPI from '@pushprotocol/restapi'
import { useAccount } from 'wagmi'
import { useQuery } from '@tanstack/react-query'

export function useNotifications() {
  const { address } = useAccount()

  return useQuery({
    queryKey: ['push-notifications', address],
    queryFn: async () => {
      const feeds = await PushAPI.user.getFeeds({
        user: `eip155:1:${address}`,
        limit: 20,
        env: 'prod',
      })
      return feeds
    },
    enabled: !!address,
    refetchInterval: 30_000, // poll every 30 seconds
  })
}

Real-time via WebSocket

import { createSocketConnection, EVENTS } from '@pushprotocol/socket'

function usePushSocket(address: string | undefined) {
  const [socket, setSocket] = useState<any>(null)
  const queryClient = useQueryClient()

  useEffect(() => {
    if (!address) return

    const sdkSocket = createSocketConnection({
      user: `eip155:1:${address}`,
      env: 'prod',
      socketOptions: { autoConnect: true }
    })

    sdkSocket.on(EVENTS.CONNECT, () => console.log('Push socket connected'))
    sdkSocket.on(EVENTS.USER_FEEDS, (feedItem: any) => {
      // Invalidate cache on new notification
      queryClient.invalidateQueries({ queryKey: ['push-notifications', address] })
      // Show toast
      showToast(feedItem.payload.notification.title)
    })

    setSocket(sdkSocket)
    return () => sdkSocket?.disconnect()
  }, [address])
}

Notification component

function NotificationBell() {
  const { data: notifications = [], isLoading } = useNotifications()
  const unread = notifications.filter(n => !n.epoch || n.epoch > lastRead)

  return (
    <Popover>
      <PopoverTrigger>
        <Bell className="h-5 w-5" />
        {unread.length > 0 && (
          <span className="absolute -top-1 -right-1 h-4 w-4 rounded-full bg-red-500 text-xs flex items-center justify-center">
            {unread.length}
          </span>
        )}
      </PopoverTrigger>
      <PopoverContent className="w-80">
        {notifications.map(n => (
          <NotificationItem key={n.sid} notification={n} />
        ))}
      </PopoverContent>
    </Popover>
  )
}

Checking subscription and opt-in

Before sending targeted notifications, check if the user is subscribed:

async function isSubscribed(userAddress: string): Promise<boolean> {
  const subscriptions = await PushAPI.user.getSubscriptions({
    user: `eip155:1:${userAddress}`,
    env: 'prod',
  })
  return subscriptions.some(
    (sub: any) => sub.channel.toLowerCase() === CHANNEL_ADDRESS.toLowerCase()
  )
}

Gasless opt-in via Push SDK — users subscribe through an off-chain signature (EIP-712), without gas. Important for onboarding — requiring gas for notification subscription discourages users.

const user = await PushAPI.initialize(signer, { env: 'prod' })
await user.notification.subscribe(`eip155:1:${CHANNEL_ADDRESS}`) // gasless via delegated signing