Execution Sandbox

Simulates and policy-checks Solana transactions before they are broadcast to the network

4 min read

Execution Sandbox

ExecutionSandbox simulates a Solana transaction using an RPC endpoint, scores its risk, checks it against your policy, and returns a verdict before any funds move.

Import

typescript
import { ExecutionSandbox } from '@sentinel-sdk/core';

How It Works

base64 tx │ ▼ Simulator ──── RPC simulateTransaction ──► balance changes, program invocations │ ▼ RiskScorer ──► riskScore (0–100), riskLevel, riskFlags │ ▼ PolicyEnforcer ──► policyViolations, approved

All three steps run on every call to evaluate(). The result is conservative: if any step fails internally, the sandbox returns approved: false with riskScore: 100.

Instantiation

typescript
const sandbox = new ExecutionSandbox({
  rpcEndpoint: 'https://api.mainnet-beta.solana.com',
  policy: {
    spendingLimits: {
      maxPerTx: 10,
      maxDaily: 50,
      maxWeekly: 200,
    },
  },
});

API

evaluate(tx)

typescript
async evaluate(tx: string): Promise<SimulationResult>

Evaluate a base64-encoded serialized transaction.

typescript
const result = await sandbox.evaluate(base64Tx);

console.log(result.approved);          // false
console.log(result.riskScore);         // 85
console.log(result.riskLevel);         // 'high'
console.log(result.policyViolations);  // [{ rule: 'MAX_PER_TX', message: '...' }]
console.log(result.balanceChanges);    // [{ mint: 'So111...', amount: -15, decimals: 9 }]
console.log(result.programInvocations); // ['11111111111111111111111111111111', ...]

SimulationResult Fields

FieldTypeDescription
approvedbooleanfalse if risk score exceeds threshold or any policy is violated
riskScorenumber0–100, composite risk score
riskLevelRiskLevel'low' | 'medium' | 'high' | 'critical'
riskFlagsRiskFlag[]Individual factors contributing to the risk score
policyViolationsPolicyViolation[]Policy rules that were violated
balanceChangesTokenChange[]Detected token balance changes (SOL + SPL tokens)
programInvocationsstring[]Program IDs invoked by the transaction
latency_msnumberTotal simulation time in milliseconds

Policy Configuration

typescript
executionSandbox: {
  rpcEndpoint: 'https://api.mainnet-beta.solana.com',
  policy: {
    spendingLimits: {
      maxPerTx: 10,    // SOL per transaction
      maxDaily: 100,   // SOL per day (rolling window)
      maxWeekly: 500,  // SOL per week (rolling window)
    },

    // Only allow interactions with these programs
    programAllowlist: [
      'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',  // SPL Token
      'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJe1bAe',  // Associated Token
    ],

    // Block transfers to these addresses
    recipientBlocklist: [
      'SuspiciousAddress123...',
    ],

    // Only allow during business hours (UTC)
    timeBounds: {
      activeHours: { start: '09:00', end: '17:00' },
      activeDays: [1, 2, 3, 4, 5],  // Monday–Friday
      timezone: 'UTC',
    },

    // Minimum time between transactions
    cooldown: {
      minMs: 5000,  // 5 seconds
    },

    // Max transactions per hour
    rateLimit: {
      maxTxPerHour: 20,
    },

    // Block if risk score exceeds this (default: 70)
    riskThreshold: 70,
  },
}

Spending limits are in SOL

maxPerTx, maxDaily, and maxWeekly are denominated in SOL, not lamports. The sandbox converts balance changes from lamports automatically.

Risk Scoring

RiskScorer aggregates signals from the simulation into a 0–100 score:

Risk LevelScore Range
low0–29
medium30–59
high60–84
critical85–100

Risk factors include: large balance changes, unlisted program invocations, multiple programs invoked, high token velocity, and policy-adjacent behavior.

The default riskThreshold is 70. Transactions scoring above this threshold are rejected even if no explicit policy rule was violated.

Spending Tracker

The sandbox maintains a rolling SpendingTracker that accumulates SOL spent over the day and week. Each evaluate() call:

  1. Checks whether the proposed transaction would exceed daily/weekly limits
  2. Records the spend if the transaction is approved

The tracker state is in-memory and resets when the process restarts. For persistent limits across restarts, manage the SpendingTracker externally and provide it to PolicyEnforcer.

Program Allowlist

When programAllowlist is set, any transaction invoking a program not on the list is blocked:

typescript
policy: {
  programAllowlist: ['TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'],
  spendingLimits: { maxPerTx: 10, maxDaily: 50, maxWeekly: 200 },
}
typescript
// Transaction invokes System Program + SPL Token
// System Program is NOT on the allowlist → blocked
result.policyViolations[0].rule // 'PROGRAM_NOT_ALLOWED'

Using Components Directly

You can use the sub-components independently:

typescript
import { Simulator, RiskScorer, PolicyEnforcer, SpendingTracker } from '@sentinel-sdk/core';

const simulator = new Simulator({ rpcEndpoint: 'https://api.mainnet-beta.solana.com' });
const scorer = new RiskScorer();
const tracker = new SpendingTracker();
const enforcer = new PolicyEnforcer(policy, tracker);

const raw = await simulator.simulate(base64Tx);
const { riskScore, riskLevel, riskFlags } = scorer.score(raw);
const { approved, violations } = enforcer.check({ raw, riskScore, riskLevel, riskFlags });