Bug: opBNB->Solana tdec withdraw_execute fails with hash mismatch (Custom:6402) #110

Closed
opened 2026-04-13 08:21:52 +00:00 by Brouie · 4 comments
Brouie commented 2026-04-13 08:21:52 +00:00 (Migrated from gitlab.com)

@PlasticDigits opBNB->Solana 1.99 tdec fails at withdraw_execute with a hash verification mismatch.

Error: Custom:6402 (0x1902) from program L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95

Log output:
Result (Failed): [235, 40, 233, 21, 6, 101, 13, 65, 211, 220, 123, 58, 231, 109, 147, 181, 159, 147, 23, 179, 0, 41, 241, 33, 176, 47, 22, 217, 14, 148, 236, 116] == [99, 143, 71, 37, 239, 101, 230, 234, 37, 21, 152, 142, 247, 101, 25, 174, 20, 30, 20, 24, 122, 235, 23, 155, 85, 42, 158, 230, 255, 23, 94, 173]

Transfer details:

  • XChain Hash: 0xc5e99a6e80fb97ff33db308c7cc4985894e6d729af5b26837fc073144fcdc271
  • Source TX: 0xb15086f7efc6f65c9b5eccbd8763f4ec2d435d0880627757af238a4fe426c23c
  • Path: opBNB -> Solana
  • Token: tdec (6 dec on Solana, 12 dec on opBNB)
  • Amount: 1.99 tdec

Context: BSC->Solana tdec and Terra->Solana tdec both pass. Only opBNB->Solana tdec fails. Likely a decimal encoding mismatch specific to the opBNB tdec token mapping (tdec is 18 dec on BSC, 12 dec on opBNB, 6 dec on Terra, 6 dec on Solana).

Tested on build 0a78fe7 with Phantom wallet, 4/13.

@PlasticDigits opBNB->Solana 1.99 tdec fails at withdraw_execute with a hash verification mismatch. **Error:** Custom:6402 (0x1902) from program L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95 **Log output:** Result (Failed): [235, 40, 233, 21, 6, 101, 13, 65, 211, 220, 123, 58, 231, 109, 147, 181, 159, 147, 23, 179, 0, 41, 241, 33, 176, 47, 22, 217, 14, 148, 236, 116] == [99, 143, 71, 37, 239, 101, 230, 234, 37, 21, 152, 142, 247, 101, 25, 174, 20, 30, 20, 24, 122, 235, 23, 155, 85, 42, 158, 230, 255, 23, 94, 173] **Transfer details:** - XChain Hash: 0xc5e99a6e80fb97ff33db308c7cc4985894e6d729af5b26837fc073144fcdc271 - Source TX: 0xb15086f7efc6f65c9b5eccbd8763f4ec2d435d0880627757af238a4fe426c23c - Path: opBNB -> Solana - Token: tdec (6 dec on Solana, 12 dec on opBNB) - Amount: 1.99 tdec **Context:** BSC->Solana tdec and Terra->Solana tdec both pass. Only opBNB->Solana tdec fails. Likely a decimal encoding mismatch specific to the opBNB tdec token mapping (tdec is 18 dec on BSC, 12 dec on opBNB, 6 dec on Terra, 6 dec on Solana). Tested on build 0a78fe7 with Phantom wallet, 4/13.
PlasticDigits commented 2026-04-13 10:57:49 +00:00 (Migrated from gitlab.com)

Investigation (repo + on-chain)

What fails

Solana withdraw_execute recomputes the V2 transfer hash from the decoded PendingWithdraw account and requires it to equal the stored transfer_hash (withdraw_execute.rs: compute_transfer_hash vs pw.transfer_hash). The reported Custom:6402 / 0x1902 aligns with BridgeError::HashMismatch on that require!.

opBNB deposit from the issue is consistent (12 decimals)

Decoded Deposit from source tx 0xb15086f7efc6f65c9b5eccbd8763f4ec2d435d0880627757af238a4fe426c23c (opBNB RPC cast receipt):

  • Net amount: 1990000000000 → 1.99 × 10^12 (12 decimal places on opBNB for tdec), not 18.
  • nonce: 25 (0x19).
  • Source token: 0x6d66d16e6cb29351aee1960ba1c395c0fb1392dd (matches opBNB tdec in register-mainnet-tokens.ts).

