Tenzro Testnet is live —request testnet TNZO
← Back to Tutorials
AI AgentsAdvanced

Build an AI Agent That Pays for Services

Build an autonomous AI agent from scratch with its own identity, wallet, and payment capabilities. This tutorial covers the complete agent economy workflow: identity registration, wallet provisioning, service discovery via A2A protocol, and autonomous payments using MPP. By the end, you'll have an agent that can pay for AI inference per token generated.

What We're Building

We're building an AI agent that can:

This is the foundation of the agent economy: autonomous entities that can find services, negotiate prices, pay for consumption, and verify receipts—all without human intervention for each transaction.

Prerequisites

  • Basic understanding of JSON-RPC APIs
  • Familiarity with public/private key cryptography
  • curl or equivalent HTTP client
  • Access to Tenzro testnet (public endpoints, no auth required)

Estimated time: 45 minutes

Architecture Overview

Here's the complete flow we'll implement:

Step 1: Create the Agent's Identity

Every AI agent on Tenzro needs a decentralized identity (DID) following the Tenzro Decentralized Identity Protocol (TDIP). Agents cannot self-register—they must be created by a human guardian who sets spending limits and permissions via a DelegationScope.

1a. Register the Human Guardian

First, the human user (you) needs a guardian identity:

curl -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_registerIdentity",
    "params": [{
      "identity_type": "human",
      "display_name": "Alice (Agent Guardian)",
      "key_type": "ed25519"
    }],
    "id": 1
  }'

# Response:
{
  "jsonrpc": "2.0",
  "result": {
    "did": "did:tenzro:human:550e8400-e29b-41d4-a716-446655440001",
    "status": "active",
    "private_key": "ed25519:5J7X9Y2Z... (keep this secret!)",
    "public_key": "ed25519:B8C9D0E1...",
    "created_at": "2026-03-24T10:00:00Z"
  },
  "id": 1
}

Important: Save the private_key securely. You'll need it to sign operations on behalf of your guardian identity. In production, this would be stored in a hardware wallet or secure enclave—never in plaintext.

1b. Create the Machine Identity with Delegation Scope

Now create the agent's machine identity. The guardian sets a DelegationScope that constrains what the agent can do:

curl -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_registerIdentity",
    "params": [{
      "identity_type": "machine",
      "controller_did": "did:tenzro:human:550e8400-e29b-41d4-a716-446655440001",
      "display_name": "InferenceBot Alpha",
      "key_type": "secp256k1",
      "delegation_scope": {
        "max_transaction_value": "10.00",
        "max_daily_spend": "100.00",
        "allowed_operations": ["InferenceRequest", "Transfer"],
        "allowed_contracts": [],
        "allowed_payment_protocols": ["Mpp", "X402"],
        "allowed_chains": ["tenzro"]
      }
    }],
    "id": 2
  }'

# Response:
{
  "jsonrpc": "2.0",
  "result": {
    "did": "did:tenzro:machine:550e8400-e29b-41d4-a716-446655440001:a1b2c3d4",
    "status": "active",
    "controller_did": "did:tenzro:human:550e8400-e29b-41d4-a716-446655440001",
    "private_key": "secp256k1:K9L0M1N2... (agent's signing key)",
    "public_key": "secp256k1:O3P4Q5R6...",
    "delegation_scope": { ... },
    "created_at": "2026-03-24T10:05:00Z"
  },
  "id": 2
}

This DelegationScope means the agent can:

The agent's DID includes the guardian's DID as the controller: did:tenzro:machine:550e8400...440001:a1b2c3d4. Anyone can verify the trust chain by resolving the DIDs.

Step 2: Provision the Agent's Wallet

The agent needs a wallet to hold funds and sign payment transactions. Tenzro uses MPC (multi-party computation) threshold wallets: 2-of-3 key shares, meaning any 2 shares can sign but no single share is enough. This provides security even if one share is compromised.

2a. Create the MPC Wallet

curl -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_createWallet",
    "params": [{
      "owner_did": "did:tenzro:machine:550e8400-e29b-41d4-a716-446655440001:a1b2c3d4",
      "threshold": 2,
      "total_shares": 3,
      "key_type": "secp256k1"
    }],
    "id": 3
  }'

