@tuwaio/pulsar-core 0.6.1 → 0.6.3
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/README.md +52 -1
- package/dist/index.d.mts +47 -4
- package/dist/index.d.ts +47 -4
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -73,6 +73,10 @@ export const usePulsarStore = createBoundedUseStore(
|
|
|
73
73
|
name: storageName,
|
|
74
74
|
adapter: pulsarEvmAdapter(config, appChains),
|
|
75
75
|
maxTransactions: 100, // Optional: defaults to 50
|
|
76
|
+
beforeTxProcess: async () => {
|
|
77
|
+
// Optional global preflight. Throw here to block a transaction before wallet interaction.
|
|
78
|
+
await assertUserCanSubmitTransactions();
|
|
79
|
+
},
|
|
76
80
|
}),
|
|
77
81
|
);
|
|
78
82
|
```
|
|
@@ -84,6 +88,53 @@ To prevent the `localStorage` from growing indefinitely, Pulsar Core implements
|
|
|
84
88
|
- **Maximum Transactions:** By default, the store keeps the last **50** transactions. You can customize this via the `maxTransactions` property in the `createPulsarStore` config.
|
|
85
89
|
- **Eviction Process:** When the pool exceeds the `maxTransactions` limit, the oldest transaction (based on `localTimestamp`) is automatically removed from the state and storage when a new one is added.
|
|
86
90
|
|
|
91
|
+
### Transaction Metadata Safety
|
|
92
|
+
|
|
93
|
+
Pulsar validates transaction metadata before it creates `initialTx`, calls the wallet action, writes to the local transaction pool, persists to `localStorage`, or calls `onRemoteCreate`.
|
|
94
|
+
|
|
95
|
+
- `title`: each string must be **100 characters or less**.
|
|
96
|
+
- `description`: each string must be **300 characters or less**.
|
|
97
|
+
- `payload`: must be JSON-serializable and **10KB or less** after UTF-8 JSON serialization.
|
|
98
|
+
- `title`, `description`, and payload string values reject executable-like patterns such as `eval(`, `Function(`, `setTimeout("...")`, `setInterval("...")`, and `javascript:`.
|
|
99
|
+
|
|
100
|
+
This validation is a defensive metadata gate, not a replacement for output escaping or HTML sanitization in UI code.
|
|
101
|
+
|
|
102
|
+
Invalid transactions are rejected before execution. Invalid pending transactions restored from persisted storage are removed during `initializeTransactionsPool()`. Invalid remote transactions passed to `injectExternalPendingTxs()` are skipped with a warning so the rest of the batch can still sync.
|
|
103
|
+
|
|
104
|
+
### `beforeTxProcess`
|
|
105
|
+
|
|
106
|
+
Use `beforeTxProcess` for custom preflight policies such as auth checks, feature flags, rate limits, or application-level transaction guards. The callback receives no transaction metadata; throw an error to block the transaction before initialization or wallet interaction.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const store = createPulsarStore<TransactionUnion>({
|
|
110
|
+
name: storageName,
|
|
111
|
+
adapter: pulsarEvmAdapter(config, appChains),
|
|
112
|
+
beforeTxProcess: async () => {
|
|
113
|
+
await assertUserCanSubmitTransactions();
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
You can override the global callback for one transaction by passing `beforeTxProcess` to `executeTxAction`.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
await store.getState().executeTxAction({
|
|
122
|
+
actionFunction: sendSwap,
|
|
123
|
+
beforeTxProcess: async () => {
|
|
124
|
+
await assertSwapIsEnabled();
|
|
125
|
+
},
|
|
126
|
+
params: {
|
|
127
|
+
adapter: OrbitAdapter.EVM,
|
|
128
|
+
desiredChainID: 1,
|
|
129
|
+
type: 'SWAP',
|
|
130
|
+
title: 'Swap',
|
|
131
|
+
description: 'Swap tokens',
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
When a local callback is provided to `executeTxAction`, it replaces the global callback for that action.
|
|
137
|
+
|
|
87
138
|
### The Returned Store API
|
|
88
139
|
|
|
89
140
|
The `createPulsarStore` function returns a vanilla Zustand store with the following state and actions:
|
|
@@ -96,7 +147,7 @@ The `createPulsarStore` function returns a vanilla Zustand store with the follow
|
|
|
96
147
|
|
|
97
148
|
#### **Actions**
|
|
98
149
|
|
|
99
|
-
- `executeTxAction(params)`: The primary, all-in-one function for initiating, sending, and tracking a new transaction.
|
|
150
|
+
- `executeTxAction(params)`: The primary, all-in-one function for initiating, sending, and tracking a new transaction. It runs `beforeTxProcess` and metadata validation before wallet interaction.
|
|
100
151
|
- `initializeTransactionsPool()`: An async function to re-initialize trackers for any pending transactions found in storage. **This is crucial for resuming tracking after a page reload.**
|
|
101
152
|
- `addTxToPool(tx)`: Adds a new transaction directly to the tracking pool.
|
|
102
153
|
- `updateTxParams(txKey, fields)`: Updates one or more properties of an existing transaction in the pool.
|
package/dist/index.d.mts
CHANGED
|
@@ -55,6 +55,8 @@ type BaseTransaction = {
|
|
|
55
55
|
chainId: number | string;
|
|
56
56
|
/**
|
|
57
57
|
* User-facing description. Can be a single string for all states, or a tuple for specific states.
|
|
58
|
+
* Each string is validated before execution and persistence. It must be 300 characters or less and must not contain
|
|
59
|
+
* executable-like patterns such as `eval(` or `javascript:`.
|
|
58
60
|
* @example
|
|
59
61
|
* // A single description for all states
|
|
60
62
|
* description: 'Swap 1 ETH for 1,500 USDC'
|
|
@@ -74,7 +76,10 @@ type BaseTransaction = {
|
|
|
74
76
|
isTrackedModalOpen?: boolean;
|
|
75
77
|
/** The local timestamp (in seconds) when the transaction was initiated by the user. */
|
|
76
78
|
localTimestamp: number;
|
|
77
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Custom JSON-serializable data (strings or numbers) to associate with the transaction.
|
|
81
|
+
* The serialized UTF-8 payload must be 10KB or less and string values must not contain executable-like patterns.
|
|
82
|
+
*/
|
|
78
83
|
payload?: Record<string, string | number>;
|
|
79
84
|
/** A flag indicating if the transaction is still awaiting on-chain confirmation. */
|
|
80
85
|
pending: boolean;
|
|
@@ -82,6 +87,8 @@ type BaseTransaction = {
|
|
|
82
87
|
status?: TransactionStatus;
|
|
83
88
|
/**
|
|
84
89
|
* User-facing title. Can be a single string for all states, or a tuple for specific states.
|
|
90
|
+
* Each string is validated before execution and persistence. It must be 100 characters or less and must not contain
|
|
91
|
+
* executable-like patterns such as `eval(` or `javascript:`.
|
|
85
92
|
* @example
|
|
86
93
|
* // A single title for all states
|
|
87
94
|
* title: 'ETH/USDC Swap'
|
|
@@ -207,11 +214,20 @@ interface SyncCallbacks<T extends Transaction> {
|
|
|
207
214
|
*/
|
|
208
215
|
onRemoteCreate?: (tx: T) => Promise<void>;
|
|
209
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Callback executed before Pulsar initializes or submits a transaction.
|
|
219
|
+
*
|
|
220
|
+
* Throw an error from this function to block the transaction before `initialTx`, wallet interaction,
|
|
221
|
+
* persistence, or remote synchronization starts.
|
|
222
|
+
*/
|
|
223
|
+
type BeforeTxProcess = () => Promise<void> | void;
|
|
210
224
|
/**
|
|
211
225
|
* The configuration object containing one or more transaction adapters.
|
|
212
226
|
* @template T The specific transaction type.
|
|
213
227
|
*/
|
|
214
228
|
type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> & {
|
|
229
|
+
/** Optional global preflight callback executed before every transaction unless locally overridden. */
|
|
230
|
+
beforeTxProcess?: BeforeTxProcess;
|
|
215
231
|
maxTransactions?: number;
|
|
216
232
|
gelatoApiKey?: string;
|
|
217
233
|
} & SyncCallbacks<T>;
|
|
@@ -363,8 +379,9 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
|
|
|
363
379
|
*
|
|
364
380
|
* @param params The parameters for handling the transaction.
|
|
365
381
|
* @param params.actionFunction The async function to execute (e.g., a smart contract write call). Must return a unique key or undefined.
|
|
366
|
-
* @param params.params The metadata for the transaction.
|
|
382
|
+
* @param params.params The metadata for the transaction. Title, description, and payload are validated before execution.
|
|
367
383
|
* @param params.defaultTracker The default tracker to use if it cannot be determined automatically.
|
|
384
|
+
* @param params.beforeTxProcess Optional local preflight callback. When provided, it overrides the global callback from `createPulsarStore`.
|
|
368
385
|
* @param params.onSuccess Callback to execute when the transaction is successfully submitted.
|
|
369
386
|
* @param params.onError Callback to execute when the transaction fails.
|
|
370
387
|
* @param params.onReplaced Callback to execute when the transaction is replaced.
|
|
@@ -373,6 +390,7 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
|
|
|
373
390
|
actionFunction: () => Promise<ActionTxKey | undefined>;
|
|
374
391
|
params: Omit<InitialTransactionParams, 'actionFunction'>;
|
|
375
392
|
defaultTracker?: TransactionTracker;
|
|
393
|
+
beforeTxProcess?: BeforeTxProcess;
|
|
376
394
|
} & TrackerCallbacks<T>) => Promise<void>;
|
|
377
395
|
/**
|
|
378
396
|
* Initializes trackers for all pending transactions in the pool.
|
|
@@ -543,7 +561,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
|
|
|
543
561
|
* @param options Configuration for the Zustand `persist` middleware.
|
|
544
562
|
* @returns A fully configured Zustand store instance.
|
|
545
563
|
*/
|
|
546
|
-
declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
|
|
564
|
+
declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, beforeTxProcess, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
|
|
547
565
|
setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
|
|
548
566
|
setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
|
|
549
567
|
persist: {
|
|
@@ -661,4 +679,29 @@ type PollingTrackerConfig<R, T extends Transaction> = {
|
|
|
661
679
|
*/
|
|
662
680
|
declare function initializePollingTracker<R, T extends Transaction>(config: PollingTrackerConfig<R, T>): void;
|
|
663
681
|
|
|
664
|
-
|
|
682
|
+
/** Maximum allowed length for each transaction title string. */
|
|
683
|
+
declare const MAX_TRANSACTION_TITLE_LENGTH = 100;
|
|
684
|
+
/** Maximum allowed length for each transaction description string. */
|
|
685
|
+
declare const MAX_TRANSACTION_DESCRIPTION_LENGTH = 300;
|
|
686
|
+
/** Maximum allowed serialized UTF-8 payload size in bytes. */
|
|
687
|
+
declare const MAX_TRANSACTION_PAYLOAD_BYTES: number;
|
|
688
|
+
/**
|
|
689
|
+
* Error thrown when transaction metadata fails Pulsar's safety limits.
|
|
690
|
+
*/
|
|
691
|
+
declare class PulsarTransactionValidationError extends Error {
|
|
692
|
+
/** The transaction field that failed validation. */
|
|
693
|
+
readonly field: string;
|
|
694
|
+
constructor(field: string, message: string);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Validates metadata used before a transaction action is executed.
|
|
698
|
+
* Throws when title, description, or payload violates Pulsar safety limits.
|
|
699
|
+
*/
|
|
700
|
+
declare function validateInitialTransactionParams(params: Omit<InitialTransactionParams, 'actionFunction'>): void;
|
|
701
|
+
/**
|
|
702
|
+
* Validates a complete transaction before it is persisted or synchronized.
|
|
703
|
+
* Throws when title, description, or payload violates Pulsar safety limits.
|
|
704
|
+
*/
|
|
705
|
+
declare function validateTransaction<T extends Transaction>(tx: T): void;
|
|
706
|
+
|
|
707
|
+
export { type ActionTxKey, type BaseTransaction, type BeforeTxProcess, type CheckTxTracker, type EvmTransaction, type IInitializeTxTrackingStore, type ITxInMemoryStore, type ITxInMemoryStoreParameters, type ITxTrackingStore, type InitialTransaction, type InitialTransactionParams, MAX_TRANSACTION_DESCRIPTION_LENGTH, MAX_TRANSACTION_PAYLOAD_BYTES, MAX_TRANSACTION_TITLE_LENGTH, type PollingFetcherParams, type PollingTrackerConfig, type PulsarAdapter, PulsarTransactionValidationError, 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, validateInitialTransactionParams, validateTransaction };
|
package/dist/index.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ type BaseTransaction = {
|
|
|
55
55
|
chainId: number | string;
|
|
56
56
|
/**
|
|
57
57
|
* User-facing description. Can be a single string for all states, or a tuple for specific states.
|
|
58
|
+
* Each string is validated before execution and persistence. It must be 300 characters or less and must not contain
|
|
59
|
+
* executable-like patterns such as `eval(` or `javascript:`.
|
|
58
60
|
* @example
|
|
59
61
|
* // A single description for all states
|
|
60
62
|
* description: 'Swap 1 ETH for 1,500 USDC'
|
|
@@ -74,7 +76,10 @@ type BaseTransaction = {
|
|
|
74
76
|
isTrackedModalOpen?: boolean;
|
|
75
77
|
/** The local timestamp (in seconds) when the transaction was initiated by the user. */
|
|
76
78
|
localTimestamp: number;
|
|
77
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Custom JSON-serializable data (strings or numbers) to associate with the transaction.
|
|
81
|
+
* The serialized UTF-8 payload must be 10KB or less and string values must not contain executable-like patterns.
|
|
82
|
+
*/
|
|
78
83
|
payload?: Record<string, string | number>;
|
|
79
84
|
/** A flag indicating if the transaction is still awaiting on-chain confirmation. */
|
|
80
85
|
pending: boolean;
|
|
@@ -82,6 +87,8 @@ type BaseTransaction = {
|
|
|
82
87
|
status?: TransactionStatus;
|
|
83
88
|
/**
|
|
84
89
|
* User-facing title. Can be a single string for all states, or a tuple for specific states.
|
|
90
|
+
* Each string is validated before execution and persistence. It must be 100 characters or less and must not contain
|
|
91
|
+
* executable-like patterns such as `eval(` or `javascript:`.
|
|
85
92
|
* @example
|
|
86
93
|
* // A single title for all states
|
|
87
94
|
* title: 'ETH/USDC Swap'
|
|
@@ -207,11 +214,20 @@ interface SyncCallbacks<T extends Transaction> {
|
|
|
207
214
|
*/
|
|
208
215
|
onRemoteCreate?: (tx: T) => Promise<void>;
|
|
209
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Callback executed before Pulsar initializes or submits a transaction.
|
|
219
|
+
*
|
|
220
|
+
* Throw an error from this function to block the transaction before `initialTx`, wallet interaction,
|
|
221
|
+
* persistence, or remote synchronization starts.
|
|
222
|
+
*/
|
|
223
|
+
type BeforeTxProcess = () => Promise<void> | void;
|
|
210
224
|
/**
|
|
211
225
|
* The configuration object containing one or more transaction adapters.
|
|
212
226
|
* @template T The specific transaction type.
|
|
213
227
|
*/
|
|
214
228
|
type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> & {
|
|
229
|
+
/** Optional global preflight callback executed before every transaction unless locally overridden. */
|
|
230
|
+
beforeTxProcess?: BeforeTxProcess;
|
|
215
231
|
maxTransactions?: number;
|
|
216
232
|
gelatoApiKey?: string;
|
|
217
233
|
} & SyncCallbacks<T>;
|
|
@@ -363,8 +379,9 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
|
|
|
363
379
|
*
|
|
364
380
|
* @param params The parameters for handling the transaction.
|
|
365
381
|
* @param params.actionFunction The async function to execute (e.g., a smart contract write call). Must return a unique key or undefined.
|
|
366
|
-
* @param params.params The metadata for the transaction.
|
|
382
|
+
* @param params.params The metadata for the transaction. Title, description, and payload are validated before execution.
|
|
367
383
|
* @param params.defaultTracker The default tracker to use if it cannot be determined automatically.
|
|
384
|
+
* @param params.beforeTxProcess Optional local preflight callback. When provided, it overrides the global callback from `createPulsarStore`.
|
|
368
385
|
* @param params.onSuccess Callback to execute when the transaction is successfully submitted.
|
|
369
386
|
* @param params.onError Callback to execute when the transaction fails.
|
|
370
387
|
* @param params.onReplaced Callback to execute when the transaction is replaced.
|
|
@@ -373,6 +390,7 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
|
|
|
373
390
|
actionFunction: () => Promise<ActionTxKey | undefined>;
|
|
374
391
|
params: Omit<InitialTransactionParams, 'actionFunction'>;
|
|
375
392
|
defaultTracker?: TransactionTracker;
|
|
393
|
+
beforeTxProcess?: BeforeTxProcess;
|
|
376
394
|
} & TrackerCallbacks<T>) => Promise<void>;
|
|
377
395
|
/**
|
|
378
396
|
* Initializes trackers for all pending transactions in the pool.
|
|
@@ -543,7 +561,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
|
|
|
543
561
|
* @param options Configuration for the Zustand `persist` middleware.
|
|
544
562
|
* @returns A fully configured Zustand store instance.
|
|
545
563
|
*/
|
|
546
|
-
declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
|
|
564
|
+
declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, beforeTxProcess, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
|
|
547
565
|
setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
|
|
548
566
|
setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
|
|
549
567
|
persist: {
|
|
@@ -661,4 +679,29 @@ type PollingTrackerConfig<R, T extends Transaction> = {
|
|
|
661
679
|
*/
|
|
662
680
|
declare function initializePollingTracker<R, T extends Transaction>(config: PollingTrackerConfig<R, T>): void;
|
|
663
681
|
|
|
664
|
-
|
|
682
|
+
/** Maximum allowed length for each transaction title string. */
|
|
683
|
+
declare const MAX_TRANSACTION_TITLE_LENGTH = 100;
|
|
684
|
+
/** Maximum allowed length for each transaction description string. */
|
|
685
|
+
declare const MAX_TRANSACTION_DESCRIPTION_LENGTH = 300;
|
|
686
|
+
/** Maximum allowed serialized UTF-8 payload size in bytes. */
|
|
687
|
+
declare const MAX_TRANSACTION_PAYLOAD_BYTES: number;
|
|
688
|
+
/**
|
|
689
|
+
* Error thrown when transaction metadata fails Pulsar's safety limits.
|
|
690
|
+
*/
|
|
691
|
+
declare class PulsarTransactionValidationError extends Error {
|
|
692
|
+
/** The transaction field that failed validation. */
|
|
693
|
+
readonly field: string;
|
|
694
|
+
constructor(field: string, message: string);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Validates metadata used before a transaction action is executed.
|
|
698
|
+
* Throws when title, description, or payload violates Pulsar safety limits.
|
|
699
|
+
*/
|
|
700
|
+
declare function validateInitialTransactionParams(params: Omit<InitialTransactionParams, 'actionFunction'>): void;
|
|
701
|
+
/**
|
|
702
|
+
* Validates a complete transaction before it is persisted or synchronized.
|
|
703
|
+
* Throws when title, description, or payload violates Pulsar safety limits.
|
|
704
|
+
*/
|
|
705
|
+
declare function validateTransaction<T extends Transaction>(tx: T): void;
|
|
706
|
+
|
|
707
|
+
export { type ActionTxKey, type BaseTransaction, type BeforeTxProcess, type CheckTxTracker, type EvmTransaction, type IInitializeTxTrackingStore, type ITxInMemoryStore, type ITxInMemoryStoreParameters, type ITxTrackingStore, type InitialTransaction, type InitialTransactionParams, MAX_TRANSACTION_DESCRIPTION_LENGTH, MAX_TRANSACTION_PAYLOAD_BYTES, MAX_TRANSACTION_TITLE_LENGTH, type PollingFetcherParams, type PollingTrackerConfig, type PulsarAdapter, PulsarTransactionValidationError, 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, validateInitialTransactionParams, validateTransaction };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var immer=require('immer'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),
|
|
1
|
+
'use strict';var immer=require('immer'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),ne=require('dayjs'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var ne__default=/*#__PURE__*/_interopDefault(ne);var z=100,B=300,N=10*1024,V=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,r){super(r),this.name="PulsarTransactionValidationError",this.field=e;}};function $(t){k({field:"title",value:t.title,maxLength:z}),k({field:"description",value:t.description,maxLength:B}),_(t.payload);}function b(t){k({field:"title",value:t.title,maxLength:z}),k({field:"description",value:t.description,maxLength:B}),_(t.payload);}function k({field:t,value:e,maxLength:r}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((n,i)=>{let a=Array.isArray(e)?`${t}[${i}]`:t;if(typeof n!="string")throw new P(a,`${a} must be a string.`);if(n.length>r)throw new P(a,`${a} must be ${r} characters or less.`);E(a,n);});}function _(t){if(t===void 0)return;let e;try{e=JSON.stringify(t);}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.`);j(t);}function j(t,e="payload"){if(typeof t=="string"){E(e,t);return}t===null||typeof t!="object"||Object.entries(t).forEach(([r,l])=>{let n=`${e}.${r}`;E(n,r),j(l,n);});}function E(t,e){if(V.some(r=>r.test(e)))throw new P(t,`${t} contains a blocked executable-like pattern.`)}function U({maxTransactions:t,onRemoteCreate:e}){return (r,l)=>({transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,addTxToPool:n=>{b(n);let i={...n,pending:true};r(a=>immer.produce(a,o=>{if(o.lastAddedTxKey=n.txKey,n.txKey){if(Object.keys(o.transactionsPool).length>=t){let d=Object.values(o.transactionsPool).sort((s,T)=>s.localTimestamp-T.localTimestamp);if(d.length>0){let s=d[0];delete o.transactionsPool[s.txKey];}}o.transactionsPool[n.txKey]=i;}})),e&&e(i).catch(a=>console.error("[Pulsar Sync] Create failed:",a));},updateTxParams:(n,i)=>{r(a=>immer.produce(a,o=>{let c=o.transactionsPool[n];c&&Object.assign(c,i);}));},removeTxFromPool:n=>{r(i=>immer.produce(i,a=>{delete a.transactionsPool[n];}));},closeTxTrackedModal:n=>{r(i=>immer.produce(i,a=>{if(n&&a.transactionsPool[n]){let o=a.transactionsPool[n];a.transactionsPool[n]={...o,isTrackedModalOpen:false};}a.initialTx=void 0;}));},getLastTxKey:()=>l().lastAddedTxKey})}var G=t=>Object.values(t).sort((e,r)=>Number(e.localTimestamp)-Number(r.localTimestamp)),xe=t=>G(t).filter(e=>e.pending),ue=(t,e)=>t[e],Y=(t,e)=>G(t).filter(r=>r.from.toLowerCase()===e.toLowerCase()),me=(t,e)=>Y(t,e).filter(r=>r.pending);var Q=(n=>(n.Ethereum="ethereum",n.Safe="safe",n.Gelato="gelato",n.Solana="solana",n))(Q||{}),R=(l=>(l.Failed="Failed",l.Success="Success",l.Replaced="Replaced",l))(R||{});var q=t=>t==="Success"||t==="Replaced",X=(t,e)=>{let r=t[e.txKey];if(r){if(q(r.status))return false;if(r.pending){if(q(e.status))return t[e.txKey]={...r,...e},true;let l=typeof e.confirmations=="number"?e.confirmations:0,n=typeof r.confirmations=="number"?r.confirmations:0;return l>n?(t[e.txKey]={...r,...e},true):false}}return t[e.txKey]=e,true};function ve({localTransactionsPool:t,getHistory:e,onHistoryFetched:r}){immer.setAutoFreeze(false);let l=i=>({isLoading:i,isError:false}),n=i=>i?(r&&queueMicrotask(()=>r(i.docs)),a=>immer.produce(a,o=>{let c=o.transactionsPool;for(let d of i.docs)X(c,d);o.currentPage=i.page,o.hasMore=i.hasNextPage,o.isLoading=false;})):a=>a;return vanilla.createStore()((i,a)=>({transactionsPool:t,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:o=>{i(c=>immer.produce(c,d=>{let s=d.transactionsPool;for(let T of Object.values(o))X(s,T);}));},fetchInitial:async o=>{if(!(!e||!o)){i(l(true));try{let c=await e({page:1,walletAddress:o});i(n(c));}catch(c){console.error("[Pulsar] Failed to fetch initial transaction history:",c),i({isLoading:false,isError:true});}}},fetchNextPage:async o=>{let{hasMore:c,isLoading:d,currentPage:s}=a();if(!(!e||!c||d||!o)){i(l(true));try{let T=s+1,m=await e({page:T,walletAddress:o});i(n(m));}catch(T){console.error(`[Pulsar] Failed to fetch transaction history page ${s+1}:`,T),i({isLoading:false,isError:true});}}}}))}function _e({adapter:t,maxTransactions:e=50,onRemoteCreate:r,gelatoApiKey:l,beforeTxProcess:n,...i}){return vanilla.createStore()(middleware.persist((a,o)=>({...U({maxTransactions:e,onRemoteCreate:r})(a,o),getAdapter:()=>t,initializeTransactionsPool:async()=>{let d=Object.values(o().transactionsPool).filter(s=>s.pending).filter(s=>{try{return b(s),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),o().removeTxFromPool(s.txKey),false}});await Promise.all(d.map(s=>orbitCore.selectAdapterByKey({adapterKey:s.adapter,adapter:t})?.checkAndInitializeTrackerInStore({tx:s,gelatoApiKey:l,...o()})));},injectExternalPendingTxs:async c=>{let s=o().getAdapter(),T=[],m=c.filter(x=>{try{return b(x),!0}catch(y){return console.warn("[Pulsar] Skipped invalid remote transaction:",y),false}});a(x=>immer.produce(x,y=>{let u=y.transactionsPool;m.forEach(p=>{let g=u[p.txKey];p.pending&&!g&&(u[p.txKey]=p,T.push(p));let v=p.status==="Success"||p.status==="Failed"||p.status==="Replaced";g?.pending&&v&&(g.status=p.status,g.pending=false,p.txKey&&(g.txKey=p.txKey),p.finishedTimestamp&&(g.finishedTimestamp=p.finishedTimestamp));});})),T.length>0&&await Promise.all(T.map(x=>orbitCore.selectAdapterByKey({adapterKey:x.adapter,adapter:s})?.checkAndInitializeTrackerInStore({tx:x,gelatoApiKey:l,...o()})));},executeTxAction:async({defaultTracker:c,actionFunction:d,params:s,beforeTxProcess:T,...m})=>{await(T??n)?.(),$(s);let{desiredChainID:x,tracker:y,...u}=s,{onSuccess:p,onError:g,onReplaced:v}=m,O=ne__default.default().unix();a({initialTx:{...s,actionFunction:d,localTimestamp:O,isInitializing:true}});let S=orbitCore.selectAdapterByKey({adapterKey:u.adapter,adapter:t}),L=f=>{a(I=>immer.produce(I,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=orbitCore.normalizeError(f));}));};if(!S){let f=new Error("No adapter found for this transaction.");throw L(f),f}try{let{connectorType:f,walletAddress:I}=S.getConnectorInfo();await S.checkChainForTx(x);let h=await d();if(!h){a({initialTx:void 0});return}let{tracker:M,txKey:w}=S.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:y,gelatoApiKey:l}),H={...u,connectorType:f,from:I,tracker:M||c,chainId:orbitCore.setChainId(x),localTimestamp:O,txKey:w,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:s.withTrackedModal};o().addTxToPool(H),a(J=>immer.produce(J,K=>{K.initialTx&&(K.initialTx.isInitializing=!1,K.initialTx.lastTxKey=w);}));let W=o().transactionsPool[w];await S.checkAndInitializeTrackerInStore({tx:W,onSuccess:p,onError:g,onReplaced:v,gelatoApiKey:l,...o()});}catch(f){throw L(f),f}}}),{...i}))}var De=(t=>e=>zustand.useStore(t,e));var se=5e3,ce=10;function Xe(t){let{tx:e,fetcher:r,onInitialize:l,onSuccess:n,onFailure:i,onIntervalTick:a,onReplaced:o,removeTxFromPool:c,pollingInterval:d=se,maxRetries:s=ce}=t;if(!e.pending)return;l?.();let T=s,m=true,x=u=>{m&&(m=false,c&&!u?.withoutRemoving&&c(e.txKey));};(async()=>{for(;m&&T>0;)try{if(await new Promise(u=>setTimeout(u,d)),!m)break;await r({tx:e,stopPolling:x,onSuccess:n,onFailure:i,onIntervalTick:a,onReplaced:o});}catch(u){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,u),T--;}T<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),i(),x());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=B;exports.MAX_TRANSACTION_PAYLOAD_BYTES=N;exports.MAX_TRANSACTION_TITLE_LENGTH=z;exports.PulsarTransactionValidationError=P;exports.TransactionStatus=R;exports.TransactionTracker=Q;exports.createBoundedUseStore=De;exports.createPulsarStore=_e;exports.createTxInMemoryStore=ve;exports.initializePollingTracker=Xe;exports.initializeTxTrackingStore=U;exports.selectAllTransactions=G;exports.selectAllTransactionsByActiveWallet=Y;exports.selectPendingTransactions=xe;exports.selectPendingTransactionsByActiveWallet=me;exports.selectTxByKey=ue;exports.validateInitialTransactionParams=$;exports.validateTransaction=b;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {produce,setAutoFreeze}from'immer';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,setChainId,normalizeError}from'@tuwaio/orbit-core';import
|
|
1
|
+
import {produce,setAutoFreeze}from'immer';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,setChainId,normalizeError}from'@tuwaio/orbit-core';import ne from'dayjs';import {persist}from'zustand/middleware';import {useStore}from'zustand';var z=100,B=300,N=10*1024,V=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,r){super(r),this.name="PulsarTransactionValidationError",this.field=e;}};function $(t){k({field:"title",value:t.title,maxLength:z}),k({field:"description",value:t.description,maxLength:B}),_(t.payload);}function b(t){k({field:"title",value:t.title,maxLength:z}),k({field:"description",value:t.description,maxLength:B}),_(t.payload);}function k({field:t,value:e,maxLength:r}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((n,i)=>{let a=Array.isArray(e)?`${t}[${i}]`:t;if(typeof n!="string")throw new P(a,`${a} must be a string.`);if(n.length>r)throw new P(a,`${a} must be ${r} characters or less.`);E(a,n);});}function _(t){if(t===void 0)return;let e;try{e=JSON.stringify(t);}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.`);j(t);}function j(t,e="payload"){if(typeof t=="string"){E(e,t);return}t===null||typeof t!="object"||Object.entries(t).forEach(([r,l])=>{let n=`${e}.${r}`;E(n,r),j(l,n);});}function E(t,e){if(V.some(r=>r.test(e)))throw new P(t,`${t} contains a blocked executable-like pattern.`)}function U({maxTransactions:t,onRemoteCreate:e}){return (r,l)=>({transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,addTxToPool:n=>{b(n);let i={...n,pending:true};r(a=>produce(a,o=>{if(o.lastAddedTxKey=n.txKey,n.txKey){if(Object.keys(o.transactionsPool).length>=t){let d=Object.values(o.transactionsPool).sort((s,T)=>s.localTimestamp-T.localTimestamp);if(d.length>0){let s=d[0];delete o.transactionsPool[s.txKey];}}o.transactionsPool[n.txKey]=i;}})),e&&e(i).catch(a=>console.error("[Pulsar Sync] Create failed:",a));},updateTxParams:(n,i)=>{r(a=>produce(a,o=>{let c=o.transactionsPool[n];c&&Object.assign(c,i);}));},removeTxFromPool:n=>{r(i=>produce(i,a=>{delete a.transactionsPool[n];}));},closeTxTrackedModal:n=>{r(i=>produce(i,a=>{if(n&&a.transactionsPool[n]){let o=a.transactionsPool[n];a.transactionsPool[n]={...o,isTrackedModalOpen:false};}a.initialTx=void 0;}));},getLastTxKey:()=>l().lastAddedTxKey})}var G=t=>Object.values(t).sort((e,r)=>Number(e.localTimestamp)-Number(r.localTimestamp)),xe=t=>G(t).filter(e=>e.pending),ue=(t,e)=>t[e],Y=(t,e)=>G(t).filter(r=>r.from.toLowerCase()===e.toLowerCase()),me=(t,e)=>Y(t,e).filter(r=>r.pending);var Q=(n=>(n.Ethereum="ethereum",n.Safe="safe",n.Gelato="gelato",n.Solana="solana",n))(Q||{}),R=(l=>(l.Failed="Failed",l.Success="Success",l.Replaced="Replaced",l))(R||{});var q=t=>t==="Success"||t==="Replaced",X=(t,e)=>{let r=t[e.txKey];if(r){if(q(r.status))return false;if(r.pending){if(q(e.status))return t[e.txKey]={...r,...e},true;let l=typeof e.confirmations=="number"?e.confirmations:0,n=typeof r.confirmations=="number"?r.confirmations:0;return l>n?(t[e.txKey]={...r,...e},true):false}}return t[e.txKey]=e,true};function ve({localTransactionsPool:t,getHistory:e,onHistoryFetched:r}){setAutoFreeze(false);let l=i=>({isLoading:i,isError:false}),n=i=>i?(r&&queueMicrotask(()=>r(i.docs)),a=>produce(a,o=>{let c=o.transactionsPool;for(let d of i.docs)X(c,d);o.currentPage=i.page,o.hasMore=i.hasNextPage,o.isLoading=false;})):a=>a;return createStore()((i,a)=>({transactionsPool:t,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:o=>{i(c=>produce(c,d=>{let s=d.transactionsPool;for(let T of Object.values(o))X(s,T);}));},fetchInitial:async o=>{if(!(!e||!o)){i(l(true));try{let c=await e({page:1,walletAddress:o});i(n(c));}catch(c){console.error("[Pulsar] Failed to fetch initial transaction history:",c),i({isLoading:false,isError:true});}}},fetchNextPage:async o=>{let{hasMore:c,isLoading:d,currentPage:s}=a();if(!(!e||!c||d||!o)){i(l(true));try{let T=s+1,m=await e({page:T,walletAddress:o});i(n(m));}catch(T){console.error(`[Pulsar] Failed to fetch transaction history page ${s+1}:`,T),i({isLoading:false,isError:true});}}}}))}function _e({adapter:t,maxTransactions:e=50,onRemoteCreate:r,gelatoApiKey:l,beforeTxProcess:n,...i}){return createStore()(persist((a,o)=>({...U({maxTransactions:e,onRemoteCreate:r})(a,o),getAdapter:()=>t,initializeTransactionsPool:async()=>{let d=Object.values(o().transactionsPool).filter(s=>s.pending).filter(s=>{try{return b(s),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),o().removeTxFromPool(s.txKey),false}});await Promise.all(d.map(s=>selectAdapterByKey({adapterKey:s.adapter,adapter:t})?.checkAndInitializeTrackerInStore({tx:s,gelatoApiKey:l,...o()})));},injectExternalPendingTxs:async c=>{let s=o().getAdapter(),T=[],m=c.filter(x=>{try{return b(x),!0}catch(y){return console.warn("[Pulsar] Skipped invalid remote transaction:",y),false}});a(x=>produce(x,y=>{let u=y.transactionsPool;m.forEach(p=>{let g=u[p.txKey];p.pending&&!g&&(u[p.txKey]=p,T.push(p));let v=p.status==="Success"||p.status==="Failed"||p.status==="Replaced";g?.pending&&v&&(g.status=p.status,g.pending=false,p.txKey&&(g.txKey=p.txKey),p.finishedTimestamp&&(g.finishedTimestamp=p.finishedTimestamp));});})),T.length>0&&await Promise.all(T.map(x=>selectAdapterByKey({adapterKey:x.adapter,adapter:s})?.checkAndInitializeTrackerInStore({tx:x,gelatoApiKey:l,...o()})));},executeTxAction:async({defaultTracker:c,actionFunction:d,params:s,beforeTxProcess:T,...m})=>{await(T??n)?.(),$(s);let{desiredChainID:x,tracker:y,...u}=s,{onSuccess:p,onError:g,onReplaced:v}=m,O=ne().unix();a({initialTx:{...s,actionFunction:d,localTimestamp:O,isInitializing:true}});let S=selectAdapterByKey({adapterKey:u.adapter,adapter:t}),L=f=>{a(I=>produce(I,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=normalizeError(f));}));};if(!S){let f=new Error("No adapter found for this transaction.");throw L(f),f}try{let{connectorType:f,walletAddress:I}=S.getConnectorInfo();await S.checkChainForTx(x);let h=await d();if(!h){a({initialTx:void 0});return}let{tracker:M,txKey:w}=S.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:y,gelatoApiKey:l}),H={...u,connectorType:f,from:I,tracker:M||c,chainId:setChainId(x),localTimestamp:O,txKey:w,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:s.withTrackedModal};o().addTxToPool(H),a(J=>produce(J,K=>{K.initialTx&&(K.initialTx.isInitializing=!1,K.initialTx.lastTxKey=w);}));let W=o().transactionsPool[w];await S.checkAndInitializeTrackerInStore({tx:W,onSuccess:p,onError:g,onReplaced:v,gelatoApiKey:l,...o()});}catch(f){throw L(f),f}}}),{...i}))}var De=(t=>e=>useStore(t,e));var se=5e3,ce=10;function Xe(t){let{tx:e,fetcher:r,onInitialize:l,onSuccess:n,onFailure:i,onIntervalTick:a,onReplaced:o,removeTxFromPool:c,pollingInterval:d=se,maxRetries:s=ce}=t;if(!e.pending)return;l?.();let T=s,m=true,x=u=>{m&&(m=false,c&&!u?.withoutRemoving&&c(e.txKey));};(async()=>{for(;m&&T>0;)try{if(await new Promise(u=>setTimeout(u,d)),!m)break;await r({tx:e,stopPolling:x,onSuccess:n,onFailure:i,onIntervalTick:a,onReplaced:o});}catch(u){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,u),T--;}T<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),i(),x());})();}export{B as MAX_TRANSACTION_DESCRIPTION_LENGTH,N as MAX_TRANSACTION_PAYLOAD_BYTES,z as MAX_TRANSACTION_TITLE_LENGTH,P as PulsarTransactionValidationError,R as TransactionStatus,Q as TransactionTracker,De as createBoundedUseStore,_e as createPulsarStore,ve as createTxInMemoryStore,Xe as initializePollingTracker,U as initializeTxTrackingStore,G as selectAllTransactions,Y as selectAllTransactionsByActiveWallet,xe as selectPendingTransactions,me as selectPendingTransactionsByActiveWallet,ue as selectTxByKey,$ as validateInitialTransactionParams,b as validateTransaction};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuwaio/pulsar-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Oleksandr Tkach",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,10 +51,12 @@
|
|
|
51
51
|
"immer": "^11.1.8",
|
|
52
52
|
"tsup": "^8.5.1",
|
|
53
53
|
"typescript": "^6.0.3",
|
|
54
|
+
"vitest": "^4.1.5",
|
|
54
55
|
"zustand": "^5.0.13"
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
58
|
"start": "tsup src/index.ts --watch",
|
|
58
|
-
"build": "tsup"
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"test": "vitest"
|
|
59
61
|
}
|
|
60
62
|
}
|