Recomputing the canonical V2 hash with srcChain = 204 (0x000000cc), destChain = 5 (0x00000005), event fields, and token = bytes32(SPL mint 765GMcrKxfevfBhnJmZDhdyHDon2nTwGemcgqJApNBR) matches the issue’s XChain hash 0xc5e99a6e80fb97ff33db308c7cc4985894e6d729af5b26837fc073144fcdc271 exactly (same computeXchainHashId path as packages/frontend/src/services/hashVerification.ts).

So the source deposit + stated xchain id are internally consistent; a blanket “UI always used 18 decimals for opBNB tdec” theory does not fit this specific tx’s on-chain amount.

What to verify next (Solana)

  1. solana account on the PendingWithdraw PDA for program L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95, seeds ["withdraw", <32-byte xchain hash>], and compare stored amount, nonce, src_chain, token (mint bytes), src_account, dest_account to the opBNB getDeposit(0xc5e9…) record.
  2. Confirm whether that program id matches the intended deployment (docs mainnet bridge id differs: 4XX8ndYX…) — if this is another cluster/build, rule out hash/layout drift vs the repo.

Note on log bytes

The two 32-byte arrays in the issue body do not equal 0xc5e9…; they may be recomputed vs stored from the failing execute, or from another layer—still worth diffing against a manual recompute from a raw PendingWithdraw dump.

## Investigation (repo + on-chain) ### What fails Solana `withdraw_execute` recomputes the V2 transfer hash from the decoded `PendingWithdraw` account and requires it to equal the stored `transfer_hash` (`withdraw_execute.rs`: `compute_transfer_hash` vs `pw.transfer_hash`). The reported `Custom:6402` / `0x1902` aligns with `BridgeError::HashMismatch` on that `require!`. ### opBNB deposit from the issue is consistent (12 decimals) Decoded **`Deposit`** from source tx `0xb15086f7efc6f65c9b5eccbd8763f4ec2d435d0880627757af238a4fe426c23c` (opBNB RPC `cast receipt`): - Net **`amount`**: `1990000000000` → **1.99 × 10^12** (12 decimal places on opBNB for tdec), not 18. - **`nonce`**: `25` (`0x19`). - Source token: `0x6d66d16e6cb29351aee1960ba1c395c0fb1392dd` (matches opBNB tdec in `register-mainnet-tokens.ts`). Recomputing the canonical V2 hash with **srcChain = 204 (`0x000000cc`)**, **destChain = 5 (`0x00000005`)**, event fields, and **token = bytes32(SPL mint `765GMcrKxfevfBhnJmZDhdyHDon2nTwGemcgqJApNBR`)** matches the issue’s **XChain hash** `0xc5e99a6e80fb97ff33db308c7cc4985894e6d729af5b26837fc073144fcdc271` exactly (same `computeXchainHashId` path as `packages/frontend/src/services/hashVerification.ts`). So the **source deposit + stated xchain id are internally consistent**; a blanket “UI always used 18 decimals for opBNB tdec” theory does **not** fit this specific tx’s on-chain amount. ### What to verify next (Solana) 1. **`solana account`** on the `PendingWithdraw` PDA for program **`L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95`**, seeds `["withdraw", <32-byte xchain hash>]`, and compare **stored** `amount`, `nonce`, `src_chain`, `token` (mint bytes), `src_account`, `dest_account` to the opBNB `getDeposit(0xc5e9…)` record. 2. Confirm whether that program id matches the intended deployment (docs mainnet bridge id differs: `4XX8ndYX…`) — if this is another cluster/build, rule out **hash/layout drift** vs the repo. ### Note on log bytes The two 32-byte arrays in the issue body do not equal `0xc5e9…`; they may be **recomputed vs stored** from the failing execute, or from another layer—still worth diffing against a manual recompute from a raw `PendingWithdraw` dump.
Brouie commented 2026-04-15 21:51:36 +00:00 (Migrated from gitlab.com)

