# KaspaCom KCC20 Public Agent API

> Public testnet API for agents, wallets, and developers that trade KCC20 through KaspaCom.

KaspaCom plans unsigned KCC20 transactions. Agents read public market state, request unsigned PSKT build payloads, sign with their own wallet/provider, broadcast with their own wallet or wRPC stack, poll settlement status, then refresh balances and orderbook state.

Use the public Swagger and these Markdown guides as the public contract. Do not use website-authenticated, admin, rewards-management, FOMO, campaign, API-payment, or private user-state routes.

## Primary References

- [KCC20 Agent Trading Guide](/agent-guide.md): Full agent guide for discovery, quotes, consolidation, builders, signing, broadcast, and settlement.
- [KCC20 Public Testnet API](/public-testnet-api.md): Short API guide for the public testnet endpoints and request flow.
- [KCC20 Agent Integration Checklist](/agent-integration.md): Implementation checklist for third-party agents and wallet providers.
- [Public Swagger UI](/public-docs): Interactive public endpoint reference.
- [Public OpenAPI JSON](/public-docs-json): Machine-readable public OpenAPI document.
- [Health](/health): API health check.

## Notes For Agents

- No SDK package is required. Direct HTTP is supported.
- The optional SDK and MCP package is @kaspacom/kcc20-agent.
- Public user wording is Coupons. Machine fields stay feeTicketId and feeTicketOutpoint.
- Consolidation is only built when required=true and canConsolidate=true.
- Broadcast is done by the wallet or wRPC stack, not by a public backend broadcast endpoint.

## Full Documentation

# KCC20 Agent Trading Guide

Source: /agent-guide.md

# KaspaCom KCC20 Agent Trading Guide

This guide is for agents, wallets, and developers that want to trade KCC20 on
KaspaCom through public testnet endpoints.

You do not need an SDK package to integrate. The required contract is HTTP:
read public market state, ask the backend to build an unsigned transaction,
sign it with your wallet, broadcast it, then poll settlement status.

## Public Surfaces

| Surface             | URL                                                |
| ------------------- | -------------------------------------------------- |
| Base API            | `https://dev-api-kcc20.kaspa.com`                  |
| Public Swagger UI   | `https://dev-api-kcc20.kaspa.com/public-docs`      |
| Public OpenAPI JSON | `https://dev-api-kcc20.kaspa.com/public-docs-json` |
| LLM index           | `https://dev-api-kcc20.kaspa.com/llms.txt`         |
| Full LLM docs       | `https://dev-api-kcc20.kaspa.com/llms-full.txt`    |
| Markdown guide      | `https://dev-api-kcc20.kaspa.com/agent-guide.md`   |
| Health              | `https://dev-api-kcc20.kaspa.com/health`           |

Use only the public Swagger/OpenAPI surface for third-party agents. Full
backend Swagger exists for operators and the KaspaCom app, but it includes
website-authenticated, admin, rewards-management, FOMO, campaign, API-payment,
and private user-state routes.

## Integration Model

KaspaCom does transaction planning. Your agent or wallet does signing and
broadcast.

```text
discover token
  -> read wrappers/orderbook/quote
  -> optional consolidation preflight
  -> build unsigned PSKT payload
  -> inspect manifest
  -> sign with wallet/provider
  -> broadcast with wallet/wRPC stack
  -> poll settlement status
  -> refresh balances and orderbook
```

The backend response is a plan, not proof of execution. Chain and indexer reads
are the source of truth after broadcast.

## Public Names And Machine Fields

Public docs use role-based names:

| Public name           | Machine/artifact fields you may still see                      |
| --------------------- | -------------------------------------------------------------- |
| KCC20                 | `KCC20`                                                        |
| KCC20 Wrapper Reserve | `KCC20Wrapper`                                                 |
| KCC20 DEX Orderbook   | `KCC20Orderbook`                                               |
| Coupons               | `feeTicketId`, `feeTicketOutpoint`, fee discount ticket fields |

Treat these machine fields as stable API keys.

## Health Check

```bash
curl -fsS https://dev-api-kcc20.kaspa.com/health
```

Expected result:

- HTTP 200.
- `ok: true`.
- Mongo and Redis dependencies are ready.

## Discover A Tradable Token

Start from market discovery when the agent wants liquidity:

```bash
curl -fsS \
  'https://dev-api-kcc20.kaspa.com/trading/markets/discovery?limit=20' | jq
```

Keep these fields from the selected row:

| Field                               | Use                                                    |
| ----------------------------------- | ------------------------------------------------------ |
| `tokenIdHex`                        | Canonical token ID for trading reads and build routes. |
| `metadata.ticker` / `metadata.name` | Display only.                                          |
| `bestAskUnitPriceSompi`             | Current lowest ask.                                    |
| `bestBidUnitPriceSompi`             | Current highest bid.                                   |

Then load token page data and wrappers:

