npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

z2-zetrix-mcp

v0.1.5

Published

MCP server for the Z2 rollup: deposit/withdraw across L1 (Zetrix) and L2 (Besu) for native ZETRIX, ZTP-20, and ZTP-721, plus L2 info.

Readme

z2-zetrix-mcp

An MCP server for the Z2 optimistic rollup. It exposes the L1 (Zetrix) and L2 (Besu EVM) sides and the bridge between them as typed tools, so an agent (Claude Desktop/Code, Cursor, …) can deposit, withdraw, and read L2 state for native ZETRIX, ZTP-20, and ZTP-721 — without hand-crafting node calls, leaf hashes, or confirmation polling.

It's intent-driven: a user says "withdraw 5 ZETRIX to my account ZTX3…" and the server resolves the asset, child token, burn, confirmation wait, and claim.

Multi-withdrawal claims are supported. When several withdrawals share one assertion window, its send root is a Merkle tree over all of them (not root == leaf). The server enumerates the window, rebuilds that tree, and derives the exit's proof automatically. It only needs the original ZTX3 token of each other non-native withdrawal in the assertion — pass them as windowTokens on the claim (native siblings need none). A sibling whose token isn't supplied fails closed rather than producing a wrong proof. See §5 and docs/03-bridge-flows.md §6.

  • Read-only by default. Write (signing) tools appear only when Z2_ALLOW_WRITES=true.
  • Fail-closed claims. A withdrawal is claimed only after its leaf is proven to match the on-chain send root.
  • No custody. Keys are passed per call (or via env), never stored, never logged.

Full design docs are in docs/ (00 overview → 08 deployment).


1. Install

Requires Node.js ≥ 20. Published to npm as z2-zetrix-mcp — no clone or build:

npx z2-zetrix-mcp          # run on demand (what MCP clients use)
npm install -g z2-zetrix-mcp   # or install the `z2-zetrix-mcp` binary globally

You normally don't run it by hand — the MCP client launches it for you (see §2).

git clone https://github.com/Zetrix-Chain/z2-zetrix-mcp.git
cd z2-zetrix-mcp && npm install && npm run build   # dist/index.js is the entry
npm test                                           # unit tests (offline)
npm run dev                                        # run from source (tsx, no build)

Zero-config on testnet

You don't need to configure anything to use testnet. The package ships a bundled profile with the current testnet addresses and endpoints, so npx z2-zetrix-mcp runs read-only out of the box — no profile file, no endpoint URLs, no contract addresses to look up. You only add config to enable writes (a signing key, §2) or to target a different deployment (e.g. mainnet).

Deployment profile (only for non-default deployments)

A profile JSON holds a deployment's contract addresses (L1 Bridge/Outbox/Rollup, L2 L2Bridge/WithdrawalManager/StateReceiver/wZETRIX) + endpoints + chainId. Z2_PROFILE defaults to the bundled profiles/testnet.example.json; point it at your own file for another deployment:

# save a profile JSON anywhere and set Z2_PROFILE to its absolute path
# (start from the bundled profiles/testnet.example.json as a template)

At startup the server validates the profile against the live chain (Bridge wiring + L2Bridge getters + chainId) and refuses to start on a mismatch — so it can never act against a stale/wrong deployment. Regenerate it from the contract repo's deployed-addresses-l1-<date>.json after any fresh redeploy.


2. MCP server settings

The server speaks MCP over stdio. Register it in your MCP client. On testnet you need no env at all — it defaults to the bundled testnet profile + endpoints, read-only.

Claude Code

# testnet, read-only — zero config
claude mcp add z2-zetrix-mcp -- npx -y z2-zetrix-mcp

# to enable deposits/withdrawals, add a signing key:
claude mcp add z2-zetrix-mcp -e Z2_ALLOW_WRITES=true -e Z2_L1_SIGNER_KEY=priv… -- npx -y z2-zetrix-mcp

Claude Desktop / Cursor (JSON config)

{
  "mcpServers": {
    "z2-zetrix-mcp": {
      "command": "npx",
      "args": ["-y", "z2-zetrix-mcp"]
      // testnet read-only needs no env. Add an "env" block (below) to enable writes
      // or to target another deployment.
    }
  }
}

