@valve-tech/tx-flight-react 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,170 @@
1
+ # AGENTS.md
2
+
3
+ Terse reference for AI agents (Claude Code, Cursor, Aider, etc.) integrating
4
+ `@valve-tech/tx-flight-react`. The full README is for humans; this file is for
5
+ agents that need to ground their work in the package's actual surface
6
+ quickly.
7
+
8
+ (For contributor-facing notes — architectural invariants, file map,
9
+ test discipline — see `INTERNALS.md` in the package root, not shipped
10
+ in the npm tarball.)
11
+
12
+ ## What this package does
13
+
14
+ React UI primitives for an "in-flight transaction strip" — the pattern
15
+ every dapp ends up rebuilding (recently-submitted txs showing
16
+ pending → confirmed | failed | dropped | replaced, with a hash link, an
17
+ age display, and optional speed-up / cancel). One Provider, one hook,
18
+ six headless components, three pluggable storage adapters.
19
+
20
+ Sits on top of `@valve-tech/wallet-adapter` (lifecycle vocabulary) and
21
+ `@valve-tech/tx-tracker` (per-tx state machine). Both are **optional
22
+ peer dependencies** — wallet-adapter-only consumers and hash-only
23
+ consumers each pay only what they use, because tx-tracker +
24
+ chain-source are *dynamic-imported* inside `addByHash`.
25
+
26
+ React 18 or 19, viem ^2.
27
+
28
+ ## Public API
29
+
30
+ ```ts
31
+ // main
32
+ import {
33
+ TxFlightProvider,
34
+ useTxFlight,
35
+ TxFlightList,
36
+ TxFlightItem,
37
+ TxFlightStatusIcon,
38
+ TxFlightHashLink,
39
+ TxFlightAge,
40
+ TxFlightActions,
41
+ // type re-exports from wallet-adapter (no runtime import — types only)
42
+ type TrackedTx,
43
+ type TxFlow,
44
+ type WriteHookParams,
45
+ } from '@valve-tech/tx-flight-react'
46
+
47
+ // storage adapters at a sub-export so the default bundle isn't bloated
48
+ import {
49
+ localStorageAdapter,
50
+ indexedDBAdapter,
51
+ memoryAdapter,
52
+ type TxFlightStorage,
53
+ } from '@valve-tech/tx-flight-react/storage'
54
+ ```
55
+
56
+ ## Five concepts you must know
57
+
58
+ | Concept | What it is |
59
+ |---|---|
60
+ | `<TxFlightProvider id?, storage?, maxItems?, terminalRetentionMs?, onError?, clientFactory?>` | Wraps your React tree. Module-level registry keyed by `id` so multiple Providers with the same id share one store. Default: `id="default"`, `localStorageAdapter` (with `tx-flight:${id}` key prefix), `maxItems: 50`, `terminalRetentionMs: 60_000`. |
61
+ | `useTxFlight(id?)` | Hook returning `{ txs, addWithWalletAdapter, addByHash, addManual, remove, clear, get }`. Throws if no Provider for the resolved id is in the tree. |
62
+ | Three add shapes | `addWithWalletAdapter` (sync, returns `{ id, hooks }`), `addByHash` (async, returns `Promise<string>`), `addManual` (sync, returns `string`). One return type each — no overloaded discriminated union. |
63
+ | Storage adapters | Two-method `TxFlightStorage` interface (`load(id) → Promise<TrackedTx[] \| null>`, `save(id, txs) → Promise<void>`). Three built-ins; consumers can implement their own. |
64
+ | Rehydrate | On mount, persisted entries seed back into state. `pending` with `hash` + `clientFactory` → fresh tx-tracker watcher async-attaches. `pending` without `clientFactory` → stays pending. `preparing`/`awaiting-signature` → translated to `failed` with `notes: 'lost during reload'`. |
65
+
66
+ ## The three add shapes — pick by how you got the tx
67
+
68
+ ### `addWithWalletAdapter` — for `@valve-tech/wallet-adapter` consumers
69
+
70
+ Sync. Wallet-adapter is statically imported (types only — no runtime bundle cost):
71
+
72
+ ```tsx
73
+ import { sendTransactionWithHooks } from '@valve-tech/wallet-adapter'
74
+ import { useTxFlight } from '@valve-tech/tx-flight-react'
75
+
76
+ const flight = useTxFlight()
77
+ const { id, hooks } = flight.addWithWalletAdapter({
78
+ hooks: { onConfirmed: (info) => myToast(`tx ${info.hash} confirmed`) },
79
+ flow: 'mint',
80
+ chainId: 1,
81
+ request: { to: contract, data, value: 0n, chainId: 1 },
82
+ })
83
+ // `hooks` is wrapped — every phase fires BOTH your callback AND a store update.
84
+ await sendTransactionWithHooks({ wallet, request, hooks })
85
+ ```
86
+
87
+ ### `addByHash` — when you have a hash + `PublicClient`
88
+
89
+ Async — `@valve-tech/tx-tracker` and `@valve-tech/chain-source` are **dynamic-imported**, so consumers who never call `addByHash` don't ship those packages. The strip builds a private `ChainSource + TxTracker` internally; `flight.remove(id)` (or unmount) cleans up the subscription.
90
+
91
+ ```tsx
92
+ const id = await flight.addByHash({
93
+ hash: '0xabc...',
94
+ chainId: 1,
95
+ client: publicClient,
96
+ flow: 'claim',
97
+ withReceipts: true,
98
+ confirmations: 3,
99
+ })
100
+ ```
101
+
102
+ ### `addManual` — when you already have a `TrackedTx`
103
+
104
+ Sync. Useful for back-fill (server push, observed-elsewhere txs). Subsequent updates are the consumer's responsibility (call `addManual` again with the same `tx.id` to overwrite, or `flight.remove(id)`).
105
+
106
+ ## Components — what's RSC-safe
107
+
108
+ | Component | RSC-safe | Notes |
109
+ |---|---|---|
110
+ | `<TxFlightProvider>` | no | `'use client'`. Owns state + storage + eviction interval + watchers. |
111
+ | `<TxFlightList>` | no | Uses `useTxFlight`. Defaults to newest-first by `submittedAt`. Optional `filter` / `sort` / `render` / `empty`. |
112
+ | `<TxFlightItem>` | yes | Default per-tx layout (icon + hash + age + actions). `render` swaps the layout while keeping the four atomic children. |
113
+ | `<TxFlightStatusIcon>` | yes | Colored dot per status. `size` (default 16). |
114
+ | `<TxFlightHashLink>` | yes | `<a>` to explorer (or `<span>` fallback when no `explorer` prop). Truncation: `'middle'` \| `'end'` \| `'none'`. |
115
+ | `<TxFlightAge>` | no | Uses `useEffect` for periodic relative-time. `format` swaps wording. |
116
+ | `<TxFlightActions>` | yes | Speed-up / cancel / dismiss button slots. Renders nothing when no callbacks wired. |
117
+
118
+ Every component accepts `className` and `style`.
119
+
120
+ ## Storage adapters
121
+
122
+ | Adapter | When to use |
123
+ |---|---|
124
+ | `localStorageAdapter({ keyPrefix? })` | Default. Sync API; SSR-safe (no-op when `window === undefined`). |
125
+ | `indexedDBAdapter({ dbName?, storeName? })` | Larger payloads, async. SSR-safe (no-op when `indexedDB === undefined`). |
126
+ | `memoryAdapter()` | Tests, or "explicit no persistence". |
127
+
128
+ Custom adapter just satisfies `TxFlightStorage`. The serializer that the built-ins use is exported as `serialize` / `deserialize` from `'@valve-tech/tx-flight-react/storage'` — bigint-safe (hex-encodes `submittedGas` fields).
129
+
130
+ ## Pitfalls (read these)
131
+
132
+ 1. **Calling `useTxFlight()` outside a `<TxFlightProvider>`.** The hook throws — this is the deliberate "no provider in tree" safety check. Wrap your app at the top level, or render-prop the strip into a sub-tree where the Provider lives.
133
+
134
+ 2. **Calling `addByHash` without installing `@valve-tech/tx-tracker` + `@valve-tech/chain-source`.** They're optional peer deps — the dynamic import will fail at runtime. Either install both or use `addManual`/`addWithWalletAdapter` only.
135
+
136
+ 3. **Expecting `preparing` / `awaiting-signature` entries to survive a reload.** They can't — the wallet popup state isn't recoverable. Persisted entries in those statuses get translated to `failed` with `notes: 'lost during reload'`. Only `pending` (with `hash`) and terminal entries survive verbatim.
137
+
138
+ 4. **Setting `clientFactory` to a closure that captures stale state.** The factory is called at *rehydrate time* (Provider mount, after a reload) per pending entry's `chainId`. It must work without depending on rendered state — typically a module-level `Record<chainId, PublicClient>`.
139
+
140
+ 5. **Two `<TxFlightProvider id="default">` in the same tree expecting independent state.** The module-level registry shares stores by `id` — same id means same store (intentional for nested layouts). Use distinct ids for distinct strips.
141
+
142
+ 6. **`<TxFlightHashLink>` with no `explorer` prop.** It silently degrades to a `<span>` (no link). If you want a link, pass `explorer="https://etherscan.io"` (or wrap with your own resolver per chainId).
143
+
144
+ 7. **Rendering `<TxFlightAge>` or `<TxFlightList>` from an RSC.** Both use hooks. Use `<TxFlightItem>` / `<TxFlightStatusIcon>` / `<TxFlightHashLink>` / `<TxFlightActions>` for RSC-safe rendering with server-resolved data.
145
+
146
+ 8. **Custom `TxFlightStorage` adapter without SSR no-op.** If your adapter throws when `window === undefined`, server-side rendering crashes. Mirror the built-ins — return `null` from `load` and resolve `save` no-op when the runtime gate is missing.
147
+
148
+ 9. **Persisting state with bigints and using `JSON.stringify` directly.** The package's serializer hex-encodes `submittedGas` fields so the payload is JSON-safe. If you implement a custom adapter, use the exported `serialize` / `deserialize` rather than rolling your own.
149
+
150
+ 10. **Wiring `onDropped` / `onReplaced` against `addWithWalletAdapter` and expecting them to fire from wallet-adapter alone.** wallet-adapter doesn't fire those — they need tx-tracker. If you want them, use `addByHash` (or use both add shapes for the same tx).
151
+
152
+ ## Composition
153
+
154
+ - `addWithWalletAdapter` produces wrapped hooks for `sendTransactionWithHooks` — that's the canonical wallet-adapter path. The wrapper fans every phase to BOTH the user's hook AND a store update.
155
+ - `addByHash` builds a private `ChainSource + TxTracker` per strip. If the user already has a shared `ChainSource`/`TxTracker` for other purposes (e.g. a gas-oracle integration), they're decoupled — the strip's tracker is its own.
156
+ - `flight.remove(id)` synchronously removes the entry AND tears down any tracker watcher attached for it. Same on Provider unmount.
157
+
158
+ ## Skills (for AI agents)
159
+
160
+ `skills/` ships in the npm tarball. If you're an AI agent working in a
161
+ project that has installed this package, look in
162
+ `node_modules/@valve-tech/tx-flight-react/skills/tx-flight-react-integration/SKILL.md`
163
+ for trigger conditions, anti-pattern flags, and integration recipes.
164
+
165
+ ## Verifying provenance
166
+
167
+ ```bash
168
+ npm view @valve-tech/tx-flight-react@latest --json | jq .dist.attestations
169
+ npm audit signatures
170
+ ```
package/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ this file.
6
6
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  and this project adheres to [Semantic Versioning](https://semver.org/).
8
8
 
9
+ ## [0.11.0] — 2026-05-11
10
+
11
+ ### Notes
12
+
13
+ - Synchronized release — no changes to this package. Bumped in
14
+ lockstep with the rest of the toolkit alongside the v0.11.0
15
+ feature work in `@valve-tech/gas-oracle` (20-block ring lifecycle,
16
+ reorg detection, gap bridging), `@valve-tech/tx-tracker` (audit
17
+ fixes — durable rehydrate, retention enforcement, replaced-by
18
+ dedup, receipt-poll race, helper extraction), `@valve-tech/
19
+ wallet-adapter` (five wallet bridge examples), and
20
+ `@valve-tech/chain-source` (canonical-owner docs for wire types).
21
+
9
22
  ## [0.10.1] — 2026-05-08
10
23
 
11
24
  Synchronized release — no changes to this package. Republished at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valve-tech/tx-flight-react",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "React UI primitives for rendering an in-flight transaction strip. Provider with multi-instance scoping (id + storage key), three add methods (addWithWalletAdapter for callers using @valve-tech/wallet-adapter, addByHash for raw hash + chain via @valve-tech/tx-tracker, addManual for back-fill), pluggable storage (localStorage default, IndexedDB and memory adapters included), atomic + layout components for headless composition, SSR-safe (the package's React-side hooks are client-only; storage adapters no-op on the server). Part of the valve-tech/evm-toolkit synchronized release line.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/valve-tech/evm-toolkit/tree/main/packages/tx-flight-react#readme",
@@ -38,7 +38,9 @@
38
38
  },
39
39
  "files": [
40
40
  "dist",
41
+ "skills",
41
42
  "README.md",
43
+ "AGENTS.md",
42
44
  "CHANGELOG.md",
43
45
  "LICENSE"
44
46
  ],
@@ -52,9 +54,9 @@
52
54
  "prepare": "yarn build"
53
55
  },
