kentucky-signer-viem 0.1.3 → 0.1.5

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 CHANGED
@@ -15,6 +15,9 @@ A custom Viem account integration for the Kentucky Signer service, enabling EVM
15
15
  - **React Integration** - Hooks and context for easy React app integration
16
16
  - **TypeScript Support** - Full type definitions included
17
17
  - **Session Management** - Automatic refresh and persistence options
18
+ - **EIP-7702 Support** - Sign authorizations for EOA code delegation
19
+ - **Relayer Integration** - Client for gasless transactions via relayer service
20
+ - **Intent Signing** - Sign execution intents for smart account operations
18
21
 
19
22
  ## Installation
20
23
 
@@ -204,6 +207,205 @@ const account = createKentuckySignerAccount({
204
207
  })
205
208
  ```
206
209
 
210
+ ## EIP-7702 Authorization
211
+
212
+ Sign authorizations to delegate your EOA's code to a smart contract, enabling features like batching and gas sponsorship.
213
+
214
+ ```typescript
215
+ import { createPublicClient, http } from 'viem'
216
+ import { arbitrum } from 'viem/chains'
217
+
218
+ const publicClient = createPublicClient({
219
+ chain: arbitrum,
220
+ transport: http(),
221
+ })
222
+
223
+ // Get current transaction count for the account
224
+ const txNonce = await publicClient.getTransactionCount({
225
+ address: account.address,
226
+ })
227
+
228
+ // Sign EIP-7702 authorization
229
+ const authorization = await account.sign7702Authorization({
230
+ contractAddress: '0x...', // Smart account delegate contract
231
+ chainId: 42161, // Arbitrum
232
+ }, BigInt(txNonce))
233
+
234
+ // Result:
235
+ // {
236
+ // chainId: 42161,
237
+ // contractAddress: '0x...',
238
+ // nonce: 0n,
239
+ // yParity: 0,
240
+ // r: '0x...',
241
+ // s: '0x...'
242
+ // }
243
+ ```
244
+
245
+ The authorization can be included in an EIP-7702 transaction's `authorizationList` to delegate the EOA.
246
+
247
+ ## Intent Signing for Relayed Execution
248
+
249
+ Sign execution intents for gasless transactions via a relayer.
250
+
251
+ ```typescript
252
+ import {
253
+ createExecutionIntent,
254
+ signIntent,
255
+ RelayerClient,
256
+ } from 'kentucky-signer-viem'
257
+ import { encodeFunctionData, parseEther } from 'viem'
258
+
259
+ // Create relayer client
260
+ const relayer = new RelayerClient({
261
+ baseUrl: 'https://relayer.example.com',
262
+ })
263
+
264
+ // Get current nonce from the delegate contract
265
+ const nonce = await relayer.getNonce(42161, account.address)
266
+
267
+ // Create an execution intent
268
+ const intent = createExecutionIntent({
269
+ nonce,
270
+ target: '0x...', // Contract to call
271
+ value: parseEther('0.1'), // ETH to send (optional)
272
+ data: encodeFunctionData({
273
+ abi: [...],
274
+ functionName: 'transfer',
275
+ args: ['0x...', 1000n],
276
+ }),
277
+ deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour (optional)
278
+ })
279
+
280
+ // Sign the intent
281
+ const signedIntent = await signIntent(account, intent)
282
+ ```
283
+
284
+ ## Relayer Client
285
+
286
+ The `RelayerClient` communicates with a relayer service to submit transactions on behalf of users.
287
+
288
+ ### Basic Usage
289
+
290
+ ```typescript
291
+ import { RelayerClient, createRelayerClient } from 'kentucky-signer-viem'
292
+
293
+ // Create client
294
+ const relayer = new RelayerClient({
295
+ baseUrl: 'https://relayer.example.com',
296
+ timeout: 30000, // Optional, default 30s
297
+ })
298
+
299
+ // Or use the factory function
300
+ const relayer = createRelayerClient('https://relayer.example.com')
301
+
302
+ // Check health
303
+ const health = await relayer.health()
304
+ // { status: 'ok', relayer: '0x...', timestamp: '2024-...' }
305
+ ```
306
+
307
+ ### Get Nonce
308
+
309
+ ```typescript
310
+ const nonce = await relayer.getNonce(42161, account.address)
311
+ // Returns bigint nonce from the delegate contract
312
+ ```
313
+
314
+ ### Estimate Gas
315
+
316
+ ```typescript
317
+ const estimate = await relayer.estimate(42161, account.address, intent)
318
+ // {
319
+ // gasEstimate: '150000',
320
+ // gasCostWei: '30000000000000',
321
+ // sponsoredAvailable: true,
322
+ // tokenOptions: [
323
+ // { token: '0x...', symbol: 'USDC', estimatedFee: '0.05', feePercentage: 5 }
324
+ // ]
325
+ // }
326
+ ```
327
+
328
+ ### Relay Transaction
329
+
330
+ ```typescript
331
+ // Sponsored (relayer pays gas)
332
+ const result = await relayer.relay(
333
+ 42161,
334
+ account.address,
335
+ signedIntent,
336
+ 'sponsored'
337
+ )
338
+
339
+ // Pay with ERC20 token
340
+ const result = await relayer.relay(
341
+ 42161,
342
+ account.address,
343
+ signedIntent,
344
+ { token: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' } // USDC on Arbitrum
345
+ )
346
+
347
+ if (result.success) {
348
+ console.log('TX Hash:', result.txHash)
349
+ } else {
350
+ console.error('Failed:', result.error)
351
+ }
352
+ ```
353
+
354
+ ### Gasless Onboarding
355
+
356
+ For users with zero ETH, combine EIP-7702 authorization with relay to delegate and execute in a single transaction:
357
+
358
+ ```typescript
359
+ import { createPublicClient, http } from 'viem'
360
+ import { arbitrum } from 'viem/chains'
361
+
362
+ const publicClient = createPublicClient({
363
+ chain: arbitrum,
364
+ transport: http(),
365
+ })
366
+
367
+ // Get current nonce
368
+ const txNonce = await publicClient.getTransactionCount({
369
+ address: account.address,
370
+ })
371
+
372
+ // Sign EIP-7702 authorization to delegate EOA
373
+ const authorization = await account.sign7702Authorization({
374
+ contractAddress: delegateAddress,
375
+ chainId: 42161,
376
+ }, BigInt(txNonce))
377
+
378
+ // Create and sign execution intent
379
+ const nonce = await relayer.getNonce(42161, account.address)
380
+ const intent = createExecutionIntent({
381
+ nonce,
382
+ target: '0x...',
383
+ data: '0x...',
384
+ })
385
+ const signedIntent = await signIntent(account, intent)
386
+
387
+ // Relay with authorization - delegates EOA and executes in one tx
388
+ const result = await relayer.relay(
389
+ 42161,
390
+ account.address,
391
+ signedIntent,
392
+ 'sponsored',
393
+ authorization // Include EIP-7702 authorization
394
+ )
395
+ ```
396
+
397
+ ### Check Transaction Status
398
+
399
+ ```typescript
400
+ const status = await relayer.getStatus(42161, txHash)
401
+ // {
402
+ // status: 'confirmed', // 'pending' | 'confirmed' | 'failed'
403
+ // txHash: '0x...',
404
+ // blockNumber: 12345678,
405
+ // gasUsed: '120000'
406
+ // }
407
+ ```
408
+
207
409
  ## Two-Factor Authentication
208
410
 
209
411
  ### Setup TOTP (Authenticator App)
@@ -270,32 +472,39 @@ Set up trusted guardians for account recovery:
270
472
  ```typescript
271
473
  const client = new KentuckySignerClient({ baseUrl })
272
474
 
273
- // Add a guardian
274
- await client.addGuardian(accountId, {
275
- guardian_account_id: 'guardian_hex_id',
475
+ // Add a guardian (requires WebAuthn attestation from guardian's device)
476
+ const { guardian_index, guardian_count } = await client.addGuardian({
477
+ attestation_object: guardianAttestationBase64url,
276
478
  label: 'My Friend',
277
479
  }, token)
278
480
 
279
481
  // List guardians
280
- const { guardians } = await client.getGuardians(accountId, token)
482
+ const { guardians } = await client.getGuardians(token)
483
+ // guardians: [{ index: 1, label: 'My Friend' }, ...]
484
+
485
+ // Initiate recovery (when locked out - register new passkey first)
486
+ const recovery = await client.initiateRecovery(
487
+ accountId,
488
+ newPasskeyAttestationObject,
489
+ 'New Owner Passkey'
490
+ )
491
+ // Returns: { challenges, guardian_count, threshold, timelock_seconds }
281
492
 
282
- // Initiate recovery (when locked out)
283
- const recovery = await client.initiateRecovery({
493
+ // Guardian signs their challenge with their passkey
494
+ await client.verifyGuardian({
284
495
  account_id: accountId,
285
- new_password: 'new-secure-password',
496
+ guardian_index: 1,
497
+ authenticator_data: authDataBase64url,
498
+ client_data_json: clientDataBase64url,
499
+ signature: signatureBase64url,
286
500
  })
287
501
 
288
- // Guardian approves recovery
289
- await client.verifyGuardianRecovery({
290
- recovery_id: recovery.recovery_id,
291
- guardian_account_id: guardianId,
292
- signature: guardianSignature,
293
- }, guardianToken)
502
+ // Check recovery status
503
+ const status = await client.getRecoveryStatus(accountId)
504
+ // { verified_count, threshold, can_complete, timelock_remaining }
294
505
 
295
- // Complete recovery after threshold met
296
- await client.completeRecovery({
297
- recovery_id: recovery.recovery_id,
298
- }, token)
506
+ // Complete recovery after threshold met and timelock expired
507
+ await client.completeRecovery(accountId)
299
508
  ```
300
509
 
301
510
  ## API Reference
@@ -311,6 +520,16 @@ await client.completeRecovery({
311
520
  | `createAccountWithPassword(options)` | Create new account with password |
312
521
  | `authenticateWithToken(...)` | Create session from JWT token |
313
522
 
523
+ ### Intent & Relayer Functions
524
+
525
+ | Function | Description |
526
+ |----------|-------------|
527
+ | `createExecutionIntent(params)` | Create an execution intent for relayed execution |
528
+ | `signIntent(account, intent)` | Sign an execution intent |
529
+ | `signBatchIntents(account, intents)` | Sign multiple intents for batch execution |
530
+ | `hashIntent(intent)` | Compute the hash of an execution intent |
531
+ | `createRelayerClient(baseUrl)` | Create a relayer client |
532
+
314
533
  ### React Hooks
315
534
 
316
535
  | Hook | Description |
@@ -328,8 +547,9 @@ await client.completeRecovery({
328
547
 
329
548
  #### Authentication
330
549
  - `getChallenge(accountId)` - Get WebAuthn challenge
331
- - `authenticatePasskey(accountId, credential)` - Authenticate with passkey
332
- - `authenticatePassword(accountId, password)` - Authenticate with password
550
+ - `authenticatePasskey(accountId, credential, ephemeralPublicKey?)` - Authenticate with passkey
551
+ - `authenticatePassword(request)` - Authenticate with password (`{ account_id, password }`)
552
+ - `refreshToken(token)` - Refresh JWT token
333
553
  - `logout(token)` - Invalidate session
334
554
 
335
555
  #### Signing
@@ -338,9 +558,10 @@ await client.completeRecovery({
338
558
 
339
559
  #### Account Management
340
560
  - `getAccountInfo(accountId, token)` - Get account info
561
+ - `getAccountInfoExtended(accountId, token)` - Get account info with auth config
341
562
  - `addPassword(accountId, request, token)` - Add password auth
342
563
  - `addPasskey(accountId, request, token)` - Add passkey
343
- - `removePasskey(accountId, credentialId, token)` - Remove passkey
564
+ - `removePasskey(accountId, passkeyIndex, token)` - Remove passkey by index
344
565
 
345
566
  #### Two-Factor Authentication
346
567
  - `get2FAStatus(token)` - Get 2FA status
@@ -350,13 +571,28 @@ await client.completeRecovery({
350
571
  - `setupPIN(pin, token)` - Setup PIN
351
572
  - `disablePIN(pin, token)` - Disable PIN
352
573
 
574
+ ### Account Methods
575
+
576
+ #### EIP-7702 Authorization
577
+ - `account.sign7702Authorization(params, nonce)` - Sign authorization to delegate EOA code
578
+
579
+ ### Relayer Client Methods
580
+
581
+ - `health()` - Check relayer health
582
+ - `getNonce(chainId, address)` - Get account nonce from delegate contract
583
+ - `estimate(chainId, address, intent)` - Estimate gas and fees
584
+ - `relay(chainId, address, signedIntent, paymentMode, authorization?)` - Submit transaction
585
+ - `getStatus(chainId, txHash)` - Get transaction status
586
+
353
587
  #### Guardian Recovery
354
- - `addGuardian(accountId, request, token)` - Add guardian
355
- - `removeGuardian(accountId, guardianId, token)` - Remove guardian
356
- - `getGuardians(accountId, token)` - List guardians
357
- - `initiateRecovery(request)` - Start recovery
358
- - `verifyGuardianRecovery(request, token)` - Guardian approval
359
- - `completeRecovery(request, token)` - Complete recovery
588
+ - `addGuardian(request, token)` - Add guardian passkey
589
+ - `removeGuardian(guardianIndex, token)` - Remove guardian
590
+ - `getGuardians(token)` - List guardians
591
+ - `initiateRecovery(accountId, attestationObject, label?)` - Start recovery
592
+ - `verifyGuardian(request)` - Submit guardian signature
593
+ - `getRecoveryStatus(accountId)` - Check recovery status
594
+ - `completeRecovery(accountId)` - Complete recovery
595
+ - `cancelRecovery(token)` - Cancel recovery (owner only)
360
596
 
361
597
  ## Error Handling
362
598