AgentKit payment method for the Machine Payments Protocol (MPP).
method="agentkit" lets servers accept proof-of-humanness as payment. Agents sign a SIWE message, the server checks the AgentBook contract on-chain, and if the agent maps to a World ID-verified human, access is granted. No stablecoins, no gas — identity is the currency.
cargo add mpp-agentkitOr in Cargo.toml:
# Server + client (default)
mpp-agentkit = "0.1"
# Server only
mpp-agentkit = { version = "0.1", default-features = false, features = ["server"] }
# Client only
mpp-agentkit = { version = "0.1", default-features = false, features = ["client"] }Agent Server AgentBook (on-chain)
| | |
|-- GET /api/resource ------------>| |
| | No credential |
|<-- 402 + WWW-Authenticate ------| |
| method="agentkit" | |
| request={ domain, nonce, ... }| |
| | |
| [Build SIWE message] | |
| [Sign with wallet key] | |
| | |
|-- GET /api/resource ------------>| |
| Authorization: Payment { | |
| payload: { message, sig } | |
| } | |
| | 1. Verify SIWE signature |
| | 2. lookupHuman(address) ----->|
| | 3. <-- humanId ---------------|
| | 4. AccessPolicy::evaluate() |
| | 5. Return receipt |
| | |
|<-- 200 + Payment-Receipt -------| |
| { method: "agentkit", | |
| reference: "<human_id>" } | |
The library handles steps 1-3 (SIWE + AgentBook). Step 4 is yours via the AccessPolicy trait.
use mpp::server::Mpp;
use mpp_agentkit::{AgentKitConfig, AgentKitMethod, AllowAll};
// Any registered agent gets access, no limits.
let method = AgentKitMethod::new(AgentKitConfig::default_mainnet(), AllowAll);
let mpp = Mpp::new(method, "api.example.com", "my-secret");
// In your handler:
// - No credential → mpp generates a 402 challenge
// - Valid credential → mpp calls AgentKitMethod::verify() → receiptuse mpp_agentkit::{AgentKitConfig, AgentKitMethod, FreeTrial, InMemoryUsageTracker};
// 10 uses per human, tracked in memory.
let policy = FreeTrial::new(InMemoryUsageTracker::new(), 10);
let method = AgentKitMethod::new(AgentKitConfig::default_mainnet(), policy);Implement AccessPolicy to define your own logic:
use mpp_agentkit::{AccessPolicy, VerifiedAgent, PolicyDecision};
#[derive(Clone)]
struct PremiumOnly {
db: MyDatabase,
}
impl AccessPolicy for PremiumOnly {
async fn evaluate(&self, agent: &VerifiedAgent) -> PolicyDecision {
if self.db.is_premium(&agent.human_id_hex).await {
PolicyDecision::Allow
} else {
PolicyDecision::Deny("Premium subscription required".into())
}
}
}
let method = AgentKitMethod::new(config, PremiumOnly { db });The VerifiedAgent you receive is guaranteed to have:
address— recovered from a valid SIWE signaturehuman_id— non-zero value fromAgentBook.lookupHuman()chain_id— the chain used for the lookup
You decide what to do with it.
For local development without on-chain access:
let method = AgentKitMethod::new(config, AllowAll)
.with_mock_mode(true);In mock mode, AgentBook.lookupHuman() is skipped and the human ID is derived from keccak256(address). SIWE signature verification still runs.
use mpp::client::Fetch;
use mpp_agentkit::AgentKitProvider;
use alloy::signers::local::PrivateKeySigner;
let signer = PrivateKeySigner::random();
let provider = AgentKitProvider::new(signer, 480); // 480 = World Chain
// Automatic 402 handling:
let response = reqwest::Client::new()
.get("https://api.example.com/resource")
.send_with_payment(&provider)
.await?;
// The provider automatically:
// 1. Detects the 402 + method="agentkit" challenge
// 2. Extracts AgentKitRequest from ChargeRequest.method_details
// 3. Builds a SIWE message with the challenge nonce/domain
// 4. Signs with the agent's wallet
// 5. Retries the request with the credentialuse mpp::client::MultiProvider;
let provider = MultiProvider::new()
.with(AgentKitProvider::new(signer.clone(), 480))
.with(TempoProvider::new(signer, rpc_url)?);
// Prefers agentkit (free) when available, falls back to tempo (paid).mpp-agentkit/
src/
lib.rs Re-exports, feature gates
types.rs AgentKitRequest, AgentKitPayload, AgentKitConfig
contracts.rs IAgentBook sol! bindings
server/
method.rs AgentKitMethod<P: AccessPolicy> → ChargeMethod
verify.rs SIWE verification + AgentBook lookup
policies/
allow_all.rs AllowAll policy
free_trial.rs FreeTrial<U: UsageTracker> policy
usage.rs UsageTracker trait + InMemoryUsageTracker
client/
provider.rs AgentKitProvider<S: Signer> → PaymentProvider
| Layer | Owns |
|---|---|
mpp crate |
402 formatting, HMAC challenge binding, credential parsing, receipt formatting |
mpp-agentkit |
SIWE verification, AgentBook lookup, AccessPolicy dispatch |
| Your server | Route definitions, challenge construction, policy implementation |
pub struct AgentKitRequest {
pub domain: String, // Domain for SIWE message binding
pub chain_id: u64, // EIP-155 chain ID for AgentBook lookup
pub nonce: String, // Server-generated nonce (min 8 chars)
pub max_uses: Option<u64>, // Informational: per-human quota
}Nested in ChargeRequest.method_details in the 402 challenge.
pub struct AgentKitPayload {
pub message: String, // Full SIWE message (EIP-4361)
pub signature: String, // Hex-encoded 65-byte EIP-191 signature
}pub struct VerifiedAgent {
pub address: Address, // Recovered from SIWE
pub human_id: FixedBytes<32>, // From AgentBook (nullifier hash)
pub human_id_hex: String, // Hex-encoded for storage keys
pub chain_id: u64, // Chain used for lookup
}pub trait AccessPolicy: Clone + Send + Sync + 'static {
fn evaluate(&self, agent: &VerifiedAgent) -> impl Future<Output = PolicyDecision> + Send;
fn on_access(&self, _agent: &VerifiedAgent) -> impl Future<Output = ()> + Send { async {} }
}
pub enum PolicyDecision {
Allow,
Deny(String),
}| Chain | Address |
|---|---|
| World Chain (480) | 0xA23aB2712eA7BBa896930544C7d6636a96b944dA |
| Base mainnet (8453) | 0xE1D1D3526A6FAa37eb36bD10B933C1b77f4561a4 |
| Base Sepolia (84532) | 0xA23aB2712eA7BBa896930544C7d6636a96b944dA |
| Feature | Default | Description |
|---|---|---|
server |
yes | Server-side verification. Pulls in siwe for EIP-191 recovery. |
client |
yes | Client-side SIWE signing. Only needs alloy signers. |
MIT OR Apache-2.0