# m2m.monster

> Non-custodial settlement for agent-to-agent payments over x402 with the m2m-split scheme on Solana. Agents sign locally; m2m.monster holds no agents' keys or funds and never signs or pays payment transactions; payers pay their own gas.

- Network: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` (solana-mainnet)
- API: https://api.m2m.monster/v1 · OpenAPI: https://api.m2m.monster/v1/openapi.json · Errors: https://api.m2m.monster/v1/errors
- MCP (streamable HTTP): https://api.m2m.monster/mcp
- x402 scheme: `m2m-split` (x402 v2)

## Fee

`fee = min(max(0.25% × amount, $0.001), $0.5)`

- Borne by the payee: it receives the amount minus the fee.
- Paid in USDC (`EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`) to the treasury `D68nkVrQD3pcs6qNcadXqWvoKqsNCKpLemy3zYVmGHym`, inside the same transaction.
- Minimum payment $0.001. Fees round up to 0.000001 USDC.
- Quotes are valid for 30 seconds and show fee_gross, discount and fee_net.
- The payer pays its own gas and needs USDC for the fee even when paying in another token.

## Tokens (exact mint addresses)

- USDC: `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`
- USDT: `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB`

## Pay a seller that answers 402 (TypeScript)

```ts
import { createApiClient, createPaymentFetch } from "@m2m-monster/sdk";
import { createKeyPairSignerFromBytes, createSolanaRpc } from "@solana/kit";

const signer = await createKeyPairSignerFromBytes(yourSecretKeyBytes); // stays local
const api = createApiClient("https://api.m2m.monster");
const pay = createPaymentFetch({
  rpc: createSolanaRpc(YOUR_RPC_URL),
  signer,
  serviceAddress: await api.serviceAddress(),
});
const res = await pay("https://seller.example/resource"); // 402 -> pay -> 200
```

## Get paid (TypeScript)

```ts
import { createApiClient, createPaywall } from "@m2m-monster/sdk";
import { createSolanaRpc } from "@solana/kit";

const paywall = createPaywall({
  api: createApiClient("https://api.m2m.monster", { apiKey: YOUR_API_KEY }), // apiKey optional
  rpc: createSolanaRpc(YOUR_RPC_URL),
  network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
  payee: YOUR_WALLET,
});
// In any fetch-style handler:
return paywall.handle(request, { mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: "100000" }, { description: "..." }, () =>
  Response.json({ result: "..." }),
);
```

The payee's token account and the treasury USDC account must exist before paying; payments never create accounts.

## Retries: never pay twice

`createPaymentFetch` keeps each payment it signs in memory, bound to the request (its
`Idempotency-Key` header, else method, URL and body), until the outcome is known. If a call fails
after paying (for example the response is lost), repeat the same call: it resends the same signed
payment instead of requesting a new quote, and decides from the seller's answer and the chain.

```ts
import { paymentOutcome } from "@m2m-monster/sdk";

const res = await pay("https://seller.example/resource"); // if it throws, repeat this same call
const outcome = paymentOutcome(res); // undefined when no payment was needed
// outcome.status: "paid" | "already_paid" | "not_paid"; outcome.transaction, outcome.receipt,
// outcome.receiptData (only when the SDK verified the receipt)
```

- `paid`: this response settled the payment.
- `already_paid`: an earlier attempt paid; the seller does not serve the resource again. The
  receipt is set when the seller returned it and the SDK verified it on-chain (service signature,
  confirmed transaction, this payment's signature); otherwise the transaction is confirmed
  on-chain.
- `not_paid`: proven not settled (failed on-chain, or not found after its blockhash expired). The
  next call pays again with a new quote.
- Error `PAYMENT_STATUS_UNKNOWN`: the outcome could not be determined (RPC unavailable, or the
  transaction not found while its blockhash is still valid, about a minute or two after signing).
  The SDK keeps the payment and does not pay again for that request; repeat the same call later.

Concurrent calls for the same request share one attempt and never sign twice.

Pending payments live in memory only. After a process restart the SDK does not know a payment
signed before the restart: repeating the request gets a new quote and signs a new payment. Before
paying again after a restart, check the chain: list your payer address's recent transactions
(`getSignaturesForAddress`) and look for an m2m-split payment to that payee (memo
`settled via m2m.monster <quote id>`) since before the restart.

### Without the SDK

A signed payment is valid once and unlocks the resource once. If you send the same
`PAYMENT-SIGNATURE` again, nothing is paid or served again. The seller answers `402` with:

- body `{"error":{"code":"QUOTE_EXPIRED","message":"This payment was already used."}}`
- header `PAYMENT-RESPONSE` (base64 JSON): `success: false`, `errorReason: "QUOTE_EXPIRED"`,
  `transaction` = the Solana signature of your original payment, `payer`, and
  `extensions["m2m-receipt"].info.receipt` = the original signed receipt.

`QUOTE_EXPIRED` with a `transaction` and a receipt is proof that you already paid: verify the
receipt (`POST /v1/receipts/verify` with `{"receipt": "<jws>"}`, or `verify_receipt` over MCP) and
do not pay again. `QUOTE_EXPIRED` without them proves nothing: before paying again, check
on-chain whether the transaction you signed landed (`getSignatureStatuses` with
`searchTransactionHistory`) and wait until its blockhash has expired.

## Quote with curl

```sh
curl -X POST https://api.m2m.monster/v1/quotes \
  -H 'content-type: application/json' \
  -H 'idempotency-key: 6f1c2e8a-quote-1' \
  -d '{"network":"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp","mint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","amount":"100000","payee":"YOUR_WALLET"}'
```

## MCP

```json
{ "mcpServers": { "m2m-monster": { "url": "https://api.m2m.monster/mcp" } } }
```

Tools: `get_pricing`, `quote_payment`, `build_payment`, `verify_payment`, `submit_payment`, `verify_receipt`, `build_refund`, `register_agent`, `get_referral_status`, `get_status`. Tools that move funds return unsigned transactions; sign them locally.

## Referrals

Register with `referred_by` (optional registration, challenge signed with your key). Referred agents get 25% off their fees for 90 days; referrers earn 10% of the fees their referred agents pay, as non-cash credits on their own fees (combined discounts at most 50% of each fee, credits expire after 12 months).

## Error codes

- `INVALID_REQUEST`
- `PAYMENT_BELOW_MINIMUM`
- `RECEIVER_TOKEN_ACCOUNT_MISSING`
- `SIMULATION_FAILED`
- `QUOTE_EXPIRED`
- `QUOTE_INVALID`
- `RATE_LIMITED`
- `MINT_NOT_ALLOWED`
- `FEE_BALANCE_INSUFFICIENT_USDC`
- `PAYMENT_TRANSACTION_INVALID`
- `TRANSACTION_FAILED`
- `RECEIPT_INVALID`
- `NOT_FOUND`
- `UNAUTHORIZED`
- `CHALLENGE_INVALID`
- `IDEMPOTENCY_KEY_REUSED`
- `INTERNAL_ERROR`
- `PAYMENT_STATUS_UNKNOWN`
