@tuwaio/pulsar-solana 0.1.0 → 0.1.1

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
@@ -1,45 +1,45 @@
1
1
  # Pulsar Solana Adapter & Toolkit
2
2
 
3
- [](https://www.npmjs.com/package/@tuwaio/pulsar-solana)
4
- [](https://www.google.com/search?q=./LICENSE)
5
- [](https://github.com/TuwaIO/pulsar-core/actions)
3
+ [![NPM Version](https://img.shields.io/npm/v/@tuwaio/pulsar-solana.svg)](https://www.npmjs.com/package/@tuwaio/pulsar-solana)
4
+ [![License](https://img.shields.io/npm/l/@tuwaio/pulsar-solana.svg)](./LICENSE)
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
- An advanced toolkit for the Pulsar Engine that adds comprehensive support for tracking transactions on the Solana blockchain. It integrates seamlessly with **`@solana/kit`** and the **`@solana/wallet-adapter-react`** ecosystem.
7
+ An advanced toolkit for the Pulsar Engine that adds comprehensive support for tracking transactions on the Solana blockchain. It is built to be **wallet-library agnostic**, integrating with any setup via a simple configuration object, and uses **`gill-sdk`** for modern blockchain interaction.
8
8
 
9
9
  -----
10
10
 
11
11
  ## 🏛️ What is `@tuwaio/pulsar-solana`?
12
12
 
13
- This package is a powerful, official adapter for `@tuwaio/pulsar-core`. It contains all the necessary logic to interact with the Solana blockchain, acting as the primary logic provider for Solana-based dApps using Pulsar.
13
+ This package is a powerful, official adapter for `@tuwaio/pulsar-core`. It's designed to be universally compatible with any wallet connection library. By providing a simple wallet state object, you can leverage Pulsar's powerful transaction tracking engine without being locked into a specific ecosystem like the Wallet Standard.
14
14
 
15
- While its main export is the `solanaAdapter`, it also includes a suite of standalone trackers and utilities that can be used for advanced or custom implementations. The architecture is designed for robustness, ensuring transactions can be tracked even after a page reload by storing the relevant RPC endpoint within each transaction object.
15
+ The architecture is designed for multi-chain robustness. You can provide RPC endpoints for different Solana clusters (e.g., Mainnet Beta, Devnet), and the adapter will automatically use the correct one based on the user's connected wallet state.
16
16
 
17
17
  -----
18
18
 
19
19
  ## ✨ Core Features
20
20
 
21
- - **🔌 Simple Integration:** A single `solanaAdapter` factory function to easily plug full Solana support into `@tuwaio/pulsar-core`.
22
- - **🛰️ Robust Polling Tracker:** A durable transaction tracker that polls for signature statuses until finality, built on Pulsar's generic polling utility.
23
- - **🔗 RPC Persistence:** The tracker is designed to be self-contained, storing the `rpcUrl` within each transaction to guarantee tracking can resume after page reloads, independent of the current wallet connection.
24
- - **🌐 Network Verification:** Includes a utility (`checkSolanaChain`) to robustly verify the connected network via its genesis hash, preventing transactions from being sent to the wrong cluster.
25
- - **👤 Name Service Support:** Built-in support for **Solana Name Service (SNS)** via Bonfida, allowing for easy resolution of `.sol` domain names and associated avatars.
26
- - **🛠️ Utility Suite:** Exports a rich set of helpers, including the SNS resolvers (`getSolanaName`, `getSolanaAvatar`) and an explorer link generator (`selectSolanaTxExplorerLink`).
21
+ - **🔌 Universal Integration:** A single `solanaAdapter` factory that works with **any wallet library**.
22
+ - **🔗 Multi-Chain RPC:** Configure with a map of RPC URLs for different clusters; the adapter intelligently selects the correct one.
23
+ - **🛰️ Robust Polling Tracker:** A durable transaction tracker that polls for signature statuses until finality.
24
+ - **🌐 Network Verification:** Includes a utility (`checkSolanaChain`) to robustly verify that the connected wallet's cluster matches the one required by the transaction.
25
+ - **👤 Name Service Support:** Built-in support for **Solana Name Service (SNS)** on the currently active network.
26
+ - **💡 Optional Wallet:** The adapter can be initialized without a wallet for read-only operations, such as displaying transaction history.
27
27
 
28
28
  -----
29
29
 
30
30
  ## 💾 Installation
31
31
 
32
- This package is designed for use in a React environment with `@solana/wallet-adapter-react` and requires `@solana/kit`. Install all necessary packages together:
32
+ The package has minimal dependencies, requiring only `gill-sdk` for core functionality.
33
33
 
34
34
  ```bash
35
35
  # Using pnpm
36
- pnpm add @tuwaio/pulsar-solana @tuwaio/pulsar-core @solana/kit @solana/wallet-adapter-react zustand immer
36
+ pnpm add @tuwaio/pulsar-solana @tuwaio/pulsar-core gill zustand immer
37
37
 
38
38
  # Using npm
39
- npm install @tuwaio/pulsar-solana @tuwaio/pulsar-core @solana/kit @solana/wallet-adapter-react zustand immer
39
+ npm install @tuwaio/pulsar-solana @tuwaio/pulsar-core gill zustand immer
40
40
 
41
41
  # Using yarn
42
- yarn add @tuwaio/pulsar-solana @tuwaio/pulsar-core @solana/kit @solana/wallet-adapter-react zustand immer
42
+ yarn add @tuwaio/pulsar-solana @tuwaio/pulsar-core gill zustand immer
43
43
  ```
44
44
 
45
45
  -----
@@ -48,68 +48,98 @@ yarn add @tuwaio/pulsar-solana @tuwaio/pulsar-core @solana/kit @solana/wallet-ad
48
48
 
49
49
  ### 1\. Primary Usage: The `solanaAdapter`
50
50
 
51
- For most applications, you'll need to import the `solanaAdapter` and pass it to your `createPulsarStore` configuration. The adapter should be initialized within a React component to access the necessary hooks from `@solana/wallet-adapter-react`.
51
+ The key is to construct a simple `wallet` object from your chosen wallet library and pass it to the adapter's configuration. This object acts as a bridge, telling Pulsar the current state of the user's wallet.
52
52
 
53
- ```tsx
53
+ **Example with `@solana/wallet-adapter-react`:**
54
+
55
+ ```typescript
54
56
  // src/store/pulsarStore.ts
55
57
  import { createPulsarStore } from '@tuwaio/pulsar-core';
56
- import { solanaAdapter } from '@tuwaio/pulsar-solana';
57
- import { useConnection, useWallet } from '@solana/wallet-adapter-react';
58
+ import { solanaAdapter, SolanaAdapterWallet } from '@tuwaio/pulsar-solana';
59
+ import { useWallet } from '@solana/wallet-adapter-react';
58
60
  import { useMemo } from 'react';
59
61
 
60
- // It's best to create the store instance via a hook to access wallet/connection hooks.
62
+ // This hook creates a Pulsar store instance that is memoized and updates
63
+ // reactively when the wallet's state changes.
61
64
  export const useMyPulsarStore = () => {
62
- const wallet = useWallet();
63
- const connection = useConnection();
64
-
65
- // useMemo ensures the store is created only once.
65
+ const { publicKey, wallet } = useWallet();
66
+ const walletAdapter = useWallet();
67
+
68
+ // 1. Create the simple wallet object required by the adapter.
69
+ const pulsarWallet: SolanaAdapterWallet | undefined = useMemo(() => {
70
+ if (!publicKey || !wallet) return undefined;
71
+
72
+ return {
73
+ walletAddress: publicKey.toBase58(),
74
+ walletType: wallet.adapter.name.toLowerCase(),
75
+ // You must determine the active cluster, e.g., from your app's state.
76
+ walletActiveChain: 'mainnet',
77
+ };
78
+ }, [publicKey, wallet]);
79
+
80
+ // 2. Create the Pulsar store.
66
81
  return useMemo(() => {
67
82
  return createPulsarStore({
68
- // A unique name for localStorage persistence
69
83
  name: 'my-solana-dapp-transactions',
70
- // Provide the solanaAdapter with the hook contexts
71
- adapters: [solanaAdapter({ wallet, connection })],
72
- // Optional: Add global callbacks for all successful transactions
73
- onSucceedCallbacks: (tx) => {
74
- console.log(`Transaction ${tx.txKey} succeeded!`);
75
- },
84
+ adapters: [
85
+ solanaAdapter({
86
+ // The wallet object can be undefined if no wallet is connected.
87
+ wallet: pulsarWallet,
88
+ rpcUrls: {
89
+ 'mainnet': 'https://api.mainnet-beta.solana.com',
90
+ 'devnet': 'https://api.devnet.solana.com',
91
+ },
92
+ }),
93
+ ],
94
+ // 3. Register your transaction actions. These will receive the raw wallet
95
+ // object for signing.
96
+ actions: {
97
+ myAction: (params: { walletAdapter: any; rpc: any; }) => mySolanaAction(params)
98
+ }
76
99
  });
77
- }, [wallet, connection]);
100
+ }, [pulsarWallet]);
78
101
  };
79
102
  ```
80
103
 
81
104
  ### 2\. Initiating a Transaction
82
105
 
83
- When calling `handleTransaction`, you must now provide the `rpcUrl` for the transaction. This ensures the tracker can function correctly even after a page reload.
106
+ When calling `handleTransaction`, provide the `desiredChainId` with the **cluster moniker** (e.g., `'mainnet'`). Your `actionFunction` (registered in the store) is now fully responsible for signing and sending the transaction using the raw wallet object passed in the payload.
84
107
 
85
108
  ```tsx
86
109
  // src/components/MyTransactionButton.tsx
87
- import { usePulsar } from '@tuwaio/pulsar-react'; // Or your custom hook
88
- import { useConnection } from '@solana/wallet-adapter-react';
110
+ import { usePulsar } from '@tuwaio/pulsar-react';
89
111
  import { TransactionAdapter } from '@tuwaio/pulsar-core';
90
-
91
- // An example action that returns a transaction signature
92
- async function mySolanaAction(): Promise<string> {
93
- // ... your logic to build and send a transaction ...
94
- const signature = await sendTransaction(...);
112
+ import { useWallet } from '@solana/wallet-adapter-react'; // Your chosen wallet library
113
+
114
+ // The action function receives the raw wallet instance and an RPC client.
115
+ // It must handle the entire transaction creation and sending process.
116
+ async function mySolanaAction({ walletAdapter, rpc }): Promise<string> {
117
+ // `walletAdapter` is the original instance from your wallet library.
118
+ const { sendTransaction } = walletAdapter;
119
+
120
+ // ... your logic to build the transaction ...
121
+ const signature = await sendTransaction(transaction, rpc.connection);
95
122
  return signature;
96
123
  }
97
124
 
98
125
  function MyTransactionButton() {
99
126
  const { handleTransaction } = usePulsar();
100
- const { connection } = useConnection();
127
+ const walletAdapter = useWallet(); // Get the raw wallet object
101
128
 
102
129
  const handleClick = async () => {
130
+ if (!walletAdapter.connected) {
131
+ // Handle wallet not connected
132
+ return;
133
+ }
103
134
  await handleTransaction({
104
- actionFunction: () => mySolanaAction(),
135
+ // The key for your action, which is registered in the store config.
136
+ actionKey: 'myAction',
105
137
  params: {
106
138
  adapter: TransactionAdapter.SOLANA,
107
- // The RPC endpoint must be provided for tracking.
108
- rpcUrl: connection.rpcEndpoint,
109
- desiredChainID: 'mainnet-beta', // The cluster name for the pre-flight check
139
+ rpcUrl: connection.connection.rpcEndpoint,
140
+ desiredChainId: 'mainnet',
110
141
  type: 'MY_ACTION',
111
142
  title: 'My Action',
112
- description: 'Executing my custom action.',
113
143
  },
114
144
  });
115
145
  };
@@ -120,34 +150,32 @@ function MyTransactionButton() {
120
150
 
121
151
  ### 3\. Using Standalone Utilities
122
152
 
123
- You can use exported utilities, like the SNS resolvers, to enrich your UI.
153
+ You can use exported utilities, like the SNS resolvers, to enrich your UI. These utilities need an RPC URL, which should ideally be sourced from your central configuration.
124
154
 
125
155
  **Example: Displaying a user's .sol domain name**
126
156
 
127
157
  ```tsx
128
158
  // src/components/DisplayName.tsx
129
159
  import { getSolanaName } from '@tuwaio/pulsar-solana';
130
- import { useConnection, useWallet } from '@solana/wallet-adapter-react';
131
160
  import { useEffect, useState } from 'react';
132
161
 
133
- function DisplayName() {
134
- const { publicKey } = useWallet();
135
- const connection = useConnection();
162
+ // Assume you have access to the wallet address and the appropriate RPC URL
163
+ function DisplayName({ address, rpcUrl }: { address: string; rpcUrl: string }) {
136
164
  const [name, setName] = useState<string | null>(null);
137
165
 
138
166
  useEffect(() => {
139
- if (publicKey && connection) {
140
- getSolanaName(connection, publicKey.toBase58()).then(setName);
167
+ if (address && rpcUrl) {
168
+ getSolanaName(rpcUrl, address).then(setName);
141
169
  }
142
- }, [publicKey, connection]);
170
+ }, [address, rpcUrl]);
143
171
 
144
- if (!publicKey) return null;
172
+ if (!address) return null;
145
173
 
146
- return <span>{name || publicKey.toBase58()}</span>;
174
+ return <span>{name || address}</span>;
147
175
  }
148
176
  ```
149
177
 
150
- ---
178
+ -----
151
179
 
152
180
  ## 🤝 Contributing & Support
153
181
 
package/dist/index.d.mts CHANGED
@@ -1,29 +1,29 @@
1
1
  import { Transaction, TxAdapter, PollingTrackerConfig, ITxTrackingStore } from '@tuwaio/pulsar-core';
2
- import { WalletContextState, ConnectionContextState } from '@solana/wallet-adapter-react';
3
- import { TransactionError } from '@solana/kit';
2
+ import { SolanaClusterMoniker, TransactionError, Rpc, SolanaRpcApi } from 'gill';
4
3
 
5
4
  /**
6
5
  * @file Defines the core types and enums specific to the @tuwaio/pulsar-solana package.
7
6
  */
8
7
 
9
8
  /**
10
- * Defines the possible Solana network clusters.
9
+ * Describes the essential wallet information needed by the Solana adapter.
10
+ * This simple, library-agnostic interface allows any wallet connection library
11
+ * to be used with Pulsar, as long as it can provide this basic data.
11
12
  */
12
- type SolanaCluster = 'mainnet-beta' | 'devnet' | 'testnet';
13
+ interface SolanaAdapterWallet {
14
+ walletAddress: string;
15
+ walletType: string;
16
+ walletActiveChain: SolanaClusterMoniker;
17
+ }
13
18
  /**
14
- * Configuration object for the `solanaAdapter`.
15
- * All properties are optional and are typically derived from the Solana wallet adapter hooks.
19
+ * The final, simplified configuration object for the solanaAdapter.
16
20
  *
17
- * @property {WalletContextState} [wallet] - The state object from `useWallet()`. Required for actions that need a connected wallet, like signing or retrying transactions.
18
- * @property {ConnectionContextState} [connection] - The state object from `useConnection()`. Required for on-chain operations like checking the network or using the Solana Name Service.
19
- * @property {string} [explorerUrl] - The base URL for the transaction explorer (e.g., "https://solscan.io"). Defaults to Solscan if not provided.
20
- * @property {SolanaCluster} [cluster] - The specific cluster the app is connected to. Used for generating correct explorer links.
21
+ * @property {SolanaAdapterWallet} wallet - A simple object representing the current state of the user's wallet.
22
+ * @property {Record<SolanaClusterMoniker, string>} rpcUrls - A map of RPC URLs for each supported Solana cluster.
21
23
  */
22
24
  interface SolanaAdapterConfig {
23
- wallet?: WalletContextState;
24
- connection?: ConnectionContextState;
25
- explorerUrl?: string;
26
- cluster?: SolanaCluster;
25
+ wallet?: SolanaAdapterWallet;
26
+ rpcUrls: Record<SolanaClusterMoniker, string>;
27
27
  }
28
28
  /**
29
29
  * Defines the tracker identifiers available in the Solana adapter.
@@ -43,21 +43,21 @@ type SolanaActionTxKey = string;
43
43
  */
44
44
 
45
45
  /**
46
- * Factory function to create a Solana adapter for Pulsar.
47
- * This adapter provides all the necessary logic to interact with the Solana ecosystem,
48
- * including wallet interactions, transaction tracking, and name services.
46
+ * Creates a Solana adapter for the Pulsar transaction tracking engine.
47
+ * This factory function produces a wallet-library-agnostic adapter that can be
48
+ * configured for multiple Solana clusters (e.g., mainnet-beta, devnet) and
49
+ * can operate even without a connected wallet for read-only tasks.
49
50
  *
50
- * @param {SolanaAdapterConfig} config - The configuration object, typically derived from Solana wallet adapter hooks.
51
- * @returns {TxAdapter} An object conforming to the `TxAdapter` interface.
51
+ * @param config The configuration object for the adapter.
52
+ * @returns An object implementing the `TxAdapter` interface for Solana.
52
53
  */
53
54
  declare function solanaAdapter<T extends Transaction<SolanaTransactionTracker>>(config: SolanaAdapterConfig): TxAdapter<SolanaTransactionTracker, T, SolanaActionTxKey>;
54
55
 
55
56
  /**
56
57
  * @file This file defines custom error classes for the @tuwaio/pulsar-solana package.
57
58
  */
58
-
59
59
  /**
60
- * Thrown when the connected Solana cluster does not match the required cluster for a transaction.
60
+ * Thrown when the connected Solana chain does not match the required chain for a transaction.
61
61
  *
62
62
  * This allows consuming applications to `catch` this specific error and
63
63
  * implement custom logic, such as prompting the user to switch networks.
@@ -65,11 +65,11 @@ declare function solanaAdapter<T extends Transaction<SolanaTransactionTracker>>(
65
65
  declare class SolanaChainMismatchError extends Error {
66
66
  /** The name of the error, for easy identification. */
67
67
  name: string;
68
- /** The cluster that the transaction requires (e.g., 'mainnet-beta'). */
69
- requiredCluster: SolanaCluster;
70
- /** The cluster the wallet is currently connected to. */
71
- currentCluster: SolanaCluster | string;
72
- constructor(requiredCluster: SolanaCluster, currentCluster: SolanaCluster | string);
68
+ /** The chain that the transaction requires (e.g., 'solana:mainnet'). */
69
+ requiredChain: string;
70
+ /** The chain the wallet is currently connected to. */
71
+ currentChain: string;
72
+ constructor(requiredChain: string, currentChain: string);
73
73
  }
74
74
 
75
75
  /**
@@ -128,20 +128,27 @@ declare function checkAndInitializeTrackerInStore<T extends Transaction<SolanaTr
128
128
  } & Pick<ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>, 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'>): Promise<void>;
129
129
 
130
130
  /**
131
- * @file This file contains a utility to robustly verify the Solana cluster by its genesis hash.
131
+ * @file This file contains a utility to verify the connected Solana chain.
132
132
  */
133
-
134
133
  /**
135
- * Checks if an RPC endpoint is connected to the required Solana cluster.
136
- * It fetches the genesis hash from the RPC endpoint and compares it
137
- * with a known hash for the specified cluster.
134
+ * Checks if the wallet's current chain matches the required chain for a transaction.
138
135
  *
139
- * @param {string} rpcUrl - The RPC endpoint URL to check.
140
- * @param {SolanaCluster} requiredCluster - The cluster that the transaction requires (e.g., 'mainnet-beta').
141
- * @throws {SolanaChainMismatchError} If the connected chain does not match the required cluster.
142
- * @throws {Error} If the cluster name is unknown or if the RPC call fails for other reasons.
136
+ * This function compares the `chain` property from the Wallet Standard account object
137
+ * with the required chain identifier (e.g., 'solana:mainnet').
138
+ *
139
+ * @param {string} requiredChain - The chain identifier that the transaction requires.
140
+ * @param {string} currentChain - The chain identifier the wallet is currently connected to.
141
+ * @throws {SolanaChainMismatchError} If the connected chain does not match the required chain.
143
142
  */
144
- declare const checkSolanaChain: (rpcUrl: string, requiredCluster: SolanaCluster) => Promise<void>;
143
+ declare const checkSolanaChain: (requiredChain: string, currentChain: string) => void;
144
+
145
+ /**
146
+ * Retrieves a cached RPC client for a given URL or creates a new one.
147
+ * @param rpcUrl - The RPC endpoint URL.
148
+ * @returns The RPC client instance.
149
+ * @internal
150
+ */
151
+ declare const createSolanaRPC: (rpcUrl: string) => Rpc<SolanaRpcApi>;
145
152
 
146
153
  /**
147
154
  * @file This file contains a utility function for generating Solana transaction explorer links.
@@ -155,25 +162,25 @@ declare const checkSolanaChain: (rpcUrl: string, requiredCluster: SolanaCluster)
155
162
  * @param {SolanaCluster} [cluster] - The optional cluster name ('devnet', 'testnet') to add as a query parameter.
156
163
  * @returns {string} The full URL to the transaction on the explorer.
157
164
  */
158
- declare const selectSolanaTxExplorerLink: (baseUrl: string, txKey: string, cluster?: SolanaCluster) => string;
165
+ declare const selectSolanaTxExplorerLink: (txKey: string, cluster?: SolanaClusterMoniker) => string;
159
166
 
160
167
  /**
161
168
  * @file This file contains utility functions for interacting with the Solana Name Service (SNS) provided by Bonfida.
162
169
  */
163
-
164
170
  /**
165
171
  * Performs a reverse lookup to find the .sol domain name for a given wallet address.
166
- * @param {ConnectionContextState} connection - The connection state object from the `useConnection` hook.
172
+ * Results are cached to avoid redundant network requests.
173
+ * @param {string} rpcUrl - The RPC endpoint URL.
167
174
  * @param {string} address - The public key of the wallet as a string.
168
175
  * @returns {Promise<string | null>} The .sol domain name (e.g., "bonfida.sol") or null if not found.
169
176
  */
170
- declare const getSolanaName: (connection: ConnectionContextState, address: string) => Promise<string | null>;
177
+ declare const getSolanaName: (rpcUrl: string, address: string) => Promise<string | null>;
171
178
  /**
172
179
  * Retrieves the avatar URL from the 'pic' record of a .sol domain name.
173
- * @param {ConnectionContextState} connection - The connection state object from the `useConnection` hook.
180
+ * @param {string} rpcUrl - The RPC endpoint URL.
174
181
  * @param {string} name - The .sol domain name (e.g., "bonfida.sol").
175
182
  * @returns {Promise<string | null>} The URL of the avatar or null if not found or set.
176
183
  */
177
- declare const getSolanaAvatar: (connection: ConnectionContextState, name: string) => Promise<string | null>;
184
+ declare const getSolanaAvatar: (rpcUrl: string, name: string) => Promise<string | null>;
178
185
 
179
- export { type SolanaActionTxKey, type SolanaAdapterConfig, SolanaChainMismatchError, type SolanaCluster, SolanaTransactionTracker, checkAndInitializeTrackerInStore, checkSolanaChain, getSolanaAvatar, getSolanaName, selectSolanaTxExplorerLink, solanaAdapter, solanaFetcher, solanaTrackerForStore };
186
+ export { type SolanaActionTxKey, type SolanaAdapterConfig, type SolanaAdapterWallet, SolanaChainMismatchError, SolanaTransactionTracker, checkAndInitializeTrackerInStore, checkSolanaChain, createSolanaRPC, getSolanaAvatar, getSolanaName, selectSolanaTxExplorerLink, solanaAdapter, solanaFetcher, solanaTrackerForStore };
package/dist/index.d.ts CHANGED
@@ -1,29 +1,29 @@
1
1
  import { Transaction, TxAdapter, PollingTrackerConfig, ITxTrackingStore } from '@tuwaio/pulsar-core';
2
- import { WalletContextState, ConnectionContextState } from '@solana/wallet-adapter-react';
3
- import { TransactionError } from '@solana/kit';
2
+ import { SolanaClusterMoniker, TransactionError, Rpc, SolanaRpcApi } from 'gill';
4
3
 
5
4
  /**
6
5
  * @file Defines the core types and enums specific to the @tuwaio/pulsar-solana package.
7
6
  */
8
7
 
9
8
  /**
10
- * Defines the possible Solana network clusters.
9
+ * Describes the essential wallet information needed by the Solana adapter.
10
+ * This simple, library-agnostic interface allows any wallet connection library
11
+ * to be used with Pulsar, as long as it can provide this basic data.
11
12
  */
12
- type SolanaCluster = 'mainnet-beta' | 'devnet' | 'testnet';
13
+ interface SolanaAdapterWallet {
14
+ walletAddress: string;
15
+ walletType: string;
16
+ walletActiveChain: SolanaClusterMoniker;
17
+ }
13
18
  /**
14
- * Configuration object for the `solanaAdapter`.
15
- * All properties are optional and are typically derived from the Solana wallet adapter hooks.
19
+ * The final, simplified configuration object for the solanaAdapter.
16
20
  *
17
- * @property {WalletContextState} [wallet] - The state object from `useWallet()`. Required for actions that need a connected wallet, like signing or retrying transactions.
18
- * @property {ConnectionContextState} [connection] - The state object from `useConnection()`. Required for on-chain operations like checking the network or using the Solana Name Service.
19
- * @property {string} [explorerUrl] - The base URL for the transaction explorer (e.g., "https://solscan.io"). Defaults to Solscan if not provided.
20
- * @property {SolanaCluster} [cluster] - The specific cluster the app is connected to. Used for generating correct explorer links.
21
+ * @property {SolanaAdapterWallet} wallet - A simple object representing the current state of the user's wallet.
22
+ * @property {Record<SolanaClusterMoniker, string>} rpcUrls - A map of RPC URLs for each supported Solana cluster.
21
23
  */
22
24
  interface SolanaAdapterConfig {
23
- wallet?: WalletContextState;
24
- connection?: ConnectionContextState;
25
- explorerUrl?: string;
26
- cluster?: SolanaCluster;
25
+ wallet?: SolanaAdapterWallet;
26
+ rpcUrls: Record<SolanaClusterMoniker, string>;
27
27
  }
28
28
  /**
29
29
  * Defines the tracker identifiers available in the Solana adapter.
@@ -43,21 +43,21 @@ type SolanaActionTxKey = string;
43
43
  */
44
44
 
45
45
  /**
46
- * Factory function to create a Solana adapter for Pulsar.
47
- * This adapter provides all the necessary logic to interact with the Solana ecosystem,
48
- * including wallet interactions, transaction tracking, and name services.
46
+ * Creates a Solana adapter for the Pulsar transaction tracking engine.
47
+ * This factory function produces a wallet-library-agnostic adapter that can be
48
+ * configured for multiple Solana clusters (e.g., mainnet-beta, devnet) and
49
+ * can operate even without a connected wallet for read-only tasks.
49
50
  *
50
- * @param {SolanaAdapterConfig} config - The configuration object, typically derived from Solana wallet adapter hooks.
51
- * @returns {TxAdapter} An object conforming to the `TxAdapter` interface.
51
+ * @param config The configuration object for the adapter.
52
+ * @returns An object implementing the `TxAdapter` interface for Solana.
52
53
  */
53
54
  declare function solanaAdapter<T extends Transaction<SolanaTransactionTracker>>(config: SolanaAdapterConfig): TxAdapter<SolanaTransactionTracker, T, SolanaActionTxKey>;
54
55
 
55
56
  /**
56
57
  * @file This file defines custom error classes for the @tuwaio/pulsar-solana package.
57
58
  */
58
-
59
59
  /**
60
- * Thrown when the connected Solana cluster does not match the required cluster for a transaction.
60
+ * Thrown when the connected Solana chain does not match the required chain for a transaction.
61
61
  *
62
62
  * This allows consuming applications to `catch` this specific error and
63
63
  * implement custom logic, such as prompting the user to switch networks.
@@ -65,11 +65,11 @@ declare function solanaAdapter<T extends Transaction<SolanaTransactionTracker>>(
65
65
  declare class SolanaChainMismatchError extends Error {
66
66
  /** The name of the error, for easy identification. */
67
67
  name: string;
68
- /** The cluster that the transaction requires (e.g., 'mainnet-beta'). */
69
- requiredCluster: SolanaCluster;
70
- /** The cluster the wallet is currently connected to. */
71
- currentCluster: SolanaCluster | string;
72
- constructor(requiredCluster: SolanaCluster, currentCluster: SolanaCluster | string);
68
+ /** The chain that the transaction requires (e.g., 'solana:mainnet'). */
69
+ requiredChain: string;
70
+ /** The chain the wallet is currently connected to. */
71
+ currentChain: string;
72
+ constructor(requiredChain: string, currentChain: string);
73
73
  }
74
74
 
75
75
  /**
@@ -128,20 +128,27 @@ declare function checkAndInitializeTrackerInStore<T extends Transaction<SolanaTr
128
128
  } & Pick<ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>, 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'>): Promise<void>;
129
129
 
130
130
  /**
131
- * @file This file contains a utility to robustly verify the Solana cluster by its genesis hash.
131
+ * @file This file contains a utility to verify the connected Solana chain.
132
132
  */
133
-
134
133
  /**
135
- * Checks if an RPC endpoint is connected to the required Solana cluster.
136
- * It fetches the genesis hash from the RPC endpoint and compares it
137
- * with a known hash for the specified cluster.
134
+ * Checks if the wallet's current chain matches the required chain for a transaction.
138
135
  *
139
- * @param {string} rpcUrl - The RPC endpoint URL to check.
140
- * @param {SolanaCluster} requiredCluster - The cluster that the transaction requires (e.g., 'mainnet-beta').
141
- * @throws {SolanaChainMismatchError} If the connected chain does not match the required cluster.
142
- * @throws {Error} If the cluster name is unknown or if the RPC call fails for other reasons.
136
+ * This function compares the `chain` property from the Wallet Standard account object
137
+ * with the required chain identifier (e.g., 'solana:mainnet').
138
+ *
139
+ * @param {string} requiredChain - The chain identifier that the transaction requires.
140
+ * @param {string} currentChain - The chain identifier the wallet is currently connected to.
141
+ * @throws {SolanaChainMismatchError} If the connected chain does not match the required chain.
143
142
  */
144
- declare const checkSolanaChain: (rpcUrl: string, requiredCluster: SolanaCluster) => Promise<void>;
143
+ declare const checkSolanaChain: (requiredChain: string, currentChain: string) => void;
144
+
145
+ /**
146
+ * Retrieves a cached RPC client for a given URL or creates a new one.
147
+ * @param rpcUrl - The RPC endpoint URL.
148
+ * @returns The RPC client instance.
149
+ * @internal
150
+ */
151
+ declare const createSolanaRPC: (rpcUrl: string) => Rpc<SolanaRpcApi>;
145
152
 
146
153
  /**
147
154
  * @file This file contains a utility function for generating Solana transaction explorer links.
@@ -155,25 +162,25 @@ declare const checkSolanaChain: (rpcUrl: string, requiredCluster: SolanaCluster)
155
162
  * @param {SolanaCluster} [cluster] - The optional cluster name ('devnet', 'testnet') to add as a query parameter.
156
163
  * @returns {string} The full URL to the transaction on the explorer.
157
164
  */
158
- declare const selectSolanaTxExplorerLink: (baseUrl: string, txKey: string, cluster?: SolanaCluster) => string;
165
+ declare const selectSolanaTxExplorerLink: (txKey: string, cluster?: SolanaClusterMoniker) => string;
159
166
 
160
167
  /**
161
168
  * @file This file contains utility functions for interacting with the Solana Name Service (SNS) provided by Bonfida.
162
169
  */
163
-
164
170
  /**
165
171
  * Performs a reverse lookup to find the .sol domain name for a given wallet address.
166
- * @param {ConnectionContextState} connection - The connection state object from the `useConnection` hook.
172
+ * Results are cached to avoid redundant network requests.
173
+ * @param {string} rpcUrl - The RPC endpoint URL.
167
174
  * @param {string} address - The public key of the wallet as a string.
168
175
  * @returns {Promise<string | null>} The .sol domain name (e.g., "bonfida.sol") or null if not found.
169
176
  */
170
- declare const getSolanaName: (connection: ConnectionContextState, address: string) => Promise<string | null>;
177
+ declare const getSolanaName: (rpcUrl: string, address: string) => Promise<string | null>;
171
178
  /**
172
179
  * Retrieves the avatar URL from the 'pic' record of a .sol domain name.
173
- * @param {ConnectionContextState} connection - The connection state object from the `useConnection` hook.
180
+ * @param {string} rpcUrl - The RPC endpoint URL.
174
181
  * @param {string} name - The .sol domain name (e.g., "bonfida.sol").
175
182
  * @returns {Promise<string | null>} The URL of the avatar or null if not found or set.
176
183
  */
177
- declare const getSolanaAvatar: (connection: ConnectionContextState, name: string) => Promise<string | null>;
184
+ declare const getSolanaAvatar: (rpcUrl: string, name: string) => Promise<string | null>;
178
185
 
179
- export { type SolanaActionTxKey, type SolanaAdapterConfig, SolanaChainMismatchError, type SolanaCluster, SolanaTransactionTracker, checkAndInitializeTrackerInStore, checkSolanaChain, getSolanaAvatar, getSolanaName, selectSolanaTxExplorerLink, solanaAdapter, solanaFetcher, solanaTrackerForStore };
186
+ export { type SolanaActionTxKey, type SolanaAdapterConfig, type SolanaAdapterWallet, SolanaChainMismatchError, SolanaTransactionTracker, checkAndInitializeTrackerInStore, checkSolanaChain, createSolanaRPC, getSolanaAvatar, getSolanaName, selectSolanaTxExplorerLink, solanaAdapter, solanaFetcher, solanaTrackerForStore };
package/dist/index.js CHANGED
@@ -1,3 +1,2 @@
1
- 'use strict';var kit=require('@solana/kit'),pulsarCore=require('@tuwaio/pulsar-core'),u=require('dayjs'),splNameService=require('@bonfida/spl-name-service');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var u__default=/*#__PURE__*/_interopDefault(u);var S=(n=>(n.Solana="solana",n))(S||{});var d=new Map,b=r=>{if(d.has(r))return d.get(r);let n=kit.createSolanaRpc(r);return d.set(r,n),n};async function N({tx:r,stopPolling:n,onSuccess:t,onFailure:a,onIntervalTick:i}){if(r.adapter!==pulsarCore.TransactionAdapter.SOLANA||!r.rpcUrl)throw new Error("RPC URL is missing from the Solana transaction.");let s=(await b(r.rpcUrl).getSignatureStatuses([r.txKey]).send())?.value[0];if(!s)return;let c=s;if(i?.(c),c.err){a(c);return}c.confirmationStatus==="finalized"&&t(c),u__default.default().diff(u__default.default.unix(r.localTimestamp),"minute")>=30&&(n({withoutRemoving:true}),a(c));}async function x({tx:r,...n}){return pulsarCore.initializePollingTracker({tx:r,fetcher:N,removeTxFromPool:n.removeTxFromPool,pollingInterval:2500,maxRetries:720,onSuccess:t=>{n.updateTxParams(r.txKey,{status:pulsarCore.TransactionStatus.Success,pending:false,isError:false,finishedTimestamp:u__default.default().unix(),confirmations:t.confirmations??1,slot:Number(t.slot)});let a=n.transactionsPool[r.txKey];n.onSucceedCallbacks&&a&&n.onSucceedCallbacks(a);},onIntervalTick:t=>{n.updateTxParams(r.txKey,{confirmations:t.confirmations??0,slot:Number(t.slot)});},onFailure:t=>{let a=t?.err?`Transaction failed: ${JSON.stringify(t.err)}`:"Transaction tracking timed out or the transaction was not found.";n.updateTxParams(r.txKey,{status:pulsarCore.TransactionStatus.Failed,pending:false,isError:true,errorMessage:a,finishedTimestamp:u__default.default().unix()});}})}async function y({tx:r,tracker:n,...t}){switch(n){case "solana":await x({tx:r,...t});break;default:console.error(`Unknown tracker type for Solana adapter: ${n}`),t.updateTxParams(r.txKey,{status:pulsarCore.TransactionStatus.Failed,pending:false,isError:true,errorMessage:`Unsupported tracker type: "${n}"`});break}}var m=class extends Error{name="SolanaChainMismatchError";requiredCluster;currentCluster;constructor(n,t){let a=`Wrong network. The transaction requires ${n}, but you are connected to ${t}.`;super(a),this.requiredCluster=n,this.currentCluster=t;}};var k={"mainnet-beta":"5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",devnet:"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",testnet:"4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY"},h=async(r,n)=>{let t=k[n];if(!t)throw new Error(`Unknown Solana cluster specified: ${n}`);let i=await kit.createSolanaRpc(r).getGenesisHash().send();if(i!==t){let o=Object.entries(k).find(([,e])=>e===i)?.[0]||"an unknown network";throw new m(n,o)}};var C=(r,n,t)=>{let a=r.endsWith("/")?r.slice(0,-1):r,i=t?`?cluster=${t}`:"";return `${a}/tx/${n}${i}`};var f=class{_bn;constructor(n){this._bn=kit.getBase58Encoder().encode(n);}toBuffer(){return Buffer.from(this._bn)}toString(){let[n]=kit.getBase58Decoder().decode(this._bn);return n}},w=async(r,n)=>{try{let t=new f(n),a=await splNameService.getDomainKeysWithReverses(r.connection,t);return a.length===0?null:`${await splNameService.performReverseLookup(r.connection,a[0])}.sol`}catch{return null}},A=async(r,n)=>{try{let t=await splNameService.getRecord(r.connection,n,splNameService.Record.Pic);return !t||!t.data?null:t.data.toString("utf-8")}catch{return null}};function Kn(r){let{wallet:n,connection:t,explorerUrl:a,cluster:i}=r;return {key:pulsarCore.TransactionAdapter.SOLANA,getWalletInfo:()=>({walletAddress:n?.publicKey?.toBase58()??"0x0",walletType:n?.wallet?.adapter.name.toLowerCase()??"unknown"}),checkChainForTx:async o=>{let e=t?.connection.rpcEndpoint;if(!e){console.warn("Cannot check chain: connection context not provided to adapter.");return}await h(e,o);},checkTransactionsTracker:o=>({tracker:"solana",txKey:o}),checkAndInitializeTrackerInStore:({tx:o,...e})=>y({tracker:o.tracker,tx:o,...e}),getExplorerUrl:()=>a??"https://solscan.io",getExplorerTxUrl:(o,e)=>C(a??"https://solscan.io",e,i),getName:async o=>t?w(t,o):(console.warn("Cannot get name: connection context not provided to adapter."),null),getAvatar:async o=>t?A(t,o):(console.warn("Cannot get avatar: connection context not provided to adapter."),null),retryTxAction:async({actions:o,onClose:e,txKey:s,handleTransaction:c,tx:l})=>{if(e(s),!n)throw new Error("Retry failed: A wallet must be connected to retry a transaction.");if(!c)throw new Error("Retry failed: handleTransaction function is not provided.");let p=l.actionKey;if(!p||!o?.[p])throw new Error(`Retry failed: No action found for actionKey "${p}".`);let R=o[p],T=l.rpcUrl??t?.connection.rpcEndpoint;if(!T)throw new Error("Retry failed: Could not determine RPC endpoint.");let P=kit.createSolanaRpc(T);await c({actionFunction:()=>R({wallet:n,connection:t?.connection,rpc:P,...l.payload}),params:l,defaultTracker:"solana"});}}}
2
- exports.SolanaChainMismatchError=m;exports.SolanaTransactionTracker=S;exports.checkAndInitializeTrackerInStore=y;exports.checkSolanaChain=h;exports.getSolanaAvatar=A;exports.getSolanaName=w;exports.selectSolanaTxExplorerLink=C;exports.solanaAdapter=Kn;exports.solanaFetcher=N;exports.solanaTrackerForStore=x;//# sourceMappingURL=index.js.map
1
+ 'use strict';var pulsarCore=require('@tuwaio/pulsar-core'),gill=require('gill'),S=require('dayjs'),splNameService=require('@bonfida/spl-name-service'),web3_js=require('@solana/web3.js');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var S__default=/*#__PURE__*/_interopDefault(S);var l=class extends Error{name="SolanaChainMismatchError";requiredChain;currentChain;constructor(r,t){let o=`Wrong chain. The transaction requires ${r}, but you are connected to ${t}.`;super(o),this.requiredChain=r,this.currentChain=t;}};var T=(r=>(r.Solana="solana",r))(T||{});var d=new Map,f=n=>{if(d.has(n))return d.get(n);let r=gill.createSolanaRpc(n);return d.set(n,r),r};async function I({tx:n,stopPolling:r,onSuccess:t,onFailure:o,onIntervalTick:s}){if(n.adapter!==pulsarCore.TransactionAdapter.SOLANA||!n.rpcUrl)throw new Error("RPC URL is missing from the Solana transaction.");let c=(await f(n.rpcUrl).getSignatureStatuses([n.txKey]).send())?.value[0];if(!c)return;let i=c;if(s?.(i),i.err){o(i);return}i.confirmationStatus==="finalized"&&t(i),S__default.default().diff(S__default.default.unix(n.localTimestamp),"minute")>=30&&(r({withoutRemoving:true}),o(i));}async function x({tx:n,...r}){return pulsarCore.initializePollingTracker({tx:n,fetcher:I,removeTxFromPool:r.removeTxFromPool,pollingInterval:2500,maxRetries:720,onSuccess:t=>{r.updateTxParams(n.txKey,{status:pulsarCore.TransactionStatus.Success,pending:false,isError:false,finishedTimestamp:S__default.default().unix(),confirmations:t.confirmations??1,slot:Number(t.slot)});let o=r.transactionsPool[n.txKey];r.onSucceedCallbacks&&o&&r.onSucceedCallbacks(o);},onIntervalTick:t=>{r.updateTxParams(n.txKey,{confirmations:t.confirmations??0,slot:Number(t.slot)});},onFailure:t=>{let o=t?.err?`Transaction failed: ${JSON.stringify(t.err)}`:"Transaction tracking timed out or the transaction was not found.";r.updateTxParams(n.txKey,{status:pulsarCore.TransactionStatus.Failed,pending:false,isError:true,errorMessage:o,finishedTimestamp:S__default.default().unix()});}})}async function y({tx:n,tracker:r,...t}){switch(r){case "solana":await x({tx:n,...t});break;default:console.error(`Unknown tracker type for Solana adapter: ${r}`),t.updateTxParams(n.txKey,{status:pulsarCore.TransactionStatus.Failed,pending:false,isError:true,errorMessage:`Unsupported tracker type: "${r}"`});break}}var w=(n,r)=>{if(r!==n)throw new l(n,r)};var C=(n,r)=>{let t=gill.getExplorerLink({cluster:r}),o=t.endsWith("/")?t.slice(0,-1):t,s=r?`?cluster=${r}`:"";return `${o}/tx/${n}${s}`};var g=new Map,p=new Map,A=n=>(g.has(n)||g.set(n,new web3_js.Connection(n)),g.get(n)),R=async(n,r)=>{if(p.has(r))return p.get(r);try{let t=A(n),o=new web3_js.PublicKey(r),s=await splNameService.getDomainKeysWithReverses(t,o);if(s.length===0)return p.set(r,null),null;let a=`${await splNameService.performReverseLookup(t,s[0])}.sol`;return p.set(r,a),a}catch{return p.set(r,null),null}},P=async(n,r)=>{try{let t=A(n),o=await splNameService.getRecord(t,r,splNameService.Record.Pic);return !o||!o.data?null:o.data.toString("utf-8")}catch{return null}};function Mr(n){let{wallet:r,rpcUrls:t}=n,o=e=>{let a="mainnet";return e?e.includes(":")?e.split(":")[1]:e:r?.walletActiveChain??a},s=e=>{let a=e??r?.walletActiveChain;if(a)return t[a]};return {key:pulsarCore.TransactionAdapter.SOLANA,getWalletInfo:()=>({walletAddress:r?.walletAddress??"0x0",walletType:r?.walletType??"disconnected"}),checkChainForTx:async e=>{if(!r)throw new Error("Wallet not provided. Cannot perform chain check.");try{w(e,r.walletActiveChain);}catch(a){throw a instanceof l?a:new Error(`Chain check failed: ${a instanceof Error?a.message:String(a)}`)}},checkTransactionsTracker:e=>({tracker:"solana",txKey:e}),checkAndInitializeTrackerInStore:({tx:e,...a})=>y({tracker:e.tracker,tx:e,...a}),getExplorerUrl:()=>{let e=r?.walletActiveChain??"mainnet-beta";return gill.getExplorerLink({cluster:e})},getExplorerTxUrl:(e,a)=>{let c=e[a],i=o(c?.chainId);return C(a,i)},getName:async e=>{let a=s(r?.walletActiveChain);return a?R(a,e):(console.warn("Cannot get name: RPC URL for the current chain is not configured."),null)},getAvatar:async e=>{let a=s(r?.walletActiveChain);return a?P(a,e):(console.warn("Cannot get avatar: RPC URL for the current chain is not configured."),null)},retryTxAction:async({actions:e,onClose:a,txKey:c,handleTransaction:i,tx:u})=>{if(a(c),!r||!r.walletAddress||r.walletAddress==="0x0")throw new Error("Retry failed: A wallet must be connected.");if(!i)throw new Error("Retry failed: handleTransaction function is not provided.");let m=u.actionKey;if(!m||!e?.[m])throw new Error(`Retry failed: No action found for actionKey "${m}".`);let v=o(u.desiredChainID),h=u.rpcUrl??s(v);if(!h)throw new Error("Retry failed: Could not determine RPC endpoint for the transaction chain.");let E=e[m],K=f(h);await i({actionFunction:()=>E({wallet:n.wallet,rpc:K,...u.payload}),params:u,defaultTracker:"solana"});}}}exports.SolanaChainMismatchError=l;exports.SolanaTransactionTracker=T;exports.checkAndInitializeTrackerInStore=y;exports.checkSolanaChain=w;exports.createSolanaRPC=f;exports.getSolanaAvatar=P;exports.getSolanaName=R;exports.selectSolanaTxExplorerLink=C;exports.solanaAdapter=Mr;exports.solanaFetcher=I;exports.solanaTrackerForStore=x;//# sourceMappingURL=index.js.map
3
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/trackers/solanaTracker.ts","../src/utils/checkAndInitializeTrackerInStore.ts","../src/errors.ts","../src/utils/checkSolanaChain.ts","../src/utils/selectSolanaTxExplorerLink.ts","../src/utils/snsUtils.ts","../src/adapters/solanaAdapter.ts"],"names":["SolanaTransactionTracker","rpcCache","getRpcClient","rpcUrl","newRpc","createSolanaRpc","solanaFetcher","tx","stopPolling","onSuccess","onFailure","onIntervalTick","TransactionAdapter","status","typedStatus","dayjs","solanaTrackerForStore","rest","initializePollingTracker","response","TransactionStatus","updatedTx","errorMessage","checkAndInitializeTrackerInStore","tracker","SolanaChainMismatchError","requiredCluster","currentCluster","message","SOLANA_GENESIS_HASHES","checkSolanaChain","expectedGenesisHash","currentGenesisHash","currentClusterName","hash","selectSolanaTxExplorerLink","baseUrl","txKey","cluster","sanitizedBaseUrl","clusterParam","PublicKey","value","getBase58Encoder","decodedString","getBase58Decoder","getSolanaName","connection","address","pubKey","domainKeys","getDomainKeysWithReverses","performReverseLookup","getSolanaAvatar","name","record","getRecord","Record","solanaAdapter","config","wallet","explorerUrl","txCluster","currentRpcUrl","actionTxKey","txPool","actions","onClose","handleTransaction","actionKey","retryAction","rpcUrlForRetry","rpcForRetry"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @file Defines the core types and enums specific to the @tuwaio/pulsar-solana package.\n */\n\nimport { ConnectionContextState, WalletContextState } from '@solana/wallet-adapter-react';\n\n/**\n * Defines the possible Solana network clusters.\n */\nexport type SolanaCluster = 'mainnet-beta' | 'devnet' | 'testnet';\n\n/**\n * Configuration object for the `solanaAdapter`.\n * All properties are optional and are typically derived from the Solana wallet adapter hooks.\n *\n * @property {WalletContextState} [wallet] - The state object from `useWallet()`. Required for actions that need a connected wallet, like signing or retrying transactions.\n * @property {ConnectionContextState} [connection] - The state object from `useConnection()`. Required for on-chain operations like checking the network or using the Solana Name Service.\n * @property {string} [explorerUrl] - The base URL for the transaction explorer (e.g., \"https://solscan.io\"). Defaults to Solscan if not provided.\n * @property {SolanaCluster} [cluster] - The specific cluster the app is connected to. Used for generating correct explorer links.\n */\nexport interface SolanaAdapterConfig {\n wallet?: WalletContextState;\n connection?: ConnectionContextState;\n explorerUrl?: string;\n cluster?: SolanaCluster;\n}\n\n/**\n * Defines the tracker identifiers available in the Solana adapter.\n */\nexport enum SolanaTransactionTracker {\n /** The default tracker for monitoring standard Solana transaction signatures. */\n Solana = 'solana',\n}\n\n/**\n * Represents the unique key returned by a transaction-creating action on Solana.\n * For standard Solana transactions, this is always the transaction signature as a base58 string.\n */\nexport type SolanaActionTxKey = string;\n","/**\n * @file Implements the transaction tracking logic for standard Solana transactions.\n * It uses a polling mechanism to query the `getSignatureStatuses` RPC method.\n */\n\nimport { createSolanaRpc, Rpc, Signature, SolanaRpcApi, TransactionError } from '@solana/kit';\nimport {\n initializePollingTracker,\n ITxTrackingStore,\n PollingTrackerConfig,\n Transaction,\n TransactionAdapter,\n TransactionStatus,\n} from '@tuwaio/pulsar-core';\nimport dayjs from 'dayjs';\n\nimport { SolanaActionTxKey, SolanaTransactionTracker } from '../types';\n\n// --- RPC Client Caching ---\n\n/**\n * An in-memory cache for RPC clients to avoid re-creating them on every poll.\n * @internal\n */\nconst rpcCache = new Map<string, Rpc<SolanaRpcApi>>();\n\n/**\n * Retrieves a cached RPC client for a given URL or creates a new one.\n * @param rpcUrl - The RPC endpoint URL.\n * @returns The RPC client instance.\n * @internal\n */\nconst getRpcClient = (rpcUrl: string): Rpc<SolanaRpcApi> => {\n if (rpcCache.has(rpcUrl)) {\n return rpcCache.get(rpcUrl)!;\n }\n const newRpc = createSolanaRpc(rpcUrl);\n rpcCache.set(rpcUrl, newRpc);\n return newRpc;\n};\n\n// --- Types ---\n\n/**\n * The shape of the status object returned for each signature from `getSignatureStatuses`.\n * @internal\n */\ntype SolanaSignatureStatusResponse = {\n slot: bigint;\n confirmations: number | null;\n err: TransactionError | null;\n confirmationStatus: 'processed' | 'confirmed' | 'finalized' | null;\n};\n\n/**\n * The parameters for our specific fetcher function.\n * @internal\n */\ntype SolanaFetcherParams = Parameters<\n PollingTrackerConfig<\n SolanaSignatureStatusResponse,\n Transaction<SolanaTransactionTracker>,\n SolanaTransactionTracker\n >['fetcher']\n>[0];\n\n// --- Fetcher Implementation ---\n\n/**\n * A reusable fetcher for `initializePollingTracker` that queries the Solana RPC for a transaction's signature status.\n * This is the core polling logic that powers the tracker.\n */\nexport async function solanaFetcher({ tx, stopPolling, onSuccess, onFailure, onIntervalTick }: SolanaFetcherParams) {\n if (tx.adapter !== TransactionAdapter.SOLANA || !tx.rpcUrl) {\n // This should not happen if the types are correct, but it's a good runtime safeguard.\n throw new Error('RPC URL is missing from the Solana transaction.');\n }\n\n const rpc = getRpcClient(tx.rpcUrl);\n const statuses = await rpc.getSignatureStatuses([tx.txKey as Signature]).send();\n const status = statuses?.value[0];\n\n if (!status) {\n // Continue polling if the transaction is not yet found by the RPC node.\n // The polling tracker will handle the retry delay.\n return;\n }\n\n const typedStatus = status as SolanaSignatureStatusResponse;\n onIntervalTick?.(typedStatus);\n\n if (typedStatus.err) {\n onFailure(typedStatus); // Terminal failure state\n return;\n }\n\n if (typedStatus.confirmationStatus === 'finalized') {\n onSuccess(typedStatus); // Terminal success state\n }\n\n // Safeguard: Stop polling for very old pending transactions.\n if (dayjs().diff(dayjs.unix(tx.localTimestamp), 'minute') >= 30) {\n stopPolling({ withoutRemoving: true });\n // When a timeout occurs, we call `onFailure` with the last known status,\n // but the tracker itself will provide the specific timeout error message.\n onFailure(typedStatus);\n }\n}\n\n// --- Store-Connected Tracker ---\n\n/**\n * A higher-level wrapper that integrates the Solana polling logic with the Pulsar store.\n * It uses the generic `solanaFetcher` and provides store-specific callbacks.\n *\n * @template T - The application-specific transaction type, constrained to Transaction.\n */\nexport async function solanaTrackerForStore<T extends Transaction<SolanaTransactionTracker>>({\n tx,\n ...rest\n}: Pick<\n ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>,\n 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'\n> & {\n tx: T;\n}) {\n return initializePollingTracker<SolanaSignatureStatusResponse, T, SolanaTransactionTracker>({\n tx,\n fetcher: solanaFetcher,\n removeTxFromPool: rest.removeTxFromPool,\n pollingInterval: 2500,\n maxRetries: 720, // 30 minutes timeout (720 intervals * 2.5s = 1800s)\n onSuccess: (response) => {\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Success,\n pending: false,\n isError: false,\n finishedTimestamp: dayjs().unix(),\n confirmations: response.confirmations ?? 1,\n slot: Number(response.slot),\n });\n\n const updatedTx = rest.transactionsPool[tx.txKey];\n if (rest.onSucceedCallbacks && updatedTx) {\n rest.onSucceedCallbacks(updatedTx);\n }\n },\n onIntervalTick: (response) => {\n rest.updateTxParams(tx.txKey, {\n confirmations: response.confirmations ?? 0,\n slot: Number(response.slot),\n });\n },\n onFailure: (response) => {\n // If `response` is undefined, it means the polling timed out from `initializePollingTracker`.\n // Otherwise, the transaction failed on-chain.\n const errorMessage = response?.err\n ? `Transaction failed: ${JSON.stringify(response.err)}`\n : 'Transaction tracking timed out or the transaction was not found.';\n\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Failed,\n pending: false,\n isError: true,\n errorMessage,\n finishedTimestamp: dayjs().unix(),\n });\n },\n });\n}\n","/**\n * @file This file contains the primary router for initializing transaction trackers.\n */\n\nimport { ITxTrackingStore, Transaction, TransactionStatus } from '@tuwaio/pulsar-core';\n\nimport { solanaTrackerForStore } from '../trackers/solanaTracker';\nimport { SolanaActionTxKey, SolanaTransactionTracker } from '../types';\n\n/**\n * Initializes the correct background tracker for a given Solana transaction.\n * This function acts as a router, selecting the appropriate tracker based on the `tx.tracker` property.\n *\n * @template T - The transaction type, constrained to Solana transactions.\n * @param {object} params - The parameters for initializing the tracker.\n * @param {T} params.tx - The transaction object to be tracked.\n * @param {SolanaTransactionTracker} params.tracker - The specific tracker to use.\n * @param {object} params.rest - The rest of the store's methods and state needed by the tracker.\n * @returns {Promise<void>} A promise that resolves when the tracker has been initialized.\n */\nexport async function checkAndInitializeTrackerInStore<T extends Transaction<SolanaTransactionTracker>>({\n tx,\n tracker,\n ...rest\n}: {\n tx: T;\n tracker: SolanaTransactionTracker;\n} & Pick<\n ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>,\n 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'\n>): Promise<void> {\n switch (tracker) {\n case SolanaTransactionTracker.Solana:\n await solanaTrackerForStore({\n tx,\n ...rest,\n });\n break;\n default:\n console.error(`Unknown tracker type for Solana adapter: ${tracker}`);\n // If an unsupported tracker is specified, mark the transaction as failed.\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Failed,\n pending: false,\n isError: true,\n errorMessage: `Unsupported tracker type: \"${tracker}\"`,\n });\n break;\n }\n}\n","/**\n * @file This file defines custom error classes for the @tuwaio/pulsar-solana package.\n */\n\nimport { SolanaCluster } from './types';\n\n/**\n * Thrown when the connected Solana cluster does not match the required cluster for a transaction.\n *\n * This allows consuming applications to `catch` this specific error and\n * implement custom logic, such as prompting the user to switch networks.\n */\nexport class SolanaChainMismatchError extends Error {\n /** The name of the error, for easy identification. */\n name = 'SolanaChainMismatchError';\n /** The cluster that the transaction requires (e.g., 'mainnet-beta'). */\n requiredCluster: SolanaCluster;\n /** The cluster the wallet is currently connected to. */\n currentCluster: SolanaCluster | string;\n\n constructor(requiredCluster: SolanaCluster, currentCluster: SolanaCluster | string) {\n const message = `Wrong network. The transaction requires ${requiredCluster}, but you are connected to ${currentCluster}.`;\n super(message);\n this.requiredCluster = requiredCluster;\n this.currentCluster = currentCluster;\n }\n}\n","/**\n * @file This file contains a utility to robustly verify the Solana cluster by its genesis hash.\n */\n\nimport { createSolanaRpc } from '@solana/kit';\n\nimport { SolanaChainMismatchError } from '../errors';\nimport { SolanaCluster } from '../types';\n\nconst SOLANA_GENESIS_HASHES: Record<SolanaCluster, string> = {\n 'mainnet-beta': '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d',\n devnet: 'GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC',\n testnet: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY',\n};\n\n/**\n * Checks if an RPC endpoint is connected to the required Solana cluster.\n * It fetches the genesis hash from the RPC endpoint and compares it\n * with a known hash for the specified cluster.\n *\n * @param {string} rpcUrl - The RPC endpoint URL to check.\n * @param {SolanaCluster} requiredCluster - The cluster that the transaction requires (e.g., 'mainnet-beta').\n * @throws {SolanaChainMismatchError} If the connected chain does not match the required cluster.\n * @throws {Error} If the cluster name is unknown or if the RPC call fails for other reasons.\n */\nexport const checkSolanaChain = async (rpcUrl: string, requiredCluster: SolanaCluster): Promise<void> => {\n const expectedGenesisHash = SOLANA_GENESIS_HASHES[requiredCluster];\n if (!expectedGenesisHash) {\n throw new Error(`Unknown Solana cluster specified: ${requiredCluster}`);\n }\n\n // Note: For optimal performance in a high-traffic app, the created RPC client could be cached.\n // However, for a one-off check like this, creating it on the fly is perfectly fine.\n const rpc = createSolanaRpc(rpcUrl);\n const currentGenesisHash = await rpc.getGenesisHash().send();\n\n if (currentGenesisHash !== expectedGenesisHash) {\n const currentClusterName =\n Object.entries(SOLANA_GENESIS_HASHES).find(([, hash]) => hash === currentGenesisHash)?.[0] ||\n 'an unknown network';\n\n throw new SolanaChainMismatchError(requiredCluster, currentClusterName as SolanaCluster | string);\n }\n};\n","/**\n * @file This file contains a utility function for generating Solana transaction explorer links.\n */\n\nimport { SolanaCluster } from '../types';\n\n/**\n * Generates a full URL to a transaction on a Solana explorer like Solscan.\n *\n * @param {string} baseUrl - The base URL of the explorer (e.g., \"https://solscan.io\").\n * @param {string} txKey - The transaction signature (hash).\n * @param {SolanaCluster} [cluster] - The optional cluster name ('devnet', 'testnet') to add as a query parameter.\n * @returns {string} The full URL to the transaction on the explorer.\n */\nexport const selectSolanaTxExplorerLink = (baseUrl: string, txKey: string, cluster?: SolanaCluster): string => {\n // Ensure there are no trailing slashes on the base URL for clean URL construction.\n const sanitizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n // Build the cluster query parameter if provided.\n const clusterParam = cluster ? `?cluster=${cluster}` : '';\n\n return `${sanitizedBaseUrl}/tx/${txKey}${clusterParam}`;\n};\n","/**\n * @file This file contains utility functions for interacting with the Solana Name Service (SNS) provided by Bonfida.\n */\n\nimport { getDomainKeysWithReverses, getRecord, performReverseLookup, Record } from '@bonfida/spl-name-service';\nimport { getBase58Decoder, getBase58Encoder } from '@solana/kit';\nimport { ConnectionContextState } from '@solana/wallet-adapter-react';\n\n// A minimal PublicKey class to satisfy the @bonfida/spl-name-service dependency\n// without importing the entire @solana/web3.js package. This is a form of duck typing.\nclass PublicKey {\n private readonly _bn: Uint8Array;\n\n constructor(value: string) {\n // The return type of `encode` is `ReadonlyUint8Array`, which we cast to `Uint8Array`.\n this._bn = getBase58Encoder().encode(value) as Uint8Array;\n }\n\n toBuffer(): Buffer {\n return Buffer.from(this._bn);\n }\n\n toString(): string {\n // The decoder returns a tuple [decodedString, bytesRead], we need the first element.\n const [decodedString] = getBase58Decoder().decode(this._bn);\n return decodedString;\n }\n}\n\n/**\n * Performs a reverse lookup to find the .sol domain name for a given wallet address.\n * @param {ConnectionContextState} connection - The connection state object from the `useConnection` hook.\n * @param {string} address - The public key of the wallet as a string.\n * @returns {Promise<string | null>} The .sol domain name (e.g., \"bonfida.sol\") or null if not found.\n */\nexport const getSolanaName = async (connection: ConnectionContextState, address: string): Promise<string | null> => {\n try {\n const pubKey = new PublicKey(address);\n\n // @ts-expect-error We are duck-typing the PublicKey to avoid a full dependency.\n const domainKeys = await getDomainKeysWithReverses(connection.connection, pubKey);\n\n if (domainKeys.length === 0) {\n return null;\n }\n\n // @ts-expect-error Duck-typing continued for the result.\n const domainName = await performReverseLookup(connection.connection, domainKeys[0]);\n return `${domainName}.sol`;\n } catch {\n // Fails silently if no domain is found.\n return null;\n }\n};\n\n/**\n * Retrieves the avatar URL from the 'pic' record of a .sol domain name.\n * @param {ConnectionContextState} connection - The connection state object from the `useConnection` hook.\n * @param {string} name - The .sol domain name (e.g., \"bonfida.sol\").\n * @returns {Promise<string | null>} The URL of the avatar or null if not found or set.\n */\nexport const getSolanaAvatar = async (connection: ConnectionContextState, name: string): Promise<string | null> => {\n try {\n const record = await getRecord(connection.connection, name, Record.Pic);\n\n if (!record || !record.data) {\n return null;\n }\n\n return record.data.toString('utf-8');\n } catch {\n // Fails silently if the record doesn't exist.\n return null;\n }\n};\n","/**\n * @file This file contains the factory function for creating the Solana adapter for Pulsar.\n */\n\nimport { createSolanaRpc } from '@solana/kit';\nimport { Transaction, TransactionAdapter, TransactionPool, TxAdapter } from '@tuwaio/pulsar-core';\n\nimport { SolanaActionTxKey, SolanaAdapterConfig, SolanaCluster, SolanaTransactionTracker } from '../types';\nimport { checkAndInitializeTrackerInStore } from '../utils/checkAndInitializeTrackerInStore';\nimport { checkSolanaChain } from '../utils/checkSolanaChain';\nimport { selectSolanaTxExplorerLink } from '../utils/selectSolanaTxExplorerLink';\nimport { getSolanaAvatar, getSolanaName } from '../utils/snsUtils';\n\n/**\n * Factory function to create a Solana adapter for Pulsar.\n * This adapter provides all the necessary logic to interact with the Solana ecosystem,\n * including wallet interactions, transaction tracking, and name services.\n *\n * @param {SolanaAdapterConfig} config - The configuration object, typically derived from Solana wallet adapter hooks.\n * @returns {TxAdapter} An object conforming to the `TxAdapter` interface.\n */\nexport function solanaAdapter<T extends Transaction<SolanaTransactionTracker>>(\n config: SolanaAdapterConfig,\n): TxAdapter<SolanaTransactionTracker, T, SolanaActionTxKey> {\n const { wallet, connection, explorerUrl, cluster } = config;\n\n return {\n key: TransactionAdapter.SOLANA,\n\n // --- Core Methods ---\n getWalletInfo: () => ({\n walletAddress: wallet?.publicKey?.toBase58() ?? '0x0',\n walletType: wallet?.wallet?.adapter.name.toLowerCase() ?? 'unknown',\n }),\n\n checkChainForTx: async (txCluster) => {\n // Use the rpcEndpoint from the main connection context for pre-flight checks.\n const currentRpcUrl = connection?.connection.rpcEndpoint;\n if (!currentRpcUrl) {\n // This can happen if the adapter is used in a read-only mode without a provider.\n // We warn but do not throw, allowing tracking to continue if the rpcUrl is in the tx.\n console.warn('Cannot check chain: connection context not provided to adapter.');\n return;\n }\n await checkSolanaChain(currentRpcUrl, txCluster as SolanaCluster);\n },\n\n checkTransactionsTracker: (actionTxKey) => {\n // For Solana, the actionTxKey is always the transaction signature.\n return { tracker: SolanaTransactionTracker.Solana, txKey: actionTxKey };\n },\n\n checkAndInitializeTrackerInStore: ({ tx, ...rest }) => {\n // Delegate to the tracker router utility.\n return checkAndInitializeTrackerInStore({\n tracker: tx.tracker,\n tx,\n ...rest,\n });\n },\n\n // --- UI & Explorer Methods ---\n getExplorerUrl: () => {\n return explorerUrl ?? 'https://solscan.io';\n },\n\n getExplorerTxUrl: (txPool: TransactionPool<any, any>, txKey: string) => {\n const baseUrl = explorerUrl ?? 'https://solscan.io';\n return selectSolanaTxExplorerLink(baseUrl, txKey, cluster);\n },\n\n // --- Optional Name Service Methods ---\n getName: async (address: string) => {\n if (!connection) {\n console.warn('Cannot get name: connection context not provided to adapter.');\n return null;\n }\n return getSolanaName(connection, address);\n },\n\n getAvatar: async (name: string) => {\n if (!connection) {\n console.warn('Cannot get avatar: connection context not provided to adapter.');\n return null;\n }\n return getSolanaAvatar(connection, name);\n },\n\n // --- Optional Actions ---\n retryTxAction: async ({ actions, onClose, txKey, handleTransaction, tx }) => {\n onClose(txKey);\n\n if (!wallet) {\n throw new Error('Retry failed: A wallet must be connected to retry a transaction.');\n }\n if (!handleTransaction) {\n throw new Error('Retry failed: handleTransaction function is not provided.');\n }\n\n const actionKey = tx.actionKey;\n if (!actionKey || !actions?.[actionKey]) {\n throw new Error(`Retry failed: No action found for actionKey \"${actionKey}\".`);\n }\n\n const retryAction = actions[actionKey];\n\n // Prioritize the RPC URL from the original transaction, falling back to the current connection.\n const rpcUrlForRetry = tx.rpcUrl ?? connection?.connection.rpcEndpoint;\n if (!rpcUrlForRetry) {\n throw new Error('Retry failed: Could not determine RPC endpoint.');\n }\n\n const rpcForRetry = createSolanaRpc(rpcUrlForRetry);\n\n await handleTransaction({\n actionFunction: () =>\n (retryAction as any)({\n wallet,\n connection: connection?.connection,\n rpc: rpcForRetry,\n ...tx.payload,\n }),\n params: tx,\n defaultTracker: SolanaTransactionTracker.Solana,\n });\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/utils/createSolanaRPC.ts","../src/trackers/solanaTracker.ts","../src/utils/checkAndInitializeTrackerInStore.ts","../src/utils/checkSolanaChain.ts","../src/utils/selectSolanaTxExplorerLink.ts","../src/utils/snsUtils.ts","../src/adapters/solanaAdapter.ts"],"names":["SolanaChainMismatchError","requiredChain","currentChain","message","SolanaTransactionTracker","rpcCache","createSolanaRPC","rpcUrl","newRpc","createSolanaRpc","solanaFetcher","tx","stopPolling","onSuccess","onFailure","onIntervalTick","TransactionAdapter","status","typedStatus","dayjs","solanaTrackerForStore","rest","initializePollingTracker","response","TransactionStatus","updatedTx","errorMessage","checkAndInitializeTrackerInStore","tracker","checkSolanaChain","selectSolanaTxExplorerLink","txKey","cluster","baseUrl","getExplorerLink","sanitizedBaseUrl","clusterParam","connectionCache","domainNameCache","getConnection","Connection","getSolanaName","address","connection","pubKey","PublicKey","domainKeys","getDomainKeysWithReverses","fullDomain","performReverseLookup","getSolanaAvatar","name","record","getRecord","Record","solanaAdapter","config","wallet","rpcUrls","getCluster","chain","defaultCluster","getRpcUrlForCluster","targetCluster","txChain","e","actionTxKey","txPool","actions","onClose","handleTransaction","actionKey","clusterForRetry","rpcUrlForRetry","retryAction","rpcForRetry"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @file This file defines custom error classes for the @tuwaio/pulsar-solana package.\n */\n\n/**\n * Thrown when the connected Solana chain does not match the required chain for a transaction.\n *\n * This allows consuming applications to `catch` this specific error and\n * implement custom logic, such as prompting the user to switch networks.\n */\nexport class SolanaChainMismatchError extends Error {\n /** The name of the error, for easy identification. */\n name = 'SolanaChainMismatchError';\n /** The chain that the transaction requires (e.g., 'solana:mainnet'). */\n requiredChain: string;\n /** The chain the wallet is currently connected to. */\n currentChain: string;\n\n constructor(requiredChain: string, currentChain: string) {\n const message = `Wrong chain. The transaction requires ${requiredChain}, but you are connected to ${currentChain}.`;\n super(message);\n this.requiredChain = requiredChain;\n this.currentChain = currentChain;\n }\n}\n","/**\n * @file Defines the core types and enums specific to the @tuwaio/pulsar-solana package.\n */\n\nimport type { SolanaClusterMoniker } from 'gill';\n\n/**\n * Describes the essential wallet information needed by the Solana adapter.\n * This simple, library-agnostic interface allows any wallet connection library\n * to be used with Pulsar, as long as it can provide this basic data.\n */\nexport interface SolanaAdapterWallet {\n walletAddress: string;\n walletType: string;\n walletActiveChain: SolanaClusterMoniker;\n}\n\n/**\n * The final, simplified configuration object for the solanaAdapter.\n *\n * @property {SolanaAdapterWallet} wallet - A simple object representing the current state of the user's wallet.\n * @property {Record<SolanaClusterMoniker, string>} rpcUrls - A map of RPC URLs for each supported Solana cluster.\n */\nexport interface SolanaAdapterConfig {\n wallet?: SolanaAdapterWallet;\n rpcUrls: Record<SolanaClusterMoniker, string>;\n}\n\n/**\n * Defines the tracker identifiers available in the Solana adapter.\n */\nexport enum SolanaTransactionTracker {\n /** The default tracker for monitoring standard Solana transaction signatures. */\n Solana = 'solana',\n}\n\n/**\n * Represents the unique key returned by a transaction-creating action on Solana.\n * For standard Solana transactions, this is always the transaction signature as a base58 string.\n */\nexport type SolanaActionTxKey = string;\n","// --- RPC Client Caching ---\n\nimport { createSolanaRpc, Rpc, SolanaRpcApi } from 'gill';\n\n/**\n * An in-memory cache for RPC clients to avoid re-creating them on every poll.\n * @internal\n */\nconst rpcCache = new Map<string, Rpc<SolanaRpcApi>>();\n\n/**\n * Retrieves a cached RPC client for a given URL or creates a new one.\n * @param rpcUrl - The RPC endpoint URL.\n * @returns The RPC client instance.\n * @internal\n */\nexport const createSolanaRPC = (rpcUrl: string): Rpc<SolanaRpcApi> => {\n if (rpcCache.has(rpcUrl)) {\n return rpcCache.get(rpcUrl)!;\n }\n const newRpc = createSolanaRpc(rpcUrl);\n rpcCache.set(rpcUrl, newRpc);\n return newRpc;\n};\n","/**\n * @file Implements the transaction tracking logic for standard Solana transactions.\n * It uses a polling mechanism to query the `getSignatureStatuses` RPC method.\n */\n\nimport {\n initializePollingTracker,\n ITxTrackingStore,\n PollingTrackerConfig,\n Transaction,\n TransactionAdapter,\n TransactionStatus,\n} from '@tuwaio/pulsar-core';\nimport dayjs from 'dayjs';\nimport { Signature, TransactionError } from 'gill';\n\nimport { SolanaActionTxKey, SolanaTransactionTracker } from '../types';\nimport { createSolanaRPC } from '../utils/createSolanaRPC';\n\n// --- Types ---\n\n/**\n * The shape of the status object returned for each signature from `getSignatureStatuses`.\n * @internal\n */\ntype SolanaSignatureStatusResponse = {\n slot: bigint;\n confirmations: number | null;\n err: TransactionError | null;\n confirmationStatus: 'processed' | 'confirmed' | 'finalized' | null;\n};\n\n/**\n * The parameters for our specific fetcher function.\n * @internal\n */\ntype SolanaFetcherParams = Parameters<\n PollingTrackerConfig<\n SolanaSignatureStatusResponse,\n Transaction<SolanaTransactionTracker>,\n SolanaTransactionTracker\n >['fetcher']\n>[0];\n\n// --- Fetcher Implementation ---\n\n/**\n * A reusable fetcher for `initializePollingTracker` that queries the Solana RPC for a transaction's signature status.\n * This is the core polling logic that powers the tracker.\n */\nexport async function solanaFetcher({ tx, stopPolling, onSuccess, onFailure, onIntervalTick }: SolanaFetcherParams) {\n if (tx.adapter !== TransactionAdapter.SOLANA || !tx.rpcUrl) {\n // This should not happen if the types are correct, but it's a good runtime safeguard.\n throw new Error('RPC URL is missing from the Solana transaction.');\n }\n\n const rpc = createSolanaRPC(tx.rpcUrl);\n const statuses = await rpc.getSignatureStatuses([tx.txKey as Signature]).send();\n const status = statuses?.value[0];\n\n if (!status) {\n // Continue polling if the transaction is not yet found by the RPC node.\n // The polling tracker will handle the retry delay.\n return;\n }\n\n const typedStatus = status as SolanaSignatureStatusResponse;\n onIntervalTick?.(typedStatus);\n\n if (typedStatus.err) {\n onFailure(typedStatus); // Terminal failure state\n return;\n }\n\n if (typedStatus.confirmationStatus === 'finalized') {\n onSuccess(typedStatus); // Terminal success state\n }\n\n // Safeguard: Stop polling for very old pending transactions.\n if (dayjs().diff(dayjs.unix(tx.localTimestamp), 'minute') >= 30) {\n stopPolling({ withoutRemoving: true });\n // When a timeout occurs, we call `onFailure` with the last known status,\n // but the tracker itself will provide the specific timeout error message.\n onFailure(typedStatus);\n }\n}\n\n// --- Store-Connected Tracker ---\n\n/**\n * A higher-level wrapper that integrates the Solana polling logic with the Pulsar store.\n * It uses the generic `solanaFetcher` and provides store-specific callbacks.\n *\n * @template T - The application-specific transaction type, constrained to Transaction.\n */\nexport async function solanaTrackerForStore<T extends Transaction<SolanaTransactionTracker>>({\n tx,\n ...rest\n}: Pick<\n ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>,\n 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'\n> & {\n tx: T;\n}) {\n return initializePollingTracker<SolanaSignatureStatusResponse, T, SolanaTransactionTracker>({\n tx,\n fetcher: solanaFetcher,\n removeTxFromPool: rest.removeTxFromPool,\n pollingInterval: 2500,\n maxRetries: 720, // 30 minutes timeout (720 intervals * 2.5s = 1800s)\n onSuccess: (response) => {\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Success,\n pending: false,\n isError: false,\n finishedTimestamp: dayjs().unix(),\n confirmations: response.confirmations ?? 1,\n slot: Number(response.slot),\n });\n\n const updatedTx = rest.transactionsPool[tx.txKey];\n if (rest.onSucceedCallbacks && updatedTx) {\n rest.onSucceedCallbacks(updatedTx);\n }\n },\n onIntervalTick: (response) => {\n rest.updateTxParams(tx.txKey, {\n confirmations: response.confirmations ?? 0,\n slot: Number(response.slot),\n });\n },\n onFailure: (response) => {\n // If `response` is undefined, it means the polling timed out from `initializePollingTracker`.\n // Otherwise, the transaction failed on-chain.\n const errorMessage = response?.err\n ? `Transaction failed: ${JSON.stringify(response.err)}`\n : 'Transaction tracking timed out or the transaction was not found.';\n\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Failed,\n pending: false,\n isError: true,\n errorMessage,\n finishedTimestamp: dayjs().unix(),\n });\n },\n });\n}\n","/**\n * @file This file contains the primary router for initializing transaction trackers.\n */\n\nimport { ITxTrackingStore, Transaction, TransactionStatus } from '@tuwaio/pulsar-core';\n\nimport { solanaTrackerForStore } from '../trackers/solanaTracker';\nimport { SolanaActionTxKey, SolanaTransactionTracker } from '../types';\n\n/**\n * Initializes the correct background tracker for a given Solana transaction.\n * This function acts as a router, selecting the appropriate tracker based on the `tx.tracker` property.\n *\n * @template T - The transaction type, constrained to Solana transactions.\n * @param {object} params - The parameters for initializing the tracker.\n * @param {T} params.tx - The transaction object to be tracked.\n * @param {SolanaTransactionTracker} params.tracker - The specific tracker to use.\n * @param {object} params.rest - The rest of the store's methods and state needed by the tracker.\n * @returns {Promise<void>} A promise that resolves when the tracker has been initialized.\n */\nexport async function checkAndInitializeTrackerInStore<T extends Transaction<SolanaTransactionTracker>>({\n tx,\n tracker,\n ...rest\n}: {\n tx: T;\n tracker: SolanaTransactionTracker;\n} & Pick<\n ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>,\n 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'\n>): Promise<void> {\n switch (tracker) {\n case SolanaTransactionTracker.Solana:\n await solanaTrackerForStore({\n tx,\n ...rest,\n });\n break;\n default:\n console.error(`Unknown tracker type for Solana adapter: ${tracker}`);\n // If an unsupported tracker is specified, mark the transaction as failed.\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Failed,\n pending: false,\n isError: true,\n errorMessage: `Unsupported tracker type: \"${tracker}\"`,\n });\n break;\n }\n}\n","/**\n * @file This file contains a utility to verify the connected Solana chain.\n */\n\nimport { SolanaChainMismatchError } from '../errors';\n\n/**\n * Checks if the wallet's current chain matches the required chain for a transaction.\n *\n * This function compares the `chain` property from the Wallet Standard account object\n * with the required chain identifier (e.g., 'solana:mainnet').\n *\n * @param {string} requiredChain - The chain identifier that the transaction requires.\n * @param {string} currentChain - The chain identifier the wallet is currently connected to.\n * @throws {SolanaChainMismatchError} If the connected chain does not match the required chain.\n */\nexport const checkSolanaChain = (requiredChain: string, currentChain: string): void => {\n if (currentChain !== requiredChain) {\n throw new SolanaChainMismatchError(requiredChain, currentChain);\n }\n};\n","/**\n * @file This file contains a utility function for generating Solana transaction explorer links.\n */\n\nimport { getExplorerLink, SolanaClusterMoniker } from 'gill';\n\n/**\n * Generates a full URL to a transaction on a Solana explorer like Solscan.\n *\n * @param {string} baseUrl - The base URL of the explorer (e.g., \"https://solscan.io\").\n * @param {string} txKey - The transaction signature (hash).\n * @param {SolanaCluster} [cluster] - The optional cluster name ('devnet', 'testnet') to add as a query parameter.\n * @returns {string} The full URL to the transaction on the explorer.\n */\nexport const selectSolanaTxExplorerLink = (txKey: string, cluster?: SolanaClusterMoniker): string => {\n // Ensure there are no trailing slashes on the base URL for clean URL construction.\n const baseUrl = getExplorerLink({ cluster });\n const sanitizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n // Build the cluster query parameter if provided.\n const clusterParam = cluster ? `?cluster=${cluster}` : '';\n\n return `${sanitizedBaseUrl}/tx/${txKey}${clusterParam}`;\n};\n","/**\n * @file This file contains utility functions for interacting with the Solana Name Service (SNS) provided by Bonfida.\n */\n\nimport { getDomainKeysWithReverses, getRecord, performReverseLookup, Record } from '@bonfida/spl-name-service';\nimport { Connection, PublicKey } from '@solana/web3.js';\n\n/**\n * A cache to store Connection instances for different RPC URLs.\n * This prevents creating a new Connection object for every function call.\n * @type {Map<string, Connection>}\n */\nconst connectionCache = new Map<string, Connection>();\n\n/**\n * A cache to store resolved domain names against wallet addresses.\n * This prevents repeated reverse lookups for the same address.\n * @type {Map<string, string | null>}\n */\nconst domainNameCache = new Map<string, string | null>();\n\n/**\n * Retrieves a cached Connection object or creates a new one if it doesn't exist.\n * @param {string} rpcUrl - The RPC endpoint URL.\n * @returns {Connection} An instance of the Connection class.\n */\nconst getConnection = (rpcUrl: string): Connection => {\n if (!connectionCache.has(rpcUrl)) {\n connectionCache.set(rpcUrl, new Connection(rpcUrl));\n }\n return connectionCache.get(rpcUrl)!;\n};\n\n/**\n * Performs a reverse lookup to find the .sol domain name for a given wallet address.\n * Results are cached to avoid redundant network requests.\n * @param {string} rpcUrl - The RPC endpoint URL.\n * @param {string} address - The public key of the wallet as a string.\n * @returns {Promise<string | null>} The .sol domain name (e.g., \"bonfida.sol\") or null if not found.\n */\nexport const getSolanaName = async (rpcUrl: string, address: string): Promise<string | null> => {\n // Return the cached domain name if it exists for the given address.\n if (domainNameCache.has(address)) {\n return domainNameCache.get(address)!;\n }\n\n try {\n const connection = getConnection(rpcUrl);\n const pubKey = new PublicKey(address);\n\n const domainKeys = await getDomainKeysWithReverses(connection, pubKey);\n\n if (domainKeys.length === 0) {\n // Cache the null result to prevent future lookups for this address.\n domainNameCache.set(address, null);\n return null;\n }\n\n // @ts-expect-error - domainKeys is an array of PublicKey objects.\n const domainName = await performReverseLookup(connection, domainKeys[0]);\n const fullDomain = `${domainName}.sol`;\n\n // Cache the successful result.\n domainNameCache.set(address, fullDomain);\n return fullDomain;\n } catch {\n // Cache the null result in case of an error.\n domainNameCache.set(address, null);\n return null;\n }\n};\n\n/**\n * Retrieves the avatar URL from the 'pic' record of a .sol domain name.\n * @param {string} rpcUrl - The RPC endpoint URL.\n * @param {string} name - The .sol domain name (e.g., \"bonfida.sol\").\n * @returns {Promise<string | null>} The URL of the avatar or null if not found or set.\n */\nexport const getSolanaAvatar = async (rpcUrl: string, name: string): Promise<string | null> => {\n try {\n const connection = getConnection(rpcUrl);\n const record = await getRecord(connection, name, Record.Pic);\n\n if (!record || !record.data) {\n return null;\n }\n\n return record.data.toString('utf-8');\n } catch {\n // Fails silently if the record doesn't exist.\n return null;\n }\n};\n","/**\n * @file This file contains the factory function for creating the Solana adapter for Pulsar.\n */\nimport type { Transaction, TxAdapter } from '@tuwaio/pulsar-core';\nimport { TransactionAdapter } from '@tuwaio/pulsar-core';\nimport { getExplorerLink, SolanaClusterMoniker } from 'gill';\n\nimport { SolanaChainMismatchError } from '../errors';\nimport { SolanaActionTxKey, SolanaAdapterConfig, SolanaTransactionTracker } from '../types';\nimport { checkAndInitializeTrackerInStore } from '../utils/checkAndInitializeTrackerInStore';\nimport { checkSolanaChain } from '../utils/checkSolanaChain';\nimport { createSolanaRPC } from '../utils/createSolanaRPC';\nimport { selectSolanaTxExplorerLink } from '../utils/selectSolanaTxExplorerLink';\nimport { getSolanaAvatar, getSolanaName } from '../utils/snsUtils';\n\n/**\n * Creates a Solana adapter for the Pulsar transaction tracking engine.\n * This factory function produces a wallet-library-agnostic adapter that can be\n * configured for multiple Solana clusters (e.g., mainnet-beta, devnet) and\n * can operate even without a connected wallet for read-only tasks.\n *\n * @param config The configuration object for the adapter.\n * @returns An object implementing the `TxAdapter` interface for Solana.\n */\nexport function solanaAdapter<T extends Transaction<SolanaTransactionTracker>>(\n config: SolanaAdapterConfig,\n): TxAdapter<SolanaTransactionTracker, T, SolanaActionTxKey> {\n const { wallet, rpcUrls } = config;\n\n /**\n * Safely extracts the cluster moniker from a chain identifier.\n * Handles both full chain IDs ('solana:mainnet-beta') and simple monikers ('mainnet-beta').\n * @param chain The chain identifier or moniker.\n * @returns The extracted cluster moniker.\n */\n const getCluster = (chain?: string): SolanaClusterMoniker => {\n const defaultCluster: SolanaClusterMoniker = 'mainnet';\n if (!chain) {\n return wallet?.walletActiveChain ?? defaultCluster;\n }\n return (chain.includes(':') ? chain.split(':')[1] : chain) as SolanaClusterMoniker;\n };\n\n /**\n * Retrieves the configured RPC URL for a given cluster moniker.\n * @param cluster The target cluster. Defaults to the wallet's active chain.\n * @returns The RPC URL or undefined if not found.\n */\n const getRpcUrlForCluster = (cluster?: SolanaClusterMoniker): string | undefined => {\n const targetCluster = cluster ?? wallet?.walletActiveChain;\n if (!targetCluster) return undefined;\n return rpcUrls[targetCluster];\n };\n\n return {\n key: TransactionAdapter.SOLANA,\n\n getWalletInfo: () => ({\n walletAddress: wallet?.walletAddress ?? '0x0',\n walletType: wallet?.walletType ?? 'disconnected',\n }),\n\n checkChainForTx: async (txChain) => {\n if (!wallet) {\n throw new Error('Wallet not provided. Cannot perform chain check.');\n }\n try {\n checkSolanaChain(txChain as string, wallet.walletActiveChain);\n } catch (e) {\n if (e instanceof SolanaChainMismatchError) throw e;\n throw new Error(`Chain check failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n },\n\n checkTransactionsTracker: (actionTxKey) => ({\n tracker: SolanaTransactionTracker.Solana,\n txKey: actionTxKey,\n }),\n\n checkAndInitializeTrackerInStore: ({ tx, ...rest }) => {\n return checkAndInitializeTrackerInStore({\n tracker: tx.tracker,\n tx,\n ...rest,\n });\n },\n\n getExplorerUrl: () => {\n const cluster = wallet?.walletActiveChain ?? 'mainnet-beta';\n return getExplorerLink({ cluster });\n },\n\n getExplorerTxUrl: (txPool, txKey) => {\n const tx = txPool[txKey];\n const cluster = getCluster(tx?.chainId as string);\n return selectSolanaTxExplorerLink(txKey, cluster);\n },\n\n getName: async (address) => {\n const rpcUrl = getRpcUrlForCluster(wallet?.walletActiveChain);\n if (!rpcUrl) {\n console.warn('Cannot get name: RPC URL for the current chain is not configured.');\n return null;\n }\n return getSolanaName(rpcUrl, address);\n },\n\n getAvatar: async (name) => {\n const rpcUrl = getRpcUrlForCluster(wallet?.walletActiveChain);\n if (!rpcUrl) {\n console.warn('Cannot get avatar: RPC URL for the current chain is not configured.');\n return null;\n }\n return getSolanaAvatar(rpcUrl, name);\n },\n\n retryTxAction: async ({ actions, onClose, txKey, handleTransaction, tx }) => {\n onClose(txKey);\n\n if (!wallet || !wallet.walletAddress || wallet.walletAddress === '0x0') {\n throw new Error('Retry failed: A wallet must be connected.');\n }\n if (!handleTransaction) {\n throw new Error('Retry failed: handleTransaction function is not provided.');\n }\n\n const actionKey = tx.actionKey;\n if (!actionKey || !actions?.[actionKey]) {\n throw new Error(`Retry failed: No action found for actionKey \"${actionKey}\".`);\n }\n\n const clusterForRetry = getCluster(tx.desiredChainID as string);\n const rpcUrlForRetry = tx.rpcUrl ?? getRpcUrlForCluster(clusterForRetry);\n if (!rpcUrlForRetry) {\n throw new Error('Retry failed: Could not determine RPC endpoint for the transaction chain.');\n }\n\n const retryAction = actions[actionKey];\n const rpcForRetry = createSolanaRPC(rpcUrlForRetry);\n\n const actionFunction = () =>\n (retryAction as any)({\n wallet: config.wallet,\n rpc: rpcForRetry,\n ...tx.payload,\n });\n\n await handleTransaction({\n actionFunction,\n params: tx,\n defaultTracker: SolanaTransactionTracker.Solana,\n });\n },\n };\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,3 +1,2 @@
1
- import {createSolanaRpc,getBase58Encoder,getBase58Decoder}from'@solana/kit';import {TransactionAdapter,initializePollingTracker,TransactionStatus}from'@tuwaio/pulsar-core';import u from'dayjs';import {getDomainKeysWithReverses,performReverseLookup,getRecord,Record}from'@bonfida/spl-name-service';var S=(n=>(n.Solana="solana",n))(S||{});var d=new Map,b=r=>{if(d.has(r))return d.get(r);let n=createSolanaRpc(r);return d.set(r,n),n};async function N({tx:r,stopPolling:n,onSuccess:t,onFailure:a,onIntervalTick:i}){if(r.adapter!==TransactionAdapter.SOLANA||!r.rpcUrl)throw new Error("RPC URL is missing from the Solana transaction.");let s=(await b(r.rpcUrl).getSignatureStatuses([r.txKey]).send())?.value[0];if(!s)return;let c=s;if(i?.(c),c.err){a(c);return}c.confirmationStatus==="finalized"&&t(c),u().diff(u.unix(r.localTimestamp),"minute")>=30&&(n({withoutRemoving:true}),a(c));}async function x({tx:r,...n}){return initializePollingTracker({tx:r,fetcher:N,removeTxFromPool:n.removeTxFromPool,pollingInterval:2500,maxRetries:720,onSuccess:t=>{n.updateTxParams(r.txKey,{status:TransactionStatus.Success,pending:false,isError:false,finishedTimestamp:u().unix(),confirmations:t.confirmations??1,slot:Number(t.slot)});let a=n.transactionsPool[r.txKey];n.onSucceedCallbacks&&a&&n.onSucceedCallbacks(a);},onIntervalTick:t=>{n.updateTxParams(r.txKey,{confirmations:t.confirmations??0,slot:Number(t.slot)});},onFailure:t=>{let a=t?.err?`Transaction failed: ${JSON.stringify(t.err)}`:"Transaction tracking timed out or the transaction was not found.";n.updateTxParams(r.txKey,{status:TransactionStatus.Failed,pending:false,isError:true,errorMessage:a,finishedTimestamp:u().unix()});}})}async function y({tx:r,tracker:n,...t}){switch(n){case "solana":await x({tx:r,...t});break;default:console.error(`Unknown tracker type for Solana adapter: ${n}`),t.updateTxParams(r.txKey,{status:TransactionStatus.Failed,pending:false,isError:true,errorMessage:`Unsupported tracker type: "${n}"`});break}}var m=class extends Error{name="SolanaChainMismatchError";requiredCluster;currentCluster;constructor(n,t){let a=`Wrong network. The transaction requires ${n}, but you are connected to ${t}.`;super(a),this.requiredCluster=n,this.currentCluster=t;}};var k={"mainnet-beta":"5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",devnet:"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",testnet:"4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY"},h=async(r,n)=>{let t=k[n];if(!t)throw new Error(`Unknown Solana cluster specified: ${n}`);let i=await createSolanaRpc(r).getGenesisHash().send();if(i!==t){let o=Object.entries(k).find(([,e])=>e===i)?.[0]||"an unknown network";throw new m(n,o)}};var C=(r,n,t)=>{let a=r.endsWith("/")?r.slice(0,-1):r,i=t?`?cluster=${t}`:"";return `${a}/tx/${n}${i}`};var f=class{_bn;constructor(n){this._bn=getBase58Encoder().encode(n);}toBuffer(){return Buffer.from(this._bn)}toString(){let[n]=getBase58Decoder().decode(this._bn);return n}},w=async(r,n)=>{try{let t=new f(n),a=await getDomainKeysWithReverses(r.connection,t);return a.length===0?null:`${await performReverseLookup(r.connection,a[0])}.sol`}catch{return null}},A=async(r,n)=>{try{let t=await getRecord(r.connection,n,Record.Pic);return !t||!t.data?null:t.data.toString("utf-8")}catch{return null}};function Kn(r){let{wallet:n,connection:t,explorerUrl:a,cluster:i}=r;return {key:TransactionAdapter.SOLANA,getWalletInfo:()=>({walletAddress:n?.publicKey?.toBase58()??"0x0",walletType:n?.wallet?.adapter.name.toLowerCase()??"unknown"}),checkChainForTx:async o=>{let e=t?.connection.rpcEndpoint;if(!e){console.warn("Cannot check chain: connection context not provided to adapter.");return}await h(e,o);},checkTransactionsTracker:o=>({tracker:"solana",txKey:o}),checkAndInitializeTrackerInStore:({tx:o,...e})=>y({tracker:o.tracker,tx:o,...e}),getExplorerUrl:()=>a??"https://solscan.io",getExplorerTxUrl:(o,e)=>C(a??"https://solscan.io",e,i),getName:async o=>t?w(t,o):(console.warn("Cannot get name: connection context not provided to adapter."),null),getAvatar:async o=>t?A(t,o):(console.warn("Cannot get avatar: connection context not provided to adapter."),null),retryTxAction:async({actions:o,onClose:e,txKey:s,handleTransaction:c,tx:l})=>{if(e(s),!n)throw new Error("Retry failed: A wallet must be connected to retry a transaction.");if(!c)throw new Error("Retry failed: handleTransaction function is not provided.");let p=l.actionKey;if(!p||!o?.[p])throw new Error(`Retry failed: No action found for actionKey "${p}".`);let R=o[p],T=l.rpcUrl??t?.connection.rpcEndpoint;if(!T)throw new Error("Retry failed: Could not determine RPC endpoint.");let P=createSolanaRpc(T);await c({actionFunction:()=>R({wallet:n,connection:t?.connection,rpc:P,...l.payload}),params:l,defaultTracker:"solana"});}}}
2
- export{m as SolanaChainMismatchError,S as SolanaTransactionTracker,y as checkAndInitializeTrackerInStore,h as checkSolanaChain,A as getSolanaAvatar,w as getSolanaName,C as selectSolanaTxExplorerLink,Kn as solanaAdapter,N as solanaFetcher,x as solanaTrackerForStore};//# sourceMappingURL=index.mjs.map
1
+ import {TransactionAdapter,initializePollingTracker,TransactionStatus}from'@tuwaio/pulsar-core';import {createSolanaRpc,getExplorerLink}from'gill';import S from'dayjs';import {getDomainKeysWithReverses,performReverseLookup,getRecord,Record}from'@bonfida/spl-name-service';import {PublicKey,Connection}from'@solana/web3.js';var l=class extends Error{name="SolanaChainMismatchError";requiredChain;currentChain;constructor(r,t){let o=`Wrong chain. The transaction requires ${r}, but you are connected to ${t}.`;super(o),this.requiredChain=r,this.currentChain=t;}};var T=(r=>(r.Solana="solana",r))(T||{});var d=new Map,f=n=>{if(d.has(n))return d.get(n);let r=createSolanaRpc(n);return d.set(n,r),r};async function I({tx:n,stopPolling:r,onSuccess:t,onFailure:o,onIntervalTick:s}){if(n.adapter!==TransactionAdapter.SOLANA||!n.rpcUrl)throw new Error("RPC URL is missing from the Solana transaction.");let c=(await f(n.rpcUrl).getSignatureStatuses([n.txKey]).send())?.value[0];if(!c)return;let i=c;if(s?.(i),i.err){o(i);return}i.confirmationStatus==="finalized"&&t(i),S().diff(S.unix(n.localTimestamp),"minute")>=30&&(r({withoutRemoving:true}),o(i));}async function x({tx:n,...r}){return initializePollingTracker({tx:n,fetcher:I,removeTxFromPool:r.removeTxFromPool,pollingInterval:2500,maxRetries:720,onSuccess:t=>{r.updateTxParams(n.txKey,{status:TransactionStatus.Success,pending:false,isError:false,finishedTimestamp:S().unix(),confirmations:t.confirmations??1,slot:Number(t.slot)});let o=r.transactionsPool[n.txKey];r.onSucceedCallbacks&&o&&r.onSucceedCallbacks(o);},onIntervalTick:t=>{r.updateTxParams(n.txKey,{confirmations:t.confirmations??0,slot:Number(t.slot)});},onFailure:t=>{let o=t?.err?`Transaction failed: ${JSON.stringify(t.err)}`:"Transaction tracking timed out or the transaction was not found.";r.updateTxParams(n.txKey,{status:TransactionStatus.Failed,pending:false,isError:true,errorMessage:o,finishedTimestamp:S().unix()});}})}async function y({tx:n,tracker:r,...t}){switch(r){case "solana":await x({tx:n,...t});break;default:console.error(`Unknown tracker type for Solana adapter: ${r}`),t.updateTxParams(n.txKey,{status:TransactionStatus.Failed,pending:false,isError:true,errorMessage:`Unsupported tracker type: "${r}"`});break}}var w=(n,r)=>{if(r!==n)throw new l(n,r)};var C=(n,r)=>{let t=getExplorerLink({cluster:r}),o=t.endsWith("/")?t.slice(0,-1):t,s=r?`?cluster=${r}`:"";return `${o}/tx/${n}${s}`};var g=new Map,p=new Map,A=n=>(g.has(n)||g.set(n,new Connection(n)),g.get(n)),R=async(n,r)=>{if(p.has(r))return p.get(r);try{let t=A(n),o=new PublicKey(r),s=await getDomainKeysWithReverses(t,o);if(s.length===0)return p.set(r,null),null;let a=`${await performReverseLookup(t,s[0])}.sol`;return p.set(r,a),a}catch{return p.set(r,null),null}},P=async(n,r)=>{try{let t=A(n),o=await getRecord(t,r,Record.Pic);return !o||!o.data?null:o.data.toString("utf-8")}catch{return null}};function Mr(n){let{wallet:r,rpcUrls:t}=n,o=e=>{let a="mainnet";return e?e.includes(":")?e.split(":")[1]:e:r?.walletActiveChain??a},s=e=>{let a=e??r?.walletActiveChain;if(a)return t[a]};return {key:TransactionAdapter.SOLANA,getWalletInfo:()=>({walletAddress:r?.walletAddress??"0x0",walletType:r?.walletType??"disconnected"}),checkChainForTx:async e=>{if(!r)throw new Error("Wallet not provided. Cannot perform chain check.");try{w(e,r.walletActiveChain);}catch(a){throw a instanceof l?a:new Error(`Chain check failed: ${a instanceof Error?a.message:String(a)}`)}},checkTransactionsTracker:e=>({tracker:"solana",txKey:e}),checkAndInitializeTrackerInStore:({tx:e,...a})=>y({tracker:e.tracker,tx:e,...a}),getExplorerUrl:()=>{let e=r?.walletActiveChain??"mainnet-beta";return getExplorerLink({cluster:e})},getExplorerTxUrl:(e,a)=>{let c=e[a],i=o(c?.chainId);return C(a,i)},getName:async e=>{let a=s(r?.walletActiveChain);return a?R(a,e):(console.warn("Cannot get name: RPC URL for the current chain is not configured."),null)},getAvatar:async e=>{let a=s(r?.walletActiveChain);return a?P(a,e):(console.warn("Cannot get avatar: RPC URL for the current chain is not configured."),null)},retryTxAction:async({actions:e,onClose:a,txKey:c,handleTransaction:i,tx:u})=>{if(a(c),!r||!r.walletAddress||r.walletAddress==="0x0")throw new Error("Retry failed: A wallet must be connected.");if(!i)throw new Error("Retry failed: handleTransaction function is not provided.");let m=u.actionKey;if(!m||!e?.[m])throw new Error(`Retry failed: No action found for actionKey "${m}".`);let v=o(u.desiredChainID),h=u.rpcUrl??s(v);if(!h)throw new Error("Retry failed: Could not determine RPC endpoint for the transaction chain.");let E=e[m],K=f(h);await i({actionFunction:()=>E({wallet:n.wallet,rpc:K,...u.payload}),params:u,defaultTracker:"solana"});}}}export{l as SolanaChainMismatchError,T as SolanaTransactionTracker,y as checkAndInitializeTrackerInStore,w as checkSolanaChain,f as createSolanaRPC,P as getSolanaAvatar,R as getSolanaName,C as selectSolanaTxExplorerLink,Mr as solanaAdapter,I as solanaFetcher,x as solanaTrackerForStore};//# sourceMappingURL=index.mjs.map
3
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/trackers/solanaTracker.ts","../src/utils/checkAndInitializeTrackerInStore.ts","../src/errors.ts","../src/utils/checkSolanaChain.ts","../src/utils/selectSolanaTxExplorerLink.ts","../src/utils/snsUtils.ts","../src/adapters/solanaAdapter.ts"],"names":["SolanaTransactionTracker","rpcCache","getRpcClient","rpcUrl","newRpc","createSolanaRpc","solanaFetcher","tx","stopPolling","onSuccess","onFailure","onIntervalTick","TransactionAdapter","status","typedStatus","dayjs","solanaTrackerForStore","rest","initializePollingTracker","response","TransactionStatus","updatedTx","errorMessage","checkAndInitializeTrackerInStore","tracker","SolanaChainMismatchError","requiredCluster","currentCluster","message","SOLANA_GENESIS_HASHES","checkSolanaChain","expectedGenesisHash","currentGenesisHash","currentClusterName","hash","selectSolanaTxExplorerLink","baseUrl","txKey","cluster","sanitizedBaseUrl","clusterParam","PublicKey","value","getBase58Encoder","decodedString","getBase58Decoder","getSolanaName","connection","address","pubKey","domainKeys","getDomainKeysWithReverses","performReverseLookup","getSolanaAvatar","name","record","getRecord","Record","solanaAdapter","config","wallet","explorerUrl","txCluster","currentRpcUrl","actionTxKey","txPool","actions","onClose","handleTransaction","actionKey","retryAction","rpcUrlForRetry","rpcForRetry"],"mappings":"ySA8BO,IAAKA,CAAAA,CAAAA,CAAAA,CAAAA,GAEVA,CAAAA,CAAA,MAAA,CAAS,SAFCA,CAAAA,CAAAA,EAAAA,CAAAA,EAAA,EAAA,ECNZ,IAAMC,CAAAA,CAAW,IAAI,GAAA,CAQfC,EAAgBC,CAAAA,EAAsC,CAC1D,GAAIF,CAAAA,CAAS,GAAA,CAAIE,CAAM,CAAA,CACrB,OAAOF,CAAAA,CAAS,GAAA,CAAIE,CAAM,CAAA,CAE5B,IAAMC,CAAAA,CAASC,eAAAA,CAAgBF,CAAM,CAAA,CACrC,OAAAF,EAAS,GAAA,CAAIE,CAAAA,CAAQC,CAAM,CAAA,CACpBA,CACT,CAAA,CAiCA,eAAsBE,CAAAA,CAAc,CAAE,GAAAC,CAAAA,CAAI,WAAA,CAAAC,EAAa,SAAA,CAAAC,CAAAA,CAAW,SAAA,CAAAC,CAAAA,CAAW,cAAA,CAAAC,CAAe,EAAwB,CAClH,GAAIJ,EAAG,OAAA,GAAYK,kBAAAA,CAAmB,QAAU,CAACL,CAAAA,CAAG,MAAA,CAElD,MAAM,IAAI,KAAA,CAAM,iDAAiD,CAAA,CAKnE,IAAMM,CAAAA,CAAAA,CADW,MADLX,CAAAA,CAAaK,CAAAA,CAAG,MAAM,CAAA,CACP,oBAAA,CAAqB,CAACA,CAAAA,CAAG,KAAkB,CAAC,EAAE,IAAA,EAAK,GACrD,MAAM,CAAC,CAAA,CAEhC,GAAI,CAACM,CAAAA,CAGH,OAGF,IAAMC,CAAAA,CAAcD,CAAAA,CAGpB,GAFAF,CAAAA,GAAiBG,CAAW,EAExBA,CAAAA,CAAY,GAAA,CAAK,CACnBJ,CAAAA,CAAUI,CAAW,CAAA,CACrB,MACF,CAEIA,CAAAA,CAAY,qBAAuB,WAAA,EACrCL,CAAAA,CAAUK,CAAW,CAAA,CAInBC,CAAAA,GAAQ,IAAA,CAAKA,CAAAA,CAAM,IAAA,CAAKR,CAAAA,CAAG,cAAc,CAAA,CAAG,QAAQ,CAAA,EAAK,EAAA,GAC3DC,CAAAA,CAAY,CAAE,eAAA,CAAiB,IAAK,CAAC,CAAA,CAGrCE,CAAAA,CAAUI,CAAW,CAAA,EAEzB,CAUA,eAAsBE,EAAuE,CAC3F,EAAA,CAAAT,EACA,GAAGU,CACL,EAKG,CACD,OAAOC,wBAAAA,CAAqF,CAC1F,EAAA,CAAAX,CAAAA,CACA,QAASD,CAAAA,CACT,gBAAA,CAAkBW,EAAK,gBAAA,CACvB,eAAA,CAAiB,KACjB,UAAA,CAAY,GAAA,CACZ,SAAA,CAAYE,CAAAA,EAAa,CACvBF,CAAAA,CAAK,eAAeV,CAAAA,CAAG,KAAA,CAAO,CAC5B,MAAA,CAAQa,iBAAAA,CAAkB,QAC1B,OAAA,CAAS,KAAA,CACT,OAAA,CAAS,KAAA,CACT,iBAAA,CAAmBL,CAAAA,GAAQ,IAAA,EAAK,CAChC,aAAA,CAAeI,CAAAA,CAAS,aAAA,EAAiB,CAAA,CACzC,KAAM,MAAA,CAAOA,CAAAA,CAAS,IAAI,CAC5B,CAAC,CAAA,CAED,IAAME,CAAAA,CAAYJ,CAAAA,CAAK,iBAAiBV,CAAAA,CAAG,KAAK,EAC5CU,CAAAA,CAAK,kBAAA,EAAsBI,CAAAA,EAC7BJ,CAAAA,CAAK,kBAAA,CAAmBI,CAAS,EAErC,CAAA,CACA,cAAA,CAAiBF,GAAa,CAC5BF,CAAAA,CAAK,eAAeV,CAAAA,CAAG,KAAA,CAAO,CAC5B,aAAA,CAAeY,CAAAA,CAAS,aAAA,EAAiB,EACzC,IAAA,CAAM,MAAA,CAAOA,EAAS,IAAI,CAC5B,CAAC,EACH,CAAA,CACA,SAAA,CAAYA,CAAAA,EAAa,CAGvB,IAAMG,EAAeH,CAAAA,EAAU,GAAA,CAC3B,uBAAuB,IAAA,CAAK,SAAA,CAAUA,EAAS,GAAG,CAAC,CAAA,CAAA,CACnD,kEAAA,CAEJF,CAAAA,CAAK,cAAA,CAAeV,EAAG,KAAA,CAAO,CAC5B,OAAQa,iBAAAA,CAAkB,MAAA,CAC1B,QAAS,KAAA,CACT,OAAA,CAAS,IAAA,CACT,YAAA,CAAAE,CAAAA,CACA,iBAAA,CAAmBP,GAAM,CAAE,IAAA,EAC7B,CAAC,EACH,CACF,CAAC,CACH,CCrJA,eAAsBQ,CAAAA,CAAkF,CACtG,GAAAhB,CAAAA,CACA,OAAA,CAAAiB,EACA,GAAGP,CACL,EAMkB,CAChB,OAAQO,CAAAA,EACN,KAAA,QAAA,CACE,MAAMR,EAAsB,CAC1B,EAAA,CAAAT,CAAAA,CACA,GAAGU,CACL,CAAC,EACD,MACF,QACE,OAAA,CAAQ,KAAA,CAAM,CAAA,yCAAA,EAA4CO,CAAO,EAAE,CAAA,CAEnEP,CAAAA,CAAK,eAAeV,CAAAA,CAAG,KAAA,CAAO,CAC5B,MAAA,CAAQa,iBAAAA,CAAkB,MAAA,CAC1B,OAAA,CAAS,KAAA,CACT,OAAA,CAAS,KACT,YAAA,CAAc,CAAA,2BAAA,EAA8BI,CAAO,CAAA,CAAA,CACrD,CAAC,EACD,KACJ,CACF,CCrCO,IAAMC,CAAAA,CAAN,cAAuC,KAAM,CAElD,KAAO,0BAAA,CAEP,eAAA,CAEA,cAAA,CAEA,WAAA,CAAYC,CAAAA,CAAgCC,CAAAA,CAAwC,CAClF,IAAMC,CAAAA,CAAU,CAAA,wCAAA,EAA2CF,CAAe,CAAA,2BAAA,EAA8BC,CAAc,IACtH,KAAA,CAAMC,CAAO,CAAA,CACb,IAAA,CAAK,eAAA,CAAkBF,CAAAA,CACvB,KAAK,cAAA,CAAiBC,EACxB,CACF,ECjBA,IAAME,EAAuD,CAC3D,cAAA,CAAgB,8CAAA,CAChB,MAAA,CAAQ,8CAAA,CACR,OAAA,CAAS,8CACX,CAAA,CAYaC,CAAAA,CAAmB,MAAO3B,CAAAA,CAAgBuB,CAAAA,GAAkD,CACvG,IAAMK,CAAAA,CAAsBF,CAAAA,CAAsBH,CAAe,CAAA,CACjE,GAAI,CAACK,CAAAA,CACH,MAAM,IAAI,KAAA,CAAM,CAAA,kCAAA,EAAqCL,CAAe,CAAA,CAAE,CAAA,CAMxE,IAAMM,CAAAA,CAAqB,MADf3B,eAAAA,CAAgBF,CAAM,CAAA,CACG,cAAA,EAAe,CAAE,IAAA,EAAK,CAE3D,GAAI6B,IAAuBD,CAAAA,CAAqB,CAC9C,IAAME,CAAAA,CACJ,MAAA,CAAO,OAAA,CAAQJ,CAAqB,CAAA,CAAE,IAAA,CAAK,CAAC,EAAGK,CAAI,CAAA,GAAMA,CAAAA,GAASF,CAAkB,CAAA,GAAI,CAAC,CAAA,EACzF,qBAEF,MAAM,IAAIP,EAAyBC,CAAAA,CAAiBO,CAA4C,CAClG,CACF,EC7BO,IAAME,CAAAA,CAA6B,CAACC,CAAAA,CAAiBC,EAAeC,CAAAA,GAAoC,CAE7G,IAAMC,CAAAA,CAAmBH,CAAAA,CAAQ,SAAS,GAAG,CAAA,CAAIA,CAAAA,CAAQ,KAAA,CAAM,CAAA,CAAG,EAAE,EAAIA,CAAAA,CAGlEI,CAAAA,CAAeF,EAAU,CAAA,SAAA,EAAYA,CAAO,GAAK,EAAA,CAEvD,OAAO,CAAA,EAAGC,CAAgB,CAAA,IAAA,EAAOF,CAAK,GAAGG,CAAY,CAAA,CACvD,ECZA,IAAMC,CAAAA,CAAN,KAAgB,CACG,GAAA,CAEjB,WAAA,CAAYC,EAAe,CAEzB,IAAA,CAAK,IAAMC,gBAAAA,EAAiB,CAAE,OAAOD,CAAK,EAC5C,CAEA,QAAA,EAAmB,CACjB,OAAO,OAAO,IAAA,CAAK,IAAA,CAAK,GAAG,CAC7B,CAEA,QAAA,EAAmB,CAEjB,GAAM,CAACE,CAAa,CAAA,CAAIC,gBAAAA,EAAiB,CAAE,OAAO,IAAA,CAAK,GAAG,EAC1D,OAAOD,CACT,CACF,CAAA,CAQaE,CAAAA,CAAgB,MAAOC,CAAAA,CAAoCC,CAAAA,GAA4C,CAClH,GAAI,CACF,IAAMC,EAAS,IAAIR,CAAAA,CAAUO,CAAO,CAAA,CAG9BE,CAAAA,CAAa,MAAMC,yBAAAA,CAA0BJ,CAAAA,CAAW,UAAA,CAAYE,CAAM,CAAA,CAEhF,OAAIC,EAAW,MAAA,GAAW,CAAA,CACjB,KAKF,CAAA,EADY,MAAME,oBAAAA,CAAqBL,CAAAA,CAAW,UAAA,CAAYG,CAAAA,CAAW,CAAC,CAAC,CAC9D,CAAA,IAAA,CACtB,CAAA,KAAQ,CAEN,OAAO,IACT,CACF,CAAA,CAQaG,CAAAA,CAAkB,MAAON,CAAAA,CAAoCO,CAAAA,GAAyC,CACjH,GAAI,CACF,IAAMC,CAAAA,CAAS,MAAMC,UAAUT,CAAAA,CAAW,UAAA,CAAYO,CAAAA,CAAMG,MAAAA,CAAO,GAAG,CAAA,CAEtE,OAAI,CAACF,CAAAA,EAAU,CAACA,CAAAA,CAAO,IAAA,CACd,KAGFA,CAAAA,CAAO,IAAA,CAAK,QAAA,CAAS,OAAO,CACrC,CAAA,KAAQ,CAEN,OAAO,IACT,CACF,ECrDO,SAASG,GACdC,CAAAA,CAC2D,CAC3D,GAAM,CAAE,MAAA,CAAAC,CAAAA,CAAQ,WAAAb,CAAAA,CAAY,WAAA,CAAAc,CAAAA,CAAa,OAAA,CAAAvB,CAAQ,CAAA,CAAIqB,EAErD,OAAO,CACL,GAAA,CAAK/C,kBAAAA,CAAmB,MAAA,CAGxB,aAAA,CAAe,KAAO,CACpB,aAAA,CAAegD,GAAQ,SAAA,EAAW,QAAA,IAAc,KAAA,CAChD,UAAA,CAAYA,CAAAA,EAAQ,MAAA,EAAQ,OAAA,CAAQ,IAAA,CAAK,aAAY,EAAK,SAC5D,GAEA,eAAA,CAAiB,MAAOE,GAAc,CAEpC,IAAMC,CAAAA,CAAgBhB,CAAAA,EAAY,UAAA,CAAW,WAAA,CAC7C,GAAI,CAACgB,CAAAA,CAAe,CAGlB,OAAA,CAAQ,IAAA,CAAK,iEAAiE,CAAA,CAC9E,MACF,CACA,MAAMjC,CAAAA,CAAiBiC,CAAAA,CAAeD,CAA0B,EAClE,CAAA,CAEA,yBAA2BE,CAAAA,GAElB,CAAE,iBAA0C,KAAA,CAAOA,CAAY,CAAA,CAAA,CAGxE,gCAAA,CAAkC,CAAC,CAAE,GAAAzD,CAAAA,CAAI,GAAGU,CAAK,CAAA,GAExCM,CAAAA,CAAiC,CACtC,OAAA,CAAShB,CAAAA,CAAG,OAAA,CACZ,EAAA,CAAAA,CAAAA,CACA,GAAGU,CACL,CAAC,CAAA,CAIH,eAAgB,IACP4C,CAAAA,EAAe,qBAGxB,gBAAA,CAAkB,CAACI,CAAAA,CAAmC5B,CAAAA,GAE7CF,CAAAA,CADS0B,CAAAA,EAAe,qBACYxB,CAAAA,CAAOC,CAAO,EAI3D,OAAA,CAAS,MAAOU,GACTD,CAAAA,CAIED,CAAAA,CAAcC,CAAAA,CAAYC,CAAO,CAAA,EAHtC,OAAA,CAAQ,KAAK,8DAA8D,CAAA,CACpE,IAAA,CAAA,CAKX,SAAA,CAAW,MAAOM,CAAAA,EACXP,EAIEM,CAAAA,CAAgBN,CAAAA,CAAYO,CAAI,CAAA,EAHrC,OAAA,CAAQ,IAAA,CAAK,gEAAgE,CAAA,CACtE,IAAA,CAAA,CAMX,cAAe,MAAO,CAAE,QAAAY,CAAAA,CAAS,OAAA,CAAAC,CAAAA,CAAS,KAAA,CAAA9B,CAAAA,CAAO,iBAAA,CAAA+B,EAAmB,EAAA,CAAA7D,CAAG,IAAM,CAG3E,GAFA4D,EAAQ9B,CAAK,CAAA,CAET,CAACuB,CAAAA,CACH,MAAM,IAAI,MAAM,kEAAkE,CAAA,CAEpF,GAAI,CAACQ,CAAAA,CACH,MAAM,IAAI,KAAA,CAAM,2DAA2D,CAAA,CAG7E,IAAMC,CAAAA,CAAY9D,EAAG,SAAA,CACrB,GAAI,CAAC8D,CAAAA,EAAa,CAACH,CAAAA,GAAUG,CAAS,CAAA,CACpC,MAAM,IAAI,KAAA,CAAM,CAAA,6CAAA,EAAgDA,CAAS,IAAI,CAAA,CAG/E,IAAMC,EAAcJ,CAAAA,CAAQG,CAAS,EAG/BE,CAAAA,CAAiBhE,CAAAA,CAAG,MAAA,EAAUwC,CAAAA,EAAY,UAAA,CAAW,WAAA,CAC3D,GAAI,CAACwB,CAAAA,CACH,MAAM,IAAI,KAAA,CAAM,iDAAiD,CAAA,CAGnE,IAAMC,CAAAA,CAAcnE,eAAAA,CAAgBkE,CAAc,CAAA,CAElD,MAAMH,CAAAA,CAAkB,CACtB,eAAgB,IACbE,CAAAA,CAAoB,CACnB,MAAA,CAAAV,CAAAA,CACA,UAAA,CAAYb,CAAAA,EAAY,UAAA,CACxB,GAAA,CAAKyB,EACL,GAAGjE,CAAAA,CAAG,OACR,CAAC,CAAA,CACH,MAAA,CAAQA,EACR,cAAA,CAAA,QACF,CAAC,EACH,CACF,CACF","file":"index.mjs","sourcesContent":["/**\n * @file Defines the core types and enums specific to the @tuwaio/pulsar-solana package.\n */\n\nimport { ConnectionContextState, WalletContextState } from '@solana/wallet-adapter-react';\n\n/**\n * Defines the possible Solana network clusters.\n */\nexport type SolanaCluster = 'mainnet-beta' | 'devnet' | 'testnet';\n\n/**\n * Configuration object for the `solanaAdapter`.\n * All properties are optional and are typically derived from the Solana wallet adapter hooks.\n *\n * @property {WalletContextState} [wallet] - The state object from `useWallet()`. Required for actions that need a connected wallet, like signing or retrying transactions.\n * @property {ConnectionContextState} [connection] - The state object from `useConnection()`. Required for on-chain operations like checking the network or using the Solana Name Service.\n * @property {string} [explorerUrl] - The base URL for the transaction explorer (e.g., \"https://solscan.io\"). Defaults to Solscan if not provided.\n * @property {SolanaCluster} [cluster] - The specific cluster the app is connected to. Used for generating correct explorer links.\n */\nexport interface SolanaAdapterConfig {\n wallet?: WalletContextState;\n connection?: ConnectionContextState;\n explorerUrl?: string;\n cluster?: SolanaCluster;\n}\n\n/**\n * Defines the tracker identifiers available in the Solana adapter.\n */\nexport enum SolanaTransactionTracker {\n /** The default tracker for monitoring standard Solana transaction signatures. */\n Solana = 'solana',\n}\n\n/**\n * Represents the unique key returned by a transaction-creating action on Solana.\n * For standard Solana transactions, this is always the transaction signature as a base58 string.\n */\nexport type SolanaActionTxKey = string;\n","/**\n * @file Implements the transaction tracking logic for standard Solana transactions.\n * It uses a polling mechanism to query the `getSignatureStatuses` RPC method.\n */\n\nimport { createSolanaRpc, Rpc, Signature, SolanaRpcApi, TransactionError } from '@solana/kit';\nimport {\n initializePollingTracker,\n ITxTrackingStore,\n PollingTrackerConfig,\n Transaction,\n TransactionAdapter,\n TransactionStatus,\n} from '@tuwaio/pulsar-core';\nimport dayjs from 'dayjs';\n\nimport { SolanaActionTxKey, SolanaTransactionTracker } from '../types';\n\n// --- RPC Client Caching ---\n\n/**\n * An in-memory cache for RPC clients to avoid re-creating them on every poll.\n * @internal\n */\nconst rpcCache = new Map<string, Rpc<SolanaRpcApi>>();\n\n/**\n * Retrieves a cached RPC client for a given URL or creates a new one.\n * @param rpcUrl - The RPC endpoint URL.\n * @returns The RPC client instance.\n * @internal\n */\nconst getRpcClient = (rpcUrl: string): Rpc<SolanaRpcApi> => {\n if (rpcCache.has(rpcUrl)) {\n return rpcCache.get(rpcUrl)!;\n }\n const newRpc = createSolanaRpc(rpcUrl);\n rpcCache.set(rpcUrl, newRpc);\n return newRpc;\n};\n\n// --- Types ---\n\n/**\n * The shape of the status object returned for each signature from `getSignatureStatuses`.\n * @internal\n */\ntype SolanaSignatureStatusResponse = {\n slot: bigint;\n confirmations: number | null;\n err: TransactionError | null;\n confirmationStatus: 'processed' | 'confirmed' | 'finalized' | null;\n};\n\n/**\n * The parameters for our specific fetcher function.\n * @internal\n */\ntype SolanaFetcherParams = Parameters<\n PollingTrackerConfig<\n SolanaSignatureStatusResponse,\n Transaction<SolanaTransactionTracker>,\n SolanaTransactionTracker\n >['fetcher']\n>[0];\n\n// --- Fetcher Implementation ---\n\n/**\n * A reusable fetcher for `initializePollingTracker` that queries the Solana RPC for a transaction's signature status.\n * This is the core polling logic that powers the tracker.\n */\nexport async function solanaFetcher({ tx, stopPolling, onSuccess, onFailure, onIntervalTick }: SolanaFetcherParams) {\n if (tx.adapter !== TransactionAdapter.SOLANA || !tx.rpcUrl) {\n // This should not happen if the types are correct, but it's a good runtime safeguard.\n throw new Error('RPC URL is missing from the Solana transaction.');\n }\n\n const rpc = getRpcClient(tx.rpcUrl);\n const statuses = await rpc.getSignatureStatuses([tx.txKey as Signature]).send();\n const status = statuses?.value[0];\n\n if (!status) {\n // Continue polling if the transaction is not yet found by the RPC node.\n // The polling tracker will handle the retry delay.\n return;\n }\n\n const typedStatus = status as SolanaSignatureStatusResponse;\n onIntervalTick?.(typedStatus);\n\n if (typedStatus.err) {\n onFailure(typedStatus); // Terminal failure state\n return;\n }\n\n if (typedStatus.confirmationStatus === 'finalized') {\n onSuccess(typedStatus); // Terminal success state\n }\n\n // Safeguard: Stop polling for very old pending transactions.\n if (dayjs().diff(dayjs.unix(tx.localTimestamp), 'minute') >= 30) {\n stopPolling({ withoutRemoving: true });\n // When a timeout occurs, we call `onFailure` with the last known status,\n // but the tracker itself will provide the specific timeout error message.\n onFailure(typedStatus);\n }\n}\n\n// --- Store-Connected Tracker ---\n\n/**\n * A higher-level wrapper that integrates the Solana polling logic with the Pulsar store.\n * It uses the generic `solanaFetcher` and provides store-specific callbacks.\n *\n * @template T - The application-specific transaction type, constrained to Transaction.\n */\nexport async function solanaTrackerForStore<T extends Transaction<SolanaTransactionTracker>>({\n tx,\n ...rest\n}: Pick<\n ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>,\n 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'\n> & {\n tx: T;\n}) {\n return initializePollingTracker<SolanaSignatureStatusResponse, T, SolanaTransactionTracker>({\n tx,\n fetcher: solanaFetcher,\n removeTxFromPool: rest.removeTxFromPool,\n pollingInterval: 2500,\n maxRetries: 720, // 30 minutes timeout (720 intervals * 2.5s = 1800s)\n onSuccess: (response) => {\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Success,\n pending: false,\n isError: false,\n finishedTimestamp: dayjs().unix(),\n confirmations: response.confirmations ?? 1,\n slot: Number(response.slot),\n });\n\n const updatedTx = rest.transactionsPool[tx.txKey];\n if (rest.onSucceedCallbacks && updatedTx) {\n rest.onSucceedCallbacks(updatedTx);\n }\n },\n onIntervalTick: (response) => {\n rest.updateTxParams(tx.txKey, {\n confirmations: response.confirmations ?? 0,\n slot: Number(response.slot),\n });\n },\n onFailure: (response) => {\n // If `response` is undefined, it means the polling timed out from `initializePollingTracker`.\n // Otherwise, the transaction failed on-chain.\n const errorMessage = response?.err\n ? `Transaction failed: ${JSON.stringify(response.err)}`\n : 'Transaction tracking timed out or the transaction was not found.';\n\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Failed,\n pending: false,\n isError: true,\n errorMessage,\n finishedTimestamp: dayjs().unix(),\n });\n },\n });\n}\n","/**\n * @file This file contains the primary router for initializing transaction trackers.\n */\n\nimport { ITxTrackingStore, Transaction, TransactionStatus } from '@tuwaio/pulsar-core';\n\nimport { solanaTrackerForStore } from '../trackers/solanaTracker';\nimport { SolanaActionTxKey, SolanaTransactionTracker } from '../types';\n\n/**\n * Initializes the correct background tracker for a given Solana transaction.\n * This function acts as a router, selecting the appropriate tracker based on the `tx.tracker` property.\n *\n * @template T - The transaction type, constrained to Solana transactions.\n * @param {object} params - The parameters for initializing the tracker.\n * @param {T} params.tx - The transaction object to be tracked.\n * @param {SolanaTransactionTracker} params.tracker - The specific tracker to use.\n * @param {object} params.rest - The rest of the store's methods and state needed by the tracker.\n * @returns {Promise<void>} A promise that resolves when the tracker has been initialized.\n */\nexport async function checkAndInitializeTrackerInStore<T extends Transaction<SolanaTransactionTracker>>({\n tx,\n tracker,\n ...rest\n}: {\n tx: T;\n tracker: SolanaTransactionTracker;\n} & Pick<\n ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>,\n 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'\n>): Promise<void> {\n switch (tracker) {\n case SolanaTransactionTracker.Solana:\n await solanaTrackerForStore({\n tx,\n ...rest,\n });\n break;\n default:\n console.error(`Unknown tracker type for Solana adapter: ${tracker}`);\n // If an unsupported tracker is specified, mark the transaction as failed.\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Failed,\n pending: false,\n isError: true,\n errorMessage: `Unsupported tracker type: \"${tracker}\"`,\n });\n break;\n }\n}\n","/**\n * @file This file defines custom error classes for the @tuwaio/pulsar-solana package.\n */\n\nimport { SolanaCluster } from './types';\n\n/**\n * Thrown when the connected Solana cluster does not match the required cluster for a transaction.\n *\n * This allows consuming applications to `catch` this specific error and\n * implement custom logic, such as prompting the user to switch networks.\n */\nexport class SolanaChainMismatchError extends Error {\n /** The name of the error, for easy identification. */\n name = 'SolanaChainMismatchError';\n /** The cluster that the transaction requires (e.g., 'mainnet-beta'). */\n requiredCluster: SolanaCluster;\n /** The cluster the wallet is currently connected to. */\n currentCluster: SolanaCluster | string;\n\n constructor(requiredCluster: SolanaCluster, currentCluster: SolanaCluster | string) {\n const message = `Wrong network. The transaction requires ${requiredCluster}, but you are connected to ${currentCluster}.`;\n super(message);\n this.requiredCluster = requiredCluster;\n this.currentCluster = currentCluster;\n }\n}\n","/**\n * @file This file contains a utility to robustly verify the Solana cluster by its genesis hash.\n */\n\nimport { createSolanaRpc } from '@solana/kit';\n\nimport { SolanaChainMismatchError } from '../errors';\nimport { SolanaCluster } from '../types';\n\nconst SOLANA_GENESIS_HASHES: Record<SolanaCluster, string> = {\n 'mainnet-beta': '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d',\n devnet: 'GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC',\n testnet: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY',\n};\n\n/**\n * Checks if an RPC endpoint is connected to the required Solana cluster.\n * It fetches the genesis hash from the RPC endpoint and compares it\n * with a known hash for the specified cluster.\n *\n * @param {string} rpcUrl - The RPC endpoint URL to check.\n * @param {SolanaCluster} requiredCluster - The cluster that the transaction requires (e.g., 'mainnet-beta').\n * @throws {SolanaChainMismatchError} If the connected chain does not match the required cluster.\n * @throws {Error} If the cluster name is unknown or if the RPC call fails for other reasons.\n */\nexport const checkSolanaChain = async (rpcUrl: string, requiredCluster: SolanaCluster): Promise<void> => {\n const expectedGenesisHash = SOLANA_GENESIS_HASHES[requiredCluster];\n if (!expectedGenesisHash) {\n throw new Error(`Unknown Solana cluster specified: ${requiredCluster}`);\n }\n\n // Note: For optimal performance in a high-traffic app, the created RPC client could be cached.\n // However, for a one-off check like this, creating it on the fly is perfectly fine.\n const rpc = createSolanaRpc(rpcUrl);\n const currentGenesisHash = await rpc.getGenesisHash().send();\n\n if (currentGenesisHash !== expectedGenesisHash) {\n const currentClusterName =\n Object.entries(SOLANA_GENESIS_HASHES).find(([, hash]) => hash === currentGenesisHash)?.[0] ||\n 'an unknown network';\n\n throw new SolanaChainMismatchError(requiredCluster, currentClusterName as SolanaCluster | string);\n }\n};\n","/**\n * @file This file contains a utility function for generating Solana transaction explorer links.\n */\n\nimport { SolanaCluster } from '../types';\n\n/**\n * Generates a full URL to a transaction on a Solana explorer like Solscan.\n *\n * @param {string} baseUrl - The base URL of the explorer (e.g., \"https://solscan.io\").\n * @param {string} txKey - The transaction signature (hash).\n * @param {SolanaCluster} [cluster] - The optional cluster name ('devnet', 'testnet') to add as a query parameter.\n * @returns {string} The full URL to the transaction on the explorer.\n */\nexport const selectSolanaTxExplorerLink = (baseUrl: string, txKey: string, cluster?: SolanaCluster): string => {\n // Ensure there are no trailing slashes on the base URL for clean URL construction.\n const sanitizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n // Build the cluster query parameter if provided.\n const clusterParam = cluster ? `?cluster=${cluster}` : '';\n\n return `${sanitizedBaseUrl}/tx/${txKey}${clusterParam}`;\n};\n","/**\n * @file This file contains utility functions for interacting with the Solana Name Service (SNS) provided by Bonfida.\n */\n\nimport { getDomainKeysWithReverses, getRecord, performReverseLookup, Record } from '@bonfida/spl-name-service';\nimport { getBase58Decoder, getBase58Encoder } from '@solana/kit';\nimport { ConnectionContextState } from '@solana/wallet-adapter-react';\n\n// A minimal PublicKey class to satisfy the @bonfida/spl-name-service dependency\n// without importing the entire @solana/web3.js package. This is a form of duck typing.\nclass PublicKey {\n private readonly _bn: Uint8Array;\n\n constructor(value: string) {\n // The return type of `encode` is `ReadonlyUint8Array`, which we cast to `Uint8Array`.\n this._bn = getBase58Encoder().encode(value) as Uint8Array;\n }\n\n toBuffer(): Buffer {\n return Buffer.from(this._bn);\n }\n\n toString(): string {\n // The decoder returns a tuple [decodedString, bytesRead], we need the first element.\n const [decodedString] = getBase58Decoder().decode(this._bn);\n return decodedString;\n }\n}\n\n/**\n * Performs a reverse lookup to find the .sol domain name for a given wallet address.\n * @param {ConnectionContextState} connection - The connection state object from the `useConnection` hook.\n * @param {string} address - The public key of the wallet as a string.\n * @returns {Promise<string | null>} The .sol domain name (e.g., \"bonfida.sol\") or null if not found.\n */\nexport const getSolanaName = async (connection: ConnectionContextState, address: string): Promise<string | null> => {\n try {\n const pubKey = new PublicKey(address);\n\n // @ts-expect-error We are duck-typing the PublicKey to avoid a full dependency.\n const domainKeys = await getDomainKeysWithReverses(connection.connection, pubKey);\n\n if (domainKeys.length === 0) {\n return null;\n }\n\n // @ts-expect-error Duck-typing continued for the result.\n const domainName = await performReverseLookup(connection.connection, domainKeys[0]);\n return `${domainName}.sol`;\n } catch {\n // Fails silently if no domain is found.\n return null;\n }\n};\n\n/**\n * Retrieves the avatar URL from the 'pic' record of a .sol domain name.\n * @param {ConnectionContextState} connection - The connection state object from the `useConnection` hook.\n * @param {string} name - The .sol domain name (e.g., \"bonfida.sol\").\n * @returns {Promise<string | null>} The URL of the avatar or null if not found or set.\n */\nexport const getSolanaAvatar = async (connection: ConnectionContextState, name: string): Promise<string | null> => {\n try {\n const record = await getRecord(connection.connection, name, Record.Pic);\n\n if (!record || !record.data) {\n return null;\n }\n\n return record.data.toString('utf-8');\n } catch {\n // Fails silently if the record doesn't exist.\n return null;\n }\n};\n","/**\n * @file This file contains the factory function for creating the Solana adapter for Pulsar.\n */\n\nimport { createSolanaRpc } from '@solana/kit';\nimport { Transaction, TransactionAdapter, TransactionPool, TxAdapter } from '@tuwaio/pulsar-core';\n\nimport { SolanaActionTxKey, SolanaAdapterConfig, SolanaCluster, SolanaTransactionTracker } from '../types';\nimport { checkAndInitializeTrackerInStore } from '../utils/checkAndInitializeTrackerInStore';\nimport { checkSolanaChain } from '../utils/checkSolanaChain';\nimport { selectSolanaTxExplorerLink } from '../utils/selectSolanaTxExplorerLink';\nimport { getSolanaAvatar, getSolanaName } from '../utils/snsUtils';\n\n/**\n * Factory function to create a Solana adapter for Pulsar.\n * This adapter provides all the necessary logic to interact with the Solana ecosystem,\n * including wallet interactions, transaction tracking, and name services.\n *\n * @param {SolanaAdapterConfig} config - The configuration object, typically derived from Solana wallet adapter hooks.\n * @returns {TxAdapter} An object conforming to the `TxAdapter` interface.\n */\nexport function solanaAdapter<T extends Transaction<SolanaTransactionTracker>>(\n config: SolanaAdapterConfig,\n): TxAdapter<SolanaTransactionTracker, T, SolanaActionTxKey> {\n const { wallet, connection, explorerUrl, cluster } = config;\n\n return {\n key: TransactionAdapter.SOLANA,\n\n // --- Core Methods ---\n getWalletInfo: () => ({\n walletAddress: wallet?.publicKey?.toBase58() ?? '0x0',\n walletType: wallet?.wallet?.adapter.name.toLowerCase() ?? 'unknown',\n }),\n\n checkChainForTx: async (txCluster) => {\n // Use the rpcEndpoint from the main connection context for pre-flight checks.\n const currentRpcUrl = connection?.connection.rpcEndpoint;\n if (!currentRpcUrl) {\n // This can happen if the adapter is used in a read-only mode without a provider.\n // We warn but do not throw, allowing tracking to continue if the rpcUrl is in the tx.\n console.warn('Cannot check chain: connection context not provided to adapter.');\n return;\n }\n await checkSolanaChain(currentRpcUrl, txCluster as SolanaCluster);\n },\n\n checkTransactionsTracker: (actionTxKey) => {\n // For Solana, the actionTxKey is always the transaction signature.\n return { tracker: SolanaTransactionTracker.Solana, txKey: actionTxKey };\n },\n\n checkAndInitializeTrackerInStore: ({ tx, ...rest }) => {\n // Delegate to the tracker router utility.\n return checkAndInitializeTrackerInStore({\n tracker: tx.tracker,\n tx,\n ...rest,\n });\n },\n\n // --- UI & Explorer Methods ---\n getExplorerUrl: () => {\n return explorerUrl ?? 'https://solscan.io';\n },\n\n getExplorerTxUrl: (txPool: TransactionPool<any, any>, txKey: string) => {\n const baseUrl = explorerUrl ?? 'https://solscan.io';\n return selectSolanaTxExplorerLink(baseUrl, txKey, cluster);\n },\n\n // --- Optional Name Service Methods ---\n getName: async (address: string) => {\n if (!connection) {\n console.warn('Cannot get name: connection context not provided to adapter.');\n return null;\n }\n return getSolanaName(connection, address);\n },\n\n getAvatar: async (name: string) => {\n if (!connection) {\n console.warn('Cannot get avatar: connection context not provided to adapter.');\n return null;\n }\n return getSolanaAvatar(connection, name);\n },\n\n // --- Optional Actions ---\n retryTxAction: async ({ actions, onClose, txKey, handleTransaction, tx }) => {\n onClose(txKey);\n\n if (!wallet) {\n throw new Error('Retry failed: A wallet must be connected to retry a transaction.');\n }\n if (!handleTransaction) {\n throw new Error('Retry failed: handleTransaction function is not provided.');\n }\n\n const actionKey = tx.actionKey;\n if (!actionKey || !actions?.[actionKey]) {\n throw new Error(`Retry failed: No action found for actionKey \"${actionKey}\".`);\n }\n\n const retryAction = actions[actionKey];\n\n // Prioritize the RPC URL from the original transaction, falling back to the current connection.\n const rpcUrlForRetry = tx.rpcUrl ?? connection?.connection.rpcEndpoint;\n if (!rpcUrlForRetry) {\n throw new Error('Retry failed: Could not determine RPC endpoint.');\n }\n\n const rpcForRetry = createSolanaRpc(rpcUrlForRetry);\n\n await handleTransaction({\n actionFunction: () =>\n (retryAction as any)({\n wallet,\n connection: connection?.connection,\n rpc: rpcForRetry,\n ...tx.payload,\n }),\n params: tx,\n defaultTracker: SolanaTransactionTracker.Solana,\n });\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/utils/createSolanaRPC.ts","../src/trackers/solanaTracker.ts","../src/utils/checkAndInitializeTrackerInStore.ts","../src/utils/checkSolanaChain.ts","../src/utils/selectSolanaTxExplorerLink.ts","../src/utils/snsUtils.ts","../src/adapters/solanaAdapter.ts"],"names":["SolanaChainMismatchError","requiredChain","currentChain","message","SolanaTransactionTracker","rpcCache","createSolanaRPC","rpcUrl","newRpc","createSolanaRpc","solanaFetcher","tx","stopPolling","onSuccess","onFailure","onIntervalTick","TransactionAdapter","status","typedStatus","dayjs","solanaTrackerForStore","rest","initializePollingTracker","response","TransactionStatus","updatedTx","errorMessage","checkAndInitializeTrackerInStore","tracker","checkSolanaChain","selectSolanaTxExplorerLink","txKey","cluster","baseUrl","getExplorerLink","sanitizedBaseUrl","clusterParam","connectionCache","domainNameCache","getConnection","Connection","getSolanaName","address","connection","pubKey","PublicKey","domainKeys","getDomainKeysWithReverses","fullDomain","performReverseLookup","getSolanaAvatar","name","record","getRecord","Record","solanaAdapter","config","wallet","rpcUrls","getCluster","chain","defaultCluster","getRpcUrlForCluster","targetCluster","txChain","e","actionTxKey","txPool","actions","onClose","handleTransaction","actionKey","clusterForRetry","rpcUrlForRetry","retryAction","rpcForRetry"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @file This file defines custom error classes for the @tuwaio/pulsar-solana package.\n */\n\n/**\n * Thrown when the connected Solana chain does not match the required chain for a transaction.\n *\n * This allows consuming applications to `catch` this specific error and\n * implement custom logic, such as prompting the user to switch networks.\n */\nexport class SolanaChainMismatchError extends Error {\n /** The name of the error, for easy identification. */\n name = 'SolanaChainMismatchError';\n /** The chain that the transaction requires (e.g., 'solana:mainnet'). */\n requiredChain: string;\n /** The chain the wallet is currently connected to. */\n currentChain: string;\n\n constructor(requiredChain: string, currentChain: string) {\n const message = `Wrong chain. The transaction requires ${requiredChain}, but you are connected to ${currentChain}.`;\n super(message);\n this.requiredChain = requiredChain;\n this.currentChain = currentChain;\n }\n}\n","/**\n * @file Defines the core types and enums specific to the @tuwaio/pulsar-solana package.\n */\n\nimport type { SolanaClusterMoniker } from 'gill';\n\n/**\n * Describes the essential wallet information needed by the Solana adapter.\n * This simple, library-agnostic interface allows any wallet connection library\n * to be used with Pulsar, as long as it can provide this basic data.\n */\nexport interface SolanaAdapterWallet {\n walletAddress: string;\n walletType: string;\n walletActiveChain: SolanaClusterMoniker;\n}\n\n/**\n * The final, simplified configuration object for the solanaAdapter.\n *\n * @property {SolanaAdapterWallet} wallet - A simple object representing the current state of the user's wallet.\n * @property {Record<SolanaClusterMoniker, string>} rpcUrls - A map of RPC URLs for each supported Solana cluster.\n */\nexport interface SolanaAdapterConfig {\n wallet?: SolanaAdapterWallet;\n rpcUrls: Record<SolanaClusterMoniker, string>;\n}\n\n/**\n * Defines the tracker identifiers available in the Solana adapter.\n */\nexport enum SolanaTransactionTracker {\n /** The default tracker for monitoring standard Solana transaction signatures. */\n Solana = 'solana',\n}\n\n/**\n * Represents the unique key returned by a transaction-creating action on Solana.\n * For standard Solana transactions, this is always the transaction signature as a base58 string.\n */\nexport type SolanaActionTxKey = string;\n","// --- RPC Client Caching ---\n\nimport { createSolanaRpc, Rpc, SolanaRpcApi } from 'gill';\n\n/**\n * An in-memory cache for RPC clients to avoid re-creating them on every poll.\n * @internal\n */\nconst rpcCache = new Map<string, Rpc<SolanaRpcApi>>();\n\n/**\n * Retrieves a cached RPC client for a given URL or creates a new one.\n * @param rpcUrl - The RPC endpoint URL.\n * @returns The RPC client instance.\n * @internal\n */\nexport const createSolanaRPC = (rpcUrl: string): Rpc<SolanaRpcApi> => {\n if (rpcCache.has(rpcUrl)) {\n return rpcCache.get(rpcUrl)!;\n }\n const newRpc = createSolanaRpc(rpcUrl);\n rpcCache.set(rpcUrl, newRpc);\n return newRpc;\n};\n","/**\n * @file Implements the transaction tracking logic for standard Solana transactions.\n * It uses a polling mechanism to query the `getSignatureStatuses` RPC method.\n */\n\nimport {\n initializePollingTracker,\n ITxTrackingStore,\n PollingTrackerConfig,\n Transaction,\n TransactionAdapter,\n TransactionStatus,\n} from '@tuwaio/pulsar-core';\nimport dayjs from 'dayjs';\nimport { Signature, TransactionError } from 'gill';\n\nimport { SolanaActionTxKey, SolanaTransactionTracker } from '../types';\nimport { createSolanaRPC } from '../utils/createSolanaRPC';\n\n// --- Types ---\n\n/**\n * The shape of the status object returned for each signature from `getSignatureStatuses`.\n * @internal\n */\ntype SolanaSignatureStatusResponse = {\n slot: bigint;\n confirmations: number | null;\n err: TransactionError | null;\n confirmationStatus: 'processed' | 'confirmed' | 'finalized' | null;\n};\n\n/**\n * The parameters for our specific fetcher function.\n * @internal\n */\ntype SolanaFetcherParams = Parameters<\n PollingTrackerConfig<\n SolanaSignatureStatusResponse,\n Transaction<SolanaTransactionTracker>,\n SolanaTransactionTracker\n >['fetcher']\n>[0];\n\n// --- Fetcher Implementation ---\n\n/**\n * A reusable fetcher for `initializePollingTracker` that queries the Solana RPC for a transaction's signature status.\n * This is the core polling logic that powers the tracker.\n */\nexport async function solanaFetcher({ tx, stopPolling, onSuccess, onFailure, onIntervalTick }: SolanaFetcherParams) {\n if (tx.adapter !== TransactionAdapter.SOLANA || !tx.rpcUrl) {\n // This should not happen if the types are correct, but it's a good runtime safeguard.\n throw new Error('RPC URL is missing from the Solana transaction.');\n }\n\n const rpc = createSolanaRPC(tx.rpcUrl);\n const statuses = await rpc.getSignatureStatuses([tx.txKey as Signature]).send();\n const status = statuses?.value[0];\n\n if (!status) {\n // Continue polling if the transaction is not yet found by the RPC node.\n // The polling tracker will handle the retry delay.\n return;\n }\n\n const typedStatus = status as SolanaSignatureStatusResponse;\n onIntervalTick?.(typedStatus);\n\n if (typedStatus.err) {\n onFailure(typedStatus); // Terminal failure state\n return;\n }\n\n if (typedStatus.confirmationStatus === 'finalized') {\n onSuccess(typedStatus); // Terminal success state\n }\n\n // Safeguard: Stop polling for very old pending transactions.\n if (dayjs().diff(dayjs.unix(tx.localTimestamp), 'minute') >= 30) {\n stopPolling({ withoutRemoving: true });\n // When a timeout occurs, we call `onFailure` with the last known status,\n // but the tracker itself will provide the specific timeout error message.\n onFailure(typedStatus);\n }\n}\n\n// --- Store-Connected Tracker ---\n\n/**\n * A higher-level wrapper that integrates the Solana polling logic with the Pulsar store.\n * It uses the generic `solanaFetcher` and provides store-specific callbacks.\n *\n * @template T - The application-specific transaction type, constrained to Transaction.\n */\nexport async function solanaTrackerForStore<T extends Transaction<SolanaTransactionTracker>>({\n tx,\n ...rest\n}: Pick<\n ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>,\n 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'\n> & {\n tx: T;\n}) {\n return initializePollingTracker<SolanaSignatureStatusResponse, T, SolanaTransactionTracker>({\n tx,\n fetcher: solanaFetcher,\n removeTxFromPool: rest.removeTxFromPool,\n pollingInterval: 2500,\n maxRetries: 720, // 30 minutes timeout (720 intervals * 2.5s = 1800s)\n onSuccess: (response) => {\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Success,\n pending: false,\n isError: false,\n finishedTimestamp: dayjs().unix(),\n confirmations: response.confirmations ?? 1,\n slot: Number(response.slot),\n });\n\n const updatedTx = rest.transactionsPool[tx.txKey];\n if (rest.onSucceedCallbacks && updatedTx) {\n rest.onSucceedCallbacks(updatedTx);\n }\n },\n onIntervalTick: (response) => {\n rest.updateTxParams(tx.txKey, {\n confirmations: response.confirmations ?? 0,\n slot: Number(response.slot),\n });\n },\n onFailure: (response) => {\n // If `response` is undefined, it means the polling timed out from `initializePollingTracker`.\n // Otherwise, the transaction failed on-chain.\n const errorMessage = response?.err\n ? `Transaction failed: ${JSON.stringify(response.err)}`\n : 'Transaction tracking timed out or the transaction was not found.';\n\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Failed,\n pending: false,\n isError: true,\n errorMessage,\n finishedTimestamp: dayjs().unix(),\n });\n },\n });\n}\n","/**\n * @file This file contains the primary router for initializing transaction trackers.\n */\n\nimport { ITxTrackingStore, Transaction, TransactionStatus } from '@tuwaio/pulsar-core';\n\nimport { solanaTrackerForStore } from '../trackers/solanaTracker';\nimport { SolanaActionTxKey, SolanaTransactionTracker } from '../types';\n\n/**\n * Initializes the correct background tracker for a given Solana transaction.\n * This function acts as a router, selecting the appropriate tracker based on the `tx.tracker` property.\n *\n * @template T - The transaction type, constrained to Solana transactions.\n * @param {object} params - The parameters for initializing the tracker.\n * @param {T} params.tx - The transaction object to be tracked.\n * @param {SolanaTransactionTracker} params.tracker - The specific tracker to use.\n * @param {object} params.rest - The rest of the store's methods and state needed by the tracker.\n * @returns {Promise<void>} A promise that resolves when the tracker has been initialized.\n */\nexport async function checkAndInitializeTrackerInStore<T extends Transaction<SolanaTransactionTracker>>({\n tx,\n tracker,\n ...rest\n}: {\n tx: T;\n tracker: SolanaTransactionTracker;\n} & Pick<\n ITxTrackingStore<SolanaTransactionTracker, T, SolanaActionTxKey>,\n 'transactionsPool' | 'updateTxParams' | 'onSucceedCallbacks' | 'removeTxFromPool'\n>): Promise<void> {\n switch (tracker) {\n case SolanaTransactionTracker.Solana:\n await solanaTrackerForStore({\n tx,\n ...rest,\n });\n break;\n default:\n console.error(`Unknown tracker type for Solana adapter: ${tracker}`);\n // If an unsupported tracker is specified, mark the transaction as failed.\n rest.updateTxParams(tx.txKey, {\n status: TransactionStatus.Failed,\n pending: false,\n isError: true,\n errorMessage: `Unsupported tracker type: \"${tracker}\"`,\n });\n break;\n }\n}\n","/**\n * @file This file contains a utility to verify the connected Solana chain.\n */\n\nimport { SolanaChainMismatchError } from '../errors';\n\n/**\n * Checks if the wallet's current chain matches the required chain for a transaction.\n *\n * This function compares the `chain` property from the Wallet Standard account object\n * with the required chain identifier (e.g., 'solana:mainnet').\n *\n * @param {string} requiredChain - The chain identifier that the transaction requires.\n * @param {string} currentChain - The chain identifier the wallet is currently connected to.\n * @throws {SolanaChainMismatchError} If the connected chain does not match the required chain.\n */\nexport const checkSolanaChain = (requiredChain: string, currentChain: string): void => {\n if (currentChain !== requiredChain) {\n throw new SolanaChainMismatchError(requiredChain, currentChain);\n }\n};\n","/**\n * @file This file contains a utility function for generating Solana transaction explorer links.\n */\n\nimport { getExplorerLink, SolanaClusterMoniker } from 'gill';\n\n/**\n * Generates a full URL to a transaction on a Solana explorer like Solscan.\n *\n * @param {string} baseUrl - The base URL of the explorer (e.g., \"https://solscan.io\").\n * @param {string} txKey - The transaction signature (hash).\n * @param {SolanaCluster} [cluster] - The optional cluster name ('devnet', 'testnet') to add as a query parameter.\n * @returns {string} The full URL to the transaction on the explorer.\n */\nexport const selectSolanaTxExplorerLink = (txKey: string, cluster?: SolanaClusterMoniker): string => {\n // Ensure there are no trailing slashes on the base URL for clean URL construction.\n const baseUrl = getExplorerLink({ cluster });\n const sanitizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n // Build the cluster query parameter if provided.\n const clusterParam = cluster ? `?cluster=${cluster}` : '';\n\n return `${sanitizedBaseUrl}/tx/${txKey}${clusterParam}`;\n};\n","/**\n * @file This file contains utility functions for interacting with the Solana Name Service (SNS) provided by Bonfida.\n */\n\nimport { getDomainKeysWithReverses, getRecord, performReverseLookup, Record } from '@bonfida/spl-name-service';\nimport { Connection, PublicKey } from '@solana/web3.js';\n\n/**\n * A cache to store Connection instances for different RPC URLs.\n * This prevents creating a new Connection object for every function call.\n * @type {Map<string, Connection>}\n */\nconst connectionCache = new Map<string, Connection>();\n\n/**\n * A cache to store resolved domain names against wallet addresses.\n * This prevents repeated reverse lookups for the same address.\n * @type {Map<string, string | null>}\n */\nconst domainNameCache = new Map<string, string | null>();\n\n/**\n * Retrieves a cached Connection object or creates a new one if it doesn't exist.\n * @param {string} rpcUrl - The RPC endpoint URL.\n * @returns {Connection} An instance of the Connection class.\n */\nconst getConnection = (rpcUrl: string): Connection => {\n if (!connectionCache.has(rpcUrl)) {\n connectionCache.set(rpcUrl, new Connection(rpcUrl));\n }\n return connectionCache.get(rpcUrl)!;\n};\n\n/**\n * Performs a reverse lookup to find the .sol domain name for a given wallet address.\n * Results are cached to avoid redundant network requests.\n * @param {string} rpcUrl - The RPC endpoint URL.\n * @param {string} address - The public key of the wallet as a string.\n * @returns {Promise<string | null>} The .sol domain name (e.g., \"bonfida.sol\") or null if not found.\n */\nexport const getSolanaName = async (rpcUrl: string, address: string): Promise<string | null> => {\n // Return the cached domain name if it exists for the given address.\n if (domainNameCache.has(address)) {\n return domainNameCache.get(address)!;\n }\n\n try {\n const connection = getConnection(rpcUrl);\n const pubKey = new PublicKey(address);\n\n const domainKeys = await getDomainKeysWithReverses(connection, pubKey);\n\n if (domainKeys.length === 0) {\n // Cache the null result to prevent future lookups for this address.\n domainNameCache.set(address, null);\n return null;\n }\n\n // @ts-expect-error - domainKeys is an array of PublicKey objects.\n const domainName = await performReverseLookup(connection, domainKeys[0]);\n const fullDomain = `${domainName}.sol`;\n\n // Cache the successful result.\n domainNameCache.set(address, fullDomain);\n return fullDomain;\n } catch {\n // Cache the null result in case of an error.\n domainNameCache.set(address, null);\n return null;\n }\n};\n\n/**\n * Retrieves the avatar URL from the 'pic' record of a .sol domain name.\n * @param {string} rpcUrl - The RPC endpoint URL.\n * @param {string} name - The .sol domain name (e.g., \"bonfida.sol\").\n * @returns {Promise<string | null>} The URL of the avatar or null if not found or set.\n */\nexport const getSolanaAvatar = async (rpcUrl: string, name: string): Promise<string | null> => {\n try {\n const connection = getConnection(rpcUrl);\n const record = await getRecord(connection, name, Record.Pic);\n\n if (!record || !record.data) {\n return null;\n }\n\n return record.data.toString('utf-8');\n } catch {\n // Fails silently if the record doesn't exist.\n return null;\n }\n};\n","/**\n * @file This file contains the factory function for creating the Solana adapter for Pulsar.\n */\nimport type { Transaction, TxAdapter } from '@tuwaio/pulsar-core';\nimport { TransactionAdapter } from '@tuwaio/pulsar-core';\nimport { getExplorerLink, SolanaClusterMoniker } from 'gill';\n\nimport { SolanaChainMismatchError } from '../errors';\nimport { SolanaActionTxKey, SolanaAdapterConfig, SolanaTransactionTracker } from '../types';\nimport { checkAndInitializeTrackerInStore } from '../utils/checkAndInitializeTrackerInStore';\nimport { checkSolanaChain } from '../utils/checkSolanaChain';\nimport { createSolanaRPC } from '../utils/createSolanaRPC';\nimport { selectSolanaTxExplorerLink } from '../utils/selectSolanaTxExplorerLink';\nimport { getSolanaAvatar, getSolanaName } from '../utils/snsUtils';\n\n/**\n * Creates a Solana adapter for the Pulsar transaction tracking engine.\n * This factory function produces a wallet-library-agnostic adapter that can be\n * configured for multiple Solana clusters (e.g., mainnet-beta, devnet) and\n * can operate even without a connected wallet for read-only tasks.\n *\n * @param config The configuration object for the adapter.\n * @returns An object implementing the `TxAdapter` interface for Solana.\n */\nexport function solanaAdapter<T extends Transaction<SolanaTransactionTracker>>(\n config: SolanaAdapterConfig,\n): TxAdapter<SolanaTransactionTracker, T, SolanaActionTxKey> {\n const { wallet, rpcUrls } = config;\n\n /**\n * Safely extracts the cluster moniker from a chain identifier.\n * Handles both full chain IDs ('solana:mainnet-beta') and simple monikers ('mainnet-beta').\n * @param chain The chain identifier or moniker.\n * @returns The extracted cluster moniker.\n */\n const getCluster = (chain?: string): SolanaClusterMoniker => {\n const defaultCluster: SolanaClusterMoniker = 'mainnet';\n if (!chain) {\n return wallet?.walletActiveChain ?? defaultCluster;\n }\n return (chain.includes(':') ? chain.split(':')[1] : chain) as SolanaClusterMoniker;\n };\n\n /**\n * Retrieves the configured RPC URL for a given cluster moniker.\n * @param cluster The target cluster. Defaults to the wallet's active chain.\n * @returns The RPC URL or undefined if not found.\n */\n const getRpcUrlForCluster = (cluster?: SolanaClusterMoniker): string | undefined => {\n const targetCluster = cluster ?? wallet?.walletActiveChain;\n if (!targetCluster) return undefined;\n return rpcUrls[targetCluster];\n };\n\n return {\n key: TransactionAdapter.SOLANA,\n\n getWalletInfo: () => ({\n walletAddress: wallet?.walletAddress ?? '0x0',\n walletType: wallet?.walletType ?? 'disconnected',\n }),\n\n checkChainForTx: async (txChain) => {\n if (!wallet) {\n throw new Error('Wallet not provided. Cannot perform chain check.');\n }\n try {\n checkSolanaChain(txChain as string, wallet.walletActiveChain);\n } catch (e) {\n if (e instanceof SolanaChainMismatchError) throw e;\n throw new Error(`Chain check failed: ${e instanceof Error ? e.message : String(e)}`);\n }\n },\n\n checkTransactionsTracker: (actionTxKey) => ({\n tracker: SolanaTransactionTracker.Solana,\n txKey: actionTxKey,\n }),\n\n checkAndInitializeTrackerInStore: ({ tx, ...rest }) => {\n return checkAndInitializeTrackerInStore({\n tracker: tx.tracker,\n tx,\n ...rest,\n });\n },\n\n getExplorerUrl: () => {\n const cluster = wallet?.walletActiveChain ?? 'mainnet-beta';\n return getExplorerLink({ cluster });\n },\n\n getExplorerTxUrl: (txPool, txKey) => {\n const tx = txPool[txKey];\n const cluster = getCluster(tx?.chainId as string);\n return selectSolanaTxExplorerLink(txKey, cluster);\n },\n\n getName: async (address) => {\n const rpcUrl = getRpcUrlForCluster(wallet?.walletActiveChain);\n if (!rpcUrl) {\n console.warn('Cannot get name: RPC URL for the current chain is not configured.');\n return null;\n }\n return getSolanaName(rpcUrl, address);\n },\n\n getAvatar: async (name) => {\n const rpcUrl = getRpcUrlForCluster(wallet?.walletActiveChain);\n if (!rpcUrl) {\n console.warn('Cannot get avatar: RPC URL for the current chain is not configured.');\n return null;\n }\n return getSolanaAvatar(rpcUrl, name);\n },\n\n retryTxAction: async ({ actions, onClose, txKey, handleTransaction, tx }) => {\n onClose(txKey);\n\n if (!wallet || !wallet.walletAddress || wallet.walletAddress === '0x0') {\n throw new Error('Retry failed: A wallet must be connected.');\n }\n if (!handleTransaction) {\n throw new Error('Retry failed: handleTransaction function is not provided.');\n }\n\n const actionKey = tx.actionKey;\n if (!actionKey || !actions?.[actionKey]) {\n throw new Error(`Retry failed: No action found for actionKey \"${actionKey}\".`);\n }\n\n const clusterForRetry = getCluster(tx.desiredChainID as string);\n const rpcUrlForRetry = tx.rpcUrl ?? getRpcUrlForCluster(clusterForRetry);\n if (!rpcUrlForRetry) {\n throw new Error('Retry failed: Could not determine RPC endpoint for the transaction chain.');\n }\n\n const retryAction = actions[actionKey];\n const rpcForRetry = createSolanaRPC(rpcUrlForRetry);\n\n const actionFunction = () =>\n (retryAction as any)({\n wallet: config.wallet,\n rpc: rpcForRetry,\n ...tx.payload,\n });\n\n await handleTransaction({\n actionFunction,\n params: tx,\n defaultTracker: SolanaTransactionTracker.Solana,\n });\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@tuwaio/pulsar-solana",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "author": "Oleksandr Tkach",
6
6
  "license": "Apache-2.0",
7
- "description": "An adapter for the Pulsar Engine that adds support for tracking transactions on the Solana blockchain. Integrates with @solana/kit and @solana/wallet-adapter-react.",
7
+ "description": "An adapter for the Pulsar Engine that adds support for tracking transactions on the Solana blockchain.",
8
8
  "main": "./dist/index.js",
9
9
  "module": "./dist/index.mjs",
10
10
  "types": "./dist/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "solana",
20
20
  "transaction",
21
21
  "tracking",
22
- "@solana/kit",
22
+ "gill",
23
23
  "zustand"
24
24
  ],
25
25
  "repository": {
@@ -39,18 +39,18 @@
39
39
  ],
40
40
  "peerDependencies": {
41
41
  "@bonfida/spl-name-service": ">=3",
42
- "@solana/kit": ">=3",
43
- "@solana/wallet-adapter-react": ">=0.15",
44
42
  "@tuwaio/pulsar-core": ">=0",
43
+ "@solana/web3.js": ">=1.98.4",
44
+ "gill": ">=0.11",
45
45
  "dayjs": ">=1",
46
46
  "immer": ">=10",
47
47
  "zustand": ">=5"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@bonfida/spl-name-service": "^3.0.16",
51
- "@solana/kit": "^3.0.2",
52
- "@solana/wallet-adapter-react": "^0.15.39",
51
+ "@solana/web3.js": "^1.98.4",
53
52
  "dayjs": "^1.11.18",
53
+ "gill": "^0.11.0",
54
54
  "immer": "^10.1.3",
55
55
  "jsdom": "^26.1.0",
56
56
  "tsup": "^8.5.0",