> ## Documentation Index
> Fetch the complete documentation index at: https://www.kitemc.asia/llms.txt
> Use this file to discover all available pages before exploring further.

# KiteMarket 市场 API 入门

> Java 11、MIT 的市场 SDK：不可变查询、成交通知与 v1.1 受限扩展。

`KiteMarket-API` 是独立的 Java 11／MIT SDK，不依赖闭源 `market-core`。原 `KiteMarketApi` 提供只读查询和成交后通知；v1.1 通过独立服务增加扩展登记和必须由玩家确认的受限请求。运行包、公开接口与示例的获取方式见[下载页](https://www.kitemc.asia/kitemarket/download.md)。

以下查询签名继续适用于 v1.0.0。标注 v1.1 的 DTO 与服务需匹配主插件和 SDK；下载时请始终选择与主插件相同版本的公开 SDK。

## 引用 SDK

项目示例使用 GitHub Packages 的 Maven 坐标引用 SDK。GitHub Releases 的直接文件下载作为备用途径；两者提供同一套公开接口，不包含闭源交易核心。

### GitHub Packages

两个 SDK 以 `compileOnly`／`provided` 引用；公开 Packages 下载需要 `read:packages` classic PAT。引用前先确认所需版本已上传。

> **[Packages 引用配置](https://www.kitemc.asia/kitemarket/api/dependencies.md#github-packages)**
>
> SDK 坐标、Gradle、Maven、用户级凭据与认证检查。

### Release 下载备用

Release 文件下载不需要 Packages Token，可作为离线开发的备用编译依赖。

> **[Release 下载与运行时约束](https://www.kitemc.asia/kitemarket/api/dependencies.md#release-下载备用)**
>
> SDK 文件获取、插件依赖与禁止打包 SDK 的约定。

### 运行时约束

`plugin.yml` 添加 `depend: [KiteMarket]`。若你的插件没有市场也能运行，可使用 `softdepend`，但仅在确认主插件存在后加载引用 API 的适配类，避免缺失类错误。

**不要打包、shade 或重定位 SDK**。运行时由 KiteMarket 提供唯一接口类，重复副本可能导致服务无法识别。SDK JAR 不是服务器插件。

## 等待服务，再异步查询

```java theme={null}
import com.kitemc.market.api.KiteMarketApi;

KiteMarketApi api = getServer().getServicesManager().load(KiteMarketApi.class);
if (api == null) return; // 数据库尚未就绪，等待 ServiceRegisterEvent。

api.orders(null, null, "", 0, 36).whenComplete((orders, failure) -> {
    if (!isEnabled()
        || getServer().getServicesManager().load(KiteMarketApi.class) != api) return;
    if (failure != null) {
        getLogger().warning("Market query unavailable");
        return;
    }
    orders.forEach(order -> getLogger().info(
        order.getId() + " " +
        order.getCurrency().display(order.getUnitPrice()).toPlainString()));
});
```

`depend` 只保证加载顺序，不保证数据库已连接。监听 `ServiceRegisterEvent` 后重新获取服务，并在卸载或替换时丢弃旧结果。可运行查询示例位于公开仓库 `examples/api-java`，包含延迟注册、失败处理和有容量限制的事件去重，不新增玩家命令。

查询返回 `CompletableFuture`。不能在主线程、Folia 区域线程或玩家线程 `get()`／`join()` 等待。回调不保证玩家调度上下文；更新 GUI、发送玩家信息或读取背包需另行正确调度。失败应显示暂不可用，不能伪装成零余额或空市场。

## 查询与结果

| 方法 | 返回内容 |
| - | - |
| `networkId()` | 市场网络的持久化 UUID |
| `currencies()` | 币种 ID 与固定精度 |
| `orders(type, owner, search, offset, limit)` | 订单页；类型可空，owner 为空仅查开放订单，指定 owner 包含其终态订单 |
| `order(id)` | 指定订单；不存在时异常完成 |
| `wallets(player)` | 指定玩家的可用余额和冻结余额 |
| `assets(player)` | 可领取条目 ID、数量与物品摘要 |
| `history(player, offset, limit)` | 指定玩家的审计白名单摘要 |

分页要求 `offset >= 0`、`1 <= limit <= 100`，搜索最多256字符。历史按原审计行分页，未知内部类型投影为 `OTHER`，不泄露原始记录。

### 金额与价格

所有金额使用 `long` 最小货币单位。`CurrencyView.getPrecision() == 2` 时，`128` 表示 `1.28`；用 `currency.display(amount).toPlainString()` 格式化。时间为 Unix 毫秒，税率使用基点。

单品一口价 `SELL` 和收购 `BUY` 的 `getUnitPrice()` 是每件单价；拍卖 `AUCTION` 为整标起价。v1.1 中还要检查 `getLotMode()`：`BUNDLE` 为整包总价，数量固定1且不可拆分。单品一口价允许部分购买，金额为单价×本次购买数量，计算时检查整数溢出，例如使用 `Math.multiplyExact`。

### 最低购买量

`OrderView.getMinimumPurchaseQuantity()` 为发布时固定的最低购买量，默认1，SELL可设为1至发布数量，其他类型为1。

购买量须大于零、不超过剩余量，且至少为 `min(getMinimumPurchaseQuantity(), getRemaining())`；尾单不足最低量时必须一次买走全部剩余。

原构造器保持可用并默认1，新重载在末尾增加 `long minimumPurchaseQuantity`。快照不替代最终校验，核心提交仍检查数量、资金和订单版本；历史与成交通知记录本次实际金额。

### 不可变 DTO

独立 DTO 在 `com.kitemc.market.api.model` 下：`CurrencyView`、`OrderView`、`WalletView`、`ClaimAssetView`、`HistoryEntry`、`TradeSummary`、`ItemSummary` 及枚举。字段和嵌套列表、集合、映射不可变。可选样品是显示摘要，不能据此生成或领取资产。

接口不返回原始审计 JSON、序列化物品字节、精确样品指纹、许可证凭据、执行令牌或恢复证据。

缺失历史金额保持 `null`，不猜成零；`RECORDED` 不表示外部副作用已经成功，`PENDING_REVIEW` 表示需要核对。物品匹配和交易提交由主插件处理，不属于公开查询接口。

### v1.1 整包、费用与业务条件摘要

| 数据 | 读取方式 |
| - | - |
| 标的模式与内容 | `getLotMode()` 区分 `SINGLE`／`BUNDLE`；`getLotItems()` 返回原始标的的 `LotItemSummary` 与实际数量，不是剩余库存 |
| 发布时费用 | `getFees()` 返回不可变 `FeeSummary`，旧单可为 `null`，此时保留 `getTaxBasisPoints()`；已付上架费与累计成交／买卖方费用分别读取 |
| 物品身份 | `ItemSummary` 可含真实 `source`／`businessId`、模型值、标签名及服主明确公开的业务字段 |
| 收购条件 | `RuleSummary` 增加对应业务条件和有界 `anyOf`；不包含原始 NBT／PDC、组件、字节或精确指纹 |

两套旧 `OrderView` 构造器仍可用，默认 `SINGLE`。不要使用当前配置重算已发布订单费用，也不要把原始整包条目数量当作可购买余量。

## 成交后通知

```java theme={null}
import com.kitemc.market.api.MarketCommittedEvent;
import com.kitemc.market.api.model.TradeSummary;
import org.bukkit.event.EventHandler;

@EventHandler
public void onTrade(MarketCommittedEvent event) {
    String deduplicationKey = event.getNetworkId() + ":" + event.getEventId();
    TradeSummary trade = event.getTrade();
    // 先按 deduplicationKey 去重；完整可运行示例提供有限近期缓存。
    getLogger().info(deduplicationKey + " " + event.getTopic()
        + " net=" + trade.getCurrency().display(trade.getNetIncome()).toPlainString());
}
```

`MarketCommittedEvent` 异步、不可取消，只报告已提交的 `BUY`、`SUPPLY`、`AUCTION_WON`。`getTrade()` 返回订单与操作 ID、类型、物品收取人、收益收取人、数量、币种、总额、税额和净收入；没有原始 `getPayload()`。

### 去重与重新查询

通知通过节点增量轮询触发，可能延迟或遗漏，启动前历史不自动补发，多个节点可能看到同一事件。

按**网络 UUID＋事件 ID**去重；事件 ID 不保证连续。需要长期去重时自行持久化，不能把通知用作补发资金或物品的依据。重启后重新查询权威状态。

## v1.1 扩展与玩家确认请求

两个独立服务通过 `ServicesManager` 获取，原 `KiteMarketApi` 保持只读：

| 服务 | 用途与边界 |
| - | - |
| `KiteMarketExtensions` | 由启用中的所属插件登记经济、真实物品身份、辅助容器预览、只可拒绝的规则／复核及成交通知提供者；插件停用自动注销 |
| `KiteMarketRequests` | `quote(...)` 获得服务端报价，`openConfirmation(...)` 打开主插件玩家确认；`discard(...)` 只丢弃未执行报价 |

请求限于 `CREATE`／`BUY`／`SUPPLY`／`BID`／`CANCEL`，绑定玩家会话、插件拥有者、订单与策略版本及有效期。没有公开 `confirm()`／`commit()`，不能发币、任意扣物、管理修复或远程无人交易。整包使用真实背包槽和数量，确认时仍由核心重新采集和校验。

### 扩展提供者与所属插件

扩展通过实际启用的 `Plugin owner` 登记。拥有者停用后自动注销，关闭旧登记句柄不会误删同 ID 的新注册；规则热更新后调用 `changed(owner)`，使基于旧修订的报价失效。

| SPI | 可扩展内容 |
| - | - |
| `EconomyProvider` | 已配置币种网关的只读外部余额，以及受核心执行许可约束的充提 |
| `ItemIdentityProvider` | 从克隆的真实物品读取公开业务 ID，返回与注册扩展 ID 一致的 `source` |
| `ContainerPreviewProvider` | 辅助只读容器摘要；原生实际内容清单优先 |
| `TradeRuleProvider` | 在核心约束之外增加拒绝规则 |
| `TradeReviewProvider` | 在锁外检查不可变、脱敏的报价 |
| `MarketNotificationListener` | 接收已提交摘要，按网络与事件 ID 去重 |

规则与复核在数据库／市场锁外执行，接收 `QUOTE` 或 `PLAYER_CONFIRM` 阶段。只能允许继续或拒绝，不能重写价格、费用、收款人或扣物选择，也不能放宽核心规则；异常会拒绝当前交易并禁用故障提供者。

身份回调异步读取克隆后的原生物品，每批最多识别128个不同属性指纹，含嵌套内容。超过范围须减少选择，不能从名称或 Lore 猜测厂商 ID。容器扩展结果标注为辅助摘要，不改变真实托管、准入或领取内容。

### 从玩家操作打开核心确认

```java theme={null}
MarketRequest intent = MarketRequest.buy(
    order.getId(), order.getRevision(), 16);
requests.quote(this, player, intent)
    .thenCompose(quote ->
        requests.openConfirmation(this, player, quote.getId()))
    .thenAccept(result ->
        getLogger().info(result.getStatus() + " " + result.getCode()));
```

从玩家明确操作发起请求；主插件在正确玩家调度上下文打开确认页。报价绑定拥有者、玩家会话、订单／策略修订和短有效期。离线、换服、拥有者停用、过期、规则变化或重复使用都要求重新报价，不能修改旧报价继续提交。

出售、竞拍与供货只引用真实背包格 `0..35` 及数量，不接受序列化物品、伪造资产 ID 或扣物回调。整包用 `MarketRequest.createBundle(...)` 和 `RequestLot` 描述各部分；跨部分不能重复槽位，每部分同属性，部分之间可以混合，标的数量固定1。批量上架逐单获得保护确认，逐项处理结果。

### 费用与完成状态

`RequestQuote.getListingFee()`、`getBuyerFee()` 与 `getChargedAmount()` 使用宿主的实际费用快照和最小货币单位。gross 为成交总额，tax 为卖方总费，net 为卖方净收入；买方预算包含买方费用。竞拍 charged 可能仅为新增冻结差额，不能根据 gross 再扣一次款。

只有 `WriteResult.Status.SUCCEEDED` 表示核心成功。`REJECTED`、`CANCELLED`、`EXPIRED` 和 `PENDING_REVIEW` 分别表示拒绝、取消、过期与待核对；报价生成或 future 完成不等于玩家已确认。提交开始后 `discard(...)` 不能撤销事务，未知结果不自动重放。

经济扩展已经进入外部调用时，慢响应不会被取消或当作失败丢弃；宿主保留 `UNKNOWN` 和迟到证据。提供者按 `operationId` 幂等，登记扩展不赋予任意发币能力。

> **[完整 v1.1 SDK 与可运行示例](https://github.com/KiteMC/KiteMarket/blob/main/docs/API.md)**
>
> 查看扩展 SPI、受限请求、费用报价、生命周期与真实示例源码。

## 需要扩展玩家界面？

使用独立的 [UI SDK](https://www.kitemc.asia/kitemarket/ui-development.md)，接收当前页面快照，并回传服务端登记的动作。最终权限、报价、背包重查和确认继续由 KiteMarket 完成。两个 SDK 均不能任意发币、扣物、绕过确认或执行远程交易写入。

自有配置主题和 Java IA 呈现器无需购买官方 DLC。第三方可自用、免费分发或独立销售；真实 IA 示例适配代码使用 Java 21，和两个 Java 11 SDK 的字节码边界分别看待。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.