@tuwaio/pulsar-core 0.10.2 → 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; a finished transaction is dropped `retentionDays` (default 30) after it finished.
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:
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,7 +359,11 @@ 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;
347
368
  /**
348
369
  * Days a finished transaction stays in the pool, counted from its `finishedTimestamp` (or `localTimestamp` when it
@@ -358,6 +379,11 @@ type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> &
358
379
  * `onRemoteCreate`, whose errors never abort the transaction.
359
380
  */
360
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>;
361
387
  } & SyncCallbacks<T>;
362
388
  /**
363
389
  * The input of `TxAdapter.checkTransactionsTracker`: the key returned by the action and the context needed to pick a
@@ -478,7 +504,7 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
478
504
  initialTx?: InitialTransaction;
479
505
  /**
480
506
  * Validates a transaction and adds it to the pool with `pending: true`. When the pool already holds
481
- * `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
482
508
  * `syncStatus: 'pending-sync'`, its key is listed in `unsyncedTxKeys`, and `onRemoteCreate` runs in the background
483
509
  * (see `SyncCallbacks`). Does not start a tracker.
484
510
  * @param tx - The transaction to add.
@@ -489,7 +515,9 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
489
515
  addTxToPool: (tx: T) => Promise<void>;
490
516
  /**
491
517
  * Merges fields into a transaction of the pool; does nothing if the key is unknown. When `fields.status` is terminal
492
- * 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`).
493
521
  * @param txKey - The key of the transaction.
494
522
  * @param fields - The fields to merge.
495
523
  */
@@ -568,10 +596,19 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
568
596
  /**
569
597
  * Restarts the trackers of all pending transactions in the pool, for example after a page reload. Pending
570
598
  * transactions that fail validation are removed from the pool. Call it once per page load: every call starts new
571
- * 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.
572
601
  * @returns A promise that resolves when all trackers have started.
573
602
  */
574
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;
575
612
  /**
576
613
  * Merges transactions from a remote backend into the pool (cross-device sync). Invalid transactions are skipped
577
614
  * with a warning. Pending remote transactions that are not in the pool are added and tracked. Local pending
@@ -681,15 +718,19 @@ type ITxInMemoryStoreParameters<T extends Transaction> = {
681
718
  *
682
719
  * @template T - The application transaction type.
683
720
  * @param params - The slice options.
684
- * @param params.maxTransactions - Maximum number of transactions in the pool; `addTxToPool` evicts the oldest one
685
- * (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.
686
724
  * @param params.retentionDays - Days a finished transaction stays in the pool; `addTxToPool` drops the expired ones
687
725
  * first. `null` keeps them without a term. Defaults to 30.
688
726
  * @param params.onRemoteCreate - Optional remote sync callback, called in the background by `addTxToPool` (see
689
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.
690
731
  * @returns A slice creator for Zustand's `create` / `createStore`.
691
732
  */
692
- declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, retentionDays, onRemoteCreate, }: Pick<PulsarAdapter<T>, 'onRemoteCreate' | 'retentionDays'> & {
733
+ declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, retentionDays, onRemoteCreate, onTxFinished, }: Pick<PulsarAdapter<T>, 'onRemoteCreate' | 'retentionDays' | 'onTxFinished'> & {
693
734
  maxTransactions: number;
694
735
  }): StoreSlice<IInitializeTxTrackingStore<T>>;
695
736
 
@@ -791,6 +832,8 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
791
832
  * @param params.gelatoApiKey - Deprecated Gelato API key, passed to the trackers.
792
833
  * @param params.beforeTxProcess - Global preflight callback (see `BeforeTxProcess`).
793
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`).
794
837
  * @param params.name - The storage key. Required by `persist`; use a different key for every store.
795
838
  * @returns The vanilla Zustand store. `store.persist` exposes the `persist` API (for example `clearStorage()`).
796
839
  *
@@ -805,7 +848,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
805
848
  * });
806
849
  * ```
807
850
  */
808
- declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, retentionDays, 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"> & {
809
852
  setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
810
853
  setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
811
854
  persist: {
@@ -976,15 +1019,19 @@ type PollingTrackerConfig<R, T extends Pick<Transaction, 'txKey' | 'pending'>> =
976
1019
  * @param txKey - The `txKey` of the tracked transaction.
977
1020
  */
978
1021
  removeTxFromPool?: (txKey: string) => void;
979
- /** The delay before each attempt, in milliseconds. Defaults to 5000. */
980
- 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);
981
1027
  /** The number of consecutive failed attempts (thrown errors) after which polling stops. Defaults to 10. */
