Skip to content

trionlabs/maestro

Repository files navigation

Maestro - Agentic Policy Engine on Solana

On-chain policy enforcement for AI agent wallets on Solana

Built by Trion Labs

Solana Anchor Rust TypeScript License

Built for OpenClaw Solana Mobile

Website · Source · ClawHub · Android APK

Caution

Unaudited, do not use with real funds.** This project is in early stages of development, has not undergone a security audit, and was built with significant AI tooling assistance. It is provided as-is for educational and experimental purposes only. Use at your own risk.


Table of Contents


What Is This?

An AI agent needs a wallet. But you don't trust it with unlimited access.

Maestro lets vault owners define exactly what an agent can and can't do. Which tokens, which programs, which recipients, how much per day, all enforced on-chain. The agent operates autonomously within those boundaries, and the owner always has a kill switch.

The system is split into two programs:

  • Agent Policy Engine: The brain. Manages vaults, access control lists, session keys, spending limits, and executes policy-checked CPI calls on behalf of the agent.
  • Vault Factory: The registry. Provides vault creation with fee collection, vault discovery by owner, and labeling.

High-Level Architecture

graph TB
    Owner["<b>Owner</b><br/>Human / Multisig"]
    Agent["<b>AI Agent</b><br/>Autonomous wallet operator"]
    Vault["<b>Vault PDA</b><br/>Holds SOL & tokens"]
    Config["<b>VaultConfig</b><br/>Spending limits & time controls"]
    Lists["<b>Green / White / Blacklist</b><br/>Access control entries"]
    SessionKey["<b>Session Key</b><br/>Time-bound agent credential"]
    PE["<b>Policy Engine</b><br/>10-step policy check"]
    Target["<b>Target Program</b><br/>Jupiter, Token, etc."]
    Factory["<b>Vault Factory</b><br/>Registry & fee collection"]
    Registry["<b>VaultRegistry</b><br/>Discovery & labels"]

    Owner -->|creates & configures| Vault
    Owner -->|sets policies| Config
    Owner -->|manages| Lists
    Owner -->|issues| SessionKey
    Agent -->|execute_action| PE
    PE -->|enforces| Config
    PE -->|checks| Lists
    PE -->|validates| SessionKey
    PE -->|CPI passthrough| Target
    Factory -->|creates vaults via CPI| PE
    Factory -->|registers| Registry

    style Owner fill:#4A9CFF,color:#fff
    style Agent fill:#FF6B6B,color:#fff
    style Vault fill:#FFD93D,color:#333
    style PE fill:#6BCB77,color:#fff
    style Factory fill:#9B59B6,color:#fff
Loading

Core Concepts

Vaults

PDA-controlled wallets. One owner can have multiple vaults (via vault_index). The vault PDA is the signer for all CPI calls, so it can hold SOL, SPL tokens, and interact with any Solana program — Jupiter swaps, token transfers, DeFi protocols, you name it.

Seeds: ["vault", owner, vault_index_le_bytes]

Three Lists

Access control uses PDA existence. If the account exists, the entry is on the list. No vectors, no reallocs, O(1) lookup.

graph LR
    BL["<b>Blacklist</b><br/>Absolute deny"]
    GL["<b>Greenlist</b><br/>Tokens & programs"]
    WL["<b>Whitelist</b><br/>Recipients"]

    BL -->|"overrides"| GL
    BL -->|"overrides"| WL
    GL -->|"required for"| TokenProgram["Token & program access"]
    WL -->|"required for"| Recipients["Recipient approval"]

    style BL fill:#FF4444,color:#fff
    style GL fill:#44BB44,color:#fff
    style WL fill:#4488FF,color:#fff
Loading
  • Greenlist: Tokens and programs the agent can interact with. If a program isn't greenlisted, the agent can't call it.
  • Whitelist: Approved recipient addresses for transfers. Optional: require_cosign_new_recipient forces owner co-sign for new recipients.
  • Blacklist: Overrides everything. A blacklisted address cannot be a target program, token mint, or recipient regardless of greenlist/whitelist status.

Session Keys

Time-bounded, spending-capped credentials issued to agents. Each session key has:

  • valid_after / valid_until - time window
  • spending_limit_usdc - max USDC the agent can spend during this session
  • nonce - tied to the vault's global_session_nonce. Incrementing the nonce instantly invalidates all active session keys (bulk revocation).

Two Execution Tiers

Tier 1: Autonomous Tier 2: Co-signed
Signers Agent only Agent + Owner
Policy checks Full 10-step chain Skips tier2 threshold and whitelist
Use case Routine operations Large transfers, new recipients
Instruction execute_action execute_action_cosigned

