@tuwaio/pulsar-core 0.5.0 → 0.6.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/dist/index.d.mts CHANGED
@@ -34,13 +34,6 @@ declare enum TransactionStatus {
34
34
  /** The transaction was replaced by another with the same nonce (e.g., a speed-up or cancel). */
35
35
  Replaced = "Replaced"
36
36
  }
37
- /**
38
- * Defines the shape of the identifier for a Gelato transaction task.
39
- */
40
- type GelatoTxKey = {
41
- /** The unique identifier assigned by the Gelato relay service. */
42
- taskId: string;
43
- };
44
37
  /**
45
38
  * A union type representing the unique identifier returned by an `actionFunction`
46
39
  * after a transaction is submitted to the network or a relay service.
@@ -50,10 +43,9 @@ type GelatoTxKey = {
50
43
  *
51
44
  * It can be one of the following:
52
45
  * - A standard `0x...` transaction hash (`Hex`).
53
- * - A structured object from a relay service like Gelato (`GelatoTxKey`).
54
46
  * - A Solana transaction signature (string).
55
47
  */
56
- type ActionTxKey = `0x${string}` | GelatoTxKey | string;
48
+ type ActionTxKey = `0x${string}` | string;
57
49
  /**
58
50
  * The fundamental structure for any transaction being tracked by Pulsar.
59
51
  * This serves as the base upon which chain-specific transaction types are built.
@@ -82,8 +74,8 @@ type BaseTransaction = {
82
74
  isTrackedModalOpen?: boolean;
83
75
  /** The local timestamp (in seconds) when the transaction was initiated by the user. */
84
76
  localTimestamp: number;
85
- /** Any additional, custom data associated with the transaction. */
86
- payload?: object;
77
+ /** Custom data (strings or numbers) to associate with the transaction. */
78
+ payload?: Record<string, string | number>;
87
79
  /** A flag indicating if the transaction is still awaiting on-chain confirmation. */
88
80
  pending: boolean;
89
81
  /** The final on-chain status of the transaction. */
@@ -105,6 +97,12 @@ type BaseTransaction = {
105
97
  type: string;
106
98
  /** The type of connector used to sign the transaction (e.g., 'injected', 'walletConnect'). */
107
99
  connectorType: string;
100
+ /** The number of confirmations required for the transaction to be considered confirmed. */
101
+ requiredConfirmations?: number;
102
+ /** The number of confirmations received. A string value indicates a confirmed transaction, while `null` means it's pending. */
103
+ confirmations?: number | string | null;
104
+ /** The RPC URL to use for the transaction. Required for Solana transactions. */
105
+ rpcUrl?: string;
108
106
  };
109
107
  /**
110
108
  * Represents an EVM-specific transaction, extending the base properties with EVM fields.
@@ -143,10 +141,6 @@ type SolanaTransaction = BaseTransaction & {
143
141
  recentBlockhash?: string;
144
142
  /** The slot in which the transaction was processed. */
145
143
  slot?: number;
146
- /** The number of confirmations received. A string value indicates a confirmed transaction, while `null` means it's pending. */
147
- confirmations?: number | string | null;
148
- /** The RPC URL used to submit and track this transaction. */
149
- rpcUrl?: string;
150
144
  };
151
145
  /**
152
146
  * Represents a Starknet-specific transaction, extending the base properties.
@@ -167,25 +161,17 @@ type Transaction = EvmTransaction | SolanaTransaction | StarknetTransaction;
167
161
  /**
168
162
  * Represents the parameters required to initiate a new transaction tracking flow.
169
163
  */
