@walletconnect/pay-state
v0.3.0
Published
Headless WalletConnect Pay runtime — injectable seam contracts and the public PaymentSnapshot view-model. Framework-agnostic (no React, no HTTP client).
Readme
@walletconnect/pay-state
The headless WalletConnect Pay runtime — built on @walletconnect/pay-core. Framework-agnostic: no React, no HTTP client. The React binding is @walletconnect/pay-react; the AppKit wallet adapter is @walletconnect/pay-appkit.
This package owns the payment logic (the XState machine, orchestration actors, and signing — landing in later PRs) behind two stable contracts established here.
The two contracts
1. Seams — what the host injects
The runtime depends only on these interfaces, never on AppKit / React / a concrete HTTP client:
| Seam | Purpose | Default |
| ---------------- | ---------------------------------------------- | --------------------- |
| Transport | Engine calls (re-exported from core) | createHttpTransport |
| WalletProvider | connect / accounts / provider / switch network | — (inject) |
| Clock | polling interval + tab-visibility | browserClock |
| Telemetry | analytics + optional haptics | noopTelemetry |
A minimal web host injects only Transport + WalletProvider and takes browserDefaults for the rest. The defaults are SSR-safe (every browser-global access is guarded). Persistence and navigation are deliberately host-owned (not seams): they're product policy that differs per app, so the runtime doesn't dictate them.
2. PaymentSnapshot — what the consumer reads
A curated, flat view-model — never the raw XState context. Fields: state, payment, options, selectedOption, collectData, wallet { isConnected, accounts }, requiresApproval, failureReason.
state is a PaymentState, mapped from the internal machine state via toPaymentState. The mapping is 1:1 today — every internal distinction a UI needs is preserved (OptionsReady vs OptionSelected, ReadyForWallet vs ConnectingWallet). The mapping exists as the seam that absorbs future internal change (a state gaining sub-states resolves to its public parent); it never collapses an existing distinction.
Compatibility policy
| Surface | Stability |
| --------------- | ---------------------------------------------------------------------------------- |
| Stable | PaymentSnapshot fields, the PaymentState set, and session action names |
| Internal | raw machine states/events/actors, the machine context, MachineStateName |
| Deprecation | breaking a stable surface requires a major version bump + a 3-month support window |
Consumers depend on PaymentState, not MachineStateName. Internal states may gain hierarchy or be renamed across minor versions; the toPaymentState table absorbs that without changing the public surface.
Note:
WalletProvider.getAccounts()must returnaccountsexpanded across all configured networks — a single active-chain address makes the Engine returnoptions: []. An emptyaccountsarray means "not connected"; it does not fall back tocaipAddress.
Packaging
Built with tsup to dist/ (ESM + d.ts). It is marked "private": true on purpose — publishing to npm happens in the Release milestone (WCPBX-788), not here; until then BX and the other SDK packages consume it via workspace:*. It depends on the built @walletconnect/pay-core, so build core before type-checking against state (CI does this via the _ci-checks.yml build-dependencies flag).
Scripts
pnpm --filter @walletconnect/pay-state build # tsup → dist (ESM + d.ts)
pnpm --filter @walletconnect/pay-state typecheck
pnpm --filter @walletconnect/pay-state test
pnpm --filter @walletconnect/pay-state lint