Payment Flow

MachineMarket uses on-chain USDC payments on Base as both payment and authentication. There are no API keys — the transaction hash proves you paid.

Overview

Payment flow
1. Agent calculates cost          →  tier.hourly * duration_hours
2. Agent sends USDC on Base       →  ERC-20 transfer() to MM wallet
3. Agent waits for confirmation   →  >= 1 block confirmation
4. Agent calls POST /v1/spawn     →  includes tx_hash + wallet_address
5. API verifies on-chain          →  receipt, Transfer event, amounts
6. API provisions server          →  or returns error

Cost calculation

Each tier has an hourly rate. Multiply by the number of hours in the chosen duration:

typescript
// Example: Small tier, 24h duration
const cost = 0.028 * 24; // = 0.672 USDC

// Tiers:
// Nano:   $0.015/hr  →  $0.36/day   →  $10.80/mo
// Small:  $0.028/hr  →  $0.67/day   →  $20.16/mo
// Medium: $0.055/hr  →  $1.32/day   →  $39.60/mo
// Large:  $0.105/hr  →  $2.52/day   →  $75.60/mo

// Durations: 1h, 24h, 7d (168h), 30d (720h)

Sending the payment

Transfer USDC (the ERC-20 at 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) on Base to the MachineMarket recipient wallet. USDC has 6 decimals.

viem example
import { createWalletClient, http, parseUnits } from "viem";
import { base } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const MM_WALLET = "0x..."; // MachineMarket recipient

const account = privateKeyToAccount(PRIVATE_KEY);
const client = createWalletClient({
  account,
  chain: base,
  transport: http(),
});

const txHash = await client.writeContract({
  address: USDC,
  abi: [{
    name: "transfer",
    type: "function",
    inputs: [
      { name: "to", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    outputs: [{ type: "bool" }],
  }],
  functionName: "transfer",
  args: [MM_WALLET, parseUnits("0.672", 6)],
});
!Wait for confirmation
The API requires at least 1 block confirmation. On Base, blocks are produced every ~2 seconds, so this is near-instant.

What the API verifies

When you submit a tx_hash to POST /v1/spawn or POST /v1/instances/:id/extend, the API performs these checks:

  1. Receipt status — The transaction must have succeeded on-chain (receipt.status === "success").
  2. Confirmations — At least 1 block confirmation.
  3. Transfer event — The API decodes the Transfer(from, to, value) event from the USDC contract logs.
  4. Recipient — The to address must be the MachineMarket wallet.
  5. Sender — The from address must match the wallet_address you submitted.
  6. Amount — The transfer amount must be >= the calculated cost.

Deduplication

Each tx_hash can only be used once. Attempting to reuse a transaction hash returns a 409 TX_DUPLICATE error. This prevents double-spending the same payment.

Wallet limits

A single wallet address can have at most 10 concurrent active instances (status provisioning or running). Exceeding this limit returns a 429 WALLET_LIMIT error.

Error codes

typescript
// 402 PAYMENT_INVALID — one of:
//   "Transaction failed on-chain"
//   "Transaction has insufficient confirmations"
//   "Sender does not match claimed wallet_address"
//   "Insufficient amount: expected X USDC, got Y USDC"
//   "No USDC Transfer to MachineMarket wallet found in transaction"

// 409 TX_DUPLICATE
//   "Transaction hash already used"

// 429 WALLET_LIMIT
//   "Maximum concurrent instances reached for this wallet"
iNote
The SDK does not handle payments. Use viem, ethers, or any Web3 library to send the USDC transfer. See the Quickstart for a full example.