Skip to content

About

Local test stack for the MetaLeX private chain design: a ZKsync OS validium, Prividium Core and the Cybercorps contracts on one machine. Fake proofs and published development keys; not a deployment.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MetaLeX private-chain local stack

One command brings up the whole private-chain design from MetaLeX's critical review of that design on one machine, onboards two issuers through the real company factory, and checks the result. The review and the experiments behind it are kept with the MetaLeX webapp, which is a private repository.

./stack up
./stack status
./stack smoke
./stack down --all

A clean up took 41 to 52 seconds here once the prerequisites were built (see docs/PREREQUISITES.md). It is a demonstration environment: batch proofs are fake, every key is a published development key, and nothing is hosted.

What up builds

Step Result
1. L1 Anvil 1.5.1 loaded with the upstream ZKsync OS v31 snapshot, 1-second blocks, history kept
2. L1 configuration Validium mode (setPubdataPricingMode, setDAValidatorPair); PrividiumTransactionFilterer set with deposits enabled; ValidatorTimelock upgraded to MultisigCommitter with a custom signing set, the recovery node's signer and threshold 1; a two-party Safe and a recovery timelock (upstream Governance) that owns the chain admin, filterer, committer, ecosystem governance and ecosystem admin
3. Nodes Main node with batch verification on; recovery node as an external node that replays and co-signs every batch
4. Cybercorps release The unmodified pinned contracts: CyberCorpFactory, IssuanceManagerFactory, CyberCorpSingleFactory, DealManagerFactory, RoundManagerFactory, CyberAgreementRegistry, CertificateUriBuilder with its image builder, HolderCapCondition behind a proxy, and a test ERC20 as the payment asset (contracts/StackRelease.s.sol). Also Multicall3, the era-contracts copy (contracts/Multicall3.sol)
5. Prividium Core Organizations off, policy listener on, its own PostgreSQL database for the run
6. Policy on Main node restarted so every transaction and simulation passes Core's policy service. Core then gets its roles, users and the app server's machine application (below)
7. Two issuers Each officer calls CyberCorpFactory.deployCyberCorp through Core; the script then registers the company's manager, corp, deal manager, round manager and beacons in Core and adds the issuer's rows. Each officer creates a printer, issues one certificate and deploys scrip
8. Smoke test 12 checks, listed below

Where to connect

What Address Notes
Core RPC http://localhost:18000/rpc The only entry point for wallets and the app. Needs Authorization: Bearer <session token>; sign in with SIWE at /api/siwe-messages and /api/auth/login/crypto-native
Core RPC, as the app server http://localhost:18000/rpc Send the key from ~/.cache/metalex-x0/stack-runs/current/app-server.key in an X-API-Key header. Loopback only, valid 7 days. It reads all state and submits signed transactions for policy to judge
Core API http://localhost:18000/api Admin wallet is CORE_ADMIN_PK in config/stack.env
L1 RPC http://127.0.0.1:18545 Chain ID 31337
Main node RPC http://127.0.0.1:13050 Unauthenticated. It answers eth_getStorageAt for anyone. Never expose it
Addresses ~/.cache/metalex-x0/stack-runs/current/state.json L1 contracts, authority contracts, release contracts (including multicall and holderCapCondition), Core role and user IDs, both companies

Private chain ID is 506. Core binds to all network interfaces on ports 18000, 18010 and 19091, and its admin key is a published one. Run the stack only on a trusted network, and stop it when done.

Smoke checks

  1. Holder A scripifies 100 units through Core and reads the balance.
  2. Holder A recertifies 20 units.
  3. Holder A reads its own certificate count.
  4. Holder A is refused six direct reads: issuer B's printer, certificate, scrip, manager and corp, and a certificate of its own issuer by raw ID.
  5. Holder B cannot use issuer A's manager.
  6. Officer A cannot call a setter that has no Core row (setRestrictionHook).
  7. The raw node port answers a storage read without credentials (a reminder, expected to pass).
  8. The app server's machine key reads both issuers' records, storage and code through Core.
  9. Multicall3 batches reads of both companies and HolderCapCondition in one call.
  10. The app server relays a holder's transaction that was signed with no chain access, and the policy service still refuses another holder's.
  11. Batches settle on L1.
  12. Every commit went through commitBatchesMultisig with the recovery node's signature.
  13. The certificate text and the holder's address are absent from the settlement transactions.
  14. The chain admin, filterer and committer are owned by the recovery timelock.
  15. The old chain owner key can no longer remove the L1 filterer.

