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

Build a DCA Agent

AI AgentsIntermediate25 min

Dollar-cost averaging is the simplest unattended capital-deployment strategy you can run, and it's the perfect shape for an agent: a fixed schedule, a fixed per-buy cap, no judgement calls. In this tutorial you will build the entire DCA loop using only the tenzro CLI and the AgentKit template system — no Rust source code required. You will provision a controller identity with TDIP, spawn an agent from the ref-mpp-payment-agent-v1 template, configure its delegation scope through the CLI, and run the agent through the full MPP payment and SettlementEngine on-chain release pipeline. Every signature is a genuine Ed25519 signature, every credential is verified against the canonical message format, and every settlement runs through the real engine with the 0.5% network fee.

What You'll Build

  • A human controller identity at KYC tier Enhanced with an auto-provisioned MPC wallet
  • A machine DCA agent spawned from the ref-mpp-payment-agent-v1 AgentKit template
  • A delegation scope with a 100 USDC per-buy cap, a 1000 USDC daily cap, and a 30-day time bound
  • A dry-run execution to validate the agent configuration before committing funds
  • A live 4-buy DCA schedule that runs end-to-end through MPP challenge, credential signing, and on-chain settlement
  • An oversized-buy rejection that proves the delegation cap is binding

Prerequisites

Before starting, make sure you have the tenzro installed and can reach the testnet RPC endpoint. You can install the CLI from the releases page or build it from source:

# Install from source
cargo install --path crates/tenzro-cli

# Verify installation
tenzro version

# Check connectivity to testnet
tenzro info --rpc https://rpc.tenzro.network

You will also need curl and jq for the JSON-RPC examples. All commands in this tutorial target the public testnet at https://rpc.tenzro.network.

Why DCA is the Right First Agent

DCA flows have the simplest possible decision logic — buy a fixed amount on a fixed cadence — so they let you focus on the plumbing rather than the strategy. That makes them the ideal vehicle for learning how MPP and the SettlementEngine fit together. Once you can issue a payment challenge, have the agent sign it with the canonical message format, verify it server-side, and release escrow on-chain through the engine, you have all the building blocks you need to wire up any commerce flow on Tenzro. The DCA pattern just happens to be the smallest one that exercises every layer.

Step 1: Join the Network

The tenzro join command is the fastest way to get started. It provisions a TDIP identity, an MPC wallet, and a hardware profile in a single call:

# One-click network participation
# This provisions your human identity, MPC wallet, and hardware profile
tenzro join --rpc https://rpc.tenzro.network

The output includes your DID, wallet address, and hardware profile. Save the DID — you will need it as the controller identity for your agent:

# Example output:
# Identity:  did:tenzro:human:a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Wallet:    0x7f3a...b21e
# Hardware:  x86_64, 16 cores, 64GB RAM
# Status:    Active

Step 2: Fund Your Wallet

Request testnet TNZO from the faucet so your agent has funds for settlement. The faucet dispenses 100 TNZO per request with a 24-hour cooldown:

# Request testnet tokens
curl -s -X POST https://api.tenzro.network/faucet \
  -H "Content-Type: application/json" \
  -d '{"address": "0x7f3a...b21e"}' | jq .

# Verify your balance
tenzro wallet balance --rpc https://rpc.tenzro.network

Testnet Only

The faucet is only available on the public testnet. On mainnet, you would acquire TNZO through an exchange or bridge. The settlement amounts in this tutorial use testnet-scale values.

Step 3: Register Your Controller Identity

If you used tenzro join, your identity is already registered. You can verify it and upgrade the KYC tier to Enhanced (tier 2), which is required for agent delegation:

# Verify your identity is registered
tenzro identity resolve \
  --did "did:tenzro:human:a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  --rpc https://rpc.tenzro.network

# List all your identities
tenzro identity list --rpc https://rpc.tenzro.network

# View the full DID document (W3C format)
tenzro identity document \
  --did "did:tenzro:human:a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  --rpc https://rpc.tenzro.network

You can also register a fresh identity directly if you need a separate controller for this tutorial:

# Register a new human identity explicitly
tenzro identity register \
  --type human \
  --display-name "DCA Strategist" \
  --kyc-tier enhanced \
  --rpc https://rpc.tenzro.network

Step 4: Explore Available Agent Templates

AgentKit provides pre-built agent templates that implement common patterns. The ref-mpp-payment-agent-v1 template handles the full MPP payment flow — challenge creation, Ed25519 credential signing, server-side verification, and on-chain settlement through the SettlementEngine. List the available templates to confirm it is registered:

# List all available agent templates
tenzro agent list-templates --rpc https://rpc.tenzro.network

Inspect the template to see what parameters it accepts:

# Get template details including required parameters and capabilities
tenzro agent get-template \
  --template-id ref-mpp-payment-agent-v1 \
  --rpc https://rpc.tenzro.network

