npm package: @kevo-ws/sdk
Description: Embedded wallet infrastructure for web applications. Drop in authentication (email OTP, Google, X, Apple), automatic EVM and Solana wallet creation, and signing — all without users ever managing seed phrases.
Latest version: 0.1.3

# @kevo-ws/sdk

Embedded wallet infrastructure for web applications. Drop in authentication (email OTP, Google, X, Apple), automatic EVM and Solana wallet creation, and signing — all without users ever managing seed phrases.

## Table of contents

- [@kevo-ws/sdk](#kevo-wssdk)
  - [Table of contents](#table-of-contents)
  - [Installation](#installation)
  - [Quick start (React)](#quick-start-react)
  - [Authentication](#authentication)
    - [Email OTP](#email-otp)
    - [Social login](#social-login)
    - [Session state](#session-state)
  - [Wallets](#wallets)
  - [Signing](#signing)
    - [Sign a message](#sign-a-message)
    - [Sign typed data (EIP-712)](#sign-typed-data-eip-712)
    - [Sign a Solana transaction](#sign-a-solana-transaction)
    - [Signing confirmation UI](#signing-confirmation-ui)
  - [Sending transactions](#sending-transactions)
    - [EVM](#evm)
    - [Solana](#solana)
    - [Default RPC URL](#default-rpc-url)
  - [Balances](#balances)
  - [Key export](#key-export)
  - [Delegation (server-side signing)](#delegation-server-side-signing)
    - [1. User grants delegation (frontend)](#1-user-grants-delegation-frontend)
    - [2. Server signs on behalf of user](#2-server-signs-on-behalf-of-user)
  - [Server SDK](#server-sdk)
    - [Users](#users)
    - [Delegations](#delegations)
  - [Webhooks](#webhooks)
    - [Verify webhook signature](#verify-webhook-signature)
    - [Available events](#available-events)
  - [Transaction helpers](#transaction-helpers)
    - [EVM](#evm-1)
    - [Solana](#solana-1)
  - [UI customisation](#ui-customisation)
    - [`<KevoDashboard>`](#kevodashboard)
  - [Vanilla JS / framework-agnostic](#vanilla-js--framework-agnostic)
  - [Configuration reference](#configuration-reference)
    - [`KevoConfig` (client)](#kevoconfig-client)
    - [`KevoAdminConfig` (server)](#kevoadminconfig-server)
  - [TypeScript reference](#typescript-reference)
  - [Support](#support)

---

## Installation

```bash
npm install @kevo-ws/sdk
```

React and React DOM are optional peer dependencies — only required if you use the React bindings.

---

## Quick start (React)

```tsx
// main.tsx
import { KevoProvider } from '@kevo-ws/sdk/react'

root.render(
  <KevoProvider config={{
    publishableKey: 'pk_live_...',
    evmRpcUrl: 'https://mainnet.infura.io/v3/YOUR_KEY',
    solanaRpcUrl: 'https://api.mainnet-beta.solana.com',
  }}>
    <App />
  </KevoProvider>
)
```

```tsx
// App.tsx
import { KevoModal } from '@kevo-ws/sdk/react'
import { useKevo, useWallets } from '@kevo-ws/sdk/react'

export function App() {
  const { isAuthenticated, isLoading } = useKevo()
  const { evmWallet, solanaWallet } = useWallets()

  if (isLoading) return <p>Loading…</p>
  if (!isAuthenticated) return <KevoModal />

  return (
    <div>
      <p>EVM: {evmWallet?.address}</p>
      <p>Solana: {solanaWallet?.address}</p>
    </div>
  )
}
```

`<KevoModal>` handles the full auth flow (email OTP + social login) with zero configuration. Style it via the portal's UI Config panel.

---

## Authentication

### Email OTP

```tsx
import { useKevo } from '@kevo-ws/sdk/react'

function LoginForm() {
  const { sendEmailOtp, verifyEmailOtp } = useKevo()
  const [step, setStep] = useState<'email' | 'otp'>('email')
  const [email, setEmail] = useState('')

  const handleSendOtp = async () => {
    await sendEmailOtp(email)
    setStep('otp')
  }

  const handleVerify = async (code: string) => {
    await verifyEmailOtp(email, code)
    // isAuthenticated becomes true, wallet is created automatically
  }
  // ...
}
```

### Social login

```tsx
const { loginWithGoogle, loginWithX, loginWithApple } = useKevo()

<button onClick={loginWithGoogle}>Continue with Google</button>
<button onClick={loginWithX}>Continue with X</button>
<button onClick={loginWithApple}>Continue with Apple</button>
```

Social auth opens a popup/redirect to the OAuth provider and returns with an active session.

### Session state

```tsx
const {
  isAuthenticated,   // boolean
  isLoading,         // true during initial session restore
  session,           // { userId, did, accessToken, expiresAt, projectId } | null
  logout,
} = useKevo()
```

---

## Wallets

Wallets are created automatically on first sign-in. Each user gets one EVM wallet and one Solana wallet depending on which chains are enabled in your project.

```tsx
import { useWallet, useSolanaWallet, useWallets } from '@kevo-ws/sdk/react'

// Single-chain
const { wallet } = useWallet()           // { id, address, createdAt } | null
const { solanaWallet } = useSolanaWallet()

// Both chains at once
const { evmWallet, solanaWallet, enabledChains } = useWallets()
```

Wallet addresses persist across sessions — the same user always gets the same address.

---

## Signing

### Sign a message

```tsx
import { useSignMessage } from '@kevo-ws/sdk/react'

const { signMessage, isLoading, error } = useSignMessage()

// Auto-routes to EVM or Solana based on your project's enabled chains
const signature = await signMessage('Hello Kevo')

// Multi-chain project — specify explicitly
const sig = await signMessage('Hello', { chain: 'solana' })
```

### Sign typed data (EIP-712)

```tsx
import { useSignTypedData } from '@kevo-ws/sdk/react'

const { signTypedData } = useSignTypedData()

const signature = await signTypedData({
  domain: { name: 'MyApp', version: '1', chainId: 1 },
  types: { Order: [{ name: 'amount', type: 'uint256' }] },
  primaryType: 'Order',
  message: { amount: 1000 },
})
```

### Sign a Solana transaction

```tsx
import { useSignSolanaTransaction } from '@kevo-ws/sdk/react'
import { solTransfer, getRecentBlockhash } from '@kevo-ws/sdk'

const { signSolanaTransaction, sendSolanaTransaction } = useSignSolanaTransaction()
const { solanaWallet } = useSolanaWallet()

const blockhash = await getRecentBlockhash('https://api.mainnet-beta.solana.com')
const txBytes = solTransfer({
  from: solanaWallet!.address,
  to: 'RecipientBase58...',
  lamports: 10_000_000,  // 0.01 SOL
  recentBlockhash: blockhash,
})

// Sign only (returns hex signature)
const sigHex = await signSolanaTransaction(txBytes)

// Sign + broadcast (returns base58 tx signature)
const txSig = await sendSolanaTransaction(txBytes, 'https://api.mainnet-beta.solana.com')
```

### Signing confirmation UI

By default a confirmation modal appears before each signing operation, showing the user what they are about to sign. Mount `<KevoSigningConfirmation>` anywhere in your tree to render it:

```tsx
import { KevoSigningConfirmation } from '@kevo-ws/sdk/react'

// Place it at the root level so it can render as an overlay
<KevoSigningConfirmation />
```

To skip confirmations entirely (e.g. for automated flows), set `hideSigningConfirmations: true` in your project's UI Config in the portal.

---

## Sending transactions

### EVM

```tsx
import { useSignTransaction } from '@kevo-ws/sdk/react'
import { erc20Transfer } from '@kevo-ws/sdk'

const { sendTransaction } = useSignTransaction()

// Native ETH transfer
await sendTransaction(
  { to: '0xRecipient...', value: 100_000_000_000_000_000n, chainId: 1 },
  'https://mainnet.infura.io/v3/YOUR_KEY'
)

// ERC-20 transfer — build the calldata with a helper
const tx = erc20Transfer({
  token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',  // USDC
  to: '0xRecipient...',
  amount: 1_000_000n,  // 1 USDC (6 decimals)
  chainId: 1,
})
await sendTransaction(tx, 'https://mainnet.infura.io/v3/YOUR_KEY')
```

### Solana

```tsx
const { sendSolanaTransaction } = useSignTransaction()

await sendSolanaTransaction(
  txBytes,
  'https://api.mainnet-beta.solana.com',
  { chain: 'solana' }
)
```

### Default RPC URL

Set a default RPC URL in the provider config so you never have to pass it per-call:

```tsx
<KevoProvider config={{
  publishableKey: 'pk_live_...',
  evmRpcUrl: 'https://mainnet.infura.io/v3/YOUR_KEY',
  solanaRpcUrl: 'https://api.mainnet-beta.solana.com',
}}>
```

---

## Balances

```tsx
import { useBalance, useTokenBalance, useSolanaBalance } from '@kevo-ws/sdk/react'

// Native ETH balance (wei, auto-refreshes every 15s)
const { balance } = useBalance()

// ERC-20 token balance
const { balance } = useTokenBalance('0xA0b8...USDC')

// SOL balance (lamports)
const { balance } = useSolanaBalance()

// Custom poll interval (ms) or disable polling
const { balance, refetch } = useBalance(undefined, 60_000)
const { balance } = useBalance(undefined, 0)  // no polling
```

All balance hooks return `{ balance: bigint | null, isLoading, error, refetch }`.

---

## Key export

Users can export their raw private key. The key is displayed inside a Kevo-controlled iframe — it is never accessible from your application's JavaScript context.

```tsx
import { useExportKey, useSolanaExportKey } from '@kevo-ws/sdk/react'

function ExportButton() {
  const { requestExport, confirmExport } = useExportKey()      // EVM
  // const { requestExport, confirmExport } = useSolanaExportKey()  // Solana

  const handleExport = async () => {
    // Step 1: sends OTP to user's email, returns masked email address
    const maskedEmail = await requestExport()
    const code = prompt(`Enter the OTP sent to ${maskedEmail}`)

    // Step 2: verifies OTP — private key is shown inside the secure iframe
    await confirmExport(code!)
  }

  return <button onClick={handleExport}>Export private key</button>
}
```

---

## Delegation (server-side signing)

Delegation lets your backend sign transactions on behalf of a user without user interaction. The user grants permission once from the frontend; your server can then sign at any time within the policies you define.

### 1. User grants delegation (frontend)

```tsx
import { useKevo } from '@kevo-ws/sdk/react'

const { grantDelegation, revokeDelegation, getDelegation } = useKevo()

// Grant with optional policy restrictions
await grantDelegation({
  include: 'evm',          // 'evm' | 'solana' | 'both'
  policies: {
    expiresAt: '2025-12-31T23:59:59Z',
    maxTxCount: 100,
    allowedChainIds: [8453, 1],      // Base + Ethereum
    allowedContracts: ['0xRouter…'], // only these contracts
    maxAmountWei: '1000000000000000000', // max 1 ETH per tx
  },
})

// Check delegation status
const delegation = await getDelegation()
console.log(delegation?.active, delegation?.txCount)

// Revoke
await revokeDelegation()
```

### 2. Server signs on behalf of user

```ts
import { KevoAdmin } from '@kevo-ws/sdk/server'

const admin = new KevoAdmin({
  secretKey: process.env.KEVO_SECRET_KEY!,
  evmRpcUrl: 'https://mainnet.infura.io/v3/YOUR_KEY',
})

// Sign + broadcast an EVM transaction (auto-fills nonce, gas, fees)
const txHash = await admin.delegations.sendTransaction(userId, {
  to: '0xRecipient...',
  value: '0x2386f26fc10000',  // 0.01 ETH in hex
  chainId: 8453,
})

// Sign + broadcast a Solana transaction
const txSig = await admin.delegations.sendSolanaTransaction(
  userId,
  { message: txMessageBytes },
  'https://api.mainnet-beta.solana.com',
)
```

Delegation policies are enforced server-side before every sign call.

---

## Server SDK

`@kevo-ws/sdk/server` provides the `KevoAdmin` class for backend use (Node.js, Bun, Deno, Edge functions).

```ts
import { KevoAdmin } from '@kevo-ws/sdk/server'

const admin = new KevoAdmin({
  secretKey: process.env.KEVO_SECRET_KEY!,
  evmRpcUrl: '...',      // optional default
  solanaRpcUrl: '...',   // optional default
})
```

### Users

```ts
// List all users (paginated)
const { users } = await admin.users.list({ limit: 50, offset: 0 })

// Get a single user with wallet + delegation info
const { user } = await admin.users.get('user-uuid')
// user.evmWallet.address, user.solanaWallet.address, user.delegation

// Force sign-out all sessions for a user
await admin.users.revokeSessions('user-uuid')
```

### Delegations

```ts
// List active delegations in your project
const { delegations } = await admin.delegations.list()

// Low-level: sign a raw 32-byte hash (EVM)
const { signature } = await admin.delegations.signEvm('user-uuid', {
  hash: 'a3f1e0d2...64hexchars',   // keccak256 hash, no 0x prefix
  chainId: 8453,
  to: '0xContract...',
  value: '0',
})

// Low-level: sign a Solana transaction message
const { signature } = await admin.delegations.signSolana('user-uuid', {
  message: Buffer.from(txMessage).toString('base64'),
})
```

---

## Webhooks

Kevo sends signed webhook events to your endpoint when users authenticate, wallets are created, transactions are submitted, and more.

### Verify webhook signature

```ts
import { verifyWebhookSignature } from '@kevo-ws/sdk/server'
import type { WebhookPayload } from '@kevo-ws/sdk/server'

// Express / Fastify / any Node framework
app.post('/webhooks/kevo', (req, res) => {
  const isValid = verifyWebhookSignature(
    req.body,                                // raw body string — do NOT parse before this
    req.headers['x-kevo-signature'],         // 'sha256=abc123...'
    process.env.KEVO_WEBHOOK_SECRET!,
  )

  if (!isValid) return res.status(401).send('Invalid signature')

  const payload = JSON.parse(req.body) as WebhookPayload
  // payload.event: 'user.created' | 'user.authenticated' | 'wallet.created' | ...
  // payload.data: { userId, method, ... }
  // payload.timestamp: unix ms

  res.sendStatus(200)
})
```

### Available events

| Event | Fired when |
|---|---|
| `user.created` | New user signs up |
| `user.authenticated` | Any successful sign-in |
| `wallet.created` | EVM or Solana wallet generated |
| `wallet.recovery_setup` | User sets up recovery |
| `wallet.recovered` | Wallet recovered |
| `private_key.exported` | User exports their private key |
| `signing.completed` | A signing request completes |
| `transaction.submitted` | A transaction is broadcast |

Configure webhooks and get the secret from the Kevo Portal → your project → Webhooks.

---

## Transaction helpers

Zero-dependency helpers for building transaction calldata. Import from `@kevo-ws/sdk` (client) or `@kevo-ws/sdk/helpers`.

### EVM

```ts
import { erc20Transfer, erc20Approve, nativeTransfer, encodeFunctionCall } from '@kevo-ws/sdk'

// ERC-20 transfer
const tx = erc20Transfer({ token: '0xUSDC…', to: '0xRecipient…', amount: 1_000_000n, chainId: 1 })

// ERC-20 approve
const tx = erc20Approve({ token: '0xUSDC…', spender: '0xRouter…', amount: 2n ** 256n - 1n, chainId: 1 })

// Native ETH transfer
const tx = nativeTransfer({ to: '0xRecipient…', value: 100_000_000_000_000_000n, chainId: 1 })

// Generic contract call (address, uint256, bool, bytes32 params)
const tx = encodeFunctionCall({
  to: '0xContract…',
  functionSignature: 'stake(address,uint256)',
  params: [
    { type: 'address', value: '0xToken…' },
    { type: 'uint256', value: 500_000_000n },
  ],
  chainId: 8453,
})
```

### Solana

```ts
import {
  solTransfer,
  splTokenTransfer,
  getAssociatedTokenAddress,
  assembleSolanaTransaction,
  getRecentBlockhash,
  broadcastSolanaTransaction,
  base58Encode, base58Decode,
} from '@kevo-ws/sdk'

// Fetch recent blockhash
const blockhash = await getRecentBlockhash('https://api.mainnet-beta.solana.com')

// Native SOL transfer
const msgBytes = solTransfer({
  from: solanaWallet.address,
  to: 'RecipientBase58…',
  lamports: 10_000_000,   // 0.01 SOL
  recentBlockhash: blockhash,
})

// SPL token transfer
const ataFrom = await getAssociatedTokenAddress(solanaWallet.address, mintAddress)
const ataTo   = await getAssociatedTokenAddress(recipientAddress, mintAddress)
const msgBytes = splTokenTransfer({
  from: solanaWallet.address,
  sourceAta: ataFrom,
  destinationAta: ataTo,
  mint: mintAddress,
  amount: 1_000_000n,     // 1 USDC (6 decimals)
  recentBlockhash: blockhash,
})

// Broadcast an already-signed transaction
const txSig = await broadcastSolanaTransaction(signedTxBytes, 'https://api.mainnet-beta.solana.com')
```

---

## UI customisation

Kevo's modal and dashboard are fully themeable from the portal (Project → Settings → Appearance), or from code by passing a `uiConfig` override. The following tokens are supported:

| Token | Description | Default |
|---|---|---|
| `accentColor` | Buttons, links, focus rings | `#111827` |
| `backgroundColor` | Modal background | `#ffffff` |
| `foregroundColor` | Text | `#111827` |
| `borderRadius` | Corner radius in px | `12` |
| `logoUrl` | Logo shown at top of modal | — |
| `fontFamily` | CSS font-family string | system-ui |
| `buttonTextColor` | Text on primary buttons | — |
| `inputBorderColor` | Input field border | — |
| `modalTitle` | Override the modal heading | "Welcome" |
| `subtitleText` | Override the subtitle | — |
| `showPoweredBy` | Show "Powered by Kevo" | `true` |
| `overlayBlur` | Backdrop blur in px | `2` |
| `modalMaxWidth` | Max modal width in px | `400` |
| `socialButtonStyle` | `'outline'` or `'filled'` | `'outline'` |
| `hideSigningConfirmations` | Skip signing confirmation modals | `false` |

### `<KevoDashboard>`

A full account management page (wallet address, auth methods, key export, etc.) that can be embedded anywhere:

```tsx
import { KevoDashboard, useKevoDashboard } from '@kevo-ws/sdk/react'

// Controlled
const { open, close, isOpen } = useKevoDashboard()

// Uncontrolled embed
<KevoDashboard />
```

---

## Vanilla JS / framework-agnostic

Use `KevoClient` directly without React:

```ts
import { KevoClient } from '@kevo-ws/sdk'

const client = new KevoClient({
  publishableKey: 'pk_live_...',
  evmRpcUrl: 'https://mainnet.infura.io/v3/YOUR_KEY',
})

// Mount the hidden signing iframe (required before any signing operation)
client.mount()

// Auth
await client.sendEmailOtp('user@example.com')
await client.verifyEmailOtp('user@example.com', '123456')

// Wallet
console.log(client.wallet?.address)       // EVM
console.log(client.solanaWallet?.address) // Solana

// Sign
const sig = await client.signMessage('hello')
const txHash = await client.sendTransaction({ to: '0x…', chainId: 1 })

// Subscribe to session changes
const unsubscribe = client.onChange(() => {
  console.log('session changed', client.session)
})

// Cleanup
client.unmount()
unsubscribe()
```

---

## Configuration reference

### `KevoConfig` (client)

| Option | Type | Default | Description |
|---|---|---|---|
| `publishableKey` | `string` | required | Your project's publishable key (`pk_live_…` or `pk_test_…`) |
| `apiUrl` | `string` | `https://api.kevo.ws` | Kevo API base URL |
| `iframeUrl` | `string` | `https://iframe.kevo.ws` | Signing iframe URL |
| `iframeTimeoutMs` | `number` | `30000` | Timeout for iframe operations |
| `evmRpcUrl` | `string` | — | Default EVM JSON-RPC endpoint |
| `solanaRpcUrl` | `string` | — | Default Solana JSON-RPC endpoint |

### `KevoAdminConfig` (server)

| Option | Type | Default | Description |
|---|---|---|---|
| `secretKey` | `string` | required | Your project's secret key (`sk_live_…`) |
| `apiUrl` | `string` | `https://api.kevo.ws` | Kevo API base URL |
| `evmRpcUrl` | `string` | — | Default EVM JSON-RPC endpoint |
| `solanaRpcUrl` | `string` | — | Default Solana JSON-RPC endpoint |

---

## TypeScript reference

All types are exported from `@kevo-ws/sdk`:

```ts
import type {
  KevoConfig,
  KevoSession,
  KevoWallet,
  KevoSolanaWallet,
  KevoAnyWallet,
  TransactionRequest,
  Eip712TypedData,
  AuthMethod,          // 'email' | 'google' | 'x' | 'apple'
  SupportedChain,      // 'evm' | 'solana'
  KevoProjectConfig,
  KevoDelegation,
  DelegationPolicies,
  GrantDelegationOptions,
  SolanaProvider,
} from '@kevo-ws/sdk'
```

Server types from `@kevo-ws/sdk/server`:

```ts
import type {
  KevoAdminConfig,
  AdminUser,
  AdminUserDetail,
  AdminDelegation,
  SignEvmOptions,
  SignEvmResult,
  SignSolanaOptions,
  SignSolanaResult,
  SendTransactionOptions,
  SendSolanaTransactionOptions,
  WebhookPayload,
  DelegationPolicies,
} from '@kevo-ws/sdk/server'
```

---

## Support

- dashboard: [portal.kevo.ws](https://dashboard.kevo.ws)
- Documentation: [docs.kevo.ws](https://docs.kevo.ws)