Usage
In this section, we showcase the aspects of using the Notify API. We'll guide you through the initial steps of initializing the Notify client and logging in a blockchain account. You'll also learn how to manage your subscriptions and messages. Additionally, we cover the process of setting up and displaying push notifications on your preferred platform. To ensure a good user experience, we include best practices for spam protection, helping you to enable the users to maintain control over the notifications wallet receives.
Content
Links to sections on this page. Some sections are platform specific and are only visible when the platform is selected. To view a summary of useful platform specific topics, check out Extra (Platform Specific) under this section.
- Initialization: Creating a new Notify Client instance and initializing it with a projectId from Cloud.
- Account login: A SIWE message must be signed by the user in order to authorize the client to use Notify API
- Subscribing to a new dapp: Opt-in to receive notifications from dapp
- Fetching active subscriptions: Get active subscriptions
- Fetching subscription’s notification: Get notifications of a subscription
- Fetching available notification types: Get latest notification types
- Updating subscriptions notification settings: Change allowed notification types sent by dapp
- Unsubscribe from a dapp: Opt-out from receiving notifications from a dapp
- Account logout: To stop receiving notifications to this client, accounts can logout of using Notify API
- Push Notification Setup: Configuring app in order to decrypt notifications
Initialization
Don't have a project ID?
Head over to Reown Cloud and create a new project now!
Initialize the SDK clients
import { NotifyClient } from '@walletconnect/notify-client'
const notifyClient = await NotifyClient.init({
projectId: '<YOUR PROJECT ID>'
})
Add listeners for relevant events
// Handle response to a `notifyClient.subscribe(...)` call
notifyClient.on('notify_subscription', async ({ params }) => {
const { error } = params
if (error) {
// Setting up the subscription failed.
// Inform the user of the error and/or clean up app state.
console.error('Setting up subscription failed: ', error)
} else {
// New subscription was successfully created.
// Inform the user and/or update app state to reflect the new subscription.
console.log(`Subscribed successfully.`)
}
})
// Handle an incoming notification
notifyClient.on('notify_message', ({ params }) => {
const { message } = params
// e.g. build a notification using the metadata from `message` and show to the user.
})
// Handle response to a `notifyClient.update(...)` call
notifyClient.on('notify_update', ({ params }) => {
const { error } = params
if (error) {
// Updating the subscription's scope failed.
// Inform the user of the error and/or clean up app state.
console.error('Setting up subscription failed: ', error)
} else {
// Subscription's scope was updated successfully.
// Inform the user and/or update app state to reflect the updated subscription.
console.log(`Successfully updated subscription scope.`)
}
})
// Handle a change in the existing subscriptions (e.g after a subscribe or update)
notifyClient.on('notify_subscriptions_changed', ({ params }) => {
const { subscriptions } = params
// `subscriptions` will contain any *changed* subscriptions since the last time this event was emitted.
// To get a full list of subscriptions for a given account you can use `notifyClient.getActiveSubscriptions({ account: 'eip155:1:0x63Be...' })`
})
Account login
In order to register account in Notify API to be able to subscribe to any dapp to start receiving notifications, account needs to sign SIWE message to prove ownership. Developers can check if an account is registered by calling isRegistered()
function. If the account is not registered, developers should call prepareRegistration()
and then register()
function to register the account.
This is a one-time action per account. It does not need to be repeated after initial registration of the new account.
Registering as a wallet
const account = `eip155:1:0x63Be2c680685d2A9620c11b0068291261aa62d76`
const domain = 'app.mydomain.com', // pass the domain (i.e. the hostname) where your dapp is hosted.
const allApps = true // The user will be prompted to authorize this wallet to send and receive messages on their behalf for ALL domains using their WalletConnect identity.
// No need to register and sign message if already registered.
if (notifyClient.isRegistered({ account, domain, allApps })) return;
const {registerParams, message} = notifyClient.prepareRegistration({
account,
domain,
allApps
});
const signature = await ethersWallet.signMessage(message);
await notifyClient.register({
registerParams,
signature,
})
Subscribing to a new dapp
To begin receiving notifications from a dapp, users must opt-in by subscribing. This subscription process grants permission for the dapp to send notifications to the user. These notifications can serve a variety of purposes, such as providing updates on the user's blockchain account activities or informing them about ongoing campaigns within the dapp. Upon initial subscription, clients will be automatically enrolled to receive all types of notifications as defined by the dapp at that moment. Users have the flexibility to modify their notification settings later, allowing them to tailor the types of alerts they receive according to their preferences.
To identify dapps that can be subscribed to via Notify, we can query the following Explorer API endpoint:
https://explorer-api.walletconnect.com/v3/dapps?projectId=YOUR_PROJECT_ID&is_notify_enabled=true
// Get the domain of the target dapp from the Explorer API response
const appDomain = new URL(fetchedExplorerDapp.platform_browser).hostname
// Subscribe to `fetchedExplorerDapp` by passing the account to be subscribed and the domain of the target dapp.
await notifyClient.subscribe({
account,
appDomain
})
// -> Success/Failure will be received via the `notify_update` event registered previously.
// -> New subscription will be emitted via the `notify_subscriptions_changed` watcher event.
Fetching active subscriptions
To fetch the current list of subscriptions an account has, call getActiveSubscriptions()
.
// Will return all active subscriptions for the provided account, keyed by subscription topic.
const accountSubscriptions = notifyClient.getActiveSubscriptions({
account: `eip155:1:0x63Be...`
})
Fetching subscription’s notifications
To fetch subscription’s notifications by calling getNotificationHistory()
.
const notifications = notifyClient.getNotificationHistory(account)
Fetching available notification types
Developers can fetch latest notification types specified by dapp by calling getNotificationTypes()
function.
You can use the scope
object of the subscription to get the available notification types.
// get notification types by accessing `scope` member of a dapp's subscription
const notificationTypes = notifyClient
.getActiveSubscriptions({ account })
.filter(subscription => subscription.topic === topic).scope
Updating subscriptions notification settings
Users can alter their notification settings to filter out unwanted alerts from a dapp. During this process, they review and select the types of notifications they wish to receive, based on the latest options provided by the dapp. Available notification types fetching is shown in the next section.
// `topic` - subscription topic of the subscription that should be updated.
// `scope` - an array of notification types that should be enabled going forward. The current scopes can be found under `subscription.scope`.
await notifyClient.update({
topic,
scope: ['alerts']
})
Unsubscribe from a dapp
To opt-out of receiving notifications from a dap, a user can decide to unsubscribe from dapp.
notifyClient.deleteSubscription({ topic: 'subscription_topic_to_unsubscribe_from' })
Account logout
If an account is removed from the client or a user no longer wants to receive notifications for this account, you can logout the account from Notify API by calling unregister()
. This will remove all subscriptions and messages for this account from the client’s storage.
const account = `eip155:1:0x63Be2c680685d2A9620c11b0068291261aa62d76`
await notifyClient.unregister({
account
})
Fetch notification history (Pagination)
There might be different approaches to implement pagination in your app depending on your needs. You can see the following example implemented with FlatList
which introduces infinite scroll functionality with a basic example:
Please make sure you have better handling of the notify client instance which handles worst cases by checking initialization status, account status, for production ready apps.
export default function SubscriptionDetailsScreen() {
const {topic} = useRoute().params as {topic: string};
const [notifications, setNotifications] = React.useState([]);
const [hasMore, setHasMore] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);
const lastItem = notifications?.[notifications.length - 1]?.id;
async function getNotificationHistory(startingAfter?: string) {
setIsLoading(true);
const notificationHistory = await notifyClient.getNotificationHistory({
topic,
limit: 15,
startingAfter,
});
setNotifications(
prevNotifications => prevNotifications.concat(notificationHistory.notifications),
);
setHasMore(notificationHistory.hasMore);
setIsLoading(false);
return notificationHistory;
}
React.useEffect(() => {
getNotificationHistory();
}, [topic]);
return (
<FlatList
data={sortedByDate}
keyExtractor={item => item.sentAt.toString()}
onEndReached={() => {
if (hasMore && lastItem) {
getNotificationHistory(lastItem)
}
}}
ListFooterComponent={() => {
if (!isLoading) return null
return <NotifcationItemSkeleton />
}}
renderItem={({item}) => (
<NotificationItem key={item.id} item={item} />
)}
/>
);
}
Push Notification Setup
Install @react-native-firebase/app
, @react-native-firebase/messaging
and @notifee/react-native
to handle Push Notifications.
Please refer to the respective package documentation to configure them properly.
yarn add @notifee/react-native @react-native-firebase/app @react-native-firebase/messaging
Update your index.js
file to include the following logic.
import { AppRegistry } from 'react-native'
import { name as appName } from './app.json'
import crypto from 'react-native-quick-crypto'
import messaging from '@react-native-firebase/messaging'
import notifee, { AndroidImportance, AndroidVisibility, EventType } from '@notifee/react-native'
import { NotifyClient } from '@walletconnect/notify-client'
import { decryptMessage } from '@walletconnect/notify-message-decrypter'
import App from './src/App'
const polyfillDigest = async (algorithm, data) => {
const algo = algorithm.replace('-', '').toLowerCase()
const hash = crypto.createHash(algo)
hash.update(data)
return hash.digest()
}
globalThis.crypto = crypto
globalThis.crypto.subtle = {
digest: polyfillDigest
}
// Create notification channel (Android only feature)
notifee.createChannel({
id: 'default',
name: 'Default Channel',
lights: false,
vibration: true,
importance: AndroidImportance.HIGH,
visibility: AndroidVisibility.PUBLIC
})
let notifyClient
const projectId = process.env.ENV_PROJECT_ID
async function registerAppWithFCM() {
// This is expected to be automatically handled on iOS. See https://rnfirebase.io/reference/messaging#registerDeviceForRemoteMessages
if (Platform.OS === 'android') {
await messaging().registerDeviceForRemoteMessages()
}
}
async function registerClient(deviceToken, clientId) {
const body = JSON.stringify({
client_id: clientId,
token: deviceToken,
type: 'fcm',
always_raw: true
})
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body
}
return fetch(`https://echo.walletconnect.com/${projectId}/clients`, requestOptions)
.then(response => response.json())
.then(result => console.log('>>> registered client', result))
.catch(error => console.log('>>> error while registering client', error))
}
async function handleGetToken(token) {
const status = await messaging().requestPermission()
const enabled =
status === messaging.AuthorizationStatus.AUTHORIZED ||
status === messaging.AuthorizationStatus.PROVISIONAL
if (enabled) {
notifyClient = await NotifyClient.init({ projectId })
const clientId = await notifyClient.core.crypto.getClientId()
return registerClient(token, clientId)
}
}
messaging().getToken().then(handleGetToken)
messaging().onTokenRefresh(handleGetToken)
async function onMessageReceived(remoteMessage) {
if (!remoteMessage.data?.blob || !remoteMessage.data?.topic) {
console.log('Missing blob or topic on notification message.')
return
}
const decryptedMessage = await decryptMessage({
topic: remoteMessage.data?.topic,
encryptedMessage: remoteMessage.data?.blob
})
return notifee.displayNotification({
title: decryptedMessage.title,
body: decryptedMessage.body,
id: 'default',
android: {
channelId: 'default',
importance: AndroidImportance.HIGH,
visibility: AndroidVisibility.PUBLIC,
smallIcon: 'ic_launcher', // optional, defaults to 'ic_launcher'.
// pressAction is needed if you want the notification to open the app when pressed. See https://notifee.app/react-native/docs/ios/interaction#press-action
pressAction: {
id: 'default'
}
}
})
}
messaging().onMessage(onMessageReceived)
messaging().setBackgroundMessageHandler(onMessageReceived)
notifee.onBackgroundEvent(async ({ type, detail }) => {
const { notification, pressAction } = detail
// Check if the user pressed the "Mark as read" action
if (type === EventType.ACTION_PRESS && pressAction.id === 'mark-as-read') {
// Remove the notification
await notifee.cancelNotification(notification.id)
}
})
function HeadlessCheck({ isHeadless }) {
if (isHeadless) {
// App has been launched in the background by iOS, ignore
return null
}
// Render the app component on foreground launch
return <App />
}
AppRegistry.registerComponent(appName, () => HeadlessCheck)