yarn add [email protected] ethers@5 @unlock-protocol/paywall @unlock-protocol/networks @unlock-protocol/contracts
This is not Unlock specific:
Add a constant to constants.ts
for the network we will use.
export const NETWORK = 5 // also called chain id
import { WagmiConfig, createClient} from 'wagmi'
import { AppProps } from 'next/app'
import '../styles/index.css'
import { ethers } from 'ethers'
import { NETWORK } from '../lib/constants'
// Create a WAGMI client
const wagmiClient = createClient({
// Unlock provides some RPC providers but you can use your own!
provider: new ethers.providers.JsonRpcProvider(`https://rpc.unlock-protocol.com/${NETWORK}`, NETWORK),
})
export default function MyApp({ Component, pageProps }: AppProps) {
return <WagmiConfig client={wagmiClient}>
<Component {...pageProps} />
</WagmiConfig>
}
This component is in fact pretty simple:
- It wraps any component that needs to be token-gated
- It checks the "status" of the user (has a membership or not), if the user is connected
- If the user is not connected, it renders a subcomponent to prompt the user to connect
- If the user is connected and they do NOT have a membership NFT, it renders a
Checkout
sub-component that promps them to purchase a membership! - If the user is connected AND they have a membership, it renders he children!
import { useAccount, useConnect, useContractRead } from "wagmi"
import {InjectedConnector} from "wagmi/connectors/injected"
import { PublicLockV13 } from "@unlock-protocol/contracts"
import { LOCK, NETWORK } from "../lib/constants"
import { ethers } from "ethers"
import { Paywall } from "@unlock-protocol/paywall"
import networks from '@unlock-protocol/networks'
export const TokenGate = ({children}) => {
const {isConnected, address} = useAccount()
const {data: isMember, isError, isLoading} = useContractRead({
address: LOCK,
abi: PublicLockV13.abi,
functionName: 'balanceOf',
chainId: NETWORK,
enabled: !!address,
args: [address],
watch: true,
select: (data: ethers.BigNumber) => {
return data.gt(0)
}
})
if (isLoading) {
return <div>Loading...</div>
}
if (isError) {
return <div>There was an error checking your membership status. Please reload the page!</div>
}
// User not connected
if (!isConnected) {
return <Connect />
}
// User does not have membership
if (!isMember) {
return <Checkout />
}
// All good: user is connected and they have a membership!
return children
}
/**
* Connect subcomponent!
* @returns
*/
const Connect = () => {
const {connect} = useConnect({
connector: new InjectedConnector()
})
return <section>
<p className="mb-4">To view this post you need to be be a member!</p>
<button onClick={() => connect()} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Sign-In
</button>
</section>
}
/**
* Checkout subcomponent!
* @returns
*/
const Checkout = () => {
const {connector} = useAccount()
const checkout = () => {
const paywall = new Paywall(networks)
const provider = connector!.provider
paywall.connect(provider)
paywall.loadCheckoutModal({
locks: {
[LOCK]: {
network: NETWORK,
}
},
pessimistic: true,
})
}
return (
<section>
<p className="mb-4">You currently don't have a membership... </p>
<button onClick={() => checkout()} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Purchase one now!
</button>
</section>
)
}
We just replace post-body.tsx
with this:
import { TokenGate } from './token-gate'
import markdownStyles from './markdown-styles.module.css'
type Props = {
content: string
}
const PostBody = ({ content }: Props) => {
return (
<div className="max-w-2xl mx-auto">
<TokenGate>
<div
className={markdownStyles['markdown']}
dangerouslySetInnerHTML={{ __html: content }}
/>
</TokenGate>
</div>
)
}
export default PostBody
Please jump in Unlock's Discord if you have any question!