DEV Community

cobra
cobra

Posted on

Decoding error 1010 on Midnight: What "Invalid Transaction" actually means

What this guide covers

1010: Invalid Transaction often leaves Midnight developers with an error that is too generic to fix on its own. In most cases, 1010 is the outer JSON-RPC / author-RPC error, not the full diagnosis. The more useful clue is often in error.data, such as Custom error: 186.

The sections below explain the 1010 error structure, how to read Custom error: N, how to interpret custom codes 139, 154, 168, 170, and 186, and how Midnight’s five-dimensional ledger cost model relates to block-limit diagnosis.

Scope and evidence basis

This is a source-backed diagnostic tutorial. It focuses on decoding 1010: Invalid Transaction, interpreting Custom error: N, mapping custom codes 139, 154, 168, 170, and 186, and explaining how Midnight’s five-dimensional ledger cost model affects block-limit diagnosis.

The mappings and worked examples here are based on public Midnight source code, documentation, forum discussions, and issue threads linked throughout the guide and in the Sources checked section. None of the five custom-code failure paths was reproduced first-hand by the author. Any local command output used during research was limited to basic tooling capture, not end-to-end reproduction of these failures.

Verification date and version scope

  • Verification date: May 10, 2026
  • Sources and version scope reverified: June 15, 2026
  • Runtime mapping source: midnight-node commit 948b7dbcd975ce5a360d83f6353c2b4872482621
  • Issue #667 reported versions: Compact compiler 0.29.0 / 0.30.0-rc.0, compact-js 2.5.0, and ledger-v8 8.0.0
  • Issue #731 reported versions: Midnight.js packages 4.0.2, ledger-v8 8.0.3, compact-js 2.5.0, Compact runtime 0.15.0, compactc 0.30.0, proof server 8.0.3, and the preview network after the March 25, 2026 reset

The numeric mappings in this guide are version-specific. Verify them against the midnight-node and ledger versions used by your application.

Prerequisites

Start by capturing the full error returned by your wallet, toolkit, DApp, or JSON-RPC client. The most useful fields are error.code, error.message, and error.data.

If you are debugging a local setup, also record your package versions, target network, node status, and indexer status if the transaction produced a hash. Include proof server status only if the failing flow depends on proof generation. Do not start by changing smart contract code from the number 1010 alone.

Error 1010 is not the whole diagnosis

1010: Invalid Transaction is best treated as a two-layer error.

The outer layer is the JSON-RPC / author-RPC invalid transaction error. It tells you that the transaction was rejected as invalid by the submission path, but it is usually not specific enough to explain why.

The inner layer is the diagnostic detail, which often appears in error.data. Depending on the failure, error.data may contain a built-in invalid transaction reason or a Midnight runtime custom code such as:

{
  "code": 1010,
  "message": "Invalid Transaction",
  "data": "Custom error: 186"
}
Enter fullscreen mode Exit fullscreen mode

In this case, 1010 tells you the transaction was rejected as invalid. Custom error: 186 tells you where to investigate next.

This distinction matters because the same outer error can point to very different underlying problems, including block-limit pressure, malformed transaction effects, or fee-calculation and builder-side issues.

How the 1010 error structure works

The outer invalid-transaction code comes from the upstream author-RPC error mapping. In the shorthand often used for this error family, AUTHOR(1000) refers to the author-RPC error family whose base value is 1000; POOL_INVALID_TX adds 10 to that base, producing 1010.

BASE_ERROR = 1000
POOL_INVALID_TX = BASE_ERROR + 10
1000 + 10 = 1010
Enter fullscreen mode Exit fullscreen mode

So 1010 is the outer invalid-transaction wrapper. It is not a Midnight-specific custom code by itself.

The custom part, when present, appears inside error.data as decimal text:

Custom error: N
Enter fullscreen mode Exit fullscreen mode

That N is the value to map against Midnight’s runtime custom-code mapping. For example:

Custom error: 186
Enter fullscreen mode Exit fullscreen mode

should be investigated as code 186, not as generic 1010.

Also note that not every 1010 has to be a Custom error: N case. Some failures can appear as built-in invalid transaction reasons. For block-limit diagnosis, this matters because a transaction may be rejected through a block-exhaustion path and still appear under the outer 1010 invalid-transaction error.

How to find the inner custom code

When you see 1010: Invalid Transaction, do this before changing code:

  1. Capture the full JSON-RPC error.
  2. Record error.code.
  3. Record error.message.
  4. Record error.data.
  5. Check whether error.data contains a built-in invalid transaction reason or Custom error: N.
  6. If it contains Custom error: N, map N using the code table below.
  7. Check wallet, toolkit, builder, node, or indexer logs where they are relevant to the failure. Include proof server logs only if the failing flow depends on proof generation.
  8. Reduce the failing action to the smallest transaction, circuit call, or deployment that still fails.
  9. Re-run after the targeted fix and compare before-and-after evidence.

