@tuwaio/pulsar-core 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/NOTICE ADDED
@@ -0,0 +1,48 @@
1
+ @tuwaio/pulsar-core
2
+ Copyright 2025 FOP Tkach Oleksandr
3
+
4
+ This product is licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+
16
+ ========================================================================
17
+
18
+ This product contains software components from the following third-party project:
19
+
20
+ Project: bgd-labs/frontend-web3-utils
21
+ Source: https://github.com/bgd-labs/frontend-web3-utils
22
+ File used: /src/store/transactionsSelectors.ts
23
+
24
+ This component is licensed under the MIT License. The original copyright notice and license text are provided below.
25
+
26
+ ---
27
+
28
+ MIT License
29
+
30
+ Copyright (c) 2023 BGD Labs
31
+
32
+ Permission is hereby granted, free of charge, to any person obtaining a copy
33
+ of this software and associated documentation files (the "Software"), to deal
34
+ in the Software without restriction, including without limitation the rights
35
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36
+ copies of the Software, and to permit persons to whom the Software is
37
+ furnished to do so, subject to the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be included in all
40
+ copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
48
+ SOFTWARE.
package/README.md CHANGED
@@ -11,11 +11,12 @@
11
11
 
12
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
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.
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) by evicting the oldest finished transactions; a transaction still pending is never evicted, so the pool can hold more while many are pending. A finished transaction is dropped `retentionDays` (default 30) after it finished.
15
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
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
+ - **Transaction finished event:** the `onTxFinished` option of `createPulsarStore` and the store's `subscribeTxFinished` are called once when a transaction reaches `Success`, `Failed` or `Replaced`, whatever finished it: its tracker, a tracker restarted after a page reload (which no longer has the `onSuccess`, `onError` and `onReplaced` of `executeTxAction`), or remote sync. Use them for reactions that must also happen after a reload, such as refetching balances; in React, `useTxFinished` of [`@tuwaio/pulsar-react`](https://pulsar.docs.tuwa.io/packages/pulsar-react).
17
18
  - **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.
19
+ - **Tracker building blocks:** `initializePollingTracker` (a polling loop with consecutive-failure retries; its `pollingInterval` can be a function of the time since polling started, to poll less often over time) and `createTxUpdater` (keeps a tracker's copy of the transaction in sync with its store updates) for custom trackers.
19
20
 
20
21
  ---
21
22
 
@@ -113,6 +114,42 @@ await pulsarStore.getState().executeTxAction({
113
114
 
114
115
  With `abortOnTxError: false` (globally or per call), a `beforeTxProcess` error is logged and the transaction continues. `abortOnTxError` does not apply to `onRemoteCreate`.
115
116
 
117
+ ### Reacting to finished transactions
118
+
119
+ The callbacks of `executeTxAction` belong to one page session: functions cannot be saved to `localStorage`, so a transaction still pending at a reload finishes without them. Put reactions that must always happen in `onTxFinished`, which is part of the store's code and runs for every transaction, also after a reload. It runs synchronously while the store records the final status, before the callbacks of the transaction; its return value is not awaited, and an error it throws or a promise it rejects is logged without stopping tracking.
120
+
121
+ ```typescript
122
+ import { createPulsarStore, type Transaction, TransactionStatus, type TxAdapter } from '@tuwaio/pulsar-core';
123
+
124
+ declare const adapter: TxAdapter<Transaction>; // pulsarEvmAdapter(...) or pulsarSolanaAdapter(...)
125
+ declare function refreshBalances(): void;
126
+
127
+ export const pulsarStore = createPulsarStore<Transaction>({
128
+ name: 'pulsar-transactions',
129
+ adapter,
130
+ onTxFinished: (tx) => {
131
+ if (tx.status === TransactionStatus.Success) refreshBalances();
132
+ },
133
+ });
134
+
135
+ // Or add a listener later, for example from a component; it returns the function that removes it
136
+ const unsubscribe = pulsarStore.getState().subscribeTxFinished((tx) => console.log(tx.txKey, tx.status));
137
+ unsubscribe();
138
+ ```
139
+
140
+ A transaction that was already final when the store restored it from storage does not call them.
141
+
142
+ **Which one to use.** Both kinds of callbacks stay; they answer different questions:
143
+
144
+ | | `onTxFinished`, `subscribeTxFinished`, `useTxFinished` | `onSuccess`, `onError`, `onReplaced` of `executeTxAction` |
145
+ | ------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
146
+ | Runs for | Every transaction of the store, once, whatever finished it | The one transaction of that call |
147
+ | After a page reload | Yes | No: the page that started the transaction is gone |
148
+ | Receives | The transaction with its final `status` (and `error` when it failed) | The transaction; `onError` also the raw error, `onReplaced` also the transaction as tracking started |
149
+ | Use it for | Data that must stay correct: refetching balances or lists, analytics, telling your backend | The interface of the current page: closing a dialog, showing a message in place, navigating to a success page |
150
+
151
+ Decide what to do from the transaction itself, not from a function attached to it: switch on `tx.type` (your own label, such as `'SWAP'` or `'APPROVE'`) and read what you need from `tx.payload`. One listener can serve any number of transaction kinds, and any number of listeners can be added. For the same transaction, the store's listeners run first, then the callbacks of the call.
152
+
116
153
  ### Remote sync and recovery
117
154
 
118
155
  Pass `onRemoteCreate` to send every new transaction to your backend, for example a server action that forwards it to [Quasar](https://docs.tuwa.io/quasar). Pulsar keeps working when the backend is slow or down:
@@ -137,7 +174,7 @@ To show the remote history, `createTxInMemoryStore` merges the pages returned by
137
174
 
138
175
  - 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
176
  - 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.
177
+ - A finished transaction (including a `Failed` one) is dropped `retentionDays` after its `finishedTimestamp` (or its `localTimestamp` when it has none), when the saved state is restored and when a transaction is added; the default is 30 days, and `retentionDays: null` keeps finished transactions without a term. Pending transactions stay until they finish. Transactions also leave the pool through the `maxTransactions` eviction and `removeTxFromPool`. `store.persist.clearStorage()` deletes the saved state.
141
178
  - 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
179
  - `createTxInMemoryStore` keeps its state in memory only.
143
180
 
package/dist/index.d.mts CHANGED
@@ -127,12 +127,13 @@ type BaseTransaction = {
127
127
  connectorType: string;
128
128
  /**
129
129
  * Number of block confirmations the EVM trackers wait for before marking the transaction successful. Defaults to 1.
130
- * The Solana tracker always waits for the `finalized` commitment instead.
130
+ * The Solana tracker always waits for the `finalized` commitment instead. For a Safe transaction, the Safe tracker
131
+ * writes the number of owner confirmations the Safe needs to execute it (its threshold).
131
132
  */
132
133
  requiredConfirmations?: number;
133
134
  /**
134
135
  * Confirmations reported by the tracker while the transaction is pending. The Solana tracker sets it to `'MAX'` when
135
- * the transaction is finalized.
136
+ * the transaction is finalized. For a Safe transaction it is the number of owners who have confirmed it so far.
136
137
  */
137
138
  confirmations?: number | string | null;
138
139
  /**
@@ -285,7 +286,8 @@ type InitialTransaction = InitialTransactionParams & {
285
286
  /**
286
287
  * Callbacks passed to `executeTxAction` and forwarded to the tracker of that transaction. They are not stored:
287
288
  * trackers restarted by `initializeTransactionsPool` or `injectExternalPendingTxs` (for example after a page reload)
288
- * run without them. Their return values are not awaited.
289
+ * run without them. Their return values are not awaited. For reactions that must also run after a reload, use the
290
+ * `onTxFinished` option of `createPulsarStore` or `subscribeTxFinished` of the store: they run first, then these.
289
291
  *
290
292
  * @template T - The application transaction type.
291
293
  */
@@ -308,6 +310,21 @@ interface TrackerCallbacks<T extends Transaction> {
308
310
  */
309
311
  onReplaced?: (newTx: T, oldTx: T) => Promise<void> | void;
310
312
  }
313
+ /**
314
+ * A listener of the "transaction finished" event: `onTxFinished` of `createPulsarStore` and `subscribeTxFinished` of
315
+ * the store.
316
+ *
317
+ * It is called once per transaction, when the transaction moves from a non-final status to `Success`, `Failed` or
318
+ * `Replaced`, whatever finished it: the tracker started by `executeTxAction`, a tracker restarted after a page reload
319
+ * by `initializeTransactionsPool`, or a remote backend through `injectExternalPendingTxs`. Transactions that are
320
+ * already final when the store restores them from storage do not call it. It runs synchronously while the store
321
+ * writes the final status, before the `TrackerCallbacks` of the transaction. Its return value is not awaited; an error
322
+ * it throws or a promise it rejects is logged and does not stop the other listeners.
323
+ *
324
+ * @template T - The application transaction type.
325
+ * @param tx - The transaction with its final `status`.
326
+ */
327
+ type TxFinishedListener<T extends Transaction> = (tx: T) => Promise<void> | void;
311
328
  /**
312
329
  * Callbacks that synchronize the local pool with a remote backend (for example Quasar). Passed to
313
330
  * `createPulsarStore`.
@@ -342,8 +359,19 @@ type BeforeTxProcess = () => Promise<void> | void;
342
359
  type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> & {
343
360
  /** Global preflight callback run before every transaction. A `beforeTxProcess` passed to `executeTxAction` replaces it. */
344
361
  beforeTxProcess?: BeforeTxProcess;
345
- /** Maximum number of transactions in the pool. When it is full, the oldest one (by `localTimestamp`) is evicted. Defaults to 50. */
362
+ /**
363
+ * Maximum number of transactions in the pool. When it is full, the oldest finished ones (by `localTimestamp`) are
364
+ * evicted; pending transactions are never evicted, so while more of them are pending the pool holds more. Defaults to
365
+ * 50.
366
+ */
346
367
  maxTransactions?: number;
368
+ /**
369
+ * Days a finished transaction stays in the pool, counted from its `finishedTimestamp` (or `localTimestamp` when it
370
+ * has none). Expired ones are dropped when the saved state is restored and when a transaction is added; pending
371
+ * transactions stay until they finish. `null` keeps finished transactions until `maxTransactions` evicts them.
372
+ * Defaults to 30.
373
+ */
374
+ retentionDays?: number | null;
347
375
  /** @deprecated Gelato relay is deprecated. Gelato API key used to track `TransactionTracker.Gelato` transactions. */
348
376
  gelatoApiKey?: string;
349
377
  /**
@@ -351,6 +379,11 @@ type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> &
351
379
  * `onRemoteCreate`, whose errors never abort the transaction.
352
380
  */
353
381
  abortOnTxError?: boolean;
382
+ /**
383
+ * Called once when a transaction finishes (see `TxFinishedListener`), also when a tracker restarted after a page
384
+ * reload finishes it. Runs before the listeners added with `subscribeTxFinished`.
385
+ */
386
+ onTxFinished?: TxFinishedListener<T>;
354
387
  } & SyncCallbacks<T>;
355
388
  /**
356
389
  * The input of `TxAdapter.checkTransactionsTracker`: the key returned by the action and the context needed to pick a
@@ -471,7 +504,7 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
471
504
  initialTx?: InitialTransaction;
472
505
  /**
473
506
  * Validates a transaction and adds it to the pool with `pending: true`. When the pool already holds
474
- * `maxTransactions` transactions, the oldest one is evicted. If `onRemoteCreate` is configured, the transaction gets
507
+ * `maxTransactions` transactions, the oldest finished ones are evicted (pending ones never are). If `onRemoteCreate` is configured, the transaction gets
475
508
  * `syncStatus: 'pending-sync'`, its key is listed in `unsyncedTxKeys`, and `onRemoteCreate` runs in the background
476
509
  * (see `SyncCallbacks`). Does not start a tracker.
477
510
  * @param tx - The transaction to add.
@@ -482,7 +515,9 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
482
515
  addTxToPool: (tx: T) => Promise<void>;
483
516
  /**
484
517
  * Merges fields into a transaction of the pool; does nothing if the key is unknown. When `fields.status` is terminal
485
- * and the transaction is in `unsyncedTxKeys`, it starts `reconcileUnsyncedTransactions` in the background.
518
+ * and the transaction is in `unsyncedTxKeys`, it starts `reconcileUnsyncedTransactions` in the background. When it
519
+ * moves the transaction from a non-final to a final status, it calls the "transaction finished" listeners (see
520
+ * `TxFinishedListener`).
486
521
  * @param txKey - The key of the transaction.
487
522
  * @param fields - The fields to merge.
488
523
  */
@@ -561,10 +596,19 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
561
596
  /**
562
597
  * Restarts the trackers of all pending transactions in the pool, for example after a page reload. Pending
563
598
  * transactions that fail validation are removed from the pool. Call it once per page load: every call starts new
564
- * trackers, and trackers started here have no `TrackerCallbacks`.
599
+ * trackers. Trackers started here have no `TrackerCallbacks`; `onTxFinished` and `subscribeTxFinished` still run
600
+ * when they finish a transaction.
565
601
  * @returns A promise that resolves when all trackers have started.
566
602
  */
567
603
  initializeTransactionsPool: () => Promise<void>;
604
+ /**
605
+ * Adds a listener of the "transaction finished" event (see `TxFinishedListener`). Listeners live in memory, not in
606
+ * storage: add them after every page load, for example in a React effect (`useTxFinished` of
607
+ * `@tuwaio/pulsar-react`). Adding the same function twice registers it once.
608
+ * @param listener - Called with the transaction when it finishes.
609
+ * @returns A function that removes the listener.
610
+ */
611
+ subscribeTxFinished: (listener: TxFinishedListener<T>) => () => void;
568
612
  /**
569
613
  * Merges transactions from a remote backend into the pool (cross-device sync). Invalid transactions are skipped
570
614
  * with a warning. Pending remote transactions that are not in the pool are added and tracked. Local pending
@@ -674,13 +718,19 @@ type ITxInMemoryStoreParameters<T extends Transaction> = {
674
718
  *
675
719
  * @template T - The application transaction type.
676
720
  * @param params - The slice options.
677
- * @param params.maxTransactions - Maximum number of transactions in the pool; `addTxToPool` evicts the oldest one
678
- * (by `localTimestamp`) when the pool is full.
721
+ * @param params.maxTransactions - Maximum number of transactions in the pool; `addTxToPool` evicts the oldest
722
+ * finished ones (by `localTimestamp`) to stay within it. Pending transactions are never evicted, so the pool can hold
723
+ * more while they are pending.
724
+ * @param params.retentionDays - Days a finished transaction stays in the pool; `addTxToPool` drops the expired ones
725
+ * first. `null` keeps them without a term. Defaults to 30.
679
726
  * @param params.onRemoteCreate - Optional remote sync callback, called in the background by `addTxToPool` (see
680
727
  * `SyncCallbacks`).
728
+ * @param params.onTxFinished - Called by `updateTxParams` once when it moves a transaction from a non-final to a final
729
+ * status (see `TxFinishedListener`). `createPulsarStore` passes a function that runs its `onTxFinished` option and the
730
+ * listeners of `subscribeTxFinished`; it must not throw.
681
731
  * @returns A slice creator for Zustand's `create` / `createStore`.
682
732
  */
683
- declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, onRemoteCreate, }: Pick<PulsarAdapter<T>, 'onRemoteCreate'> & {
733
+ declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, retentionDays, onRemoteCreate, onTxFinished, }: Pick<PulsarAdapter<T>, 'onRemoteCreate' | 'retentionDays' | 'onTxFinished'> & {
684
734
  maxTransactions: number;
685
735
  }): StoreSlice<IInitializeTxTrackingStore<T>>;
686
736
 
@@ -763,7 +813,8 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
763
813
  * `createBoundedUseStore`) that runs transactions through chain adapters and tracks them in the background.
764
814
  *
765
815
  * Side effects: the state is saved with Zustand's `persist` middleware under the key `name`, by default in
766
- * `localStorage`, on every change: `transactionsPool`, `lastAddedTxKey` and `unsyncedTxKeys`. `initialTx` is neither
816
+ * `localStorage`, on every change: `transactionsPool`, `lastAddedTxKey` and `unsyncedTxKeys`. A finished transaction
817
+ * is kept for `retentionDays` (30 by default) and at most `maxTransactions` are kept. `initialTx` is neither
767
818
  * saved nor restored (pass your own `partialize` and `merge` to change that). In the browser the saved state is
768
819
  * restored synchronously when the store is created. Where
769
820
  * `localStorage` is not available (server rendering), nothing is read or written and Zustand logs a warning on
@@ -774,10 +825,15 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
774
825
  * @param params.adapter - A chain adapter, or an array of adapters, such as `pulsarEvmAdapter` from
775
826
  * `@tuwaio/pulsar-evm` or `pulsarSolanaAdapter` from `@tuwaio/pulsar-solana`.
776
827
  * @param params.maxTransactions - Maximum number of transactions in the pool. Defaults to 50.
828
+ * @param params.retentionDays - Days a finished transaction stays in the pool, from its `finishedTimestamp`: expired
829
+ * ones are dropped when the saved state is restored and when a transaction is added. `null` keeps them without a
830
+ * term. Defaults to 30.
777
831
  * @param params.onRemoteCreate - Remote sync callback (see `SyncCallbacks`).
778
832
  * @param params.gelatoApiKey - Deprecated Gelato API key, passed to the trackers.
779
833
  * @param params.beforeTxProcess - Global preflight callback (see `BeforeTxProcess`).
780
834
  * @param params.abortOnTxError - Whether a `beforeTxProcess` error aborts the transaction. Defaults to `true`.
835
+ * @param params.onTxFinished - Called once when a transaction finishes, also after a page reload (see
836
+ * `TxFinishedListener`).
781
837
  * @param params.name - The storage key. Required by `persist`; use a different key for every store.
782
838
  * @returns The vanilla Zustand store. `store.persist` exposes the `persist` API (for example `clearStorage()`).
783
839
  *
@@ -792,7 +848,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
792
848
  * });
