# Preprod: Custom error 173 + "Wallet.InsufficientFunds: could not balance dust" despite large dust.balance(now) on shared bridge claimer

**URL:** <https://forum.midnight.network/t/preprod-custom-error-173-wallet-insufficientfunds-could-not-balance-dust-despite-large-dust-balance-now-on-shared-bridge-claimer/1164>\
**Category:** Technical Questions\
**Tags:** previously-asked, error-help\
**Created:** [April 18, 2026, 11:07pm UTC](https://forum.midnight.network/t/preprod-custom-error-173-wallet-insufficientfunds-could-not-balance-dust-despite-large-dust-balance-now-on-shared-bridge-claimer/1164 "2026-04-18T23:07:42Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![iampress](https://avatars.discourse-cdn.com/v4/letter/i/bb73d2/32.png) [@iampress](https://forum.midnight.network/u/iampress)\
**Post date:** [April 18, 2026, 11:07pm UTC](https://forum.midnight.network/t/preprod-custom-error-173-wallet-insufficientfunds-could-not-balance-dust-despite-large-dust-balance-now-on-shared-bridge-claimer/1164/1 "2026-04-18T23:07:42Z")

</div>

## What are we trying to do?

Build a production bridge / capacity-exchange service on Midnight Preprod that uses a **shared bridge claimer wallet** to submit Ethereum- and BNB-originated claim transactions on Midnight (via `@midnight-ntwrk/midnight-js` `ethereum-bridge-claim`). The same claimer seed is used by three scripts:

- `register-bridge-claimer-dust.ts` — registers unshielded coins for dust generation
- `check-bridge-claimer-dust-balance.ts` — read-only dust snapshot
- live claim submit path: `submitEthereumClaimReal → sdkSubmitEthereumClaim → buildEthereumSdkProviders → getEthereumBridgeClaimerSeed() + initFacadeWallet`

Claimer address (shared between all three paths):  
`mn_addr_preprod1yr0f5cqps3rmtfarhtkrrhqka5slrufax46uy3w333e7y63xarqsuddncn`

## What’s not working or unclear?

Two related errors, both on Preprod:

**1. Dust-registration submit fails with Custom error 173**

```auto
RPC-CORE: submitAndWatchExtrinsic(extrinsic: Extrinsic): ExtrinsicStatus:: 1010: Invalid Transaction: Custom error: 173
[info] Dust submit returned non-fatal node response: Transaction submission error

```

I’ve seen this same error in the `#dev-chat` thread _“If a wallet has sufficient DUST balance”_ (by Cadalt, 04-04-2026). In that thread `norm` explained the related **170** error is caused by a 1–3h gap between root pruning in `root_history` (~1h) and `OutOfDustValidityWindow` kicking in (~3h). It’s unclear whether **173** is in the same family, a different check, or something else — please confirm.

**2. Live claim submit fails with `Wallet.InsufficientFunds: could not balance dust`**

After `facade.waitForSyncedState()` + `dust.waitForSyncedState()` complete, a read-only snapshot of the **same** claimer shows:

- `usable_dust_balance_specks`: `9225916793000000000` (large, non-zero)
- Unshielded summary: `{"totalCoins":2,"registeredCount":2,"unregisteredCount":0,"unregisteredValueSum":"0"}`

A separate run of `register-bridge-claimer-dust.ts` after long sync confirmed two coins both registered: `totalCoins: 2, registeredCount: 2, unregisteredCount: 0`.

Yet a live BNB claim submit against this same wallet fails with:

```auto
Wallet.InsufficientFunds: could not balance dust

```

Originating from `@midnight-ntwrk/wallet-sdk-dust-wallet` / the transacting path used when balancing the scoped claim transaction.

**Live BNB smoke evidence:**

- Deposit id: `bnb_7eb008c69866af1fdfb63c4b643459d40ceadc30e3c9c7b2caf01b955e1963a9_21`
- EVM tx hash: `0x7eb008c69866af1fdfb63c4b643459d40ceadc30e3c9c7b2caf01b955e1963a9`
- Observed state: `CREDIT_READY`; process reaches real Midnight ethereum-bridge-claim submission before failing.

## What have you tried so far?

- Confirmed the seed is identical between registration, read-only balance check, and submit (same `getEthereumBridgeClaimerSeed()` + `initFacadeWallet` everywhere).
- Resolved a prior `INVALID_SIGNATURE` BNB attestation issue (witness/message alignment) — not the current failure.
- Ruled out “no dust at all” — `dust.balance(now)` shows a large value.
- Let `dust.waitForSyncedState()` run for several minutes on Preprod and re-checked — dust is visible and registered.
- Read the `#dev-chat` Discord discussion where `norm` (Midnight mod) and `Facu | Midnames` advised on the 170 variant: _“keep the wallet synced and submit fast. If the wallet sat idle between building the proof and submitting, that’s usually the cause.”_ I will try tightening our submit-after-proof window, but our service is a **long-lived shared claimer** that must handle bursts of claims, so a “submit within seconds of sync” pattern is fragile for production.
- Verified the SDK-known `isStrictlyComplete()` hang on idle chains; using index check (`appliedIndex >= highestRelevantWalletIndex`) as backup.

## Any relevant code or error messages?

Key error, verbatim, from the `POST .../process` call:

```auto
Wallet.InsufficientFunds: could not balance dust

```

Key error from dust-registration submit:

```auto
1010: Invalid Transaction: Custom error: 173
[info] Dust submit returned non-fatal node response: Transaction submission error

```

Full evidence packages attached:

- `MIDNIGHT_DUST_USABILITY_BLOCKER.md`
- `173-PREPROD-SUPPORT-POST.md`

## Questions for the Midnight team

1. Is **Custom error 173** in the 1010 extrinsic family the same root-pruning / `OutOfDustValidityWindow` cause as 170, or a different check? What does 173 specifically indicate?
2. How should operators reconcile a **large `dust.balance(now)`** snapshot with a **`Wallet.InsufficientFunds` at balancing** on the same wallet, same moment, same seed? What is the dust-wallet balancer actually checking that `dust.balance(now)` is not?
3. Is there a recommended pattern for a **long-lived, always-on shared bridge claimer** on Preprod/Mainnet — one that doesn’t rely on “build proof and submit within seconds”? E.g. a sync-before-each-submit pattern, a keepalive pattern, or a scoped-transaction rebuild on every attempt?
4. Will the upcoming V5 indexer (server-side viewing-key scan) or the larger-proof allowance mentioned in `#dev-chat` change any of the above?

## Where are you asking from?

- Network: **Preprod**
- SDKs: `@midnight-ntwrk/midnight-js`, `@midnight-ntwrk/wallet-sdk-dust-wallet` (exact versions in attached files)
- Proof server: self-hosted, `docker run -p 6300:6300 midnightntwrk/proof-server:latest`
- Service: shared bridge claimer for Ethereum + BNB, capacity-exchange rollout currently **paused** pending this clarification

Happy to provide more logs, a minimal repro, or a private session if useful.

---

<div class="post-metadata">

**Author:** ![norm](https://yyz2.discourse-cdn.com/flex010/user_avatar/forum.midnight.network/norm/32/408_2.png) [@norm](https://forum.midnight.network/u/norm)\
**Post date:** [April 19, 2026, 8:05am UTC](https://forum.midnight.network/t/preprod-custom-error-173-wallet-insufficientfunds-could-not-balance-dust-despite-large-dust-balance-now-on-shared-bridge-claimer/1164/2 "2026-04-19T08:05:39Z")

</div>

Custom error 173 is InsufficientDustForRegistrationFee. It’s in the dust error family (169-173) but is not the same root-pruning cause as 170 or 171. The registration’s allow\_fee\_payment amount exceeded the available unclaimed funds for that key in the UTXO state. Check that your claimer has enough dust generated before the registration call, not just tNIGHT.

Full dust error family for reference:

- 169 = InvalidDustRegistrationSignature
- 170 = InvalidDustSpendProof (the root-pruning one)
- 171 = OutOfDustValidityWindow (the 1-3h gap one)
- 172 = MultipleDustRegistrationsForKey
- 173 = InsufficientDustForRegistrationFee

On the InsufficientFunds despite large balance: dust.balance(now) reports total dust value but the balancer needs to construct spend proofs against specific UTXOs whose merkle roots must still be in root\_history. If the wallet sat idle, those roots get pruned (~1h), so the balance reads fine but nothing is actually spendable. A resync rebuilds the wallet’s view against current roots.

For a long-lived shared claimer, one approach suggested in dev-chat was resync-before-each-submit: save wallet state to disk, resync the delta before each claim, then submit immediately. That said, there’s no established pattern for production bridge claimers yet. Flagging this to the team for guidance on recommended architecture.

On V5 indexer and larger proof allowance — don’t have visibility on whether those address these specific issues. Routing that to the team as well.

Sources:

- Error code mapping: [midnight-node/ledger/src/versions/common/types.rs at main · midnightntwrk/midnight-node · GitHub](https://github.com/midnightntwrk/midnight-node/blob/main/ledger/src/versions/common/types.rs#L371)
- InsufficientDustForRegistrationFee check: [midnight-ledger/ledger/src/dust.rs at ledger-8 · midnightntwrk/midnight-ledger · GitHub](https://github.com/midnightntwrk/midnight-ledger/blob/ledger-8/ledger/src/dust.rs#L812)
- Error enum definition: [midnight-ledger/ledger/src/error.rs at ledger-8 · midnightntwrk/midnight-ledger · GitHub](https://github.com/midnightntwrk/midnight-ledger/blob/ledger-8/ledger/src/error.rs#L469)
- root\_history (TimeFilterMap): [midnight-ledger/ledger/src/dust.rs at ledger-8 · midnightntwrk/midnight-ledger · GitHub](https://github.com/midnightntwrk/midnight-ledger/blob/ledger-8/ledger/src/dust.rs#L890)

---

<div class="post-metadata">

**Author:** ![faculerena](https://yyz2.discourse-cdn.com/flex010/user_avatar/forum.midnight.network/faculerena/32/22_2.png) [@faculerena](https://forum.midnight.network/u/faculerena)\
**Post date:** [April 19, 2026, 8:00pm UTC](https://forum.midnight.network/t/preprod-custom-error-173-wallet-insufficientfunds-could-not-balance-dust-despite-large-dust-balance-now-on-shared-bridge-claimer/1164/3 "2026-04-19T20:00:48Z")

</div>

Another thing you could try is to get multiple NIGHT UTXOs registerd for DUST generation, so you can concurrently manage at least a few users without requiring to resync the wallet or wait for transaction acceptance. I’m not 100% how to do this, but I think this is the correct way of doing this.

---

<div class="post-metadata">

**Author:** ![iampress](https://avatars.discourse-cdn.com/v4/letter/i/bb73d2/32.png) [@iampress](https://forum.midnight.network/u/iampress)\
**Post date:** [April 19, 2026, 8:05pm UTC](https://forum.midnight.network/t/preprod-custom-error-173-wallet-insufficientfunds-could-not-balance-dust-despite-large-dust-balance-now-on-shared-bridge-claimer/1164/4 "2026-04-19T20:05:44Z")

</div>

Thank you this is very helpful. The error-enum and dust.rs links clarified the 169-173 family in a way the SDK surface doesn’t expose yet.

So if I understand correctly:

1. Our `173` on dust registration is specifically `InsufficientDustForRegistrationFee`, not the root-pruning / validity-window family.
2. Our later `Wallet.InsufficientFunds: could not balance dust` during live claim submit is likely a separate spendability issue, where `dust.balance(now)` can still look large while the balancer cannot construct a currently spendable proof set.

That matches what we’re seeing.

Two follow-ups:

1. Is there an SDK-supported way to distinguish “total dust balance” from “currently spendable dust for balancing right now” on the same wallet?
2. For a long-lived shared bridge claimer, is “resync immediately before each submit, then submit right away” the recommended pattern for now, or is there a better production architecture the Midnight team recommends?

Happy to be a reference implementation / test case for the long-lived claimer pattern if that would help the team design against a real production use case — can document our setup and the specific failure modes we’ve hit.

---

<div class="post-metadata">

**Author:** ![iampress](https://avatars.discourse-cdn.com/v4/letter/i/bb73d2/32.png) [@iampress](https://forum.midnight.network/u/iampress)\
**Post date:** [April 19, 2026, 8:13pm UTC](https://forum.midnight.network/t/preprod-custom-error-173-wallet-insufficientfunds-could-not-balance-dust-despite-large-dust-balance-now-on-shared-bridge-claimer/1164/5 "2026-04-19T20:13:50Z")

</div>

Appreciate this \_ that’s a really helpful direction.

Combined with the earlier resync-before-submit suggestion, it sounds like a production shared claimer may need more than a single registered NIGHT source, so it can handle multiple claims without every attempt racing the same spendability / root-history constraints.

a couple of follow-ups on that pattern:

1. Is the intended approach to maintain a pool of multiple NIGHT UTXOs already registered for DUST generation, rather than relying on one registered source?
2. If so, is there SDK support for influencing which registered source / UTXO gets used when balancing a claim, or does the balancer always choose automatically?
3. If there is no UTXO-level control today, would the recommended production pattern be multiple sub-wallets / claimers, each with its own registered NIGHT source?

If nobody has documented this pattern yet, happy to prototype it on our side and share what works — we’ll need it anyway for a real production bridge claimer.

---

<div class="post-metadata">

**Author:** ![iampress](https://avatars.discourse-cdn.com/v4/letter/i/bb73d2/32.png) [@iampress](https://forum.midnight.network/u/iampress)\
**Post date:** [April 20, 2026, 10:57pm UTC](https://forum.midnight.network/t/preprod-custom-error-173-wallet-insufficientfunds-could-not-balance-dust-despite-large-dust-balance-now-on-shared-bridge-claimer/1164/6 "2026-04-20T22:57:29Z")

</div>

Update: successful end-to-end BNB → Midnight claim on Preprod, indexer-confirmed. Here’s what the path looked like and what I learned.

## What succeeded

The previously-failing BNB deposit was successfully claimed on Midnight:

- Deposit: `bnb_7eb008c69866af1fdfb63c4b643459d40ceadc30e3c9c7b2caf01b955e1963a9_21`
- SDK-returned tx identifier: `001fb1c03b784b98e45c7acef72c9e00c3639d864d15a1145e15da0a832072638c`
- On-chain block hash: `9efd79cb833992e6b725f2d737627bcaa1141a18badec8d67bb245afdfa2e4bd`
- Block height: 437820
- Submit latency: ~20s end-to-end via warm claimer daemon

Verified by querying the v3 indexer GraphQL directly with `transactions(offset: { identifier: $txId })`. The explorer UI didn’t resolve the identifier directly (404), which is a separate indexing question, but the underlying chain state is confirmed.

## What I built

A long-running daemon wrapping the facade wallet, with:

- HTTP `/health`, `/ready`, `/status`, `/claim` endpoints
- Bounded initial sync (strict → index-fallback → best-effort)
- Continuous poll loop on `facade.state()` (3s default)
- Readiness gate: dust caught up, usable dust \> 0, state fresh, bounded consecutive read failures
- Graceful shutdown via SIGINT/SIGTERM
- Warm claim path that reuses providers instead of rebuilding per request

## What I observed

1. **Cold-start dust sync works in a single long-lived process.**  
In watch mode, the dust layer caught up from far behind to tip within ~15 minutes. `usable_dust_balance_specks` became nonzero within ~60s of watch loop start.

2. **Cross-process dust progress is misleading.**  
Re-running one-shot CLI diagnostics made dust `applied` look inconsistent between invocations (it appeared to move backward). Watching one persistent process showed monotonic forward progress instead. Takeaway: cold-start sync state cannot be compared across separate invocations.

3. **The warm daemon pattern appears to be the right operational shape.**  
Per-operation CLI cold starts consume the full sync window before the claim can be built. A long-lived process lets the readiness gate and warm providers work together.

4. **The earlier `Wallet.InsufficientFunds: could not balance dust` failure may have been related to dust spendability timing / warm-state availability in the SDK.**  
I can’t confirm the root cause definitively without more instrumentation. What I can say is that the warm daemon pattern resolved it for this deposit.

5. **The readiness gate saved me from a blind retry cycle.**  
At one point during a ~16h daemon session, `usable_dust_balance_specks` went to 0 even though dust layer indexes stayed caught up. The daemon correctly returned 503 `daemon_not_ready` reason `usable_dust_balance_zero` instead of attempting a doomed submit. Spendability recovered later during the same process without a restart — I don’t yet fully understand those dynamics.

## Current operational pattern

For my production use case, the strongest pattern so far:

- Keep the claimer alive continuously
- Maintain synced/warm wallet state
- Gate claim submission on explicit readiness (dust caught up, usable dust \> 0, recent successful reads)
- Submit immediately once ready

## Follow-up questions

1. Does this align with the Midnight team’s expected operational model for a production bridge claimer?
2. What’s the recommended strategy for keeping spendability warm over long runtimes, especially during extended idle periods? I observed recovery without intervention but don’t understand the mechanism.
3. Is there a canonical way to reconcile SDK-returned `txId` with the `block.hash` field the indexer uses? My explorer lookups failed on the identifier but the indexer resolved it fine — would be useful to document for other operators.
4. If I turn this into a reference write-up for the ecosystem, is there existing team guidance I should align with first?

Happy to share the daemon pattern and empirical findings as a cleaner reference write-up if that would be useful.

@norm @faculerena — thanks for the earlier guidance. This result wouldn’t exist without the root-pruning explanation and the multi-UTXO suggestion that shaped the architecture.
