loop-agent-sdk 0.1.0

Trustless agent SDK for Loop Protocol — intent-based execution on Solana.
Documentation
# Loop Agent SDK

[![crates.io](https://img.shields.io/crates/v/loop-agent-sdk.svg)](https://crates.io/crates/loop-agent-sdk)
[![docs.rs](https://docs.rs/loop-agent-sdk/badge.svg)](https://docs.rs/loop-agent-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Trustless agent infrastructure for the Loop Protocol. Build AI agents that capture value, stake Cred, and optimize yield — without ever touching user funds.

Part of the [loop-protocol](https://github.com/OAR-Technologies-Inc/loop-protocol) monorepo. For the TypeScript SDK, see [`sdk/`](../sdk/).

## Installation

```toml
[dependencies]
loop-agent-sdk = "0.1"
```

MSRV: Rust 1.81.

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│                    LOOP AGENT SDK                       │
├─────────────────┬─────────────────┬─────────────────────┤
│     STATE       │     ACTION      │     PERCEPTION      │
│                 │                 │                     │
│ context_fetch   │ capture_value   │ on_transaction      │
│ context_push    │ stake_cred      │ on_location_hit     │
│ agent_memory    │ claim_yield     │ on_proof_submitted  │
│                 │ optimize_yield  │ on_position_unlock  │
└─────────────────┴─────────────────┴─────────────────────┘
```

## Security Model

**Intent-Based Execution**: Agents don't move funds. They request actions, and smart contracts execute with enforced rules.

- **Session Keys**: Scoped permissions (read/capture/stake) with TTL
- **Trustless Distribution**: 80/14/6 split enforced by contract
- **No Key Access**: Agents never hold private keys

```rust
// Agent requests capture, contract enforces rules
vault.capture_value(&session, merchant_id, proof)?;
// Contract automatically splits: 80% user, 14% treasury, 6% stakers
```

## MCP Compliance

This SDK is [Model Context Protocol](https://modelcontextprotocol.io) compliant. Any MCP-compatible AI can interact with Loop vaults:

```json
// From mcp/loop-vault-manifest.json
{
  "tools": [
    { "name": "capture_value", "description": "Submit purchase proof..." },
    { "name": "stake_cred", "description": "Stake for yield..." },
    { "name": "get_vault_status", "description": "Check balances..." }
  ]
}
```

A user can tell Claude or Gemini: *"How's my Loop Vault doing?"* and the AI knows exactly how to fetch the data.

## Quick Start

```rust
use loop_agent_sdk::{
    action::{VaultAction, SessionKey, ZkProof},
    perception::{PerceptionEvent, TransactionEvent},
    state::{StateStore, UserContext},
};

async fn handle_pos_transaction(tx: TransactionEvent) -> Result<(), Error> {
    // 1. Load user context (<100ms target)
    let ctx = state_store.context_fetch(&find_user_by_card(&tx.card_fingerprint)?)?;
    
    // 2. Get scoped session key
    let session = get_session_key(&ctx.pubkey, &[PermissionScope::Capture]).await?;
    
    // 3. Build proof from POS data
    let proof = ZkProof {
        proof_type: ProofType::SquarePos,
        data: tx.raw_payload.to_vec(),
        timestamp: tx.occurred_at,
        amount_cents: tx.amount_cents,
        card_fingerprint: Some(tx.card_fingerprint),
    };
    
    // 4. Capture value (contract enforces 80/14/6 split)
    let result = vault.capture_value(&session, tx.merchant_id.parse()?, proof)?;
    
    println!("Minted {} Cred, {} to user", 
        result.cred_minted, 
        result.user_amount
    );
    
    Ok(())
}
```

## Feature Flags

The SDK is split into composable feature flags so a deployment only pulls in what it needs. The `default` feature enables the full core (`action` + `perception` + `state`); opt into the runtime-specific features on top.

| feature | enables | pulls in |
| --- | --- | --- |
| `default` = `full` | core SDK: action, perception, state | (pure-Rust) |
| `action` | `vault.capture_value`, `stake_cred`, yield actions ||
| `perception` | transaction / location / proof event handling ||
| `state` | context fetch/push, agent memory ||
| `lambda` | AWS Lambda runtime integration | `lambda_runtime`, `tokio` |
| `dynamodb` | DynamoDB state backend | `aws-sdk-dynamodb`, `aws-config`, `tokio` |
| `privacy` | HMAC fingerprinting + zeroize for sensitive data | `hmac`, `zeroize`, `rand`, `aws-sdk-secretsmanager` |
| `webhook` | Fidel / Square / Stripe webhook handlers (implies `privacy`) | `hmac` |
| `notifications` | Supabase push notifications (implies `dynamodb`) | `reqwest` |
| `supabase` | reputation attestation integration | `reqwest`, `tokio` |
| `agent` | the full agent stack: `lambda` + `dynamodb` + `privacy` + `webhook` + `notifications` + `supabase` | all of the above |
| `cli` | `loop-cli` binary | `clap` |

The `loop-agent` binary requires the `agent` feature; the `loop-cli` binary requires the `cli` feature. The `lambda_handler` example requires `lambda + dynamodb`.

`docs.rs` builds with `all-features = true`, so the published documentation reflects every surface.

## AWS Lambda Deployment

```toml
[dependencies]
loop-agent-sdk = { version = "0.1", features = ["lambda", "dynamodb"] }
```

```rust
use lambda_runtime::{service_fn, LambdaEvent, Error};
use loop_agent_sdk::perception::PerceptionEvent;

#[tokio::main]
async fn main() -> Result<(), Error> {
    lambda_runtime::run(service_fn(handler)).await
}

async fn handler(event: LambdaEvent<PerceptionEvent>) -> Result<(), Error> {
    let (event, _context) = event.into_parts();
    
    match event {
        PerceptionEvent::TransactionDetected(tx) => handle_transaction(tx).await,
        PerceptionEvent::LocationHit(loc) => handle_location(loc).await,
        PerceptionEvent::PositionUnlocking(unlock) => handle_unlock(unlock).await,
        _ => Ok(()),
    }
}
```

## EventBridge Integration

The SDK generates EventBridge rules for you:

```rust
use loop_agent_sdk::perception::generate_eventbridge_rules;

let rules = generate_eventbridge_rules("arn:aws:lambda:us-east-1:123456:function:loop-agent");
// Deploy rules to EventBridge
```

Events trigger your Lambda:
- `loop.pos/TransactionDetected` → POS webhook
- `loop.mobile/LocationHit` → User enters geofence
- `loop.staking/PositionUnlocking` → 24h before unlock

## Distribution Policy

Every capture follows this split (enforced on-chain):

| Recipient | Share | Purpose |
|-----------|-------|---------|
| User | 80% | Their reward vault |
| Treasury | 14% | Protocol sustainability |
| Stakers | 6% | Yield for staked positions |

## Yield Rates

| Duration | APY |
|----------|-----|
| 30 days | 8% |
| 90 days | 12% |
| 180 days | 16% |
| 365 days | 20% |

## DynamoDB State Store

The SDK uses DynamoDB for high-speed state persistence (<100ms context reload, <5ms fingerprint lookup).

### Table Schema

```
Table: loop-agent-state

Primary Key: pk (String), sk (String)
GSI: CardFingerprintIndex (fingerprint → user_pubkey)
TTL: Enabled on 'ttl' attribute

Item Types:
  USER#{pubkey} | CONTEXT       → User preferences, vault cache
  USER#{pubkey} | SESSION       → Active session key (auto-expires)
  USER#{pubkey} | PENDING#{ts}  → Pending captures (ring buffer, max 10)
  USER#{pubkey} | LOCATION      → Current merchant (geofence)
  CARD#{fp}     | META          → Card fingerprint → user mapping
  TXN#{id}      | PROCESSED     → Transaction dedup (30 day TTL)
```

### Setup

```bash
# Create table with GSI and TTL
./scripts/create-dynamodb-table.sh

# Or use Rust
use loop_agent_sdk::dynamo::{create_table, DynamoConfig};
create_table(&client, &DynamoConfig::default()).await?;
```

### Environment Variables

```bash
DYNAMO_TABLE=loop-agent-state      # Table name
DYNAMO_FINGERPRINT_GSI=CardFingerprintIndex  # GSI name
MAX_PENDING_CAPTURES=10            # Ring buffer limit
SESSION_TTL_SECONDS=86400          # 24 hours
PENDING_TTL_SECONDS=1814400        # 21 days
DAX_ENDPOINT=                      # Optional DAX cluster
```

### Usage

```rust
use loop_agent_sdk::{DynamoStateStore, PendingCapture};

// Initialize
let store = DynamoStateStore::new().await?;

// Card fingerprint lookup (<5ms)
let user = store.lookup_user_by_card("fp_abc123").await?;

// User context (<100ms with consistent reads)
let ctx = store.context_fetch_async(&user_pubkey).await?;

// Mark transaction processed (dedup)
store.mark_transaction_processed(&txn_id, &user_pubkey, amount).await?;

// Session key with TTL
store.store_session_key(&user_pubkey, &session_data).await?;

// Pending captures (ring buffer)
store.add_pending_capture(&user_pubkey, &capture).await?;
```

## License

MIT - OAR Technologies Inc