54
56
  "peerDependencies": {
55
- "@valve-tech/chain-source": "^0.10.1",
56
- "@valve-tech/tx-tracker": "^0.10.1",
57
- "@valve-tech/wallet-adapter": "^0.10.1",
57
+ "@valve-tech/chain-source": "^0.11.0",
58
+ "@valve-tech/tx-tracker": "^0.11.0",
59
+ "@valve-tech/wallet-adapter": "^0.11.0",
58
60
  "react": "^18.2.0 || ^19.0.0",
59
61
  "react-dom": "^18.2.0 || ^19.0.0",
60
62
  "viem": "^2.0.0"
@@ -75,9 +77,9 @@
75
77
  "@testing-library/react": "^16.1.0",
76
78
  "@types/react": "^18.3.0",
77
79
  "@types/react-dom": "^18.3.0",
78
- "@valve-tech/chain-source": "^0.10.1",
79
- "@valve-tech/tx-tracker": "^0.10.1",
80
- "@valve-tech/wallet-adapter": "^0.10.1",
80
+ "@valve-tech/chain-source": "^0.11.0",
81
+ "@valve-tech/tx-tracker": "^0.11.0",
82
+ "@valve-tech/wallet-adapter": "^0.11.0",
81
83
  "fake-indexeddb": "^6.0.0",
82
84
  "happy-dom": "^15.0.0",
83
85
  "react": "^18.3.1",
@@ -0,0 +1,172 @@
1
+ ---
2
+ name: tx-flight-react-integration
3
+ description: Integrate `@valve-tech/tx-flight-react` — React UI primitives for an in-flight transaction strip — into a dapp. Use when the user is building or asking about a "transaction strip", "in-flight transactions" UI, "recent txs" panel, "pending tx toast list", or "show me my recent transactions confirming/failing/dropped" panel; wiring `<TxFlightProvider>` + `useTxFlight` into a React 18/19 app; picking between `addWithWalletAdapter` (sync, types-only wallet-adapter import), `addByHash` (async, dynamic-imports tx-tracker + chain-source), or `addManual` (back-fill); choosing a storage adapter (`localStorageAdapter` / `indexedDBAdapter` / `memoryAdapter`); enabling rehydrate-on-reload via `clientFactory`; or composing the headless components (`<TxFlightList>`, `<TxFlightItem>`, `<TxFlightStatusIcon>`, `<TxFlightHashLink>`, `<TxFlightAge>`, `<TxFlightActions>`). Also fires on imports of `@valve-tech/tx-flight-react`, on questions about RSC vs client-component boundaries within the package, multi-instance scoping by `id`, or "why doesn't `addByHash` work" (usually missing optional peer deps). Skip when the user is NOT in React (delegate to wallet-adapter-integration for vanilla JS / SDK code, or tx-tracker-integration for raw tx tracking), or only wants to define the SDK-side `WriteHookParams` shape without React state (delegate to wallet-adapter-integration).
4
+ ---
5
+
6
+ # Integrating `@valve-tech/tx-flight-react`
7
+
8
+ React UI primitives for an in-flight transaction strip — Provider +
9
+ hook + headless components + pluggable storage. Sits on top of
10
+ `@valve-tech/wallet-adapter` (lifecycle vocabulary) and
11
+ `@valve-tech/tx-tracker` (per-tx state machine), both as **optional**
12
+ peer deps. This skill is for AI agents working in a React project that
13
+ imports the package.
14
+
15
+ ## Decision tree: which add shape to use
16
+
17
+ ```
18
+ How is the user submitting the tx?
19
+ ├── Through `@valve-tech/wallet-adapter`'s `sendTransactionWithHooks`
20
+ │ → use `addWithWalletAdapter`. Sync. Wallet-adapter is types-only
21
+ │ imported (no runtime bundle cost). The strip wraps your
22
+ │ WriteHookParams so each phase fans to BOTH the user's callback
23
+ │ AND a store update.
24
+ ├── Already have a hash + a viem `PublicClient`
25
+ │ → use `addByHash`. Async (tx-tracker + chain-source dynamic-import).
26
+ │ Strip builds a private ChainSource + TxTracker; tears down on
27
+ │ `flight.remove(id)` or unmount.
28
+ └── Already have a fully-formed `TrackedTx` (server push,
29
+ observed-elsewhere, manually constructed)
30
+ → use `addManual`. Sync. Subsequent updates are caller's
31
+ responsibility (call `addManual` again with same `tx.id`).
32
+ ```
33
+
34
+ ## How to recognize this package in the user's code
35
+
36
+ ```tsx
37
+ import {
38
+ TxFlightProvider,
39
+ useTxFlight,
40
+ TxFlightList,
41
+ } from '@valve-tech/tx-flight-react'
42
+ import { localStorageAdapter } from '@valve-tech/tx-flight-react/storage'
43
+ ```
44
+
45
+ `package.json` will show `"@valve-tech/tx-flight-react": "^0.10.x"`. Optional peers (install only if used):
46
+ - `@valve-tech/wallet-adapter` — for `addWithWalletAdapter`
47
+ - `@valve-tech/tx-tracker` + `@valve-tech/chain-source` — for `addByHash`
48
+
49
+ ## 30-second canonical setup
50
+
51
+ ```tsx
52
+ import {
53
+ TxFlightProvider,
54
+ TxFlightList,
55
+ useTxFlight,
56
+ } from '@valve-tech/tx-flight-react'
57
+
58
+ function App() {
59
+ return (
60
+ <TxFlightProvider>
61
+ <Header />
62
+ <TxFlightList />
63
+ <Routes />
64
+ </TxFlightProvider>
65
+ )
66
+ }
67
+
68
+ function SubmitButton() {
69
+ const flight = useTxFlight()
70
+ return (
71
+ <button onClick={async () => {
72
+ await flight.addByHash({ hash, chainId: 1, client: publicClient, flow: 'mint' })
73
+ }}>
74
+ Submit
75
+ </button>
76
+ )
77
+ }
78
+ ```
79
+
80
+ Defaults: `id="default"`, `localStorageAdapter` (key `tx-flight:default`), `maxItems: 50`, `terminalRetentionMs: 60_000` (1 minute).
81
+
82
+ ## Anti-patterns to flag
83
+
84
+ When reviewing user code, watch for these and suggest fixes:
85
+
86
+ 1. **`useTxFlight()` outside a `<TxFlightProvider>`.** The hook throws on missing provider — that's the deliberate safety check. Wrap your app at the top level. Common cause: rendering the strip in a portal or a sub-tree that's outside the Provider's children.
87
+
88
+ 2. **`addByHash` without installing `@valve-tech/tx-tracker` + `@valve-tech/chain-source`.** They're optional peer deps — the dynamic import will fail at runtime with a module-not-found. Either install both, or use `addManual` for hash-bearing entries (sacrificing the watcher).
89
+
90
+ 3. **Setting `clientFactory` to a closure capturing rendered state.** The factory runs at *Provider mount + per-pending-entry* (during rehydrate), not at render. It must work without depending on React state — typically a module-level `Record<chainId, PublicClient>`:
91
+ ```tsx
92
+ // ✅ stable
93
+ const CLIENTS: Record<number, PublicClient> = {
94
+ 1: createPublicClient({ chain: mainnet, transport: http() }),
95
+ 369: createPublicClient({ chain: pulsechain, transport: http() }),
96
+ }
97
+ <TxFlightProvider clientFactory={(chainId) => CLIENTS[chainId]} />
98
+ ```
99
+
100
+ 4. **Two `<TxFlightProvider id="default">` mounted at peer locations expecting independent state.** The module-level registry shares stores by `id`. Same id = same store (deliberate for nested layouts). Use distinct ids when you want isolation:
101
+ ```tsx
102
+ <TxFlightProvider id="main">...</TxFlightProvider>
103
+ <TxFlightProvider id="settings">...</TxFlightProvider>
104
+ ```
105
+
106
+ 5. **Rendering `<TxFlightAge>` or `<TxFlightList>` from a React Server Component.** Both use hooks (`useEffect`, `useTxFlight`). RSC-safe components: `<TxFlightItem>` (default render path), `<TxFlightStatusIcon>`, `<TxFlightHashLink>`, `<TxFlightActions>`. The Provider itself is `'use client'`.
107
+
108
+ 6. **`<TxFlightHashLink>` with no `explorer` prop.** Silently degrades to `<span>` (no link). If the user wants a link, pass `explorer="https://etherscan.io"` (or a per-chainId resolver).
109
+
110
+ 7. **Custom `TxFlightStorage` adapter that throws on SSR.** If your adapter dereferences `window` / `localStorage` / `indexedDB` without a runtime gate, the server-side render crashes. Built-ins return `null` from `load` and resolve `save` no-op when the runtime is missing. Mirror that pattern.
111
+
112
+ 8. **`JSON.stringify`-ing `TrackedTx` directly in a custom adapter.** `submittedGas` fields are bigints — stringify will throw. The package exports `serialize` / `deserialize` from `'@valve-tech/tx-flight-react/storage'` that hex-encode bigint fields safely. Use those.
113
+
114
+ 9. **Wiring `onDropped` / `onReplaced` against `addWithWalletAdapter` alone.** wallet-adapter doesn't fire those (per its skill). If the user wants them, they need `addByHash` (which uses tx-tracker), or they need to also wire tx-tracker manually.
115
+
116
+ 10. **Persisting state but expecting `preparing` / `awaiting-signature` to resume on reload.** They can't — the wallet popup state isn't recoverable. The strip translates them to `failed` with `notes: 'lost during reload'`. Only `pending` (with hash) and terminal entries survive verbatim.
117
+
118
+ ## Storage adapter selection
119
+
120
+ | Adapter | Sync? | Default? | Use when |
121
+ |---|---|---|---|
122
+ | `localStorageAdapter({ keyPrefix? })` | sync | ✓ | Most apps. ~5MB per origin (browser limit). |
123
+ | `indexedDBAdapter({ dbName?, storeName? })` | async | — | Larger payloads (history-heavy strips with 100+ items per chain). |
124
+ | `memoryAdapter()` | sync | — | Tests, or "explicit no persistence" (the strip resets every reload). |
125
+ | Custom (`TxFlightStorage`) | either | — | Server-backed sync (e.g. Supabase per-user persistence), or mirroring to your existing app state. |
126
+
127
+ ## Multi-instance pattern
128
+
129
+ For nested layouts where the same logical strip mounts in more than one place (e.g. a sticky header + a detail panel), use the same `id` — the module-level registry refCounts so they share state:
130
+
131
+ ```tsx
132
+ <TxFlightProvider id="main">
133
+ <Header />
134
+ <Layout>
135
+ <TxFlightProvider id="main">
136
+ <DetailPanel />
137
+ </TxFlightProvider>
138
+ </Layout>
139
+ </TxFlightProvider>
140
+ ```
141
+
142
+ For genuinely independent strips (different routes, different scopes), use distinct ids:
143
+
144
+ ```tsx
145
+ <TxFlightProvider id="trading">...</TxFlightProvider>
146
+ <TxFlightProvider id="staking">...</TxFlightProvider>
147
+ ```
148
+
149
+ ## Rehydrate semantics — the rules
150
+
151
+ On Provider mount, the storage adapter's `load(id)` runs and persisted entries seed back into state. Per entry:
152
+
153
+ - `pending` with `hash` AND `clientFactory` is wired → fresh tx-tracker watcher async-attaches via the same internal `ChainSource + TxTracker` machinery.
154
+ - `pending` with `hash` but no `clientFactory` → stays `pending` indefinitely. Caller can re-issue `addByHash` to re-arm.
155
+ - `pending` without `hash` → impossible by construction; the strip never persists hashless entries past the wallet-sign window.
156
+ - `preparing` / `awaiting-signature` → translated to `failed` with `notes: 'lost during reload'`.
157
+ - Terminal entries (`confirmed` / `failed` / `dropped` / `replaced`) → preserved verbatim; eviction interval (~5s tick) prunes them past `terminalRetentionMs`.
158
+
159
+ ## Composition with sibling packages
160
+
161
+ - For the SDK side (define `WriteHookParams`, throw `WalletRejectedError` / `ContractRevertedError`), use `@valve-tech/wallet-adapter` directly. Skill: `wallet-adapter-integration`.
162
+ - For per-tx state-machine work without React (vanilla JS, SDK internals), use `@valve-tech/tx-tracker` directly. Skill: `tx-tracker-integration`.
163
+ - For sharing a `ChainSource` between multiple derived views (oracle + tracker + strip), see the chain-source skill — but the strip's `addByHash` deliberately uses its OWN private source, decoupled from any shared one. That's intentional; the strip is self-contained.
164
+
165
+ ## Where to find more
166
+
167
+ - Full API + types: `node_modules/@valve-tech/tx-flight-react/AGENTS.md`
168
+ - Human-facing docs: `node_modules/@valve-tech/tx-flight-react/README.md`
169
+ - Compiled output: `node_modules/@valve-tech/tx-flight-react/dist/`
170
+ - Sibling skills:
171
+ - wallet-adapter-integration for the lifecycle hook contract
172
+ - tx-tracker-integration for the per-tx state machine details