982
1028
  maxRetries?: number;
983
1029
  };
984
1030
  /**
985
1031
  * Starts polling a transaction in the background and returns immediately. Does nothing if `tx.pending` is `false`.
986
1032
  *
987
- * 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
988
1035
  * fetcher calls `stopPolling`. A fetcher that throws counts as a failed attempt; after `maxRetries` consecutive failed
989
1036
  * attempts the tracker calls `onFailure()` without arguments, logs a warning and stops (which calls
990
1037
  * `removeTxFromPool`, if configured). A successful attempt resets the count.
@@ -1060,4 +1107,4 @@ declare function validateInitialTransactionParams(params: Omit<InitialTransactio
1060
1107
  declare function validateTransaction<T extends Transaction>(tx: T): void;
1061
1108
 
1062
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 };
1063
- 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,7 +359,11 @@ 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;
347
368
  /**
348
369
  * Days a finished transaction stays in the pool, counted from its `finishedTimestamp` (or `localTimestamp` when it
@@ -358,6 +379,11 @@ type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> &
358
379
  * `onRemoteCreate`, whose errors never abort the transaction.
359
380
  */
360
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>;
361
387
  } & SyncCallbacks<T>;
362
388
  /**
363
389
  * The input of `TxAdapter.checkTransactionsTracker`: the key returned by the action and the context needed to pick a
@@ -478,7 +504,7 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
478
504
  initialTx?: InitialTransaction;
479
505
  /**
480
506
  * Validates a transaction and adds it to the pool with `pending: true`. When the pool already holds
481
- * `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
482
508
  * `syncStatus: 'pending-sync'`, its key is listed in `unsyncedTxKeys`, and `onRemoteCreate` runs in the background
483
509
  * (see `SyncCallbacks`). Does not start a tracker.
484
510
  * @param tx - The transaction to add.
@@ -489,7 +515,9 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
489
515
  addTxToPool: (tx: T) => Promise<void>;
490
516
  /**
491
517
  * Merges fields into a transaction of the pool; does nothing if the key is unknown. When `fields.status` is terminal
492
- * 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`).
493
521
  * @param txKey - The key of the transaction.
494
522
  * @param fields - The fields to merge.
495
523
  */
@@ -568,10 +596,19 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
568
596
  /**
569
597
  * Restarts the trackers of all pending transactions in the pool, for example after a page reload. Pending
570
598
  * transactions that fail validation are removed from the pool. Call it once per page load: every call starts new
571
- * 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.
572
601
  * @returns A promise that resolves when all trackers have started.
573
602
  */
574
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;
575
612
  /**
576
613
  * Merges transactions from a remote backend into the pool (cross-device sync). Invalid transactions are skipped
577
614
  * with a warning. Pending remote transactions that are not in the pool are added and tracked. Local pending
@@ -681,15 +718,19 @@ type ITxInMemoryStoreParameters<T extends Transaction> = {
681
718
  *
682
719
  * @template T - The application transaction type.
683
720
  * @param params - The slice options.
684
- * @param params.maxTransactions - Maximum number of transactions in the pool; `addTxToPool` evicts the oldest one
685
- * (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.
686
724
  * @param params.retentionDays - Days a finished transaction stays in the pool; `addTxToPool` drops the expired ones
687
725
  * first. `null` keeps them without a term. Defaults to 30.
688
726
  * @param params.onRemoteCreate - Optional remote sync callback, called in the background by `addTxToPool` (see
689
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.
690
731
  * @returns A slice creator for Zustand's `create` / `createStore`.
691
732
  */
692
- declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, retentionDays, onRemoteCreate, }: Pick<PulsarAdapter<T>, 'onRemoteCreate' | 'retentionDays'> & {
733
+ declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, retentionDays, onRemoteCreate, onTxFinished, }: Pick<PulsarAdapter<T>, 'onRemoteCreate' | 'retentionDays' | 'onTxFinished'> & {
693
734
  maxTransactions: number;
694
735
  }): StoreSlice<IInitializeTxTrackingStore<T>>;
695
736
 
@@ -791,6 +832,8 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
791
832
  * @param params.gelatoApiKey - Deprecated Gelato API key, passed to the trackers.
792
833
  * @param params.beforeTxProcess - Global preflight callback (see `BeforeTxProcess`).
793
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`).
794
837
  * @param params.name - The storage key. Required by `persist`; use a different key for every store.
795
838
  * @returns The vanilla Zustand store. `store.persist` exposes the `persist` API (for example `clearStorage()`).
796
839
  *
@@ -805,7 +848,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
805
848
  * });
806
849
  * ```