The 10-Step Policy Check Chain

Every execute_action call passes through this chain. Any failure rejects the transaction.

flowchart TD
    Start(["Agent calls execute_action"]) --> TrackerCheck{"Tracker matches<br/>current day?"}
    TrackerCheck -->|No| R0["TrackerDayMismatch"]
    TrackerCheck -->|Yes| S1

    S1{"1. Vault frozen?"} -->|Yes| R1["VaultFrozen"]
    S1 -->|No| S2

    S2{"2. Session key valid?<br/>(not revoked, nonce matches,<br/>agent matches, within time window)"} -->|No| R2["SessionKey error<br/>(5 possible codes)"]
    S2 -->|Yes| S3

    S3{"3. Within operating<br/>hours?"} -->|No| R3["OutsideOperatingHours"]
    S3 -->|Yes| S4

    S4{"4. Cooldown<br/>elapsed?"} -->|No| R4["CooldownActive"]
    S4 -->|Yes| S5

    S5{"5. Target program<br/>blacklisted?"} -->|Yes| R5["AddressBlacklisted"]
    S5 -->|No| S6

    S6{"6. Target program<br/>greenlisted?"} -->|No| R6["ProgramNotGreenlisted"]
    S6 -->|Yes| S7

    S7{"7. Token & recipient<br/>checks pass?<br/>(blacklist, greenlist,<br/>whitelist)"} -->|No| R7["Token/Recipient error<br/>(4 possible codes)"]
    S7 -->|Yes| S8

    S8{"8. USDC limits OK?<br/>(per-tx, daily, session,<br/>tier2, amount verified)"} -->|No| R8["Limit error<br/>(4 possible codes)"]
    S8 -->|Yes| S9

    S9["9. Execute CPI<br/>via invoke_signed"] --> S10

    S10["10. Emit ActionExecuted<br/>event"]

    style Start fill:#4A9CFF,color:#fff
    style S9 fill:#6BCB77,color:#fff
    style S10 fill:#6BCB77,color:#fff
    style R0 fill:#FF4444,color:#fff
    style R1 fill:#FF4444,color:#fff
    style R2 fill:#FF4444,color:#fff
    style R3 fill:#FF4444,color:#fff
    style R4 fill:#FF4444,color:#fff
    style R5 fill:#FF4444,color:#fff
    style R6 fill:#FF4444,color:#fff
    style R7 fill:#FF4444,color:#fff
    style R8 fill:#FF4444,color:#fff
Loading

Account Architecture