```bash
TOKEN_ID='<64-hex-token-id>'

curl -fsS \
  "https://dev-api-kcc20.kaspa.com/tokens/$TOKEN_ID/page-data" | jq

curl -fsS \
  "https://dev-api-kcc20.kaspa.com/tokens/$TOKEN_ID/wrappers" | jq
```

Keep the enabled wrapper covenant ID as `wrappedMarketId`. Trading builders
need both IDs:

- `covenantId`: the canonical KCC20 ID.
- `wrappedMarketId`: the KCC20 Wrapper Reserve V1 market ID.

## Read Orderbook And Quote

Read the orderbook:

```bash
curl -fsS \
  "https://dev-api-kcc20.kaspa.com/trading/tokens/$TOKEN_ID/orderbook" | jq
```

Ask for a quote before building a fill:

```bash
curl -fsS \
  "https://dev-api-kcc20.kaspa.com/trading/tokens/$TOKEN_ID/quote?side=buy&amount=100&mode=limit&limitUnitPriceSompi=90000000" | jq
```

Continue only when:

- `valid` is `true`.
- `errors` is empty.
- the selected fills match the user's intent.

Use a single-order fill when the user or strategy selected one exact order.
Use sweep only when the strategy intentionally wants to consume multiple
orders.

## Consolidation Preflight

KCC20 balances live in UTXOs. A wallet can have enough aggregate balance but no
single compatible UTXO large enough for the requested operation. Agents should
check this before transfer, wrap, unwrap, sell-order creation, and any flow
that spends token holder state.

Public route:

```text
GET /kcc20/build/tokens/:covenantId/consolidate/status
```

Native KCC20 check:

```bash
WALLET='kaspatest:...'

curl -fsS \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/consolidate/status?walletAddress=$WALLET&asset=native&tokenAmount=100" | jq
```

DEX Orderbook V1 wrapped-holder check:

```bash
WRAPPED_MARKET_ID='<64-hex-wrapper-id>'

curl -fsS \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/consolidate/status?walletAddress=$WALLET&asset=orderbook&wrappedMarketId=$WRAPPED_MARKET_ID&tokenAmount=100" | jq
```

Unwrap also checks wrapper reserve fragmentation:

```bash
curl -fsS \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/consolidate/status?walletAddress=$WALLET&asset=orderbook&operation=unwrap&wrappedMarketId=$WRAPPED_MARKET_ID&tokenAmount=100" | jq
```

If `required` and `canConsolidate` are both `true`, do not build the target
operation from stale state. Build and submit consolidation first, wait for
indexing, then refresh balances and build again.

If `required` is `true` and `canConsolidate` is `false`, do not call the
consolidation build endpoint. Handle `reason` instead:

- `action_chunking_required`: split the target operation into smaller actions,
  or follow the sequential plan returned for unwrap.
- `incompatible_sources`: refresh the wallet state and require a compatible
  holder source before continuing.
- `insufficient_balance` or `insufficient_reserve`: stop and surface the
  shortage to the wallet or strategy.

## Build Unsigned Transactions

Every public build body includes the signing wallet:

```json
{
  "walletAddress": "kaspatest:...",
  "ownerIdentifier": "optional 64 hex owner or kaspatest address"
}
```

`ownerIdentifier` is optional. If omitted, the backend derives the KCC20 owner
from `walletAddress`.

### Deploy Token

Use deploy when the agent or developer wants to create a new KCC20.

```bash
curl -fsS -X POST \
  'https://dev-api-kcc20.kaspa.com/kcc20/build/deploy' \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"ticker\": \"TEST\",
    \"tokenName\": \"Test Token\",
    \"maxSupply\": \"1000000\",
    \"premintSupply\": \"1000\",
    \"mintPolicy\": \"public\",
    \"mintPricePerTokenSompi\": \"0\"
  }" | jq
```

### Mint

Use mint when the token supports public mint or when the wallet has the
required mint authority.

```bash
curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/mint" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"tokenAmount\": \"100\"
  }" | jq
```

### Transfer

Use transfer for canonical KCC20 balance.

```bash
RECIPIENT='kaspatest:...'

curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/transfer" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"recipientOwner\": \"$RECIPIENT\",
    \"tokenAmount\": \"100\"
  }" | jq
```

### Consolidate

Use consolidation only when preflight returns `required=true` and
`canConsolidate=true`. This builds an unsigned consolidation transaction that
the wallet signs and broadcasts like any other build response.

```bash
curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/consolidate" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"asset\": \"native\"
  }" | jq
```

For DEX Orderbook V1 holder consolidation, include `wrappedMarketId` and set
`asset` to `orderbook`.

### Wrap

Use wrap when the agent has canonical KCC20 balance and wants tradable
DEX Orderbook V1 balance.

```bash
curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/wrap" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"wrappedMarketId\": \"$WRAPPED_MARKET_ID\",
    \"tokenAmount\": \"100\"
  }" | jq
```

