# Kevo SDK — LLM Reference
> Embedded wallet SDK for EVM and Solana with server-backed signing, social onboarding, gas sponsorship, and delegations.
> Package: `@kevo-ws/sdk` · Docs: https://kevo.ws/docs
---
## Security Architecture
Kevo embedded wallets use a server-backed signing architecture designed for seamless multi-device access. A user can authenticate on a new device and continue using the same embedded wallet without migrating a browser-held key share.
Kevo's browser SDK runs sensitive wallet UX inside a Kevo-controlled, cross-origin iframe. The parent application can request wallet actions, but it does not directly read iframe state or embedded wallet key material. Communication uses a typed `postMessage` contract and origin validation.
Kevo is not a classic browser-held MPC/TSS wallet for embedded wallets. It does not rely on the user's browser permanently storing a client signing share. Instead, wallet operations are routed through authenticated API checks and a hardware-isolated signer boundary.
The signer boundary runs inside a hardware-backed trusted execution environment such as AWS Nitro Enclaves. The API layer handles session validation, origin/project checks, quotas, delegation policies, and sponsorship rules. The TEE signer handles provisioning, signing, export, and key-operation isolation.
Private key export is explicit and OTP-gated. Users authenticated through providers that do not expose email may need to link an email first. Export and signing prompts are mediated through the Kevo iframe.
Security docs:
- Product overview: https://kevo.ws/product/security-model
- Technical reference: https://kevo.ws/docs/security
---
## Installation
```bash
npm install @kevo-ws/sdk
```
---
## Quick Start (React)
```tsx
import { KevoProvider, KevoModal, useKevo, useKevoModal } from '@kevo-ws/sdk/react'
function App() {
return (
)
}
```
---
## KevoConfig (all fields)
| Field | Type | Required | Description |
|---|---|---|---|
| `publishableKey` | `string` | yes | `pk_live_...` from Kevo Portal |
| `chains` | `Record` | no | Multi-chain EVM RPC map `{ chainId: rpcUrl }` |
| `defaultChainId` | `number` | no | Startup chain — must be a key in `chains` |
| `evmRpcUrl` | `string` | no | Single-chain fallback. Ignored when `chains` is set |
| `solanaRpcUrl` | `string` | no | Solana JSON-RPC URL |
| `apiUrl` | `string` | no | Default: `https://api.kevo.ws` |
| `iframeUrl` | `string` | no | Default: `https://iframe.kevo.ws` |
| `iframeTimeoutMs` | `number` | no | Default: `30000` |
| `uiConfig` | `Partial` | no | Override modal appearance |
---
## React Hooks
### useKevo — session & auth
```ts
const {
isAuthenticated, // boolean
isLoading, // boolean — true during initial session restore
session, // KevoSession | null { accessToken, userId, did, projectId, expiresAt }
userProfile, // KevoResolvedUserProfile { name, username, avatarUrl, address, raw }
rawUserProfile, // KevoUserProfile | null { email, profile, authMethods }
enabledChains, // SupportedChain[] e.g. ['evm', 'solana']
logout, // () => Promise
sendEmailOtp, // (email: string) => Promise
verifyEmailOtp, // (email: string, code: string) => Promise
loginWithGoogle, // () => void
loginWithX, // () => void
loginWithApple, // () => void
getDelegation, // () => Promise
grantDelegation, // (opts?: GrantDelegationOptions) => Promise
revokeDelegation, // () => Promise
} = useKevo()
```
### useUserProfile — display profile and email linking
```ts
const {
profile, // resolved display profile; name is shortened wallet address
rawProfile, // raw Kevo profile incl. email/authMethods
refresh,
requestEmailLink, // sends OTP to link email to current account
confirmEmailLink, // verifies OTP and links email
} = useUserProfile()
await requestEmailLink('user@example.com')
await confirmEmailLink('user@example.com', '123456')
```
Google can provide email, name, and picture. X provides username/name/profile image but not email through the standard profile endpoint. Apple can provide email/name when included by Apple. If no avatar is provided, the SDK generates a deterministic avatar from the wallet address.
### useWallets — wallet addresses
```ts
const { evmWallet, solanaWallet, isLoading } = useWallets()
// evmWallet: KevoWallet | null { id, address: '0x...', createdAt }
// solanaWallet: KevoSolanaWallet | null { id, address: 'Base58...', createdAt }
```
### useChain — multi-chain switching
```ts
const { activeChainId, setChain, chains, rpcUrl } = useChain()
// setChain(8453) — throws if chainId not in KevoConfig.chains
// activeChainId: number | null
// chains: Record (full map from config)
// rpcUrl: string | null (rpc for active chain)
```
### useSignMessage
```ts
const { signMessage, isLoading, error } = useSignMessage()
const sig = await signMessage('Hello from Kevo!') // returns hex signature
```
### useSignTypedData
```ts
const { signTypedData, isLoading, error } = useSignTypedData()
const sig = await signTypedData({ domain, types, primaryType, message })
```
### useSignTransaction — EVM
```ts
const { signTransaction, sendTransaction, isLoading, error } = useSignTransaction()
// signTransaction(tx) — returns raw signed tx hex (no broadcast)
// sendTransaction(tx, rpcUrl?) — auto-fills nonce/gas/fees, broadcasts, returns txHash
// tx.chainId is required. RPC resolved from: rpcUrl arg → chains[tx.chainId] → evmRpcUrl
```
### useSignSolanaTransaction
```ts
const { signSolanaTransaction, sendSolanaTransaction, isLoading, error } = useSignSolanaTransaction()
// signSolanaTransaction(msgBytes) — returns Ed25519 sig as hex
// sendSolanaTransaction(msgBytes, rpcUrl?) — signs + broadcasts, returns base58 txSig
```
### useSignSolanaMessage
```ts
const { signSolanaMessage, isLoading, error } = useSignSolanaMessage()
const sigHex = await signSolanaMessage('Hello Solana!') // string or Uint8Array input
```
### useBalance / useTokenBalance / useSolanaBalance
```ts
const { balance, isLoading, error, refetch } = useBalance()
// balance: bigint | null (wei). Auto-polls every 15s. Re-fetches on chain switch.
const { balance } = useTokenBalance('0xTokenAddress')
// balance: bigint | null (smallest unit)
const { balance } = useSolanaBalance()
// balance: bigint | null (lamports)
```
### useEstimateGas
```ts
const { estimateGas, isLoading, error } = useEstimateGas()
const gas: bigint = await estimateGas({ to: '0x...', value: '0x0', chainId: 1 })
```
### useKevoModal / useKevoDashboard
```ts
const { open, close, isOpen } = useKevoModal()
const { open, close, isOpen } = useKevoDashboard()
```
### useExportKey / useSolanaExportKey
```ts
const { requestExport, confirmExport, isLoading, error } = useExportKey()
const maskedEmail = await requestExport() // sends OTP email
await confirmExport('123456') // key shown in secure iframe
```
Private key export requires a verified email on the user. Users authenticated through X, Apple, passkeys, or external wallets may need to link an email first with `useUserProfile()`.
---
## EVM Helpers (`@kevo-ws/sdk/helpers`)
All helpers return a `TransactionRequest` object (not a calldata string). Pass directly to `sendTransaction()`.
```ts
import {
nativeTransfer, erc20Transfer, erc20Approve, encodeFunctionCall,
keccak256Selector,
} from '@kevo-ws/sdk/helpers'
// Native ETH transfer
const tx = nativeTransfer({ to: '0xRecipient', value: 1_000_000_000_000_000n, chainId: 1 })
// ERC-20 transfer
const tx = erc20Transfer({
token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
to: '0xRecipient', amount: 100_000_000n, chainId: 1,
})
// ERC-20 approve
const tx = erc20Approve({
token: '0xTokenAddress', spender: '0xSpender',
amount: 2n ** 256n - 1n, chainId: 1,
})
// Generic contract call — params takes { type, value } objects
const tx = encodeFunctionCall({
to: '0xContractAddress',
functionSignature: 'transfer(address,uint256)',
params: [
{ type: 'address', value: '0xRecipient' },
{ type: 'uint256', value: 1_000_000n },
],
chainId: 1,
})
// tx.data contains the encoded calldata hex string
// tx.to is the contract address
// tx.chainId is the chain ID
const txHash = await sendTransaction(tx)
```
---
## Solana Helpers (`@kevo-ws/sdk/helpers`)
```ts
import {
solTransfer, getRecentBlockhash, splTokenTransfer,
getAssociatedTokenAddress, base58Encode, base58Decode,
} from '@kevo-ws/sdk/helpers'
const blockhash = await getRecentBlockhash(rpcUrl)
const msgBytes = solTransfer({
from: 'Base58From', to: 'Base58To',
lamports: 1_000_000, recentBlockhash: blockhash,
})
const txSig = await sendSolanaTransaction(msgBytes)
```
---
## TransactionRequest (EVM)
```ts
interface TransactionRequest {
to?: string
data?: string
value?: string | bigint
chainId: number // required
nonce?: number
gasLimit?: string | bigint
maxFeePerGas?: string | bigint
maxPriorityFeePerGas?: string | bigint
gasPrice?: string | bigint
}
```
`sendTransaction()` auto-fills `nonce`, `gasLimit`, `maxFeePerGas`, `maxPriorityFeePerGas` from the RPC. Override by passing them explicitly.
---
## RPC Resolution Order (EVM)
For `sendTransaction(tx, rpcUrl?)` and `estimateGas(tx, rpcUrl?)`:
1. Explicit `rpcUrl` argument
2. `chains[tx.chainId]` from `KevoConfig`
3. `chains[activeChainId]` from `KevoConfig`
4. `evmRpcUrl` from `KevoConfig`
5. Throws `Error('No EVM RPC URL found...')`
---
## Server SDK (`@kevo-ws/sdk/server`)
```ts
import { KevoAdmin, KevoAdminError, verifyWebhookSignature } from '@kevo-ws/sdk/server'
const admin = new KevoAdmin({
secretKey: process.env.KEVO_SECRET_KEY!, // sk_...
evmRpcUrl: 'https://mainnet.base.org', // optional
solanaRpcUrl: 'https://api.mainnet-beta.solana.com', // optional
})
// Users
const { users } = await admin.users.list({ limit: 50, offset: 0 })
const { user } = await admin.users.get('user-uuid')
await admin.users.revokeSessions('user-uuid')
// Delegations — sign on behalf of users
const { delegations } = await admin.delegations.list()
// Sign EVM hash (low-level)
const { signature } = await admin.delegations.signEvm('user-uuid', {
hash: '64hexchars', chainId: 8453, to: '0x...', value: '0',
})
// Sign + broadcast EVM tx (high-level, preferred)
const txHash = await admin.delegations.sendTransaction('user-uuid', {
to: '0xRecipient', data: '0x...', value: '0x0', chainId: 8453,
})
// Sign + broadcast Solana tx
const sig = await admin.delegations.sendSolanaTransaction('user-uuid', {
message: messageUint8ArrayOrBase64,
})
```
Errors throw `KevoAdminError` with `.status` (HTTP code) and `.message`.
---
## Delegations — Client Side
```ts
interface GrantDelegationOptions {
include?: 'evm' | 'solana' | 'both' // default: 'both'
policies?: {
expiresAt?: string // ISO 8601
maxTxCount?: number
allowedChainIds?: number[] // EVM only
allowedContracts?: string[] // EVM only, case-insensitive
maxAmountWei?: string // EVM only, decimal string
}
}
const { grantDelegation, getDelegation, revokeDelegation } = useKevo()
const delegation = await grantDelegation({ include: 'evm', policies: { maxTxCount: 100 } })
```
---
## Webhooks
Kevo sends `POST` requests with JSON body and `X-Kevo-Signature: sha256=` header.
Webhooks are **at-least-once** — make your handler idempotent.
```ts
import { verifyWebhookSignature } from '@kevo-ws/sdk/server'
import type { WebhookPayload } from '@kevo-ws/sdk/server'
const isValid = verifyWebhookSignature(rawBody, signature, process.env.KEVO_WEBHOOK_SECRET!)
// WebhookPayload shape:
// { event: 'user.created' | 'user.authenticated', projectId, timestamp, data: { userId, method, address? } }
```
---
## Authentication Methods
- **Email OTP**: `sendEmailOtp(email)` → `verifyEmailOtp(email, code)`
- **Google OAuth**: `loginWithGoogle()` (popup)
- **X (Twitter) OAuth**: `loginWithX()` (popup)
- **Apple OAuth**: `loginWithApple()` (popup)
- **EVM Wallet**: `AuthEvmWallet` component / `/docs/auth/evm-wallet`
- **Solana Wallet**: `AuthSolanaWallet` component / `/docs/auth/solana-wallet`
Enabled auth methods are configured per-project in the Kevo Portal.
---
## Import Map
```ts
// Client SDK
import { KevoClient } from '@kevo-ws/sdk'
import type { KevoConfig, KevoSession, KevoWallet, KevoSolanaWallet, TransactionRequest, Eip712TypedData, UIConfig } from '@kevo-ws/sdk'
// React
import { KevoProvider, KevoModal, KevoDashboard, useKevo, useWallets, useChain, useSignMessage, useSignTypedData, useSignTransaction, useSignSolanaTransaction, useSignSolanaMessage, useBalance, useTokenBalance, useSolanaBalance, useEstimateGas, useExportKey, useSolanaExportKey, useKevoModal, useKevoDashboard } from '@kevo-ws/sdk/react'
// Helpers (zero-dependency EVM + Solana utilities)
import { nativeTransfer, erc20Transfer, erc20Approve, encodeFunctionCall, keccak256Selector, solTransfer, getRecentBlockhash, splTokenTransfer, getAssociatedTokenAddress, base58Encode, base58Decode } from '@kevo-ws/sdk/helpers'
// Server (Node.js only)
import { KevoAdmin, KevoAdminError, verifyWebhookSignature } from '@kevo-ws/sdk/server'
import type { SendTransactionOptions, SendSolanaTransactionOptions, WebhookPayload } from '@kevo-ws/sdk/server'
```