erDiagram
    Vault ||--|| VaultConfig : "has config"
    Vault ||--o{ GreenlistEntry : "approved tokens & programs"
    Vault ||--o{ WhitelistEntry : "approved recipients"
    Vault ||--o{ BlacklistEntry : "denied addresses"
    Vault ||--o{ SessionKey : "agent credentials"
    Vault ||--o{ SpendingTracker : "daily spending"
    FactoryState ||--o{ VaultRegistry : "registered vaults"
    Owner ||--o{ OwnerIndex : "vault count"

    Vault {
        Pubkey owner
        Pubkey usdc_mint
        u64 vault_index
        u64 global_session_nonce
        bool is_frozen
        u64 session_counter
        u8 bump
    }
    VaultConfig {
        Pubkey vault
        u64 per_tx_limit_usdc
        u64 daily_limit_usdc
        u32 operating_hours_start
        u32 operating_hours_end
        u32 cooldown_seconds
        u64 cooldown_threshold_usdc
        i64 last_cooldown_trigger
        u64 tier2_threshold_usdc
        bool require_cosign_new_recipient
        u8 bump
    }
    SessionKey {
        Pubkey vault
        Pubkey agent
        u64 nonce
        i64 valid_after
        i64 valid_until
        u64 spending_limit_usdc
        u64 amount_spent_usdc
        bool is_revoked
        u8 bump
    }
    SpendingTracker {
        Pubkey vault
        u64 day_epoch
        u64 total_spent_usdc
        u8 bump
    }
Loading

Account Sizes

Account Size (bytes) Program
Vault 98 Policy Engine
VaultConfig 94 Policy Engine
GreenlistEntry 73 Policy Engine
WhitelistEntry 73 Policy Engine
BlacklistEntry 73 Policy Engine
SessionKey 114 Policy Engine
SpendingTracker 57 Policy Engine
FactoryState 154 Factory
VaultRegistry 122 Factory
OwnerIndex 49 Factory

PDA Seeds Reference

Policy Engine

Account Seeds
Vault ["vault", owner, vault_index_le_bytes]
VaultConfig ["vault_config", vault]
GreenlistEntry ["greenlist", vault, pubkey]
WhitelistEntry ["whitelist", vault, address]
BlacklistEntry ["blacklist", vault, address]
SessionKey ["session", vault, agent, counter_le_bytes]
SpendingTracker ["tracker", vault, day_epoch_le_bytes]

Factory

Account Seeds
FactoryState ["factory"]
VaultRegistry ["registry", vault_pubkey]
OwnerIndex ["owner_index", owner]

Instructions Reference

Policy Engine (20 instructions)

Admin: Vault CRUD

Instruction Parameters Description
create_vault vault_index: u64, usdc_mint: Pubkey Create a new vault with config
close_vault Close vault and reclaim rent
update_vault_config UpdateVaultConfigParams Update spending limits, time controls

Admin: List Management

Instruction Parameters Description
add_greenlist entry_pubkey: Pubkey Approve a token or program
remove_greenlist entry_pubkey: Pubkey Remove token/program approval
add_whitelist address: Pubkey Approve a recipient
remove_whitelist address: Pubkey Remove recipient approval
add_blacklist address: Pubkey Block an address (overrides all)
remove_blacklist address: Pubkey Unblock an address

Admin: Session Keys

Instruction Parameters Description
create_session_key agent, valid_after, valid_until, spending_limit_usdc Issue a time-bound credential
revoke_session_key Revoke a single session key
revoke_all_sessions Increment nonce, invalidate all keys

Admin: Freeze & Withdraw

Instruction Parameters Description
freeze_vault Block all agent operations
unfreeze_vault Resume agent operations
withdraw_sol amount: u64 Owner withdraws SOL from vault
withdraw_spl amount: u64 Owner withdraws SPL tokens from vault

Agent: Execution

Instruction Parameters Description
execute_action ExecuteActionParams Tier 1: autonomous, full policy check
execute_action_cosigned ExecuteActionParams Tier 2: owner co-signs, relaxed checks

Utility

Instruction Parameters Description
init_tracker day_epoch: u64 Initialize daily spending tracker
close_spent_tracker Close expired tracker, reclaim rent

Factory (8 instructions)

Admin

Instruction Parameters Description
initialize_factory treasury, creation_fee_lamports, policy_engine_program Bootstrap factory state
update_factory_config UpdateFactoryConfigParams Update fees, treasury, pause
transfer_admin new_admin: Pubkey Initiate admin transfer
accept_admin New admin accepts transfer

User

Instruction Parameters Description
create_vault_via_factory vault_index, usdc_mint, label Create vault with fee, register it
register_existing_vault label Register a pre-existing vault
deregister_vault Remove vault from registry
update_vault_label label Update vault display label

SDK Quick Start

npm install @trionlabs/agent-policy-engine
import { Program, AnchorProvider, BN } from "@coral-xyz/anchor";
import { OwnerClient, AgentWallet, PROGRAM_ID } from "@trionlabs/agent-policy-engine";
import type { AgentPolicyEngine } from "@trionlabs/agent-policy-engine";

const provider = AnchorProvider.env();
const program = new Program<AgentPolicyEngine>(idl, PROGRAM_ID, provider);

// Owner: set up vault and policies
const ownerClient = new OwnerClient(program, provider.wallet.publicKey);

await ownerClient.createVault(usdcMint);
await ownerClient.updateVaultConfig({
  perTxLimitUsdc: new BN(100_000_000),    // 100 USDC
  dailyLimitUsdc: new BN(500_000_000),    // 500 USDC
  tier2ThresholdUsdc: new BN(200_000_000), // 200 USDC requires co-sign
});

// Greenlist tokens/programs the agent can use
await ownerClient.addGreenlist(usdcMint);
await ownerClient.addGreenlist(TOKEN_PROGRAM_ID);

// Whitelist approved recipients
await ownerClient.addWhitelist(recipientAddress);

// Issue a 24-hour session key with 200 USDC spending cap
const { sessionKeyPda } = await ownerClient.createSessionKey(
  agentPubkey,
  new BN(Math.floor(Date.now() / 1000) - 60),
  new BN(Math.floor(Date.now() / 1000) + 86400),
  new BN(200_000_000),
);

// Agent: execute actions within policies
const agentClient = new AgentWallet(program, agentPubkey, ownerPubkey);

await agentClient.initTracker();

await agentClient.executeAction(
  {
    instructionData: Buffer.from([3, ...amountLE]),
    usdcAmount: new BN(50_000_000),
    recipient: recipientAddress,
    tokenMint: usdcMint,
  },
  sessionKeyPda,
  trackerPda,
  TOKEN_PROGRAM_ID,
  {
    tokenMint: usdcMint,
    recipient: recipientAddress,
    cpiAccounts: [
      { pubkey: vaultTokenAccount, isWritable: true, isSigner: false },
      { pubkey: destTokenAccount, isWritable: true, isSigner: false },
      { pubkey: vaultPda, isWritable: false, isSigner: false },
    ],
  },
);

See the sdk/ directory for the full API.

Factory Flow

sequenceDiagram
    participant User
    participant Factory
    participant PolicyEngine

    User->>Factory: createVaultViaFactory(index, mint, label)
    Factory->>Factory: Check not paused
    Factory->>Factory: Collect creation fee (SOL-> treasury)
    Factory->>PolicyEngine: Manual CPI -> create_vault
    PolicyEngine->>PolicyEngine: Init Vault PDA + VaultConfig PDA
    Factory->>Factory: Init VaultRegistry PDA
    Factory->>Factory: Init/update OwnerIndex PDA
    Factory-->>User: Vault created & registered
Loading

The Factory uses manual CPI (solana_program::program::invoke) instead of Anchor's cross-program feature because Anchor's features = ["cpi"] dependency between programs fails IDL generation.

Spending Limits Deep Dive

Four stacking limits protect against overspending. All amounts are in USDC (6 decimals).

Example scenario: Vault configured with per-tx $100, daily $500, session $1000, tier2 $200. Agent tries to spend $80 USDC.

flowchart TD
    TX["Agent: spend $80 USDC"] --> S1

    S1{"Per-tx limit<br/>$80 <= $100?"} -->|"No: exceeds"| R1["PerTxLimitExceeded"]
    S1 -->|"Yes: within limit"| S2

    S2{"Daily limit<br/>spent_today + $80 <= $500?"} -->|"No: exceeds"| R2["DailyLimitExceeded"]
    S2 -->|"Yes: within limit"| S3

    S3{"Session limit<br/>session_spent + $80 <= $1000?"} -->|"No: exceeds"| R3["SessionLimitExceeded"]
    S3 -->|"Yes: within limit"| S4

    S4{"Tier 2 threshold<br/>$80 > $200?"} -->|"Yes: needs co-sign"| R4["Tier2ThresholdExceeded"]
    S4 -->|"No: autonomous OK"| PASS["Execute CPI"]

    style TX fill:#4A9CFF,color:#fff
    style PASS fill:#6BCB77,color:#fff
    style R1 fill:#FF4444,color:#fff
    style R2 fill:#FF4444,color:#fff
    style R3 fill:#FF4444,color:#fff
    style R4 fill:#FF4444,color:#fff
Loading
Limit Config Field Scope Purpose
Per-transaction per_tx_limit_usdc Single CPI call Cap individual transaction size
Daily daily_limit_usdc UTC day (via SpendingTracker) Cap cumulative daily spending
Session spending_limit_usdc Session key lifetime Cap total spend per credential
Tier 2 threshold tier2_threshold_usdc Single CPI call Force owner co-sign above amount

Set any limit to 0 to disable it.

Transfer Amount Verification

When the target program is SPL Token Program and the token mint is the vault's configured USDC mint, the engine parses the instruction data to verify that the actual transfer amount matches the declared usdc_amount parameter. This prevents an agent from underreporting spending to bypass limits.

  • Covers: Transfer (discriminator 3) and TransferChecked (discriminator 12)
  • Skips: Non-Token-Program targets, non-USDC token mints, and non-transfer instructions
  • Limitation: Only verifies direct SPL Token Program calls. Wrapped transfers via DEX aggregators or other programs are not parsed (they are controlled by greenlist membership instead)

Tier 1 vs Tier 2

flowchart LR
    subgraph Tier1["<b>Tier 1: Autonomous</b>"]
        direction TB
        A1["Agent signs alone"] --> A2["Full 10-step policy check"]
        A2 --> A3["Whitelist enforced"]
        A3 --> A4["Tier 2 threshold enforced"]
    end

    subgraph Tier2["<b>Tier 2: Co-signed</b>"]
        direction TB
        B1["Agent + Owner sign"] --> B2["Policy check with exceptions"]
        B2 --> B3["Whitelist SKIPPED"]
        B3 --> B4["Tier 2 threshold SKIPPED"]
    end

    style Tier1 fill:#E8F5E9
    style Tier2 fill:#FFF3E0
Loading

Co-signing allows the owner to explicitly approve operations that would normally be blocked (large transfers, new recipients, etc.), while still enforcing blacklist, greenlist, operating hours, and cooldown.

Mobile

Set the limits. Watch them hold. Manage vaults, policies, and session keys from your phone.

Feature Description
Dashboard SOL and USDC at a glance. Daily spending bar shows where you stand against your limit.
Policies Recipients, blacklist, greenlist, time windows, spending caps, security — all configurable from one screen.
Session Keys Time-bound agent authorization from one hour to one year. Monitor or revoke instantly.
Emergency Vault locks immediately. No confirmation chain, no waiting period. You stay in control.

Download the Android APK from Releases.


Development

Prerequisites

Tool Version Install
Solana CLI Agave 2.2.20 agave-install init 2.2.20
Anchor CLI 0.32.1 avm install 0.32.1 && avm use 0.32.1
Rust 1.89.0 rustup default 1.89.0
Node.js 18+ -

Build

# Ensure toolchain is in PATH
export PATH="$HOME/.local/share/solana/install/releases/2.2.20/solana-release/bin:$HOME/.avm/bin:$PATH"

# Build both programs with IDL generation
anchor build

# Sync generated IDL/types to SDK
./scripts/sync-idl.sh

# Build SDK
cd sdk && npm run build

Test

# macOS: prevent ._ metadata files from corrupting genesis
COPYFILE_DISABLE=1 anchor test

# Skip rebuild if already built
COPYFILE_DISABLE=1 anchor test --skip-build

Known Build Issues

  • blake3 pin: After cargo update, run cargo update -p blake3 --precise 1.7.0 (blake3 1.8+ needs edition2024, incompatible with platform-tools Cargo 1.84.0)
  • cfg warnings: custom-heap, custom-panic, anchor-debug warnings from Anchor macros are harmless

Security Model

What this system protects against:

  • Agent spending more than allowed (per-tx, daily, session limits)
  • Agent interacting with unauthorized programs or tokens (greenlist)
  • Agent sending to unauthorized recipients (whitelist)
  • Agent accessing explicitly banned addresses (blacklist overrides all)
  • Compromised session keys (time-bound, individually revocable, bulk revocable via nonce)
  • Agent underreporting USDC transfer amounts to bypass spending limits (transfer amount verification)

Owner escape hatches:

  • freeze_vault, instantly blocks all agent operations
  • revoke_all_sessions, atomic invalidation of every session key
  • withdraw_sol / withdraw_spl, owner can always pull funds

Error Codes

Code Name Description
6000 VaultFrozen Vault is frozen, all agent operations blocked
6001 SessionKeyRevoked Session key has been revoked
6002 SessionKeyNonceMismatch Session key nonce doesn't match vault nonce
6003 SessionKeyAgentMismatch Signer doesn't match the agent on the key
6004 SessionKeyNotYetValid Session key not yet valid (future valid_after)
6005 SessionKeyExpired Session key has expired
6006 OutsideOperatingHours Operation outside allowed hours
6007 CooldownActive Vault in cooldown after large transaction
6008 AddressBlacklisted Address is blacklisted
6009 ProgramNotGreenlisted Target program not on greenlist
6010 TokenNotGreenlisted Token mint not on greenlist
6011 RecipientNotWhitelisted Recipient not on whitelist
6012 RecipientRequiresCosign Non-whitelisted recipient needs co-sign
6013 PerTxLimitExceeded USDC exceeds per-transaction limit
6014 DailyLimitExceeded USDC would exceed daily limit
6015 SessionLimitExceeded USDC would exceed session key limit
6016 Tier2ThresholdExceeded USDC exceeds tier 2, co-sign required
6017 ArithmeticOverflow Overflow in spending calculation
6018 InvalidDayEpoch day_epoch doesn't match current UTC day
6019 TrackerDayMismatch Tracker day doesn't match current day
6020 TrackerNotExpired Tracker hasn't expired yet
6021 InvalidSessionKeyWindow valid_until must be after valid_after
6022 InvalidSessionKeySpendingLimit Spending limit must be > 0
6023 InvalidOperatingHoursStart Start must be < 86400
6024 InvalidOperatingHoursEnd End must be < 86400
6025 UsdcAmountMismatch Declared USDC amount doesn't match Token Program transfer amount

License

MIT

About

Agentic policy engine on Solana. On-chain guardrails for AI-operated wallets with session keys, spending limits, and more.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors