Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hypertekorg/hyperstack/llms.txt

Use this file to discover all available pages before exploring further.

Instructions are transaction handlers that let you execute blockchain operations. They handle account resolution, serialization, signing, and confirmation.

What are Instructions?

Instructions are generated from your Rust program and bundled with your stack. Each instruction includes:
  • Account metadata - Defines required accounts (signer, PDA, user-provided)
  • Serialization logic - Converts arguments to instruction data
  • Error definitions - Parses program errors

Basic Usage

import { executeInstruction } from 'hyperstack-typescript';
import { MyStack } from './stack';
import { walletAdapter } from './wallet';

// Get instruction handler from stack
const swapHandler = MyStack.instructions.swap;

// Execute the instruction
const result = await executeInstruction(swapHandler, 
  // Arguments
  {
    amount: 1000000,
    minOut: 900000,
  },
  // Options
  {
    wallet: walletAdapter,
    accounts: {
      mint: 'So11111...', // User-provided account
    },
  }
);

console.log('Transaction:', result.signature);
console.log('Slot:', result.slot);

Instruction Definition

Instructions are defined in your stack SDK:
interface InstructionHandler {
  // Build the instruction with resolved accounts
  build(args: Record<string, unknown>, accounts: ResolvedAccounts): BuiltInstruction;
  
  // Account metadata for resolution
  accounts: AccountMeta[];
  
  // Error definitions for parsing
  errors: ErrorMetadata[];
  
  // Program ID for PDA derivation
  programId?: string;
}

Account Categories

Accounts are categorized by how they’re resolved:
type AccountCategory = 
  | 'signer'        // Auto-resolved from wallet
  | 'pda'           // Auto-derived from seeds
  | 'user_provided' // Provided in options.accounts
  | 'readonly';     // Read-only account

Account Resolution

Accounts are automatically resolved based on their category:

Signer Accounts

// Signer is auto-resolved from wallet
const result = await executeInstruction(handler, args, {
  wallet: walletAdapter, // Provides signer address
});

PDA Accounts

PDAs are derived automatically:
// PDA derived from seeds defined in the instruction
const result = await executeInstruction(handler, 
  {
    mint: 'So11111...', // Used as seed
  },
  {
    wallet: walletAdapter,
  }
);
// PDA automatically derived using: [b"vault", mint.as_bytes()]

User-Provided Accounts

// Provide accounts explicitly
const result = await executeInstruction(handler, args, {
  wallet: walletAdapter,
  accounts: {
    mint: 'So11111...',
    pool: 'EPjFWdd5...',
    vault: 'Gh9ZwEmd...',
  },
});

Execution Options

interface ExecuteOptions {
  // Required: wallet for signing
  wallet: WalletAdapter;
  
  // Optional: user-provided accounts
  accounts?: Record<string, string>;
  
  // Optional: confirmation level
  confirmationLevel?: 'processed' | 'confirmed' | 'finalized';
  
  // Optional: timeout in milliseconds
  timeout?: number;
}

Confirmation Levels

// Wait for processed (fastest)
const result = await executeInstruction(handler, args, {
  wallet,
  confirmationLevel: 'processed',
});

// Wait for confirmed (recommended)
const result = await executeInstruction(handler, args, {
  wallet,
  confirmationLevel: 'confirmed', // Default
});

// Wait for finalized (slowest, most secure)
const result = await executeInstruction(handler, args, {
  wallet,
  confirmationLevel: 'finalized',
  timeout: 90000, // Longer timeout for finalized
});

Execution Result

interface ExecutionResult {
  signature: string;           // Transaction signature
  confirmationLevel: string;   // Achieved confirmation level
  slot: number;               // Slot number
}

const result = await executeInstruction(handler, args, options);
console.log(`Transaction confirmed at slot ${result.slot}`);
console.log(`Signature: ${result.signature}`);

Error Handling

import { executeInstruction, parseInstructionError } from 'hyperstack-typescript';

try {
  const result = await executeInstruction(handler, args, {
    wallet: walletAdapter,
  });
  console.log('Success:', result.signature);
} catch (error) {
  // Parse program errors
  const programError = parseInstructionError(error, handler.errors);
  
  if (programError) {
    console.error('Program error:', programError.name);
    console.error('Message:', programError.message);
    console.error('Code:', programError.code);
  } else {
    console.error('Transaction failed:', error);
  }
}
Program errors are defined in your Rust program:
#[error_code]
pub enum ErrorCode {
    #[msg("Insufficient balance")]
    InsufficientBalance,
    #[msg("Invalid amount")]
    InvalidAmount,
}

Wallet Adapters

Wallet adapters handle signing and sending:
interface WalletAdapter {
  // Get wallet address
  publicKey: string;
  
  // Sign and send transaction
  signAndSend(transaction: unknown): Promise<string>;
  
  // Optional: connection state
  connected?: boolean;
}

Example Adapter

import { useWallet } from '@solana/wallet-adapter-react';

function createWalletAdapter(wallet: ReturnType<typeof useWallet>): WalletAdapter {
  return {
    publicKey: wallet.publicKey?.toBase58() ?? '',
    connected: wallet.connected,
    signAndSend: async (transaction) => {
      if (!wallet.signTransaction || !wallet.sendTransaction) {
        throw new Error('Wallet not ready');
      }
      // Implementation depends on wallet library
      const signed = await wallet.signTransaction(transaction);
      return await wallet.sendTransaction(signed);
    },
  };
}

Creating Bound Executors

Bind a wallet to avoid passing it each time:
import { createInstructionExecutor } from 'hyperstack-typescript';

const executor = createInstructionExecutor(walletAdapter);

// Execute without passing wallet
const result = await executor.execute(handler, args, {
  confirmationLevel: 'confirmed',
});

PDA Derivation

Manually derive PDAs when needed:
import { findProgramAddress, createPublicKeySeed } from 'hyperstack-typescript';

// Derive PDA
const [pda, bump] = await findProgramAddress(
  [
    Buffer.from('vault'),
    createPublicKeySeed('So11111...'),
  ],
  'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' // Program ID
);

console.log('PDA:', pda);
console.log('Bump:', bump);

Common Patterns

Transaction with Multiple Instructions

// Currently executeInstruction handles single instruction
// For multiple instructions, build them separately and combine
const ix1 = handler1.build(args1, accounts1);
const ix2 = handler2.build(args2, accounts2);

// Build transaction with both instructions
const transaction = {
  instructions: [ix1, ix2],
};

const signature = await wallet.signAndSend(transaction);

Retry Logic

async function executeWithRetry(
  handler: InstructionHandler,
  args: Record<string, unknown>,
  options: ExecuteOptions,
  maxRetries = 3
): Promise<ExecutionResult> {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await executeInstruction(handler, args, options);
    } catch (error) {
      lastError = error;
      console.log(`Attempt ${i + 1} failed, retrying...`);
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
  
  throw lastError;
}

Simulation

// Build instruction without executing
const instruction = handler.build(args, {
  signer: wallet.publicKey,
  mint: 'So11111...',
});

// Simulate using your RPC client
const simulation = await connection.simulateTransaction(instruction);
console.log('Logs:', simulation.logs);
console.log('Units consumed:', simulation.unitsConsumed);

Next Steps

Views

Learn about accessing data

Subscriptions

Subscribe to real-time updates

Build docs developers (and LLMs) love