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/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ALCHEMY_SEMI_MODULAR_ACCOUNT_7702: () => ALCHEMY_SEMI_MODULAR_ACCOUNT_7702,
23
24
  EphemeralKeyManager: () => EphemeralKeyManager,
24
25
  IndexedDBEphemeralKeyStorage: () => IndexedDBEphemeralKeyStorage,
25
26
  KentuckySignerClient: () => KentuckySignerClient,
@@ -27,6 +28,7 @@ __export(index_exports, {
27
28
  LocalStorageTokenStorage: () => LocalStorageTokenStorage,
28
29
  MemoryEphemeralKeyStorage: () => MemoryEphemeralKeyStorage,
29
30
  MemoryTokenStorage: () => MemoryTokenStorage,
31
+ RelayerClient: () => RelayerClient,
30
32
  SecureKentuckySignerClient: () => SecureKentuckySignerClient,
31
33
  authenticateWithPasskey: () => authenticateWithPasskey,
32
34
  authenticateWithPassword: () => authenticateWithPassword,
@@ -36,12 +38,16 @@ __export(index_exports, {
36
38
  bytesToHex: () => bytesToHex,
37
39
  createAccountWithPassword: () => createAccountWithPassword,
38
40
  createClient: () => createClient,
41
+ createExecutionIntent: () => createExecutionIntent,
39
42
  createKentuckySignerAccount: () => createKentuckySignerAccount,
43
+ createRelayerClient: () => createRelayerClient,
40
44
  createSecureClient: () => createSecureClient,
41
45
  createServerAccount: () => createServerAccount,
42
46
  formatError: () => formatError,
43
47
  generateEphemeralKeyPair: () => generateEphemeralKeyPair,
44
48
  getJwtExpiration: () => getJwtExpiration,
49
+ hashBatchIntents: () => hashBatchIntents,
50
+ hashIntent: () => hashIntent,
45
51
  hexToBytes: () => hexToBytes,
46
52
  isSessionValid: () => isSessionValid,
47
53
  isValidAccountId: () => isValidAccountId,
@@ -51,6 +57,8 @@ __export(index_exports, {
51
57
  parseJwt: () => parseJwt,
52
58
  refreshSessionIfNeeded: () => refreshSessionIfNeeded,
53
59
  registerPasskey: () => registerPasskey,
60
+ signBatchIntents: () => signBatchIntents,
61
+ signIntent: () => signIntent,
54
62
  signPayload: () => signPayload,
55
63
  verifyPayload: () => verifyPayload,
56
64
  withRetry: () => withRetry
@@ -205,9 +213,9 @@ var KentuckySignerClient = class {
205
213
  /**
206
214
  * Sign an EVM transaction hash
207
215
  *
208
- * @param request - Sign request with tx_hash and chain_id
216
+ * @param request - Sign request with tx_hash
209
217
  * @param token - JWT token
210
- * @returns Signature response with r, s, v components
218
+ * @returns Signature response with r, s, v components (v is always 27 or 28)
211
219
  */
212
220
  async signEvmTransaction(request, token) {
213
221
  return this.request("/api/sign/evm", {
@@ -222,13 +230,12 @@ var KentuckySignerClient = class {
222
230
  * Convenience method that wraps signEvmTransaction.
223
231
  *
224
232
  * @param hash - 32-byte hash to sign (hex encoded with 0x prefix)
225
- * @param chainId - Chain ID
226
233
  * @param token - JWT token
227
234
  * @returns Full signature (hex encoded with 0x prefix)
228
235
  */
229
- async signHash(hash, chainId, token) {
236
+ async signHash(hash, token) {
230
237
  const response = await this.signEvmTransaction(
231
- { tx_hash: hash, chain_id: chainId },
238
+ { tx_hash: hash },
232
239
  token
233
240
  );
234
241
  return response.signature.full;
@@ -645,6 +652,15 @@ function createClient(options) {
645
652
  }
646
653
 
647
654
  // src/account.ts
655
+ var EIP7702_MAGIC = "0x05";
656
+ function hashAuthorization(params) {
657
+ const rlpEncoded = (0, import_viem.toRlp)([
658
+ params.chainId === 0 ? "0x" : (0, import_viem.numberToHex)(params.chainId),
659
+ params.contractAddress,
660
+ params.nonce === 0n ? "0x" : (0, import_viem.numberToHex)(params.nonce)
661
+ ]);
662
+ return (0, import_viem.keccak256)((0, import_viem.concat)([EIP7702_MAGIC, rlpEncoded]));
663
+ }
648
664
  function createKentuckySignerAccount(options) {
649
665
  const { config, defaultChainId = 1, onSessionExpired, secureClient, on2FARequired } = options;
650
666
  let session = options.session;
@@ -663,14 +679,17 @@ function createKentuckySignerAccount(options) {
663
679
  }
664
680
  return session.token;
665
681
  }
666
- async function signHash(hash, chainId) {
682
+ function ensureHexPrefix(value) {
683
+ return value.startsWith("0x") ? value : `0x${value}`;
684
+ }
685
+ async function signHash(hash) {
667
686
  const token = await getToken();
668
687
  try {
669
688
  const response = await client.signEvmTransaction(
670
- { tx_hash: hash, chain_id: chainId },
689
+ { tx_hash: hash },
671
690
  token
672
691
  );
673
- return response.signature.full;
692
+ return ensureHexPrefix(response.signature.full);
674
693
  } catch (err) {
675
694
  if (err instanceof KentuckySignerError && err.code === "2FA_REQUIRED" && on2FARequired) {
676
695
  const totpRequired = err.message.includes("TOTP") || (err.details?.includes("totp_code") ?? false);
@@ -681,24 +700,24 @@ function createKentuckySignerAccount(options) {
681
700
  throw new KentuckySignerError("2FA verification cancelled", "2FA_CANCELLED", "User cancelled 2FA input");
682
701
  }
683
702
  const response = await client.signEvmTransactionWith2FA(
684
- { tx_hash: hash, chain_id: chainId, totp_code: codes.totpCode, pin: codes.pin },
703
+ { tx_hash: hash, totp_code: codes.totpCode, pin: codes.pin },
685
704
  token
686
705
  );
687
- return response.signature.full;
706
+ return ensureHexPrefix(response.signature.full);
688
707
  }
689
708
  throw err;
690
709
  }
691
710
  }
692
- async function signHashWithComponents(hash, chainId) {
711
+ async function signHashWithComponents(hash) {
693
712
  const token = await getToken();
694
713
  try {
695
714
  const response = await client.signEvmTransaction(
696
- { tx_hash: hash, chain_id: chainId },
715
+ { tx_hash: hash },
697
716
  token
698
717
  );
699
718
  return {
700
- r: response.signature.r,
701
- s: response.signature.s,
719
+ r: ensureHexPrefix(response.signature.r),
720
+ s: ensureHexPrefix(response.signature.s),
702
721
  v: response.signature.v
703
722
  };
704
723
  } catch (err) {
@@ -711,12 +730,12 @@ function createKentuckySignerAccount(options) {
711
730
  throw new KentuckySignerError("2FA verification cancelled", "2FA_CANCELLED", "User cancelled 2FA input");
712
731
  }
713
732
  const response = await client.signEvmTransactionWith2FA(
714
- { tx_hash: hash, chain_id: chainId, totp_code: codes.totpCode, pin: codes.pin },
733
+ { tx_hash: hash, totp_code: codes.totpCode, pin: codes.pin },
715
734
  token
716
735
  );
717
736
  return {
718
- r: response.signature.r,
719
- s: response.signature.s,
737
+ r: ensureHexPrefix(response.signature.r),
738
+ s: ensureHexPrefix(response.signature.s),
720
739
  v: response.signature.v
721
740
  };
722
741
  }
@@ -732,29 +751,36 @@ function createKentuckySignerAccount(options) {
732
751
  */
733
752
  async signMessage({ message }) {
734
753
  const messageHash = (0, import_viem.hashMessage)(message);
735
- return signHash(messageHash, defaultChainId);
754
+ return signHash(messageHash);
736
755
  },
737
756
  /**
738
757
  * Sign a transaction
739
758
  *
740
759
  * Serializes the transaction, hashes it, signs via Kentucky Signer,
741
760
  * and returns the signed serialized transaction.
761
+ *
762
+ * For legacy transactions, applies EIP-155 encoding (v = chainId * 2 + 35 + recoveryId)
763
+ * For modern transactions (EIP-1559, EIP-2930, etc.), uses yParity (0 or 1)
742
764
  */
743
765
  async signTransaction(transaction) {
744
766
  const chainId = transaction.chainId ?? defaultChainId;
745
767
  const serializedUnsigned = (0, import_viem.serializeTransaction)(transaction);
746
768
  const txHash = (0, import_viem.keccak256)(serializedUnsigned);
747
- const { r, s, v } = await signHashWithComponents(txHash, chainId);
769
+ const { r, s, v } = await signHashWithComponents(txHash);
770
+ const recoveryId = v - 27;
771
+ let signatureV;
748
772
  let yParity;
749
773
  if (transaction.type === "eip1559" || transaction.type === "eip2930" || transaction.type === "eip4844" || transaction.type === "eip7702") {
750
- yParity = v >= 27 ? v - 27 : v;
774
+ yParity = recoveryId;
775
+ signatureV = BigInt(yParity);
751
776
  } else {
752
- yParity = v;
777
+ signatureV = BigInt(chainId * 2 + 35 + recoveryId);
778
+ yParity = recoveryId;
753
779
  }
754
780
  const serializedSigned = (0, import_viem.serializeTransaction)(transaction, {
755
781
  r,
756
782
  s,
757
- v: BigInt(yParity),
783
+ v: signatureV,
758
784
  yParity
759
785
  });
760
786
  return serializedSigned;
@@ -764,7 +790,7 @@ function createKentuckySignerAccount(options) {
764
790
  */
765
791
  async signTypedData(typedData) {
766
792
  const hash = (0, import_viem.hashTypedData)(typedData);
767
- return signHash(hash, defaultChainId);
793
+ return signHash(hash);
768
794
  }
769
795
  });
770
796
  account.source = "kentuckySigner";
@@ -777,6 +803,25 @@ function createKentuckySignerAccount(options) {
777
803
  account.address = newSession.evmAddress;
778
804
  }
779
805
  };
806
+ account.sign7702Authorization = async (params, currentNonce) => {
807
+ const authNonce = params.executor === "self" ? currentNonce + 1n : params.nonce ?? currentNonce;
808
+ const chainId = params.chainId ?? defaultChainId;
809
+ const authHash = hashAuthorization({
810
+ contractAddress: params.contractAddress,
811
+ chainId,
812
+ nonce: authNonce
813
+ });
814
+ const { r, s, v } = await signHashWithComponents(authHash);
815
+ const yParity = v - 27;
816
+ return {
817
+ chainId,
818
+ contractAddress: params.contractAddress,
819
+ nonce: authNonce,
820
+ yParity,
821
+ r,
822
+ s
823
+ };
824
+ };
780
825
  return account;
781
826
  }
782
827
  function createServerAccount(baseUrl, accountId, token, evmAddress, chainId = 1) {
@@ -1309,9 +1354,9 @@ var SecureKentuckySignerClient = class {
1309
1354
  /**
1310
1355
  * Sign a raw hash for EVM (signed request)
1311
1356
  */
1312
- async signHash(hash, chainId, token) {
1357
+ async signHash(hash, token) {
1313
1358
  const response = await this.signEvmTransaction(
1314
- { tx_hash: hash, chain_id: chainId },
1359
+ { tx_hash: hash },
1315
1360
  token
1316
1361
  );
1317
1362
  return response.signature.full;
@@ -1647,8 +1692,212 @@ async function createAccountWithPassword(options) {
1647
1692
  confirmation: options.confirmation
1648
1693
  });
1649
1694
  }
1695
+
1696
+ // src/intent.ts
1697
+ var import_viem2 = require("viem");
1698
+ var INTENT_TYPEHASH = (0, import_viem2.keccak256)(
1699
+ (0, import_viem2.encodePacked)(
1700
+ ["string"],
1701
+ ["ExecutionIntent(uint256 nonce,uint256 deadline,address target,uint256 value,bytes data)"]
1702
+ )
1703
+ );
1704
+ function createExecutionIntent(params) {
1705
+ return {
1706
+ nonce: params.nonce,
1707
+ deadline: params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 3600),
1708
+ // 1 hour default
1709
+ target: params.target,
1710
+ value: params.value ?? 0n,
1711
+ data: params.data ?? "0x"
1712
+ };
1713
+ }
1714
+ function hashIntent(intent) {
1715
+ const dataHash = (0, import_viem2.keccak256)(intent.data);
1716
+ return (0, import_viem2.keccak256)(
1717
+ (0, import_viem2.encodeAbiParameters)(
1718
+ (0, import_viem2.parseAbiParameters)("bytes32, uint256, uint256, address, uint256, bytes32"),
1719
+ [INTENT_TYPEHASH, intent.nonce, intent.deadline, intent.target, intent.value, dataHash]
1720
+ )
1721
+ );
1722
+ }
1723
+ async function signIntent(account, intent) {
1724
+ const intentHash = hashIntent(intent);
1725
+ const signature = await account.signMessage({
1726
+ message: { raw: intentHash }
1727
+ });
1728
+ return {
1729
+ intent,
1730
+ signature
1731
+ };
1732
+ }
1733
+ function hashBatchIntents(intents) {
1734
+ const intentHashes = intents.map(hashIntent);
1735
+ return (0, import_viem2.keccak256)((0, import_viem2.encodePacked)(["bytes32[]"], [intentHashes]));
1736
+ }
1737
+ async function signBatchIntents(account, intents) {
1738
+ const signedIntents = [];
1739
+ for (const intent of intents) {
1740
+ const signed = await signIntent(account, intent);
1741
+ signedIntents.push(signed);
1742
+ }
1743
+ return signedIntents;
1744
+ }
1745
+
1746
+ // src/relayer-client.ts
1747
+ var RelayerClient = class {
1748
+ constructor(options) {
1749
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
1750
+ this.timeout = options.timeout ?? 3e4;
1751
+ }
1752
+ /**
1753
+ * Check if the relayer is healthy
1754
+ */
1755
+ async health() {
1756
+ const response = await this.fetch("/health");
1757
+ return response;
1758
+ }
1759
+ /**
1760
+ * Get the current nonce for an account
1761
+ *
1762
+ * @param chainId - Chain ID
1763
+ * @param address - Account address
1764
+ * @returns Current nonce as bigint
1765
+ */
1766
+ async getNonce(chainId, address) {
1767
+ const response = await this.fetch(`/nonce/${chainId}/${address}`);
1768
+ return BigInt(response.nonce);
1769
+ }
1770
+ /**
1771
+ * Estimate gas and fees for an intent
1772
+ *
1773
+ * @param chainId - Chain ID
1774
+ * @param accountAddress - Account address (the delegated EOA)
1775
+ * @param intent - Execution intent
1776
+ * @returns Estimate response
1777
+ */
1778
+ async estimate(chainId, accountAddress, intent) {
1779
+ const response = await this.fetch("/estimate", {
1780
+ method: "POST",
1781
+ body: JSON.stringify({
1782
+ chainId,
1783
+ accountAddress,
1784
+ intent: {
1785
+ nonce: intent.nonce.toString(),
1786
+ deadline: intent.deadline.toString(),
1787
+ target: intent.target,
1788
+ value: intent.value.toString(),
1789
+ data: intent.data
1790
+ }
1791
+ })
1792
+ });
1793
+ return response;
1794
+ }
1795
+ /**
1796
+ * Relay a signed intent
1797
+ *
1798
+ * @param chainId - Chain ID
1799
+ * @param accountAddress - Account address (the delegated EOA)
1800
+ * @param signedIntent - Signed execution intent
1801
+ * @param paymentMode - Payment mode ('sponsored' or { token: Address })
1802
+ * @param authorization - Optional EIP-7702 authorization for gasless onboarding
1803
+ * @returns Relay response with transaction hash
1804
+ *
1805
+ * @example Gasless onboarding (delegate + execute in one tx)
1806
+ * ```typescript
1807
+ * // Get current nonce for authorization
1808
+ * const txNonce = await publicClient.getTransactionCount({ address: accountAddress })
1809
+ *
1810
+ * // Sign EIP-7702 authorization
1811
+ * const authorization = await account.sign7702Authorization({
1812
+ * contractAddress: delegateAddress,
1813
+ * chainId: 42161,
1814
+ * }, txNonce)
1815
+ *
1816
+ * // Relay with authorization
1817
+ * const result = await relayer.relay(
1818
+ * 42161,
1819
+ * accountAddress,
1820
+ * signedIntent,
1821
+ * 'sponsored',
1822
+ * authorization
1823
+ * )
1824
+ * ```
1825
+ */
1826
+ async relay(chainId, accountAddress, signedIntent, paymentMode, authorization) {
1827
+ const body = {
1828
+ chainId,
1829
+ accountAddress,
1830
+ intent: {
1831
+ nonce: signedIntent.intent.nonce.toString(),
1832
+ deadline: signedIntent.intent.deadline.toString(),
1833
+ target: signedIntent.intent.target,
1834
+ value: signedIntent.intent.value.toString(),
1835
+ data: signedIntent.intent.data
1836
+ },
1837
+ ownerSignature: signedIntent.signature,
1838
+ paymentMode
1839
+ };
1840
+ if (authorization) {
1841
+ body.authorization = {
1842
+ chainId: authorization.chainId,
1843
+ contractAddress: authorization.contractAddress,
1844
+ nonce: authorization.nonce.toString(),
1845
+ yParity: authorization.yParity,
1846
+ r: authorization.r,
1847
+ s: authorization.s
1848
+ };
1849
+ }
1850
+ const response = await this.fetch("/relay", {
1851
+ method: "POST",
1852
+ body: JSON.stringify(body)
1853
+ });
1854
+ return response;
1855
+ }
1856
+ /**
1857
+ * Get transaction status
1858
+ *
1859
+ * @param chainId - Chain ID
1860
+ * @param txHash - Transaction hash
1861
+ * @returns Status response
1862
+ */
1863
+ async getStatus(chainId, txHash) {
1864
+ const response = await this.fetch(`/status/${chainId}/${txHash}`);
1865
+ return response;
1866
+ }
1867
+ /**
1868
+ * Make a fetch request to the relayer API
1869
+ */
1870
+ async fetch(path, options) {
1871
+ const controller = new AbortController();
1872
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1873
+ try {
1874
+ const response = await fetch(`${this.baseUrl}${path}`, {
1875
+ ...options,
1876
+ headers: {
1877
+ "Content-Type": "application/json",
1878
+ ...options?.headers
1879
+ },
1880
+ signal: controller.signal
1881
+ });
1882
+ const data = await response.json();
1883
+ if (!response.ok) {
1884
+ throw new Error(data.error || `Request failed: ${response.status}`);
1885
+ }
1886
+ return data;
1887
+ } finally {
1888
+ clearTimeout(timeoutId);
1889
+ }
1890
+ }
1891
+ };
1892
+ function createRelayerClient(baseUrl) {
1893
+ return new RelayerClient({ baseUrl });
1894
+ }
1895
+
1896
+ // src/index.ts
1897
+ var ALCHEMY_SEMI_MODULAR_ACCOUNT_7702 = "0x69007702764179f14F51cdce752f4f775d74E139";
1650
1898
  // Annotate the CommonJS export names for ESM import in node:
1651
1899
  0 && (module.exports = {
1900
+ ALCHEMY_SEMI_MODULAR_ACCOUNT_7702,
1652
1901
  EphemeralKeyManager,
1653
1902
  IndexedDBEphemeralKeyStorage,
1654
1903
  KentuckySignerClient,
@@ -1656,6 +1905,7 @@ async function createAccountWithPassword(options) {
1656
1905
  LocalStorageTokenStorage,
1657
1906
  MemoryEphemeralKeyStorage,
1658
1907
  MemoryTokenStorage,
1908
+ RelayerClient,
1659
1909
  SecureKentuckySignerClient,
1660
1910
  authenticateWithPasskey,
1661
1911
  authenticateWithPassword,
@@ -1665,12 +1915,16 @@ async function createAccountWithPassword(options) {
1665
1915
  bytesToHex,
1666
1916
  createAccountWithPassword,
1667
1917
  createClient,
1918
+ createExecutionIntent,
1668
1919
  createKentuckySignerAccount,
1920
+ createRelayerClient,
1669
1921
  createSecureClient,
1670
1922
  createServerAccount,
1671
1923
  formatError,
1672
1924
  generateEphemeralKeyPair,
1673
1925
  getJwtExpiration,
1926
+ hashBatchIntents,
1927
+ hashIntent,
1674
1928
  hexToBytes,
1675
1929
  isSessionValid,
1676
1930
  isValidAccountId,
@@ -1680,6 +1934,8 @@ async function createAccountWithPassword(options) {
1680
1934
  parseJwt,
1681
1935
  refreshSessionIfNeeded,
1682
1936
  registerPasskey,
1937
+ signBatchIntents,
1938
+ signIntent,
1683
1939
  signPayload,
1684
1940
  verifyPayload,
1685
1941
  withRetry