A cross-chain bridge between Ethereum and Acki Nacki, with
zero-knowledge proofs on both directions of travel. USDC is custodied by AckiNackiBridge.sol on
Ethereum; Acki Nacki state is committed on-chain from Halo2 proofs verified by SHPLONK aggregator
verifiers deployed as Yul bytecode.
Where the truth is. This README orients you; it is not a specification. The document derived from the sources line by line, with a
file:linecitation for every behavioural claim, isdocs/EVM-contracts-spec.md;DOCS.mdis the register of current documentation. When any prose disagrees with the code, the code wins.
Arbitrum Sepolia ↔ shellnet, deployed from tag v0.7.0.
The CLI, relayers and web form take their addresses from configuration.
AckiNackiBridge |
0x3FB082a905a895151224F6d93bD074E816338638 |
| deployment block | 323405848 |
| USDC | 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d |
eccUSDCBridge |
0:1a1a…1a1a |
| version | 1.14.0, code hash 5cada5db… |
| Arbitrum block verifier (421614, parent chain Sepolia) | 0:f1def367fa6bc245589bfa7bcf54937f5c926624d29e59e19826ee9033f14108 |
| Sepolia beacon light client | 0:9121fc37c8ac55a4fc42a73bccaf725cb0d87b9a8c7dfbc6dd2be7d1690d8462 |
| GraphQL | https://shellnet.ackinacki.org/graphql |
BRIDGE_ADDRESS=0x3FB082a905a895151224F6d93bD074E816338638
BRIDGE_DEPLOY_BLOCK=323405848
FROM_BLOCK=323405848
RPC_URL=https://arbitrum-sepolia-rpc.publicnode.comdeposit-relayer arbitrum-anchors needs an Arbitrum RPC that keeps a few minutes of state; the
public endpoints do not (crates/deposit-relayer-daemon/docs/operator_runbook.md, section 9).
Ethereum → Acki Nacki (deposit). The user approves USDC and calls
deposit(uint256 amount, bytes32 anAccount). The contract takes custody via
transferFrom and emits a Deposit event carrying the Acki Nacki destination. Nothing is verified on
the Ethereum side. Off-chain, deposit-prover/ proves the event's presence in a real Ethereum block
(receipt-trie inclusion + log binding, with a keccak coprocessor), and the AN-side USDCBridge
consumes that Halo2 proof natively through the ZKHALO2VERIFYWITHVK TVM opcode, then mints.
Each transfer is capped at 50 USDC, and the outstanding principal at 10,000 USDC
per L1 chain.
Acki Nacki → Ethereum (state attestation + payout). Three permissionless entrypoints:
| Call | Does |
|---|---|
verifyBlock |
Advances the rolling commitment to AN state from a cross-bound pair of proofs: Circuit 1A (≥ 2/3 attestation quorum) or 1B (> 1/2 split), plus Circuit 2 (layer-hash movement). |
applyBkSetUpdate |
Rotates the AN validator-set (BK-set) commitment through a 16-leaf, depth-4 block-id tree. |
withdrawByProof |
Pays out USDC against a Circuit-4 proof of a WithdrawalInitiated event, anchored into state that verifyBlock already recorded. Nullifier-guarded and chain-id scoped. |
There is no refund-style withdraw(depositId, …); it was retired. The owner can pause the bridge
(pause() / unpause(), read back with paused()): while it is paused, deposit, verifyBlock,
applyBkSetUpdate and withdrawByProof revert with BridgePaused(), and the owner's AAVE
management stays available. There is no upgrade path — every verifier binding is immutable, so
replacing a verifier means deploying a new bridge.
Idle USDC can be routed into AAVE V3 by the owner. That module cannot reach user principal: yield
collection is bounded by the surplus above the book value of user deposits, and the functions that
collect it do not appear in the principal-accounting equation at all. Contract detail in
docs/EVM-contracts-spec.md §10; the operator runbook is
docs/aave-yield.md.
Every component and the interface each connection goes through, with its call site in the code:
docs/bridge-components.md.
Ethereum → Acki Nacki (deposit)
user ──approve+deposit──▶ AckiNackiBridge ──Deposit event──▶ tx receipt log
│ │
custody (USDC) MPT receipt proof
│ ▼
AAVE V3 (optional) deposit-prover (Halo2, off-chain)
│
SHPLONK proof + 12 public inputs
▼
AN USDCBridge.finalizeDeposit
(ZKHALO2VERIFYWITHVK opcode)
Acki Nacki → Ethereum (state + payout)
AN node ──GraphQL──▶ bridge-prover-libraries ──Circuits 1A/1B, 2, 4──▶ bridge-relayer-daemon
│
verifyBlock / applyBkSetUpdate / withdrawByProof
▼
AckiNackiBridge
│
Primary / Fallback / LayerHashes / BridgeWithdrawal adapters
▼
ShplonkHalo2Verifier ──▶ Yul Halo2Verifier (CREATE)
| Path | What lives there |
|---|---|
contracts/ethereum/src/ |
The bridge, four verifier adapters, the SHPLONK shim, oracles. Solidity 0.8.19, Foundry. |
contracts/ethereum/verifiers/ |
Production SHPLONK Yul creation bytecode (.bin) per circuit, the generated Solidity source (.sol) it compiles from, and reference calldata. The .bin is what deploys; the .sol is what aggregate-proof self-checks a proof against. |
contracts/ethereum/script/ |
Deploy scripts; ShplonkDeployLib.sol wires .bin → shim → typed adapter. |
deposit-prover/ |
ETH → AN deposit proof (Halo2 on axiom-eth: receipt MPT, log binding, keccak coprocessor). |
crates/deposit-relayer-daemon/ |
Watches the Deposit log, drives the prover, submits finalizeDeposit on AN. |
crates/deposit-chain-ids/ |
The single allowlist of deposit source chains, shared by prover and relayer. |
crates/bridge-prover-libraries/ |
AN-side prover: block-id tree, bridge state, live driver, Circuit-4 event witness. Standalone workspace. |
crates/bridge-relayer-daemon/ |
AN → ETH relayer. src/withdraw_e2e/ is the in-process withdrawal pipeline behind relayer withdraw-e2e. |
crates/ackinacki-bridge/ |
End-user CLI: withdraw moves USDC from an Acki Nacki multisig to an EVM recipient, deposit from an EVM wallet to an Acki Nacki account. Counterpart to the relayers — the daemons own the continuous streams, this owns one withdrawal or one deposit. Installed from published releases, see QUICKSTART.md. |
crates/bridge-snark-utils/, crates/bridge-evm-aggregator/ |
Prover orchestration and the R15 SHPLONK aggregator. |
frontend/ |
WASM deposit UI (Yew). |
docs/ |
EVM-contracts-spec.md and the rest of the current documentation; the register is DOCS.md. |
Cargo workspaces. The root workspace holds crates/eth-frontend, crates/acki-nacki-interface
and crates/deposit-chain-ids. Everything else is excluded and built standalone, because the Halo2
forks in play cannot share a dependency tree: deposit-prover/ (axiom-eth), crates/bridge-prover-libraries/
(gosh-fork halo2-base), crates/bridge-snark-utils/, crates/deposit-relayer-daemon/, and frontend/.
The AN→ETH relayer crates/bridge-relayer-daemon/ and the end-user CLI crates/ackinacki-bridge/
are symlinked members of crates/bridge-prover-libraries/ and build only from there.
make setup # toolchains and dependencies
make build # Rust workspace + Solidity
make test # both test suites
make test-all # every crate's tests + forge test
make check # format-check + lint + testRun make pre-push before pushing: on top of make check it covers the relayer, the aggregator and
forge coverage, none of which any GitHub pipeline runs. The CI section of AGENTS.md
lists what it does and does not cover.
The end-user CLI is not built from here at all —
crates/ackinacki-bridge/scripts/install.sh downloads
the published binaries (the CLI, the prover subprocess it shells out to, the verifier bytecode
and sources, and the deposit prover), so an operator needs neither Rust nor a checkout. Start at
QUICKSTART.md.
Contracts on their own:
cd contracts/ethereum
forge test # full suite
forge test --match-contract AckiNackiBridgeWithdrawByProof -vv
FOUNDRY_PROFILE=fork forge test --match-contract AaveFork # needs a mainnet RPCThe suite is inventoried per file, with what each one covers, in
docs/EVM-contracts-spec.md §13. Two build settings there are
deliberate and worth knowing before you touch them: optimizer_runs = 1 and via_ir = true. The
bridge sits close to the EIP-170 size limit, and several functions are otherwise stack-too-deep.
Standalone crates build from their own directories; the relayer and the end-user CLI from the prover workspace they belong to:
cd crates/bridge-prover-libraries && cargo build --release
cd crates/bridge-prover-libraries && cargo test --locked -p bridge-relayer-daemonDeployment, environment variables and genesis parameters: docs/EVM-contracts-spec.md §12. One
constraint bites early — the genesis seed must sit on a key-block boundary, currently
W·P = 128 × 8 = 1024.
Operating procedures — bootstrapping the prover daemons, the two live proving lanes, deploy timing and
recovery — are documented next to the code they describe: the crate READMEs under
crates/bridge-prover-libraries/, and each daemon's own --help.
Shellnet / Sepolia. Not audited for mainnet. Known trade-offs and limitations are enumerated in
docs/EVM-contracts-spec.md §15 — read that section before making any
claim about the bridge's security properties.