zkLogin Wallets (Next.js)

How to build your first zkLogin app

Overview

This tutorial will help you build your first zkLogin app - we've created a starter template that makes it very easy! It uses the Shinami Next.js zkLogin SDK to make requests to our zkLogin wallet API.

The app also uses Shinami Gas Station to sponsor transactions for the zkLogin wallets created. Sponsoring your users' transactions removes friction like completing KYC checks to buy SUI for gas. This is especially useful for zkLogin wallets since when you create one the user will not have any SUI in it (and won't even know their wallet address unless you tell them).

Tutorial Steps

The tutorial requires a Shinami account. If you don't have one, get started on our free tier today (use referral code "zkLogin").

1. Create a Shinami Gas Station fund on Testnet and add free SUI to it

See our product FAQ for how to:

  1. Create a Gas Station fund on Testnet
  2. Find the fund's deposit address
  3. Add free Testnet SUI to the fund

2. Create two Shinami API access keys

You'll need two Shinami API access keys: a Testnet Node Service key, and a Testnet key with rights to all services. For security purposes, any access keys with Gas Station or Wallet Services rights should be used only on your app backend to prevent exposing them.

See our Authentication and API Keys guide for guidance on how to make both keys:

  1. a key with Node Service only rights
  2. a key with rights to all services (that links to the Gas Station fund you created in step 1)

3. Create an OAuth client app for Twitch, Google, and/or Facebook

Below, we integrate with Twitch, Facebook, and Google, but you're only required to integrate with one. See Mysten's zkLogin doc for information on how to set up a developer account and an OAuth login client app with any of the OpenID Connect providers currently supported.

The redirect URL to set up in the Twitch developer console for your app is http://localhost:3000/auth/twitch. You'll also need to copy the Client ID value that Twitch assigns your app to use as an environmental variable in the next step.

For your app in the Facebook developer console, http://localhost:3000 is automatically allowed as a redirect URI and doesn't need to be explicitly set. You'll also need to copy the App ID value Facebook assigns your app to use as an environmental variable in the next step.

For your Google app , you'll need to set the redirect URI to: http://localhost:3000/auth/google.

You'll also need to copy the App ID value that Google assigns your app to use as an environmental variable in the next step.

4. Download and configure the sample project

Download

This project is located on github. You can use npm, Yarn, or pnpm to bootstrap the example:

npx create-next-app --example https://github.com/shinamicorp/shinami-typescript-sdk/tree/main/examples/nextjs-zklogin my-zklogin-app

yarn create next-app --example https://github.com/shinamicorp/shinami-typescript-sdk/tree/main/examples/nextjs-zklogin my-zklogin-app

pnpm create next-app --example https://github.com/shinamicorp/shinami-typescript-sdk/tree/main/examples/nextjs-zklogin my-zklogin-app

Configure

Next, you need to set the values for key environmental variables for the project. These should be in a .env.local file which should not committed to git since it contains sensitive information like API access key values. In the root directory of the project, run cp .env .env.local on the command line to make a .env.local file that's a copy of the .env file. Below, we show how to fill in values for the new .env.local file.

