Skip to main content
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 ExamplesSome 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

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.
ScenarioChainPackageHooks to interact with the walletFull guide
Ethereum / EVMEthereum + 18 other EVM chains@civic/auth-web3/wagmiuseAccount(), useBalance(), useSendTransaction() from wagmiEthereum & EVM →
Solana — without wallet-adapterSolana@civic/auth-web3/reactuseWallet({ type: "solana" }) from @civic/auth-web3/reactSolana →
Solana — with wallet-adapterSolana@solana/wallet-adapter-react + @civic/auth-web3useWallet() + useConnection() from @solana/wallet-adapter-reactSolana → 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 walletNew 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 use useAutoConnect() 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 WalletMultiButton modal — no explicit createWallet() call is needed in your code.
See the Ethereum and Solana guides for full examples.

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

Civic embedded wallets use automatic recovery through reauthentication. Unlike traditional wallets that require seed phrases or manual backups, your users’ wallets are recovered transparently when they log in again with their registered identity (email or phone). Key points:
  • No seed phrases or recovery codes to manage
  • Wallets are automatically accessible after reauthentication via SSO
  • Recovery is handled seamlessly by our wallet provider 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 you need additional user-controlled backup options (such as recovery codes or key export), please contact MetaKeep support directly to inquire about their roadmap for these features.