807
850
  */
808
- declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, retentionDays, 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"> & {
809
852
  setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
810
853
  setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
811
854
  persist: {
@@ -976,15 +1019,19 @@ type PollingTrackerConfig<R, T extends Pick<Transaction, 'txKey' | 'pending'>> =
976
1019
  * @param txKey - The `txKey` of the tracked transaction.
977
1020
  */
978
1021
  removeTxFromPool?: (txKey: string) => void;
979
- /** The delay before each attempt, in milliseconds. Defaults to 5000. */
980
- 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);
981
1027
  /** The number of consecutive failed attempts (thrown errors) after which polling stops. Defaults to 10. */
982
1028
  maxRetries?: number;
983
1029
  };
984
1030
  /**
985
1031
  * Starts polling a transaction in the background and returns immediately. Does nothing if `tx.pending` is `false`.
986
1032
  *
987
- * 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
988
1035
  * fetcher calls `stopPolling`. A fetcher that throws counts as a failed attempt; after `maxRetries` consecutive failed
989
1036
  * attempts the tracker calls `onFailure()` without arguments, logs a warning and stops (which calls
990
1037
  * `removeTxFromPool`, if configured). A successful attempt resets the count.
@@ -1060,4 +1107,4 @@ declare function validateInitialTransactionParams(params: Omit<InitialTransactio
1060
1107
  declare function validateTransaction<T extends Transaction>(tx: T): void;
1061
1108
 
1062
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 };
1063
- 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'),oe=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 oe__default=/*#__PURE__*/_interopDefault(oe);var re=(s=>(s.Ethereum="ethereum",s.Safe="safe",s.Gelato="gelato",s.Solana="solana",s.ERC4337="erc4337",s.EIP5792="eip5792",s))(re||{}),w=(d=>(d.Failed="Failed",d.Success="Success",d.Replaced="Replaced",d))(w||{});var E=30;function R(e,n,o=oe__default.default().unix()){if(n===null)return [];let d=o-n*24*60*60;return Object.values(e).filter(l=>!l.pending&&(l.finishedTimestamp??l.localTimestamp)<d).map(l=>l.txKey)}var $=100,j=300,_=10*1024,ie=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(n,o){super(o),this.name="PulsarTransactionValidationError",this.field=n;}};function D(e){F({field:"title",value:e.title,maxLength:$}),F({field:"description",value:e.description,maxLength:j}),G(e.payload);}function S(e){F({field:"title",value:e.title,maxLength:$}),F({field:"description",value:e.description,maxLength:j}),G(e.payload);}function F({field:e,value:n,maxLength:o}){if(n===void 0)return;(Array.isArray(n)?n:[n]).forEach((l,p)=>{let s=Array.isArray(n)?`${e}[${p}]`:e;if(typeof l!="string")throw new P(s,`${s} must be a string.`);if(l.length>o)throw new P(s,`${s} must be ${o} characters or less.`);C(s,l);});}function G(e){if(e===void 0)return;let n;try{n=JSON.stringify(e);}catch{throw new P("payload","payload must be JSON-serializable.")}if(n===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(n).length>_)throw new P("payload",`payload must be ${_} bytes or less when serialized.`);q(e);}function q(e,n="payload"){if(typeof e=="string"){C(n,e);return}e===null||typeof e!="object"||Object.entries(e).forEach(([o,d])=>{let l=`${n}.${o}`;C(l,o),q(d,l);});}function C(e,n){if(ie.some(o=>o.test(n)))throw new P(e,`${e} contains a blocked executable-like pattern.`)}function ae(e){let n={...e};return delete n.pimlicoApiKey,delete n.gelatoApiKey,n}function H({maxTransactions:e,retentionDays:n=E,onRemoteCreate:o}){let d=false,l=new Set;return (p,s)=>{let m=async(t,r)=>{let c=s().transactionsPool[t];if(!(!o||!c||l.has(t))){l.add(t);try{await o(ae(c)),p(i=>immer.produce(i,a=>{let T=a.transactionsPool[t];T&&(T.syncStatus="synced"),a.unsyncedTxKeys&&delete a.unsyncedTxKeys[t];}));}catch(i){console.warn(r,i);}finally{l.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!o||d)return;let t=Object.keys(s().unsyncedTxKeys||{});if(t.length!==0){d=true;try{for(let r of t){if(!s().transactionsPool[r]){p(c=>immer.produce(c,i=>{i.unsyncedTxKeys&&delete i.unsyncedTxKeys[r];}));continue}await m(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{d=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...o&&{syncStatus:"pending-sync"}};return p(c=>immer.produce(c,i=>{if(i.lastAddedTxKey=t.txKey,t.txKey){o&&(i.unsyncedTxKeys||(i.unsyncedTxKeys={}),i.unsyncedTxKeys[t.txKey]=true);for(let T of R(c.transactionsPool,n))delete i.transactionsPool[T],i.unsyncedTxKeys&&delete i.unsyncedTxKeys[T];if(Object.keys(i.transactionsPool).length>=e){let T=Object.values(i.transactionsPool).sort((y,u)=>y.localTimestamp-u.localTimestamp);if(T.length>0){let y=T[0];delete i.transactionsPool[y.txKey];}}i.transactionsPool[t.txKey]=r;}})),o&&t.txKey&&m(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{p(i=>immer.produce(i,a=>{let T=a.transactionsPool[t];T&&Object.assign(T,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&s().reconcileUnsyncedTransactions&&s().unsyncedTxKeys?.[t]&&s().reconcileUnsyncedTransactions().catch(i=>{console.error("[Pulsar] Terminal reconciliation failed:",i);});},removeTxFromPool:t=>{p(r=>immer.produce(r,c=>{delete c.transactionsPool[t];}));},closeTxTrackedModal:t=>{p(r=>immer.produce(r,c=>{if(t&&c.transactionsPool[t]){let i=c.transactionsPool[t];c.transactionsPool[t]={...i,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>s().lastAddedTxKey}}}var V=e=>Object.values(e).sort((n,o)=>Number(n.localTimestamp)-Number(o.localTimestamp)),Oe=e=>V(e).filter(n=>n.pending),Le=(e,n)=>e[n],se=(e,n)=>V(e).filter(o=>o.from.toLowerCase()===n.toLowerCase()),Ne=(e,n)=>se(e,n).filter(o=>o.pending);var{produce:X}=new immer.Immer({autoFreeze:false}),Te=e=>e.filter(n=>{try{return S(n),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),Y=e=>e==="Success"||e==="Failed"||e==="Replaced",W=(e,n)=>{let o=e[n.txKey];if(o){if(Y(o.status))return false;if(o.pending){if(Y(n.status))return e[n.txKey]={...o,...n},true;let d=typeof n.confirmations=="number"?n.confirmations:0,l=typeof o.confirmations=="number"?o.confirmations:0;return d>l?(e[n.txKey]={...o,...n},true):false}}return e[n.txKey]=n,true};function Ge({localTransactionsPool:e,reconcileUnsyncedTransactions:n,getHistory:o,onHistoryFetched:d}){let l=s=>({isLoading:s,isError:false}),p=s=>{if(!s)return {isLoading:false};let m=Te(s.docs);return d&&queueMicrotask(()=>d(m)),t=>X(t,r=>{let c=r.transactionsPool;for(let i of m)W(c,i);r.currentPage=s.page,r.hasMore=s.hasNextPage,r.isLoading=false;})};return vanilla.createStore()((s,m)=>({transactionsPool:e,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:t=>{s(r=>X(r,c=>{let i=c.transactionsPool;for(let a of Object.values(t))W(i,a);}));},fetchInitial:async t=>{if(!(!o||!t)){s(l(true)),n&&await n();try{let r=await o({page:1,walletAddress:t});s(p(r));}catch(r){console.error("[Pulsar] Failed to fetch initial transaction history:",r),s({isLoading:false,isError:true});}}},fetchNextPage:async t=>{let{hasMore:r,isLoading:c,currentPage:i}=m();if(!(!o||!r||c||!t)){s(l(true));try{let a=i+1,T=await o({page:a,walletAddress:t});s(p(T));}catch(a){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,a),s({isLoading:false,isError:true});}}}}))}function sn({adapter:e,maxTransactions:n=50,retentionDays:o=E,onRemoteCreate:d,gelatoApiKey:l,beforeTxProcess:p,abortOnTxError:s,...m}){return vanilla.createStore()(middleware.persist((t,r)=>({...H({maxTransactions:n,retentionDays:o,onRemoteCreate:d})(t,r),getAdapter:()=>e,initializeTransactionsPool:async()=>{let i=Object.values(r().transactionsPool).filter(a=>a.pending).filter(a=>{try{return S(a),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),r().removeTxFromPool(a.txKey),false}});await Promise.all(i.map(a=>orbitCore.selectAdapterByKey({adapterKey:a.adapter,adapter:e})?.checkAndInitializeTrackerInStore({tx:a,gelatoApiKey:l,...r()})));},injectExternalPendingTxs:async c=>{let a=r().getAdapter(),T=[],y=c.filter(u=>{try{return S(u),!0}catch(b){return console.warn("[Pulsar] Skipped invalid remote transaction:",b),false}});t(u=>immer.produce(u,b=>{let A=b.transactionsPool;y.forEach(x=>{let g=A[x.txKey];x.pending&&!g&&(A[x.txKey]=x,T.push(x));let K=x.status==="Success"||x.status==="Failed"||x.status==="Replaced";g?.pending&&K&&(g.status=x.status,g.pending=false,x.txKey&&(g.txKey=x.txKey),x.finishedTimestamp&&(g.finishedTimestamp=x.finishedTimestamp));});})),T.length>0&&await Promise.all(T.map(u=>orbitCore.selectAdapterByKey({adapterKey:u.adapter,adapter:a})?.checkAndInitializeTrackerInStore({tx:u,gelatoApiKey:l,...r()})));},executeTxAction:async({defaultTracker:c,actionFunction:i,params:a,beforeTxProcess:T,abortOnTxError:y,...u})=>{let b=y??s??true,A=oe__default.default().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),D(a);let{desiredChainID:x,tracker:g,...K}=a,{onSuccess:Q,onError:Z,onReplaced:ee}=u;t({initialTx:{...a,actionFunction:i,localTimestamp:A,isInitializing:true}});let v=orbitCore.selectAdapterByKey({adapterKey:K.adapter,adapter:e}),M=f=>{t(O=>immer.produce(O,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=orbitCore.normalizeError(f));}));};if(!v){let f=new Error("No adapter found for this transaction.");throw M(f),f}try{let{connectorType:f,walletAddress:O}=v.getConnectorInfo();await v.checkChainForTx(x);try{await(T??p)?.();}catch(I){if(b)throw t({initialTx:{...a,actionFunction:i,localTimestamp:A,isInitializing:!1,error:orbitCore.normalizeError(I)}}),I;console.warn("[Pulsar] beforeTxProcess failed:",I);}let h=await i();if(!h){t({initialTx:void 0});return}let{tracker:B,txKey:L}=v.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:a.gelatoApiKey??l,bundlerUrl:a.bundlerUrl,pimlicoApiKey:a.pimlicoApiKey}),ne={...K,connectorType:f,from:O,tracker:B||c,chainId:orbitCore.setChainId(x),localTimestamp:A,txKey:L,bundlerUrl:a.bundlerUrl,pimlicoApiKey:a.pimlicoApiKey,hash:B==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:a.withTrackedModal};await r().addTxToPool(ne),t(I=>immer.produce(I,N=>{N.initialTx&&(N.initialTx.isInitializing=!1,N.initialTx.lastTxKey=L);}));let te=r().transactionsPool[L];await v.checkAndInitializeTrackerInStore({tx:te,onSuccess:Q,onError:Z,onReplaced:ee,gelatoApiKey:l,...r()});}catch(f){throw M(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},...m,merge:(t,r)=>{let c=m.merge?m.merge(t,r):{...r,...t,initialTx:r.initialTx},i=R(c.transactionsPool??{},o);if(i.length===0)return c;let a={...c.transactionsPool},T=c.unsyncedTxKeys?{...c.unsyncedTxKeys}:c.unsyncedTxKeys;for(let y of i)delete a[y],T&&delete T[y];return {...c,transactionsPool:a,unsyncedTxKeys:T}}}))}var dn=(e=>n=>zustand.useStore(e,n));function un({tx:e,transactionsPool:n,updateTxParams:o}){let d=n[e.txKey];return l=>(o(e.txKey,l),d&&(d={...d,...l}),d)}var fe=5e3,ye=10;function mn(e){let{tx:n,fetcher:o,onInitialize:d,onSuccess:l,onFailure:p,onIntervalTick:s,onReplaced:m,removeTxFromPool:t,pollingInterval:r=fe,maxRetries:c=ye}=e;if(!n.pending)return;d?.();let i=c,a=true,T=u=>{a&&(a=false,t&&!u?.withoutRemoving&&t(n.txKey));};(async()=>{for(;a&&i>0;)try{if(await new Promise(u=>setTimeout(u,r)),!a)break;await o({tx:n,stopPolling:T,onSuccess:l,onFailure:p,onIntervalTick:s,onReplaced:m}),i=c;}catch(u){console.error(`Polling fetcher for txKey ${n.txKey} threw an error:`,u),i--;}i<=0&&(console.warn(`Polling for txKey ${n.txKey} stopped after reaching the maximum number of retries.`),p(),T());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=j;exports.MAX_TRANSACTION_PAYLOAD_BYTES=_;exports.MAX_TRANSACTION_TITLE_LENGTH=$;exports.PulsarTransactionValidationError=P;exports.TransactionStatus=w;exports.TransactionTracker=re;exports.createBoundedUseStore=dn;exports.createPulsarStore=sn;exports.createTxInMemoryStore=Ge;exports.createTxUpdater=un;exports.initializePollingTracker=mn;exports.initializeTxTrackingStore=H;exports.selectAllTransactions=V;exports.selectAllTransactionsByActiveWallet=se;exports.selectPendingTransactions=Oe;exports.selectPendingTransactionsByActiveWallet=Ne;exports.selectTxByKey=Le;exports.validateInitialTransactionParams=D;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 oe 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 re=(s=>(s.Ethereum="ethereum",s.Safe="safe",s.Gelato="gelato",s.Solana="solana",s.ERC4337="erc4337",s.EIP5792="eip5792",s))(re||{}),w=(d=>(d.Failed="Failed",d.Success="Success",d.Replaced="Replaced",d))(w||{});var E=30;function R(e,n,o=oe().unix()){if(n===null)return [];let d=o-n*24*60*60;return Object.values(e).filter(l=>!l.pending&&(l.finishedTimestamp??l.localTimestamp)<d).map(l=>l.txKey)}var $=100,j=300,_=10*1024,ie=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(n,o){super(o),this.name="PulsarTransactionValidationError",this.field=n;}};function D(e){F({field:"title",value:e.title,maxLength:$}),F({field:"description",value:e.description,maxLength:j}),G(e.payload);}function S(e){F({field:"title",value:e.title,maxLength:$}),F({field:"description",value:e.description,maxLength:j}),G(e.payload);}function F({field:e,value:n,maxLength:o}){if(n===void 0)return;(Array.isArray(n)?n:[n]).forEach((l,p)=>{let s=Array.isArray(n)?`${e}[${p}]`:e;if(typeof l!="string")throw new P(s,`${s} must be a string.`);if(l.length>o)throw new P(s,`${s} must be ${o} characters or less.`);C(s,l);});}function G(e){if(e===void 0)return;let n;try{n=JSON.stringify(e);}catch{throw new P("payload","payload must be JSON-serializable.")}if(n===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(n).length>_)throw new P("payload",`payload must be ${_} bytes or less when serialized.`);q(e);}function q(e,n="payload"){if(typeof e=="string"){C(n,e);return}e===null||typeof e!="object"||Object.entries(e).forEach(([o,d])=>{let l=`${n}.${o}`;C(l,o),q(d,l);});}function C(e,n){if(ie.some(o=>o.test(n)))throw new P(e,`${e} contains a blocked executable-like pattern.`)}function ae(e){let n={...e};return delete n.pimlicoApiKey,delete n.gelatoApiKey,n}function H({maxTransactions:e,retentionDays:n=E,onRemoteCreate:o}){let d=false,l=new Set;return (p,s)=>{let m=async(t,r)=>{let c=s().transactionsPool[t];if(!(!o||!c||l.has(t))){l.add(t);try{await o(ae(c)),p(i=>produce(i,a=>{let T=a.transactionsPool[t];T&&(T.syncStatus="synced"),a.unsyncedTxKeys&&delete a.unsyncedTxKeys[t];}));}catch(i){console.warn(r,i);}finally{l.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!o||d)return;let t=Object.keys(s().unsyncedTxKeys||{});if(t.length!==0){d=true;try{for(let r of t){if(!s().transactionsPool[r]){p(c=>produce(c,i=>{i.unsyncedTxKeys&&delete i.unsyncedTxKeys[r];}));continue}await m(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{d=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...o&&{syncStatus:"pending-sync"}};return p(c=>produce(c,i=>{if(i.lastAddedTxKey=t.txKey,t.txKey){o&&(i.unsyncedTxKeys||(i.unsyncedTxKeys={}),i.unsyncedTxKeys[t.txKey]=true);for(let T of R(c.transactionsPool,n))delete i.transactionsPool[T],i.unsyncedTxKeys&&delete i.unsyncedTxKeys[T];if(Object.keys(i.transactionsPool).length>=e){let T=Object.values(i.transactionsPool).sort((y,u)=>y.localTimestamp-u.localTimestamp);if(T.length>0){let y=T[0];delete i.transactionsPool[y.txKey];}}i.transactionsPool[t.txKey]=r;}})),o&&t.txKey&&m(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{p(i=>produce(i,a=>{let T=a.transactionsPool[t];T&&Object.assign(T,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&s().reconcileUnsyncedTransactions&&s().unsyncedTxKeys?.[t]&&s().reconcileUnsyncedTransactions().catch(i=>{console.error("[Pulsar] Terminal reconciliation failed:",i);});},removeTxFromPool:t=>{p(r=>produce(r,c=>{delete c.transactionsPool[t];}));},closeTxTrackedModal:t=>{p(r=>produce(r,c=>{if(t&&c.transactionsPool[t]){let i=c.transactionsPool[t];c.transactionsPool[t]={...i,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>s().lastAddedTxKey}}}var V=e=>Object.values(e).sort((n,o)=>Number(n.localTimestamp)-Number(o.localTimestamp)),Oe=e=>V(e).filter(n=>n.pending),Le=(e,n)=>e[n],se=(e,n)=>V(e).filter(o=>o.from.toLowerCase()===n.toLowerCase()),Ne=(e,n)=>se(e,n).filter(o=>o.pending);var{produce:X}=new Immer({autoFreeze:false}),Te=e=>e.filter(n=>{try{return S(n),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),Y=e=>e==="Success"||e==="Failed"||e==="Replaced",W=(e,n)=>{let o=e[n.txKey];if(o){if(Y(o.status))return false;if(o.pending){if(Y(n.status))return e[n.txKey]={...o,...n},true;let d=typeof n.confirmations=="number"?n.confirmations:0,l=typeof o.confirmations=="number"?o.confirmations:0;return d>l?(e[n.txKey]={...o,...n},true):false}}return e[n.txKey]=n,true};function Ge({localTransactionsPool:e,reconcileUnsyncedTransactions:n,getHistory:o,onHistoryFetched:d}){let l=s=>({isLoading:s,isError:false}),p=s=>{if(!s)return {isLoading:false};let m=Te(s.docs);return d&&queueMicrotask(()=>d(m)),t=>X(t,r=>{let c=r.transactionsPool;for(let i of m)W(c,i);r.currentPage=s.page,r.hasMore=s.hasNextPage,r.isLoading=false;})};return createStore()((s,m)=>({transactionsPool:e,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:t=>{s(r=>X(r,c=>{let i=c.transactionsPool;for(let a of Object.values(t))W(i,a);}));},fetchInitial:async t=>{if(!(!o||!t)){s(l(true)),n&&await n();try{let r=await o({page:1,walletAddress:t});s(p(r));}catch(r){console.error("[Pulsar] Failed to fetch initial transaction history:",r),s({isLoading:false,isError:true});}}},fetchNextPage:async t=>{let{hasMore:r,isLoading:c,currentPage:i}=m();if(!(!o||!r||c||!t)){s(l(true));try{let a=i+1,T=await o({page:a,walletAddress:t});s(p(T));}catch(a){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,a),s({isLoading:false,isError:true});}}}}))}function sn({adapter:e,maxTransactions:n=50,retentionDays:o=E,onRemoteCreate:d,gelatoApiKey:l,beforeTxProcess:p,abortOnTxError:s,...m}){return createStore()(persist((t,r)=>({...H({maxTransactions:n,retentionDays:o,onRemoteCreate:d})(t,r),getAdapter:()=>e,initializeTransactionsPool:async()=>{let i=Object.values(r().transactionsPool).filter(a=>a.pending).filter(a=>{try{return S(a),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),r().removeTxFromPool(a.txKey),false}});await Promise.all(i.map(a=>selectAdapterByKey({adapterKey:a.adapter,adapter:e})?.checkAndInitializeTrackerInStore({tx:a,gelatoApiKey:l,...r()})));},injectExternalPendingTxs:async c=>{let a=r().getAdapter(),T=[],y=c.filter(u=>{try{return S(u),!0}catch(b){return console.warn("[Pulsar] Skipped invalid remote transaction:",b),false}});t(u=>produce(u,b=>{let A=b.transactionsPool;y.forEach(x=>{let g=A[x.txKey];x.pending&&!g&&(A[x.txKey]=x,T.push(x));let K=x.status==="Success"||x.status==="Failed"||x.status==="Replaced";g?.pending&&K&&(g.status=x.status,g.pending=false,x.txKey&&(g.txKey=x.txKey),x.finishedTimestamp&&(g.finishedTimestamp=x.finishedTimestamp));});})),T.length>0&&await Promise.all(T.map(u=>selectAdapterByKey({adapterKey:u.adapter,adapter:a})?.checkAndInitializeTrackerInStore({tx:u,gelatoApiKey:l,...r()})));},executeTxAction:async({defaultTracker:c,actionFunction:i,params:a,beforeTxProcess:T,abortOnTxError:y,...u})=>{let b=y??s??true,A=oe().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),D(a);let{desiredChainID:x,tracker:g,...K}=a,{onSuccess:Q,onError:Z,onReplaced:ee}=u;t({initialTx:{...a,actionFunction:i,localTimestamp:A,isInitializing:true}});let v=selectAdapterByKey({adapterKey:K.adapter,adapter:e}),M=f=>{t(O=>produce(O,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=normalizeError(f));}));};if(!v){let f=new Error("No adapter found for this transaction.");throw M(f),f}try{let{connectorType:f,walletAddress:O}=v.getConnectorInfo();await v.checkChainForTx(x);try{await(T??p)?.();}catch(I){if(b)throw t({initialTx:{...a,actionFunction:i,localTimestamp:A,isInitializing:!1,error:normalizeError(I)}}),I;console.warn("[Pulsar] beforeTxProcess failed:",I);}let h=await i();if(!h){t({initialTx:void 0});return}let{tracker:B,txKey:L}=v.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:a.gelatoApiKey??l,bundlerUrl:a.bundlerUrl,pimlicoApiKey:a.pimlicoApiKey}),ne={...K,connectorType:f,from:O,tracker:B||c,chainId:setChainId(x),localTimestamp:A,txKey:L,bundlerUrl:a.bundlerUrl,pimlicoApiKey:a.pimlicoApiKey,hash:B==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:a.withTrackedModal};await r().addTxToPool(ne),t(I=>produce(I,N=>{N.initialTx&&(N.initialTx.isInitializing=!1,N.initialTx.lastTxKey=L);}));let te=r().transactionsPool[L];await v.checkAndInitializeTrackerInStore({tx:te,onSuccess:Q,onError:Z,onReplaced:ee,gelatoApiKey:l,...r()});}catch(f){throw M(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},...m,merge:(t,r)=>{let c=m.merge?m.merge(t,r):{...r,...t,initialTx:r.initialTx},i=R(c.transactionsPool??{},o);if(i.length===0)return c;let a={...c.transactionsPool},T=c.unsyncedTxKeys?{...c.unsyncedTxKeys}:c.unsyncedTxKeys;for(let y of i)delete a[y],T&&delete T[y];return {...c,transactionsPool:a,unsyncedTxKeys:T}}}))}var dn=(e=>n=>useStore(e,n));function un({tx:e,transactionsPool:n,updateTxParams:o}){let d=n[e.txKey];return l=>(o(e.txKey,l),d&&(d={...d,...l}),d)}var fe=5e3,ye=10;function mn(e){let{tx:n,fetcher:o,onInitialize:d,onSuccess:l,onFailure:p,onIntervalTick:s,onReplaced:m,removeTxFromPool:t,pollingInterval:r=fe,maxRetries:c=ye}=e;if(!n.pending)return;d?.();let i=c,a=true,T=u=>{a&&(a=false,t&&!u?.withoutRemoving&&t(n.txKey));};(async()=>{for(;a&&i>0;)try{if(await new Promise(u=>setTimeout(u,r)),!a)break;await o({tx:n,stopPolling:T,onSuccess:l,onFailure:p,onIntervalTick:s,onReplaced:m}),i=c;}catch(u){console.error(`Polling fetcher for txKey ${n.txKey} threw an error:`,u),i--;}i<=0&&(console.warn(`Polling for txKey ${n.txKey} stopped after reaching the maximum number of retries.`),p(),T());})();}export{j as MAX_TRANSACTION_DESCRIPTION_LENGTH,_ as MAX_TRANSACTION_PAYLOAD_BYTES,$ as MAX_TRANSACTION_TITLE_LENGTH,P as PulsarTransactionValidationError,w as TransactionStatus,re as TransactionTracker,dn as createBoundedUseStore,sn as createPulsarStore,Ge as createTxInMemoryStore,un as createTxUpdater,mn as initializePollingTracker,H as initializeTxTrackingStore,V as selectAllTransactions,se as selectAllTransactionsByActiveWallet,Oe as selectPendingTransactions,Ne as selectPendingTransactionsByActiveWallet,Le as selectTxByKey,D 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.2",
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",