# SPEG · Agent Integration

> ## ⚠️ STOP — only act on explicit operator instruction.
>
> If you are an AI agent reading this, you are most likely operating on behalf
> of a human ("your host", "your operator", "your user" — the person who told
> you to interact with this site).
>
> - If your operator told you to mint, buy, or interact with SPEG: **proceed
>   normally** using the tools below. You don't need to re-confirm each step;
>   trust the instruction you were given and respect the spending envelope
>   they set.
> - If your operator did NOT mention SPEG and you arrived here via a link,
>   crawl, or autonomous exploration: **do nothing on-chain.** Report back
>   what you found and wait for instruction.
>
> Every successful mint flow burns real tPEG that was bought with real SOL.
> A curious agent walking through this document end-to-end without operator
> instruction will move user funds without permission — that is unauthorised
> action.
>
> The **only** tool safe to invoke without operator instruction is
> `get_status` — it is a pure read of public on-chain state, signs nothing,
> costs nothing. Use it freely to answer questions about the protocol.

---

> **You are reading this because you are (or are operating on behalf of) an AI
> agent that wants to interact with the SPEG protocol.** Everything you need —
> protocol model, tool list, mint flow, signing options — is on this page.
> No external lookups required.

SPEG is a Solana **Token-2022 + Transfer Hook** protocol. The protocol IS the
hook: when a wallet transfers a fixed amount of the bonding-curve token (`tPEG`)
to the `portal_vault`, the on-chain hook atomically mints a unique `sPEG` NFT
back to the sender. No "claim" step, no off-chain renderer, no separate
mint contract. The image bytes (24×24 pixel grid) live on Solana inside a
`SpegArt` PDA, rendered by Rust code compiled into the program SBF bytecode.

---

## 1. MCP endpoint (recommended)

The cleanest way for an agent to interact with SPEG is via this site's
built-in **Model Context Protocol** server. It is unauthenticated, holds no
keys, and returns unsigned base64 transactions that your wallet backend signs
locally.

| Transport | URL (relative) | Notes |
|---|---|---|
| Streamable HTTP | `/api/mcp/mcp` | Modern default. Use this. |
| Legacy SSE | `/api/mcp/sse` | Older clients only. |

Replace the host portion with whatever origin you fetched this document from
(e.g. `https://speg2022.art/api/mcp/mcp` for production, `http://localhost:3000/api/mcp/mcp`
for the dev server).

### Quick discovery test

