Web3-Native Exclusive Content Platform built with CyberConnect and Lit Protocol
Background
In our ‘Getting Started with CyberConnect’ tutorial,' we introduced the CyberConnect protocol as a Web3 social network infrastructure that empowers developers in bootstrapping their decentralized social applications while enabling users to manage their self-sovereign social data. In the tutorial, we demonstrated how CyberConnect’s data infrastructure and smart contract protocol can power a decentralized Twitter-like social content platform, a demo of which can be found here. Note that all content data on this demo app is stored in a plaintext format on IPFS. While this approach is relevant for most use cases, there may be many scenarios where there is a need for content data to be encrypted with limited access. For example:
A content creator/thought leader who publishes high-quality research-driven articles for their paid subscribers.
An influencer who just wants key people in their inner circle (followers or mutually followed people) to access their posts.
Although the CyberConnect protocol cannot provide such functionality by itself, these features can be implemented by integrating decentralized access control solutions like Lit Protocol, thanks to the powerful composability and interoperability of Web3 technologies. In this article, we will demonstrate how easy it is to build a Web3-native exclusive content platform with CyberConnect and Lit Protocol.
CyberConnect: Recap
CyberConnect is a decentralized social network protocol that helps developers build and bootstrap their Web3 social applications. Its on-chain protocol represents key elements of social data in the format of ERC-721 tokens with the flexibility of customizing them as non-transferable SBTs (soulbound tokens). There are three core concepts that make up the CyberConnect protocol, namely ProfileNFT, SubscriberNFT, and EssenceNFT. When user A follows user B from their on-chain profile, user A mints user B’s SubscriberNFT. Further, when user A creates a post, they may choose to use EssenceNFT to represent the post and implement on-chain monetization.
The protocol also offers a rich set of customizable and extensible middleware smart contracts for ProfileNFT owners to choose when they want to issue their SubscriberNFT or EssenceNFT. The middleware design enables users to implement Web3-native use cases like “only BAYC holders can subscribe” or “pay 1 ETH to collect my 500 limited edition posts.” To learn more, visit CyberConnect Developer Center or dive deep into our previous tutorials.
Lit Protocol: Overview
Lit Protocol is a decentralized access control protocol that provides a rich set of tools and infrastructures for developers to encrypt decentralized data and manage its access conditions. With Lit, developers can freely choose from a variety of decentralized data stores like IPFS, Arweave, and Ceramic to enable powerful new use cases that require robust and trustless access control features. Lit protocol has its own novel abstraction and cryptographic primitives to deal with data encryption and decryption as it also supports both static and dynamic data. In terms of programmable access conditions, it offers extensible shared models and SDKs for creating and managing access control. It currently supports various blockchains such as Ethereum, Solana, and Cosmos. To learn more about Lit Protocol, visit their Developer Center.
Building an App using CyberConnect & Lit Protocol
This tutorial will build on top of the previous content demo app. The core additional features we are going to implement are encrypted posts that only creators’ subscribers have access to. The end result will look like the following, and the full dome app can be found here.
Prerequisites
For this tutorial, you will need the following:
Basic understanding of React.js, GraphQL, and Web3
Have Node.js, NPM, and MetaMask Chrome extensions installed on your machine
Quick Setup
- Clone the GitHub repo
git clone https://github.com/cyberconnecthq/cc-lit-demo.git
- Install & run locally
npm install
npm run dev
Create an Encrypted Post
Remember we need to construct a metadata object when creating a post in our previous tutorial. This metadata object is a CyberConnect-defined standard template that complies with OpenSea Metadata Standards to ensure the NFT can be displayed properly on OpenSea and other marketplaces.
const metadata: IEssenceMetadata = {
metadata_id: uuidv4(),
app_id: "cyberconnect",
content:
JSON.stringify({
contentHash: encryptedContentHash,
encryptedSymmetricKey: encryptedContent.encryptedSymmetricKey,
}),
// other fields
...,
};
The content
field is the place where plaintext data goes. We will upload the data to IPFS and utilize Lit Protocol SDKs for encryption.
const encryptedContent = await encryptWithLit(content);
const encryptWithLit = async (data: any) => {
const client = new LitJsSdk.LitNodeClient();
await client.connect();
const chain = "bscTestnet";
const onlySubscriberCondition = [
{
conditionType: "evmContract",
permanent: false,
contractAddress: "0x0561d367868B2d8E405B1241Ba568C40aB8fD2c8",
functionName: "isSubscribedByMe",
functionParams: [String(primaryProfile?.profileID), ":userAddress"],
functionAbi: {
inputs: [
{
internalType: "uint256",
name: "profileId",
type: "uint256",
},
{
internalType: "address",
name: "me",
type: "address",
},
],
name: "isSubscribedByMe",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "view",
type: "function",
},
chain: chain,
returnValueTest: {
key: "",
comparator: "=",
value: "true",
},
},
];
const authSig = await LitJsSdk.checkAndSignAuthMessage({
chain: chain,
});
const { encryptedString, symmetricKey } = await LitJsSdk.encryptString(
data
);
const encryptedSymmetricKey = await client.saveEncryptionKey({
onlySubscriberCondition,
symmetricKey,
authSig,
chain: chain,
});
return {
encryptedString,
encryptedSymmetricKey: LitJsSdk.uint8arrayToString(
encryptedSymmetricKey,
"base16"
),
};
};
const encryptedContentHash = await pinFileToIPFS(
encryptedContent.encryptedString
);
After the content is successfully uploaded to IPFS, we can check the data and ensure it’s in the expected format. For example, https://gateway.pinata.cloud/ipfs/QmRYEYMbMW2mLx9adjtLgp3BX84zHRmVrpP6AAC71wQnSt will return the following example.
{
"metadata_id": "bd83150f-2658-4fee-9c17-4b32625e9f3c",
"app_id": "cyberconnect",
"content": "{\"contentHash\":{\"ipfshash\":\"bafkreibmw4xpjzn2fxfomc245fra734wvhj3jq6hz4aeqcwihqcm2cib4i\"},\"encryptedSymmetricKey\":\"06810ac07af6c6c46f020cfaeba03fc2e2cae50732e90c0c0382f1fb437a8cbadaeab8bc7301db6e1bde8c807caf2916e109a1c7766748a11946d7f7d90a6752b3db07789e094ba7a7eed65cc9466e8857d4ae9dcc2408e6d77b8379f58a72816dca8f422fc26ba3bf6cb992bd8e1792bd11ac006647ed547d04afeaf774428400000000000000201b461b86cf2520cb9096afd8b680a5ff270713201e5658dce946831ec725ac252a2e447a26f80118ba5572d05b706ca5\"}",
"image": "https://images.unsplash.com/photo-1468581264429-2548ef9eb732?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80",
"name": "The Old Man and the Sea",
"description": "The Old Man and the Sea is a novella written by the American author Ernest Hemingway in 1951 in Cayo Blanco (Cuba), and published in 1952.",
// other fields
...,
}
It’s worth mentioning that the encryption and decryption process happens on the client side only. There is no centralized backend to hold such an encryption key to get unauthorized access to private content.
Define Access Condition
You may have observed that the previous step is only about data encryption, but where can we define the access rules like “only my subscribers can see my content” or “only people who paid ETH to collect my article can see my content”? The onlySubscriberCondition
in the above example is how we will implement our desired rules:
The user can set various access conditions, such as the accessing user:
Must be a member of a DAO
Holds an NFT from a specific collection
Holds at least 0.1 ETH
Owns a specific wallet address
Further, the user may implement any condition that is the result of any smart control function call as well as uses boolean operations (And + Or) for any of the above requirements. More detailed documentation and supported blockchains can be found in Lit Protocol’s Developer Center.
In our case, we want to set the access condition to the user “holding a profile’s issued SubscriberNFT”. The tricky part, however, is that the SubscriberNFT is deployed from a factory in a lazy way (for gas efficiency) in the CyberConnect protocol and we cannot directly make a smart contract call to get the NFT holding status since the dynamically deployed contract address is unknown when defining that access condition.
Instead, CyberConnect deploys a customized smart contract called RelationshipChecker
to check the on-chain relationship which can be used by everyone directly.
function isSubscribedByMe(uint256 profileId, address me)
external
view
returns (bool)
{
address subNFTAddr = IProfileNFT(_namespace).getSubscribeNFT(profileId);
if (subNFTAddr == address(0)) {
return false;
}
return IERC721(subNFTAddr).balanceOf(me) > 0;
}
The full code and abi can be found here. It basically states that only the current user address holding a SubscriberNFT from a certain ProfileID can pass this check. With this on-chain relationship checker, we can program our access conditions now as above in onlySubscriberCondition
.
Decrypted Post and Display
Now, we need to write some code to decrypt the private data assuming the user meets the access condition we defined above.
const decryptWithLit = async (
encryptedSymmetricKey: string,
blob: Blob,
profileId: string,
address: string
) => {
const client = new LitJsSdk.LitNodeClient({ alertWhenUnauthorized: false });
await client.connect();
const chain = "bscTestnet";
const authSig = await LitJsSdk.checkAndSignAuthMessage({ chain: chain });
const onlySubscriberCondition = [
{
conditionType: "evmContract",
permanent: false,
contractAddress: "0x0561d367868B2d8E405B1241Ba568C40aB8fD2c8",
functionName: "isSubscribedByMe",
functionParams: [String(profileId), ":userAddress"],
functionAbi: {
inputs: [
{
internalType: "uint256",
name: "profileId",
type: "uint256",
},
{
internalType: "address",
name: "me",
type: "address",
},
],
name: "isSubscribedByMe",
outputs: [
{
internalType: "bool",
name: "",
type: "bool",
},
],
stateMutability: "view",
type: "function",
},
chain: chain,
returnValueTest: {
key: "",
comparator: "=",
value: "true",
},
},
];
const symmetricKey = await client.getEncryptionKey({
onlySubscriberCondition,
toDecrypt: encryptedSymmetricKey,
chain: chain,
authSig,
});
const decryptedString = await LitJsSdk.decryptString(blob, symmetricKey);
return decryptedString;
};
We simply just need to re-construct the symmetricKey
using Lit SDK and use that key to decrypt the final content.
Conclusion
Congratulations! You’ve successfully created a Web3-native exclusive content app using CyberConnect and Lit Protocol. We hope it provides a good understanding of the CyberConnect Protocol and how it can be integrated with Lit Protocol.
We strongly recommend the following on your journey in building with CyberConnect:
Learn more about the CyberConnect Recommendation Engine
For lots of interesting data like recommended Web2 social profiles based on trading history, or recommended tokens the address should purchase based on modeling trading behavior & holdings relative to other addresses (i.e. collaborative filtering model).
Join our Discord if you have any questions!