> For the complete documentation index, see [llms.txt](https://bluwhale.gitbook.io/bluwhaleai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bluwhale.gitbook.io/bluwhaleai/oceanum-oxn/smart-contract-development/debugging-and-troubleshooting.md).

# Debugging and Troubleshooting

Debugging Oceanum (OXN) transactions is mostly like debugging Ethereum — same gas concepts, same reverts, same event logs — with one big twist: **revert reasons are encrypted**. If a transaction fails, you cannot read the error message from the outside. This page covers the practical debugging playbook.

### Reading revert reasons <a href="#reading-revert-reasons" id="reading-revert-reasons"></a>

**On Ethereum:** if a call reverts with `require(false, "not authorized")`, block explorers show "not authorized" in the transaction details.

**On Oceanum (OXN):** the same call reverts with an *encrypted* revert reason. Explorers see ciphertext. Only the caller who submitted the transaction can decrypt the error.

#### How to see the actual reason <a href="#how-to-see-the-actual-reason" id="how-to-see-the-actual-reason"></a>

Use a wrapped provider that has the session key:

```
try {
  await token.transfer(recipient, amount, { gasLimit: 500_000n });
} catch (err) {
  console.log(err.reason);      // "not authorized" — decrypted by the wrapped provider
  console.log(err.data);        // full error object
}
```

If the caller is your own client (which is usually the case), you have the key. If the caller is a user and you're debugging their failure from your side, you don't have the key — you have to ask them for the error, or reproduce the failure yourself.

#### Debugging user-reported failures <a href="#debugging-user-reported-failures" id="debugging-user-reported-failures"></a>

When a user reports "my transaction failed", your options are:

1. **Ask the user to check their wallet.** MetaMask / Oceanum (OXN) Wallet show the decrypted revert reason if they were the caller.
2. **Reproduce with the same inputs.** Sign a call from your own address using the same arguments and see what reverts.
3. **Add better on-chain assertions.** Custom errors (`error NotAuthorized(address caller);`) with structured fields are easier to identify than string reasons.

### Common reverts and their causes <a href="#common-reverts-and-their-causes" id="common-reverts-and-their-causes"></a>

**`invalid code`** — Compiled with `evmVersion` newer than `paris`. Contains `PUSH0` (or another unsupported opcode) that the runtime cannot execute. See [EVM Compatibility](https://docs.bout.network/concepts/evm-compatibility). Recompile with `evmVersion: "paris"`.

**`out of gas`** — Set `gasLimit` too low. Bump it. Common defaults are documented in [Gas and Fees](https://docs.bout.network/concepts/gas-and-fees).

**`insufficient balance to pay fees`** — Account balance too low. Faucet more or use `gasPrice: 0n` on testnet.

**"Invalid address" in the wallet UI** — Might actually be `insufficient balance` — the Oceanum (OXN) Web Wallet mislabels this error in some versions. Check the underlying transaction status via a script or the explorer before concluding the address is bad.

**Call returns default value (`0`, empty string, zero address) unexpectedly** — You sent a plain `eth_call` instead of a signed query. Confirm the contract was constructed with a signer, not a raw provider. See [Signed Queries](https://docs.bout.network/confidentiality/signed-queries).

**Deployment succeeds but `getCode` returns `0x`** — Gas ran out during deployment. Bump `gasLimit` to `5_000_000n` or more and redeploy.

### Tracing transactions <a href="#tracing-transactions" id="tracing-transactions"></a>

For deeper debugging, use `debug_traceTransaction`:

```
const trace = await rawProvider.send("debug_traceTransaction", [
  txHash,
  { tracer: "callTracer" }
]);
console.log(JSON.stringify(trace, null, 2));
```

This shows the call stack and gas usage per frame. On OXN, encrypted calldata appears as opaque hex bytes in the trace unless you're running it from the caller's context.

### Reading events from a failed transaction <a href="#reading-events-from-a-failed-transaction" id="reading-events-from-a-failed-transaction"></a>

Failed transactions do **not** emit events (Solidity emits are rolled back on revert). What you see in `getTransactionReceipt(txHash).logs` is empty when a transaction failed. The revert reason is in the receipt's status:

```
const receipt = await provider.getTransactionReceipt(txHash);
if (receipt.status === 0) {
  console.log("Transaction failed");
}
```

For decrypted reason, catch the error from the original send, as shown above.

### Console.log for Solidity <a href="#consolelog-for-solidity" id="consolelog-for-solidity"></a>

Hardhat provides `console.log` for Solidity that works in tests:

```
import "hardhat/console.sol";

contract MyContract {
    function doThing(uint256 x) external {
        console.log("Received:", x);
        console.log("Sender  :", msg.sender);
    }
}
```

These logs appear in your Hardhat test output. They are stripped during compilation for deployment, so no gas cost on real networks. However, `console.log` output on real Oceanum (OXN) is not available — it works only in local Hardhat network.

### Debugging with Foundry traces <a href="#debugging-with-foundry-traces" id="debugging-with-foundry-traces"></a>

Foundry's `forge test -vvvv` prints a full execution trace of each test:

```
forge test -vvvv --match-test testMyFailure
```

Shows every call, revert, log, and gas per frame. Locally.

### Explorer trace tools <a href="#explorer-trace-tools" id="explorer-trace-tools"></a>

The block explorer at [explorer.bout.network](https://explorer.bout.network/) shows transaction traces (call flow, gas per frame) for public data. Encrypted calldata and events show as ciphertext in the explorer UI. Use it for the "did the transaction execute at all, and what path did it take" question; use your local debugger for "why did it fail".

### Common integration debugging patterns <a href="#common-integration-debugging-patterns" id="common-integration-debugging-patterns"></a>

Verify a transaction was encrypted

```
const raw = new ethers.JsonRpcProvider("https://rpc.bout.network");
const tx = await raw.getTransaction(txHash);
const firstByte = parseInt(tx.data.slice(2, 4), 16);
console.log("Encrypted:", (firstByte & 0xe0) === 0xa0);
```

Compare receipts as owner vs outsider

```
const asOwner   = await wrapped.getTransactionReceipt(txHash);  // logs decrypted
const asOutsider = await raw.getTransactionReceipt(txHash);      // logs ciphertext

console.log("Decrypted event count:", asOwner.logs.length);
console.log("Raw event topic 0:    ", asOutsider.logs[0].topics[0]);
```

Dry-run before broadcast

```
try {
  const result = await contract.myFunction.staticCall(args);
  console.log("Would return:", result);
} catch (err) {
  console.log("Would revert with:", err.reason);
}
```

### Next steps <a href="#next-steps" id="next-steps"></a>

* [**Deploying Contracts**](https://docs.bout.network/build/deploying)
* [**Testing Confidential Behavior**](https://docs.bout.network/build/testing-confidential)
* [**FAQ**](https://docs.bout.network/resources/faq)

[Edit this page](https://github.com/oxn-network/oxn-docs/edit/main/docs/06-build/debugging.md)[PreviousVerifying Contracts on the Explorer](https://docs.bout.network/build/verifying)[NextPrecompiles](https://docs.bout.network/category/precompiles)
