# Spendot

Spendot is a wallet and a spending limit for a dot. The owner funds the wallet with USDC on Solana and sets a daily cap, a per-payment cap and the sites it may pay. An agent holding the owner's dot token can pay those sites over x402, inside those limits. It cannot change a limit, unfreeze the wallet or withdraw.

## The token

The owner makes a dot token in the app (section "Your dot") and gives it to you. Send it on every call:

    Authorization: Bearer dot_...

## What the wallet allows

    GET /api/agent/status

Returns the wallet address, the asset (the USDC mint), the network, the balance, the caps, what was spent today (the day runs on UTC), whether it is frozen, and the allowed sites. Amounts are whole micro-USDC: 1 USDC is 1000000.

## Paying a site

1. Request the resource. A site that wants to be paid answers `402` with a body like `{ "x402Version": 1, "accepts": [ ... ] }`.
2. Hand that body to Spendot as it is:

       POST /api/agent/pay
       Content-Type: application/json
       Idempotency-Key: <a key of your own, 8 to 80 characters>

       { "x402Version": 1, "accepts": [ { "scheme": "exact", "network": "solana", "maxAmountRequired": "50000",
         "resource": "https://api.example.com/report", "payTo": "<address>", "asset": "<USDC mint>" } ] }

   A flat form works too: `{ "amount": 50000, "recipient": "<address>", "resource": "https://..." }`.
3. Spendot checks, in this order: frozen, the resource's site is on the list, the per-payment cap, the daily cap, the balance. The checks and the hold on the daily cap happen together: two payments at once cannot both pass a cap they jointly exceed.
   - `200` `{ "status": "paid", "proof": { "header": "X-PAYMENT", "value": "..." } }`: ask the site again with that header. The proof carries the Solana transaction signature of the USDC transfer.
   - `403` `{ "status": "refused", "reason": "Over the daily cap" }`: do not try another way. Tell the owner.
   - `202` `{ "status": "pending" }`: sent, not confirmed yet. Ask again with the same Idempotency-Key; it will not pay twice.
   - `502`: nothing was sent.
4. Every attempt, paid or refused, is on the owner's statement.

## What to tell the owner

    GET  /api/agent/inbox                things the owner should hear about (refused payments)
    POST /api/agent/inbox/<id>/ack       once you have told them
    GET  /api/agent/statement            the latest statement lines

## Example

    curl -s -H "Authorization: Bearer $TOKEN" https://<this site>/api/agent/status
    curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
         -H "Idempotency-Key: report-0001" -d @402-body.json https://<this site>/api/agent/pay

Independent project. Not affiliated with OpenAI.
