Embedded Wallets
The Civic Auth Web3 SDK ( @civic/auth-web3 ) extends the functionality of the base Civic Auth SDK by including the ability to provision a Web3 wallet for users. This allows Civic Auth apps to provide their users with access to the world of Cryptocurrencies and Web3 without any hassle or prior knowledge.
Supported Networks: Civic embedded wallets support 18+ EVM-compatible chains including Ethereum, Base, Polygon, BSC, Arbitrum One, Avalanche, and many more. See the complete list for all supported networks.
Currently, we don't support connecting users' existing self-custodial wallets. This is coming soon.
Right now, we only support embedded wallets, which are generated on behalf of the user by our non-custodial wallet partner. Neither Civic nor your app ever has access to the wallets' private keys.
Note: Embedded wallets are not currently supported when X (Twitter) is enabled as a login method. This is because X does not return email addresses, which are required for embedded wallet provisioning. This limitation will be resolved in a future update using composable logins.
Quick Start
Sign up for Civic Auth at auth.civic.com and make sure to select the "Web3 wallet" option.
If you already have an account, just log in and select the Web3 wallet option in the configuration to enable Web3 wallets.
Code Examples
Some complete examples for Solana and EVM can be found on Github here.
The Ethereum and Solana pages have links to specific examples.
Also see the code examples in this documentation.
Installation
Install the Civic Auth Web3 SDK:
npm install @civic/auth-web3
yarn add @civic/auth-web3
pnpm install @civic/auth-web3
bun add @civic/auth-web3
Integration
Choose your framework for instructions on how to integrate Civic Auth Web3 into your application.
NOTE - Web3 wallets are available for React and Next.js environments.
If you are interested in using Civic's Web3 wallet feature in other environments, please contact us in our developer community.
Choosing Your Integration
Before diving in, pick the path that matches your blockchain and setup. The hooks and packages you use depend entirely on this choice — mixing them up is the most common source of errors.
| Scenario | Chain | Package | Hooks to interact with the wallet | Full guide |
|---|---|---|---|---|
| Ethereum / EVM | Ethereum + 18 other EVM chains | @civic/auth-web3/wagmi | useAccount(), useBalance(), useSendTransaction() from wagmi | Ethereum & EVM → |
| Solana — without wallet-adapter | Solana | @civic/auth-web3/react | useWallet({ type: "solana" }) from @civic/auth-web3/react | Solana → |
| Solana — with wallet-adapter | Solana | @solana/wallet-adapter-react + @civic/auth-web3 | useWallet() + useConnection() from @solana/wallet-adapter-react | Solana → Wallet Adapter |
@solana/wallet-adapter-react is Solana-only. Do not use it in Ethereum/EVM integrations. For Ethereum, use wagmi hooks exclusively.
Creating vs using a wallet
New users don't have a wallet by default. Before the hooks in the table above will return an address or balance, a wallet must be created. How that happens depends on your integration:
- Ethereum/EVM: call
createWallet()then connect via wagmi, or useuseAutoConnect()which handles both steps in one. - Solana without wallet-adapter: call
createWallet()explicitly after the user logs in. - Solana with wallet-adapter: wallet creation is triggered by the user selecting Civic Auth in the
WalletMultiButtonmodal — no explicitcreateWallet()call is needed in your code.
Using the Wallet
Follow these guides to set up Web3 wallets for your users:
Server-Side Integration
To access a user's embedded wallet address on the server side, use getWallets() from @civic/auth-web3/server. A full working example is available here: express-with-wallet.
Installation
npm install @civic/auth @civic/auth-web3
Setup
Configure CivicAuth with cookie-based storage per request, and attach it to each Express request via middleware:
import express, { Request, Response, NextFunction } from "express";
import cookieParser from "cookie-parser";
import { CookieStorage, CivicAuth } from "@civic/auth/server";
const app = express();
app.use(cookieParser());
const config = {
clientId: process.env.CLIENT_ID!,
redirectUrl: "http://localhost:3001/auth/callback",
postLogoutRedirectUrl: "http://localhost:3001/",
};
class ExpressCookieStorage extends CookieStorage {
constructor(private req: Request, private res: Response) {
super({ secure: process.env.NODE_ENV === "production" });
}
async get(key: string): Promise<string | null> {
return this.req.cookies[key];
}
async set(key: string, value: string): Promise<void> {
this.res.cookie(key, value, this.settings);
}
async clear(): Promise<void> {
for (const key in this.req.cookies) {
this.res.clearCookie(key);
}
}
async delete(key: string): Promise<void> {
this.res.clearCookie(key);
}
}
app.use((req: Request, res: Response, next: NextFunction) => {
req.storage = new ExpressCookieStorage(req, res);
req.civicAuth = new CivicAuth(req.storage, config);
next();
});
Authentication Flow
app.get("/", async (req: Request, res: Response) => {
const url = await req.civicAuth.buildLoginUrl();
res.redirect(url.toString());
});
app.get("/auth/callback", async (req: Request, res: Response) => {
const { code, state } = req.query as { code: string; state: string };
await req.civicAuth.resolveOAuthAccessCode(code, state);
res.redirect("/admin/dashboard");
});
Retrieving the Wallet Address
Once the user is authenticated, use getWallets() from @civic/auth-web3/server to retrieve the user's embedded wallet address(es):
import { getWallets } from "@civic/auth-web3/server";
app.get("/admin/dashboard", async (req: Request, res: Response) => {
const user = await req.civicAuth.getUser();
const tokens = await req.civicAuth.getTokens();
if (!user) return res.status(401).send("Unauthorized");
const userDetails = {
...user,
idToken: tokens?.idToken || "",
};
const wallets = await getWallets(userDetails);
res.json({
user: user.name,
walletAddresses: wallets.map((wallet) => wallet.walletAddress),
});
});
getWallets() returns an array of wallet objects, each with a walletAddress property.
Do not decode the ID token JWT to retrieve the wallet address, and there is no user.wallet property — always use getWallets() from @civic/auth-web3/server.
Auth Middleware
const authMiddleware = async (req: Request, res: Response, next: NextFunction) => {
if (!req.civicAuth.isLoggedIn()) return res.status(401).send("Unauthorized");
next();
};
app.use("/admin", authMiddleware);
TypeScript: Extending the Request Type
declare global {
namespace Express {
interface Request {
storage: ExpressCookieStorage;
civicAuth: CivicAuth;
}
}
}
About Embedded Wallets
What are Embedded Wallets?
Embedded wallets are cryptocurrency wallets provided directly by an app or website, rather than requiring the user to supply their own.
This approach is a powerful tool for onboarding non-crypto users into Web3, enabling apps to cater to both crypto-native users and newcomers alike.
By removing the need for complicated wallet setup, remembering seed phrases etc., embedded wallets are a vital tool for bridging the gap between Web2 and Web3.
Civic's Embedded Wallets
The Civic Embedded Wallet Service is non-custodial, meaning neither Civic nor Civic's customers have control over users' wallets or assets.
Each wallet is linked to a user's SSO (Single Sign-On) provider, such as Google, ensuring that only authenticated users can sign transactions.
Civic's wallets are EOA (Externally Owned Account) wallets, rather than smart contract (AKA account abstraction) wallets. This has benefits in terms of simplicity and lower gas costs. However, they can be upgraded to support account abstraction, for example to support gas sponsorship. Contact us in our developer community if you're interested in this feature.
Metakeep uses industry-standard security measures such as Hardware Security Modules (HSMs) and FIPS 140-2-validated cryptographic silicon to maintain strict key isolation. Each wallet is linked to the user's email, and signatures require user consent. This design ensures that Metakeep cannot take control of a user's wallet or move assets without their approval — preserving true non-custodianship by design. The private keys never leave the secure hardware; even MetaKeep operators do not have access to the raw key data.
Recovery
Our wallet provider, MetaKeep, includes a recovery feature that ensures wallet access can be restored even in the event of a service interruption with Civic Auth. Recovery is managed directly by MetaKeep and operates independently of Civic's infrastructure.
Key points:
- No seed phrases or recovery codes to manage
- Wallets are automatically accessible after reauthentication via SSO
- Recovery is handled seamlessly by MetaKeep
MetaKeep does not provide traditional recovery mechanisms like seed phrase export or manual backup options. Wallet access is tied exclusively to the user's identity provider authentication.
If a user needs to recover their wallet — for example, after losing their SSO credentials — they can contact MetaKeep support directly at contact@metakeep.xyz.
If you need to initiate a wallet recovery on behalf of a user, contact us in our developer community and we will coordinate the process with MetaKeep.