{
  "schema": "spark.exposed.exit-practical-risks.v1",
  "reviewedAt": "2026-09-11",
  "title": "An escape hatch that must be prepared before you need it",
  "summary": "The pessimistic case is practical: a user can lose ordinary access before having the data, economical leaves, Bitcoin fee funds and recovery software needed to leave independently. A valid exit mechanism does not establish that the wallet has delivered usable recovery.",
  "scope": "Analysis of pinned public code and one custom mainnet experiment. The optimized exit was still pending at the recorded September 11 checkpoint. These measurements are not a benchmark for every wallet or proof of a production operator outage.",
  "findings": [
    {
      "id": "preparation",
      "title": "One operator can block the preparation that makes exit practical",
      "basis": "Code-path analysis",
      "argument": "The reviewed consensus flows require every configured operator to prepare, even though signing uses a two-of-three threshold. Optimization relies on a cooperative SSP and on claiming replacement leaves through an all-operator flow. A user already facing refusal cannot assume they can consolidate or renew their way into a better exit.",
      "limit": "This applies to fresh preparation in the reviewed flows, not every API version or a failure after the commit decision. Production outage behavior was not tested. A complete presigned exit needs zero operators.",
      "sources": [
        {
          "label": "All-operator selection",
          "url": "https://github.com/buildonspark/spark/blob/0b3a32a05c9ac06cc411683551dd1f1bde9d0caa/spark/so/helper/operator_helper.go#L65-L82"
        },
        {
          "label": "Prepare failure and rollback",
          "url": "https://github.com/buildonspark/spark/blob/0b3a32a05c9ac06cc411683551dd1f1bde9d0caa/spark/so/consensus/twopc.go#L150-L183"
        },
        {
          "label": "Claim consensus",
          "url": "https://github.com/buildonspark/spark/blob/0b3a32a05c9ac06cc411683551dd1f1bde9d0caa/spark/so/handler/claim_transfer_flow_handler.go#L175-L180"
        },
        {
          "label": "Swap and counter-transfer claim",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark/src/services/swap.rs#L116-L389"
        }
      ]
    },
    {
      "id": "backup",
      "title": "A successful export can still leave the exit unusable",
      "basis": "Observed integration failure",
      "argument": "After the live optimization, two of ten available leaves had incomplete ancestry in the stock SDK export, including the 65,536-sat leaf. Custom parent-ID queries repaired the backup while an operator was reachable. If missing data cannot be obtained during an outage, the seed alone does not supply that transaction history.",
      "limit": "This is the pinned SDK result for this wallet. Other saved copies, willing data holders or confirmed ancestors may help; incomplete export does not establish permanent loss. The repaired JSON was about 459 KB, so this is not evidence that mobile storage is infeasible.",
      "sources": [
        {
          "label": "Sanitized mainnet observations and before/after quotes",
          "url": "data/potential-exculpatory-factors.json"
        },
        {
          "label": "Best-effort export",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark-wallet/src/wallet.rs#L1630-L1665"
        },
        {
          "label": "Pedigree fetch",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark/src/tree/service.rs#L214-L236"
        }
      ]
    },
    {
      "id": "partial",
      "title": "Even successful optimization did not make the whole balance economical to exit",
      "basis": "Mainnet swap; subsequent planner quote",
      "argument": "The live swap reduced 38 leaves to 10 without reducing the 89,569-sat balance. At 1 sat/vB, the native profitable selection still excluded eight leaves totaling 7,649 sats, about 8.5% of the balance. The selected principal improved from 65,536 to 81,920 sats, but selection is not receipt of spendable Bitcoin.",
      "limit": "The excluded value is not proved lost. Different fees, ancestry or recovery methods can change the result. The selected optimized exit had not completed at the recorded checkpoint.",
      "sources": [
        {
          "label": "Sanitized mainnet observations and before/after quotes",
          "url": "data/potential-exculpatory-factors.json"
        },
        {
          "label": "Economic selection",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark/src/services/unilateral_exit.rs#L605-L795"
        }
      ]
    },
    {
      "id": "funding",
      "title": "The user needs Bitcoin outside Spark to recover Bitcoin inside Spark",
      "basis": "Signed mainnet plan",
      "argument": "The tested path requires an external Bitcoin fee reserve. The stock funding quote requested 5,228 sats against our 4,976-sat reserve. Custom allocation omitted optional sweep headroom and made the signed plan fit. Its planned fan-out and CPFP fees total 4,335 sats, excluding any later optional sweep. A consumer cannot assume the nominal Spark balance can pay these upfront costs.",
      "limit": "These are planned fees at 1 sat/vB, not all confirmed expenditure. A sponsor could supply funding but creates an additional availability dependency. Other presigned exit variants have different economics.",
      "sources": [
        {
          "label": "Sanitized mainnet observations and before/after quotes",
          "url": "data/potential-exculpatory-factors.json"
        },
        {
          "label": "Funding construction and headroom",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark/src/services/unilateral_exit.rs#L234-L415"
        }
      ]
    },
    {
      "id": "waiting",
      "title": "Recovery can mean weeks without the selected money",
      "basis": "Observed transaction timelocks",
      "argument": "The two selected paths retain 1,200 and 3,200 blocks of relative waiting: approximately 8.3 and 22.2 days at ten minutes per block, plus earlier confirmation dependencies. Optimization reduced fragmentation but did not turn the exit into an immediately spendable withdrawal.",
      "limit": "Block-time estimates are not deadlines. One replacement shortened its prior 2,400-block wait to 1,200 blocks; this is favorable evidence, but the denomination optimizer does not guarantee favorable replacement history.",
      "sources": [
        {
          "label": "Sanitized mainnet observations and before/after quotes",
          "url": "data/potential-exculpatory-factors.json"
        },
        {
          "label": "Optimizer requests denominations",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark/src/tree/leaf_optimizer.rs#L605-L674"
        }
      ]
    },
    {
      "id": "interruption",
      "title": "Optimization itself temporarily ties up funds",
      "basis": "Mainnet observation and SDK failure handling",
      "argument": "Available-balance samples were zero for roughly 23 seconds during the 23.7-second swap. The SDK can also defer claiming the counter-transfer after the original swap has begun. A phone losing connectivity must reconcile that pending operation before claiming the money is available or recovery preparation is complete.",
      "limit": "Temporary reservation is not theft or loss. An offline smaller-batch simulation preserved 65,536 sats outside the swap; it was not a live concurrent-payment test. Cancellation between rounds does not reverse an already submitted swap.",
      "sources": [
        {
          "label": "Sanitized mainnet observations and before/after quotes",
          "url": "data/potential-exculpatory-factors.json"
        },
        {
          "label": "Reservations and cancellation",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark/src/tree/leaf_optimizer.rs#L324-L599"
        },
        {
          "label": "Deferred counter-transfer claim",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark/src/services/swap.rs#L340-L389"
        }
      ]
    },
    {
      "id": "mobile",
      "title": "The application must deliver a recovery system, not just expose an SDK call",
      "basis": "Implementation requirement; dated wallet review",
      "argument": "A usable phone flow must preserve current recovery state, fund fees, broadcast dependent packages, handle timelocks and conflicting variants, and resume across suspension, restarts and days of confirmations. Our optimized plan contains 29 Bitcoin transactions. The reviewed consumer-wallet surfaces did not demonstrate this complete flow, and our custom desktop tooling does not fill that product gap.",
      "limit": "Mobile implementation is possible in principle. SDK primitives and durable storage are meaningful groundwork. The wallet finding is limited to the dated reviewed versions and public surfaces, not every present or future application.",
      "sources": [
        {
          "label": "Dated consumer-wallet review",
          "url": "wallet-audit.html"
        },
        {
          "label": "SDK leaves orchestration to the application",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/docs/breez-sdk/src/guide/unilateral_exit.md#L125-L165"
        },
        {
          "label": "Sanitized mainnet observations and before/after quotes",
          "url": "data/potential-exculpatory-factors.json"
        }
      ]
    },
    {
      "id": "stress",
      "title": "The emergency scenario removes the services used to prepare the favorable result",
      "basis": "Conditional stress analysis",
      "argument": "Our favorable optimization happened with a working SSP and operators. If refusal starts first, the user must work with existing leaves and whatever valid recovery material was already saved. Higher Bitcoin fees can make additional leaves uneconomical, and service loss may also prevent normal payments. Routine cooperative performance therefore does not measure the cost of recovery during a service failure.",
      "limit": "No coordinated outage or mass exit was induced. This analysis does not quantify network-wide congestion, prove permanent loss, or show that operators can veto already valid Bitcoin transactions.",
      "sources": [
        {
          "label": "Economic leaf selection",
          "url": "https://github.com/breez/spark-sdk/blob/61d5b0b494e0c18dd71c2859bed1e946c24b62b1/crates/spark/src/services/unilateral_exit.rs#L605-L795"
        },
        {
          "label": "All-operator preparation",
          "url": "https://github.com/buildonspark/spark/blob/0b3a32a05c9ac06cc411683551dd1f1bde9d0caa/spark/so/consensus/twopc.go#L150-L183"
        },
        {
          "label": "Sanitized mainnet observations and before/after quotes",
          "url": "data/potential-exculpatory-factors.json"
        }
      ]
    }
  ],
  "availability": [
    {
      "title": "One operator refuses or is offline",
      "text": "Fresh all-operator preparation can fail despite two remaining signing shares. The reviewed end-to-end optimization cannot be assumed available."
    },
    {
      "title": "Two operators refuse or are offline",
      "text": "Fresh all-operator flows fail and only one signing share remains, below the published threshold. Another SSP does not replace missing operator shares or state."
    },
    {
      "title": "All three refuse or are offline",
      "text": "A user with complete valid presigned recovery material can still exit through Bitcoin with zero operators. A user missing required data cannot assume the seed can reconstruct it."
    }
  ],
  "boundary": "An outage does not force an immediate exit: users can wait for restoration. Preparation failures and long waits are not proof of custody or theft. The supported criticism is dependence on advance preparation and an unproven consumer recovery experience.",
  "counterEvidence": "A real unilateral mechanism exists. Five selected leaves from the earlier mainnet attempt were subsequently found recovered as 72,370 sats; those funds were used in the new experiment. Who broadcast the earlier path is not established, and operator unavailability was not demonstrated on mainnet. The new optimization succeeded, while its subsequent exit remained pending at the recorded checkpoint."
}
