@tuwaio/pulsar-core 0.7.0 → 0.8.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/README.md CHANGED
@@ -1,380 +1,152 @@
1
- # Pulsar Core
1
+ # @tuwaio/pulsar-core
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/@tuwaio/pulsar-core.svg)](https://www.npmjs.com/package/@tuwaio/pulsar-core)
4
- [![License](https://img.shields.io/npm/l/@tuwaio/pulsar-core.svg)](./LICENSE)
5
- [![Build Status](https://img.shields.io/github/actions/workflow/status/TuwaIO/pulsar-core/release.yml?branch=main)](https://github.com/TuwaIO/pulsar-core/actions)
4
+ [![License](https://img.shields.io/npm/l/@tuwaio/pulsar-core.svg)](https://github.com/TuwaIO/pulsar-core/blob/main/packages/pulsar-core/LICENSE)
6
5
 
7
- Layer 3 (L3) of the TUWA Ecosystem. Framework-agnostic headless core store providing append-only localStorage transaction history ledgers.
6
+ `@tuwaio/pulsar-core` is the Layer 3 (L3) core package of **Pulsar**, the transaction tracking project of TUWA Stage 2 ("State & Connection", next to Satellite Connect). Built on **`zustand`** (with the `persist` middleware), **`immer`** and **`@tuwaio/orbit-core`**, it keeps the pool of tracked transactions, runs new transactions through chain adapters and restarts their trackers after a page reload. It has no chain logic, UI or network requests of its own: the chain adapters are [`@tuwaio/pulsar-evm`](https://pulsar.docs.tuwa.io/packages/pulsar-evm) and [`@tuwaio/pulsar-solana`](https://pulsar.docs.tuwa.io/packages/pulsar-solana).
8
7
 
9
8
  ---
10
9
 
11
- ## 🏛️ What is `@tuwaio/pulsar-core`?
10
+ ## 🏛️ Core Capabilities
12
11
 
13
- `@tuwaio/pulsar-core` is the framework-agnostic, zero-dependency client-side ledger layer of the Pulsar ecosystem. It contains no visual interface components or framework-specific render hooks.
14
-
15
- Its single purpose is to act as a headless state machine providing deterministic transaction status reconciliation and client-side state persistence across user sessions. Built on top of **Zustand** and **Immer**, it maintains transaction pool stability using persistent browser storage engines to secure an append-only transaction ledger.
16
-
17
- This package exports one primary factory function: `createPulsarStore`.
18
-
19
- ---
20
-
21
- ## ✨ Key Features
22
-
23
- - **Framework-Agnostic:** Orchestrate and integrate state logic into any environment (React, Vue, Svelte, or Node.js).
24
- - **Multi-Chain by Design:** Isolated state machine adapter system to support heterogeneous blockchain networks.
25
- - **Persistent State:** Client-side state persistence powered by browser storage engines to resume lifecycle tracking across refreshes.
26
- - **Append-Only Ledger:** High-stability transaction pool management using FIFO eviction policies.
27
- - **Type-Safe:** Zero-compromise TypeScript implementation with explicit interfaces.
12
+ - **Transaction store:** `createPulsarStore` returns a vanilla Zustand store. Its `executeTxAction` validates the metadata, sets `initialTx` for immediate UI feedback, checks the wallet's chain, runs the `beforeTxProcess` preflight, calls your `actionFunction` to sign and submit, adds the transaction to `transactionsPool` and starts its tracker.
13
+ - **Chain adapters:** pass one adapter or an array. Each transaction goes to the adapter whose `key` matches its `adapter` (the first adapter when none matches). Adapters implement the `TxAdapter` contract, so other chains can be added.
14
+ - **Persistence and resume:** the pool is saved to `localStorage` (see Browser Storage below). After a reload, `initializeTransactionsPool` restarts the trackers of pending transactions. The pool keeps at most `maxTransactions` (default 50) and evicts the oldest one.
15
+ - **Metadata safety:** each `title` string is limited to 100 characters, each `description` string to 300 and the JSON of `payload` to 10 KB, and strings must not contain executable-like patterns (`eval(`, `Function(`, `setTimeout`/`setInterval` with a string, `javascript:`). Invalid metadata throws `PulsarTransactionValidationError` before anything runs; invalid restored or remote transactions are dropped. This is a defensive gate, not a replacement for escaping output in your UI.
16
+ - **Remote sync:** `onRemoteCreate` sends every new transaction to your backend (for example Quasar) in the background, without its API keys, retries unconfirmed syncs later, and never delays or blocks tracking. `injectExternalPendingTxs` and `createTxInMemoryStore` bring the remote history back into the app; both drop transactions that fail validation.
17
+ - **Selectors and React binding:** `selectAllTransactions`, `selectPendingTransactions`, `selectTxByKey` and the `…ByActiveWallet` variants; `createBoundedUseStore` turns the store into a typed React hook.
18
+ - **Tracker building blocks:** `initializePollingTracker` (a polling loop with consecutive-failure retries) and `createTxUpdater` (keeps a tracker's copy of the transaction in sync with its store updates) for custom trackers.
28
19
 
29
20
  ---
30
21
 
31
22
  ## 💾 Installation
32
23
 
33
- This package requires `zustand`, `immer` and `dayjs` as peer dependencies. You must install them alongside `@tuwaio/pulsar-core`.
34
-
35
24
  ```bash
36
- # Using pnpm (recommended), but you can use npm, yarn or bun as well
37
25
  pnpm add @tuwaio/pulsar-core @tuwaio/orbit-core zustand immer dayjs
38
26
  ```
39
27
 
40
- ---
41
-
42
- ## 🚀 API & Usage
28
+ > [!IMPORTANT]
29
+ > `@tuwaio/orbit-core` (>=0.3), `zustand` (5.x), `immer` (11.x) and `dayjs` (1.x) are peer dependencies and must be installed alongside `@tuwaio/pulsar-core`. Add [`@tuwaio/pulsar-evm`](https://pulsar.docs.tuwa.io/packages/pulsar-evm) and/or [`@tuwaio/pulsar-solana`](https://pulsar.docs.tuwa.io/packages/pulsar-solana) for the chain adapters, and [`@tuwaio/pulsar-react`](https://pulsar.docs.tuwa.io/packages/pulsar-react) for React apps.
43
30
 
44
- ### `createPulsarStore(config)`
31
+ ---
45
32
 
46
- This is the main factory function that creates your transaction store. It takes a configuration object and returns a fully typed, ready-to-use vanilla Zustand store.
33
+ ## 🚀 Usage
47
34
 
48
- #### **Configuration Example**
35
+ ### Creating the store and sending a transaction
49
36
 
50
- ```ts
51
- import { createBoundedUseStore, createPulsarStore, Transaction } from '@tuwaio/pulsar-core';
37
+ ```typescript
38
+ import { OrbitAdapter } from '@tuwaio/orbit-core';
39
+ import { createPulsarStore, type EvmTransaction, TransactionStatus } from '@tuwaio/pulsar-core';
52
40
  import { pulsarEvmAdapter } from '@tuwaio/pulsar-evm';
41
+ import { createConfig, http, injected, sendTransaction } from '@wagmi/core';
42
+ import { sepolia } from 'viem/chains';
53
43
 
54
- import { appChains, config } from '@/configs/wagmiConfig';
44
+ const wagmiConfig = createConfig({
45
+ chains: [sepolia],
46
+ connectors: [injected()],
47
+ transports: { [sepolia.id]: http() },
48
+ });
55
49
 
56
- const storageName = 'transactions-tracking-storage';
50
+ export const pulsarStore = createPulsarStore<EvmTransaction>({
51
+ name: 'pulsar-transactions', // localStorage key
52
+ adapter: pulsarEvmAdapter(wagmiConfig, [sepolia]),
53
+ });
57
54
 
58
- export enum TxType {
59
- example = 'example',
55
+ // Once per page load, on the client: resume the transactions that were pending before a reload.
56
+ void pulsarStore.getState().initializeTransactionsPool();
57
+
58
+ export async function sendTip(to: `0x${string}`) {
59
+ await pulsarStore.getState().executeTxAction({
60
+ actionFunction: () => sendTransaction(wagmiConfig, { to, value: 1_000_000_000_000_000n }),
61
+ params: {
62
+ adapter: OrbitAdapter.EVM,
63
+ desiredChainID: sepolia.id,
64
+ type: 'tip',
65
+ title: ['Sending tip', 'Tip sent', 'Tip failed', 'Tip replaced'],
66
+ },
67
+ onSuccess: (tx) => console.log(tx.txKey, tx.status === TransactionStatus.Success),
68
+ });
60
69
  }
61
70
 
62
- type ExampleTx = Transaction & {
63
- type: TxType.example;
64
- payload: {
65
- value: number;
66
- };
67
- };
68
-
69
- export type TransactionUnion = ExampleTx;
70
-
71
- export const usePulsarStore = createBoundedUseStore(
72
- createPulsarStore<TransactionUnion>({
73
- name: storageName,
74
- adapter: pulsarEvmAdapter(config, appChains),
75
- maxTransactions: 100, // Optional: defaults to 50
76
- beforeTxProcess: async () => {
77
- // Optional global preflight. Throw here to block a transaction before wallet interaction.
78
- await assertUserCanSubmitTransactions();
79
- },
80
- }),
81
- );
71
+ // Read the state anywhere; `pulsarStore.subscribe` notifies you about changes.
72
+ pulsarStore.subscribe((state) => {
73
+ const pending = Object.values(state.transactionsPool).filter((tx) => tx.pending);
74
+ console.log(`${pending.length} pending transaction(s)`);
75
+ });
82
76
  ```
83
77
 
84
- ### Transaction Pool Management (FIFO)
85
-
86
- To prevent the `localStorage` from growing indefinitely, Pulsar Core implements a **FIFO (First-In, First-Out) Eviction Policy**.
78
+ `executeTxAction` rejects when the metadata is invalid, the wallet is on another chain and does not switch, `beforeTxProcess` throws (unless `abortOnTxError: false`) or the wallet rejects the transaction; `initialTx.error` holds the normalized error. For standard EVM transactions it resolves only when tracking has finished, so drive the UI from the store instead of awaiting it.
87
79
 
88
- - **Maximum Transactions:** By default, the store keeps the last **50** transactions. You can customize this via the `maxTransactions` property in the `createPulsarStore` config.
89
- - **Eviction Process:** When the pool exceeds the `maxTransactions` limit, the oldest transaction (based on `localTimestamp`) is automatically removed from the state and storage when a new one is added.
80
+ The full React setup, with a wallet connector, typed transactions and the Solana variant, is on the **[Getting Started](https://pulsar.docs.tuwa.io/gettingStarted)** page.
90
81
 
91
- ### Transaction Metadata Safety
82
+ ### Preflight checks
92
83
 
93
- Pulsar validates transaction metadata before it creates `initialTx`, calls the wallet action, writes to the local transaction pool, persists to `localStorage`, or calls `onRemoteCreate`.
84
+ `beforeTxProcess` runs after the chain check and before the wallet is asked to sign. Throw to block the transaction; a `beforeTxProcess` passed to `executeTxAction` replaces the global one for that transaction:
94
85
 
95
- - `title`: each string must be **100 characters or less**.
96
- - `description`: each string must be **300 characters or less**.
97
- - `payload`: must be JSON-serializable and **10KB or less** after UTF-8 JSON serialization.
98
- - `title`, `description`, and payload string values reject executable-like patterns such as `eval(`, `Function(`, `setTimeout("...")`, `setInterval("...")`, and `javascript:`.
99
-
100
- This validation is a defensive metadata gate, not a replacement for output escaping or HTML sanitization in UI code.
101
-
102
- Invalid transactions are rejected before execution. Invalid pending transactions restored from persisted storage are removed during `initializeTransactionsPool()`. Invalid remote transactions passed to `injectExternalPendingTxs()` are skipped with a warning so the rest of the batch can still sync.
103
-
104
- ### `beforeTxProcess`
86
+ ```typescript
87
+ import { OrbitAdapter } from '@tuwaio/orbit-core';
88
+ import { createPulsarStore, type EvmTransaction } from '@tuwaio/pulsar-core';
89
+ import { pulsarEvmAdapter } from '@tuwaio/pulsar-evm';
90
+ import { type Config } from '@wagmi/core';
91
+ import { sepolia } from 'viem/chains';
105
92
 
106
- Use `beforeTxProcess` for custom preflight policies such as auth checks, feature flags, rate limits, or application-level transaction guards. The callback receives no transaction metadata; throw an error to block the transaction before initialization or wallet interaction.
93
+ declare const wagmiConfig: Config;
94
+ declare function sendSwap(): Promise<`0x${string}`>;
107
95
 
108
- ```ts
109
- const store = createPulsarStore<TransactionUnion>({
110
- name: storageName,
111
- adapter: pulsarEvmAdapter(config, appChains),
112
- beforeTxProcess: async () => {
113
- await assertUserCanSubmitTransactions();
96
+ const pulsarStore = createPulsarStore<EvmTransaction>({
97
+ name: 'pulsar-transactions',
98
+ adapter: pulsarEvmAdapter(wagmiConfig, [sepolia]),
99
+ beforeTxProcess: () => {
100
+ if (!navigator.onLine) throw new Error('You are offline.');
114
101
  },
115
102
  });
116
- ```
117
-
118
- You can override the global callback for one transaction by passing `beforeTxProcess` to `executeTxAction`.
119
103
 
120
- ```ts
121
- await store.getState().executeTxAction({
104
+ await pulsarStore.getState().executeTxAction({
122
105
  actionFunction: sendSwap,
123
106
  beforeTxProcess: async () => {
124
- await assertSwapIsEnabled();
125
- },
126
- params: {
127
- adapter: OrbitAdapter.EVM,
128
- desiredChainID: 1,
129
- type: 'SWAP',
130
- title: 'Swap',
131
- description: 'Swap tokens',
107
+ const response = await fetch('/api/swaps/enabled');
108
+ if (!response.ok) throw new Error('Swaps are paused.');
132
109
  },
110
+ params: { adapter: OrbitAdapter.EVM, desiredChainID: sepolia.id, type: 'swap', title: 'Swap' },
133
111
  });
134
112
  ```
135
113
 
136
- When a local callback is provided to `executeTxAction`, it replaces the global callback for that action.
137
-
138
- ### `abortOnTxError`
139
-
140
- Use the `abortOnTxError` parameter (defaults to `true`) to control error propagation during transaction preflight and remote synchronization:
141
-
142
- - **Preflight hook (`beforeTxProcess`)**:
143
- - When `abortOnTxError` is `true` (default), any error thrown in `beforeTxProcess` will populate the `initialTx.error` state and throw, aborting the transaction action immediately.
144
- - When `abortOnTxError` is `false`, any error thrown in `beforeTxProcess` is caught, logged to the console as a warning, and the transaction execution continues.
145
- - **Remote sync hook (`onRemoteCreate`)**:
146
- - Errors in `onRemoteCreate` **always** abort the transaction flow (the transaction is not added to the local tracking pool, `initialTx.error` is populated, and the error is thrown) regardless of the `abortOnTxError` setting.
147
-
148
- You can set `abortOnTxError` globally during store creation or override it locally in `executeTxAction`:
149
-
150
- ```ts
151
- // Set globally (defaults to true if not provided)
152
- const store = createPulsarStore<TransactionUnion>({
153
- name: storageName,
154
- adapter: pulsarEvmAdapter(config, appChains),
155
- abortOnTxError: false, // Disable aborting for beforeTxProcess errors globally
156
- });
157
-
158
- // Override locally for a specific action
159
- await store.getState().executeTxAction({
160
- actionFunction: sendSwap,
161
- abortOnTxError: true, // Force abort on beforeTxProcess errors for this action
162
- params: {
163
- adapter: OrbitAdapter.EVM,
164
- desiredChainID: 1,
165
- type: 'SWAP',
166
- title: 'Swap',
167
- description: 'Swap tokens',
168
- },
169
- });
170
- ```
171
-
172
- ### The Returned Store API
173
-
174
- The `createPulsarStore` function returns a vanilla Zustand store with the following state and actions:
175
-
176
- #### **State**
177
-
178
- - `transactionsPool: Record<string, T>`: The primary state object. This is a map of all tracked transactions, where the key is the transaction's unique `txKey` (e.g., a transaction hash).
179
- - `initialTx?: InitialTransaction`: Holds the state of a transaction that is currently being initiated (e.g., waiting for a user's signature) but is not yet submitted to the network. Useful for providing instant UI feedback.
180
- - `lastAddedTxKey?: string`: The `txKey` of the most recently added transaction.
181
-
182
- #### **Actions**
183
-
184
- - `executeTxAction(params)`: The primary, all-in-one function for initiating, sending, and tracking a new transaction. It runs `beforeTxProcess` and metadata validation before wallet interaction.
185
- - `initializeTransactionsPool()`: An async function to re-initialize trackers for any pending transactions found in storage. **This is crucial for resuming tracking after a page reload.**
186
- - `addTxToPool(tx)`: Adds a new transaction directly to the tracking pool.
187
- - `updateTxParams(txKey, fields)`: Updates one or more properties of an existing transaction in the pool.
188
- - `removeTxFromPool(txKey)`: Removes a transaction from the pool by its key.
189
- - `closeTxTrackedModal(txKey?)`: A helper to manage UI state, which sets `isTrackedModalOpen` to `false` and clears the `initialTx` state.
190
- - `getLastTxKey()`: Returns the `txKey` of the most recently added transaction.
191
-
192
- #### **Selectors**
193
-
194
- The package also provides a set of selector functions to help you efficiently query the transaction pool:
195
-
196
- - `selectAllTransactions(pool)`: Returns all transactions sorted chronologically.
197
- - `selectPendingTransactions(pool)`: Returns only transactions that are currently pending.
198
- - `selectTxByKey(pool, txKey)`: Retrieves a specific transaction by its key.
199
- - `selectAllTransactionsByActiveWallet(pool, address)`: Returns all transactions for a specific wallet.
200
- - `selectPendingTransactionsByActiveWallet(pool, address)`: Returns pending transactions for a specific wallet.
201
-
202
- ---
203
-
204
- ### `createTxInMemoryStore({ ... })`
205
-
206
- While `createPulsarStore` is the primary entry point for tracking _active_ transactions, `createTxInMemoryStore` provides an in-memory transaction store with synchronized local and remote sources. It is designed to keep a local transaction pool in sync with remote history, preserve terminal transaction states, and support paginated history loading.
207
-
208
- #### **Configuration Example**
209
-
210
- ```ts
211
- // src/app/actions - next js app routes example of server actions
212
- 'use server';
213
-
214
- import { Quasar, Transaction } from '@tuwaio/quasar-sdk';
215
-
216
- const quasar = new Quasar({
217
- secretKey: process.env.QUASAR_SDK_SK ?? '',
218
- });
219
-
220
- // --- Server Action for syncCreate ---
221
- export async function syncTransaction(tx: Transaction) {
222
- try {
223
- console.log('Syncing tx to Quasar...', tx.txKey);
224
-
225
- await quasar.pulsar.syncCreate(tx, RP_NAME);
226
-
227
- return { success: true };
228
- } catch (error) {
229
- console.error('Sync failed', error);
230
- throw error;
231
- }
232
- }
233
-
234
- // --- Server Action for getHistory ---
235
- export async function getHistory(params?: {
236
- walletAddress: string;
237
- page?: number;
238
- limit?: number;
239
- chainId?: string;
240
- status?: string;
241
- txKey?: string;
242
- appName?: string;
243
- }) {
244
- try {
245
- const history = await quasar.pulsar.getHistory({
246
- ...params,
247
- });
248
-
249
- return history;
250
- } catch (error) {
251
- console.error('Get history failed', error);
252
- throw error;
253
- }
254
- }
255
- ```
256
-
257
- ```ts
258
- // src/store/pulsarStoreHook.ts - pulsar store hook example
259
-
260
- 'use client';
261
-
262
- import { createBoundedUseStore, createPulsarStore, createTxInMemoryStore } from '@tuwaio/pulsar-core';
263
- import { pulsarEvmAdapter } from '@tuwaio/pulsar-evm';
264
- import { useSiwxSessionStore } from '@tuwaio/siwx-react';
265
-
266
- import { appChains, config } from '@/configs/wagmiConfig';
267
- import { getHistory, syncTransaction } from '@/app/actions';
114
+ With `abortOnTxError: false` (globally or per call), a `beforeTxProcess` error is logged and the transaction continues. `abortOnTxError` does not apply to `onRemoteCreate`.
268
115
 
269
- const storageName = 'transactions-tracking-storage-with-bd';
116
+ ### Remote sync and recovery
270
117
 
271
- export enum TxType {
272
- example = 'example',
273
- }
118
+ Pass `onRemoteCreate` to send every new transaction to your backend, for example a server action that forwards it to [Quasar](https://sdk.docs.tuwa.io/quasar-cloud/overview). Pulsar keeps working when the backend is slow or down:
274
119
 
275
- type ExampleTx = Transaction & {
276
- type: TxType.example;
277
- payload: {
278
- value: number;
279
- };
280
- };
281
-
282
- export type TransactionUnion = ExampleTx;
283
-
284
- const initialStore = createPulsarStore<TransactionUnion>({
285
- name: storageName,
286
- adapter: [pulsarEvmAdapter(config, appChains)],
287
- onRemoteCreate: async (tx) => {
288
- const auth = useSiwxSessionStore.getState().session;
289
- await syncTransaction(tx, auth);
290
- },
291
- });
120
+ 1. `addTxToPool` writes the transaction to the pool (and to `localStorage`) first, with `syncStatus: 'pending-sync'` and its key in `unsyncedTxKeys`, and the tracker starts right away. `onRemoteCreate` runs in the background: when it resolves, the transaction becomes `'synced'` and the key is removed; when it rejects, the error is logged and the key stays. Reject (throw) on failure: a resolved promise counts as synced.
121
+ 2. The trackers keep following the transaction in the browser, so its status stays correct without the backend. A slow backend never delays tracking, and a sync interrupted by a closed tab is still listed after the reload.
122
+ 3. `reconcileUnsyncedTransactions` calls `onRemoteCreate` again for every unsynced transaction that is not already being sent: at the start of every `executeTxAction`, when an unsynced transaction reaches a terminal status, and when `createTxInMemoryStore` loads the first history page. Successful ones become `'synced'`; failed ones stay listed, also across reloads.
292
123
 
293
- export const usePulsarStore = createBoundedUseStore(initialStore);
294
-
295
- const pulsarInMemoryStore = createTxInMemoryStore<TransactionUnion>({
296
- localTransactionsPool: initialStore.getState().transactionsPool,
297
- reconcileUnsyncedTransactions: initialStore.getState().reconcileUnsyncedTransactions,
298
-
299
- getHistory: async ({ page, walletAddress }) => {
300
- try {
301
- const auth = useSiwxSessionStore.getState().session;
302
- const history = await getHistory(
303
- {
304
- walletAddress,
305
- page,
306
- limit: 10,
307
- appName: 'Example App',
308
- },
309
- auth,
310
- );
311
-
312
- if (!history) {
313
- return null;
314
- }
315
-
316
- return {
317
- ...history,
318
- docs: history.docs as TransactionUnion[],
319
- };
320
- } catch (error) {
321
- console.error('[PulsarHook] Failed to fetch history:', error);
322
- throw error;
323
- }
324
- },
124
+ `onRemoteCreate` receives a copy of the transaction without `pimlicoApiKey` and `gelatoApiKey`: configure provider keys on the backend instead (for Quasar, in the app settings). `bundlerUrl` is sent, so do not put API keys in it.
325
125
 
326
- onHistoryFetched: async (remoteTxs) => {
327
- await initialStore.getState().injectExternalPendingTxs(remoteTxs);
328
- },
329
- });
330
-
331
- initialStore.subscribe((state) => pulsarInMemoryStore.getState().syncWithLocalPool(state.transactionsPool));
332
-
333
- export const usePulsarInMemoryStore = createBoundedUseStore(pulsarInMemoryStore);
334
- ```
126
+ To show the remote history, `createTxInMemoryStore` merges the pages returned by your `getHistory` with the local pool (terminal transactions are never overwritten by stale data, and transactions that fail validation are skipped), and `injectExternalPendingTxs` adds pending transactions from other devices to the local pool and tracks them. The complete Next.js + Quasar integration, with server actions and SIWX sessions, is in the **[TUWA SDK documentation](https://sdk.docs.tuwa.io/full-stack)**.
335
127
 
336
128
  ---
337
129
 
338
- ## 🛠️ Advanced Usage: `initializePollingTracker`
130
+ ## 🗄️ Browser Storage
339
131
 
340
- For custom tracking requirements (like server-side tracking or non-standard APIs), you can use the low-level `initializePollingTracker` utility. This is the same engine used internally by Pulsar adapters for Gelato, Safe, and Solana.
132
+ `createPulsarStore` saves its state with Zustand's `persist` middleware, by default in `localStorage`. Pass another `storage` (and other `persist` options such as `partialize` or `version`) in the same config object to change it:
341
133
 
342
- ```ts
343
- import { initializePollingTracker } from '@tuwaio/pulsar-core';
134
+ | Key | Written by | Content |
135
+ | ---------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
136
+ | The `name` you pass (unique) | `createPulsarStore` | `{ state, version }` where `state` is `transactionsPool` (every tracked transaction), `lastAddedTxKey` and `unsyncedTxKeys` (keys not yet confirmed by `onRemoteCreate`) |
344
137
 
345
- await initializePollingTracker({
346
- tx: myTransaction,
347
- fetcher: async ({ stopPolling, onSuccess, onFailure }) => {
348
- const status = await checkMyCustomApi(myTransaction.txKey);
349
- if (status === 'done') onSuccess(status);
350
- if (status === 'error') onFailure(status);
351
- },
352
- onSuccess: (status) => console.log('Success!', status),
353
- onFailure: (status) => console.error('Failed!', status),
354
- });
355
- ```
356
-
357
- ---
358
-
359
- ## ✨ How It Connects to the Ecosystem
360
-
361
- Pulsar is a modular ecosystem. Here’s how the pieces fit together:
362
-
363
- - **`@tuwaio/pulsar-core`:** Provides the generic, headless state machine (`createPulsarStore`). It knows _how_ to manage state but doesn't know anything about specific blockchains.
364
- - **`@tuwaio/pulsar-evm`**: An adapter that plugs into the `adapters` config. It teaches the core store how to interact with EVM chains (e.g., how to check transaction receipts, get wallet info from Wagmi, etc.).
365
- - **`@tuwaio/pulsar-solana`**: An adapter that plugs into the `adapters` config. It extends the core store to work with the Solana ecosystem, teaching it how to track transactions, get wallet info from `@wallet-ui/react`, and use Solana RPCs.
366
- - **`@tuwaio/pulsar-react`**: Provides React bindings and hooks (like `useInitializeTransactionsPool`) to easily connect the Pulsar store to your React application's lifecycle.
138
+ - The state is written on every change: when a transaction is added, updated by its tracker or removed. `initialTx`, the transaction being signed, is neither saved nor restored, so a reload during signing leaves no stale signing state (pass your own `partialize` and `merge` to change that).
139
+ - In the browser the saved state is restored synchronously when the store is created, so the first client render already has the pool. Server rendering starts with an empty pool: render transaction lists on the client only. Where `localStorage` is unavailable, nothing is read or written.
140
+ - Nothing expires on its own. Transactions leave the pool only through the `maxTransactions` eviction or `removeTxFromPool`: the built-in trackers keep failed transactions as `Failed`. `store.persist.clearStorage()` deletes the saved state.
141
+ - Everything in a transaction is saved, including `payload` and, for ERC-4337, `bundlerUrl` and `pimlicoApiKey` (needed to resume tracking after a reload; a Pimlico key used in the browser is public anyway). Do not put secrets in `payload`.
142
+ - `createTxInMemoryStore` keeps its state in memory only.
367
143
 
368
144
  ---
369
145
 
370
- ## 🤝 Contributing & Support
371
-
372
- Contributions are welcome! Please read our main **[Contribution Guidelines](https://github.com/TuwaIO/workflows/blob/main/CONTRIBUTING.md)**.
373
-
374
- If you find this library useful, please consider supporting its development. Every contribution helps!
146
+ ## 📚 API Reference
375
147
 
376
- [**➡️ View Support Options**](https://github.com/TuwaIO/workflows/blob/main/Donation.md)
148
+ Every export, with signatures and types generated from the source, is documented at **[pulsar.docs.tuwa.io/packages/pulsar-core](https://pulsar.docs.tuwa.io/packages/pulsar-core)**.
377
149
 
378
150
  ## 📄 License
379
151
 
380
- This project is licensed under the **Apache-2.0 License** - see the [LICENSE](./LICENSE) file for details.
152
+ Licensed under the **Apache-2.0 License**. See the [LICENSE](https://github.com/TuwaIO/pulsar-core/blob/main/packages/pulsar-core/LICENSE) file for details.