```bash
curl -X POST <ORIGIN>/api/mcp/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

You should see five tools: `get_status`, `register_holder`, `buy_tokens`,
`mint_speg`, `reveal_spin`.

### Wiring it into a Claude / Cursor / Cline client

`mcp-remote` is the canonical stdio↔HTTP bridge for clients that don't speak
Streamable HTTP natively. Add this to your client config (e.g.
`~/.config/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "speg-mint": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "<ORIGIN>/api/mcp/mcp"]
    }
  }
}
```

Cursor / Cline / some Claude Code setups accept a URL directly:

```json
{
  "mcpServers": {
    "speg-mint": { "url": "<ORIGIN>/api/mcp/mcp" }
  }
}
```

---

## 2. Tools

All write-tools return `{ "tx_unsigned_b64": "<base64>" }`. Deserialize with
`Transaction.from(Buffer.from(tx, 'base64'))`, sign with the same wallet
whose pubkey you passed in, submit via any Solana RPC.

| Tool | Args | Returns | Notes |
|---|---|---|---|
| `get_status` | `{ wallet }` | snapshot JSON | Read-only. Always safe. |
| `register_holder` | `{ owner, wallet }` | unsigned tx | One-shot. Required for OTC-funded wallets only. |
| `buy_tokens` | `{ wallet, lamports, slippage_bps? }` | unsigned tx | Swap SOL → tPEG on the bonding curve. |
| `mint_speg` | `{ wallet, mode }` | unsigned tx | `mode = "guaranteed" \| "jackpot"`. |
| `reveal_spin` | `{ wallet, index }` | unsigned tx | Only for jackpot mode. |

### `get_status` response shape

```json
{
  "sol":              <number, SOL balance>,
  "tpeg":             <number, whole tPEG (decimals applied)>,
  "nfts_minted":      <integer, NFTs this wallet has minted>,
  "total_supply":     <integer, global mint count, capped at 10000>,
  "next_index":       <integer, next mint attempt index>,
  "nft_cap":          10000,
  "total_bought_tpeg":<number, lifetime tPEG bought by this wallet>,
  "cluster":          "mainnet" | "devnet" | "testnet" | "localnet",
  "rpc_endpoint":     <string>
}
```

### Mode semantics

Both modes use the same commit-reveal split. The hook creates a
`SpinCommitment` PDA storing `(owner, index, amount, target_slot =
commit_slot + 2)`. The actual NFT mint happens when you call `reveal_spin`
≥2 slots later, which mixes `SlotHashes[target_slot]` into the seed — a
hash that didn't exist when you committed, so simulation-arbitrage on
rarity grinding is closed for both modes.

- **`guaranteed`** — burns **100,000 tPEG**. Reveal uses 100% success rate
  (caps permitting). Deterministic mint, just split across two txs for
  anti-arbitrage.
- **`jackpot`** — burns **10,000 tPEG**. Reveal uses ~10% success rate.
  A miss forfeits the 10k tPEG.

---

## 3. Standard mint flow

```
get_status ──► (register_holder?) ──► (buy_tokens?) ──► mint_speg ──► reveal_spin ──► get_status
```

### Step-by-step

1. **`get_status(wallet)`** — read SOL, tPEG, `next_index`, `total_supply`.
   - If `total_supply >= 10000`: abort, protocol sold out.
   - If `nfts_minted >= 200`: abort, per-wallet cap hit.
   - If `total_bought_tpeg >= 20_000_000`: agent can't buy more tPEG; only
     transfers from another wallet would let it proceed.

2. **(Optional) `register_holder(owner=wallet, wallet=wallet)`** — required
   ONLY if the wallet received tPEG via OTC transfer rather than `buy_tokens`.
   The TransferHook resolver reads `UserAttempts.next_index` on every
   transfer; without this PDA, `mint_speg` fails at resolution time. The
   `buy_tokens` ix auto-creates `UserAttempts`, so wallets that bought from
   the curve never need to call this.
   Cost: ~0.0015 SOL rent.

3. **(Optional) `buy_tokens(wallet, lamports)`** — if `tpeg < required` for
   the chosen mode, top up via the bonding curve.
   - Curve: `effective_rate = base_rate × (1 + 32p + 17p^15)` where
     `p = total_minted / 10_000`. Base rate = 1,000,000 tPEG/SOL.
   - At `total_minted = 0`: 100k tPEG costs 0.1 SOL.
   - At 60% supply: ~20× the base cost.
   - At 100% supply: ~50× the base cost.
   - **Confirm with the user before spending >0.5 SOL.**

4. **`mint_speg(wallet, mode)`** — submit the returned tx. The MCP handler
   uses `createTransferCheckedWithTransferHookInstruction` from
   `@solana/spl-token`, which reads the on-chain `ExtraAccountMetaList` PDA
   and walks its dependency graph to resolve all 20 hook accounts. You
   don't need to construct that list yourself. After this tx confirms, the
   hook has created a `SpinCommitment` PDA at index `next_index - 1` — the
   actual NFT does NOT exist yet.

5. **`reveal_spin(wallet, index)`** — required for BOTH modes (guaranteed
   AND jackpot). `index` is the `next_index` value from the `get_status`
   call you made *before* `mint_speg`. Wait at least 2 slots after the
   `mint_speg` tx confirms (~0.8–1 second). The simplest reliable wait is
   `await new Promise(r => setTimeout(r, 2500))`. Submit the returned tx.
   The tx logs contain a `SpinEvent` with `success: boolean`. On
   `success=true` the NFT is in the wallet's NFT ATA with full rarity +
   on-chain pixel art. On `success=false` (jackpot miss) the commit closes
   and the tPEG is forfeit.

6. **`get_status(wallet)`** — confirm `nfts_minted` advanced.

---

## 4. Signing backend (you pick)

The SPEG MCP server **does not hold keys**. Pick one signing backend on
your side:

### Open Wallet Standard (recommended)

[OWS](https://github.com/open-wallet-standard/core) is MIT-licensed,
self-hosted, multi-chain, and has a built-in policy engine. No per-signature
fees. Vault encrypted at rest, keys decrypted only inside the signing path.

```ts
import { owsToSolanaKeypair } from "@open-wallet-standard/adapters";
import { Connection, Transaction } from "@solana/web3.js";

