Skip to main content

Smart Wallets & Session Keys

While building web3 apps and games, you often want to:

  • Deploy smart wallets to onboard your users, and give your users full custody over their own wallets
  • But, you still want your backend to occasionally perform onchain actions for your users - like minting NFTs, spending currency, or listing assets on their behalf.

In this guide, we're going to use smart wallets & session keys with Engine to build a flow where:

  1. Your backend deploys smart wallets for your users
  2. Your users can "login" to your backend with these smart wallets
  3. Your backend gains temporary access to perform onchain actions on behalf of your users accounts

Step 1: Deploy an account factory

In order to deploy smart wallets for your users, you need to deploy an AccountFactory contract first.

You can deploy one from the thirdweb dashboard - thirdweb offers the following 3 types of account factory contracts:

You can read about the differences here - for most simple use cases, the AccountFactory is a good start.

Step 2: Connect smart wallets in your application

Now, we can follow the steps to allow our users to connect to our application with smart wallets.

Make sure to replace the configuration for factoryAddress with the account factory address that you just deployed

This setup does the following:

  • Automatically deploys smart accounts for your users when the first transaction on their account is performed.
  • Your users can connect to their smart wallet with any personal wallet (in this case, we've used embeddedWallet which let's users control their smart wallets with their email).
  • Handle all transaction gas cost via the thirdweb paymaster
  • Select your deployed account factory and client ID to use thirdweb infrastructure
import { ThirdwebProvider, ConnectWallet, smartWallet, embeddedWallet } from "@thirdweb-dev/react";
export default function App() {
return (
<ThirdwebProvider
clientId="<your-thirdweb-client-id>"
activeChain="goerli"
supportedWallets={[
smartWallet(
embeddedWallet(),
{
factoryAddress: "<your-account-factory-address>", // your deployed factory address
gasless: true // enable or disable gasless transactions
})
]}
>
<ConnectWallet />
</ThirdwebProvider>
);
}

Step 3: Let users grant your backend a session key

So far, your users will be able to connect to your client-side application with their smart wallet and perform onchain actions from there.

Now, we want to create a flow to enable your backend to perform actions on their smart wallets using Engine.

In order to accomplish this, we ned to prompt users to grant our engine backend wallet a session key.

This will allow engine to perform onchain actions for the user smart wallet by signing user operations with the engine backend wallet.

In this snippet, we have a button that prompts the users to grant the engine backend wallet a session key giving Engine access to send transactions to any contract from the user's smart wallet for 3 hours.

const Component = () => {
const { mutate: createSessionKey, isLoading, error } = useCreateSessionKey();

return (
<button
disabled={isLoading}
onClick={() =>
createSessionKey({
keyAddress: "<your-backend-wallet-address>",
permissions: {
approvedCallTargets: "*", // send a transaction to any contract
nativeTokenLimitPerTransaction: 0, // backend cant spend any funds
startDate: new Date(), // start now
expirationDate: new Date(Date.now() + 3 * 60 * 60 * 1000), // expire in 3 hours
},
})
}
>
Create Session Key
</button>
);
};

Step 4: Perform onchain actions for users from your backend with Engine

Once users grant your backend wallet a session key, you can use Engine to perform onchain actions on behalf of your user wallets from your backend.

You can accomplish this by making requests to Engine from your backend and specifying the Engine backend wallet and user smart account address to use.

We need to set the x-backend-wallet-address header to the backend wallet that the user assigned the session key to, and the x-account-address header to the user's account that we want to act on behalf of.

For example, the following snippet will transfer an NFT from the user's wallet using Engine:

import { Engine } from "@thirdweb-dev/engine";

export default async function handler(req, res) {
const { accountAddress } = req.body;

const engine = new Engine({
url: "http://localhost:3005/",
accessToken: "<your-engine-access-token>"
})

await engine.erc721.transfer(
"mumbai",
"<contract-address>",
"<x-backend-wallet-address>",
{
to: "0x...",
tokenId: 0,
}
"<x-account-address>"
)
}

Using this flow, you can perform any onchain action from your backend with the users wallet!