### Unwrap

Use unwrap when the agent wants to move DEX Orderbook V1 holder balance back to
canonical KCC20 balance.

```bash
curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/unwrap" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"wrappedMarketId\": \"$WRAPPED_MARKET_ID\",
    \"tokenAmount\": \"100\"
  }" | jq
```

### Create Limit Order

`side=buy` creates a bid. `side=sell` creates an ask.

```bash
curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/orders" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"wrappedMarketId\": \"$WRAPPED_MARKET_ID\",
    \"side\": \"buy\",
    \"tokenAmount\": \"100\",
    \"unitPriceSompi\": \"90000000\"
  }" | jq
```

### Fill One Order

Use this when the agent targets one order ID from the orderbook.

```bash
ORDER_ID='<txid:vout>'

curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/orders/fill" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"wrappedMarketId\": \"$WRAPPED_MARKET_ID\",
    \"side\": \"buy\",
    \"targetOrderId\": \"$ORDER_ID\",
    \"tokenAmount\": \"100\",
    \"unitPriceSompi\": \"90000000\"
  }" | jq
```

### Sweep Multiple Orders

Use this only when the agent intentionally chooses a multi-order execution.
`expectedFills` should come from the quote/orderbook decision and must be
rebuilt if the market changes.

```bash
curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/orders/sweep" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"wrappedMarketId\": \"$WRAPPED_MARKET_ID\",
    \"side\": \"buy\",
    \"tokenAmount\": \"200\",
    \"mode\": \"limit\",
    \"limitUnitPriceSompi\": \"90000000\",
    \"expectedFills\": [
      {
        \"orderId\": \"<txid:vout>\",
        \"tokenAmount\": \"10000000000\",
        \"unitPriceSompi\": \"90000000\"
      }
    ],
    \"maxBuyerPaysSompi\": \"18000000000\"
  }" | jq
```

### Cancel Order

Use `side=ask` to cancel a sell order and `side=bid` to cancel a buy order.

```bash
curl -fsS -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/orders/cancel" \
  -H 'Content-Type: application/json' \
  --data "{
    \"walletAddress\": \"$WALLET\",
    \"wrappedMarketId\": \"$WRAPPED_MARKET_ID\",
    \"side\": \"bid\",
    \"targetOrderId\": \"$ORDER_ID\"
  }" | jq
```

## Sign And Broadcast

A successful build returns an unsigned wallet payload. The important part is
`payload.signing`:

| Field                        | Meaning                                                     |
| ---------------------------- | ----------------------------------------------------------- |
| `standard`                   | Expected signing standard, normally `pskt`.                 |
| `walletAction`               | Wallet action hint, normally `sign-pskt-transaction`.       |
| `status`                     | `ready-to-sign` means the unsigned PSKT is present.         |
| `psktTransactionJson`        | Unsigned PSKT transaction JSON to sign.                     |
| `signInputs`                 | Exact inputs the wallet should sign.                        |
| `submitTransactionSupported` | Whether wallet-side submission is expected by this payload. |

Minimal wallet-provider handoff:

```ts
type SignPsktRequest = {
  psktTransactionJson: string;
  signInputs: Array<{ index: number; sighashType?: number }>;
};

async function signAndBroadcast(
  wallet: {
    signPsktTransaction(input: SignPsktRequest): Promise<{
      signedTransactionJson: string;
    }>;
    broadcastTransaction(input: { signedTransactionJson: string }): Promise<{
      txid: string;
    }>;
  },
  build: any,
) {
  const signing = build.payload?.signing;
  if (signing?.status !== 'ready-to-sign' || !signing.psktTransactionJson) {
    throw new Error('build is not ready for wallet signing');
  }

  const signed = await wallet.signPsktTransaction({
    psktTransactionJson: signing.psktTransactionJson,
    signInputs: signing.signInputs ?? [],
  });

  return wallet.broadcastTransaction(signed);
}
```

Do not sign a payload if:

- `status` is not `ready-to-sign`;
- `builderError` is present;
- the operation, token ID, order ID, amount, price, or wallet owner is not what
  the user or strategy requested;
- quote/orderbook data is stale.

## Track Settlement

After broadcast, poll settlement status:

```bash
TXID='<broadcast-txid>'

curl -fsS \
  "https://dev-api-kcc20.kaspa.com/trading/tx/$TXID/settlement-status" | jq
```

The operation is complete only when the response shows matched covenant actions
for the expected token/order flow. Then refresh:

- token page data;
- wallet-provider balances;
- orderbook;
- owner orders;
- owner history;
- settlement status.

## Scheduler Rule

Serialize operations per `(owner, tokenId)` or `(owner, wrappedMarketId)`.

Do not build the next same-token transaction until the previous transaction is
broadcast, indexed, and visible through fresh reads. Parallel actions on
unrelated tokens are fine only when they do not spend the same source UTXOs.

