@cofhe/sdk 0.1.0 → 0.2.0

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.
Files changed (121) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/adapters/ethers5.test.ts +174 -0
  3. package/adapters/ethers5.ts +36 -0
  4. package/adapters/ethers6.test.ts +169 -0
  5. package/adapters/ethers6.ts +36 -0
  6. package/adapters/hardhat-node.ts +167 -0
  7. package/adapters/hardhat.hh2.test.ts +159 -0
  8. package/adapters/hardhat.ts +36 -0
  9. package/adapters/index.test.ts +20 -0
  10. package/adapters/index.ts +5 -0
  11. package/adapters/smartWallet.ts +99 -0
  12. package/adapters/test-utils.ts +53 -0
  13. package/adapters/types.ts +6 -0
  14. package/adapters/wagmi.test.ts +156 -0
  15. package/adapters/wagmi.ts +17 -0
  16. package/chains/chains/arbSepolia.ts +14 -0
  17. package/chains/chains/baseSepolia.ts +14 -0
  18. package/chains/chains/hardhat.ts +15 -0
  19. package/chains/chains/localcofhe.ts +14 -0
  20. package/chains/chains/sepolia.ts +14 -0
  21. package/chains/chains.test.ts +50 -0
  22. package/chains/defineChain.ts +18 -0
  23. package/chains/index.ts +35 -0
  24. package/chains/types.ts +32 -0
  25. package/core/baseBuilder.ts +119 -0
  26. package/core/client.test.ts +315 -0
  27. package/core/client.ts +292 -0
  28. package/core/clientTypes.ts +108 -0
  29. package/core/config.test.ts +235 -0
  30. package/core/config.ts +220 -0
  31. package/core/decrypt/MockQueryDecrypterAbi.ts +129 -0
  32. package/core/decrypt/cofheMocksSealOutput.ts +57 -0
  33. package/core/decrypt/decryptHandleBuilder.ts +287 -0
  34. package/core/decrypt/decryptUtils.ts +28 -0
  35. package/core/decrypt/tnSealOutputV1.ts +59 -0
  36. package/core/decrypt/tnSealOutputV2.ts +298 -0
  37. package/core/encrypt/MockZkVerifierAbi.ts +106 -0
  38. package/core/encrypt/cofheMocksZkVerifySign.ts +284 -0
  39. package/core/encrypt/encryptInputsBuilder.test.ts +751 -0
  40. package/core/encrypt/encryptInputsBuilder.ts +560 -0
  41. package/core/encrypt/encryptUtils.ts +67 -0
  42. package/core/encrypt/zkPackProveVerify.ts +335 -0
  43. package/core/error.ts +168 -0
  44. package/core/fetchKeys.test.ts +195 -0
  45. package/core/fetchKeys.ts +144 -0
  46. package/core/index.ts +89 -0
  47. package/core/keyStore.test.ts +226 -0
  48. package/core/keyStore.ts +154 -0
  49. package/core/permits.test.ts +494 -0
  50. package/core/permits.ts +200 -0
  51. package/core/types.ts +398 -0
  52. package/core/utils.ts +130 -0
  53. package/dist/adapters.cjs +88 -0
  54. package/dist/adapters.d.cts +14576 -0
  55. package/dist/adapters.d.ts +14576 -0
  56. package/dist/adapters.js +83 -0
  57. package/dist/chains.cjs +114 -0
  58. package/dist/chains.d.cts +121 -0
  59. package/dist/chains.d.ts +121 -0
  60. package/dist/chains.js +1 -0
  61. package/dist/chunk-UGBVZNRT.js +818 -0
  62. package/dist/chunk-WEAZ25JO.js +105 -0
  63. package/dist/chunk-WGCRJCBR.js +2523 -0
  64. package/dist/clientTypes-5_1nwtUe.d.cts +914 -0
  65. package/dist/clientTypes-Es7fyi65.d.ts +914 -0
  66. package/dist/core.cjs +3414 -0
  67. package/dist/core.d.cts +111 -0
  68. package/dist/core.d.ts +111 -0
  69. package/dist/core.js +3 -0
  70. package/dist/node.cjs +3286 -0
  71. package/dist/node.d.cts +22 -0
  72. package/dist/node.d.ts +22 -0
  73. package/dist/node.js +91 -0
  74. package/dist/permit-fUSe6KKq.d.cts +349 -0
  75. package/dist/permit-fUSe6KKq.d.ts +349 -0
  76. package/dist/permits.cjs +871 -0
  77. package/dist/permits.d.cts +1045 -0
  78. package/dist/permits.d.ts +1045 -0
  79. package/dist/permits.js +1 -0
  80. package/dist/types-KImPrEIe.d.cts +48 -0
  81. package/dist/types-KImPrEIe.d.ts +48 -0
  82. package/dist/web.cjs +3478 -0
  83. package/dist/web.d.cts +38 -0
  84. package/dist/web.d.ts +38 -0
  85. package/dist/web.js +240 -0
  86. package/dist/zkProve.worker.cjs +93 -0
  87. package/dist/zkProve.worker.d.cts +2 -0
  88. package/dist/zkProve.worker.d.ts +2 -0
  89. package/dist/zkProve.worker.js +91 -0
  90. package/node/client.test.ts +147 -0
  91. package/node/config.test.ts +68 -0
  92. package/node/encryptInputs.test.ts +155 -0
  93. package/node/index.ts +97 -0
  94. package/node/storage.ts +51 -0
  95. package/package.json +27 -15
  96. package/permits/index.ts +68 -0
  97. package/permits/localstorage.test.ts +117 -0
  98. package/permits/permit.test.ts +477 -0
  99. package/permits/permit.ts +405 -0
  100. package/permits/sealing.test.ts +84 -0
  101. package/permits/sealing.ts +131 -0
  102. package/permits/signature.ts +79 -0
  103. package/permits/store.test.ts +128 -0
  104. package/permits/store.ts +166 -0
  105. package/permits/test-utils.ts +20 -0
  106. package/permits/types.ts +191 -0
  107. package/permits/utils.ts +62 -0
  108. package/permits/validation.test.ts +288 -0
  109. package/permits/validation.ts +369 -0
  110. package/web/client.web.test.ts +147 -0
  111. package/web/config.web.test.ts +69 -0
  112. package/web/encryptInputs.web.test.ts +172 -0
  113. package/web/index.ts +161 -0
  114. package/web/storage.ts +34 -0
  115. package/web/worker.builder.web.test.ts +148 -0
  116. package/web/worker.config.web.test.ts +329 -0
  117. package/web/worker.output.web.test.ts +84 -0
  118. package/web/workerManager.test.ts +80 -0
  119. package/web/workerManager.ts +214 -0
  120. package/web/workerManager.web.test.ts +114 -0
  121. package/web/zkProve.worker.ts +133 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,62 @@