@PlasticDigits PendingWithdraw PDA dump for the failed opBNB->Solana tdec transfer:

PDA: DcyiDQpJqPpCKp3HPgsVgD74TLDiEFVZRWxLRZayyMG (owner: 4XX8ndYXupw4Sb4SsRgAPTmBJJjfZbg8rWjj87iKEhVt)
Seeds: [withdraw, xchain_hash]
Length: 186 bytes

Raw hex:
d77d3e520c8f7085c5e99a6e80fb97ff33db308c7cc4985894e6d729af5b26837fc073144fcdc271000000cc000000000000000000000000c46b15f4b56489a16f561c22d5f0ba8bdca80650f6ff5c03da91192aebad1241b5981504deb9d04667ad2d5c6f76a4d912d4cca2018f38b5187a52d81baab1034a584f413289ca4b27828babca407cad74e63558003c3e55cf010000000000000000000019000000000000000c0600000000000000000123a5dc69000000000000ff

Confirmed: transfer_hash matches xchain hash 0xc5e99a6e..., src_chain = 0xcc (opBNB). Also confirmed L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95 is a separate executable program on mainnet (not the bridge).

@PlasticDigits PendingWithdraw PDA dump for the failed opBNB->Solana tdec transfer: PDA: DcyiDQpJqPpCKp3HPgsVgD74TLDiEFVZRWxLRZayyMG (owner: 4XX8ndYXupw4Sb4SsRgAPTmBJJjfZbg8rWjj87iKEhVt) Seeds: [withdraw, xchain_hash] Length: 186 bytes Raw hex: d77d3e520c8f7085c5e99a6e80fb97ff33db308c7cc4985894e6d729af5b26837fc073144fcdc271000000cc000000000000000000000000c46b15f4b56489a16f561c22d5f0ba8bdca80650f6ff5c03da91192aebad1241b5981504deb9d04667ad2d5c6f76a4d912d4cca2018f38b5187a52d81baab1034a584f413289ca4b27828babca407cad74e63558003c3e55cf010000000000000000000019000000000000000c0600000000000000000123a5dc69000000000000ff Confirmed: transfer_hash matches xchain hash 0xc5e99a6e..., src_chain = 0xcc (opBNB). Also confirmed L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95 is a separate executable program on mainnet (not the bridge).
PlasticDigits commented 2026-04-16 03:19:10 +00:00 (Migrated from gitlab.com)

Follow-up: on-chain verification (RPC)

Checked 2026-04-16 via public JSON-RPC (no wallet).