This avoids common failures where a second request is built against holder,
order, or wrapper UTXOs that were already consumed by the first transaction.

## Coupons

KaspaCom UI may call these Coupons. API and contract fields keep their machine
names:

- `feeTicketId`
- `feeTicketOutpoint`
- fee discount ticket fields in quote/build responses

For the first public agent flow, Coupons are optional discount inputs. Agents
should not create, burn, transfer, or list wallet Coupon inventory through the
public trading API. `GET /rewards/fee-ticket-root` is the supported public
config read for fee-ticket-aware builders.

## Error Handling

| Status/symptom                       | Agent behavior                                                                        |
| ------------------------------------ | ------------------------------------------------------------------------------------- |
| `400`                                | Fix malformed ID, amount, side, missing wrapper, stale order, or bad body.            |
| `429`                                | Back off and avoid concurrent builders for the same wallet/token lane.                |
| `5xx`                                | Treat as backend/indexer/builder dependency failure; retry later after reads recover. |
| `signing.status=missing-funded-pskt` | The build could not produce a signable PSKT. Refresh state and rebuild.               |
| settlement has no matched actions    | Keep polling with backoff, then refresh affected reads.                               |

Retry means: refresh reads, re-quote if trading, and build a new payload. Do
not replay an old unsigned payload after orderbook or wallet state changes.

## Public Launch Checklist

Before announcing a new agent workflow:

- public Swagger lists only supported public routes;
- examples use `https://dev-api-kcc20.kaspa.com`;
- every build route is tested with a real wallet address;
- signing handoff checks `ready-to-sign`, `psktTransactionJson`, and
  `signInputs`;
- broadcast path is clear: external wallet/wRPC stack unless a public
  broadcast endpoint is explicitly added;
- settlement polling is documented;
- one-operation-per-token scheduling is enforced by the agent.

# KCC20 Public Testnet API

Source: /public-testnet-api.md

# KCC20 Public Testnet API

This guide is the external integration contract for third-party testnet
developers and agents.

Base URL:

```text
https://dev-api-kcc20.kaspa.com
```

Public Swagger:

- UI: `GET /public-docs`
- OpenAPI JSON: `GET /public-docs-json`
- LLM index: `GET /llms.txt`
- Full LLM docs: `GET /llms-full.txt`
- Markdown guide: `GET /agent-guide.md`

Full agent trading guide:

- [AGENT_TRADING_GUIDE.md](./AGENT_TRADING_GUIDE.md)

Full backend Swagger remains available at `GET /docs` and `GET /docs-json`,
but it includes admin, website-authenticated, FOMO, campaign, API-payment, and
other out-of-scope routes. Do not use it as the third-party agent contract.

## Scope

Use this public API for KCC20 token discovery, trading data, wallet-provider
reads, fee-ticket root config, and unsigned transaction builders.

Out of scope for this guide:

- FOMO routes.
- API payment routes.
- Campaign routes.
- Admin routes.
- Fee discount ticket create, burn, transfer, and wallet history routes under
  `/rewards/fee-tickets/*` and `/rewards/me/*`.
- Referral/profile/notification/user-state edits.
- Website wallet-cookie routes under `/users`, `/wallet-operations`,
  `/metadata/:covenantId/permissions`, `/deploy/token/build`, and
  authenticated `/tokens/:covenantId/actions/*`.

Some out-of-scope routes are still present in full Swagger because Swagger
documents the whole backend. Third-party agents should treat the route groups
below as the supported public surface.

## Data Model

- Network is testnet-10.
- KAS prices are in sompi unless a field explicitly says display amount.
- This guide assumes the public testnet routes are free. If a documented route
  returns HTTP 402, paid API enforcement has changed and that path is outside
  this guide until API payments are in scope.
- User-entered KCC20 token amount fields use display strings, for example
  `"10.5"`.
- Sweep `expectedFills[*].tokenAmount` uses the base-unit string copied from a
  quote or orderbook decision.
- Token, covenant, txid, and owner hex identifiers are lowercase 64-char hex.
- Order IDs are `txid:vout`, for example
  `09fa307cdf375615feb88c489414b2085af3e09c50fb95e0d09a9e85bd947d71:0`.
- Kaspa P2PK addresses are accepted where a route says owner can be an address.
- Chain/indexer data is the source of truth for orders, balances, fills,
  volume, candles, and settlement status.

## Health

Use this before running longer workflows.

```bash
curl -s https://dev-api-kcc20.kaspa.com/health
```

Completion check: the response is HTTP 200 and the service reports ready
dependencies.

## Discover Tokens

Use token routes to list public KCC20 tokens, mintable tokens, token page data,
holders, wrappers, and supply/trading summaries.

Common routes:

- `GET /tokens`
- `GET /tokens/mints`
- `GET /tokens/mints/v2`
- `GET /tokens/:covenantId`
- `GET /tokens/:covenantId/page-data`
- `GET /tokens/:covenantId/identity`
- `GET /tokens/:covenantId/trading-summary`
- `GET /tokens/:covenantId/page-actions`
- `GET /tokens/:covenantId/supply-summary`
- `GET /tokens/:covenantId/holders-summary`
- `GET /tokens/:covenantId/holders`
- `GET /tokens/:covenantId/wrappers`
- `GET /tokens/owners/:ownerIdentifier/deployed`

Useful query fields:

- `limit`, `offset`
- `sortBy`, `sortDirection`
- `refresh=true`
- `search`
- `tokenId`
- `policy`, `mintStatus`, `wrapperStatus`
- `from`, `to` on `/tokens/mints` and `/tokens/mints/v2` filter token
  creation/deploy time as inclusive ISO-8601 bounds.

Example:

```bash
curl -s \
  "https://dev-api-kcc20.kaspa.com/tokens/mints/v2?limit=20&sortBy=lastTradeTimeMs&sortDirection=desc"
```

Newest mintable tokens:

```bash
curl -s \
  "https://dev-api-kcc20.kaspa.com/tokens/mints/v2?limit=20&sortBy=createdAtMs&sortDirection=desc"
```

Paused public mints remain discoverable with `mintStatus=paused`. Read
`mintPolicy.publicMintControlSupported` before showing owner controls and
`mintPolicy.publicMintActive` before calling a public mint builder. Public mint
builds must stop while that field is `false`.

The availability control is a website-authenticated owner action, not a public
agent route:

```text
POST /tokens/:covenantId/actions/mint-availability/build
body: { "active": true | false }
```

The backend checks that the authenticated wallet owns the active minter UTXO.
Setting `active=false` blocks public mint builds, but the owner may still mint
through the authority path. Third-party agents cannot change this setting.

Completion check: select a `covenantIdHex` or `tokenIdHex` from the response
before calling trading or build routes.

## Read Markets

Use trading routes for orderbooks, quotes, candles, activity, owner history,
and transaction settlement status.

Market routes:

- `GET /trading/markets`
- `GET /trading/markets/discovery`
- `GET /trading/freshness`
- `GET /trading/tokens/:tokenId/orderbook`
- `GET /trading/tokens/:tokenId/stats`
- `GET /trading/tokens/:tokenId/candles`
- `GET /trading/tokens/:tokenId/activity`
- `GET /trading/tokens/:tokenId/quote`
- `GET /trading/tokens/:tokenId/orders/:orderId`
- `GET /trading/tx/:txid/settlement-status`

Owner routes:

- `GET /trading/owners/:owner/orders`
- `GET /trading/owners/:owner/trades`
- `GET /trading/owners/:owner/history`
- `GET /trading/owners/:owner/balances`
- `GET /trading/owners/:owner/positions`
- `GET /trading/addresses/:owner/orders`
- `GET /trading/addresses/:owner/trades`
- `GET /trading/addresses/:owner/history`
- `GET /trading/addresses/:owner/balances`
- `GET /trading/addresses/:owner/positions`

Use `/addresses/:owner` when the caller may pass a Kaspa P2PK address. Use
`/owners/:owner` when the caller already has the normalized 64-hex owner.

Example quote:

```bash
TOKEN_ID=5c00fca7025f29c05f8deaf8600baa6d1a9e3a741dc8383b59f7610047bf85f0

curl -s \
  "https://dev-api-kcc20.kaspa.com/trading/tokens/$TOKEN_ID/quote?side=buy&amount=100&mode=limit&limitUnitPriceSompi=9000000"
```

Quote completion check:

- `valid` is `true`.
- `errors` is empty.
- For a single-order fill, use the selected order's `orderId`.
- For a multi-order sweep, preserve the quote's selected fills and caps in the
  build request.
- If the quote includes fee discount ticket fields, treat them as optional
  discount inputs for trading. Creating, burning, transferring, or listing a
  wallet's tickets is not part of this public API guide.

## Wallet Provider Reads

Use wallet-provider routes when integrating wallet views or agent portfolio
state.

Routes:

- `GET /wallet-provider/tokens`
- `GET /wallet-provider/owners/:owner/balances`
- `GET /wallet-provider/owners/:owner/tokens/:covenantId/activity`

Example:

```bash
OWNER=kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx

curl -s \
  "https://dev-api-kcc20.kaspa.com/wallet-provider/owners/$OWNER/balances?limit=50&tradeable=true"
```

Completion check: use the returned canonical token rows and capability flags to
decide whether transfer, wrap, unwrap, or trading builders are available.

## Build Preflight And Unsigned Transactions

Public builders do not use the website wallet-cookie session. They require the
caller to send the wallet identity in the request body:

- `walletAddress`: Kaspa address that will sign and fund the PSKT.
- `ownerIdentifier`: optional 64-hex owner or Kaspa P2PK address. Defaults to
  the owner derived from `walletAddress`.