The main rule is simple: do not diagnose from 1010 alone. Diagnose from the inner detail.

Worked example: reading the useful part of the error

Suppose your client shows this error:

{
  "code": 1010,
  "message": "Invalid Transaction",
  "data": "Custom error: 186"
}
Enter fullscreen mode Exit fullscreen mode

Read it in two passes:

  1. code: 1010 means the transaction was rejected as invalid by the submission path.
  2. data: "Custom error: 186" is the part to investigate next.

Using the code map below, treat 186 as an effects-check failure, so the next question is not “why did JSON-RPC return 1010?” It is whether the transaction’s declared effects match the effects the ledger computes during validation.

Code map: 139, 154, 168, 170, and 186

This guide focuses on five custom codes that are especially useful when diagnosing 1010: Invalid Transaction: 139, 154, 168, 170, and 186.

The source-defined mappings below are verified against the midnight-node runtime custom-code mapping at commit 948b7dbcd975ce5a360d83f6353c2b4872482621. The table separates the exact runtime mapping from the practical area to investigate. These mappings are version-specific, so check the source for the exact Midnight version you are running.

Code Source-defined runtime mapping First thing to suspect Practical diagnostic area
139 MalformedError::UnknownError the number alone is not specific enough inspect wallet, toolkit, builder, and fee-calculation logs
154 LedgerApiError::BlockLimitExceededError transaction or deployment pressure against block limits check transaction size, deployment size, and cost-model pressure
168 MalformedError::FeeCalculation fee calculation or transaction assembly inspect fee calculation, balancing, transcript partitioning, settlement, and transaction-assembly logs
170 MalformedError::InvalidDustSpendProof an invalid or stale DUST spend proof inspect DUST proof validity, Merkle-root freshness, and witness freshness
186 MalformedError::EffectsCheckFailure declared effects do not match ledger-computed effects inspect the mismatch between declared and computed transaction effects

Evidence and reproduction status

The runtime mappings below are verified from the pinned midnight-node source. “Not reproduced” means the author did not personally trigger and capture that error in a local or network transaction.

Code Evidence basis First-hand reproduction
139 Pinned runtime source and Midnight.js issue #667 No. All five mappings are source-backed; none was reproduced first-hand by author.
154 Pinned runtime source and public block-limit documentation and discussion
168 Pinned runtime source; practical guidance based on the source-defined FeeCalculation mapping
170 Pinned runtime source; practical guidance based on the source-defined InvalidDustSpendProof mapping
186 Pinned runtime source, Midnight.js issue #731, and its validation repository

Quick diagnostic matrix

Use this table for a first-pass triage after you identify the inner custom code.

If you see First place to look Why
Custom error: 139 wallet/toolkit/builder logs the number alone may not identify the final root cause
Custom error: 154 transaction size, deployment size, cost pressure the transaction may be pushing against block limits
Custom error: 168 batch settlement, fee calculation, balancing this often points to a settlement-adjacent transaction assembly path
Custom error: 170 Merkle-root freshness, DUST spend proof, witness freshness stale or pruned root assumptions can surface through the proof/witness path
Custom error: 186 declared vs ledger-computed effects the transaction effects may not match what ledger validation computes

The matrix above is the fast triage view. The sections below explain each code in more detail.

Code 139

What you may see:

A transaction submission fails under outer 1010, with the inner detail showing Custom error: 139. In practice, developers may first encounter this during transaction construction in builder, wallet, or toolkit flows.

What it usually means in practice:

At the pinned runtime version, code 139 maps to MalformedError::UnknownError. Treat it as a non-specific malformed-transaction signal rather than a final diagnosis. The useful place to look is the surrounding wallet, toolkit, and builder logs.

In Midnight.js issue #667, an unshielded NIGHT operation surfaced through RPC as 1010: Invalid Transaction: Custom error: 139, while the more detailed toolkit output identified a fee-calculation rejection. This example shows why you should use 139 as a starting point and inspect the surrounding toolchain logs for the more specific failure.

First place to check:

Start with wallet, toolkit, builder, and transaction-construction diagnostics. The RPC code may be less specific than the logs produced by the surrounding toolchain.

