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/src/utils.ts ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Utility functions for Kentucky Signer Viem integration
3
+ */
4
+
5
+ /**
6
+ * Base64URL encode a Uint8Array
7
+ *
8
+ * @param data - Data to encode
9
+ * @returns Base64URL encoded string (no padding)
10
+ */
11
+ export function base64UrlEncode(data: Uint8Array): string {
12
+ // Convert to regular base64
13
+ let base64: string
14
+ if (typeof Buffer !== 'undefined') {
15
+ // Node.js
16
+ base64 = Buffer.from(data).toString('base64')
17
+ } else {
18
+ // Browser
19
+ const binary = Array.from(data)
20
+ .map((byte) => String.fromCharCode(byte))
21
+ .join('')
22
+ base64 = btoa(binary)
23
+ }
24
+
25
+ // Convert to base64url (replace + with -, / with _, remove padding)
26
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
27
+ }
28
+
29
+ /**
30
+ * Base64URL decode a string to Uint8Array
31
+ *
32
+ * @param str - Base64URL encoded string
33
+ * @returns Decoded bytes
34
+ */
35
+ export function base64UrlDecode(str: string): Uint8Array {
36
+ // Convert base64url to regular base64
37
+ let base64 = str.replace(/-/g, '+').replace(/_/g, '/')
38
+
39
+ // Add padding if needed
40
+ const padding = (4 - (base64.length % 4)) % 4
41
+ base64 += '='.repeat(padding)
42
+
43
+ if (typeof Buffer !== 'undefined') {
44
+ // Node.js
45
+ return new Uint8Array(Buffer.from(base64, 'base64'))
46
+ } else {
47
+ // Browser
48
+ const binary = atob(base64)
49
+ const bytes = new Uint8Array(binary.length)
50
+ for (let i = 0; i < binary.length; i++) {
51
+ bytes[i] = binary.charCodeAt(i)
52
+ }
53
+ return bytes
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Convert a hex string to Uint8Array
59
+ *
60
+ * @param hex - Hex string (with or without 0x prefix)
61
+ * @returns Byte array
62
+ */
63
+ export function hexToBytes(hex: string): Uint8Array {
64
+ const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex
65
+ const bytes = new Uint8Array(cleanHex.length / 2)
66
+ for (let i = 0; i < bytes.length; i++) {
67
+ bytes[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16)
68
+ }
69
+ return bytes
70
+ }
71
+
72
+ /**
73
+ * Convert a Uint8Array to hex string
74
+ *
75
+ * @param bytes - Byte array
76
+ * @param withPrefix - Include 0x prefix (default: true)
77
+ * @returns Hex string
78
+ */
79
+ export function bytesToHex(bytes: Uint8Array, withPrefix: boolean = true): string {
80
+ const hex = Array.from(bytes)
81
+ .map((b) => b.toString(16).padStart(2, '0'))
82
+ .join('')
83
+ return withPrefix ? `0x${hex}` : hex
84
+ }
85
+
86
+ /**
87
+ * Validate an account ID format
88
+ *
89
+ * Account IDs are 64-character hex strings (32 bytes).
90
+ *
91
+ * @param accountId - Account ID to validate
92
+ * @returns True if valid
93
+ */
94
+ export function isValidAccountId(accountId: string): boolean {
95
+ return /^[0-9a-fA-F]{64}$/.test(accountId)
96
+ }
97
+
98
+ /**
99
+ * Validate an EVM address format
100
+ *
101
+ * @param address - Address to validate
102
+ * @returns True if valid
103
+ */
104
+ export function isValidEvmAddress(address: string): boolean {
105
+ return /^0x[0-9a-fA-F]{40}$/.test(address)
106
+ }
107
+
108
+ /**
109
+ * Sleep for a specified duration
110
+ *
111
+ * @param ms - Milliseconds to sleep
112
+ */
113
+ export function sleep(ms: number): Promise<void> {
114
+ return new Promise((resolve) => setTimeout(resolve, ms))
115
+ }
116
+
117
+ /**
118
+ * Retry a function with exponential backoff
119
+ *
120
+ * @param fn - Function to retry
121
+ * @param maxRetries - Maximum number of retries (default: 3)
122
+ * @param baseDelay - Base delay in ms (default: 1000)
123
+ * @returns Result of the function
124
+ */
125
+ export async function withRetry<T>(
126
+ fn: () => Promise<T>,
127
+ maxRetries: number = 3,
128
+ baseDelay: number = 1000
129
+ ): Promise<T> {
130
+ let lastError: Error | undefined
131
+
132
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
133
+ try {
134
+ return await fn()
135
+ } catch (error) {
136
+ lastError = error as Error
137
+
138
+ if (attempt < maxRetries) {
139
+ const delay = baseDelay * Math.pow(2, attempt)
140
+ await sleep(delay)
141
+ }
142
+ }
143
+ }
144
+
145
+ throw lastError
146
+ }
147
+
148
+ /**
149
+ * Parse a JWT token (without validation)
150
+ *
151
+ * @param token - JWT token string
152
+ * @returns Decoded payload
153
+ */
154
+ export function parseJwt(token: string): Record<string, unknown> {
155
+ const parts = token.split('.')
156
+ if (parts.length !== 3) {
157
+ throw new Error('Invalid JWT format')
158
+ }
159
+
160
+ const payload = base64UrlDecode(parts[1])
161
+ const text = new TextDecoder().decode(payload)
162
+ return JSON.parse(text)
163
+ }
164
+
165
+ /**
166
+ * Get JWT expiration time
167
+ *
168
+ * @param token - JWT token string
169
+ * @returns Expiration timestamp in milliseconds, or null if no exp claim
170
+ */
171
+ export function getJwtExpiration(token: string): number | null {
172
+ try {
173
+ const payload = parseJwt(token)
174
+ if (typeof payload.exp === 'number') {
175
+ return payload.exp * 1000 // Convert seconds to milliseconds
176
+ }
177
+ return null
178
+ } catch {
179
+ return null
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Format an error for display
185
+ *
186
+ * @param error - Error to format
187
+ * @returns Formatted error message
188
+ */
189
+ export function formatError(error: unknown): string {
190
+ if (error instanceof Error) {
191
+ return error.message
192
+ }
193
+ if (typeof error === 'string') {
194
+ return error
195
+ }
196
+ return 'An unknown error occurred'
197
+ }