npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

kentucky-signer-viem

v0.3.0

Published

Custom Viem account integration for Kentucky Signer with passkey authentication

Downloads

50

Readme

kentucky-signer-viem

A custom Viem account integration for the Kentucky Signer service, enabling EVM transaction signing using passkey (WebAuthn) or password authentication with optional two-factor authentication (TOTP/PIN).

Features

  • Custom Viem Account - Full Viem compatibility for signing transactions, messages, and typed data
  • Multiple Authentication Methods
    • Passkey (WebAuthn) for browser environments
    • Password authentication for browser and Node.js
    • JWT token authentication for server environments
  • Secure Mode - Ephemeral key signing with client-side key generation
  • Two-Factor Authentication - TOTP (authenticator app) and PIN support
  • Guardian Recovery - Social recovery with trusted guardians
  • React Integration - Hooks and context for easy React app integration
  • TypeScript Support - Full type definitions included
  • Session Management - Automatic refresh and persistence options
  • EIP-7702 Support - Sign authorizations for EOA code delegation
  • Relayer Integration - Client for gasless transactions via relayer service
  • Intent Signing - Sign execution intents for smart account operations

Installation

npm install kentucky-signer-viem viem
# or
yarn add kentucky-signer-viem viem
# or
pnpm add kentucky-signer-viem viem

Quick Start

Browser (with Passkey)

import { createWalletClient, http, parseEther } from 'viem'
import { mainnet } from 'viem/chains'
import {
  createKentuckySignerAccount,
  authenticateWithPasskey,
} from 'kentucky-signer-viem'

// 1. Authenticate with passkey
const session = await authenticateWithPasskey({
  baseUrl: 'https://signer.example.com',
  accountId: '0123456789abcdef...', // 64-char hex account ID
})

// 2. Create Kentucky Signer account
const account = createKentuckySignerAccount({
  config: {
    baseUrl: 'https://signer.example.com',
    accountId: session.accountId,
  },
  session,
  defaultChainId: 1,
})

// 3. Create Viem wallet client
const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})

// 4. Sign and send transaction
const hash = await walletClient.sendTransaction({
  to: '0x...',
  value: parseEther('0.1'),
})

Password Authentication

import {
  authenticateWithPassword,
  createAccountWithPassword,
} from 'kentucky-signer-viem'

// Create a new account
const newAccount = await createAccountWithPassword({
  baseUrl: 'https://signer.example.com',
  password: 'your-secure-password',
  confirmation: 'your-secure-password',
})

// Or authenticate with existing account
const session = await authenticateWithPassword({
  baseUrl: 'https://signer.example.com',
  accountId: 'existing_account_id',
  password: 'your-secure-password',
})

Node.js (with JWT Token)

import { createServerAccount } from 'kentucky-signer-viem'

const account = createServerAccount(
  'https://signer.example.com',
  'account_id_hex',
  'jwt_token',
  '0xYourEvmAddress',
  1 // chainId
)

React Integration

Setup Provider

import { KentuckySignerProvider } from 'kentucky-signer-viem/react'

function App() {
  return (
    <KentuckySignerProvider
      baseUrl="https://signer.example.com"
      defaultChainId={1}
      persistSession={true}
      useEphemeralKeys={true} // Enable secure mode
    >
      <YourApp />
    </KentuckySignerProvider>
  )
}

Authentication Hook

import { useKentuckySigner } from 'kentucky-signer-viem/react'

function LoginButton() {
  const { isAuthenticated, account, authenticate, logout } = useKentuckySigner()

  if (isAuthenticated && account) {
    return (
      <div>
        <span>Connected: {account.address}</span>
        <button onClick={logout}>Logout</button>
      </div>
    )
  }

  return (
    <button onClick={() => authenticate('account_id')}>
      Login with Passkey
    </button>
  )
}

Wallet Client Hook

import { useWalletClient, useIsReady } from 'kentucky-signer-viem/react'
import { mainnet } from 'viem/chains'

function SendTransaction() {
  const isReady = useIsReady()
  const walletClient = useWalletClient({ chain: mainnet })

  async function send() {
    if (!walletClient) return
    const hash = await walletClient.sendTransaction({
      to: '0x...',
      value: parseEther('0.1'),
    })
  }

  return <button onClick={send} disabled={!isReady}>Send</button>
}

