@marigoldlabs/web3-tester 0.1.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.
- package/.env.example +27 -0
- package/README.md +181 -0
- package/dist/anvil.d.ts +52 -0
- package/dist/anvil.d.ts.map +1 -0
- package/dist/anvil.js +203 -0
- package/dist/anvil.js.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -0
- package/dist/fixtures.d.ts +15 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +87 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/injected-provider.d.ts +5 -0
- package/dist/injected-provider.d.ts.map +1 -0
- package/dist/injected-provider.js +141 -0
- package/dist/injected-provider.js.map +1 -0
- package/dist/live-fixtures.d.ts +9 -0
- package/dist/live-fixtures.d.ts.map +1 -0
- package/dist/live-fixtures.js +33 -0
- package/dist/live-fixtures.js.map +1 -0
- package/dist/mock-wallet-controller.d.ts +41 -0
- package/dist/mock-wallet-controller.d.ts.map +1 -0
- package/dist/mock-wallet-controller.js +224 -0
- package/dist/mock-wallet-controller.js.map +1 -0
- package/dist/private-key-rpc-client.d.ts +23 -0
- package/dist/private-key-rpc-client.d.ts.map +1 -0
- package/dist/private-key-rpc-client.js +112 -0
- package/dist/private-key-rpc-client.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/API.md +175 -0
- package/docs/ARCHITECTURE.md +81 -0
- package/docs/CONSUMING_FROM_FJORD.md +123 -0
- package/docs/FJORD_LIVE_QA.md +87 -0
- package/docs/RELEASE_CHECKLIST.md +49 -0
- package/examples/live-sepolia.spec.ts +21 -0
- package/examples/local-wallet.spec.ts +34 -0
- package/examples/playwright.config.ts +16 -0
- package/package.json +70 -0
package/.env.example
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# App under test
|
|
2
|
+
DAPP_URL=https://v4.fjordfoundry.com
|
|
3
|
+
|
|
4
|
+
# Local Anvil
|
|
5
|
+
ANVIL_RUNTIME=binary
|
|
6
|
+
ANVIL_EXECUTABLE=anvil
|
|
7
|
+
ANVIL_DOCKER_IMAGE=ghcr.io/foundry-rs/foundry:latest
|
|
8
|
+
ANVIL_HOST=127.0.0.1
|
|
9
|
+
ANVIL_PORT=8545
|
|
10
|
+
ANVIL_CHAIN_ID=31337
|
|
11
|
+
ANVIL_FORK_URL=
|
|
12
|
+
ANVIL_SILENT=true
|
|
13
|
+
|
|
14
|
+
# Live Sepolia QA
|
|
15
|
+
# Never commit a real value here. Set it only in your shell or CI secret store.
|
|
16
|
+
FJORD_PRIVATE_KEY=
|
|
17
|
+
SEPOLIA_RPC_URL=
|
|
18
|
+
|
|
19
|
+
# Explicit live-mutation gates
|
|
20
|
+
FJORD_RUN_TRANSACTIONS=false
|
|
21
|
+
FJORD_MUTATE_STATE=false
|
|
22
|
+
FJORD_PUBLISH_SALES=false
|
|
23
|
+
FJORD_ADMIN_MUTATE=false
|
|
24
|
+
FJORD_QA_RUN_ID=
|
|
25
|
+
FJORD_RESUME_DRAFT_NAME=
|
|
26
|
+
FJORD_PROJECT_TOKEN_ADDRESS=
|
|
27
|
+
FJORD_EXPLORE_SALE_CREATE=false
|
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Web3 Tester
|
|
2
|
+
|
|
3
|
+
`@marigoldlabs/web3-tester` is a Playwright, Anvil, and Viem harness for deterministic Web3 end-to-end tests without a browser wallet extension.
|
|
4
|
+
|
|
5
|
+
The core rule is simple: test the dApp, not MetaMask's popup UI. The harness injects a standards-shaped EIP-1193 provider before application scripts run, bridges wallet RPC calls back to the Playwright process, and sends chain operations directly to Anvil or to an explicit live-chain signer.
|
|
6
|
+
|
|
7
|
+
## What It Provides
|
|
8
|
+
|
|
9
|
+
- Playwright fixtures that start one Anvil node per worker for parallel-safe local EVM tests.
|
|
10
|
+
- Automatic `evm_snapshot` and `evm_revert` around every wallet test.
|
|
11
|
+
- A programmable `MockWalletController` for approval, rejection, disconnects, account changes, and network-change events.
|
|
12
|
+
- EIP-6963 provider announcements for wallet selector testing.
|
|
13
|
+
- Viem-backed chain helpers for impersonation, balance setup, time travel, and block mining.
|
|
14
|
+
- Optional live-chain fixtures for controlled Sepolia QA with a runtime-only private key.
|
|
15
|
+
- Fjord v4 QA specs and reports that document the current state of `https://v4.fjordfoundry.com`.
|
|
16
|
+
|
|
17
|
+
## Install In A Consumer App
|
|
18
|
+
|
|
19
|
+
From the Fjord v4 package, install this repo as a dev dependency:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install --save-dev github:AndyMarigoldLabs/web3-tester
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then import the local deterministic fixture:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { expect, test } from '@marigoldlabs/web3-tester/fixtures';
|
|
29
|
+
|
|
30
|
+
test('user can submit a wallet transaction', async ({ page, wallet }) => {
|
|
31
|
+
await page.goto('/swap');
|
|
32
|
+
|
|
33
|
+
await page.getByRole('button', { name: /connect wallet/i }).click();
|
|
34
|
+
await page.getByText('Mock Wallet').click();
|
|
35
|
+
|
|
36
|
+
await page.getByRole('button', { name: /swap/i }).click();
|
|
37
|
+
await expect(page.getByText(/success/i)).toBeVisible();
|
|
38
|
+
|
|
39
|
+
expect(wallet.primaryAccount).toMatch(/^0x/);
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For live Sepolia tests, import the live fixture instead:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { expect, test } from '@marigoldlabs/web3-tester/live-fixtures';
|
|
47
|
+
|
|
48
|
+
test('signs in through SIWE on Sepolia', async ({ page, wallet }) => {
|
|
49
|
+
await page.goto('/');
|
|
50
|
+
await page.getByRole('button', { name: /connect/i }).click();
|
|
51
|
+
await expect(page.getByText(wallet.primaryAccount.slice(0, 6))).toBeVisible();
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Local Development
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install
|
|
59
|
+
npx playwright install chromium
|
|
60
|
+
npm run typecheck
|
|
61
|
+
npm run build
|
|
62
|
+
npm test
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Foundry's `anvil` executable must be available on `PATH`, or set `ANVIL_EXECUTABLE`.
|
|
66
|
+
|
|
67
|
+
This repo also auto-detects local Foundry binaries at:
|
|
68
|
+
|
|
69
|
+
- `tools/foundry/anvil`
|
|
70
|
+
- `tools/foundry/anvil.exe`
|
|
71
|
+
|
|
72
|
+
## Docker Anvil Runtime
|
|
73
|
+
|
|
74
|
+
If Docker Desktop is running, Anvil can be launched in a per-worker Foundry container:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
ANVIL_RUNTIME=docker npm test
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
PowerShell:
|
|
81
|
+
|
|
82
|
+
```powershell
|
|
83
|
+
$env:ANVIL_RUNTIME = 'docker'
|
|
84
|
+
npm test
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Each Playwright worker maps a unique host port to the container's Anvil RPC port, so tests remain parallel-safe.
|
|
88
|
+
|
|
89
|
+
## Environment Variables
|
|
90
|
+
|
|
91
|
+
Copy `.env.example` for local reference. Do not commit real private keys.
|
|
92
|
+
|
|
93
|
+
| Variable | Default | Purpose |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| `DAPP_URL` | `https://v4.fjordfoundry.com` | Playwright base URL for app tests. |
|
|
96
|
+
| `ANVIL_EXECUTABLE` | `anvil` | Path to the Anvil binary. |
|
|
97
|
+
| `ANVIL_RUNTIME` | `binary` | Set to `docker` to run Anvil through Docker Desktop. |
|
|
98
|
+
| `ANVIL_DOCKER_IMAGE` | `ghcr.io/foundry-rs/foundry:latest` | Docker image used when `ANVIL_RUNTIME=docker`. |
|
|
99
|
+
| `ANVIL_HOST` | `127.0.0.1` | Host for worker Anvil RPC endpoints. |
|
|
100
|
+
| `ANVIL_PORT` | `8545` | Base port. Playwright worker index is added for isolation. |
|
|
101
|
+
| `ANVIL_CHAIN_ID` | `31337` | Chain ID exposed by local Anvil and the injected provider. |
|
|
102
|
+
| `ANVIL_FORK_URL` | unset | Optional fork RPC URL. |
|
|
103
|
+
| `ANVIL_SILENT` | `true` | Set to `false` to stream Anvil logs. |
|
|
104
|
+
| `FJORD_PRIVATE_KEY` | unset | Runtime-only private key for live Sepolia QA. |
|
|
105
|
+
| `SEPOLIA_RPC_URL` | Viem default | Optional Sepolia RPC URL for live tests. |
|
|
106
|
+
| `FJORD_RUN_TRANSACTIONS` | unset | Must be `true` to run live transaction-spending tests. |
|
|
107
|
+
| `FJORD_MUTATE_STATE` | unset | Must be `true` to deploy QA tokens or create sale drafts. |
|
|
108
|
+
| `FJORD_PUBLISH_SALES` | unset | Must be `true` to attempt live sale publishing. |
|
|
109
|
+
| `FJORD_ADMIN_MUTATE` | unset | Must be `true` to attempt admin mutation tests. |
|
|
110
|
+
|
|
111
|
+
## Package Surface
|
|
112
|
+
|
|
113
|
+
The installable package exports:
|
|
114
|
+
|
|
115
|
+
- `@marigoldlabs/web3-tester`
|
|
116
|
+
- `@marigoldlabs/web3-tester/fixtures`
|
|
117
|
+
- `@marigoldlabs/web3-tester/live-fixtures`
|
|
118
|
+
- `@marigoldlabs/web3-tester/anvil`
|
|
119
|
+
- `@marigoldlabs/web3-tester/mock-wallet-controller`
|
|
120
|
+
- `@marigoldlabs/web3-tester/private-key-rpc-client`
|
|
121
|
+
|
|
122
|
+
Full API notes are in [docs/API.md](docs/API.md).
|
|
123
|
+
|
|
124
|
+
## Chain Control
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
await chain.impersonateAccount('0x0000000000000000000000000000000000000001');
|
|
128
|
+
await chain.setBalance(wallet.primaryAccount, 10_000n * 10n ** 18n);
|
|
129
|
+
await chain.fastForward(7 * 24 * 60 * 60);
|
|
130
|
+
await chain.mine(3);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Wallet Control
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await wallet.simulateRejection('eth_sendTransaction');
|
|
137
|
+
await wallet.disconnect();
|
|
138
|
+
await wallet.reconnect();
|
|
139
|
+
await wallet.setAccounts(['0x0000000000000000000000000000000000000001']);
|
|
140
|
+
await wallet.switchNetwork(11155111);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Multiple Wallet Selectors
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
test.use({
|
|
147
|
+
walletOptions: {
|
|
148
|
+
providerInfo: { name: 'Mock MetaMask', rdns: 'io.metamask' },
|
|
149
|
+
additionalProviders: [
|
|
150
|
+
{ name: 'Mock Rabby', rdns: 'io.rabby' },
|
|
151
|
+
{ name: 'Mock Rainbow', rdns: 'me.rainbow' },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Repository Layout
|
|
158
|
+
|
|
159
|
+
| Path | Purpose |
|
|
160
|
+
| --- | --- |
|
|
161
|
+
| `src/` | Reusable package source. |
|
|
162
|
+
| `tests/provider-injection.spec.ts` | Harness self-tests. |
|
|
163
|
+
| `tests/fjord*.spec.ts` | Fjord v4 public, live, and mutation QA specs. |
|
|
164
|
+
| `docs/` | Dependency, API, architecture, and Fjord QA documentation. |
|
|
165
|
+
| `examples/` | Copyable consumer-app snippets. |
|
|
166
|
+
| `reports/` | Current Fjord v4 QA reports. |
|
|
167
|
+
|
|
168
|
+
## Safety Model
|
|
169
|
+
|
|
170
|
+
- Local tests use deterministic Anvil accounts only.
|
|
171
|
+
- Live tests require explicit environment variables and never store private keys in source.
|
|
172
|
+
- Mutation tests are skipped unless their opt-in flag is set.
|
|
173
|
+
- Published reports redact secrets and record transaction hashes only when useful for auditability.
|
|
174
|
+
|
|
175
|
+
## More Documentation
|
|
176
|
+
|
|
177
|
+
- [docs/API.md](docs/API.md)
|
|
178
|
+
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
|
179
|
+
- [docs/CONSUMING_FROM_FJORD.md](docs/CONSUMING_FROM_FJORD.md)
|
|
180
|
+
- [docs/FJORD_LIVE_QA.md](docs/FJORD_LIVE_QA.md)
|
|
181
|
+
- [docs/RELEASE_CHECKLIST.md](docs/RELEASE_CHECKLIST.md)
|
package/dist/anvil.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type Address, type Chain, type Hex, type PublicActions, type TestClient, type Transport, type WalletActions } from 'viem';
|
|
2
|
+
import type { JsonRpcRequest, RpcClient } from './types.js';
|
|
3
|
+
export type AnvilOptions = {
|
|
4
|
+
runtime?: 'binary' | 'docker';
|
|
5
|
+
executable?: string;
|
|
6
|
+
dockerImage?: string;
|
|
7
|
+
containerName?: string;
|
|
8
|
+
host?: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
chainId?: number;
|
|
11
|
+
accounts?: number;
|
|
12
|
+
balance?: number;
|
|
13
|
+
mnemonic?: string;
|
|
14
|
+
blockTime?: number;
|
|
15
|
+
forkUrl?: string;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
silent?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type AnvilSnapshotId = Hex;
|
|
20
|
+
export type AnvilViemClient = TestClient<'anvil', Transport, Chain> & PublicActions<Transport, Chain> & WalletActions<Chain>;
|
|
21
|
+
export declare class AnvilInstance {
|
|
22
|
+
private readonly process;
|
|
23
|
+
private readonly containerName?;
|
|
24
|
+
readonly host: string;
|
|
25
|
+
readonly port: number;
|
|
26
|
+
readonly chainId: number;
|
|
27
|
+
readonly rpcUrl: string;
|
|
28
|
+
private constructor();
|
|
29
|
+
static start(options?: AnvilOptions): Promise<AnvilInstance>;
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
private isReady;
|
|
32
|
+
}
|
|
33
|
+
export type ChainControllerOptions = {
|
|
34
|
+
rpcUrl: string;
|
|
35
|
+
chainId?: number;
|
|
36
|
+
};
|
|
37
|
+
export declare class ChainController implements RpcClient {
|
|
38
|
+
readonly rpcUrl: string;
|
|
39
|
+
readonly chainId: number;
|
|
40
|
+
readonly client: AnvilViemClient;
|
|
41
|
+
constructor(options: ChainControllerOptions);
|
|
42
|
+
snapshot(): Promise<AnvilSnapshotId>;
|
|
43
|
+
revert(id: AnvilSnapshotId): Promise<void>;
|
|
44
|
+
accounts(): Promise<Address[]>;
|
|
45
|
+
request(request: JsonRpcRequest): Promise<unknown>;
|
|
46
|
+
impersonateAccount(address: Address): Promise<void>;
|
|
47
|
+
stopImpersonatingAccount(address: Address): Promise<void>;
|
|
48
|
+
setBalance(address: Address, value: bigint): Promise<void>;
|
|
49
|
+
fastForward(seconds: number): Promise<void>;
|
|
50
|
+
mine(blocks?: number): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=anvil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anvil.d.ts","sourceRoot":"","sources":["../src/anvil.ts"],"names":[],"mappings":"AAGA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,KAAK,EACV,KAAK,GAAG,EACR,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC;AAClC,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,GACjE,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,GAC/B,aAAa,CAAC,KAAK,CAAC,CAAC;AAwDvB,qBAAa,aAAa;IAOtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAExB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IARjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,OAAO;WAWM,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IA0FhE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAab,OAAO;CAkBtB;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,eAAgB,YAAW,SAAS;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;gBAErB,OAAO,EAAE,sBAAsB;IAYrC,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;IAIpC,MAAM,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI9B,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAIlD,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,IAAI,CAAC,MAAM,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAGtC"}
|
package/dist/anvil.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
import { createTestClient, http, publicActions, walletActions, } from 'viem';
|
|
4
|
+
import { foundry } from 'viem/chains';
|
|
5
|
+
const DEFAULT_MNEMONIC = 'test test test test test test test test test test test junk';
|
|
6
|
+
const DEFAULT_FOUNDRY_DOCKER_IMAGE = 'ghcr.io/foundry-rs/foundry:latest';
|
|
7
|
+
const CONTAINER_ANVIL_PORT = 8545;
|
|
8
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
const buildAnvilArgs = (options, host, port, chainId) => {
|
|
10
|
+
const args = [
|
|
11
|
+
'--host',
|
|
12
|
+
host,
|
|
13
|
+
'--port',
|
|
14
|
+
String(port),
|
|
15
|
+
'--chain-id',
|
|
16
|
+
String(chainId),
|
|
17
|
+
'--mnemonic',
|
|
18
|
+
options.mnemonic ?? DEFAULT_MNEMONIC,
|
|
19
|
+
'--accounts',
|
|
20
|
+
String(options.accounts ?? 10),
|
|
21
|
+
'--balance',
|
|
22
|
+
String(options.balance ?? 10_000),
|
|
23
|
+
];
|
|
24
|
+
if (options.blockTime !== undefined) {
|
|
25
|
+
args.push('--block-time', String(options.blockTime));
|
|
26
|
+
}
|
|
27
|
+
if (options.forkUrl) {
|
|
28
|
+
args.push('--fork-url', options.forkUrl);
|
|
29
|
+
}
|
|
30
|
+
return args;
|
|
31
|
+
};
|
|
32
|
+
const stopDockerContainer = async (containerName) => {
|
|
33
|
+
const stopper = spawn('docker', ['stop', containerName], {
|
|
34
|
+
stdio: 'ignore',
|
|
35
|
+
windowsHide: true,
|
|
36
|
+
});
|
|
37
|
+
await Promise.race([
|
|
38
|
+
once(stopper, 'exit').catch(() => undefined),
|
|
39
|
+
once(stopper, 'error').catch(() => undefined),
|
|
40
|
+
sleep(5_000),
|
|
41
|
+
]);
|
|
42
|
+
};
|
|
43
|
+
export class AnvilInstance {
|
|
44
|
+
process;
|
|
45
|
+
containerName;
|
|
46
|
+
host;
|
|
47
|
+
port;
|
|
48
|
+
chainId;
|
|
49
|
+
rpcUrl;
|
|
50
|
+
constructor(process, options, containerName) {
|
|
51
|
+
this.process = process;
|
|
52
|
+
this.containerName = containerName;
|
|
53
|
+
this.host = options.host;
|
|
54
|
+
this.port = options.port;
|
|
55
|
+
this.chainId = options.chainId;
|
|
56
|
+
this.rpcUrl = `http://${this.host}:${this.port}`;
|
|
57
|
+
}
|
|
58
|
+
static async start(options = {}) {
|
|
59
|
+
const host = options.host ?? '127.0.0.1';
|
|
60
|
+
const port = options.port ?? 8545;
|
|
61
|
+
const chainId = options.chainId ?? foundry.id;
|
|
62
|
+
const runtime = options.runtime ?? 'binary';
|
|
63
|
+
const executable = options.executable ?? process.env.ANVIL_EXECUTABLE ?? 'anvil';
|
|
64
|
+
const dockerImage = options.dockerImage ?? DEFAULT_FOUNDRY_DOCKER_IMAGE;
|
|
65
|
+
const containerName = runtime === 'docker'
|
|
66
|
+
? (options.containerName ?? `invisible-wallet-anvil-${port}-${Date.now()}`)
|
|
67
|
+
: undefined;
|
|
68
|
+
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
69
|
+
const command = runtime === 'docker'
|
|
70
|
+
? 'docker'
|
|
71
|
+
: executable;
|
|
72
|
+
const args = runtime === 'docker'
|
|
73
|
+
? [
|
|
74
|
+
'run',
|
|
75
|
+
'--rm',
|
|
76
|
+
'--name',
|
|
77
|
+
containerName,
|
|
78
|
+
'-e',
|
|
79
|
+
'FOUNDRY_DISABLE_NIGHTLY_WARNING=true',
|
|
80
|
+
'-p',
|
|
81
|
+
`${host}:${port}:${CONTAINER_ANVIL_PORT}`,
|
|
82
|
+
'--entrypoint',
|
|
83
|
+
'anvil',
|
|
84
|
+
dockerImage,
|
|
85
|
+
...buildAnvilArgs(options, '0.0.0.0', CONTAINER_ANVIL_PORT, chainId),
|
|
86
|
+
]
|
|
87
|
+
: buildAnvilArgs(options, host, port, chainId);
|
|
88
|
+
const anvilProcess = spawn(command, args, {
|
|
89
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
90
|
+
windowsHide: true,
|
|
91
|
+
});
|
|
92
|
+
const instance = new AnvilInstance(anvilProcess, { host, port, chainId }, containerName);
|
|
93
|
+
const logs = [];
|
|
94
|
+
let spawnError;
|
|
95
|
+
anvilProcess.once('error', (error) => {
|
|
96
|
+
spawnError = error;
|
|
97
|
+
});
|
|
98
|
+
anvilProcess.stdout.on('data', (chunk) => {
|
|
99
|
+
const text = chunk.toString();
|
|
100
|
+
logs.push(text);
|
|
101
|
+
if (!options.silent) {
|
|
102
|
+
process.stdout.write(text);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
anvilProcess.stderr.on('data', (chunk) => {
|
|
106
|
+
const text = chunk.toString();
|
|
107
|
+
logs.push(text);
|
|
108
|
+
if (!options.silent) {
|
|
109
|
+
process.stderr.write(text);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const deadline = Date.now() + timeoutMs;
|
|
113
|
+
while (Date.now() < deadline) {
|
|
114
|
+
if (spawnError) {
|
|
115
|
+
throw new Error(runtime === 'docker'
|
|
116
|
+
? `Failed to start Anvil with Docker image "${dockerImage}". Make sure Docker Desktop is running and the image can be pulled.\n${spawnError.message}`
|
|
117
|
+
: `Failed to start Anvil executable "${executable}". Install Foundry or set ANVIL_EXECUTABLE to the Anvil binary path.\n${spawnError.message}`);
|
|
118
|
+
}
|
|
119
|
+
if (anvilProcess.exitCode !== null) {
|
|
120
|
+
throw new Error(`Anvil exited before it became ready with code ${anvilProcess.exitCode}.\n${logs.join('')}`);
|
|
121
|
+
}
|
|
122
|
+
if (await instance.isReady()) {
|
|
123
|
+
return instance;
|
|
124
|
+
}
|
|
125
|
+
await sleep(100);
|
|
126
|
+
}
|
|
127
|
+
await instance.stop();
|
|
128
|
+
throw new Error(`Timed out waiting for Anvil at ${instance.rpcUrl}.\n${logs.join('')}`);
|
|
129
|
+
}
|
|
130
|
+
async stop() {
|
|
131
|
+
if (this.containerName) {
|
|
132
|
+
await stopDockerContainer(this.containerName);
|
|
133
|
+
}
|
|
134
|
+
if (this.process.exitCode !== null) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.process.kill();
|
|
138
|
+
await once(this.process, 'exit').catch(() => undefined);
|
|
139
|
+
}
|
|
140
|
+
async isReady() {
|
|
141
|
+
try {
|
|
142
|
+
const response = await fetch(this.rpcUrl, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: { 'content-type': 'application/json' },
|
|
145
|
+
body: JSON.stringify({
|
|
146
|
+
id: 1,
|
|
147
|
+
jsonrpc: '2.0',
|
|
148
|
+
method: 'web3_clientVersion',
|
|
149
|
+
params: [],
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
152
|
+
return response.ok;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export class ChainController {
|
|
160
|
+
rpcUrl;
|
|
161
|
+
chainId;
|
|
162
|
+
client;
|
|
163
|
+
constructor(options) {
|
|
164
|
+
this.rpcUrl = options.rpcUrl;
|
|
165
|
+
this.chainId = options.chainId ?? foundry.id;
|
|
166
|
+
this.client = createTestClient({
|
|
167
|
+
chain: { ...foundry, id: this.chainId },
|
|
168
|
+
mode: 'anvil',
|
|
169
|
+
transport: http(this.rpcUrl),
|
|
170
|
+
})
|
|
171
|
+
.extend(publicActions)
|
|
172
|
+
.extend(walletActions);
|
|
173
|
+
}
|
|
174
|
+
async snapshot() {
|
|
175
|
+
return this.client.snapshot();
|
|
176
|
+
}
|
|
177
|
+
async revert(id) {
|
|
178
|
+
await this.client.revert({ id });
|
|
179
|
+
}
|
|
180
|
+
async accounts() {
|
|
181
|
+
return this.client.getAddresses();
|
|
182
|
+
}
|
|
183
|
+
async request(request) {
|
|
184
|
+
return this.client.request(request);
|
|
185
|
+
}
|
|
186
|
+
async impersonateAccount(address) {
|
|
187
|
+
await this.client.impersonateAccount({ address });
|
|
188
|
+
}
|
|
189
|
+
async stopImpersonatingAccount(address) {
|
|
190
|
+
await this.client.stopImpersonatingAccount({ address });
|
|
191
|
+
}
|
|
192
|
+
async setBalance(address, value) {
|
|
193
|
+
await this.client.setBalance({ address, value });
|
|
194
|
+
}
|
|
195
|
+
async fastForward(seconds) {
|
|
196
|
+
await this.client.increaseTime({ seconds });
|
|
197
|
+
await this.mine(1);
|
|
198
|
+
}
|
|
199
|
+
async mine(blocks = 1) {
|
|
200
|
+
await this.client.mine({ blocks });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=anvil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anvil.js","sourceRoot":"","sources":["../src/anvil.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { JsonRpcErrorPayload, ProviderRpcErrorLike } from './types.js';
|
|
2
|
+
export declare const providerError: (code: number, message: string, data?: unknown) => ProviderRpcErrorLike;
|
|
3
|
+
export declare const serializeRpcError: (error: unknown) => JsonRpcErrorPayload;
|
|
4
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE5E,eAAO,MAAM,aAAa,SAClB,MAAM,WACH,MAAM,SACR,OAAO,KACb,oBAOF,CAAC;AAEF,eAAO,MAAM,iBAAiB,UAAW,OAAO,KAAG,mBAiBlD,CAAC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const providerError = (code, message, data) => {
|
|
2
|
+
const error = new Error(message);
|
|
3
|
+
error.code = code;
|
|
4
|
+
if (data !== undefined) {
|
|
5
|
+
error.data = data;
|
|
6
|
+
}
|
|
7
|
+
return error;
|
|
8
|
+
};
|
|
9
|
+
export const serializeRpcError = (error) => {
|
|
10
|
+
if (typeof error === 'object' && error !== null) {
|
|
11
|
+
const maybeError = error;
|
|
12
|
+
return {
|
|
13
|
+
code: typeof maybeError.code === 'number' ? maybeError.code : -32603,
|
|
14
|
+
message: typeof maybeError.message === 'string'
|
|
15
|
+
? maybeError.message
|
|
16
|
+
: 'Internal JSON-RPC error',
|
|
17
|
+
data: maybeError.data,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
code: -32603,
|
|
22
|
+
message: typeof error === 'string' ? error : 'Internal JSON-RPC error',
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,IAAY,EACZ,OAAe,EACf,IAAc,EACQ,EAAE;IACxB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,CAAyB,CAAC;IACzD,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAuB,EAAE;IACvE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,UAAU,GAAG,KAAsC,CAAC;QAC1D,OAAO;YACL,IAAI,EAAE,OAAO,UAAU,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK;YACpE,OAAO,EACL,OAAO,UAAU,CAAC,OAAO,KAAK,QAAQ;gBACpC,CAAC,CAAC,UAAU,CAAC,OAAO;gBACpB,CAAC,CAAC,yBAAyB;YAC/B,IAAI,EAAE,UAAU,CAAC,IAAI;SACtB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,CAAC,KAAK;QACZ,OAAO,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,yBAAyB;KACvE,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AnvilInstance, ChainController, type AnvilOptions } from './anvil.js';
|
|
2
|
+
import { MockWalletController, type MockWalletControllerOptions } from './mock-wallet-controller.js';
|
|
3
|
+
export type Web3Fixtures = {
|
|
4
|
+
wallet: MockWalletController;
|
|
5
|
+
walletOptions: MockWalletFixtureOptions;
|
|
6
|
+
};
|
|
7
|
+
export type Web3WorkerFixtures = {
|
|
8
|
+
anvil: AnvilInstance;
|
|
9
|
+
chain: ChainController;
|
|
10
|
+
anvilOptions: AnvilOptions;
|
|
11
|
+
};
|
|
12
|
+
export type MockWalletFixtureOptions = Omit<Partial<MockWalletControllerOptions>, 'accounts' | 'chainId'>;
|
|
13
|
+
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & Web3Fixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & Web3WorkerFixtures>;
|
|
14
|
+
export { expect } from '@playwright/test';
|
|
15
|
+
//# sourceMappingURL=fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,KAAK,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAErG,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,aAAa,EAAE,wBAAwB,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,OAAO,CAAC,2BAA2B,CAAC,EACpC,UAAU,GAAG,SAAS,CACvB,CAAC;AAkBF,eAAO,MAAM,IAAI,iRA2Ef,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/fixtures.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { test as base } from '@playwright/test';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { foundry } from 'viem/chains';
|
|
5
|
+
import { AnvilInstance, ChainController } from './anvil.js';
|
|
6
|
+
import { MockWalletController } from './mock-wallet-controller.js';
|
|
7
|
+
const workerPort = (workerIndex) => Number(process.env.ANVIL_PORT ?? 8545) + workerIndex;
|
|
8
|
+
const resolveAnvilExecutable = () => {
|
|
9
|
+
if (process.env.ANVIL_EXECUTABLE) {
|
|
10
|
+
return process.env.ANVIL_EXECUTABLE;
|
|
11
|
+
}
|
|
12
|
+
const localCandidates = [
|
|
13
|
+
join(process.cwd(), 'tools', 'foundry', 'anvil.exe'),
|
|
14
|
+
join(process.cwd(), 'tools', 'foundry', 'anvil'),
|
|
15
|
+
];
|
|
16
|
+
return localCandidates.find((candidate) => existsSync(candidate));
|
|
17
|
+
};
|
|
18
|
+
export const test = base.extend({
|
|
19
|
+
walletOptions: [
|
|
20
|
+
async ({}, use) => {
|
|
21
|
+
await use({});
|
|
22
|
+
},
|
|
23
|
+
{ option: true },
|
|
24
|
+
],
|
|
25
|
+
anvilOptions: [
|
|
26
|
+
async ({}, use) => {
|
|
27
|
+
await use({
|
|
28
|
+
runtime: process.env.ANVIL_RUNTIME === 'docker' ? 'docker' : 'binary',
|
|
29
|
+
executable: resolveAnvilExecutable(),
|
|
30
|
+
dockerImage: process.env.ANVIL_DOCKER_IMAGE,
|
|
31
|
+
host: process.env.ANVIL_HOST ?? '127.0.0.1',
|
|
32
|
+
chainId: Number(process.env.ANVIL_CHAIN_ID ?? foundry.id),
|
|
33
|
+
forkUrl: process.env.ANVIL_FORK_URL,
|
|
34
|
+
silent: process.env.ANVIL_SILENT !== 'false',
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
{ scope: 'worker', option: true },
|
|
38
|
+
],
|
|
39
|
+
anvil: [
|
|
40
|
+
async ({ anvilOptions }, use, workerInfo) => {
|
|
41
|
+
const anvil = await AnvilInstance.start({
|
|
42
|
+
...anvilOptions,
|
|
43
|
+
port: anvilOptions.port ?? workerPort(workerInfo.workerIndex),
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
await use(anvil);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await anvil.stop();
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{ scope: 'worker' },
|
|
53
|
+
],
|
|
54
|
+
chain: [
|
|
55
|
+
async ({ anvil }, use) => {
|
|
56
|
+
await use(new ChainController({
|
|
57
|
+
rpcUrl: anvil.rpcUrl,
|
|
58
|
+
chainId: anvil.chainId,
|
|
59
|
+
}));
|
|
60
|
+
},
|
|
61
|
+
{ scope: 'worker' },
|
|
62
|
+
],
|
|
63
|
+
wallet: async ({ page, chain, walletOptions: customWalletOptions }, use) => {
|
|
64
|
+
const snapshotId = await chain.snapshot();
|
|
65
|
+
const [defaultAccount] = await chain.accounts();
|
|
66
|
+
if (!defaultAccount) {
|
|
67
|
+
throw new Error('Anvil did not expose any default accounts.');
|
|
68
|
+
}
|
|
69
|
+
const walletOptions = {
|
|
70
|
+
accounts: [defaultAccount],
|
|
71
|
+
chainId: chain.chainId,
|
|
72
|
+
autoApprove: true,
|
|
73
|
+
connected: true,
|
|
74
|
+
...customWalletOptions,
|
|
75
|
+
};
|
|
76
|
+
const wallet = new MockWalletController(page, chain, walletOptions);
|
|
77
|
+
await wallet.injectMockProvider();
|
|
78
|
+
try {
|
|
79
|
+
await use(wallet);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await chain.revert(snapshotId);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
export { expect } from '@playwright/test';
|
|
87
|
+
//# sourceMappingURL=fixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAqB,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAoC,MAAM,6BAA6B,CAAC;AAkBrG,MAAM,UAAU,GAAG,CAAC,WAAmB,EAAU,EAAE,CACjD,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,WAAW,CAAC;AAEvD,MAAM,sBAAsB,GAAG,GAAuB,EAAE;IACtD,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACtC,CAAC;IAED,MAAM,eAAe,GAAG;QACtB,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC;QACpD,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC;KACjD,CAAC;IAEF,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;AACpE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAmC;IAChE,aAAa,EAAE;QACb,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE;YAChB,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,CAAC;QACD,EAAE,MAAM,EAAE,IAAI,EAAE;KACjB;IAED,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE;YAChB,MAAM,GAAG,CAAC;gBACR,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;gBACrE,UAAU,EAAE,sBAAsB,EAAE;gBACpC,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB;gBAC3C,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,WAAW;gBAC3C,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC,EAAE,CAAC;gBACzD,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc;gBACnC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,OAAO;aAC7C,CAAC,CAAC;QACL,CAAC;QACD,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE;KAClC;IAED,KAAK,EAAE;QACL,KAAK,EAAE,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE;YAC1C,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC;gBACtC,GAAG,YAAY;gBACf,IAAI,EAAE,YAAY,CAAC,IAAI,IAAI,UAAU,CAAC,UAAU,CAAC,WAAW,CAAC;aAC9D,CAAC,CAAC;YAEH,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;oBAAS,CAAC;gBACT,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;QACD,EAAE,KAAK,EAAE,QAAQ,EAAE;KACpB;IAED,KAAK,EAAE;QACL,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;YACvB,MAAM,GAAG,CACP,IAAI,eAAe,CAAC;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,OAAO,EAAE,KAAK,CAAC,OAAO;aACvB,CAAC,CACH,CAAC;QACJ,CAAC;QACD,EAAE,KAAK,EAAE,QAAQ,EAAE;KACpB;IAED,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,EAAE,GAAG,EAAE,EAAE;QACzE,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1C,MAAM,CAAC,cAAc,CAAC,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChD,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAChE,CAAC;QAED,MAAM,aAAa,GAAgC;YACjD,QAAQ,EAAE,CAAC,cAAc,CAAC;YAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,WAAW,EAAE,IAAI;YACjB,SAAS,EAAE,IAAI;YACf,GAAG,mBAAmB;SACvB,CAAC;QAEF,MAAM,MAAM,GAAG,IAAI,oBAAoB,CAAC,IAAI,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;QACpE,MAAM,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAElC,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;CACF,CAAC,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AnvilInstance, ChainController } from './anvil.js';
|
|
2
|
+
export { test, expect } from './fixtures.js';
|
|
3
|
+
export { MockWalletController } from './mock-wallet-controller.js';
|
|
4
|
+
export { PrivateKeyRpcClient } from './private-key-rpc-client.js';
|
|
5
|
+
export type { AnvilOptions, AnvilSnapshotId, AnvilViemClient, ChainControllerOptions, } from './anvil.js';
|
|
6
|
+
export type { MockWalletControllerOptions, RejectionRule, } from './mock-wallet-controller.js';
|
|
7
|
+
export type { JsonRpcRequest, MockWalletConfig, WalletProviderInfo, } from './types.js';
|
|
8
|
+
export type { PrivateKeyRpcClientOptions } from './private-key-rpc-client.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EACV,YAAY,EACZ,eAAe,EACf,eAAe,EACf,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,2BAA2B,EAC3B,aAAa,GACd,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AnvilInstance, ChainController } from './anvil.js';
|
|
2
|
+
export { test, expect } from './fixtures.js';
|
|
3
|
+
export { MockWalletController } from './mock-wallet-controller.js';
|
|
4
|
+
export { PrivateKeyRpcClient } from './private-key-rpc-client.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC"}
|