Installed globally (npm install -g z2-zetrix-mcp)? Use "command": "z2-zetrix-mcp", "args": [] — or keep npx, which also finds the global bin. From source, use "command": "node", "args": ["/abs/path/dist/index.js"].

To enable writes (deposit/withdraw/claim), set Z2_ALLOW_WRITES=true and provide a signing key — per tool call (preferred) or as an env fallback. You only need the key(s) for what you do: L1 key signs deposits/claims; L2 key signs the withdrawal burn (see §4/§5).

"env": {
  "Z2_ALLOW_WRITES": "true",
  "Z2_L1_SIGNER_KEY": "priv…",   // Zetrix (L1) key — deposits, mapToken, claim
  "Z2_L2_SIGNER_KEY": "0x…"      // EVM (L2) key — the withdrawal burn (omit if deposit-only)
}

To target another deployment (e.g. mainnet), also set Z2_PROFILE to your profile path (and optionally Z2_L1_NODE_URL / Z2_L2_RPC_URL to override the endpoints).

Security: prefer passing signer in the call over env keys (an env key is inherited by child processes / readable via /proc/<pid>/environ). Note that a key placed in a client config file (the JSON above / Claude Desktop / Cursor) is stored at rest on disk in that file — treat it like any other stored secret, or prefer per-call signers. For a shared HTTP-hosted instance, keep it read-only unless a scoped signer is deliberately provisioned behind TLS + auth. See docs/07-security.md.

Environment variables

| Var | Required | Default | Meaning | |-----|----------|---------|---------| | Z2_L1_NODE_URL | | test-node.zetrix.com | Zetrix L1 node host (no scheme). Override for another deployment. | | Z2_L2_RPC_URL | | https://z2-test-node.zetrix.com | Besu L2 JSON-RPC URL. Override for another deployment. | | Z2_PROFILE | | bundled testnet.example.json | Path to the deployment profile JSON. Override for mainnet/custom. | | Z2_L2_CHAIN_ID | | 1337 | L2 chainId | | Z2_L1_SECURE | | true | HTTPS to the L1 node | | Z2_ALLOW_WRITES | | false | Register the signing tools | | Z2_L1_SIGNER_KEY / Z2_L2_SIGNER_KEY | | – | Optional signer fallbacks | | Z2_CONFIRM_TIMEOUT_MS | | 900000 | Max wait for assertion confirmation (withdraw) | | Z2_POLL_INTERVAL_MS | | 45000 | Confirmation/relay poll cadence | | Z2_LOG_LEVEL | | info | Log level (stderr; keys always redacted) |


3. Tools

Read tools need no key. Write tools require a signer and appear only when Z2_ALLOW_WRITES=true. Every write tool supports dryRun: true — build + fee-estimate (and, for a claim, run the full leaf verification) and return what would be submitted, without sending.

Utility (pure/local)

| Tool | What it does | |------|--------------| | create_l2_account | Generate a new L2 (EVM) account — address + private key + mnemonic. Use the address as a deposit l2Recipient; keep the key to withdraw later. Nothing sent on-chain; key not stored (save it!). | | derive_l2_address | L1 ZTX3 token → its L2 EVM address (first 20 bytes of sha256(utf8(ztx3))) + 32-byte slot | | compute_leaf_hash | Compute an exit-leaf hash (pre-flight check for a claim) |

Read / query

| Tool | What it does | |------|--------------| | bridge_state | L1 Bridge wiring (owner/outbox/rollup/l2Counterpart) + native escrow + the L2 wZETRIX address + delayed-message count. Good liveness probe. | | l2_balance | An account's L2 balance. asset: native resolves wZETRIX automatically (no address needed); ztp20/ztp721 take the L1 ZTX3 token and resolve the L2 child. Use it to confirm a deposit minted. | | token_info | For a mapped L1 token: metadata + derived L2 root-token + deployed L2 child + bridge escrow | | withdrawal_status | Where an L2 withdrawal is + whether it's claimable: decodes the event, finds the covering assertion, checks confirmation, verifies the leaf vs getSendRoot. | | l1_query | Call any L1 contract query (escape hatch) | | l2_call | Call any L2 EVM view (escape hatch) | | get_l1_transaction | L1 tx outcome (CONFIRMED_OK/CONFIRMED_FAILED, ledgerSeq) | | get_l2_receipt | L2 receipt (status, block, logs) |

