Search by name, SuiNS, address, digest, object ID, coin type or checkpoint.

API
Sign in with Google

Sign in with Google

Give every user a Sui wallet from their Google account (zkLogin). tide checks the sign-in, keeps each user's salt so their address never changes, and makes the proof that lets their browser sign. Your app never holds a user's key.

How it works

  1. Your app makes an ephemeral key pair and a nonce from it, and sends the user to Google with that nonce.
  2. Google returns an ID token (JWT). Send it to /v1/zklogin/salt for the user's salt and Sui address. The same Google account always gets the same address in your app.
  3. Send the token and the ephemeral public key to /v1/zklogin/proof. The proof lets the ephemeral key sign for the address until maxEpoch.
  4. Sign transactions with the ephemeral key wrapped in the proof (ZkLoginSigner).

The salt keeps the link between a Google account and its Sui address private. tide derives it from a secret seed, the issuer, your client id and the user's id, so it never changes and needs no database to stay the same.

Set up

  1. Get a tide API key at tide.ad/developers/keys. For calls from the browser, set its allowed origins to your site.
  2. Make a Google OAuth client (type: web application) and add your sign-in page as an authorized redirect URI.
  3. Register its client id to your key. tide accepts only tokens whose aud is a client id registered to the calling key. Add jwt (any ID token for that client id) to verify that it is yours; importing salts needs a verified client id.
curl
curl -X POST "https://api.tide.ad/v1/zklogin/clients" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "clientId": "1234-abc.apps.googleusercontent.com" }'

Example

The whole flow in a browser app with @mysten/sui. Step 1 runs before the redirect, steps 2 and 3 on the page Google sends the user back to.

TypeScript
import { SuiGrpcClient } from "@mysten/sui/grpc";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { Transaction } from "@mysten/sui/transactions";
import { ZkLoginSigner, generateNonce, generateRandomness, getExtendedEphemeralPublicKey } from "@mysten/sui/zklogin";

const tide = (path: string, body: object) =>
  fetch(`https://api.tide.ad${path}`, { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "content-type": "application/json" }, body: JSON.stringify(body) }).then((r) => r.json());
const sui = new SuiGrpcClient({ network: "mainnet", baseUrl: "https://fullnode.mainnet.sui.io:443" });

// 1. Before sign-in: an ephemeral key and the nonce Google puts in the token (keep both in sessionStorage)
const ephemeral = new Ed25519Keypair();
const maxEpoch = Number((await sui.core.getCurrentSystemState()).systemState.epoch) + 2;
const randomness = generateRandomness();
const nonce = generateNonce(ephemeral.getPublicKey(), maxEpoch, randomness);
location.href = `https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_GOOGLE_CLIENT_ID&response_type=id_token&scope=openid&redirect_uri=${encodeURIComponent(location.origin + "/auth")}&nonce=${nonce}`;

// 2. Back on /auth: the user's address and a proof for the ephemeral key
const jwt = new URLSearchParams(location.hash.slice(1)).get("id_token")!;
const { address } = await tide("/v1/zklogin/salt", { jwt });
const proof = await tide("/v1/zklogin/proof", { jwt, extendedEphemeralPublicKey: getExtendedEphemeralPublicKey(ephemeral.getPublicKey()), maxEpoch, randomness });

// 3. Sign and send until maxEpoch
const signer = new ZkLoginSigner({ ephemeralSigner: ephemeral, maxEpoch, inputs: proof, legacyAddress: false, address });
const tx = new Transaction();
await sui.signAndExecuteTransaction({ transaction: tx, signer });

A proof stays valid until maxEpoch (Sui allows up to 30 epochs ahead). Keep it with the ephemeral key in the tab, and ask for a new one after that. Sending the same request again returns the same proof from the cache, free, so a page reload costs nothing.

Coming soon: gas sponsorship, so new users can send their first transaction before they hold any SUI.

Move existing users