170
- type InitialTransactionParams = {
164
+ type InitialTransactionParams = Pick<BaseTransaction, 'description' | 'title' | 'type' | 'requiredConfirmations' | 'rpcUrl' | 'payload'> & {
171
165
  /** The specific blockchain adapter for this transaction. */
172
166
  adapter: OrbitAdapter;
173
167
  /** The function that executes the on-chain action (e.g., sending a transaction) and returns a preliminary identifier like a hash. */
174
168
  actionFunction: (...args: any[]) => Promise<ActionTxKey | undefined>;
175
- /** A user-facing description for the transaction. Supports state-specific descriptions. */
176
- description?: string | [string, string, string, string];
177
169
  /** The target chain ID for the transaction. */
178
170
  desiredChainID: number | string;
179
- /** Any custom data to associate with the transaction. */
180
- payload?: object;
181
- /** A user-facing title for the transaction. Supports state-specific titles. */
182
- title?: string | [string, string, string, string];
183
- /** The application-specific type of the transaction. */
184
- type: string;
185
171
  /** If true, the detailed tracking modal will open automatically upon initiation. */
186
172
  withTrackedModal?: boolean;
187
- /** The RPC URL to use for the transaction. Required for Solana transactions. */
188
- rpcUrl?: string;
173
+ /** The specific tracker responsible for monitoring this transaction's status. Required for Gelato tracker. */
174
+ tracker?: TransactionTracker;
189
175
  };
190
176
  /**
191
177
  * Represents a transaction in its temporary, pre-submission state.
@@ -211,20 +197,38 @@ interface TrackerCallbacks<T extends Transaction> {
211
197
  onReplaced?: (newTx: T, oldTx: T) => Promise<void> | void;
212
198
  }
213
199
  /**
214
- * @deprecated Use `TrackerCallbacks<T>` instead.
215
- * Defines a callback function to be executed upon a successful transaction.
216
- * @template T The specific transaction type, extending `Transaction`.
200
+ * Callbacks for synchronizing local transaction state with a remote backend.
201
+ * These are injected into the store at creation time.
217
202
  */
218
- type OnSuccessCallback<T extends Transaction> = {
219
- /** Callback to execute when the transaction is successfully submitted. */
220
- onSuccessCallback?: (tx: T) => Promise<void> | void;
221
- };
203
+ interface SyncCallbacks<T extends Transaction> {
204
+ /**
205
+ * Called immediately after a transaction is created locally (added to pool).
206
+ * Use this to POST the active pending transaction to the backend.
207
+ */
208
+ onRemoteCreate?: (tx: T) => Promise<void>;
209
+ }
222
210
  /**
223
211
  * The configuration object containing one or more transaction adapters.
224
212
  * @template T The specific transaction type.
225
213
  */
226
214
  type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> & {
227
215
  maxTransactions?: number;
216
+ gelatoApiKey?: string;
217
+ } & SyncCallbacks<T>;
218
+ /**
219
+ * Represents a tracker for a specific transaction tied to an action and a connector.
220
+ *
221
+ * @typedef {Object} CheckTxTracker
222
+ * @property {ActionTxKey} actionTxKey - The key identifying the specific action related to the transaction.
223
+ * @property {string} connectorType - The type of connector used for the transaction (e.g., wallet provider, blockchain interface).
224
+ * @property {TransactionTracker} [tracker] - An optional tracker object that monitors the status and progress of the transaction.
225
+ * @property {string} [gelatoApiKey] - An optional Gelato API key for Gelato relayer integration.
226
+ */
227
+ type CheckTxTracker = {
228
+ actionTxKey: ActionTxKey;
229
+ connectorType: string;
230
+ tracker?: TransactionTracker;
231
+ gelatoApiKey?: string;
228
232
  };
229
233
  /**
230
234
  * Defines the interface for a transaction adapter, which provides chain-specific logic and utilities.
@@ -250,11 +254,9 @@ type TxAdapter<T extends Transaction> = Pick<BaseAdapter, 'getExplorerUrl'> & {
250
254
  checkChainForTx: (chainId: string | number) => Promise<void>;
251
255
  /**
252
256
  * Determines the appropriate tracker and final `txKey` from the result of an action.
253
- * @param actionTxKey The preliminary key returned after an action function is executed.
254
- * @param connectorType The type of connector used for the transaction.
255
257
  * @returns An object containing the final `txKey` and the `TransactionTracker` to be used.
256
258
  */
257
- checkTransactionsTracker: (actionTxKey: ActionTxKey, connectorType: string) => {
259
+ checkTransactionsTracker: ({ actionTxKey, connectorType, tracker }: CheckTxTracker) => {
258
260
  txKey: string;
259
261
  tracker: TransactionTracker;
260
262
  };
@@ -264,6 +266,7 @@ type TxAdapter<T extends Transaction> = Pick<BaseAdapter, 'getExplorerUrl'> & {
264
266
  */
265
267
  checkAndInitializeTrackerInStore: (params: {
266
268
  tx: T;
269
+ gelatoApiKey?: string;
267
270
  } & TrackerCallbacks<T> & Pick<ITxTrackingStore<T>, 'updateTxParams' | 'removeTxFromPool' | 'transactionsPool'>) => Promise<void> | void;
268
271
  /**
269
272
  * Optional: Logic to cancel a pending EVM transaction.
@@ -307,7 +310,7 @@ type TransactionPool<T extends Transaction> = Record<string, T>;
307
310
  * on a transaction object via the `updateTxParams` action. This ensures type safety
308
311
  * and prevents accidental modification of immutable properties.
309
312
  */
310
- type UpdatableTransactionFields = Partial<Pick<EvmTransaction, 'to' | 'nonce' | 'txKey' | 'pending' | 'hash' | 'status' | 'replacedTxHash' | 'error' | 'finishedTimestamp' | 'isTrackedModalOpen' | 'isError' | 'maxPriorityFeePerGas' | 'maxFeePerGas' | 'input' | 'value'>> & Partial<Pick<SolanaTransaction, 'slot' | 'confirmations' | 'fee' | 'instructions' | 'recentBlockhash' | 'rpcUrl'>>;
313
+ type UpdatableTransactionFields = Partial<Pick<EvmTransaction, 'to' | 'nonce' | 'txKey' | 'pending' | 'hash' | 'status' | 'replacedTxHash' | 'error' | 'finishedTimestamp' | 'isTrackedModalOpen' | 'isError' | 'maxPriorityFeePerGas' | 'maxFeePerGas' | 'input' | 'value' | 'confirmations' | 'requiredConfirmations'>> & Partial<Pick<SolanaTransaction, 'slot' | 'confirmations' | 'fee' | 'instructions' | 'recentBlockhash' | 'rpcUrl'>>;
311
314
  /**
312
315
  * The interface for the base transaction tracking store slice.
313
316
  * It includes the state and actions for managing the transaction lifecycle.
@@ -376,6 +379,77 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
376
379
  * This is essential for resuming tracking after a page reload or application restart.
377
380
  */
378
381
  initializeTransactionsPool: () => Promise<void>;
382
+ /**
383
+ * Cross-device synchronization bridge.
384
+ * Injects remote pending transactions into the local pool and starts their lifecycle trackers.
385
+ * Also self-heals local pending transactions if the remote DB knows they are terminal.
386
+ */
387
+ injectExternalPendingTxs: (remoteTxs: T[]) => Promise<void>;
388
+ };
389
+ /**
390
+ * Represents the structure and behavior of an in-memory pagination system
391
+ * for managing transaction history.
392
+ */
393
+ type TxInMemoryPagination = {
394
+ /** Indicates whether the store is currently loading transaction history. */
395
+ isLoading: boolean;
396
+ /** Indicates whether the last loading request ended with an error. */
397
+ isError: boolean;
398
+ /** Indicates whether more history pages are available. */
399
+ hasMore: boolean;
400
+ /** The current page number in the paginated history. */
401
+ currentPage: number;
402
+ /** Loads the next page of transaction history and appends it to the pool. */
403
+ fetchNextPage: (walletAddress: string) => Promise<void>;
404
+ };
405
+ /**
406
+ * The complete interface for the Pulsar transaction in-memory store.
407
+ * It keeps a paginated remote history in sync with a local transaction pool.
408
+ *
409
+ * @template T The transaction type.
410
+ */
411
+ type ITxInMemoryStore<T extends Transaction> = {
412
+ /** A pool of all transactions currently being tracked and loaded from history, indexed by `txKey`. */
413
+ transactionsPool: TransactionPool<T>;
414
+ /** Loads the first page of transaction history. */
415
+ fetchInitial: (walletAddress: string) => Promise<void>;
416
+ /** Merges a local transaction pool into the in-memory store. */
417
+ syncWithLocalPool: (localPool: TransactionPool<T>) => void;
418
+ } & TxInMemoryPagination;
419
+ /**
420
+ * Parameters used to configure and manage an in-memory transaction store.
421
+ *
422
+ * @template T The transaction type.
423
+ */
424
+ type ITxInMemoryStoreParameters<T extends Transaction> = {
425
+ /** A localTransactionsPool. */
426
+ localTransactionsPool: TransactionPool<T>;
427
+ /** * Callback fired when remote history is successfully fetched.
428
+ * Used to inject remote pending transactions into the persistent tracking store.
429
+ */
430
+ onHistoryFetched?: (remoteTxs: T[]) => void;
431
+ getHistory?: ({ page, walletAddress, }: {
432
+ /**
433
+ * Page number for pagination.
434
+ *
435
+ * @defaultValue `1`
436
+ */
437
+ page?: number;
438
+ walletAddress: string;
439
+ }) => Promise<{
440
+ /** Array of transactions for the current page. */
441
+ docs: T[];
442
+ /** Total number of transactions matching the query. */
443
+ totalDocs: number;
444
+ /** Total number of available pages. */
445
+ totalPages: number;
446
+ /** Current page number. */
447
+ page: number;
448
+ /** Indicates whether a next page exists. */
449
+ hasNextPage: boolean;
450
+ /** Indicates whether a previous page exists. */
451
+ hasPrevPage: boolean;
452
+ } | null>;
379
453
  };
380
454
 
381
455
  /**
@@ -391,7 +465,7 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
391
465
  * @param options Configuration for the store slice.
392
466
  * @returns A Zustand store slice implementing `IInitializeTxTrackingStore`.
393
467
  */
394
- declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, }: {
468
+ declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, onRemoteCreate, }: Pick<PulsarAdapter<T>, 'onRemoteCreate'> & {
395
469
  maxTransactions: number;
396
470
  }): StoreSlice<IInitializeTxTrackingStore<T>>;
397
471
 