# Response:
{
  "jsonrpc": "2.0",
  "result": {
    "wallet_id": "wallet_9f8e7d6c5b4a",
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
    "public_key": "secp256k1:0x04a1b2c3...",
    "threshold": 2,
    "total_shares": 3,
    "owner_did": "did:tenzro:machine:550e8400-e29b-41d4-a716-446655440001:a1b2c3d4",
    "created_at": "2026-03-24T10:10:00Z"
  },
  "id": 3
}

The wallet's Ethereum-style address is 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4. The 3 MPC key shares are stored securely:

To sign a transaction, the agent combines share 1 (which it controls) with share 2 (obtained by proving its DID to the guardian service). This 2-of-3 threshold is sufficient to create a valid signature.

2b. Fund the Wallet via Faucet

For testnet, we'll use the faucet to give the agent 100 TNZO:

curl -X POST https://api.tenzro.network/faucet \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4"
  }'

# Response:
{
  "success": true,
  "amount": "100.0",
  "tx_hash": "0x1a2b3c4d5e6f7890abcdef1234567890abcdef12",
  "message": "Sent 100 TNZO to 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4"
}

2c. Verify Balance

curl -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_getBalance",
    "params": ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4"],
    "id": 4
  }'

# Response:
{
  "jsonrpc": "2.0",
  "result": "0x56bc75e2d63100000",  # 100 TNZO in hex wei
  "id": 4
}

# Convert hex to decimal:
# 0x56bc75e2d63100000 = 100000000000000000000 wei = 100 TNZO

The agent now has 100 TNZO and can make up to 10 transactions at the max $10 limit before needing to be refunded.

Step 3: Discover Services via A2A Protocol

The Agent-to-Agent (A2A) protocol is how AI agents discover each other's capabilities. Every agent publishes an Agent Card at /.well-known/agent.json describing its skills.

3a. Fetch the Tenzro Network Agent Card

curl https://a2a.tenzro.network/.well-known/agent.json | jq .

# Response:
{
  "agent_id": "tenzro-network-a2a-v1",
  "name": "Tenzro Network Agent",
  "description": "Primary A2A interface for Tenzro Network services",
  "version": "1.0.0",
  "capabilities": {
    "protocols": ["a2a-v1", "mcp-v1"],
    "skills": [
      {
        "id": "wallet",
        "name": "Wallet Operations",
        "description": "Create wallets, check balances, send transactions"
      },
      {
        "id": "identity",
        "name": "Identity Management",
        "description": "Register and resolve DIDs, manage credentials"
      },
      {
        "id": "inference",
        "name": "AI Inference",
        "description": "Submit inference requests to registered model providers"
      },
      {
        "id": "settlement",
        "name": "Payment Settlement",
        "description": "Process payments, escrow, and micropayment channels"
      },
      {
        "id": "verification",
        "name": "Cryptographic Verification",
        "description": "Verify ZK proofs, TEE attestations, signatures"
      }
    ]
  },
  "endpoints": {
    "rpc": "https://a2a.tenzro.network/a2a",
    "stream": "https://a2a.tenzro.network/a2a/stream"
  },
  "payment": {
    "methods": ["mpp", "x402"],
    "currencies": ["TNZO", "USDC"]
  }
}

The agent now knows that Tenzro Network offers inference services and supports MPP payments in TNZO. Perfect for our use case.

3b. Send an A2A Task

Let's send a task to request AI inference. A2A uses JSON-RPC 2.0:

curl -X POST https://a2a.tenzro.network/a2a \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tasks/send",
    "params": {
      "skill": "inference",
      "action": "request",
      "inputs": {
        "model": "gpt-4",
        "prompt": "Explain quantum computing in one sentence.",
        "max_tokens": 50
      },
      "requester_did": "did:tenzro:machine:550e8400-e29b-41d4-a716-446655440001:a1b2c3d4"
    },
    "id": 5
  }'

# Response:
{
  "jsonrpc": "2.0",
  "result": {
    "task_id": "task_k9j8h7g6f5",
    "status": "payment_required",
    "payment_challenge": {
      "protocol": "mpp",
      "amount": "2.50",
      "currency": "TNZO",
      "session_id": "mpp_sess_a1b2c3d4",
      "challenge_nonce": "0x9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c",
      "expires_at": "2026-03-24T10:25:00Z"
    }
  },
  "id": 5
}

The inference provider responds with a payment challenge. The agent must create an MPP credential to proceed.

Step 4: Pay for Inference Using MPP

