# withdrawer is Base's own tool for proving and finalising a withdrawal from an op-stack chain

> withdrawer is a small Go utility that handles the two L1 transactions an op-stack withdrawal needs, the proof and the finalisation, with signing by private key, mnemonic or Ledger. It targets base-mainnet, base-sepolia, op-mainnet and op-sepolia, and it has separate flags for the pre-fault-proof and post-fault-proof flows.

**base/withdrawer** — Golang utility for proving and finalizing withdrawals from op-stack chains.

- Repository: https://github.com/base/withdrawer
- Stars: 536 · Forks: 370
- Language: Go
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/base-withdrawer

## What a withdrawal actually involves, and where the utility fits

withdrawer is described as a Golang utility for proving and finalizing ETH withdrawals from op-stack chains, and the README's usage section splits the job into three steps.

Step one happens on the layer 2 and is not this tool's job. You initiate a withdrawal by sending ETH to the L2StandardBridge contract at 0x4200000000000000000000000000000000000010 and note the transaction hash.

Step two is the proof, and it is what withdrawer does. You give it the withdrawal transaction hash, an L1 RPC URL and a signing method, and it constructs and submits the transaction that proves on L1 that the withdrawal happened on L2. The output shows the resulting transaction hash, then a line saying it is waiting for tx confirmation, then the confirmation.

Step three is the finalisation, and it uses the same command again. After the finalization period, you run the same invocation and it produces the Completed withdrawal output instead of the Proved withdrawal one.

So the tool is two transaction submissions with a waiting period in between, and the fact that it is a small Go program rather than a service or a library is the point. It is a one-shot command you run twice.

One detail appears twice in the README and is worth repeating because it surprises people. The note after both the proof and the finalisation says this can be called from any L1 address, it does not have to be the same address that initiated the withdrawal on the L2. In the non-fault-proofs flow, the prover and the finaliser are decoupled from the original withdrawer, which means a service can prove on behalf of someone else.

The primary language is Go, the licence is MIT, the default branch is main, and the last push was on 2026-10-14. There are no GitHub releases, so the version you get is the current main branch. The repository is base/withdrawer, and the README links the Base website, blog and docs.

## The installation is three commands

The installation section is the whole of it:

```bash
git clone https://github.com/base/withdrawer.git
cd withdrawer
go install .
```

There is no binary release to download, no package manager entry and no configuration file, which is consistent with a tool this small. The go.mod requires Go 1.23.0 with a toolchain directive of go1.24.3.

The dependency list explains most of what the tool actually does. github.com/ethereum/go-ethereum at 1.16.1 is the Ethereum client library, which supplies the transaction types, the RPC client and the ABI encoding. github.com/ethereum-optimism/optimism at 1.13.5 is the op-stack implementation, which is where the withdrawal proving and finalisation logic lives, including the contracts the tool calls on L1. github.com/gorilla/websocket is how the tool talks to the node over subscriptions, which is how it waits for a transaction to be mined rather than polling.

The signing side is three separate stacks. github.com/decred/dcrd/hdkeychain/v3 and github.com/tyler-smith/go-bip39 handle mnemonics and BIP-32 derivation, and github.com/ethereum/go-ethereum's hdwallet is also present as an indirect dependency. The Ledger path goes through the same derivation code.

The repository layout is telling. There is main.go at the root, a withdraw/ package for the withdrawal logic, a signer/ package for the three signing methods, bindings/ for the generated contract bindings, and main_test.go and operation_backend_test.go at the root. The separate operation backend test suggests the network-specific behaviour, the pre-fault-proof and post-fault-proof flows, is behind an interface so that both can be exercised.

The top-level entry list also includes go.mod, go.sum and the LICENSE file, with no config directory and no Docker setup.

## The flags, and the signing choices that matter

The flags section is printed usage output, and it is worth reading as the full surface of the tool.

The -rpc string is the Ethereum L1 RPC url. The -network string selects the op-stack network to withdraw from, one of base-mainnet, base-sepolia, op-mainnet or op-sepolia, and it defaults to base-mainnet. The -withdrawal string is the TX hash of the L2 withdrawal transaction, so the tool identifies your withdrawal by the L2 transaction rather than by an index or a nonce.

The -timeout duration is the overall operation timeout, with examples such as 30s, 10m or 1h, and it defaults to 10 minutes. That default is short for a proof against a busy L1 and worth raising.

Three signing methods are supported. -private-key string signs with a private key. -mnemonic string signs with a mnemonic. And -ledger uses a ledger device for signing transactions, with -hd-path string giving the hierarchical deterministic derivation path for the mnemonic or the ledger, defaulting to m/44'/60'/0'/0/0, the standard Ethereum account path.

The -fault-proofs flag is a boolean, and the usage text says to use the fault proofs withdrawal flow only for networks that support fault proofs.

That is the entire interface, and it is deliberately small. There is no flag for output format, no configuration file, and no option to change the L2StandardBridge address, which is hardcoded to 0x4200000000000000000000000000000000000010. The one safety note in the README is a CAUTION block: do not send ERC-20 or other tokens to the L2StandardBridge, only native ETH is supported.

The three signing methods are the part with a practical dimension. A Ledger means the key never sits on the machine running the tool, which is the right choice for a withdrawal of real value. A private key on the command line is the opposite, and on a shared machine it ends up in shell history and in the process list.

## Fault proofs change one rule, and it is an important one

The README documents two flows, one before fault proofs and one after, and the difference is not just an extra flag.