1
+ # @cofhe/sdk Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8fda09a: Removes `Promise<boolean>` return type from `client.connect(...)`, instead throws an error if the connection fails.
8
+ - e0caeca: Adds `environment: 'node' | 'web' | 'hardhat' | 'react'` option to config. Exposed via `client.config.enviroment`. Automatically populated appropriately within the various `createCofhesdkConfig` functions.
9
+ - 2a9d6c5: Updated to new CoFHE /sealoutputV2 endpoint - uses polling to fetch decryption results instead of long running open HTTP connections.
10
+
11
+ ### Patch Changes
12
+
13
+ - 4057a76: Add WebWorker for zkProve
14
+ - dba2759: Add getOrCreate permit functions
15
+ - 7c861af: Remove `initializationResults` from cofhesdk client.
16
+
17
+ ## 0.1.1
18
+
19
+ ### Patch Changes
20
+
21
+ - a1d1323: Add repository info to package.json of public packages to fix npm publish provenance issue.
22
+ - d232d11: Ensure publish includes correct src and dist files
23
+ - b6521fb: Update publish workflow to create versioning PR upon merge with changeset.
24
+
25
+ ## 0.1.0
26
+
27
+ ### Minor Changes
28
+
29
+ - 87fc8a0: Initial extraction from cofhejs. Split permit `create` into type specific creators: `createSelf`, `createShared`, and `importShared`
30
+ - 8d41cf2: Combine existing packages into more reasonable defaults. New package layout is @cofhe/sdk (includes all the core logic for configuring and creating a @cofhe/sdk client, encrypting values, and decrypting handles), mock-contracts, hardhat-plugin, and react.
31
+ - 738b9f5: Adding adapters for ethers5/6, Wagmi and HardHat
32
+ - a83facb: Prepare for initial release. Rename scope from `@cofhesdk` to `@cofhe` and rename `cofhesdk` package to `@cofhe/sdk`. Create `publish.yml` to publish `beta` packages on merged PR, and `latest` on changeset PR.
33
+ - 9a7c98e: Create core store, split initialize into `create` and `connect`, and port `encryptInputs` with improvements.
34
+ - 58e93a8: Migrate cofhe-mock-contracts and cofhe-hardhat-plugin into @cofhe/sdk.
35
+ - fdf26d4: Move storage handling to web/node targets; add `fheKeysStorage` config with environment-specific defaults.
36
+ - f5b8e25: Add @cofhe/sdk config type and parsing.
37
+ - 3b135a8: Create @cofhe/sdk/node and @cofhe/sdk/web
38
+
39
+ Additional changes:
40
+
41
+ - Fhe keys aren't fetched until `client.encryptInputs(...).encrypt()`, they aren't used anywhere else other than encrypting inputs, so their fetching is deferred until then.
42
+ - Initializing the tfhe wasm is also deferred until `client.encryptInputs(...).encrypt()` is called (allows for deferred async initialization)
43
+
44
+ - 5b7c43b: Create @cofhe/sdk client, hook in permits.
45
+ Create encryptInputs functionality. (+ mocks)
46
+ Create decryptHandles functionality. (+ mocks)
47
+ Improve @cofhe/sdk errors and error handling.
48
+
49
+ fix - seal_output include error_message in failing Error.
50
+
51
+ ### Patch Changes
52
+
53
+ - cba73fd: Add duration and context information to the step callback function for encryptInputs. Split fetch keys step into InitTfhe and FetchKeys.
54
+ InitTfhe context includes a `tfheInitializationExecuted` indicating if tfhe was initialized (true) or already initialized (false).
55
+ FetchKeys returns flags for whether the fhe and crs keys were fetch from remote (true) or from cache (false).
56
+ - 4bc8182: Add storage and key store
57
+
58
+ This changelog is maintained by Changesets and will be populated on each release.
59
+
60
+ - Do not edit this file by hand.
61
+ - Upcoming changes can be previewed with `pnpm changeset status --verbose`.
62
+ - Entries are generated when the Changesets "Version Packages" PR is created/merged.
@@ -0,0 +1,174 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { parseEther } from 'viem';
3
+ import { Ethers5Adapter } from './ethers5.js';
4
+ import { createMockEIP1193Provider } from './test-utils.js';
5
+ import * as ethers5 from 'ethers5';
6
+
7
+ describe('Ethers5Adapter', () => {
8
+ const testRpcUrl = 'https://ethereum-sepolia.rpc.subquery.network/public';
9
+ const SEPOLIA_CHAIN_ID = 11155111;
10
+ let provider: ethers5.providers.JsonRpcProvider;
11
+ let wallet: ethers5.Wallet;
12
+
13
+ beforeEach(() => {
14
+ // Create common setup for all tests
15
+ provider = new ethers5.providers.JsonRpcProvider(testRpcUrl);
16
+ wallet = new ethers5.Wallet('0x' + '1'.repeat(64), provider);
17
+ });
18
+
19
+ it('should work with real JsonRpcProvider and Wallet', async () => {
20
+ const result = await Ethers5Adapter(provider, wallet);
21
+
22
+ expect(result).toHaveProperty('publicClient');
23
+ expect(result).toHaveProperty('walletClient');
24
+ expect(result.publicClient).toBeDefined();
25
+ expect(result.walletClient).toBeDefined();
26
+ });
27
+
28
+ it('should work with Web3Provider signer', async () => {
29
+ const mockEthereum = createMockEIP1193Provider();
30
+ const web3Provider = new ethers5.providers.Web3Provider(mockEthereum as any);
31
+ const wallet = new ethers5.Wallet('0x' + '1'.repeat(64), web3Provider);
32
+
33
+ const result = await Ethers5Adapter(web3Provider, wallet);
34
+
35
+ expect(result).toHaveProperty('publicClient');
36
+ expect(result).toHaveProperty('walletClient');
37
+ });
38
+
39
+ it('should work without configuration', async () => {
40
+ const result = await Ethers5Adapter(provider, wallet);
41
+
42
+ expect(result).toHaveProperty('publicClient');
43
+ expect(result).toHaveProperty('walletClient');
44
+ expect(result.publicClient).toBeDefined();
45
+ expect(result.walletClient).toBeDefined();
46
+ });
47
+
48
+ it('should throw error when signer has no provider', async () => {
49
+ const signerWithoutProvider = { provider: null };
50
+
51
+ await expect(async () => {
52
+ await Ethers5Adapter(signerWithoutProvider as any, wallet);
53
+ }).rejects.toThrow('Provider does not support EIP-1193 interface');
54
+ });
55
+
56
+ it('should work with real network call', async () => {
57
+ const result = await Ethers5Adapter(provider, wallet);
58
+
59
+ // Test real network call
60
+ const chainId = await result.publicClient.getChainId();
61
+ expect(typeof chainId).toBe('number');
62
+ expect(chainId).toBeGreaterThan(0);
63
+ }, 10000);
64
+
65
+ describe('Provider Functions', () => {
66
+ it('should support getChainId', async () => {
67
+ const { publicClient } = await Ethers5Adapter(provider, wallet);
68
+
69
+ const chainId = await publicClient.getChainId();
70
+ expect(typeof chainId).toBe('number');
71
+ expect(chainId).toBe(SEPOLIA_CHAIN_ID); // Sepolia
72
+ }, 10000);
73
+
74
+ it('should support call (contract read)', async () => {
75
+ const { publicClient } = await Ethers5Adapter(provider, wallet);
76
+
77
+ // Test eth_call - get ETH balance of zero address
78
+ const balance = await publicClient.getBalance({
79
+ address: '0x0000000000000000000000000000000000000000',
80
+ });
81
+ expect(typeof balance).toBe('bigint');
82
+ }, 10000);
83
+
84
+ it('should support request (raw RPC)', async () => {
85
+ const { publicClient } = await Ethers5Adapter(provider, wallet);
86
+
87
+ // Test raw RPC request - viem requires both method and params
88
+ const blockNumber = (await publicClient.request({
89
+ method: 'eth_blockNumber',
90
+ })) as string;
91
+ expect(typeof blockNumber).toBe('string');
92
+ expect(blockNumber.startsWith('0x')).toBe(true);
93
+ }, 10000);
94
+ });
95
+
96
+ describe('Signer Functions', () => {
97
+ it('should support getAddress', async () => {
98
+ const { walletClient } = await Ethers5Adapter(provider, wallet);
99
+
100
+ const addresses = await walletClient.getAddresses();
101
+ expect(Array.isArray(addresses)).toBe(true);
102
+ // Note: For HTTP transport, this might return empty array
103
+ // For EIP-1193 providers, it would return actual addresses
104
+ }, 10000);
105
+
106
+ it('should support signTypedData', async () => {
107
+ const { walletClient } = await Ethers5Adapter(provider, wallet);
108
+
109
+ const domain = {
110
+ name: 'Test',
111
+ version: '1',
112
+ chainId: SEPOLIA_CHAIN_ID, // Sepolia
113
+ verifyingContract: '0x0000000000000000000000000000000000000000' as const,
114
+ };
115
+
116
+ const types = {
117
+ Message: [{ name: 'content', type: 'string' }],
118
+ };
119
+
120
+ const message = { content: 'Hello World' };
121
+
122
+ const signature = await walletClient.signTypedData({
123
+ domain,
124
+ types,
125
+ primaryType: 'Message',
126
+ message,
127
+ account: walletClient.account!,
128
+ });
129
+
130
+ expect(typeof signature).toBe('string');
131
+ expect(signature.startsWith('0x')).toBe(true);
132
+ }, 10000);
133
+
134
+ it('should support sendTransaction', async () => {
135
+ const { publicClient, walletClient } = await Ethers5Adapter(provider, wallet);
136
+
137
+ const account = await walletClient.account;
138
+ const chain = await publicClient.getChainId();
139
+
140
+ console.log('Wallet client ethers5', account, chain);
141
+
142
+ // Try to send a transaction - this will fail due to insufficient funds
143
+ try {
144
+ console.log('estimating gas');
145
+ const gas = await publicClient.estimateGas({
146
+ account: wallet.address as `0x${string}`,
147
+ to: '0x0000000000000000000000000000000000000000',
148
+ value: parseEther('0'),
149
+ });
150
+
151
+ console.log('sending transaction', await wallet.getAddress());
152
+ const hash = await walletClient.sendTransaction({
153
+ to: '0x0000000000000000000000000000000000000000' as `0x${string}`,
154
+ value: parseEther('0'),
155
+ gas,
156
+ account: walletClient.account!,
157
+ chain: walletClient.chain,
158
+ });
159
+ console.log('transaction sent', hash);
160
+
161
+ // If it succeeds (shouldn't due to no funds), verify the format
162
+ expect(typeof hash).toBe('string');
163
+ expect(hash.startsWith('0x')).toBe(true);
164
+ expect(hash.length).toBe(66);
165
+ } catch (error: any) {
166
+ // Expected errors: either insufficient funds (good!) or method not supported
167
+ const isInsufficientFunds = error.message.includes('insufficient funds') || error.message.includes('balance');
168
+
169
+ expect(isInsufficientFunds).toBe(true);
170
+ console.log('Expected error (insufficient funds):', error.message);
171
+ }
172
+ }, 10000);
173
+ });
174
+ });
@@ -0,0 +1,36 @@
1
+ import { createPublicClient, createWalletClient, custom } from 'viem';
2
+ import { privateKeyToAccount, toAccount } from 'viem/accounts';
3
+ import type { Signer as AbstractSigner, Wallet, providers } from 'ethers5';
4
+ import { type AdapterResult } from './types.js';
5
+
6
+ type Ethers5Signer = AbstractSigner | Wallet;
7
+
8
+ export async function Ethers5Adapter(provider: providers.Provider, signer: Ethers5Signer): Promise<AdapterResult> {
9
+ // Create transport from provider
10
+ const transport =
11
+ provider && 'send' in provider && typeof provider.send === 'function'
12
+ ? // @ts-ignore - ethers5 provider.send is not typed
13
+ custom({ request: ({ method, params }: any) => provider.send(method, params ?? []) })
14
+ : (() => {
15
+ throw new Error('Provider does not support EIP-1193 interface');
16
+ })();
17
+
18
+ // build a viem Account
19
+ const address = (await signer.getAddress()) as `0x${string}`;
20
+ let account: ReturnType<typeof privateKeyToAccount> | ReturnType<typeof toAccount> | `0x${string}`;
21
+
22
+ if ('privateKey' in signer && typeof (signer as Wallet).privateKey === 'string') {
23
+ // Local (true offline) signing → works with Infura via sendRawTransaction
24
+ account = privateKeyToAccount((signer as Wallet).privateKey as `0x${string}`);
25
+ } else if (provider && typeof provider.send === 'function') {
26
+ // Injected wallet (MetaMask/Coinbase) → wallet signs via eth_sendTransaction
27
+ account = address; // JSON-RPC account (not local signing)
28
+ } else {
29
+ throw new Error('Signer does not expose a private key and no injected wallet is available.');
30
+ }
31
+
32
+ const publicClient = createPublicClient({ transport });
33
+ const walletClient = createWalletClient({ transport, account });
34
+
35
+ return { publicClient, walletClient };
36
+ }
@@ -0,0 +1,169 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { parseEther } from 'viem';
3
+ import { Ethers6Adapter } from './ethers6.js';
4
+ import { createMockEIP1193Provider } from './test-utils.js';
5
+ import * as ethers6 from 'ethers6';
6
+
7
+ describe('Ethers6Adapter', () => {
8
+ const testRpcUrl = 'https://ethereum-sepolia.rpc.subquery.network/public';
9
+ const SEPOLIA_CHAIN_ID = 11155111;
10
+ let provider: ethers6.JsonRpcProvider;
11
+ let wallet: ethers6.Wallet;
12
+
13
+ beforeEach(() => {
14
+ // Create common setup for all tests
15
+ provider = new ethers6.JsonRpcProvider(testRpcUrl);
16
+ wallet = new ethers6.Wallet('0x' + '1'.repeat(64), provider);
17
+ });
18
+
19
+ it('should work with real JsonRpcProvider and Wallet', async () => {
20
+ const result = await Ethers6Adapter(provider, wallet);
21
+
22
+ expect(result).toHaveProperty('publicClient');
23
+ expect(result).toHaveProperty('walletClient');
24
+ expect(result.publicClient).toBeDefined();
25
+ expect(result.walletClient).toBeDefined();
26
+ });
27
+
28
+ it('should work with BrowserProvider signer', async () => {
29
+ const mockEthereum = createMockEIP1193Provider();
30
+ const browserProvider = new ethers6.BrowserProvider(mockEthereum as any);
31
+ const wallet = new ethers6.Wallet('0x' + '1'.repeat(64), browserProvider);
32
+
33
+ const result = await Ethers6Adapter(browserProvider, wallet);
34
+
35
+ expect(result).toHaveProperty('publicClient');
36
+ expect(result).toHaveProperty('walletClient');
37
+ });
38
+
39
+ it('should work without configuration', async () => {
40
+ const result = await Ethers6Adapter(provider, wallet);
41
+
42
+ expect(result).toHaveProperty('publicClient');
43
+ expect(result).toHaveProperty('walletClient');
44
+ expect(result.publicClient).toBeDefined();
45
+ expect(result.walletClient).toBeDefined();
46
+ });
47
+
48
+ it('should throw error when signer has no provider', async () => {
49
+ const signerWithoutProvider = { provider: null };
50
+
51
+ await expect(async () => {
52
+ await Ethers6Adapter(signerWithoutProvider as any, wallet);
53
+ }).rejects.toThrow('Provider does not support EIP-1193 interface');
54
+ });
55
+
56
+ it('should work with real network call', async () => {
57
+ const result = await Ethers6Adapter(provider, wallet);
58
+
59
+ // Test real network call
60
+ const chainId = await result.publicClient.getChainId();
61
+ expect(typeof chainId).toBe('number');
62
+ expect(chainId).toBeGreaterThan(0);
63
+ }, 10000);
64
+
65
+ describe('Provider Functions', () => {
66
+ it('should support getChainId', async () => {
67
+ const { publicClient } = await Ethers6Adapter(provider, wallet);
68
+
69
+ const chainId = await publicClient.getChainId();
70
+ expect(typeof chainId).toBe('number');
71
+ expect(chainId).toBe(SEPOLIA_CHAIN_ID); // sepolia
72
+ }, 10000);
73
+
74
+ it('should support call (contract read)', async () => {
75
+ const { publicClient } = await Ethers6Adapter(provider, wallet);
76
+
77
+ // Test eth_call - get ETH balance of zero address
78
+ const balance = await publicClient.getBalance({
79
+ address: '0x0000000000000000000000000000000000000000',
80
+ });
81
+ expect(typeof balance).toBe('bigint');
82
+ }, 10000);
83
+
84
+ it('should support request (raw RPC)', async () => {
85
+ const { publicClient } = await Ethers6Adapter(provider, wallet);
86
+
87
+ // Test raw RPC request - viem requires both method and params
88
+ const blockNumber = (await publicClient.request({
89
+ method: 'eth_blockNumber',
90
+ })) as string;
91
+ expect(typeof blockNumber).toBe('string');
92
+ expect(blockNumber.startsWith('0x')).toBe(true);
93
+ }, 10000);
94
+ });
95
+
96
+ describe('Signer Functions', () => {
97
+ it('should support getAddress', async () => {
98
+ const { walletClient } = await Ethers6Adapter(provider, wallet);
99
+
100
+ const addresses = await walletClient.getAddresses();
101
+ expect(Array.isArray(addresses)).toBe(true);
102
+ // Note: For HTTP transport, this might return empty array
103
+ // For EIP-1193 providers, it would return actual addresses
104
+ }, 10000);
105
+
106
+ it('should support signTypedData', async () => {
107
+ const { walletClient } = await Ethers6Adapter(provider, wallet);
108
+
109
+ const domain = {
110
+ name: 'Test',
111
+ version: '1',
112
+ chainId: SEPOLIA_CHAIN_ID, // Sepolia
113
+ verifyingContract: '0x0000000000000000000000000000000000000000' as const,
114
+ };
115
+
116
+ const types = {
117
+ Message: [{ name: 'content', type: 'string' }],
118
+ };
119
+
120
+ const message = { content: 'Hello World' };
121
+
122
+ const signature = await walletClient.signTypedData({
123
+ domain,
124
+ types,
125
+ primaryType: 'Message',
126
+ message,
127
+ account: walletClient.account!,
128
+ });
129
+
130
+ expect(typeof signature).toBe('string');
131
+ expect(signature.startsWith('0x')).toBe(true);
132
+ }, 10000);
133
+
134
+ it('should support sendTransaction', async () => {
135
+ const { publicClient, walletClient } = await Ethers6Adapter(provider, wallet);
136
+
137
+ // Try to send a transaction - this will fail with Infura but we'll catch the error
138
+ try {
139
+ console.log('estimating gas');
140
+ const gas = await publicClient.estimateGas({
141
+ account: wallet.address as `0x${string}`,
142
+ to: '0x0000000000000000000000000000000000000000',
143
+ value: parseEther('0'),
144
+ });
145
+
146
+ console.log('sending transaction', await wallet.getAddress());
147
+ const hash = await walletClient.sendTransaction({
148
+ to: '0x0000000000000000000000000000000000000000',
149
+ value: parseEther('0'),
150
+ gas,
151
+ account: walletClient.account!,
152
+ chain: walletClient.chain,
153
+ });
154
+ console.log('transaction sent', hash);
155
+
156
+ // If it succeeds (shouldn't with Infura), verify the format
157
+ expect(typeof hash).toBe('string');
158
+ expect(hash.startsWith('0x')).toBe(true);
159
+ expect(hash.length).toBe(66);
160
+ } catch (error: any) {
161
+ // Expected errors: either insufficient funds (good!) or method not supported
162
+ const isInsufficientFunds = error.message.includes('insufficient funds') || error.message.includes('balance');
163
+
164
+ expect(isInsufficientFunds).toBe(true);
165
+ console.log('Expected error (insufficient funds or method not supported):', error.message);
166
+ }
167
+ }, 10000);
168
+ });
169
+ });
@@ -0,0 +1,36 @@
1
+ import { createPublicClient, createWalletClient, custom } from 'viem';
2
+ import { privateKeyToAccount, toAccount } from 'viem/accounts';
3
+ import type { Wallet, AbstractSigner, Provider } from 'ethers6';
4
+ import { type AdapterResult } from './types.js';
5
+
6
+ type Ethers6Signer = AbstractSigner | Wallet;
7
+
8
+ export async function Ethers6Adapter(provider: Provider, signer: Ethers6Signer): Promise<AdapterResult> {
9
+ // Create transport from provider
10
+ const transport =
11
+ provider && 'send' in provider && typeof provider.send === 'function'
12
+ ? // @ts-ignore - ethers6 provider.send is not typed
13
+ custom({ request: ({ method, params }: any) => provider.send(method, params ?? []) })
14
+ : (() => {
15
+ throw new Error('Provider does not support EIP-1193 interface');
16
+ })();
17
+
18
+ // build a viem Account
19
+ const address = (await signer.getAddress()) as `0x${string}`;
20
+ let account: ReturnType<typeof privateKeyToAccount> | ReturnType<typeof toAccount> | `0x${string}`;
21
+
22
+ if ('privateKey' in signer && typeof (signer as Wallet).privateKey === 'string') {
23
+ // Local (true offline) signing → works with Infura via sendRawTransaction
24
+ account = privateKeyToAccount((signer as Wallet).privateKey as `0x${string}`); // local account
25
+ } else if (provider && typeof provider.send === 'function') {
26
+ // Injected wallet (MetaMask/Coinbase) → wallet signs via eth_sendTransaction
27
+ account = address; // JSON-RPC account (not local signing)
28
+ } else {
29
+ throw new Error('Signer does not expose a private key and no injected wallet is available.');
30
+ }
31
+
32
+ const publicClient = createPublicClient({ transport });
33
+ const walletClient = createWalletClient({ transport, account });
34
+
35
+ return { publicClient, walletClient };
36
+ }
@@ -0,0 +1,167 @@
1
+ import { spawn, ChildProcess } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { createConnection } from 'net';
4
+
5
+ const sleep = promisify(setTimeout);
6
+
7
+ async function isPortAvailable(port: number): Promise<boolean> {
8
+ return new Promise((resolve) => {
9
+ const connection = createConnection({ port, host: '127.0.0.1' });
10
+
11
+ connection.on('connect', () => {
12
+ connection.destroy();
13
+ resolve(false); // Port is in use
14
+ });
15
+
16
+ connection.on('error', () => {
17
+ resolve(true); // Port is available
18
+ });
19
+ });
20
+ }
21
+
22
+ async function testHardhatNode(port: number): Promise<boolean> {
23
+ try {
24
+ const response = await fetch(`http://127.0.0.1:${port}`, {
25
+ method: 'POST',
26
+ headers: { 'Content-Type': 'application/json' },
27
+ body: JSON.stringify({
28
+ jsonrpc: '2.0',
29
+ method: 'eth_chainId',
30
+ params: [],
31
+ id: 1,
32
+ }),
33
+ });
34
+
35
+ if (response.ok) {
36
+ const data = (await response.json()) as { result: string };
37
+ // Check if it returns Hardhat's chain ID (31337 = 0x7a69)
38
+ return data.result === '0x7a69';
39
+ }
40
+ return false;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ export class HardhatNode {
47
+ private process: ChildProcess | null = null;
48
+ private isReady = false;
49
+
50
+ async start(): Promise<void> {
51
+ if (this.process) {
52
+ return; // Already started
53
+ }
54
+
55
+ // Check if port 8545 is already in use
56
+ const portAvailable = await isPortAvailable(8545);
57
+ if (!portAvailable) {
58
+ // Port is in use, check if it's a Hardhat node we can use
59
+ const isHardhatNode = await testHardhatNode(8545);
60
+ if (isHardhatNode) {
61
+ console.log('Found existing Hardhat node on port 8545, using it...');
62
+ this.isReady = true;
63
+ return; // Use the existing node
64
+ } else {
65
+ throw new Error('Port 8545 is in use by a non-Hardhat service. Please free the port.');
66
+ }
67
+ }
68
+
69
+ console.log('Starting Hardhat node...');
70
+
71
+ this.process = spawn('npx', ['hardhat', 'node'], {
72
+ cwd: process.cwd(),
73
+ stdio: ['ignore', 'pipe', 'pipe'],
74
+ });
75
+
76
+ // Wait for the node to be ready
77
+ await new Promise<void>((resolve, reject) => {
78
+ const timeout = setTimeout(() => {
79
+ reject(new Error('Hardhat node failed to start within 30 seconds'));
80
+ }, 30000);
81
+
82
+ const onData = (data: Buffer) => {
83
+ const output = data.toString();
84
+ if (output.includes('Started HTTP and WebSocket JSON-RPC server at')) {
85
+ clearTimeout(timeout);
86
+ this.isReady = true;
87
+ console.log('Hardhat node is ready!');
88
+ resolve();
89
+ }
90
+ };
91
+
92
+ this.process!.stdout?.on('data', onData);
93
+ this.process!.stderr?.on('data', onData);
94
+
95
+ this.process!.on('error', (error) => {
96
+ clearTimeout(timeout);
97
+ reject(error);
98
+ });
99
+
100
+ this.process!.on('exit', (code) => {
101
+ if (code !== 0 && !this.isReady) {
102
+ clearTimeout(timeout);
103
+ reject(new Error(`Hardhat node exited with code ${code}`));
104
+ }
105
+ });
106
+ });
107
+
108
+ // Give it a bit more time to be fully ready
109
+ await sleep(2000);
110
+ }
111
+
112
+ async stop(): Promise<void> {
113
+ if (this.process) {
114
+ console.log('Stopping Hardhat node...');
115
+
116
+ try {
117
+ // Immediately force kill - no graceful shutdown to avoid hanging
118
+ this.process.kill('SIGKILL');
119
+ this.process = null;
120
+ this.isReady = false;
121
+
122
+ // Quick port cleanup
123
+ await this.killProcessOnPort(8545);
124
+
125
+ console.log('Hardhat node stopped');
126
+ } catch (error) {
127
+ console.log('Error stopping node:', error);
128
+ // Force cleanup regardless
129
+ this.process = null;
130
+ this.isReady = false;
131
+ await this.killProcessOnPort(8545);
132
+ }
133
+ } else if (this.isReady) {
134
+ // We're using an existing node, just mark as not ready
135
+ console.log('Using external Hardhat node, not stopping it...');
136
+ this.isReady = false;
137
+ }
138
+ }
139
+
140
+ isRunning(): boolean {
141
+ return this.process !== null && this.isReady;
142
+ }
143
+
144
+ private async killProcessOnPort(port: number): Promise<void> {
145
+ try {
146
+ const { spawn } = await import('child_process');
147
+
148
+ // Kill any process using the port (Linux/macOS) - faster timeout
149
+ const killProcess = spawn('sh', ['-c', `lsof -ti :${port} | xargs -r kill -9`], {
150
+ stdio: 'ignore',
151
+ });
152
+
153
+ await new Promise<void>((resolve) => {
154
+ killProcess.on('exit', () => resolve());
155
+ killProcess.on('error', () => resolve()); // Ignore errors
156
+ setTimeout(resolve, 300); // Shorter timeout - 300ms
157
+ });
158
+ } catch {
159
+ // Ignore errors - this is a cleanup attempt
160
+ }
161
+ }
162
+ }
163
+
164
+ // Global instance
165
+ export const hardhatNode = new HardhatNode();
166
+
167
+ // Global instance - no process handlers to avoid interfering with test process