Machine Payments Protocol (MPP) is session-based: open a session, accumulate charges as the service is consumed (per token for AI inference), then settle at the end. This is more efficient than paying per token on-chain.

4a. Create MPP Credential

The agent creates a credential proving it can pay:

# Agent's wallet signs the challenge nonce
SIGNATURE=$(echo -n "mpp_sess_a1b2c3d4:0x9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c" | \
  openssl dgst -sha256 -sign agent_wallet_key.pem | base64)

curl -X POST https://a2a.tenzro.network/a2a \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tasks/send",
    "params": {
      "task_id": "task_k9j8h7g6f5",
      "payment_credential": {
        "protocol": "mpp",
        "session_id": "mpp_sess_a1b2c3d4",
        "payer_did": "did:tenzro:machine:550e8400-e29b-41d4-a716-446655440001:a1b2c3d4",
        "payer_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
        "amount": "2.50",
        "currency": "TNZO",
        "signature": "'"$SIGNATURE"'"
      }
    },
    "id": 6
  }'

4b. Provider Verifies and Starts Inference

# Response:
{
  "jsonrpc": "2.0",
  "result": {
    "task_id": "task_k9j8h7g6f5",
    "status": "processing",
    "inference": {
      "model": "gpt-4",
      "tokens_generated": 0,
      "cost_per_token": "0.05",
      "total_cost": "0.00"
    },
    "session": {
      "id": "mpp_sess_a1b2c3d4",
      "vouchers": []
    }
  },
  "id": 6
}

4c. Stream Tokens with Per-Token Charges

As the model generates tokens, the provider issues vouchers—signed micropayment receipts—for each token:

# Agent polls or subscribes to SSE stream at /a2a/stream
curl https://a2a.tenzro.network/a2a/stream?task_id=task_k9j8h7g6f5

# Server-Sent Events stream:
event: token
data: {"token": "Quantum", "cost": "0.05", "total": "0.05"}

event: voucher
data: {"session_id": "mpp_sess_a1b2c3d4", "amount": "0.05", "signature": "0x1a2b3c..."}

event: token
data: {"token": " computing", "cost": "0.05", "total": "0.10"}

event: voucher
data: {"session_id": "mpp_sess_a1b2c3d4", "amount": "0.10", "signature": "0x4d5e6f..."}

# ... continues for 48 tokens ...

event: complete
data: {
  "task_id": "task_k9j8h7g6f5",
  "tokens_generated": 48,
  "total_cost": "2.40",
  "response": "Quantum computing uses quantum bits (qubits) that exist in superposition states to perform certain calculations exponentially faster than classical computers.",
  "receipt": {
    "session_id": "mpp_sess_a1b2c3d4",
    "amount": "2.40",
    "settled": true,
    "tx_hash": "0x7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z",
    "timestamp": "2026-03-24T10:20:15Z"
  }
}

The final cost was 2.40 TNZO (48 tokens × 0.05 TNZO/token), which is under the agent's max_transaction_value of $10 and within its daily budget. The provider settled on-chain with a single transaction instead of 48 separate payments—this is why MPP is efficient for streaming services.

Step 5: AP2 Protocol — Agent-Native Payments

While MPP is designed for HTTP 402 challenge/credential flows, AP2 is the agent-native payment protocol. With AP2, agents discover providers, negotiate sessions, and settle autonomously without human intervention at each step. The flow is: discover a provider, create a session with spending bounds, authorize individual payments within the session's spending policy, execute, and receive a receipt.

AP2 sessions are scoped to a specific service and carry a maximum spend cap. The agent cannot exceed the cap without creating a new session, which re-evaluates the delegation scope. This gives guardians a second layer of control beyond the delegation scope itself.

5a. Full AP2 Flow

Here is the complete AP2 flow using the TypeScript SDK:

import { TenzroClient } from "@tenzro/sdk";

const client = new TenzroClient("https://rpc.tenzro.network");
const ap2 = client.ap2();

// Create AP2 session with spending bounds
const session = await ap2.createSession(
  "did:tenzro:machine:my-agent",
  "did:tenzro:machine:provider-001",
  "inference:gemma4-e4b",
  50_000_000_000_000_000_000n, // 50 TNZO max
  "TNZO"
);

// Authorize payment within spending policy
const auth = await ap2.authorizePayment(session.sessionId, tokenCost);

// Execute payment
const receipt = await ap2.executePayment(session.sessionId, auth.authorizationId);