Write / workflow (need Z2_ALLOW_WRITES=true)

| Tool | What it does | |------|--------------| | deposit | Deposit native / ztp20 / ztp721 into L2. Auto-approves the Bridge for tokens (and refuses to deposit if the approve isn't confirmed). The relayer mints on L2 after L1 finality. | | withdraw | One-call L2→L1: burn on L2 → wait for the assertion to confirm → claim on L1. Returns both tx hashes, or a resumable handle if confirmation outlasts the timeout. | | start_withdrawal | Just the L2 burn (returns the decoded Withdrawal); use with withdrawal_status + claim_withdrawal for manual control | | claim_withdrawal | Claim a confirmed withdrawal on L1 (fail-closed). Rebuilds the Merkle proof automatically; for a multi-withdrawal assertion pass windowTokens (the other exits' ZTX3 tokens). | | map_token | Owner-only: register an L1 ZTP-20/721 so it can be bridged | | l1_invoke / l2_send | Signed contract-call escape hatches |

Full schemas + examples: docs/02-tools-reference.md.


4. How to use — DEPOSIT (L1 → L2)

No L2 address yet? Say "create an L2 account"create_l2_account returns a fresh 0x… address + private key. Deposit to that address (you only need the address to receive — the private key stays with you and is only needed later to withdraw). L1 and L2 are separate keypairs: your L1 ZTX3… account signs the deposit; the L2 0x… account just receives.

What the user prompts (natural language) → what the agent calls:

| You say | The agent calls | |---------|-----------------| | "Deposit 1 ZETRIX to my L2 address 0x6459…" | deposit { asset: "native", amount: "1000000", l2Recipient: "0x6459…" } | | "Deposit 100000 DUSD (token ZTX3Tcct…) to 0x6459…" | deposit { asset: "ztp20", token: "ZTX3Tcct…", amount: "100000", l2Recipient: "0x6459…" } | | "Bridge my NFT #3 of collection ZTX3b3Wh… to 0x6459…" | deposit { asset: "ztp721", token: "ZTX3b3Wh…", tokenId: "3", l2Recipient: "0x6459…" } |

What the user needs to provide in the prompt:

  • the asset — "ZETRIX"/"native", or the token (its L1 ZTX3… address or symbol) for ZTP-20/721;
  • the amount (native/ZTP-20, in base units — 1 ZETRIX = 1,000,000 ZETA) or tokenId (NFT);
  • the L2 recipient — the 0x… address to receive the minted asset;
  • an L1 signing key — passed as signer in the call or configured as Z2_L1_SIGNER_KEY.

The tool submits the L1 deposit (auto-approving the Bridge for tokens) and returns the L1 tx hash. The relayer mints on L2 automatically after ~3 L1 blocks.

On L2 the asset arrives as a token contract, not the chain's gas coin: native ZETRIX mints as the wZETRIX ERC-20, ZTP-20 as an ERC-20 child, ZTP-721 as an ERC-721 child. Confirm the mint with l2_balance { holder, asset } (native needs no address — it resolves wZETRIX for you):

l2_balance { holder: "0x…", asset: "native" }              → your wZETRIX balance
l2_balance { holder: "0x…", asset: "ztp20", token: "ZTX3…" } → your child-token balance

Preview a deposit first with dryRun: true.


5. How to use — WITHDRAW (L2 → L1)

Withdrawal is inherently multi-step (burn on L2 → wait for the optimistic-rollup confirmation window → claim on L1). The withdraw tool does all of it in one call.

What the user prompts → what the agent calls:

| You say | The agent calls | |---------|-----------------| | "Withdraw 0.5 ZETRIX back to my L1 account ZTX3WJqe…" | withdraw { asset: "native", amount: "500000", l1Recipient: "ZTX3WJqe…" } | | "Withdraw 50000 DUSD (ZTX3Tcct…) to ZTX3WJqe…" | withdraw { asset: "ztp20", token: "ZTX3Tcct…", amount: "50000", l1Recipient: "ZTX3WJqe…" } | | "Withdraw my bridged NFT #3 (ZTX3b3Wh…) to ZTX3WJqe…" | withdraw { asset: "ztp721", token: "ZTX3b3Wh…", tokenId: "3", l1Recipient: "ZTX3WJqe…" } |

