📄 src/web3/session/writeClient.test.ts
D-OPEN SOVEREIGN
1import { describe, it, expect, vi, beforeEach } from 'vitest';
2
3const mocks = vi.hoisted(() => ({
4  getReadyEvmClient: vi.fn(),
5  addressFromPrivateKey: vi.fn(),
6  createPrivateKeyWalletClient: vi.fn(),
7  EvmWriteClient: vi.fn(),
8}));
9
10vi.mock('../client/registry', () => ({ getReadyEvmClient: mocks.getReadyEvmClient }));
11vi.mock('../wallet/PrivateKeyConnector', () => ({
12  addressFromPrivateKey: mocks.addressFromPrivateKey,
13  createPrivateKeyWalletClient: mocks.createPrivateKeyWalletClient,
14}));
15vi.mock('../client/EvmWriteClient', () => ({ EvmWriteClient: mocks.EvmWriteClient }));
16
17import { createWriteClient, createPrivateKeyWriteClient } from './writeClient';
18
19const FAKE_EVM_CLIENT = { chainId: 1116 };
20const FAKE_WALLET_CLIENT = { account: { address: '0xacc' } };
21
22beforeEach(() => {
23  Object.values(mocks).forEach((m) => m.mockReset());
24  mocks.getReadyEvmClient.mockResolvedValue(FAKE_EVM_CLIENT);
25  mocks.EvmWriteClient.mockImplementation(function (this: any, ...args: any[]) {
26    Object.assign(this, { args });
27  });
28});
29
30function fakeConnector(overrides: any = {}) {
31  return {
32    type: 'metamask',
33    getAccounts: vi.fn().mockResolvedValue(['0xAccount1']),
34    getWalletClient: vi.fn().mockResolvedValue(FAKE_WALLET_CLIENT),
35    ...overrides,
36  };
37}
38
39describe('createWriteClient', () => {
40  it('throws when the connector does not implement getWalletClient', async () => {
41    const connector = fakeConnector({ getWalletClient: undefined, type: 'walletconnect' });
42    await expect(createWriteClient(connector as any, 1116)).rejects.toThrow(
43      "Connector 'walletconnect' does not support EVM write operations"
44    );
45  });
46
47  it('throws when the connector has no accounts', async () => {
48    const connector = fakeConnector({ getAccounts: vi.fn().mockResolvedValue([]) });
49    await expect(createWriteClient(connector as any, 1116)).rejects.toThrow(
50      "No account available for wallet 'metamask' — connect() first"
51    );
52  });
53
54  it('defaults environment to production and wires the ready EvmClient + walletClient + first account into EvmWriteClient', async () => {
55    const connector = fakeConnector();
56    await createWriteClient(connector as any, 1116);
57
58    expect(mocks.getReadyEvmClient).toHaveBeenCalledWith(1116, 'production');
59    expect(connector.getWalletClient).toHaveBeenCalledWith(1116);
60    expect(mocks.EvmWriteClient).toHaveBeenCalledWith('metamask', FAKE_EVM_CLIENT, FAKE_WALLET_CLIENT, '0xAccount1');
61  });
62
63  it('passes a non-default environment straight through to getReadyEvmClient', async () => {
64    const connector = fakeConnector();
65    await createWriteClient(connector as any, 1114, 'staging');
66    expect(mocks.getReadyEvmClient).toHaveBeenCalledWith(1114, 'staging');
67  });
68
69  it('uses only the first account when the connector exposes multiple', async () => {
70    const connector = fakeConnector({ getAccounts: vi.fn().mockResolvedValue(['0xFirst', '0xSecond']) });
71    await createWriteClient(connector as any, 1116);
72    expect(mocks.EvmWriteClient).toHaveBeenCalledWith('metamask', FAKE_EVM_CLIENT, FAKE_WALLET_CLIENT, '0xFirst');
73  });
74});
75
76describe('createPrivateKeyWriteClient', () => {
77  it('derives the account from the private key and wires everything into EvmWriteClient with wallet type "private_key"', async () => {
78    mocks.createPrivateKeyWalletClient.mockReturnValue(FAKE_WALLET_CLIENT);
79    mocks.addressFromPrivateKey.mockReturnValue('0xDerivedAddress');
80
81    await createPrivateKeyWriteClient(1116, '0xabc');
82
83    expect(mocks.getReadyEvmClient).toHaveBeenCalledWith(1116, 'production');
84    expect(mocks.createPrivateKeyWalletClient).toHaveBeenCalledWith(1116, '0xabc');
85    expect(mocks.addressFromPrivateKey).toHaveBeenCalledWith('0xabc');
86    expect(mocks.EvmWriteClient).toHaveBeenCalledWith('private_key', FAKE_EVM_CLIENT, FAKE_WALLET_CLIENT, '0xDerivedAddress');
87  });
88
89  it('passes a non-default environment straight through to getReadyEvmClient', async () => {
90    mocks.createPrivateKeyWalletClient.mockReturnValue(FAKE_WALLET_CLIENT);
91    mocks.addressFromPrivateKey.mockReturnValue('0xDerivedAddress');
92
93    await createPrivateKeyWriteClient(1114, '0xabc', 'staging');
94    expect(mocks.getReadyEvmClient).toHaveBeenCalledWith(1114, 'staging');
95  });
96});
97