Here's the deal: Dialect just made it stupid easy to slap push notifications onto your Solana apps, so users get instant pings on their phones-even if the app's closed. No more "check the app every 5 seconds" BS for trades, alerts, or whatever. I set this up last week for a little DeFi thing I'm messing with, and it took like 20 minutes. Why does this rock for Solana? Blockchain moves fast. Liquidations? Price pumps? You ping 'em right now.
It's this alerts stack built for Web3, but the push notification part hooks into Firebase for mobile magic. Your Solana app sends a notification, Dialect routes it to the user's phone via their wallet address. Users subscribe once, and boom-cross app inbox too if you want universal vibes. Powers stuff like Jupiter Mobile now. In my experience, it's perfect for DeFi apps where timing is everything. No infra to manage. Fees? Super cheap, like under 0.000005 SOL equivalent per notification burst, scales to millions without breaking.
Sound familiar? That FOMO when you miss a trade? This fixes it. But you gotta have your app registered with Dialect first. Head to their dashboard, sign up, grab your client. That's your golden ticket.
The thing is, without FCM, nothing pushes. I usually start with React Native since it's cross platform. Firebase SDK install? npm i @react native firebase/messaging. Boom.
Grab your firebaseConfig from console.firebase.google.com. iOS needs APNs cert-upload it or notifications ghost you. Android? google services.json in android/app. Test with a dummy push first.
Now the fun part. User opens your app, you ask for notification perms nicely. Don't blast it on launch-wait till they do something cool, like connect wallet.
import messaging from '@react native firebase/messaging';async function getFCMToken() { try { const authStatus = await messaging().requestPermission(); const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED || authStatus === messaging.AuthorizationStatus.PROVISIONAL; if (enabled) { const fcmToken = await messaging().getToken(); console.log('FCM Token:', fcmToken); return fcmToken; } } catch (error) { console.error('Failed to get FCM token:', error); }
} Tokens refresh sometimes-listen for it with messaging().onTokenRefresh. Store it securely, tie to user session.
Got the token? Auth'd user with JWT? Client ready? Hit the subscribe endpoint. Here's the exact fetch I copy paste every time:
async function subscribeToDialectPush(fcmToken, jwtToken, clientKey) { try { const response = await fetch('https://alerts api.dial.to/v2/push/subscribe', { method: 'POST', headers: { 'Authorization': Bearer ${jwtToken}, 'X Dialect Client': clientKey, 'Content Type': 'application/json' }, body: JSON.stringify({ fcmToken: fcmToken, deviceId: 'your unique device id', // UUID from expo device or whatever appId: 'YOURAPPID' // Optional, for app specific }) }); if (response.ok) { console.log('Subscribed! Pushes incoming.'); } else { console.error('Subscribe failed:', await response.text()); } } catch (error) { console.error('Push sub error:', error); }
} Call this after wallet connect. deviceId helps if they swap phones-avoids duplicates. Unsub same way, swap to /unsubscribe. Easy toggle in settings.
Why appId? Limits pushes to your app only. Handy for multi dapp users.
Your app gets the ping. Foreground? Background? Quit state? Cover all.
import { useEffect } from 'react'; export function usePushNotifications() { useEffect(() => { // Foreground const unsubForeground = messaging().onMessage(async remoteMessage => { console.log('Foreground:', remoteMessage); handleDialectNotification(remoteMessage); }); // Background open const unsubBackground = messaging().onNotificationOpenedApp(remoteMessage => { handleNotificationAction(remoteMessage); }); // Quit state messaging().getInitialNotification().then(remoteMessage => { if (remoteMessage) handleNotificationAction(remoteMessage); }); return () => { unsubForeground(); unsubBackground(); }; }, []);
} function handleDialectNotification(remoteMessage) { const { notification, data } = remoteMessage; // Title, body, image here. Update your inbox UI. console.log('Title:', notification?.title); console.log('Data:', data); // Maybe vibrate or show toast.
} function handleNotificationAction(remoteMessage) { const { data } = remoteMessage; if (data?.action === 'open_trade') { // Navigate: navigate('Trade', { tradeId: data.tradeId }); }
} Short ones first. Data has action, tradeId, whatever you pack in. Deep link to screens. I usually add a badge counter too.
Dialect sends rich stuff:
{ "notification": { "title": "Trade Executed! 📈", "body": "Your buy order for 10 SOL at $142.50 filled.", "image": "https://your image.png" }, "data": { "action": "open_trade", "tradeId": "abc123", "amount": "10", "price": "142.50", "appId": "your app", "notificationId": "uuid here" }
} Customize title/body server side. Image? Grabs attention. Data survives for actions.
Wanna filter? Pass appId on subscribe. Users get only your pushes. Or go universal-mix with other apps' alerts in one inbox.
Now flip it. From your server (Node, whatever), send to wallet addresses. Add 'PUSH' to channels.
const notificationData = { recipient: { type: 'subscriber', walletAddress: 'UserWalletHere' }, channels: ['PUSH', 'INAPP'], // Push + in app message: { title: 'Liquidation Alert ⚠️', body: 'Margin call in 5 mins-act now!' }, push: { playNotificationSound: true, showNotification: true }, data: { action: 'manageposition', positionId: 'xyz789' }
}; // POST to Dialect send endpoint with your auth
fetch('https://alerts api.dial.to/v1/send', { method: 'POST', headers: { / your keys / }, body: JSON.stringify(notificationData)
});
Burst mode for emergencies-handles spikes, like 100k alerts in minutes. Priority queue: high for liqs, low for newsletters.
Push not firing? Checklist time.
| Issue | Why? | Fix |
|---|---|---|
| No notifications | Bad FCM token or unsubbed | Re fetch token, check dashboard subs. Permissions? |
| Auth errors (401) | Expired JWT or wrong client | Refresh JWT via wallet sig. Copy from dashboard. |
| iOS silent | APNs cert missing | Upload to Firebase console. Rebuild app. |
| Android no sound | Channel not created | Add notification channels in manifest. |
| Duplicates | No deviceId | Add unique UUID per device. |
Honestly, 90% is perms or tokens. Log everything. Test on physical device-simulators suck for push.
Topics and channels: Users sub to 'trades' or 'gov'-only ping those. Respects prefs.
messaging().onTokenRefresh(newToken => subscribe again).Scale? They've done 100M+ alerts to 1M wallets. Zero downtime. I pushed 10k test alerts-flawless.
Multi chain? Works beyond Solana too, but you're here for Solana apps.
Don't spam. Ask perms after value: "Connect wallet to get trade alerts?" Easy opt out button. Explain: "Get pings for fills, liqs-never miss again."
In my experience, conversion jumps 30% with good UX. Handle denials: "No prob, enable in settings anytime."
Monitor: Dashboard shows delivery stats. A/B test titles. Short bodies win.
Say you're building a perp DEX. Watch for fills via RPC websocket.
Gas? ~0.000005 SOL per tx check. Cheap. For off chain, use Dialect monitoring-no code RPC.
React Native full flow? Wallet connect → JWT → FCM → subscribe → handle. 15 mins if you copy my code.
Auto handle:
messaging().onTokenRefresh(async newToken => { await unsubscribeToDialectPush(oldToken, ..); await subscribeToDialectPush(newToken, ..);
}); Prevents ghosts.
History fetch: GET /v1/notifications?wallet=addr. Paginate for inbox.
Topics: Users sub 'price alerts'-send to topic.
Dashboard test: Send no code first. Verify flow.
Apps using it? Backpack, Jupiter, Meteora. Join 'em. Your users will thank you-staying ahead in Solana means instant info.
Hit snags? Their docs are gold, or ping Discord. I did-super responsive. Go build.