Soroban smart contracts for the AgentPay protocol: escrow, usage recording, and payment settlement on Stellar.
- escrow — Records usage and supports settlement logic for machine-to-machine payments.
A service's ServiceMetadata carries a description and an owner. The
current owner (or the admin) can reassign the owner via
transfer_service_ownership(caller, service_id, new_owner) without touching the
description. The call honours the pause gate and emits owner_chg for
indexers.
A service's metadata (description + owner) and its registration flag live in
independent storage slots. clear_service_metadata (admin-gated, idempotent)
removes only the metadata; the registration flag and per-(agent, service) usage
history are untouched.
propose_admin_transfer rejects proposing the current admin as the new admin
(panics with InvalidAdminProposal). This surfaces no-op handovers as caller
mistakes rather than silently storing a pending entry equal to the active admin.
record_usage supports an optional per-agent rate limit anchored to
env.ledger().timestamp(). It is configured by two admin settings and is
disabled by default (both default to 0):
set_max_requests_per_window(max)— maxrequestsan agent may accumulate per window (get_max_requests_per_window).set_rate_window_seconds(seconds)— the fixed window length (get_rate_window_seconds).
The limiter is active only when both are non-zero. Semantics are a
fixed window (not sliding): the window opens at an agent's first in-window
call and rolls forward as a whole once now >= window_start + window_seconds,
resetting the count. A call that would push the in-window count above the cap
is rejected with RateLimitExceeded (#15). State is per-agent
(DataKey::RateWindow(agent)), and an agent can never reset its own window
early — window_start only advances. Window arithmetic is saturating.
init stamps the current storage schema version (v2) directly, so a freshly
deployed contract reports get_schema_version() == 2 without ever running a
migration. A legacy contract deployed before this change carries the implicit v1
default and must call migrate_v1_to_v2() to reach v2; calling that migration on
a fresh v2 deploy panics with MigrationVersionMismatch.
- Rust (stable, with
rustfmt) - Stellar Soroban CLI (optional, for deployment)
-
Clone the repo (or add remote and pull):
git clone <repo-url> && cd agentpay-contracts
-
Install Rust (if needed):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup component add rustfmt
-
Verify setup:
cargo fmt --all -- --check cargo build cargo test
agentpay-contracts/
├── Cargo.toml # Workspace root
├── contracts/
│ └── escrow/
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # Contract logic
│ └── test.rs # Unit tests
└── .github/workflows/
└── ci.yml # CI: fmt, build, test
| Command | Description |
|---|---|
cargo fmt --all |
Format code |
cargo fmt --all -- --check |
Check formatting (CI) |
cargo build |
Build |
cargo test |
Run tests |
- Escrow: Build, Test, and Deploy Guide — build the release WASM, run the test suite, and deploy to testnet with the Stellar/Soroban CLI.
- Escrow: Schema Versioning & Migration — the difference between
version()andSchemaVersion, the double-run guard, and the migration runbook.
On push/PR to main, GitHub Actions runs:
- Format check (
cargo fmt --all -- --check) - Build (
cargo build) - Tests (
cargo test)
See CONTRIBUTING.md for the full guide, including the append-only error-code table, event conventions, and the test/coverage gate.
- Fork the repo and create a branch.
- Make changes; ensure
cargo fmt,cargo build, andcargo testpass locally. - Open a pull request. CI must pass before merge.
MIT