# 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 V1 launch names:

| Public name              | Machine/artifact fields you may still see                      |
| ------------------------ | -------------------------------------------------------------- |
| KCC20 Token V1           | `KCC20V2`                                                      |
| KCC20 Wrapper Reserve V1 | `KCC20V2V3Wrapper`                                             |
| KCC20 DEX Orderbook V1   | `KCC20V3Wrapped`                                               |
| Coupons                  | `feeTicketId`, `feeTicketOutpoint`, fee discount ticket fields |

Do not rename machine fields. Agents should treat them as compatibility 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 Token V1 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 Token V1 check:

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

curl -fsS \
  "https://dev-api-kcc20.kaspa.com/kcc20/build/tokens/$TOKEN_ID/consolidate/status?walletAddress=$WALLET&asset=v2&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=v3-wrapped&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=v3-wrapped&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 Token V1.

```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\",
    \"mintMode\": \"publicMint\",
    \"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 Token V1 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\": \"v2\"
  }" | jq
```

For DEX Orderbook V1 holder consolidation, include `wrappedMarketId` and set
`asset` to `v3-wrapped`.

### Wrap

Use wrap when the agent has canonical KCC20 Token V1 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 Token V1 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.

## 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.
