Delegated Spend is an end-to-end Solana delegated-spending system for the Swig technical challenge. It includes a Pinocchio program, shared state layouts, SDK/client helpers, a REST API, a CLI, a LaserStream/Kafka/Postgres indexer, and database models for indexed wallet/payment state.
The implementation is designed around one boundary: security-critical authority and policy enforcement live on-chain, while product labels, querying, demos, indexing, and reconciliation live off-chain.
flowchart LR
subgraph OnChain["On-chain Solana runtime"]
Program["Pinocchio program<br/>authority + policy enforcement"]
Accounts["PDAs + SPL token accounts<br/>spending / receipts / escrow"]
Program --> Accounts
end
subgraph Gateway["Chain access gateway"]
Rpc["Solana RPC<br/>submit + account reads"]
Laser["Helius LaserStream<br/>confirmed tx/account stream"]
end
subgraph OffChain["Off-chain app and operator environment"]
CLI["delegated-spend CLI"]
API["Rust API<br/>transaction builder + indexed reads"]
SDK["Rust SDK<br/>instruction helpers + decoders"]
Producer["indexer-producer"]
Kafka["Redpanda/Kafka"]
Consumer["indexer-consumer"]
Models["models crate<br/>Diesel DB boundary"]
Db["Postgres"]
end
State["state crate<br/>shared account/event ABI"]
CLI --> API
API --> SDK
SDK --> State
Program --> State
API -->|"submit signed transactions"| Rpc
Rpc --> Program
Program -->|"events + account writes"| Laser
Laser --> Producer
Producer --> Kafka
Kafka --> Consumer
Consumer --> Models
Models --> Db
API --> Models
API --> Db
The API runs embedded Diesel migrations when it starts. The indexer writes through models::ChainIngestBatch::upsert_atomic, so raw transactions, events, decoded account writes, and checkpoint state commit together.
programs/delegated-spend/ Pinocchio on-chain program
crates/state/ fixed account layouts, policy actions, events, PDA helpers
crates/sdk/ instruction construction and account/event decoding helpers
crates/solana-adapter/ RPC/client traits and Solana adapter layer
crates/serde-utils/ serde helpers for public API DTOs
crates/cli/ developer CLI over the REST API
models/ Diesel models and all database access
services/api/ REST API, auth, migrations, tx builders
services/indexer/ LaserStream producer, parser core, Kafka consumer
migrations/ Postgres schema migrations embedded by the API
scripts/ local setup and API-key helpers
Protocol-specific docs:
- programs/delegated-spend/README.md
- crates/state/README.md
- crates/sdk/README.md
- crates/solana-adapter/README.md
- models/README.md
- services/indexer/README.md
- crates/cli/README.md
- docs/cli-demo-flows.md
| Assignment area | Implementation |
|---|---|
| Part 1: Solana program | CreateSpendingAccount, FundSubaccount, delegate add/update/revoke, spend execution, escrow preauthorize/release, kill switch, replay receipts, and structured events. |
| Part 2: event indexer | Helius LaserStream producer, Kafka handoff, restart checkpoint, idempotent Postgres upserts, decoded event/account writes. |
| Part 3: developer interface | Rust SDK, REST API, and CLI for create/fund/delegate/policy/spend/escrow/kill/read flows. |
| Part 4: ledger/reconciliation | Indexed payment, escrow, spending-account, event, raw transaction, and checkpoint tables with documented reconciliation path against on-chain account reads. |
Required demo flows are covered by programs/delegated-spend/tests/assignment_flow_test.rs and can also be driven through the API and CLI once the local stack is running. The reviewer-oriented CLI walkthrough lives in docs/cli-demo-flows.md.
The developer surface is intentionally layered:
- The SDK owns instruction construction, PDA helpers, and account/event decoding for Rust callers.
- The API exposes transaction-build endpoints for writes and indexed read endpoints for spending accounts, delegates, policies, payments, events, and health.
- The CLI is a thin authenticated client over the API. It keeps signing local with
--sign-and-submit, prints tables by default, and preserves exact API responses with--json.
The API list endpoints clamp limit to 1..=100; events also support an id cursor through ?cursor=<event_id>. GET /health and GET /v1/health return API status, deployed program ID, and indexer checkpoints with last processed slot/signature.
The ledger model is event-derived rather than hand-written by API routes:
indexed_eventsis append-only provenance keyed by transaction signature, instruction position, and event ordinal.intent_receiptis the executed payment table: delegate, recipient token account, mint, raw amount, intent hash, slot, signature, and source event.escrow_reservationtracks third-party escrow reservation and release state with reserved/released raw amounts and provenance.spending_accountstores the latest decoded account projection, including role/policy JSON and observed slot/signature.indexer_checkpointrecords the ingestion backend cursor used for restart, replay, and health reporting.
Reconciliation is currently an operator process: compare spending_account, intent_receipt, and escrow_reservation rows against fresh RPC account reads for the same PDAs, verify the decoded bytes match indexed JSON state, and replay a slot range with scripts/backfill-indexer.sh if a gap is found. A standalone reconciliation command is the next production step; the model boundary is already in place so it can report drift without bypassing Diesel-owned database access.
Run the preflight. On first run it creates .env from .sample.env if needed:
bash scripts/setup-repo.shReview local env:
$EDITOR .envStart local services:
tilt upTilt starts Postgres, Redpanda, the indexer producer, the indexer consumer, and the API. The API listens on http://localhost:5001 and exposes GET /health.
Create a local API key after Postgres and the API are healthy:
scripts/create-api-key.sh --name local-cli
export DELEGATED_SPEND_API_KEY="<printed key>"Smoke checks:
curl http://localhost:5001/health
cargo run -p delegated-spend-cli -- --help
cargo run -p delegated-spend-cli -- --api-key "$DELEGATED_SPEND_API_KEY" spending-accounts listAPI docs are served by the API process:
Swagger UI: http://localhost:5001/swagger-ui/
OpenAPI JSON: http://localhost:5001/api-docs/openapi.json
The docs routes are public for local review. API operations inside Swagger still require x-api-key authorization and the x-network-env header.
.sample.env is safe to commit and contains every expected variable. .env is private and may contain real Helius keys, API keys, and local keypair paths.
Required for local API/indexer:
DATABASE_URL
KAFKA_BROKERS
KAFKA_TOPIC
KAFKA_GROUP_ID
SWIG_DELEGATED_SPEND_PROGRAM_ID
Required for live LaserStream indexing:
HELIUS_LASERSTREAM_URL
HELIUS_LASERSTREAM_API_KEY
HELIUS_RPC_URL or DEVNET_RPC_URL
LASERSTREAM_START_SLOT (optional manual replay)
LASERSTREAM_STOP_SLOT (optional finite replay bound)
Useful for CLI demos:
DELEGATED_SPEND_API_URL
DELEGATED_SPEND_API_KEY
DELEGATED_SPEND_NETWORK
DELEGATE_KEYPAIR
DELEGATE
DEVNET_USDC_MINT
Do not commit real DELEGATED_SPEND_API_KEY, Helius keys, or absolute private keypair paths.
flowchart TD
Parent["Parent authority"]
Spending["SpendingAccount PDA"]
Manager["Root manager role"]
Delegate["Delegate role"]
Policy["Policy actions"]
Vault["Vault authority PDA"]
Receipt["IntentReceipt PDA"]
Escrow["EscrowReservation PDA"]
Parent --> Manager
Manager --> Spending
Spending --> Delegate
Delegate --> Policy
Spending --> Vault
Delegate --> Receipt
Delegate --> Escrow
The program supports SPL token delegated spend. SOL support is intentionally out of scope. Agent, card issuer, and escrow-service roles are represented as delegate authorities plus policy actions; product-specific labels live off-chain.
Policy controls include supported mint, one-shot token cap, recurring slot-window cap, destination token account cap, inclusive slot range, and a mock normalized USD oracle limit. Daily caps are modeled as slot-window recurring limits for the challenge demo.
sequenceDiagram
participant Program
participant Laser as LaserStream
participant Producer
participant Kafka
participant Consumer
participant DB as Postgres
participant API
Program-->>Laser: confirmed transaction and account updates
Laser-->>Producer: filtered program stream
Producer-->>Kafka: ChainIngestBatch JSON keyed by signature
Consumer-->>DB: atomic upsert of tx, events, accounts, checkpoint
API-->>DB: indexed reads for CLI and integrations
The database schema stores append-only event provenance plus decoded current account/payment state. See models/README.md for the ER diagram and table ownership rules.
Manual backfill uses the same producer, Kafka topic, consumer, and idempotent Postgres writes as live ingestion:
scripts/backfill-indexer.sh <start-slot> [stop-slot]Omit stop-slot to replay from that slot and then remain live. Include it for a
bounded catch-up job that exits after flushing the range.
Build and run program integration coverage:
cargo build-sbf --manifest-path programs/delegated-spend/Cargo.toml --arch v1
cargo test -p swig-delegated-spend --features program-testsCLI flow shape:
cargo run -p delegated-spend-cli -- spending-accounts create --sign-and-submit
cargo run -p delegated-spend-cli -- funding create <SPENDING_ACCOUNT> --source-token-account <SOURCE_ATA> --vault-token-account <VAULT_ATA> --mint <MINT> --amount 1000000 --sign-and-submit
cargo run -p delegated-spend-cli -- delegates add <SPENDING_ACCOUNT> --delegate-authority <DELEGATE> --mint <MINT> --token-limit 1000000 --sign-and-submit
cargo run -p delegated-spend-cli -- spends execute <SPENDING_ACCOUNT> --role-id <ROLE_ID> --mint <MINT> --vault-token-account <VAULT_ATA> --recipient-token-account <RECIPIENT_ATA> --amount 1000 --intent-hash-hex <HEX_32_BYTES> --keypair "$DELEGATE_KEYPAIR" --sign-and-submit
cargo run -p delegated-spend-cli -- events list --limit 20Use docs/cli-demo-flows.md for assignment-mapped agentic, card issuer, first-party escrow, and third-party escrow walkthroughs. Use crates/cli/README.md for the complete CLI reference and expected output.
- All write instructions check required signers.
- Program-owned accounts verify discriminator, owner, and PDA derivation.
- Token movement uses typed Pinocchio token instructions and validates token account mint/owner facts.
- Replay protection is account-based through deterministic receipt PDAs.
- Kill switch blocks delegated spend and escrow release.
- Policy decrements and token movements happen inside one Solana transaction.
- Indexer writes are idempotent through database uniqueness and upserts.
Known limitations:
- USD oracle support uses a mock normalized account layout rather than direct Pyth/Switchboard parsing.
- No provider redundancy is included in the proof of concept.
- Reconciliation is documented and model-ready, but not yet exposed as a standalone command.
- Health reports indexer checkpoints; richer lag alerting and Prometheus metrics are production follow-ups.
Core checks:
cargo fmt --all -- --check
cargo check --workspace --all-targets
cargo test -p state
cargo test -p swig-delegated-spend --lib
cargo test -p indexer-core
cargo test -p indexer-producer
cargo test -p indexer-consumer
cargo test -p api
cargo test -p delegated-spend-cli
cargo run -p delegated-spend-cli -- --helpProgram integration checks:
cargo build-sbf --manifest-path programs/delegated-spend/Cargo.toml --arch v1
cargo test -p swig-delegated-spend --features program-tests