Live on Avalanche C-Chain

Machine Payments Protocol
on Avalanche

Streaming payment channels for AI agents and services. Deposit once, sign unlimited off-chain vouchers at zero gas, settle when ready. Adapted from Tempo's MPP for any EVM chain.

Mainnet 0xF1EB69d85897ba945B5E2EbcBAD831bf3671F137
Fuji 0x6e2DD66C1bfb66a2b579D291CdF6EA559E93619b

How It Works

Payment channels let agents pay per-request without a transaction each time. Only the open and settle/close touch the chain.

1

Open Channel

Payer deposits USDC into the StreamChannel contract, naming the payee.

On-chain ~150k gas
2

Sign Vouchers

Payer signs EIP-712 cumulative vouchers off-chain. Each voucher says "I owe you X total". Send via HTTP headers.

Zero gas
3

Settle or Close

Payee submits the latest voucher on-chain to collect. Unused deposit is refunded to payer.

On-chain ~90k gas

⚡ Cumulative Vouchers

Each voucher supersedes the last. Only the final one matters — settle once, not per request.

🔒 Escrowed Deposits

Funds are locked in the contract. Payee can't take more than signed. Payer can reclaim after grace period.

🌐 Any ERC-20 Token

Works with USDC, USDT, WAVAX, or any standard token on Avalanche.

🔗 Chain-Aware IDs

Channel IDs include block.chainid — no cross-chain replay attacks.

Deployed Contracts

NetworkContractChain IDUSDC
Mainnet 0xF1EB69d85897ba945B5E2EbcBAD831bf3671F137 43114 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E
Fuji 0x6e2DD66C1bfb66a2b579D291CdF6EA559E93619b 43113 0x5425890298aed601595a70AB815c96711a31Bc65
EIP-712 Domain: The contract uses domain name "Tempo Stream Channel" version "1" — matching Tempo's original spec for cross-compatibility.
Need test tokens? Get Fuji AVAX from Core Faucet and test USDC from Aave Faucet (select Fuji network).

Try the MPP API

This is a real API gated by MPP payments. No voucher = 402 rejected. Valid voucher = data returned. Try it.

Controls

localhost:3402

1. Call without payment

Hit the API with no voucher — get 402.

2. Discover pricing

Learn the service's payment terms.

3. Open a payment channel

Connect wallet, approve USDC, deposit into channel.

Not connected

4. Make paid API calls

Signs a voucher (zero gas), sends via HTTP headers, gets data.

Requests: 0 · Paid: 0.00 USDC (off-chain)

Live Request / Response

Integration Guide

For Services (Accept Payments)

Add MPP payment gating to any HTTP API. Agents discover your pricing via /.well-known/mpp, then send signed vouchers with each request.

service.ts
import express from "express";

// MPP discovery endpoint
app.get("/.well-known/mpp", (req, res) => {
  res.json({
    version: "draft-tempo-stream-00",
    payee: SERVICE_ADDRESS,
    channel_contract: "0xF1EB69d85897ba945B5E2EbcBAD831bf3671F137",
    chain_id: 43114,
    price_per_request: "100000",  // 0.10 USDC
    accepted_tokens: [USDC_ADDRESS],
  });
});

// Paid API endpoint
app.post("/api/data", async (req, res) => {
  const channelId = req.headers["x-mpp-channel-id"];
  const amount    = req.headers["x-mpp-cumulative-amount"];
  const voucher   = req.headers["x-mpp-voucher"];

  if (!channelId || !amount || !voucher) {
    return res.status(402).json({
      error: "Payment Required",
      mpp_info: "GET /.well-known/mpp"
    });
  }

  // Store voucher, serve data, settle later
  storeVoucher(channelId, amount, voucher);
  res.json({ data: "your response here" });
});
agent.ts
import { createWalletClient, http, parseUnits } from "viem";

// 1. Open channel (one-time, on-chain)
const channelId = await walletClient.writeContract({
  address: STREAM_CHANNEL,
  abi: streamChannelAbi,
  functionName: "open",
  args: [payeeAddress, USDC, parseUnits("1", 6), salt, zeroAddress],
});

// 2. Sign vouchers off-chain (zero gas, repeat per request)
const signature = await walletClient.signTypedData({
  domain: {
    name: "Tempo Stream Channel",
    version: "1",
    chainId: 43114,
    verifyingContract: STREAM_CHANNEL,
  },
  types: {
    Voucher: [
      { name: "channelId", type: "bytes32" },
      { name: "cumulativeAmount", type: "uint128" },
    ],
  },
  primaryType: "Voucher",
  message: { channelId, cumulativeAmount: parseUnits("0.10", 6) },
});

// 3. Send to service via HTTP
await fetch("https://api.example.com/data", {
  headers: {
    "X-MPP-Channel-Id": channelId,
    "X-MPP-Cumulative-Amount": "100000",
    "X-MPP-Voucher": signature,
  },
});
HTTP 402 Flow
# 1. Agent makes request without payment
POST /api/data
→ 402 Payment Required
{
  "error": "Payment Required",
  "mpp_info": "GET /.well-known/mpp"
}

# 2. Agent discovers pricing
GET /.well-known/mpp
→ 200 OK
{
  "version": "draft-tempo-stream-00",
  "payee": "0x3d7A...D787",
  "channel_contract": "0xF1EB...F137",
  "chain_id": 43114,
  "price_per_request": "100000",
  "accepted_tokens": ["0xB97E...a6E"]
}

# 3. Agent opens channel on-chain, then sends with voucher
POST /api/data
X-MPP-Channel-Id: 0x9a0b6130...
X-MPP-Cumulative-Amount: 100000
X-MPP-Voucher: 0x1daffe21...
→ 200 OK
{ "data": "..." }
IStreamChannel.sol
interface IStreamChannel {
  struct Channel {
    bool finalized;
    uint64 closeRequestedAt;
    address payer;
    address payee;
    address token;
    address authorizedSigner;
    uint128 deposit;
    uint128 settled;
  }

  function open(
    address payee, address token,
    uint128 deposit, bytes32 salt,
    address authorizedSigner
  ) external returns (bytes32 channelId);

  function settle(
    bytes32 channelId, uint128 cumulativeAmount,
    bytes calldata signature
  ) external;

  function close(...) external;
  function topUp(bytes32, uint256) external;
  function requestClose(bytes32) external;
  function withdraw(bytes32) external;

  function getChannel(bytes32)
    external view returns (Channel memory);
}

Contract Functions

FunctionCallerDescription
open()PayerDeposit tokens and create a new payment channel
settle()PayeeSubmit a signed voucher to claim accumulated payment
topUp()PayerAdd more tokens to an existing channel (cancels pending close)
close()PayeeFinal settlement + refund remaining deposit to payer
requestClose()PayerStart 15-minute grace period for unilateral withdrawal
withdraw()PayerReclaim unsettled deposit after grace period expires
getChannel()AnyoneRead channel state (view function, no gas)
computeChannelId()AnyoneCompute deterministic channel ID from parameters
getVoucherDigest()AnyoneGet the EIP-712 digest for a voucher (for verification)
getChannelsBatch()AnyoneRead multiple channels in one call

Events

EventEmitted When
ChannelOpenedA new channel is created with a deposit
SettledPayee claims payment via a signed voucher
TopUpPayer adds more funds to a channel
CloseRequestedPayer initiates the 15-minute grace period
CloseRequestCancelledPayer tops up, cancelling a pending close
ChannelClosedChannel is finalized (cooperative or after grace)
ChannelExpiredPayer withdraws after grace period