Разработка системы уведомлений для dApp через Push Protocol

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы уведомлений для dApp через Push Protocol
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка системы уведомлений для dApp через Push Protocol

Email-уведомления в DeFi не работают — у пользователей нет email в протоколе, только адрес кошелька. Телеграм-боты требуют отдельной подписки. Push Protocol (ранее EPNS) решает это: децентрализованный messaging layer, где уведомления привязаны к адресу кошелька, пользователь подписывается on-chain на каналы и получает уведомления в Push App, браузерный extension или прямо в dApp через SDK.

Как работает Push Protocol

Channel — это адрес, который отправляет уведомления. Создаётся один раз, требует стейкинга 50 PUSH на Ethereum (или Polygon). Протокол — платный в части создания канала, но рассылка уведомлений бесплатна.

Subscriber — пользователь, который opt-in в канал. Подписка — on-chain транзакция (или off-chain через gasless механизм на Polygon).

Notification — JSON payload, хранится в IPFS, индексируется узлами Push Protocol сети. Типы: Broadcast (всем подписчикам), Targeted (конкретному адресу), Subset (списку адресов).

Создание канала и отправка уведомлений

npm install @pushprotocol/restapi @pushprotocol/socket ethers

Создание канала — через Push dApp (app.push.org). Программная отправка уведомлений с сервера:

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',
  })
}

Типичные триггеры для DeFi уведомлений

Событие Тип уведомления Задержка
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 в 24h Broadcast Scheduled
Large price movement (>10%) Broadcast Price oracle

Для real-time триггеров нужен on-chain event listener:

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

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

// Смотрим события ликвидации
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,
        'Позиция ликвидирована',
        `Ликвидировано ${formatUnits(debtToCover, 18)} USDC. Проверьте ваши позиции.`,
        `https://app.protocol.xyz/positions`
      )
    }
  }
})

Отображение уведомлений в dApp

Fetch уведомлений для пользователя

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, // polling каждые 30 секунд
  })
}

Real-time через 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) => {
      // Инвалидируем кэш при новом уведомлении
      queryClient.invalidateQueries({ queryKey: ['push-notifications', address] })
      // Показываем toast
      showToast(feedItem.payload.notification.title)
    })

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

Компонент уведомлений

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>
  )
}

Проверка подписки и opt-in

Перед отправкой targeted уведомлений нужно проверить, подписан ли пользователь:

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 через Push SDK — пользователь подписывается через off-chain подпись (EIP-712), без gas. Важно для onboarding — требовать gas за подписку на уведомления отпугивает пользователей.

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