@marigoldlabs/web3-tester 0.1.1 → 0.4.1
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 +26 -17
- package/LICENSE +21 -0
- package/README.md +167 -41
- package/dist/anvil.d.ts +90 -2
- package/dist/anvil.d.ts.map +1 -1
- package/dist/anvil.js +215 -13
- package/dist/anvil.js.map +1 -1
- package/dist/contracts/test-erc20.d.ts +227 -0
- package/dist/contracts/test-erc20.d.ts.map +1 -0
- package/dist/contracts/test-erc20.js +8 -0
- package/dist/contracts/test-erc20.js.map +1 -0
- package/dist/erc20.d.ts +38 -0
- package/dist/erc20.d.ts.map +1 -0
- package/dist/erc20.js +229 -0
- package/dist/erc20.js.map +1 -0
- package/dist/fixtures.d.ts +44 -2
- package/dist/fixtures.d.ts.map +1 -1
- package/dist/fixtures.js +162 -17
- package/dist/fixtures.js.map +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/injected-provider.d.ts.map +1 -1
- package/dist/injected-provider.js +142 -79
- package/dist/injected-provider.js.map +1 -1
- package/dist/live-fixtures.d.ts +32 -3
- package/dist/live-fixtures.d.ts.map +1 -1
- package/dist/live-fixtures.js +64 -27
- package/dist/live-fixtures.js.map +1 -1
- package/dist/matchers.d.ts +90 -0
- package/dist/matchers.d.ts.map +1 -0
- package/dist/matchers.js +268 -0
- package/dist/matchers.js.map +1 -0
- package/dist/metamask-extension.d.ts +34 -0
- package/dist/metamask-extension.d.ts.map +1 -0
- package/dist/metamask-extension.js +97 -0
- package/dist/metamask-extension.js.map +1 -0
- package/dist/mock-wallet-controller.d.ts +205 -3
- package/dist/mock-wallet-controller.d.ts.map +1 -1
- package/dist/mock-wallet-controller.js +843 -46
- package/dist/mock-wallet-controller.js.map +1 -1
- package/dist/private-key-rpc-client.d.ts +1730 -0
- package/dist/private-key-rpc-client.d.ts.map +1 -1
- package/dist/private-key-rpc-client.js +105 -12
- package/dist/private-key-rpc-client.js.map +1 -1
- package/dist/real-wallet-cache.d.ts +65 -0
- package/dist/real-wallet-cache.d.ts.map +1 -0
- package/dist/real-wallet-cache.js +245 -0
- package/dist/real-wallet-cache.js.map +1 -0
- package/dist/real-wallet-fixtures.d.ts +52 -0
- package/dist/real-wallet-fixtures.d.ts.map +1 -0
- package/dist/real-wallet-fixtures.js +73 -0
- package/dist/real-wallet-fixtures.js.map +1 -0
- package/dist/real-wallet-setup.d.ts +4 -0
- package/dist/real-wallet-setup.d.ts.map +1 -0
- package/dist/real-wallet-setup.js +5 -0
- package/dist/real-wallet-setup.js.map +1 -0
- package/dist/real-wallet.d.ts +124 -15
- package/dist/real-wallet.d.ts.map +1 -1
- package/dist/real-wallet.js +1682 -138
- package/dist/real-wallet.js.map +1 -1
- package/dist/transactions.d.ts +118 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/transactions.js +207 -0
- package/dist/transactions.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/walletconnect.d.ts +206 -0
- package/dist/walletconnect.d.ts.map +1 -0
- package/dist/walletconnect.js +359 -0
- package/dist/walletconnect.js.map +1 -0
- package/examples/live-sepolia.spec.ts +21 -2
- package/examples/local-wallet.spec.ts +1 -1
- package/package.json +62 -6
- package/docs/API.md +0 -223
- package/docs/ARCHITECTURE.md +0 -81
- package/docs/CONSUMING_FROM_FJORD.md +0 -123
- package/docs/FJORD_LIVE_QA.md +0 -87
- package/docs/RELEASE_CHECKLIST.md +0 -55
package/.env.example
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
|
-
#
|
|
2
|
-
DAPP_URL=https://v4.fjordfoundry.com
|
|
3
|
-
|
|
4
|
-
# Local Anvil
|
|
1
|
+
# --- Library: local Anvil (used by `npm test`) ---
|
|
5
2
|
ANVIL_RUNTIME=binary
|
|
6
3
|
ANVIL_EXECUTABLE=anvil
|
|
7
4
|
ANVIL_DOCKER_IMAGE=ghcr.io/foundry-rs/foundry:latest
|
|
8
5
|
ANVIL_HOST=127.0.0.1
|
|
9
|
-
|
|
6
|
+
# Anvil refuses to bind beyond loopback (its admin RPC is unauthenticated)
|
|
7
|
+
# unless this is explicitly set to true.
|
|
8
|
+
ANVIL_ALLOW_NON_LOOPBACK=false
|
|
9
|
+
# Worker base port; worker index is added for isolation. Defaults to 8645 so
|
|
10
|
+
# it never collides with a developer-run node on 8545.
|
|
11
|
+
ANVIL_PORT=8645
|
|
10
12
|
ANVIL_CHAIN_ID=31337
|
|
11
13
|
ANVIL_FORK_URL=
|
|
12
14
|
ANVIL_SILENT=true
|
|
13
15
|
|
|
14
|
-
#
|
|
16
|
+
# --- Library: live-chain fixtures ---
|
|
15
17
|
# Never commit a real value here. Set it only in your shell or CI secret store.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
WEB3_TESTER_PRIVATE_KEY=
|
|
19
|
+
WEB3_TESTER_RPC_URL=
|
|
20
|
+
|
|
21
|
+
# --- Real-wallet (MetaMask extension) mode ---
|
|
22
|
+
WEB3_TESTER_METAMASK_VERSION=
|
|
23
|
+
WEB3_TESTER_REAL_WALLET_EXTENSION_PATH=
|
|
24
|
+
WEB3_TESTER_REAL_WALLET_PROFILE_DIR=
|
|
25
|
+
WEB3_TESTER_REAL_WALLET_PASSWORD=
|
|
26
|
+
WEB3_TESTER_REAL_WALLET_SECRET_RECOVERY_PHRASE=
|
|
27
|
+
# Required choice for real-wallet runs: true or false (no default). Headed
|
|
28
|
+
# (false) is the fully validated mode; headless needs the full Chromium
|
|
29
|
+
# binary (npx playwright install chromium).
|
|
30
|
+
WEB3_TESTER_REAL_WALLET_HEADLESS=
|
|
31
|
+
# Set true to run the opt-in real-MetaMask smoke suite.
|
|
32
|
+
WEB3_TESTER_REAL_WALLET_SMOKE=
|
|
18
33
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
34
|
+
# --- WalletConnect simulation (opt-in relay suite) ---
|
|
35
|
+
# Reown project id; needed only for the './walletconnect' relay tests.
|
|
36
|
+
WEB3_TESTER_WC_PROJECT_ID=
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marigold Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -8,16 +8,16 @@ The injected fixtures test dApp behavior with a programmable EIP-1193 provider.
|
|
|
8
8
|
|
|
9
9
|
- Playwright fixtures that start one Anvil node per worker for parallel-safe local EVM tests.
|
|
10
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.
|
|
11
|
+
- A programmable `MockWalletController` for approval, rejection, pending-approval holds, disconnects, account changes, and network-change events, with transaction recording (`sentTransactions`, `waitForNextTransaction`).
|
|
12
|
+
- EIP-6963 provider announcements (one distinct provider object per wallet) for wallet selector testing.
|
|
13
13
|
- Viem-backed chain helpers for impersonation, balance setup, time travel, and block mining.
|
|
14
|
-
- Optional live-chain fixtures for controlled
|
|
15
|
-
-
|
|
16
|
-
-
|
|
14
|
+
- Optional live-chain fixtures for controlled testnet QA with a runtime-only private key (`createLiveFixtures` for custom chains/env names).
|
|
15
|
+
- Optional WalletConnect/AppKit simulation (`@marigoldlabs/web3-tester/walletconnect`): a headless WC v2 wallet peer that pairs with the dapp's QR modal and answers every request through the same wallet gating — needs the optional `@walletconnect/*` peers and a Reown project id.
|
|
16
|
+
- Real MetaMask mode: pinned-version extension download (`prepareMetaMaskExtension`), one-time onboarding into a cached profile with disposable per-test clones (`buildWalletProfile`/`cloneWalletProfile`), Playwright fixtures (`@marigoldlabs/web3-tester/real-wallet-fixtures`), wallet-side network add/switch, dapp connection, signature/transaction confirmation and rejection, and token approval helpers — validated end to end by an opt-in smoke suite against the pinned MetaMask build.
|
|
17
17
|
|
|
18
18
|
## Install In A Consumer App
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Install as a dev dependency:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
npm install --save-dev @marigoldlabs/web3-tester
|
|
@@ -41,34 +41,93 @@ test('user can submit a wallet transaction', async ({ page, wallet }) => {
|
|
|
41
41
|
});
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
> Note: fixtures are lazy. The provider is only injected when a test
|
|
45
|
+
> references the `wallet` fixture — a test that destructures only `page`
|
|
46
|
+
> will have no `window.ethereum`.
|
|
47
|
+
|
|
48
|
+
Testing pending-approval UI and rejection paths:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
test('shows a pending state until the user confirms', async ({ page, wallet }) => {
|
|
52
|
+
const held = wallet.holdNextRequest('eth_sendTransaction');
|
|
53
|
+
await page.getByRole('button', { name: /swap/i }).click();
|
|
54
|
+
await expect(page.getByText(/confirm in your wallet/i)).toBeVisible();
|
|
55
|
+
|
|
56
|
+
(await held).approve();
|
|
57
|
+
await expect(page.getByText(/success/i)).toBeVisible();
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For live testnet tests, import the live fixture (Sepolia by default; use
|
|
62
|
+
`createLiveFixtures({ chain })` for other chains):
|
|
45
63
|
|
|
46
64
|
```ts
|
|
47
65
|
import { expect, test } from '@marigoldlabs/web3-tester/live-fixtures';
|
|
48
66
|
|
|
49
67
|
test('signs in through SIWE on Sepolia', async ({ page, wallet }) => {
|
|
50
68
|
await page.goto('/');
|
|
69
|
+
|
|
70
|
+
// Live wallets are deny-by-default: arm each prompt before triggering it.
|
|
71
|
+
wallet.approveNext('eth_requestAccounts');
|
|
51
72
|
await page.getByRole('button', { name: /connect/i }).click();
|
|
73
|
+
|
|
74
|
+
wallet.approveNext('personal_sign');
|
|
75
|
+
await page.getByRole('button', { name: /sign in/i }).click();
|
|
76
|
+
|
|
52
77
|
await expect(page.getByText(wallet.primaryAccount.slice(0, 6))).toBeVisible();
|
|
53
78
|
});
|
|
54
79
|
```
|
|
55
80
|
|
|
56
|
-
|
|
81
|
+
To deliberately auto-approve a whole test instead, use
|
|
82
|
+
`test.use({ liveOptions: { walletOptions: { autoApprove: true } } })`.
|
|
83
|
+
|
|
84
|
+
For fully in-UI real wallet tests, use the real-wallet fixtures. The pinned
|
|
85
|
+
MetaMask build is downloaded automatically, onboarding runs once into a
|
|
86
|
+
cached profile, and every test gets a disposable clone of that profile:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { expect, test } from '@marigoldlabs/web3-tester/real-wallet-fixtures';
|
|
90
|
+
|
|
91
|
+
test.use({
|
|
92
|
+
realWalletOptions: {
|
|
93
|
+
setup: { seedPhrase: process.env.WEB3_TESTER_REAL_WALLET_SECRET_RECOVERY_PHRASE },
|
|
94
|
+
baseURL: 'https://app.example.com',
|
|
95
|
+
// Required: pick headed or headless explicitly (or set
|
|
96
|
+
// WEB3_TESTER_REAL_WALLET_HEADLESS). Headed is the fully validated mode.
|
|
97
|
+
headless: false,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('confirms a real MetaMask transaction', async ({ page, realWallet }) => {
|
|
102
|
+
await realWallet.addNetwork({
|
|
103
|
+
name: 'Anvil Local',
|
|
104
|
+
rpcUrl: 'http://127.0.0.1:8645',
|
|
105
|
+
chainId: 31337,
|
|
106
|
+
symbol: 'ETH',
|
|
107
|
+
});
|
|
108
|
+
await realWallet.switchNetwork('Anvil Local');
|
|
109
|
+
|
|
110
|
+
await page.goto('/');
|
|
111
|
+
await page.getByRole('button', { name: /connect/i }).click();
|
|
112
|
+
await realWallet.connectToDapp();
|
|
113
|
+
|
|
114
|
+
await page.getByRole('button', { name: /swap/i }).click();
|
|
115
|
+
await realWallet.confirmTransaction();
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The imperative API remains available for custom setups (preconfigured
|
|
120
|
+
profiles, attaching to a real Chrome profile):
|
|
57
121
|
|
|
58
122
|
```ts
|
|
59
123
|
import { launchRealWallet } from '@marigoldlabs/web3-tester/real-wallet';
|
|
124
|
+
import { prepareMetaMaskExtension } from '@marigoldlabs/web3-tester/metamask-extension';
|
|
60
125
|
|
|
61
126
|
const wallet = await launchRealWallet({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
setup: process.env.FJORD_REAL_WALLET_PASSWORD
|
|
67
|
-
? {
|
|
68
|
-
password: process.env.FJORD_REAL_WALLET_PASSWORD,
|
|
69
|
-
seedPhrase: process.env.FJORD_REAL_WALLET_SECRET_RECOVERY_PHRASE,
|
|
70
|
-
}
|
|
71
|
-
: undefined,
|
|
127
|
+
extensionPath: await prepareMetaMaskExtension(),
|
|
128
|
+
profileDir: process.env.WEB3_TESTER_REAL_WALLET_PROFILE_DIR as string,
|
|
129
|
+
setup: { seedPhrase: process.env.WEB3_TESTER_REAL_WALLET_SECRET_RECOVERY_PHRASE },
|
|
130
|
+
headless: false,
|
|
72
131
|
});
|
|
73
132
|
|
|
74
133
|
await wallet.connectToDapp();
|
|
@@ -77,6 +136,20 @@ await wallet.confirmTransaction();
|
|
|
77
136
|
await wallet.close();
|
|
78
137
|
```
|
|
79
138
|
|
|
139
|
+
MetaMask version pinning: selectors are maintained against
|
|
140
|
+
`DEFAULT_METAMASK_VERSION` (currently 13.34.1, current MetaMask) and validated
|
|
141
|
+
by the opt-in smoke suite (`npm run smoke:real-wallet`), which runs the full
|
|
142
|
+
journey — onboarding, add/switch network, connect, sign, send, reject — plus
|
|
143
|
+
the account/token/settings surface against the real extension. The UI
|
|
144
|
+
generation (13.x "multichain" vs the older 12.x) is an explicit configuration,
|
|
145
|
+
derived from the extension manifest at launch and overridable via the
|
|
146
|
+
`generation` option: only the configured generation's selectors are driven —
|
|
147
|
+
the other generation is never probed as a fallback. Set
|
|
148
|
+
`WEB3_TESTER_METAMASK_VERSION` to pin a specific build (e.g. `12.23.1`; 12.x
|
|
149
|
+
is supported on a best-effort validation cadence — 13.x gates releases). Bump
|
|
150
|
+
the pin deliberately and re-run the smoke suite, since MetaMask UI selectors
|
|
151
|
+
can drift between releases.
|
|
152
|
+
|
|
80
153
|
## Local Development
|
|
81
154
|
|
|
82
155
|
```bash
|
|
@@ -84,7 +157,8 @@ npm install
|
|
|
84
157
|
npx playwright install chromium
|
|
85
158
|
npm run typecheck
|
|
86
159
|
npm run build
|
|
87
|
-
npm test
|
|
160
|
+
npm test # hermetic library tests (needs anvil)
|
|
161
|
+
npm run smoke:real-wallet # opt-in real-MetaMask smoke suite (headed)
|
|
88
162
|
```
|
|
89
163
|
|
|
90
164
|
Foundry's `anvil` executable must be available on `PATH`, or set `ANVIL_EXECUTABLE`.
|
|
@@ -117,26 +191,25 @@ Copy `.env.example` for local reference. Do not commit real private keys.
|
|
|
117
191
|
|
|
118
192
|
| Variable | Default | Purpose |
|
|
119
193
|
| --- | --- | --- |
|
|
120
|
-
| `DAPP_URL` | `https://v4.fjordfoundry.com` | Playwright base URL for app tests. |
|
|
121
194
|
| `ANVIL_EXECUTABLE` | `anvil` | Path to the Anvil binary. |
|
|
122
195
|
| `ANVIL_RUNTIME` | `binary` | Set to `docker` to run Anvil through Docker Desktop. |
|
|
123
196
|
| `ANVIL_DOCKER_IMAGE` | `ghcr.io/foundry-rs/foundry:latest` | Docker image used when `ANVIL_RUNTIME=docker`. |
|
|
124
|
-
| `ANVIL_HOST` | `127.0.0.1` | Host for worker Anvil RPC endpoints. |
|
|
125
|
-
| `
|
|
197
|
+
| `ANVIL_HOST` | `127.0.0.1` | Host for worker Anvil RPC endpoints. Non-loopback hosts are refused unless `ANVIL_ALLOW_NON_LOOPBACK=true`. |
|
|
198
|
+
| `ANVIL_ALLOW_NON_LOOPBACK` | `false` | Explicit opt-in to bind Anvil beyond loopback (exposes its unauthenticated admin RPC to the network). |
|
|
199
|
+
| `ANVIL_PORT` | `8645` | Base port (worker index is added for isolation). Defaults off 8545 so a developer-run dev node never collides. |
|
|
126
200
|
| `ANVIL_CHAIN_ID` | `31337` | Chain ID exposed by local Anvil and the injected provider. |
|
|
127
201
|
| `ANVIL_FORK_URL` | unset | Optional fork RPC URL. |
|
|
128
202
|
| `ANVIL_SILENT` | `true` | Set to `false` to stream Anvil logs. |
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `FJORD_REAL_WALLET_SECRET_RECOVERY_PHRASE` | unset | Optional seed phrase used only when MetaMask opens on onboarding. |
|
|
203
|
+
| `WEB3_TESTER_PRIVATE_KEY` | unset | Runtime-only private key for live-chain fixtures. |
|
|
204
|
+
| `WEB3_TESTER_RPC_URL` | Viem default | Optional RPC URL for live fixtures (`SEPOLIA_RPC_URL` legacy alias). |
|
|
205
|
+
| `WEB3_TESTER_METAMASK_VERSION` | pinned default | MetaMask release downloaded by `prepareMetaMaskExtension`. |
|
|
206
|
+
| `WEB3_TESTER_REAL_WALLET_EXTENSION_PATH` | auto-download | Path to an unpacked MetaMask extension (skips the download). |
|
|
207
|
+
| `WEB3_TESTER_REAL_WALLET_PROFILE_DIR` | profile cache | Explicit persistent Chromium user-data directory, or a Chrome profile directory such as `Profile 1`. Disables the per-test profile cache. |
|
|
208
|
+
| `WEB3_TESTER_REAL_WALLET_PASSWORD` | deterministic test password | MetaMask password used to unlock profiles. |
|
|
209
|
+
| `WEB3_TESTER_REAL_WALLET_SECRET_RECOVERY_PHRASE` | unset | Seed phrase used to build the cached real-wallet profile. |
|
|
210
|
+
| `WEB3_TESTER_REAL_WALLET_HEADLESS` | none — explicit choice required | `true`/`false`. Real-wallet launches refuse to guess: pick headed (fully validated) or headless (needs the full Chromium from `npx playwright install chromium`) here or via the `headless` option. |
|
|
211
|
+
| `WEB3_TESTER_REAL_WALLET_SMOKE` | unset | Set `true` to run the real-MetaMask smoke suite (`npm run smoke:real-wallet`). |
|
|
212
|
+
| `WEB3_TESTER_WC_PROJECT_ID` | unset | Reown project id; set to run the opt-in WalletConnect relay suite. |
|
|
140
213
|
|
|
141
214
|
## Package Surface
|
|
142
215
|
|
|
@@ -146,6 +219,8 @@ The installable package exports:
|
|
|
146
219
|
- `@marigoldlabs/web3-tester/fixtures`
|
|
147
220
|
- `@marigoldlabs/web3-tester/live-fixtures`
|
|
148
221
|
- `@marigoldlabs/web3-tester/real-wallet`
|
|
222
|
+
- `@marigoldlabs/web3-tester/real-wallet-fixtures`
|
|
223
|
+
- `@marigoldlabs/web3-tester/metamask-extension`
|
|
149
224
|
- `@marigoldlabs/web3-tester/anvil`
|
|
150
225
|
- `@marigoldlabs/web3-tester/mock-wallet-controller`
|
|
151
226
|
- `@marigoldlabs/web3-tester/private-key-rpc-client`
|
|
@@ -159,6 +234,11 @@ await chain.impersonateAccount('0x0000000000000000000000000000000000000001');
|
|
|
159
234
|
await chain.setBalance(wallet.primaryAccount, 10_000n * 10n ** 18n);
|
|
160
235
|
await chain.fastForward(7 * 24 * 60 * 60);
|
|
161
236
|
await chain.mine(3);
|
|
237
|
+
|
|
238
|
+
// Token seeding and deployment (forge-style cheatcodes):
|
|
239
|
+
const token = await chain.deployErc20({ symbol: 'USDX', decimals: 6 });
|
|
240
|
+
await chain.dealErc20(token.address, wallet.primaryAccount, 5_000_000_000n);
|
|
241
|
+
// dealErc20 also works on ANVIL_FORK_URL forks against real mainnet tokens.
|
|
162
242
|
```
|
|
163
243
|
|
|
164
244
|
## Wallet Control
|
|
@@ -167,8 +247,39 @@ await chain.mine(3);
|
|
|
167
247
|
await wallet.simulateRejection('eth_sendTransaction');
|
|
168
248
|
await wallet.disconnect();
|
|
169
249
|
await wallet.reconnect();
|
|
170
|
-
|
|
250
|
+
// Accounts are validated against the node's signers — use chain.accounts()
|
|
251
|
+
// entries (or chain.impersonateAccount(addr) first for send-only flows).
|
|
252
|
+
const [, second] = await chain.accounts();
|
|
253
|
+
await wallet.setAccounts([second]);
|
|
254
|
+
await wallet.switchAccount(second); // reorders + emits accountsChanged
|
|
171
255
|
await wallet.switchNetwork(11155111);
|
|
256
|
+
|
|
257
|
+
// Pending-approval simulation and transaction assertions:
|
|
258
|
+
const held = wallet.holdNextRequest('personal_sign');
|
|
259
|
+
// ... trigger the dapp action, assert the pending UI ...
|
|
260
|
+
(await held).reject('User changed their mind.');
|
|
261
|
+
|
|
262
|
+
const txPromise = wallet.waitForNextTransaction();
|
|
263
|
+
// ... trigger the dapp action ...
|
|
264
|
+
const hash = await txPromise;
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Dapp-initiated `wallet_switchEthereumChain` follows MetaMask semantics: it
|
|
268
|
+
throws 4902 for chains the wallet does not know; chains become known via
|
|
269
|
+
`wallet_addEthereumChain` or a test-driven `wallet.switchNetwork(...)`.
|
|
270
|
+
|
|
271
|
+
Multi-account and multi-user testing:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
// Start connected with three anvil accounts:
|
|
275
|
+
test.use({ walletOptions: { accountIndexes: [0, 1, 2] } });
|
|
276
|
+
|
|
277
|
+
// Two users, one chain — seller lists, buyer purchases:
|
|
278
|
+
test('buyer sees the listing', async ({ page, wallet, createUser }) => {
|
|
279
|
+
const buyer = await createUser(); // own context + page, anvil account #1
|
|
280
|
+
await buyer.page.goto('/listings/1');
|
|
281
|
+
await buyer.page.getByRole('button', { name: 'Buy' }).click();
|
|
282
|
+
});
|
|
172
283
|
```
|
|
173
284
|
|
|
174
285
|
## Multiple Wallet Selectors
|
|
@@ -190,16 +301,32 @@ test.use({
|
|
|
190
301
|
| Path | Purpose |
|
|
191
302
|
| --- | --- |
|
|
192
303
|
| `src/` | Reusable package source. |
|
|
193
|
-
| `tests
|
|
194
|
-
| `
|
|
195
|
-
| `docs/` | Dependency, API, architecture, and Fjord QA documentation. |
|
|
304
|
+
| `tests/` (library project) | Hermetic harness self-tests: `anvil`, `live-fixtures`, `mock-wallet`, `private-key-rpc-client`, `provider-injection`, `real-wallet`, `real-wallet-smoke` (opt-in). |
|
|
305
|
+
| `docs/` | API, architecture, and roadmap documentation. |
|
|
196
306
|
| `examples/` | Copyable consumer-app snippets. |
|
|
197
|
-
| `reports/` | Current Fjord v4 QA reports. |
|
|
198
307
|
|
|
199
308
|
## Safety Model
|
|
200
309
|
|
|
201
|
-
- Local tests use deterministic Anvil accounts only
|
|
310
|
+
- Local tests use deterministic Anvil accounts only, and Anvil refuses to
|
|
311
|
+
bind beyond loopback unless `ANVIL_ALLOW_NON_LOOPBACK=true` is set — its
|
|
312
|
+
admin RPC (impersonation, `setBalance`, the fork URL) is unauthenticated.
|
|
202
313
|
- Live tests require explicit environment variables and never store private keys in source.
|
|
314
|
+
- Live wallets are deny-by-default: signing, sending (including
|
|
315
|
+
`eth_sendRawTransaction`), and wallet prompts (`eth_requestAccounts`
|
|
316
|
+
included, even though the wallet starts pre-connected) throw `4001` until
|
|
317
|
+
the test arms them — `wallet.approveNext(methods?, match?)` per request, or
|
|
318
|
+
`wallet.autoApprove(true)` /
|
|
319
|
+
`test.use({ liveOptions: { walletOptions: { autoApprove: true } } })` as a
|
|
320
|
+
deliberate whole-test opt-in. So page scripts — including third-party
|
|
321
|
+
includes on the dapp under test — cannot spend or sign unprompted.
|
|
322
|
+
- When Playwright's `baseURL` is configured, the live provider is
|
|
323
|
+
origin-scoped to it: out-of-scope frames get no `window.ethereum` at all
|
|
324
|
+
and the RPC bridge refuses them with `4100`. Override with
|
|
325
|
+
`allowedOrigins`; without a `baseURL`, every frame is served.
|
|
326
|
+
- `PrivateKeyRpcClient` refuses chains that are not testnets or local dev
|
|
327
|
+
chains unless constructed with `allowMainnet: true`, and verifies the RPC
|
|
328
|
+
endpoint's `eth_chainId` matches the configured chain before the first
|
|
329
|
+
broadcast (`eth_sendTransaction` / `eth_sendRawTransaction`).
|
|
203
330
|
- Real-wallet tests use a persistent browser profile and keep extension-side automation inside this package.
|
|
204
331
|
- Mutation tests are skipped unless their opt-in flag is set.
|
|
205
332
|
- Published reports redact secrets and record transaction hashes only when useful for auditability.
|
|
@@ -207,7 +334,6 @@ test.use({
|
|
|
207
334
|
## More Documentation
|
|
208
335
|
|
|
209
336
|
- [docs/API.md](docs/API.md)
|
|
337
|
+
- [docs/ROADMAP.md](docs/ROADMAP.md)
|
|
210
338
|
- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
|
211
|
-
- [docs/CONSUMING_FROM_FJORD.md](docs/CONSUMING_FROM_FJORD.md)
|
|
212
|
-
- [docs/FJORD_LIVE_QA.md](docs/FJORD_LIVE_QA.md)
|
|
213
339
|
- [docs/RELEASE_CHECKLIST.md](docs/RELEASE_CHECKLIST.md)
|
package/dist/anvil.d.ts
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
|
-
import { type Address, type Chain, type Hex, type PublicActions, type TestClient, type Transport, type WalletActions } from 'viem';
|
|
1
|
+
import { type Abi, type Address, type Chain, type Hex, type PublicActions, type TestClient, type TransactionReceipt, type Transport, type WalletActions } from 'viem';
|
|
2
|
+
import type { Account, SignedAuthorization } from 'viem';
|
|
3
|
+
import { TEST_ERC20_ABI } from './contracts/test-erc20.js';
|
|
4
|
+
import { type DealErc20Options } from './erc20.js';
|
|
5
|
+
import { type DecodedTransaction } from './transactions.js';
|
|
2
6
|
import type { JsonRpcRequest, RpcClient } from './types.js';
|
|
7
|
+
export type DeployContractOptions = {
|
|
8
|
+
abi: Abi;
|
|
9
|
+
bytecode: Hex;
|
|
10
|
+
args?: readonly unknown[];
|
|
11
|
+
/** Defaults to the first Anvil unlocked account. */
|
|
12
|
+
from?: Address;
|
|
13
|
+
value?: bigint;
|
|
14
|
+
};
|
|
15
|
+
export type DeployedContract = {
|
|
16
|
+
address: Address;
|
|
17
|
+
hash: Hex;
|
|
18
|
+
receipt: TransactionReceipt;
|
|
19
|
+
};
|
|
20
|
+
export type DeployErc20Options = {
|
|
21
|
+
name?: string;
|
|
22
|
+
symbol?: string;
|
|
23
|
+
decimals?: number;
|
|
24
|
+
/** Minted to mintTo in the constructor. Default 0n. */
|
|
25
|
+
initialSupply?: bigint;
|
|
26
|
+
/** Defaults to the deployer. */
|
|
27
|
+
mintTo?: Address;
|
|
28
|
+
from?: Address;
|
|
29
|
+
};
|
|
30
|
+
export type DeployedErc20 = DeployedContract & {
|
|
31
|
+
abi: typeof TEST_ERC20_ABI;
|
|
32
|
+
name: string;
|
|
33
|
+
symbol: string;
|
|
34
|
+
decimals: number;
|
|
35
|
+
};
|
|
36
|
+
export type ChainAuthorizationOptions = {
|
|
37
|
+
/** Authority: a viem local account or a raw private key. */
|
|
38
|
+
account: Account | Hex;
|
|
39
|
+
/** The delegate contract; the zero address revokes. */
|
|
40
|
+
contractAddress: Address;
|
|
41
|
+
nonce?: number;
|
|
42
|
+
/** Default: this chain's id. 0 = valid on any chain. */
|
|
43
|
+
chainId?: number;
|
|
44
|
+
/** 'self' when the authority submits its own type-4 tx (nonce+1 rules). */
|
|
45
|
+
executor?: 'self';
|
|
46
|
+
};
|
|
47
|
+
export type DelegateOptions = {
|
|
48
|
+
/** Authority whose key signs the authorization. */
|
|
49
|
+
account: Account | Hex;
|
|
50
|
+
contractAddress: Address;
|
|
51
|
+
/** Unlocked anvil account paying gas. Default: accounts()[0]. */
|
|
52
|
+
sponsor?: Address;
|
|
53
|
+
};
|
|
3
54
|
export type AnvilOptions = {
|
|
4
55
|
runtime?: 'binary' | 'docker';
|
|
5
56
|
executable?: string;
|
|
@@ -13,8 +64,12 @@ export type AnvilOptions = {
|
|
|
13
64
|
mnemonic?: string;
|
|
14
65
|
blockTime?: number;
|
|
15
66
|
forkUrl?: string;
|
|
67
|
+
forkBlockNumber?: number;
|
|
68
|
+
extraArgs?: readonly string[];
|
|
16
69
|
timeoutMs?: number;
|
|
17
70
|
silent?: boolean;
|
|
71
|
+
/** Opt in to binding Anvil to a non-loopback interface. */
|
|
72
|
+
allowNonLoopbackHost?: boolean;
|
|
18
73
|
};
|
|
19
74
|
export type AnvilSnapshotId = Hex;
|
|
20
75
|
export type AnvilViemClient = TestClient<'anvil', Transport, Chain> & PublicActions<Transport, Chain> & WalletActions<Chain>;
|
|
@@ -28,7 +83,7 @@ export declare class AnvilInstance {
|
|
|
28
83
|
private constructor();
|
|
29
84
|
static start(options?: AnvilOptions): Promise<AnvilInstance>;
|
|
30
85
|
stop(): Promise<void>;
|
|
31
|
-
private
|
|
86
|
+
private reportedChainId;
|
|
32
87
|
}
|
|
33
88
|
export type ChainControllerOptions = {
|
|
34
89
|
rpcUrl: string;
|
|
@@ -48,5 +103,38 @@ export declare class ChainController implements RpcClient {
|
|
|
48
103
|
setBalance(address: Address, value: bigint): Promise<void>;
|
|
49
104
|
fastForward(seconds: number): Promise<void>;
|
|
50
105
|
mine(blocks?: number): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Waits for the receipt and returns it with decoded logs (when an abi is
|
|
108
|
+
* given) and, for reverted transactions, the recovered revert reason.
|
|
109
|
+
*/
|
|
110
|
+
waitForTransaction(hash: Hex, options?: {
|
|
111
|
+
abi?: Abi;
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
}): Promise<DecodedTransaction>;
|
|
114
|
+
private readonly erc20SlotCache;
|
|
115
|
+
deployContract(options: DeployContractOptions): Promise<DeployedContract>;
|
|
116
|
+
deployErc20(options?: DeployErc20Options): Promise<DeployedErc20>;
|
|
117
|
+
/** forge-std deal parity: set any standard ERC-20 balance, fork included. */
|
|
118
|
+
dealErc20(token: Address, account: Address, amount: bigint, options?: DealErc20Options): Promise<void>;
|
|
119
|
+
getErc20Balance(token: Address, account: Address): Promise<bigint>;
|
|
120
|
+
setStorageAt(address: Address, slot: Hex | bigint | number, value: Hex | bigint): Promise<void>;
|
|
121
|
+
setCode(address: Address, bytecode: Hex): Promise<void>;
|
|
122
|
+
setNonce(address: Address, nonce: number): Promise<void>;
|
|
123
|
+
signAuthorization(options: ChainAuthorizationOptions): Promise<SignedAuthorization>;
|
|
124
|
+
/**
|
|
125
|
+
* Signs and submits a type-4 delegation from an unlocked sponsor; resolves
|
|
126
|
+
* once the authority's code is the 0xef0100‖address designator.
|
|
127
|
+
*/
|
|
128
|
+
delegate(options: DelegateOptions): Promise<{
|
|
129
|
+
hash: Hex;
|
|
130
|
+
authority: Address;
|
|
131
|
+
}>;
|
|
132
|
+
/** Authorization to the zero address: resets the authority's code to 0x. */
|
|
133
|
+
revokeDelegation(options: Omit<DelegateOptions, 'contractAddress'>): Promise<{
|
|
134
|
+
hash: Hex;
|
|
135
|
+
authority: Address;
|
|
136
|
+
}>;
|
|
137
|
+
/** Parses the EIP-7702 designator out of eth_getCode; null when not delegated. */
|
|
138
|
+
getDelegation(authority: Address): Promise<Address | null>;
|
|
51
139
|
}
|
|
52
140
|
//# sourceMappingURL=anvil.d.ts.map
|
package/dist/anvil.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anvil.d.ts","sourceRoot":"","sources":["../src/anvil.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"anvil.d.ts","sourceRoot":"","sources":["../src/anvil.ts"],"names":[],"mappings":"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"}
|