Solana mainnet-beta (https://api.mainnet-beta.solana.com)

Check Result
PendingWithdraw PDA DcyiDQpJqPpCKp3HPgsVgD74TLDiEFVZRWxLRZayyMG Account exists (186 bytes, owner 4XX8ndYXupw4Sb4SsRgAPTmBJJjfZbg8rWjj87iKEhVt)
transfer_hash c5e99a6e80fb97ff33db308c7cc4985894e6d729af5b26837fc073144fcdc271 (matches issue XChain id)
amount / nonce 1990000000000 / 25 (1.99 tdec at 12 source decimals)
approved / executed true / false — withdrawal still not executed on-chain
V2 hash recompute (dest chain Solana 0x00000005) Matches stored transfer_hash (same as prior offline decode)
BridgeConfig PDA HarAAW2pPcgBwMhcwRsUxRqiDeihCJVjZCmdCWpJbmsD chain_id bytes 00000005 (Solana)
Bridge program 4XX8ndYX… Executable upgradeable program (expected)

Conclusion: Live Solana state matches the pasted hex dump and remains approved-but-not-executed. Nothing in this account contradicts a correct withdraw_execute hash check against the canonical bridge BridgeConfig.

opBNB (https://opbnb-mainnet-rpc.bnbchain.org)

  • eth_getTransactionReceipt for source tx 0xb15086f7efc6f65c9b5eccbd8763f4ec2d435d0880627757af238a4fe426c23c: status success (0x1).
  • Bridge Deposit log amount field includes 0x1cf553e3c00 = 1990000000000 base units (12 decimals), consistent with Solana PendingWithdraw.amount.

Program id L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95 (from the error report)

  • getAccountInfo: account exists and is an executable upgradeable BPF program — not the same address as the bridge 4XX8ndYX…. If a wallet sends withdraw_execute to L2TEx… while passing PDAs derived for 4XX8nd…, Anchor account validation should fail before a hash check; if the failure truly was HashMismatch, we need the exact failing transaction (program id + account keys + logs) to reconcile.

Prior analysis (unchanged)

  • Decoded PendingWithdraw is internally consistent; decimal mismatch (18 vs 12) does not explain this transfer’s hashed amount.
  • Repo IDL maps HashMismatch → 6007, not 6402 — raw tx error code should be confirmed from simulation logs.

@Brouie Could you please reproduce and attach one failing withdraw_execute transaction (Solscan link or base58 signature) with:

  1. Program id shown as the instruction target (confirm 4XX8ndYX… vs anything else).
  2. Full transaction logs / simulation error (exact custom error code).
  3. Build / env: VITE_SOLANA_PROGRAM_ID, RPC URL, wallet (Phantom build if relevant).

The pending account above is still open; withdraw_execute should still be runnable after delay if the client targets the correct program and accounts. If it still fails with a hash error, the raw tx will tell us whether we are looking at the wrong program, wrong bridge account, or a different failure path.

## Follow-up: on-chain verification (RPC) Checked **2026-04-16** via public JSON-RPC (no wallet). ### Solana `mainnet-beta` (`https://api.mainnet-beta.solana.com`) | Check | Result | |-------|--------| | **`PendingWithdraw` PDA** `DcyiDQpJqPpCKp3HPgsVgD74TLDiEFVZRWxLRZayyMG` | Account **exists** (186 bytes, owner **`4XX8ndYXupw4Sb4SsRgAPTmBJJjfZbg8rWjj87iKEhVt`**) | | **`transfer_hash`** | `c5e99a6e80fb97ff33db308c7cc4985894e6d729af5b26837fc073144fcdc271` (matches issue XChain id) | | **`amount` / `nonce`** | `1990000000000` / `25` (1.99 tdec at **12** source decimals) | | **`approved` / `executed`** | `true` / **`false`** — withdrawal still **not** executed on-chain | | **V2 hash recompute** (dest chain Solana `0x00000005`) | Matches stored `transfer_hash` (same as prior offline decode) | | **`BridgeConfig` PDA** `HarAAW2pPcgBwMhcwRsUxRqiDeihCJVjZCmdCWpJbmsD` | **`chain_id`** bytes **`00000005`** (Solana) | | **Bridge program** `4XX8ndYX…` | Executable upgradeable program (expected) | **Conclusion:** Live Solana state matches the pasted hex dump and remains **approved-but-not-executed**. Nothing in this account contradicts a correct `withdraw_execute` hash check against the canonical bridge `BridgeConfig`. ### opBNB (`https://opbnb-mainnet-rpc.bnbchain.org`) - **`eth_getTransactionReceipt`** for source tx `0xb15086f7efc6f65c9b5eccbd8763f4ec2d435d0880627757af238a4fe426c23c`: **`status` success** (`0x1`). - Bridge **`Deposit`** log amount field includes **`0x1cf553e3c00`** = **`1990000000000`** base units (12 decimals), consistent with Solana `PendingWithdraw.amount`. ### Program id `L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95` (from the error report) - **`getAccountInfo`**: account **exists** and is an **executable** upgradeable BPF program — **not** the same address as the bridge **`4XX8ndYX…`**. If a wallet sends `withdraw_execute` to **`L2TEx…`** while passing PDAs derived for **`4XX8nd…`**, Anchor account validation should fail before a hash check; if the failure truly was `HashMismatch`, we need the **exact** failing transaction (program id + account keys + logs) to reconcile. --- ## Prior analysis (unchanged) - Decoded `PendingWithdraw` is **internally consistent**; decimal mismatch (18 vs 12) does **not** explain **this** transfer’s hashed amount. - Repo IDL maps **`HashMismatch` → 6007**, not **6402** — raw tx error code should be confirmed from simulation logs. --- @Brouie Could you **please reproduce** and attach **one failing `withdraw_execute` transaction** (Solscan link or base58 signature) with: 1. **Program id** shown as the instruction target (confirm **`4XX8ndYX…`** vs anything else). 2. Full **transaction logs** / **simulation error** (exact custom error code). 3. **Build / env**: `VITE_SOLANA_PROGRAM_ID`, RPC URL, wallet (Phantom build if relevant). The pending account above is still open; **`withdraw_execute` should still be runnable** after delay if the client targets the correct program and accounts. If it still fails with a hash error, the raw tx will tell us whether we are looking at the wrong program, wrong bridge account, or a different failure path.
Brouie commented 2026-04-20 04:49:42 +00:00 (Migrated from gitlab.com)

@PlasticDigits Not reproducing on build v0.1.82 - f227b6f (frontend bundle main-CvcBOh67), 4/20 mainnet.

Fresh opBNB -> Solana tdec 1.99 test completed successfully:

XChain Hash ID: 0x426e21e759ecc7b9bb8c92b23f80c2ac55babd584d1126c3049427d1c7f720d4
Source TX (opBNB): 0xc08173a2eb0fbec86019c9ba484e68c4aa959104706a1d19428371d79276859c
Recipient: 0xb0c8dccbb82263d1524de950d2e9d32af183d80636cc82713844803a3827a234
Program: 4XX8ndYXupw4Sb4SsRgAPTmBJJjfZbg8rWjj87iKEhVt (correct bridge program)
Wallet: Backpack
Result: tokens received -- Backpack shows 765G...pNBR: 1.99 tdec (SPL mint matches)

No 6402 error, no hash mismatch, no L2TEx... program involvement. The withdraw_execute succeeded normally despite the earlier auto-submit signature expiry (block height exceeded on first attempt, retry succeeded).

Ongoing signatureSubscribe malformed and block height exceeded errors present in console but did not block token delivery -- those are tracked under #106.

Closing #110 as not reproducing. The pending account from the original failed tx (0xc5e99a6e... PDA DcyiDQpJqPpCKp3HPgsVgD74TLDiEFVZRWxLRZayyMG) remains open and unexecuted -- that specific instance may have been a transient issue or Phantom-specific problem at the time. If it resurfaces on future opBNB tdec transfers we'll refile with the exact failing tx.

@PlasticDigits Not reproducing on build v0.1.82 - f227b6f (frontend bundle main-CvcBOh67), 4/20 mainnet. Fresh opBNB -> Solana tdec 1.99 test completed successfully: **XChain Hash ID:** 0x426e21e759ecc7b9bb8c92b23f80c2ac55babd584d1126c3049427d1c7f720d4 **Source TX (opBNB):** 0xc08173a2eb0fbec86019c9ba484e68c4aa959104706a1d19428371d79276859c **Recipient:** 0xb0c8dccbb82263d1524de950d2e9d32af183d80636cc82713844803a3827a234 **Program:** 4XX8ndYXupw4Sb4SsRgAPTmBJJjfZbg8rWjj87iKEhVt (correct bridge program) **Wallet:** Backpack **Result:** tokens received -- Backpack shows 765G...pNBR: 1.99 tdec (SPL mint matches) No 6402 error, no hash mismatch, no L2TEx... program involvement. The withdraw_execute succeeded normally despite the earlier auto-submit signature expiry (block height exceeded on first attempt, retry succeeded). Ongoing signatureSubscribe malformed and block height exceeded errors present in console but did not block token delivery -- those are tracked under #106. Closing #110 as not reproducing. The pending account from the original failed tx (0xc5e99a6e... PDA DcyiDQpJqPpCKp3HPgsVgD74TLDiEFVZRWxLRZayyMG) remains open and unexecuted -- that specific instance may have been a transient issue or Phantom-specific problem at the time. If it resurfaces on future opBNB tdec transfers we'll refile with the exact failing tx.
Brouie (Migrated from gitlab.com) closed this issue 2026-04-20 04:49:48 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-bridge-monorepo#110
No description provided.