793
849
  * ```
794
850
  */
795
- declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
851
+ declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, retentionDays, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, onTxFinished, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
796
852
  setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
797
853
  setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
798
854
  persist: {
@@ -963,15 +1019,19 @@ type PollingTrackerConfig<R, T extends Pick<Transaction, 'txKey' | 'pending'>> =
963
1019
  * @param txKey - The `txKey` of the tracked transaction.
964
1020
  */
965
1021
  removeTxFromPool?: (txKey: string) => void;
966
- /** The delay before each attempt, in milliseconds. Defaults to 5000. */
967
- pollingInterval?: number;
1022
+ /**
1023
+ * The delay before each attempt, in milliseconds, or a function that returns it from the milliseconds elapsed since
1024
+ * polling started, to poll less often over time. Defaults to 5000.
1025
+ */
1026
+ pollingInterval?: number | ((elapsedMs: number) => number);
968
1027
  /** The number of consecutive failed attempts (thrown errors) after which polling stops. Defaults to 10. */
969
1028
  maxRetries?: number;
970
1029
  };
971
1030
  /**
972
1031
  * Starts polling a transaction in the background and returns immediately. Does nothing if `tx.pending` is `false`.
973
1032
  *
974
- * Every `pollingInterval` milliseconds (the first attempt also waits) it calls `fetcher`. Polling continues until the
1033
+ * Every `pollingInterval` milliseconds (the first attempt also waits; a function is asked before each wait) it calls
1034
+ * `fetcher`. Polling continues until the
975
1035
  * fetcher calls `stopPolling`. A fetcher that throws counts as a failed attempt; after `maxRetries` consecutive failed
976
1036
  * attempts the tracker calls `onFailure()` without arguments, logs a warning and stops (which calls
977
1037
  * `removeTxFromPool`, if configured). A successful attempt resets the count.
@@ -1047,4 +1107,4 @@ declare function validateInitialTransactionParams(params: Omit<InitialTransactio
1047
1107
  declare function validateTransaction<T extends Transaction>(tx: T): void;
1048
1108
 
1049
1109
  export { MAX_TRANSACTION_DESCRIPTION_LENGTH, MAX_TRANSACTION_PAYLOAD_BYTES, MAX_TRANSACTION_TITLE_LENGTH, PulsarTransactionValidationError, TransactionStatus, TransactionTracker, createBoundedUseStore, createPulsarStore, createTxInMemoryStore, createTxUpdater, initializePollingTracker, initializeTxTrackingStore, selectAllTransactions, selectAllTransactionsByActiveWallet, selectPendingTransactions, selectPendingTransactionsByActiveWallet, selectTxByKey, validateInitialTransactionParams, validateTransaction };
1050
- export type { ActionTxKey, BaseTransaction, BeforeTxProcess, CheckTxTracker, EvmTransaction, ExtractState, IInitializeTxTrackingStore, ITxInMemoryStore, ITxInMemoryStoreParameters, ITxTrackingStore, InitialTransaction, InitialTransactionParams, PollingFetcherParams, PollingTrackerConfig, PulsarAdapter, SolanaTransaction, StarknetTransaction, StoreSlice, SyncCallbacks, TrackerCallbacks, Transaction, TransactionPool, TxAdapter, TxInMemoryPagination, UpdatableTransactionFields };
1110
+ export type { ActionTxKey, BaseTransaction, BeforeTxProcess, CheckTxTracker, EvmTransaction, ExtractState, IInitializeTxTrackingStore, ITxInMemoryStore, ITxInMemoryStoreParameters, ITxTrackingStore, InitialTransaction, InitialTransactionParams, PollingFetcherParams, PollingTrackerConfig, PulsarAdapter, SolanaTransaction, StarknetTransaction, StoreSlice, SyncCallbacks, TrackerCallbacks, Transaction, TransactionPool, TxAdapter, TxFinishedListener, TxInMemoryPagination, UpdatableTransactionFields };
package/dist/index.d.ts CHANGED
@@ -127,12 +127,13 @@ type BaseTransaction = {
127
127
  connectorType: string;
128
128
  /**
129
129
  * Number of block confirmations the EVM trackers wait for before marking the transaction successful. Defaults to 1.
130
- * The Solana tracker always waits for the `finalized` commitment instead.
130
+ * The Solana tracker always waits for the `finalized` commitment instead. For a Safe transaction, the Safe tracker
131
+ * writes the number of owner confirmations the Safe needs to execute it (its threshold).
131
132
  */
132
133
  requiredConfirmations?: number;
133
134
  /**
134
135
  * Confirmations reported by the tracker while the transaction is pending. The Solana tracker sets it to `'MAX'` when
135
- * the transaction is finalized.
136
+ * the transaction is finalized. For a Safe transaction it is the number of owners who have confirmed it so far.
136
137
  */
137
138
  confirmations?: number | string | null;
138
139
  /**
@@ -285,7 +286,8 @@ type InitialTransaction = InitialTransactionParams & {
285
286
  /**
286
287
  * Callbacks passed to `executeTxAction` and forwarded to the tracker of that transaction. They are not stored:
287
288
  * trackers restarted by `initializeTransactionsPool` or `injectExternalPendingTxs` (for example after a page reload)
288
- * run without them. Their return values are not awaited.
289
+ * run without them. Their return values are not awaited. For reactions that must also run after a reload, use the
290
+ * `onTxFinished` option of `createPulsarStore` or `subscribeTxFinished` of the store: they run first, then these.
289
291
  *
290
292
  * @template T - The application transaction type.
291
293
  */
@@ -308,6 +310,21 @@ interface TrackerCallbacks<T extends Transaction> {
308
310
  */
309
311
  onReplaced?: (newTx: T, oldTx: T) => Promise<void> | void;
310
312
  }
313
+ /**
314
+ * A listener of the "transaction finished" event: `onTxFinished` of `createPulsarStore` and `subscribeTxFinished` of
315
+ * the store.
316
+ *
317
+ * It is called once per transaction, when the transaction moves from a non-final status to `Success`, `Failed` or
318
+ * `Replaced`, whatever finished it: the tracker started by `executeTxAction`, a tracker restarted after a page reload
319
+ * by `initializeTransactionsPool`, or a remote backend through `injectExternalPendingTxs`. Transactions that are
320
+ * already final when the store restores them from storage do not call it. It runs synchronously while the store
321
+ * writes the final status, before the `TrackerCallbacks` of the transaction. Its return value is not awaited; an error
322
+ * it throws or a promise it rejects is logged and does not stop the other listeners.
323
+ *
324
+ * @template T - The application transaction type.
325
+ * @param tx - The transaction with its final `status`.
326
+ */
327
+ type TxFinishedListener<T extends Transaction> = (tx: T) => Promise<void> | void;
311
328
  /**
312
329
  * Callbacks that synchronize the local pool with a remote backend (for example Quasar). Passed to
313
330
  * `createPulsarStore`.
@@ -342,8 +359,19 @@ type BeforeTxProcess = () => Promise<void> | void;
342
359
  type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> & {
343
360
  /** Global preflight callback run before every transaction. A `beforeTxProcess` passed to `executeTxAction` replaces it. */
344
361
  beforeTxProcess?: BeforeTxProcess;
345
- /** Maximum number of transactions in the pool. When it is full, the oldest one (by `localTimestamp`) is evicted. Defaults to 50. */
362
+ /**
363
+ * Maximum number of transactions in the pool. When it is full, the oldest finished ones (by `localTimestamp`) are
364
+ * evicted; pending transactions are never evicted, so while more of them are pending the pool holds more. Defaults to
365
+ * 50.
366
+ */
346
367
  maxTransactions?: number;
368
+ /**
369
+ * Days a finished transaction stays in the pool, counted from its `finishedTimestamp` (or `localTimestamp` when it
370
+ * has none). Expired ones are dropped when the saved state is restored and when a transaction is added; pending
371
+ * transactions stay until they finish. `null` keeps finished transactions until `maxTransactions` evicts them.
372
+ * Defaults to 30.
373
+ */
374
+ retentionDays?: number | null;
347
375
  /** @deprecated Gelato relay is deprecated. Gelato API key used to track `TransactionTracker.Gelato` transactions. */
348
376
  gelatoApiKey?: string;
349
377
  /**
@@ -351,6 +379,11 @@ type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> &
351
379
  * `onRemoteCreate`, whose errors never abort the transaction.
352
380
  */
353
381
  abortOnTxError?: boolean;
382
+ /**
383
+ * Called once when a transaction finishes (see `TxFinishedListener`), also when a tracker restarted after a page
384
+ * reload finishes it. Runs before the listeners added with `subscribeTxFinished`.
385
+ */
386
+ onTxFinished?: TxFinishedListener<T>;
354
387
  } & SyncCallbacks<T>;
355
388
  /**
356
389
  * The input of `TxAdapter.checkTransactionsTracker`: the key returned by the action and the context needed to pick a
@@ -471,7 +504,7 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
471
504
  initialTx?: InitialTransaction;
472
505
  /**
473
506
  * Validates a transaction and adds it to the pool with `pending: true`. When the pool already holds
474
- * `maxTransactions` transactions, the oldest one is evicted. If `onRemoteCreate` is configured, the transaction gets
507
+ * `maxTransactions` transactions, the oldest finished ones are evicted (pending ones never are). If `onRemoteCreate` is configured, the transaction gets
475
508
  * `syncStatus: 'pending-sync'`, its key is listed in `unsyncedTxKeys`, and `onRemoteCreate` runs in the background
476
509
  * (see `SyncCallbacks`). Does not start a tracker.
477
510
  * @param tx - The transaction to add.
@@ -482,7 +515,9 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
482
515
  addTxToPool: (tx: T) => Promise<void>;
483
516
  /**
484
517
  * Merges fields into a transaction of the pool; does nothing if the key is unknown. When `fields.status` is terminal
485
- * and the transaction is in `unsyncedTxKeys`, it starts `reconcileUnsyncedTransactions` in the background.
518
+ * and the transaction is in `unsyncedTxKeys`, it starts `reconcileUnsyncedTransactions` in the background. When it
519
+ * moves the transaction from a non-final to a final status, it calls the "transaction finished" listeners (see
520
+ * `TxFinishedListener`).
486
521
  * @param txKey - The key of the transaction.
487
522
  * @param fields - The fields to merge.
488
523
  */
@@ -561,10 +596,19 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
561
596
  /**
562
597
  * Restarts the trackers of all pending transactions in the pool, for example after a page reload. Pending
563
598
  * transactions that fail validation are removed from the pool. Call it once per page load: every call starts new
564
- * trackers, and trackers started here have no `TrackerCallbacks`.
599
+ * trackers. Trackers started here have no `TrackerCallbacks`; `onTxFinished` and `subscribeTxFinished` still run
600
+ * when they finish a transaction.
565
601
  * @returns A promise that resolves when all trackers have started.
566
602
  */
567
603
  initializeTransactionsPool: () => Promise<void>;
604
+ /**
605
+ * Adds a listener of the "transaction finished" event (see `TxFinishedListener`). Listeners live in memory, not in
606
+ * storage: add them after every page load, for example in a React effect (`useTxFinished` of
607
+ * `@tuwaio/pulsar-react`). Adding the same function twice registers it once.
608
+ * @param listener - Called with the transaction when it finishes.
609
+ * @returns A function that removes the listener.
610
+ */
611
+ subscribeTxFinished: (listener: TxFinishedListener<T>) => () => void;
568
612
  /**
569
613
  * Merges transactions from a remote backend into the pool (cross-device sync). Invalid transactions are skipped
570
614
  * with a warning. Pending remote transactions that are not in the pool are added and tracked. Local pending
@@ -674,13 +718,19 @@ type ITxInMemoryStoreParameters<T extends Transaction> = {
674
718
  *
675
719
  * @template T - The application transaction type.
676
720
  * @param params - The slice options.
677
- * @param params.maxTransactions - Maximum number of transactions in the pool; `addTxToPool` evicts the oldest one
678
- * (by `localTimestamp`) when the pool is full.
721
+ * @param params.maxTransactions - Maximum number of transactions in the pool; `addTxToPool` evicts the oldest
722
+ * finished ones (by `localTimestamp`) to stay within it. Pending transactions are never evicted, so the pool can hold
723
+ * more while they are pending.
724
+ * @param params.retentionDays - Days a finished transaction stays in the pool; `addTxToPool` drops the expired ones
725
+ * first. `null` keeps them without a term. Defaults to 30.
679
726
  * @param params.onRemoteCreate - Optional remote sync callback, called in the background by `addTxToPool` (see
680
727
  * `SyncCallbacks`).
728
+ * @param params.onTxFinished - Called by `updateTxParams` once when it moves a transaction from a non-final to a final
729
+ * status (see `TxFinishedListener`). `createPulsarStore` passes a function that runs its `onTxFinished` option and the
730
+ * listeners of `subscribeTxFinished`; it must not throw.
681
731
  * @returns A slice creator for Zustand's `create` / `createStore`.
682
732
  */
683
- declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, onRemoteCreate, }: Pick<PulsarAdapter<T>, 'onRemoteCreate'> & {
733
+ declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, retentionDays, onRemoteCreate, onTxFinished, }: Pick<PulsarAdapter<T>, 'onRemoteCreate' | 'retentionDays' | 'onTxFinished'> & {
684
734
  maxTransactions: number;
685
735
  }): StoreSlice<IInitializeTxTrackingStore<T>>;
686
736
 
@@ -763,7 +813,8 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
763
813
  * `createBoundedUseStore`) that runs transactions through chain adapters and tracks them in the background.
764
814
  *
765
815
  * Side effects: the state is saved with Zustand's `persist` middleware under the key `name`, by default in
766
- * `localStorage`, on every change: `transactionsPool`, `lastAddedTxKey` and `unsyncedTxKeys`. `initialTx` is neither
816
+ * `localStorage`, on every change: `transactionsPool`, `lastAddedTxKey` and `unsyncedTxKeys`. A finished transaction
817
+ * is kept for `retentionDays` (30 by default) and at most `maxTransactions` are kept. `initialTx` is neither
767
818
  * saved nor restored (pass your own `partialize` and `merge` to change that). In the browser the saved state is
768
819
  * restored synchronously when the store is created. Where
769
820
  * `localStorage` is not available (server rendering), nothing is read or written and Zustand logs a warning on
@@ -774,10 +825,15 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
774
825
  * @param params.adapter - A chain adapter, or an array of adapters, such as `pulsarEvmAdapter` from
775
826
  * `@tuwaio/pulsar-evm` or `pulsarSolanaAdapter` from `@tuwaio/pulsar-solana`.
776
827
  * @param params.maxTransactions - Maximum number of transactions in the pool. Defaults to 50.
828
+ * @param params.retentionDays - Days a finished transaction stays in the pool, from its `finishedTimestamp`: expired
829
+ * ones are dropped when the saved state is restored and when a transaction is added. `null` keeps them without a
830
+ * term. Defaults to 30.
777
831
  * @param params.onRemoteCreate - Remote sync callback (see `SyncCallbacks`).
778
832
  * @param params.gelatoApiKey - Deprecated Gelato API key, passed to the trackers.
779
833
  * @param params.beforeTxProcess - Global preflight callback (see `BeforeTxProcess`).
780
834
  * @param params.abortOnTxError - Whether a `beforeTxProcess` error aborts the transaction. Defaults to `true`.
835
+ * @param params.onTxFinished - Called once when a transaction finishes, also after a page reload (see
836
+ * `TxFinishedListener`).
781
837
  * @param params.name - The storage key. Required by `persist`; use a different key for every store.
782
838
  * @returns The vanilla Zustand store. `store.persist` exposes the `persist` API (for example `clearStorage()`).
783
839
  *
@@ -792,7 +848,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
792
848
  * });
793
849
  * ```
