@tuwaio/pulsar-core 0.9.1 → 0.10.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 +23 -4
- package/dist/index.d.ts +23 -4
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -32,7 +32,12 @@ declare enum TransactionTracker {
|
|
|
32
32
|
/** A Solana transaction, tracked by its signature through RPC (`@tuwaio/pulsar-solana`). */
|
|
33
33
|
Solana = "solana",
|
|
34
34
|
/** An ERC-4337 UserOperation, tracked by its `userOpHash` through a bundler RPC and then on-chain. */
|
|
35
|
-
ERC4337 = "erc4337"
|
|
35
|
+
ERC4337 = "erc4337",
|
|
36
|
+
/**
|
|
37
|
+
* A batch of calls sent with EIP-5792 `wallet_sendCalls`, tracked by its batch ID through the wallet's
|
|
38
|
+
* `wallet_getCallsStatus` and then on-chain (`@tuwaio/pulsar-evm`).
|
|
39
|
+
*/
|
|
40
|
+
EIP5792 = "eip5792"
|
|
36
41
|
}
|
|
37
42
|
/**
|
|
38
43
|
* Terminal status of a transaction. Trackers set it together with `pending: false`.
|
|
@@ -197,6 +202,19 @@ type SolanaTransaction = BaseTransaction & {
|
|
|
197
202
|
recentBlockhash?: string;
|
|
198
203
|
/** The slot in which the transaction was processed. */
|
|
199
204
|
slot?: number;
|
|
205
|
+
/**
|
|
206
|
+
* The commitment the transaction has reached, updated by the Solana tracker while it is pending: `processed`,
|
|
207
|
+
* `confirmed` (voted on by a supermajority, usually within a second; UIs can show the transaction as confirmed), then
|
|
208
|
+
* `finalized`, when the tracker marks it `Success`.
|
|
209
|
+
*/
|
|
210
|
+
confirmationStatus?: 'processed' | 'confirmed' | 'finalized';
|
|
211
|
+
/**
|
|
212
|
+
* The last block height at which the blockhash of the transaction is valid. When the chain passes it and the
|
|
213
|
+
* signature is still unknown, the transaction can no longer land and the Solana tracker marks it `Failed`. Saved
|
|
214
|
+
* automatically for transactions sent with `signAndSendSolanaTx` of `@tuwaio/pulsar-solana`; for transactions sent
|
|
215
|
+
* otherwise, set it with `updateTxParams`. Without it, an unknown signature fails one hour after `localTimestamp`.
|
|
216
|
+
*/
|
|
217
|
+
lastValidBlockHeight?: number;
|
|
200
218
|
};
|
|
201
219
|
/**
|
|
202
220
|
* A Starknet transaction. Reserved for a Starknet adapter; Pulsar does not ship one.
|
|
@@ -239,8 +257,9 @@ type InitialTransactionParams = Pick<BaseTransaction, 'description' | 'title' |
|
|
|
239
257
|
/** When `true`, the transaction is created with `isTrackedModalOpen: true`. */
|
|
240
258
|
withTrackedModal?: boolean;
|
|
241
259
|
/**
|
|
242
|
-
* Forces a tracker. Required for ERC-4337 (`TransactionTracker.ERC4337`)
|
|
243
|
-
*
|
|
260
|
+
* Forces a tracker. Required for ERC-4337 (`TransactionTracker.ERC4337`), EIP-5792 batches
|
|
261
|
+
* (`TransactionTracker.EIP5792`, the action returns the batch ID) and Gelato; otherwise the adapter picks one from
|
|
262
|
+
* the returned key and the connector.
|
|
244
263
|
*/
|
|
245
264
|
tracker?: TransactionTracker;
|
|
246
265
|
/**
|
|
@@ -434,7 +453,7 @@ type TransactionPool<T extends Transaction> = Record<string, T>;
|
|
|
434
453
|
/**
|
|
435
454
|
* The fields `updateTxParams` accepts: the fields trackers change while a transaction is tracked.
|
|
436
455
|
*/
|
|
437
|
-
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'>>;
|
|
456
|
+
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' | 'confirmationStatus' | 'lastValidBlockHeight'>>;
|
|
438
457
|
/**
|
|
439
458
|
* The state and actions of the core store slice created by `initializeTxTrackingStore`.
|
|
440
459
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,12 @@ declare enum TransactionTracker {
|
|
|
32
32
|
/** A Solana transaction, tracked by its signature through RPC (`@tuwaio/pulsar-solana`). */
|
|
33
33
|
Solana = "solana",
|
|
34
34
|
/** An ERC-4337 UserOperation, tracked by its `userOpHash` through a bundler RPC and then on-chain. */
|
|
35
|
-
ERC4337 = "erc4337"
|
|
35
|
+
ERC4337 = "erc4337",
|
|
36
|
+
/**
|
|
37
|
+
* A batch of calls sent with EIP-5792 `wallet_sendCalls`, tracked by its batch ID through the wallet's
|
|
38
|
+
* `wallet_getCallsStatus` and then on-chain (`@tuwaio/pulsar-evm`).
|
|
39
|
+
*/
|
|
40
|
+
EIP5792 = "eip5792"
|
|
36
41
|
}
|
|
37
42
|
/**
|
|
38
43
|
* Terminal status of a transaction. Trackers set it together with `pending: false`.
|
|
@@ -197,6 +202,19 @@ type SolanaTransaction = BaseTransaction & {
|
|
|
197
202
|
recentBlockhash?: string;
|
|
198
203
|
/** The slot in which the transaction was processed. */
|
|
199
204
|
slot?: number;
|
|
205
|
+
/**
|
|
206
|
+
* The commitment the transaction has reached, updated by the Solana tracker while it is pending: `processed`,
|
|
207
|
+
* `confirmed` (voted on by a supermajority, usually within a second; UIs can show the transaction as confirmed), then
|
|
208
|
+
* `finalized`, when the tracker marks it `Success`.
|
|
209
|
+
*/
|
|
210
|
+
confirmationStatus?: 'processed' | 'confirmed' | 'finalized';
|
|
211
|
+
/**
|
|
212
|
+
* The last block height at which the blockhash of the transaction is valid. When the chain passes it and the
|
|
213
|
+
* signature is still unknown, the transaction can no longer land and the Solana tracker marks it `Failed`. Saved
|
|
214
|
+
* automatically for transactions sent with `signAndSendSolanaTx` of `@tuwaio/pulsar-solana`; for transactions sent
|
|
215
|
+
* otherwise, set it with `updateTxParams`. Without it, an unknown signature fails one hour after `localTimestamp`.
|
|
216
|
+
*/
|
|
217
|
+
lastValidBlockHeight?: number;
|
|
200
218
|
};
|
|
201
219
|
/**
|
|
202
220
|
* A Starknet transaction. Reserved for a Starknet adapter; Pulsar does not ship one.
|
|
@@ -239,8 +257,9 @@ type InitialTransactionParams = Pick<BaseTransaction, 'description' | 'title' |
|
|
|
239
257
|
/** When `true`, the transaction is created with `isTrackedModalOpen: true`. */
|
|
240
258
|
withTrackedModal?: boolean;
|
|
241
259
|
/**
|
|
242
|
-
* Forces a tracker. Required for ERC-4337 (`TransactionTracker.ERC4337`)
|
|
243
|
-
*
|
|
260
|
+
* Forces a tracker. Required for ERC-4337 (`TransactionTracker.ERC4337`), EIP-5792 batches
|
|
261
|
+
* (`TransactionTracker.EIP5792`, the action returns the batch ID) and Gelato; otherwise the adapter picks one from
|
|
262
|
+
* the returned key and the connector.
|
|
244
263
|
*/
|
|
245
264
|
tracker?: TransactionTracker;
|
|
246
265
|
/**
|
|
@@ -434,7 +453,7 @@ type TransactionPool<T extends Transaction> = Record<string, T>;
|
|
|
434
453
|
/**
|
|
435
454
|
* The fields `updateTxParams` accepts: the fields trackers change while a transaction is tracked.
|
|
436
455
|
*/
|
|
437
|
-
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'>>;
|
|
456
|
+
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' | 'confirmationStatus' | 'lastValidBlockHeight'>>;
|
|
438
457
|
/**
|
|
439
458
|
* The state and actions of the core store slice created by `initializeTxTrackingStore`.
|
|
440
459
|
*
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var immer=require('immer'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),ce=require('dayjs'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var ce__default=/*#__PURE__*/_interopDefault(ce);var ee=(p=>(p.Ethereum="ethereum",p.Safe="safe",p.Gelato="gelato",p.Solana="solana",p.ERC4337="erc4337",p))(ee||{}),K=(l=>(l.Failed="Failed",l.Success="Success",l.Replaced="Replaced",l))(K||{});var U=100,B=300,N=10*1024,ne=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,o){super(o),this.name="PulsarTransactionValidationError",this.field=e;}};function $(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function S(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function w({field:n,value:e,maxLength:o}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((d,p)=>{let T=Array.isArray(e)?`${n}[${p}]`:n;if(typeof d!="string")throw new P(T,`${T} must be a string.`);if(d.length>o)throw new P(T,`${T} must be ${o} characters or less.`);O(T,d);});}function j(n){if(n===void 0)return;let e;try{e=JSON.stringify(n);}catch{throw new P("payload","payload must be JSON-serializable.")}if(e===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(e).length>N)throw new P("payload",`payload must be ${N} bytes or less when serialized.`);_(n);}function _(n,e="payload"){if(typeof n=="string"){O(e,n);return}n===null||typeof n!="object"||Object.entries(n).forEach(([o,l])=>{let d=`${e}.${o}`;O(d,o),_(l,d);});}function O(n,e){if(ne.some(o=>o.test(e)))throw new P(n,`${n} contains a blocked executable-like pattern.`)}function te(n){let e={...n};return delete e.pimlicoApiKey,delete e.gelatoApiKey,e}function G({maxTransactions:n,onRemoteCreate:e}){let o=false,l=new Set;return (d,p)=>{let T=async(t,r)=>{let s=p().transactionsPool[t];if(!(!e||!s||l.has(t))){l.add(t);try{await e(te(s)),d(a=>immer.produce(a,i=>{let c=i.transactionsPool[t];c&&(c.syncStatus="synced"),i.unsyncedTxKeys&&delete i.unsyncedTxKeys[t];}));}catch(a){console.warn(r,a);}finally{l.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!e||o)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){o=true;try{for(let r of t){if(!p().transactionsPool[r]){d(s=>immer.produce(s,a=>{a.unsyncedTxKeys&&delete a.unsyncedTxKeys[r];}));continue}await T(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{o=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...e&&{syncStatus:"pending-sync"}};return d(s=>immer.produce(s,a=>{if(a.lastAddedTxKey=t.txKey,t.txKey){if(e&&(a.unsyncedTxKeys||(a.unsyncedTxKeys={}),a.unsyncedTxKeys[t.txKey]=true),Object.keys(a.transactionsPool).length>=n){let c=Object.values(a.transactionsPool).sort((x,y)=>x.localTimestamp-y.localTimestamp);if(c.length>0){let x=c[0];delete a.transactionsPool[x.txKey];}}a.transactionsPool[t.txKey]=r;}})),e&&t.txKey&&T(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{d(a=>immer.produce(a,i=>{let c=i.transactionsPool[t];c&&Object.assign(c,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(a=>{console.error("[Pulsar] Terminal reconciliation failed:",a);});},removeTxFromPool:t=>{d(r=>immer.produce(r,s=>{delete s.transactionsPool[t];}));},closeTxTrackedModal:t=>{d(r=>immer.produce(r,s=>{if(t&&s.transactionsPool[t]){let a=s.transactionsPool[t];s.transactionsPool[t]={...a,isTrackedModalOpen:false};}s.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var D=n=>Object.values(n).sort((e,o)=>Number(e.localTimestamp)-Number(o.localTimestamp)),ke=n=>D(n).filter(e=>e.pending),ve=(n,e)=>n[e],re=(n,e)=>D(n).filter(o=>o.from.toLowerCase()===e.toLowerCase()),Ie=(n,e)=>re(n,e).filter(o=>o.pending);var{produce:q}=new immer.Immer({autoFreeze:false}),ae=n=>n.filter(e=>{try{return S(e),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),X=n=>n==="Success"||n==="Failed"||n==="Replaced",H=(n,e)=>{let o=n[e.txKey];if(o){if(X(o.status))return false;if(o.pending){if(X(e.status))return n[e.txKey]={...o,...e},true;let l=typeof e.confirmations=="number"?e.confirmations:0,d=typeof o.confirmations=="number"?o.confirmations:0;return l>d?(n[e.txKey]={...o,...e},true):false}}return n[e.txKey]=e,true};function Me({localTransactionsPool:n,reconcileUnsyncedTransactions:e,getHistory:o,onHistoryFetched:l}){let d=T=>({isLoading:T,isError:false}),p=T=>{if(!T)return {isLoading:false};let t=ae(T.docs);return l&&queueMicrotask(()=>l(t)),r=>q(r,s=>{let a=s.transactionsPool;for(let i of t)H(a,i);s.currentPage=T.page,s.hasMore=T.hasNextPage,s.isLoading=false;})};return vanilla.createStore()((T,t)=>({transactionsPool:n,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{T(s=>q(s,a=>{let i=a.transactionsPool;for(let c of Object.values(r))H(i,c);}));},fetchInitial:async r=>{if(!(!o||!r)){T(d(true)),e&&await e();try{let s=await o({page:1,walletAddress:r});T(p(s));}catch(s){console.error("[Pulsar] Failed to fetch initial transaction history:",s),T({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:s,isLoading:a,currentPage:i}=t();if(!(!o||!s||a||!r)){T(d(true));try{let c=i+1,x=await o({page:c,walletAddress:r});T(p(x));}catch(c){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,c),T({isLoading:false,isError:true});}}}}))}function Ye({adapter:n,maxTransactions:e=50,onRemoteCreate:o,gelatoApiKey:l,beforeTxProcess:d,abortOnTxError:p,...T}){return vanilla.createStore()(middleware.persist((t,r)=>({...G({maxTransactions:e,onRemoteCreate:o})(t,r),getAdapter:()=>n,initializeTransactionsPool:async()=>{let a=Object.values(r().transactionsPool).filter(i=>i.pending).filter(i=>{try{return S(i),!0}catch(c){return console.warn("[Pulsar] Removed invalid persisted transaction:",c),r().removeTxFromPool(i.txKey),false}});await Promise.all(a.map(i=>orbitCore.selectAdapterByKey({adapterKey:i.adapter,adapter:n})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:l,...r()})));},injectExternalPendingTxs:async s=>{let i=r().getAdapter(),c=[],x=s.filter(y=>{try{return S(y),!0}catch(m){return console.warn("[Pulsar] Skipped invalid remote transaction:",m),false}});t(y=>immer.produce(y,m=>{let b=m.transactionsPool;x.forEach(u=>{let g=b[u.txKey];u.pending&&!g&&(b[u.txKey]=u,c.push(u));let I=u.status==="Success"||u.status==="Failed"||u.status==="Replaced";g?.pending&&I&&(g.status=u.status,g.pending=false,u.txKey&&(g.txKey=u.txKey),u.finishedTimestamp&&(g.finishedTimestamp=u.finishedTimestamp));});})),c.length>0&&await Promise.all(c.map(y=>orbitCore.selectAdapterByKey({adapterKey:y.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:y,gelatoApiKey:l,...r()})));},executeTxAction:async({defaultTracker:s,actionFunction:a,params:i,beforeTxProcess:c,abortOnTxError:x,...y})=>{let m=x??p??true,b=ce__default.default().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),$(i);let{desiredChainID:u,tracker:g,...I}=i,{onSuccess:W,onError:J,onReplaced:Y}=y;t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:true}});let k=orbitCore.selectAdapterByKey({adapterKey:I.adapter,adapter:n}),z=f=>{t(E=>immer.produce(E,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=orbitCore.normalizeError(f));}));};if(!k){let f=new Error("No adapter found for this transaction.");throw z(f),f}try{let{connectorType:f,walletAddress:E}=k.getConnectorInfo();await k.checkChainForTx(u);try{await(c??d)?.();}catch(v){if(m)throw t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:!1,error:orbitCore.normalizeError(v)}}),v;console.warn("[Pulsar] beforeTxProcess failed:",v);}let h=await a();if(!h){t({initialTx:void 0});return}let{tracker:M,txKey:R}=k.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:i.gelatoApiKey??l,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey}),Q={...I,connectorType:f,from:E,tracker:M||s,chainId:orbitCore.setChainId(u),localTimestamp:b,txKey:R,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await r().addTxToPool(Q),t(v=>immer.produce(v,F=>{F.initialTx&&(F.initialTx.isInitializing=!1,F.initialTx.lastTxKey=R);}));let Z=r().transactionsPool[R];await k.checkAndInitializeTrackerInStore({tx:Z,onSuccess:W,onError:J,onReplaced:Y,gelatoApiKey:l,...r()});}catch(f){throw z(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},merge:(t,r)=>({...r,...t,initialTx:r.initialTx}),...T}))}var nn=(n=>e=>zustand.useStore(n,e));function rn({tx:n,transactionsPool:e,updateTxParams:o}){let l=e[n.txKey];return d=>(o(n.txKey,d),l&&(l={...l,...d}),l)}var pe=5e3,ue=10;function an(n){let{tx:e,fetcher:o,onInitialize:l,onSuccess:d,onFailure:p,onIntervalTick:T,onReplaced:t,removeTxFromPool:r,pollingInterval:s=pe,maxRetries:a=ue}=n;if(!e.pending)return;l?.();let i=a,c=true,x=m=>{c&&(c=false,r&&!m?.withoutRemoving&&r(e.txKey));};(async()=>{for(;c&&i>0;)try{if(await new Promise(m=>setTimeout(m,s)),!c)break;await o({tx:e,stopPolling:x,onSuccess:d,onFailure:p,onIntervalTick:T,onReplaced:t}),i=a;}catch(m){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,m),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),p(),x());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=B;exports.MAX_TRANSACTION_PAYLOAD_BYTES=N;exports.MAX_TRANSACTION_TITLE_LENGTH=U;exports.PulsarTransactionValidationError=P;exports.TransactionStatus=K;exports.TransactionTracker=ee;exports.createBoundedUseStore=nn;exports.createPulsarStore=Ye;exports.createTxInMemoryStore=Me;exports.createTxUpdater=rn;exports.initializePollingTracker=an;exports.initializeTxTrackingStore=G;exports.selectAllTransactions=D;exports.selectAllTransactionsByActiveWallet=re;exports.selectPendingTransactions=ke;exports.selectPendingTransactionsByActiveWallet=Ie;exports.selectTxByKey=ve;exports.validateInitialTransactionParams=$;exports.validateTransaction=S;
|
|
1
|
+
'use strict';var immer=require('immer'),vanilla=require('zustand/vanilla'),orbitCore=require('@tuwaio/orbit-core'),ce=require('dayjs'),middleware=require('zustand/middleware'),zustand=require('zustand');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var ce__default=/*#__PURE__*/_interopDefault(ce);var ee=(s=>(s.Ethereum="ethereum",s.Safe="safe",s.Gelato="gelato",s.Solana="solana",s.ERC4337="erc4337",s.EIP5792="eip5792",s))(ee||{}),K=(T=>(T.Failed="Failed",T.Success="Success",T.Replaced="Replaced",T))(K||{});var U=100,B=300,N=10*1024,ne=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,o){super(o),this.name="PulsarTransactionValidationError",this.field=e;}};function $(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function S(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function w({field:n,value:e,maxLength:o}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((d,p)=>{let s=Array.isArray(e)?`${n}[${p}]`:n;if(typeof d!="string")throw new P(s,`${s} must be a string.`);if(d.length>o)throw new P(s,`${s} must be ${o} characters or less.`);O(s,d);});}function j(n){if(n===void 0)return;let e;try{e=JSON.stringify(n);}catch{throw new P("payload","payload must be JSON-serializable.")}if(e===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(e).length>N)throw new P("payload",`payload must be ${N} bytes or less when serialized.`);_(n);}function _(n,e="payload"){if(typeof n=="string"){O(e,n);return}n===null||typeof n!="object"||Object.entries(n).forEach(([o,T])=>{let d=`${e}.${o}`;O(d,o),_(T,d);});}function O(n,e){if(ne.some(o=>o.test(e)))throw new P(n,`${n} contains a blocked executable-like pattern.`)}function te(n){let e={...n};return delete e.pimlicoApiKey,delete e.gelatoApiKey,e}function G({maxTransactions:n,onRemoteCreate:e}){let o=false,T=new Set;return (d,p)=>{let s=async(t,r)=>{let c=p().transactionsPool[t];if(!(!e||!c||T.has(t))){T.add(t);try{await e(te(c)),d(a=>immer.produce(a,i=>{let l=i.transactionsPool[t];l&&(l.syncStatus="synced"),i.unsyncedTxKeys&&delete i.unsyncedTxKeys[t];}));}catch(a){console.warn(r,a);}finally{T.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!e||o)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){o=true;try{for(let r of t){if(!p().transactionsPool[r]){d(c=>immer.produce(c,a=>{a.unsyncedTxKeys&&delete a.unsyncedTxKeys[r];}));continue}await s(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{o=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...e&&{syncStatus:"pending-sync"}};return d(c=>immer.produce(c,a=>{if(a.lastAddedTxKey=t.txKey,t.txKey){if(e&&(a.unsyncedTxKeys||(a.unsyncedTxKeys={}),a.unsyncedTxKeys[t.txKey]=true),Object.keys(a.transactionsPool).length>=n){let l=Object.values(a.transactionsPool).sort((x,y)=>x.localTimestamp-y.localTimestamp);if(l.length>0){let x=l[0];delete a.transactionsPool[x.txKey];}}a.transactionsPool[t.txKey]=r;}})),e&&t.txKey&&s(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{d(a=>immer.produce(a,i=>{let l=i.transactionsPool[t];l&&Object.assign(l,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(a=>{console.error("[Pulsar] Terminal reconciliation failed:",a);});},removeTxFromPool:t=>{d(r=>immer.produce(r,c=>{delete c.transactionsPool[t];}));},closeTxTrackedModal:t=>{d(r=>immer.produce(r,c=>{if(t&&c.transactionsPool[t]){let a=c.transactionsPool[t];c.transactionsPool[t]={...a,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var D=n=>Object.values(n).sort((e,o)=>Number(e.localTimestamp)-Number(o.localTimestamp)),Ae=n=>D(n).filter(e=>e.pending),ve=(n,e)=>n[e],re=(n,e)=>D(n).filter(o=>o.from.toLowerCase()===e.toLowerCase()),Ie=(n,e)=>re(n,e).filter(o=>o.pending);var{produce:q}=new immer.Immer({autoFreeze:false}),ae=n=>n.filter(e=>{try{return S(e),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),H=n=>n==="Success"||n==="Failed"||n==="Replaced",V=(n,e)=>{let o=n[e.txKey];if(o){if(H(o.status))return false;if(o.pending){if(H(e.status))return n[e.txKey]={...o,...e},true;let T=typeof e.confirmations=="number"?e.confirmations:0,d=typeof o.confirmations=="number"?o.confirmations:0;return T>d?(n[e.txKey]={...o,...e},true):false}}return n[e.txKey]=e,true};function Me({localTransactionsPool:n,reconcileUnsyncedTransactions:e,getHistory:o,onHistoryFetched:T}){let d=s=>({isLoading:s,isError:false}),p=s=>{if(!s)return {isLoading:false};let t=ae(s.docs);return T&&queueMicrotask(()=>T(t)),r=>q(r,c=>{let a=c.transactionsPool;for(let i of t)V(a,i);c.currentPage=s.page,c.hasMore=s.hasNextPage,c.isLoading=false;})};return vanilla.createStore()((s,t)=>({transactionsPool:n,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{s(c=>q(c,a=>{let i=a.transactionsPool;for(let l of Object.values(r))V(i,l);}));},fetchInitial:async r=>{if(!(!o||!r)){s(d(true)),e&&await e();try{let c=await o({page:1,walletAddress:r});s(p(c));}catch(c){console.error("[Pulsar] Failed to fetch initial transaction history:",c),s({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:c,isLoading:a,currentPage:i}=t();if(!(!o||!c||a||!r)){s(d(true));try{let l=i+1,x=await o({page:l,walletAddress:r});s(p(x));}catch(l){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,l),s({isLoading:false,isError:true});}}}}))}function Ye({adapter:n,maxTransactions:e=50,onRemoteCreate:o,gelatoApiKey:T,beforeTxProcess:d,abortOnTxError:p,...s}){return vanilla.createStore()(middleware.persist((t,r)=>({...G({maxTransactions:e,onRemoteCreate:o})(t,r),getAdapter:()=>n,initializeTransactionsPool:async()=>{let a=Object.values(r().transactionsPool).filter(i=>i.pending).filter(i=>{try{return S(i),!0}catch(l){return console.warn("[Pulsar] Removed invalid persisted transaction:",l),r().removeTxFromPool(i.txKey),false}});await Promise.all(a.map(i=>orbitCore.selectAdapterByKey({adapterKey:i.adapter,adapter:n})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:T,...r()})));},injectExternalPendingTxs:async c=>{let i=r().getAdapter(),l=[],x=c.filter(y=>{try{return S(y),!0}catch(m){return console.warn("[Pulsar] Skipped invalid remote transaction:",m),false}});t(y=>immer.produce(y,m=>{let b=m.transactionsPool;x.forEach(u=>{let g=b[u.txKey];u.pending&&!g&&(b[u.txKey]=u,l.push(u));let I=u.status==="Success"||u.status==="Failed"||u.status==="Replaced";g?.pending&&I&&(g.status=u.status,g.pending=false,u.txKey&&(g.txKey=u.txKey),u.finishedTimestamp&&(g.finishedTimestamp=u.finishedTimestamp));});})),l.length>0&&await Promise.all(l.map(y=>orbitCore.selectAdapterByKey({adapterKey:y.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:y,gelatoApiKey:T,...r()})));},executeTxAction:async({defaultTracker:c,actionFunction:a,params:i,beforeTxProcess:l,abortOnTxError:x,...y})=>{let m=x??p??true,b=ce__default.default().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),$(i);let{desiredChainID:u,tracker:g,...I}=i,{onSuccess:W,onError:J,onReplaced:Y}=y;t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:true}});let A=orbitCore.selectAdapterByKey({adapterKey:I.adapter,adapter:n}),z=f=>{t(E=>immer.produce(E,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=orbitCore.normalizeError(f));}));};if(!A){let f=new Error("No adapter found for this transaction.");throw z(f),f}try{let{connectorType:f,walletAddress:E}=A.getConnectorInfo();await A.checkChainForTx(u);try{await(l??d)?.();}catch(v){if(m)throw t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:!1,error:orbitCore.normalizeError(v)}}),v;console.warn("[Pulsar] beforeTxProcess failed:",v);}let h=await a();if(!h){t({initialTx:void 0});return}let{tracker:M,txKey:R}=A.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:i.gelatoApiKey??T,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey}),Q={...I,connectorType:f,from:E,tracker:M||c,chainId:orbitCore.setChainId(u),localTimestamp:b,txKey:R,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await r().addTxToPool(Q),t(v=>immer.produce(v,F=>{F.initialTx&&(F.initialTx.isInitializing=!1,F.initialTx.lastTxKey=R);}));let Z=r().transactionsPool[R];await A.checkAndInitializeTrackerInStore({tx:Z,onSuccess:W,onError:J,onReplaced:Y,gelatoApiKey:T,...r()});}catch(f){throw z(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},merge:(t,r)=>({...r,...t,initialTx:r.initialTx}),...s}))}var nn=(n=>e=>zustand.useStore(n,e));function rn({tx:n,transactionsPool:e,updateTxParams:o}){let T=e[n.txKey];return d=>(o(n.txKey,d),T&&(T={...T,...d}),T)}var pe=5e3,ue=10;function an(n){let{tx:e,fetcher:o,onInitialize:T,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t,removeTxFromPool:r,pollingInterval:c=pe,maxRetries:a=ue}=n;if(!e.pending)return;T?.();let i=a,l=true,x=m=>{l&&(l=false,r&&!m?.withoutRemoving&&r(e.txKey));};(async()=>{for(;l&&i>0;)try{if(await new Promise(m=>setTimeout(m,c)),!l)break;await o({tx:e,stopPolling:x,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t}),i=a;}catch(m){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,m),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),p(),x());})();}exports.MAX_TRANSACTION_DESCRIPTION_LENGTH=B;exports.MAX_TRANSACTION_PAYLOAD_BYTES=N;exports.MAX_TRANSACTION_TITLE_LENGTH=U;exports.PulsarTransactionValidationError=P;exports.TransactionStatus=K;exports.TransactionTracker=ee;exports.createBoundedUseStore=nn;exports.createPulsarStore=Ye;exports.createTxInMemoryStore=Me;exports.createTxUpdater=rn;exports.initializePollingTracker=an;exports.initializeTxTrackingStore=G;exports.selectAllTransactions=D;exports.selectAllTransactionsByActiveWallet=re;exports.selectPendingTransactions=Ae;exports.selectPendingTransactionsByActiveWallet=Ie;exports.selectTxByKey=ve;exports.validateInitialTransactionParams=$;exports.validateTransaction=S;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {Immer,produce}from'immer';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,normalizeError,setChainId}from'@tuwaio/orbit-core';import ce from'dayjs';import {persist}from'zustand/middleware';import {useStore}from'zustand';var ee=(p=>(p.Ethereum="ethereum",p.Safe="safe",p.Gelato="gelato",p.Solana="solana",p.ERC4337="erc4337",p))(ee||{}),K=(l=>(l.Failed="Failed",l.Success="Success",l.Replaced="Replaced",l))(K||{});var U=100,B=300,N=10*1024,ne=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,o){super(o),this.name="PulsarTransactionValidationError",this.field=e;}};function $(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function S(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function w({field:n,value:e,maxLength:o}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((d,p)=>{let T=Array.isArray(e)?`${n}[${p}]`:n;if(typeof d!="string")throw new P(T,`${T} must be a string.`);if(d.length>o)throw new P(T,`${T} must be ${o} characters or less.`);O(T,d);});}function j(n){if(n===void 0)return;let e;try{e=JSON.stringify(n);}catch{throw new P("payload","payload must be JSON-serializable.")}if(e===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(e).length>N)throw new P("payload",`payload must be ${N} bytes or less when serialized.`);_(n);}function _(n,e="payload"){if(typeof n=="string"){O(e,n);return}n===null||typeof n!="object"||Object.entries(n).forEach(([o,l])=>{let d=`${e}.${o}`;O(d,o),_(l,d);});}function O(n,e){if(ne.some(o=>o.test(e)))throw new P(n,`${n} contains a blocked executable-like pattern.`)}function te(n){let e={...n};return delete e.pimlicoApiKey,delete e.gelatoApiKey,e}function G({maxTransactions:n,onRemoteCreate:e}){let o=false,l=new Set;return (d,p)=>{let T=async(t,r)=>{let s=p().transactionsPool[t];if(!(!e||!s||l.has(t))){l.add(t);try{await e(te(s)),d(a=>produce(a,i=>{let c=i.transactionsPool[t];c&&(c.syncStatus="synced"),i.unsyncedTxKeys&&delete i.unsyncedTxKeys[t];}));}catch(a){console.warn(r,a);}finally{l.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!e||o)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){o=true;try{for(let r of t){if(!p().transactionsPool[r]){d(s=>produce(s,a=>{a.unsyncedTxKeys&&delete a.unsyncedTxKeys[r];}));continue}await T(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{o=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...e&&{syncStatus:"pending-sync"}};return d(s=>produce(s,a=>{if(a.lastAddedTxKey=t.txKey,t.txKey){if(e&&(a.unsyncedTxKeys||(a.unsyncedTxKeys={}),a.unsyncedTxKeys[t.txKey]=true),Object.keys(a.transactionsPool).length>=n){let c=Object.values(a.transactionsPool).sort((x,y)=>x.localTimestamp-y.localTimestamp);if(c.length>0){let x=c[0];delete a.transactionsPool[x.txKey];}}a.transactionsPool[t.txKey]=r;}})),e&&t.txKey&&T(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{d(a=>produce(a,i=>{let c=i.transactionsPool[t];c&&Object.assign(c,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(a=>{console.error("[Pulsar] Terminal reconciliation failed:",a);});},removeTxFromPool:t=>{d(r=>produce(r,s=>{delete s.transactionsPool[t];}));},closeTxTrackedModal:t=>{d(r=>produce(r,s=>{if(t&&s.transactionsPool[t]){let a=s.transactionsPool[t];s.transactionsPool[t]={...a,isTrackedModalOpen:false};}s.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var D=n=>Object.values(n).sort((e,o)=>Number(e.localTimestamp)-Number(o.localTimestamp)),ke=n=>D(n).filter(e=>e.pending),ve=(n,e)=>n[e],re=(n,e)=>D(n).filter(o=>o.from.toLowerCase()===e.toLowerCase()),Ie=(n,e)=>re(n,e).filter(o=>o.pending);var{produce:q}=new Immer({autoFreeze:false}),ae=n=>n.filter(e=>{try{return S(e),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),X=n=>n==="Success"||n==="Failed"||n==="Replaced",H=(n,e)=>{let o=n[e.txKey];if(o){if(X(o.status))return false;if(o.pending){if(X(e.status))return n[e.txKey]={...o,...e},true;let l=typeof e.confirmations=="number"?e.confirmations:0,d=typeof o.confirmations=="number"?o.confirmations:0;return l>d?(n[e.txKey]={...o,...e},true):false}}return n[e.txKey]=e,true};function Me({localTransactionsPool:n,reconcileUnsyncedTransactions:e,getHistory:o,onHistoryFetched:l}){let d=T=>({isLoading:T,isError:false}),p=T=>{if(!T)return {isLoading:false};let t=ae(T.docs);return l&&queueMicrotask(()=>l(t)),r=>q(r,s=>{let a=s.transactionsPool;for(let i of t)H(a,i);s.currentPage=T.page,s.hasMore=T.hasNextPage,s.isLoading=false;})};return createStore()((T,t)=>({transactionsPool:n,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{T(s=>q(s,a=>{let i=a.transactionsPool;for(let c of Object.values(r))H(i,c);}));},fetchInitial:async r=>{if(!(!o||!r)){T(d(true)),e&&await e();try{let s=await o({page:1,walletAddress:r});T(p(s));}catch(s){console.error("[Pulsar] Failed to fetch initial transaction history:",s),T({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:s,isLoading:a,currentPage:i}=t();if(!(!o||!s||a||!r)){T(d(true));try{let c=i+1,x=await o({page:c,walletAddress:r});T(p(x));}catch(c){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,c),T({isLoading:false,isError:true});}}}}))}function Ye({adapter:n,maxTransactions:e=50,onRemoteCreate:o,gelatoApiKey:l,beforeTxProcess:d,abortOnTxError:p,...T}){return createStore()(persist((t,r)=>({...G({maxTransactions:e,onRemoteCreate:o})(t,r),getAdapter:()=>n,initializeTransactionsPool:async()=>{let a=Object.values(r().transactionsPool).filter(i=>i.pending).filter(i=>{try{return S(i),!0}catch(c){return console.warn("[Pulsar] Removed invalid persisted transaction:",c),r().removeTxFromPool(i.txKey),false}});await Promise.all(a.map(i=>selectAdapterByKey({adapterKey:i.adapter,adapter:n})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:l,...r()})));},injectExternalPendingTxs:async s=>{let i=r().getAdapter(),c=[],x=s.filter(y=>{try{return S(y),!0}catch(m){return console.warn("[Pulsar] Skipped invalid remote transaction:",m),false}});t(y=>produce(y,m=>{let b=m.transactionsPool;x.forEach(u=>{let g=b[u.txKey];u.pending&&!g&&(b[u.txKey]=u,c.push(u));let I=u.status==="Success"||u.status==="Failed"||u.status==="Replaced";g?.pending&&I&&(g.status=u.status,g.pending=false,u.txKey&&(g.txKey=u.txKey),u.finishedTimestamp&&(g.finishedTimestamp=u.finishedTimestamp));});})),c.length>0&&await Promise.all(c.map(y=>selectAdapterByKey({adapterKey:y.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:y,gelatoApiKey:l,...r()})));},executeTxAction:async({defaultTracker:s,actionFunction:a,params:i,beforeTxProcess:c,abortOnTxError:x,...y})=>{let m=x??p??true,b=ce().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),$(i);let{desiredChainID:u,tracker:g,...I}=i,{onSuccess:W,onError:J,onReplaced:Y}=y;t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:true}});let k=selectAdapterByKey({adapterKey:I.adapter,adapter:n}),z=f=>{t(E=>produce(E,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=normalizeError(f));}));};if(!k){let f=new Error("No adapter found for this transaction.");throw z(f),f}try{let{connectorType:f,walletAddress:E}=k.getConnectorInfo();await k.checkChainForTx(u);try{await(c??d)?.();}catch(v){if(m)throw t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:!1,error:normalizeError(v)}}),v;console.warn("[Pulsar] beforeTxProcess failed:",v);}let h=await a();if(!h){t({initialTx:void 0});return}let{tracker:M,txKey:R}=k.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:i.gelatoApiKey??l,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey}),Q={...I,connectorType:f,from:E,tracker:M||s,chainId:setChainId(u),localTimestamp:b,txKey:R,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await r().addTxToPool(Q),t(v=>produce(v,F=>{F.initialTx&&(F.initialTx.isInitializing=!1,F.initialTx.lastTxKey=R);}));let Z=r().transactionsPool[R];await k.checkAndInitializeTrackerInStore({tx:Z,onSuccess:W,onError:J,onReplaced:Y,gelatoApiKey:l,...r()});}catch(f){throw z(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},merge:(t,r)=>({...r,...t,initialTx:r.initialTx}),...T}))}var nn=(n=>e=>useStore(n,e));function rn({tx:n,transactionsPool:e,updateTxParams:o}){let l=e[n.txKey];return d=>(o(n.txKey,d),l&&(l={...l,...d}),l)}var pe=5e3,ue=10;function an(n){let{tx:e,fetcher:o,onInitialize:l,onSuccess:d,onFailure:p,onIntervalTick:T,onReplaced:t,removeTxFromPool:r,pollingInterval:s=pe,maxRetries:a=ue}=n;if(!e.pending)return;l?.();let i=a,c=true,x=m=>{c&&(c=false,r&&!m?.withoutRemoving&&r(e.txKey));};(async()=>{for(;c&&i>0;)try{if(await new Promise(m=>setTimeout(m,s)),!c)break;await o({tx:e,stopPolling:x,onSuccess:d,onFailure:p,onIntervalTick:T,onReplaced:t}),i=a;}catch(m){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,m),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),p(),x());})();}export{B as MAX_TRANSACTION_DESCRIPTION_LENGTH,N as MAX_TRANSACTION_PAYLOAD_BYTES,U as MAX_TRANSACTION_TITLE_LENGTH,P as PulsarTransactionValidationError,K as TransactionStatus,ee as TransactionTracker,nn as createBoundedUseStore,Ye as createPulsarStore,Me as createTxInMemoryStore,rn as createTxUpdater,an as initializePollingTracker,G as initializeTxTrackingStore,D as selectAllTransactions,re as selectAllTransactionsByActiveWallet,ke as selectPendingTransactions,Ie as selectPendingTransactionsByActiveWallet,ve as selectTxByKey,$ as validateInitialTransactionParams,S as validateTransaction};
|
|
1
|
+
import {Immer,produce}from'immer';import {createStore}from'zustand/vanilla';import {selectAdapterByKey,normalizeError,setChainId}from'@tuwaio/orbit-core';import ce from'dayjs';import {persist}from'zustand/middleware';import {useStore}from'zustand';var ee=(s=>(s.Ethereum="ethereum",s.Safe="safe",s.Gelato="gelato",s.Solana="solana",s.ERC4337="erc4337",s.EIP5792="eip5792",s))(ee||{}),K=(T=>(T.Failed="Failed",T.Success="Success",T.Replaced="Replaced",T))(K||{});var U=100,B=300,N=10*1024,ne=[/\beval\s*\(/i,/\bFunction\s*\(/,/\bset(?:Timeout|Interval)\s*\(\s*['"`]/i,/javascript\s*:/i],P=class extends Error{field;constructor(e,o){super(o),this.name="PulsarTransactionValidationError",this.field=e;}};function $(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function S(n){w({field:"title",value:n.title,maxLength:U}),w({field:"description",value:n.description,maxLength:B}),j(n.payload);}function w({field:n,value:e,maxLength:o}){if(e===void 0)return;(Array.isArray(e)?e:[e]).forEach((d,p)=>{let s=Array.isArray(e)?`${n}[${p}]`:n;if(typeof d!="string")throw new P(s,`${s} must be a string.`);if(d.length>o)throw new P(s,`${s} must be ${o} characters or less.`);O(s,d);});}function j(n){if(n===void 0)return;let e;try{e=JSON.stringify(n);}catch{throw new P("payload","payload must be JSON-serializable.")}if(e===void 0)throw new P("payload","payload must be JSON-serializable.");if(new TextEncoder().encode(e).length>N)throw new P("payload",`payload must be ${N} bytes or less when serialized.`);_(n);}function _(n,e="payload"){if(typeof n=="string"){O(e,n);return}n===null||typeof n!="object"||Object.entries(n).forEach(([o,T])=>{let d=`${e}.${o}`;O(d,o),_(T,d);});}function O(n,e){if(ne.some(o=>o.test(e)))throw new P(n,`${n} contains a blocked executable-like pattern.`)}function te(n){let e={...n};return delete e.pimlicoApiKey,delete e.gelatoApiKey,e}function G({maxTransactions:n,onRemoteCreate:e}){let o=false,T=new Set;return (d,p)=>{let s=async(t,r)=>{let c=p().transactionsPool[t];if(!(!e||!c||T.has(t))){T.add(t);try{await e(te(c)),d(a=>produce(a,i=>{let l=i.transactionsPool[t];l&&(l.syncStatus="synced"),i.unsyncedTxKeys&&delete i.unsyncedTxKeys[t];}));}catch(a){console.warn(r,a);}finally{T.delete(t);}}};return {transactionsPool:{},lastAddedTxKey:void 0,initialTx:void 0,unsyncedTxKeys:{},reconcileUnsyncedTransactions:async()=>{if(!e||o)return;let t=Object.keys(p().unsyncedTxKeys||{});if(t.length!==0){o=true;try{for(let r of t){if(!p().transactionsPool[r]){d(c=>produce(c,a=>{a.unsyncedTxKeys&&delete a.unsyncedTxKeys[r];}));continue}await s(r,`[Pulsar] Failed to reconcile tx ${r}:`);}}finally{o=false;}}},addTxToPool:t=>{S(t);let r={...t,pending:true,...e&&{syncStatus:"pending-sync"}};return d(c=>produce(c,a=>{if(a.lastAddedTxKey=t.txKey,t.txKey){if(e&&(a.unsyncedTxKeys||(a.unsyncedTxKeys={}),a.unsyncedTxKeys[t.txKey]=true),Object.keys(a.transactionsPool).length>=n){let l=Object.values(a.transactionsPool).sort((x,y)=>x.localTimestamp-y.localTimestamp);if(l.length>0){let x=l[0];delete a.transactionsPool[x.txKey];}}a.transactionsPool[t.txKey]=r;}})),e&&t.txKey&&s(t.txKey,"[Pulsar] onRemoteCreate failed, transaction queued for background sync:"),Promise.resolve()},updateTxParams:(t,r)=>{d(a=>produce(a,i=>{let l=i.transactionsPool[t];l&&Object.assign(l,r);})),(r.status==="Success"||r.status==="Failed"||r.status==="Replaced")&&p().reconcileUnsyncedTransactions&&p().unsyncedTxKeys?.[t]&&p().reconcileUnsyncedTransactions().catch(a=>{console.error("[Pulsar] Terminal reconciliation failed:",a);});},removeTxFromPool:t=>{d(r=>produce(r,c=>{delete c.transactionsPool[t];}));},closeTxTrackedModal:t=>{d(r=>produce(r,c=>{if(t&&c.transactionsPool[t]){let a=c.transactionsPool[t];c.transactionsPool[t]={...a,isTrackedModalOpen:false};}c.initialTx=void 0;}));},getLastTxKey:()=>p().lastAddedTxKey}}}var D=n=>Object.values(n).sort((e,o)=>Number(e.localTimestamp)-Number(o.localTimestamp)),Ae=n=>D(n).filter(e=>e.pending),ve=(n,e)=>n[e],re=(n,e)=>D(n).filter(o=>o.from.toLowerCase()===e.toLowerCase()),Ie=(n,e)=>re(n,e).filter(o=>o.pending);var{produce:q}=new Immer({autoFreeze:false}),ae=n=>n.filter(e=>{try{return S(e),!0}catch(o){return console.warn("[Pulsar] Skipped invalid transaction from history:",o),false}}),H=n=>n==="Success"||n==="Failed"||n==="Replaced",V=(n,e)=>{let o=n[e.txKey];if(o){if(H(o.status))return false;if(o.pending){if(H(e.status))return n[e.txKey]={...o,...e},true;let T=typeof e.confirmations=="number"?e.confirmations:0,d=typeof o.confirmations=="number"?o.confirmations:0;return T>d?(n[e.txKey]={...o,...e},true):false}}return n[e.txKey]=e,true};function Me({localTransactionsPool:n,reconcileUnsyncedTransactions:e,getHistory:o,onHistoryFetched:T}){let d=s=>({isLoading:s,isError:false}),p=s=>{if(!s)return {isLoading:false};let t=ae(s.docs);return T&&queueMicrotask(()=>T(t)),r=>q(r,c=>{let a=c.transactionsPool;for(let i of t)V(a,i);c.currentPage=s.page,c.hasMore=s.hasNextPage,c.isLoading=false;})};return createStore()((s,t)=>({transactionsPool:n,isLoading:false,isError:false,hasMore:false,currentPage:1,syncWithLocalPool:r=>{s(c=>q(c,a=>{let i=a.transactionsPool;for(let l of Object.values(r))V(i,l);}));},fetchInitial:async r=>{if(!(!o||!r)){s(d(true)),e&&await e();try{let c=await o({page:1,walletAddress:r});s(p(c));}catch(c){console.error("[Pulsar] Failed to fetch initial transaction history:",c),s({isLoading:false,isError:true});}}},fetchNextPage:async r=>{let{hasMore:c,isLoading:a,currentPage:i}=t();if(!(!o||!c||a||!r)){s(d(true));try{let l=i+1,x=await o({page:l,walletAddress:r});s(p(x));}catch(l){console.error(`[Pulsar] Failed to fetch transaction history page ${i+1}:`,l),s({isLoading:false,isError:true});}}}}))}function Ye({adapter:n,maxTransactions:e=50,onRemoteCreate:o,gelatoApiKey:T,beforeTxProcess:d,abortOnTxError:p,...s}){return createStore()(persist((t,r)=>({...G({maxTransactions:e,onRemoteCreate:o})(t,r),getAdapter:()=>n,initializeTransactionsPool:async()=>{let a=Object.values(r().transactionsPool).filter(i=>i.pending).filter(i=>{try{return S(i),!0}catch(l){return console.warn("[Pulsar] Removed invalid persisted transaction:",l),r().removeTxFromPool(i.txKey),false}});await Promise.all(a.map(i=>selectAdapterByKey({adapterKey:i.adapter,adapter:n})?.checkAndInitializeTrackerInStore({tx:i,gelatoApiKey:T,...r()})));},injectExternalPendingTxs:async c=>{let i=r().getAdapter(),l=[],x=c.filter(y=>{try{return S(y),!0}catch(m){return console.warn("[Pulsar] Skipped invalid remote transaction:",m),false}});t(y=>produce(y,m=>{let b=m.transactionsPool;x.forEach(u=>{let g=b[u.txKey];u.pending&&!g&&(b[u.txKey]=u,l.push(u));let I=u.status==="Success"||u.status==="Failed"||u.status==="Replaced";g?.pending&&I&&(g.status=u.status,g.pending=false,u.txKey&&(g.txKey=u.txKey),u.finishedTimestamp&&(g.finishedTimestamp=u.finishedTimestamp));});})),l.length>0&&await Promise.all(l.map(y=>selectAdapterByKey({adapterKey:y.adapter,adapter:i})?.checkAndInitializeTrackerInStore({tx:y,gelatoApiKey:T,...r()})));},executeTxAction:async({defaultTracker:c,actionFunction:a,params:i,beforeTxProcess:l,abortOnTxError:x,...y})=>{let m=x??p??true,b=ce().unix();r().reconcileUnsyncedTransactions().catch(f=>console.error("[Pulsar] Reconciliation failed:",f)),$(i);let{desiredChainID:u,tracker:g,...I}=i,{onSuccess:W,onError:J,onReplaced:Y}=y;t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:true}});let A=selectAdapterByKey({adapterKey:I.adapter,adapter:n}),z=f=>{t(E=>produce(E,h=>{h.initialTx&&(h.initialTx.isInitializing=false,h.initialTx.error=normalizeError(f));}));};if(!A){let f=new Error("No adapter found for this transaction.");throw z(f),f}try{let{connectorType:f,walletAddress:E}=A.getConnectorInfo();await A.checkChainForTx(u);try{await(l??d)?.();}catch(v){if(m)throw t({initialTx:{...i,actionFunction:a,localTimestamp:b,isInitializing:!1,error:normalizeError(v)}}),v;console.warn("[Pulsar] beforeTxProcess failed:",v);}let h=await a();if(!h){t({initialTx:void 0});return}let{tracker:M,txKey:R}=A.checkTransactionsTracker({actionTxKey:h,connectorType:f,tracker:g,gelatoApiKey:i.gelatoApiKey??T,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey}),Q={...I,connectorType:f,from:E,tracker:M||c,chainId:setChainId(u),localTimestamp:b,txKey:R,bundlerUrl:i.bundlerUrl,pimlicoApiKey:i.pimlicoApiKey,hash:M==="ethereum"?h:void 0,pending:!1,isTrackedModalOpen:i.withTrackedModal};await r().addTxToPool(Q),t(v=>produce(v,F=>{F.initialTx&&(F.initialTx.isInitializing=!1,F.initialTx.lastTxKey=R);}));let Z=r().transactionsPool[R];await A.checkAndInitializeTrackerInStore({tx:Z,onSuccess:W,onError:J,onReplaced:Y,gelatoApiKey:T,...r()});}catch(f){throw z(f),f}}}),{partialize:t=>{let r={...t};return delete r.initialTx,r},merge:(t,r)=>({...r,...t,initialTx:r.initialTx}),...s}))}var nn=(n=>e=>useStore(n,e));function rn({tx:n,transactionsPool:e,updateTxParams:o}){let T=e[n.txKey];return d=>(o(n.txKey,d),T&&(T={...T,...d}),T)}var pe=5e3,ue=10;function an(n){let{tx:e,fetcher:o,onInitialize:T,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t,removeTxFromPool:r,pollingInterval:c=pe,maxRetries:a=ue}=n;if(!e.pending)return;T?.();let i=a,l=true,x=m=>{l&&(l=false,r&&!m?.withoutRemoving&&r(e.txKey));};(async()=>{for(;l&&i>0;)try{if(await new Promise(m=>setTimeout(m,c)),!l)break;await o({tx:e,stopPolling:x,onSuccess:d,onFailure:p,onIntervalTick:s,onReplaced:t}),i=a;}catch(m){console.error(`Polling fetcher for txKey ${e.txKey} threw an error:`,m),i--;}i<=0&&(console.warn(`Polling for txKey ${e.txKey} stopped after reaching the maximum number of retries.`),p(),x());})();}export{B as MAX_TRANSACTION_DESCRIPTION_LENGTH,N as MAX_TRANSACTION_PAYLOAD_BYTES,U as MAX_TRANSACTION_TITLE_LENGTH,P as PulsarTransactionValidationError,K as TransactionStatus,ee as TransactionTracker,nn as createBoundedUseStore,Ye as createPulsarStore,Me as createTxInMemoryStore,rn as createTxUpdater,an as initializePollingTracker,G as initializeTxTrackingStore,D as selectAllTransactions,re as selectAllTransactionsByActiveWallet,Ae as selectPendingTransactions,Ie as selectPendingTransactionsByActiveWallet,ve as selectTxByKey,$ as validateInitialTransactionParams,S as validateTransaction};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuwaio/pulsar-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Oleksandr Tkach",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"zustand": "5.x.x"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@tuwaio/orbit-core": "^0.5.
|
|
54
|
+
"@tuwaio/orbit-core": "^0.5.1",
|
|
55
55
|
"dayjs": "^1.11.23",
|
|
56
56
|
"immer": "^11.1.21",
|
|
57
57
|
"tsup": "^8.5.1",
|