Skip to main content

Add a Google Account as a Recovery Method using Lit (Guide 1/2)

This is the first of the two guides on how to add a Google account as a Recovery method for a Safe Account. In the second guide, we show the steps to execute the recovery.

What is Lit Protocol?

Lit is a network for signing and encryption. One of their usecases is to enable users to login using various authentication methods like Social Logins and Email OTP.

To leverage the full potential of Account Abstraction, you can combine Lit with AbstractionKit to enable email / social recovery experience, while using a Smart Account as the smart wallet to sponsor gas for users, batch transactions, and more.

For additional information during this guide:

Installation

Install required dependencies

npm i abstractionkit && @lit-protocol/lit-auth-client

Configure .env file

Configure the values you created from Lit dashboard in an .env file

// Lit
LIT_API_KEY=Request Relay Server API Key from Lit at https://forms.gle/RNZYtGYTY9BcD9MEA

// Candide
BUNDLER_URL="https://api.candide.dev/bundler/version/network/YOUR_API_KEY" // Other networks are found here: https://docs.candide.dev/wallet/bundler/rpc-endpoints
PAYMASTER_URL="Request an API key from Candide on Discord"

// Generate a Public/Private Key
OWNER_PUBLIC_ADDRESS=
OWNER_PRIVATE_KEY=

// Network Info
VITE_CHAIN_ID=
JSON_RPC_NODE_PROVIDER= // "Get an RPC from a Node provider"

Setup The Google Account with Lit

Initialize the Lit client and provider

  • Connect to the Lit Network using LitNodeClient.
  • Set up the LitAuthClient for authentication.
  • Initialize a GoogleProvider for Google sign-in.
import { LitNodeClient } from "@lit-protocol/lit-node-client";
import { LitAuthClient, GoogleProvider } from "@lit-protocol/lit-auth-client";
import { ProviderType } from "@lit-protocol/constants";

const initalizeClientsAndProvider = async () => {
const litNodeClient = new LitNodeClient({
litNetwork: "datil-dev",
debug: true,
});
await litNodeClient.connect();

const litAuthClient = new LitAuthClient({
litRelayConfig: {
relayApiKey: "Anything",
},
litNodeClient,
});

console.log("Connected to Lit Node and Lit Auth Clients ✔️");

const provider = litAuthClient.initProvider<GoogleProvider>(
ProviderType.Google,
{
//redirectUri: VITE_REDIRECT_URI,
}
);
return { litNodeClient, litAuthClient, provider };
};

Authentication with Gmail

  • Generate Authentication Method
  • Check if the user is already authenticated. If not, redirect to Google sign-in
import { AuthMethod } from "@lit-protocol/types";

const generateAuthMethod = async () => {
const url = new URL(window.location.href);
if (!url.searchParams.get("provider")) {
console.log("Signing in with Google...");
provider.signIn((url) => {
window.location.href = url;
});
} else if (url.searchParams.get("provider") === "google") {
const authMethod = await provider.authenticate();
return authMethod;
}
};

const authMethod = await generateAuthMethod();
if (!authMethod) {
return;
}

Mint PKP (Programmable Key Pair)

import { LitAuthClient } from "@lit-protocol/lit-auth-client";

const mintWithGoogle = async (authMethod) => {
const pkp = await litAuthClient.mintPKPWithAuthMethods([authMethod], {
addPkpEthAddressAsPermittedAddress: true
});
console.log("Fetched PKP", pkp);
return pkp;
};

const pkp = await mintWithGoogle(authMethod);
console.log("Minted PKP ✔️");

Setup the Google Account as a Guardian

import { PKPEthersWallet } from "@lit-protocol/pkp-ethers";
import { LitAbility, LitPKPResource } from "@lit-protocol/auth-helpers";
import { AuthCallbackParams } from "@lit-protocol/types";

