@tuwaio/orbit-solana
@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.
🏛️ Core Capabilities
- RPC clients:
createSolanaClientWithCachereturns a cached{ rpc, rpcSubscriptions }pair (the WebSocket URL is derived from the HTTP URL);createSolanaRPCreturns a cachedrpconly. Both accept a full URL or a cluster moniker (mainnet,devnet,testnet,localnet). - Wallet Standard discovery:
getAvailableSolanaConnectorslists 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.getConnectedSolanaConnectorfinds the wallet that holds the last connected address stored by@tuwaio/orbit-core.watchAvailableSolanaConnectorscalls back when a wallet registers or unregisters, so a shown list can be read again. - Transaction sending signer:
createSolanaTransactionSendingSignerturns a Wallet Standard account (UiWalletAccount) into an@solana/kitTransactionSendingSignerthat asks the wallet to sign and send withsolana:signAndSendTransaction. It needs no UI framework and does whatuseWalletAccountTransactionSendingSignerof@solana/reactdoes in React. - Message signer:
createSolanaMessageSignersigns a text message with a Wallet Standard wallet, a wallet adapter oruseWallet()of@solana/wallet-adapter(v1 and v3) and returns the base58 signature. It signs the UTF-8 bytes, or the version 1 off-chain message that hardware wallets can show (checked against the envelope built with@solana/kit).@tuwaio/siwx-solanaand@tuwaio/satellite-solanasign with it. The@tuwaio/orbit-solana/signerentry loads only@solana/kit, so a server can import it without the Wallet Standard packages. - Clusters and explorer links:
getCluster,getRpcUrlForCluster,getSolanaClusters,getAvailableSolanaClusters,isValidSolanaCluster,isSolanaChainList, andgetSolanaExplorerLinkfor Solana Explorer URLs. - Names and avatars:
getSolanaAddressNameresolves an address to its favorite SNS (.sol) domain;getSolanaAddressAvatarreturns the SNS profile image or a generated identicon. Both cache results in memory (clear…Cache,get…CacheSize,preloadSolanaAvatar).
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 .
💾 Installation
pnpm add @tuwaio/orbit-solana @tuwaio/orbit-core @solana/kit @wallet-standard/app @wallet-standard/ui-core @wallet-standard/ui-registry[!IMPORTANT]
@tuwaio/orbit-core(>=0.4),@solana/kit(>=6) and the@wallet-standardpackages (1.x) are peer dependencies and must be installed alongside@tuwaio/orbit-solana. The@wallet-standardpackages are optional for code that imports only@tuwaio/orbit-solana/signer:pnpm add @tuwaio/orbit-solana @tuwaio/orbit-core @solana/kitis enough there.
🚀 Usage
Cached RPC client
import { address } from '@solana/kit';
import { createSolanaClientWithCache } from '@tuwaio/orbit-solana';
const { rpc } = createSolanaClientWithCache({
rpcUrlOrMoniker: 'devnet',
rpcUrls: { devnet: 'https://devnet.my-rpc-provider.example' }, // optional; falls back to the public endpoint
});
const { value: lamports } = await rpc.getBalance(address('So11111111111111111111111111111111111111112')).send();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.
Wallet discovery
import { getAvailableSolanaConnectors } from '@tuwaio/orbit-solana';
const wallets = getAvailableSolanaConnectors(); // UiWallet[] from @wallet-standard/ui-registry
console.log(wallets.map((wallet) => wallet.name));Signing and sending a transaction
import {
appendTransactionMessageInstruction,
createTransactionMessage,
type Instruction,
pipe,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signAndSendTransactionMessageWithSigners,
} from '@solana/kit';
import { createSolanaClientWithCache, createSolanaTransactionSendingSigner } from '@tuwaio/orbit-solana';
import type { UiWalletAccount } from '@wallet-standard/ui-core';
declare const account: UiWalletAccount; // for example `connectedAccount` of a Satellite Connect connection
declare const instruction: Instruction; // for example from a Codama-generated program client
const signer = createSolanaTransactionSendingSigner(account, 'devnet'); // or 'solana:devnet', or the CAIP-2 chain ID
const { rpc } = createSolanaClientWithCache({ rpcUrlOrMoniker: 'devnet' });
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const message = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayerSigner(signer, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
(m) => appendTransactionMessageInstruction(instruction, m),
);
const signature = await signAndSendTransactionMessageWithSigners(message); // opens the wallet promptThe account must list the chain of the cluster and the solana:signAndSendTransaction feature, otherwise the function throws. Several transactions are sent in one wallet request; an abortSignal in the call config rejects the call, but the wallet prompt itself cannot be cancelled. signAndSendSolanaTx of @tuwaio/pulsar-solana takes the same signer.
Signing a message
import { createSolanaMessageSigner } from '@tuwaio/orbit-solana/signer';
import type { Wallet, WalletAccount } from '@wallet-standard/base';
declare const wallet: Wallet; // or a wallet adapter, or useWallet() of @solana/wallet-adapter as the whole target
declare const account: WalletAccount; // one of wallet.accounts
const signMessage = createSolanaMessageSigner({ wallet, account });
const signature = await signMessage('Hello from my app'); // opens the wallet prompt; base58 signatureThe signer uses the first capability it finds: the solana:signMessage feature, a signMessages method, then a signMessage method of the wallet, its adapter, the account or the target itself. An account that lists solana:signOffchainMessage but not solana:signMessage signs the version 1 off-chain message instead; messageFormat: 'message' or 'offchainMessage' forces one format. The wallet’s own error is passed through when the user rejects.
Names, avatars and explorer links
import { getSolanaAddressAvatar, getSolanaAddressName, getSolanaExplorerLink } from '@tuwaio/orbit-solana';
const owner = 'So11111111111111111111111111111111111111112';
const name = await getSolanaAddressName(owner); // "name.sol", or the address itself if no SNS domain is set
const avatarUrl = await getSolanaAddressAvatar(name);
getSolanaExplorerLink(`/address/${owner}`, 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1'); // "https://explorer.solana.com/address/…?cluster=devnet"The cluster helpers (getCluster, getSolanaClusters, getSolanaExplorerLink) accept a cluster in any form: the CAIP-2 chain ID with the genesis hash, as in SOLANA_CHAIN_IDS of @tuwaio/orbit-core, a Wallet Standard chain such as solana:devnet, or the moniker devnet.
🌐 External Services
Some helpers call public third-party HTTP services. The address or domain being looked up is sent to these hosts:
| Helper | Host | Purpose |
|---|---|---|
| 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 |
getSolanaAddressName | sns-api.bonfida.com | Favorite SNS domain of an address |
getSolanaAddressAvatar | image-api.bonfida.com | SNS profile image of a .sol domain |
getSolanaAddressAvatar | api.dicebear.com | Fallback identicon URL (loaded by the browser when rendered) |
📚 API Reference
Every export, with signatures and types generated from the source, is documented at orbit.docs.tuwa.io/packages/orbit-solana .
📄 License
Licensed under the Apache-2.0 License. See the LICENSE file for details.
Interfaces
Type Aliases
Functions
- clearSolanaAvatarCache
- clearSolanaNameCache
- createSolanaClientWithCache
- createSolanaMessageSigner
- createSolanaRPC
- createSolanaTransactionSendingSigner
- getAvailableSolanaClusters
- getAvailableSolanaConnectors
- getCluster
- getConnectedSolanaConnector
- getRpcUrlForCluster
- getSolanaAddressAvatar
- getSolanaAddressName
- getSolanaAvatarCacheSize
- getSolanaClusters
- getSolanaExplorerLink
- getSolanaNameCacheSize
- isSolanaChainList
- isValidSolanaCluster
- preloadSolanaAvatar
- watchAvailableSolanaConnectors