Diagnostic steps:

  1. Capture the full JSON-RPC error and confirm whether error.data contains Custom error: 139.
  2. Check toolkit, wallet, and builder logs for a more specific malformed-transaction or fee-related diagnostic.
  3. Review the transaction-construction path that assembled the failing extrinsic.
  4. Reduce the failing action to the smallest transaction or circuit call that still produces the same inner code.

What not to assume:

Do not assume that 139 by itself proves one exact root cause. Do not present “transaction builder” as the verified public enum name. The safer move is to treat 139 as a malformed-transaction signal and use the surrounding toolkit or builder logs for the more useful clue.

Code 154

What you may see:

A transaction fails under outer 1010 with a block-limit-style symptom. It may appear as custom 154, or it may appear as a built-in invalid transaction reason such as block exhaustion.

What it usually means in practice:

At the pinned runtime version, code 154 maps to LedgerApiError::BlockLimitExceededError. Treat it as a block-limit signal. The transaction, deployment, or state update may be pushing against one or more ledger cost dimensions.

The useful next step is to inspect transaction size, deployment size, smart contract complexity, and cost-model pressure before assuming the smart contract logic itself is broken.

First place to check:

Start with transaction size, deployment size, smart contract complexity, and Midnight’s five-dimensional ledger cost model: readTime, computeTime, blockUsage, bytesWritten, and bytesChurned.

Diagnostic steps:

  1. Capture the full error and check whether the inner detail is Custom error: 154 or a built-in block-exhaustion reason.
  2. Inspect whether the failing action is a large deployment, state-heavy update, or complex transaction.
  3. Check which cost-model dimension is most likely under pressure: reads, compute, block usage, bytes written, or bytes churned.
  4. Reduce transaction or deployment complexity, then re-run and compare the new error output.

What not to assume:

Do not assume every block-limit failure appears as custom 154. Some block-limit failures may still be wrapped by outer 1010 while surfacing through a built-in invalid transaction path.

Code 168

What you may see:

A transaction fails under outer 1010 while the wallet, toolkit, or DApp is assembling a transaction that needs settlement, balancing, or fee calculation. Developers may see this described as a batch-settlement-style failure because the transaction fails while the final transaction package is being assembled or reconciled.

What it usually means in practice:

At the pinned runtime version, code 168 maps to MalformedError::FeeCalculation. In practice, inspect the settlement and transaction-assembly path: fee calculation, balancing, transcript partitioning, and how the final transaction package is assembled.

This is especially relevant when the failing transaction touches NIGHT movement paths such as sendUnshielded or receiveUnshielded, because the transaction may fail before the problem looks like a bug in the smart contract.

First place to check:

Start with the batch-settlement or transaction-assembly path: fee calculation, balancing behavior, transcript partitioning, and wallet or toolkit diagnostics around how the final transaction was assembled.

Diagnostic steps:

  1. Capture the full error and confirm whether the inner detail is Custom error: 168.
  2. Inspect wallet and toolkit logs for settlement, balancing, transcript, or fee-calculation details.
  3. Review how the final transaction package is assembled, especially where unshielded receive or send paths are involved.
  4. Reduce the action to the smallest transaction that still exercises the same settlement, balancing, or fee-calculation path.

What not to assume:

Do not assume that 168 proves one exact settlement mechanism or a Compact circuit bug. The safer debugging move is to inspect the batch-settlement, fee-calculation, balancing, and transaction-assembly path before changing smart contract code.

Code 170

What you may see:

A transaction fails under outer 1010, with the inner detail showing Custom error: 170. The pinned runtime source maps code 170 to MalformedError::InvalidDustSpendProof. Stale or mismatched proof, witness, or state-root data are possible diagnostic areas, not additional source-defined meanings of the code.

What it usually means in practice:

At the pinned runtime version, code 170 maps to MalformedError::InvalidDustSpendProof. Treat it as a DUST spend proof failure whose investigation may include Merkle-root and witness freshness. The useful place to look is whether the proof, witness, or state root used to assemble the transaction is stale, mismatched, pruned, or no longer matches the state expected during validation.

First place to check:

Start with Merkle-root freshness, DUST spend proof validity, stale proof assumptions, and whether the proof or witness binding still matches the transaction being submitted.

Diagnostic steps:

  1. Capture the full error and confirm whether error.data contains Custom error: 170.
  2. Check whether the failing transaction includes a DUST spend proof, witness, or state-root-dependent path.
  3. Review whether the proof, witness, Merkle root, and transaction data are stale, pruned, mismatched, or assembled from different states.
  4. Rebuild the transaction from fresh state where possible, then rerun and compare the before-and-after error data.

What not to assume:

Do not assume that 170 proves one exact pruning trigger or one exact stale-root mechanism. The safer diagnostic area is Merkle-root freshness plus the DUST spend proof path: proof validity, witness freshness, and whether the proof still matches the transaction being submitted.

Code 186

What you may see:

A transaction fails under outer 1010, with the inner detail showing Custom error: 186.

What it usually means in practice:

At the pinned runtime version, code 186 maps to MalformedError::EffectsCheckFailure. Midnight.js issue #731 documents a 1010: Invalid Transaction: Custom error: 186 case involving a circuit that combined mintShieldedToken with receiveUnshielded. The transaction was assembled with one set of effects, but the ledger validation path rejected the resulting effects check.

That means the next debugging step is not the proof server, and not the outer 1010 wrapper. The next step is to inspect how the final transaction effects were assembled.

First place to check:

Start with the transaction effects. The key question is whether the effects declared by the assembled transaction match the effects computed by the ledger during validation.

Diagnostic steps:

  1. Capture the full error and confirm whether error.data contains Custom error: 186.
  2. Inspect the transaction assembly path for combined actions that may alter the final declared effects.
  3. Compare the intended effects with the effects the ledger would compute from the final transaction.
  4. Reduce the failing action to the smallest transaction that still produces the effects-check failure.

What not to assume:

Do not treat 186 as a generic proof-server problem. The useful diagnostic area is effects mismatch: compare the transaction’s declared effects against the effects the ledger computes during validation.

Block-limit-related failures need one more piece of context: Midnight’s five-dimensional ledger cost model.

The five-dimensional ledger cost model

Midnight’s block-limit diagnosis should be understood through its five-dimensional ledger cost model:

  • readTime
  • computeTime
  • blockUsage
  • bytesWritten
  • bytesChurned

The practical model is:

transaction cost = (readTime, computeTime, blockUsage, bytesWritten, bytesChurned)
Enter fullscreen mode Exit fullscreen mode

Each dimension is normalized against its corresponding block limit. The dimension with the highest normalized value becomes the main source of pressure. If any dimension exceeds its limit, the transaction can be rejected.

This matters because a transaction does not need to be “too big” in every way to fail. One dimension can dominate. For example, a deployment might be acceptable in read pressure and compute pressure but fail because bytesWritten exceeds the allowed limit. Another transaction might have moderate byte size but fail because validation or execution pressure is too high.

Be careful with exact numbers. Unless you have current network-specific parameters, do not claim exact block-limit thresholds or exact units for every dimension. For diagnostics, the safe lesson is that block-limit failures are measured across five dimensions, and the largest normalized dimension is the one to investigate first.

Diagnostic workflow

Use this order when investigating 1010: Invalid Transaction:

  1. Capture the full error object.
  2. Separate the outer 1010 wrapper from the inner detail.
  3. Look for error.data.
  4. Decide whether error.data is a built-in invalid transaction reason or Custom error: N.
  5. If you have Custom error: N, map N to the code table.
  6. Record package versions, tooling versions, and target network.
  7. Check toolkit, wallet, builder, node, and indexer logs where relevant. Include proof server logs only if the failing flow depends on proof generation.
  8. Reduce the failing action to the smallest transaction, circuit call, or deployment that still fails.
  9. Apply a targeted fix based on the mapped failure family.
  10. Re-run and compare the before-and-after evidence.

The most important habit is to preserve the full error before changing anything. A screenshot or copied line that only says 1010 is usually not enough.

Evidence to capture before asking for help

Before posting in a forum, Discord, or GitHub issue, capture:

  • full JSON-RPC error
  • error.code
  • error.message
  • error.data
  • custom code, if present
  • package versions
  • network used
  • transaction hash, if available
  • wallet/toolkit logs
  • node logs, if relevant
  • proof server status, only if the failing flow depends on proof generation
  • indexer status, if relevant
  • smallest failing smart contract, circuit, deployment, or action

This evidence helps others distinguish between transaction-construction issues, fee calculation, block-limit pressure, invalid DUST spend proof, and effects-check mismatch.

Sources checked

This guide is based on:

Final checklist

When debugging 1010: Invalid Transaction, ask:

  • Did I capture the full error object?
  • Did I inspect error.data?
  • Is this a built-in invalid transaction reason or Custom error: N?
  • Did I use the custom code to choose the right first place to investigate?
  • Am I accidentally treating 1010 as the root cause?
  • Am I overclaiming what the custom code proves before checking logs and version-specific mappings?
  • Could this be a five-dimensional cost-model / block-limit problem?
  • Have I reduced the failing action to the smallest reproducible case?

The core rule is simple: 1010 tells you the transaction was rejected. The inner detail tells you where to look next.

Top comments (0)