Submit Single Transaction
Submit individual transactions to NextBlock using JavaScript/TypeScript with proper tipping and error handling.
import {
Connection,
Keypair,
PublicKey,
SystemProgram,
Transaction,
sendAndConfirmTransaction
} from '@solana/web3.js';
// NextBlock tip wallets for load balancing
const NEXTBLOCK_TIP_WALLETS = [
'NEXTbLoCkB51HpLBLojQfpyVAMorm3zzKg7w9NFdqid',
'nextBLoCkPMgmG8ZgJtABeScP35qLa2AMCNKntAP7Xc',
'NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE',
'NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2',
'NeXTBLoCKs9F1y5PJS9CKrFNNLU1keHW71rfh7KgA1X',
'NexTBLockJYZ7QD7p2byrUa6df8ndV2WSd8GkbWqfbb',
'neXtBLock1LeC67jYd1QdAa32kbVeubsfPNTJC1V5At',
'nEXTBLockYgngeRmRrjDV31mGSekVPqZoMGhQEZtPVG',
];
// Submission options interface
interface SubmissionOptions {
skipPreflight?: boolean;
frontRunningProtection?: boolean;
revertOnFail?: boolean;
disableRetries?: boolean;
snipeTransaction?: boolean;
}
// Get random tip wallet for load balancing
function getRandomNextblockTipWallet(): PublicKey {
const randomIndex = Math.floor(Math.random() * NEXTBLOCK_TIP_WALLETS.length);
return new PublicKey(NEXTBLOCK_TIP_WALLETS[randomIndex]);
}
// Build transaction with tip
async function buildTransactionWithTip(
connection: Connection,
payer: Keypair,
tipAmount: number,
instructions: any[]
): Promise<Transaction> {
// Get recent blockhash
const { blockhash } = await connection.getLatestBlockhash('finalized');
// Create tip instruction (should be first)
const tipWallet = getRandomNextblockTipWallet();
const tipInstruction = SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: tipWallet,
lamports: tipAmount,
});
// Create transaction with tip and user instructions
const transaction = new Transaction({
recentBlockhash: blockhash,
feePayer: payer.publicKey,
});
transaction.add(tipInstruction);
instructions.forEach(instruction => transaction.add(instruction));
// Sign transaction
transaction.sign(payer);
return transaction;
}
// Submit single transaction to NextBlock
async function submitSingleTransaction(
// nextblockClient: any, // Your generated gRPC client
connection: Connection,
signer: Keypair,
recipient: PublicKey,
transferAmount: number,
tipAmount: number,
options: SubmissionOptions = {}
): Promise<string> {
// Default options
const opts: SubmissionOptions = {
skipPreflight: true,
frontRunningProtection: false,
revertOnFail: false,
disableRetries: false,
snipeTransaction: false,
...options,
};
// Create transfer instruction
const transferInstruction = SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: recipient,
lamports: transferAmount,
});
// Build transaction with tip
const transaction = await buildTransactionWithTip(
connection,
signer,
tipAmount,
[transferInstruction]
);
// Convert to base64 for submission
const serializedTx = transaction.serialize();
const base64Tx = serializedTx.toString('base64');
// Submit to NextBlock
/* Uncomment when you have the generated gRPC client
const request = {
transaction: { content: base64Tx },
skipPreFlight: opts.skipPreflight,
snipeTransaction: opts.snipeTransaction,
frontRunningProtection: opts.frontRunningProtection,
disableRetries: opts.disableRetries,
revertOnFail: opts.revertOnFail,
};
const response = await nextblockClient.postSubmitV2(request);
console.log('Transaction submitted successfully!');
console.log(`Signature: ${response.signature}`);
console.log(`UUID: ${response.uuid}`);
return response.signature;
*/
// Mock response for demonstration
const signature = transaction.signature?.toString() || 'mock-signature';
console.log(`Transaction built successfully: ${signature}`);
return signature;
}
// Submit with priority-based tip calculation
async function submitWithOptimalTip(
// nextblockClient: any,
connection: Connection,
signer: Keypair,
recipient: PublicKey,
transferAmount: number,
priorityLevel: 'conservative' | 'normal' | 'aggressive' | 'priority' = 'normal'
): Promise<string> {
// Tip amounts based on priority level
// Get tip amounts from current tip floor data
// Higher tips = higher priority
// Use tip floor streaming API to get current optimal tips
const tipAmount = await getOptimalTipFromTipFloor(priorityLevel);
return await submitSingleTransaction(
// nextblockClient,
connection,
signer,
recipient,
transferAmount,
tipAmount
);
}
// Transaction builder class for complex transactions
class TransactionBuilder {
private payer: Keypair;
private instructions: any[] = [];
private tipAmount: number = 1_000_000; // Default tip
constructor(payer: Keypair) {
this.payer = payer;
}
addInstruction(instruction: any): TransactionBuilder {
this.instructions.push(instruction);
return this;
}
setTipAmount(amount: number): TransactionBuilder {
this.tipAmount = amount;
return this;
}
async buildAndSubmit(
// nextblockClient: any,
connection: Connection,
options?: SubmissionOptions
): Promise<string> {
if (this.instructions.length === 0) {
throw new Error('No instructions added to transaction');
}
const transaction = await buildTransactionWithTip(
connection,
this.payer,
this.tipAmount,
this.instructions
);
// Convert and submit (similar to submitSingleTransaction)
const serializedTx = transaction.serialize();
const base64Tx = serializedTx.toString('base64');
const signature = transaction.signature?.toString() || 'mock-signature';
console.log(`Custom transaction submitted: ${signature}`);
return signature;
}
}
// Batch multiple single transactions (concurrent submission)
async function submitMultipleSingleTransactions(
// nextblockClient: any,
connection: Connection,
signer: Keypair,
transactionsData: Array<{
recipient: PublicKey;
transferAmount: number;
tipAmount: number;
}>
): Promise<string[]> {
// Create promises for all transactions
const transactionPromises = transactionsData.map(async ({ recipient, transferAmount, tipAmount }) => {
try {
return await submitSingleTransaction(
// nextblockClient,
connection,
signer,
recipient,
transferAmount,
tipAmount
);
} catch (error) {
console.error(`Transaction failed:`, error);
return null;
}
});
// Execute all transactions concurrently
const results = await Promise.allSettled(transactionPromises);
// Process results
const successfulSignatures: string[] = [];
results.forEach((result, index) => {
if (result.status === 'fulfilled' && result.value) {
successfulSignatures.push(result.value);
console.log(`Transaction ${index + 1} successful: ${result.value}`);
} else {
console.error(`Transaction ${index + 1} failed:`, result);
}
});
return successfulSignatures;
}
// Performance monitoring
class TransactionMetrics {
private submissions: number = 0;
private successes: number = 0;
private failures: number = 0;
private totalTime: number = 0;
private startTime?: number;
startSubmission(): void {
this.submissions++;
this.startTime = Date.now();
}
recordSuccess(): void {
if (this.startTime) {
this.totalTime += Date.now() - this.startTime;
this.successes++;
this.startTime = undefined;
}
}
recordFailure(): void {
if (this.startTime) {
this.totalTime += Date.now() - this.startTime;
this.failures++;
this.startTime = undefined;
}
}
getStats() {
return {
totalSubmissions: this.submissions,
successes: this.successes,
failures: this.failures,
successRate: (this.successes / Math.max(this.submissions, 1)) * 100,
averageTime: this.totalTime / Math.max(this.successes, 1),
};
}
}
// Submit with monitoring
async function submitWithMonitoring(
// nextblockClient: any,
connection: Connection,
signer: Keypair,
recipient: PublicKey,
transferAmount: number,
tipAmount: number,
metrics: TransactionMetrics
): Promise<string | null> {
metrics.startSubmission();
try {
const signature = await submitSingleTransaction(
// nextblockClient,
connection,
signer,
recipient,
transferAmount,
tipAmount
);
metrics.recordSuccess();
return signature;
} catch (error) {
metrics.recordFailure();
console.error('Transaction failed:', error);
return null;
}
}
Usage Examples
async function main() {
// Initialize Solana connection
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
const signer = Keypair.generate(); // Use your actual keypair
const recipient = new PublicKey('<recipient-public-key>');
// Connect to NextBlock (see connection.md)
// const config = configFromEnv();
// const manager = new NextBlockConnectionManager(config);
// await manager.connect();
// const nextblockClient = manager.grpcClient;
try {
// Example 1: Basic transaction submission
console.log('1. Basic transaction submission:');
const signature1 = await submitSingleTransaction(
// nextblockClient,
connection,
signer,
recipient,
10_000, // Transfer 10,000 lamports
1_000_000 // Tip 1,000,000 lamports (0.001 SOL)
);
console.log(`Basic transaction: ${signature1}\n`);
// Example 2: Transaction with optimal tip
console.log('2. Transaction with optimal tip:');
const signature2 = await submitWithOptimalTip(
// nextblockClient,
connection,
signer,
recipient,
20_000, // Transfer 20,000 lamports
'aggressive' // Use aggressive tip strategy
);
console.log(`Optimally tipped transaction: ${signature2}\n`);
// Example 3: Custom transaction builder
console.log('3. Custom transaction builder:');
const builder = new TransactionBuilder(signer);
const signature3 = await builder
.addInstruction(SystemProgram.transfer({
fromPubkey: signer.publicKey,
toPubkey: recipient,
lamports: 30_000,
}))
.setTipAmount(1_500_000)
.buildAndSubmit(
// nextblockClient,
connection
);
console.log(`Custom built transaction: ${signature3}\n`);
// Example 4: Multiple concurrent transactions
console.log('4. Multiple concurrent transactions:');
const transactionsData = [
{ recipient, transferAmount: 5_000, tipAmount: 500_000 },
{ recipient, transferAmount: 7_500, tipAmount: 750_000 },
{ recipient, transferAmount: 12_500, tipAmount: 1_250_000 },
];
const signatures = await submitMultipleSingleTransactions(
// nextblockClient,
connection,
signer,
transactionsData
);
console.log(`Multiple transactions: ${signatures}\n`);
// Example 5: Transaction monitoring
console.log('5. Transaction with performance monitoring:');
const metrics = new TransactionMetrics();
for (let i = 0; i < 5; i++) {
await submitWithMonitoring(
// nextblockClient,
connection,
signer,
recipient,
1_000 * (i + 1), // Varying amounts
500_000, // Fixed tip
metrics
);
}
console.log('Performance metrics:', metrics.getStats());
} catch (error) {
console.error('Error in main:', error);
} finally {
// Clean up connection
// await manager.disconnect();
}
}
// Error handling with retry logic
async function submitWithRetry(
// nextblockClient: any,
connection: Connection,
signer: Keypair,
recipient: PublicKey,
transferAmount: number,
tipAmount: number,
maxRetries: number = 3
): Promise<string> {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await submitSingleTransaction(
// nextblockClient,
connection,
signer,
recipient,
transferAmount,
tipAmount
);
} catch (error) {
lastError = error as Error;
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError!;
}
if (require.main === module) {
main().catch(console.error);
}
export {
submitSingleTransaction,
submitWithOptimalTip,
TransactionBuilder,
submitMultipleSingleTransactions,
TransactionMetrics,
submitWithMonitoring,
submitWithRetry,
};
Best Practices
Always include tips: NextBlock prioritizes transactions with appropriate tips
Use random tip wallets: Distribute load across multiple tip addresses
Monitor tip floors: Adjust tips based on current network conditions
Handle errors gracefully: Implement retry logic with exponential backoff
Validate inputs: Always validate public keys and amounts before submission
Use TypeScript: Leverage type safety for better development experience
Monitor performance: Track success rates and response times
Choose appropriate RPC endpoints: Use reliable RPC providers for blockhash retrieval
Last updated