Integrating Opt-in/Subscribe to channel in dApp
If a channel exists with EPNS, then from any dApp a user can opt in to that channel without having to go to the EPNS dApp with this feature
Your dApp can use these abstractions to trigger opt in functionality from your UI to the EPNS backend. We also provide a UI component to show the users available EPNS apps if they are interested to checkout directly in the EPNS ecosystem of apps their notifications.

NOTE: In order to implement signing, we take advantage of EIP-712, more details on the signer parameter can be found here​

We will use below channels methods from the front end SDK to implement OPT-IN/OUT functionality in your app.

import { channels } from "@epnsproject/frontend-sdk-staging";
​
/* OPT IN a channel */
channels.optIn(
signer,
channelAddress,
chainId,
userAccount,
{
onSuccess: () => {} // do something after a successfull subscription, like bring up a modal or a notification
}
);
​
/* OPT OUT a channel */
channels.optOut(
signer,
channelAddress,
chainId,
userAccount,
{
onSuccess: () => {} // do something after a successfull unsubscription, like bring up a modal or a notification
}
);
Let's see how to use these methods in a sample Create React App.
Please make sure you have these software installed
  • NodeJS v14.x
  • NPM v6.x
  • YARN 1.x (we will be using Yarn 1 for our demo)

1. Setup Create React App