@@ -439,6 +513,22 @@ declare const selectAllTransactionsByActiveWallet: <T extends Transaction>(trans
439
513
  */
440
514
  declare const selectPendingTransactionsByActiveWallet: <T extends Transaction>(transactionsPool: TransactionPool<T>, from: string) => T[];
441
515
 
516
+ /**
517
+ * Creates an in-memory transaction store with synchronized local and remote sources.
518
+ *
519
+ * The store is designed to:
520
+ * - keep a local transaction pool in sync with remote history
521
+ * - preserve terminal transaction states
522
+ * - support paginated history loading
523
+ * - avoid duplicated merge logic across store actions
524
+ *
525
+ * @template T The transaction type.
526
+ * @param params Store configuration parameters.
527
+ * @param params.getHistory Optional remote history fetcher.
528
+ * @returns A Zustand vanilla store instance for in-memory transaction management.
529
+ */
530
+ declare function createTxInMemoryStore<T extends Transaction>({ localTransactionsPool, getHistory, onHistoryFetched, }: ITxInMemoryStoreParameters<T>): zustand.StoreApi<ITxInMemoryStore<T>>;
531
+
442
532
  /**
443
533
  * Creates the main Pulsar store for transaction tracking.
444
534
  *
@@ -453,7 +543,7 @@ declare const selectPendingTransactionsByActiveWallet: <T extends Transaction>(t
453
543
  * @param options Configuration for the Zustand `persist` middleware.
454
544
  * @returns A fully configured Zustand store instance.
455
545
  */
456
- declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
546
+ declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
457
547
  setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
458
548
  setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
459
549
  persist: {
@@ -571,4 +661,4 @@ type PollingTrackerConfig<R, T extends Transaction> = {
571
661
  */
572
662
  declare function initializePollingTracker<R, T extends Transaction>(config: PollingTrackerConfig<R, T>): void;
573
663
 
574
- export { type ActionTxKey, type BaseTransaction, type EvmTransaction, type GelatoTxKey, type IInitializeTxTrackingStore, type ITxTrackingStore, type InitialTransaction, type InitialTransactionParams, type OnSuccessCallback, type PollingFetcherParams, type PollingTrackerConfig, type PulsarAdapter, type SolanaTransaction, type StarknetTransaction, type StoreSlice, type TrackerCallbacks, type Transaction, type TransactionPool, TransactionStatus, TransactionTracker, type TxAdapter, createBoundedUseStore, createPulsarStore, initializePollingTracker, initializeTxTrackingStore, selectAllTransactions, selectAllTransactionsByActiveWallet, selectPendingTransactions, selectPendingTransactionsByActiveWallet, selectTxByKey };
664
+ export { type ActionTxKey, type BaseTransaction, type CheckTxTracker, type EvmTransaction, type IInitializeTxTrackingStore, type ITxInMemoryStore, type ITxInMemoryStoreParameters, type ITxTrackingStore, type InitialTransaction, type InitialTransactionParams, type PollingFetcherParams, type PollingTrackerConfig, type PulsarAdapter, type SolanaTransaction, type StarknetTransaction, type StoreSlice, type SyncCallbacks, type TrackerCallbacks, type Transaction, type TransactionPool, TransactionStatus, TransactionTracker, type TxAdapter, type TxInMemoryPagination, type UpdatableTransactionFields, createBoundedUseStore, createPulsarStore, createTxInMemoryStore, initializePollingTracker, initializeTxTrackingStore, selectAllTransactions, selectAllTransactionsByActiveWallet, selectPendingTransactions, selectPendingTransactionsByActiveWallet, selectTxByKey };
package/dist/index.d.ts CHANGED
@@ -34,13 +34,6 @@ declare enum TransactionStatus {
34
34
  /** The transaction was replaced by another with the same nonce (e.g., a speed-up or cancel). */
35
35
  Replaced = "Replaced"
36
36
  }
37
- /**
38
- * Defines the shape of the identifier for a Gelato transaction task.
39
- */
40
- type GelatoTxKey = {
41
- /** The unique identifier assigned by the Gelato relay service. */
42
- taskId: string;
43
- };
44
37
  /**
45
38
  * A union type representing the unique identifier returned by an `actionFunction`
46
39
  * after a transaction is submitted to the network or a relay service.
@@ -50,10 +43,9 @@ type GelatoTxKey = {
50
43
  *
51
44
  * It can be one of the following:
52
45
  * - A standard `0x...` transaction hash (`Hex`).
53
- * - A structured object from a relay service like Gelato (`GelatoTxKey`).
54
46
  * - A Solana transaction signature (string).
55
47
  */
56
- type ActionTxKey = `0x${string}` | GelatoTxKey | string;
48
+ type ActionTxKey = `0x${string}` | string;
57
49
  /**
58
50
  * The fundamental structure for any transaction being tracked by Pulsar.
59
51
  * This serves as the base upon which chain-specific transaction types are built.
@@ -82,8 +74,8 @@ type BaseTransaction = {
82
74
  isTrackedModalOpen?: boolean;
83
75
  /** The local timestamp (in seconds) when the transaction was initiated by the user. */
84
76
  localTimestamp: number;
85
- /** Any additional, custom data associated with the transaction. */
86
- payload?: object;
77
+ /** Custom data (strings or numbers) to associate with the transaction. */
78
+ payload?: Record<string, string | number>;
87
79
  /** A flag indicating if the transaction is still awaiting on-chain confirmation. */
88
80
  pending: boolean;
89
81
  /** The final on-chain status of the transaction. */
@@ -105,6 +97,12 @@ type BaseTransaction = {
105
97
  type: string;
106
98
  /** The type of connector used to sign the transaction (e.g., 'injected', 'walletConnect'). */
107
99
  connectorType: string;
100
+ /** The number of confirmations required for the transaction to be considered confirmed. */
101
+ requiredConfirmations?: number;
102
+ /** The number of confirmations received. A string value indicates a confirmed transaction, while `null` means it's pending. */
103
+ confirmations?: number | string | null;
104
+ /** The RPC URL to use for the transaction. Required for Solana transactions. */
105
+ rpcUrl?: string;
108
106
  };
109
107
  /**
110
108
  * Represents an EVM-specific transaction, extending the base properties with EVM fields.
@@ -143,10 +141,6 @@ type SolanaTransaction = BaseTransaction & {
143
141
  recentBlockhash?: string;
144
142
  /** The slot in which the transaction was processed. */
145
143
  slot?: number;
146
- /** The number of confirmations received. A string value indicates a confirmed transaction, while `null` means it's pending. */
147
- confirmations?: number | string | null;
148
- /** The RPC URL used to submit and track this transaction. */
149
- rpcUrl?: string;
150
144
  };
151
145
  /**
152
146
  * Represents a Starknet-specific transaction, extending the base properties.
@@ -167,25 +161,17 @@ type Transaction = EvmTransaction | SolanaTransaction | StarknetTransaction;
167
161
  /**
168
162
  * Represents the parameters required to initiate a new transaction tracking flow.
169
163
  */
170
- type InitialTransactionParams = {
164
+ type InitialTransactionParams = Pick<BaseTransaction, 'description' | 'title' | 'type' | 'requiredConfirmations' | 'rpcUrl' | 'payload'> & {
171
165
  /** The specific blockchain adapter for this transaction. */
172
166
  adapter: OrbitAdapter;
173
167
  /** The function that executes the on-chain action (e.g., sending a transaction) and returns a preliminary identifier like a hash. */
174
168
  actionFunction: (...args: any[]) => Promise<ActionTxKey | undefined>;
175
- /** A user-facing description for the transaction. Supports state-specific descriptions. */
176
- description?: string | [string, string, string, string];
177
169
  /** The target chain ID for the transaction. */
178
170
  desiredChainID: number | string;
179
- /** Any custom data to associate with the transaction. */
180
- payload?: object;
181
- /** A user-facing title for the transaction. Supports state-specific titles. */
182
- title?: string | [string, string, string, string];
183
- /** The application-specific type of the transaction. */
184
- type: string;
185
171
  /** If true, the detailed tracking modal will open automatically upon initiation. */
186
172
  withTrackedModal?: boolean;
187
- /** The RPC URL to use for the transaction. Required for Solana transactions. */
188
- rpcUrl?: string;
173
+ /** The specific tracker responsible for monitoring this transaction's status. Required for Gelato tracker. */
174
+ tracker?: TransactionTracker;
189
175
  };
190
176
  /**
191
177
  * Represents a transaction in its temporary, pre-submission state.
@@ -211,20 +197,38 @@ interface TrackerCallbacks<T extends Transaction> {
211
197
  onReplaced?: (newTx: T, oldTx: T) => Promise<void> | void;
212
198
  }
213
199
  /**
214
- * @deprecated Use `TrackerCallbacks<T>` instead.
215
- * Defines a callback function to be executed upon a successful transaction.
216
- * @template T The specific transaction type, extending `Transaction`.
200
+ * Callbacks for synchronizing local transaction state with a remote backend.
201
+ * These are injected into the store at creation time.
217
202
  */
218
- type OnSuccessCallback<T extends Transaction> = {
219
- /** Callback to execute when the transaction is successfully submitted. */
220
- onSuccessCallback?: (tx: T) => Promise<void> | void;
221
- };
203
+ interface SyncCallbacks<T extends Transaction> {
204
+ /**
205
+ * Called immediately after a transaction is created locally (added to pool).
206
+ * Use this to POST the active pending transaction to the backend.
207
+ */
208
+ onRemoteCreate?: (tx: T) => Promise<void>;
209
+ }
222
210
  /**
223
211
  * The configuration object containing one or more transaction adapters.
224
212
  * @template T The specific transaction type.
225
213
  */
226
214
  type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> & {
227
215
  maxTransactions?: number;
216
+ gelatoApiKey?: string;
217
+ } & SyncCallbacks<T>;
218
+ /**
219
+ * Represents a tracker for a specific transaction tied to an action and a connector.
220
+ *
221
+ * @typedef {Object} CheckTxTracker
222
+ * @property {ActionTxKey} actionTxKey - The key identifying the specific action related to the transaction.
223
+ * @property {string} connectorType - The type of connector used for the transaction (e.g., wallet provider, blockchain interface).
224
+ * @property {TransactionTracker} [tracker] - An optional tracker object that monitors the status and progress of the transaction.
225
+ * @property {string} [gelatoApiKey] - An optional Gelato API key for Gelato relayer integration.
226
+ */
227
+ type CheckTxTracker = {
228
+ actionTxKey: ActionTxKey;
229
+ connectorType: string;
230
+ tracker?: TransactionTracker;
231
+ gelatoApiKey?: string;
228
232
  };
229
233
  /**
230
234
  * Defines the interface for a transaction adapter, which provides chain-specific logic and utilities.
@@ -250,11 +254,9 @@ type TxAdapter<T extends Transaction> = Pick<BaseAdapter, 'getExplorerUrl'> & {
250
254
  checkChainForTx: (chainId: string | number) => Promise<void>;
251
255
  /**
252
256
  * Determines the appropriate tracker and final `txKey` from the result of an action.
253
- * @param actionTxKey The preliminary key returned after an action function is executed.
254
- * @param connectorType The type of connector used for the transaction.
255
257
  * @returns An object containing the final `txKey` and the `TransactionTracker` to be used.
256
258
  */
257
- checkTransactionsTracker: (actionTxKey: ActionTxKey, connectorType: string) => {
259
+ checkTransactionsTracker: ({ actionTxKey, connectorType, tracker }: CheckTxTracker) => {
258
260
  txKey: string;
259
261
  tracker: TransactionTracker;
260
262
  };
@@ -264,6 +266,7 @@ type TxAdapter<T extends Transaction> = Pick<BaseAdapter, 'getExplorerUrl'> & {
264
266
  */
265
267
  checkAndInitializeTrackerInStore: (params: {
266
268
  tx: T;
269
+ gelatoApiKey?: string;
267
270
  } & TrackerCallbacks<T> & Pick<ITxTrackingStore<T>, 'updateTxParams' | 'removeTxFromPool' | 'transactionsPool'>) => Promise<void> | void;
268
271
  /**
269
272
  * Optional: Logic to cancel a pending EVM transaction.
@@ -307,7 +310,7 @@ type TransactionPool<T extends Transaction> = Record<string, T>;
307
310
  * on a transaction object via the `updateTxParams` action. This ensures type safety
308
311
  * and prevents accidental modification of immutable properties.
309
312
  */
310
- type UpdatableTransactionFields = Partial<Pick<EvmTransaction, 'to' | 'nonce' | 'txKey' | 'pending' | 'hash' | 'status' | 'replacedTxHash' | 'error' | 'finishedTimestamp' | 'isTrackedModalOpen' | 'isError' | 'maxPriorityFeePerGas' | 'maxFeePerGas' | 'input' | 'value'>> & Partial<Pick<SolanaTransaction, 'slot' | 'confirmations' | 'fee' | 'instructions' | 'recentBlockhash' | 'rpcUrl'>>;
313
+ type UpdatableTransactionFields = Partial<Pick<EvmTransaction, 'to' | 'nonce' | 'txKey' | 'pending' | 'hash' | 'status' | 'replacedTxHash' | 'error' | 'finishedTimestamp' | 'isTrackedModalOpen' | 'isError' | 'maxPriorityFeePerGas' | 'maxFeePerGas' | 'input' | 'value' | 'confirmations' | 'requiredConfirmations'>> & Partial<Pick<SolanaTransaction, 'slot' | 'confirmations' | 'fee' | 'instructions' | 'recentBlockhash' | 'rpcUrl'>>;
311
314
  /**
312
315
  * The interface for the base transaction tracking store slice.
313
316
  * It includes the state and actions for managing the transaction lifecycle.
@@ -376,6 +379,77 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
376
379
  * This is essential for resuming tracking after a page reload or application restart.
377
380
  */
378
381
  initializeTransactionsPool: () => Promise<void>;
382
+ /**
383
+ * Cross-device synchronization bridge.
384
+ * Injects remote pending transactions into the local pool and starts their lifecycle trackers.
385
+ * Also self-heals local pending transactions if the remote DB knows they are terminal.
386
+ */
387
+ injectExternalPendingTxs: (remoteTxs: T[]) => Promise<void>;
388
+ };
389
+ /**
390
+ * Represents the structure and behavior of an in-memory pagination system
391
+ * for managing transaction history.
392
+ */
393
+ type TxInMemoryPagination = {
394
+ /** Indicates whether the store is currently loading transaction history. */
395
+ isLoading: boolean;
396
+ /** Indicates whether the last loading request ended with an error. */
397
+ isError: boolean;
398
+ /** Indicates whether more history pages are available. */
399
+ hasMore: boolean;
400
+ /** The current page number in the paginated history. */
401
+ currentPage: number;
402
+ /** Loads the next page of transaction history and appends it to the pool. */
403
+ fetchNextPage: (walletAddress: string) => Promise<void>;
404
+ };
405
+ /**
406
+ * The complete interface for the Pulsar transaction in-memory store.
407
+ * It keeps a paginated remote history in sync with a local transaction pool.
408
+ *
409
+ * @template T The transaction type.
410
+ */
411
+ type ITxInMemoryStore<T extends Transaction> = {
412
+ /** A pool of all transactions currently being tracked and loaded from history, indexed by `txKey`. */
413
+ transactionsPool: TransactionPool<T>;
414
+ /** Loads the first page of transaction history. */
415
+ fetchInitial: (walletAddress: string) => Promise<void>;
416
+ /** Merges a local transaction pool into the in-memory store. */
417
+ syncWithLocalPool: (localPool: TransactionPool<T>) => void;
418
+ } & TxInMemoryPagination;
419
+ /**
420
+ * Parameters used to configure and manage an in-memory transaction store.
421
+ *
422
+ * @template T The transaction type.
423
+ */
424
+ type ITxInMemoryStoreParameters<T extends Transaction> = {
425
+ /** A localTransactionsPool. */
426
+ localTransactionsPool: TransactionPool<T>;
427
+ /** * Callback fired when remote history is successfully fetched.
428
+ * Used to inject remote pending transactions into the persistent tracking store.
429
+ */
430
+ onHistoryFetched?: (remoteTxs: T[]) => void;
431
+ getHistory?: ({ page, walletAddress, }: {
432
+ /**
433
+ * Page number for pagination.
434
+ *
435
+ * @defaultValue `1`
436
+ */
437
+ page?: number;
438
+ walletAddress: string;
439
+ }) => Promise<{
440
+ /** Array of transactions for the current page. */
441
+ docs: T[];
442
+ /** Total number of transactions matching the query. */
443
+ totalDocs: number;
444
+ /** Total number of available pages. */
445
+ totalPages: number;
446
+ /** Current page number. */
447
+ page: number;
448
+ /** Indicates whether a next page exists. */
449
+ hasNextPage: boolean;
450
+ /** Indicates whether a previous page exists. */
451
+ hasPrevPage: boolean;
452
+ } | null>;
379
453
  };
380
454
 
381
455
  /**
@@ -391,7 +465,7 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
391
465
  * @param options Configuration for the store slice.
392
466
  * @returns A Zustand store slice implementing `IInitializeTxTrackingStore`.
393
467
  */
394
- declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, }: {
468
+ declare function initializeTxTrackingStore<T extends Transaction>({ maxTransactions, onRemoteCreate, }: Pick<PulsarAdapter<T>, 'onRemoteCreate'> & {
395
469
  maxTransactions: number;
396
470
  }): StoreSlice<IInitializeTxTrackingStore<T>>;
397
471
 
@@ -439,6 +513,22 @@ declare const selectAllTransactionsByActiveWallet: <T extends Transaction>(trans
439
513
  */
440
514
  declare const selectPendingTransactionsByActiveWallet: <T extends Transaction>(transactionsPool: TransactionPool<T>, from: string) => T[];
441
515
 
516
+ /**
517
+ * Creates an in-memory transaction store with synchronized local and remote sources.
518
+ *
519
+ * The store is designed to:
520
+ * - keep a local transaction pool in sync with remote history
521
+ * - preserve terminal transaction states
522
+ * - support paginated history loading
523
+ * - avoid duplicated merge logic across store actions
524
+ *
525
+ * @template T The transaction type.
526
+ * @param params Store configuration parameters.
527
+ * @param params.getHistory Optional remote history fetcher.
528
+ * @returns A Zustand vanilla store instance for in-memory transaction management.
529
+ */
530
+ declare function createTxInMemoryStore<T extends Transaction>({ localTransactionsPool, getHistory, onHistoryFetched, }: ITxInMemoryStoreParameters<T>): zustand.StoreApi<ITxInMemoryStore<T>>;
531
+
442
532
  /**
443
533
  * Creates the main Pulsar store for transaction tracking.
444
534
  *
@@ -453,7 +543,7 @@ declare const selectPendingTransactionsByActiveWallet: <T extends Transaction>(t
453
543
  * @param options Configuration for the Zustand `persist` middleware.
454
544
  * @returns A fully configured Zustand store instance.
455
545
  */
456
- declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
546
+ declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
457
547
  setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
458
548
  setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
459
549
  persist: {
@@ -571,4 +661,4 @@ type PollingTrackerConfig<R, T extends Transaction> = {
571
661
  */
572
662
  declare function initializePollingTracker<R, T extends Transaction>(config: PollingTrackerConfig<R, T>): void;
573
663
 
574
- export { type ActionTxKey, type BaseTransaction, type EvmTransaction, type GelatoTxKey, type IInitializeTxTrackingStore, type ITxTrackingStore, type InitialTransaction, type InitialTransactionParams, type OnSuccessCallback, type PollingFetcherParams, type PollingTrackerConfig, type PulsarAdapter, type SolanaTransaction, type StarknetTransaction, type StoreSlice, type TrackerCallbacks, type Transaction, type TransactionPool, TransactionStatus, TransactionTracker, type TxAdapter, createBoundedUseStore, createPulsarStore, initializePollingTracker, initializeTxTrackingStore, selectAllTransactions, selectAllTransactionsByActiveWallet, selectPendingTransactions, selectPendingTransactionsByActiveWallet, selectTxByKey };
664
+ export { type ActionTxKey, type BaseTransaction, type CheckTxTracker, type EvmTransaction, type IInitializeTxTrackingStore, type ITxInMemoryStore, type ITxInMemoryStoreParameters, type ITxTrackingStore, type InitialTransaction, type InitialTransactionParams, type PollingFetcherParams, type PollingTrackerConfig, type PulsarAdapter, type SolanaTransaction, type StarknetTransaction, type StoreSlice, type SyncCallbacks, type TrackerCallbacks, type Transaction, type TransactionPool, TransactionStatus, TransactionTracker, type TxAdapter, type TxInMemoryPagination, type UpdatableTransactionFields, createBoundedUseStore, createPulsarStore, createTxInMemoryStore, initializePollingTracker, initializeTxTrackingStore, selectAllTransactions, selectAllTransactionsByActiveWallet, selectPendingTransactions, selectPendingTransactionsByActiveWallet, selectTxByKey };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var immer=require('immer'),orbitCore=require('@tuwaio/orbit-core'),C=require('dayjs'),middleware=require('zustand/middleware'),vanilla=require('zustand/vanilla'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var C__default=/*#__PURE__*/_interopDefault(C);function h({maxTransactions:o}){return (t,a)=>({transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,addTxToPool:e=>{t(r=>immer.produce(r,n=>{if(n.lastAddedTxKey=e.txKey,e.txKey){if(Object.keys(n.transactionsPool).length>=o){let d=Object.values(n.transactionsPool).sort((l,x)=>l.localTimestamp-x.localTimestamp);if(d.length>0){let l=d[0];delete n.transactionsPool[l.txKey];}}let s={...e,pending:true};n.transactionsPool[e.txKey]=s;}}));},updateTxParams:(e,r)=>{t(n=>immer.produce(n,i=>{let s=i.transactionsPool[e];s&&Object.assign(s,r);}));},removeTxFromPool:e=>{t(r=>immer.produce(r,n=>{delete n.transactionsPool[e];}));},closeTxTrackedModal:e=>{t(r=>immer.produce(r,n=>{if(e&&n.transactionsPool[e]){let i=n.transactionsPool[e];n.transactionsPool[e]={...i,isTrackedModalOpen:false};}n.initialTx=void 0;}));},getLastTxKey:()=>a().lastAddedTxKey})}var b=o=>Object.values(o).sort((t,a)=>Number(t.localTimestamp)-Number(a.localTimestamp)),_=o=>b(o).filter(t=>t.pending),H=(o,t)=>o[t],E=(o,t)=>b(o).filter(a=>a.from.toLowerCase()===t.toLowerCase()),V=(o,t)=>E(o,t).filter(a=>a.pending);function te({adapter:o,maxTransactions:t=50,...a}){return vanilla.createStore()(middleware.persist((e,r)=>({...h({maxTransactions:t})(e,r),getAdapter:()=>o,initializeTransactionsPool:async()=>{let n=Object.values(r().transactionsPool).filter(i=>i.pending);await Promise.all(n.map(i=>orbitCore.selectAdapterByKey({adapterKey:i.adapter,adapter:o})?.checkAndInitializeTrackerInStore({tx:i,...r()})));},executeTxAction:async({defaultTracker:n,actionFunction:i,params:s,...d})=>{let{desiredChainID:l,...x}=s,{onSuccess:g,onError:m,onReplaced:u}=d,y=C__default.default().unix();e({initialTx:{...s,actionFunction:i,localTimestamp:y,isInitializing:true}});let c=orbitCore.selectAdapterByKey({adapterKey:x.adapter,adapter:o}),A=T=>{e(f=>immer.produce(f,p=>{p.initialTx&&(p.initialTx.isInitializing=false,p.initialTx.error=orbitCore.normalizeError(T));}));};if(!c){let T=new Error("No adapter found for this transaction.");throw A(T),T}try{let{connectorType:T,walletAddress:f}=c.getConnectorInfo();await c.checkChainForTx(l);let p=await i();if(!p){e({initialTx:void 0});return}let{tracker:v,txKey:k}=c.checkTransactionsTracker(p,T),w={...x,connectorType:T,from:f,tracker:v||n,chainId:orbitCore.setChainId(l),localTimestamp:y,txKey:k,hash:v==="ethereum"?p:void 0,pending:!1,isTrackedModalOpen:s.withTrackedModal};r().addTxToPool(w),e(F=>immer.produce(F,S=>{S.initialTx&&(S.initialTx.isInitializing=!1,S.initialTx.lastTxKey=k);}));let R=r().transactionsPool[k];await c.checkAndInitializeTrackerInStore({tx:R,onSuccess:g,onError:m,onReplaced:u,...r()});}catch(T){throw A(T),T}}}),{...a}))}var U=(r=>(r.Ethereum="ethereum",r.Safe="safe",r.Gelato="gelato",r.Solana="solana",r))(U||{}),j=(e=>(e.Failed="Failed",e.Success="Success",e.Replaced="Replaced",e))(j||{});var ae=(o=>t=>zustand.useStore(o,t));var M=5e3,$=10;function ce(o){let{tx:t,fetcher:a,onInitialize:e,onSuccess:r,onFailure:n,onIntervalTick:i,onReplaced:s,removeTxFromPool:d,pollingInterval:l=M,maxRetries:x=$}=o;if(!t.pending)return;e?.();let g=x,m=true,u=c=>{m&&(m=false,d&&!c?.withoutRemoving&&d(t.txKey));};(async()=>{for(;m&&g>0;)try{if(await new Promise(c=>setTimeout(c,l)),!m)break;await a({tx:t,stopPolling:u,onSuccess:r,onFailure:n,onIntervalTick:i,onReplaced:s});}catch(c){console.error(`Polling fetcher for txKey ${t.txKey} threw an error:`,c),g--;}g<=0&&(console.warn(`Polling for txKey ${t.txKey} stopped after reaching the maximum number of retries.`),n(),u());})();}exports.TransactionStatus=j;exports.TransactionTracker=U;exports.createBoundedUseStore=ae;exports.createPulsarStore=te;exports.initializePollingTracker=ce;exports.initializeTxTrackingStore=h;exports.selectAllTransactions=b;exports.selectAllTransactionsByActiveWallet=E;exports.selectPendingTransactions=_;exports.selectPendingTransactionsByActiveWallet=V;exports.selectTxByKey=H;
1
+ 'use strict';var immer=require('immer'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),D=require('dayjs'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var D__default=/*#__PURE__*/_interopDefault(D);function E({maxTransactions:o,onRemoteCreate:t}){return (s,p)=>({transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,addTxToPool:a=>{let e={...a,pending:true};s(n=>immer.produce(n,r=>{if(r.lastAddedTxKey=a.txKey,a.txKey){if(Object.keys(r.transactionsPool).length>=o){let l=Object.values(r.transactionsPool).sort((T,c)=>T.localTimestamp-c.localTimestamp);if(l.length>0){let T=l[0];delete r.transactionsPool[T.txKey];}}r.transactionsPool[a.txKey]=e;}})),t&&t(e).catch(n=>console.error("[Pulsar Sync] Create failed:",n));},updateTxParams:(a,e)=>{s(n=>immer.produce(n,r=>{let i=r.transactionsPool[a];i&&Object.assign(i,e);}));},removeTxFromPool:a=>{s(e=>immer.produce(e,n=>{delete n.transactionsPool[a];}));},closeTxTrackedModal:a=>{s(e=>immer.produce(e,n=>{if(a&&n.transactionsPool[a]){let r=n.transactionsPool[a];n.transactionsPool[a]={...r,isTrackedModalOpen:false};}n.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey})}var F=o=>Object.values(o).sort((t,s)=>Number(t.localTimestamp)-Number(s.localTimestamp)),Y=o=>F(o).filter(t=>t.pending),Z=(o,t)=>o[t],B=(o,t)=>F(o).filter(s=>s.from.toLowerCase()===t.toLowerCase()),ee=(o,t)=>B(o,t).filter(s=>s.pending);var N=(a=>(a.Ethereum="ethereum",a.Safe="safe",a.Gelato="gelato",a.Solana="solana",a))(N||{}),A=(p=>(p.Failed="Failed",p.Success="Success",p.Replaced="Replaced",p))(A||{});var $=o=>o==="Success"||o==="Replaced",M=(o,t)=>{let s=o[t.txKey];return $(s?.status)?false:(o[t.txKey]=t,true)};function Te({localTransactionsPool:o,getHistory:t,onHistoryFetched:s}){immer.setAutoFreeze(false);let p=e=>({isLoading:e,isError:false}),a=e=>e?(s&&queueMicrotask(()=>s(e.docs)),n=>immer.produce(n,r=>{let i=r.transactionsPool;for(let l of e.docs)M(i,l);r.currentPage=e.page,r.hasMore=e.hasNextPage,r.isLoading=false;})):n=>n;return vanilla.createStore()((e,n)=>({transactionsPool:o,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{e(i=>immer.produce(i,l=>{let T=l.transactionsPool;for(let c of Object.values(r))M(T,c);}));},fetchInitial:async r=>{if(!(!t||!r)){e(p(true));try{let i=await t({page:1,walletAddress:r});e(a(i));}catch(i){console.error("[Pulsar] Failed to fetch initial transaction history:",i),e({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:i,isLoading:l,currentPage:T}=n();if(!(!t||!i||l||!r)){e(p(true));try{let c=T+1,m=await t({page:c,walletAddress:r});e(a(m));}catch(c){console.error(`[Pulsar] Failed to fetch transaction history page ${T+1}:`,c),e({isLoading:false,isError:true});}}}}))}function Ae({adapter:o,maxTransactions:t=50,onRemoteCreate:s,gelatoApiKey:p,...a}){return vanilla.createStore()(middleware.persist((e,n)=>({...E({maxTransactions:t,onRemoteCreate:s})(e,n),getAdapter:()=>o,initializeTransactionsPool:async()=>{let r=Object.values(n().transactionsPool).filter(i=>i.pending);await Promise.all(r.map(i=>orbitCore.selectAdapterByKey({adapterKey:i.adapter,adapter:o})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:p,...n()})));},injectExternalPendingTxs:async r=>{let l=n().getAdapter(),T=[];e(c=>immer.produce(c,m=>{let g=m.transactionsPool;r.forEach(d=>{let x=g[d.txKey];d.pending&&!x&&(g[d.txKey]=d,T.push(d));let S=d.status==="Success"||d.status==="Failed"||d.status==="Replaced";x?.pending&&S&&(x.status=d.status,x.pending=false,d.txKey&&(x.txKey=d.txKey),d.finishedTimestamp&&(x.finishedTimestamp=d.finishedTimestamp));});})),T.length>0&&await Promise.all(T.map(c=>orbitCore.selectAdapterByKey({adapterKey:c.adapter,adapter:l})?.checkAndInitializeTrackerInStore({tx:c,gelatoApiKey:p,...n()})));},executeTxAction:async({defaultTracker:r,actionFunction:i,params:l,...T})=>{let{desiredChainID:c,tracker:m,...g}=l,{onSuccess:d,onError:x,onReplaced:S}=T,K=D__default.default().unix();e({initialTx:{...l,actionFunction:i,localTimestamp:K,isInitializing:true}});let f=orbitCore.selectAdapterByKey({adapterKey:g.adapter,adapter:o}),w=u=>{e(k=>immer.produce(k,P=>{P.initialTx&&(P.initialTx.isInitializing=false,P.initialTx.error=orbitCore.normalizeError(u));}));};if(!f){let u=new Error("No adapter found for this transaction.");throw w(u),u}try{let{connectorType:u,walletAddress:k}=f.getConnectorInfo();await f.checkChainForTx(c);let P=await i();if(!P){e({initialTx:void 0});return}let{tracker:R,txKey:h}=f.checkTransactionsTracker({actionTxKey:P,connectorType:u,tracker:m,gelatoApiKey:p}),O={...g,connectorType:u,from:k,tracker:R||r,chainId:orbitCore.setChainId(c),localTimestamp:K,txKey:h,hash:R==="ethereum"?P:void 0,pending:!1,isTrackedModalOpen:l.withTrackedModal};n().addTxToPool(O),e(L=>immer.produce(L,I=>{I.initialTx&&(I.initialTx.isInitializing=!1,I.initialTx.lastTxKey=h);}));let z=n().transactionsPool[h];await f.checkAndInitializeTrackerInStore({tx:z,onSuccess:d,onError:x,onReplaced:S,gelatoApiKey:p,...n()});}catch(u){throw w(u),u}}}),{...a}))}var we=(o=>t=>zustand.useStore(o,t));var V=5e3,X=10;function Ee(o){let{tx:t,fetcher:s,onInitialize:p,onSuccess:a,onFailure:e,onIntervalTick:n,onReplaced:r,removeTxFromPool:i,pollingInterval:l=V,maxRetries:T=X}=o;if(!t.pending)return;p?.();let c=T,m=true,g=x=>{m&&(m=false,i&&!x?.withoutRemoving&&i(t.txKey));};(async()=>{for(;m&&c>0;)try{if(await new Promise(x=>setTimeout(x,l)),!m)break;await s({tx:t,stopPolling:g,onSuccess:a,onFailure:e,onIntervalTick:n,onReplaced:r});}catch(x){console.error(`Polling fetcher for txKey ${t.txKey} threw an error:`,x),c--;}c<=0&&(console.warn(`Polling for txKey ${t.txKey} stopped after reaching the maximum number of retries.`),e(),g());})();}exports.TransactionStatus=A;exports.TransactionTracker=N;exports.createBoundedUseStore=we;exports.createPulsarStore=Ae;exports.createTxInMemoryStore=Te;exports.initializePollingTracker=Ee;exports.initializeTxTrackingStore=E;exports.selectAllTransactions=F;exports.selectAllTransactionsByActiveWallet=B;exports.selectPendingTransactions=Y;exports.selectPendingTransactionsByActiveWallet=ee;exports.selectTxByKey=Z;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import {produce}from'immer';import {selectAdapterByKey,setChainId,normalizeError}from'@tuwaio/orbit-core';import C from'dayjs';import {persist}from'zustand/middleware';import {createStore}from'zustand/vanilla';import {useStore}from'zustand';function h({maxTransactions:o}){return (t,a)=>({transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,addTxToPool:e=>{t(r=>produce(r,n=>{if(n.lastAddedTxKey=e.txKey,e.txKey){if(Object.keys(n.transactionsPool).length>=o){let d=Object.values(n.transactionsPool).sort((l,x)=>l.localTimestamp-x.localTimestamp);if(d.length>0){let l=d[0];delete n.transactionsPool[l.txKey];}}let s={...e,pending:true};n.transactionsPool[e.txKey]=s;}}));},updateTxParams:(e,r)=>{t(n=>produce(n,i=>{let s=i.transactionsPool[e];s&&Object.assign(s,r);}));},removeTxFromPool:e=>{t(r=>produce(r,n=>{delete n.transactionsPool[e];}));},closeTxTrackedModal:e=>{t(r=>produce(r,n=>{if(e&&n.transactionsPool[e]){let i=n.transactionsPool[e];n.transactionsPool[e]={...i,isTrackedModalOpen:false};}n.initialTx=void 0;}));},getLastTxKey:()=>a().lastAddedTxKey})}var b=o=>Object.values(o).sort((t,a)=>Number(t.localTimestamp)-Number(a.localTimestamp)),_=o=>b(o).filter(t=>t.pending),H=(o,t)=>o[t],E=(o,t)=>b(o).filter(a=>a.from.toLowerCase()===t.toLowerCase()),V=(o,t)=>E(o,t).filter(a=>a.pending);function te({adapter:o,maxTransactions:t=50,...a}){return createStore()(persist((e,r)=>({...h({maxTransactions:t})(e,r),getAdapter:()=>o,initializeTransactionsPool:async()=>{let n=Object.values(r().transactionsPool).filter(i=>i.pending);await Promise.all(n.map(i=>selectAdapterByKey({adapterKey:i.adapter,adapter:o})?.checkAndInitializeTrackerInStore({tx:i,...r()})));},executeTxAction:async({defaultTracker:n,actionFunction:i,params:s,...d})=>{let{desiredChainID:l,...x}=s,{onSuccess:g,onError:m,onReplaced:u}=d,y=C().unix();e({initialTx:{...s,actionFunction:i,localTimestamp:y,isInitializing:true}});let c=selectAdapterByKey({adapterKey:x.adapter,adapter:o}),A=T=>{e(f=>produce(f,p=>{p.initialTx&&(p.initialTx.isInitializing=false,p.initialTx.error=normalizeError(T));}));};if(!c){let T=new Error("No adapter found for this transaction.");throw A(T),T}try{let{connectorType:T,walletAddress:f}=c.getConnectorInfo();await c.checkChainForTx(l);let p=await i();if(!p){e({initialTx:void 0});return}let{tracker:v,txKey:k}=c.checkTransactionsTracker(p,T),w={...x,connectorType:T,from:f,tracker:v||n,chainId:setChainId(l),localTimestamp:y,txKey:k,hash:v==="ethereum"?p:void 0,pending:!1,isTrackedModalOpen:s.withTrackedModal};r().addTxToPool(w),e(F=>produce(F,S=>{S.initialTx&&(S.initialTx.isInitializing=!1,S.initialTx.lastTxKey=k);}));let R=r().transactionsPool[k];await c.checkAndInitializeTrackerInStore({tx:R,onSuccess:g,onError:m,onReplaced:u,...r()});}catch(T){throw A(T),T}}}),{...a}))}var U=(r=>(r.Ethereum="ethereum",r.Safe="safe",r.Gelato="gelato",r.Solana="solana",r))(U||{}),j=(e=>(e.Failed="Failed",e.Success="Success",e.Replaced="Replaced",e))(j||{});var ae=(o=>t=>useStore(o,t));var M=5e3,$=10;function ce(o){let{tx:t,fetcher:a,onInitialize:e,onSuccess:r,onFailure:n,onIntervalTick:i,onReplaced:s,removeTxFromPool:d,pollingInterval:l=M,maxRetries:x=$}=o;if(!t.pending)return;e?.();let g=x,m=true,u=c=>{m&&(m=false,d&&!c?.withoutRemoving&&d(t.txKey));};(async()=>{for(;m&&g>0;)try{if(await new Promise(c=>setTimeout(c,l)),!m)break;await a({tx:t,stopPolling:u,onSuccess:r,onFailure:n,onIntervalTick:i,onReplaced:s});}catch(c){console.error(`Polling fetcher for txKey ${t.txKey} threw an error:`,c),g--;}g<=0&&(console.warn(`Polling for txKey ${t.txKey} stopped after reaching the maximum number of retries.`),n(),u());})();}export{j as TransactionStatus,U as TransactionTracker,ae as createBoundedUseStore,te as createPulsarStore,ce as initializePollingTracker,h as initializeTxTrackingStore,b as selectAllTransactions,E as selectAllTransactionsByActiveWallet,_ as selectPendingTransactions,V as selectPendingTransactionsByActiveWallet,H as selectTxByKey};
1
+ import {produce,setAutoFreeze}from'immer';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,setChainId,normalizeError}from'@tuwaio/orbit-core';import D from'dayjs';import {persist}from'zustand/middleware';import {useStore}from'zustand';function E({maxTransactions:o,onRemoteCreate:t}){return (s,p)=>({transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,addTxToPool:a=>{let e={...a,pending:true};s(n=>produce(n,r=>{if(r.lastAddedTxKey=a.txKey,a.txKey){if(Object.keys(r.transactionsPool).length>=o){let l=Object.values(r.transactionsPool).sort((T,c)=>T.localTimestamp-c.localTimestamp);if(l.length>0){let T=l[0];delete r.transactionsPool[T.txKey];}}r.transactionsPool[a.txKey]=e;}})),t&&t(e).catch(n=>console.error("[Pulsar Sync] Create failed:",n));},updateTxParams:(a,e)=>{s(n=>produce(n,r=>{let i=r.transactionsPool[a];i&&Object.assign(i,e);}));},removeTxFromPool:a=>{s(e=>produce(e,n=>{delete n.transactionsPool[a];}));},closeTxTrackedModal:a=>{s(e=>produce(e,n=>{if(a&&n.transactionsPool[a]){let r=n.transactionsPool[a];n.transactionsPool[a]={...r,isTrackedModalOpen:false};}n.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey})}var F=o=>Object.values(o).sort((t,s)=>Number(t.localTimestamp)-Number(s.localTimestamp)),Y=o=>F(o).filter(t=>t.pending),Z=(o,t)=>o[t],B=(o,t)=>F(o).filter(s=>s.from.toLowerCase()===t.toLowerCase()),ee=(o,t)=>B(o,t).filter(s=>s.pending);var N=(a=>(a.Ethereum="ethereum",a.Safe="safe",a.Gelato="gelato",a.Solana="solana",a))(N||{}),A=(p=>(p.Failed="Failed",p.Success="Success",p.Replaced="Replaced",p))(A||{});var $=o=>o==="Success"||o==="Replaced",M=(o,t)=>{let s=o[t.txKey];return $(s?.status)?false:(o[t.txKey]=t,true)};function Te({localTransactionsPool:o,getHistory:t,onHistoryFetched:s}){setAutoFreeze(false);let p=e=>({isLoading:e,isError:false}),a=e=>e?(s&&queueMicrotask(()=>s(e.docs)),n=>produce(n,r=>{let i=r.transactionsPool;for(let l of e.docs)M(i,l);r.currentPage=e.page,r.hasMore=e.hasNextPage,r.isLoading=false;})):n=>n;return createStore()((e,n)=>({transactionsPool:o,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{e(i=>produce(i,l=>{let T=l.transactionsPool;for(let c of Object.values(r))M(T,c);}));},fetchInitial:async r=>{if(!(!t||!r)){e(p(true));try{let i=await t({page:1,walletAddress:r});e(a(i));}catch(i){console.error("[Pulsar] Failed to fetch initial transaction history:",i),e({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:i,isLoading:l,currentPage:T}=n();if(!(!t||!i||l||!r)){e(p(true));try{let c=T+1,m=await t({page:c,walletAddress:r});e(a(m));}catch(c){console.error(`[Pulsar] Failed to fetch transaction history page ${T+1}:`,c),e({isLoading:false,isError:true});}}}}))}function Ae({adapter:o,maxTransactions:t=50,onRemoteCreate:s,gelatoApiKey:p,...a}){return createStore()(persist((e,n)=>({...E({maxTransactions:t,onRemoteCreate:s})(e,n),getAdapter:()=>o,initializeTransactionsPool:async()=>{let r=Object.values(n().transactionsPool).filter(i=>i.pending);await Promise.all(r.map(i=>selectAdapterByKey({adapterKey:i.adapter,adapter:o})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:p,...n()})));},injectExternalPendingTxs:async r=>{let l=n().getAdapter(),T=[];e(c=>produce(c,m=>{let g=m.transactionsPool;r.forEach(d=>{let x=g[d.txKey];d.pending&&!x&&(g[d.txKey]=d,T.push(d));let S=d.status==="Success"||d.status==="Failed"||d.status==="Replaced";x?.pending&&S&&(x.status=d.status,x.pending=false,d.txKey&&(x.txKey=d.txKey),d.finishedTimestamp&&(x.finishedTimestamp=d.finishedTimestamp));});})),T.length>0&&await Promise.all(T.map(c=>selectAdapterByKey({adapterKey:c.adapter,adapter:l})?.checkAndInitializeTrackerInStore({tx:c,gelatoApiKey:p,...n()})));},executeTxAction:async({defaultTracker:r,actionFunction:i,params:l,...T})=>{let{desiredChainID:c,tracker:m,...g}=l,{onSuccess:d,onError:x,onReplaced:S}=T,K=D().unix();e({initialTx:{...l,actionFunction:i,localTimestamp:K,isInitializing:true}});let f=selectAdapterByKey({adapterKey:g.adapter,adapter:o}),w=u=>{e(k=>produce(k,P=>{P.initialTx&&(P.initialTx.isInitializing=false,P.initialTx.error=normalizeError(u));}));};if(!f){let u=new Error("No adapter found for this transaction.");throw w(u),u}try{let{connectorType:u,walletAddress:k}=f.getConnectorInfo();await f.checkChainForTx(c);let P=await i();if(!P){e({initialTx:void 0});return}let{tracker:R,txKey:h}=f.checkTransactionsTracker({actionTxKey:P,connectorType:u,tracker:m,gelatoApiKey:p}),O={...g,connectorType:u,from:k,tracker:R||r,chainId:setChainId(c),localTimestamp:K,txKey:h,hash:R==="ethereum"?P:void 0,pending:!1,isTrackedModalOpen:l.withTrackedModal};n().addTxToPool(O),e(L=>produce(L,I=>{I.initialTx&&(I.initialTx.isInitializing=!1,I.initialTx.lastTxKey=h);}));let z=n().transactionsPool[h];await f.checkAndInitializeTrackerInStore({tx:z,onSuccess:d,onError:x,onReplaced:S,gelatoApiKey:p,...n()});}catch(u){throw w(u),u}}}),{...a}))}var we=(o=>t=>useStore(o,t));var V=5e3,X=10;function Ee(o){let{tx:t,fetcher:s,onInitialize:p,onSuccess:a,onFailure:e,onIntervalTick:n,onReplaced:r,removeTxFromPool:i,pollingInterval:l=V,maxRetries:T=X}=o;if(!t.pending)return;p?.();let c=T,m=true,g=x=>{m&&(m=false,i&&!x?.withoutRemoving&&i(t.txKey));};(async()=>{for(;m&&c>0;)try{if(await new Promise(x=>setTimeout(x,l)),!m)break;await s({tx:t,stopPolling:g,onSuccess:a,onFailure:e,onIntervalTick:n,onReplaced:r});}catch(x){console.error(`Polling fetcher for txKey ${t.txKey} threw an error:`,x),c--;}c<=0&&(console.warn(`Polling for txKey ${t.txKey} stopped after reaching the maximum number of retries.`),e(),g());})();}export{A as TransactionStatus,N as TransactionTracker,we as createBoundedUseStore,Ae as createPulsarStore,Te as createTxInMemoryStore,Ee as initializePollingTracker,E as initializeTxTrackingStore,F as selectAllTransactions,B as selectAllTransactionsByActiveWallet,Y as selectPendingTransactions,ee as selectPendingTransactionsByActiveWallet,Z as selectTxByKey};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuwaio/pulsar-core",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "author": "Oleksandr Tkach",
6
6
  "license": "Apache-2.0",
@@ -40,18 +40,18 @@
40
40
  }
41
41
  ],
42
42
  "peerDependencies": {
43
- "@tuwaio/orbit-core": ">=0.2.7",
43
+ "@tuwaio/orbit-core": ">=0.2.8",
44
44
  "dayjs": "1.x.x",
45
45
  "immer": "11.x.x",
46
46
  "zustand": "5.x.x"
47
47
  },
48
48
  "devDependencies": {
49
- "@tuwaio/orbit-core": "^0.2.7",
50
- "dayjs": "^1.11.19",
51
- "immer": "^11.1.3",
49
+ "@tuwaio/orbit-core": "^0.2.8",
50
+ "dayjs": "^1.11.20",
51
+ "immer": "^11.1.4",
52
52
  "tsup": "^8.5.1",
53
- "typescript": "^5.9.3",
54
- "zustand": "^5.0.11"
53
+ "typescript": "^6.0.3",
54
+ "zustand": "^5.0.12"
55
55
  },
56
56
  "scripts": {
57
57
  "start": "tsup src/index.ts --watch",