The session binds the agent DID, the provider DID, the service identifier, and the maximum TNZO spend into a single object. Each authorizePayment call checks the cumulative session spend against the cap before issuing an authorization token. The executePayment call settles on-chain and returns a cryptographic receipt.

Step 6: Agent Transaction Executor

The Agent Transaction Executor enforces spending policies at the transaction level. A SpendingPolicy constrains per-transaction limits, daily totals, allowed recipients, and allowed operation types. For high-value transactions, TEE attestation can be required to prove the agent is running in a trusted execution environment before the payment is authorized.

6a. Set a Spending Policy

Spending policies are set by the guardian or by the agent itself (within its delegation scope). The policy is enforced by the node before any payment reaches the settlement engine:

const agentPayments = client.agentPayments();

// Set spending policy for an agent
await agentPayments.setSpendingPolicy("did:tenzro:machine:my-agent", {
  maxPerTransaction: "100000000000000000000", // 100 TNZO
  maxDailyTotal: "1000000000000000000000",    // 1000 TNZO
  allowedRecipients: [providerAddress],
  requireTeeAttestation: true,
  allowedOperations: ["inference", "settlement"],
});

// Agent pays for service (enforced by policy)
const receipt = await agentPayments.payForService(
  "did:tenzro:machine:my-agent",
  providerAddress,
  tokenCost,
  "inference"
);

If the agent attempts to pay an unlisted recipient, exceed the per-transaction cap, or perform a disallowed operation, the node rejects the request before it touches the settlement layer. The requireTeeAttestation flag adds hardware-level assurance: the agent must present a valid TEE attestation (Intel TDX, AMD SEV-SNP, AWS Nitro, or NVIDIA GPU CC) proving it runs in a secure enclave before the payment is processed.

Step 7: Micropayment Channels for Ultra-Low Fees

For even higher frequency payments (e.g., 100,000 tokens/day), even MPP's single settlement per session can add up in gas fees. Micropayment channels solve this by keeping payments entirely off-chain until channel close.

How Micropayment Channels Work

Opening a Channel

curl -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_openPaymentChannel",
    "params": [{
      "payer": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
      "payee": "0x9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b",
      "amount": "10.0",
      "duration": 86400  # 24 hours
    }],
    "id": 7
  }'

# Response:
{
  "jsonrpc": "2.0",
  "result": {
    "channel_id": "ch_m9n8o7p6q5",
    "payer": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb4",
    "payee": "0x9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b",
    "locked_amount": "10.0",
    "current_balance": "0.0",
    "nonce": 0,
    "expires_at": "2026-03-25T10:30:00Z",
    "tx_hash": "0xa1b2c3d4e5f6..."
  },
  "id": 7
}

Off-Chain Updates During Inference

# Provider generates first token, sends signed update:
{
  "channel_id": "ch_m9n8o7p6q5",
  "amount": "0.05",
  "nonce": 1,
  "signature": "0x1a2b3c..."  # Provider's signature over (channel_id, amount, nonce)
}

# Agent verifies:
# 1. Signature is from expected payee (0x9a8b7c...)
# 2. Nonce is incremental (was 0, now 1)
# 3. Amount is <= locked_amount (0.05 <= 10.0)
# 4. Store this update, discard previous

# ... 1000 more tokens generated ...

# Provider sends update after 1000 tokens:
{
  "channel_id": "ch_m9n8o7p6q5",
  "amount": "50.00",  # 1000 tokens * 0.05 TNZO
  "nonce": 1001,
  "signature": "0x9z8y7x..."
}

# Agent notices: 50 TNZO > 10 TNZO locked. Time to close and open new channel!

Closing the Channel

curl -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_closePaymentChannel",
    "params": [{
      "channel_id": "ch_m9n8o7p6q5",
      "final_amount": "10.0",
      "nonce": 1000,
      "signature": "0x8w7v6u..."  # Provider's signature on final state
    }],
    "id": 8
  }'

# Response:
{
  "jsonrpc": "2.0",
  "result": {
    "channel_id": "ch_m9n8o7p6q5",
    "final_amount": "10.0",
    "payer_refund": "0.0",
    "payee_payment": "10.0",
    "tx_hash": "0xz9y8x7w6...",
    "settled_at": "2026-03-24T11:00:00Z"
  },
  "id": 8
}

The channel consumed all 10 TNZO locked in escrow. If the final amount was only 6.50 TNZO, the agent would receive a 3.50 TNZO refund.