Public consolidation status uses the same identity fields as query parameters.
The builder returns an unsigned wallet payload. The integrator must pass that
payload to a wallet/signer, then broadcast or submit through its own wallet
flow.

Public build and preflight routes:

- `POST /kcc20/build/deploy`
- `POST /kcc20/build/tokens/:covenantId/mint`
- `POST /kcc20/build/tokens/:covenantId/transfer`
- `POST /kcc20/build/tokens/:covenantId/wrap`
- `POST /kcc20/build/tokens/:covenantId/unwrap`
- `GET /kcc20/build/tokens/:covenantId/consolidate/status`
- `POST /kcc20/build/tokens/:covenantId/consolidate`
- `POST /kcc20/build/tokens/:covenantId/orders`
- `POST /kcc20/build/tokens/:covenantId/orders/fill`
- `POST /kcc20/build/tokens/:covenantId/orders/sweep`
- `POST /kcc20/build/tokens/:covenantId/orders/cancel`
- `GET /rewards/fee-ticket-root`

Do not use authenticated website build routes under
`/deploy/token/build`, `/tokens/:covenantId/actions/*`, or
`/rewards/fee-tickets/*` for public integrations.

Before calling the public mint route, read the token and require
`mintPolicy.publicMintActive=true`. A paused response is owner policy, not a
retryable builder error.

Use `wrappedMarketId` from `GET /tokens/:covenantId/wrappers` or
`GET /tokens/:covenantId/page-data`. The examples below use placeholder IDs.

### Consolidation Status

Agents need this preflight when the wallet may have enough aggregate token
balance but not one compatible UTXO large enough for the requested action.

Native KCC20:

```bash
curl -s \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/consolidate/status?walletAddress=kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx&asset=native&tokenAmount=10"
```

DEX Orderbook V1 wrapped holder:

```bash
curl -s \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/consolidate/status?walletAddress=kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx&asset=orderbook&wrappedMarketId=1111111111111111111111111111111111111111111111111111111111111111&tokenAmount=10"
```

If `required` and `canConsolidate` are both `true`, run consolidation through a
wallet/app flow first, wait for indexing, refresh balances, then build the
target operation from fresh state.

If `required` is `true` and `canConsolidate` is `false`, do not call the
consolidation build endpoint. Use `reason` to decide whether to split the action
into smaller chunks, refresh incompatible sources, or stop for insufficient
balance or reserve.

Build consolidation:

```bash
curl -s -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/consolidate" \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "asset": "native"
  }'
```

For `asset=orderbook`, include `wrappedMarketId`. To force a specific
mass-bounded merge batch, include two to eight `sourceOutpoints` returned by
the status response.

### Deploy

```bash
curl -s -X POST https://dev-api-kcc20.kaspa.com/kcc20/build/deploy \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "ticker": "TEST",
    "tokenName": "Test Token",
    "maxSupply": "1000000",
    "premintSupply": "1000",
    "mintPolicy": "public",
    "mintPricePerTokenSompi": "0"
  }'
```

### Mint

```bash
curl -s -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/mint" \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "tokenAmount": "10"
  }'
```

### Transfer

```bash
curl -s -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/transfer" \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "tokenAmount": "10",
    "recipientOwner": "kaspatest:qz5fy6efxrxrkrrv3g8v7p2qgx57fxcq3e2trwqqzaj9kjy5j6ycaaf44vqk9"
  }'
```

### Wrap Or Unwrap

```bash
curl -s -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/wrap" \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "wrappedMarketId": "1111111111111111111111111111111111111111111111111111111111111111",
    "tokenAmount": "10"
  }'
```

Use `/unwrap` with the same body shape for DEX Orderbook V1 back to KCC20
Token V1.

### Create Order

```bash
curl -s -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/orders" \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "wrappedMarketId": "1111111111111111111111111111111111111111111111111111111111111111",
    "side": "buy",
    "tokenAmount": "100",
    "unitPriceSompi": "9000000"
  }'
```

### Fill One Order

Use this route when the UI or agent picked one specific order.

```bash
curl -s -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/orders/fill" \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "wrappedMarketId": "1111111111111111111111111111111111111111111111111111111111111111",
    "side": "buy",
    "targetOrderId": "09fa307cdf375615feb88c489414b2085af3e09c50fb95e0d09a9e85bd947d71:0",
    "tokenAmount": "100",
    "unitPriceSompi": "9000000"
  }'
```

Completion check: the response is ready for wallet signing and targets only the
order supplied by `targetOrderId`.

### Sweep Multiple Orders

Use this route only when the caller intentionally wants a multi-order sweep.
The caller must provide the expected fills from a quote/orderbook decision.

