@tuwaio/pulsar-core 0.6.3 → 0.6.5

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 CHANGED
@@ -4,15 +4,15 @@
4
4
  [![License](https://img.shields.io/npm/l/@tuwaio/pulsar-core.svg)](./LICENSE)
5
5
  [![Build Status](https://img.shields.io/github/actions/workflow/status/TuwaIO/pulsar-core/release.yml?branch=main)](https://github.com/TuwaIO/pulsar-core/actions)
6
6
 
7
- The core, framework-agnostic engine for real-time tracking of Web3 transaction lifecycles (pending, success, failed, replaced).
7
+ Tier 3 of the TUWA Ecosystem. Framework-agnostic headless core store providing append-only localStorage transaction history ledgers.
8
8
 
9
9
  ---
10
10
 
11
11
  ## 🏛️ What is `@tuwaio/pulsar-core`?
12
12
 
13
- `@tuwaio/pulsar-core` is the central nervous system of the Pulsar ecosystem. It is a **headless** and **framework-agnostic** library, meaning it contains no UI components or framework-specific code (like React hooks).
13
+ `@tuwaio/pulsar-core` is the framework-agnostic, zero-dependency client-side ledger layer of the Pulsar ecosystem. It contains no visual interface components or framework-specific render hooks.
14
14
 
15
- Its sole purpose is to provide a powerful state machine for managing multi-chain transactions, built on top of **Zustand** (for state management) and **Immer** (for immutable updates). Think of it as a pre-configured, extensible "brain" that you can integrate into any JavaScript or TypeScript application to handle complex transaction tracking logic with ease.
15
+ Its single purpose is to act as a headless state machine providing deterministic transaction status reconciliation and client-side state persistence across user sessions. Built on top of **Zustand** and **Immer**, it maintains transaction pool stability using persistent browser storage engines to secure an append-only transaction ledger.
16
16
 
17
17
  This package exports one primary factory function: `createPulsarStore`.
18
18
 
@@ -20,11 +20,11 @@ This package exports one primary factory function: `createPulsarStore`.
20
20
 
21
21
  ## ✨ Key Features
22
22
 
23
- - **Framework-Agnostic:** Use it with React, Vue, Svelte, or even in a Node.js environment.
24
- - **Multi-Chain by Design:** Built to handle different blockchain architectures via a powerful adapter system.
25
- - **Persistent State:** Automatically saves transaction state to `localStorage`, allowing tracking to resume after a page refresh.
26
- - **Extensible:** Customize its behavior with adapters, callbacks, and custom trackers.
27
- - **Type-Safe:** Written entirely in TypeScript for a robust developer experience.
23
+ - **Framework-Agnostic:** Orchestrate and integrate state logic into any environment (React, Vue, Svelte, or Node.js).
24
+ - **Multi-Chain by Design:** Isolated state machine adapter system to support heterogeneous blockchain networks.
25
+ - **Persistent State:** Client-side state persistence powered by browser storage engines to resume lifecycle tracking across refreshes.
26
+ - **Append-Only Ledger:** High-stability transaction pool management using FIFO eviction policies.
27
+ - **Type-Safe:** Zero-compromise TypeScript implementation with explicit interfaces.
28
28
 
29
29
  ---
30
30
 
@@ -135,6 +135,40 @@ await store.getState().executeTxAction({
135
135
 
136
136
  When a local callback is provided to `executeTxAction`, it replaces the global callback for that action.
137
137
 
138
+ ### `abortOnTxError`
139
+
140
+ Use the `abortOnTxError` parameter (defaults to `true`) to control error propagation during transaction preflight and remote synchronization:
141
+
142
+ - **Preflight hook (`beforeTxProcess`)**:
143
+ - When `abortOnTxError` is `true` (default), any error thrown in `beforeTxProcess` will populate the `initialTx.error` state and throw, aborting the transaction action immediately.
144
+ - When `abortOnTxError` is `false`, any error thrown in `beforeTxProcess` is caught, logged to the console as a warning, and the transaction execution continues.
145
+ - **Remote sync hook (`onRemoteCreate`)**:
146
+ - Errors in `onRemoteCreate` **always** abort the transaction flow (the transaction is not added to the local tracking pool, `initialTx.error` is populated, and the error is thrown) regardless of the `abortOnTxError` setting.
147
+
148
+ You can set `abortOnTxError` globally during store creation or override it locally in `executeTxAction`:
149
+
150
+ ```ts
151
+ // Set globally (defaults to true if not provided)
152
+ const store = createPulsarStore<TransactionUnion>({
153
+ name: storageName,
154
+ adapter: pulsarEvmAdapter(config, appChains),
155
+ abortOnTxError: false, // Disable aborting for beforeTxProcess errors globally
156
+ });
157
+
158
+ // Override locally for a specific action
159
+ await store.getState().executeTxAction({
160
+ actionFunction: sendSwap,
161
+ abortOnTxError: true, // Force abort on beforeTxProcess errors for this action
162
+ params: {
163
+ adapter: OrbitAdapter.EVM,
164
+ desiredChainID: 1,
165
+ type: 'SWAP',
166
+ title: 'Swap',
167
+ description: 'Swap tokens',
168
+ },
169
+ });
170
+ ```
171
+
138
172
  ### The Returned Store API
139
173
 
140
174
  The `createPulsarStore` function returns a vanilla Zustand store with the following state and actions:
@@ -248,9 +282,7 @@ export type TransactionUnion = ExampleTx;
248
282
 
249
283
  const initialStore = createPulsarStore<TransactionUnion>({
250
284
  name: storageName,
251
- adapter: [
252
- pulsarEvmAdapter(config, appChains),
253
- ],
285
+ adapter: [pulsarEvmAdapter(config, appChains)],
254
286
  onRemoteCreate: async (tx) => {
255
287
  await syncTransaction(tx);
256
288
  },
package/dist/index.d.mts CHANGED
@@ -230,6 +230,8 @@ type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> &
230
230
  beforeTxProcess?: BeforeTxProcess;
231
231
  maxTransactions?: number;
232
232
  gelatoApiKey?: string;
233
+ /** Optional setting to abort the transaction if the beforeTxProcess hook or remote creation fails. Defaults to true. */
234
+ abortOnTxError?: boolean;
233
235
  } & SyncCallbacks<T>;
234
236
  /**
235
237
  * Represents a tracker for a specific transaction tied to an action and a connector.
@@ -343,7 +345,7 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
343
345
  * Adds a new transaction to the tracking pool and marks it as pending.
344
346
  * @param tx The transaction object to add.
345
347
  */
346
- addTxToPool: (tx: T) => void;
348
+ addTxToPool: (tx: T) => Promise<void>;
347
349
  /**
348
350
  * Updates one or more properties of an existing transaction in the pool.
349
351
  * @param txKey The key of the transaction to update.
@@ -391,6 +393,7 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
391
393
  params: Omit<InitialTransactionParams, 'actionFunction'>;
392
394
  defaultTracker?: TransactionTracker;
393
395
  beforeTxProcess?: BeforeTxProcess;
396
+ abortOnTxError?: boolean;
394
397
  } & TrackerCallbacks<T>) => Promise<void>;
395
398
  /**
396
399
  * Initializes trackers for all pending transactions in the pool.
@@ -561,7 +564,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
561
564
  * @param options Configuration for the Zustand `persist` middleware.
562
565
  * @returns A fully configured Zustand store instance.
563
566
  */
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"> & {
567
+ declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
565
568
  setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
566
569
  setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
567
570
  persist: {
package/dist/index.d.ts CHANGED
@@ -230,6 +230,8 @@ type PulsarAdapter<T extends Transaction> = OrbitGenericAdapter<TxAdapter<T>> &
230
230
  beforeTxProcess?: BeforeTxProcess;
231
231
  maxTransactions?: number;
232
232
  gelatoApiKey?: string;
233
+ /** Optional setting to abort the transaction if the beforeTxProcess hook or remote creation fails. Defaults to true. */
234
+ abortOnTxError?: boolean;
233
235
  } & SyncCallbacks<T>;
234
236
  /**
235
237
  * Represents a tracker for a specific transaction tied to an action and a connector.
@@ -343,7 +345,7 @@ interface IInitializeTxTrackingStore<T extends Transaction> {
343
345
  * Adds a new transaction to the tracking pool and marks it as pending.
344
346
  * @param tx The transaction object to add.
345
347
  */
346
- addTxToPool: (tx: T) => void;
348
+ addTxToPool: (tx: T) => Promise<void>;
347
349
  /**
348
350
  * Updates one or more properties of an existing transaction in the pool.
349
351
  * @param txKey The key of the transaction to update.
@@ -391,6 +393,7 @@ type ITxTrackingStore<T extends Transaction> = IInitializeTxTrackingStore<T> & {
391
393
  params: Omit<InitialTransactionParams, 'actionFunction'>;
392
394
  defaultTracker?: TransactionTracker;
393
395
  beforeTxProcess?: BeforeTxProcess;
396
+ abortOnTxError?: boolean;
394
397
  } & TrackerCallbacks<T>) => Promise<void>;
395
398
  /**
396
399
  * Initializes trackers for all pending transactions in the pool.
@@ -561,7 +564,7 @@ declare function createTxInMemoryStore<T extends Transaction>({ localTransaction
561
564
  * @param options Configuration for the Zustand `persist` middleware.
562
565
  * @returns A fully configured Zustand store instance.
563
566
  */
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"> & {
567
+ declare function createPulsarStore<T extends Transaction>({ adapter, maxTransactions, onRemoteCreate, gelatoApiKey, beforeTxProcess, abortOnTxError, ...options }: PulsarAdapter<T> & PersistOptions<ITxTrackingStore<T>>): Omit<zustand.StoreApi<ITxTrackingStore<T>>, "setState" | "persist"> & {
565
568
  setState(partial: ITxTrackingStore<T> | Partial<ITxTrackingStore<T>> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T> | Partial<ITxTrackingStore<T>>), replace?: false | undefined): unknown;
566
569
  setState(state: ITxTrackingStore<T> | ((state: ITxTrackingStore<T>) => ITxTrackingStore<T>), replace: true): unknown;
567
570
  persist: {
package/dist/index.js CHANGED
@@ -1 +1 @@
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;
1
+ 'use strict';var immer=require('immer'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),ie=require('dayjs'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var ie__default=/*#__PURE__*/_interopDefault(ie);var N=100,B=300,z=10*1024,ee=[/\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){I({field:"title",value:t.title,maxLength:N}),I({field:"description",value:t.description,maxLength:B}),_(t.payload);}function k(t){I({field:"title",value:t.title,maxLength:N}),I({field:"description",value:t.description,maxLength:B}),_(t.payload);}function I({field:t,value:e,maxLength:r}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((n,o)=>{let c=Array.isArray(e)?`${t}[${o}]`:t;if(typeof n!="string")throw new P(c,`${c} must be a string.`);if(n.length>r)throw new P(c,`${c} must be ${r} characters or less.`);R(c,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>z)throw new P("payload",`payload must be ${z} bytes or less when serialized.`);j(t);}function j(t,e="payload"){if(typeof t=="string"){R(e,t);return}t===null||typeof t!="object"||Object.entries(t).forEach(([r,l])=>{let n=`${e}.${r}`;R(n,r),j(l,n);});}function R(t,e){if(ee.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=>{k(n);let o={...n,pending:true};return (async()=>{e&&await e(o),r(s=>immer.produce(s,a=>{if(a.lastAddedTxKey=n.txKey,n.txKey){if(Object.keys(a.transactionsPool).length>=t){let d=Object.values(a.transactionsPool).sort((i,T)=>i.localTimestamp-T.localTimestamp);if(d.length>0){let i=d[0];delete a.transactionsPool[i.txKey];}}a.transactionsPool[n.txKey]=o;}}));})()},updateTxParams:(n,o)=>{r(c=>immer.produce(c,s=>{let a=s.transactionsPool[n];a&&Object.assign(a,o);}));},removeTxFromPool:n=>{r(o=>immer.produce(o,c=>{delete c.transactionsPool[n];}));},closeTxTrackedModal:n=>{r(o=>immer.produce(o,c=>{if(n&&c.transactionsPool[n]){let s=c.transactionsPool[n];c.transactionsPool[n]={...s,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>l().lastAddedTxKey})}var G=t=>Object.values(t).sort((e,r)=>Number(e.localTimestamp)-Number(r.localTimestamp)),fe=t=>G(t).filter(e=>e.pending),ge=(t,e)=>t[e],te=(t,e)=>G(t).filter(r=>r.from.toLowerCase()===e.toLowerCase()),Pe=(t,e)=>te(t,e).filter(r=>r.pending);var re=(n=>(n.Ethereum="ethereum",n.Safe="safe",n.Gelato="gelato",n.Solana="solana",n))(re||{}),O=(l=>(l.Failed="Failed",l.Success="Success",l.Replaced="Replaced",l))(O||{});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 Ee({localTransactionsPool:t,getHistory:e,onHistoryFetched:r}){immer.setAutoFreeze(false);let l=o=>({isLoading:o,isError:false}),n=o=>o?(r&&queueMicrotask(()=>r(o.docs)),c=>immer.produce(c,s=>{let a=s.transactionsPool;for(let m of o.docs)X(a,m);s.currentPage=o.page,s.hasMore=o.hasNextPage,s.isLoading=false;})):c=>c;return vanilla.createStore()((o,c)=>({transactionsPool:t,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:s=>{o(a=>immer.produce(a,m=>{let d=m.transactionsPool;for(let i of Object.values(s))X(d,i);}));},fetchInitial:async s=>{if(!(!e||!s)){o(l(true));try{let a=await e({page:1,walletAddress:s});o(n(a));}catch(a){console.error("[Pulsar] Failed to fetch initial transaction history:",a),o({isLoading:false,isError:true});}}},fetchNextPage:async s=>{let{hasMore:a,isLoading:m,currentPage:d}=c();if(!(!e||!a||m||!s)){o(l(true));try{let i=d+1,T=await e({page:i,walletAddress:s});o(n(T));}catch(i){console.error(`[Pulsar] Failed to fetch transaction history page ${d+1}:`,i),o({isLoading:false,isError:true});}}}}))}function Ge({adapter:t,maxTransactions:e=50,onRemoteCreate:r,gelatoApiKey:l,beforeTxProcess:n,abortOnTxError:o,...c}){return vanilla.createStore()(middleware.persist((s,a)=>({...U({maxTransactions:e,onRemoteCreate:r})(s,a),getAdapter:()=>t,initializeTransactionsPool:async()=>{let d=Object.values(a().transactionsPool).filter(i=>i.pending).filter(i=>{try{return k(i),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),a().removeTxFromPool(i.txKey),false}});await Promise.all(d.map(i=>orbitCore.selectAdapterByKey({adapterKey:i.adapter,adapter:t})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:l,...a()})));},injectExternalPendingTxs:async m=>{let i=a().getAdapter(),T=[],h=m.filter(f=>{try{return k(f),!0}catch(u){return console.warn("[Pulsar] Skipped invalid remote transaction:",u),false}});s(f=>immer.produce(f,u=>{let S=u.transactionsPool;h.forEach(p=>{let g=S[p.txKey];p.pending&&!g&&(S[p.txKey]=p,T.push(p));let A=p.status==="Success"||p.status==="Failed"||p.status==="Replaced";g?.pending&&A&&(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(f=>orbitCore.selectAdapterByKey({adapterKey:f.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:f,gelatoApiKey:l,...a()})));},executeTxAction:async({defaultTracker:m,actionFunction:d,params:i,beforeTxProcess:T,abortOnTxError:h,...f})=>{let u=h??o??true,S=ie__default.default().unix();try{await(T??n)?.();}catch(x){if(u)throw s({initialTx:{...i,actionFunction:d,localTimestamp:S,isInitializing:false,error:orbitCore.normalizeError(x)}}),x;console.warn("[Pulsar] beforeTxProcess failed:",x);}$(i);let{desiredChainID:p,tracker:g,...A}=i,{onSuccess:W,onError:J,onReplaced:V}=f;s({initialTx:{...i,actionFunction:d,localTimestamp:S,isInitializing:true}});let b=orbitCore.selectAdapterByKey({adapterKey:A.adapter,adapter:t}),L=x=>{s(w=>immer.produce(w,y=>{y.initialTx&&(y.initialTx.isInitializing=false,y.initialTx.error=orbitCore.normalizeError(x));}));};if(!b){let x=new Error("No adapter found for this transaction.");throw L(x),x}try{let{connectorType:x,walletAddress:w}=b.getConnectorInfo();await b.checkChainForTx(p);let y=await d();if(!y){s({initialTx:void 0});return}let{tracker:M,txKey:E}=b.checkTransactionsTracker({actionTxKey:y,connectorType:x,tracker:g,gelatoApiKey:l}),Y={...A,connectorType:x,from:w,tracker:M||m,chainId:orbitCore.setChainId(p),localTimestamp:S,txKey:E,hash:M==="ethereum"?y:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await a().addTxToPool(Y),s(Z=>immer.produce(Z,K=>{K.initialTx&&(K.initialTx.isInitializing=!1,K.initialTx.lastTxKey=E);}));let Q=a().transactionsPool[E];await b.checkAndInitializeTrackerInStore({tx:Q,onSuccess:W,onError:J,onReplaced:V,gelatoApiKey:l,...a()});}catch(x){throw L(x),x}}}),{...c}))}var He=(t=>e=>zustand.useStore(t,e));var Te=5e3,de=10;function Je(t){let{tx:e,fetcher:r,onInitialize:l,onSuccess:n,onFailure:o,onIntervalTick:c,onReplaced:s,removeTxFromPool:a,pollingInterval:m=Te,maxRetries:d=de}=t;if(!e.pending)return;l?.();let i=d,T=true,h=u=>{T&&(T=false,a&&!u?.withoutRemoving&&a(e.txKey));};(async()=>{for(;T&&i>0;)try{if(await new Promise(u=>setTimeout(u,m)),!T)break;await r({tx:e,stopPolling:h,onSuccess:n,onFailure:o,onIntervalTick:c,onReplaced:s});}catch(u){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,u),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),o(),h());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=B;exports.MAX_TRANSACTION_PAYLOAD_BYTES=z;exports.MAX_TRANSACTION_TITLE_LENGTH=N;exports.PulsarTransactionValidationError=P;exports.TransactionStatus=O;exports.TransactionTracker=re;exports.createBoundedUseStore=He;exports.createPulsarStore=Ge;exports.createTxInMemoryStore=Ee;exports.initializePollingTracker=Je;exports.initializeTxTrackingStore=U;exports.selectAllTransactions=G;exports.selectAllTransactionsByActiveWallet=te;exports.selectPendingTransactions=fe;exports.selectPendingTransactionsByActiveWallet=Pe;exports.selectTxByKey=ge;exports.validateInitialTransactionParams=$;exports.validateTransaction=k;
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 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};
1
+ import {produce,setAutoFreeze}from'immer';import {createStore}from'zustand/vanilla';import {normalizeError,selectAdapterByKey,setChainId}from'@tuwaio/orbit-core';import ie from'dayjs';import {persist}from'zustand/middleware';import {useStore}from'zustand';var N=100,B=300,z=10*1024,ee=[/\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){I({field:"title",value:t.title,maxLength:N}),I({field:"description",value:t.description,maxLength:B}),_(t.payload);}function k(t){I({field:"title",value:t.title,maxLength:N}),I({field:"description",value:t.description,maxLength:B}),_(t.payload);}function I({field:t,value:e,maxLength:r}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((n,o)=>{let c=Array.isArray(e)?`${t}[${o}]`:t;if(typeof n!="string")throw new P(c,`${c} must be a string.`);if(n.length>r)throw new P(c,`${c} must be ${r} characters or less.`);R(c,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>z)throw new P("payload",`payload must be ${z} bytes or less when serialized.`);j(t);}function j(t,e="payload"){if(typeof t=="string"){R(e,t);return}t===null||typeof t!="object"||Object.entries(t).forEach(([r,l])=>{let n=`${e}.${r}`;R(n,r),j(l,n);});}function R(t,e){if(ee.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=>{k(n);let o={...n,pending:true};return (async()=>{e&&await e(o),r(s=>produce(s,a=>{if(a.lastAddedTxKey=n.txKey,n.txKey){if(Object.keys(a.transactionsPool).length>=t){let d=Object.values(a.transactionsPool).sort((i,T)=>i.localTimestamp-T.localTimestamp);if(d.length>0){let i=d[0];delete a.transactionsPool[i.txKey];}}a.transactionsPool[n.txKey]=o;}}));})()},updateTxParams:(n,o)=>{r(c=>produce(c,s=>{let a=s.transactionsPool[n];a&&Object.assign(a,o);}));},removeTxFromPool:n=>{r(o=>produce(o,c=>{delete c.transactionsPool[n];}));},closeTxTrackedModal:n=>{r(o=>produce(o,c=>{if(n&&c.transactionsPool[n]){let s=c.transactionsPool[n];c.transactionsPool[n]={...s,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>l().lastAddedTxKey})}var G=t=>Object.values(t).sort((e,r)=>Number(e.localTimestamp)-Number(r.localTimestamp)),fe=t=>G(t).filter(e=>e.pending),ge=(t,e)=>t[e],te=(t,e)=>G(t).filter(r=>r.from.toLowerCase()===e.toLowerCase()),Pe=(t,e)=>te(t,e).filter(r=>r.pending);var re=(n=>(n.Ethereum="ethereum",n.Safe="safe",n.Gelato="gelato",n.Solana="solana",n))(re||{}),O=(l=>(l.Failed="Failed",l.Success="Success",l.Replaced="Replaced",l))(O||{});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 Ee({localTransactionsPool:t,getHistory:e,onHistoryFetched:r}){setAutoFreeze(false);let l=o=>({isLoading:o,isError:false}),n=o=>o?(r&&queueMicrotask(()=>r(o.docs)),c=>produce(c,s=>{let a=s.transactionsPool;for(let m of o.docs)X(a,m);s.currentPage=o.page,s.hasMore=o.hasNextPage,s.isLoading=false;})):c=>c;return createStore()((o,c)=>({transactionsPool:t,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:s=>{o(a=>produce(a,m=>{let d=m.transactionsPool;for(let i of Object.values(s))X(d,i);}));},fetchInitial:async s=>{if(!(!e||!s)){o(l(true));try{let a=await e({page:1,walletAddress:s});o(n(a));}catch(a){console.error("[Pulsar] Failed to fetch initial transaction history:",a),o({isLoading:false,isError:true});}}},fetchNextPage:async s=>{let{hasMore:a,isLoading:m,currentPage:d}=c();if(!(!e||!a||m||!s)){o(l(true));try{let i=d+1,T=await e({page:i,walletAddress:s});o(n(T));}catch(i){console.error(`[Pulsar] Failed to fetch transaction history page ${d+1}:`,i),o({isLoading:false,isError:true});}}}}))}function Ge({adapter:t,maxTransactions:e=50,onRemoteCreate:r,gelatoApiKey:l,beforeTxProcess:n,abortOnTxError:o,...c}){return createStore()(persist((s,a)=>({...U({maxTransactions:e,onRemoteCreate:r})(s,a),getAdapter:()=>t,initializeTransactionsPool:async()=>{let d=Object.values(a().transactionsPool).filter(i=>i.pending).filter(i=>{try{return k(i),!0}catch(T){return console.warn("[Pulsar] Removed invalid persisted transaction:",T),a().removeTxFromPool(i.txKey),false}});await Promise.all(d.map(i=>selectAdapterByKey({adapterKey:i.adapter,adapter:t})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:l,...a()})));},injectExternalPendingTxs:async m=>{let i=a().getAdapter(),T=[],h=m.filter(f=>{try{return k(f),!0}catch(u){return console.warn("[Pulsar] Skipped invalid remote transaction:",u),false}});s(f=>produce(f,u=>{let S=u.transactionsPool;h.forEach(p=>{let g=S[p.txKey];p.pending&&!g&&(S[p.txKey]=p,T.push(p));let A=p.status==="Success"||p.status==="Failed"||p.status==="Replaced";g?.pending&&A&&(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(f=>selectAdapterByKey({adapterKey:f.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:f,gelatoApiKey:l,...a()})));},executeTxAction:async({defaultTracker:m,actionFunction:d,params:i,beforeTxProcess:T,abortOnTxError:h,...f})=>{let u=h??o??true,S=ie().unix();try{await(T??n)?.();}catch(x){if(u)throw s({initialTx:{...i,actionFunction:d,localTimestamp:S,isInitializing:false,error:normalizeError(x)}}),x;console.warn("[Pulsar] beforeTxProcess failed:",x);}$(i);let{desiredChainID:p,tracker:g,...A}=i,{onSuccess:W,onError:J,onReplaced:V}=f;s({initialTx:{...i,actionFunction:d,localTimestamp:S,isInitializing:true}});let b=selectAdapterByKey({adapterKey:A.adapter,adapter:t}),L=x=>{s(w=>produce(w,y=>{y.initialTx&&(y.initialTx.isInitializing=false,y.initialTx.error=normalizeError(x));}));};if(!b){let x=new Error("No adapter found for this transaction.");throw L(x),x}try{let{connectorType:x,walletAddress:w}=b.getConnectorInfo();await b.checkChainForTx(p);let y=await d();if(!y){s({initialTx:void 0});return}let{tracker:M,txKey:E}=b.checkTransactionsTracker({actionTxKey:y,connectorType:x,tracker:g,gelatoApiKey:l}),Y={...A,connectorType:x,from:w,tracker:M||m,chainId:setChainId(p),localTimestamp:S,txKey:E,hash:M==="ethereum"?y:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await a().addTxToPool(Y),s(Z=>produce(Z,K=>{K.initialTx&&(K.initialTx.isInitializing=!1,K.initialTx.lastTxKey=E);}));let Q=a().transactionsPool[E];await b.checkAndInitializeTrackerInStore({tx:Q,onSuccess:W,onError:J,onReplaced:V,gelatoApiKey:l,...a()});}catch(x){throw L(x),x}}}),{...c}))}var He=(t=>e=>useStore(t,e));var Te=5e3,de=10;function Je(t){let{tx:e,fetcher:r,onInitialize:l,onSuccess:n,onFailure:o,onIntervalTick:c,onReplaced:s,removeTxFromPool:a,pollingInterval:m=Te,maxRetries:d=de}=t;if(!e.pending)return;l?.();let i=d,T=true,h=u=>{T&&(T=false,a&&!u?.withoutRemoving&&a(e.txKey));};(async()=>{for(;T&&i>0;)try{if(await new Promise(u=>setTimeout(u,m)),!T)break;await r({tx:e,stopPolling:h,onSuccess:n,onFailure:o,onIntervalTick:c,onReplaced:s});}catch(u){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,u),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),o(),h());})();}export{B as MAX_TRANSACTION_DESCRIPTION_LENGTH,z as MAX_TRANSACTION_PAYLOAD_BYTES,N as MAX_TRANSACTION_TITLE_LENGTH,P as PulsarTransactionValidationError,O as TransactionStatus,re as TransactionTracker,He as createBoundedUseStore,Ge as createPulsarStore,Ee as createTxInMemoryStore,Je as initializePollingTracker,U as initializeTxTrackingStore,G as selectAllTransactions,te as selectAllTransactionsByActiveWallet,fe as selectPendingTransactions,Pe as selectPendingTransactionsByActiveWallet,ge as selectTxByKey,$ as validateInitialTransactionParams,k as validateTransaction};
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@tuwaio/pulsar-core",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "private": false,
5
5
  "author": "Oleksandr Tkach",
6
6
  "license": "Apache-2.0",
7
- "description": "The core, framework-agnostic engine for real-time tracking of Web3 transaction lifecycles (pending, success, failed, replaced).",
7
+ "description": "Tier 3 of the TUWA Ecosystem. Framework-agnostic headless core store providing append-only localStorage transaction history ledgers.",
8
8
  "main": "./dist/index.js",
9
9
  "module": "./dist/index.mjs",
10
10
  "types": "./dist/index.d.ts",
@@ -46,13 +46,13 @@
46
46
  "zustand": "5.x.x"
47
47
  },
48
48
  "devDependencies": {
49
- "@tuwaio/orbit-core": "^0.2.8",
50
- "dayjs": "^1.11.20",
51
- "immer": "^11.1.8",
49
+ "@tuwaio/orbit-core": "^0.2.12",
50
+ "dayjs": "^1.11.21",
51
+ "immer": "^11.1.9",
52
52
  "tsup": "^8.5.1",
53
53
  "typescript": "^6.0.3",
54
- "vitest": "^4.1.5",
55
- "zustand": "^5.0.13"
54
+ "vitest": "^4.1.9",
55
+ "zustand": "^5.0.14"
56
56
  },
57
57
  "scripts": {
58
58
  "start": "tsup src/index.ts --watch",