Web3NonCustodialProvider
The Web3NonCustodialProvider is a low-level class for building custom authentication and wallet management flows. It provides direct access to OAuth sign-in, email OTP, wallet creation with Shamir’s Secret Sharing, and wallet recovery.
This is an advanced API. For most use cases, use Web3Wallet.enable() instead, which handles authentication and wallet management automatically.
When to Use This
Use Web3NonCustodialProvider when you need:
- Custom OAuth redirect handling
- Custom UI for authentication flows
- Direct control over wallet creation and recovery
- Chrome extension development (with
chrome.storagesupport)
Installation
npm install @utxos/sdkInitialization
import { Web3NonCustodialProvider } from "@utxos/sdk";
const provider = new Web3NonCustodialProvider({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
googleOauth2ClientId: "your-google-client-id",
twitterOauth2ClientId: "your-twitter-client-id",
discordOauth2ClientId: "your-discord-client-id",
appleOauth2ClientId: "your-apple-client-id",
storageLocation: "local_storage", // "local_storage" | "chrome_local" | "chrome_sync"
appOrigin: "https://utxos.dev", // optional, defaults to utxos.dev
});Configuration Options
| Option | Type | Required | Description |
|---|---|---|---|
projectId | string | Yes | Your UTXOS project ID |
googleOauth2ClientId | string | Yes | Google OAuth 2.0 client ID |
twitterOauth2ClientId | string | Yes | Twitter/X OAuth 2.0 client ID |
discordOauth2ClientId | string | Yes | Discord OAuth 2.0 client ID |
appleOauth2ClientId | string | Yes | Apple OAuth client ID |
storageLocation | StorageLocation | No | Where to store credentials (default: "local_storage") |
appOrigin | string | No | API origin (default: "https://utxos.dev") |
Authentication
OAuth Sign-In
Initiate OAuth sign-in with a social provider:
provider.signInWithProvider(
"google", // "google" | "twitter" | "discord" | "apple"
"https://yourapp.com/callback", // redirect URL after auth
(authorizationUrl) => {
// Redirect user to the authorization URL
window.location.href = authorizationUrl;
}
);Handle OAuth Callback
On your callback page, handle the authentication response:
// Call this on your /auth/mesh callback page
const result = await provider.handleAuthenticationRoute();
if (result?.error) {
console.error("Authentication failed:", result.error);
}
// On success, user is redirected to your specified redirect URLEmail OTP Authentication
Send and verify email OTP codes:
// Step 1: Send OTP to email
const { error: sendError } = await provider.sendEmailOtp("user@example.com");
if (sendError) {
console.error("Failed to send OTP:", sendError);
return;
}
// Step 2: Verify OTP code
const { data: user, error: verifyError } = await provider.verifyEmailOtp(
"user@example.com",
"123456" // 6-digit OTP code
);
if (verifyError) {
console.error("Invalid OTP:", verifyError);
return;
}
console.log("Authenticated user:", user);Get Current User
Check if user is authenticated and get their info:
const { data: user, error } = await provider.getUser();
if (error) {
if (error instanceof NotAuthenticatedError) {
console.log("User not logged in");
} else if (error instanceof SessionExpiredError) {
console.log("Session expired, please log in again");
}
return;
}
console.log("User ID:", user.id);
console.log("Provider:", user.provider); // "google", "twitter", etc.
console.log("Email:", user.email);
console.log("Username:", user.username);
console.log("Avatar:", user.avatarUrl);Wallet Management
Create Wallet
Create a new wallet with Shamir’s Secret Sharing:
// Generate encryption keys for device and recovery shards
const deviceKey = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
const recoveryKey = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
const { data, error } = await provider.createWallet(
deviceKey,
recoveryKey,
"What is your pet's name?", // optional recovery question
undefined // optional WebAuthn credential ID for passkey auth
);
if (error) {
console.error("Wallet creation failed:", error);
return;
}
console.log("Wallet ID:", data.walletId);
console.log("Device ID:", data.deviceId);The wallet uses 2-of-3 Shamir’s Secret Sharing. The three shards are:
- Device shard: Encrypted and stored locally on this device
- Auth shard: Stored on UTXOS servers, requires authentication to access
- Recovery shard: Encrypted and stored on UTXOS servers for wallet recovery
Get Wallets
Retrieve all wallets for the authenticated user on this device:
const { data: wallets, error } = await provider.getWallets();
if (error) {
console.error("Failed to get wallets:", error);
return;
}
wallets.forEach((wallet) => {
console.log("Wallet ID:", wallet.walletId);
console.log("Device ID:", wallet.deviceId);
console.log("Auth Shard:", wallet.authShard);
console.log("Local Shard:", wallet.localShard);
});Check Server Wallets
Check all wallets registered on the server (without local shard):
const { data: serverWallets, error } = await provider.checkNonCustodialWalletsOnServer();
if (error) {
console.error("Failed to check server wallets:", error);
return;
}
console.log("Wallets on server:", serverWallets);Recover Wallet
Recover access to a wallet on a new device:
const { data, error } = await provider.performRecovery(
"wallet-id-to-recover",
recoveryShardEncryptionKey, // key to decrypt recovery shard
newDeviceShardEncryptionKey // key for new device shard
);
if (error) {
console.error("Recovery failed:", error);
return;
}
console.log("Wallet recovered, full key available");Error Types
The provider exports specific error classes for handling different failure scenarios:
import {
NotAuthenticatedError,
SessionExpiredError,
WalletServerRetrievalError,
WalletServerCreationError,
AuthRouteError,
StorageRetrievalError,
StorageInsertError,
} from "@utxos/sdk";
// Example error handling
const { data, error } = await provider.getUser();
if (error instanceof NotAuthenticatedError) {
// Redirect to login
} else if (error instanceof SessionExpiredError) {
// Refresh authentication
} else if (error instanceof WalletServerRetrievalError) {
// Handle server communication failure
}| Error Class | When Thrown |
|---|---|
NotAuthenticatedError | User is not logged in |
SessionExpiredError | JWT has expired |
WalletServerRetrievalError | Failed to fetch wallet data from server |
WalletServerCreationError | Failed to create wallet on server |
AuthRouteError | OAuth callback handling failed |
StorageRetrievalError | Failed to read from local storage |
StorageInsertError | Failed to write to local storage |
TypeScript Types
import type {
Web3NonCustodialProviderParams,
Web3NonCustodialProviderUser,
Web3NonCustodialWallet,
StorageLocation,
CreateWalletBody,
GetWalletBody,
WalletDevice,
} from "@utxos/sdk";Key Types
type StorageLocation = "local_storage" | "chrome_local" | "chrome_sync";
type Web3NonCustodialProviderUser = {
id: string;
scopes: string[];
provider: string;
providerId: string;
avatarUrl: string | null;
email: string | null;
username: string | null;
token: string;
};
type Web3NonCustodialWallet = {
deviceId: string;
walletId: string;
authShard: string;
localShard: string;
userAgent: string | null;
webauthnCredentialId: string | null;
};Complete Example
Here’s a complete example of a custom authentication flow:
import { Web3NonCustodialProvider, NotAuthenticatedError } from "@utxos/sdk";
const provider = new Web3NonCustodialProvider({
projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
googleOauth2ClientId: process.env.GOOGLE_CLIENT_ID,
twitterOauth2ClientId: process.env.TWITTER_CLIENT_ID,
discordOauth2ClientId: process.env.DISCORD_CLIENT_ID,
appleOauth2ClientId: process.env.APPLE_CLIENT_ID,
});
async function checkAuthStatus() {
const { data: user, error } = await provider.getUser();
if (error instanceof NotAuthenticatedError) {
return { authenticated: false, user: null };
}
return { authenticated: true, user };
}
function loginWithGoogle() {
provider.signInWithProvider(
"google",
window.location.origin + "/dashboard",
(url) => { window.location.href = url; }
);
}
async function loginWithEmail(email: string, otp: string) {
// First call sendEmailOtp, then verify
const { data: user, error } = await provider.verifyEmailOtp(email, otp);
if (error) {
throw error;
}
return user;
}
async function createNewWallet() {
const deviceKey = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
const recoveryKey = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
const { data, error } = await provider.createWallet(
deviceKey,
recoveryKey,
"Security question here"
);
if (error) {
throw error;
}
// Store recoveryKey securely - user needs this for recovery!
return data;
}Chrome Extension Support
For Chrome extensions, use chrome_local or chrome_sync storage:
const provider = new Web3NonCustodialProvider({
projectId: "your-project-id",
storageLocation: "chrome_local", // Uses chrome.storage.local
// ... oauth client IDs
});This stores credentials in Chrome’s extension storage instead of localStorage, allowing the wallet to persist across browser sessions and sync across devices (with chrome_sync).