Create a Connection (Follow / Unfollow an Address)
Here, we will demonstrate how you can help the users of your DApp follow each other by embedding a simple follow button to Scaffold-ETH.
Write a Get Connections Query
Before allowing users to follow another user, we first check whether such a connection already exists. You can retrieve the connection status between two addresses (whether A follows/is followed by B) using our "Connections" GraphQL query. For more details and all fields retrievable from CyberConnect Connections API, please read Connections.
Go to packages\react-app\src\queries\cyberconnect.js and add the following code in cyberconnect.js to implement a Get Follow Status query in cyberconnect.js:
packages\react-app\src\queries\cyberconnect.jsimport { GraphQLClient, gql } from "graphql-request";
// CyberConnect Protocol endpoint
const CYBERCONNECT_ENDPOINT = "https://api.cybertino.io/connect/";
// Initialize the GraphQL Client
const client = new GraphQLClient(CYBERCONNECT_ENDPOINT);
// You can add/remove fields in query
export const GET_IDENTITY = gql`
query ($address: String!, $first: Int) {
identity(address: $address) {
address
domain
avatar
followerCount
followingCount
twitter {
handle
}
followings(first: $first) {
list {
address
domain
}
}
followers(first: $first) {
list {
address
domain
}
}
}
}
`;
export const GET_FOLLOWSTATUS = gql`
query ($fromAddr: String!, $toAddrList: [String!]!) {
connections(fromAddr: $fromAddr, toAddrList: $toAddrList) {
followStatus {
isFollowing
isFollowed
}
}
}
`;
// Get Address Profile Identity
export async function getIdentity({ address }) {
if (!address) return;
const res = await client.request(GET_IDENTITY, {
address: address,
first: 5,
});
return res?.identity;
}
// Get Address Follow Status
export async function getFollowStatus({ fromAddr, toAddr }) {
if (!fromAddr) return;
if (!toAddr) return;
const res = await client.request(GET_FOLLOWSTATUS, {
fromAddr: fromAddr,
toAddrList: [toAddr],
});
return res?.connections[0]?.followStatus?.isFollowing;
}
Create a Follow Button
To create a follow button component, go to directory packages/react-app/src/components/ and create CyberConnectFollowBtn.jsx.
Add the following code to CyberConnectFollowBtn.jsx:
packages/react-app/src/components/CyberConnectFollowBtn.jsximport CyberConnect, { Env, Blockchain } from "@cyberlab/cyberconnect";
function CyberConnectFollowButton({
isFollowing,
targetAddress,
injectedProvider,
onSuccess,
}) {
const cyberConnect = new CyberConnect({
namespace: "CyberConnect-Scaffold-Eth",
env: Env.PRODUCTION,
chain: Blockchain.ETH,
provider: injectedProvider,
});
const handleOnClick = async () => {
if (!injectedProvider) {
alert("Please connect your wallet first!");
return;
}
try {
if (isFollowing) {
await cyberConnect.disconnect(targetAddress);
alert(`Success: you've unfollowed ${targetAddress}!`);
if (onSuccess) {
onSuccess();
}
} else {
await cyberConnect.connect(targetAddress);
alert(`Success: you're following ${targetAddress}!`);
if (onSuccess) {
onSuccess();
}
}
} catch (err) {
console.error(err.message);
}
};
return (
<button
onClick={handleOnClick}
style={{
background: "black",
borderRadius: "20px",
border: "1px solid white",
color: "white",
cursor: "pointer",
fontSize: "14px",
padding: "6px 20px",
}}
>
{isFollowing ? "Unfollow" : "Follow"}
</button>
);
}
export default CyberConnectFollowButton;Add the follow button to ExampleUI at packages\react-app\src\views\ExampleUI.jsx.
packages\react-app\src\views\ExampleUI.jsximport { SyncOutlined } from "@ant-design/icons";
import { utils } from "ethers";
import { Button, Card, DatePicker, Divider, Input, Progress, Slider, Spin, Switch, Row, Col } from "antd";
import React, { useEffect, useState } from "react";
import { Address, Balance, Events } from "../components";
import CyberConnectFollowButton from "../components/CyberConnectFollowBtn";
import { getIdentity, getFollowStatus } from "../queries/cyberconnect";
export default function ExampleUI({
purpose,
address,
mainnetProvider,
localProvider,
yourLocalBalance,
price,
tx,
readContracts,
writeContracts,
injectedProvider,
}) {
const [newPurpose, setNewPurpose] = useState("loading...");
const demoAddr = "cyberlab.eth";
const [identityInput, setIdentityInput] = useState(demoAddr);
const [followInput, setFollowInput] = useState(demoAddr);
const [followAddr, setFollowAddr] = useState("");
const [identity, setIdentity] = useState(null);
const [searchedIdentity, setSearchedIdentity] = useState(null);
const [isFollowing, setIsFollowing] = useState(null);
const fetchIdentity = async () => {
if (!address) return;
const res = await getIdentity({ address: address });
if (res) {
setIdentity(res);
}
};
useEffect(() => {
fetchIdentity();
}, [address]);
const searchIdentityHandler = async () => {
if (!identityInput) return;
const res = await getIdentity({ address: identityInput });
if (res) {
setSearchedIdentity(res);
}
};
const searchFollowHandler = async () => {
if (!followInput) return;
setFollowAddr(followInput);
const res = await getFollowStatus({ fromAddr: address, toAddr: followInput });
setIsFollowing(res);
};
const formatAddress = address => {
const len = address.length;
return address.substr(0, 5) + "..." + address.substring(len - 4, len);
};
return (
<div>
{/*
⚙️ Here is an example UI that displays and sets the purpose in your smart contract:
*/}
<div style={{ border: "1px solid #cccccc", padding: 16, width: 500, margin: "auto", marginTop: 64 }}>
{/* CyberConnect Profile Section */}
<div>
<h2>CyberConnect Example UI:</h2>
<Divider />
<div style={{ textAlign: "left", marginLeft: "10px" }}>
<h3>Your Profile Identity:</h3>
{identity && (
<div>
<Row>
<Col span={6}>Twitter handle:</Col>
<Col span={18}>{identity.twitter?.handle ? identity.twitter.handle : "n/a"}</Col>
</Row>
<Row>
<Col span={6}>Address:</Col>
<Col span={18}>{identity.address}</Col>
</Row>
<Row>
<Col span={6}>Domain:</Col>
<Col span={18}>{identity.domain ? identity.domain : "n/a"}</Col>
</Row>
<Row>
<Col span={6}>Followers:</Col>
<Col span={18}>{identity.followerCount}</Col>
</Row>
<Row>
<Col span={6}>Following:</Col>
<Col span={18}>{identity.followingCount}</Col>
</Row>
</div>
)}
</div>
</div>
<Divider />
<div style={{ textAlign: "left", marginLeft: "10px" }}>
<h3>Search a Profile Identity:</h3>
<Input
placeholder="Enter the address/ens you want to search.."
onChange={e => setIdentityInput(e.target.value)}
value={identityInput}
/>
<Button style={{ margin: "8px 0px" }} onClick={searchIdentityHandler}>
Submit
</Button>
{searchedIdentity && (
<div>
<Row>
<Col span={6}>Twitter handle:</Col>
<Col span={18}>{searchedIdentity.twitter?.handle ? searchedIdentity.twitter.handle : "n/a"}</Col>
</Row>
<Row>
<Col span={6}>Address:</Col>
<Col span={18}>{searchedIdentity.address}</Col>
</Row>
<Row>
<Col span={6}>Domain:</Col>
<Col span={18}>{searchedIdentity.domain ? searchedIdentity.domain : "n/a"}</Col>
</Row>
<Row>
<Col span={6}>Followers:</Col>
<Col span={18}>{searchedIdentity.followerCount}</Col>
</Row>
<Row>
<Col span={6}>Following:</Col>
<Col span={18}>{searchedIdentity.followingCount}</Col>
</Row>
<Row span={18} justify={"center"}>
<div style={{ padding: "20px" }}>
<h4>Followers</h4>
{searchedIdentity &&
searchedIdentity.followers?.list.map(user => {
return (
<div key={user.address}>
<div>{user.domain || formatAddress(user.address)}</div>
</div>
);
})}
</div>
<div style={{ padding: "20px" }}>
<h4>Followings</h4>
{searchedIdentity &&
searchedIdentity.followings?.list.map(user => {
return (
<div key={user.address}>
<div>{user.domain || formatAddress(user.address)}</div>
</div>
);
})}
</div>
</Row>
</div>
)}
</div>
<Divider />
<div style={{ textAlign: "left", marginLeft: "10px" }}>
<h3>Follow/Unfollow a Profile:</h3>
<Input
placeholder="Enter the address/ens you want to follow/unfollow.."
onChange={e => setFollowInput(e.target.value)}
value={followInput}
/>
<Button style={{ margin: "8px 0px" }} onClick={searchFollowHandler}>
Submit
</Button>
<div>
{followAddr && (
<CyberConnectFollowButton
isFollowing={isFollowing}
targetAddress={followAddr}
injectedProvider={injectedProvider}
onSuccess={() => {
fetchIdentity();
searchIdentityHandler();
searchFollowHandler();
}}
/>
)}
</div>
</div>
<Divider />
In order to make sure that your user use their logged-in account to follow another address, add injectedProvider={injectedProvider}
to packages\react-app\src\App.jsx:
<Route path="/exampleui">
<ExampleUI
address={address}
userSigner={userSigner}
mainnetProvider={mainnetProvider}
localProvider={localProvider}
yourLocalBalance={yourLocalBalance}
price={price}
tx={tx}
writeContracts={writeContracts}
readContracts={readContracts}
purpose={purpose}
injectedProvider={injectedProvider}
/>
</Route>
If successful, ExampleUI should display the following:
Play around with the connect functionality by inputting different addresses or ENS domains. Feel free to check out several popular users here. When you follow a new user, you’ll find your followings count and list to be updated as well.
That’s it! You’ve done a simple integration of CyberConnect to Scaffold-ETH. Check out the finished Starter Project here. We look forward to seeing more DApps built on top of the CyberConnect Protocol!