@tuwaio/orbit-solana 0.2.13 → 0.3.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,73 +1,93 @@
1
1
  # @tuwaio/orbit-solana
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/@tuwaio/orbit-solana.svg)](https://www.npmjs.com/package/@tuwaio/orbit-solana)
4
- [![License](https://img.shields.io/npm/l/@tuwaio/orbit-solana.svg)](./LICENSE)
4
+ [![License](https://img.shields.io/npm/l/@tuwaio/orbit-solana.svg)](https://github.com/TuwaIO/orbit/blob/main/packages/orbit-solana/LICENSE)
5
5
 
6
- `@tuwaio/orbit-solana` provides concrete implementations of low-level Solana-specific communication primitives for Layer 2 (L2) of the TUWA Orbit stack. Engineered strictly on top of **`gill`** (the modern high-performance alternative to legacy Solana web3.js classes), this package integrates Wallet Standard capabilities for discovery and persistent caching of RPC connections.
6
+ `@tuwaio/orbit-solana` is the Solana Layer 2 (L2) package of **Orbit Utils**, the Stage 1 primitives layer of the TUWA ecosystem. It is built on **`@solana/kit`** and the **Wallet Standard** (`@wallet-standard/*`), and provides cached RPC clients, wallet discovery, cluster and explorer helpers, and SNS name and avatar lookups. It does not use the legacy `@solana/web3.js` or `gill`.
7
7
 
8
8
  ---
9
9
 
10
10
  ## 🏛️ Core Capabilities
11
11
 
12
- - **Gill RPC Caching:** Creates and caches high-performance Solana RPC connections (`createSolanaClientWithCache`, `createSolanaRPC`) to optimize transaction dispatching and query pipelines.
13
- - **Wallet Standard Integration:** Low-level discovery routines (`getAvailableSolanaConnectors`, `getConnectedSolanaConnector`) that strictly align with the `@wallet-standard/app` specifications.
14
- - **Profile Metadata Engine:** Resolves user-defined account descriptors (names, avatars) directly from Wallet Standard providers.
15
- - **Cluster Parameter Resolution:** Maps Solana RPC clusters (mainnet, devnet, testnet) to explorer endpoints and RPC configurations.
12
+ - **RPC clients:** `createSolanaClientWithCache` returns a cached `{ rpc, rpcSubscriptions }` pair (the WebSocket URL is derived from the HTTP URL); `createSolanaRPC` returns a cached `rpc` only. Both accept a full URL or a cluster moniker (`mainnet`, `devnet`, `testnet`, `localnet`).
13
+ - **Wallet Standard discovery:** `getAvailableSolanaConnectors` lists installed wallets that support every feature Orbit relies on (`standard:connect`, `standard:disconnect`, `standard:events`, `solana:signAndSendTransaction`, `solana:signTransaction`, `solana:signMessage`) and only Solana chains. `getConnectedSolanaConnector` finds the wallet that holds the last connected address stored by `@tuwaio/orbit-core`.
14
+ - **Clusters and explorer links:** `getCluster`, `getRpcUrlForCluster`, `getSolanaClusters`, `getAvailableSolanaClusters`, `isValidSolanaCluster`, `isSolanaChainList`, and `getSolanaExplorerLink` for [Solana Explorer](https://explorer.solana.com) URLs.
15
+ - **Names and avatars:** `getSolanaAddressName` resolves an address to its favorite SNS (`.sol`) domain; `getSolanaAddressAvatar` returns the SNS profile image or a generated identicon. Both cache results in memory (`clear…Cache`, `get…CacheSize`, `preloadSolanaAvatar`).
16
+
17
+ Transactions are built and sent with `@solana/kit` itself, so any kit-compatible instruction builder works with the `rpc` returned here, including program clients generated by [Codama](https://github.com/codama-idl/codama).
16
18
 
17
19
  ---
18
20
 
19
21
  ## 💾 Installation
20
22
 
21
23
  ```bash
22
- pnpm add @tuwaio/orbit-solana @tuwaio/orbit-core gill @wallet-standard/app @wallet-standard/ui-core @wallet-standard/ui-registry
24
+ pnpm add @tuwaio/orbit-solana @tuwaio/orbit-core @solana/kit @wallet-standard/app @wallet-standard/ui-core @wallet-standard/ui-registry
23
25
  ```
24
26
 
25
27
  > [!IMPORTANT]
26
- > `gill` and `@wallet-standard` packages are peer dependencies and must be installed alongside `@tuwaio/orbit-solana`.
28
+ > `@tuwaio/orbit-core` (>=0.2), `@solana/kit` (>=8.2) and the `@wallet-standard` packages (1.x) are peer dependencies and must be installed alongside `@tuwaio/orbit-solana`.
27
29
 
28
30
  ---
29
31
 
30
- ## 🚀 Technical Integration
31
-
32
- ### Cached RPC Connection
32
+ ## 🚀 Usage
33
33
 
34
- Establish cached RPC endpoints dynamically:
34
+ ### Cached RPC client
35
35
 
36
36
  ```typescript
37
+ import { address } from '@solana/kit';
37
38
  import { createSolanaClientWithCache } from '@tuwaio/orbit-solana';
38
39
 
39
- // Obtain cached connection primitive
40
- const client = createSolanaClientWithCache({ rpcUrlOrMoniker: 'mainnet' });
40
+ const { rpc } = createSolanaClientWithCache({
41
+ rpcUrlOrMoniker: 'devnet',
42
+ rpcUrls: { devnet: 'https://devnet.my-rpc-provider.example' }, // optional; falls back to the public endpoint
43
+ });
44
+
45
+ const { value: lamports } = await rpc.getBalance(address('So11111111111111111111111111111111111111112')).send();
41
46
  ```
42
47
 
43
- ### Wallet Standard Discovery
48
+ Clients are cached by the `rpcUrlOrMoniker` string. `localnet` has no default URL, so pass it in `rpcUrls`. The public `api.*.solana.com` endpoints are rate-limited; use your own RPC provider in production.
44
49
 
45
- Query the browser runtime for installed Wallet Standard adapters:
50
+ ### Wallet discovery
46
51
 
47
52
  ```typescript
48
53
  import { getAvailableSolanaConnectors } from '@tuwaio/orbit-solana';
49
54
 
50
- const connectors = getAvailableSolanaConnectors();
51
- console.log(
52
- 'Available Standard Adapters:',
53
- connectors.map((c) => c.name),
54
- );
55
+ const wallets = getAvailableSolanaConnectors(); // UiWallet[] from @wallet-standard/ui-registry
56
+ console.log(wallets.map((wallet) => wallet.name));
57
+ ```
58
+
59
+ ### Names, avatars and explorer links
60
+
61
+ ```typescript
62
+ import { getSolanaAddressAvatar, getSolanaAddressName, getSolanaExplorerLink } from '@tuwaio/orbit-solana';
63
+
64
+ const owner = 'So11111111111111111111111111111111111111112';
65
+
66
+ const name = await getSolanaAddressName(owner); // "name.sol", or the address itself if no SNS domain is set
67
+ const avatarUrl = await getSolanaAddressAvatar(name);
68
+
69
+ getSolanaExplorerLink(`/address/${owner}`, 'solana:devnet'); // "https://explorer.solana.com/address/…?cluster=devnet"
55
70
  ```
56
71
 
57
72
  ---
58
73
 
59
- ## 🔧 API & Module Architecture
74
+ ## 🌐 External Services
60
75
 
61
- `@tuwaio/orbit-solana` exposes the following modules:
76
+ Some helpers call public third-party HTTP services. The address or domain being looked up is sent to these hosts:
62
77
 
63
- - **RPC Factory:** `createSolanaClientWithCache`, `createSolanaRPC`.
64
- - **Connector Discovery:** `getAvailableSolanaConnectors`, `getConnectedSolanaConnector`.
65
- - **Account Resolvers:** `getSolanaAddressName`, `getSolanaAddressAvatar`.
66
- - **Network Helpers:** `getCluster`, `getRpcUrlForCluster`.
67
- - **Explorer Helpers:** `getSolanaExplorerLink`.
78
+ | Helper | Host | Purpose |
79
+ | -------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------ |
80
+ | RPC clients (default URLs) | `api.mainnet-beta.solana.com`, `api.devnet.solana.com`, `api.testnet.solana.com` | Public Solana RPC, used when no custom URL is configured |
81
+ | `getSolanaAddressName` | `sns-api.bonfida.com` | Favorite SNS domain of an address |
82
+ | `getSolanaAddressAvatar` | `image-api.bonfida.com` | SNS profile image of a `.sol` domain |
83
+ | `getSolanaAddressAvatar` | `api.dicebear.com` | Fallback identicon URL (loaded by the browser when rendered) |
68
84
 
69
85
  ---
70
86
 
87
+ ## 📚 API Reference
88
+
89
+ Every export, with signatures and types generated from the source, is documented at **[orbit.docs.tuwa.io/packages/orbit-solana](https://orbit.docs.tuwa.io/packages/orbit-solana)**.
90
+
71
91
  ## 📄 License
72
92
 
73
- Licensed under the **Apache-2.0 License**. See the [LICENSE](./LICENSE) file for details.
93
+ Licensed under the **Apache-2.0 License**. See the [LICENSE](https://github.com/TuwaIO/orbit/blob/main/packages/orbit-solana/LICENSE) file for details.
package/dist/index.d.mts CHANGED
@@ -1,34 +1,67 @@
1
- import { SolanaClusterMoniker, SolanaClient, Rpc, SolanaRpcApi } from 'gill';
1
+ import { Rpc, SolanaRpcApi, RpcSubscriptions, SolanaRpcSubscriptionsApi, MainnetUrl, DevnetUrl, TestnetUrl } from '@solana/kit';
2
2
  import { ChainIdentifierArray } from '@tuwaio/orbit-core';
3
3
  import * as _wallet_standard_ui_core from '@wallet-standard/ui-core';
4
4
 
5
+ /**
6
+ * Solana cluster moniker type inferred directly from @solana/kit branded cluster URLs plus localnet.
7
+ */
8
+ type SolanaClusterMoniker = (MainnetUrl | DevnetUrl | TestnetUrl)['~cluster'] | 'localnet';
9
+ /**
10
+ * Configuration mapping Solana clusters to their RPC URLs.
11
+ */
5
12
  type SolanaRPCUrls = {
6
13
  rpcUrls: Partial<Record<SolanaClusterMoniker, string>>;
7
14
  };
15
+ /**
16
+ * Alias for Solana RPC methods interface.
17
+ */
18
+ type SolanaRpcMethods = SolanaRpcApi;
19
+ /**
20
+ * Lightweight Solana client wrapper holding RPC and optional subscriptions clients.
21
+ */
22
+ interface SolanaClient {
23
+ rpc: Rpc<SolanaRpcApi>;
24
+ rpcSubscriptions?: RpcSubscriptions<SolanaRpcSubscriptionsApi>;
25
+ }
8
26
 
9
27
  /**
10
- * Get Solana clusters from configuration
28
+ * Resolves the Solana clusters an app can use.
29
+ *
30
+ * @param solanaRPCUrls - Optional mapping of cluster monikers to RPC URLs. When provided, only these clusters are allowed.
31
+ * @param chains - Optional chain identifiers (e.g. `'solana:devnet'`, `'solana:mainnet-beta'`); when provided, only
32
+ * clusters present in this list (and allowed by `solanaRPCUrls`) are returned.
33
+ * @returns Cluster monikers, e.g. `['mainnet', 'devnet']`. Defaults to every cluster with a default RPC URL.
11
34
  */
12
35
  declare function getSolanaClusters(solanaRPCUrls?: Partial<Record<SolanaClusterMoniker, string>>, chains?: ChainIdentifierArray): string[];
13
36
  /**
14
- * Type guard to check if a chain list contains Solana cluster names
37
+ * Type guard to check if a chain list contains Solana cluster names.
38
+ *
39
+ * @param chains - Mixed list of chain identifiers.
40
+ * @returns `true` if the list is non-empty and contains only strings.
15
41
  */
16
42
  declare function isSolanaChainList(chains: (string | number)[]): chains is string[];
17
43
  /**
18
- * Gets available Solana clusters from the default configuration
44
+ * Gets the Solana clusters that have a default public RPC URL.
45
+ *
46
+ * @returns `['mainnet', 'devnet', 'testnet']`.
19
47
  */
20
48
  declare function getAvailableSolanaClusters(): SolanaClusterMoniker[];
21
49
  /**
22
- * Validates if a string is a valid Solana cluster moniker
50
+ * Validates if a string is a Solana cluster moniker with a default RPC URL.
51
+ *
52
+ * @param cluster - Value to check.
53
+ * @returns `true` for `'mainnet'`, `'devnet'` and `'testnet'`.
23
54
  */
24
55
  declare function isValidSolanaCluster(cluster: string): boolean;
25
56
 
26
57
  /**
27
58
  * Safely extracts the cluster moniker from a chain identifier.
28
59
  * Handles both full chain IDs ('solana:mainnet-beta') and simple monikers ('mainnet-beta').
29
- * @returns The extracted cluster moniker.
30
- * @param walletCluster
31
- * @param cluster
60
+ *
61
+ * @param params - Cluster sources.
62
+ * @param params.cluster - Chain identifier or cluster moniker. Takes precedence when provided.
63
+ * @param params.walletCluster - Cluster of the connected wallet, used when `cluster` is omitted.
64
+ * @returns The extracted cluster moniker, falling back to `walletCluster` and then to `'mainnet'`.
32
65
  */
33
66
  declare const getCluster: ({ cluster, walletCluster }: {
34
67
  cluster?: string;
@@ -36,8 +69,12 @@ declare const getCluster: ({ cluster, walletCluster }: {
36
69
  }) => string;
37
70
  /**
38
71
  * Retrieves the configured RPC URL for a given cluster moniker.
39
- * @param cluster The target cluster. Defaults to the wallet's active chain.
40
- * @returns The RPC URL or undefined if not found.
72
+ *
73
+ * @param params - Cluster selection and the configured RPC URLs.
74
+ * @param params.cluster - The target cluster, used when `walletCluster` is not provided.
75
+ * @param params.walletCluster - The cluster of the connected wallet. Takes precedence over `cluster`.
76
+ * @param params.rpcUrls - Mapping of cluster monikers to RPC URLs.
77
+ * @returns The configured RPC URL, or the public mainnet-beta endpoint if the cluster has no URL configured.
41
78
  */
42
79
  declare const getRpcUrlForCluster: ({ cluster, walletCluster, rpcUrls, }: {
43
80
  cluster: SolanaClusterMoniker;
@@ -61,36 +98,40 @@ declare const getRpcUrlForCluster: ({ cluster, walletCluster, rpcUrls, }: {
61
98
  * - Optimize memory usage
62
99
  * - Maintain consistent client instances throughout the application
63
100
  *
64
- * @param rpcUrlOrMoniker - RPC endpoint URL or cluster moniker (e.g., 'mainnet', 'devnet')
101
+ * @param params - Object containing rpcUrlOrMoniker and optional rpcUrls
102
+ * @param params.rpcUrlOrMoniker - RPC endpoint URL or cluster moniker (e.g., 'mainnet', 'devnet')
103
+ * @param params.rpcUrls - Optional custom mapping of cluster monikers to RPC endpoints
65
104
  * @returns Cached or newly created Solana RPC client instance
66
105
  * @throws Error if unable to resolve a valid RPC URL
67
- *
68
- * @example
69
- * ```typescript
70
- * // Using cluster moniker
71
- * const mainnetClient = createSolanaClientWithCache('mainnet');
72
- *
73
- * // Using custom RPC URL
74
- * const customClient = createSolanaClientWithCache('https://my-rpc.example.com');
75
- * ```
76
106
  */
77
107
  declare const createSolanaClientWithCache: ({ rpcUrlOrMoniker, rpcUrls, }: {
78
108
  rpcUrlOrMoniker: string;
79
109
  rpcUrls?: Partial<Record<SolanaClusterMoniker, string>>;
80
110
  }) => SolanaClient;
111
+ /**
112
+ * Clears the Solana client cache.
113
+ * @internal
114
+ */
115
+ declare const clearSolanaClientCache: () => void;
81
116
 
82
117
  /**
83
118
  * Retrieves a cached RPC client for a given URL or cluster moniker.
84
119
  * If no cached client exists, it creates a new instance.
85
120
  *
86
- * @param rpcUrlOrMoniker - Either a full RPC URL or a cluster moniker like 'mainnet'.
121
+ * @param params - Object containing rpcUrlOrMoniker and optional rpcUrls map.
122
+ * @param params.rpcUrlOrMoniker - Either a full RPC URL or a cluster moniker like 'mainnet'.
123
+ * @param params.rpcUrls - Optional custom mapping of cluster monikers to RPC endpoints.
87
124
  * @returns The RPC client instance.
88
- * @internal
89
125
  */
90
126
  declare const createSolanaRPC: ({ rpcUrlOrMoniker, rpcUrls, }: {
91
127
  rpcUrlOrMoniker: string;
92
128
  rpcUrls?: Partial<Record<SolanaClusterMoniker, string>>;
93
129
  }) => Rpc<SolanaRpcApi>;
130
+ /**
131
+ * Clears the Solana RPC client cache.
132
+ * @internal
133
+ */
134
+ declare const clearSolanaRpcCache: () => void;
94
135
 
95
136
  /**
96
137
  * The default RPC URLs for each Solana cluster.
@@ -99,8 +140,26 @@ declare const createSolanaRPC: ({ rpcUrlOrMoniker, rpcUrls, }: {
99
140
  */
100
141
  declare const defaultRpcUrlsByMoniker: Partial<Record<SolanaClusterMoniker, string>>;
101
142
 
143
+ /**
144
+ * Lists the Solana wallets registered through the Wallet Standard that Orbit can work with.
145
+ *
146
+ * A wallet is included only if it supports `standard:connect`, `standard:disconnect`, `standard:events`,
147
+ * `solana:signAndSendTransaction`, `solana:signTransaction` and `solana:signMessage`, and every chain it
148
+ * declares is a `solana:*` chain. Wallets registered more than once are deduplicated by name.
149
+ *
150
+ * @returns The matching wallets as `UiWallet` objects from `@wallet-standard/ui-registry`
151
+ * (empty on the server, where no wallets are registered).
152
+ */
102
153
  declare function getAvailableSolanaConnectors(): _wallet_standard_ui_core.UiWallet[];
103
154
 
155
+ /**
156
+ * Finds the Wallet Standard wallet that holds the last connected address.
157
+ * The address is read from `lastConnectedConnectorHelpers` in `@tuwaio/orbit-core` (localStorage) and matched
158
+ * case-insensitively against the accounts of {@link getAvailableSolanaConnectors}.
159
+ *
160
+ * @returns The connected wallet as a `UiWallet`.
161
+ * @throws {Error} "Connector not provided. Cannot perform chain check." if no available wallet holds that address.
162
+ */
104
163
  declare function getConnectedSolanaConnector(): _wallet_standard_ui_core.UiWallet;
105
164
 
106
165
  /**
@@ -153,12 +212,13 @@ declare const getSolanaNameCacheSize: () => number;
153
212
  * @file This file contains a utility function for generating Solana transaction explorer links.
154
213
  */
155
214
  /**
156
- * Generates a full URL to a transaction on a Solana explorer like Solscan.
215
+ * Generates a full URL to an account, transaction, or block on the Solana explorer.
157
216
  *
158
- * @param {string} url - The url after baseUrl.
159
- * @param chainId
160
- * @returns {string} The full URL to the transaction on the explorer.
217
+ * @param url - The path after baseUrl (e.g. '/tx/...' or '/address/...').
218
+ * @param chainId - Chain ID or cluster name (e.g. 'devnet' or 'solana:devnet').
219
+ * Mainnet ('mainnet', 'mainnet-beta') or an omitted value produces a link without a `cluster` query parameter.
220
+ * @returns The full URL on the Solana explorer.
161
221
  */
162
222
  declare const getSolanaExplorerLink: (url?: string, chainId?: string | number | undefined) => string;
163
223
 
164
- export { type SolanaRPCUrls, clearSolanaAvatarCache, clearSolanaNameCache, createSolanaClientWithCache, createSolanaRPC, defaultRpcUrlsByMoniker, getAvailableSolanaClusters, getAvailableSolanaConnectors, getCluster, getConnectedSolanaConnector, getRpcUrlForCluster, getSolanaAddressAvatar, getSolanaAddressName, getSolanaAvatarCacheSize, getSolanaClusters, getSolanaExplorerLink, getSolanaNameCacheSize, isSolanaChainList, isValidSolanaCluster, preloadSolanaAvatar };
224
+ export { type SolanaClient, type SolanaClusterMoniker, type SolanaRPCUrls, type SolanaRpcMethods, clearSolanaAvatarCache, clearSolanaClientCache, clearSolanaNameCache, clearSolanaRpcCache, createSolanaClientWithCache, createSolanaRPC, defaultRpcUrlsByMoniker, getAvailableSolanaClusters, getAvailableSolanaConnectors, getCluster, getConnectedSolanaConnector, getRpcUrlForCluster, getSolanaAddressAvatar, getSolanaAddressName, getSolanaAvatarCacheSize, getSolanaClusters, getSolanaExplorerLink, getSolanaNameCacheSize, isSolanaChainList, isValidSolanaCluster, preloadSolanaAvatar };
package/dist/index.d.ts CHANGED
@@ -1,34 +1,67 @@
1
- import { SolanaClusterMoniker, SolanaClient, Rpc, SolanaRpcApi } from 'gill';
1
+ import { Rpc, SolanaRpcApi, RpcSubscriptions, SolanaRpcSubscriptionsApi, MainnetUrl, DevnetUrl, TestnetUrl } from '@solana/kit';
2
2
  import { ChainIdentifierArray } from '@tuwaio/orbit-core';
3
3
  import * as _wallet_standard_ui_core from '@wallet-standard/ui-core';
4
4
 
5
+ /**
6
+ * Solana cluster moniker type inferred directly from @solana/kit branded cluster URLs plus localnet.
7
+ */
8
+ type SolanaClusterMoniker = (MainnetUrl | DevnetUrl | TestnetUrl)['~cluster'] | 'localnet';
9
+ /**
10
+ * Configuration mapping Solana clusters to their RPC URLs.
11
+ */
5
12
  type SolanaRPCUrls = {
6
13
  rpcUrls: Partial<Record<SolanaClusterMoniker, string>>;
7
14
  };
15
+ /**
16
+ * Alias for Solana RPC methods interface.
17
+ */
18
+ type SolanaRpcMethods = SolanaRpcApi;
19
+ /**
20
+ * Lightweight Solana client wrapper holding RPC and optional subscriptions clients.
21
+ */
22
+ interface SolanaClient {
23
+ rpc: Rpc<SolanaRpcApi>;
24
+ rpcSubscriptions?: RpcSubscriptions<SolanaRpcSubscriptionsApi>;
25
+ }
8
26
 
9
27
  /**
10
- * Get Solana clusters from configuration
28
+ * Resolves the Solana clusters an app can use.
29
+ *
30
+ * @param solanaRPCUrls - Optional mapping of cluster monikers to RPC URLs. When provided, only these clusters are allowed.
31
+ * @param chains - Optional chain identifiers (e.g. `'solana:devnet'`, `'solana:mainnet-beta'`); when provided, only
32
+ * clusters present in this list (and allowed by `solanaRPCUrls`) are returned.
33
+ * @returns Cluster monikers, e.g. `['mainnet', 'devnet']`. Defaults to every cluster with a default RPC URL.
11
34
  */
12
35
  declare function getSolanaClusters(solanaRPCUrls?: Partial<Record<SolanaClusterMoniker, string>>, chains?: ChainIdentifierArray): string[];
13
36
  /**
14
- * Type guard to check if a chain list contains Solana cluster names
37
+ * Type guard to check if a chain list contains Solana cluster names.
38
+ *
39
+ * @param chains - Mixed list of chain identifiers.
40
+ * @returns `true` if the list is non-empty and contains only strings.
15
41
  */
16
42
  declare function isSolanaChainList(chains: (string | number)[]): chains is string[];
17
43
  /**
18
- * Gets available Solana clusters from the default configuration
44
+ * Gets the Solana clusters that have a default public RPC URL.
45
+ *
46
+ * @returns `['mainnet', 'devnet', 'testnet']`.
19
47
  */
20
48
  declare function getAvailableSolanaClusters(): SolanaClusterMoniker[];
21
49
  /**
22
- * Validates if a string is a valid Solana cluster moniker
50
+ * Validates if a string is a Solana cluster moniker with a default RPC URL.
51
+ *
52
+ * @param cluster - Value to check.
53
+ * @returns `true` for `'mainnet'`, `'devnet'` and `'testnet'`.
23
54
  */
24
55
  declare function isValidSolanaCluster(cluster: string): boolean;
25
56
 
26
57
  /**
27
58
  * Safely extracts the cluster moniker from a chain identifier.
28
59
  * Handles both full chain IDs ('solana:mainnet-beta') and simple monikers ('mainnet-beta').
29
- * @returns The extracted cluster moniker.
30
- * @param walletCluster
31
- * @param cluster
60
+ *
61
+ * @param params - Cluster sources.
62
+ * @param params.cluster - Chain identifier or cluster moniker. Takes precedence when provided.
63
+ * @param params.walletCluster - Cluster of the connected wallet, used when `cluster` is omitted.
64
+ * @returns The extracted cluster moniker, falling back to `walletCluster` and then to `'mainnet'`.
32
65
  */
33
66
  declare const getCluster: ({ cluster, walletCluster }: {
34
67
  cluster?: string;
@@ -36,8 +69,12 @@ declare const getCluster: ({ cluster, walletCluster }: {
36
69
  }) => string;
37
70
  /**
38
71
  * Retrieves the configured RPC URL for a given cluster moniker.
39
- * @param cluster The target cluster. Defaults to the wallet's active chain.
40
- * @returns The RPC URL or undefined if not found.
72
+ *
73
+ * @param params - Cluster selection and the configured RPC URLs.
74
+ * @param params.cluster - The target cluster, used when `walletCluster` is not provided.
75
+ * @param params.walletCluster - The cluster of the connected wallet. Takes precedence over `cluster`.
76
+ * @param params.rpcUrls - Mapping of cluster monikers to RPC URLs.
77
+ * @returns The configured RPC URL, or the public mainnet-beta endpoint if the cluster has no URL configured.
41
78
  */
42
79
  declare const getRpcUrlForCluster: ({ cluster, walletCluster, rpcUrls, }: {
43
80
  cluster: SolanaClusterMoniker;
@@ -61,36 +98,40 @@ declare const getRpcUrlForCluster: ({ cluster, walletCluster, rpcUrls, }: {
61
98
  * - Optimize memory usage
62
99
  * - Maintain consistent client instances throughout the application
63
100
  *
64
- * @param rpcUrlOrMoniker - RPC endpoint URL or cluster moniker (e.g., 'mainnet', 'devnet')
101
+ * @param params - Object containing rpcUrlOrMoniker and optional rpcUrls
102
+ * @param params.rpcUrlOrMoniker - RPC endpoint URL or cluster moniker (e.g., 'mainnet', 'devnet')
103
+ * @param params.rpcUrls - Optional custom mapping of cluster monikers to RPC endpoints
65
104
  * @returns Cached or newly created Solana RPC client instance
66
105
  * @throws Error if unable to resolve a valid RPC URL
67
- *
68
- * @example
69
- * ```typescript
70
- * // Using cluster moniker
71
- * const mainnetClient = createSolanaClientWithCache('mainnet');
72
- *
73
- * // Using custom RPC URL
74
- * const customClient = createSolanaClientWithCache('https://my-rpc.example.com');
75
- * ```
76
106
  */
77
107
  declare const createSolanaClientWithCache: ({ rpcUrlOrMoniker, rpcUrls, }: {
78
108
  rpcUrlOrMoniker: string;
79
109
  rpcUrls?: Partial<Record<SolanaClusterMoniker, string>>;
80
110
  }) => SolanaClient;
111
+ /**
112
+ * Clears the Solana client cache.
113
+ * @internal
114
+ */
115
+ declare const clearSolanaClientCache: () => void;
81
116
 
82
117
  /**
83
118
  * Retrieves a cached RPC client for a given URL or cluster moniker.
84
119
  * If no cached client exists, it creates a new instance.
85
120
  *
86
- * @param rpcUrlOrMoniker - Either a full RPC URL or a cluster moniker like 'mainnet'.
121
+ * @param params - Object containing rpcUrlOrMoniker and optional rpcUrls map.
122
+ * @param params.rpcUrlOrMoniker - Either a full RPC URL or a cluster moniker like 'mainnet'.
123
+ * @param params.rpcUrls - Optional custom mapping of cluster monikers to RPC endpoints.
87
124
  * @returns The RPC client instance.
88
- * @internal
89
125
  */
90
126
  declare const createSolanaRPC: ({ rpcUrlOrMoniker, rpcUrls, }: {
91
127
  rpcUrlOrMoniker: string;
92
128
  rpcUrls?: Partial<Record<SolanaClusterMoniker, string>>;
93
129
  }) => Rpc<SolanaRpcApi>;
130
+ /**
131
+ * Clears the Solana RPC client cache.
132
+ * @internal
133
+ */
134
+ declare const clearSolanaRpcCache: () => void;
94
135
 
95
136
  /**
96
137
  * The default RPC URLs for each Solana cluster.
@@ -99,8 +140,26 @@ declare const createSolanaRPC: ({ rpcUrlOrMoniker, rpcUrls, }: {
99
140
  */
100
141
  declare const defaultRpcUrlsByMoniker: Partial<Record<SolanaClusterMoniker, string>>;
101
142
 
143
+ /**
144
+ * Lists the Solana wallets registered through the Wallet Standard that Orbit can work with.
145
+ *
146
+ * A wallet is included only if it supports `standard:connect`, `standard:disconnect`, `standard:events`,
147
+ * `solana:signAndSendTransaction`, `solana:signTransaction` and `solana:signMessage`, and every chain it
148
+ * declares is a `solana:*` chain. Wallets registered more than once are deduplicated by name.
149
+ *
150
+ * @returns The matching wallets as `UiWallet` objects from `@wallet-standard/ui-registry`
151
+ * (empty on the server, where no wallets are registered).
152
+ */
102
153
  declare function getAvailableSolanaConnectors(): _wallet_standard_ui_core.UiWallet[];
103
154
 
155
+ /**
156
+ * Finds the Wallet Standard wallet that holds the last connected address.
157
+ * The address is read from `lastConnectedConnectorHelpers` in `@tuwaio/orbit-core` (localStorage) and matched
158
+ * case-insensitively against the accounts of {@link getAvailableSolanaConnectors}.
159
+ *
160
+ * @returns The connected wallet as a `UiWallet`.
161
+ * @throws {Error} "Connector not provided. Cannot perform chain check." if no available wallet holds that address.
162
+ */
104
163
  declare function getConnectedSolanaConnector(): _wallet_standard_ui_core.UiWallet;
105
164
 
106
165
  /**
@@ -153,12 +212,13 @@ declare const getSolanaNameCacheSize: () => number;
153
212
  * @file This file contains a utility function for generating Solana transaction explorer links.
154
213
  */
155
214
  /**
156
- * Generates a full URL to a transaction on a Solana explorer like Solscan.
215
+ * Generates a full URL to an account, transaction, or block on the Solana explorer.
157
216
  *
158
- * @param {string} url - The url after baseUrl.
159
- * @param chainId
160
- * @returns {string} The full URL to the transaction on the explorer.
217
+ * @param url - The path after baseUrl (e.g. '/tx/...' or '/address/...').
218
+ * @param chainId - Chain ID or cluster name (e.g. 'devnet' or 'solana:devnet').
219
+ * Mainnet ('mainnet', 'mainnet-beta') or an omitted value produces a link without a `cluster` query parameter.
220
+ * @returns The full URL on the Solana explorer.
161
221
  */
162
222
  declare const getSolanaExplorerLink: (url?: string, chainId?: string | number | undefined) => string;
163
223
 
164
- export { type SolanaRPCUrls, clearSolanaAvatarCache, clearSolanaNameCache, createSolanaClientWithCache, createSolanaRPC, defaultRpcUrlsByMoniker, getAvailableSolanaClusters, getAvailableSolanaConnectors, getCluster, getConnectedSolanaConnector, getRpcUrlForCluster, getSolanaAddressAvatar, getSolanaAddressName, getSolanaAvatarCacheSize, getSolanaClusters, getSolanaExplorerLink, getSolanaNameCacheSize, isSolanaChainList, isValidSolanaCluster, preloadSolanaAvatar };
224
+ export { type SolanaClient, type SolanaClusterMoniker, type SolanaRPCUrls, type SolanaRpcMethods, clearSolanaAvatarCache, clearSolanaClientCache, clearSolanaNameCache, clearSolanaRpcCache, createSolanaClientWithCache, createSolanaRPC, defaultRpcUrlsByMoniker, getAvailableSolanaClusters, getAvailableSolanaConnectors, getCluster, getConnectedSolanaConnector, getRpcUrlForCluster, getSolanaAddressAvatar, getSolanaAddressName, getSolanaAvatarCacheSize, getSolanaClusters, getSolanaExplorerLink, getSolanaNameCacheSize, isSolanaChainList, isValidSolanaCluster, preloadSolanaAvatar };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var gill=require('gill'),orbitCore=require('@tuwaio/orbit-core'),app=require('@wallet-standard/app'),uiRegistry=require('@wallet-standard/ui-registry');var o={mainnet:"https://api.mainnet-beta.solana.com",devnet:"https://api.devnet.solana.com",testnet:"https://api.testnet.solana.com"};function d(t){let e=t.split(":");if(e.length<2)return null;let r=e[1];r==="mainnet-beta"&&(r="mainnet");let n=r;return n in o?n:null}function S(t,e){let r={},n=Object.keys(e||o);for(let a of t){if(typeof a!="string")continue;let s=d(a);if(!s||!n.includes(s))continue;let f=e?.[s]??o[s];f&&(r[s]=f);}return r}function F(t,e){if(e&&e.length>0){let r=S(e,t);return Object.keys(r)}return Object.keys(t||o)}function j(t){return t.length>0&&t.every(e=>typeof e=="string")}function B(){return Object.keys(o)}function T(t){return t in o}var m=({cluster:t,walletCluster:e})=>{let r="mainnet";return t?t.includes(":")?t.split(":")[1]:t:e??r},_=({cluster:t,walletCluster:e,rpcUrls:r})=>r[e??t]??"https://api.mainnet-beta.solana.com/";function h(t){try{return new URL(t),!0}catch{return false}}var u=new Map,Z=({rpcUrlOrMoniker:t,rpcUrls:e})=>{if(u.has(t))return u.get(t);let r=h(t)?t:e?e[t]??o[t]:o[t];if(!r)throw new Error(`Unable to resolve RPC URL for input: "${t}". Ensure it's a valid URL or known moniker.`);let n=gill.createSolanaClient({urlOrMoniker:r});return u.set(t,n),n};function A(t){try{return new URL(t),!0}catch{return false}}var p=new Map,tt=({rpcUrlOrMoniker:t,rpcUrls:e})=>{if(p.has(t))return p.get(t);let r=A(t)?t:e?e[t]??o[t]:o[t];if(!r)throw new Error(`Unable to resolve RPC URL for input: "${t}". Ensure it's a valid URL or known moniker.`);let n=gill.createSolanaRpc(r);return p.set(t,n),n};function g(){return orbitCore.filterUniqueByKey(app.getWallets().get().map(uiRegistry.getOrCreateUiWalletForStandardWallet),"name").filter(t=>{try{return !t||!Array.isArray(t.chains)||!t.features||!Array.isArray(t.features)||!["standard:connect","standard:disconnect","standard:events","solana:signAndSendTransaction","solana:signTransaction","solana:signMessage"].every(n=>t.features.includes(n))?!1:t.chains.every(n=>{try{if(typeof n!="string")return !1;let a=n.split(":");return a.length>=1&&a[0]==="solana"}catch(a){return console.warn("Error parsing chain:",n,a),!1}})}catch(e){return console.warn("Error filtering wallet:",e),false}})}function lt(){let t=orbitCore.lastConnectedConnectorHelpers.getLastConnectedConnector(),r=g().find(n=>n.accounts.find(a=>a.address.toLowerCase()===t?.address?.toLowerCase()));if(!r)throw new Error("Connector not provided. Cannot perform chain check.");return r}var l=new Map,b="https://image-api.bonfida.com",k=t=>/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(t),P=t=>/^[a-z0-9-]+\.sol$/.test(t.toLowerCase()),U=async t=>{try{let e=await fetch(`${b}/image?domain=${encodeURIComponent(t)}`);if(!e.ok)return null;let r=await e.json();return r.result&&Array.isArray(r.result)&&r.result.length>0?r.result.find(a=>a.domain===t)?.image??r.result[0]?.image??null:null}catch(e){return console.warn("Failed to fetch SNS avatar:",e),null}},i=t=>`https://api.dicebear.com/7.x/identicon/svg?seed=${encodeURIComponent(t)}`,N=async t=>{if(!t)return i("default");let e=t.trim();if(!e)return i("default");let r=e.toLowerCase(),n=l.get(r);if(n!==void 0)return n;let a;try{if(P(e)){let s=await U(e);s?a=s:a=i(e);}else k(e),a=i(e);}catch(s){console.warn("Error resolving avatar:",s),a=i(e);}return l.set(r,a),a},ut=()=>{l.clear();},pt=()=>l.size,ft=async t=>{try{await N(t);}catch(e){console.warn("Failed to preload avatar:",e);}};var c=new Map,M="https://sns-api.bonfida.com",L=async t=>{try{let e=await fetch(`${M}/v2/user/fav-domains/${t}`);if(!e.ok)return null;let r=await e.json();return r.result&&typeof r.result=="object"&&t in r.result?r.result[t]:null}catch(e){return console.warn("Failed to fetch SNS name:",e),null}},$=t=>/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(t),gt=async t=>{if(!t)return t||"";if(!$(t.trim()))return t;let e=t.toLowerCase().trim(),r=c.get(e);if(r!==void 0)return r;let n=t;try{let a=await L(t);a&&(n=a);}catch(a){console.warn("Error resolving address name:",a);}return c.set(e,n),n},dt=()=>{c.clear();},St=()=>c.size;var At=(t,e)=>{let r=m({cluster:String(e)})??"mainnet",n=gill.getExplorerLink(),a=n.endsWith("/")?n.slice(0,-1):n,s=r?`?cluster=${r}`:"";return `${a}${t||"/"}${s}`};
2
- exports.clearSolanaAvatarCache=ut;exports.clearSolanaNameCache=dt;exports.createSolanaClientWithCache=Z;exports.createSolanaRPC=tt;exports.defaultRpcUrlsByMoniker=o;exports.getAvailableSolanaClusters=B;exports.getAvailableSolanaConnectors=g;exports.getCluster=m;exports.getConnectedSolanaConnector=lt;exports.getRpcUrlForCluster=_;exports.getSolanaAddressAvatar=N;exports.getSolanaAddressName=gt;exports.getSolanaAvatarCacheSize=pt;exports.getSolanaClusters=F;exports.getSolanaExplorerLink=At;exports.getSolanaNameCacheSize=St;exports.isSolanaChainList=j;exports.isValidSolanaCluster=T;exports.preloadSolanaAvatar=ft;
1
+ 'use strict';var kit=require('@solana/kit'),orbitCore=require('@tuwaio/orbit-core'),app=require('@wallet-standard/app'),uiRegistry=require('@wallet-standard/ui-registry');var s={mainnet:"https://api.mainnet-beta.solana.com",devnet:"https://api.devnet.solana.com",testnet:"https://api.testnet.solana.com"};function d(t){let e=t.split(":");if(e.length<2)return null;let r=e[1];r==="mainnet-beta"&&(r="mainnet");let a=r;return a in s?a:null}function S(t,e){let r={},a=Object.keys(e||s);for(let n of t){if(typeof n!="string")continue;let o=d(n);if(!o||!a.includes(o))continue;let f=e?.[o]??s[o];f&&(r[o]=f);}return r}function z(t,e){if(e&&e.length>0){let r=S(e,t);return Object.keys(r)}return Object.keys(t||s)}function T(t){return t.length>0&&t.every(e=>typeof e=="string")}function W(){return Object.keys(s)}function D(t){return t in s}var m=({cluster:t,walletCluster:e})=>{let r="mainnet";return t?t.includes(":")?t.split(":")[1]:t:e??r},q=({cluster:t,walletCluster:e,rpcUrls:r})=>r[e??t]??"https://api.mainnet-beta.solana.com/";function h(t){try{return new URL(t),!0}catch{return false}}function R(t){return t.replace(/^http/,"ws")}var l=new Map,G=({rpcUrlOrMoniker:t,rpcUrls:e})=>{if(l.has(t))return l.get(t);let r=h(t)?t:e?e[t]??s[t]:s[t];if(!r)throw new Error(`Unable to resolve RPC URL for input: "${t}". Ensure it's a valid URL or known moniker.`);let a=kit.createSolanaRpc(r),n;try{n=kit.createSolanaRpcSubscriptions(R(r));}catch{}let o={rpc:a,...n?{rpcSubscriptions:n}:{}};return l.set(t,o),o},J=()=>{l.clear();};function v(t){try{return new URL(t),!0}catch{return false}}var c=new Map,O=({rpcUrlOrMoniker:t,rpcUrls:e})=>{if(c.has(t))return c.get(t);let r=v(t)?t:e?e[t]??s[t]:s[t];if(!r)throw new Error(`Unable to resolve RPC URL for input: "${t}". Ensure it's a valid URL or known moniker.`);let a=kit.createSolanaRpc(r);return c.set(t,a),a},tt=()=>{c.clear();};function g(){return orbitCore.filterUniqueByKey(app.getWallets().get().map(uiRegistry.getOrCreateUiWalletForStandardWallet),"name").filter(t=>{try{return !t||!Array.isArray(t.chains)||!t.features||!Array.isArray(t.features)||!["standard:connect","standard:disconnect","standard:events","solana:signAndSendTransaction","solana:signTransaction","solana:signMessage"].every(a=>t.features.includes(a))?!1:t.chains.every(a=>{try{if(typeof a!="string")return !1;let n=a.split(":");return n.length>=1&&n[0]==="solana"}catch(n){return console.warn("Error parsing chain:",a,n),!1}})}catch(e){return console.warn("Error filtering wallet:",e),false}})}function lt(){let t=orbitCore.lastConnectedConnectorHelpers.getLastConnectedConnector(),r=g().find(a=>a.accounts.find(n=>n.address.toLowerCase()===t?.address?.toLowerCase()));if(!r)throw new Error("Connector not provided. Cannot perform chain check.");return r}var u=new Map,L="https://image-api.bonfida.com",P=t=>/^[a-z0-9-]+\.sol$/.test(t.toLowerCase()),N=async t=>{try{let e=await fetch(`${L}/image?domain=${encodeURIComponent(t)}`);if(!e.ok)return null;let r=await e.json();return r.result&&Array.isArray(r.result)&&r.result.length>0?r.result.find(n=>n.domain===t)?.image??r.result[0]?.image??null:null}catch(e){return console.warn("Failed to fetch SNS avatar:",e),null}},i=t=>`https://api.dicebear.com/7.x/identicon/svg?seed=${encodeURIComponent(t)}`,E=async t=>{if(!t)return i("default");let e=t.trim();if(!e)return i("default");let r=e.toLowerCase(),a=u.get(r);if(a!==void 0)return a;let n;try{if(P(e)){let o=await N(e);o?n=o:n=i(e);}else kit.isAddress(e),n=i(e);}catch(o){console.warn("Error resolving avatar:",o),n=i(e);}return u.set(r,n),n},pt=()=>{u.clear();},ft=()=>u.size,mt=async t=>{try{await E(t);}catch(e){console.warn("Failed to preload avatar:",e);}};var p=new Map,$="https://sns-api.bonfida.com",I=async t=>{try{let e=await fetch(`${$}/v2/user/fav-domains/${t}`);if(!e.ok)return null;let r=await e.json();return r.result&&typeof r.result=="object"&&t in r.result?r.result[t]:null}catch(e){return console.warn("Failed to fetch SNS name:",e),null}},St=async t=>{if(!t)return t||"";let e=t.trim();if(!kit.isAddress(e))return t;let r=e.toLowerCase(),a=p.get(r);if(a!==void 0)return a;let n=t;try{let o=await I(e);o&&(n=o);}catch(o){console.warn("Error resolving address name:",o);}return p.set(r,n),n},Ct=()=>{p.clear();},yt=()=>p.size;var _="https://explorer.solana.com",F=new Set(["mainnet","mainnet-beta"]),At=(t,e)=>{let r=m({cluster:e===void 0?void 0:String(e)}),a=_,n=F.has(r)?"":`?cluster=${r}`,o=t?t.startsWith("/")?t:`/${t}`:"/";return `${a}${o}${n}`};
2
+ exports.clearSolanaAvatarCache=pt;exports.clearSolanaClientCache=J;exports.clearSolanaNameCache=Ct;exports.clearSolanaRpcCache=tt;exports.createSolanaClientWithCache=G;exports.createSolanaRPC=O;exports.defaultRpcUrlsByMoniker=s;exports.getAvailableSolanaClusters=W;exports.getAvailableSolanaConnectors=g;exports.getCluster=m;exports.getConnectedSolanaConnector=lt;exports.getRpcUrlForCluster=q;exports.getSolanaAddressAvatar=E;exports.getSolanaAddressName=St;exports.getSolanaAvatarCacheSize=ft;exports.getSolanaClusters=z;exports.getSolanaExplorerLink=At;exports.getSolanaNameCacheSize=yt;exports.isSolanaChainList=T;exports.isValidSolanaCluster=D;exports.preloadSolanaAvatar=mt;
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import {createSolanaClient,createSolanaRpc,getExplorerLink}from'gill';import {filterUniqueByKey,lastConnectedConnectorHelpers}from'@tuwaio/orbit-core';import {getWallets}from'@wallet-standard/app';import {getOrCreateUiWalletForStandardWallet}from'@wallet-standard/ui-registry';var o={mainnet:"https://api.mainnet-beta.solana.com",devnet:"https://api.devnet.solana.com",testnet:"https://api.testnet.solana.com"};function d(t){let e=t.split(":");if(e.length<2)return null;let r=e[1];r==="mainnet-beta"&&(r="mainnet");let n=r;return n in o?n:null}function S(t,e){let r={},n=Object.keys(e||o);for(let a of t){if(typeof a!="string")continue;let s=d(a);if(!s||!n.includes(s))continue;let f=e?.[s]??o[s];f&&(r[s]=f);}return r}function F(t,e){if(e&&e.length>0){let r=S(e,t);return Object.keys(r)}return Object.keys(t||o)}function j(t){return t.length>0&&t.every(e=>typeof e=="string")}function B(){return Object.keys(o)}function T(t){return t in o}var m=({cluster:t,walletCluster:e})=>{let r="mainnet";return t?t.includes(":")?t.split(":")[1]:t:e??r},_=({cluster:t,walletCluster:e,rpcUrls:r})=>r[e??t]??"https://api.mainnet-beta.solana.com/";function h(t){try{return new URL(t),!0}catch{return false}}var u=new Map,Z=({rpcUrlOrMoniker:t,rpcUrls:e})=>{if(u.has(t))return u.get(t);let r=h(t)?t:e?e[t]??o[t]:o[t];if(!r)throw new Error(`Unable to resolve RPC URL for input: "${t}". Ensure it's a valid URL or known moniker.`);let n=createSolanaClient({urlOrMoniker:r});return u.set(t,n),n};function A(t){try{return new URL(t),!0}catch{return false}}var p=new Map,tt=({rpcUrlOrMoniker:t,rpcUrls:e})=>{if(p.has(t))return p.get(t);let r=A(t)?t:e?e[t]??o[t]:o[t];if(!r)throw new Error(`Unable to resolve RPC URL for input: "${t}". Ensure it's a valid URL or known moniker.`);let n=createSolanaRpc(r);return p.set(t,n),n};function g(){return filterUniqueByKey(getWallets().get().map(getOrCreateUiWalletForStandardWallet),"name").filter(t=>{try{return !t||!Array.isArray(t.chains)||!t.features||!Array.isArray(t.features)||!["standard:connect","standard:disconnect","standard:events","solana:signAndSendTransaction","solana:signTransaction","solana:signMessage"].every(n=>t.features.includes(n))?!1:t.chains.every(n=>{try{if(typeof n!="string")return !1;let a=n.split(":");return a.length>=1&&a[0]==="solana"}catch(a){return console.warn("Error parsing chain:",n,a),!1}})}catch(e){return console.warn("Error filtering wallet:",e),false}})}function lt(){let t=lastConnectedConnectorHelpers.getLastConnectedConnector(),r=g().find(n=>n.accounts.find(a=>a.address.toLowerCase()===t?.address?.toLowerCase()));if(!r)throw new Error("Connector not provided. Cannot perform chain check.");return r}var l=new Map,b="https://image-api.bonfida.com",k=t=>/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(t),P=t=>/^[a-z0-9-]+\.sol$/.test(t.toLowerCase()),U=async t=>{try{let e=await fetch(`${b}/image?domain=${encodeURIComponent(t)}`);if(!e.ok)return null;let r=await e.json();return r.result&&Array.isArray(r.result)&&r.result.length>0?r.result.find(a=>a.domain===t)?.image??r.result[0]?.image??null:null}catch(e){return console.warn("Failed to fetch SNS avatar:",e),null}},i=t=>`https://api.dicebear.com/7.x/identicon/svg?seed=${encodeURIComponent(t)}`,N=async t=>{if(!t)return i("default");let e=t.trim();if(!e)return i("default");let r=e.toLowerCase(),n=l.get(r);if(n!==void 0)return n;let a;try{if(P(e)){let s=await U(e);s?a=s:a=i(e);}else k(e),a=i(e);}catch(s){console.warn("Error resolving avatar:",s),a=i(e);}return l.set(r,a),a},ut=()=>{l.clear();},pt=()=>l.size,ft=async t=>{try{await N(t);}catch(e){console.warn("Failed to preload avatar:",e);}};var c=new Map,M="https://sns-api.bonfida.com",L=async t=>{try{let e=await fetch(`${M}/v2/user/fav-domains/${t}`);if(!e.ok)return null;let r=await e.json();return r.result&&typeof r.result=="object"&&t in r.result?r.result[t]:null}catch(e){return console.warn("Failed to fetch SNS name:",e),null}},$=t=>/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(t),gt=async t=>{if(!t)return t||"";if(!$(t.trim()))return t;let e=t.toLowerCase().trim(),r=c.get(e);if(r!==void 0)return r;let n=t;try{let a=await L(t);a&&(n=a);}catch(a){console.warn("Error resolving address name:",a);}return c.set(e,n),n},dt=()=>{c.clear();},St=()=>c.size;var At=(t,e)=>{let r=m({cluster:String(e)})??"mainnet",n=getExplorerLink(),a=n.endsWith("/")?n.slice(0,-1):n,s=r?`?cluster=${r}`:"";return `${a}${t||"/"}${s}`};
2
- export{ut as clearSolanaAvatarCache,dt as clearSolanaNameCache,Z as createSolanaClientWithCache,tt as createSolanaRPC,o as defaultRpcUrlsByMoniker,B as getAvailableSolanaClusters,g as getAvailableSolanaConnectors,m as getCluster,lt as getConnectedSolanaConnector,_ as getRpcUrlForCluster,N as getSolanaAddressAvatar,gt as getSolanaAddressName,pt as getSolanaAvatarCacheSize,F as getSolanaClusters,At as getSolanaExplorerLink,St as getSolanaNameCacheSize,j as isSolanaChainList,T as isValidSolanaCluster,ft as preloadSolanaAvatar};
1
+ import {createSolanaRpc,createSolanaRpcSubscriptions,isAddress}from'@solana/kit';import {filterUniqueByKey,lastConnectedConnectorHelpers}from'@tuwaio/orbit-core';import {getWallets}from'@wallet-standard/app';import {getOrCreateUiWalletForStandardWallet}from'@wallet-standard/ui-registry';var s={mainnet:"https://api.mainnet-beta.solana.com",devnet:"https://api.devnet.solana.com",testnet:"https://api.testnet.solana.com"};function d(t){let e=t.split(":");if(e.length<2)return null;let r=e[1];r==="mainnet-beta"&&(r="mainnet");let a=r;return a in s?a:null}function S(t,e){let r={},a=Object.keys(e||s);for(let n of t){if(typeof n!="string")continue;let o=d(n);if(!o||!a.includes(o))continue;let f=e?.[o]??s[o];f&&(r[o]=f);}return r}function z(t,e){if(e&&e.length>0){let r=S(e,t);return Object.keys(r)}return Object.keys(t||s)}function T(t){return t.length>0&&t.every(e=>typeof e=="string")}function W(){return Object.keys(s)}function D(t){return t in s}var m=({cluster:t,walletCluster:e})=>{let r="mainnet";return t?t.includes(":")?t.split(":")[1]:t:e??r},q=({cluster:t,walletCluster:e,rpcUrls:r})=>r[e??t]??"https://api.mainnet-beta.solana.com/";function h(t){try{return new URL(t),!0}catch{return false}}function R(t){return t.replace(/^http/,"ws")}var l=new Map,G=({rpcUrlOrMoniker:t,rpcUrls:e})=>{if(l.has(t))return l.get(t);let r=h(t)?t:e?e[t]??s[t]:s[t];if(!r)throw new Error(`Unable to resolve RPC URL for input: "${t}". Ensure it's a valid URL or known moniker.`);let a=createSolanaRpc(r),n;try{n=createSolanaRpcSubscriptions(R(r));}catch{}let o={rpc:a,...n?{rpcSubscriptions:n}:{}};return l.set(t,o),o},J=()=>{l.clear();};function v(t){try{return new URL(t),!0}catch{return false}}var c=new Map,O=({rpcUrlOrMoniker:t,rpcUrls:e})=>{if(c.has(t))return c.get(t);let r=v(t)?t:e?e[t]??s[t]:s[t];if(!r)throw new Error(`Unable to resolve RPC URL for input: "${t}". Ensure it's a valid URL or known moniker.`);let a=createSolanaRpc(r);return c.set(t,a),a},tt=()=>{c.clear();};function g(){return filterUniqueByKey(getWallets().get().map(getOrCreateUiWalletForStandardWallet),"name").filter(t=>{try{return !t||!Array.isArray(t.chains)||!t.features||!Array.isArray(t.features)||!["standard:connect","standard:disconnect","standard:events","solana:signAndSendTransaction","solana:signTransaction","solana:signMessage"].every(a=>t.features.includes(a))?!1:t.chains.every(a=>{try{if(typeof a!="string")return !1;let n=a.split(":");return n.length>=1&&n[0]==="solana"}catch(n){return console.warn("Error parsing chain:",a,n),!1}})}catch(e){return console.warn("Error filtering wallet:",e),false}})}function lt(){let t=lastConnectedConnectorHelpers.getLastConnectedConnector(),r=g().find(a=>a.accounts.find(n=>n.address.toLowerCase()===t?.address?.toLowerCase()));if(!r)throw new Error("Connector not provided. Cannot perform chain check.");return r}var u=new Map,L="https://image-api.bonfida.com",P=t=>/^[a-z0-9-]+\.sol$/.test(t.toLowerCase()),N=async t=>{try{let e=await fetch(`${L}/image?domain=${encodeURIComponent(t)}`);if(!e.ok)return null;let r=await e.json();return r.result&&Array.isArray(r.result)&&r.result.length>0?r.result.find(n=>n.domain===t)?.image??r.result[0]?.image??null:null}catch(e){return console.warn("Failed to fetch SNS avatar:",e),null}},i=t=>`https://api.dicebear.com/7.x/identicon/svg?seed=${encodeURIComponent(t)}`,E=async t=>{if(!t)return i("default");let e=t.trim();if(!e)return i("default");let r=e.toLowerCase(),a=u.get(r);if(a!==void 0)return a;let n;try{if(P(e)){let o=await N(e);o?n=o:n=i(e);}else isAddress(e),n=i(e);}catch(o){console.warn("Error resolving avatar:",o),n=i(e);}return u.set(r,n),n},pt=()=>{u.clear();},ft=()=>u.size,mt=async t=>{try{await E(t);}catch(e){console.warn("Failed to preload avatar:",e);}};var p=new Map,$="https://sns-api.bonfida.com",I=async t=>{try{let e=await fetch(`${$}/v2/user/fav-domains/${t}`);if(!e.ok)return null;let r=await e.json();return r.result&&typeof r.result=="object"&&t in r.result?r.result[t]:null}catch(e){return console.warn("Failed to fetch SNS name:",e),null}},St=async t=>{if(!t)return t||"";let e=t.trim();if(!isAddress(e))return t;let r=e.toLowerCase(),a=p.get(r);if(a!==void 0)return a;let n=t;try{let o=await I(e);o&&(n=o);}catch(o){console.warn("Error resolving address name:",o);}return p.set(r,n),n},Ct=()=>{p.clear();},yt=()=>p.size;var _="https://explorer.solana.com",F=new Set(["mainnet","mainnet-beta"]),At=(t,e)=>{let r=m({cluster:e===void 0?void 0:String(e)}),a=_,n=F.has(r)?"":`?cluster=${r}`,o=t?t.startsWith("/")?t:`/${t}`:"/";return `${a}${o}${n}`};
2
+ export{pt as clearSolanaAvatarCache,J as clearSolanaClientCache,Ct as clearSolanaNameCache,tt as clearSolanaRpcCache,G as createSolanaClientWithCache,O as createSolanaRPC,s as defaultRpcUrlsByMoniker,W as getAvailableSolanaClusters,g as getAvailableSolanaConnectors,m as getCluster,lt as getConnectedSolanaConnector,q as getRpcUrlForCluster,E as getSolanaAddressAvatar,St as getSolanaAddressName,ft as getSolanaAvatarCacheSize,z as getSolanaClusters,At as getSolanaExplorerLink,yt as getSolanaNameCacheSize,T as isSolanaChainList,D as isValidSolanaCluster,mt as preloadSolanaAvatar};
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@tuwaio/orbit-solana",
3
- "version": "0.2.13",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "author": "Oleksandr Tkach",
6
6
  "license": "Apache-2.0",
7
- "description": "Layer 2 (L2) of the TUWA Ecosystem. Low-level Solana-specific communication primitives and RPC caching utilities powered strictly by gill.",
7
+ "description": "L2 Solana package of Orbit Utils (TUWA): cached @solana/kit RPC clients, Wallet Standard discovery, cluster and explorer helpers, and SNS name/avatar lookups.",
8
8
  "main": "./dist/index.js",
9
9
  "module": "./dist/index.mjs",
10
10
  "types": "./dist/index.d.ts",
@@ -16,9 +16,14 @@
16
16
  "socket.json"
17
17
  ],
18
18
  "keywords": [
19
+ "tuwa",
20
+ "orbit",
19
21
  "web3",
20
- "typescript",
21
22
  "solana",
23
+ "@solana/kit",
24
+ "wallet-standard",
25
+ "sns",
26
+ "typescript",
22
27
  "headless"
23
28
  ],
24
29
  "repository": {
@@ -38,22 +43,23 @@
38
43
  ],
39
44
  "peerDependencies": {
40
45
  "@tuwaio/orbit-core": ">=0.2",
41
- "gill": ">=0.14",
46
+ "@solana/kit": ">=8.2",
42
47
  "@wallet-standard/app": "1.x.x",
43
48
  "@wallet-standard/ui-core": "1.x.x",
44
49
  "@wallet-standard/ui-registry": "1.x.x"
45
50
  },
46
51
  "devDependencies": {
52
+ "@tuwaio/orbit-core": "^0.3.1",
47
53
  "tsup": "^8.5.1",
48
- "typescript": "^6.0.3",
49
- "gill": "^0.14.0",
54
+ "typescript": "6.0.3",
55
+ "@solana/kit": "^8.3.0",
50
56
  "@wallet-standard/app": "^1.1.1",
51
57
  "@wallet-standard/ui-core": "^1.0.1",
52
- "@wallet-standard/ui-registry": "^1.1.1",
53
- "@tuwaio/orbit-core": "^0.2.15"
58
+ "@wallet-standard/ui-registry": "^1.1.1"
54
59
  },
55
60
  "scripts": {
56
61
  "start": "tsup src/index.ts --watch",
57
- "build": "tsup"
62
+ "build": "tsup",
63
+ "test": "vitest run"
58
64
  }
59
65
  }