The template details show you the capabilities the agent will have (dca, trade, mpp-payment), the delegation parameters it expects (per-transaction cap, daily cap, time bound, allowed operations), and the MPP configuration fields (recipient address, asset type).

You can also inspect the template via JSON-RPC:

curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_getAgentTemplate",
    "params": {
      "template_id": "ref-mpp-payment-agent-v1"
    },
    "id": 1
  }' | jq .result

Step 5: Spawn the DCA Agent

Spawn a new agent instance from the MPP payment template. The spawn-template command registers a machine identity under your controller DID, provisions an MPC wallet for the agent, and configures the delegation scope — all in one step. The delegation scope defines what the agent is allowed to do: a 100 USDC per-buy cap, a 1000 USDC daily cap, allowed operations restricted to dca-buy and trade, and a 30-day time window:

# Spawn a DCA agent from the MPP payment template
tenzro agent spawn-template \
  --template-id ref-mpp-payment-agent-v1 \
  --display-name "My DCA Agent" \
  --controller-did "did:tenzro:human:a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  --max-transaction-value 100000000 \
  --max-daily-spend 1000000000 \
  --allowed-operations dca-buy,trade \
  --time-bound-days 30 \
  --param recipient=0xdca-recipient \
  --param asset=USDC \
  --param schedule='[{"asset":"BTC","spend":25000000},{"asset":"ETH","spend":25000000},{"asset":"SOL","spend":25000000},{"asset":"BTC","spend":75000000}]' \
  --rpc https://rpc.tenzro.network

The command outputs the agent ID, its machine DID, and its wallet address:

# Example output:
# Agent ID:       agent-7e2f4a91
# Machine DID:    did:tenzro:machine:a1b2c3d4-e5f6-7890-abcd-ef1234567890:8b3c1d5e
# Wallet:         0x9c4b...d73f
# Template:       ref-mpp-payment-agent-v1
# Controller:     did:tenzro:human:a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Delegation:
#   Max per-tx:   100 USDC
#   Max daily:    1,000 USDC
#   Operations:   dca-buy, trade
#   Expires:      2026-05-08T00:00:00Z
# Status:         Ready

Understanding the Delegation Scope

The delegation scope is the security boundary around your agent. The max-transaction-value of 100,000,000 (100 USDC in 6-decimal base units) means the agent cannot execute any single buy exceeding 100 USDC. The max-daily-spend of 1,000,000,000 (1000 USDC) caps the total daily volume. The allowed-operations restrict the agent to only dca-buy and trade — it cannot, for example, transfer funds or register new identities. And the 30-day time bound means the delegation automatically expires, requiring explicit renewal.

Step 6: Verify the Agent Identity

Before running the agent, verify that the machine identity was correctly registered under your controller and that the delegation scope matches your intent:

# Resolve the agent's machine DID
tenzro identity resolve \
  --did "did:tenzro:machine:a1b2c3d4-e5f6-7890-abcd-ef1234567890:8b3c1d5e" \
  --rpc https://rpc.tenzro.network

# View the agent's DID document to confirm controller binding
tenzro identity document \
  --did "did:tenzro:machine:a1b2c3d4-e5f6-7890-abcd-ef1234567890:8b3c1d5e" \
  --rpc https://rpc.tenzro.network

You can also verify the delegation scope via JSON-RPC to see the exact constraints:

curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_resolveIdentity",
    "params": {
      "did": "did:tenzro:machine:a1b2c3d4-e5f6-7890-abcd-ef1234567890:8b3c1d5e"
    },
    "id": 1
  }' | jq '.result.delegation_scope'

The response shows the full delegation scope including max_transaction_value, max_daily_spend, allowed_operations, time_bound, and the controller_did that owns this agent.

Step 7: Dry-Run the Agent

Before committing real funds, execute a dry run. The dry-run mode walks through the entire DCA schedule — delegation enforcement, MPP challenge creation, credential signing, and settlement — but does not broadcast transactions on-chain. This lets you verify the schedule, delegation scope, and payment flow without spending tokens:

# Dry-run: validate the full pipeline without on-chain settlement
tenzro agent run-template \
  --agent-id agent-7e2f4a91 \
  --dry-run \
  --rpc https://rpc.tenzro.network

The dry-run output walks through each buy in the schedule:

