Summary
An AssetLock (identity credit-funding) transaction is persisted to the wallet store's transactions row as direction = INTERNAL, netAmount = 0, even though the wallet irreversibly spends the OP_RETURN burn plus the L1 fee. Field case (mainnet wallet, 2025-01-21): a credit purchase burning 0.03 DASH with a 241-duff fee — true wallet net −0.03000241 — stored as net-0 INTERNAL, making the purchase invisible to every net-based consumer (history sums, CSV/tax export, balance reconciliation).
This was isolated by a full duff-level reconciliation of a field wallet against its on-chain history: the stored-net sum over all 134 transactions differed from the UTXO-derived balance by exactly the AssetLock's burn+fee (plus one #4387 case — separate issue).
Why it happens
The persistence-side net computation treats the asset lock as entirely-self: every address-bearing output (the change) returns to owned addresses, and the value-bearing OP_RETURN output has no address, so nothing is counted as leaving the wallet. The burn is real spend: those duffs become Platform credits and never return to L1.
Expected
netAmount = −(op_return_burn + fee) and a non-INTERNAL classification (OUTGOING, or a dedicated kind if consumers want to render "credit purchase" distinctly — the transactionType = AssetLock column already distinguishes it).
Workaround shipped app-side (Android)
The exporter now includes entirely-self transactions with a value-bearing OP_RETURN as an Expense valued −(burn+fee), so tax exports reconcile. That branch is written to go inert once the store persists the true net (it only fires on netAmount == 0).
Scope note
Related but distinct from #4387 (multi-account spend persisting a per-account net slice): both are persistence-side netAmount truth defects, and a fix PR could reasonably cover both paths in rs-platform-wallet's transaction recording.
Summary
An AssetLock (identity credit-funding) transaction is persisted to the wallet store's
transactionsrow asdirection = INTERNAL, netAmount = 0, even though the wallet irreversibly spends the OP_RETURN burn plus the L1 fee. Field case (mainnet wallet, 2025-01-21): a credit purchase burning 0.03 DASH with a 241-duff fee — true wallet net −0.03000241 — stored as net-0 INTERNAL, making the purchase invisible to every net-based consumer (history sums, CSV/tax export, balance reconciliation).This was isolated by a full duff-level reconciliation of a field wallet against its on-chain history: the stored-net sum over all 134 transactions differed from the UTXO-derived balance by exactly the AssetLock's burn+fee (plus one #4387 case — separate issue).
Why it happens
The persistence-side net computation treats the asset lock as entirely-self: every address-bearing output (the change) returns to owned addresses, and the value-bearing OP_RETURN output has no address, so nothing is counted as leaving the wallet. The burn is real spend: those duffs become Platform credits and never return to L1.
Expected
netAmount = −(op_return_burn + fee)and a non-INTERNAL classification (OUTGOING, or a dedicated kind if consumers want to render "credit purchase" distinctly — thetransactionType = AssetLockcolumn already distinguishes it).Workaround shipped app-side (Android)
The exporter now includes entirely-self transactions with a value-bearing OP_RETURN as an Expense valued −(burn+fee), so tax exports reconcile. That branch is written to go inert once the store persists the true net (it only fires on
netAmount == 0).Scope note
Related but distinct from #4387 (multi-account spend persisting a per-account net slice): both are persistence-side
netAmounttruth defects, and a fix PR could reasonably cover both paths inrs-platform-wallet's transaction recording.