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.mjs CHANGED
@@ -3,7 +3,10 @@ import {
3
3
  hashMessage,
4
4
  hashTypedData,
5
5
  keccak256,
6
- serializeTransaction
6
+ serializeTransaction,
7
+ toRlp,
8
+ concat,
9
+ numberToHex
7
10
  } from "viem";
8
11
  import { toAccount } from "viem/accounts";
9
12
 
@@ -151,9 +154,9 @@ var KentuckySignerClient = class {
151
154
  /**
152
155
  * Sign an EVM transaction hash
153
156
  *
154
- * @param request - Sign request with tx_hash and chain_id
157
+ * @param request - Sign request with tx_hash
155
158
  * @param token - JWT token
156
- * @returns Signature response with r, s, v components
159
+ * @returns Signature response with r, s, v components (v is always 27 or 28)
157
160
  */
158
161
  async signEvmTransaction(request, token) {
159
162
  return this.request("/api/sign/evm", {
@@ -168,13 +171,12 @@ var KentuckySignerClient = class {
168
171
  * Convenience method that wraps signEvmTransaction.
169
172
  *
170
173
  * @param hash - 32-byte hash to sign (hex encoded with 0x prefix)
171
- * @param chainId - Chain ID
172
174
  * @param token - JWT token
173
175
  * @returns Full signature (hex encoded with 0x prefix)
174
176
  */
175
- async signHash(hash, chainId, token) {
177
+ async signHash(hash, token) {
176
178
  const response = await this.signEvmTransaction(
177
- { tx_hash: hash, chain_id: chainId },
179
+ { tx_hash: hash },
178
180
  token
179
181
  );
180
182
  return response.signature.full;
@@ -591,6 +593,15 @@ function createClient(options) {
591
593
  }
592
594
 
593
595
  // src/account.ts
596
+ var EIP7702_MAGIC = "0x05";
597
+ function hashAuthorization(params) {
598
+ const rlpEncoded = toRlp([
599
+ params.chainId === 0 ? "0x" : numberToHex(params.chainId),
600
+ params.contractAddress,
601
+ params.nonce === 0n ? "0x" : numberToHex(params.nonce)
602
+ ]);
603
+ return keccak256(concat([EIP7702_MAGIC, rlpEncoded]));
604
+ }
594
605
  function createKentuckySignerAccount(options) {
595
606
  const { config, defaultChainId = 1, onSessionExpired, secureClient, on2FARequired } = options;
596
607
  let session = options.session;
@@ -609,14 +620,17 @@ function createKentuckySignerAccount(options) {
609
620
  }
610
621
  return session.token;
611
622
  }
612
- async function signHash(hash, chainId) {
623
+ function ensureHexPrefix(value) {
624
+ return value.startsWith("0x") ? value : `0x${value}`;
625
+ }
626
+ async function signHash(hash) {
613
627
  const token = await getToken();
614
628
  try {
615
629
  const response = await client.signEvmTransaction(
616
- { tx_hash: hash, chain_id: chainId },
630
+ { tx_hash: hash },
617
631
  token
618
632
  );
619
- return response.signature.full;
633
+ return ensureHexPrefix(response.signature.full);
620
634
  } catch (err) {
621
635
  if (err instanceof KentuckySignerError && err.code === "2FA_REQUIRED" && on2FARequired) {
622
636
  const totpRequired = err.message.includes("TOTP") || (err.details?.includes("totp_code") ?? false);
@@ -627,24 +641,24 @@ function createKentuckySignerAccount(options) {
627
641
  throw new KentuckySignerError("2FA verification cancelled", "2FA_CANCELLED", "User cancelled 2FA input");
628
642
  }
629
643
  const response = await client.signEvmTransactionWith2FA(
630
- { tx_hash: hash, chain_id: chainId, totp_code: codes.totpCode, pin: codes.pin },
644
+ { tx_hash: hash, totp_code: codes.totpCode, pin: codes.pin },
631
645
  token
632
646
  );
633
- return response.signature.full;
647
+ return ensureHexPrefix(response.signature.full);
634
648
  }
635
649
  throw err;
636
650
  }
637
651
  }
638
- async function signHashWithComponents(hash, chainId) {
652
+ async function signHashWithComponents(hash) {
639
653
  const token = await getToken();
640
654
  try {
641
655
  const response = await client.signEvmTransaction(
642
- { tx_hash: hash, chain_id: chainId },
656
+ { tx_hash: hash },
643
657
  token
644
658
  );
645
659
  return {
646
- r: response.signature.r,
647
- s: response.signature.s,
660
+ r: ensureHexPrefix(response.signature.r),
661
+ s: ensureHexPrefix(response.signature.s),
648
662
  v: response.signature.v
649
663
  };
650
664
  } catch (err) {
@@ -657,12 +671,12 @@ function createKentuckySignerAccount(options) {
657
671
  throw new KentuckySignerError("2FA verification cancelled", "2FA_CANCELLED", "User cancelled 2FA input");
658
672
  }
659
673
  const response = await client.signEvmTransactionWith2FA(
660
- { tx_hash: hash, chain_id: chainId, totp_code: codes.totpCode, pin: codes.pin },
674
+ { tx_hash: hash, totp_code: codes.totpCode, pin: codes.pin },
661
675
  token
662
676
  );
663
677
  return {
664
- r: response.signature.r,
665
- s: response.signature.s,
678
+ r: ensureHexPrefix(response.signature.r),
679
+ s: ensureHexPrefix(response.signature.s),
666
680
  v: response.signature.v
667
681
  };
668
682
  }
@@ -678,29 +692,36 @@ function createKentuckySignerAccount(options) {
678
692
  */
679
693
  async signMessage({ message }) {
680
694
  const messageHash = hashMessage(message);
681
- return signHash(messageHash, defaultChainId);
695
+ return signHash(messageHash);
682
696
  },
683
697
  /**
684
698
  * Sign a transaction
685
699
  *
686
700
  * Serializes the transaction, hashes it, signs via Kentucky Signer,
687
701
  * and returns the signed serialized transaction.
702
+ *
703
+ * For legacy transactions, applies EIP-155 encoding (v = chainId * 2 + 35 + recoveryId)
704
+ * For modern transactions (EIP-1559, EIP-2930, etc.), uses yParity (0 or 1)
688
705
  */
689
706
  async signTransaction(transaction) {
690
707
  const chainId = transaction.chainId ?? defaultChainId;
691
708
  const serializedUnsigned = serializeTransaction(transaction);
692
709
  const txHash = keccak256(serializedUnsigned);
693
- const { r, s, v } = await signHashWithComponents(txHash, chainId);
710
+ const { r, s, v } = await signHashWithComponents(txHash);
711
+ const recoveryId = v - 27;
712
+ let signatureV;
694
713
  let yParity;
695
714
  if (transaction.type === "eip1559" || transaction.type === "eip2930" || transaction.type === "eip4844" || transaction.type === "eip7702") {
696
- yParity = v >= 27 ? v - 27 : v;
715
+ yParity = recoveryId;
716
+ signatureV = BigInt(yParity);
697
717
  } else {
698
- yParity = v;
718
+ signatureV = BigInt(chainId * 2 + 35 + recoveryId);
719
+ yParity = recoveryId;
699
720
  }
700
721
  const serializedSigned = serializeTransaction(transaction, {
701
722
  r,
702
723
  s,
703
- v: BigInt(yParity),
724
+ v: signatureV,
704
725
  yParity
705
726
  });
706
727
  return serializedSigned;
@@ -710,7 +731,7 @@ function createKentuckySignerAccount(options) {
710
731
  */
711
732
  async signTypedData(typedData) {
712
733
  const hash = hashTypedData(typedData);
713
- return signHash(hash, defaultChainId);
734
+ return signHash(hash);
714
735
  }
715
736
  });
716
737
  account.source = "kentuckySigner";
@@ -723,6 +744,25 @@ function createKentuckySignerAccount(options) {
723
744
  account.address = newSession.evmAddress;
724
745
  }
725
746
  };
747
+ account.sign7702Authorization = async (params, currentNonce) => {
748
+ const authNonce = params.executor === "self" ? currentNonce + 1n : params.nonce ?? currentNonce;
749
+ const chainId = params.chainId ?? defaultChainId;
750
+ const authHash = hashAuthorization({
751
+ contractAddress: params.contractAddress,
752
+ chainId,
753
+ nonce: authNonce
754
+ });
755
+ const { r, s, v } = await signHashWithComponents(authHash);
756
+ const yParity = v - 27;
757
+ return {
758
+ chainId,
759
+ contractAddress: params.contractAddress,
760
+ nonce: authNonce,
761
+ yParity,
762
+ r,
763
+ s
764
+ };
765
+ };
726
766
  return account;
727
767
  }
728
768
  function createServerAccount(baseUrl, accountId, token, evmAddress, chainId = 1) {
@@ -1255,9 +1295,9 @@ var SecureKentuckySignerClient = class {
1255
1295
  /**
1256
1296
  * Sign a raw hash for EVM (signed request)
1257
1297
  */
1258
- async signHash(hash, chainId, token) {
1298
+ async signHash(hash, token) {
1259
1299
  const response = await this.signEvmTransaction(
1260
- { tx_hash: hash, chain_id: chainId },
1300
+ { tx_hash: hash },
1261
1301
  token
1262
1302
  );
1263
1303
  return response.signature.full;
@@ -1593,7 +1633,216 @@ async function createAccountWithPassword(options) {
1593
1633
  confirmation: options.confirmation
1594
1634
  });
1595
1635
  }
1636
+
1637
+ // src/intent.ts
1638
+ import {
1639
+ keccak256 as keccak2562,
1640
+ encodeAbiParameters,
1641
+ parseAbiParameters,
1642
+ encodePacked
1643
+ } from "viem";
1644
+ var INTENT_TYPEHASH = keccak2562(
1645
+ encodePacked(
1646
+ ["string"],
1647
+ ["ExecutionIntent(uint256 nonce,uint256 deadline,address target,uint256 value,bytes data)"]
1648
+ )
1649
+ );
1650
+ function createExecutionIntent(params) {
1651
+ return {
1652
+ nonce: params.nonce,
1653
+ deadline: params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 3600),
1654
+ // 1 hour default
1655
+ target: params.target,
1656
+ value: params.value ?? 0n,
1657
+ data: params.data ?? "0x"
1658
+ };
1659
+ }
1660
+ function hashIntent(intent) {
1661
+ const dataHash = keccak2562(intent.data);
1662
+ return keccak2562(
1663
+ encodeAbiParameters(
1664
+ parseAbiParameters("bytes32, uint256, uint256, address, uint256, bytes32"),
1665
+ [INTENT_TYPEHASH, intent.nonce, intent.deadline, intent.target, intent.value, dataHash]
1666
+ )
1667
+ );
1668
+ }
1669
+ async function signIntent(account, intent) {
1670
+ const intentHash = hashIntent(intent);
1671
+ const signature = await account.signMessage({
1672
+ message: { raw: intentHash }
1673
+ });
1674
+ return {
1675
+ intent,
1676
+ signature
1677
+ };
1678
+ }
1679
+ function hashBatchIntents(intents) {
1680
+ const intentHashes = intents.map(hashIntent);
1681
+ return keccak2562(encodePacked(["bytes32[]"], [intentHashes]));
1682
+ }
1683
+ async function signBatchIntents(account, intents) {
1684
+ const signedIntents = [];
1685
+ for (const intent of intents) {
1686
+ const signed = await signIntent(account, intent);
1687
+ signedIntents.push(signed);
1688
+ }
1689
+ return signedIntents;
1690
+ }
1691
+
1692
+ // src/relayer-client.ts
1693
+ var RelayerClient = class {
1694
+ constructor(options) {
1695
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
1696
+ this.timeout = options.timeout ?? 3e4;
1697
+ }
1698
+ /**
1699
+ * Check if the relayer is healthy
1700
+ */
1701
+ async health() {
1702
+ const response = await this.fetch("/health");
1703
+ return response;
1704
+ }
1705
+ /**
1706
+ * Get the current nonce for an account
1707
+ *
1708
+ * @param chainId - Chain ID
1709
+ * @param address - Account address
1710
+ * @returns Current nonce as bigint
1711
+ */
1712
+ async getNonce(chainId, address) {
1713
+ const response = await this.fetch(`/nonce/${chainId}/${address}`);
1714
+ return BigInt(response.nonce);
1715
+ }
1716
+ /**
1717
+ * Estimate gas and fees for an intent
1718
+ *
1719
+ * @param chainId - Chain ID
1720
+ * @param accountAddress - Account address (the delegated EOA)
1721
+ * @param intent - Execution intent
1722
+ * @returns Estimate response
1723
+ */
1724
+ async estimate(chainId, accountAddress, intent) {
1725
+ const response = await this.fetch("/estimate", {
1726
+ method: "POST",
1727
+ body: JSON.stringify({
1728
+ chainId,
1729
+ accountAddress,
1730
+ intent: {
1731
+ nonce: intent.nonce.toString(),
1732
+ deadline: intent.deadline.toString(),
1733
+ target: intent.target,
1734
+ value: intent.value.toString(),
1735
+ data: intent.data
1736
+ }
1737
+ })
1738
+ });
1739
+ return response;
1740
+ }
1741
+ /**
1742
+ * Relay a signed intent
1743
+ *
1744
+ * @param chainId - Chain ID
1745
+ * @param accountAddress - Account address (the delegated EOA)
1746
+ * @param signedIntent - Signed execution intent
1747
+ * @param paymentMode - Payment mode ('sponsored' or { token: Address })
1748
+ * @param authorization - Optional EIP-7702 authorization for gasless onboarding
1749
+ * @returns Relay response with transaction hash
1750
+ *
1751
+ * @example Gasless onboarding (delegate + execute in one tx)
1752
+ * ```typescript
1753
+ * // Get current nonce for authorization
1754
+ * const txNonce = await publicClient.getTransactionCount({ address: accountAddress })
1755
+ *
1756
+ * // Sign EIP-7702 authorization
1757
+ * const authorization = await account.sign7702Authorization({
1758
+ * contractAddress: delegateAddress,
1759
+ * chainId: 42161,
1760
+ * }, txNonce)
1761
+ *
1762
+ * // Relay with authorization
1763
+ * const result = await relayer.relay(
1764
+ * 42161,
1765
+ * accountAddress,
1766
+ * signedIntent,
1767
+ * 'sponsored',
1768
+ * authorization
1769
+ * )
1770
+ * ```
1771
+ */
1772
+ async relay(chainId, accountAddress, signedIntent, paymentMode, authorization) {
1773
+ const body = {
1774
+ chainId,
1775
+ accountAddress,
1776
+ intent: {
1777
+ nonce: signedIntent.intent.nonce.toString(),
1778
+ deadline: signedIntent.intent.deadline.toString(),
1779
+ target: signedIntent.intent.target,
1780
+ value: signedIntent.intent.value.toString(),
1781
+ data: signedIntent.intent.data
1782
+ },
1783
+ ownerSignature: signedIntent.signature,
1784
+ paymentMode
1785
+ };
1786
+ if (authorization) {
1787
+ body.authorization = {
1788
+ chainId: authorization.chainId,
1789
+ contractAddress: authorization.contractAddress,
1790
+ nonce: authorization.nonce.toString(),
1791
+ yParity: authorization.yParity,
1792
+ r: authorization.r,
1793
+ s: authorization.s
1794
+ };
1795
+ }
1796
+ const response = await this.fetch("/relay", {
1797
+ method: "POST",
1798
+ body: JSON.stringify(body)
1799
+ });
1800
+ return response;
1801
+ }
1802
+ /**
1803
+ * Get transaction status
1804
+ *
1805
+ * @param chainId - Chain ID
1806
+ * @param txHash - Transaction hash
1807
+ * @returns Status response
1808
+ */
1809
+ async getStatus(chainId, txHash) {
1810
+ const response = await this.fetch(`/status/${chainId}/${txHash}`);
1811
+ return response;
1812
+ }
1813
+ /**
1814
+ * Make a fetch request to the relayer API
1815
+ */
1816
+ async fetch(path, options) {
1817
+ const controller = new AbortController();
1818
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1819
+ try {
1820
+ const response = await fetch(`${this.baseUrl}${path}`, {
1821
+ ...options,
1822
+ headers: {
1823
+ "Content-Type": "application/json",
1824
+ ...options?.headers
1825
+ },
1826
+ signal: controller.signal
1827
+ });
1828
+ const data = await response.json();
1829
+ if (!response.ok) {
1830
+ throw new Error(data.error || `Request failed: ${response.status}`);
1831
+ }
1832
+ return data;
1833
+ } finally {
1834
+ clearTimeout(timeoutId);
1835
+ }
1836
+ }
1837
+ };
1838
+ function createRelayerClient(baseUrl) {
1839
+ return new RelayerClient({ baseUrl });
1840
+ }
1841
+
1842
+ // src/index.ts
1843
+ var ALCHEMY_SEMI_MODULAR_ACCOUNT_7702 = "0x69007702764179f14F51cdce752f4f775d74E139";
1596
1844
  export {
1845
+ ALCHEMY_SEMI_MODULAR_ACCOUNT_7702,
1597
1846
  EphemeralKeyManager,
1598
1847
  IndexedDBEphemeralKeyStorage,
1599
1848
  KentuckySignerClient,
@@ -1601,6 +1850,7 @@ export {
1601
1850
  LocalStorageTokenStorage,
1602
1851
  MemoryEphemeralKeyStorage,
1603
1852
  MemoryTokenStorage,
1853
+ RelayerClient,
1604
1854
  SecureKentuckySignerClient,
1605
1855
  authenticateWithPasskey,
1606
1856
  authenticateWithPassword,
@@ -1610,12 +1860,16 @@ export {
1610
1860
  bytesToHex,
1611
1861
  createAccountWithPassword,
1612
1862
  createClient,
1863
+ createExecutionIntent,
1613
1864
  createKentuckySignerAccount,
1865
+ createRelayerClient,
1614
1866
  createSecureClient,
1615
1867
  createServerAccount,
1616
1868
  formatError,
1617
1869
  generateEphemeralKeyPair,
1618
1870
  getJwtExpiration,
1871
+ hashBatchIntents,
1872
+ hashIntent,
1619
1873
  hexToBytes,
1620
1874
  isSessionValid,
1621
1875
  isValidAccountId,
@@ -1625,6 +1879,8 @@ export {
1625
1879
  parseJwt,
1626
1880
  refreshSessionIfNeeded,
1627
1881
  registerPasskey,
1882
+ signBatchIntents,
1883
+ signIntent,
1628
1884
  signPayload,
1629
1885
  verifyPayload,
1630
1886
  withRetry