A TypeScript project for sending messages using transactions on the Solana blockchain. This tool allows you to send messages encoded in transaction memos, either with or without SOL transfers.
- 📨 Send messages to specific addresses with small SOL transfers
- 📝 Send memo-only messages without any SOL transfer
- 🚀 NEW: Bulk messaging to multiple recipients
- 📋 NEW: Send multiple memo-only messages in batch
- 📄 NEW: File-based recipients - send to addresses from a text file
- ✅ NEW: Address validation and file parsing with error handling
- 💼 Support for multiple wallet loading methods (file, private key, or generate new)
- 🌐 Configurable for devnet, testnet, or mainnet-beta
- 🔍 Automatic balance checking and validation
- 🔗 Direct links to Solana Explorer for transaction viewing
- ⚡ CLI interface for quick message sending
- ⏱️ Configurable delays between transactions to avoid rate limiting
- 🛡️ Error handling with continue-on-error option
- Clone the repository:
git clone https://github.com/shakkernerd/solana-send-tx-msg.git
cd solana-send-tx-msg
- Install dependencies:
npm install
- Set up your environment (optional):
cp .env.example .env
# Edit .env with your configuration
The project can be configured using environment variables:
# Solana Configuration
SOLANA_RPC_URL=https://api.devnet.solana.com
SOLANA_WS_URL=wss://api.devnet.solana.com
SOLANA_NETWORK=devnet # devnet, testnet, or mainnet-beta
# Wallet Configuration (choose one)
WALLET_PATH=~/.config/solana/id.json # Path to Solana CLI wallet
# OR
PRIVATE_KEY=your_base58_private_key_here
# Transaction Configuration
COMMITMENT=confirmed
SKIP_PREFLIGHT=false
Run the example with default settings:
npm run dev
This will:
- Load or generate a wallet
- Check the balance
- Send a test message with transfer
- Send a memo-only message
⚠️ Important: When using messages with spaces, use single quotes ('
) instead of double quotes ("
) to avoid shell parsing issues.
Send a custom message to a specific address:
npm run dev -- --message 'Hello from Solana!' --recipient <recipient-address>
Send a memo-only message (no SOL transfer):
npm run dev -- --message 'This is a memo' --memo-only
NEW: Bulk messaging to multiple recipients:
npm run dev -- --message 'Hello everyone!' --recipients "addr1,addr2,addr3" --bulk
NEW: Send multiple memo-only messages:
npm run dev -- --messages 'Hello','World','Test' --memo-only --bulk
Bulk messaging with advanced options:
npm run dev -- --message 'Hello' --recipients "addr1,addr2" --bulk --delay 1000 --continue-on-error
NEW: File-based recipients:
# Create a sample recipients.txt file
npm run dev -- --create-sample
# Send to addresses from recipients.txt
npm run dev -- --message 'Hello everyone!' --recipients-file recipients.txt
# Send from custom file with options
npm run dev -- --message 'Hello' --recipients-file my-list.txt --delay 1000 --continue-on-error
Show help:
npm run dev -- --help
Single message:
import { SolanaMessageSender, WalletUtils } from "./src/index"
async function sendMessage() {
// Initialize message sender
const messageSender = new SolanaMessageSender({
rpcUrl: "https://api.devnet.solana.com",
network: "devnet",
})
// Load wallet
const senderKeypair = WalletUtils.loadKeypairFromFile("./wallet.json")
// Send message with transfer
const result = await messageSender.sendMessage({
message: "Hello from my app!",
recipientAddress: WalletUtils.toPublicKey("target-address"),
senderKeypair,
commitment: "confirmed",
})
if (result.success) {
console.log(`Message sent! Signature: ${result.signature}`)
console.log(`Explorer: ${result.explorerUrl}`)
}
}
NEW: Bulk messaging:
import { SolanaMessageSender, WalletUtils } from "./src/index"
async function sendBulkMessages() {
const messageSender = new SolanaMessageSender({
rpcUrl: "https://api.devnet.solana.com",
network: "devnet",
})
const senderKeypair = WalletUtils.loadKeypairFromFile("./wallet.json")
// Send to multiple recipients
const recipients = ["address1...", "address2...", "address3..."].map((addr) => WalletUtils.toPublicKey(addr))
const result = await messageSender.sendBulkMessages({
message: "Hello everyone!",
recipientAddresses: recipients,
senderKeypair,
delayBetweenTx: 1000, // 1 second delay
continueOnError: true, // Don't stop on errors
commitment: "confirmed",
})
console.log(`Sent: ${result.totalSent}, Failed: ${result.totalFailed}`)
// Check individual results
result.results.forEach((res, i) => {
if (res.success) {
console.log(`✅ [${i + 1}] ${res.signature}`)
} else {
console.log(`❌ [${i + 1}] ${res.error}`)
}
})
}
NEW: File-based recipients:
import { SolanaMessageSender, WalletUtils, FileUtils } from "./src/index"
async function sendFromFile() {
const messageSender = new SolanaMessageSender({
rpcUrl: "https://api.devnet.solana.com",
network: "devnet",
})
const senderKeypair = WalletUtils.loadKeypairFromFile("./wallet.json")
// Send to addresses from file
const result = await messageSender.sendMessagesFromFile({
message: "Hello from file!",
recipientsFile: "recipients.txt",
senderKeypair,
delayBetweenTx: 1000,
continueOnError: true,
})
console.log(`Sent: ${result.totalSent}, Failed: ${result.totalFailed}`)
}
// You can also manually read and validate the file
async function validateRecipientsFile() {
const validation = await FileUtils.readRecipientsFile("recipients.txt")
if (validation.isValid) {
console.log(`Found ${validation.validAddresses.length} valid addresses`)
if (validation.invalidAddresses.length > 0) {
console.warn(`Skipped ${validation.invalidAddresses.length} invalid addresses`)
}
} else {
console.error(`File validation failed: ${validation.error}`)
}
}
The recipients file supports a simple text format with one address per line:
# Recipients file for Solana Message Sender
# Lines starting with # are comments and will be ignored
# Empty lines are also ignored
# Example addresses (replace with real addresses):
11111111111111111111111111111112
So11111111111111111111111111111111111111112
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
# Add your recipient addresses below:
YourRecipientAddress1Here...
YourRecipientAddress2Here...
Option 1: Use the built-in generator
npm run dev -- --create-sample
This creates a recipients.txt
template with instructions and examples.
Option 2: Create manually
Create a text file with one Solana address per line. Comments (starting with #) and empty lines are ignored.
The system automatically validates all addresses in the file:
- ✅ Valid addresses: Added to the recipient list
⚠️ Invalid addresses: Skipped with warnings (continues with valid ones)- ❌ File errors: Clear error messages for missing files or read errors
Basic usage:
npm run dev -- --message 'Hello!' --recipients-file recipients.txt
With custom file:
npm run dev -- --message 'Hello!' --recipients-file ./my-addresses.txt
With error handling options:
npm run dev -- --message 'Hello!' --recipients-file recipients.txt --continue-on-error --delay 2000
// Check if file exists and is readable
const exists = await FileUtils.checkRecipientsFile("recipients.txt")
// Read and validate all addresses
const validation = await FileUtils.readRecipientsFile("recipients.txt")
// Create a sample file
await FileUtils.createSampleRecipientsFile("my-recipients.txt")
// Validate addresses (throws error if invalid)
FileUtils.validateRecipients(validation)
If you have Solana CLI installed:
solana-keygen new --outfile ~/.config/solana/id.json
Set the PRIVATE_KEY
environment variable with your base58 encoded private key.
If no wallet is configured, the application will generate a new one automatically.
Visit the Solana Faucet to get free devnet SOL.
Use the Solana CLI:
solana airdrop 1 <your-address> --url testnet
- Creates a transaction with two instructions:
- Memo instruction containing your message
- System transfer instruction (small amount of SOL)
- Signs and sends the transaction
- Returns the transaction signature and explorer URL
- Creates a transaction with only a memo instruction
- No SOL transfer involved
- Still requires transaction fees (very small amount)
src/
├── index.ts # Main entry point and CLI
├── messageSender.ts # Core message sending functionality
├── walletUtils.ts # Wallet loading and validation utilities
├── fileUtils.ts # File-based recipients handling utilities
├── config.ts # Configuration management
└── types.ts # TypeScript type definitions
sendMessage(config: MessageConfig)
- Send message with SOL transfersendMemoOnly(config: MemoConfig)
- Send memo-only messagesendBulkMessages(config: BulkMessageConfig)
- NEW: Send messages to multiple recipientssendBulkMemoOnly(config: BulkMemoConfig)
- NEW: Send multiple memo-only messagessendMessagesFromFile(config: FileRecipientConfig)
- NEW: Send messages to recipients from filegetBalance(publicKey: PublicKey)
- Get wallet balance in SOLcheckBalance(publicKey: PublicKey, required?: number)
- Check if wallet has sufficient balance
interface BulkMessageConfig {
message: string
recipientAddresses: PublicKey[]
senderKeypair: Keypair
connection?: Connection
commitment?: "processed" | "confirmed" | "finalized"
skipPreflight?: boolean
delayBetweenTx?: number // milliseconds delay between transactions
continueOnError?: boolean // continue sending even if some fail
}
interface BulkMessageResult {
totalSent: number
totalFailed: number
results: BulkTransactionResult[]
overallSuccess: boolean
}
interface BulkTransactionResult {
recipientAddress: string
signature: string
success: boolean
error?: string
explorerUrl?: string
}
// NEW: File-based recipient interfaces
interface FileRecipientConfig extends Omit<BulkMessageConfig, "recipientAddresses"> {
recipientsFile: string
}
interface FileValidationResult {
isValid: boolean
addresses: string[]
validAddresses: PublicKey[]
invalidAddresses: string[]
error?: string
}
loadKeypairFromFile(path?: string)
- Load keypair from fileloadKeypairFromPrivateKey(privateKey: string)
- Load keypair from base58 private keygenerateKeypair()
- Generate new random keypairgetPrivateKeyString(keypair: Keypair)
- Get base58 private key from keypairisValidPublicKey(address: string)
- Validate Solana addresstoPublicKey(address: string)
- Convert string to PublicKey with validation
checkRecipientsFile(filePath?: string)
- Check if recipients file exists and is readablereadRecipientsFile(filePath?: string)
- Read and validate addresses from filecreateSampleRecipientsFile(filePath?: string)
- Create a template recipients filevalidateRecipients(validation: FileValidationResult)
- Validate file parsing results (throws on error)
npm run bulk-example
const recipients = ["Address1...", "Address2...", "Address3..."]
for (const recipient of recipients) {
const result = await messageSender.sendMessage({
message: `Hello ${recipient}!`,
recipientAddress: WalletUtils.toPublicKey(recipient),
senderKeypair,
})
console.log(`Sent to ${recipient}: ${result.signature}`)
}
const recipients = ["Address1...", "Address2...", "Address3..."].map((addr) => WalletUtils.toPublicKey(addr))
const result = await messageSender.sendBulkMessages({
message: "Hello everyone!",
recipientAddresses: recipients,
senderKeypair,
delayBetweenTx: 1000,
continueOnError: true,
})
console.log(`✅ Sent: ${result.totalSent}, ❌ Failed: ${result.totalFailed}`)
const messages = ["Message 1", "Message 2", "Message 3"]
for (const message of messages) {
await messageSender.sendMemoOnly({
message,
senderKeypair,
})
}
const messages = ["Message 1", "Message 2", "Message 3"]
const result = await messageSender.sendBulkMemoOnly({
message: "Default message",
messages: messages,
senderKeypair,
delayBetweenTx: 500,
continueOnError: true,
})
console.log(`✅ Sent: ${result.totalSent}, ❌ Failed: ${result.totalFailed}`)
// Create a sample recipients file
await FileUtils.createSampleRecipientsFile("my-recipients.txt")
// Edit the file to add your addresses, then:
const result = await messageSender.sendMessagesFromFile({
message: "Hello from file!",
recipientsFile: "my-recipients.txt",
senderKeypair,
delayBetweenTx: 1000,
continueOnError: true,
})
console.log(`📄 File-based sending complete:`)
console.log(`✅ Sent: ${result.totalSent}, ❌ Failed: ${result.totalFailed}`)
// Manual file validation
const validation = await FileUtils.readRecipientsFile("my-recipients.txt")
if (validation.isValid) {
console.log(`📋 Found ${validation.validAddresses.length} valid addresses`)
if (validation.invalidAddresses.length > 0) {
console.warn(`⚠️ Skipped ${validation.invalidAddresses.length} invalid addresses`)
}
}
- Never commit private keys to version control
- Use environment variables for sensitive data
- Test on devnet before using mainnet
- Keep your private keys secure and backed up
-
"Insufficient funds" error
- Check your wallet balance
- Get devnet SOL from the faucet
- Ensure you have enough for transaction fees
-
"Invalid address" error
- Verify the recipient address is a valid Solana public key
- Use the
WalletUtils.isValidPublicKey()
method to validate
-
RPC connection issues
- Check your internet connection
- Try a different RPC endpoint
- Verify the network configuration
-
File-based recipients issues
- Ensure the recipients file exists and is readable
- Check file format: one address per line
- Verify all addresses are valid Solana public keys
- Use
--create-sample
to generate a template file
-
Command hanging or not responding
- Use single quotes instead of double quotes for messages with spaces
- Example:
npm run dev -- --message 'Hello world!' --recipients-file recipients.txt
- Double quotes may cause shell parsing issues in some environments
Build the project:
npm run build
Run the built version:
npm start
Clean build files:
npm run clean
MIT License - feel free to use this project for any purpose.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
Built with ❤️ for the Solana ecosystem