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.
Relevant links
For additional information during this guide:
- Full demo example using Lit on Github
- How on-chain guardian recovery works
- Guardian Recovery SDK Reference
- Simple recovery demo on GitHub
- Lit Documentation Website
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
);
Sponsor the Gas
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.