```bash
curl -s -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/orders/sweep" \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "wrappedMarketId": "1111111111111111111111111111111111111111111111111111111111111111",
    "side": "buy",
    "tokenAmount": "200",
    "mode": "limit",
    "limitUnitPriceSompi": "9000000",
    "expectedFills": [
      {
        "orderId": "09fa307cdf375615feb88c489414b2085af3e09c50fb95e0d09a9e85bd947d71:0",
        "tokenAmount": "100000000",
        "unitPriceSompi": "9000000"
      },
      {
        "orderId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:0",
        "tokenAmount": "100000000",
        "unitPriceSompi": "9000000"
      }
    ],
    "maxBuyerPaysSompi": "2000000000"
  }'
```

Completion check: use this only when the user selected a sweep. A direct click
on one order should use `/orders/fill`.

### Cancel Order

```bash
curl -s -X POST \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/orders/cancel" \
  -H 'Content-Type: application/json' \
  --data '{
    "walletAddress": "kaspatest:qr8zzq8xsxtu3e54h4up6plzh4w70mth7k5hv6yggxg9xx7uvvkqzt3rrs0zx",
    "wrappedMarketId": "1111111111111111111111111111111111111111111111111111111111111111",
    "side": "ask",
    "targetOrderId": "09fa307cdf375615feb88c489414b2085af3e09c50fb95e0d09a9e85bd947d71:0"
  }'
```

## After Signing

The public builders prepare unsigned payloads. They do not prove broadcast or
indexing by themselves.

After the wallet signs and broadcasts a transaction, poll:

```bash
curl -s \
  "https://dev-api-kcc20.kaspa.com/trading/tx/$TXID/settlement-status"
```

Completion check: the response contains matched covenant actions for the
expected token/order operation.

Use the same scheduler rule as the KaspaCom app: keep one active operation per
`(owner, tokenId)`.

For repeated operations on the same token and owner:

1. Build and sign the transaction.
2. Broadcast it.
3. Poll `GET /trading/tx/:txid/settlement-status`.
4. Wait until the expected covenant action is indexed.
5. Refresh token page data, wallet-provider balances, orderbook, owner orders,
   and owner history.
6. Build the next same-token request from the refreshed state.

Done when the previous transaction is indexed and fresh reads show the updated
balance/order state. Operations for other tokens can run independently.

## Coupons

KaspaCom UI may call these Coupons. API and contract fields keep their machine
names:

- `feeTicketId`
- `feeTicketOutpoint`
- fee discount ticket fields in quote/build responses

Coupons are optional discount inputs for public order fill and sweep builders.
Agents can read `GET /rewards/fee-ticket-root` for the current root config.
They should not create, burn, transfer, or list wallet Coupon inventory through
the public API.

## Error Handling

- `400`: malformed identifier, unknown field, invalid amount, invalid order ID,
  or failed preflight.
- `429`: public build rate limit.
- `5xx`: backend, indexer, or builder dependency issue.

Agents should refresh token/orderbook data and rebuild after preflight or stale
source errors. They should not retry the exact same stale build body when the
orderbook or owner balance changed.

If the previous same-token operation is still syncing, the next build can fail
before wallet signing with
`signing.status=missing-funded-pskt`, `failureCode=BUILDER_FAILED`, and a
technical builder message such as an active token/order UTXO not being found.
Show this to users as wallet data still syncing, wait a few seconds, refresh the
reads above, then build a new unsigned payload.

# KCC20 Agent Integration Checklist

Source: /agent-integration.md

# KCC20 Agent Integration

Use this runbook when an agent needs to inspect KCC20 markets or prepare
testnet trading transactions.

Base URL: `https://dev-api-kcc20.kaspa.com`

Primary references:

- Public API guide: [PUBLIC_TESTNET_API.md](./PUBLIC_TESTNET_API.md)
- Full agent trading guide: [AGENT_TRADING_GUIDE.md](./AGENT_TRADING_GUIDE.md)
- Public Swagger UI: `GET /public-docs`
- Public Swagger JSON: `GET /public-docs-json`
- LLM index: `GET /llms.txt`
- Full LLM docs: `GET /llms-full.txt`
- Public Markdown guide: `GET /agent-guide.md`

## Rules

- Use only public routes from the public API guide.
- Do not use FOMO, API payment, campaign, admin, referral edit, profile,
  notification, user-state, fee discount ticket management, or website
  wallet-cookie routes.
- Use `/kcc20/build/*` for public unsigned transaction builders.
- Use `GET /rewards/fee-ticket-root` only as public fee-ticket config; do not
  use wallet inventory or fee-ticket management routes.
- Require `mintPolicy.publicMintActive=true` before building a public mint.
  Treat `false` as an owner pause and wait for a fresh token read.
- Use `/deploy/token/build`, `/tokens/:covenantId/actions/*`, and
  `/rewards/fee-tickets/*` only for website-authenticated app flows, not
  third-party agents.
- Do not call the authenticated `mint-availability/build` action from an agent
  integration. Only the active minter owner may change public availability.
- Get `wrappedMarketId` from token page data or wrapper discovery before wrap,
  unwrap, order, fill, sweep, or cancel builds.
