kentucky-signer-viem 0.1.4 → 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 +225 -0
- package/dist/index.d.mts +376 -10
- package/dist/index.d.ts +376 -10
- package/dist/index.js +281 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +282 -26
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +465 -3
- package/dist/react/index.d.ts +465 -3
- package/dist/react/index.js +390 -25
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +391 -26
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/account.ts +183 -23
- package/src/client.ts +4 -5
- package/src/index.ts +32 -0
- package/src/intent.ts +167 -0
- package/src/react/index.ts +33 -0
- package/src/react/relayer-hooks.ts +318 -0
- package/src/relayer-client.ts +305 -0
- package/src/secure-client.ts +2 -2
- package/src/types.ts +4 -3
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
import type { Address, Hex } from 'viem'
|
|
3
|
+
import type {
|
|
4
|
+
RelayerClient,
|
|
5
|
+
PaymentMode,
|
|
6
|
+
EstimateResponse,
|
|
7
|
+
RelayResponse,
|
|
8
|
+
StatusResponse,
|
|
9
|
+
TransactionStatus,
|
|
10
|
+
Authorization7702,
|
|
11
|
+
} from '../relayer-client'
|
|
12
|
+
import type { ExecutionIntent, SignedIntent } from '../intent'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hook for relaying intents through the relayer
|
|
16
|
+
*/
|
|
17
|
+
export interface UseRelayIntentResult {
|
|
18
|
+
/** Relay a signed intent */
|
|
19
|
+
relay: (
|
|
20
|
+
chainId: number,
|
|
21
|
+
accountAddress: Address,
|
|
22
|
+
signedIntent: SignedIntent,
|
|
23
|
+
paymentMode: PaymentMode,
|
|
24
|
+
authorization?: Authorization7702
|
|
25
|
+
) => Promise<RelayResponse>
|
|
26
|
+
/** Whether a relay is in progress */
|
|
27
|
+
isRelaying: boolean
|
|
28
|
+
/** Last relay response */
|
|
29
|
+
response: RelayResponse | null
|
|
30
|
+
/** Last error */
|
|
31
|
+
error: Error | null
|
|
32
|
+
/** Reset state */
|
|
33
|
+
reset: () => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Hook for relaying signed intents through the Kentucky Signer Relayer
|
|
38
|
+
*
|
|
39
|
+
* @param client - Relayer client instance
|
|
40
|
+
* @returns Relay functions and state
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* const relayer = useRelayerClient('https://relayer.example.com')
|
|
45
|
+
* const { relay, isRelaying, response, error } = useRelayIntent(relayer)
|
|
46
|
+
*
|
|
47
|
+
* const handleSubmit = async () => {
|
|
48
|
+
* const result = await relay(42161, accountAddress, signedIntent, 'sponsored')
|
|
49
|
+
* if (result.success) {
|
|
50
|
+
* console.log('TX:', result.txHash)
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function useRelayIntent(client: RelayerClient): UseRelayIntentResult {
|
|
56
|
+
const [isRelaying, setIsRelaying] = useState(false)
|
|
57
|
+
const [response, setResponse] = useState<RelayResponse | null>(null)
|
|
58
|
+
const [error, setError] = useState<Error | null>(null)
|
|
59
|
+
|
|
60
|
+
const relay = useCallback(
|
|
61
|
+
async (
|
|
62
|
+
chainId: number,
|
|
63
|
+
accountAddress: Address,
|
|
64
|
+
signedIntent: SignedIntent,
|
|
65
|
+
paymentMode: PaymentMode,
|
|
66
|
+
authorization?: Authorization7702
|
|
67
|
+
): Promise<RelayResponse> => {
|
|
68
|
+
setIsRelaying(true)
|
|
69
|
+
setError(null)
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const result = await client.relay(chainId, accountAddress, signedIntent, paymentMode, authorization)
|
|
73
|
+
setResponse(result)
|
|
74
|
+
return result
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const error = err instanceof Error ? err : new Error('Relay failed')
|
|
77
|
+
setError(error)
|
|
78
|
+
return { success: false, error: error.message }
|
|
79
|
+
} finally {
|
|
80
|
+
setIsRelaying(false)
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[client]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
const reset = useCallback(() => {
|
|
87
|
+
setResponse(null)
|
|
88
|
+
setError(null)
|
|
89
|
+
}, [])
|
|
90
|
+
|
|
91
|
+
return { relay, isRelaying, response, error, reset }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Hook for tracking transaction status
|
|
96
|
+
*/
|
|
97
|
+
export interface UseTransactionStatusResult {
|
|
98
|
+
/** Current transaction status */
|
|
99
|
+
status: TransactionStatus | null
|
|
100
|
+
/** Full status response */
|
|
101
|
+
statusResponse: StatusResponse | null
|
|
102
|
+
/** Whether status is being fetched */
|
|
103
|
+
isLoading: boolean
|
|
104
|
+
/** Last error */
|
|
105
|
+
error: Error | null
|
|
106
|
+
/** Manually refresh status */
|
|
107
|
+
refresh: () => void
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Hook for tracking transaction status with polling
|
|
112
|
+
*
|
|
113
|
+
* @param client - Relayer client instance
|
|
114
|
+
* @param chainId - Chain ID
|
|
115
|
+
* @param txHash - Transaction hash to track (null to disable)
|
|
116
|
+
* @param pollInterval - Polling interval in ms (default: 3000)
|
|
117
|
+
* @returns Transaction status and state
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```tsx
|
|
121
|
+
* const { status, statusResponse, isLoading } = useTransactionStatus(
|
|
122
|
+
* relayer,
|
|
123
|
+
* 42161,
|
|
124
|
+
* txHash
|
|
125
|
+
* )
|
|
126
|
+
*
|
|
127
|
+
* if (status === 'confirmed') {
|
|
128
|
+
* console.log('Transaction confirmed in block:', statusResponse?.blockNumber)
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export function useTransactionStatus(
|
|
133
|
+
client: RelayerClient,
|
|
134
|
+
chainId: number,
|
|
135
|
+
txHash: Hex | null,
|
|
136
|
+
pollInterval: number = 3000
|
|
137
|
+
): UseTransactionStatusResult {
|
|
138
|
+
const [status, setStatus] = useState<TransactionStatus | null>(null)
|
|
139
|
+
const [statusResponse, setStatusResponse] = useState<StatusResponse | null>(null)
|
|
140
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
141
|
+
const [error, setError] = useState<Error | null>(null)
|
|
142
|
+
const intervalRef = useRef<NodeJS.Timeout | null>(null)
|
|
143
|
+
|
|
144
|
+
const fetchStatus = useCallback(async () => {
|
|
145
|
+
if (!txHash) return
|
|
146
|
+
|
|
147
|
+
setIsLoading(true)
|
|
148
|
+
try {
|
|
149
|
+
const response = await client.getStatus(chainId, txHash)
|
|
150
|
+
setStatusResponse(response)
|
|
151
|
+
setStatus(response.status)
|
|
152
|
+
setError(null)
|
|
153
|
+
|
|
154
|
+
// Stop polling if transaction is finalized
|
|
155
|
+
if (response.status === 'confirmed' || response.status === 'failed') {
|
|
156
|
+
if (intervalRef.current) {
|
|
157
|
+
clearInterval(intervalRef.current)
|
|
158
|
+
intervalRef.current = null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
setError(err instanceof Error ? err : new Error('Failed to fetch status'))
|
|
163
|
+
} finally {
|
|
164
|
+
setIsLoading(false)
|
|
165
|
+
}
|
|
166
|
+
}, [client, chainId, txHash])
|
|
167
|
+
|
|
168
|
+
// Start polling when txHash is set
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (!txHash) {
|
|
171
|
+
setStatus(null)
|
|
172
|
+
setStatusResponse(null)
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Fetch immediately
|
|
177
|
+
fetchStatus()
|
|
178
|
+
|
|
179
|
+
// Start polling
|
|
180
|
+
intervalRef.current = setInterval(fetchStatus, pollInterval)
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
if (intervalRef.current) {
|
|
184
|
+
clearInterval(intervalRef.current)
|
|
185
|
+
intervalRef.current = null
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}, [txHash, pollInterval, fetchStatus])
|
|
189
|
+
|
|
190
|
+
return { status, statusResponse, isLoading, error, refresh: fetchStatus }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Hook for estimating gas and fees
|
|
195
|
+
*/
|
|
196
|
+
export interface UseEstimateResult {
|
|
197
|
+
/** Estimate gas and fees */
|
|
198
|
+
estimate: (
|
|
199
|
+
chainId: number,
|
|
200
|
+
accountAddress: Address,
|
|
201
|
+
intent: ExecutionIntent
|
|
202
|
+
) => Promise<EstimateResponse | null>
|
|
203
|
+
/** Last estimate response */
|
|
204
|
+
estimateResponse: EstimateResponse | null
|
|
205
|
+
/** Whether estimate is in progress */
|
|
206
|
+
isEstimating: boolean
|
|
207
|
+
/** Last error */
|
|
208
|
+
error: Error | null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Hook for estimating gas and token fees
|
|
213
|
+
*
|
|
214
|
+
* @param client - Relayer client instance
|
|
215
|
+
* @returns Estimate functions and state
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```tsx
|
|
219
|
+
* const { estimate, estimateResponse, isEstimating } = useEstimate(relayer)
|
|
220
|
+
*
|
|
221
|
+
* useEffect(() => {
|
|
222
|
+
* estimate(42161, accountAddress, intent)
|
|
223
|
+
* }, [intent])
|
|
224
|
+
*
|
|
225
|
+
* if (estimateResponse) {
|
|
226
|
+
* console.log('Gas:', estimateResponse.gasEstimate)
|
|
227
|
+
* console.log('Tokens:', estimateResponse.tokenOptions)
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export function useEstimate(client: RelayerClient): UseEstimateResult {
|
|
232
|
+
const [estimateResponse, setEstimateResponse] = useState<EstimateResponse | null>(null)
|
|
233
|
+
const [isEstimating, setIsEstimating] = useState(false)
|
|
234
|
+
const [error, setError] = useState<Error | null>(null)
|
|
235
|
+
|
|
236
|
+
const estimate = useCallback(
|
|
237
|
+
async (
|
|
238
|
+
chainId: number,
|
|
239
|
+
accountAddress: Address,
|
|
240
|
+
intent: ExecutionIntent
|
|
241
|
+
): Promise<EstimateResponse | null> => {
|
|
242
|
+
setIsEstimating(true)
|
|
243
|
+
setError(null)
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const response = await client.estimate(chainId, accountAddress, intent)
|
|
247
|
+
setEstimateResponse(response)
|
|
248
|
+
return response
|
|
249
|
+
} catch (err) {
|
|
250
|
+
const error = err instanceof Error ? err : new Error('Estimate failed')
|
|
251
|
+
setError(error)
|
|
252
|
+
return null
|
|
253
|
+
} finally {
|
|
254
|
+
setIsEstimating(false)
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
[client]
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return { estimate, estimateResponse, isEstimating, error }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Hook for fetching account nonce
|
|
265
|
+
*/
|
|
266
|
+
export interface UseNonceResult {
|
|
267
|
+
/** Current nonce */
|
|
268
|
+
nonce: bigint | null
|
|
269
|
+
/** Whether nonce is being fetched */
|
|
270
|
+
isLoading: boolean
|
|
271
|
+
/** Last error */
|
|
272
|
+
error: Error | null
|
|
273
|
+
/** Refresh nonce */
|
|
274
|
+
refresh: () => void
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Hook for fetching account nonce
|
|
279
|
+
*
|
|
280
|
+
* @param client - Relayer client instance
|
|
281
|
+
* @param chainId - Chain ID
|
|
282
|
+
* @param address - Account address (null to disable)
|
|
283
|
+
* @returns Nonce and state
|
|
284
|
+
*/
|
|
285
|
+
export function useNonce(
|
|
286
|
+
client: RelayerClient,
|
|
287
|
+
chainId: number,
|
|
288
|
+
address: Address | null
|
|
289
|
+
): UseNonceResult {
|
|
290
|
+
const [nonce, setNonce] = useState<bigint | null>(null)
|
|
291
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
292
|
+
const [error, setError] = useState<Error | null>(null)
|
|
293
|
+
|
|
294
|
+
const fetchNonce = useCallback(async () => {
|
|
295
|
+
if (!address) return
|
|
296
|
+
|
|
297
|
+
setIsLoading(true)
|
|
298
|
+
try {
|
|
299
|
+
const result = await client.getNonce(chainId, address)
|
|
300
|
+
setNonce(result)
|
|
301
|
+
setError(null)
|
|
302
|
+
} catch (err) {
|
|
303
|
+
setError(err instanceof Error ? err : new Error('Failed to fetch nonce'))
|
|
304
|
+
} finally {
|
|
305
|
+
setIsLoading(false)
|
|
306
|
+
}
|
|
307
|
+
}, [client, chainId, address])
|
|
308
|
+
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
if (address) {
|
|
311
|
+
fetchNonce()
|
|
312
|
+
} else {
|
|
313
|
+
setNonce(null)
|
|
314
|
+
}
|
|
315
|
+
}, [address, fetchNonce])
|
|
316
|
+
|
|
317
|
+
return { nonce, isLoading, error, refresh: fetchNonce }
|
|
318
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import type { Address, Hex } from 'viem'
|
|
2
|
+
import type { ExecutionIntent, SignedIntent } from './intent'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Payment mode for relaying
|
|
6
|
+
*/
|
|
7
|
+
export type PaymentMode = 'sponsored' | { token: Address }
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* EIP-7702 Authorization for gasless onboarding
|
|
11
|
+
* When provided to relay(), allows users with 0 ETH to delegate their EOA
|
|
12
|
+
* to the smart account delegate in the same transaction as execution
|
|
13
|
+
*/
|
|
14
|
+
export interface Authorization7702 {
|
|
15
|
+
/** Chain ID (0 for all chains) */
|
|
16
|
+
chainId: number
|
|
17
|
+
/** Contract address to delegate to */
|
|
18
|
+
contractAddress: Address
|
|
19
|
+
/** Nonce for the authorization */
|
|
20
|
+
nonce: bigint
|
|
21
|
+
/** Recovery identifier (0 or 1) */
|
|
22
|
+
yParity: number
|
|
23
|
+
/** Signature r component */
|
|
24
|
+
r: Hex
|
|
25
|
+
/** Signature s component */
|
|
26
|
+
s: Hex
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Token payment option returned by estimate
|
|
31
|
+
*/
|
|
32
|
+
export interface TokenOption {
|
|
33
|
+
/** Token address */
|
|
34
|
+
token: Address
|
|
35
|
+
/** Token symbol */
|
|
36
|
+
symbol: string
|
|
37
|
+
/** Estimated fee in token units */
|
|
38
|
+
estimatedFee: string
|
|
39
|
+
/** Fee percentage (e.g., 5 = 5%) */
|
|
40
|
+
feePercentage: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gas estimate response
|
|
45
|
+
*/
|
|
46
|
+
export interface EstimateResponse {
|
|
47
|
+
/** Estimated gas units */
|
|
48
|
+
gasEstimate: string
|
|
49
|
+
/** Estimated gas cost in wei */
|
|
50
|
+
gasCostWei: string
|
|
51
|
+
/** Whether sponsored mode is available */
|
|
52
|
+
sponsoredAvailable: boolean
|
|
53
|
+
/** Available token payment options */
|
|
54
|
+
tokenOptions: TokenOption[]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Relay response
|
|
59
|
+
*/
|
|
60
|
+
export interface RelayResponse {
|
|
61
|
+
/** Whether the relay was successful */
|
|
62
|
+
success: boolean
|
|
63
|
+
/** Transaction hash if successful */
|
|
64
|
+
txHash?: Hex
|
|
65
|
+
/** Error message if failed */
|
|
66
|
+
error?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Transaction status
|
|
71
|
+
*/
|
|
72
|
+
export type TransactionStatus = 'pending' | 'confirmed' | 'failed'
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Status response
|
|
76
|
+
*/
|
|
77
|
+
export interface StatusResponse {
|
|
78
|
+
/** Current status */
|
|
79
|
+
status: TransactionStatus
|
|
80
|
+
/** Transaction hash */
|
|
81
|
+
txHash: Hex
|
|
82
|
+
/** Block number if confirmed */
|
|
83
|
+
blockNumber?: number
|
|
84
|
+
/** Gas used if confirmed */
|
|
85
|
+
gasUsed?: string
|
|
86
|
+
/** Token amount paid if applicable */
|
|
87
|
+
tokenPaid?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Relayer client options
|
|
92
|
+
*/
|
|
93
|
+
export interface RelayerClientOptions {
|
|
94
|
+
/** Relayer API base URL */
|
|
95
|
+
baseUrl: string
|
|
96
|
+
/** Request timeout in ms (default: 30000) */
|
|
97
|
+
timeout?: number
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Client for interacting with the Kentucky Signer Relayer API
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const relayer = new RelayerClient({ baseUrl: 'https://relayer.example.com' })
|
|
106
|
+
*
|
|
107
|
+
* // Get nonce
|
|
108
|
+
* const nonce = await relayer.getNonce(42161, accountAddress)
|
|
109
|
+
*
|
|
110
|
+
* // Create and sign intent
|
|
111
|
+
* const intent = createExecutionIntent({ nonce, target: '0x...' })
|
|
112
|
+
* const signed = await signIntent(account, intent)
|
|
113
|
+
*
|
|
114
|
+
* // Estimate fees
|
|
115
|
+
* const estimate = await relayer.estimate(42161, accountAddress, intent)
|
|
116
|
+
*
|
|
117
|
+
* // Relay transaction
|
|
118
|
+
* const result = await relayer.relay(42161, accountAddress, signed, 'sponsored')
|
|
119
|
+
* console.log('TX Hash:', result.txHash)
|
|
120
|
+
*
|
|
121
|
+
* // Check status
|
|
122
|
+
* const status = await relayer.getStatus(42161, result.txHash!)
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export class RelayerClient {
|
|
126
|
+
private baseUrl: string
|
|
127
|
+
private timeout: number
|
|
128
|
+
|
|
129
|
+
constructor(options: RelayerClientOptions) {
|
|
130
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, '') // Remove trailing slash
|
|
131
|
+
this.timeout = options.timeout ?? 30000
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if the relayer is healthy
|
|
136
|
+
*/
|
|
137
|
+
async health(): Promise<{ status: string; relayer: Address; timestamp: string }> {
|
|
138
|
+
const response = await this.fetch('/health')
|
|
139
|
+
return response
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the current nonce for an account
|
|
144
|
+
*
|
|
145
|
+
* @param chainId - Chain ID
|
|
146
|
+
* @param address - Account address
|
|
147
|
+
* @returns Current nonce as bigint
|
|
148
|
+
*/
|
|
149
|
+
async getNonce(chainId: number, address: Address): Promise<bigint> {
|
|
150
|
+
const response = await this.fetch(`/nonce/${chainId}/${address}`)
|
|
151
|
+
return BigInt(response.nonce)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Estimate gas and fees for an intent
|
|
156
|
+
*
|
|
157
|
+
* @param chainId - Chain ID
|
|
158
|
+
* @param accountAddress - Account address (the delegated EOA)
|
|
159
|
+
* @param intent - Execution intent
|
|
160
|
+
* @returns Estimate response
|
|
161
|
+
*/
|
|
162
|
+
async estimate(
|
|
163
|
+
chainId: number,
|
|
164
|
+
accountAddress: Address,
|
|
165
|
+
intent: ExecutionIntent
|
|
166
|
+
): Promise<EstimateResponse> {
|
|
167
|
+
const response = await this.fetch('/estimate', {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: JSON.stringify({
|
|
170
|
+
chainId,
|
|
171
|
+
accountAddress,
|
|
172
|
+
intent: {
|
|
173
|
+
nonce: intent.nonce.toString(),
|
|
174
|
+
deadline: intent.deadline.toString(),
|
|
175
|
+
target: intent.target,
|
|
176
|
+
value: intent.value.toString(),
|
|
177
|
+
data: intent.data,
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
})
|
|
181
|
+
return response
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Relay a signed intent
|
|
186
|
+
*
|
|
187
|
+
* @param chainId - Chain ID
|
|
188
|
+
* @param accountAddress - Account address (the delegated EOA)
|
|
189
|
+
* @param signedIntent - Signed execution intent
|
|
190
|
+
* @param paymentMode - Payment mode ('sponsored' or { token: Address })
|
|
191
|
+
* @param authorization - Optional EIP-7702 authorization for gasless onboarding
|
|
192
|
+
* @returns Relay response with transaction hash
|
|
193
|
+
*
|
|
194
|
+
* @example Gasless onboarding (delegate + execute in one tx)
|
|
195
|
+
* ```typescript
|
|
196
|
+
* // Get current nonce for authorization
|
|
197
|
+
* const txNonce = await publicClient.getTransactionCount({ address: accountAddress })
|
|
198
|
+
*
|
|
199
|
+
* // Sign EIP-7702 authorization
|
|
200
|
+
* const authorization = await account.sign7702Authorization({
|
|
201
|
+
* contractAddress: delegateAddress,
|
|
202
|
+
* chainId: 42161,
|
|
203
|
+
* }, txNonce)
|
|
204
|
+
*
|
|
205
|
+
* // Relay with authorization
|
|
206
|
+
* const result = await relayer.relay(
|
|
207
|
+
* 42161,
|
|
208
|
+
* accountAddress,
|
|
209
|
+
* signedIntent,
|
|
210
|
+
* 'sponsored',
|
|
211
|
+
* authorization
|
|
212
|
+
* )
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
async relay(
|
|
216
|
+
chainId: number,
|
|
217
|
+
accountAddress: Address,
|
|
218
|
+
signedIntent: SignedIntent,
|
|
219
|
+
paymentMode: PaymentMode,
|
|
220
|
+
authorization?: Authorization7702
|
|
221
|
+
): Promise<RelayResponse> {
|
|
222
|
+
const body: any = {
|
|
223
|
+
chainId,
|
|
224
|
+
accountAddress,
|
|
225
|
+
intent: {
|
|
226
|
+
nonce: signedIntent.intent.nonce.toString(),
|
|
227
|
+
deadline: signedIntent.intent.deadline.toString(),
|
|
228
|
+
target: signedIntent.intent.target,
|
|
229
|
+
value: signedIntent.intent.value.toString(),
|
|
230
|
+
data: signedIntent.intent.data,
|
|
231
|
+
},
|
|
232
|
+
ownerSignature: signedIntent.signature,
|
|
233
|
+
paymentMode,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Include authorization for gasless onboarding if provided
|
|
237
|
+
if (authorization) {
|
|
238
|
+
body.authorization = {
|
|
239
|
+
chainId: authorization.chainId,
|
|
240
|
+
contractAddress: authorization.contractAddress,
|
|
241
|
+
nonce: authorization.nonce.toString(),
|
|
242
|
+
yParity: authorization.yParity,
|
|
243
|
+
r: authorization.r,
|
|
244
|
+
s: authorization.s,
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const response = await this.fetch('/relay', {
|
|
249
|
+
method: 'POST',
|
|
250
|
+
body: JSON.stringify(body),
|
|
251
|
+
})
|
|
252
|
+
return response
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get transaction status
|
|
257
|
+
*
|
|
258
|
+
* @param chainId - Chain ID
|
|
259
|
+
* @param txHash - Transaction hash
|
|
260
|
+
* @returns Status response
|
|
261
|
+
*/
|
|
262
|
+
async getStatus(chainId: number, txHash: Hex): Promise<StatusResponse> {
|
|
263
|
+
const response = await this.fetch(`/status/${chainId}/${txHash}`)
|
|
264
|
+
return response
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Make a fetch request to the relayer API
|
|
269
|
+
*/
|
|
270
|
+
private async fetch(path: string, options?: RequestInit): Promise<any> {
|
|
271
|
+
const controller = new AbortController()
|
|
272
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
276
|
+
...options,
|
|
277
|
+
headers: {
|
|
278
|
+
'Content-Type': 'application/json',
|
|
279
|
+
...options?.headers,
|
|
280
|
+
},
|
|
281
|
+
signal: controller.signal,
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const data = await response.json()
|
|
285
|
+
|
|
286
|
+
if (!response.ok) {
|
|
287
|
+
throw new Error(data.error || `Request failed: ${response.status}`)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return data
|
|
291
|
+
} finally {
|
|
292
|
+
clearTimeout(timeoutId)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Create a relayer client
|
|
299
|
+
*
|
|
300
|
+
* @param baseUrl - Relayer API base URL
|
|
301
|
+
* @returns Relayer client instance
|
|
302
|
+
*/
|
|
303
|
+
export function createRelayerClient(baseUrl: string): RelayerClient {
|
|
304
|
+
return new RelayerClient({ baseUrl })
|
|
305
|
+
}
|
package/src/secure-client.ts
CHANGED
|
@@ -296,9 +296,9 @@ export class SecureKentuckySignerClient {
|
|
|
296
296
|
/**
|
|
297
297
|
* Sign a raw hash for EVM (signed request)
|
|
298
298
|
*/
|
|
299
|
-
async signHash(hash: Hex,
|
|
299
|
+
async signHash(hash: Hex, token: string): Promise<Hex> {
|
|
300
300
|
const response = await this.signEvmTransaction(
|
|
301
|
-
{ tx_hash: hash
|
|
301
|
+
{ tx_hash: hash },
|
|
302
302
|
token
|
|
303
303
|
)
|
|
304
304
|
return response.signature.full
|
package/src/types.ts
CHANGED
|
@@ -66,16 +66,19 @@ export interface AccountInfoResponse {
|
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* EVM signature response from Kentucky Signer
|
|
69
|
+
*
|
|
70
|
+
* Note: v is always 27 or 28 (standard format).
|
|
71
|
+
* EIP-155 encoding should be applied by the caller when needed for legacy transactions.
|
|
69
72
|
*/
|
|
70
73
|
export interface EvmSignatureResponse {
|
|
71
74
|
success: boolean
|
|
72
75
|
signature: {
|
|
73
76
|
r: Hex
|
|
74
77
|
s: Hex
|
|
78
|
+
/** v value: 27 or 28 (recovery_id + 27) */
|
|
75
79
|
v: number
|
|
76
80
|
full: Hex
|
|
77
81
|
}
|
|
78
|
-
chain_id: number
|
|
79
82
|
signer_address: string
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -163,8 +166,6 @@ export interface ClientOptions {
|
|
|
163
166
|
export interface SignEvmRequest {
|
|
164
167
|
/** Transaction hash to sign (32 bytes, hex encoded) */
|
|
165
168
|
tx_hash: Hex
|
|
166
|
-
/** Chain ID */
|
|
167
|
-
chain_id: number
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
/**
|