kentucky-signer-viem 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,428 @@
1
+ # kentucky-signer-viem
2
+
3
+ A custom Viem account integration for the Kentucky Signer service, enabling EVM transaction signing using passkey (WebAuthn) or password authentication.
4
+
5
+ ## Features
6
+
7
+ - Custom Viem account backed by Kentucky Signer
8
+ - Passkey (WebAuthn) authentication for browser environments
9
+ - Password authentication for browser and Node.js environments
10
+ - JWT token authentication for Node.js/server environments
11
+ - Account creation with passkey or password
12
+ - React hooks and context for easy integration
13
+ - TypeScript support with full type definitions
14
+ - Session management with automatic refresh
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install kentucky-signer-viem viem
20
+ # or
21
+ yarn add kentucky-signer-viem viem
22
+ # or
23
+ pnpm add kentucky-signer-viem viem
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### Browser (with Passkey)
29
+
30
+ ```typescript
31
+ import { createWalletClient, http, parseEther } from 'viem'
32
+ import { mainnet } from 'viem/chains'
33
+ import {
34
+ createKentuckySignerAccount,
35
+ authenticateWithPasskey,
36
+ } from 'kentucky-signer-viem'
37
+
38
+ // 1. Authenticate with passkey
39
+ const session = await authenticateWithPasskey({
40
+ baseUrl: 'https://signer.example.com',
41
+ accountId: '0123456789abcdef...', // 64-char hex account ID
42
+ })
43
+
44
+ // 2. Create Kentucky Signer account
45
+ const account = createKentuckySignerAccount({
46
+ config: {
47
+ baseUrl: 'https://signer.example.com',
48
+ accountId: session.accountId,
49
+ },
50
+ session,
51
+ defaultChainId: 1,
52
+ })
53
+
54
+ // 3. Create Viem wallet client
55
+ const walletClient = createWalletClient({
56
+ account,
57
+ chain: mainnet,
58
+ transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
59
+ })
60
+
61
+ // 4. Sign and send transaction
62
+ const hash = await walletClient.sendTransaction({
63
+ to: '0x...',
64
+ value: parseEther('0.1'),
65
+ })
66
+ console.log('Transaction hash:', hash)
67
+ ```
68
+
69
+ ### Password Authentication (Browser or Node.js)
70
+
71
+ ```typescript
72
+ import { createWalletClient, http, parseEther } from 'viem'
73
+ import { mainnet } from 'viem/chains'
74
+ import {
75
+ createKentuckySignerAccount,
76
+ authenticateWithPassword,
77
+ createAccountWithPassword,
78
+ } from 'kentucky-signer-viem'
79
+
80
+ // Option 1: Create a new account with password
81
+ const newAccount = await createAccountWithPassword({
82
+ baseUrl: 'https://signer.example.com',
83
+ password: 'your-secure-password',
84
+ confirmation: 'your-secure-password',
85
+ })
86
+ console.log('Account ID:', newAccount.account_id)
87
+ console.log('EVM Address:', newAccount.addresses.evm)
88
+
89
+ // Option 2: Authenticate with existing account
90
+ const session = await authenticateWithPassword({
91
+ baseUrl: 'https://signer.example.com',
92
+ accountId: newAccount.account_id,
93
+ password: 'your-secure-password',
94
+ })
95
+
96
+ // Create Kentucky Signer account
97
+ const account = createKentuckySignerAccount({
98
+ config: {
99
+ baseUrl: 'https://signer.example.com',
100
+ accountId: session.accountId,
101
+ },
102
+ session,
103
+ defaultChainId: 1,
104
+ })
105
+
106
+ // Use with Viem
107
+ const walletClient = createWalletClient({
108
+ account,
109
+ chain: mainnet,
110
+ transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
111
+ })
112
+
113
+ const hash = await walletClient.sendTransaction({
114
+ to: '0x...',
115
+ value: parseEther('0.1'),
116
+ })
117
+ ```
118
+
119
+ ### Node.js (with JWT Token)
120
+
121
+ ```typescript
122
+ import { createWalletClient, http, parseEther } from 'viem'
123
+ import { mainnet } from 'viem/chains'
124
+ import { createServerAccount } from 'kentucky-signer-viem'
125
+
126
+ // Create account with existing JWT token
127
+ const account = createServerAccount(
128
+ 'https://signer.example.com',
129
+ 'account_id_hex',
130
+ 'jwt_token',
131
+ '0xYourEvmAddress',
132
+ 1 // chainId
133
+ )
134
+
135
+ // Use with Viem
136
+ const walletClient = createWalletClient({
137
+ account,
138
+ chain: mainnet,
139
+ transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
140
+ })
141
+
142
+ const hash = await walletClient.sendTransaction({
143
+ to: '0x...',
144
+ value: parseEther('0.1'),
145
+ })
146
+ ```
147
+
148
+ ## React Integration
149
+
150
+ ### Setup Provider
151
+
152
+ ```tsx
153
+ import { KentuckySignerProvider } from 'kentucky-signer-viem/react'
154
+
155
+ function App() {
156
+ return (
157
+ <KentuckySignerProvider
158
+ baseUrl="https://signer.example.com"
159
+ defaultChainId={1}
160
+ persistSession={true}
161
+ >
162
+ <YourApp />
163
+ </KentuckySignerProvider>
164
+ )
165
+ }
166
+ ```
167
+
168
+ ### Authentication Hook
169
+
170
+ ```tsx
171
+ import { useKentuckySigner, usePasskeyAuth } from 'kentucky-signer-viem/react'
172
+
173
+ function LoginButton() {
174
+ const { isAuthenticated, account } = useKentuckySigner()
175
+ const { login, isLoading, error } = usePasskeyAuth()
176
+ const [accountId, setAccountId] = useState('')
177
+
178
+ if (isAuthenticated && account) {
179
+ return <div>Connected: {account.address}</div>
180
+ }
181
+
182
+ return (
183
+ <div>
184
+ <input
185
+ placeholder="Account ID"
186
+ value={accountId}
187
+ onChange={(e) => setAccountId(e.target.value)}
188
+ />
189
+ <button onClick={() => login(accountId)} disabled={isLoading}>
190
+ {isLoading ? 'Authenticating...' : 'Login with Passkey'}
191
+ </button>
192
+ {error && <div className="error">{error.message}</div>}
193
+ </div>
194
+ )
195
+ }
196
+ ```
197
+
198
+ ### Wallet Client Hook
199
+
200
+ ```tsx
201
+ import { useWalletClient, useIsReady } from 'kentucky-signer-viem/react'
202
+ import { mainnet } from 'viem/chains'
203
+ import { parseEther } from 'viem'
204
+
205
+ function SendTransaction() {
206
+ const isReady = useIsReady()
207
+ const walletClient = useWalletClient({
208
+ chain: mainnet,
209
+ rpcUrl: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY',
210
+ })
211
+
212
+ async function send() {
213
+ if (!walletClient) return
214
+
215
+ const hash = await walletClient.sendTransaction({
216
+ to: '0x...',
217
+ value: parseEther('0.1'),
218
+ })
219
+ console.log('Transaction hash:', hash)
220
+ }
221
+
222
+ return (
223
+ <button onClick={send} disabled={!isReady}>
224
+ Send 0.1 ETH
225
+ </button>
226
+ )
227
+ }
228
+ ```
229
+
230
+ ### Sign Message Hook
231
+
232
+ ```tsx
233
+ import { useSignMessage } from 'kentucky-signer-viem/react'
234
+
235
+ function SignMessageDemo() {
236
+ const { signMessage, isLoading, isAvailable } = useSignMessage()
237
+ const [signature, setSignature] = useState('')
238
+
239
+ async function sign() {
240
+ const sig = await signMessage('Hello, Kentucky Signer!')
241
+ setSignature(sig)
242
+ }
243
+
244
+ return (
245
+ <div>
246
+ <button onClick={sign} disabled={isLoading || !isAvailable}>
247
+ {isLoading ? 'Signing...' : 'Sign Message'}
248
+ </button>
249
+ {signature && <pre>{signature}</pre>}
250
+ </div>
251
+ )
252
+ }
253
+ ```
254
+
255
+ ## API Reference
256
+
257
+ ### Core Functions
258
+
259
+ #### `createKentuckySignerAccount(options)`
260
+
261
+ Creates a custom Viem account backed by Kentucky Signer.
262
+
263
+ ```typescript
264
+ const account = createKentuckySignerAccount({
265
+ config: {
266
+ baseUrl: string, // Kentucky Signer API URL
267
+ accountId: string, // 64-char hex account ID
268
+ },
269
+ session: AuthSession, // Authenticated session
270
+ defaultChainId?: number, // Default chain ID (default: 1)
271
+ onSessionExpired?: () => Promise<AuthSession>, // Session refresh callback
272
+ })
273
+ ```
274
+
275
+ #### `authenticateWithPasskey(options)`
276
+
277
+ Authenticates using WebAuthn passkey (browser only).
278
+
279
+ ```typescript
280
+ const session = await authenticateWithPasskey({
281
+ baseUrl: string, // Kentucky Signer API URL
282
+ accountId: string, // Account ID to authenticate
283
+ rpId?: string, // WebAuthn Relying Party ID
284
+ allowCredentials?: string[], // Allowed credential IDs
285
+ })
286
+ ```
287
+
288
+ #### `authenticateWithPassword(options)`
289
+
290
+ Authenticates using password (works in browser and Node.js).
291
+
292
+ ```typescript
293
+ const session = await authenticateWithPassword({
294
+ baseUrl: string, // Kentucky Signer API URL
295
+ accountId: string, // Account ID to authenticate
296
+ password: string, // Account password
297
+ })
298
+ ```
299
+
300
+ #### `createAccountWithPassword(options)`
301
+
302
+ Creates a new account with password authentication.
303
+
304
+ ```typescript
305
+ const account = await createAccountWithPassword({
306
+ baseUrl: string, // Kentucky Signer API URL
307
+ password: string, // Password (8-128 characters)
308
+ confirmation: string, // Must match password
309
+ })
310
+ // Returns: { account_id, addresses: { evm, bitcoin, solana } }
311
+ ```
312
+
313
+ #### `authenticateWithToken(baseUrl, accountId, token, expiresAt?)`
314
+
315
+ Creates a session from an existing JWT token (Node.js compatible).
316
+
317
+ ```typescript
318
+ const session = await authenticateWithToken(
319
+ 'https://signer.example.com',
320
+ 'account_id',
321
+ 'jwt_token',
322
+ Date.now() + 3600000 // Optional expiration
323
+ )
324
+ ```
325
+
326
+ ### Client Class
327
+
328
+ #### `KentuckySignerClient`
329
+
330
+ Low-level API client for Kentucky Signer.
331
+
332
+ ```typescript
333
+ const client = new KentuckySignerClient({ baseUrl: 'https://signer.example.com' })
334
+
335
+ // Get challenge for authentication
336
+ const challenge = await client.getChallenge(accountId)
337
+
338
+ // Authenticate with passkey credential
339
+ const auth = await client.authenticatePasskey(accountId, credential)
340
+
341
+ // Sign EVM transaction
342
+ const signature = await client.signEvmTransaction(
343
+ { tx_hash: '0x...', chain_id: 1 },
344
+ jwtToken
345
+ )
346
+
347
+ // Get account info
348
+ const info = await client.getAccountInfo(accountId, jwtToken)
349
+ ```
350
+
351
+ ### React Hooks
352
+
353
+ | Hook | Description |
354
+ |------|-------------|
355
+ | `useKentuckySigner()` | Access auth state and actions |
356
+ | `useKentuckySignerAccount()` | Get the current account |
357
+ | `useWalletClient(options)` | Create a Viem WalletClient |
358
+ | `usePasskeyAuth()` | Authentication flow with loading state |
359
+ | `useSignMessage()` | Sign messages with loading state |
360
+ | `useSignTypedData()` | Sign EIP-712 typed data |
361
+ | `useIsReady()` | Check if signer is ready |
362
+ | `useAddress()` | Get connected address |
363
+
364
+ ### Types
365
+
366
+ ```typescript
367
+ interface AuthSession {
368
+ token: string // JWT access token
369
+ accountId: string // Account ID
370
+ evmAddress: Address // EVM address
371
+ btcAddress?: string // Bitcoin address
372
+ solAddress?: string // Solana address
373
+ expiresAt: number // Expiration timestamp (ms)
374
+ }
375
+
376
+ interface KentuckySignerConfig {
377
+ baseUrl: string // API URL
378
+ accountId: string // Account ID
379
+ }
380
+ ```
381
+
382
+ ## Session Management
383
+
384
+ Sessions are automatically managed:
385
+
386
+ - **Browser**: Sessions can be persisted to localStorage
387
+ - **Auto-refresh**: Sessions are refreshed before expiration
388
+ - **Expiration handling**: Provide `onSessionExpired` callback for custom handling
389
+
390
+ ```typescript
391
+ const account = createKentuckySignerAccount({
392
+ config: { baseUrl, accountId },
393
+ session,
394
+ onSessionExpired: async () => {
395
+ // Re-authenticate or refresh token
396
+ return await authenticateWithPasskey({ baseUrl, accountId })
397
+ },
398
+ })
399
+ ```
400
+
401
+ ## Error Handling
402
+
403
+ ```typescript
404
+ import { KentuckySignerError } from 'kentucky-signer-viem'
405
+
406
+ try {
407
+ await authenticate(accountId)
408
+ } catch (error) {
409
+ if (error instanceof KentuckySignerError) {
410
+ console.error('Code:', error.code)
411
+ console.error('Message:', error.message)
412
+ console.error('Details:', error.details)
413
+ }
414
+ }
415
+ ```
416
+
417
+ Common error codes:
418
+ - `WEBAUTHN_NOT_AVAILABLE` - WebAuthn not supported
419
+ - `USER_CANCELLED` - User cancelled authentication
420
+ - `SESSION_EXPIRED` - JWT token expired
421
+ - `UNAUTHORIZED` - Invalid or missing token
422
+ - `NOT_FOUND` - Account not found
423
+ - `PASSWORD_MISMATCH` - Password and confirmation don't match
424
+ - `INVALID_PASSWORD` - Password doesn't meet requirements (8-128 chars)
425
+
426
+ ## License
427
+
428
+ MIT