The fault proofs flow exists because of the recent fault proofs upgrade for Base on the Sepolia testnet. With that upgrade, withdrawals are required to wait for a period of seven days, mirroring the Challenge Period that exists for Base mainnet. Additionally, withdrawals are required to be finalized against dispute games that resolve in favor of the output root claim.

That last clause introduces a possibility the old flow did not have: a withdrawal can fail. If the dispute game is blacklisted, resolves against the output root claim because the challenger wins, or the respected game type is changed, then the withdrawal will need to be re-proven.

So the fault proofs flow adds a second point of failure between the proof and the finalisation. The proof is not the last word; there is a dispute window in which the claim can be challenged, and a successful challenge means you start again.

And then there is the rule that changes: unlike the non fault proof withdrawal flow, you MUST use the same address that proved the withdrawal to finalize the withdrawal.

That is the operational difference between the two flows. In the old flow, the prover and the finaliser can be anyone. In the fault proofs flow, the finaliser must be the prover. For an automated service that proves on behalf of many users, that constraint changes the architecture, because you need to hold the proving key for the whole seven day window rather than using it once.

Both flows take the same --fault-proofs flag on the same command, and the README says to finalise after the dispute game has resolved in favor of the root claim and the finalization period has elapsed.

The Challenge Period on mainnet is explained separately in a note: users are required to wait for a period of seven days when moving assets out of Base mainnet into the Ethereum mainnet, and this period serves to help secure the assets stored on Base mainnet. Seven days is the window in which a withdrawal can be challenged, which is the same idea as the dispute game, expressed differently.

## Where withdrawer is the wrong tool

Three limits, and the first is about scope.

The first is that this handles native ETH only. The CAUTION at the top of the usage section says not to send ERC-20 or other tokens to the L2StandardBridge, only native ETH is supported. A withdrawal of a token is a different operation with different preconditions, and this tool does not touch it.

The second is that it is a command, not a service. There is no daemon, no queue, no watch for pending withdrawals, and no state file. For an end user withdrawing once, that is exactly right. For a platform that custodies funds for many users and needs to prove each withdrawal as it matures, the tool is a building block rather than a system: somebody has to track which withdrawals are pending, when their seven days expire, which address proved each one, and what to do about a dispute game that resolves against the claim. The README does not describe any of that, and the fault proofs rule that the finaliser must be the prover makes it worse, because the proving key has to be held for a week.

The third is the version situation. There are no GitHub releases, so the artefact is the current main branch, and the dependency on optimism at 1.13.5 and go-ethereum at 1.16.1 means a rebase can bring in a large diff in the chain libraries underneath. The tool is also network-specific by construction, with a fixed list of four networks and a hardcoded L2StandardBridge address, so a new op-stack chain requires a code change rather than a configuration.

The alternative approach is to build the transactions yourself against the op-stack contracts, using the same optimism and go-ethereum libraries the tool uses, and to handle the withdrawal lifecycle in your own service. The difference in approach is that a purpose-built service can batch, track and retry, and can survive a chain upgrade without a recompile, whereas withdrawer gives you a correct, audited-by-the-chain-team transaction construction in one command and leaves everything around it to you. For a single withdrawal, the tool is clearly the better deal. For a custodial platform, the question is not whether the transactions are right but whether a seven day window of holding a proving key is a risk you are willing to take.

## Conclusion

Adopt withdrawer if you are moving ETH out of Base or another op-stack chain and want the proof and finalisation transactions produced by a tool the chain's own team maintains, rather than by hand-built contract calls. Do not adopt it for token withdrawals, because the README carries an explicit caution that only native ETH is supported and that sending ERC-20 or other tokens to the L2StandardBridge will not work. Verify first that the network you pick matches where you actually withdrew from, since the flag accepts one of base-mainnet, base-sepolia, op-mainnet or op-sepolia, that you have waited out the seven day Challenge Period before attempting to finalise, and that on the fault proofs flow you finalise from the same address that proved the withdrawal, which is a requirement the README marks as MUST and which differs from the older flow.

## FAQ

### What does the Base withdrawer utility do?

It is a Golang utility for proving and finalizing ETH withdrawals from op-stack chains. You send ETH to the L2StandardBridge contract on L2, then run withdrawer once to prove the withdrawal on L1 and again after the finalization period to finalize it.

### How do I install withdrawer?

Run git clone https://github.com/base/withdrawer.git, change into the withdrawer directory and run go install . The module requires Go 1.23.0 with a toolchain of go1.24.3.

### Which networks and signing methods does withdrawer support?

The network flag accepts base-mainnet, base-sepolia, op-mainnet or op-sepolia and defaults to base-mainnet. Signing can use a private key, a mnemonic with an HD derivation path that defaults to m/44'/60'/0'/0/0, or a Ledger device.

### Can withdrawer move tokens as well as ETH?

No. The README carries a caution that you should not send ERC-20 or other tokens to the L2StandardBridge, and that only native ETH is supported.

### How long does a Base withdrawal take?

Users are required to wait for a period of seven days when moving assets out of Base mainnet into the Ethereum mainnet. This period is called the Challenge Period and serves to help secure the assets stored on Base mainnet.

### What is different about the fault proofs withdrawal flow?

Withdrawals must be finalized against dispute games that resolve in favor of the output root claim, and if a game is blacklisted, resolves against the claim or the respected game type changes, the withdrawal has to be re-proven. You MUST also finalize using the same address that proved the withdrawal, unlike the non fault proof flow.

## Sources

- [base/withdrawer on GitHub](https://github.com/base/withdrawer)
- [Issues](https://github.com/base/withdrawer/issues)
- [License: MIT](https://github.com/base/withdrawer/blob/main/LICENSE)
- [README](https://github.com/base/withdrawer/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/base-withdrawer