npx create-react-app optin-demo
cd optin-demo
yarn start
You can follow the detailed steps and related details from the official link CREATE REACT APP (https://reactjs.org/docs/create-a-new-react-app.html#create-react-app)

2. Add Web3 dependencies to the demo project

yarn add ethers
yarn add @web3-react/core
yarn add web3-react/injected-connector
yarn add @epnsproject/frontend-sdk-staging
Note- As of now the frontend-sdk has a peerDependency on styled-components so if your app is not using this, you must install this to avoid issues.
yarn add styled-components

3. Set up Web3ReactProvider

// inside your index.js file replace with this snippet
​
import React from "react";
import ReactDOM from "react-dom";
import {ethers} from "ethers";
import { Web3ReactProvider } from "@web3-react/core";
import "./index.css";
import App from "./App";
​
function getLibrary(provider) {
// this will vary according to whether you use e.g. ethers or web3.js
const gottenProvider = new ethers.providers.Web3Provider(provider, "any");
return gottenProvider;
}
​
ReactDOM.render(
<React.StrictMode>
<Web3ReactProvider getLibrary={getLibrary}>
<App />
</Web3ReactProvider>
</React.StrictMode>,
document.getElementById("root")
);
​
​

4. Create a connect button for connecting the App to Metamask

// create a file called connect.js in the same level as that of App.js
​
import { useEffect } from "react"
import { InjectedConnector } from '@web3-react/injected-connector'
import { useWeb3React } from "@web3-react/core"
​
​
const injected = new InjectedConnector({
supportedChainIds: [1, 3, 4, 5, 42],
})
​
const wrapperStyles = {
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
margin: 20
};
​
​
const ConnectButton = () => {
const { active, account, activate, deactivate } = useWeb3React()
​
​
async function connect() {
try {
await activate(injected)
localStorage.setItem('isWalletConnected', true)
} catch (ex) {
console.log(ex)
}
}
​
async function disconnect() {
try {
deactivate()
localStorage.setItem('isWalletConnected', false)
} catch (ex) {
console.log(ex)
}
}
​
useEffect(() => {
const connectWalletOnPageLoad = async () => {
if (localStorage?.getItem('isWalletConnected') === 'true') {
try {
await activate(injected)
localStorage.setItem('isWalletConnected', true)
} catch (ex) {
console.log(ex)
}
}
}
connectWalletOnPageLoad()
}, [activate]);
​
return (
<div style={wrapperStyles}>
<button onClick={connect}>Connect to MetaMask</button>
{active ? <p>Connected with <b>{account}</b></p> : <p>Not connected</p>}
<button onClick={disconnect}>Disconnect</button>
</div>
)
};
​
export default ConnectButton;
​
​
The above code is not super critical to understand how to use the OPT IN functionality. Any dApp will have some sort of wallet connection boilerplate code.
We have shown here a sample code using web3-react how to connect to Metamask, but you can connect to any wallet of your choice using the same library (https://github.com/NoahZinsmeister/web3-react#packages)
​

5. Add the channel methods in App.js to Opt In/Out

// inside your App.js replace the code with below snippet
​
​
import React, { useState, useEffect } from 'react'
import { useWeb3React } from "@web3-react/core";
import { channels } from "@epnsproject/frontend-sdk-staging";
import ConnectButton from './connect';
​
// EPNS's API base url
const BASE_URL = "https://backend-kovan.epns.io/apis";
// const BASE_URL = "https://backend-prod.epns.io/apis"; // prod
​
// You have to provide your "channel" address here
const CHANNEL_ADDRESS = "0x94c3016ef3e503774630fC71F59B8Da9f7D470B7"; // sample
​
​
/* Your App */
function App() {
const { library, active, account, chainId } = useWeb3React();
const [isSubscribed, setSubscribeStatus] = useState(false);
const [channel, setChannel] = useState();
​
const onClickHandler = (e) => {
e.preventDefault();
if (!isSubscribed) {
channels.optIn(
library.getSigner(account),
CHANNEL_ADDRESS,
chainId,
account,
{
onSuccess: () => {
console.log("channel opted in");
setSubscribeStatus(true);
}
}
);
} else {
channels.optOut(
library.getSigner(account),
CHANNEL_ADDRESS,
chainId,
account,
{
onSuccess: () => {
console.log("channel opted out");
setSubscribeStatus(false);
}
}
);
}
};
useEffect(() => {
if (!account) return;
​
// on page load, fetch channel details
channels.getChannelByAddress(CHANNEL_ADDRESS, BASE_URL).then((channelData) => {
setChannel(channelData);
});
// fetch if user is subscribed to channel
channels.isUserSubscribed(account, CHANNEL_ADDRESS).then((status) => {
setSubscribeStatus(status);
});
}, [account]);
return (
<div>
<ConnectButton />
​
{active ? (
channel ? (
<div
style={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
margin: 20
}}>
<p><b>Channel Address: </b>{CHANNEL_ADDRESS}</p>
{
isSubscribed ? (
<button onClick={onClickHandler}>OPT-OUT</button>
) : (
<button onClick={onClickHandler}>OPT-IN</button>
)
}
</div>
) : (
<div>No Channel Details</div>
)
) : null}
</div>
);
}
​
export default App;
​
​
​
​
​
In the above code,
  • (line 10) BASE_URL = the API BASE url needed to call the EPNS api's. Here we have given a KOVAN or staging API, for MAIN NET use the PROD url given below it.
  • (line 54) We first fetch the channel details using the channels APIs we get status of the subscription - by calling isUserSubscribed() channel details - by calling getChannelByAddress()
  • If and when the Metamask wallet is connected we show the OPT-IN or OPT-OUT buttons for the channel address you have provided based on the data EPNS has about it.
  • (line 83) If the channel is not OPTED-IN already , you will see OPT-IN as the button text or OPT-OUT accordingly
  • (line 23) on click of either buttons we call channels.optIn or channels.optOut
  • (line 33, 46) we also have an onSuccess callback that you can leverage to call any custom code once opt-in or opt-out is successful
UI flow
When the app loads initially and Metamask is not connected
After we click on the connect to Metamask button and connect, note the button below Channel address shows "OPT-IN" which means this channel is not opted in yet.
When we click on the OPT-IN button it will show up MetaMask extension and ask you to sign the transaction. Click on sign to Opt-In. After that the button should show "OPT-OUT" in the next screen.
Now you can again click on the OPT-OUT button to opt-out of the channel.
UX ADD ON: OnSubscribeModal
We can also show an EPNS customized modal window when you opt-in to a channel, which will basically show the links of other EPNS mediums by which you can view your notifications
// Just import OnSubscribeModal and use it in optIn() call
​
import React, { useState, useEffect } from 'react'
import { useWeb3React } from "@web3-react/core";
import { channels, OnSubscribeModal } from "@epnsproject/frontend-sdk-staging";
import ConnectButton from './connect';
​
// EPNS's API base url
const BASE_URL = "https://backend-kovan.epns.io/apis";
​
// You have to provide your "channel" address here
const CHANNEL_ADDRESS = "0x94c3016ef3e503774630fC71F59B8Da9f7D470B7"; // sample
​
​
/* Your App */
function App() {
const { library, active, account, chainId } = useWeb3React();
const [isSubscribed, setSubscribeStatus] = useState(false);
const [channel, setChannel] = useState();
const [showModal, setShowModal] = useState();
​
const onClickHandler = (e) => {
e.preventDefault();
if (!isSubscribed) {
channels.optIn(
library.getSigner(account),
CHANNEL_ADDRESS,
chainId,
account,
{
onSuccess: () => {
console.log("channel opted in");
setShowModal(true);
setSubscribeStatus(true);
}
}
);
} else {
channels.optOut(
library.getSigner(account),
CHANNEL_ADDRESS,
chainId,
account,
{
onSuccess: () => {
console.log("channel opted out");
setSubscribeStatus(false);
}
}
);
}
};
useEffect(() => {
if (!account) return;
​
// on page load, fetch channel details
channels.getChannelByAddress(CHANNEL_ADDRESS, BASE_URL).then((channelData) => {
setChannel(channelData);
});
// fetch if user is subscribed to channel
channels.isUserSubscribed(account, CHANNEL_ADDRESS).then((status) => {
setSubscribeStatus(status);
});
}, [account]);
return (
<div>
<ConnectButton />
​
{showModal && <OnSubscribeModal onClose={() => setShowModal(false)} />}
​
{active ? (
channel ? (
<div
style={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
margin: 20
}}>
<p><b>Channel Address: </b>{CHANNEL_ADDRESS}</p>
{
isSubscribed ? (
<button onClick={onClickHandler}>OPT-OUT</button>
) : (
<button onClick={onClickHandler}>OPT-IN</button>
)
}
</div>
) : (
<div>No Channel Details</div>
)
) : null}
</div>
);
}
​
export default App;
Here everything is same except the fact that when you opt-in you will see the below modal window
This modal window has links to all EPNS mediums on which you can view your notifications.
Copy link
Outline