If your users already have zkLogin addresses, import their salts before you switch. An imported salt wins over tide's own, so every address stays exactly the same. Salts are encrypted at rest, and only your key can write them for your client id.

  • In bulk: up to 1,000 users per call, each as { subHash, salt } where subHash is the hex SHA-256 of the user's sub (or send sub itself).
  • At sign-in: for a user you did not export, send { jwt, salt } once with the salt you used before.
  • A user who already has a different imported salt, or who already signed in with tide's own salt, is skipped unless you pass replace: true, because their address would change.
curl
curl -X POST "https://api.tide.ad/v1/zklogin/import" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "clientId": "1234-abc.apps.googleusercontent.com",
  "users": [
    { "subHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "salt": "129390038577185583942388216820280642146" }
  ]
}'

Check one user afterwards: /v1/zklogin/salt returns source: "imported" and their old address.

Backups

/v1/zklogin/backup returns a recovery kit for the signed-in user: the issuer, your client id (aud), the user's id at Google (sub), the salt and the address, as JSON and as a short code. Show it to the user to save in a file or a password manager.

The salt is the only part of a zkLogin wallet that tide keeps. With the kit and their Google account, a user can rebuild the same wallet with any standard zkLogin prover, even if tide is gone: sign in through your app's Google client as usual, send the token and the kit's salt to the prover, and sign. The address is computed from iss, aud, sub and salt alone (computeZkLoginAddress in @mysten/sui).

Anyone can check a kit at tide.ad/docs/login/recover: it rebuilds the address in the browser, and can confirm the kit against a fresh Google sign-in.

A "Back up your wallet" button for your app, from a small script with no dependencies:

HTML
<div id="backup"></div>
<script type="module">
  import { backupButton } from "https://tide.ad/sdk/zklogin.js";
  backupButton(document.getElementById("backup"), {
    apiKey: "YOUR_API_KEY",                        // a key with your site in its allowed origins
    getJwt: () => sessionStorage.getItem("jwt"),   // the user's current ID token
  });
</script>

It shows the code and the address and offers the kit as a file. If your server calls /v1/zklogin/backup instead, pass getKit (returning that answer) in place of apiKey and getJwt.

Limits and pricing

Salt, backup, import, client idsFree
Proofs50,000 new proofs a month free per app, then 1,000 credits each ($0.003). A repeated request is answered from the cache and is free.
Proof timeAbout 5 seconds
Rate30 new proofs a minute per IP, 60 a minute per app on the Free plan (600 on paid plans), 20 an hour per user. Sign-in calls: 120 a minute per IP.
Shared free useApps calling from one network share 75,000 free proofs a month. A Free account whose address has no on-chain history gets 100 free proofs a day.
IssuersGoogle

Over a limit the answer is HTTP 429 with retry-after. Past the free proofs with no credits left it is HTTP 402 with the USDC offers (see Pay with USDC). /v1/zklogin/clients shows this month's proofs and what is left.

Methods

MethodWhat it doesCredits
zklogin.saltPOST /v1/zklogin/saltThe salt and Sui address for a Google sign-in.Free
zklogin.proofPOST /v1/zklogin/proofA zkLogin proof for an ephemeral key, ready to sign Sui transactions.varies
zklogin.backupPOST /v1/zklogin/backupA recovery kit for the signed-in user: issuer, client id, user id, salt and address.Free
zklogin.importPOST /v1/zklogin/importImport the salts your users already have, so their addresses stay the same.Free
zklogin.clientAddPOST /v1/zklogin/clientsRegister an OAuth client id (aud) to your account.Free
zklogin.clientsGET /v1/zklogin/clientsYour registered sign-in client ids, proof usage this month and the free allowance.Free
zklogin.clientRemoveDELETE /v1/zklogin/clients/:clientIdRemove a client id from your account.Free
zklogin.configGET /v1/zklogin/configSign-in settings: accepted issuers, free allowance, proof price and whether the prover is up.Free