Result on 10 November 2026: 15 of 15 on a clean run (docs/evidence/up-run-20261110114452.txt). The first 12 checks passed on two earlier clean runs (docs/evidence/up-run-20261110103248.txt).

What it found

  • The release needs one setting on a new chain. CyberCorpFactory.initialize points lexchexAuth at a fixed address that exists only on the public chains, and company creation calls it. The stack clears it with the owner setter setLexchexAuth(0). LeXcheX is not deployed, so public rounds that mint a LeXcheX badge will not work here.
  • Company creation works through Core under one umbrella row. deployCyberCorp creates five contracts in one transaction (about 2.43 million gas) and none of them needs to be registered first. Deploying the release itself took about 111 million gas on the private chain.
  • A bridge lock works through Core (docs/evidence/bridge-lock-through-core.txt). It needs a row for the scrip's approve and an umbrella on L2AssetRouter.withdraw(bytes32,bytes). Nobody can call the vault's registerToken through Core, because Core's built-in description of that contract omits the function; the router registers the token itself when the holder passes the asset ID computed offline (keccak256(abi.encode(chainId, vault, token))). The holder can read the receipt and the message proof through Core. An umbrella on withdraw lets a holder lock any token it holds, so the refuse-the-vault hook stays the only per-token control.
  • The app server needs a Core machine application with two permissions. full_read_access alone gives logs, blocks and receipts but refuses eth_call, storage, code and nonce reads; full_sequencer_rpc_access adds those and lets the server submit other wallets' signed transactions, which the policy service still judges by signer. A machine application answers nothing until its IP allowlist has an entry.
  • The webapp's send-path guard needs two contracts a new chain lacks. It batches reads through Multicall3, and it reads HolderCapCondition, which on the public chains sits at one fixed address that a new chain cannot reproduce. The stack deploys both in the release step. With them, the unmodified guard from the webapp's develop branch passed for a healthy company and refused a changed manager, using this stack's machine key. The webapp still needs that address per chain.
  • Deploy before policy is on. Afterwards only the zone admin can deploy, through Core, and the policy service refused a proxy whose constructor calls its implementation.
  • Core refuses some published test keys as user wallets (Anvil account 8, for example).
  • Two shell variable names break tools. Any variable starting with FOUNDRY_ makes cast abort, and Core reads METRICS_PORT and similar names from the environment before its .env. The stack avoids the first and starts Core with a clean environment.

What it does not do

  • No real proofs, no v32 chain, no public testnet, no hosting.
  • The webapp is not part of up. Its server code for the private chain has been run against the stack from scripts, and one screen (create a company) has been run in a browser with a Privy embedded wallet. No injected wallet has been tried against Core.
  • No indexer runs here, and the webapp's read screens depend on one. The webapp's indexer was run once against the recovery node from a scratch copy, which is not in this repository.
  • Deals, rounds, LeXcheX, extensions and conditions are deployed or deployable but have no Core rows and were not exercised.
  • The L1 finalize, public transfer and return half of the bridge is not part of up; it was run without Core in experiment X2.
  • Prerequisites are not fetched or built; see docs/PREREQUISITES.md.

Layout

Path Content
stack Entry script: up, smoke, status, down
config/stack.env Paths, ports, keys and the part each test account plays
lib/stacklib.py L1 signing set and authority arrangement, Core configuration, issuer onboarding, smoke test
contracts/StackRelease.s.sol Deployment of the Cybercorps release for a chain the repository's own script does not know
contracts/Multicall3.sol Unmodified copy from era-contracts (MIT), deployed with the release
docs/ Prerequisites and evidence from the runs
s1-privy-harness/ A Vite test page: a Privy embedded wallet signs a transaction that the local server prepared, and the server relays it. provision.sh registers the wallet in Core

What this repository leaves out

  • The scratch copy of the webapp's indexer that one experiment used. It is private application code.
  • The result file of the wallet signing test, which holds a wallet address and a login identifier.
  • Every upstream build. docs/PREREQUISITES.md names the pinned versions and where each is expected on disk.

Paths in the scripts are those of the machine the stack was built on. config/stack.env sets most of them.

About

Local test stack for the MetaLeX private chain design: a ZKsync OS validium, Prividium Core and the Cybercorps contracts on one machine. Fake proofs and published development keys; not a deployment.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages