👤 LoginWithPaper


  1. Create an OAuth Client to get a Client ID in Dashboard: Developers.
  2. Render the LoginWithPaper component.
  3. Pass the Client ID to the PaperSDKProvider.
  4. When a user signs in, the onSuccess callback is called with a code.
  5. Exchange the code for a userToken via the POST /v1/oauth/token API.
  6. Query the NFTs in the user's wallet with the userToken to access NFT-gated content on your app.

Use Cases

  • You want to provide a web3 authentication flow for users without a wallet. This flow can complement or replace your existing web3 authentication (e.g. Metamask, WalletConnect).
  • You require all authenticated users on your app to have a unique and verifiable wallet address that can hold NFTs.
  • You need to verify ownership of tokens in a user's wallet to grant access to NFT-gated content.




This SDK is available in ReactJS.

Integration (ReactJS)

In Dashboard: Developers, create an OAuth client to get a Client ID, a public identifier that associates logins with your account.

On your frontend, render the LoginWithPaper component. In the onSuccess callback, call your backend to exchange the expiring code for a permanent user token.

<PaperSDKProvider clientId='MY_CLIENT_ID' chainName='Polygon'>
  <LoginWithPaper onSuccess={async (code: string) => {
      await fetch('/api/exchange-user-token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code }),

On your backend, exchange the expiring code for a permanent user token.

const resp = await fetch('https://paper.xyz/api/v1/oauth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: 'Bearer MY_API_SECRET_KEY',
  body: JSON.stringify({
    code: req.body.code,
    clientId: 'MY_CLIENT_ID',
if (resp.status === 200) {
  const { userToken } = await resp.json();
  // ...`userToken` is a permanent token used to retrieve user/wallet details.

With the userToken, you can query NFTs in the user's wallet or retrieve user details.

const resp = await fetch('https://paper.xyz/api/v1/nft/token-balance', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: 'Bearer MY_API_SECRET_KEY',
  body: JSON.stringify({
    clientId: 'MY_CLIENT_ID',
    tokenType: 'erc721',
    chainName: 'Polygon',
    contractAddress: '0x...',
if (resp.status === 200) {
  const nftsHeld = await resp.json();
  console.log(`You are holding ${nftsHeld.length} of my NFT.`);

User flow

The user initially sees a login button.

`LoginWithPaper` button

When clicked a popup is opened to sign in.

`LoginWithPaper` Modal


Callback FunctionTypeDescription
onSuccess *(code: string) => voidA callback called when the user successfully verifies their email address and a wallet is created for them.
onError(error: PaperSDKError) => voidA callback called if there is an error sending the email or creating a wallet address.
onClose() => voidA callback called when the login popup is closed.
redirectUrlstringA URL to redirect the user once they click the link in their email.

If not provided, the email link prompts the user to close the tab and return to your app.


If no child element is provided, a default button is rendered.

Provide your own button by passing a child element into the React component.

<LoginWithPaper onSuccess={onSuccessLogin}>
    <button class='my-button'>Sign in with email</button>