Secure Mode (Ephemeral Keys)

Secure mode adds an extra layer of security by requiring client-side ephemeral key signatures for all operations:

import { SecureKentuckySignerClient, EphemeralKeyManager } from 'kentucky-signer-viem'

// Create ephemeral key manager
const keyManager = new EphemeralKeyManager()

// Create secure client
const secureClient = new SecureKentuckySignerClient({
  baseUrl: 'https://signer.example.com',
  ephemeralKeyManager: keyManager,
})

// Authenticate with ephemeral key binding
const session = await authenticateWithPasskey({
  baseUrl: 'https://signer.example.com',
  accountId: 'account_id',
  ephemeralPublicKey: await keyManager.getPublicKey(),
})

// Create account with secure client
const account = createKentuckySignerAccount({
  config: { baseUrl, accountId },
  session,
  secureClient, // Uses ephemeral key signing
})

EIP-7702 Authorization

Sign authorizations to delegate your EOA's code to a smart contract, enabling features like batching and gas sponsorship.

import { createPublicClient, http } from 'viem'
import { arbitrum } from 'viem/chains'

const publicClient = createPublicClient({
  chain: arbitrum,
  transport: http(),
})

// Get current transaction count for the account
const txNonce = await publicClient.getTransactionCount({
  address: account.address,
})

// Sign EIP-7702 authorization
const authorization = await account.sign7702Authorization({
  contractAddress: '0x...', // Smart account delegate contract
  chainId: 42161, // Arbitrum
}, BigInt(txNonce))

// Result:
// {
//   chainId: 42161,
//   contractAddress: '0x...',
//   nonce: 0n,
//   yParity: 0,
//   r: '0x...',
//   s: '0x...'
// }

The authorization can be included in an EIP-7702 transaction's authorizationList to delegate the EOA.

Intent Signing for Relayed Execution

Sign execution intents for gasless transactions via a relayer.

import {
  createExecutionIntent,
  signIntent,
  RelayerClient,
} from 'kentucky-signer-viem'
import { encodeFunctionData, parseEther } from 'viem'

// Create relayer client
const relayer = new RelayerClient({
  baseUrl: 'https://relayer.example.com',
})

// Get current nonce from the delegate contract
const nonce = await relayer.getNonce(42161, account.address)

// Create an execution intent
const intent = createExecutionIntent({
  nonce,
  target: '0x...', // Contract to call
  value: parseEther('0.1'), // ETH to send (optional)
  data: encodeFunctionData({
    abi: [...],
    functionName: 'transfer',
    args: ['0x...', 1000n],
  }),
  deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour (optional)
})

// Sign the intent
const signedIntent = await signIntent(account, intent)

Relayer Client

The RelayerClient communicates with a relayer service to submit transactions on behalf of users.

Basic Usage

import { RelayerClient, createRelayerClient } from 'kentucky-signer-viem'

// Create client
const relayer = new RelayerClient({
  baseUrl: 'https://relayer.example.com',
  timeout: 30000, // Optional, default 30s
})

// Or use the factory function
const relayer = createRelayerClient('https://relayer.example.com')

// Check health
const health = await relayer.health()
// { status: 'ok', relayer: '0x...', timestamp: '2024-...' }

Get Nonce

const nonce = await relayer.getNonce(42161, account.address)
// Returns bigint nonce from the delegate contract

Estimate Gas

const estimate = await relayer.estimate(42161, account.address, intent)
// {
//   gasEstimate: '150000',
//   gasCostWei: '30000000000000',
//   sponsoredAvailable: true,
//   tokenOptions: [
//     { token: '0x...', symbol: 'USDC', estimatedFee: '0.05', feePercentage: 5 }
//   ]
// }

Relay Transaction

// Sponsored (relayer pays gas)
const result = await relayer.relay(
  42161,
  account.address,
  signedIntent,
  'sponsored'
)

