@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 +48 -0
- package/README.md +39 -2
- package/dist/index.d.mts +62 -15
- package/dist/index.d.ts +62 -15
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -2
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)
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
685
|
-
* (by `localTimestamp`)
|
|
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
|
-
/**
|
|
980
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
685
|
-
* (by `localTimestamp`)
|
|
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
|
-
/**
|
|
980
|
-
|
|
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
|
|
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.
|
|
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",
|