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.
@@ -153,9 +153,9 @@ var KentuckySignerClient = class {
153
153
  /**
154
154
  * Sign an EVM transaction hash
155
155
  *
156
- * @param request - Sign request with tx_hash and chain_id
156
+ * @param request - Sign request with tx_hash
157
157
  * @param token - JWT token
158
- * @returns Signature response with r, s, v components
158
+ * @returns Signature response with r, s, v components (v is always 27 or 28)
159
159
  */
160
160
  async signEvmTransaction(request, token) {
161
161
  return this.request("/api/sign/evm", {
@@ -170,13 +170,12 @@ var KentuckySignerClient = class {
170
170
  * Convenience method that wraps signEvmTransaction.
171
171
  *
172
172
  * @param hash - 32-byte hash to sign (hex encoded with 0x prefix)
173
- * @param chainId - Chain ID
174
173
  * @param token - JWT token
175
174
  * @returns Full signature (hex encoded with 0x prefix)
176
175
  */
177
- async signHash(hash, chainId, token) {
176
+ async signHash(hash, token) {
178
177
  const response = await this.signEvmTransaction(
179
- { tx_hash: hash, chain_id: chainId },
178
+ { tx_hash: hash },
180
179
  token
181
180
  );
182
181
  return response.signature.full;
@@ -1010,9 +1009,9 @@ var SecureKentuckySignerClient = class {
1010
1009
  /**
1011
1010
  * Sign a raw hash for EVM (signed request)
1012
1011
  */
1013
- async signHash(hash, chainId, token) {
1012
+ async signHash(hash, token) {
1014
1013
  const response = await this.signEvmTransaction(
1015
- { tx_hash: hash, chain_id: chainId },
1014
+ { tx_hash: hash },
1016
1015
  token
1017
1016
  );
1018
1017
  return response.signature.full;
@@ -1234,9 +1233,21 @@ import {
1234
1233
  hashMessage,
1235
1234
  hashTypedData,
1236
1235
  keccak256,
1237
- serializeTransaction
1236
+ serializeTransaction,
1237
+ toRlp,
1238
+ concat,
1239
+ numberToHex
1238
1240
  } from "viem";
1239
1241
  import { toAccount } from "viem/accounts";
1242
+ var EIP7702_MAGIC = "0x05";
1243
+ function hashAuthorization(params) {
1244
+ const rlpEncoded = toRlp([
1245
+ params.chainId === 0 ? "0x" : numberToHex(params.chainId),
1246
+ params.contractAddress,
1247
+ params.nonce === 0n ? "0x" : numberToHex(params.nonce)
1248
+ ]);
1249
+ return keccak256(concat([EIP7702_MAGIC, rlpEncoded]));
1250
+ }
1240
1251
  function createKentuckySignerAccount(options) {
1241
1252
  const { config, defaultChainId = 1, onSessionExpired, secureClient, on2FARequired } = options;
1242
1253
  let session = options.session;
@@ -1255,14 +1266,17 @@ function createKentuckySignerAccount(options) {
1255
1266
  }
1256
1267
  return session.token;
1257
1268
  }
1258
- async function signHash(hash, chainId) {
1269
+ function ensureHexPrefix(value) {
1270
+ return value.startsWith("0x") ? value : `0x${value}`;
1271
+ }
1272
+ async function signHash(hash) {
1259
1273
  const token = await getToken();
1260
1274
  try {
1261
1275
  const response = await client.signEvmTransaction(
1262
- { tx_hash: hash, chain_id: chainId },
1276
+ { tx_hash: hash },
1263
1277
  token
1264
1278
  );
1265
- return response.signature.full;
1279
+ return ensureHexPrefix(response.signature.full);
1266
1280
  } catch (err) {
1267
1281
  if (err instanceof KentuckySignerError && err.code === "2FA_REQUIRED" && on2FARequired) {
1268
1282
  const totpRequired = err.message.includes("TOTP") || (err.details?.includes("totp_code") ?? false);
@@ -1273,24 +1287,24 @@ function createKentuckySignerAccount(options) {
1273
1287
  throw new KentuckySignerError("2FA verification cancelled", "2FA_CANCELLED", "User cancelled 2FA input");
1274
1288
  }
1275
1289
  const response = await client.signEvmTransactionWith2FA(
1276
- { tx_hash: hash, chain_id: chainId, totp_code: codes.totpCode, pin: codes.pin },
1290
+ { tx_hash: hash, totp_code: codes.totpCode, pin: codes.pin },
1277
1291
  token
1278
1292
  );
1279
- return response.signature.full;
1293
+ return ensureHexPrefix(response.signature.full);
1280
1294
  }
1281
1295
  throw err;
1282
1296
  }
1283
1297
  }
1284
- async function signHashWithComponents(hash, chainId) {
1298
+ async function signHashWithComponents(hash) {
1285
1299
  const token = await getToken();
1286
1300
  try {
1287
1301
  const response = await client.signEvmTransaction(
1288
- { tx_hash: hash, chain_id: chainId },
1302
+ { tx_hash: hash },
1289
1303
  token
1290
1304
  );
1291
1305
  return {
1292
- r: response.signature.r,
1293
- s: response.signature.s,
1306
+ r: ensureHexPrefix(response.signature.r),
1307
+ s: ensureHexPrefix(response.signature.s),
1294
1308
  v: response.signature.v
1295
1309
  };
1296
1310
  } catch (err) {
@@ -1303,12 +1317,12 @@ function createKentuckySignerAccount(options) {
1303
1317
  throw new KentuckySignerError("2FA verification cancelled", "2FA_CANCELLED", "User cancelled 2FA input");
1304
1318
  }
1305
1319
  const response = await client.signEvmTransactionWith2FA(
1306
- { tx_hash: hash, chain_id: chainId, totp_code: codes.totpCode, pin: codes.pin },
1320
+ { tx_hash: hash, totp_code: codes.totpCode, pin: codes.pin },
1307
1321
  token
1308
1322
  );
1309
1323
  return {
1310
- r: response.signature.r,
1311
- s: response.signature.s,
1324
+ r: ensureHexPrefix(response.signature.r),
1325
+ s: ensureHexPrefix(response.signature.s),
1312
1326
  v: response.signature.v
1313
1327
  };
1314
1328
  }
@@ -1324,29 +1338,36 @@ function createKentuckySignerAccount(options) {
1324
1338
  */
1325
1339
  async signMessage({ message }) {
1326
1340
  const messageHash = hashMessage(message);
1327
- return signHash(messageHash, defaultChainId);
1341
+ return signHash(messageHash);
1328
1342
  },
1329
1343
  /**
1330
1344
  * Sign a transaction
1331
1345
  *
1332
1346
  * Serializes the transaction, hashes it, signs via Kentucky Signer,
1333
1347
  * and returns the signed serialized transaction.
1348
+ *
1349
+ * For legacy transactions, applies EIP-155 encoding (v = chainId * 2 + 35 + recoveryId)
1350
+ * For modern transactions (EIP-1559, EIP-2930, etc.), uses yParity (0 or 1)
1334
1351
  */
1335
1352
  async signTransaction(transaction) {
1336
1353
  const chainId = transaction.chainId ?? defaultChainId;
1337
1354
  const serializedUnsigned = serializeTransaction(transaction);
1338
1355
  const txHash = keccak256(serializedUnsigned);
1339
- const { r, s, v } = await signHashWithComponents(txHash, chainId);
1356
+ const { r, s, v } = await signHashWithComponents(txHash);
1357
+ const recoveryId = v - 27;
1358
+ let signatureV;
1340
1359
  let yParity;
1341
1360
  if (transaction.type === "eip1559" || transaction.type === "eip2930" || transaction.type === "eip4844" || transaction.type === "eip7702") {
1342
- yParity = v >= 27 ? v - 27 : v;
1361
+ yParity = recoveryId;
1362
+ signatureV = BigInt(yParity);
1343
1363
  } else {
1344
- yParity = v;
1364
+ signatureV = BigInt(chainId * 2 + 35 + recoveryId);
1365
+ yParity = recoveryId;
1345
1366
  }
1346
1367
  const serializedSigned = serializeTransaction(transaction, {
1347
1368
  r,
1348
1369
  s,
1349
- v: BigInt(yParity),
1370
+ v: signatureV,
1350
1371
  yParity
1351
1372
  });
1352
1373
  return serializedSigned;
@@ -1356,7 +1377,7 @@ function createKentuckySignerAccount(options) {
1356
1377
  */
1357
1378
  async signTypedData(typedData) {
1358
1379
  const hash = hashTypedData(typedData);
1359
- return signHash(hash, defaultChainId);
1380
+ return signHash(hash);
1360
1381
  }
1361
1382
  });
1362
1383
  account.source = "kentuckySigner";
@@ -1369,6 +1390,25 @@ function createKentuckySignerAccount(options) {
1369
1390
  account.address = newSession.evmAddress;
1370
1391
  }
1371
1392
  };
1393
+ account.sign7702Authorization = async (params, currentNonce) => {
1394
+ const authNonce = params.executor === "self" ? currentNonce + 1n : params.nonce ?? currentNonce;
1395
+ const chainId = params.chainId ?? defaultChainId;
1396
+ const authHash = hashAuthorization({
1397
+ contractAddress: params.contractAddress,
1398
+ chainId,
1399
+ nonce: authNonce
1400
+ });
1401
+ const { r, s, v } = await signHashWithComponents(authHash);
1402
+ const yParity = v - 27;
1403
+ return {
1404
+ chainId,
1405
+ contractAddress: params.contractAddress,
1406
+ nonce: authNonce,
1407
+ yParity,
1408
+ r,
1409
+ s
1410
+ };
1411
+ };
1372
1412
  return account;
1373
1413
  }
1374
1414
 
@@ -1890,16 +1930,341 @@ function useAddress() {
1890
1930
  const { account } = useKentuckySignerContext();
1891
1931
  return account?.address;
1892
1932
  }
1933
+
1934
+ // src/react/relayer-hooks.ts
1935
+ import { useState as useState3, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2 } from "react";
1936
+ function useRelayIntent(client) {
1937
+ const [isRelaying, setIsRelaying] = useState3(false);
1938
+ const [response, setResponse] = useState3(null);
1939
+ const [error, setError] = useState3(null);
1940
+ const relay = useCallback3(
1941
+ async (chainId, accountAddress, signedIntent, paymentMode, authorization) => {
1942
+ setIsRelaying(true);
1943
+ setError(null);
1944
+ try {
1945
+ const result = await client.relay(chainId, accountAddress, signedIntent, paymentMode, authorization);
1946
+ setResponse(result);
1947
+ return result;
1948
+ } catch (err) {
1949
+ const error2 = err instanceof Error ? err : new Error("Relay failed");
1950
+ setError(error2);
1951
+ return { success: false, error: error2.message };
1952
+ } finally {
1953
+ setIsRelaying(false);
1954
+ }
1955
+ },
1956
+ [client]
1957
+ );
1958
+ const reset = useCallback3(() => {
1959
+ setResponse(null);
1960
+ setError(null);
1961
+ }, []);
1962
+ return { relay, isRelaying, response, error, reset };
1963
+ }
1964
+ function useTransactionStatus(client, chainId, txHash, pollInterval = 3e3) {
1965
+ const [status, setStatus] = useState3(null);
1966
+ const [statusResponse, setStatusResponse] = useState3(null);
1967
+ const [isLoading, setIsLoading] = useState3(false);
1968
+ const [error, setError] = useState3(null);
1969
+ const intervalRef = useRef2(null);
1970
+ const fetchStatus = useCallback3(async () => {
1971
+ if (!txHash) return;
1972
+ setIsLoading(true);
1973
+ try {
1974
+ const response = await client.getStatus(chainId, txHash);
1975
+ setStatusResponse(response);
1976
+ setStatus(response.status);
1977
+ setError(null);
1978
+ if (response.status === "confirmed" || response.status === "failed") {
1979
+ if (intervalRef.current) {
1980
+ clearInterval(intervalRef.current);
1981
+ intervalRef.current = null;
1982
+ }
1983
+ }
1984
+ } catch (err) {
1985
+ setError(err instanceof Error ? err : new Error("Failed to fetch status"));
1986
+ } finally {
1987
+ setIsLoading(false);
1988
+ }
1989
+ }, [client, chainId, txHash]);
1990
+ useEffect2(() => {
1991
+ if (!txHash) {
1992
+ setStatus(null);
1993
+ setStatusResponse(null);
1994
+ return;
1995
+ }
1996
+ fetchStatus();
1997
+ intervalRef.current = setInterval(fetchStatus, pollInterval);
1998
+ return () => {
1999
+ if (intervalRef.current) {
2000
+ clearInterval(intervalRef.current);
2001
+ intervalRef.current = null;
2002
+ }
2003
+ };
2004
+ }, [txHash, pollInterval, fetchStatus]);
2005
+ return { status, statusResponse, isLoading, error, refresh: fetchStatus };
2006
+ }
2007
+ function useEstimate(client) {
2008
+ const [estimateResponse, setEstimateResponse] = useState3(null);
2009
+ const [isEstimating, setIsEstimating] = useState3(false);
2010
+ const [error, setError] = useState3(null);
2011
+ const estimate = useCallback3(
2012
+ async (chainId, accountAddress, intent) => {
2013
+ setIsEstimating(true);
2014
+ setError(null);
2015
+ try {
2016
+ const response = await client.estimate(chainId, accountAddress, intent);
2017
+ setEstimateResponse(response);
2018
+ return response;
2019
+ } catch (err) {
2020
+ const error2 = err instanceof Error ? err : new Error("Estimate failed");
2021
+ setError(error2);
2022
+ return null;
2023
+ } finally {
2024
+ setIsEstimating(false);
2025
+ }
2026
+ },
2027
+ [client]
2028
+ );
2029
+ return { estimate, estimateResponse, isEstimating, error };
2030
+ }
2031
+ function useNonce(client, chainId, address) {
2032
+ const [nonce, setNonce] = useState3(null);
2033
+ const [isLoading, setIsLoading] = useState3(false);
2034
+ const [error, setError] = useState3(null);
2035
+ const fetchNonce = useCallback3(async () => {
2036
+ if (!address) return;
2037
+ setIsLoading(true);
2038
+ try {
2039
+ const result = await client.getNonce(chainId, address);
2040
+ setNonce(result);
2041
+ setError(null);
2042
+ } catch (err) {
2043
+ setError(err instanceof Error ? err : new Error("Failed to fetch nonce"));
2044
+ } finally {
2045
+ setIsLoading(false);
2046
+ }
2047
+ }, [client, chainId, address]);
2048
+ useEffect2(() => {
2049
+ if (address) {
2050
+ fetchNonce();
2051
+ } else {
2052
+ setNonce(null);
2053
+ }
2054
+ }, [address, fetchNonce]);
2055
+ return { nonce, isLoading, error, refresh: fetchNonce };
2056
+ }
2057
+
2058
+ // src/relayer-client.ts
2059
+ var RelayerClient = class {
2060
+ constructor(options) {
2061
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
2062
+ this.timeout = options.timeout ?? 3e4;
2063
+ }
2064
+ /**
2065
+ * Check if the relayer is healthy
2066
+ */
2067
+ async health() {
2068
+ const response = await this.fetch("/health");
2069
+ return response;
2070
+ }
2071
+ /**
2072
+ * Get the current nonce for an account
2073
+ *
2074
+ * @param chainId - Chain ID
2075
+ * @param address - Account address
2076
+ * @returns Current nonce as bigint
2077
+ */
2078
+ async getNonce(chainId, address) {
2079
+ const response = await this.fetch(`/nonce/${chainId}/${address}`);
2080
+ return BigInt(response.nonce);
2081
+ }
2082
+ /**
2083
+ * Estimate gas and fees for an intent
2084
+ *
2085
+ * @param chainId - Chain ID
2086
+ * @param accountAddress - Account address (the delegated EOA)
2087
+ * @param intent - Execution intent
2088
+ * @returns Estimate response
2089
+ */
2090
+ async estimate(chainId, accountAddress, intent) {
2091
+ const response = await this.fetch("/estimate", {
2092
+ method: "POST",
2093
+ body: JSON.stringify({
2094
+ chainId,
2095
+ accountAddress,
2096
+ intent: {
2097
+ nonce: intent.nonce.toString(),
2098
+ deadline: intent.deadline.toString(),
2099
+ target: intent.target,
2100
+ value: intent.value.toString(),
2101
+ data: intent.data
2102
+ }
2103
+ })
2104
+ });
2105
+ return response;
2106
+ }
2107
+ /**
2108
+ * Relay a signed intent
2109
+ *
2110
+ * @param chainId - Chain ID
2111
+ * @param accountAddress - Account address (the delegated EOA)
2112
+ * @param signedIntent - Signed execution intent
2113
+ * @param paymentMode - Payment mode ('sponsored' or { token: Address })
2114
+ * @param authorization - Optional EIP-7702 authorization for gasless onboarding
2115
+ * @returns Relay response with transaction hash
2116
+ *
2117
+ * @example Gasless onboarding (delegate + execute in one tx)
2118
+ * ```typescript
2119
+ * // Get current nonce for authorization
2120
+ * const txNonce = await publicClient.getTransactionCount({ address: accountAddress })
2121
+ *
2122
+ * // Sign EIP-7702 authorization
2123
+ * const authorization = await account.sign7702Authorization({
2124
+ * contractAddress: delegateAddress,
2125
+ * chainId: 42161,
2126
+ * }, txNonce)
2127
+ *
2128
+ * // Relay with authorization
2129
+ * const result = await relayer.relay(
2130
+ * 42161,
2131
+ * accountAddress,
2132
+ * signedIntent,
2133
+ * 'sponsored',
2134
+ * authorization
2135
+ * )
2136
+ * ```
2137
+ */
2138
+ async relay(chainId, accountAddress, signedIntent, paymentMode, authorization) {
2139
+ const body = {
2140
+ chainId,
2141
+ accountAddress,
2142
+ intent: {
2143
+ nonce: signedIntent.intent.nonce.toString(),
2144
+ deadline: signedIntent.intent.deadline.toString(),
2145
+ target: signedIntent.intent.target,
2146
+ value: signedIntent.intent.value.toString(),
2147
+ data: signedIntent.intent.data
2148
+ },
2149
+ ownerSignature: signedIntent.signature,
2150
+ paymentMode
2151
+ };
2152
+ if (authorization) {
2153
+ body.authorization = {
2154
+ chainId: authorization.chainId,
2155
+ contractAddress: authorization.contractAddress,
2156
+ nonce: authorization.nonce.toString(),
2157
+ yParity: authorization.yParity,
2158
+ r: authorization.r,
2159
+ s: authorization.s
2160
+ };
2161
+ }
2162
+ const response = await this.fetch("/relay", {
2163
+ method: "POST",
2164
+ body: JSON.stringify(body)
2165
+ });
2166
+ return response;
2167
+ }
2168
+ /**
2169
+ * Get transaction status
2170
+ *
2171
+ * @param chainId - Chain ID
2172
+ * @param txHash - Transaction hash
2173
+ * @returns Status response
2174
+ */
2175
+ async getStatus(chainId, txHash) {
2176
+ const response = await this.fetch(`/status/${chainId}/${txHash}`);
2177
+ return response;
2178
+ }
2179
+ /**
2180
+ * Make a fetch request to the relayer API
2181
+ */
2182
+ async fetch(path, options) {
2183
+ const controller = new AbortController();
2184
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2185
+ try {
2186
+ const response = await fetch(`${this.baseUrl}${path}`, {
2187
+ ...options,
2188
+ headers: {
2189
+ "Content-Type": "application/json",
2190
+ ...options?.headers
2191
+ },
2192
+ signal: controller.signal
2193
+ });
2194
+ const data = await response.json();
2195
+ if (!response.ok) {
2196
+ throw new Error(data.error || `Request failed: ${response.status}`);
2197
+ }
2198
+ return data;
2199
+ } finally {
2200
+ clearTimeout(timeoutId);
2201
+ }
2202
+ }
2203
+ };
2204
+ function createRelayerClient(baseUrl) {
2205
+ return new RelayerClient({ baseUrl });
2206
+ }
2207
+
2208
+ // src/intent.ts
2209
+ import {
2210
+ keccak256 as keccak2562,
2211
+ encodeAbiParameters,
2212
+ parseAbiParameters,
2213
+ encodePacked
2214
+ } from "viem";
2215
+ var INTENT_TYPEHASH = keccak2562(
2216
+ encodePacked(
2217
+ ["string"],
2218
+ ["ExecutionIntent(uint256 nonce,uint256 deadline,address target,uint256 value,bytes data)"]
2219
+ )
2220
+ );
2221
+ function createExecutionIntent(params) {
2222
+ return {
2223
+ nonce: params.nonce,
2224
+ deadline: params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 3600),
2225
+ // 1 hour default
2226
+ target: params.target,
2227
+ value: params.value ?? 0n,
2228
+ data: params.data ?? "0x"
2229
+ };
2230
+ }
2231
+ function hashIntent(intent) {
2232
+ const dataHash = keccak2562(intent.data);
2233
+ return keccak2562(
2234
+ encodeAbiParameters(
2235
+ parseAbiParameters("bytes32, uint256, uint256, address, uint256, bytes32"),
2236
+ [INTENT_TYPEHASH, intent.nonce, intent.deadline, intent.target, intent.value, dataHash]
2237
+ )
2238
+ );
2239
+ }
2240
+ async function signIntent(account, intent) {
2241
+ const intentHash = hashIntent(intent);
2242
+ const signature = await account.signMessage({
2243
+ message: { raw: intentHash }
2244
+ });
2245
+ return {
2246
+ intent,
2247
+ signature
2248
+ };
2249
+ }
1893
2250
  export {
1894
2251
  KentuckySignerProvider,
2252
+ RelayerClient,
2253
+ createExecutionIntent,
2254
+ createRelayerClient,
2255
+ signIntent,
1895
2256
  useAddress,
2257
+ useEstimate,
1896
2258
  useIsReady,
1897
2259
  useKentuckySigner,
1898
2260
  useKentuckySignerAccount,
1899
2261
  useKentuckySignerContext,
2262
+ useNonce,
1900
2263
  usePasskeyAuth,
2264
+ useRelayIntent,
1901
2265
  useSignMessage,
1902
2266
  useSignTypedData,
2267
+ useTransactionStatus,
1903
2268
  useWalletClient
1904
2269
  };
1905
2270
  //# sourceMappingURL=index.mjs.map