feat: mock development environment (wallet, RPC, GraphQL) - #72
Draft
belopash wants to merge 70 commits into
Draft
Conversation
Introduces a MOCK_WALLET_ADDRESS environment variable that, when set to a valid 0x-prefixed Ethereum address, makes the app use wagmi's built-in mock connector instead of RainbowKit. The mock connector auto-connects on startup so the entire app is immediately in a 'connected' state without requiring a browser wallet extension or WalletConnect session. Changes: - packages/client/vite.config.ts — expose MOCK_WALLET_ADDRESS as a build-time define - packages/client/src/config.ts — export mockConfig (createConfig + mock connector) when the var is set - packages/client/src/components/MockWalletAutoConnect.tsx — new component that calls connect() on the mock connector on first render - packages/client/src/App.tsx — branch on mockConfig to swap in the lightweight mock provider tree (no RainbowKitProvider) - .env.example — document the new variable Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Account switcher (client): - packages/client/src/components/MockAccountSwitcher.tsx — new component rendered inside UserMenu when MOCK_WALLET_ADDRESS is set. Shows 4 preset Hardhat addresses plus a free-text input; selecting any address persists it to localStorage and reloads the page so the mock wagmi connector picks it up without restarting the dev server. - packages/client/src/config.ts — resolveMockAddress() prefers the localStorage key (set by MockAccountSwitcher) over the build-time env var, enabling per-reload address switching without a Vite restart. - packages/client/src/layouts/NetworkLayout/UserMenu.tsx — render MockAccountSwitcher at the bottom of the dropdown in mock mode only. Mock GraphQL fixture server (server): - packages/server/src/services/mockGraphqlServer.ts — lightweight in-process HTTP server (Node http, zero extra deps) that returns realistic canned responses for every Squid GraphQL operation (workers, gateways, token). Activated by MOCK_GRAPHQL=true, listens on MOCK_GRAPHQL_PORT (default 4321). - packages/server/src/env.ts — isMockGraphql() helper; getWorkers/Gateways/ TokenSquidUrl() all redirect to the mock server URL when active. - packages/server/src/main.ts — await startMockGraphqlServer() before the tRPC server starts when MOCK_GRAPHQL=true. - .env.example — documents MOCK_GRAPHQL and MOCK_GRAPHQL_PORT variables. Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
…lect-account flow - Single MOCK_WALLET=true env flag activates everything (no more MOCK_WALLET_ADDRESS, MOCK_GRAPHQL separate flags) - 4 fixture accounts (Alice/Bob/Carol/Dave) with different roles and balances - Account selection dialog (MockConnectDialog) with role descriptions - sessionStorage persists the selected account across page reloads - MockWalletAutoConnect re-fires connect() after reload when selection is stored - Disconnect clears sessionStorage so next connect starts fresh - Mock RPC server (mockRpcServer.ts) handles eth_call, eth_sendTransaction, eth_getBalance etc. with in-memory state that resets on restart - Mock GraphQL server returns account-scoped data per fixture persona - ConnectButton branches on isMockMode constant to avoid calling useConnectModal outside RainbowKitProvider Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
mockGraphqlServer.ts:
- Rewrote with full field coverage matching exact GraphQL document shapes
- Fixed workersSummary to use onlineWorkersCount (not onlineWorkers)
- Added all worker fields: online, jailed, dialOk, statusHistory, uptime24Hours,
capedDelegation, delegationCount, locked, lockEnd, bond, claimableReward,
claimedReward, queries24Hours/90Days, servedData, storedData, dayUptimes
- currentEpoch: blockTimeL1, lastBlockL1, lastBlockTimestampL1 (used by epoch timer)
- myDelegations returns Bob's delegation with deposit/claimableReward/claimedReward
- myWorkers/myWorkersCount returns Carol's 2 workers correctly
- vestingsByAccount/vestingByAddress/accountsByOwner return Dave's vesting account
- All timeseries now use requested from/to dates (not hardcoded PAST/NOW)
- RewardTimeseries: proper {workerReward, stakerReward} value shape
- AprTimeseries: proper {workerApr, stakerApr} value shape
- TransfersByTypeTimeseries: proper {deposit,withdraw,transfer,reward,release} shape
- workerDelegationInfo: adds liveness/dTenure/trafficWeight/settings fields
- 4 workers on the public list (Alice's, Bob's, Carol's x2) for realistic list view
mockRpcServer.ts:
- handleMulticall3: returns proper ABI-encoded Result[] instead of empty array,
so wagmi useReadContracts gets success=true + 32-byte zero per call rather
than treating the response as a failed multicall
- All unrecognised eth_call selectors now return 32-byte zero instead of '0x'
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
- plans/wagmi-viem-testing.md: mock environment plan (Anvil + TS deploy harness + log-driven mini-indexer + Vitest unit/integration); replaces the hand-rolled mock RPC and mock GraphQL servers. - plans/playwright-e2e.md: post-connect app-flow E2E plan built on top of the mock-stack package. Made-with: Cursor
Adds the Vitest test runner to both @subsquid/client and @subsquid/server.
@subsquid/client:
- Two Vitest projects: 'unit' (jsdom, parallel threads, canned RPC transport)
and 'integration' (jsdom, single-fork, will be wired to Anvil + mini-indexer
via @subsquid/mock-stack in Phase 8).
- src/test/setup.ts wires @testing-library/jest-dom + post-test cleanup.
- src/test/render.tsx exposes renderWithProviders + TestProviders helpers.
- src/test/wagmi/{testConfig,customTransport}.ts: wagmi mock connector +
viem 'custom' transport for deterministic Layer-1 hook tests.
- src/test/anvil/{global-setup,snapshot}.ts: placeholders for Phase 8.
- Drop @types/jest, add vitest/jsdom/@testing-library/* /msw deps.
- Scripts: test, test:unit, test:integration, test:watch.
@subsquid/server:
- Single 'node' Vitest project. test + test:watch scripts.
Root:
- turbo.json: 'test' task with dependsOn ['^build', 'transit'].
- package.json: 'test' script.
This unblocks Layer-1 test development. Layer-2 lights up in Phase 8 when the
mock-stack package + anvil bootstrap land.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Replaces module-level mockConfig/rainbowConfig constants with a single
factory that App.tsx calls inside a useMemo. Tests can now obtain a wagmi
config matching the production tree without depending on import-time env
reads.
App.tsx collapses the two WagmiProvider branches into one tree with the
RainbowKitProvider / MockWalletAutoConnect choice happening inside.
Mock account selection still uses the existing sessionStorage + reload
flow (MockConnectDialog unchanged) — Phase 10 will replace that with a
connector 'change' event once the mock-stack package and friends land.
Investigation note (recorded for Phase 10): wagmi.useSwitchAccount
switches between connector instances, not accounts within one connector,
so it's the wrong tool. The wagmi mock connector's onAccountsChanged hook
emits a 'change' event that updates connection.accounts reactively — the
new switcher will call connector.emitter.emit('change', { accounts: [...] })
with the picked address hoisted to index 0.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Adds connectMockConnector() helper that programmatically connects the wagmi mock connector before tests render hooks — wagmi's defaultConnected feature flips an internal flag but doesn't emit a connect event, so useAccount() would otherwise report disconnected on first render. Strips the multicall3 contract from chains exposed by createUnitWagmiConfig so useReadContract calls hit eth_call directly with human-readable function selectors instead of being aggregated through Multicall3.aggregate3. Reference spec wagmi-harness.test.tsx covers: - useAccount resolves the first fixture address from the mock connector - useReadContract resolves with a value supplied by the canned RPC map Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Adds the @subsquid/mock-stack workspace package — the single owner of
the mock environment lifecycle (anvil + deploy harness + mini-indexer).
Public API:
startMockStack(opts) → MockStackHandle
.rpcUrl, .graphqlUrl, .deployments, .indexer, .stop()
Phase 4 ships the skeleton:
- package.json with stack:prepare / stack:rebuild scripts (avoiding
the 'prepare' name which pnpm treats as a lifecycle hook)
- tsconfig.json + turbo.json with cache inputs covering both
submodules and contracts/
- foundry.toml ready for Phase 5 mock contracts
- src/index.ts exporting the public types; startMockStack() throws
until Phases 5–7 land
- scripts/prepare.ts stub
- PORT_VERSION placeholder for the upstream squid commit pin
- README.md describing intent + status
@subsquid/mock-stack added as a devDependency of @subsquid/client so
Vitest globalSetup (Phase 8) can import startMockStack() without
declaring a per-spec dep.
.gitignore covers out/, .anvil-state.json, .deployments.json, lib/.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
…lticall3)
contracts/MockERC20.sol — minimal ERC-20 base with public mint/burn,
plus MockSQD (18 dec), MockUSDC (6 dec), MockWETH (18 dec, +
deposit/withdraw for raw ETH wrapping). Inlined to keep forge build
fast and free of external lib dependencies.
contracts/MockV3Router.sol — minimal Pancake/Uniswap V3 SwapRouter
stub. Implements exactInputSingle and exactInput with deterministic
1:1 swaps and no AMM math; emits MockSwap so the indexer can track
trades. Tokens are pre-funded into the router by the deploy harness
(Phase 6).
contracts/Multicall3.sol — vendored copy of the canonical mds1
Multicall3 contract. Deployed at a non-canonical address; the wagmi
config picks up the actual address via createAppWagmiConfig's
multicall3Override option (driven by .deployments.json in mock mode).
forge build produces:
out/MockERC20.sol/{MockERC20,MockSQD,MockUSDC,MockWETH}.json
out/MockV3Router.sol/MockV3Router.json
out/Multicall3.sol/Multicall3.json
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Submodule:
- sqd-network-contracts (branch: main) added at /sqd-network-contracts.
forge build produces artifacts at packages/contracts/artifacts/<File>.sol/<Contract>.json.
mock-stack package:
- src/chain.ts: spawnAnvil() with eth_blockNumber boot polling. Resolves
the anvil binary via FOUNDRY_PATH or ~/.foundry/bin, falling back to PATH.
- src/artifacts.ts: typed readers for local mock-stack artifacts +
network-contracts + portal-contracts submodules.
- src/personas.ts: 4 fixture personas (Alice/Bob/Carol/Dave) with
deterministic anvil dev keys, mirroring MOCK_FIXTURE_ACCOUNTS in
client/config.ts.
- src/deploy.ts: TS deploy harness. Phase 6 ships the minimal
mock-only deploy (Multicall3 + MockSQD/USDC/WETH + MockV3Router) so
consumers can build against the harness. Network/portal contract
deploys layer in incrementally as Phase 8 specs need them. Also
exports dumpAnvilState() / loadAnvilState() — these use the
hex-compressed gzip format from anvil_dumpState/anvil_loadState
RPC methods, NOT the --load-state CLI flag (which expects raw JSON).
- src/deployments.ts: read/write .deployments.json.
- scripts/prepare.ts: orchestrates spawn → deploy → dump state →
shutdown. Smoke-tested round-trip: dump → fresh anvil → loadState
→ balances + bytecode preserved.
- scripts/_round-trip-smoke.ts: manual smoke test (not wired to a
Turbo task; underscore-prefixed).
Address override:
- @subsquid/common exports getContractAddresses({ network?, override? })
so callers can layer in mock-stack-deployed addresses.
- packages/server/src/env.ts.getContractAddresses() now reads
packages/mock-stack/.deployments.json (or MOCK_STACK_DEPLOYMENTS
env override) when isMockMode() is true and merges those addresses
into the live contract address book.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Lights up startMockStack() end-to-end:
1. spawnAnvil + loadAnvilState (Phase 6)
2. GraphQL HTTP server replicating mockGraphqlServer.ts contract
3. Indexer runtime exposing lastBlock + waitUntilCaughtUp barrier
Pieces added:
src/indexer/prng.ts — mulberry32 PRNG + FNV-1a hashSeed; replaces
Math.random() so synthetic timeseries are
reproducible run-to-run (plan §4.4 requirement).
src/indexer/synthetic.ts — port of mockGraphqlServer.ts resolveFixture +
makeWorker / numericSeries / objectSeries
helpers, with PRNG-driven variance and
persona lookups self-contained in the package.
Exports SYNTHETIC_OPERATIONS (the 41 named
operations from packages/server/graphql/*.graphql).
src/indexer/dispatcher.ts — registerResolver/dispatch contract: custom
resolvers override synthetic, otherwise
synthetic fallback, otherwise empty data
with a console.warn diagnostic.
src/indexer/server.ts — minimal HTTP shell (no GraphQL parser);
accepts {operationName, variables}, returns
{data}. CORS-permissive to match legacy
server. Default port 4321.
src/indexer/runtime.ts — lastBlock polling + resetAndReplay /
waitUntilCaughtUp barriers.
src/__tests__/parity.test.ts — enumerates *.graphql operations and
asserts every one is handled. Wired via
new vitest.config.ts.
src/index.ts (rewrite) — startMockStack() now ships a working stack:
anvil + state load + graphql server +
indexer; stop() unwinds in reverse.
Smoke-tested via scripts/_start-smoke.ts:
rpc=http://127.0.0.1:8545 graphql=http://127.0.0.1:4321/graphql
deployments populated (5 contracts), allWorkers returns 4 workers,
indexer.lastBlock matches prepare bootBlock=8.
Phase 7's per-operation chain-derived overrides (replacing synthetic
fallbacks with entity-store-backed resolvers) layer in via
registerResolver() in follow-up commits as Phase 8 specs need them.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Wires the integration project's globalSetup to startMockStack() and
runs an in-process tRPC server against it, then ships two reference
specs:
status.integration.test.ts:
- tRPC client → in-process server → mockGraphqlServer fallback
(squidNetworkHeight) → assertion. Proves the full stack roundtrips.
anvil-balance.integration.test.ts:
- viem PublicClient → anvil → assert Bob has 10 ETH (anvil_setBalance)
- viem readContract(MockSQD.balanceOf) → assert 200000 SQD
Validates that .anvil-state.json round-trip preserves persona seeding.
Wiring:
test/anvil/global-setup.ts — boots @subsquid/mock-stack, sets server
env vars (MOCK_WALLET=true, MOCK_GRAPHQL_PORT, MOCK_STACK_DEPLOYMENTS,
RPC_URL) BEFORE importing appRouter, starts an ephemeral tRPC server,
publishes URLs via Vitest's ctx.provide() (consumed by inject() in
specs).
test/anvil/types.ts — ProvidedContext augmentations.
test/anvil/snapshot.ts — beforeEach evm_snapshot/evm_revert pair so
each spec starts from the post-deploy state.
test/msw/server.ts — MSW v2 setupServer scaffold (handlers added per-spec
in Phase 9).
packages/server/package.json — add ./router, ./trpc, ./env subpath
exports so the integration globalSetup can import them.
Vitest 4 globalSetup signature: declare a minimal structural type for
the ctx argument since vitest 4 doesn't export GlobalSetupContext.
Tests run in singleFork mode (set in vitest.config.ts in Phase 1) so
all integration specs share one anvil + indexer.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
src/test/msw/trpc-error-handlers.ts:
trpcError(procedure, { code, message, httpStatus? }) builds an MSW
handler that returns a tRPC-shaped error JSON. Pattern matches both
unbatched (httpLink) and comma-separated batched (httpBatchLink)
request URLs, so callers don't have to know which link type the spec
under test uses.
src/test/__tests__/trpc-error.test.ts:
Reference spec — wires MSW v2's setupServer (via existing
test/msw/server.ts), uses trpcError() to force INTERNAL_SERVER_ERROR
on status.get, and asserts the resulting TRPCClientError carries the
expected code + message.
turbo.json:
Tighten the 'test' task config — drop the dangling coverage/**
output (warns when not produced), pass through MOCK_* env vars so
cache busts when those flip locally.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Previously MockConnectDialog called window.location.reload() after
persisting the picked persona index — that worked but was a poor UX
hit, threw away in-memory state, and was unusable in tests.
Phase 0 of the testing plan analysed wagmi.useSwitchAccount and
confirmed it switches between connector instances, not accounts within
one connector — wrong tool for the job. The right tool is the wagmi
mock connector's own onAccountsChanged hook, which emits a 'change'
event that updates the connection's account list reactively.
MockConnectDialog now:
1. Persists the index via setMockAccountIndex (reload survivability).
2. Connects the mock connector if it isn't already.
3. Calls connector.onAccountsChanged with the picked address hoisted
to position 0. useAccount() updates everywhere, no remount.
Falls back to the old reload flow if no mock connector is registered
(defensive — should not trigger in practice).
The legacy mockRpcServer.ts / mockGraphqlServer.ts in @subsquid/server
stay in place — replacing them in pnpm dev mode would force every
contributor to install foundry. That migration is a follow-up
workstream tracked separately.
Test: src/test/__tests__/account-switch.test.tsx exercises the
onAccountsChanged path against the wagmi mock connector and asserts
useAccount().address updates without a remount.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
.github/workflows/test.yaml:
Triggers on PRs and pushes to main/develop. Steps:
- Checkout with submodules: recursive (network + portal contracts).
- Install Node 22 + pnpm 9 + Foundry (foundry-rs/foundry-toolchain@v1).
- Cache pnpm store, .turbo, mock-stack out/ + .anvil-state.json +
.deployments.json + submodule artifact dirs, keyed on contract +
mock-stack source SHAs so PRs not touching contracts skip
forge build + the deploy harness.
- forge build per submodule + the local mock contracts (skipped
when artefacts already exist).
- pnpm --filter @subsquid/mock-stack stack:prepare (skipped when
.anvil-state.json already exists).
- pnpm tsc, pnpm lint, pnpm test in sequence.
AGENTS.md §Testing & Quality Gates:
Replace the 'no automated test suite' note with the Vitest split
(unit / integration projects, mock-stack parity test) and document
Foundry as an integration-test prerequisite. Add 'pnpm test' to
the Finish-the-Task checklist.
README.md:
New 'Testing' section pointing at pnpm test, the unit/integration
filter scripts, and the Foundry + stack:prepare prerequisites,
plus a link to packages/mock-stack/README.md for the mock environment
internals.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Test envs are deterministic — there's only one mock topology — so the
indirection via process.env was pure ceremony. This refactor strips it.
Server runtime override:
packages/server/src/env.ts adds setRuntimeOverride({ network,
squidGraphqlUrl, rpcUrl, contractAddressOverride, mockMode }) — every
getter checks the override first, then falls through to process.env.
Tests configure the server in one call instead of mutating
process.env.MOCK_WALLET / MOCK_GRAPHQL / MOCK_GRAPHQL_PORT /
MOCK_STACK_DEPLOYMENTS / RPC_URL / NETWORK before importing appRouter.
Ephemeral ports:
spawnAnvil() and startGraphqlServer() default to OS-allocated free
ports. startMockStack() now defaults to ephemeral RPC + GraphQL ports
too. Concurrent runs and rerun loops can't collide on 8545/4321.
The dev-mode pnpm dev workflow pins them via MOCK_RPC_PORT /
MOCK_GRAPHQL_PORT env vars (unchanged).
Auto-prepare:
startMockStack({ autoPrepare: true }) runs forge build + the deploy
harness in-process if packages/mock-stack/.anvil-state.json is
missing. Vitest's globalSetup opts in, so 'pnpm test' on a fresh
checkout works with no manual setup step beyond having Foundry
installed. runPrepare() (new src/prepare.ts) is the single
implementation shared by the script and the auto-prepare path.
CI:
test.yaml drops the explicit 'Prepare mock-stack snapshot' step plus
the network/portal contract forge-build steps (they're not yet
consumed by the harness — their builds were dead work). pnpm test
handles everything via auto-prepare.
Vitest config:
Lift the duplicated define block into a shared TEST_DEFINES constant
to keep both projects aligned.
Verified: deleted out/ + .anvil-state.json + .deployments.json; ran
'pnpm test' from cold; auto-prepare ran forge build + deploy + tests
(12/12 green). Also verified an already-cached run (no rebuild).
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Mock mode is now a separate run target, not a runtime flag flipped via
env vars. .env.mock has the same shape as .env.example (no special
MOCK_* keys) and just points the squid + RPC URLs at the in-process
mock servers.
Server side:
- New main.mock.ts entrypoint: starts the in-process mock GraphQL +
mock RPC servers, then runs main.ts as usual. main.ts itself no
longer has any mock-mode branching — it reads its env unchanged.
- dev:mock script uses --env-file=../../.env.mock + main.mock.ts.
- env.ts loses isMockMode / isMockGraphql / getMockGraphqlPort / the
.deployments.json fallback in getContractAddresses + the
'mockMode' field on RuntimeOverride. The three getXxxSquidUrl
helpers fold into a single getSquidUrl(envKey) that composes the
network prefix.
- mockRpcServer.ts / mockGraphqlServer.ts hardcode the dev ports
(8545 / 4321) — they're a fixed contract with .env.mock.
Client side:
- vite.config.ts switches to defineConfig(({ mode }) => ...). When
mode === 'mock' it loads .env.mock and injects
process.env.MOCK = 'true'. dev:mock script adds --mode mock.
- config.ts derives isMockMode from process.env.MOCK; deletes the
MOCK_RPC_URL re-export and the AppMode union; createAppWagmiConfig
no longer takes a 'mode' arg (reads isMockMode internally).
- App.tsx simplified — no more mode/mockRpcUrl plumbing.
- vitest config and global-setup updated to match.
.env.mock + .env.example:
- .env.mock: WALLET_CONNECT_PROJECT_ID + *_SQUID_API_URL all pointed
at http://localhost:4321/graphql; ARBITRUM_ONE_RPC_URL pointed at
http://localhost:8545. Same shape as .env.example, no flags.
- .env.example: drop MOCK_WALLET / MOCK_RPC_PORT / MOCK_GRAPHQL_PORT;
add a pointer to dev:mock.
Verified:
- pnpm dev: squid URLs resolve to mainnet endpoints, RPC undefined.
- pnpm dev:mock: squid URLs resolve to localhost:4321, RPC to
localhost:8545, mock servers boot.
- pnpm test: 12/12 green.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Run mock-mode local development with 'pnpm mock' instead of 'pnpm dev:mock'. Updates root, client, server, turbo task name, plus all comments / .env.example / README references. Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
P-A:
src/deploy.ts grows from 5-mock-deploy to 15-contract deploy in
dependency order (Router proxy + impl, NetworkController, Staking,
WorkerRegistration, RewardTreasury, SoftCap,
DistributedRewardsDistribution, RewardCalculation, GatewayRegistry
proxy + impl + initialize, VestingFactory). Roles wired:
Staking.REWARDS_DISTRIBUTOR_ROLE -> Distributor;
Treasury.setWhitelistedDistributor; Distributor.REWARDS_TREASURY_ROLE
-> Treasury; NetworkController.setAllowedVestedTarget for the four
user-action targets. epochLength set to 2 blocks so workers go
active quickly in tests.
src/chain.ts spawns anvil with --code-size-limit 50000 (the network
contracts exceed EIP-170's 24KB runtime limit; Arbitrum mainnet
doesn't enforce it either).
P-B:
src/seed.ts (new) — extracted from deploy.ts. mints + sets ETH per
persona, then runs realistic on-chain operations:
* Carol: approve(WorkerRegistration, 2*bondAmount); register two
workers with deterministic test peerIds (sha2-256 multihash
framing of fixed seed strings).
* Bob: approve(Staking, 50_000); deposit(workerId=1, 50_000).
* (Dave's vesting deferred — VESTING_CREATOR_ROLE flow lands when
the vesting page chain-derives.)
anvil_mine 0x10 between Carol's registers and Bob's deposit so
workers are 'active' by the time deposit's guard runs.
src/prepare.ts auto-runs forge build for sqd-network-contracts
artefacts when missing (same pattern as the local mock-stack
artefacts).
Verified: 'pnpm --filter @subsquid/mock-stack stack:prepare' from
cold installs everything, registers Carol's two workers (ids 1, 2),
delegates 50k SQD from Bob to worker 1, dumps state at bootBlock=50.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
P-C — runtime.ts no longer just polls lastBlock; it subscribes to
WorkerRegistration + Staking logs and projects them into a TS Map
entity store.
src/indexer/entities.ts:
EntityStore { workers, workersByPeerId, workersByOwner,
delegations, delegationsByOwner, delegationsByWorker }
Helpers: createEntityStore(), clearEntities(), delegationKey(),
addToSetMap().
src/indexer/mappings/workerRegistration.ts:
Pure function applying WorkerRegistered, WorkerDeregistered,
MetadataUpdated to the entity store.
src/indexer/mappings/staking.ts:
Pure function applying Deposited and Withdrawn (Rewarded/Claimed
stay synthetic for now).
src/indexer/runtime.ts (rewrite):
- Bootstrap: viem.getLogs(fromBlock=0, toBlock='latest', address=[…])
for every contract in deployments. Apply mappings, advance cursor.
- Tail loop: every pollIntervalMs, fetch logs from cursor onward.
- resetAndReplay() clears the store and re-bootstraps from genesis.
- waitUntilCaughtUp() awaits any in-flight bootstrap, then waits
until lastBlock catches the chain head.
Indexer is wired into startMockStack() and the entity store is
exposed on the public handle (handle.indexer.store) so resolvers
in P-D can consult it.
Smoke-tested via scripts/_indexer-smoke.ts:
workers: 2 (ids 1, 2; peerIds = 0x12206d6f… (test multihash bytes);
owner = Carol)
delegations: 1 (worker 1 ← Bob, 50000 SQD)
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
P-D adds three resolver modules that consult the entity store +
on-chain reads instead of returning hand-rolled placeholders.
src/indexer/operations/workers.ts:
registerWorkerResolvers({ store, client, deployments, blockTimestamp })
Replaces the synthetic fallback for:
allWorkers — entity store, sorted by id
workerByPeerId — base58-decode the queried peerId, look up
via store.workersByPeerId
myWorkers — store.workersByOwner intersection
myWorkersCount — count of myWorkers
workerDelegationInfo — entity worker + sum of delegations
workersByOwner — id/name/peerId/ownerId/bond/claimableReward
myDelegations — store.delegationsByOwner -> worker projection
delegationsByOwner — flat delegation list
Worker fixture uses bs58-encoded peerId from the on-chain bytes;
numeric workerId becomes the GraphQL string id; ownerId is the
registrar's lower-cased address. Off-chain metric fields (apr,
uptime, queries, served data) stay synthetic but are now seeded by
workerId so each worker has visually distinct numbers.
bondAmount + delegationLimitCoefficient are read once from
NetworkController (cached) so the cap math matches the contract.
src/indexer/operations/network.ts:
registerNetworkResolvers({ client, deployments, getLastBlock })
- settings: NetworkController.bondAmount() via viem readContract.
- squidNetworkHeight: indexer.lastBlock.
- currentEpoch: NetworkController.epochNumber/nextEpoch/epochLength.
src/indexer/operations/token.ts:
registerTokenResolvers({ client, deployments, store })
- sources / accountsByOwner: MockSQD.balanceOf via viem.
- vestingByAddress / vestingsByAccount: still null/empty (vesting
deploy deferred).
src/indexer/dispatcher.ts: dispatch() is async-aware — chains can
return Promises and the server awaits them. Resolver type widened to
allow Promise<unknown> returns. Parity test updated.
src/indexer/server.ts: wraps dispatch(...) in an async IIFE so the
HTTP handler can await it and surface 500s on resolver errors.
bs58 added as a dependency. startMockStack() registers all three
resolver groups after starting the indexer; clearResolvers() at the
top wipes any prior registrations so hot-restarts don't leak.
Smoke-tested via scripts/_resolver-smoke.ts:
allWorkers: 2 workers, ids 1 + 2, peerIds 'QmVhq…' base58
myWorkers([carol]): 2 workers
myDelegations([bob]): 1 worker with delegations[]
sources(bob): balance=150_000 SQD (200k initial − 50k delegated;
chain-derived, no longer hand-rolled)
settings: bondAmount from NetworkController
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Phase 10 of the original wagmi-viem-testing plan: replace the legacy
in-process mock servers with the chain-derived mock-stack.
packages/server/src/main.mock.ts (rewrite):
Boots startMockStack({ autoPrepare: true, rpcPort: 8545,
graphqlPort: 4321 }), then setRuntimeOverride
with the resulting URLs + deployments map. Hands off to main.ts
unchanged. SIGINT/SIGTERM unwind the stack cleanly.
packages/server/package.json:
Add @subsquid/mock-stack as a runtime dep.
Deleted:
packages/server/src/services/mockGraphqlServer.ts (625 lines of
hand-rolled fixtures with placeholder worker IDs)
packages/server/src/services/mockRpcServer.ts (570 lines of
selector-dispatch eth_call shim that drifted from on-chain state)
packages/client/src/test/__tests__/anvil-balance.integration.test.ts:
Bob's seeded balance is now 150_000 SQD (200k initial − 50k
delegated to Carol's worker during seed). ETH balance asserted
within 0.1 ETH of 10 to allow for gas spent on approve + deposit.
Verified:
- pnpm tsc + pnpm lint clean.
- pnpm mock boots: 15 contracts deployed, GraphQL on 4321, anvil
on 8545, server log shows mock-stack-deployed addresses for
SQD/WORKER_REGISTRATION/STAKING/etc. (no longer mainnet defaults).
- pnpm test: all 12 tests pass.
Now the GraphQL surface is consistent with on-chain state: worker IDs
are real numeric strings (1, 2), peerIds are base58 multihashes from
contract bytes, owner addresses match registered events, delegation
amounts come from Staking.Deposited logs.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
worker-registration.integration.test.ts: registers a new worker on
chain as Alice (mints + approves + register), waits for the indexer
tail loop to ingest the WorkerRegistered log, queries allWorkers via
the GraphQL endpoint and asserts:
- the new worker's peerId (base58 of the supplied multihash bytes)
is present
- the count grew by 1 over the seeded baseline
- ownerId matches Alice's lower-cased address
- id is the next numeric workerId after the baseline
This is the proof that the chain → indexer → resolver pipeline is
fully connected end-to-end without any synthetic intervention.
README.md updated to describe what 'pnpm mock' actually does — full
mock-stack with deployed contracts, persona seeding, log-driven
indexer, chain-derived GraphQL surface.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Deferred work from the Phase 10 plan: VestingFactory creates a
SubsquidVesting contract for Dave at seed time, the indexer ingests
VestingCreated events, and the token-squid resolvers project the
vesting entity into the GraphQL accounts shape with a chain-derived
locked balance.
src/seed.ts:
createVestingForDave() — calls VestingFactory.createVesting (the
deployer holds VESTING_CREATOR_ROLE from construction) with
start=now-30d, duration=365d, immediateRelease=10%, total=1M SQD.
Pulls the Vesting contract address from the receipt's
VestingCreated event topic, then mints 1M SQD into it so
release() has something to release.
src/indexer/entities.ts:
Added VestingEntity { id, beneficiaryId, startTimestamp,
durationSeconds, expectedTotalAmount }
+ vestings + vestingsByBeneficiary maps. clearEntities() updated.
src/indexer/mappings/vestingFactory.ts:
Decodes VestingCreated logs and projects into the entity store.
src/indexer/runtime.ts:
Subscribes to deployments.VESTING_FACTORY in addition to
WORKER_REGISTRATION + STAKING.
src/indexer/operations/token.ts:
- vestingByAddress: looks up by vesting contract id, returns the
projected account (id, type=VESTING, balance from MockSQD.balanceOf,
owner = beneficiary).
- vestingsByAccount: returns all vestings for a beneficiary.
- accountsByOwner: returns the USER account + any VESTING accounts
where owner=address (matches the squid OR filter).
Resolvers no longer return null/empty placeholders.
Smoke-tested:
Dave has USER account (50_000 SQD) + VESTING account
(1_000_000 SQD locked, owner=Dave). vestingByAddress(<vesting addr>)
returns the same account.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
…resolvers
The mini-indexer projected every contract event into a TS Map of
hand-rolled entities (workers, delegations, portal pool deposits, etc.)
which resolvers then read from. Two stores to keep in sync, ~800 lines of
mapping code.
The contracts already expose enumeration views (getActiveWorkerIds,
getOwnedWorkers, Staking.delegates, getPortalCount, …) and per-record
view functions (getWorker, Staking.getDeposit, Pool.getPoolInfo, …) that
make the indexer mostly redundant. The only thing the indexer is still
needed for is enumerating dynamic addresses that aren't enumerable
on-chain: portal pool addresses (PortalPoolFactory.PoolCreated) and
vesting contract addresses keyed by beneficiary (VestingFactory.VestingCreated).
This commit slims the indexer to just that:
src/indexer/registry.ts (new):
IndexerRegistry { portalPools, portalPoolCreatedAt, vestings,
vestingsByBeneficiary }
+ helpers (createRegistry, clearRegistry, rememberPortalPool,
rememberVesting). That's the entire indexer state.
src/indexer/runtime.ts (rewrite):
Subscribes only to PortalPoolFactory + VestingFactory. Bootstrap +
tail loop unchanged in shape; mappings just call the slim
remember*() helpers.
src/indexer/mappings/{portalFactory,vestingFactory}.ts:
Slimmed to a single rememberX() call each.
src/indexer/operations/workers.ts (rewrite):
Read-through. allWorkers → WorkerRegistration.getActiveWorkerIds()
+ getWorker(id) + Staking.delegated(id) per worker.
myWorkers → getOwnedWorkers(owner) + project per id.
myDelegations → Staking.delegates(staker) + getDeposit(staker, id).
workerByPeerId → WorkerRegistration.workerIds(peerIdBytes).
No entity store consulted.
src/indexer/operations/token.ts (rewrite):
sources / accountsByOwner → MockSQD.balanceOf via viem;
delegationCount derived from Staking.delegates(staker).length.
vestingByAddress / vestingsByAccount → registry tells us the
vesting addresses, MockSQD.balanceOf(<vesting>) gives the locked
amount.
src/indexer/operations/portals.ts (rewrite):
portalPools / portalPoolById → for each id in registry,
Pool.getPoolInfo() + getActiveStake() + getRewardToken().
poolProvidersByOwner → for each pool × owner, getProviderStake(owner).
gatewaysSummary.totalPortalPoolTvl → sum of getActiveStake() across pools.
poolEvents → empty (per-event timeline isn't reproducible from views;
cosmetic in mock mode anyway).
Deleted:
src/indexer/entities.ts (5 KB / 158 lines)
src/indexer/mappings/staking.ts (~70 lines)
src/indexer/mappings/workerRegistration.ts (~80 lines)
src/indexer/mappings/portalPool.ts (~70 lines)
src/index.ts:
Public handle exposes handle.indexer.registry instead of
handle.indexer.store. EntityStore + DelegationEntity +
WorkerEntity + PortalPoolEntity + PortalPoolDepositEntity +
VestingEntity exports gone; IndexerRegistry replaces them.
Verified:
- pnpm --filter @subsquid/mock-stack stack:prepare clean.
- resolver smoke: allWorkers/myWorkers/myDelegations/sources/
settings/accountsByOwner/portalPools/poolProvidersByOwner/
gatewaysSummary all return correct chain-derived data.
- pnpm tsc + pnpm lint clean.
- pnpm test: 13/13 pass. The worker-registration integration test
runs in ~150ms now (was ~2700ms) because there's no indexer wait;
the resolver reads getActiveWorkerIds directly so newly-registered
workers are visible on the next anvil block.
- pnpm mock boots cleanly.
Net: ~600 lines of indexer/projection code removed; zero drift risk
between contract storage and resolver responses.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
User reported 'Failed running src/main.mock.ts. Waiting for file
changes before restarting...' with no further info — the tsx --watch
flag was swallowing the actual error. Three fixes so a single
`pnpm mock` either works end-to-end or fails with a clear,
actionable message.
packages/server/package.json:
Drop --watch from the mock script. There's no reason to hot-reload
the server in mock mode (every save tears down anvil + the booted
state) and tsx's --watch error UX hides the real failure behind a
cryptic 'waiting for file changes' message.
packages/server/src/main.mock.ts:
- Pre-flight: ensure 'forge' and 'anvil' are on PATH before doing
anything; if not, print the foundryup install snippet and exit 1.
- Wrap startMockStack() in try/catch so any deploy harness or
indexer bootstrap failure surfaces with a hint about the most
common causes (port already taken, submodule missing, forge build
error) plus the original stack trace.
packages/mock-stack/src/chain.ts:
ensurePortFree(port) probes the requested port up front; on
EADDRINUSE the error explains the cause ('previous pnpm mock still
running, find with lsof -i:8545') instead of letting anvil's spawn
fail silently and waitForAnvil report a generic 10s timeout.
packages/mock-stack/src/indexer/server.ts:
Same EADDRINUSE → friendly-message wrap for the GraphQL HTTP server's
listen() call (port 4321 in mock mode).
Verified:
- Cold start: rm .anvil-state.json + .deployments.json, run
'pnpm mock' once. forge build runs, deploy harness runs, anvil +
GraphQL come up, server reports 'Server is running on port 3001'.
- Stale-process failure: leaving a previous mock running and
starting a second yields a clear 'Port 8545 is already in use'
error and exit 1, no 'waiting for file changes' dangling state.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
User reported a fresh-checkout crash:
Error: Foundry artifact not found:
sqd-portal-contracts/out/PortalPoolImplementation.sol/PortalPoolImplementation.json
The portal/worker/token resolver modules eagerly read ABI artefacts at
top-level module load. On a fresh checkout the portal out/ dir doesn't
exist yet — startMockStack()'s autoPrepare runs forge build, but only
*after* the resolver modules have already been imported and crashed.
This commit fixes the crash AND splits the dev workflow into two
processes so the chain (slow, long-lived) and the app (fast, hot-
reloading) restart independently.
Lazy ABIs (fixes the crash):
packages/mock-stack/src/indexer/operations/{portals,token,workers}.ts:
Wrap each top-level networkArtifact()/portalArtifact() call in a
lazy getter so module import is free even when forge artefacts
aren't on disk yet. The first resolver call triggers the read.
New 'mock:chain' (long-lived):
packages/mock-stack/scripts/serve.ts spawns autoPrepare + anvil:8545
+ GraphQL:4321 + a heartbeat loop. Holds the snapshot files. Foundry
preflight + actionable error on EADDRINUSE / forge failure.
Wired as 'pnpm --filter @subsquid/mock-stack mock:chain' and root
'pnpm mock:chain'.
New 'mock:app' (hot-reload):
packages/server/src/main.app.ts replaces main.mock.ts. Doesn't own
the chain — polls .deployments.json on disk (waits if it's not there
yet, so order doesn't matter), then probes the anvil + GraphQL
endpoints with a 60s budget, then setRuntimeOverride() + delegates
to main.ts. Runs under tsx --watch so saving a server file doesn't
tear down anvil.
Wired as 'pnpm --filter @subsquid/server mock:app' (server) +
'pnpm --filter @subsquid/client mock:app' (vite mock mode) + root
'pnpm mock:app'.
Convenience:
Root 'pnpm mock' runs both 'mock:chain' and 'mock:app' as parallel
turbo tasks for one-off work; the recommended dev loop is
'pnpm mock:chain' in one terminal + 'pnpm mock:app' in another.
Verified end-to-end:
- Cold (no .deployments.json, no out/): 'pnpm mock:chain' takes ~4s
to print 'chain is ready' (forge build skipped since cached); the
chain is fully populated (15 contracts, persona seeding,
portal pool, vesting). 'pnpm mock:app' connects in ~3s.
- Plain 'pnpm mock' from cold: both halves come up together in
parallel; chain reaches 'ready', server prints 'Server is running
on port 3001'.
- GraphQL roundtrip: curl POST to :4321 returns the chain-derived
allWorkers list with real workerIds + base58 peerIds.
- pnpm tsc + pnpm lint clean. pnpm test 13/13 green.
Docs: README.md + AGENTS.md describe the new two-process layout.
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Front the mock anvil with a tiny JSON-RPC HTTP shim that splices `l1BlockNumber := number` into every `eth_getBlockBy*` response when the upstream omits it. From the consumer's POV the mock chain now looks like a real Arbitrum nitro RPC: any code reading `(block as any).l1BlockNumber` works transparently in mock mode without a special branch, and on Arbitrum's well-known quirk where `block.number` already equals the L1 block number, the value space is consistent end-to-end. Anvil 1.5+ already exposes the field natively, but the shim is defensive against older anvil versions and any other test chain plugged into `startMockStack`. Existing l1BlockNumber values from upstream are passed through unchanged; non-block methods are forwarded verbatim; batched requests are handled by pairing request/response IDs. Lifecycle is owned by `startMockStack`: anvil now binds an ephemeral port and the shim takes the caller-requested public port (e.g. 8545 for `pnpm mock:chain`). Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
…ation - Add console.error to tRPC onError so all errors are always logged to stderr, not just when Sentry is configured - Add per-procedure timing middleware in trpc.ts that logs --> / <-- with elapsed ms for every query/mutation, making slow calls immediately visible - Add HTTP request/response logging on the Node server (method, path, status, ms) to complement the tRPC-level logs - Set server.requestTimeout = 10_000 ms so the Node http.Server closes stale connections that were never aborted by the client - Add a 4 500 ms AbortSignal to every Squid GraphQL fetch so the server returns a proper timeout error before the browser's 5 s client abort fires; previously slow GraphQL responses caused frontend timeouts with no corresponding server-side error logged - Improve GraphQL error surfacing: check HTTP status before parsing JSON, and join all GraphQL error messages instead of only the first - Wire up the pre-existing (but dead) errorToastTimestamps dedup map in client.ts so duplicate error toasts are suppressed within 3 s - Add MutationCache.onError handler so mutation errors also show a toast Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
fix: improve server error logging and fix request timeout misconfiguration
The mock myDelegations resolver was ignoring the peerId variable, returning all delegated workers instead of just the one matching the current worker page. Worker.tsx takes delegationsData?.[0] (safe in production where the query is filtered to one worker), so in mock mode the undelegate dialog showed the deposit for the wrong worker — causing the Staking contract to revert with "Insufficient staked amount" when the submitted amount exceeded what was actually staked in the viewed worker. Fix resolves peerId → workerId via WorkerRegistration.workerIds() and skips non-matching workers, mirroring the peerId_eq filter in the production myDelegations GraphQL query. Made-with: Cursor
…h local RPC The shim was rejecting OPTIONS preflight requests with 405 and no Access-Control-Allow-Origin header, causing browsers to block every cross-origin JSON-RPC call from the dev client (localhost:3005) to the mock chain (localhost:8545). Set the CORS headers on every response and short-circuit OPTIONS with 204 so the browser's preflight succeeds. Made-with: Cursor
Add `blockTime` option to `SpawnAnvilOpts` / `StartMockStackOpts` that passes `--block-time <N>` to anvil for interval mining. `pnpm mock:chain` now sets `blockTime: 12`; test callers are unaffected (instant mining). Made-with: Cursor
…ISO strings The UI computes epoch end time as (epoch.end - lastBlockL1 + 1) * blockTimeL1 + new Date(lastBlockTimestampL1) so epoch.start/end must be block numbers. The resolver was passing them through blockTimestamp(), producing ISO strings and causing NaN in the UI. Made-with: Cursor
Made-with: Cursor
pnpm mock:chain now runs two processes under one terminal:
- chain (anvil + shim at :8545) — inline, stable across reloads
- indexer (indexer + GraphQL at :4321) — spawned via `tsx watch`, so
changes to resolver / indexer source files restart only this child
startMockStack gains an `externalRpcUrl` option that skips spawning anvil
and attaches the indexer to an already-running chain. _indexer.ts is the
private entry point used by the tsx watch child.
Made-with: Cursor
…pochLength NetworkController.epochLength() was returning 0 because .anvil-state.json was generated from an older contract build with a different storage layout. - Drop stale nextEpoch()/epochNumber() reads; compute epoch number and boundaries from getLastBlock() / epochLength() directly - Throw a clear actionable error (mentioning `pnpm stack:rebuild`) instead of silently falling back to a magic number when epochLength is 0 Made-with: Cursor
…blockTimeL1 in ms Two bugs: - `epochLength` storage variable is deprecated (holds lock period, always 0); the actual epoch length is `workerEpochLength`. Switch ABI + resolver to read `workerEpochLength` alongside the live `epochNumber()` / `nextEpoch()` calls. - `blockTimeL1` was 12 (seconds) but the UI adds it directly to a ms timestamp, so it must be 12_000 ms. Fixed in both network and synthetic resolvers. Made-with: Cursor
All magic numbers (CHAIN_ID, RPC_PORT, GRAPHQL_PORT, BLOCK_TIME_SEC, EPOCH_LENGTH_BLOCKS, WORKER/STAKER_REWARD_PER_CYCLE_SQD) are now exported from a single src/config.ts. Runtime values take effect on the next `pnpm mock:chain` restart; deploy-time values (★) also require `pnpm stack:rebuild`. No behaviour changes. Made-with: Cursor
wagmi's built-in reconnect calls connector.connect({ isReconnecting: true }),
which the mock() connector rejects when provider.connected === false (resets
on every page load, since defaultConnected is false).
Fix:
- Override connect/disconnect on each persona connector to write/clear the
chosen connector id ('mock-persona-N') in localStorage under sqd.mock.persona.
- Also override connector id to our stable 'mock-persona-N' (wagmi's mock()
hardcodes id:'mock' for every instance, breaking config.connectors lookup).
- Add MockPersonaManager (null component inside WagmiProvider + QueryClientProvider)
that calls useMockPersonaRestore: once wagmi reaches status 'disconnected',
reads localStorage and calls connect() directly on the stored connector.
- Persist SourceContext.selectedSourceId per wallet address in localStorage
so the active gateway source survives page reloads too.
Made-with: Cursor
…orkers Unify the seed step and the auto-distributor behind a single `commitRewardChunks` primitive that mirrors the production reward bot: each commit covers exactly REWARD_BLOCKS_PER_COMMIT (10) blocks, ranges are strictly contiguous and never overlap, and every commit pays every active worker the per-cycle amounts. Both phases now share the same cadence and payload — preseeding emits the first chunk, the auto-distributor keeps emitting the rest. Also fix the indexer's per-delegation `claimableReward` projection: it used to be hardcoded to '0', which made the Delegations-page "Total reward" column disagree with the on-chain `Staking.claimable` shown on the Assets page. The mock indexer now replays `Distributed` events from DistributedRewardsDistribution and attributes the staker portion pro-rata to each delegation's deposit, matching the on-chain pending reward exactly. Made-with: Cursor
- PageTitle: remove Box wrapper inside h4 Typography; apply color directly on Typography - NetworkPageTitle: move layout div outside h1 Typography; Typography now wraps only the title text - Summary.ColumnLabel: render as component="div" so block children are valid; fix SquaredChip label Typography to use component="span" - NoItems: render children directly instead of wrapping them in a Typography p element; Typography is only used for the default message fallback - PaginatedTable: pass emptyMessage via message prop instead of wrapping in Typography inside NoItems - GatewaysPage: use NoItems message prop directly instead of Typography child - AutoExtension: use component="span" on Typography inside FormControlLabel label to avoid p-inside-label - DashboardPage: use Stack component="span" inside Tab label to avoid div-inside-span - PageTabs: use Box component="span" inside Tab label to avoid div-inside-span Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
fix(client): correct invalid DOM nesting warnings
… logger - Add packages/server/src/logger.ts: pino logger with ISO timestamps and structured JSON output. Sentry is wired at the logger level — every error() and fatal() call automatically calls Sentry.captureException / captureMessage when a Sentry client is initialised, so callers never need to call Sentry directly. - Update main.ts: log structured startup context via logger.info; replace inline Sentry.captureException calls in onError / unhandledRejection / uncaughtException with logger.error / logger.fatal (Sentry forwarding is now handled by the logger). - Update trpc.ts: replace console.log/error with logger.debug/warn/error, include structured fields (trpcType, trpcPath, ms). - Update main.app.ts: replace console.log/error with logger.info/error, include structured fields (deploymentsPath, url, label, contractCount). - Add pino dependency to packages/server. Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
feat(server): replace console.log with pino, connect Sentry to logger
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
…r-squid URL overrides Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
…mple) Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
…s real-indexer modes) Co-authored-by: Alexander Belopashentsev <belopash@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Set
MOCK_WALLET=truein.envto activate a fully offline development stack — no browser wallet, no live APIs needed.What it does
pnpm devwithMOCK_WALLET=truestarts two extra in-process servers and replaces RainbowKit with a simple account picker:mockconnector — no MetaMask/WalletConnect neededFour fixture personas
Account switching uses the real disconnect → connect → select flow. State (transactions, balances) lives only for the dev server process lifetime.
Usage
pnpm dev # app starts at localhost:3005 — click Connect Wallet to pick a personaKey files
packages/server/src/services/mockRpcServer.ts— EIP-1193 server with in-memory EVM statepackages/server/src/services/mockGraphqlServer.ts— fixture responses for all 30+ GraphQL operationspackages/client/src/components/MockConnectDialog.tsx— persona picker dialogpackages/client/src/config.ts—buildMockConfig(), sessionStorage-based account selection