794
850
  */
795
- declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
851
+ declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, retentionDays, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, onTxFinished, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
796
852
  setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
797
853
  setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
798
854
  persist: {
@@ -963,15 +1019,19 @@ type PollingTrackerConfig<R, T extends Pick<Transaction, 'txKey' | 'pending'>> =
963
1019
  * @param txKey - The `txKey` of the tracked transaction.
964
1020
  */
965
1021
  removeTxFromPool?: (txKey: string) => void;
966
- /** The delay before each attempt, in milliseconds. Defaults to 5000. */
967
- pollingInterval?: number;
1022
+ /**
1023
+ * The delay before each attempt, in milliseconds, or a function that returns it from the milliseconds elapsed since
1024
+ * polling started, to poll less often over time. Defaults to 5000.
1025
+ */
1026
+ pollingInterval?: number | ((elapsedMs: number) => number);
968
1027
  /** The number of consecutive failed attempts (thrown errors) after which polling stops. Defaults to 10. */
969
1028
  maxRetries?: number;
970
1029
  };
971
1030
  /**
972
1031
  * Starts polling a transaction in the background and returns immediately. Does nothing if `tx.pending` is `false`.
973
1032
  *
974
- * Every `pollingInterval` milliseconds (the first attempt also waits) it calls `fetcher`. Polling continues until the
1033
+ * Every `pollingInterval` milliseconds (the first attempt also waits; a function is asked before each wait) it calls
1034
+ * `fetcher`. Polling continues until the
975
1035
  * fetcher calls `stopPolling`. A fetcher that throws counts as a failed attempt; after `maxRetries` consecutive failed
976
1036
  * attempts the tracker calls `onFailure()` without arguments, logs a warning and stops (which calls
977
1037
  * `removeTxFromPool`, if configured). A successful attempt resets the count.
@@ -1047,4 +1107,4 @@ declare function validateInitialTransactionParams(params: Omit<InitialTransactio
1047
1107
  declare function validateTransaction<T extends Transaction>(tx: T): void;
1048
1108
 
1049
1109
  export { MAX_TRANSACTION_DESCRIPTION_LENGTH, MAX_TRANSACTION_PAYLOAD_BYTES, MAX_TRANSACTION_TITLE_LENGTH, PulsarTransactionValidationError, TransactionStatus, TransactionTracker, createBoundedUseStore, createPulsarStore, createTxInMemoryStore, createTxUpdater, initializePollingTracker, initializeTxTrackingStore, selectAllTransactions, selectAllTransactionsByActiveWallet, selectPendingTransactions, selectPendingTransactionsByActiveWallet, selectTxByKey, validateInitialTransactionParams, validateTransaction };
1050
- export type { ActionTxKey, BaseTransaction, BeforeTxProcess, CheckTxTracker, EvmTransaction, ExtractState, IInitializeTxTrackingStore, ITxInMemoryStore, ITxInMemoryStoreParameters, ITxTrackingStore, InitialTransaction, InitialTransactionParams, PollingFetcherParams, PollingTrackerConfig, PulsarAdapter, SolanaTransaction, StarknetTransaction, StoreSlice, SyncCallbacks, TrackerCallbacks, Transaction, TransactionPool, TxAdapter, TxInMemoryPagination, UpdatableTransactionFields };
1110
+ export type { ActionTxKey, BaseTransaction, BeforeTxProcess, CheckTxTracker, EvmTransaction, ExtractState, IInitializeTxTrackingStore, ITxInMemoryStore, ITxInMemoryStoreParameters, ITxTrackingStore, InitialTransaction, InitialTransactionParams, PollingFetcherParams, PollingTrackerConfig, PulsarAdapter, SolanaTransaction, StarknetTransaction, StoreSlice, SyncCallbacks, TrackerCallbacks, Transaction, TransactionPool, TxAdapter, TxFinishedListener, TxInMemoryPagination, UpdatableTransactionFields };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var immer=require('immer'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),ce=require('dayjs'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var ce__default=/*#__PURE__*/_interopDefault(ce);var ee=(s=>(s.Ethereum="ethereum",s.Safe="safe",s.Gelato="gelato",s.Solana="solana",s.ERC4337="erc4337",s.EIP5792="eip5792",s))(ee||{}),K=(T=>(T.Failed="Failed",T.Success="Success",T.Replaced="Replaced",T))(K||{});var U=100,B=300,N=10*1024,ne=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,o){super(o),this.name="PulsarTransactionValidationError",this.field=e;}};function $(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function S(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function w({field:n,value:e,maxLength:o}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((d,p)=>{let s=Array.isArray(e)?`${n}[${p}]`:n;if(typeof d!="string")throw new P(s,`${s} must be a string.`);if(d.length>o)throw new P(s,`${s} must be ${o} characters or less.`);O(s,d);});}function j(n){if(n===void 0)return;let e;try{e=JSON.stringify(n);}catch{throw new P("payload","payload must be JSON-serializable.")}if(e===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(e).length>N)throw new P("payload",`payload must be ${N} bytes or less when serialized.`);_(n);}function _(n,e="payload"){if(typeof n=="string"){O(e,n);return}n===null||typeof n!="object"||Object.entries(n).forEach(([o,T])=>{let d=`${e}.${o}`;O(d,o),_(T,d);});}function O(n,e){if(ne.some(o=>o.test(e)))throw new P(n,`${n} contains a blocked executable-like pattern.`)}function te(n){let e={...n};return delete e.pimlicoApiKey,delete e.gelatoApiKey,e}function G({maxTransactions:n,onRemoteCreate:e}){let o=false,T=new Set;return (d,p)=>{let s=async(t,r)=>{let c=p().transactionsPool[t];if(!(!e||!c||T.has(t))){T.add(t);try{await e(te(c)),d(a=>immer.produce(a,i=>{let l=i.transactionsPool[t];l&&(l.syncStatus="synced"),i.unsyncedTxKeys&&delete i.unsyncedTxKeys[t];}));}catch(a){console.warn(r,a);}finally{T.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!e||o)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){o=true;try{for(let r of t){if(!p().transactionsPool[r]){d(c=>immer.produce(c,a=>{a.unsyncedTxKeys&&delete a.unsyncedTxKeys[r];}));continue}await s(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{o=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...e&&{syncStatus:"pending-sync"}};return d(c=>immer.produce(c,a=>{if(a.lastAddedTxKey=t.txKey,t.txKey){if(e&&(a.unsyncedTxKeys||(a.unsyncedTxKeys={}),a.unsyncedTxKeys[t.txKey]=true),Object.keys(a.transactionsPool).length>=n){let l=Object.values(a.transactionsPool).sort((x,y)=>x.localTimestamp-y.localTimestamp);if(l.length>0){let x=l[0];delete a.transactionsPool[x.txKey];}}a.transactionsPool[t.txKey]=r;}})),e&&t.txKey&&s(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{d(a=>immer.produce(a,i=>{let l=i.transactionsPool[t];l&&Object.assign(l,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(a=>{console.error("[Pulsar] Terminal reconciliation failed:",a);});},removeTxFromPool:t=>{d(r=>immer.produce(r,c=>{delete c.transactionsPool[t];}));},closeTxTrackedModal:t=>{d(r=>immer.produce(r,c=>{if(t&&c.transactionsPool[t]){let a=c.transactionsPool[t];c.transactionsPool[t]={...a,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var D=n=>Object.values(n).sort((e,o)=>Number(e.localTimestamp)-Number(o.localTimestamp)),Ae=n=>D(n).filter(e=>e.pending),ve=(n,e)=>n[e],re=(n,e)=>D(n).filter(o=>o.from.toLowerCase()===e.toLowerCase()),Ie=(n,e)=>re(n,e).filter(o=>o.pending);var{produce:q}=new immer.Immer({autoFreeze:false}),ae=n=>n.filter(e=>{try{return S(e),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),H=n=>n==="Success"||n==="Failed"||n==="Replaced",V=(n,e)=>{let o=n[e.txKey];if(o){if(H(o.status))return false;if(o.pending){if(H(e.status))return n[e.txKey]={...o,...e},true;let T=typeof e.confirmations=="number"?e.confirmations:0,d=typeof o.confirmations=="number"?o.confirmations:0;return T>d?(n[e.txKey]={...o,...e},true):false}}return n[e.txKey]=e,true};function Me({localTransactionsPool:n,reconcileUnsyncedTransactions:e,getHistory:o,onHistoryFetched:T}){let d=s=>({isLoading:s,isError:false}),p=s=>{if(!s)return {isLoading:false};let t=ae(s.docs);return T&&queueMicrotask(()=>T(t)),r=>q(r,c=>{let a=c.transactionsPool;for(let i of t)V(a,i);c.currentPage=s.page,c.hasMore=s.hasNextPage,c.isLoading=false;})};return vanilla.createStore()((s,t)=>({transactionsPool:n,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{s(c=>q(c,a=>{let i=a.transactionsPool;for(let l of Object.values(r))V(i,l);}));},fetchInitial:async r=>{if(!(!o||!r)){s(d(true)),e&&await e();try{let c=await o({page:1,walletAddress:r});s(p(c));}catch(c){console.error("[Pulsar] Failed to fetch initial transaction history:",c),s({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:c,isLoading:a,currentPage:i}=t();if(!(!o||!c||a||!r)){s(d(true));try{let l=i+1,x=await o({page:l,walletAddress:r});s(p(x));}catch(l){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,l),s({isLoading:false,isError:true});}}}}))}function Ye({adapter:n,maxTransactions:e=50,onRemoteCreate:o,gelatoApiKey:T,beforeTxProcess:d,abortOnTxError:p,...s}){return vanilla.createStore()(middleware.persist((t,r)=>({...G({maxTransactions:e,onRemoteCreate:o})(t,r),getAdapter:()=>n,initializeTransactionsPool:async()=>{let a=Object.values(r().transactionsPool).filter(i=>i.pending).filter(i=>{try{return S(i),!0}catch(l){return console.warn("[Pulsar] Removed invalid persisted transaction:",l),r().removeTxFromPool(i.txKey),false}});await Promise.all(a.map(i=>orbitCore.selectAdapterByKey({adapterKey:i.adapter,adapter:n})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:T,...r()})));},injectExternalPendingTxs:async c=>{let i=r().getAdapter(),l=[],x=c.filter(y=>{try{return S(y),!0}catch(m){return console.warn("[Pulsar] Skipped invalid remote transaction:",m),false}});t(y=>immer.produce(y,m=>{let b=m.transactionsPool;x.forEach(u=>{let g=b[u.txKey];u.pending&&!g&&(b[u.txKey]=u,l.push(u));let I=u.status==="Success"||u.status==="Failed"||u.status==="Replaced";g?.pending&&I&&(g.status=u.status,g.pending=false,u.txKey&&(g.txKey=u.txKey),u.finishedTimestamp&&(g.finishedTimestamp=u.finishedTimestamp));});})),l.length>0&&await Promise.all(l.map(y=>orbitCore.selectAdapterByKey({adapterKey:y.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:y,gelatoApiKey:T,...r()})));},executeTxAction:async({defaultTracker:c,actionFunction:a,params:i,beforeTxProcess:l,abortOnTxError:x,...y})=>{let m=x??p??true,b=ce__default.default().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),$(i);let{desiredChainID:u,tracker:g,...I}=i,{onSuccess:W,onError:J,onReplaced:Y}=y;t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:true}});let A=orbitCore.selectAdapterByKey({adapterKey:I.adapter,adapter:n}),z=f=>{t(E=>immer.produce(E,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=orbitCore.normalizeError(f));}));};if(!A){let f=new Error("No adapter found for this transaction.");throw z(f),f}try{let{connectorType:f,walletAddress:E}=A.getConnectorInfo();await A.checkChainForTx(u);try{await(l??d)?.();}catch(v){if(m)throw t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:!1,error:orbitCore.normalizeError(v)}}),v;console.warn("[Pulsar] beforeTxProcess failed:",v);}let h=await a();if(!h){t({initialTx:void 0});return}let{tracker:M,txKey:R}=A.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:i.gelatoApiKey??T,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey}),Q={...I,connectorType:f,from:E,tracker:M||c,chainId:orbitCore.setChainId(u),localTimestamp:b,txKey:R,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await r().addTxToPool(Q),t(v=>immer.produce(v,F=>{F.initialTx&&(F.initialTx.isInitializing=!1,F.initialTx.lastTxKey=R);}));let Z=r().transactionsPool[R];await A.checkAndInitializeTrackerInStore({tx:Z,onSuccess:W,onError:J,onReplaced:Y,gelatoApiKey:T,...r()});}catch(f){throw z(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},merge:(t,r)=>({...r,...t,initialTx:r.initialTx}),...s}))}var nn=(n=>e=>zustand.useStore(n,e));function rn({tx:n,transactionsPool:e,updateTxParams:o}){let T=e[n.txKey];return d=>(o(n.txKey,d),T&&(T={...T,...d}),T)}var pe=5e3,ue=10;function an(n){let{tx:e,fetcher:o,onInitialize:T,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t,removeTxFromPool:r,pollingInterval:c=pe,maxRetries:a=ue}=n;if(!e.pending)return;T?.();let i=a,l=true,x=m=>{l&&(l=false,r&&!m?.withoutRemoving&&r(e.txKey));};(async()=>{for(;l&&i>0;)try{if(await new Promise(m=>setTimeout(m,c)),!l)break;await o({tx:e,stopPolling:x,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t}),i=a;}catch(m){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,m),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),p(),x());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=B;exports.MAX_TRANSACTION_PAYLOAD_BYTES=N;exports.MAX_TRANSACTION_TITLE_LENGTH=U;exports.PulsarTransactionValidationError=P;exports.TransactionStatus=K;exports.TransactionTracker=ee;exports.createBoundedUseStore=nn;exports.createPulsarStore=Ye;exports.createTxInMemoryStore=Me;exports.createTxUpdater=rn;exports.initializePollingTracker=an;exports.initializeTxTrackingStore=G;exports.selectAllTransactions=D;exports.selectAllTransactionsByActiveWallet=re;exports.selectPendingTransactions=Ae;exports.selectPendingTransactionsByActiveWallet=Ie;exports.selectTxByKey=ve;exports.validateInitialTransactionParams=$;exports.validateTransaction=S;
1
+ 'use strict';var immer=require('immer'),se=require('dayjs'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var se__default=/*#__PURE__*/_interopDefault(se);var R=30;function O(e,n,r=se__default.default().unix()){if(n===null)return [];let d=r-n*24*60*60;return Object.values(e).filter(l=>!l.pending&&(l.finishedTimestamp??l.localTimestamp)<d).map(l=>l.txKey)}var G=100,q=300,D=10*1024,ce=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],A=class extends Error{field;constructor(n,r){super(r),this.name="PulsarTransactionValidationError",this.field=n;}};function H(e){L({field:"title",value:e.title,maxLength:G}),L({field:"description",value:e.description,maxLength:q}),V(e.payload);}function I(e){L({field:"title",value:e.title,maxLength:G}),L({field:"description",value:e.description,maxLength:q}),V(e.payload);}function L({field:e,value:n,maxLength:r}){if(n===void 0)return;(Array.isArray(n)?n:[n]).forEach((l,x)=>{let a=Array.isArray(n)?`${e}[${x}]`:e;if(typeof l!="string")throw new A(a,`${a} must be a string.`);if(l.length>r)throw new A(a,`${a} must be ${r} characters or less.`);C(a,l);});}function V(e){if(e===void 0)return;let n;try{n=JSON.stringify(e);}catch{throw new A("payload","payload must be JSON-serializable.")}if(n===void 0)throw new A("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(n).length>D)throw new A("payload",`payload must be ${D} bytes or less when serialized.`);X(e);}function X(e,n="payload"){if(typeof e=="string"){C(n,e);return}e===null||typeof e!="object"||Object.entries(e).forEach(([r,d])=>{let l=`${n}.${r}`;C(l,r),X(d,l);});}function C(e,n){if(ce.some(r=>r.test(n)))throw new A(e,`${e} contains a blocked executable-like pattern.`)}var le=(a=>(a.Ethereum="ethereum",a.Safe="safe",a.Gelato="gelato",a.Solana="solana",a.ERC4337="erc4337",a.EIP5792="eip5792",a))(le||{}),M=(d=>(d.Failed="Failed",d.Success="Success",d.Replaced="Replaced",d))(M||{});var k=e=>e==="Success"||e==="Failed"||e==="Replaced";function Y(e){let n=new Set,r=x=>console.error("[Pulsar] A transaction finished listener failed:",x);return {notify:x=>{for(let a of e?[e,...n]:[...n])try{let p=a(x);p&&typeof p.then=="function"&&p.then(void 0,r);}catch(p){r(p);}},subscribe:x=>(n.add(x),()=>{n.delete(x);})}}function Te(e){let n={...e};return delete n.pimlicoApiKey,delete n.gelatoApiKey,n}function W({maxTransactions:e,retentionDays:n=R,onRemoteCreate:r,onTxFinished:d}){let l=false,x=new Set;return (a,p)=>{let f=async(t,s)=>{let i=p().transactionsPool[t];if(!(!r||!i||x.has(t))){x.add(t);try{await r(Te(i)),a(o=>immer.produce(o,u=>{let c=u.transactionsPool[t];c&&(c.syncStatus="synced"),u.unsyncedTxKeys&&delete u.unsyncedTxKeys[t];}));}catch(o){console.warn(s,o);}finally{x.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!r||l)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){l=true;try{for(let s of t){if(!p().transactionsPool[s]){a(i=>immer.produce(i,o=>{o.unsyncedTxKeys&&delete o.unsyncedTxKeys[s];}));continue}await f(s,`[Pulsar] Failed to reconcile tx ${s}:`);}}finally{l=false;}}},addTxToPool:t=>{I(t);let s={...t,pending:true,...r&&{syncStatus:"pending-sync"}};return a(i=>immer.produce(i,o=>{if(o.lastAddedTxKey=t.txKey,t.txKey){r&&(o.unsyncedTxKeys||(o.unsyncedTxKeys={}),o.unsyncedTxKeys[t.txKey]=true);for(let T of O(i.transactionsPool,n))delete o.transactionsPool[T],o.unsyncedTxKeys&&delete o.unsyncedTxKeys[T];let u=Object.values(o.transactionsPool).filter(T=>T.txKey!==t.txKey&&(!T.pending||k(T.status))).sort((T,P)=>T.localTimestamp-P.localTimestamp),c=Object.keys(o.transactionsPool).length+(o.transactionsPool[t.txKey]?0:1);for(let T of u){if(c<=e)break;delete o.transactionsPool[T.txKey],c--;}o.transactionsPool[t.txKey]=s;}})),r&&t.txKey&&f(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,s)=>{let i=k(p().transactionsPool[t]?.status);a(u=>immer.produce(u,c=>{let T=c.transactionsPool[t];T&&Object.assign(T,s);}));let o=p().transactionsPool[t];d&&o&&!i&&k(o.status)&&d(o),k(s.status)&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(u=>{console.error("[Pulsar] Terminal reconciliation failed:",u);});},removeTxFromPool:t=>{a(s=>immer.produce(s,i=>{delete i.transactionsPool[t];}));},closeTxTrackedModal:t=>{a(s=>immer.produce(s,i=>{if(t&&i.transactionsPool[t]){let o=i.transactionsPool[t];i.transactionsPool[t]={...o,isTrackedModalOpen:false};}i.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var J=e=>Object.values(e).sort((n,r)=>Number(n.localTimestamp)-Number(r.localTimestamp)),ze=e=>J(e).filter(n=>n.pending),Ue=(e,n)=>e[n],de=(e,n)=>J(e).filter(r=>r.from.toLowerCase()===n.toLowerCase()),Ce=(e,n)=>de(e,n).filter(r=>r.pending);var{produce:Q}=new immer.Immer({autoFreeze:false}),xe=e=>e.filter(n=>{try{return I(n),!0}catch(r){return console.warn("[Pulsar] Skipped invalid transaction from history:",r),false}}),Z=e=>e==="Success"||e==="Failed"||e==="Replaced",ee=(e,n)=>{let r=e[n.txKey];if(r){if(Z(r.status))return false;if(r.pending){if(Z(n.status))return e[n.txKey]={...r,...n},true;let d=typeof n.confirmations=="number"?n.confirmations:0,l=typeof r.confirmations=="number"?r.confirmations:0;return d>l?(e[n.txKey]={...r,...n},true):false}}return e[n.txKey]=n,true};function Ve({localTransactionsPool:e,reconcileUnsyncedTransactions:n,getHistory:r,onHistoryFetched:d}){let l=a=>({isLoading:a,isError:false}),x=a=>{if(!a)return {isLoading:false};let p=xe(a.docs);return d&&queueMicrotask(()=>d(p)),f=>Q(f,t=>{let s=t.transactionsPool;for(let i of p)ee(s,i);t.currentPage=a.page,t.hasMore=a.hasNextPage,t.isLoading=false;})};return vanilla.createStore()((a,p)=>({transactionsPool:e,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:f=>{a(t=>Q(t,s=>{let i=s.transactionsPool;for(let o of Object.values(f))ee(i,o);}));},fetchInitial:async f=>{if(!(!r||!f)){a(l(true)),n&&await n();try{let t=await r({page:1,walletAddress:f});a(x(t));}catch(t){console.error("[Pulsar] Failed to fetch initial transaction history:",t),a({isLoading:false,isError:true});}}},fetchNextPage:async f=>{let{hasMore:t,isLoading:s,currentPage:i}=p();if(!(!r||!t||s||!f)){a(l(true));try{let o=i+1,u=await r({page:o,walletAddress:f});a(x(u));}catch(o){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,o),a({isLoading:false,isError:true});}}}}))}function an({adapter:e,maxTransactions:n=50,retentionDays:r=R,onRemoteCreate:d,gelatoApiKey:l,beforeTxProcess:x,abortOnTxError:a,onTxFinished:p,...f}){let t=Y(p);return vanilla.createStore()(middleware.persist((s,i)=>({...W({maxTransactions:n,retentionDays:r,onRemoteCreate:d,onTxFinished:t.notify})(s,i),getAdapter:()=>e,subscribeTxFinished:t.subscribe,initializeTransactionsPool:async()=>{let u=Object.values(i().transactionsPool).filter(c=>c.pending).filter(c=>{try{return I(c),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),i().removeTxFromPool(c.txKey),false}});await Promise.all(u.map(c=>orbitCore.selectAdapterByKey({adapterKey:c.adapter,adapter:e})?.checkAndInitializeTrackerInStore({tx:c,gelatoApiKey:l,...i()})));},injectExternalPendingTxs:async o=>{let c=i().getAdapter(),T=[],P=[],S=o.filter(h=>{try{return I(h),!0}catch(g){return console.warn("[Pulsar] Skipped invalid remote transaction:",g),false}});s(h=>immer.produce(h,g=>{let w=g.transactionsPool;S.forEach(m=>{let b=w[m.txKey];m.pending&&!b&&(w[m.txKey]=m,T.push(m)),b?.pending&&k(m.status)&&(k(b.status)||P.push(m.txKey),b.status=m.status,b.pending=false,m.txKey&&(b.txKey=m.txKey),m.finishedTimestamp&&(b.finishedTimestamp=m.finishedTimestamp));});}));for(let h of P){let g=i().transactionsPool[h];g&&t.notify(g);}T.length>0&&await Promise.all(T.map(h=>orbitCore.selectAdapterByKey({adapterKey:h.adapter,adapter:c})?.checkAndInitializeTrackerInStore({tx:h,gelatoApiKey:l,...i()})));},executeTxAction:async({defaultTracker:o,actionFunction:u,params:c,beforeTxProcess:T,abortOnTxError:P,...S})=>{let h=P??a??true,g=se__default.default().unix();i().reconcileUnsyncedTransactions().catch(y=>console.error("[Pulsar] Reconciliation failed:",y)),H(c);let{desiredChainID:w,tracker:m,...b}=c,{onSuccess:te,onError:re,onReplaced:oe}=S;s({initialTx:{...c,actionFunction:u,localTimestamp:g,isInitializing:true}});let E=orbitCore.selectAdapterByKey({adapterKey:b.adapter,adapter:e}),$=y=>{s(N=>immer.produce(N,v=>{v.initialTx&&(v.initialTx.isInitializing=false,v.initialTx.error=orbitCore.normalizeError(y));}));};if(!E){let y=new Error("No adapter found for this transaction.");throw $(y),y}try{let{connectorType:y,walletAddress:N}=E.getConnectorInfo();await E.checkChainForTx(w);try{await(T??x)?.();}catch(F){if(h)throw s({initialTx:{...c,actionFunction:u,localTimestamp:g,isInitializing:!1,error:orbitCore.normalizeError(F)}}),F;console.warn("[Pulsar] beforeTxProcess failed:",F);}let v=await u();if(!v){s({initialTx:void 0});return}let{tracker:j,txKey:z}=E.checkTransactionsTracker({actionTxKey:v,connectorType:y,tracker:m,gelatoApiKey:c.gelatoApiKey??l,bundlerUrl:c.bundlerUrl,pimlicoApiKey:c.pimlicoApiKey}),ie={...b,connectorType:y,from:N,tracker:j||o,chainId:orbitCore.setChainId(w),localTimestamp:g,txKey:z,bundlerUrl:c.bundlerUrl,pimlicoApiKey:c.pimlicoApiKey,hash:j==="ethereum"?v:void 0,pending:!1,isTrackedModalOpen:c.withTrackedModal};await i().addTxToPool(ie),s(F=>immer.produce(F,U=>{U.initialTx&&(U.initialTx.isInitializing=!1,U.initialTx.lastTxKey=z);}));let ae=i().transactionsPool[z];await E.checkAndInitializeTrackerInStore({tx:ae,onSuccess:te,onError:re,onReplaced:oe,gelatoApiKey:l,...i()});}catch(y){throw $(y),y}}}),{partialize:s=>{let i={...s};return delete i.initialTx,i},...f,merge:(s,i)=>{let o=f.merge?f.merge(s,i):{...i,...s,initialTx:i.initialTx},u=O(o.transactionsPool??{},r);if(u.length===0)return o;let c={...o.transactionsPool},T=o.unsyncedTxKeys?{...o.unsyncedTxKeys}:o.unsyncedTxKeys;for(let P of u)delete c[P],T&&delete T[P];return {...o,transactionsPool:c,unsyncedTxKeys:T}}}))}var Tn=(e=>n=>zustand.useStore(e,n));function pn({tx:e,transactionsPool:n,updateTxParams:r}){let d=n[e.txKey];return l=>(r(e.txKey,l),d&&(d={...d,...l}),d)}var he=5e3,be=10;function xn(e){let{tx:n,fetcher:r,onInitialize:d,onSuccess:l,onFailure:x,onIntervalTick:a,onReplaced:p,removeTxFromPool:f,pollingInterval:t=he,maxRetries:s=be}=e;if(!n.pending)return;d?.();let i=s,o=true,u=Date.now(),c=()=>typeof t=="function"?t(Date.now()-u):t,T=S=>{o&&(o=false,f&&!S?.withoutRemoving&&f(n.txKey));};(async()=>{for(;o&&i>0;)try{if(await new Promise(S=>setTimeout(S,c())),!o)break;await r({tx:n,stopPolling:T,onSuccess:l,onFailure:x,onIntervalTick:a,onReplaced:p}),i=s;}catch(S){console.error(`Polling fetcher for txKey ${n.txKey} threw an error:`,S),i--;}i<=0&&(console.warn(`Polling for txKey ${n.txKey} stopped after reaching the maximum number of retries.`),x(),T());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=q;exports.MAX_TRANSACTION_PAYLOAD_BYTES=D;exports.MAX_TRANSACTION_TITLE_LENGTH=G;exports.PulsarTransactionValidationError=A;exports.TransactionStatus=M;exports.TransactionTracker=le;exports.createBoundedUseStore=Tn;exports.createPulsarStore=an;exports.createTxInMemoryStore=Ve;exports.createTxUpdater=pn;exports.initializePollingTracker=xn;exports.initializeTxTrackingStore=W;exports.selectAllTransactions=J;exports.selectAllTransactionsByActiveWallet=de;exports.selectPendingTransactions=ze;exports.selectPendingTransactionsByActiveWallet=Ce;exports.selectTxByKey=Ue;exports.validateInitialTransactionParams=H;exports.validateTransaction=I;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import {Immer,produce}from'immer';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,normalizeError,setChainId}from'@tuwaio/orbit-core';import ce from'dayjs';import {persist}from'zustand/middleware';import {useStore}from'zustand';var ee=(s=>(s.Ethereum="ethereum",s.Safe="safe",s.Gelato="gelato",s.Solana="solana",s.ERC4337="erc4337",s.EIP5792="eip5792",s))(ee||{}),K=(T=>(T.Failed="Failed",T.Success="Success",T.Replaced="Replaced",T))(K||{});var U=100,B=300,N=10*1024,ne=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,o){super(o),this.name="PulsarTransactionValidationError",this.field=e;}};function $(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function S(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function w({field:n,value:e,maxLength:o}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((d,p)=>{let s=Array.isArray(e)?`${n}[${p}]`:n;if(typeof d!="string")throw new P(s,`${s} must be a string.`);if(d.length>o)throw new P(s,`${s} must be ${o} characters or less.`);O(s,d);});}function j(n){if(n===void 0)return;let e;try{e=JSON.stringify(n);}catch{throw new P("payload","payload must be JSON-serializable.")}if(e===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(e).length>N)throw new P("payload",`payload must be ${N} bytes or less when serialized.`);_(n);}function _(n,e="payload"){if(typeof n=="string"){O(e,n);return}n===null||typeof n!="object"||Object.entries(n).forEach(([o,T])=>{let d=`${e}.${o}`;O(d,o),_(T,d);});}function O(n,e){if(ne.some(o=>o.test(e)))throw new P(n,`${n} contains a blocked executable-like pattern.`)}function te(n){let e={...n};return delete e.pimlicoApiKey,delete e.gelatoApiKey,e}function G({maxTransactions:n,onRemoteCreate:e}){let o=false,T=new Set;return (d,p)=>{let s=async(t,r)=>{let c=p().transactionsPool[t];if(!(!e||!c||T.has(t))){T.add(t);try{await e(te(c)),d(a=>produce(a,i=>{let l=i.transactionsPool[t];l&&(l.syncStatus="synced"),i.unsyncedTxKeys&&delete i.unsyncedTxKeys[t];}));}catch(a){console.warn(r,a);}finally{T.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!e||o)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){o=true;try{for(let r of t){if(!p().transactionsPool[r]){d(c=>produce(c,a=>{a.unsyncedTxKeys&&delete a.unsyncedTxKeys[r];}));continue}await s(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{o=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...e&&{syncStatus:"pending-sync"}};return d(c=>produce(c,a=>{if(a.lastAddedTxKey=t.txKey,t.txKey){if(e&&(a.unsyncedTxKeys||(a.unsyncedTxKeys={}),a.unsyncedTxKeys[t.txKey]=true),Object.keys(a.transactionsPool).length>=n){let l=Object.values(a.transactionsPool).sort((x,y)=>x.localTimestamp-y.localTimestamp);if(l.length>0){let x=l[0];delete a.transactionsPool[x.txKey];}}a.transactionsPool[t.txKey]=r;}})),e&&t.txKey&&s(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{d(a=>produce(a,i=>{let l=i.transactionsPool[t];l&&Object.assign(l,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(a=>{console.error("[Pulsar] Terminal reconciliation failed:",a);});},removeTxFromPool:t=>{d(r=>produce(r,c=>{delete c.transactionsPool[t];}));},closeTxTrackedModal:t=>{d(r=>produce(r,c=>{if(t&&c.transactionsPool[t]){let a=c.transactionsPool[t];c.transactionsPool[t]={...a,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var D=n=>Object.values(n).sort((e,o)=>Number(e.localTimestamp)-Number(o.localTimestamp)),Ae=n=>D(n).filter(e=>e.pending),ve=(n,e)=>n[e],re=(n,e)=>D(n).filter(o=>o.from.toLowerCase()===e.toLowerCase()),Ie=(n,e)=>re(n,e).filter(o=>o.pending);var{produce:q}=new Immer({autoFreeze:false}),ae=n=>n.filter(e=>{try{return S(e),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),H=n=>n==="Success"||n==="Failed"||n==="Replaced",V=(n,e)=>{let o=n[e.txKey];if(o){if(H(o.status))return false;if(o.pending){if(H(e.status))return n[e.txKey]={...o,...e},true;let T=typeof e.confirmations=="number"?e.confirmations:0,d=typeof o.confirmations=="number"?o.confirmations:0;return T>d?(n[e.txKey]={...o,...e},true):false}}return n[e.txKey]=e,true};function Me({localTransactionsPool:n,reconcileUnsyncedTransactions:e,getHistory:o,onHistoryFetched:T}){let d=s=>({isLoading:s,isError:false}),p=s=>{if(!s)return {isLoading:false};let t=ae(s.docs);return T&&queueMicrotask(()=>T(t)),r=>q(r,c=>{let a=c.transactionsPool;for(let i of t)V(a,i);c.currentPage=s.page,c.hasMore=s.hasNextPage,c.isLoading=false;})};return createStore()((s,t)=>({transactionsPool:n,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{s(c=>q(c,a=>{let i=a.transactionsPool;for(let l of Object.values(r))V(i,l);}));},fetchInitial:async r=>{if(!(!o||!r)){s(d(true)),e&&await e();try{let c=await o({page:1,walletAddress:r});s(p(c));}catch(c){console.error("[Pulsar] Failed to fetch initial transaction history:",c),s({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:c,isLoading:a,currentPage:i}=t();if(!(!o||!c||a||!r)){s(d(true));try{let l=i+1,x=await o({page:l,walletAddress:r});s(p(x));}catch(l){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,l),s({isLoading:false,isError:true});}}}}))}function Ye({adapter:n,maxTransactions:e=50,onRemoteCreate:o,gelatoApiKey:T,beforeTxProcess:d,abortOnTxError:p,...s}){return createStore()(persist((t,r)=>({...G({maxTransactions:e,onRemoteCreate:o})(t,r),getAdapter:()=>n,initializeTransactionsPool:async()=>{let a=Object.values(r().transactionsPool).filter(i=>i.pending).filter(i=>{try{return S(i),!0}catch(l){return console.warn("[Pulsar] Removed invalid persisted transaction:",l),r().removeTxFromPool(i.txKey),false}});await Promise.all(a.map(i=>selectAdapterByKey({adapterKey:i.adapter,adapter:n})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:T,...r()})));},injectExternalPendingTxs:async c=>{let i=r().getAdapter(),l=[],x=c.filter(y=>{try{return S(y),!0}catch(m){return console.warn("[Pulsar] Skipped invalid remote transaction:",m),false}});t(y=>produce(y,m=>{let b=m.transactionsPool;x.forEach(u=>{let g=b[u.txKey];u.pending&&!g&&(b[u.txKey]=u,l.push(u));let I=u.status==="Success"||u.status==="Failed"||u.status==="Replaced";g?.pending&&I&&(g.status=u.status,g.pending=false,u.txKey&&(g.txKey=u.txKey),u.finishedTimestamp&&(g.finishedTimestamp=u.finishedTimestamp));});})),l.length>0&&await Promise.all(l.map(y=>selectAdapterByKey({adapterKey:y.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:y,gelatoApiKey:T,...r()})));},executeTxAction:async({defaultTracker:c,actionFunction:a,params:i,beforeTxProcess:l,abortOnTxError:x,...y})=>{let m=x??p??true,b=ce().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),$(i);let{desiredChainID:u,tracker:g,...I}=i,{onSuccess:W,onError:J,onReplaced:Y}=y;t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:true}});let A=selectAdapterByKey({adapterKey:I.adapter,adapter:n}),z=f=>{t(E=>produce(E,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=normalizeError(f));}));};if(!A){let f=new Error("No adapter found for this transaction.");throw z(f),f}try{let{connectorType:f,walletAddress:E}=A.getConnectorInfo();await A.checkChainForTx(u);try{await(l??d)?.();}catch(v){if(m)throw t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:!1,error:normalizeError(v)}}),v;console.warn("[Pulsar] beforeTxProcess failed:",v);}let h=await a();if(!h){t({initialTx:void 0});return}let{tracker:M,txKey:R}=A.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:i.gelatoApiKey??T,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey}),Q={...I,connectorType:f,from:E,tracker:M||c,chainId:setChainId(u),localTimestamp:b,txKey:R,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await r().addTxToPool(Q),t(v=>produce(v,F=>{F.initialTx&&(F.initialTx.isInitializing=!1,F.initialTx.lastTxKey=R);}));let Z=r().transactionsPool[R];await A.checkAndInitializeTrackerInStore({tx:Z,onSuccess:W,onError:J,onReplaced:Y,gelatoApiKey:T,...r()});}catch(f){throw z(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},merge:(t,r)=>({...r,...t,initialTx:r.initialTx}),...s}))}var nn=(n=>e=>useStore(n,e));function rn({tx:n,transactionsPool:e,updateTxParams:o}){let T=e[n.txKey];return d=>(o(n.txKey,d),T&&(T={...T,...d}),T)}var pe=5e3,ue=10;function an(n){let{tx:e,fetcher:o,onInitialize:T,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t,removeTxFromPool:r,pollingInterval:c=pe,maxRetries:a=ue}=n;if(!e.pending)return;T?.();let i=a,l=true,x=m=>{l&&(l=false,r&&!m?.withoutRemoving&&r(e.txKey));};(async()=>{for(;l&&i>0;)try{if(await new Promise(m=>setTimeout(m,c)),!l)break;await o({tx:e,stopPolling:x,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t}),i=a;}catch(m){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,m),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),p(),x());})();}export{B as MAX_TRANSACTION_DESCRIPTION_LENGTH,N as MAX_TRANSACTION_PAYLOAD_BYTES,U as MAX_TRANSACTION_TITLE_LENGTH,P as PulsarTransactionValidationError,K as TransactionStatus,ee as TransactionTracker,nn as createBoundedUseStore,Ye as createPulsarStore,Me as createTxInMemoryStore,rn as createTxUpdater,an as initializePollingTracker,G as initializeTxTrackingStore,D as selectAllTransactions,re as selectAllTransactionsByActiveWallet,Ae as selectPendingTransactions,Ie as selectPendingTransactionsByActiveWallet,ve as selectTxByKey,$ as validateInitialTransactionParams,S as validateTransaction};
1
+ import {Immer,produce}from'immer';import se from'dayjs';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,normalizeError,setChainId}from'@tuwaio/orbit-core';import {persist}from'zustand/middleware';import {useStore}from'zustand';var R=30;function O(e,n,r=se().unix()){if(n===null)return [];let d=r-n*24*60*60;return Object.values(e).filter(l=>!l.pending&&(l.finishedTimestamp??l.localTimestamp)<d).map(l=>l.txKey)}var G=100,q=300,D=10*1024,ce=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],A=class extends Error{field;constructor(n,r){super(r),this.name="PulsarTransactionValidationError",this.field=n;}};function H(e){L({field:"title",value:e.title,maxLength:G}),L({field:"description",value:e.description,maxLength:q}),V(e.payload);}function I(e){L({field:"title",value:e.title,maxLength:G}),L({field:"description",value:e.description,maxLength:q}),V(e.payload);}function L({field:e,value:n,maxLength:r}){if(n===void 0)return;(Array.isArray(n)?n:[n]).forEach((l,x)=>{let a=Array.isArray(n)?`${e}[${x}]`:e;if(typeof l!="string")throw new A(a,`${a} must be a string.`);if(l.length>r)throw new A(a,`${a} must be ${r} characters or less.`);C(a,l);});}function V(e){if(e===void 0)return;let n;try{n=JSON.stringify(e);}catch{throw new A("payload","payload must be JSON-serializable.")}if(n===void 0)throw new A("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(n).length>D)throw new A("payload",`payload must be ${D} bytes or less when serialized.`);X(e);}function X(e,n="payload"){if(typeof e=="string"){C(n,e);return}e===null||typeof e!="object"||Object.entries(e).forEach(([r,d])=>{let l=`${n}.${r}`;C(l,r),X(d,l);});}function C(e,n){if(ce.some(r=>r.test(n)))throw new A(e,`${e} contains a blocked executable-like pattern.`)}var le=(a=>(a.Ethereum="ethereum",a.Safe="safe",a.Gelato="gelato",a.Solana="solana",a.ERC4337="erc4337",a.EIP5792="eip5792",a))(le||{}),M=(d=>(d.Failed="Failed",d.Success="Success",d.Replaced="Replaced",d))(M||{});var k=e=>e==="Success"||e==="Failed"||e==="Replaced";function Y(e){let n=new Set,r=x=>console.error("[Pulsar] A transaction finished listener failed:",x);return {notify:x=>{for(let a of e?[e,...n]:[...n])try{let p=a(x);p&&typeof p.then=="function"&&p.then(void 0,r);}catch(p){r(p);}},subscribe:x=>(n.add(x),()=>{n.delete(x);})}}function Te(e){let n={...e};return delete n.pimlicoApiKey,delete n.gelatoApiKey,n}function W({maxTransactions:e,retentionDays:n=R,onRemoteCreate:r,onTxFinished:d}){let l=false,x=new Set;return (a,p)=>{let f=async(t,s)=>{let i=p().transactionsPool[t];if(!(!r||!i||x.has(t))){x.add(t);try{await r(Te(i)),a(o=>produce(o,u=>{let c=u.transactionsPool[t];c&&(c.syncStatus="synced"),u.unsyncedTxKeys&&delete u.unsyncedTxKeys[t];}));}catch(o){console.warn(s,o);}finally{x.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!r||l)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){l=true;try{for(let s of t){if(!p().transactionsPool[s]){a(i=>produce(i,o=>{o.unsyncedTxKeys&&delete o.unsyncedTxKeys[s];}));continue}await f(s,`[Pulsar] Failed to reconcile tx ${s}:`);}}finally{l=false;}}},addTxToPool:t=>{I(t);let s={...t,pending:true,...r&&{syncStatus:"pending-sync"}};return a(i=>produce(i,o=>{if(o.lastAddedTxKey=t.txKey,t.txKey){r&&(o.unsyncedTxKeys||(o.unsyncedTxKeys={}),o.unsyncedTxKeys[t.txKey]=true);for(let T of O(i.transactionsPool,n))delete o.transactionsPool[T],o.unsyncedTxKeys&&delete o.unsyncedTxKeys[T];let u=Object.values(o.transactionsPool).filter(T=>T.txKey!==t.txKey&&(!T.pending||k(T.status))).sort((T,P)=>T.localTimestamp-P.localTimestamp),c=Object.keys(o.transactionsPool).length+(o.transactionsPool[t.txKey]?0:1);for(let T of u){if(c<=e)break;delete o.transactionsPool[T.txKey],c--;}o.transactionsPool[t.txKey]=s;}})),r&&t.txKey&&f(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,s)=>{let i=k(p().transactionsPool[t]?.status);a(u=>produce(u,c=>{let T=c.transactionsPool[t];T&&Object.assign(T,s);}));let o=p().transactionsPool[t];d&&o&&!i&&k(o.status)&&d(o),k(s.status)&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(u=>{console.error("[Pulsar] Terminal reconciliation failed:",u);});},removeTxFromPool:t=>{a(s=>produce(s,i=>{delete i.transactionsPool[t];}));},closeTxTrackedModal:t=>{a(s=>produce(s,i=>{if(t&&i.transactionsPool[t]){let o=i.transactionsPool[t];i.transactionsPool[t]={...o,isTrackedModalOpen:false};}i.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var J=e=>Object.values(e).sort((n,r)=>Number(n.localTimestamp)-Number(r.localTimestamp)),ze=e=>J(e).filter(n=>n.pending),Ue=(e,n)=>e[n],de=(e,n)=>J(e).filter(r=>r.from.toLowerCase()===n.toLowerCase()),Ce=(e,n)=>de(e,n).filter(r=>r.pending);var{produce:Q}=new Immer({autoFreeze:false}),xe=e=>e.filter(n=>{try{return I(n),!0}catch(r){return console.warn("[Pulsar] Skipped invalid transaction from history:",r),false}}),Z=e=>e==="Success"||e==="Failed"||e==="Replaced",ee=(e,n)=>{let r=e[n.txKey];if(r){if(Z(r.status))return false;if(r.pending){if(Z(n.status))return e[n.txKey]={...r,...n},true;let d=typeof n.confirmations=="number"?n.confirmations:0,l=typeof r.confirmations=="number"?r.confirmations:0;return d>l?(e[n.txKey]={...r,...n},true):false}}return e[n.txKey]=n,true};function Ve({localTransactionsPool:e,reconcileUnsyncedTransactions:n,getHistory:r,onHistoryFetched:d}){let l=a=>({isLoading:a,isError:false}),x=a=>{if(!a)return {isLoading:false};let p=xe(a.docs);return d&&queueMicrotask(()=>d(p)),f=>Q(f,t=>{let s=t.transactionsPool;for(let i of p)ee(s,i);t.currentPage=a.page,t.hasMore=a.hasNextPage,t.isLoading=false;})};return createStore()((a,p)=>({transactionsPool:e,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:f=>{a(t=>Q(t,s=>{let i=s.transactionsPool;for(let o of Object.values(f))ee(i,o);}));},fetchInitial:async f=>{if(!(!r||!f)){a(l(true)),n&&await n();try{let t=await r({page:1,walletAddress:f});a(x(t));}catch(t){console.error("[Pulsar] Failed to fetch initial transaction history:",t),a({isLoading:false,isError:true});}}},fetchNextPage:async f=>{let{hasMore:t,isLoading:s,currentPage:i}=p();if(!(!r||!t||s||!f)){a(l(true));try{let o=i+1,u=await r({page:o,walletAddress:f});a(x(u));}catch(o){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,o),a({isLoading:false,isError:true});}}}}))}function an({adapter:e,maxTransactions:n=50,retentionDays:r=R,onRemoteCreate:d,gelatoApiKey:l,beforeTxProcess:x,abortOnTxError:a,onTxFinished:p,...f}){let t=Y(p);return createStore()(persist((s,i)=>({...W({maxTransactions:n,retentionDays:r,onRemoteCreate:d,onTxFinished:t.notify})(s,i),getAdapter:()=>e,subscribeTxFinished:t.subscribe,initializeTransactionsPool:async()=>{let u=Object.values(i().transactionsPool).filter(c=>c.pending).filter(c=>{try{return I(c),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),i().removeTxFromPool(c.txKey),false}});await Promise.all(u.map(c=>selectAdapterByKey({adapterKey:c.adapter,adapter:e})?.checkAndInitializeTrackerInStore({tx:c,gelatoApiKey:l,...i()})));},injectExternalPendingTxs:async o=>{let c=i().getAdapter(),T=[],P=[],S=o.filter(h=>{try{return I(h),!0}catch(g){return console.warn("[Pulsar] Skipped invalid remote transaction:",g),false}});s(h=>produce(h,g=>{let w=g.transactionsPool;S.forEach(m=>{let b=w[m.txKey];m.pending&&!b&&(w[m.txKey]=m,T.push(m)),b?.pending&&k(m.status)&&(k(b.status)||P.push(m.txKey),b.status=m.status,b.pending=false,m.txKey&&(b.txKey=m.txKey),m.finishedTimestamp&&(b.finishedTimestamp=m.finishedTimestamp));});}));for(let h of P){let g=i().transactionsPool[h];g&&t.notify(g);}T.length>0&&await Promise.all(T.map(h=>selectAdapterByKey({adapterKey:h.adapter,adapter:c})?.checkAndInitializeTrackerInStore({tx:h,gelatoApiKey:l,...i()})));},executeTxAction:async({defaultTracker:o,actionFunction:u,params:c,beforeTxProcess:T,abortOnTxError:P,...S})=>{let h=P??a??true,g=se().unix();i().reconcileUnsyncedTransactions().catch(y=>console.error("[Pulsar] Reconciliation failed:",y)),H(c);let{desiredChainID:w,tracker:m,...b}=c,{onSuccess:te,onError:re,onReplaced:oe}=S;s({initialTx:{...c,actionFunction:u,localTimestamp:g,isInitializing:true}});let E=selectAdapterByKey({adapterKey:b.adapter,adapter:e}),$=y=>{s(N=>produce(N,v=>{v.initialTx&&(v.initialTx.isInitializing=false,v.initialTx.error=normalizeError(y));}));};if(!E){let y=new Error("No adapter found for this transaction.");throw $(y),y}try{let{connectorType:y,walletAddress:N}=E.getConnectorInfo();await E.checkChainForTx(w);try{await(T??x)?.();}catch(F){if(h)throw s({initialTx:{...c,actionFunction:u,localTimestamp:g,isInitializing:!1,error:normalizeError(F)}}),F;console.warn("[Pulsar] beforeTxProcess failed:",F);}let v=await u();if(!v){s({initialTx:void 0});return}let{tracker:j,txKey:z}=E.checkTransactionsTracker({actionTxKey:v,connectorType:y,tracker:m,gelatoApiKey:c.gelatoApiKey??l,bundlerUrl:c.bundlerUrl,pimlicoApiKey:c.pimlicoApiKey}),ie={...b,connectorType:y,from:N,tracker:j||o,chainId:setChainId(w),localTimestamp:g,txKey:z,bundlerUrl:c.bundlerUrl,pimlicoApiKey:c.pimlicoApiKey,hash:j==="ethereum"?v:void 0,pending:!1,isTrackedModalOpen:c.withTrackedModal};await i().addTxToPool(ie),s(F=>produce(F,U=>{U.initialTx&&(U.initialTx.isInitializing=!1,U.initialTx.lastTxKey=z);}));let ae=i().transactionsPool[z];await E.checkAndInitializeTrackerInStore({tx:ae,onSuccess:te,onError:re,onReplaced:oe,gelatoApiKey:l,...i()});}catch(y){throw $(y),y}}}),{partialize:s=>{let i={...s};return delete i.initialTx,i},...f,merge:(s,i)=>{let o=f.merge?f.merge(s,i):{...i,...s,initialTx:i.initialTx},u=O(o.transactionsPool??{},r);if(u.length===0)return o;let c={...o.transactionsPool},T=o.unsyncedTxKeys?{...o.unsyncedTxKeys}:o.unsyncedTxKeys;for(let P of u)delete c[P],T&&delete T[P];return {...o,transactionsPool:c,unsyncedTxKeys:T}}}))}var Tn=(e=>n=>useStore(e,n));function pn({tx:e,transactionsPool:n,updateTxParams:r}){let d=n[e.txKey];return l=>(r(e.txKey,l),d&&(d={...d,...l}),d)}var he=5e3,be=10;function xn(e){let{tx:n,fetcher:r,onInitialize:d,onSuccess:l,onFailure:x,onIntervalTick:a,onReplaced:p,removeTxFromPool:f,pollingInterval:t=he,maxRetries:s=be}=e;if(!n.pending)return;d?.();let i=s,o=true,u=Date.now(),c=()=>typeof t=="function"?t(Date.now()-u):t,T=S=>{o&&(o=false,f&&!S?.withoutRemoving&&f(n.txKey));};(async()=>{for(;o&&i>0;)try{if(await new Promise(S=>setTimeout(S,c())),!o)break;await r({tx:n,stopPolling:T,onSuccess:l,onFailure:x,onIntervalTick:a,onReplaced:p}),i=s;}catch(S){console.error(`Polling fetcher for txKey ${n.txKey} threw an error:`,S),i--;}i<=0&&(console.warn(`Polling for txKey ${n.txKey} stopped after reaching the maximum number of retries.`),x(),T());})();}export{q as MAX_TRANSACTION_DESCRIPTION_LENGTH,D as MAX_TRANSACTION_PAYLOAD_BYTES,G as MAX_TRANSACTION_TITLE_LENGTH,A as PulsarTransactionValidationError,M as TransactionStatus,le as TransactionTracker,Tn as createBoundedUseStore,an as createPulsarStore,Ve as createTxInMemoryStore,pn as createTxUpdater,xn as initializePollingTracker,W as initializeTxTrackingStore,J as selectAllTransactions,de as selectAllTransactionsByActiveWallet,ze as selectPendingTransactions,Ce as selectPendingTransactionsByActiveWallet,Ue as selectTxByKey,H as validateInitialTransactionParams,I as validateTransaction};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuwaio/pulsar-core",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "private": false,
5
5
  "author": "Oleksandr Tkach",
6
6
  "license": "Apache-2.0",
@@ -13,7 +13,8 @@
13
13
  },
14
14
  "files": [
15
15
  "dist",
16
- "socket.json"
16
+ "socket.json",
17
+ "NOTICE"
17
18
  ],
18
19
  "keywords": [
19
20
  "tuwa",