// Pay with ERC20 token
const result = await relayer.relay(
  42161,
  account.address,
  signedIntent,
  { token: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' } // USDC on Arbitrum
)

if (result.success) {
  console.log('TX Hash:', result.txHash)
} else {
  console.error('Failed:', result.error)
}

Gasless Onboarding

For users with zero ETH, combine EIP-7702 authorization with relay to delegate and execute in a single transaction:

import { createPublicClient, http } from 'viem'
import { arbitrum } from 'viem/chains'

const publicClient = createPublicClient({
  chain: arbitrum,
  transport: http(),
})

// Get current nonce
const txNonce = await publicClient.getTransactionCount({
  address: account.address,
})

// Sign EIP-7702 authorization to delegate EOA
const authorization = await account.sign7702Authorization({
  contractAddress: delegateAddress,
  chainId: 42161,
}, BigInt(txNonce))

// Create and sign execution intent
const nonce = await relayer.getNonce(42161, account.address)
const intent = createExecutionIntent({
  nonce,
  target: '0x...',
  data: '0x...',
})
const signedIntent = await signIntent(account, intent)

// Relay with authorization - delegates EOA and executes in one tx
const result = await relayer.relay(
  42161,
  account.address,
  signedIntent,
  'sponsored',
  authorization // Include EIP-7702 authorization
)

Check Transaction Status

const status = await relayer.getStatus(42161, txHash)
// {
//   status: 'confirmed', // 'pending' | 'confirmed' | 'failed'
//   txHash: '0x...',
//   blockNumber: 12345678,
//   gasUsed: '120000'
// }

Two-Factor Authentication

Setup TOTP (Authenticator App)

import { KentuckySignerClient } from 'kentucky-signer-viem'

const client = new KentuckySignerClient({ baseUrl })

// Start TOTP setup - returns QR code URI
const setup = await client.setupTOTP(token)
console.log('Scan this QR code:', setup.uri)
console.log('Or enter manually:', setup.secret)

// Enable TOTP with verification code
await client.enableTOTP('123456', token)

// Check 2FA status
const status = await client.get2FAStatus(token)
// { totp_enabled: true, pin_enabled: false, pin_length: 0 }

Setup PIN

// Setup 4 or 6 digit PIN
await client.setupPIN('123456', token)

// Disable PIN (requires current PIN)
await client.disablePIN('123456', token)

Signing with 2FA

When 2FA is enabled, signing operations will automatically prompt for codes:

import { useKentuckySigner } from 'kentucky-signer-viem/react'

function App() {
  const { twoFactorPrompt, submit2FA, cancel2FA } = useKentuckySigner()

  // The 2FA prompt appears automatically when signing requires it
  if (twoFactorPrompt.isVisible) {
    return (
      <TwoFactorModal
        totpRequired={twoFactorPrompt.totpRequired}
        pinRequired={twoFactorPrompt.pinRequired}
        pinLength={twoFactorPrompt.pinLength}
        onSubmit={(codes) => submit2FA(codes)}
        onCancel={() => cancel2FA()}
      />
    )
  }

  return <YourApp />
}

Guardian Recovery

Set up trusted guardians for account recovery:

const client = new KentuckySignerClient({ baseUrl })

// Add a guardian (requires WebAuthn attestation from guardian's device)
const { guardian_index, guardian_count } = await client.addGuardian({
  attestation_object: guardianAttestationBase64url,
  label: 'My Friend',
}, token)

// List guardians
const { guardians } = await client.getGuardians(token)
// guardians: [{ index: 1, label: 'My Friend' }, ...]

// Initiate recovery (when locked out - register new passkey first)
const recovery = await client.initiateRecovery(
  accountId,
  newPasskeyAttestationObject,
  'New Owner Passkey'
)
// Returns: { challenges, guardian_count, threshold, timelock_seconds }

// Guardian signs their challenge with their passkey
await client.verifyGuardian({
  account_id: accountId,
  guardian_index: 1,
  authenticator_data: authDataBase64url,
  client_data_json: clientDataBase64url,
  signature: signatureBase64url,
})

// Check recovery status
const status = await client.getRecoveryStatus(accountId)
// { verified_count, threshold, can_complete, timelock_remaining }

// Complete recovery after threshold met and timelock expired
await client.completeRecovery(accountId)

API Reference

Core Functions

| Function | Description | |----------|-------------| | createKentuckySignerAccount(options) | Create a Viem-compatible account | | createServerAccount(...) | Create account with JWT token (Node.js) | | authenticateWithPasskey(options) | Authenticate using WebAuthn | | authenticateWithPassword(options) | Authenticate using password | | createAccountWithPassword(options) | Create new account with password | | authenticateWithToken(...) | Create session from JWT token |

Intent & Relayer Functions

| Function | Description | |----------|-------------| | createExecutionIntent(params) | Create an execution intent for relayed execution | | signIntent(account, intent) | Sign an execution intent | | signBatchIntents(account, intents) | Sign multiple intents for batch execution | | hashIntent(intent) | Compute the hash of an execution intent | | createRelayerClient(baseUrl) | Create a relayer client |

React Hooks

| Hook | Description | |------|-------------| | useKentuckySigner() | Access auth state, actions, and 2FA | | useKentuckySignerAccount() | Get the current account | | useWalletClient(options) | Create a Viem WalletClient | | usePasskeyAuth() | Authentication flow with loading state | | useSignMessage() | Sign messages with loading state | | useSignTypedData() | Sign EIP-712 typed data | | useIsReady() | Check if signer is ready | | useAddress() | Get connected address |

Client Methods

Authentication

  • getChallenge(accountId) - Get WebAuthn challenge
  • authenticatePasskey(accountId, credential, ephemeralPublicKey?) - Authenticate with passkey
  • authenticatePassword(request) - Authenticate with password ({ account_id, password })
  • refreshToken(token) - Refresh JWT token
  • logout(token) - Invalidate session

Signing

  • signEvmTransaction(request, token) - Sign EVM transaction hash
  • signEvmTransactionWith2FA(request, token) - Sign with 2FA codes

Account Management

  • getAccountInfo(accountId, token) - Get account info
  • getAccountInfoExtended(accountId, token) - Get account info with auth config
  • addPassword(accountId, request, token) - Add password auth
  • addPasskey(accountId, request, token) - Add passkey
  • removePasskey(accountId, passkeyIndex, token) - Remove passkey by index

Two-Factor Authentication

  • get2FAStatus(token) - Get 2FA status
  • setupTOTP(token) - Start TOTP setup
  • enableTOTP(code, token) - Enable TOTP
  • disableTOTP(code, token) - Disable TOTP
  • setupPIN(pin, token) - Setup PIN
  • disablePIN(pin, token) - Disable PIN

Account Methods

EIP-7702 Authorization

  • account.sign7702Authorization(params, nonce) - Sign authorization to delegate EOA code

Relayer Client Methods

  • health() - Check relayer health
  • getNonce(chainId, address) - Get account nonce from delegate contract
  • estimate(chainId, address, intent) - Estimate gas and fees
  • relay(chainId, address, signedIntent, paymentMode, authorization?) - Submit transaction
  • getStatus(chainId, txHash) - Get transaction status

Guardian Recovery

  • addGuardian(request, token) - Add guardian passkey
  • removeGuardian(guardianIndex, token) - Remove guardian
  • getGuardians(token) - List guardians
  • initiateRecovery(accountId, attestationObject, label?) - Start recovery
  • verifyGuardian(request) - Submit guardian signature
  • getRecoveryStatus(accountId) - Check recovery status
  • completeRecovery(accountId) - Complete recovery
  • cancelRecovery(token) - Cancel recovery (owner only)

Error Handling

import { KentuckySignerError } from 'kentucky-signer-viem'

try {
  await authenticate(accountId)
} catch (error) {
  if (error instanceof KentuckySignerError) {
    switch (error.code) {
      case 'WEBAUTHN_NOT_AVAILABLE':
        // WebAuthn not supported
        break
      case 'USER_CANCELLED':
        // User cancelled authentication
        break
      case 'SESSION_EXPIRED':
        // JWT token expired
        break
      case '2FA_REQUIRED':
        // 2FA verification needed
        break
      case '2FA_CANCELLED':
        // User cancelled 2FA input
        break
      // ... handle other codes
    }
  }
}

Documentation

For detailed documentation, see the docs folder:

License

MIT