# Example dry-run output:
#
# [dry-run] Buy #1: 25 USDC of BTC
#   delegation check ........... OK (25 USDC <= 100 USDC cap)
#   MPP challenge created ...... challenge-a1b2c3d4
#   credential signed .......... Ed25519, canonical format
#   credential verified ........ OK
#   settlement (simulated) ..... 25,000 base units
#   network fee (0.5%) ......... 125 base units
#   [WOULD SETTLE ON-CHAIN]
#
# [dry-run] Buy #2: 25 USDC of ETH
#   delegation check ........... OK (25 USDC <= 100 USDC cap)
#   MPP challenge created ...... challenge-e5f6a7b8
#   credential signed .......... Ed25519, canonical format
#   credential verified ........ OK
#   settlement (simulated) ..... 25,000 base units
#   network fee (0.5%) ......... 125 base units
#   [WOULD SETTLE ON-CHAIN]
#
# [dry-run] Buy #3: 25 USDC of SOL
#   delegation check ........... OK (25 USDC <= 100 USDC cap)
#   ...
#
# [dry-run] Buy #4: 75 USDC of BTC
#   delegation check ........... OK (75 USDC <= 100 USDC cap)
#   ...
#
# Dry-run complete: 4/4 buys would succeed

Why Dry-Run First

The dry-run validates that every buy in your schedule passes delegation enforcement before any tokens leave your wallet. This catches misconfigurations early — for example, if your per-buy cap were set to 50 USDC instead of 100, the fourth buy (75 USDC of BTC) would fail in the dry-run, saving you from a partial execution on mainnet.

Step 8: Run the DCA Agent

Once the dry-run passes, run the agent for real. The --max-iterations flag controls how many buy cycles the agent executes. For this 4-buy schedule, set it to 4 (or omit it to run the full schedule once):

# Run the agent for real
tenzro agent run-template \
  --agent-id agent-7e2f4a91 \
  --max-iterations 4 \
  --rpc https://rpc.tenzro.network

The agent executes each buy through a five-stage pipeline. Here is what happens at each stage:

  1. Delegation enforcement — the agent checks its delegation scope to confirm the buy amount is within the per-transaction cap, the daily cap has not been exhausted, the operation (dca-buy) is in the allowed list, and the time bound has not expired.
  2. MPP challenge creation — the agent requests a payment challenge from the MPP server for the target asset and amount. The challenge includes a unique challenge_id, the amount, and the asset denomination.
  3. Credential signing — the agent signs the MPP credential using the canonical message format: challenge_id ++ payer_did ++ amount.to_le_bytes() ++ asset. The signature uses the agent's Ed25519 key.
  4. MPP verification and settlement — the MPP server verifies the signed credential against the canonical message, confirms the amount and asset match the challenge, and issues a receipt.
  5. On-chain release — the SettlementEngine releases escrow with a signed ServiceProof. The engine deducts the 0.5% network fee and routes it to the treasury.

The live output shows the real balances after each buy:

# Example live output:
#
# Buy #1: 25 USDC of BTC
#   delegation ................. OK
#   MPP challenge .............. challenge-a1b2c3d4
#   credential verified ........ OK
#   settlement tx .............. 0x8a3f...c912
#   customer balance ........... 975,000 (was 1,000,000)
#   provider balance ........... 24,875 (25,000 minus 0.5% fee)
#
# Buy #2: 25 USDC of ETH
#   delegation ................. OK
#   MPP challenge .............. challenge-e5f6a7b8
#   credential verified ........ OK
#   settlement tx .............. 0x1b4e...d823
#   customer balance ........... 950,000
#   provider balance ........... 24,875
#
# Buy #3: 25 USDC of SOL
#   delegation ................. OK
#   MPP challenge .............. challenge-c9d0e1f2
#   credential verified ........ OK
#   settlement tx .............. 0x5c7d...a634
#   customer balance ........... 925,000
#   provider balance ........... 24,875
#
# Buy #4: 75 USDC of BTC
#   delegation ................. OK
#   MPP challenge .............. challenge-3a4b5c6d
#   credential verified ........ OK
#   settlement tx .............. 0x9e0f...b745
#   customer balance ........... 850,000
#   provider balance ........... 74,625 (75,000 minus 0.5% fee)
#
# Schedule complete: 4/4 buys executed
# Total spent: 150 USDC
# Total network fees: 750 base units

Step 9: Verify Settlements On-Chain

Each settlement creates a transaction on the Tenzro Ledger. You can look up any settlement by its transaction hash:

# Look up a settlement transaction
curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_getTransaction",
    "params": {
      "hash": "0x8a3f...c912"
    },
    "id": 1
  }' | jq .result

You can also check your wallet balance after all buys to confirm the total deduction:

# Check wallet balance after all buys
tenzro wallet balance --rpc https://rpc.tenzro.network

# Or via JSON-RPC
curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_getBalance",
    "params": ["0x7f3a...b21e", "latest"],
    "id": 1
  }' | jq .result

Step 10: Confirm the Per-Buy Cap is Binding

The delegation scope is a hard security boundary, not a suggestion. To prove this, attempt a buy that exceeds the 100 USDC per-transaction cap. You can test this by creating a payment challenge directly and observing the delegation rejection:

