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.
@@ -21,14 +21,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  KentuckySignerProvider: () => KentuckySignerProvider,
24
+ RelayerClient: () => RelayerClient,
25
+ createExecutionIntent: () => createExecutionIntent,
26
+ createRelayerClient: () => createRelayerClient,
27
+ signIntent: () => signIntent,
24
28
  useAddress: () => useAddress,
29
+ useEstimate: () => useEstimate,
25
30
  useIsReady: () => useIsReady,
26
31
  useKentuckySigner: () => useKentuckySigner,
27
32
  useKentuckySignerAccount: () => useKentuckySignerAccount,
28
33
  useKentuckySignerContext: () => useKentuckySignerContext,
34
+ useNonce: () => useNonce,
29
35
  usePasskeyAuth: () => usePasskeyAuth,
36
+ useRelayIntent: () => useRelayIntent,
30
37
  useSignMessage: () => useSignMessage,
31
38
  useSignTypedData: () => useSignTypedData,
39
+ useTransactionStatus: () => useTransactionStatus,
32
40
  useWalletClient: () => useWalletClient
33
41
  });
34
42
  module.exports = __toCommonJS(index_exports);
@@ -180,9 +188,9 @@ var KentuckySignerClient = class {
180
188
  /**
181
189
  * Sign an EVM transaction hash
182
190
  *
183
- * @param request - Sign request with tx_hash and chain_id
191
+ * @param request - Sign request with tx_hash
184
192
  * @param token - JWT token
185
- * @returns Signature response with r, s, v components
193
+ * @returns Signature response with r, s, v components (v is always 27 or 28)
186
194
  */
187
195
  async signEvmTransaction(request, token) {
188
196
  return this.request("/api/sign/evm", {
@@ -197,13 +205,12 @@ var KentuckySignerClient = class {
197
205
  * Convenience method that wraps signEvmTransaction.
198
206
  *
199
207
  * @param hash - 32-byte hash to sign (hex encoded with 0x prefix)
200
- * @param chainId - Chain ID
201
208
  * @param token - JWT token
202
209
  * @returns Full signature (hex encoded with 0x prefix)
203
210
  */
204
- async signHash(hash, chainId, token) {
211
+ async signHash(hash, token) {
205
212
  const response = await this.signEvmTransaction(
206
- { tx_hash: hash, chain_id: chainId },
213
+ { tx_hash: hash },
207
214
  token
208
215
  );
209
216
  return response.signature.full;
@@ -1037,9 +1044,9 @@ var SecureKentuckySignerClient = class {
1037
1044
  /**
1038
1045
  * Sign a raw hash for EVM (signed request)
1039
1046
  */
1040
- async signHash(hash, chainId, token) {
1047
+ async signHash(hash, token) {
1041
1048
  const response = await this.signEvmTransaction(
1042
- { tx_hash: hash, chain_id: chainId },
1049
+ { tx_hash: hash },
1043
1050
  token
1044
1051
  );
1045
1052
  return response.signature.full;
@@ -1259,6 +1266,15 @@ async function authenticateWithPassword(options) {
1259
1266
  // src/account.ts
1260
1267
  var import_viem = require("viem");
1261
1268
  var import_accounts = require("viem/accounts");
1269
+ var EIP7702_MAGIC = "0x05";
1270
+ function hashAuthorization(params) {
1271
+ const rlpEncoded = (0, import_viem.toRlp)([
1272
+ params.chainId === 0 ? "0x" : (0, import_viem.numberToHex)(params.chainId),
1273
+ params.contractAddress,
1274
+ params.nonce === 0n ? "0x" : (0, import_viem.numberToHex)(params.nonce)
1275
+ ]);
1276
+ return (0, import_viem.keccak256)((0, import_viem.concat)([EIP7702_MAGIC, rlpEncoded]));
1277
+ }
1262
1278
  function createKentuckySignerAccount(options) {
1263
1279
  const { config, defaultChainId = 1, onSessionExpired, secureClient, on2FARequired } = options;
1264
1280
  let session = options.session;
@@ -1277,14 +1293,17 @@ function createKentuckySignerAccount(options) {
1277
1293
  }
1278
1294
  return session.token;
1279
1295
  }
1280
- async function signHash(hash, chainId) {
1296
+ function ensureHexPrefix(value) {
1297
+ return value.startsWith("0x") ? value : `0x${value}`;
1298
+ }
1299
+ async function signHash(hash) {
1281
1300
  const token = await getToken();
1282
1301
  try {
1283
1302
  const response = await client.signEvmTransaction(
1284
- { tx_hash: hash, chain_id: chainId },
1303
+ { tx_hash: hash },
1285
1304
  token
1286
1305
  );
1287
- return response.signature.full;
1306
+ return ensureHexPrefix(response.signature.full);
1288
1307
  } catch (err) {
1289
1308
  if (err instanceof KentuckySignerError && err.code === "2FA_REQUIRED" && on2FARequired) {
1290
1309
  const totpRequired = err.message.includes("TOTP") || (err.details?.includes("totp_code") ?? false);
@@ -1295,24 +1314,24 @@ function createKentuckySignerAccount(options) {
1295
1314
  throw new KentuckySignerError("2FA verification cancelled", "2FA_CANCELLED", "User cancelled 2FA input");
1296
1315
  }
1297
1316
  const response = await client.signEvmTransactionWith2FA(
1298
- { tx_hash: hash, chain_id: chainId, totp_code: codes.totpCode, pin: codes.pin },
1317
+ { tx_hash: hash, totp_code: codes.totpCode, pin: codes.pin },
1299
1318
  token
1300
1319
  );
1301
- return response.signature.full;
1320
+ return ensureHexPrefix(response.signature.full);
1302
1321
  }
1303
1322
  throw err;
1304
1323
  }
1305
1324
  }
1306
- async function signHashWithComponents(hash, chainId) {
1325
+ async function signHashWithComponents(hash) {
1307
1326
  const token = await getToken();
1308
1327
  try {
1309
1328
  const response = await client.signEvmTransaction(
1310
- { tx_hash: hash, chain_id: chainId },
1329
+ { tx_hash: hash },
1311
1330
  token
1312
1331
  );
1313
1332
  return {
1314
- r: response.signature.r,
1315
- s: response.signature.s,
1333
+ r: ensureHexPrefix(response.signature.r),
1334
+ s: ensureHexPrefix(response.signature.s),
1316
1335
  v: response.signature.v
1317
1336
  };
1318
1337
  } catch (err) {
@@ -1325,12 +1344,12 @@ function createKentuckySignerAccount(options) {
1325
1344
  throw new KentuckySignerError("2FA verification cancelled", "2FA_CANCELLED", "User cancelled 2FA input");
1326
1345
  }
1327
1346
  const response = await client.signEvmTransactionWith2FA(
1328
- { tx_hash: hash, chain_id: chainId, totp_code: codes.totpCode, pin: codes.pin },
1347
+ { tx_hash: hash, totp_code: codes.totpCode, pin: codes.pin },
1329
1348
  token
1330
1349
  );
1331
1350
  return {
1332
- r: response.signature.r,
1333
- s: response.signature.s,
1351
+ r: ensureHexPrefix(response.signature.r),
1352
+ s: ensureHexPrefix(response.signature.s),
1334
1353
  v: response.signature.v
1335
1354
  };
1336
1355
  }
@@ -1346,29 +1365,36 @@ function createKentuckySignerAccount(options) {
1346
1365
  */
1347
1366
  async signMessage({ message }) {
1348
1367
  const messageHash = (0, import_viem.hashMessage)(message);
1349
- return signHash(messageHash, defaultChainId);
1368
+ return signHash(messageHash);
1350
1369
  },
1351
1370
  /**
1352
1371
  * Sign a transaction
1353
1372
  *
1354
1373
  * Serializes the transaction, hashes it, signs via Kentucky Signer,
1355
1374
  * and returns the signed serialized transaction.
1375
+ *
1376
+ * For legacy transactions, applies EIP-155 encoding (v = chainId * 2 + 35 + recoveryId)
1377
+ * For modern transactions (EIP-1559, EIP-2930, etc.), uses yParity (0 or 1)
1356
1378
  */
1357
1379
  async signTransaction(transaction) {
1358
1380
  const chainId = transaction.chainId ?? defaultChainId;
1359
1381
  const serializedUnsigned = (0, import_viem.serializeTransaction)(transaction);
1360
1382
  const txHash = (0, import_viem.keccak256)(serializedUnsigned);
1361
- const { r, s, v } = await signHashWithComponents(txHash, chainId);
1383
+ const { r, s, v } = await signHashWithComponents(txHash);
1384
+ const recoveryId = v - 27;
1385
+ let signatureV;
1362
1386
  let yParity;
1363
1387
  if (transaction.type === "eip1559" || transaction.type === "eip2930" || transaction.type === "eip4844" || transaction.type === "eip7702") {
1364
- yParity = v >= 27 ? v - 27 : v;
1388
+ yParity = recoveryId;
1389
+ signatureV = BigInt(yParity);
1365
1390
  } else {
1366
- yParity = v;
1391
+ signatureV = BigInt(chainId * 2 + 35 + recoveryId);
1392
+ yParity = recoveryId;
1367
1393
  }
1368
1394
  const serializedSigned = (0, import_viem.serializeTransaction)(transaction, {
1369
1395
  r,
1370
1396
  s,
1371
- v: BigInt(yParity),
1397
+ v: signatureV,
1372
1398
  yParity
1373
1399
  });
1374
1400
  return serializedSigned;
@@ -1378,7 +1404,7 @@ function createKentuckySignerAccount(options) {
1378
1404
  */
1379
1405
  async signTypedData(typedData) {
1380
1406
  const hash = (0, import_viem.hashTypedData)(typedData);
1381
- return signHash(hash, defaultChainId);
1407
+ return signHash(hash);
1382
1408
  }
1383
1409
  });
1384
1410
  account.source = "kentuckySigner";
@@ -1391,6 +1417,25 @@ function createKentuckySignerAccount(options) {
1391
1417
  account.address = newSession.evmAddress;
1392
1418
  }
1393
1419
  };
1420
+ account.sign7702Authorization = async (params, currentNonce) => {
1421
+ const authNonce = params.executor === "self" ? currentNonce + 1n : params.nonce ?? currentNonce;
1422
+ const chainId = params.chainId ?? defaultChainId;
1423
+ const authHash = hashAuthorization({
1424
+ contractAddress: params.contractAddress,
1425
+ chainId,
1426
+ nonce: authNonce
1427
+ });
1428
+ const { r, s, v } = await signHashWithComponents(authHash);
1429
+ const yParity = v - 27;
1430
+ return {
1431
+ chainId,
1432
+ contractAddress: params.contractAddress,
1433
+ nonce: authNonce,
1434
+ yParity,
1435
+ r,
1436
+ s
1437
+ };
1438
+ };
1394
1439
  return account;
1395
1440
  }
1396
1441
 
@@ -1912,17 +1957,337 @@ function useAddress() {
1912
1957
  const { account } = useKentuckySignerContext();
1913
1958
  return account?.address;
1914
1959
  }
1960
+
1961
+ // src/react/relayer-hooks.ts
1962
+ var import_react3 = require("react");
1963
+ function useRelayIntent(client) {
1964
+ const [isRelaying, setIsRelaying] = (0, import_react3.useState)(false);
1965
+ const [response, setResponse] = (0, import_react3.useState)(null);
1966
+ const [error, setError] = (0, import_react3.useState)(null);
1967
+ const relay = (0, import_react3.useCallback)(
1968
+ async (chainId, accountAddress, signedIntent, paymentMode, authorization) => {
1969
+ setIsRelaying(true);
1970
+ setError(null);
1971
+ try {
1972
+ const result = await client.relay(chainId, accountAddress, signedIntent, paymentMode, authorization);
1973
+ setResponse(result);
1974
+ return result;
1975
+ } catch (err) {
1976
+ const error2 = err instanceof Error ? err : new Error("Relay failed");
1977
+ setError(error2);
1978
+ return { success: false, error: error2.message };
1979
+ } finally {
1980
+ setIsRelaying(false);
1981
+ }
1982
+ },
1983
+ [client]
1984
+ );
1985
+ const reset = (0, import_react3.useCallback)(() => {
1986
+ setResponse(null);
1987
+ setError(null);
1988
+ }, []);
1989
+ return { relay, isRelaying, response, error, reset };
1990
+ }
1991
+ function useTransactionStatus(client, chainId, txHash, pollInterval = 3e3) {
1992
+ const [status, setStatus] = (0, import_react3.useState)(null);
1993
+ const [statusResponse, setStatusResponse] = (0, import_react3.useState)(null);
1994
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
1995
+ const [error, setError] = (0, import_react3.useState)(null);
1996
+ const intervalRef = (0, import_react3.useRef)(null);
1997
+ const fetchStatus = (0, import_react3.useCallback)(async () => {
1998
+ if (!txHash) return;
1999
+ setIsLoading(true);
2000
+ try {
2001
+ const response = await client.getStatus(chainId, txHash);
2002
+ setStatusResponse(response);
2003
+ setStatus(response.status);
2004
+ setError(null);
2005
+ if (response.status === "confirmed" || response.status === "failed") {
2006
+ if (intervalRef.current) {
2007
+ clearInterval(intervalRef.current);
2008
+ intervalRef.current = null;
2009
+ }
2010
+ }
2011
+ } catch (err) {
2012
+ setError(err instanceof Error ? err : new Error("Failed to fetch status"));
2013
+ } finally {
2014
+ setIsLoading(false);
2015
+ }
2016
+ }, [client, chainId, txHash]);
2017
+ (0, import_react3.useEffect)(() => {
2018
+ if (!txHash) {
2019
+ setStatus(null);
2020
+ setStatusResponse(null);
2021
+ return;
2022
+ }
2023
+ fetchStatus();
2024
+ intervalRef.current = setInterval(fetchStatus, pollInterval);
2025
+ return () => {
2026
+ if (intervalRef.current) {
2027
+ clearInterval(intervalRef.current);
2028
+ intervalRef.current = null;
2029
+ }
2030
+ };
2031
+ }, [txHash, pollInterval, fetchStatus]);
2032
+ return { status, statusResponse, isLoading, error, refresh: fetchStatus };
2033
+ }
2034
+ function useEstimate(client) {
2035
+ const [estimateResponse, setEstimateResponse] = (0, import_react3.useState)(null);
2036
+ const [isEstimating, setIsEstimating] = (0, import_react3.useState)(false);
2037
+ const [error, setError] = (0, import_react3.useState)(null);
2038
+ const estimate = (0, import_react3.useCallback)(
2039
+ async (chainId, accountAddress, intent) => {
2040
+ setIsEstimating(true);
2041
+ setError(null);
2042
+ try {
2043
+ const response = await client.estimate(chainId, accountAddress, intent);
2044
+ setEstimateResponse(response);
2045
+ return response;
2046
+ } catch (err) {
2047
+ const error2 = err instanceof Error ? err : new Error("Estimate failed");
2048
+ setError(error2);
2049
+ return null;
2050
+ } finally {
2051
+ setIsEstimating(false);
2052
+ }
2053
+ },
2054
+ [client]
2055
+ );
2056
+ return { estimate, estimateResponse, isEstimating, error };
2057
+ }
2058
+ function useNonce(client, chainId, address) {
2059
+ const [nonce, setNonce] = (0, import_react3.useState)(null);
2060
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
2061
+ const [error, setError] = (0, import_react3.useState)(null);
2062
+ const fetchNonce = (0, import_react3.useCallback)(async () => {
2063
+ if (!address) return;
2064
+ setIsLoading(true);
2065
+ try {
2066
+ const result = await client.getNonce(chainId, address);
2067
+ setNonce(result);
2068
+ setError(null);
2069
+ } catch (err) {
2070
+ setError(err instanceof Error ? err : new Error("Failed to fetch nonce"));
2071
+ } finally {
2072
+ setIsLoading(false);
2073
+ }
2074
+ }, [client, chainId, address]);
2075
+ (0, import_react3.useEffect)(() => {
2076
+ if (address) {
2077
+ fetchNonce();
2078
+ } else {
2079
+ setNonce(null);
2080
+ }
2081
+ }, [address, fetchNonce]);
2082
+ return { nonce, isLoading, error, refresh: fetchNonce };
2083
+ }
2084
+
2085
+ // src/relayer-client.ts
2086
+ var RelayerClient = class {
2087
+ constructor(options) {
2088
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
2089
+ this.timeout = options.timeout ?? 3e4;
2090
+ }
2091
+ /**
2092
+ * Check if the relayer is healthy
2093
+ */
2094
+ async health() {
2095
+ const response = await this.fetch("/health");
2096
+ return response;
2097
+ }
2098
+ /**
2099
+ * Get the current nonce for an account
2100
+ *
2101
+ * @param chainId - Chain ID
2102
+ * @param address - Account address
2103
+ * @returns Current nonce as bigint
2104
+ */
2105
+ async getNonce(chainId, address) {
2106
+ const response = await this.fetch(`/nonce/${chainId}/${address}`);
2107
+ return BigInt(response.nonce);
2108
+ }
2109
+ /**
2110
+ * Estimate gas and fees for an intent
2111
+ *
2112
+ * @param chainId - Chain ID
2113
+ * @param accountAddress - Account address (the delegated EOA)
2114
+ * @param intent - Execution intent
2115
+ * @returns Estimate response
2116
+ */
2117
+ async estimate(chainId, accountAddress, intent) {
2118
+ const response = await this.fetch("/estimate", {
2119
+ method: "POST",
2120
+ body: JSON.stringify({
2121
+ chainId,
2122
+ accountAddress,
2123
+ intent: {
2124
+ nonce: intent.nonce.toString(),
2125
+ deadline: intent.deadline.toString(),
2126
+ target: intent.target,
2127
+ value: intent.value.toString(),
2128
+ data: intent.data
2129
+ }
2130
+ })
2131
+ });
2132
+ return response;
2133
+ }
2134
+ /**
2135
+ * Relay a signed intent
2136
+ *
2137
+ * @param chainId - Chain ID
2138
+ * @param accountAddress - Account address (the delegated EOA)
2139
+ * @param signedIntent - Signed execution intent
2140
+ * @param paymentMode - Payment mode ('sponsored' or { token: Address })
2141
+ * @param authorization - Optional EIP-7702 authorization for gasless onboarding
2142
+ * @returns Relay response with transaction hash
2143
+ *
2144
+ * @example Gasless onboarding (delegate + execute in one tx)
2145
+ * ```typescript
2146
+ * // Get current nonce for authorization
2147
+ * const txNonce = await publicClient.getTransactionCount({ address: accountAddress })
2148
+ *
2149
+ * // Sign EIP-7702 authorization
2150
+ * const authorization = await account.sign7702Authorization({
2151
+ * contractAddress: delegateAddress,
2152
+ * chainId: 42161,
2153
+ * }, txNonce)
2154
+ *
2155
+ * // Relay with authorization
2156
+ * const result = await relayer.relay(
2157
+ * 42161,
2158
+ * accountAddress,
2159
+ * signedIntent,
2160
+ * 'sponsored',
2161
+ * authorization
2162
+ * )
2163
+ * ```
2164
+ */
2165
+ async relay(chainId, accountAddress, signedIntent, paymentMode, authorization) {
2166
+ const body = {
2167
+ chainId,
2168
+ accountAddress,
2169
+ intent: {
2170
+ nonce: signedIntent.intent.nonce.toString(),
2171
+ deadline: signedIntent.intent.deadline.toString(),
2172
+ target: signedIntent.intent.target,
2173
+ value: signedIntent.intent.value.toString(),
2174
+ data: signedIntent.intent.data
2175
+ },
2176
+ ownerSignature: signedIntent.signature,
2177
+ paymentMode
2178
+ };
2179
+ if (authorization) {
2180
+ body.authorization = {
2181
+ chainId: authorization.chainId,
2182
+ contractAddress: authorization.contractAddress,
2183
+ nonce: authorization.nonce.toString(),
2184
+ yParity: authorization.yParity,
2185
+ r: authorization.r,
2186
+ s: authorization.s
2187
+ };
2188
+ }
2189
+ const response = await this.fetch("/relay", {
2190
+ method: "POST",
2191
+ body: JSON.stringify(body)
2192
+ });
2193
+ return response;
2194
+ }
2195
+ /**
2196
+ * Get transaction status
2197
+ *
2198
+ * @param chainId - Chain ID
2199
+ * @param txHash - Transaction hash
2200
+ * @returns Status response
2201
+ */
2202
+ async getStatus(chainId, txHash) {
2203
+ const response = await this.fetch(`/status/${chainId}/${txHash}`);
2204
+ return response;
2205
+ }
2206
+ /**
2207
+ * Make a fetch request to the relayer API
2208
+ */
2209
+ async fetch(path, options) {
2210
+ const controller = new AbortController();
2211
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2212
+ try {
2213
+ const response = await fetch(`${this.baseUrl}${path}`, {
2214
+ ...options,
2215
+ headers: {
2216
+ "Content-Type": "application/json",
2217
+ ...options?.headers
2218
+ },
2219
+ signal: controller.signal
2220
+ });
2221
+ const data = await response.json();
2222
+ if (!response.ok) {
2223
+ throw new Error(data.error || `Request failed: ${response.status}`);
2224
+ }
2225
+ return data;
2226
+ } finally {
2227
+ clearTimeout(timeoutId);
2228
+ }
2229
+ }
2230
+ };
2231
+ function createRelayerClient(baseUrl) {
2232
+ return new RelayerClient({ baseUrl });
2233
+ }
2234
+
2235
+ // src/intent.ts
2236
+ var import_viem3 = require("viem");
2237
+ var INTENT_TYPEHASH = (0, import_viem3.keccak256)(
2238
+ (0, import_viem3.encodePacked)(
2239
+ ["string"],
2240
+ ["ExecutionIntent(uint256 nonce,uint256 deadline,address target,uint256 value,bytes data)"]
2241
+ )
2242
+ );
2243
+ function createExecutionIntent(params) {
2244
+ return {
2245
+ nonce: params.nonce,
2246
+ deadline: params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 3600),
2247
+ // 1 hour default
2248
+ target: params.target,
2249
+ value: params.value ?? 0n,
2250
+ data: params.data ?? "0x"
2251
+ };
2252
+ }
2253
+ function hashIntent(intent) {
2254
+ const dataHash = (0, import_viem3.keccak256)(intent.data);
2255
+ return (0, import_viem3.keccak256)(
2256
+ (0, import_viem3.encodeAbiParameters)(
2257
+ (0, import_viem3.parseAbiParameters)("bytes32, uint256, uint256, address, uint256, bytes32"),
2258
+ [INTENT_TYPEHASH, intent.nonce, intent.deadline, intent.target, intent.value, dataHash]
2259
+ )
2260
+ );
2261
+ }
2262
+ async function signIntent(account, intent) {
2263
+ const intentHash = hashIntent(intent);
2264
+ const signature = await account.signMessage({
2265
+ message: { raw: intentHash }
2266
+ });
2267
+ return {
2268
+ intent,
2269
+ signature
2270
+ };
2271
+ }
1915
2272
  // Annotate the CommonJS export names for ESM import in node:
1916
2273
  0 && (module.exports = {
1917
2274
  KentuckySignerProvider,
2275
+ RelayerClient,
2276
+ createExecutionIntent,
2277
+ createRelayerClient,
2278
+ signIntent,
1918
2279
  useAddress,
2280
+ useEstimate,
1919
2281
  useIsReady,
1920
2282
  useKentuckySigner,
1921
2283
  useKentuckySignerAccount,
1922
2284
  useKentuckySignerContext,
2285
+ useNonce,
1923
2286
  usePasskeyAuth,
2287
+ useRelayIntent,
1924
2288
  useSignMessage,
1925
2289
  useSignTypedData,
2290
+ useTransactionStatus,
1926
2291
  useWalletClient
1927
2292
  });
1928
2293
  //# sourceMappingURL=index.js.map