What the user needs to provide in the prompt:

  • the asset — "ZETRIX"/"native", or the token's L1 ZTX3… address for ZTP-20/721 (the server resolves the L2 child to burn, and needs the ZTX3 to build the claim);
  • the amount (native/ZTP-20) or tokenId (NFT);
  • the L1 recipient — the ZTX3… address to receive the released asset;
  • an L2 signing key (l2Signer, owns the L2 asset — signs the burn) and an L1 signing key (l1Signer, pays the claim). Both can come from env (Z2_L2_SIGNER_KEY / Z2_L1_SIGNER_KEY).

The tool burns on L2, polls until the covering assertion is CONFIRMED (up to Z2_CONFIRM_TIMEOUT_MS, ~minutes), verifies the leaf against the on-chain send root, then claims — returning { l2WithdrawTx, l1ClaimTx, releasedTo, … }. If confirmation takes longer than the timeout, it returns a resumable handle; finish later with:

claim_withdrawal { l2WithdrawTx: "0x…", rootToken: "ZTX3Tcct…" }   // rootToken omitted for native

Prefer step-by-step control? Use start_withdrawal → poll withdrawal_status until claimable: trueclaim_withdrawal. Preview any step with dryRun: true (a claim dry-run still runs the full CONFIRMED + leaf-match verification).

Multi-withdrawal assertions (windowTokens)

Several withdrawals can be batched into one assertion — then its send root is a Merkle tree over all of them, and each claim needs its proof within that tree. The server builds this for you: it enumerates every withdrawal in the assertion window and rebuilds the tree. To hash the sibling leaves it needs their original ZTX3 tokens, which aren't recoverable from the L2 event — so supply the non-native ones via windowTokens:

claim_withdrawal {
  l2WithdrawTx: "0x…",                 // the exit you're claiming
  rootToken:    "ZTX3Tcct…",           // this exit's token (omit for native)
  windowTokens: ["ZTX3Tcct…", "ZTX3b3Wh…"]   // ZTX3 of the OTHER exits in the same assertion
}

Single-withdrawal assertions need no windowTokens (the proof is empty, root == leaf). If a sibling's token is missing the claim fails closed — it never guesses. The proof is always re-verified against the on-chain getSendRoot before anything is submitted.


6. Safety model

  • Read-only by default — write tools aren't registered unless Z2_ALLOW_WRITES=true.
  • Fail-closed claimsclaim/withdraw submit only if the assertion is CONFIRMED and the computed Merkle root equals getSendRoot; otherwise they raise (AssertionNotConfirmed / LeafMismatch / RootTokenUnresolved) and submit nothing.
  • dryRun on every write tool for preview-before-sign.
  • Deposit approve-gate — a token deposit is not attempted if the auto-approve isn't confirmed.
  • UNKNOWN ≠ success — an L1 write whose outcome isn't observed returns status: "UNKNOWN" (never a false success); poll the hash.
  • Keys — never persisted or logged; any signer that appears in an error is redacted.
  • Profile validation — startup aborts if the profile doesn't match live on-chain wiring.

See docs/07-security.md.


7. Testing

npm test                 # unit tests (offline; golden vectors + mocked adapters)

# live integration against a real deployment (gated; needs a profile pointing at it)
Z2_INTEGRATION=1 \
Z2_L1_NODE_URL=test-node.zetrix.com \
Z2_L2_RPC_URL=https://z2-test-node.zetrix.com \
Z2_PROFILE=./profiles/testnet.json \
npm run test:integration

The unit suite includes golden vectors seeded from proven testnet round-trips: ZTP-20 & ZTP-721 leaf hashes verified against the real on-chain send roots, and the real 3-leaf tree (native + ZTP-20 + ZTP-721 sharing send root e4a32ea5…) that locks the multi-leaf proof builder byte-for-byte against the reexecutor. The gated integration suite boots the real context (validating the profile against the live chain) and verifies withdrawal_status against those proven exits.


8. Architecture (one line)

stdio server → tools (zod-validated) → services (bridge/rollup/outbox/withdrawal/ token/mapping/proof) → adapters (zetrix-sdk-nodejs for L1, ethers for L2) → crypto (pure, byte-parity with the Outbox contract + reexecutor Merkle tree). See docs/01-architecture.md.