Build the local-validator integration test suite. Off by default so that
building this package's tests needs no WebSocket dependency and no running
validator; see the Integration tests section of the README.
Disabled
Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info
API documentation is generated with Haddock (cabal haddock). Serialization correctness methodology: every instruction, message, and transaction encoder is asserted byte-identical to vectors generated from the official Rust crates (solana-sdk, spl-token, mpl-token-metadata) โ see tools/README.md.
Integration tests
An opt-in test suite runs the SDK against a local validator. It sits behind the integration cabal flag, so a plain cabal test all neither builds it nor pulls its extra dependencies:
solana-test-validator --reset
cabal test integration-tests --flags=integration
Without a validator listening on 127.0.0.1:8899 the suite skips itself and exits 0, so it stays green in CI even when enabled. Set SOLANA_INTEGRATION=1 to make an unreachable validator a failure instead, or SOLANA_RPC_URL to target a different endpoint.
WebSocket subscriptions
Network.Solana.RPC.WebSocket speaks Solana's PubSub protocol: signature and account subscriptions, and awaitSignature, which waits for a transaction to be pushed to you instead of polling for it.
The module is transport-agnostic, so the SDK carries no WebSocket dependency and the same code serves plain ws:// and TLS wss:// endpoints โ you supply the connection. With the websockets package:
import Network.Solana.RPC.WebSocket
import Network.WebSockets qualified as WS
WS.runClient "127.0.0.1" 8900 "/" $ \conn -> do
let transport = WsTransport (WS.sendTextData conn) (WS.receiveData conn)
result <- awaitSignature transport (RequestId 1) (Just "confirmed") 30 signature
print result -- Right <slot>, or Left with the on-chain or protocol error
For wss:// endpoints use wuss's runSecureClient in place of runClient; nothing else changes.
Features
Full JSON-RPC API client (accounts, blocks, chain, ledger, tokens, tokenomics, transactions)
Wallet, account and keys management (ed25519 keypairs, base58 addresses)
Program Derived Addresses (findProgramAddress / createProgramAddress)
Transaction building, signing and submission
Priority fees via Compute Budget (setComputeUnitLimit, setComputeUnitPrice)
Versioned (v0) transactions and Address Lookup Tables
Clients for native programs
Address Lookup Table
BPF Loader (upgradeable)
Compute Budget
Secp256k1 (instruction construction; signing out of scope)
Stake (delegation lifecycle; seed-authority variants out of scope)
On-chain account state decoders (SPL token accounts and mints, lookup tables, stake and nonce accounts, Metaplex metadata)
PubSub (WebSocket) signature and account subscriptions, with push-based confirmation
Serialization verified byte-for-byte against the official Rust crates (solana-sdk, spl-token, mpl-token-metadata) (golden-vector tests)
Release status
The current stable release is v1.2.0.0. Serialization is verified byte-for-byte against the official Rust SDK by golden-vector tests (see test/fixtures/ and tools/README.md).
Usage Examples
Simple transfer
This example demonstrates how to use the Haskell Solana SDK to interact with a Solana validator and perform basic blockchain operations such as keypair generation, account funding via airdrop, balance checking, and transferring SOL tokens.
In the provided sample:
We start by generating a new keypair to act as the transaction sender and fee payer.
We connect to a local Solana validator using an HTTP provider.
The newly generated keypair receives an airdrop of 10 SOL to ensure it has sufficient funds.
We define a recipient's public address from a base58 encoded string.
We construct, sign and submit the transaction by defining the signers and the transaction's list of instructions with their parameters.
Before and after performing the transfer of 1 SOL to the recipient, we check and print the account balances to verify the transaction's success.
This straightforward example highlights the convenience and expressiveness of Haskell when building decentralized applications on Solana.
The examples use GHC2021 (this package's default-language). If you compile them under Haskell2010, additionally enable NumericUnderscores and ImportQualifiedPost.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad (void)
import Control.Monad.IO.Class (liftIO)
import Network.Solana.Core.Crypto
import Network.Solana.NativePrograms.SystemProgram qualified as SystemProgram
import Network.Solana.RPC.HTTP.Transaction
import Network.Solana.SolanaWeb3
import Network.Web3.Provider
main :: IO ()
main = do
-- Generate keypairs for fee payer (sender)
(myPublicKey, myPrivateKey) <- createSolanaKeyPair
-- Create Connection, local validator in this example
result <- runWeb3' (HttpProvider "http://127.0.0.1:8899") $ do
-- Fund fee payer
void $ requestAirdrop myPublicKey 10_000_000_000
wait 15 -- Wait 15 seconds be sure the tx was confirmed
-- Define recipient's address from a base58-encoded string
let recipient = "A988FuUtUVk8jMUuVc1ccaoTA3VS9CB4dkEf9XUAUqV4"
-- Check balance
printBalances [myPublicKey, recipient]
-- Create a new transaction.
txId <-
newTransaction
[myPrivateKey] -- Signing keys (with all required signers)
-- List of instructions
[ SystemProgram.transfer
myPublicKey -- sender address
recipient -- recipient address
1_000_000_000 -- amount to transfer 1 SOL
]
liftIO $ putStrLn ("Transaction sent: " <> show txId)
void $ confirmTransaction txId
-- Check balance
printBalances [myPublicKey, recipient]
either (\e -> putStrLn ("RPC error: " <> show e)) pure result
SPL token transfer (Associated Token Accounts)
Tokens live in associated token accounts (ATAs) โ program-derived addresses computed from the wallet and the mint. This example derives both ATAs with getAssociatedTokenAddress, creates the recipient's ATA if missing (idempotent, safe to include unconditionally), and moves tokens with the decimals-checked transferChecked.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad (void)
import Data.Maybe (fromJust)
import Network.Solana.Core.Crypto
import Network.Solana.SolanaWeb3
import Network.Solana.SplPrograms.AssociatedTokenAccount qualified as Ata
import Network.Solana.SplPrograms.Token qualified as Token
import Network.Web3.Provider
main :: IO ()
main = do
(myPublicKey, myPrivateKey) <- createSolanaKeyPair
void $ runWeb3' (HttpProvider "http://127.0.0.1:8899") $ do
let mint = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" -- the token's mint address
recipient = "A988FuUtUVk8jMUuVc1ccaoTA3VS9CB4dkEf9XUAUqV4" -- recipient wallet
-- ATAs are PDAs of (wallet, token program, mint): derived, not generated.
sourceAta = fromJust (Ata.getAssociatedTokenAddress myPublicKey mint)
destinationAta = fromJust (Ata.getAssociatedTokenAddress recipient mint)
void $
newTransaction
[myPrivateKey]
[ -- Create the recipient's token account if it does not exist yet (no-op otherwise).
Ata.createAssociatedTokenAccountIdempotent
myPublicKey -- funder (pays rent)
recipient -- wallet that will own the ATA
mint,
-- Transfer 1 token (here: 6 decimals); mint and decimals are verified on-chain.
Token.transferChecked
sourceAta -- source token account
mint -- token mint
destinationAta -- destination token account
myPublicKey -- owner of the source account
[] -- extra multisig signers (none)
1_000_000 -- amount in base units
6 -- decimals of the mint
]
Priority fees and memo
Compute Budget instructions raise a transaction's scheduling priority by paying a fee per compute unit; a memo attaches a signed, human-readable note recorded on-chain. Both are ordinary Instructions added to the same instruction list.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad (void)
import Network.Solana.Core.Crypto
import Network.Solana.NativePrograms.ComputeBudget qualified as ComputeBudget
import Network.Solana.NativePrograms.SystemProgram qualified as SystemProgram
import Network.Solana.SolanaWeb3
import Network.Solana.SplPrograms.Memo qualified as Memo
import Network.Web3.Provider
main :: IO ()
main = do
(myPublicKey, myPrivateKey) <- createSolanaKeyPair
void $ runWeb3' (HttpProvider "http://127.0.0.1:8899") $ do
let recipient = "A988FuUtUVk8jMUuVc1ccaoTA3VS9CB4dkEf9XUAUqV4"
void $
newTransaction
[myPrivateKey]
[ ComputeBudget.setComputeUnitLimit 200_000, -- cap the compute units this tx may use
ComputeBudget.setComputeUnitPrice 10_000, -- priority fee: micro-lamports per compute unit
SystemProgram.transfer myPublicKey recipient 1_000_000_000, -- 1 SOL
Memo.buildMemo "thanks for the coffee" [myPublicKey] -- signed on-chain note
]
Contributing
We welcome all contributors! See contributing guide for how to get started.