Dispute Resolution

What if the provider tries to cheat by submitting an old state update (lower nonce, lower amount) during channel close?

# Escrow contract logic (simplified):
function closeChannel(
  bytes32 channelId,
  uint256 amount,
  uint256 nonce,
  bytes signature
) public {
  Channel storage channel = channels[channelId];

  // Verify signature is from payee
  require(verify(channelId, amount, nonce, signature, channel.payee));

  // Reject if nonce is lower than previously submitted nonce
  require(nonce > channel.lastNonce, "Old state submitted");

  // If agent has a higher nonce, they can challenge within dispute period
  if (block.timestamp < channel.closeRequestedAt + DISPUTE_PERIOD) {
    // Agent submits their highest nonce update
    // Contract accepts whichever has higher nonce
  }

  // Settle based on highest nonce update
  transfer(channel.payee, amount);
  transfer(channel.payer, channel.lockedAmount - amount);
}

The agent has a dispute period (typically 24 hours) to submit a higher-nonce update if the provider tries to cheat. Since the agent stores all signed updates, it can always prove the true final state.

On Tenzro, disputes are first-class: MicropaymentChannelManager::open_dispute records the challenger's evidence in CF_CHANNELS under dispute:<dispute_id>, and either party can read state via tenzro_getDispute or tenzro_listDisputesByChannel. The agent CLI exposes the same surface as tenzro dispute status <id> and tenzro dispute list-by-channel --channel-id <id>.

Complete Architecture

Here's how all the pieces fit together:

Production Considerations

Key Storage

In this tutorial, we displayed private keys in JSON responses for learning purposes. In production:

Delegation Renewal

After the 90-day delegation expires, the agent must request renewal from its guardian. This forces periodic review of agent behavior:

# Agent payment refused  daily limit hit
{
  "error": "DelegationViolation",
  "message": "Daily spending cap reached (50 TNZO)",
  "guardian_did": "did:tenzro:human:550e8400...440001"
}

# Guardian reviews agent spending history, then raises the cap via setDelegationScope:
curl -X POST https://rpc.tenzro.network \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tenzro_setDelegationScope",
    "params": [{
      "did":                  "did:tenzro:machine:550e8400...440001:a1b2c3d4",
      "max_transaction_value": "10000000000000000000",   // 10 TNZO in wei
      "max_daily_spend":       "100000000000000000000",  // 100 TNZO in wei
      "allowed_operations":   ["pay", "inference"],
      "allowed_chains":       ["tenzro"],
      "allowed_payment_protocols": ["Mpp", "X402"]
    }]
  }'

Monitoring and Alerts

Guardians should monitor their controlled agents in real-time:

Multi-Chain Operations

This tutorial used Tenzro chain only. To enable Ethereum or Solana payments, update allowed_chains:

"delegation_scope": {
  "allowed_chains": ["tenzro", "ethereum", "base", "solana"],
  "allowed_payment_protocols": ["Mpp", "X402", "Direct"]
}

# Agent can now pay for Ethereum mainnet API calls via x402 (USDC on Base)
# or use Solana for high-frequency, low-value transactions

Cross-VM Token Operations

Cross-VM token operations via Tenzro precompiles (0x10010x1005) are intra-network and do not require separate chain allowlisting in the delegation scope. The pointer model means wTNZO on EVM, SPL-wrapped TNZO on SVM, and CIP-56 holdings on Canton all access the same underlying native balance — no bridging, no liquidity fragmentation.

allowed_chains only governs external chain interactions (Ethereum mainnet, Solana, Base, etc.) that go through cross-chain bridges.

Both the Rust and TypeScript SDKs expose a TokenClient as the unified abstraction for cross-VM token operations — wrapping, unwrapping, cross-VM transfers, and balance queries across all three VMs. See client.token() in the Rust SDK or client.tokens in the TypeScript SDK.

AgentKit Shortcut

This payment agent flow is available as a declarative template via AgentKit. The ref-mpp-payment-agent-v1 template handles the full HTTP 402 challenge / credential / receipt flow autonomously with delegation-gated spending limits. See sdk/tenzro-sdk/examples/agent_kit_mpp.rs.

What You Learned

Next Steps

You've built a complete AI agent with identity, wallet, and payment capabilities. Here's what to explore next:

Build More with Tenzro

Ready to deploy your agent to production? Explore our documentation and SDKs.