@urblock/connect-react
Pre-built React components and hooks for integrating self-custody smart accounts (ERC-4337) into your frontend. Supports passkey, EOA (MetaMask), WalletConnect, and Google/Apple OAuth login.
npm install @urblock/connect-react
# peer deps
npm install react react-dom
This package depends on @urblock/connect-core (installed automatically). For WalletConnect support, also install @walletconnect/ethereum-provider.
Quick Start
import { UrblockProvider, ConnectButton, ConnectModal } from "@urblock/connect-react";
import "@urblock/connect-react/styles.css";
function App() {
return (
<UrblockProvider
apiKey="pk_test_abc123"
chainId={11155111}
rpcUrl="https://rpc.sepolia.org"
>
<ConnectButton />
<ConnectModal />
</UrblockProvider>
);
}
Components
<UrblockProvider>
Root provider — wraps your app and manages connection state, wallet persistence, and modal.
<UrblockProvider
apiKey="pk_test_abc123" // SaaS mode (routes through urblock API)
chainId={11155111} // default: Sepolia
rpcUrl="https://rpc.sepolia.org"
theme="dark" // "light" | "dark" | "auto" (default)
walletTypes={["passkey", "eoa", "walletconnect"]}
onConnect={(wallet) => console.log("Connected:", wallet.address)}
onDisconnect={() => console.log("Disconnected")}
onError={(err) => console.error(err)}
brandConfig={{
logo: "/logo.svg",
modalTitle: "Sign In",
primaryColor: "#6366f1",
borderRadius: "16px",
fontFamily: "Inter, sans-serif",
footerText: "Powered by MyApp",
}}
>
{children}
</UrblockProvider>
Props — UrblockProviderProps
| Prop | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Urblock API key. When set, SDK runs in SaaS mode |
apiUrl | string | "https://api.urblock.com" | API base URL |
chainId | number | 11155111 | Chain ID |
rpcUrl | string | — | JSON-RPC URL. Required in standalone mode |
factoryAddress | 0x${string} | — | Factory contract. Required in standalone mode |
bundlerUrl | string | — | ERC-4337 bundler. Required in standalone mode |
rpId | string | window.location.hostname | WebAuthn Relying Party ID |
rpName | string | "urblock" | WebAuthn Relying Party name |
theme | "light" | "dark" | "auto" | "auto" | Modal theme |
walletTypes | WalletType[] | ["passkey","eoa","walletconnect"] | Enabled login methods |
walletConnectProjectId | string | — | WalletConnect Cloud project ID |
onConnect | (wallet) => void | — | Callback on connect |
onDisconnect | () => void | — | Callback on disconnect |
onError | (error) => void | — | Callback on error |
brandConfig | BrandConfig | — | Visual customization |
sessionKeyValidatorAddress | 0x${string} | — | SessionKeyValidator contract (standalone) |
BrandConfig
| Field | Type | Description |
|---|---|---|
logo | string | Logo URL for modal header |
modalTitle | string | Modal title (default: "Connect Wallet") |
primaryColor | string | CSS accent color |
primaryColorHover | string | Accent hover (auto-derived if omitted) |
borderRadius | string | CSS border radius |
fontFamily | string | CSS font family |
footerText | string | null | Footer text. Set to null to hide |
<ConnectButton>
Pre-styled connect/disconnect button. Shows address + wallet type when connected.
<ConnectButton />
<ConnectButton label="Sign In" />
<ConnectButton showType={false} className="my-btn" />
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Connect Wallet" | Button text when disconnected |
showType | boolean | true | Show wallet type badge (Passkey, EOA, WC) |
className | string | — | Additional CSS classes |
<ConnectModal>
Modal overlay for choosing a connection method. Renders passkey, EOA, WalletConnect, and OAuth options based on walletTypes config.
<ConnectModal />
<ConnectModal className="custom-modal" />
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes |
The modal is controlled by openModal() / closeModal() from the provider context. ConnectButton opens it automatically.
<ConnectAuthCard> / <ConnectMethodButton>
Building blocks for fully custom login UIs. Exported from @urblock/connect-react/auth.
import { ConnectAuthCard, ConnectMethodButton } from "@urblock/connect-react/auth";
<ConnectAuthCard
title="Welcome"
subtitle="Choose a sign-in method"
methods={
<>
<ConnectMethodButton
icon="🔑"
label="Passkey"
description="Biometric login"
onClick={() => connectWithPasskey()}
/>
<ConnectMethodButton
icon="🦊"
label="MetaMask"
description="Browser wallet"
onClick={() => connectWithEOA()}
/>
</>
}
footer="Powered by MyApp"
/>
ConnectMethodButton Props
| Prop | Type | Description |
|---|---|---|
icon | ReactNode | Button icon |
label | ReactNode | Primary label |
description | ReactNode | Secondary text |
loading | boolean | Show spinner |
ConnectAuthCard Props
| Prop | Type | Description |
|---|---|---|
title | ReactNode | Card title |
subtitle | ReactNode | Optional subtitle |
error | ReactNode | Error message |
methods | ReactNode | Login method buttons |
fallback | ReactNode | Fallback section (e.g., email login) |
fallbackLabel | ReactNode | Divider label (default: "Legacy email") |
footer | ReactNode | Footer text |
Hooks
useUrblockWallet()
Primary hook for wallet state and connection actions.
const {
status, // "disconnected" | "connecting" | "connected" | "error"
wallet, // ConnectedWallet | null
address, // `0x${string}` | null (shortcut)
isConnected, // boolean
isConnecting, // boolean
isSaasMode, // boolean
chainId, // number
activeChainId, // number (may differ after switchChain)
error, // Error | null
openModal, // () => void
closeModal, // () => void
connectWithPasskey, // () => Promise<void>
connectWithEOA, // () => Promise<void>
switchChain, // (chainId: number) => Promise<void>
disconnect, // () => Promise<void>
} = useUrblockWallet();
ConnectedWallet
| Field | Type | Description |
|---|---|---|
address | `0x${string}` | Smart account address |
walletType | WalletType | "passkey" | "eoa" | "walletconnect" | "google" | "apple" |
chainId | number | Chain ID |
signers | SignerInfo[] | Active signers |
passkey | WebAuthnCredential | Present if passkey auth |
eoaAddress | `0x${string}` | Present if EOA/WC auth |
useSignMessage()
Sign an arbitrary message with the connected account.
const { signMessage, isLoading, signature, error, reset } = useSignMessage();
const sig = await signMessage("Hello, world!");
// sig: `0x${string}` | null
| Return | Type | Description |
|---|---|---|
signMessage | (message: string) => Promise<0x… | null> | Sign a message |
isLoading | boolean | Loading state |
signature | `0x${string}` | null | Last signature |
error | Error | null | Last error |
reset | () => void | Reset state |
useSignTypedData()
Sign EIP-712 typed data.
const { signTypedData, isLoading, signature, error, reset } = useSignTypedData();
const sig = await signTypedData({
domain: { name: "MyApp", version: "1", chainId: 11155111 },
types: { Greeting: [{ name: "text", type: "string" }] },
primaryType: "Greeting",
message: { text: "Hello" },
});
Same return shape as useSignMessage.
useSendUserOp()
Build, sign, and submit ERC-4337 UserOperations.
const { sendTransaction, sendUserOp, isLoading, userOpHash, error, reset } = useSendUserOp();
// High-level: pass an array of calls, hook builds + signs + submits
const hash = await sendTransaction([
{ target: "0xRecipient...", value: 1000000000000000n }, // send ETH
{ target: "0xToken...", data: "0xa9059cbb000000000000000000000000..." }, // ERC-20 transfer
]);
// Low-level: pass a pre-built PackedUserOperation
const hash2 = await sendUserOp(preBuiltUserOp);
| Return | Type | Description |
|---|---|---|
sendTransaction | (calls: Call[]) => Promise<0x… | null> | Build + sign + submit |
sendUserOp | (userOp: PackedUserOperation) => Promise<0x… | null> | Submit pre-built op |
isLoading | boolean | Loading state |
userOpHash | `0x${string}` | null | Last UserOp hash |
error | Error | null | Last error |
reset | () => void | Reset state |
Call
interface Call {
target: `0x${string}`; // contract address
value?: bigint; // wei
data?: `0x${string}`; // calldata
}
useBalance()
Read native + ERC-20 balances for the connected account. Supports auto-polling.
const { balance, isLoading, error, refetch } = useBalance({
tokenAddresses: ["0xUSDC...", "0xDAI..."],
pollInterval: 10_000, // refresh every 10s
});
if (balance) {
console.log(`${balance.native.formatted} ${balance.native.symbol}`);
balance.tokens.forEach((t) => console.log(`${t.formatted} ${t.symbol}`));
}
| Option | Type | Default | Description |
|---|---|---|---|
tokenAddresses | `0x${string}`[] | [] | ERC-20 contracts to fetch |
nativeSymbol | string | "ETH" | Override native symbol |
pollInterval | number | 0 | Auto-refresh ms (0 = disabled) |
enabled | boolean | true | Enable fetch on mount |
| Return | Type | Description |
|---|---|---|
balance | BalanceResult | null | { native, tokens[] } |
isLoading | boolean | Loading state |
error | Error | null | Last error |
refetch | () => Promise<void> | Manual refetch |
useSessionKey()
Create scoped session keys for delegated access.
const { createSessionKey, isLoading, sessionKey, error, reset } = useSessionKey();
const key = await createSessionKey({
validDuration: 3600, // 1 hour
allowedTargets: ["0xTokenContract..."], // restrict to this contract
spendLimit: 1000000000000000000n, // 1 ETH max
label: "game-session", // optional label
});
if (key) {
console.log(key.sessionKeyId, key.key, key.validUntil);
}
CreateSessionKeyParams
| Field | Type | Description |
|---|---|---|
validDuration | number | Duration in seconds |
allowedTargets | `0x${string}`[] | Contracts the key can call |
spendLimit | bigint | Max ETH spend in wei |
label | string | Human label |
useSiwe()
Sign In With Ethereum (EIP-4361) authentication.
const { signIn, isLoading, isAuthenticated, token, error, reset } = useSiwe();
const success = await signIn({
statement: "Sign in to MyApp",
resources: ["https://myapp.com/tos"],
});
if (success) {
// `token` contains session token (SaaS mode) or "standalone-verified"
console.log("Authenticated!", token);
}
| Return | Type | Description |
|---|---|---|
signIn | (options?) => Promise<boolean> | Perform SIWE flow |
isAuthenticated | boolean | Auth state |
token | string | null | Session token |
isLoading / error / reset | — | Standard hook state |
useRecovery()
On-chain social recovery for the connected smart account.
const { installRecoveryAction, executeRecovery, isLoading, error, reset } = useRecovery();
// Step 1: Install the RecoveryAction module
const installAction = await installRecoveryAction("0xRecoveryActionContract...");
// installAction.callData → submit via useSendUserOp
// Step 2: Execute recovery (rotate the validator key)
const recoveryAction = await executeRecovery({
validatorAddress: "0xWebAuthnValidator...",
newOwnerKeyData: "0x...", // ABI-encoded new P256 or ECDSA key
});
// recoveryAction.callData → submit via guardian
| Return | Type | Description |
|---|---|---|
installRecoveryAction | (address) => Promise<RecoveryAction | null> | Encode install callData |
executeRecovery | (params) => Promise<RecoveryAction | null> | Encode recovery callData |
useOAuthLogin()
Google/Apple OAuth login with automatic passkey creation for new users.
Requires SaaS mode (apiKey must be set in UrblockProvider).
const { loginWithOAuth, checkSafetyNet, loading, error, oauthResult } = useOAuthLogin();
// After obtaining an id_token from Google Identity Services:
await loginWithOAuth("google", googleIdToken);
// Check account safety
const safety = await checkSafetyNet();
if (safety && !safety.has_recovery) {
console.warn("Set up recovery for your account");
}
Flow for new users:
- DApp gets
id_tokenfrom Google/Apple - Hook verifies token with urblock API
- Automatically creates a passkey + smart account
- Links OAuth identity to the account
- User is connected — zero friction
| Return | Type | Description |
|---|---|---|
loginWithOAuth | (provider, idToken) => Promise<void> | Verify + connect |
checkSafetyNet | () => Promise<SafetyNetResponse | null> | Check account security |
loading | boolean | Loading state |
error | Error | null | Last error |
oauthResult | OAuthVerifyResponse | null | Verification result |
Modes
SaaS Mode
Pass apiKey to UrblockProvider. The SDK routes all operations (account creation, relay, session keys, passkeys, OAuth) through the urblock API. No factory/bundler/validator addresses needed.
<UrblockProvider apiKey="pk_test_abc123" chainId={11155111} rpcUrl="...">
Standalone Mode
Omit apiKey. You must provide factoryAddress, bundlerUrl, and rpcUrl. Account creation and UserOp submission happen directly on-chain.
<UrblockProvider
chainId={11155111}
rpcUrl="https://rpc.sepolia.org"
factoryAddress="0xYourFactory..."
bundlerUrl="https://your-bundler.example.com"
>
CSS
Import the built-in stylesheet for pre-styled components:
import "@urblock/connect-react/styles.css";
All CSS classes are prefixed with ub-. Override CSS variables for theming:
:root {
--ub-accent: #6366f1;
--ub-accent-hover: #4f46e5;
--ub-radius: 12px;
--ub-font: "Inter", sans-serif;
}
Or use the brandConfig prop on UrblockProvider for JS-based theming.
Advanced
UrblockContext
For advanced use cases, access the raw context value:
import { UrblockContext } from "@urblock/connect-react";
import { useContext } from "react";
const ctx = useContext(UrblockContext);
UrblockApiClient
Low-level API client used internally. Available for custom integrations:
import { UrblockApiClient } from "@urblock/connect-react";
const client = new UrblockApiClient({
apiKey: "pk_test_abc123",
apiUrl: "https://api.urblock.com",
});
const account = await client.createAccount({ ... });