You'll need to make four changes to the file:

  1. For each OpenID Connect provider you created a client app with, uncomment the associated NEXT_PUBLIC_*_CLIENT_ID variable and set the value to the associated client/app ID. In my examples above, the partial ID values are 2bc0920 for Twitch, 141521 for Facebook, and 766995 for Google. Use your own client/app id values, which will be longer.
  2. Uncomment the IRON_SESSION_SECRET variable and generate a session secret, e.g. with openssl rand -hex 32 on the command line. Example output is bdd51e3b307c7cdae48c3dd2ce83f5e7a0b6db10fdeea0b4d5990f857c156dd7 (but don't share your secrets!)
  3. Uncomment the SHINAMI_SUPER_ACCESS_KEY variable and set it to the Testnet access key you created with rights to Node Service, Wallet Services, and Gas Station. Let's imagine it's sui_testnet_abc123. This is too short for a real key. Also, do not expose your keys! This key will only be used on the backend, as should be done with any Shinami API access key with Gas Station or Wallet Service rights.
  4. Uncomment the NEXT_PUBLIC_SHINAMI_NODE_ACCESS_KEY variable and set it to the Node service Testnet access key you created. Let's imagine it's sui_testnet_def456. Again: too short for a real key and keep your keys secret. This one has PUBLIC in the name because it will be exposed on the Frontend.

Open the file and fill in those values:

# OAuth application configs. Configure the ones you want to support. They'll serve as both the auth
# mechanism for this app and transaction signing mechanism (zkLogin). Note that if you have multiple
# providers configured, the same person will be considered a different user for each provider they
# log in with (with different zkLogin wallets), even if they may share the same email.
#
# Refer to this link for instructions on setting up these OAuth applications:
# https://docs.sui.io/concepts/cryptography/zklogin#configure-a-developer-account-with-openid-provider
#
NEXT_PUBLIC_GOOGLE_CLIENT_ID='766995'
NEXT_PUBLIC_FACEBOOK_CLIENT_ID='141521'
NEXT_PUBLIC_TWITCH_CLIENT_ID='2bc0920'

# Example command to generate random secrets:
#   openssl rand -hex 32
#
IRON_SESSION_SECRET='bdd51e3b307c7cdae48c3dd2ce83f5e7a0b6db10fdeea0b4d5990f857c156dd7'

# Obtain your Shinami access keys from https://app.shinami.com.
# The example code uses Shinami node, gas station, and wallet services by default to provide the
# most seamless experience. However, you can also choose to use other providers by making some small
# adjustments.
#
SHINAMI_SUPER_ACCESS_KEY='sui_testnet_abc123'
NEXT_PUBLIC_SHINAMI_NODE_ACCESS_KEY='sui_testnet_def456'

# 'devnet', 'testnet', or 'mainnet'
NEXT_PUBLIC_SUI_NETWORK='testnet'

# This example package is deployed on testnet.
# Source code: https://github.com/shinamicorp/shinami-typescript-sdk/tree/main/move_example
EXAMPLE_MOVE_PACKAGE_ID='0xd8f042479dcb0028d868051bd53f0d3a41c600db7b14241674db1c2e60124975'

We've included the address of a Move package we've deployed to Testnet so that your sample project can interact with it, via the environmental variable EXAMPLE_MOVE_PACKAGE_ID. This package has a public entry function that takes two numbers and adds them together (I know, super exciting!).

Beyond this, our starter template sets the maxEpoch value, which determines the expiration time of the ephemeral KeyPair, to current epoch + 1. This means that when the Sui epoch number moves above that value, a new ephemeral KeyPair and thus JWT will be needed, so the user will need to complete the OpenID authentication flow again. If you instead use relativeToCurrentEpoch(sui, 0) for example, the ephemeral KeyPair will expire when the current Sui epoch does. For more on KeyPair expiration, see Sui Foundation's zkLogin doc.

5. Run and interact with the app

Run the app and login

Now, you can run the project! On the command line, run npm run dev . Then, visit http://localhost:3000 in your browser and you'll be greeted with prompt to sign in.

Click "Sign in", and you'll see an option for each of the OpenID Connect providers you have a *_CLIENT_ID environmental variable for.

Choose one and log in. For me, I chose Twitch:

After clicking "Authorize", wait a few seconds for the OpenID Connect flow to complete.

Make a Move function call

Now, you should be successfully logged in and see the following:

Click "Sui calculator". You'll see:

When you enter values and click "Calculate on Sui":

  1. The frontend sends a request to the /api/add/tx endpoint with the numbers the user entered (which will be the arguments to the Move function call)
    1. From my the Network tab of my Chrome developer tools

      From my the Network tab of my Chrome developer tools

  2. The backend generates the programmable transaction block (without a gas object) and then makes a request to Shinami Gas Station to sponsor the transaction using the Testnet fund linked to the SHINAMI_SUPER_ACCESS_KEY you provided.
  3. The frontend signs the sponsored transaction with the zkLogin wallet's ephemeral KeyPair. And then makes a request to the /api/add/tx endpoint.
  4. The Backend assembles the zkLogin signature and then uses Shinami Node service to submit the transaction to the Sui Testnet with sui_executeTransactionBlock.

For a helpful image of the full zkLogin flow, see the Sui Foundation's zkLogin doc

Assuming you have added SUI to your Gas Station, enter 1 + 2, click "Calculate on Sui" and wait a few seconds. During the processing, you'll see output like the following in the terminal window running the project: Preparing add tx for zkLogin wallet 0x6a7b07da9d2c210bbbe044dbe222225ae20dd4584a4d8ee9a804839f8256ea3c. Then, shortly thereafter, you'll see the result and the associated transaction digest (your digest will be different):

Clicking on the transaction digest link will take you to the transaction in the SuiVision explorer. If you choose the "User Signatures" tab, you'll see that two signatures were submitted - a zkLogin signature from the sender (your zkLogin wallet) and an ED25519 gas sponsor signature from Shinami's Gas Station:

As expected, this transaction digest will be listed for a brief time under the in-flight transactions tab in the Gas Station page of your Shinami dashboard (sponsored transactions can remain here after a successful execution for a few minutes until our gas object cleanup job runs and recognizes that the gas object as been used).

A note on zkLogin addresses

If you click "Sign out" in your localhost tab and then sign in via the same (OpenID provider,username) pair, you'll have the same zkLogin wallet address. Signing in with a different combination will create a different address (see more info on what makes up a zkLogin address in our zkLogin wallet API doc). As you can see, if I go back to http://localhost:3000/ and sign out

and then log in with Facebook, I have no recent transactions because this is a different address:

Conclusion

You've just successfully set up and run a zkLogin app that creates zkLogin wallets and executes sponsored transactions for them! For more information on zkLogin, see: