Issue 11 · DApp #28
Purchase workflow, visible state by state.
XState coordinates round loading, pricing, identity, KYC, wallet intake, recovery, and backend confirmation. Regions run in parallel; context carries data and evidence.
Source: vscPurchaseWorkflow.ts
Top-level machine
One machine. Five independent regions. UI reads one projected selector.
purchase · type: parallel
round
loading
loaded
failed
pricing
idle
working
ESTIMATEintake
idle
working
CONTRIBUTEverification
idle
starting
redirecting
identity
checking
idle
refreshing
Implemented state-machine workflow
Exact XState regions and event-driven transitions. Horizontal scroll available on small screens.
createPurchaseWorkflow()
Implemented transitionFailure, guard, or safety branch
Normal purchase path
1
Load round.
Fetch snapshot, inspect recovery storage, trigger first estimate.
2
Choose amount.
Tokens or EUR input starts pricing actor. Chain returns quote.
3
Verify identity.
KYC status must become verified before contribution gate opens.
4
Review and contribute.
Machine enters intake.working only after every safety gate passes.
5
Confirm.
Wallet receipt and backend record produce final chain, record, and email states.
UI projection
selectPurchase(snapshot)
// derived UI flags
checkoutDisabled =
isSubmitting
|| persistedIntake
|| recoveryUnavailable
|| !kycReady
|| !realDepositsEnabled
|| !estimate
|| estimateBusy
|| belowMinimum
// UI receives one coherent view
estimateBusy, isSubmitting,
kycReady, recoveryPresentation,
chainState, recordState, emailState
// derived UI flags
checkoutDisabled =
isSubmitting
|| persistedIntake
|| recoveryUnavailable
|| !kycReady
|| !realDepositsEnabled
|| !estimate
|| estimateBusy
|| belowMinimum
// UI receives one coherent view
estimateBusy, isSubmitting,
kycReady, recoveryPresentation,
chainState, recordState, emailState
Contribution path
CONTRIBUTE
→
Validate gates
→
Persist intent
→
Enroll
→
Approve
→
Re-read quote
→
Simulate
→
Wallet contribute
→
Backend confirm
Gate rejects wallet request when:
- Wallet not authenticated or wrong chain.
- KYC not verified.
- Amount below round minimum.
- Recovery unavailable or scope mismatched.
- Quote missing or deposits disabled.
Quote changed after review:
agreement-requiredPayment maximum increased. Machine returns to safe idle state. User must review and explicitly accept new amount.
Recovery journal
Create contributionId
→
Persist intent
→
Save tx hashes
→
Confirm record
→
Clear intent
- Unresolved payment keeps same contributionId.
- Recovery retries confirmation, not payment submission.
- Wallet, chain, deployment, manager, and round must match.
- Confirmed payment gets new ID for next purchase.
Final result
chainState: idle | pending | confirmed | reverted | suspended-mismatch | unknown
recordState: not-started | pending | confirmed | ...
emailState: unknown | sent | failed
suspended-mismatch
Recovery evidence does not match.
Stored details remain. Retry disabled.
recordState: not-started | pending | confirmed | ...
emailState: unknown | sent | failed
suspended-mismatch
Recovery evidence does not match.
Stored details remain. Retry disabled.
Ownership boundaries
State machine owns
- Transitions and guards
- Parallel operation status
- UI-facing context
- Errors and recovery presentation
Browser dependencies own
- Wallet client and chain reads
- localStorage journal
- Enrollment and allowance
- On-chain contribution and API confirmation
Key limitationThis is not one atomic transaction. Multiple wallet transactions run under a persisted recovery journal. Automatic resend stays disabled after unresolved outcomes.