# 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' ```