Repository navigation
Stuck Pre-Bedrock Withdrawal (2022) after Upgrade 20 / Super Root upgrade #957
Replies: 1 comment
|
Your 2022 withdrawal ( I pulled your L2 tx ( 1. Why you got Since Upgrade 20 (Super Roots), Instead of reading const l2BlockNumber = 105235063n + (superTimestamp - 1686068903n) / 2n;Also note that for GameType 9, 2. Why the
If you hash that migrated struct, your Working import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
import { mainnet, optimism } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
const PORTAL = '0xbEb5Fc579115071764c7423A4f12eDde41f106Ed';
const L2_PASSER = '0x4200000000000000000000000000000000000016';
const STORAGE_SLOT = '0xd4da58a851797e0b49599f8c40fdfb7d37740617e85c1de0d4bbde0331a25a07';
// Your exact Bedrock-migrated WithdrawalTransaction tuple
const withdrawalTx = {
nonce: 104551n,
sender: '0x4200000000000000000000000000000000000007',
target: '0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1',
value: 96909001099673980n,
gasLimit: 1307593n,
data: '0xd764ad0b0000000000000000000000000000000000000000000000000000000000019867000000000000000000000000420000000000000000000000000000000000001000000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be100000000000000000000000000000000000000000000000001584a391a15817c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41532ec34000000000000000000000000fd3d8bc8e46ed736f82cbe892b6fe17369dd2f44000000000000000000000000fd3d8bc8e46ed736f82cbe892b6fe17369dd2f4400000000000000000000000000000000000000000000000001584a391a15817c0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
};
const portalAbi = parseAbi([
'function disputeGameFactory() view returns (address)',
'function respectedGameType() view returns (uint32)',
'function proveWithdrawalTransaction((uint256 nonce, address sender, address target, uint256 value, uint256 gasLimit, bytes data) _tx, uint256 _disputeGameIndex, (bytes32 version, bytes32 stateRoot, bytes32 messagePasserStorageRoot, bytes32 latestBlockhash) _outputRootProof, bytes[] _withdrawalProof)',
]);
const dgfAbi = parseAbi([
'function gameCount() view returns (uint256)',
'function gameAtIndex(uint256 _index) view returns (uint32 gameType, uint64 timestamp, address proxy)',
]);
const gameAbi = parseAbi([
'function l2SequenceNumber() pure returns (uint256)',
]);
const l1 = createPublicClient({ chain: mainnet, transport: http(process.env.L1_RPC_URL) });
const l2 = createPublicClient({ chain: optimism, transport: http('https://mainnet.optimism.io') });
async function main() {
const [factory, respectedType] = await Promise.all([
l1.readContract({ address: PORTAL, abi: portalAbi, functionName: 'disputeGameFactory' }),
l1.readContract({ address: PORTAL, abi: portalAbi, functionName: 'respectedGameType' }),
]);
const count = await l1.readContract({ address: factory, abi: dgfAbi, functionName: 'gameCount' });
let disputeGameIndex, gameProxy;
for (let i = count - 2n; i >= 0n; i--) {
const [gameType, , proxy] = await l1.readContract({ address: factory, abi: dgfAbi, functionName: 'gameAtIndex', args: [i] });
if (gameType === respectedType) {
disputeGameIndex = i;
gameProxy = proxy;
break;
}
}
const superTs = await l1.readContract({ address: gameProxy, abi: gameAbi, functionName: 'l2SequenceNumber' });
const l2BlockNumber = 105235063n + (superTs - 1686068903n) / 2n;
const [block, proof] = await Promise.all([
l2.getBlock({ blockNumber: l2BlockNumber }),
l2.getProof({ address: L2_PASSER, storageKeys: [STORAGE_SLOT], blockNumber: l2BlockNumber }),
]);
const outputRootProof = {
version: '0x0000000000000000000000000000000000000000000000000000000000000000',
stateRoot: block.stateRoot,
messagePasserStorageRoot: proof.storageHash,
latestBlockhash: block.hash,
};
// Simulate first to verify zero revert
await l1.simulateContract({
address: PORTAL,
abi: portalAbi,
functionName: 'proveWithdrawalTransaction',
args: [withdrawalTx, disputeGameIndex, outputRootProof, proof.storageProof[0].proof],
account: '0xfd3d8bc8e46ed736f82cbe892b6fe17369dd2f44',
});
console.log('Simulation succeeded against game index:', disputeGameIndex.toString());
if (process.env.PRIVATE_KEY) {
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const wallet = createWalletClient({ account, chain: mainnet, transport: http(process.env.L1_RPC_URL) });
const hash = await wallet.writeContract({
address: PORTAL,
abi: portalAbi,
functionName: 'proveWithdrawalTransaction',
args: [withdrawalTx, disputeGameIndex, outputRootProof, proof.storageProof[0].proof],
});
console.log('Prove tx sent:', hash);
}
}
main().catch(console.error);Once |
Uh oh!
There was an error while loading. Please reload this page.
Did you check the documentation and specs?
Was there anything unclear or missing?
The official guide (https://docs.optimism.io/stack/transactions/withdrawal) states that pre-Bedrock withdrawals (before June 2023) are still claimable. However, the provided methods and SDKs fail to process these legacy transactions after the Upgrade 20 / Super Root changes. There is no clear documentation on how to construct the outputRootProof for legacy withdrawals using the new Super Root dispute games.
Did you check for duplicate questions?
Similar Questions or Issues Found
#953
Please select the type of request
Steps to Reproduce
1、Initiated a withdrawal on OP Mainnet on April 23, 2022 (Pre-Bedrock).
2、L2 Tx Hash: 0x1cd3449f75b4ae40cf90268f69807aed20086528a2f6dafdef8022ed12eac22b
3、Attempted to use the official viem SDK (getWithdrawalStatus / waitToProve).
4、Attempted to use the official @eth-optimism/sdk (toCrossChainMessage / proveMessage).
5、Attempted manual proof construction using the latest DisputeGameFactory and OptimismPortal contracts.
Expected vs. Actual Behaviour
Expected Behaviour:
The legacy Pre-Bedrock withdrawal should be successfully parsed by the official SDKs, and a valid proveWithdrawalTransaction payload should be generated to prove the withdrawal on L1.
Actual Behaviour:
viem SDK throws ReceiptContainsNoWithdrawalsError because the 2022 transaction lacks the modern MessagePassed event.
@eth-optimism/sdk throws state root for message not yet published when calling proveMessage.
When manually querying Super Root dispute games (GameType 9), the l2BlockNumber resolves to a massive timestamp (l2SequenceNumber, e.g., 4523128486...), causing eth_getProof to fail with block not found.
Software Versions
viem v2.57.7, @eth-optimism/sdk (latest), Node.js v24.22.0
Operating System
Windows 10 (x64)
Hardware Resources
N/A (Running a local Node.js script, not a node operator).
Configuration Files, Startup Flags & Environment Variables
Using Alchemy RPC for L1 and BlockPI (Archive) RPC for L2. Running a standard Node.js script to construct the proof.
Error Messages / Logs
Error 1 (viem):
ReceiptContainsNoWithdrawalsError: The provided transaction receipt contains no withdrawals.
Error 2 (@eth-optimism/sdk):
Error: state root for message not yet published
Error 3 (Manual eth_getProof on Super Root game):
Error: bad response (status=503) ... "message":"block not found" (Because the l2BlockNumber passed was actually a massive l2SequenceNumber like 452312848627203193806088654388322240570316205978440188055511601039263399936).
Feature Description
No response
Purpose and Benefits
No response
Relevant Context or Examples
Hello Optimism Devs,
I am facing a severe issue with a Legacy Pre-Bedrock Withdrawal initiated in April 2022. The funds are stuck, and the current official tools fail to process them after the Upgrade 20 / Super Root upgrade.
Transaction Details:
L2 Tx Hash: 0x1cd3449f75b4ae40cf90268f69807aed20086528a2f6dafdef8022ed12eac22b
Wallet: 0xfd3d8bc8e46ed736f82cbe892b6fe17369dd2f44
Amount: ~0.0969 ETH
What I have tried and the errors:
Official viem SDK: Throws ReceiptContainsNoWithdrawalsError. The 2022 transaction lacks the MessagePassed event expected by modern SDK logic.
Official @eth-optimism/sdk: proveMessage throws state root for message not yet published.
Manual Proof Construction: When querying the latest DisputeGameFactory (Super Root games), the l2BlockNumber resolves to a massive l2SequenceNumber timestamp (e.g., 4523128486...). Passing this to eth_getProof fails because it is not a valid block height.
Question:
Since the Dispute Game mechanism has changed to Super Roots (GameType 9), how do we correctly construct the outputRootProof and withdrawalProof for these legacy txs? Is there a specific gameType we should filter for, or a legacy contract/ABI we should call to bypass the Super Root check?
Any guidance or a working snippet would be highly appreciated. Thank you!
All reactions