# Attempt a 250 USDC buy  this exceeds the 100 USDC per-tx cap
tenzro payment challenge \
  --did "did:tenzro:machine:a1b2c3d4-e5f6-7890-abcd-ef1234567890:8b3c1d5e" \
  --resource "/dca/buy/BTC" \
  --amount 250000000 \
  --asset USDC \
  --rpc https://rpc.tenzro.network

The command returns a DelegationViolation error because 250 USDC exceeds the max_transaction_value of 100 USDC:

# Expected output:
# Error: DelegationViolation
#   operation: dca-buy
#   value: 250000000 (250 USDC)
#   max_transaction_value: 100000000 (100 USDC)
#   message: "transaction value 250000000 exceeds max allowed 100000000"

You can also verify this through the JSON-RPC directly. The tenzro_resolveIdentity call returns the delegation scope, and any operation that violates the scope is rejected before a payment challenge is even created:

# Verify the delegation scope is enforced on the server
curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_createPaymentChallenge",
    "params": {
      "payer_did": "did:tenzro:machine:a1b2c3d4-e5f6-7890-abcd-ef1234567890:8b3c1d5e",
      "resource": "/dca/buy/BTC",
      "amount": 250000000,
      "asset": "USDC",
      "protocol": "mpp"
    },
    "id": 1
  }' | jq .error

Step 11: Inspect Payment Sessions

The MPP protocol maintains session state for each payment flow. After running the agent, you can list all payment sessions to see the history:

# List all payment sessions for your agent
tenzro payment sessions --rpc https://rpc.tenzro.network

# Get details on a specific session
tenzro payment receipt \
  --session-id "session-a1b2c3d4" \
  --rpc https://rpc.tenzro.network

# View supported payment protocols
tenzro payment info --rpc https://rpc.tenzro.network

Understanding the Payment Flow

The five-stage pipeline that the ref-mpp-payment-agent-v1 template implements is the fundamental building block for all commerce on Tenzro. Here is the exact sequence:

# Stage 1: Delegation enforcement (client-side)
# The agent checks its own delegation scope before making any network call.
# If the buy exceeds max_transaction_value, max_daily_spend, or the
# operation is not in allowed_operations, the buy is rejected locally.

# Stage 2: MPP challenge (server-side)
# POST /dca/buy/BTC -> HTTP 402
# Response: { challenge_id, amount, asset, recipient, expires_at }

# Stage 3: Credential signing (client-side)
# Canonical message = challenge_id ++ payer_did ++ amount.to_le_bytes() ++ asset
# Signature = Ed25519.sign(canonical_message, agent_private_key)

# Stage 4: MPP verification (server-side)
# The server reconstructs the canonical message from the challenge,
# verifies the Ed25519 signature against the agent's public key,
# and confirms the amount/asset match.

# Stage 5: On-chain settlement (ledger)
# SettlementEngine.settle(request) ->
#   customer balance -= settlement_amount
#   network_fee = settlement_amount * 0.005 (0.5%)
#   provider balance += settlement_amount - network_fee
#   treasury balance += network_fee

Why the LE-Bytes Order Matters

The canonical message uses amount.to_le_bytes() (little-endian), not to_be_bytes()or string formatting. If the encoding does not match the server's expectation, the credential verifies against the wrong digest and the server returns an opaque rejection. The AgentKit template handles this correctly — you never need to construct the canonical message yourself.

Advanced: Modify the Delegation Scope

If you need to adjust the delegation scope after the agent is spawned — for example, to increase the per-buy cap or extend the time window — you can do so through the identity CLI. Only the controller identity can modify the delegation scope of its child agents:

# Update the delegation scope via JSON-RPC
# Only the controller can modify its agent's delegation
curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_setDelegationScope",
    "params": {
      "did": "did:tenzro:machine:a1b2c3d4-e5f6-7890-abcd-ef1234567890:8b3c1d5e",
      "scope": {
        "max_transaction_value": 200000000,
        "max_daily_spend": 2000000000,
        "allowed_operations": ["dca-buy", "trade", "rebalance"],
        "time_bound_days": 60
      }
    },
    "id": 1
  }' | jq .result

Advanced: Monitor via JSON-RPC

For programmatic monitoring or integration with external systems, you can query the agent's state and recent activity through the JSON-RPC interface:

# Check node status and current block height
curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tenzro_nodeInfo",
    "params": {},
    "id": 1
  }' | jq .result

# Get the latest block
curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_blockNumber",
    "params": [],
    "id": 1
  }' | jq .result

# Query the agent's wallet balance (EVM-compatible)
curl -s -X POST https://rpc.tenzro.network \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_getBalance",
    "params": ["0x9c4b...d73f", "latest"],
    "id": 1
  }' | jq .result

What You Learned

Next Steps