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 +428 -0
- package/dist/index.d.mts +603 -0
- package/dist/index.d.ts +603 -0
- package/dist/index.js +760 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +714 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react/index.d.mts +288 -0
- package/dist/react/index.d.ts +288 -0
- package/dist/react/index.js +815 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +791 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +60 -0
- package/src/account.ts +255 -0
- package/src/auth.ts +427 -0
- package/src/client.ts +308 -0
- package/src/index.ts +73 -0
- package/src/react/context.tsx +317 -0
- package/src/react/hooks.ts +287 -0
- package/src/react/index.ts +31 -0
- package/src/types.ts +237 -0
- package/src/utils.ts +197 -0
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
|