|
| 1 | +# Session Keys Guide |
| 2 | + |
| 3 | +Session keys enable secure transaction execution on behalf of smart accounts without requiring direct access to the main account's private key. This guide will walk you through creating and using session keys with the thirdweb TypeScript SDK. |
| 4 | + |
| 5 | +## Prerequisites |
| 6 | + |
| 7 | +Before you begin, ensure you have: |
| 8 | +- A thirdweb client configured |
| 9 | +- Access to a session key account address |
| 10 | +- Vault access token for Engine operations |
| 11 | + |
| 12 | +## Setup |
| 13 | + |
| 14 | +First, let's set up the necessary imports and configuration: |
| 15 | + |
| 16 | +```typescript |
| 17 | +import { |
| 18 | + generateAccount, |
| 19 | + smartWallet, |
| 20 | + sendTransaction, |
| 21 | + getContract, |
| 22 | + createThirdwebClient |
| 23 | +} from "thirdweb"; |
| 24 | +import { sepolia } from "thirdweb/chains"; |
| 25 | +import { getAllActiveSigners } from "thirdweb/extensions/erc4337"; |
| 26 | +import { Engine } from "thirdweb/engine"; |
| 27 | + |
| 28 | +// Configure your client |
| 29 | +const client = createThirdwebClient({ |
| 30 | + clientId: "your-client-id", |
| 31 | + secretKey: "your-secret-key" // Only use in server environments |
| 32 | +}); |
| 33 | + |
| 34 | +// Your session key account address |
| 35 | +const sessionKeyAccountAddress = "0x..."; // Replace with your session key address |
| 36 | + |
| 37 | +// Target address for transactions |
| 38 | +const targetAddress = "0x..."; // Replace with your target address |
| 39 | +``` |
| 40 | + |
| 41 | +## Step 1: Configure User Smart Wallet with Session Key |
| 42 | + |
| 43 | +The first step is to add our session key address as a signer to the user's smart account. This is typically done on the client side since it needs explicit user approval. This can be done by configuring the smart wallet with the session key address and permissions. |
| 44 | + |
| 45 | +In a React application, this can be done by using the `ConnectButton` or `ConnectEmbed` component. This will automatically configure the smart wallet with the session key address and permissions. |
| 46 | + |
| 47 | +```typescript |
| 48 | +<ConnectButton |
| 49 | + accountAbstraction={{ |
| 50 | + chain: sepolia, |
| 51 | + sessionKey: { |
| 52 | + address: sessionKeyAccountAddress, |
| 53 | + permissions: { |
| 54 | + // "*" allows all targets, or specify specific contract addresses |
| 55 | + approvedTargets: "*", |
| 56 | + }, |
| 57 | + }, |
| 58 | + sponsorGas: true, |
| 59 | + }} |
| 60 | +/> |
| 61 | +``` |
| 62 | + |
| 63 | +This can also be done in pure TypeScript by using the `smartWallet` function and connecting it to a personal account. |
| 64 | + |
| 65 | +For this guide, we'll generate a random personal account that will be used to create the smart wallet: |
| 66 | + |
| 67 | +```typescript |
| 68 | +// this would be the user's personal account |
| 69 | +const personalAccount = await generateAccount({ |
| 70 | + client: client, |
| 71 | +}); |
| 72 | + |
| 73 | +// wrapped in a smart wallet with session key permissions |
| 74 | +const smart = smartWallet({ |
| 75 | + chain: sepolia, |
| 76 | + sessionKey: { |
| 77 | + address: sessionKeyAccountAddress, |
| 78 | + permissions: { |
| 79 | + // "*" allows all targets, or specify specific contract addresses |
| 80 | + approvedTargets: "*", |
| 81 | + }, |
| 82 | + }, |
| 83 | + sponsorGas: true, // Enable gas sponsorship |
| 84 | +}); |
| 85 | + |
| 86 | +console.log("Personal account created:", personalAccount.address); |
| 87 | +``` |
| 88 | + |
| 89 | +### Session Key Permissions |
| 90 | + |
| 91 | +The `permissions` object allows you to control what the session key can do: |
| 92 | + |
| 93 | +- `approvedTargets`: Specify which contract addresses the session key can interact with |
| 94 | + - Use `"*"` for all targets |
| 95 | + - Use an array of addresses for specific contracts: `["0x123...", "0x456..."]` |
| 96 | + |
| 97 | +## Step 2: Connect Smart Account |
| 98 | + |
| 99 | +Connect the smart wallet using the personal account: |
| 100 | + |
| 101 | +```typescript |
| 102 | +const smartAccount = await smart.connect({ |
| 103 | + client: client, |
| 104 | + personalAccount: personalAccount, |
| 105 | +}); |
| 106 | + |
| 107 | +console.log("Smart account address:", smartAccount.address); |
| 108 | +``` |
| 109 | + |
| 110 | +Note that in a React application, this would be done automatically by the `ConnectButton` or `ConnectEmbed` component. |
| 111 | + |
| 112 | +## Step 3 (Optional): Verify Session Key Registration |
| 113 | + |
| 114 | +Check that the session key is properly registered as an active signer: |
| 115 | + |
| 116 | +```typescript |
| 117 | +const signers = await getAllActiveSigners({ |
| 118 | + contract: getContract({ |
| 119 | + address: smartAccount.address, |
| 120 | + chain: sepolia, |
| 121 | + client: client, |
| 122 | + }), |
| 123 | +}); |
| 124 | + |
| 125 | +// Verify the session key is in the list of active signers |
| 126 | +const isSessionKeyActive = signers |
| 127 | + .map((s) => s.signer) |
| 128 | + .includes(sessionKeyAccountAddress); |
| 129 | + |
| 130 | +console.log("Session key is active:", isSessionKeyActive); |
| 131 | +console.log("All active signers:", signers.map((s) => s.signer)); |
| 132 | +``` |
| 133 | + |
| 134 | +## Step 4: Create Engine Server Wallet |
| 135 | + |
| 136 | +Set up an Engine server wallet using the session key for transaction execution: |
| 137 | + |
| 138 | +```typescript |
| 139 | +const serverWallet = Engine.serverWallet({ |
| 140 | + address: sessionKeyAccountAddress, |
| 141 | + chain: sepolia, |
| 142 | + client: client, |
| 143 | + executionOptions: { |
| 144 | + entrypointVersion: "0.6", // ERC-4337 entrypoint version |
| 145 | + signerAddress: sessionKeyAccountAddress, |
| 146 | + smartAccountAddress: smartAccount.address, |
| 147 | + type: "ERC4337", |
| 148 | + }, |
| 149 | + vaultAccessToken: process.env.VAULT_TOKEN as string, // Your vault access token |
| 150 | +}); |
| 151 | +``` |
| 152 | + |
| 153 | +### Execution Options |
| 154 | + |
| 155 | +- `entrypointVersion`: The ERC-4337 entrypoint version to use |
| 156 | +- `signerAddress`: The session key address that will sign transactions |
| 157 | +- `smartAccountAddress`: The smart account address that will execute transactions |
| 158 | +- `type`: The account abstraction type (ERC4337) |
| 159 | + |
| 160 | +## Step 5: Execute Transactions |
| 161 | + |
| 162 | +Now you can execute transactions using the session key: |
| 163 | + |
| 164 | +```typescript |
| 165 | +const tx = await sendTransaction({ |
| 166 | + account: serverWallet, |
| 167 | + transaction: { |
| 168 | + chain: sepolia, |
| 169 | + client: client, |
| 170 | + to: targetAddress, |
| 171 | + value: 0n, // Amount in wei (0 for no ETH transfer) |
| 172 | + // data: "0x...", // Optional: contract call data |
| 173 | + }, |
| 174 | +}); |
| 175 | + |
| 176 | +console.log("Transaction sent:", tx.transactionHash); |
| 177 | +``` |
| 178 | + |
| 179 | +## Complete Example |
| 180 | + |
| 181 | +Here's a complete example putting it all together: |
| 182 | + |
| 183 | +```typescript |
| 184 | +import { |
| 185 | + generateAccount, |
| 186 | + smartWallet, |
| 187 | + sendTransaction, |
| 188 | + getContract, |
| 189 | + createThirdwebClient |
| 190 | +} from "thirdweb"; |
| 191 | +import { sepolia } from "thirdweb/chains"; |
| 192 | +import { getAllActiveSigners } from "thirdweb/extensions/erc4337"; |
| 193 | +import { Engine } from "thirdweb/engine"; |
| 194 | + |
| 195 | +async function executeTransactionWithSessionKey() { |
| 196 | + // Configuration |
| 197 | + const client = createThirdwebClient({ |
| 198 | + clientId: "your-client-id", |
| 199 | + secretKey: "your-secret-key" |
| 200 | + }); |
| 201 | + |
| 202 | + const sessionKeyAccountAddress = "0x..."; // Your session key address |
| 203 | + const targetAddress = "0x..."; // Target address for the final transaction |
| 204 | + |
| 205 | + try { |
| 206 | + // Step 1: Create personal account |
| 207 | + const personalAccount = await generateAccount({ client }); |
| 208 | + |
| 209 | + // Step 2: Configure smart wallet |
| 210 | + const smart = smartWallet({ |
| 211 | + chain: sepolia, |
| 212 | + sessionKey: { |
| 213 | + address: sessionKeyAccountAddress, |
| 214 | + permissions: { |
| 215 | + approvedTargets: "*", |
| 216 | + }, |
| 217 | + }, |
| 218 | + sponsorGas: true, |
| 219 | + }); |
| 220 | + |
| 221 | + // Step 3: Connect smart account |
| 222 | + const smartAccount = await smart.connect({ |
| 223 | + client, |
| 224 | + personalAccount, |
| 225 | + }); |
| 226 | + |
| 227 | + // Step 4: Verify session key |
| 228 | + const signers = await getAllActiveSigners({ |
| 229 | + contract: getContract({ |
| 230 | + address: smartAccount.address, |
| 231 | + chain: sepolia, |
| 232 | + client, |
| 233 | + }), |
| 234 | + }); |
| 235 | + |
| 236 | + const isSessionKeyActive = signers |
| 237 | + .map((s) => s.signer) |
| 238 | + .includes(sessionKeyAccountAddress); |
| 239 | + |
| 240 | + if (!isSessionKeyActive) { |
| 241 | + throw new Error("Session key is not active"); |
| 242 | + } |
| 243 | + |
| 244 | + // Step 5: Create server wallet |
| 245 | + const serverWallet = Engine.serverWallet({ |
| 246 | + address: sessionKeyAccountAddress, |
| 247 | + chain: sepolia, |
| 248 | + client, |
| 249 | + executionOptions: { |
| 250 | + entrypointVersion: "0.6", |
| 251 | + signerAddress: sessionKeyAccountAddress, |
| 252 | + smartAccountAddress: smartAccount.address, |
| 253 | + type: "ERC4337", |
| 254 | + }, |
| 255 | + vaultAccessToken: process.env.VAULT_TOKEN as string, |
| 256 | + }); |
| 257 | + |
| 258 | + // Step 6: Execute transaction |
| 259 | + const tx = await sendTransaction({ |
| 260 | + account: serverWallet, |
| 261 | + transaction: { |
| 262 | + chain: sepolia, |
| 263 | + client, |
| 264 | + to: targetAddress, |
| 265 | + value: 0n, |
| 266 | + }, |
| 267 | + }); |
| 268 | + |
| 269 | + console.log("Transaction successful:", tx.transactionHash); |
| 270 | + return tx; |
| 271 | + |
| 272 | + } catch (error) { |
| 273 | + console.error("Error executing transaction:", error); |
| 274 | + throw error; |
| 275 | + } |
| 276 | +} |
| 277 | + |
| 278 | +// Execute the function |
| 279 | +executeTransactionWithSessionKey() |
| 280 | + .then((tx) => console.log("Done!", tx.transactionHash)) |
| 281 | + .catch((error) => console.error("Failed:", error)); |
| 282 | +``` |
| 283 | + |
| 284 | +## Security Considerations |
| 285 | + |
| 286 | +- **Session Key Storage**: Store session keys securely, preferably in a vault system |
| 287 | +- **Permission Scope**: Limit session key permissions to only necessary targets |
| 288 | +- **Key Rotation**: Regularly rotate session keys for enhanced security |
| 289 | +- **Monitoring**: Monitor session key usage for suspicious activity |
| 290 | + |
| 291 | +## Troubleshooting |
| 292 | + |
| 293 | +### Common Issues |
| 294 | + |
| 295 | +1. **Session key not active**: Ensure the session key is properly registered with the smart account |
| 296 | +2. **Permission denied**: Check that the target address is included in `approvedTargets` |
| 297 | +3. **Gas estimation failed**: Verify that gas sponsorship is properly configured |
| 298 | +4. **Vault token invalid**: Ensure your vault access token is valid and has proper permissions |
| 299 | + |
| 300 | +### Error Handling |
| 301 | + |
| 302 | +Always wrap your session key operations in try-catch blocks: |
| 303 | + |
| 304 | +```typescript |
| 305 | +try { |
| 306 | + const tx = await sendTransaction({ |
| 307 | + account: serverWallet, |
| 308 | + transaction: { |
| 309 | + chain: sepolia, |
| 310 | + client, |
| 311 | + to: targetAddress, |
| 312 | + value: 0n, |
| 313 | + }, |
| 314 | + }); |
| 315 | +} catch (error) { |
| 316 | + if (error.message.includes("permission")) { |
| 317 | + console.error("Session key lacks permission for this operation"); |
| 318 | + } else if (error.message.includes("gas")) { |
| 319 | + console.error("Gas estimation or sponsorship failed"); |
| 320 | + } else { |
| 321 | + console.error("Transaction failed:", error); |
| 322 | + } |
| 323 | +} |
| 324 | +``` |
| 325 | + |
| 326 | +## Next Steps |
| 327 | + |
| 328 | +- Learn more about [Smart Wallets](/engine/v3/configure-wallets/server-wallets) |
| 329 | +- Explore [Engine API Reference](https://engine.thirdweb.com/reference) |
| 330 | +- Check out the [TypeScript SDK](/references/typescript/v5/serverWallet) documentation |
0 commit comments