- Check `GET /kcc20/build/tokens/:covenantId/consolidate/status` before token
  spends when wallet state may be fragmented across multiple UTXOs.
- Re-read orderbook or quote data before building if the previous read is more
  than a few seconds old.
- For one clicked order, build `/orders/fill`.
- For intentional multi-order execution, build `/orders/sweep`.
- For repeated operations on the same token and owner, wait for the prior tx to
  index before building the next same-token action.

## Discover A Tradable Token

1. Call `GET /tokens/mints/v2` or `GET /trading/markets/discovery`.
2. Select `tokenIdHex` or `covenantIdHex`.
3. Call `GET /tokens/:covenantId/page-data`.

Done when the selected token has enough metadata and action availability for
the intended workflow.

## Quote A Fill

1. Call `GET /trading/tokens/:tokenId/orderbook`.
2. Call `GET /trading/tokens/:tokenId/quote` with:
   - `side=buy` or `side=sell`
   - `amount`
   - `mode=market` or `mode=limit`
   - `limitUnitPriceSompi` for limit mode
   - `orderId` when targeting one known order
3. Continue only if `valid` is `true` and `errors` is empty.

Done when the quote matches the user intent:

- One selected order: keep the selected `orderId`.
- Sweep: keep all intended fills and caps.
- fee discount ticket fields may appear in quote/build contracts, but ticket
  creation, burn, transfer, and wallet history are outside this public runbook.

## Build A Public Transaction

Every public build body includes:

```json
{
  "walletAddress": "kaspatest:...",
  "ownerIdentifier": "optional 64 hex owner or kaspatest address"
}
```

Use these routes:

- Deploy: `POST /kcc20/build/deploy`
- Mint: `POST /kcc20/build/tokens/:covenantId/mint`
- Transfer: `POST /kcc20/build/tokens/:covenantId/transfer`
- Consolidate: `POST /kcc20/build/tokens/:covenantId/consolidate`
- Wrap: `POST /kcc20/build/tokens/:covenantId/wrap`
- Unwrap: `POST /kcc20/build/tokens/:covenantId/unwrap`
- Consolidation status:
  `GET /kcc20/build/tokens/:covenantId/consolidate/status`
- Create order: `POST /kcc20/build/tokens/:covenantId/orders`
- Fill one order: `POST /kcc20/build/tokens/:covenantId/orders/fill`
- Sweep multiple orders: `POST /kcc20/build/tokens/:covenantId/orders/sweep`
- Cancel order: `POST /kcc20/build/tokens/:covenantId/orders/cancel`

Done when the response contains an unsigned wallet payload ready for wallet
signing.

Consolidation status is a read preflight. It uses the same `walletAddress` and
optional `ownerIdentifier` fields as query parameters and tells the agent
whether wallet UTXOs block the target action. Only build `/consolidate` when
`required=true` and `canConsolidate=true`, then sign and broadcast it, wait for
indexing, and rebuild the original transfer, wrap, unwrap, sell, or fill request
from fresh state. When `required=true` and `canConsolidate=false`, handle
`reason` instead of building consolidation.

## Sign, Track, Then Continue

1. Hand the unsigned payload to the wallet or signer.
2. Broadcast through the wallet integration.
3. Poll `GET /trading/tx/:txid/settlement-status`.
4. Wait until the expected covenant action is indexed.
5. Refresh affected token, orderbook, owner balance, and activity reads.
6. Start the next same-token operation only after the refresh is complete.

Done when settlement status shows the expected matched covenant actions and the
orderbook/balance projection reflects the transaction.

Agents need the same scheduler rule the KaspaCom app uses: keep one active
operation per `(owner, tokenId)`. For that lane, do not build the next mint,
transfer, wrap, unwrap, create-order, fill, sweep, or cancel until the previous
transaction is indexed and fresh reads show the updated state. Operations for
other tokens can run independently.

## Recovery

- If quote is invalid, show the returned `errors` and stop.
- If a build fails because the previous same-token operation is still syncing,
  wait, refresh token/orderbook/wallet-provider reads, then build a new request.
- Retryable stale-source build failures usually appear before signing with
  `signing.status=missing-funded-pskt`, `failureCode=BUILDER_FAILED`, and a
  technical message such as an active token/order UTXO not being found.
- If a user clicked one order but the prepared request includes multiple fills,
  switch from `/orders/sweep` to `/orders/fill`.
- If the API returns `429`, wait before retrying and avoid concurrent builders
  for the same wallet/order.
- If settlement status has no matched actions yet, keep polling with backoff.

## Out Of Scope

Do not integrate these in the first public testnet agent flow:

- FOMO
- API payments
- Campaigns
- Fee discount ticket management
- Admin tools
- Private website state
- Referral/profile/notification edits
- Custom generated wrappers beyond the supported `@kaspacom/kcc20-agent`
  SDK/MCP/CLI package
