@tuwaio/pulsar-core 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +48 -0
- package/README.md +40 -3
- package/dist/index.d.mts +76 -16
- package/dist/index.d.ts +76 -16
- 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:
|
|
@@ -137,7 +174,7 @@ To show the remote history, `createTxInMemoryStore` merges the pages returned by
|
|
|
137
174
|
|
|
138
175
|
- The state is written on every change: when a transaction is added, updated by its tracker or removed. `initialTx`, the transaction being signed, is neither saved nor restored, so a reload during signing leaves no stale signing state (pass your own `partialize` and `merge` to change that).
|
|
139
176
|
- In the browser the saved state is restored synchronously when the store is created, so the first client render already has the pool. Server rendering starts with an empty pool: render transaction lists on the client only. Where `localStorage` is unavailable, nothing is read or written.
|
|
140
|
-
-
|
|
177
|
+
- A finished transaction (including a `Failed` one) is dropped `retentionDays` after its `finishedTimestamp` (or its `localTimestamp` when it has none), when the saved state is restored and when a transaction is added; the default is 30 days, and `retentionDays: null` keeps finished transactions without a term. Pending transactions stay until they finish. Transactions also leave the pool through the `maxTransactions` eviction and `removeTxFromPool`. `store.persist.clearStorage()` deletes the saved state.
|
|
141
178
|
- Everything in a transaction is saved, including `payload` and, for ERC-4337, `bundlerUrl` and `pimlicoApiKey` (needed to resume tracking after a reload; a Pimlico key used in the browser is public anyway). Do not put secrets in `payload`.
|
|
142
179
|
- `createTxInMemoryStore` keeps its state in memory only.
|
|
143
180
|
|
package/dist/index.d.mts
CHANGED
|
@@ -127,12 +127,13 @@ type BaseTransaction = {
|
|
|
127
127
|
connectorType: string;
|
|
128
128
|
/**
|
|
129
129
|
* Number of block confirmations the EVM trackers wait for before marking the transaction successful. Defaults to 1.
|
|
130
|
-
* The Solana tracker always waits for the `finalized` commitment instead.
|
|
130
|
+
* The Solana tracker always waits for the `finalized` commitment instead. For a Safe transaction, the Safe tracker
|
|
131
|
+
* writes the number of owner confirmations the Safe needs to execute it (its threshold).
|
|
131
132
|
*/
|
|
132
133
|
requiredConfirmations?: number;
|
|
133
134
|
/**
|
|
134
135
|
* Confirmations reported by the tracker while the transaction is pending. The Solana tracker sets it to `'MAX'` when
|
|
135
|
-
* the transaction is finalized.
|
|
136
|
+
* the transaction is finalized. For a Safe transaction it is the number of owners who have confirmed it so far.
|
|
136
137
|
*/
|
|
137
138
|
confirmations?: number | string | null;
|
|
138
139
|
/**
|
|
@@ -285,7 +286,8 @@ type InitialTransaction = InitialTransactionParams & {
|
|
|
285
286
|
/**
|
|
286
287
|
* Callbacks passed to `executeTxAction` and forwarded to the tracker of that transaction. They are not stored:
|
|
287
288
|
* trackers restarted by `initializeTransactionsPool` or `injectExternalPendingTxs` (for example after a page reload)
|
|
288
|
-
* run without them. Their return values are not awaited.
|
|
289
|
+
* run without them. Their return values are not awaited. For reactions that must also run after a reload, use the
|
|
290
|
+
* `onTxFinished` option of `createPulsarStore` or `subscribeTxFinished` of the store: they run first, then these.
|
|
289
291
|
*
|
|
290
292
|
* @template T - The application transaction type.
|
|
291
293
|
*/
|
|
@@ -308,6 +310,21 @@ interface TrackerCallbacks<T extends Transaction> {
|
|
|
308
310
|
*/
|
|
309
311
|
onReplaced?: (newTx: T, oldTx: T) => Promise<void> | void;
|
|
310
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* A listener of the "transaction finished" event: `onTxFinished` of `createPulsarStore` and `subscribeTxFinished` of
|
|
315
|
+
* the store.
|
|
316
|
+
*
|
|
317
|
+
* It is called once per transaction, when the transaction moves from a non-final status to `Success`, `Failed` or
|
|
318
|
+
* `Replaced`, whatever finished it: the tracker started by `executeTxAction`, a tracker restarted after a page reload
|
|
319
|
+
* by `initializeTransactionsPool`, or a remote backend through `injectExternalPendingTxs`. Transactions that are
|
|
320
|
+
* already final when the store restores them from storage do not call it. It runs synchronously while the store
|
|
321
|
+
* writes the final status, before the `TrackerCallbacks` of the transaction. Its return value is not awaited; an error
|
|
322
|
+
* it throws or a promise it rejects is logged and does not stop the other listeners.
|
|
323
|
+
*
|
|
324
|
+
* @template T - The application transaction type.
|
|
325
|
+
* @param tx - The transaction with its final `status`.
|
|
326
|
+
*/
|
|
327
|
+
type TxFinishedListener<T extends Transaction> = (tx: T) => Promise<void> | void;
|
|
311
328
|
/**
|
|
312
329
|
* Callbacks that synchronize the local pool with a remote backend (for example Quasar). Passed to
|
|
313
330
|
* `createPulsarStore`.
|
|
@@ -342,8 +359,19 @@ type BeforeTxProcess = () => Promise<void> | void;
|
|
|
342
359
|
type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> & {
|
|
343
360
|
/** Global preflight callback run before every transaction. A `beforeTxProcess` passed to `executeTxAction` replaces it. */
|
|
344
361
|
beforeTxProcess?: BeforeTxProcess;
|
|
345
|
-
/**
|
|
362
|
+
/**
|
|
363
|
+
* Maximum number of transactions in the pool. When it is full, the oldest finished ones (by `localTimestamp`) are
|
|
364
|
+
* evicted; pending transactions are never evicted, so while more of them are pending the pool holds more. Defaults to
|
|
365
|
+
* 50.
|
|
366
|
+
*/
|
|
346
367
|
maxTransactions?: number;
|
|
368
|
+
/**
|
|
369
|
+
* Days a finished transaction stays in the pool, counted from its `finishedTimestamp` (or `localTimestamp` when it
|
|
370
|
+
* has none). Expired ones are dropped when the saved state is restored and when a transaction is added; pending
|
|
371
|
+
* transactions stay until they finish. `null` keeps finished transactions until `maxTransactions` evicts them.
|
|
372
|
+
* Defaults to 30.
|
|
373
|
+
*/
|
|
374
|
+
retentionDays?: number | null;
|
|
347
375
|
/** @deprecated Gelato relay is deprecated. Gelato API key used to track `TransactionTracker.Gelato` transactions. */
|
|
348
376
|
gelatoApiKey?: string;
|
|
349
377
|
/**
|
|
@@ -351,6 +379,11 @@ type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> &
|
|
|
351
379
|
* `onRemoteCreate`, whose errors never abort the transaction.
|
|
352
380
|
*/
|
|
353
381
|
abortOnTxError?: boolean;
|
|
382
|
+
/**
|
|
383
|
+
* Called once when a transaction finishes (see `TxFinishedListener`), also when a tracker restarted after a page
|
|
384
|
+
* reload finishes it. Runs before the listeners added with `subscribeTxFinished`.
|
|
385
|
+
*/
|
|
386
|
+
onTxFinished?: TxFinishedListener<T>;
|
|
354
387
|
} & SyncCallbacks<T>;
|
|
355
388
|
/**
|
|
356
389
|
* The input of `TxAdapter.checkTransactionsTracker`: the key returned by the action and the context needed to pick a
|
|
@@ -471,7 +504,7 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
|
|
|
471
504
|
initialTx?: InitialTransaction;
|
|
472
505
|
/**
|
|
473
506
|
* Validates a transaction and adds it to the pool with `pending: true`. When the pool already holds
|
|
474
|
-
* `maxTransactions` transactions, the oldest
|
|
507
|
+
* `maxTransactions` transactions, the oldest finished ones are evicted (pending ones never are). If `onRemoteCreate` is configured, the transaction gets
|
|
475
508
|
* `syncStatus: 'pending-sync'`, its key is listed in `unsyncedTxKeys`, and `onRemoteCreate` runs in the background
|
|
476
509
|
* (see `SyncCallbacks`). Does not start a tracker.
|
|
477
510
|
* @param tx - The transaction to add.
|
|
@@ -482,7 +515,9 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
|
|
|
482
515
|
addTxToPool: (tx: T) => Promise<void>;
|
|
483
516
|
/**
|
|
484
517
|
* Merges fields into a transaction of the pool; does nothing if the key is unknown. When `fields.status` is terminal
|
|
485
|
-
* and the transaction is in `unsyncedTxKeys`, it starts `reconcileUnsyncedTransactions` in the background.
|
|
518
|
+
* and the transaction is in `unsyncedTxKeys`, it starts `reconcileUnsyncedTransactions` in the background. When it
|
|
519
|
+
* moves the transaction from a non-final to a final status, it calls the "transaction finished" listeners (see
|
|
520
|
+
* `TxFinishedListener`).
|
|
486
521
|
* @param txKey - The key of the transaction.
|
|
487
522
|
* @param fields - The fields to merge.
|
|
488
523
|
*/
|
|
@@ -561,10 +596,19 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
|
|
|
561
596
|
/**
|
|
562
597
|
* Restarts the trackers of all pending transactions in the pool, for example after a page reload. Pending
|
|
563
598
|
* transactions that fail validation are removed from the pool. Call it once per page load: every call starts new
|
|
564
|
-
* trackers
|
|
599
|
+
* trackers. Trackers started here have no `TrackerCallbacks`; `onTxFinished` and `subscribeTxFinished` still run
|
|
600
|
+
* when they finish a transaction.
|
|
565
601
|
* @returns A promise that resolves when all trackers have started.
|
|
566
602
|
*/
|
|
567
603
|
initializeTransactionsPool: () => Promise<void>;
|
|
604
|
+
/**
|
|
605
|
+
* Adds a listener of the "transaction finished" event (see `TxFinishedListener`). Listeners live in memory, not in
|
|
606
|
+
* storage: add them after every page load, for example in a React effect (`useTxFinished` of
|
|
607
|
+
* `@tuwaio/pulsar-react`). Adding the same function twice registers it once.
|
|
608
|
+
* @param listener - Called with the transaction when it finishes.
|
|
609
|
+
* @returns A function that removes the listener.
|
|
610
|
+
*/
|
|
611
|
+
subscribeTxFinished: (listener: TxFinishedListener<T>) => () => void;
|
|
568
612
|
/**
|
|
569
613
|
* Merges transactions from a remote backend into the pool (cross-device sync). Invalid transactions are skipped
|
|
570
614
|
* with a warning. Pending remote transactions that are not in the pool are added and tracked. Local pending
|
|
@@ -674,13 +718,19 @@ type ITxInMemoryStoreParameters<T extends Transaction> = {
|
|
|
674
718
|
*
|
|
675
719
|
* @template T - The application transaction type.
|
|
676
720
|
* @param params - The slice options.
|
|
677
|
-
* @param params.maxTransactions - Maximum number of transactions in the pool; `addTxToPool` evicts the oldest
|
|
678
|
-
* (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.
|
|
724
|
+
* @param params.retentionDays - Days a finished transaction stays in the pool; `addTxToPool` drops the expired ones
|
|
725
|
+
* first. `null` keeps them without a term. Defaults to 30.
|
|
679
726
|
* @param params.onRemoteCreate - Optional remote sync callback, called in the background by `addTxToPool` (see
|
|
680
727
|
* `SyncCallbacks`).
|
|
728
|
+
* @param params.onTxFinished - Called by `updateTxParams` once when it moves a transaction from a non-final to a final
|
|
729
|
+
* status (see `TxFinishedListener`). `createPulsarStore` passes a function that runs its `onTxFinished` option and the
|
|
730
|
+
* listeners of `subscribeTxFinished`; it must not throw.
|
|
681
731
|
* @returns A slice creator for Zustand's `create` / `createStore`.
|
|
682
732
|
*/
|
|
683
|
-
declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, onRemoteCreate, }: Pick<PulsarAdapter<T>, 'onRemoteCreate'> & {
|
|
733
|
+
declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, retentionDays, onRemoteCreate, onTxFinished, }: Pick<PulsarAdapter<T>, 'onRemoteCreate' | 'retentionDays' | 'onTxFinished'> & {
|
|
684
734
|
maxTransactions: number;
|
|
685
735
|
}): StoreSlice<IInitializeTxTrackingStore<T>>;
|
|
686
736
|
|
|
@@ -763,7 +813,8 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
|
|
|
763
813
|
* `createBoundedUseStore`) that runs transactions through chain adapters and tracks them in the background.
|
|
764
814
|
*
|
|
765
815
|
* Side effects: the state is saved with Zustand's `persist` middleware under the key `name`, by default in
|
|
766
|
-
* `localStorage`, on every change: `transactionsPool`, `lastAddedTxKey` and `unsyncedTxKeys`.
|
|
816
|
+
* `localStorage`, on every change: `transactionsPool`, `lastAddedTxKey` and `unsyncedTxKeys`. A finished transaction
|
|
817
|
+
* is kept for `retentionDays` (30 by default) and at most `maxTransactions` are kept. `initialTx` is neither
|
|
767
818
|
* saved nor restored (pass your own `partialize` and `merge` to change that). In the browser the saved state is
|
|
768
819
|
* restored synchronously when the store is created. Where
|
|
769
820
|
* `localStorage` is not available (server rendering), nothing is read or written and Zustand logs a warning on
|
|
@@ -774,10 +825,15 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
|
|
|
774
825
|
* @param params.adapter - A chain adapter, or an array of adapters, such as `pulsarEvmAdapter` from
|
|
775
826
|
* `@tuwaio/pulsar-evm` or `pulsarSolanaAdapter` from `@tuwaio/pulsar-solana`.
|
|
776
827
|
* @param params.maxTransactions - Maximum number of transactions in the pool. Defaults to 50.
|
|
828
|
+
* @param params.retentionDays - Days a finished transaction stays in the pool, from its `finishedTimestamp`: expired
|
|
829
|
+
* ones are dropped when the saved state is restored and when a transaction is added. `null` keeps them without a
|
|
830
|
+
* term. Defaults to 30.
|
|
777
831
|
* @param params.onRemoteCreate - Remote sync callback (see `SyncCallbacks`).
|
|
778
832
|
* @param params.gelatoApiKey - Deprecated Gelato API key, passed to the trackers.
|
|
779
833
|
* @param params.beforeTxProcess - Global preflight callback (see `BeforeTxProcess`).
|
|
780
834
|
* @param params.abortOnTxError - Whether a `beforeTxProcess` error aborts the transaction. Defaults to `true`.
|
|
835
|
+
* @param params.onTxFinished - Called once when a transaction finishes, also after a page reload (see
|
|
836
|
+
* `TxFinishedListener`).
|
|
781
837
|
* @param params.name - The storage key. Required by `persist`; use a different key for every store.
|
|
782
838
|
* @returns The vanilla Zustand store. `store.persist` exposes the `persist` API (for example `clearStorage()`).
|
|
783
839
|
*
|
|
@@ -792,7 +848,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
|
|
|
792
848
|
* });
|
|
793
849
|
* ```
|
|
794
850
|
*/
|
|
795
|
-
declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
|
|
851
|
+
declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, retentionDays, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, onTxFinished, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
|
|
796
852
|
setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
|
|
797
853
|
setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
|
|
798
854
|
persist: {
|
|
@@ -963,15 +1019,19 @@ type PollingTrackerConfig<R, T extends Pick<Transaction, 'txKey' | 'pending'>> =
|
|
|
963
1019
|
* @param txKey - The `txKey` of the tracked transaction.
|
|
964
1020
|
*/
|
|
965
1021
|
removeTxFromPool?: (txKey: string) => void;
|
|
966
|
-
/**
|
|
967
|
-
|
|
1022
|
+
/**
|
|
1023
|
+
* The delay before each attempt, in milliseconds, or a function that returns it from the milliseconds elapsed since
|
|
1024
|
+
* polling started, to poll less often over time. Defaults to 5000.
|
|
1025
|
+
*/
|
|
1026
|
+
pollingInterval?: number | ((elapsedMs: number) => number);
|
|
968
1027
|
/** The number of consecutive failed attempts (thrown errors) after which polling stops. Defaults to 10. */
|
|
969
1028
|
maxRetries?: number;
|
|
970
1029
|
};
|
|
971
1030
|
/**
|
|
972
1031
|
* Starts polling a transaction in the background and returns immediately. Does nothing if `tx.pending` is `false`.
|
|
973
1032
|
*
|
|
974
|
-
* Every `pollingInterval` milliseconds (the first attempt also waits
|
|
1033
|
+
* Every `pollingInterval` milliseconds (the first attempt also waits; a function is asked before each wait) it calls
|
|
1034
|
+
* `fetcher`. Polling continues until the
|
|
975
1035
|
* fetcher calls `stopPolling`. A fetcher that throws counts as a failed attempt; after `maxRetries` consecutive failed
|
|
976
1036
|
* attempts the tracker calls `onFailure()` without arguments, logs a warning and stops (which calls
|
|
977
1037
|
* `removeTxFromPool`, if configured). A successful attempt resets the count.
|
|
@@ -1047,4 +1107,4 @@ declare function validateInitialTransactionParams(params: Omit<InitialTransactio
|
|
|
1047
1107
|
declare function validateTransaction<T extends Transaction>(tx: T): void;
|
|
1048
1108
|
|
|
1049
1109
|
export { MAX_TRANSACTION_DESCRIPTION_LENGTH, MAX_TRANSACTION_PAYLOAD_BYTES, MAX_TRANSACTION_TITLE_LENGTH, PulsarTransactionValidationError, TransactionStatus, TransactionTracker, createBoundedUseStore, createPulsarStore, createTxInMemoryStore, createTxUpdater, initializePollingTracker, initializeTxTrackingStore, selectAllTransactions, selectAllTransactionsByActiveWallet, selectPendingTransactions, selectPendingTransactionsByActiveWallet, selectTxByKey, validateInitialTransactionParams, validateTransaction };
|
|
1050
|
-
export type { ActionTxKey, BaseTransaction, BeforeTxProcess, CheckTxTracker, EvmTransaction, ExtractState, IInitializeTxTrackingStore, ITxInMemoryStore, ITxInMemoryStoreParameters, ITxTrackingStore, InitialTransaction, InitialTransactionParams, PollingFetcherParams, PollingTrackerConfig, PulsarAdapter, SolanaTransaction, StarknetTransaction, StoreSlice, SyncCallbacks, TrackerCallbacks, Transaction, TransactionPool, TxAdapter, TxInMemoryPagination, UpdatableTransactionFields };
|
|
1110
|
+
export type { ActionTxKey, BaseTransaction, BeforeTxProcess, CheckTxTracker, EvmTransaction, ExtractState, IInitializeTxTrackingStore, ITxInMemoryStore, ITxInMemoryStoreParameters, ITxTrackingStore, InitialTransaction, InitialTransactionParams, PollingFetcherParams, PollingTrackerConfig, PulsarAdapter, SolanaTransaction, StarknetTransaction, StoreSlice, SyncCallbacks, TrackerCallbacks, Transaction, TransactionPool, TxAdapter, TxFinishedListener, TxInMemoryPagination, UpdatableTransactionFields };
|
package/dist/index.d.ts
CHANGED
|
@@ -127,12 +127,13 @@ type BaseTransaction = {
|
|
|
127
127
|
connectorType: string;
|
|
128
128
|
/**
|
|
129
129
|
* Number of block confirmations the EVM trackers wait for before marking the transaction successful. Defaults to 1.
|
|
130
|
-
* The Solana tracker always waits for the `finalized` commitment instead.
|
|
130
|
+
* The Solana tracker always waits for the `finalized` commitment instead. For a Safe transaction, the Safe tracker
|
|
131
|
+
* writes the number of owner confirmations the Safe needs to execute it (its threshold).
|
|
131
132
|
*/
|
|
132
133
|
requiredConfirmations?: number;
|
|
133
134
|
/**
|
|
134
135
|
* Confirmations reported by the tracker while the transaction is pending. The Solana tracker sets it to `'MAX'` when
|
|
135
|
-
* the transaction is finalized.
|
|
136
|
+
* the transaction is finalized. For a Safe transaction it is the number of owners who have confirmed it so far.
|
|
136
137
|
*/
|
|
137
138
|
confirmations?: number | string | null;
|
|
138
139
|
/**
|
|
@@ -285,7 +286,8 @@ type InitialTransaction = InitialTransactionParams & {
|
|
|
285
286
|
/**
|
|
286
287
|
* Callbacks passed to `executeTxAction` and forwarded to the tracker of that transaction. They are not stored:
|
|
287
288
|
* trackers restarted by `initializeTransactionsPool` or `injectExternalPendingTxs` (for example after a page reload)
|
|
288
|
-
* run without them. Their return values are not awaited.
|
|
289
|
+
* run without them. Their return values are not awaited. For reactions that must also run after a reload, use the
|
|
290
|
+
* `onTxFinished` option of `createPulsarStore` or `subscribeTxFinished` of the store: they run first, then these.
|
|
289
291
|
*
|
|
290
292
|
* @template T - The application transaction type.
|
|
291
293
|
*/
|
|
@@ -308,6 +310,21 @@ interface TrackerCallbacks<T extends Transaction> {
|
|
|
308
310
|
*/
|
|
309
311
|
onReplaced?: (newTx: T, oldTx: T) => Promise<void> | void;
|
|
310
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* A listener of the "transaction finished" event: `onTxFinished` of `createPulsarStore` and `subscribeTxFinished` of
|
|
315
|
+
* the store.
|
|
316
|
+
*
|
|
317
|
+
* It is called once per transaction, when the transaction moves from a non-final status to `Success`, `Failed` or
|
|
318
|
+
* `Replaced`, whatever finished it: the tracker started by `executeTxAction`, a tracker restarted after a page reload
|
|
319
|
+
* by `initializeTransactionsPool`, or a remote backend through `injectExternalPendingTxs`. Transactions that are
|
|
320
|
+
* already final when the store restores them from storage do not call it. It runs synchronously while the store
|
|
321
|
+
* writes the final status, before the `TrackerCallbacks` of the transaction. Its return value is not awaited; an error
|
|
322
|
+
* it throws or a promise it rejects is logged and does not stop the other listeners.
|
|
323
|
+
*
|
|
324
|
+
* @template T - The application transaction type.
|
|
325
|
+
* @param tx - The transaction with its final `status`.
|
|
326
|
+
*/
|
|
327
|
+
type TxFinishedListener<T extends Transaction> = (tx: T) => Promise<void> | void;
|
|
311
328
|
/**
|
|
312
329
|
* Callbacks that synchronize the local pool with a remote backend (for example Quasar). Passed to
|
|
313
330
|
* `createPulsarStore`.
|
|
@@ -342,8 +359,19 @@ type BeforeTxProcess = () => Promise<void> | void;
|
|
|
342
359
|
type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> & {
|
|
343
360
|
/** Global preflight callback run before every transaction. A `beforeTxProcess` passed to `executeTxAction` replaces it. */
|
|
344
361
|
beforeTxProcess?: BeforeTxProcess;
|
|
345
|
-
/**
|
|
362
|
+
/**
|
|
363
|
+
* Maximum number of transactions in the pool. When it is full, the oldest finished ones (by `localTimestamp`) are
|
|
364
|
+
* evicted; pending transactions are never evicted, so while more of them are pending the pool holds more. Defaults to
|
|
365
|
+
* 50.
|
|
366
|
+
*/
|
|
346
367
|
maxTransactions?: number;
|
|
368
|
+
/**
|
|
369
|
+
* Days a finished transaction stays in the pool, counted from its `finishedTimestamp` (or `localTimestamp` when it
|
|
370
|
+
* has none). Expired ones are dropped when the saved state is restored and when a transaction is added; pending
|
|
371
|
+
* transactions stay until they finish. `null` keeps finished transactions until `maxTransactions` evicts them.
|
|
372
|
+
* Defaults to 30.
|
|
373
|
+
*/
|
|
374
|
+
retentionDays?: number | null;
|
|
347
375
|
/** @deprecated Gelato relay is deprecated. Gelato API key used to track `TransactionTracker.Gelato` transactions. */
|
|
348
376
|
gelatoApiKey?: string;
|
|
349
377
|
/**
|
|
@@ -351,6 +379,11 @@ type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> &
|
|
|
351
379
|
* `onRemoteCreate`, whose errors never abort the transaction.
|
|
352
380
|
*/
|
|
353
381
|
abortOnTxError?: boolean;
|
|
382
|
+
/**
|
|
383
|
+
* Called once when a transaction finishes (see `TxFinishedListener`), also when a tracker restarted after a page
|
|
384
|
+
* reload finishes it. Runs before the listeners added with `subscribeTxFinished`.
|
|
385
|
+
*/
|
|
386
|
+
onTxFinished?: TxFinishedListener<T>;
|
|
354
387
|
} & SyncCallbacks<T>;
|
|
355
388
|
/**
|
|
356
389
|
* The input of `TxAdapter.checkTransactionsTracker`: the key returned by the action and the context needed to pick a
|
|
@@ -471,7 +504,7 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
|
|
|
471
504
|
initialTx?: InitialTransaction;
|
|
472
505
|
/**
|
|
473
506
|
* Validates a transaction and adds it to the pool with `pending: true`. When the pool already holds
|
|
474
|
-
* `maxTransactions` transactions, the oldest
|
|
507
|
+
* `maxTransactions` transactions, the oldest finished ones are evicted (pending ones never are). If `onRemoteCreate` is configured, the transaction gets
|
|
475
508
|
* `syncStatus: 'pending-sync'`, its key is listed in `unsyncedTxKeys`, and `onRemoteCreate` runs in the background
|
|
476
509
|
* (see `SyncCallbacks`). Does not start a tracker.
|
|
477
510
|
* @param tx - The transaction to add.
|
|
@@ -482,7 +515,9 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
|
|
|
482
515
|
addTxToPool: (tx: T) => Promise<void>;
|
|
483
516
|
/**
|
|
484
517
|
* Merges fields into a transaction of the pool; does nothing if the key is unknown. When `fields.status` is terminal
|
|
485
|
-
* and the transaction is in `unsyncedTxKeys`, it starts `reconcileUnsyncedTransactions` in the background.
|
|
518
|
+
* and the transaction is in `unsyncedTxKeys`, it starts `reconcileUnsyncedTransactions` in the background. When it
|
|
519
|
+
* moves the transaction from a non-final to a final status, it calls the "transaction finished" listeners (see
|
|
520
|
+
* `TxFinishedListener`).
|
|
486
521
|
* @param txKey - The key of the transaction.
|
|
487
522
|
* @param fields - The fields to merge.
|
|
488
523
|
*/
|
|
@@ -561,10 +596,19 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
|
|
|
561
596
|
/**
|
|
562
597
|
* Restarts the trackers of all pending transactions in the pool, for example after a page reload. Pending
|
|
563
598
|
* transactions that fail validation are removed from the pool. Call it once per page load: every call starts new
|
|
564
|
-
* trackers
|
|
599
|
+
* trackers. Trackers started here have no `TrackerCallbacks`; `onTxFinished` and `subscribeTxFinished` still run
|
|
600
|
+
* when they finish a transaction.
|
|
565
601
|
* @returns A promise that resolves when all trackers have started.
|
|
566
602
|
*/
|
|
567
603
|
initializeTransactionsPool: () => Promise<void>;
|
|
604
|
+
/**
|
|
605
|
+
* Adds a listener of the "transaction finished" event (see `TxFinishedListener`). Listeners live in memory, not in
|
|
606
|
+
* storage: add them after every page load, for example in a React effect (`useTxFinished` of
|
|
607
|
+
* `@tuwaio/pulsar-react`). Adding the same function twice registers it once.
|
|
608
|
+
* @param listener - Called with the transaction when it finishes.
|
|
609
|
+
* @returns A function that removes the listener.
|
|
610
|
+
*/
|
|
611
|
+
subscribeTxFinished: (listener: TxFinishedListener<T>) => () => void;
|
|
568
612
|
/**
|
|
569
613
|
* Merges transactions from a remote backend into the pool (cross-device sync). Invalid transactions are skipped
|
|
570
614
|
* with a warning. Pending remote transactions that are not in the pool are added and tracked. Local pending
|
|
@@ -674,13 +718,19 @@ type ITxInMemoryStoreParameters<T extends Transaction> = {
|
|
|
674
718
|
*
|
|
675
719
|
* @template T - The application transaction type.
|
|
676
720
|
* @param params - The slice options.
|
|
677
|
-
* @param params.maxTransactions - Maximum number of transactions in the pool; `addTxToPool` evicts the oldest
|
|
678
|
-
* (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.
|
|
724
|
+
* @param params.retentionDays - Days a finished transaction stays in the pool; `addTxToPool` drops the expired ones
|
|
725
|
+
* first. `null` keeps them without a term. Defaults to 30.
|
|
679
726
|
* @param params.onRemoteCreate - Optional remote sync callback, called in the background by `addTxToPool` (see
|
|
680
727
|
* `SyncCallbacks`).
|
|
728
|
+
* @param params.onTxFinished - Called by `updateTxParams` once when it moves a transaction from a non-final to a final
|
|
729
|
+
* status (see `TxFinishedListener`). `createPulsarStore` passes a function that runs its `onTxFinished` option and the
|
|
730
|
+
* listeners of `subscribeTxFinished`; it must not throw.
|
|
681
731
|
* @returns A slice creator for Zustand's `create` / `createStore`.
|
|
682
732
|
*/
|
|
683
|
-
declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, onRemoteCreate, }: Pick<PulsarAdapter<T>, 'onRemoteCreate'> & {
|
|
733
|
+
declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, retentionDays, onRemoteCreate, onTxFinished, }: Pick<PulsarAdapter<T>, 'onRemoteCreate' | 'retentionDays' | 'onTxFinished'> & {
|
|
684
734
|
maxTransactions: number;
|
|
685
735
|
}): StoreSlice<IInitializeTxTrackingStore<T>>;
|
|
686
736
|
|
|
@@ -763,7 +813,8 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
|
|
|
763
813
|
* `createBoundedUseStore`) that runs transactions through chain adapters and tracks them in the background.
|
|
764
814
|
*
|
|
765
815
|
* Side effects: the state is saved with Zustand's `persist` middleware under the key `name`, by default in
|
|
766
|
-
* `localStorage`, on every change: `transactionsPool`, `lastAddedTxKey` and `unsyncedTxKeys`.
|
|
816
|
+
* `localStorage`, on every change: `transactionsPool`, `lastAddedTxKey` and `unsyncedTxKeys`. A finished transaction
|
|
817
|
+
* is kept for `retentionDays` (30 by default) and at most `maxTransactions` are kept. `initialTx` is neither
|
|
767
818
|
* saved nor restored (pass your own `partialize` and `merge` to change that). In the browser the saved state is
|
|
768
819
|
* restored synchronously when the store is created. Where
|
|
769
820
|
* `localStorage` is not available (server rendering), nothing is read or written and Zustand logs a warning on
|
|
@@ -774,10 +825,15 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
|
|
|
774
825
|
* @param params.adapter - A chain adapter, or an array of adapters, such as `pulsarEvmAdapter` from
|
|
775
826
|
* `@tuwaio/pulsar-evm` or `pulsarSolanaAdapter` from `@tuwaio/pulsar-solana`.
|
|
776
827
|
* @param params.maxTransactions - Maximum number of transactions in the pool. Defaults to 50.
|
|
828
|
+
* @param params.retentionDays - Days a finished transaction stays in the pool, from its `finishedTimestamp`: expired
|
|
829
|
+
* ones are dropped when the saved state is restored and when a transaction is added. `null` keeps them without a
|
|
830
|
+
* term. Defaults to 30.
|
|
777
831
|
* @param params.onRemoteCreate - Remote sync callback (see `SyncCallbacks`).
|
|
778
832
|
* @param params.gelatoApiKey - Deprecated Gelato API key, passed to the trackers.
|
|
779
833
|
* @param params.beforeTxProcess - Global preflight callback (see `BeforeTxProcess`).
|
|
780
834
|
* @param params.abortOnTxError - Whether a `beforeTxProcess` error aborts the transaction. Defaults to `true`.
|
|
835
|
+
* @param params.onTxFinished - Called once when a transaction finishes, also after a page reload (see
|
|
836
|
+
* `TxFinishedListener`).
|
|
781
837
|
* @param params.name - The storage key. Required by `persist`; use a different key for every store.
|
|
782
838
|
* @returns The vanilla Zustand store. `store.persist` exposes the `persist` API (for example `clearStorage()`).
|
|
783
839
|
*
|
|
@@ -792,7 +848,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
|
|
|
792
848
|
* });
|
|
793
849
|
* ```
|
|
794
850
|
*/
|
|
795
|
-
declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
|
|
851
|
+
declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, retentionDays, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, onTxFinished, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
|
|
796
852
|
setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
|
|
797
853
|
setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
|
|
798
854
|
persist: {
|
|
@@ -963,15 +1019,19 @@ type PollingTrackerConfig<R, T extends Pick<Transaction, 'txKey' | 'pending'>> =
|
|
|
963
1019
|
* @param txKey - The `txKey` of the tracked transaction.
|
|
964
1020
|
*/
|
|
965
1021
|
removeTxFromPool?: (txKey: string) => void;
|
|
966
|
-
/**
|
|
967
|
-
|
|
1022
|
+
/**
|
|
1023
|
+
* The delay before each attempt, in milliseconds, or a function that returns it from the milliseconds elapsed since
|
|
1024
|
+
* polling started, to poll less often over time. Defaults to 5000.
|
|
1025
|
+
*/
|
|
1026
|
+
pollingInterval?: number | ((elapsedMs: number) => number);
|
|
968
1027
|
/** The number of consecutive failed attempts (thrown errors) after which polling stops. Defaults to 10. */
|
|
969
1028
|
maxRetries?: number;
|
|
970
1029
|
};
|
|
971
1030
|
/**
|
|
972
1031
|
* Starts polling a transaction in the background and returns immediately. Does nothing if `tx.pending` is `false`.
|
|
973
1032
|
*
|
|
974
|
-
* Every `pollingInterval` milliseconds (the first attempt also waits
|
|
1033
|
+
* Every `pollingInterval` milliseconds (the first attempt also waits; a function is asked before each wait) it calls
|
|
1034
|
+
* `fetcher`. Polling continues until the
|
|
975
1035
|
* fetcher calls `stopPolling`. A fetcher that throws counts as a failed attempt; after `maxRetries` consecutive failed
|
|
976
1036
|
* attempts the tracker calls `onFailure()` without arguments, logs a warning and stops (which calls
|
|
977
1037
|
* `removeTxFromPool`, if configured). A successful attempt resets the count.
|
|
@@ -1047,4 +1107,4 @@ declare function validateInitialTransactionParams(params: Omit<InitialTransactio
|
|
|
1047
1107
|
declare function validateTransaction<T extends Transaction>(tx: T): void;
|
|
1048
1108
|
|
|
1049
1109
|
export { MAX_TRANSACTION_DESCRIPTION_LENGTH, MAX_TRANSACTION_PAYLOAD_BYTES, MAX_TRANSACTION_TITLE_LENGTH, PulsarTransactionValidationError, TransactionStatus, TransactionTracker, createBoundedUseStore, createPulsarStore, createTxInMemoryStore, createTxUpdater, initializePollingTracker, initializeTxTrackingStore, selectAllTransactions, selectAllTransactionsByActiveWallet, selectPendingTransactions, selectPendingTransactionsByActiveWallet, selectTxByKey, validateInitialTransactionParams, validateTransaction };
|
|
1050
|
-
export type { ActionTxKey, BaseTransaction, BeforeTxProcess, CheckTxTracker, EvmTransaction, ExtractState, IInitializeTxTrackingStore, ITxInMemoryStore, ITxInMemoryStoreParameters, ITxTrackingStore, InitialTransaction, InitialTransactionParams, PollingFetcherParams, PollingTrackerConfig, PulsarAdapter, SolanaTransaction, StarknetTransaction, StoreSlice, SyncCallbacks, TrackerCallbacks, Transaction, TransactionPool, TxAdapter, TxInMemoryPagination, UpdatableTransactionFields };
|
|
1110
|
+
export type { ActionTxKey, BaseTransaction, BeforeTxProcess, CheckTxTracker, EvmTransaction, ExtractState, IInitializeTxTrackingStore, ITxInMemoryStore, ITxInMemoryStoreParameters, ITxTrackingStore, InitialTransaction, InitialTransactionParams, PollingFetcherParams, PollingTrackerConfig, PulsarAdapter, SolanaTransaction, StarknetTransaction, StoreSlice, SyncCallbacks, TrackerCallbacks, Transaction, TransactionPool, TxAdapter, TxFinishedListener, TxInMemoryPagination, UpdatableTransactionFields };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var immer=require('immer'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),ce=require('dayjs'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var ce__default=/*#__PURE__*/_interopDefault(ce);var ee=(s=>(s.Ethereum="ethereum",s.Safe="safe",s.Gelato="gelato",s.Solana="solana",s.ERC4337="erc4337",s.EIP5792="eip5792",s))(ee||{}),K=(T=>(T.Failed="Failed",T.Success="Success",T.Replaced="Replaced",T))(K||{});var U=100,B=300,N=10*1024,ne=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,o){super(o),this.name="PulsarTransactionValidationError",this.field=e;}};function $(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function S(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function w({field:n,value:e,maxLength:o}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((d,p)=>{let s=Array.isArray(e)?`${n}[${p}]`:n;if(typeof d!="string")throw new P(s,`${s} must be a string.`);if(d.length>o)throw new P(s,`${s} must be ${o} characters or less.`);O(s,d);});}function j(n){if(n===void 0)return;let e;try{e=JSON.stringify(n);}catch{throw new P("payload","payload must be JSON-serializable.")}if(e===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(e).length>N)throw new P("payload",`payload must be ${N} bytes or less when serialized.`);_(n);}function _(n,e="payload"){if(typeof n=="string"){O(e,n);return}n===null||typeof n!="object"||Object.entries(n).forEach(([o,T])=>{let d=`${e}.${o}`;O(d,o),_(T,d);});}function O(n,e){if(ne.some(o=>o.test(e)))throw new P(n,`${n} contains a blocked executable-like pattern.`)}function te(n){let e={...n};return delete e.pimlicoApiKey,delete e.gelatoApiKey,e}function G({maxTransactions:n,onRemoteCreate:e}){let o=false,T=new Set;return (d,p)=>{let s=async(t,r)=>{let c=p().transactionsPool[t];if(!(!e||!c||T.has(t))){T.add(t);try{await e(te(c)),d(a=>immer.produce(a,i=>{let l=i.transactionsPool[t];l&&(l.syncStatus="synced"),i.unsyncedTxKeys&&delete i.unsyncedTxKeys[t];}));}catch(a){console.warn(r,a);}finally{T.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!e||o)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){o=true;try{for(let r of t){if(!p().transactionsPool[r]){d(c=>immer.produce(c,a=>{a.unsyncedTxKeys&&delete a.unsyncedTxKeys[r];}));continue}await s(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{o=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...e&&{syncStatus:"pending-sync"}};return d(c=>immer.produce(c,a=>{if(a.lastAddedTxKey=t.txKey,t.txKey){if(e&&(a.unsyncedTxKeys||(a.unsyncedTxKeys={}),a.unsyncedTxKeys[t.txKey]=true),Object.keys(a.transactionsPool).length>=n){let l=Object.values(a.transactionsPool).sort((x,y)=>x.localTimestamp-y.localTimestamp);if(l.length>0){let x=l[0];delete a.transactionsPool[x.txKey];}}a.transactionsPool[t.txKey]=r;}})),e&&t.txKey&&s(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{d(a=>immer.produce(a,i=>{let l=i.transactionsPool[t];l&&Object.assign(l,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(a=>{console.error("[Pulsar] Terminal reconciliation failed:",a);});},removeTxFromPool:t=>{d(r=>immer.produce(r,c=>{delete c.transactionsPool[t];}));},closeTxTrackedModal:t=>{d(r=>immer.produce(r,c=>{if(t&&c.transactionsPool[t]){let a=c.transactionsPool[t];c.transactionsPool[t]={...a,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var D=n=>Object.values(n).sort((e,o)=>Number(e.localTimestamp)-Number(o.localTimestamp)),Ae=n=>D(n).filter(e=>e.pending),ve=(n,e)=>n[e],re=(n,e)=>D(n).filter(o=>o.from.toLowerCase()===e.toLowerCase()),Ie=(n,e)=>re(n,e).filter(o=>o.pending);var{produce:q}=new immer.Immer({autoFreeze:false}),ae=n=>n.filter(e=>{try{return S(e),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),H=n=>n==="Success"||n==="Failed"||n==="Replaced",V=(n,e)=>{let o=n[e.txKey];if(o){if(H(o.status))return false;if(o.pending){if(H(e.status))return n[e.txKey]={...o,...e},true;let T=typeof e.confirmations=="number"?e.confirmations:0,d=typeof o.confirmations=="number"?o.confirmations:0;return T>d?(n[e.txKey]={...o,...e},true):false}}return n[e.txKey]=e,true};function Me({localTransactionsPool:n,reconcileUnsyncedTransactions:e,getHistory:o,onHistoryFetched:T}){let d=s=>({isLoading:s,isError:false}),p=s=>{if(!s)return {isLoading:false};let t=ae(s.docs);return T&&queueMicrotask(()=>T(t)),r=>q(r,c=>{let a=c.transactionsPool;for(let i of t)V(a,i);c.currentPage=s.page,c.hasMore=s.hasNextPage,c.isLoading=false;})};return vanilla.createStore()((s,t)=>({transactionsPool:n,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{s(c=>q(c,a=>{let i=a.transactionsPool;for(let l of Object.values(r))V(i,l);}));},fetchInitial:async r=>{if(!(!o||!r)){s(d(true)),e&&await e();try{let c=await o({page:1,walletAddress:r});s(p(c));}catch(c){console.error("[Pulsar] Failed to fetch initial transaction history:",c),s({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:c,isLoading:a,currentPage:i}=t();if(!(!o||!c||a||!r)){s(d(true));try{let l=i+1,x=await o({page:l,walletAddress:r});s(p(x));}catch(l){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,l),s({isLoading:false,isError:true});}}}}))}function Ye({adapter:n,maxTransactions:e=50,onRemoteCreate:o,gelatoApiKey:T,beforeTxProcess:d,abortOnTxError:p,...s}){return vanilla.createStore()(middleware.persist((t,r)=>({...G({maxTransactions:e,onRemoteCreate:o})(t,r),getAdapter:()=>n,initializeTransactionsPool:async()=>{let a=Object.values(r().transactionsPool).filter(i=>i.pending).filter(i=>{try{return S(i),!0}catch(l){return console.warn("[Pulsar] Removed invalid persisted transaction:",l),r().removeTxFromPool(i.txKey),false}});await Promise.all(a.map(i=>orbitCore.selectAdapterByKey({adapterKey:i.adapter,adapter:n})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:T,...r()})));},injectExternalPendingTxs:async c=>{let i=r().getAdapter(),l=[],x=c.filter(y=>{try{return S(y),!0}catch(m){return console.warn("[Pulsar] Skipped invalid remote transaction:",m),false}});t(y=>immer.produce(y,m=>{let b=m.transactionsPool;x.forEach(u=>{let g=b[u.txKey];u.pending&&!g&&(b[u.txKey]=u,l.push(u));let I=u.status==="Success"||u.status==="Failed"||u.status==="Replaced";g?.pending&&I&&(g.status=u.status,g.pending=false,u.txKey&&(g.txKey=u.txKey),u.finishedTimestamp&&(g.finishedTimestamp=u.finishedTimestamp));});})),l.length>0&&await Promise.all(l.map(y=>orbitCore.selectAdapterByKey({adapterKey:y.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:y,gelatoApiKey:T,...r()})));},executeTxAction:async({defaultTracker:c,actionFunction:a,params:i,beforeTxProcess:l,abortOnTxError:x,...y})=>{let m=x??p??true,b=ce__default.default().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),$(i);let{desiredChainID:u,tracker:g,...I}=i,{onSuccess:W,onError:J,onReplaced:Y}=y;t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:true}});let A=orbitCore.selectAdapterByKey({adapterKey:I.adapter,adapter:n}),z=f=>{t(E=>immer.produce(E,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=orbitCore.normalizeError(f));}));};if(!A){let f=new Error("No adapter found for this transaction.");throw z(f),f}try{let{connectorType:f,walletAddress:E}=A.getConnectorInfo();await A.checkChainForTx(u);try{await(l??d)?.();}catch(v){if(m)throw t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:!1,error:orbitCore.normalizeError(v)}}),v;console.warn("[Pulsar] beforeTxProcess failed:",v);}let h=await a();if(!h){t({initialTx:void 0});return}let{tracker:M,txKey:R}=A.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:i.gelatoApiKey??T,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey}),Q={...I,connectorType:f,from:E,tracker:M||c,chainId:orbitCore.setChainId(u),localTimestamp:b,txKey:R,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await r().addTxToPool(Q),t(v=>immer.produce(v,F=>{F.initialTx&&(F.initialTx.isInitializing=!1,F.initialTx.lastTxKey=R);}));let Z=r().transactionsPool[R];await A.checkAndInitializeTrackerInStore({tx:Z,onSuccess:W,onError:J,onReplaced:Y,gelatoApiKey:T,...r()});}catch(f){throw z(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},merge:(t,r)=>({...r,...t,initialTx:r.initialTx}),...s}))}var nn=(n=>e=>zustand.useStore(n,e));function rn({tx:n,transactionsPool:e,updateTxParams:o}){let T=e[n.txKey];return d=>(o(n.txKey,d),T&&(T={...T,...d}),T)}var pe=5e3,ue=10;function an(n){let{tx:e,fetcher:o,onInitialize:T,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t,removeTxFromPool:r,pollingInterval:c=pe,maxRetries:a=ue}=n;if(!e.pending)return;T?.();let i=a,l=true,x=m=>{l&&(l=false,r&&!m?.withoutRemoving&&r(e.txKey));};(async()=>{for(;l&&i>0;)try{if(await new Promise(m=>setTimeout(m,c)),!l)break;await o({tx:e,stopPolling:x,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t}),i=a;}catch(m){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,m),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),p(),x());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=B;exports.MAX_TRANSACTION_PAYLOAD_BYTES=N;exports.MAX_TRANSACTION_TITLE_LENGTH=U;exports.PulsarTransactionValidationError=P;exports.TransactionStatus=K;exports.TransactionTracker=ee;exports.createBoundedUseStore=nn;exports.createPulsarStore=Ye;exports.createTxInMemoryStore=Me;exports.createTxUpdater=rn;exports.initializePollingTracker=an;exports.initializeTxTrackingStore=G;exports.selectAllTransactions=D;exports.selectAllTransactionsByActiveWallet=re;exports.selectPendingTransactions=Ae;exports.selectPendingTransactionsByActiveWallet=Ie;exports.selectTxByKey=ve;exports.validateInitialTransactionParams=$;exports.validateTransaction=S;
|
|
1
|
+
'use strict';var immer=require('immer'),se=require('dayjs'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var se__default=/*#__PURE__*/_interopDefault(se);var R=30;function O(e,n,r=se__default.default().unix()){if(n===null)return [];let d=r-n*24*60*60;return Object.values(e).filter(l=>!l.pending&&(l.finishedTimestamp??l.localTimestamp)<d).map(l=>l.txKey)}var G=100,q=300,D=10*1024,ce=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],A=class extends Error{field;constructor(n,r){super(r),this.name="PulsarTransactionValidationError",this.field=n;}};function H(e){L({field:"title",value:e.title,maxLength:G}),L({field:"description",value:e.description,maxLength:q}),V(e.payload);}function I(e){L({field:"title",value:e.title,maxLength:G}),L({field:"description",value:e.description,maxLength:q}),V(e.payload);}function L({field:e,value:n,maxLength:r}){if(n===void 0)return;(Array.isArray(n)?n:[n]).forEach((l,x)=>{let a=Array.isArray(n)?`${e}[${x}]`:e;if(typeof l!="string")throw new A(a,`${a} must be a string.`);if(l.length>r)throw new A(a,`${a} must be ${r} characters or less.`);C(a,l);});}function V(e){if(e===void 0)return;let n;try{n=JSON.stringify(e);}catch{throw new A("payload","payload must be JSON-serializable.")}if(n===void 0)throw new A("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(n).length>D)throw new A("payload",`payload must be ${D} bytes or less when serialized.`);X(e);}function X(e,n="payload"){if(typeof e=="string"){C(n,e);return}e===null||typeof e!="object"||Object.entries(e).forEach(([r,d])=>{let l=`${n}.${r}`;C(l,r),X(d,l);});}function C(e,n){if(ce.some(r=>r.test(n)))throw new A(e,`${e} contains a blocked executable-like pattern.`)}var le=(a=>(a.Ethereum="ethereum",a.Safe="safe",a.Gelato="gelato",a.Solana="solana",a.ERC4337="erc4337",a.EIP5792="eip5792",a))(le||{}),M=(d=>(d.Failed="Failed",d.Success="Success",d.Replaced="Replaced",d))(M||{});var k=e=>e==="Success"||e==="Failed"||e==="Replaced";function Y(e){let n=new Set,r=x=>console.error("[Pulsar] A transaction finished listener failed:",x);return {notify:x=>{for(let a of e?[e,...n]:[...n])try{let p=a(x);p&&typeof p.then=="function"&&p.then(void 0,r);}catch(p){r(p);}},subscribe:x=>(n.add(x),()=>{n.delete(x);})}}function Te(e){let n={...e};return delete n.pimlicoApiKey,delete n.gelatoApiKey,n}function W({maxTransactions:e,retentionDays:n=R,onRemoteCreate:r,onTxFinished:d}){let l=false,x=new Set;return (a,p)=>{let f=async(t,s)=>{let i=p().transactionsPool[t];if(!(!r||!i||x.has(t))){x.add(t);try{await r(Te(i)),a(o=>immer.produce(o,u=>{let c=u.transactionsPool[t];c&&(c.syncStatus="synced"),u.unsyncedTxKeys&&delete u.unsyncedTxKeys[t];}));}catch(o){console.warn(s,o);}finally{x.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!r||l)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){l=true;try{for(let s of t){if(!p().transactionsPool[s]){a(i=>immer.produce(i,o=>{o.unsyncedTxKeys&&delete o.unsyncedTxKeys[s];}));continue}await f(s,`[Pulsar] Failed to reconcile tx ${s}:`);}}finally{l=false;}}},addTxToPool:t=>{I(t);let s={...t,pending:true,...r&&{syncStatus:"pending-sync"}};return a(i=>immer.produce(i,o=>{if(o.lastAddedTxKey=t.txKey,t.txKey){r&&(o.unsyncedTxKeys||(o.unsyncedTxKeys={}),o.unsyncedTxKeys[t.txKey]=true);for(let T of O(i.transactionsPool,n))delete o.transactionsPool[T],o.unsyncedTxKeys&&delete o.unsyncedTxKeys[T];let u=Object.values(o.transactionsPool).filter(T=>T.txKey!==t.txKey&&(!T.pending||k(T.status))).sort((T,P)=>T.localTimestamp-P.localTimestamp),c=Object.keys(o.transactionsPool).length+(o.transactionsPool[t.txKey]?0:1);for(let T of u){if(c<=e)break;delete o.transactionsPool[T.txKey],c--;}o.transactionsPool[t.txKey]=s;}})),r&&t.txKey&&f(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,s)=>{let i=k(p().transactionsPool[t]?.status);a(u=>immer.produce(u,c=>{let T=c.transactionsPool[t];T&&Object.assign(T,s);}));let o=p().transactionsPool[t];d&&o&&!i&&k(o.status)&&d(o),k(s.status)&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(u=>{console.error("[Pulsar] Terminal reconciliation failed:",u);});},removeTxFromPool:t=>{a(s=>immer.produce(s,i=>{delete i.transactionsPool[t];}));},closeTxTrackedModal:t=>{a(s=>immer.produce(s,i=>{if(t&&i.transactionsPool[t]){let o=i.transactionsPool[t];i.transactionsPool[t]={...o,isTrackedModalOpen:false};}i.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var J=e=>Object.values(e).sort((n,r)=>Number(n.localTimestamp)-Number(r.localTimestamp)),ze=e=>J(e).filter(n=>n.pending),Ue=(e,n)=>e[n],de=(e,n)=>J(e).filter(r=>r.from.toLowerCase()===n.toLowerCase()),Ce=(e,n)=>de(e,n).filter(r=>r.pending);var{produce:Q}=new immer.Immer({autoFreeze:false}),xe=e=>e.filter(n=>{try{return I(n),!0}catch(r){return console.warn("[Pulsar] Skipped invalid transaction from history:",r),false}}),Z=e=>e==="Success"||e==="Failed"||e==="Replaced",ee=(e,n)=>{let r=e[n.txKey];if(r){if(Z(r.status))return false;if(r.pending){if(Z(n.status))return e[n.txKey]={...r,...n},true;let d=typeof n.confirmations=="number"?n.confirmations:0,l=typeof r.confirmations=="number"?r.confirmations:0;return d>l?(e[n.txKey]={...r,...n},true):false}}return e[n.txKey]=n,true};function Ve({localTransactionsPool:e,reconcileUnsyncedTransactions:n,getHistory:r,onHistoryFetched:d}){let l=a=>({isLoading:a,isError:false}),x=a=>{if(!a)return {isLoading:false};let p=xe(a.docs);return d&&queueMicrotask(()=>d(p)),f=>Q(f,t=>{let s=t.transactionsPool;for(let i of p)ee(s,i);t.currentPage=a.page,t.hasMore=a.hasNextPage,t.isLoading=false;})};return vanilla.createStore()((a,p)=>({transactionsPool:e,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:f=>{a(t=>Q(t,s=>{let i=s.transactionsPool;for(let o of Object.values(f))ee(i,o);}));},fetchInitial:async f=>{if(!(!r||!f)){a(l(true)),n&&await n();try{let t=await r({page:1,walletAddress:f});a(x(t));}catch(t){console.error("[Pulsar] Failed to fetch initial transaction history:",t),a({isLoading:false,isError:true});}}},fetchNextPage:async f=>{let{hasMore:t,isLoading:s,currentPage:i}=p();if(!(!r||!t||s||!f)){a(l(true));try{let o=i+1,u=await r({page:o,walletAddress:f});a(x(u));}catch(o){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,o),a({isLoading:false,isError:true});}}}}))}function an({adapter:e,maxTransactions:n=50,retentionDays:r=R,onRemoteCreate:d,gelatoApiKey:l,beforeTxProcess:x,abortOnTxError:a,onTxFinished:p,...f}){let t=Y(p);return vanilla.createStore()(middleware.persist((s,i)=>({...W({maxTransactions:n,retentionDays:r,onRemoteCreate:d,onTxFinished:t.notify})(s,i),getAdapter:()=>e,subscribeTxFinished:t.subscribe,initializeTransactionsPool:async()=>{let u=Object.values(i().transactionsPool).filter(c=>c.pending).filter(c=>{try{return I(c),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),i().removeTxFromPool(c.txKey),false}});await Promise.all(u.map(c=>orbitCore.selectAdapterByKey({adapterKey:c.adapter,adapter:e})?.checkAndInitializeTrackerInStore({tx:c,gelatoApiKey:l,...i()})));},injectExternalPendingTxs:async o=>{let c=i().getAdapter(),T=[],P=[],S=o.filter(h=>{try{return I(h),!0}catch(g){return console.warn("[Pulsar] Skipped invalid remote transaction:",g),false}});s(h=>immer.produce(h,g=>{let w=g.transactionsPool;S.forEach(m=>{let b=w[m.txKey];m.pending&&!b&&(w[m.txKey]=m,T.push(m)),b?.pending&&k(m.status)&&(k(b.status)||P.push(m.txKey),b.status=m.status,b.pending=false,m.txKey&&(b.txKey=m.txKey),m.finishedTimestamp&&(b.finishedTimestamp=m.finishedTimestamp));});}));for(let h of P){let g=i().transactionsPool[h];g&&t.notify(g);}T.length>0&&await Promise.all(T.map(h=>orbitCore.selectAdapterByKey({adapterKey:h.adapter,adapter:c})?.checkAndInitializeTrackerInStore({tx:h,gelatoApiKey:l,...i()})));},executeTxAction:async({defaultTracker:o,actionFunction:u,params:c,beforeTxProcess:T,abortOnTxError:P,...S})=>{let h=P??a??true,g=se__default.default().unix();i().reconcileUnsyncedTransactions().catch(y=>console.error("[Pulsar] Reconciliation failed:",y)),H(c);let{desiredChainID:w,tracker:m,...b}=c,{onSuccess:te,onError:re,onReplaced:oe}=S;s({initialTx:{...c,actionFunction:u,localTimestamp:g,isInitializing:true}});let E=orbitCore.selectAdapterByKey({adapterKey:b.adapter,adapter:e}),$=y=>{s(N=>immer.produce(N,v=>{v.initialTx&&(v.initialTx.isInitializing=false,v.initialTx.error=orbitCore.normalizeError(y));}));};if(!E){let y=new Error("No adapter found for this transaction.");throw $(y),y}try{let{connectorType:y,walletAddress:N}=E.getConnectorInfo();await E.checkChainForTx(w);try{await(T??x)?.();}catch(F){if(h)throw s({initialTx:{...c,actionFunction:u,localTimestamp:g,isInitializing:!1,error:orbitCore.normalizeError(F)}}),F;console.warn("[Pulsar] beforeTxProcess failed:",F);}let v=await u();if(!v){s({initialTx:void 0});return}let{tracker:j,txKey:z}=E.checkTransactionsTracker({actionTxKey:v,connectorType:y,tracker:m,gelatoApiKey:c.gelatoApiKey??l,bundlerUrl:c.bundlerUrl,pimlicoApiKey:c.pimlicoApiKey}),ie={...b,connectorType:y,from:N,tracker:j||o,chainId:orbitCore.setChainId(w),localTimestamp:g,txKey:z,bundlerUrl:c.bundlerUrl,pimlicoApiKey:c.pimlicoApiKey,hash:j==="ethereum"?v:void 0,pending:!1,isTrackedModalOpen:c.withTrackedModal};await i().addTxToPool(ie),s(F=>immer.produce(F,U=>{U.initialTx&&(U.initialTx.isInitializing=!1,U.initialTx.lastTxKey=z);}));let ae=i().transactionsPool[z];await E.checkAndInitializeTrackerInStore({tx:ae,onSuccess:te,onError:re,onReplaced:oe,gelatoApiKey:l,...i()});}catch(y){throw $(y),y}}}),{partialize:s=>{let i={...s};return delete i.initialTx,i},...f,merge:(s,i)=>{let o=f.merge?f.merge(s,i):{...i,...s,initialTx:i.initialTx},u=O(o.transactionsPool??{},r);if(u.length===0)return o;let c={...o.transactionsPool},T=o.unsyncedTxKeys?{...o.unsyncedTxKeys}:o.unsyncedTxKeys;for(let P of u)delete c[P],T&&delete T[P];return {...o,transactionsPool:c,unsyncedTxKeys:T}}}))}var Tn=(e=>n=>zustand.useStore(e,n));function pn({tx:e,transactionsPool:n,updateTxParams:r}){let d=n[e.txKey];return l=>(r(e.txKey,l),d&&(d={...d,...l}),d)}var he=5e3,be=10;function xn(e){let{tx:n,fetcher:r,onInitialize:d,onSuccess:l,onFailure:x,onIntervalTick:a,onReplaced:p,removeTxFromPool:f,pollingInterval:t=he,maxRetries:s=be}=e;if(!n.pending)return;d?.();let i=s,o=true,u=Date.now(),c=()=>typeof t=="function"?t(Date.now()-u):t,T=S=>{o&&(o=false,f&&!S?.withoutRemoving&&f(n.txKey));};(async()=>{for(;o&&i>0;)try{if(await new Promise(S=>setTimeout(S,c())),!o)break;await r({tx:n,stopPolling:T,onSuccess:l,onFailure:x,onIntervalTick:a,onReplaced:p}),i=s;}catch(S){console.error(`Polling fetcher for txKey ${n.txKey} threw an error:`,S),i--;}i<=0&&(console.warn(`Polling for txKey ${n.txKey} stopped after reaching the maximum number of retries.`),x(),T());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=q;exports.MAX_TRANSACTION_PAYLOAD_BYTES=D;exports.MAX_TRANSACTION_TITLE_LENGTH=G;exports.PulsarTransactionValidationError=A;exports.TransactionStatus=M;exports.TransactionTracker=le;exports.createBoundedUseStore=Tn;exports.createPulsarStore=an;exports.createTxInMemoryStore=Ve;exports.createTxUpdater=pn;exports.initializePollingTracker=xn;exports.initializeTxTrackingStore=W;exports.selectAllTransactions=J;exports.selectAllTransactionsByActiveWallet=de;exports.selectPendingTransactions=ze;exports.selectPendingTransactionsByActiveWallet=Ce;exports.selectTxByKey=Ue;exports.validateInitialTransactionParams=H;exports.validateTransaction=I;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {Immer,produce}from'immer';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,normalizeError,setChainId}from'@tuwaio/orbit-core';import ce from'dayjs';import {persist}from'zustand/middleware';import {useStore}from'zustand';var ee=(s=>(s.Ethereum="ethereum",s.Safe="safe",s.Gelato="gelato",s.Solana="solana",s.ERC4337="erc4337",s.EIP5792="eip5792",s))(ee||{}),K=(T=>(T.Failed="Failed",T.Success="Success",T.Replaced="Replaced",T))(K||{});var U=100,B=300,N=10*1024,ne=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,o){super(o),this.name="PulsarTransactionValidationError",this.field=e;}};function $(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function S(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function w({field:n,value:e,maxLength:o}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((d,p)=>{let s=Array.isArray(e)?`${n}[${p}]`:n;if(typeof d!="string")throw new P(s,`${s} must be a string.`);if(d.length>o)throw new P(s,`${s} must be ${o} characters or less.`);O(s,d);});}function j(n){if(n===void 0)return;let e;try{e=JSON.stringify(n);}catch{throw new P("payload","payload must be JSON-serializable.")}if(e===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(e).length>N)throw new P("payload",`payload must be ${N} bytes or less when serialized.`);_(n);}function _(n,e="payload"){if(typeof n=="string"){O(e,n);return}n===null||typeof n!="object"||Object.entries(n).forEach(([o,T])=>{let d=`${e}.${o}`;O(d,o),_(T,d);});}function O(n,e){if(ne.some(o=>o.test(e)))throw new P(n,`${n} contains a blocked executable-like pattern.`)}function te(n){let e={...n};return delete e.pimlicoApiKey,delete e.gelatoApiKey,e}function G({maxTransactions:n,onRemoteCreate:e}){let o=false,T=new Set;return (d,p)=>{let s=async(t,r)=>{let c=p().transactionsPool[t];if(!(!e||!c||T.has(t))){T.add(t);try{await e(te(c)),d(a=>produce(a,i=>{let l=i.transactionsPool[t];l&&(l.syncStatus="synced"),i.unsyncedTxKeys&&delete i.unsyncedTxKeys[t];}));}catch(a){console.warn(r,a);}finally{T.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!e||o)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){o=true;try{for(let r of t){if(!p().transactionsPool[r]){d(c=>produce(c,a=>{a.unsyncedTxKeys&&delete a.unsyncedTxKeys[r];}));continue}await s(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{o=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...e&&{syncStatus:"pending-sync"}};return d(c=>produce(c,a=>{if(a.lastAddedTxKey=t.txKey,t.txKey){if(e&&(a.unsyncedTxKeys||(a.unsyncedTxKeys={}),a.unsyncedTxKeys[t.txKey]=true),Object.keys(a.transactionsPool).length>=n){let l=Object.values(a.transactionsPool).sort((x,y)=>x.localTimestamp-y.localTimestamp);if(l.length>0){let x=l[0];delete a.transactionsPool[x.txKey];}}a.transactionsPool[t.txKey]=r;}})),e&&t.txKey&&s(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{d(a=>produce(a,i=>{let l=i.transactionsPool[t];l&&Object.assign(l,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(a=>{console.error("[Pulsar] Terminal reconciliation failed:",a);});},removeTxFromPool:t=>{d(r=>produce(r,c=>{delete c.transactionsPool[t];}));},closeTxTrackedModal:t=>{d(r=>produce(r,c=>{if(t&&c.transactionsPool[t]){let a=c.transactionsPool[t];c.transactionsPool[t]={...a,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var D=n=>Object.values(n).sort((e,o)=>Number(e.localTimestamp)-Number(o.localTimestamp)),Ae=n=>D(n).filter(e=>e.pending),ve=(n,e)=>n[e],re=(n,e)=>D(n).filter(o=>o.from.toLowerCase()===e.toLowerCase()),Ie=(n,e)=>re(n,e).filter(o=>o.pending);var{produce:q}=new Immer({autoFreeze:false}),ae=n=>n.filter(e=>{try{return S(e),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),H=n=>n==="Success"||n==="Failed"||n==="Replaced",V=(n,e)=>{let o=n[e.txKey];if(o){if(H(o.status))return false;if(o.pending){if(H(e.status))return n[e.txKey]={...o,...e},true;let T=typeof e.confirmations=="number"?e.confirmations:0,d=typeof o.confirmations=="number"?o.confirmations:0;return T>d?(n[e.txKey]={...o,...e},true):false}}return n[e.txKey]=e,true};function Me({localTransactionsPool:n,reconcileUnsyncedTransactions:e,getHistory:o,onHistoryFetched:T}){let d=s=>({isLoading:s,isError:false}),p=s=>{if(!s)return {isLoading:false};let t=ae(s.docs);return T&&queueMicrotask(()=>T(t)),r=>q(r,c=>{let a=c.transactionsPool;for(let i of t)V(a,i);c.currentPage=s.page,c.hasMore=s.hasNextPage,c.isLoading=false;})};return createStore()((s,t)=>({transactionsPool:n,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{s(c=>q(c,a=>{let i=a.transactionsPool;for(let l of Object.values(r))V(i,l);}));},fetchInitial:async r=>{if(!(!o||!r)){s(d(true)),e&&await e();try{let c=await o({page:1,walletAddress:r});s(p(c));}catch(c){console.error("[Pulsar] Failed to fetch initial transaction history:",c),s({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:c,isLoading:a,currentPage:i}=t();if(!(!o||!c||a||!r)){s(d(true));try{let l=i+1,x=await o({page:l,walletAddress:r});s(p(x));}catch(l){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,l),s({isLoading:false,isError:true});}}}}))}function Ye({adapter:n,maxTransactions:e=50,onRemoteCreate:o,gelatoApiKey:T,beforeTxProcess:d,abortOnTxError:p,...s}){return createStore()(persist((t,r)=>({...G({maxTransactions:e,onRemoteCreate:o})(t,r),getAdapter:()=>n,initializeTransactionsPool:async()=>{let a=Object.values(r().transactionsPool).filter(i=>i.pending).filter(i=>{try{return S(i),!0}catch(l){return console.warn("[Pulsar] Removed invalid persisted transaction:",l),r().removeTxFromPool(i.txKey),false}});await Promise.all(a.map(i=>selectAdapterByKey({adapterKey:i.adapter,adapter:n})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:T,...r()})));},injectExternalPendingTxs:async c=>{let i=r().getAdapter(),l=[],x=c.filter(y=>{try{return S(y),!0}catch(m){return console.warn("[Pulsar] Skipped invalid remote transaction:",m),false}});t(y=>produce(y,m=>{let b=m.transactionsPool;x.forEach(u=>{let g=b[u.txKey];u.pending&&!g&&(b[u.txKey]=u,l.push(u));let I=u.status==="Success"||u.status==="Failed"||u.status==="Replaced";g?.pending&&I&&(g.status=u.status,g.pending=false,u.txKey&&(g.txKey=u.txKey),u.finishedTimestamp&&(g.finishedTimestamp=u.finishedTimestamp));});})),l.length>0&&await Promise.all(l.map(y=>selectAdapterByKey({adapterKey:y.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:y,gelatoApiKey:T,...r()})));},executeTxAction:async({defaultTracker:c,actionFunction:a,params:i,beforeTxProcess:l,abortOnTxError:x,...y})=>{let m=x??p??true,b=ce().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),$(i);let{desiredChainID:u,tracker:g,...I}=i,{onSuccess:W,onError:J,onReplaced:Y}=y;t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:true}});let A=selectAdapterByKey({adapterKey:I.adapter,adapter:n}),z=f=>{t(E=>produce(E,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=normalizeError(f));}));};if(!A){let f=new Error("No adapter found for this transaction.");throw z(f),f}try{let{connectorType:f,walletAddress:E}=A.getConnectorInfo();await A.checkChainForTx(u);try{await(l??d)?.();}catch(v){if(m)throw t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:!1,error:normalizeError(v)}}),v;console.warn("[Pulsar] beforeTxProcess failed:",v);}let h=await a();if(!h){t({initialTx:void 0});return}let{tracker:M,txKey:R}=A.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:i.gelatoApiKey??T,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey}),Q={...I,connectorType:f,from:E,tracker:M||c,chainId:setChainId(u),localTimestamp:b,txKey:R,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await r().addTxToPool(Q),t(v=>produce(v,F=>{F.initialTx&&(F.initialTx.isInitializing=!1,F.initialTx.lastTxKey=R);}));let Z=r().transactionsPool[R];await A.checkAndInitializeTrackerInStore({tx:Z,onSuccess:W,onError:J,onReplaced:Y,gelatoApiKey:T,...r()});}catch(f){throw z(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},merge:(t,r)=>({...r,...t,initialTx:r.initialTx}),...s}))}var nn=(n=>e=>useStore(n,e));function rn({tx:n,transactionsPool:e,updateTxParams:o}){let T=e[n.txKey];return d=>(o(n.txKey,d),T&&(T={...T,...d}),T)}var pe=5e3,ue=10;function an(n){let{tx:e,fetcher:o,onInitialize:T,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t,removeTxFromPool:r,pollingInterval:c=pe,maxRetries:a=ue}=n;if(!e.pending)return;T?.();let i=a,l=true,x=m=>{l&&(l=false,r&&!m?.withoutRemoving&&r(e.txKey));};(async()=>{for(;l&&i>0;)try{if(await new Promise(m=>setTimeout(m,c)),!l)break;await o({tx:e,stopPolling:x,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t}),i=a;}catch(m){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,m),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),p(),x());})();}export{B as MAX_TRANSACTION_DESCRIPTION_LENGTH,N as MAX_TRANSACTION_PAYLOAD_BYTES,U as MAX_TRANSACTION_TITLE_LENGTH,P as PulsarTransactionValidationError,K as TransactionStatus,ee as TransactionTracker,nn as createBoundedUseStore,Ye as createPulsarStore,Me as createTxInMemoryStore,rn as createTxUpdater,an as initializePollingTracker,G as initializeTxTrackingStore,D as selectAllTransactions,re as selectAllTransactionsByActiveWallet,Ae as selectPendingTransactions,Ie as selectPendingTransactionsByActiveWallet,ve as selectTxByKey,$ as validateInitialTransactionParams,S as validateTransaction};
|
|
1
|
+
import {Immer,produce}from'immer';import se from'dayjs';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,normalizeError,setChainId}from'@tuwaio/orbit-core';import {persist}from'zustand/middleware';import {useStore}from'zustand';var R=30;function O(e,n,r=se().unix()){if(n===null)return [];let d=r-n*24*60*60;return Object.values(e).filter(l=>!l.pending&&(l.finishedTimestamp??l.localTimestamp)<d).map(l=>l.txKey)}var G=100,q=300,D=10*1024,ce=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],A=class extends Error{field;constructor(n,r){super(r),this.name="PulsarTransactionValidationError",this.field=n;}};function H(e){L({field:"title",value:e.title,maxLength:G}),L({field:"description",value:e.description,maxLength:q}),V(e.payload);}function I(e){L({field:"title",value:e.title,maxLength:G}),L({field:"description",value:e.description,maxLength:q}),V(e.payload);}function L({field:e,value:n,maxLength:r}){if(n===void 0)return;(Array.isArray(n)?n:[n]).forEach((l,x)=>{let a=Array.isArray(n)?`${e}[${x}]`:e;if(typeof l!="string")throw new A(a,`${a} must be a string.`);if(l.length>r)throw new A(a,`${a} must be ${r} characters or less.`);C(a,l);});}function V(e){if(e===void 0)return;let n;try{n=JSON.stringify(e);}catch{throw new A("payload","payload must be JSON-serializable.")}if(n===void 0)throw new A("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(n).length>D)throw new A("payload",`payload must be ${D} bytes or less when serialized.`);X(e);}function X(e,n="payload"){if(typeof e=="string"){C(n,e);return}e===null||typeof e!="object"||Object.entries(e).forEach(([r,d])=>{let l=`${n}.${r}`;C(l,r),X(d,l);});}function C(e,n){if(ce.some(r=>r.test(n)))throw new A(e,`${e} contains a blocked executable-like pattern.`)}var le=(a=>(a.Ethereum="ethereum",a.Safe="safe",a.Gelato="gelato",a.Solana="solana",a.ERC4337="erc4337",a.EIP5792="eip5792",a))(le||{}),M=(d=>(d.Failed="Failed",d.Success="Success",d.Replaced="Replaced",d))(M||{});var k=e=>e==="Success"||e==="Failed"||e==="Replaced";function Y(e){let n=new Set,r=x=>console.error("[Pulsar] A transaction finished listener failed:",x);return {notify:x=>{for(let a of e?[e,...n]:[...n])try{let p=a(x);p&&typeof p.then=="function"&&p.then(void 0,r);}catch(p){r(p);}},subscribe:x=>(n.add(x),()=>{n.delete(x);})}}function Te(e){let n={...e};return delete n.pimlicoApiKey,delete n.gelatoApiKey,n}function W({maxTransactions:e,retentionDays:n=R,onRemoteCreate:r,onTxFinished:d}){let l=false,x=new Set;return (a,p)=>{let f=async(t,s)=>{let i=p().transactionsPool[t];if(!(!r||!i||x.has(t))){x.add(t);try{await r(Te(i)),a(o=>produce(o,u=>{let c=u.transactionsPool[t];c&&(c.syncStatus="synced"),u.unsyncedTxKeys&&delete u.unsyncedTxKeys[t];}));}catch(o){console.warn(s,o);}finally{x.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!r||l)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){l=true;try{for(let s of t){if(!p().transactionsPool[s]){a(i=>produce(i,o=>{o.unsyncedTxKeys&&delete o.unsyncedTxKeys[s];}));continue}await f(s,`[Pulsar] Failed to reconcile tx ${s}:`);}}finally{l=false;}}},addTxToPool:t=>{I(t);let s={...t,pending:true,...r&&{syncStatus:"pending-sync"}};return a(i=>produce(i,o=>{if(o.lastAddedTxKey=t.txKey,t.txKey){r&&(o.unsyncedTxKeys||(o.unsyncedTxKeys={}),o.unsyncedTxKeys[t.txKey]=true);for(let T of O(i.transactionsPool,n))delete o.transactionsPool[T],o.unsyncedTxKeys&&delete o.unsyncedTxKeys[T];let u=Object.values(o.transactionsPool).filter(T=>T.txKey!==t.txKey&&(!T.pending||k(T.status))).sort((T,P)=>T.localTimestamp-P.localTimestamp),c=Object.keys(o.transactionsPool).length+(o.transactionsPool[t.txKey]?0:1);for(let T of u){if(c<=e)break;delete o.transactionsPool[T.txKey],c--;}o.transactionsPool[t.txKey]=s;}})),r&&t.txKey&&f(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,s)=>{let i=k(p().transactionsPool[t]?.status);a(u=>produce(u,c=>{let T=c.transactionsPool[t];T&&Object.assign(T,s);}));let o=p().transactionsPool[t];d&&o&&!i&&k(o.status)&&d(o),k(s.status)&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(u=>{console.error("[Pulsar] Terminal reconciliation failed:",u);});},removeTxFromPool:t=>{a(s=>produce(s,i=>{delete i.transactionsPool[t];}));},closeTxTrackedModal:t=>{a(s=>produce(s,i=>{if(t&&i.transactionsPool[t]){let o=i.transactionsPool[t];i.transactionsPool[t]={...o,isTrackedModalOpen:false};}i.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var J=e=>Object.values(e).sort((n,r)=>Number(n.localTimestamp)-Number(r.localTimestamp)),ze=e=>J(e).filter(n=>n.pending),Ue=(e,n)=>e[n],de=(e,n)=>J(e).filter(r=>r.from.toLowerCase()===n.toLowerCase()),Ce=(e,n)=>de(e,n).filter(r=>r.pending);var{produce:Q}=new Immer({autoFreeze:false}),xe=e=>e.filter(n=>{try{return I(n),!0}catch(r){return console.warn("[Pulsar] Skipped invalid transaction from history:",r),false}}),Z=e=>e==="Success"||e==="Failed"||e==="Replaced",ee=(e,n)=>{let r=e[n.txKey];if(r){if(Z(r.status))return false;if(r.pending){if(Z(n.status))return e[n.txKey]={...r,...n},true;let d=typeof n.confirmations=="number"?n.confirmations:0,l=typeof r.confirmations=="number"?r.confirmations:0;return d>l?(e[n.txKey]={...r,...n},true):false}}return e[n.txKey]=n,true};function Ve({localTransactionsPool:e,reconcileUnsyncedTransactions:n,getHistory:r,onHistoryFetched:d}){let l=a=>({isLoading:a,isError:false}),x=a=>{if(!a)return {isLoading:false};let p=xe(a.docs);return d&&queueMicrotask(()=>d(p)),f=>Q(f,t=>{let s=t.transactionsPool;for(let i of p)ee(s,i);t.currentPage=a.page,t.hasMore=a.hasNextPage,t.isLoading=false;})};return createStore()((a,p)=>({transactionsPool:e,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:f=>{a(t=>Q(t,s=>{let i=s.transactionsPool;for(let o of Object.values(f))ee(i,o);}));},fetchInitial:async f=>{if(!(!r||!f)){a(l(true)),n&&await n();try{let t=await r({page:1,walletAddress:f});a(x(t));}catch(t){console.error("[Pulsar] Failed to fetch initial transaction history:",t),a({isLoading:false,isError:true});}}},fetchNextPage:async f=>{let{hasMore:t,isLoading:s,currentPage:i}=p();if(!(!r||!t||s||!f)){a(l(true));try{let o=i+1,u=await r({page:o,walletAddress:f});a(x(u));}catch(o){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,o),a({isLoading:false,isError:true});}}}}))}function an({adapter:e,maxTransactions:n=50,retentionDays:r=R,onRemoteCreate:d,gelatoApiKey:l,beforeTxProcess:x,abortOnTxError:a,onTxFinished:p,...f}){let t=Y(p);return createStore()(persist((s,i)=>({...W({maxTransactions:n,retentionDays:r,onRemoteCreate:d,onTxFinished:t.notify})(s,i),getAdapter:()=>e,subscribeTxFinished:t.subscribe,initializeTransactionsPool:async()=>{let u=Object.values(i().transactionsPool).filter(c=>c.pending).filter(c=>{try{return I(c),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),i().removeTxFromPool(c.txKey),false}});await Promise.all(u.map(c=>selectAdapterByKey({adapterKey:c.adapter,adapter:e})?.checkAndInitializeTrackerInStore({tx:c,gelatoApiKey:l,...i()})));},injectExternalPendingTxs:async o=>{let c=i().getAdapter(),T=[],P=[],S=o.filter(h=>{try{return I(h),!0}catch(g){return console.warn("[Pulsar] Skipped invalid remote transaction:",g),false}});s(h=>produce(h,g=>{let w=g.transactionsPool;S.forEach(m=>{let b=w[m.txKey];m.pending&&!b&&(w[m.txKey]=m,T.push(m)),b?.pending&&k(m.status)&&(k(b.status)||P.push(m.txKey),b.status=m.status,b.pending=false,m.txKey&&(b.txKey=m.txKey),m.finishedTimestamp&&(b.finishedTimestamp=m.finishedTimestamp));});}));for(let h of P){let g=i().transactionsPool[h];g&&t.notify(g);}T.length>0&&await Promise.all(T.map(h=>selectAdapterByKey({adapterKey:h.adapter,adapter:c})?.checkAndInitializeTrackerInStore({tx:h,gelatoApiKey:l,...i()})));},executeTxAction:async({defaultTracker:o,actionFunction:u,params:c,beforeTxProcess:T,abortOnTxError:P,...S})=>{let h=P??a??true,g=se().unix();i().reconcileUnsyncedTransactions().catch(y=>console.error("[Pulsar] Reconciliation failed:",y)),H(c);let{desiredChainID:w,tracker:m,...b}=c,{onSuccess:te,onError:re,onReplaced:oe}=S;s({initialTx:{...c,actionFunction:u,localTimestamp:g,isInitializing:true}});let E=selectAdapterByKey({adapterKey:b.adapter,adapter:e}),$=y=>{s(N=>produce(N,v=>{v.initialTx&&(v.initialTx.isInitializing=false,v.initialTx.error=normalizeError(y));}));};if(!E){let y=new Error("No adapter found for this transaction.");throw $(y),y}try{let{connectorType:y,walletAddress:N}=E.getConnectorInfo();await E.checkChainForTx(w);try{await(T??x)?.();}catch(F){if(h)throw s({initialTx:{...c,actionFunction:u,localTimestamp:g,isInitializing:!1,error:normalizeError(F)}}),F;console.warn("[Pulsar] beforeTxProcess failed:",F);}let v=await u();if(!v){s({initialTx:void 0});return}let{tracker:j,txKey:z}=E.checkTransactionsTracker({actionTxKey:v,connectorType:y,tracker:m,gelatoApiKey:c.gelatoApiKey??l,bundlerUrl:c.bundlerUrl,pimlicoApiKey:c.pimlicoApiKey}),ie={...b,connectorType:y,from:N,tracker:j||o,chainId:setChainId(w),localTimestamp:g,txKey:z,bundlerUrl:c.bundlerUrl,pimlicoApiKey:c.pimlicoApiKey,hash:j==="ethereum"?v:void 0,pending:!1,isTrackedModalOpen:c.withTrackedModal};await i().addTxToPool(ie),s(F=>produce(F,U=>{U.initialTx&&(U.initialTx.isInitializing=!1,U.initialTx.lastTxKey=z);}));let ae=i().transactionsPool[z];await E.checkAndInitializeTrackerInStore({tx:ae,onSuccess:te,onError:re,onReplaced:oe,gelatoApiKey:l,...i()});}catch(y){throw $(y),y}}}),{partialize:s=>{let i={...s};return delete i.initialTx,i},...f,merge:(s,i)=>{let o=f.merge?f.merge(s,i):{...i,...s,initialTx:i.initialTx},u=O(o.transactionsPool??{},r);if(u.length===0)return o;let c={...o.transactionsPool},T=o.unsyncedTxKeys?{...o.unsyncedTxKeys}:o.unsyncedTxKeys;for(let P of u)delete c[P],T&&delete T[P];return {...o,transactionsPool:c,unsyncedTxKeys:T}}}))}var Tn=(e=>n=>useStore(e,n));function pn({tx:e,transactionsPool:n,updateTxParams:r}){let d=n[e.txKey];return l=>(r(e.txKey,l),d&&(d={...d,...l}),d)}var he=5e3,be=10;function xn(e){let{tx:n,fetcher:r,onInitialize:d,onSuccess:l,onFailure:x,onIntervalTick:a,onReplaced:p,removeTxFromPool:f,pollingInterval:t=he,maxRetries:s=be}=e;if(!n.pending)return;d?.();let i=s,o=true,u=Date.now(),c=()=>typeof t=="function"?t(Date.now()-u):t,T=S=>{o&&(o=false,f&&!S?.withoutRemoving&&f(n.txKey));};(async()=>{for(;o&&i>0;)try{if(await new Promise(S=>setTimeout(S,c())),!o)break;await r({tx:n,stopPolling:T,onSuccess:l,onFailure:x,onIntervalTick:a,onReplaced:p}),i=s;}catch(S){console.error(`Polling fetcher for txKey ${n.txKey} threw an error:`,S),i--;}i<=0&&(console.warn(`Polling for txKey ${n.txKey} stopped after reaching the maximum number of retries.`),x(),T());})();}export{q as MAX_TRANSACTION_DESCRIPTION_LENGTH,D as MAX_TRANSACTION_PAYLOAD_BYTES,G as MAX_TRANSACTION_TITLE_LENGTH,A as PulsarTransactionValidationError,M as TransactionStatus,le as TransactionTracker,Tn as createBoundedUseStore,an as createPulsarStore,Ve as createTxInMemoryStore,pn as createTxUpdater,xn as initializePollingTracker,W as initializeTxTrackingStore,J as selectAllTransactions,de as selectAllTransactionsByActiveWallet,ze as selectPendingTransactions,Ce as selectPendingTransactionsByActiveWallet,Ue as selectTxByKey,H as validateInitialTransactionParams,I as validateTransaction};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuwaio/pulsar-core",
|
|
3
|
-
"version": "0.
|
|
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",
|