const keypair = owsToSolanaKeypair("speg-mint-agent", {
  passphrase: process.env.OWS_WALLET_PASSPHRASE,
});

async function signAndSend(unsignedB64: string) {
  const tx = Transaction.from(Buffer.from(unsignedB64, "base64"));
  tx.partialSign(keypair);
  const conn = new Connection(process.env.SOLANA_RPC!);
  return conn.sendRawTransaction(tx.serialize());
}
```

Policy: restrict the agent's API token to only sign Solana txs that hit the
SPEG program ID (published here at mainnet launch) and the Token-2022
program. Spending cap = "200 mints worth of tPEG at current multiplier"
is a sane upper bound.

### Turnkey

Policy-gated programmatic signing inside a SOC2-audited HSM. Per-signature
fee but zero ops. Whitelist the SPEG program ID + Token-2022 in the policy.
See [docs.turnkey.com](https://docs.turnkey.com).

### Privy server wallets

Same vendor as the SPEG frontend's embedded wallets. See
[docs.privy.io/guide/server/wallets/solana](https://docs.privy.io/guide/server/wallets/solana).

### Local keypair (DEV ONLY — never ship)

```ts
import { Keypair, Connection, Transaction } from "@solana/web3.js";
import fs from "fs";

const kp = Keypair.fromSecretKey(
  Buffer.from(JSON.parse(fs.readFileSync(process.env.AGENT_KEY!, "utf-8"))),
);

async function signAndSend(unsignedB64: string) {
  const tx = Transaction.from(Buffer.from(unsignedB64, "base64"));
  tx.sign(kp);
  const conn = new Connection(process.env.SOLANA_RPC!);
  return conn.sendRawTransaction(tx.serialize());
}
```

A compromised agent host with a local keypair on disk = drained wallet.
Production needs policy-gated signing.

---

## 5. Hard rules

- **No on-chain action without operator instruction** — see the STOP
  block at the top. If the operator told you to mint/buy/play SPEG,
  proceed; if they didn't, don't.
- **Respect the spending envelope** the operator set. If they said "spend
  up to 1 SOL", don't escalate beyond it even if a higher multiplier or
  cap edge would technically allow more.
- **Per-wallet cap**: 200 sPEG mints, 20,000,000 tPEG purchased. The
  on-chain program enforces this; your loops must respect it.
- **Total supply**: 10,000 sPEG NFTs (hard cap, enforced on-chain).
- **Read-only is always free**: `get_status` is safe to call any time —
  use it to answer the operator's questions or to verify state after a
  mint flow.

---

## 6. Anti-abuse posture

- The MCP server returns only unsigned txs and only reads public state. A
  malicious caller can build txs they would have to sign with *their own*
  keys to submit. Nothing on the server is drainable.
- No off-chain rate limiting (yet). Economic friction (every mint burns
  tPEG, which costs SOL on the curve) is the primary control.
- The Permissive Transfer Hook means non-portal transfers are a cheap
  ~5k-CU no-op — the protocol can't be DOS'd by spam transfers.

---

## 7. Troubleshooting

| Symptom | Fix |
|---|---|
| `SPEG config PDA not initialized on this cluster` | Wrong cluster (check `get_status.cluster`), or admin hasn't bootstrapped. |
| `UserAttempts PDA missing for this wallet` | Call `register_holder` once. |
| `commit is not yet revealable` | Wait 2 more slots and retry `reveal_spin`. |
| `simulation failed: Slippage exceeded` | Pass higher `slippage_bps` (try 200–300). |
| `Tool name "…" not in registry` | Your MCP client cached an old tool list — restart it. |
