React Integration
Kevo's React bindings provide a pre-built modal, hooks for every operation, and a context provider that manages session and project configuration automatically.
KevoProvider
KevoProvider is the root context for all Kevo hooks and components. It must wrap any component that uses Kevo's hooks.
import { KevoProvider } from '@kevo-ws/sdk/react'
function App() {
return (
<KevoProvider
config={{
publishableKey: 'pk_live_xxxxxxxxxxxx',
// Multi-chain EVM (recommended):
defaultChainId: 1,
chains: {
1: 'https://eth-mainnet.infura.io/v3/YOUR_KEY',
8453: 'https://base-mainnet.infura.io/v3/YOUR_KEY',
137: 'https://polygon-mainnet.infura.io/v3/YOUR_KEY',
},
// Single-chain shorthand (simple projects):
// evmRpcUrl: 'https://eth.llamarpc.com',
solanaRpcUrl: 'https://api.mainnet-beta.solana.com',
}}
>
<YourApp />
</KevoProvider>
)
}| Prop | Type | Description |
|---|---|---|
config* | KevoConfig | Configuration object (see below). |
KevoConfig fields:
| Prop | Type | Description |
|---|---|---|
publishableKey* | string | Your project's publishable key from the Kevo Portal. |
apiUrl | string | Base URL of the Kevo API. |
iframeUrl | string | URL of the signing iframe. |
iframeTimeoutMs | number | Timeout in ms for iframe signing operations. |
chains | Record<number, string> | Multi-chain EVM RPC map — chainId → JSON-RPC URL. Enables useChain() and auto-RPC resolution. |
defaultChainId | number | Active chain on startup. Defaults to the first key in chains. |
evmRpcUrl | string | Single-chain fallback RPC. Ignored when chains is set. Kept for backwards compatibility. |
solanaRpcUrl | string | Default Solana JSON-RPC URL. Used by sendSolanaTransaction, getSolanaBalance, etc. |
uiConfig | Partial<UIConfig> | Override UI configuration. Merged with project-level config from the portal. |
KevoModal
KevoModal is the pre-built sign-in modal. Place it once anywhere inside the provider. It renders as nothing until useKevoModal().open() is called.
import { KevoModal } from '@kevo-ws/sdk/react'
// Place once inside KevoProvider
function RootLayout({ children }) {
return (
<>
<KevoModal />
{children}
</>
)
}The modal automatically reads your project's enabledAuthMethods and enabledChains from the Kevo Portal and shows only the relevant options.
Controlling the modal
import { useKevoModal } from '@kevo-ws/sdk/react'
function SignInButton() {
const { open, close, isOpen } = useKevoModal()
return <button onClick={open}>Sign In</button>
}Session Management
useKevo
The main hook for session state and auth methods. isLoading is true during the initial session restoration from storage.
import { useKevo } from '@kevo-ws/sdk/react'
function Profile() {
const { isAuthenticated, isLoading, session, logout } = useKevo()
if (isLoading) return <Spinner />
if (!isAuthenticated) return <p>Not signed in</p>
return (
<div>
<p>User ID: {session?.userId}</p>
<button onClick={logout}>Sign Out</button>
</div>
)
}| Prop | Type | Description |
|---|---|---|
isAuthenticated | boolean | True when the user has a valid session. |
isLoading | boolean | True during initial session restoration. |
session | KevoSession | null | Active session object, or null. |
enabledChains | SupportedChain[] | The project's enabled chains (e.g. ['evm', 'solana']). |
logout | () => Promise<void> | Sign out and clear session. |
sendEmailOtp | (email: string) => Promise<void> | Send OTP to email (programmatic auth). |
verifyEmailOtp | (email: string, code: string) => Promise<void> | Verify OTP code. |
loginWithGoogle | () => void | Open Google OAuth popup. |
getDelegation | () => Promise<KevoDelegation | null> | Check delegation status. |
grantDelegation | (opts?) => Promise<KevoDelegation> | Grant backend signing delegation. |
revokeDelegation | () => Promise<void> | Revoke active delegation. |
Sign out
import { useKevo } from '@kevo-ws/sdk/react'
function SignOutButton() {
const { logout } = useKevo()
return <button onClick={logout}>Sign Out</button>
}Complete Example
A minimal but complete authenticated app with wallet display and transaction sending:
import {
KevoProvider, KevoModal,
useKevoModal, useKevo, useWallets, useSignTransaction, useBalance,
} from '@kevo-ws/sdk/react'
import { nativeTransfer } from '@kevo-ws/sdk/helpers'
function App() {
return (
<KevoProvider config={{
publishableKey: 'pk_live_xxxxxxxxxxxx',
evmRpcUrl: 'https://eth.llamarpc.com',
}}>
<KevoModal />
<MainPage />
</KevoProvider>
)
}
function MainPage() {
const { isAuthenticated, isLoading } = useKevo()
const { open } = useKevoModal()
if (isLoading) return <div>Loading…</div>
if (!isAuthenticated) return <button onClick={open}>Sign In</button>
return <Dashboard />
}
function Dashboard() {
const { evmWallet } = useWallets()
const { sendTransaction, isLoading, error } = useSignTransaction()
const { balance } = useBalance()
const handleSend = async () => {
if (!evmWallet) return
const txHash = await sendTransaction(
nativeTransfer({
to: '0xRecipientAddress',
value: 1_000_000_000_000_000n, // 0.001 ETH
chainId: 1,
})
)
console.log('TX hash:', txHash)
}
return (
<div>
<p>EVM address: {evmWallet?.address}</p>
<p>Balance: {balance !== null
? (Number(balance) / 1e18).toFixed(6) + ' ETH'
: 'Loading…'}</p>
{error && <p style={{ color: 'red' }}>{error.message}</p>}
<button onClick={handleSend} disabled={isLoading || !evmWallet}>
{isLoading ? 'Sending…' : 'Send 0.001 ETH'}
</button>
</div>
)
}KevoProvider. If you call a hook outside the provider, it throws an error with a descriptive message.