const authNeededCallback = async (params: AuthCallbackParams) => {
console.log(`auth needed callback params`, JSON.stringify(params, null, 2));
const response = await litNodeClient.signSessionKey({
statement: params.statement,
authMethods: [authMethod],
resourceAbilityRequests: [
{
resource: new LitPKPResource("*"),
ability: LitAbility.PKPSigning,
},
],
expiration: params.expiration,
resources: params.resources,
chainId: 1,
pkpPublicKey: pkp.pkpPublicKey,
});
console.log("AUTHSIG", response);
return response.authSig;
};

const guardianSigner = new PKPEthersWallet({
litNodeClient,
authContext: {
getSessionSigsProps: {
chain: "ethereum",
expiration: new Date(Date.now() + 60_000 * 60).toISOString(),
resourceAbilityRequests: [
{
resource: new LitPKPResource("*"),
ability: LitAbility.PKPSigning,
},
],
authNeededCallback: authNeededCallback,
},
},
pkpPubKey: pkp.pkpPublicKey,
rpc: "https://yellowstone-rpc.litprotocol.com",
});
console.log("Created PKPEthersWallet using the PKP ✔️");

Add Google as a Guardian to the Smart Account

We will integrate Lit as the guardian of the Smart Account. Not only that, we will be creating a special Smart Account that will be owned by the Lit key, allowing us to sponsor gas fees during the recovery process. No need to fund the associated Gmail account with funds to execute a recovery process.

Initilize a Smart Account

import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit";

const smartAccount = SafeAccount.initializeNewAccount([ownerPublicAddress]);
console.log("Smart Account Address:", smartAccount.accountAddress);

Enable The Module and Add the Guardian

import { SocialRecoveryModule } from "abstractionkit";

const srm = new SocialRecoveryModule();

const enableModuleTx = srm.createEnableModuleMetaTransaction(
smartAccount.accountAddress
);

// Note that the Guardian added is also a Smart Account, to enable easy recovery without gas
const guardianOwnerAddress = await guardianSigner.getAddress();
const guardianSmartAccount = SafeAccount.initializeNewAccount([guardianOwnerAddress]);
const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction(
smartAccount.accountAddress,
guardianSmartAccount.accountAddress,
1n
);

let userOperation = await smartAccount.createUserOperation(
[enableModuleTx, addGuardianTx],
process.env.JSON_RPC_NODE_PROVIDER,
process.env.BUNDLER_URL
);
import { CandidePaymaster } from "abstractionkit";

const paymaster = new CandidePaymaster(process.env.PAYMASTER_URL);
userOperation = await paymaster.createSponsorPaymasterUserOperation(
userOperation,
process.env.BUNDLER_URL
);

Sign and Submit UserOperation

userOperation.signature = smartAccount.signUserOperation(
userOperation,
[process.env.OWNER_PRIVATE_KEY],
process.env.CHAIN_ID
);

const sendUserOperationResponse = await smartAccount.sendUserOperation(
userOperation,
process.env.BUNDLER_URL
);

Monitor UserOperation

console.log("User operation sent. Waiting to be included...");
const userOperationReceiptResult = await sendUserOperationResponse.included();

if (userOperationReceiptResult.success) {
console.log(
"Successful User operation. Transaction hash: " +
userOperationReceiptResult.receipt.transactionHash
);
const isGuardian = await srm.isGuardian(
jsonRpcNodeProvider,
smartAccount.accountAddress,
guardianSmartAccount.accountAddress
);
if (isGuardian) {
console.log("Guardian added confirmed ✔️. Guardian address: " +
guardianSmartAccount.accountAddress);
} else {
console.log("Adding guardian failed.");
}
} else {
console.log("User operation execution failed");
}

That's it! You've successfully added a Guardian capable of recovering a smart account with a Google Account using Lit. If you're interested in learning how to recovery the account with Google using Lit, you can explore the second guide that published on lit docs.

Find here the complete documentation for Account Recovery.