CLI for bridging assets between Mina and Zeko.
zeko-bridge is built around a single high-level bridge command, which signs, submits, waits, retries transient network checks, advances queued claims in order, and streams progress until the requested bridge completes.
Lower-level commands still exist, but they are advanced tools for debugging, recovery, and explicit operator control.
The package is maintained in the private Zeko monorepo and mirrored to zeko-labs/bridge-cli for public source, issues, and releases.
Enabled routes:
mina:testnet -> zeko:testnetzeko:testnet -> mina:testnet
Known but not yet enabled:
mina:mainnet -> zeko:mainnetzeko:mainnet -> mina:mainneteth:testnet -> zeko:testnetzeko:testnet -> eth:testneteth:mainnet -> zeko:mainnetzeko:mainnet -> eth:mainnet
Install globally:
npm install -g @zeko-labs/bridge-cliOr run it directly:
npx -y @zeko-labs/bridge-cli bridge --from mina:testnet --to zeko:testnet --amount 1The npm release is a single built JavaScript tarball. There are no platform-specific bridge-cli packages.
The CLI signs transactions from environment variables only in v1.
Required variables:
| Variable | Required | Description |
|---|---|---|
WALLET_PRIVATE_KEY |
yes | Shared wallet private key used for all currently supported routes. |
For human CLI usage, zeko-bridge auto-loads dotenv files in this order before falling back to inherited shell environment variables:
~/.zeko/.env- a sibling
.envnext to the CLI binary - a sibling
.envnext to the invocation path
Earlier files win over later files. If none of those files provide WALLET_PRIVATE_KEY, the CLI falls back to the existing exported environment variable.
Use bridge by default.
zeko-bridge bridge --from mina:testnet --to zeko:testnet --amount 1
zeko-bridge bridge --from zeko:testnet --to mina:testnet --amount 3 --recipient B62q...
zeko-bridge bridge --from mina:testnet --to zeko:testnet --amount 1 --jsonFlags:
| Flag | Description |
|---|---|
--from |
Source chain reference. Defaults to mina:testnet. |
--to |
Destination chain reference. Defaults to zeko:testnet. |
--amount |
Amount in Mina units. |
--recipient |
Optional destination account. Defaults to the account derived from the active signer. |
--timeout-slots |
Optional deposit timeout override for Mina-originated bridges. |
--json |
Emit the final result as JSON on stdout. Progress continues on stderr. |
Behavior:
- waits for completion by default
- retries transient bridge status and capability checks
- keeps printing useful progress during long waits without spamming every poll
- advances earlier queued claims in order when the account already has pending bridge work
- writes structured logs to disk for debugging and recovery
- is intended to complete unattended from a single CLI invocation, even when the network wait is very long
- validation of that unattended contract is only valid if the same original
bridgeprocess remains alive through completion; local persisted operation state alone is not proof that the live command is still running
- Archive queries are most reliable when they scan recent history in roughly
10_000-block windows instead of using a giantfrom=0range. - After
submitDeposit, long waits inwaiting-submissionare primarily an L1 visibility/finality concern. Mina-side progression can be slow, so a long pre-target wait does not by itself imply a stuck proof or sequencer bug. - After a deposit has a target index,
canFinalizeDepositandcanCancelDepositstill depend on the sequencer committing enough state to make the capability checks true. Long waits inwaiting-finalizationcan therefore also be normal sequencer-side delay rather than a broken command loop. - The
Invalid keyfailure mode is not a general long-wait condition. It applies only afterfinalizeDeposithas been submitted and the server-side proof request then takes too long to complete. If that post-finalize proving step runs for roughly an hour or longer, the sequencer may evict the proof request from memory and later returnInvalid key. That case is currently treated as a terminal failure and is not yet automatically recoverable. - A cancellable deposit can never be finalized.
- Deposit queue behavior is asymmetric:
- cancellable deposits are skippable
- finalizable deposits must never be skipped
- the protocol uses
lastFinalizedDepositon L2 andlastCancelledDepositon L1, and only deposits with higher indices than those state values may be claimed
- The CLI does not fully implement cancellable-deposit skipping yet. The current behavior is documented here so operators understand the protocol rule and the current gap.
--json prints a final object like:
{
"success": true,
"operation_id": "f3d3d12a-8a1c-4b8b-9a1e-2a6e6bc7a1d7",
"route": "mina:testnet->zeko:testnet",
"direction": "deposit",
"status": "completed",
"amount": "1",
"recipient": "B62qexample",
"submitted_transactions": [
{ "action": "submit", "hash": "5Jsubmit" },
{ "action": "finalize", "hash": "5Jfinalize" }
],
"final_transaction": {
"action": "finalize",
"hash": "5Jfinalize",
"explorerUrl": "https://zekoscan.io/testnet/tx/5Jfinalize"
},
"explorer_urls": ["https://zekoscan.io/testnet/tx/5Jfinalize"],
"log_path": "/Users/example/Library/Logs/zeko-bridge/f3d3d12a-8a1c-4b8b-9a1e-2a6e6bc7a1d7.jsonl"
}These commands are supported, but bridge is the default path for normal usage:
depositfor low-level Mina-to-Zeko submit/finalize/cancel/status controlwithdrawalfor low-level Zeko-to-Mina submit/finalize/status controloperationfor listing, inspecting logs, and resuming persisted operationstuifor viewing active and recent operations in OpenTUIdoctorfor checking local paths, signer env status, and known routes
Use them when you need explicit low-level recovery or debugging. If you are simply trying to bridge, use bridge.
The CLI writes:
- persisted operation state under the OS/XDG state directory
- append-only JSONL logs under the OS/XDG log directory
Use operation logs <operation-id> to inspect the recorded event stream for a specific bridge.
operation status now refreshes the persisted session with live SDK/network reads before printing it, and it emits a short stderr progress line first so the terminal does not look hung during that refresh. operation list still reads local persisted state only.
operation status is a recovery/debugging command, not a validation runner. It does not replace the original long-lived bridge process, it does not sign follow-up transactions on its own, and it must not be used as a polling loop to claim that a one-command bridge validation is still alive.
For funded real-network validation, run the built CLI directly so one long-lived bridge process owns the whole attempt:
export WALLET_PRIVATE_KEY='<wallet private key>'
node ./packages/bridge-cli/dist/cli.js bridge --from mina:testnet --to zeko:testnet --amount 1 --json --verboseexport WALLET_PRIVATE_KEY='<wallet private key>'
node ./packages/bridge-cli/dist/cli.js bridge --from zeko:testnet --to mina:testnet --amount 1 --json --verboseKeep that same command alive through completion. Progress stays on stderr and the final JSON result stays on stdout.
Run from the monorepo root:
moon run bridge-cli:build
moon run bridge-cli:typecheck
moon run bridge-cli:test
moon run bridge-cli:lint